summaryrefslogtreecommitdiffstats
path: root/dom/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/base
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/base/AbstractRange.cpp397
-rw-r--r--dom/base/AbstractRange.h208
-rw-r--r--dom/base/AncestorIterator.h107
-rw-r--r--dom/base/AnchorAreaFormRelValues.cpp16
-rw-r--r--dom/base/AnchorAreaFormRelValues.h21
-rw-r--r--dom/base/AnimationFrameProvider.cpp53
-rw-r--r--dom/base/AnimationFrameProvider.h78
-rw-r--r--dom/base/AnonymousContent.cpp220
-rw-r--r--dom/base/AnonymousContent.h93
-rw-r--r--dom/base/Attr.cpp221
-rw-r--r--dom/base/Attr.h110
-rw-r--r--dom/base/AttrArray.cpp547
-rw-r--r--dom/base/AttrArray.h261
-rw-r--r--dom/base/AutoPrintEventDispatcher.h62
-rw-r--r--dom/base/AutoSuppressEventHandlingAndSuspend.h66
-rw-r--r--dom/base/AutocompleteFieldList.h225
-rw-r--r--dom/base/BarProps.cpp242
-rw-r--r--dom/base/BarProps.h133
-rw-r--r--dom/base/BindContext.cpp51
-rw-r--r--dom/base/BindContext.h101
-rw-r--r--dom/base/BodyConsumer.cpp835
-rw-r--r--dom/base/BodyConsumer.h142
-rw-r--r--dom/base/BodyUtil.cpp474
-rw-r--r--dom/base/BodyUtil.h72
-rw-r--r--dom/base/BorrowedAttrInfo.cpp22
-rw-r--r--dom/base/BorrowedAttrInfo.h39
-rw-r--r--dom/base/CCGCScheduler.cpp1076
-rw-r--r--dom/base/CCGCScheduler.h541
-rw-r--r--dom/base/CORSMode.h38
-rw-r--r--dom/base/CallState.h23
-rw-r--r--dom/base/CharacterData.cpp591
-rw-r--r--dom/base/CharacterData.h226
-rw-r--r--dom/base/ChildIterator.cpp305
-rw-r--r--dom/base/ChildIterator.h202
-rw-r--r--dom/base/ChildProcessMessageManager.h35
-rw-r--r--dom/base/ChromeMessageBroadcaster.cpp21
-rw-r--r--dom/base/ChromeMessageBroadcaster.h55
-rw-r--r--dom/base/ChromeMessageSender.cpp19
-rw-r--r--dom/base/ChromeMessageSender.h44
-rw-r--r--dom/base/ChromeNodeList.cpp57
-rw-r--r--dom/base/ChromeNodeList.h36
-rw-r--r--dom/base/ChromeUtils.cpp1895
-rw-r--r--dom/base/ChromeUtils.h314
-rw-r--r--dom/base/Comment.cpp71
-rw-r--r--dom/base/Comment.h62
-rw-r--r--dom/base/CompressionStream.cpp252
-rw-r--r--dom/base/CompressionStream.h59
-rw-r--r--dom/base/ContentAreaDropListener.sys.mjs329
-rw-r--r--dom/base/ContentFrameMessageManager.cpp28
-rw-r--r--dom/base/ContentFrameMessageManager.h62
-rw-r--r--dom/base/ContentIterator.cpp1057
-rw-r--r--dom/base/ContentIterator.h257
-rw-r--r--dom/base/ContentProcessMessageManager.cpp125
-rw-r--r--dom/base/ContentProcessMessageManager.h104
-rw-r--r--dom/base/Crypto.cpp114
-rw-r--r--dom/base/Crypto.h52
-rw-r--r--dom/base/CustomElementRegistry.cpp1587
-rw-r--r--dom/base/CustomElementRegistry.h592
-rw-r--r--dom/base/DOMArena.h58
-rw-r--r--dom/base/DOMException.cpp448
-rw-r--r--dom/base/DOMException.h197
-rw-r--r--dom/base/DOMImplementation.cpp213
-rw-r--r--dom/base/DOMImplementation.h73
-rw-r--r--dom/base/DOMIntersectionObserver.cpp839
-rw-r--r--dom/base/DOMIntersectionObserver.h206
-rw-r--r--dom/base/DOMMatrix.cpp1027
-rw-r--r--dom/base/DOMMatrix.h343
-rw-r--r--dom/base/DOMMozPromiseRequestHolder.h112
-rw-r--r--dom/base/DOMParser.cpp323
-rw-r--r--dom/base/DOMParser.h97
-rw-r--r--dom/base/DOMPoint.cpp133
-rw-r--r--dom/base/DOMPoint.h107
-rw-r--r--dom/base/DOMQuad.cpp151
-rw-r--r--dom/base/DOMQuad.h89
-rw-r--r--dom/base/DOMRect.cpp160
-rw-r--r--dom/base/DOMRect.h179
-rw-r--r--dom/base/DOMRequest.cpp256
-rw-r--r--dom/base/DOMRequest.h103
-rw-r--r--dom/base/DOMRequestHelper.sys.mjs335
-rw-r--r--dom/base/DOMStringList.cpp29
-rw-r--r--dom/base/DOMStringList.h84
-rw-r--r--dom/base/DOMTokenListSupportedTokens.h29
-rw-r--r--dom/base/DecompressionStream.cpp295
-rw-r--r--dom/base/DecompressionStream.h59
-rw-r--r--dom/base/DirectionalityUtils.cpp1271
-rw-r--r--dom/base/DirectionalityUtils.h166
-rw-r--r--dom/base/DispatcherTrait.cpp28
-rw-r--r--dom/base/DispatcherTrait.h44
-rw-r--r--dom/base/DocGroup.cpp441
-rw-r--r--dom/base/DocGroup.h161
-rw-r--r--dom/base/Document.cpp18628
-rw-r--r--dom/base/Document.h5628
-rw-r--r--dom/base/DocumentFragment.cpp122
-rw-r--r--dom/base/DocumentFragment.h113
-rw-r--r--dom/base/DocumentInlines.h57
-rw-r--r--dom/base/DocumentOrShadowRoot.cpp696
-rw-r--r--dom/base/DocumentOrShadowRoot.h301
-rw-r--r--dom/base/DocumentType.cpp80
-rw-r--r--dom/base/DocumentType.h73
-rw-r--r--dom/base/Element.cpp4949
-rw-r--r--dom/base/Element.h2288
-rw-r--r--dom/base/ElementInlines.h45
-rw-r--r--dom/base/EventSource.cpp2144
-rw-r--r--dom/base/EventSource.h104
-rw-r--r--dom/base/EventSourceEventService.cpp312
-rw-r--r--dom/base/EventSourceEventService.h76
-rw-r--r--dom/base/External.cpp17
-rw-r--r--dom/base/External.h39
-rw-r--r--dom/base/FilteredNodeIterator.h57
-rw-r--r--dom/base/FlushType.h70
-rw-r--r--dom/base/FormData.cpp390
-rw-r--r--dom/base/FormData.h169
-rw-r--r--dom/base/FragmentOrElement.cpp2054
-rw-r--r--dom/base/FragmentOrElement.h350
-rw-r--r--dom/base/FromParser.h25
-rw-r--r--dom/base/FullscreenChange.h164
-rw-r--r--dom/base/FuzzingFunctions.cpp390
-rw-r--r--dom/base/FuzzingFunctions.h80
-rw-r--r--dom/base/GeneratedImageContent.cpp43
-rw-r--r--dom/base/GeneratedImageContent.h74
-rw-r--r--dom/base/GlobalTeardownObserver.cpp73
-rw-r--r--dom/base/GlobalTeardownObserver.h87
-rw-r--r--dom/base/HTMLSplitOnSpacesTokenizer.h16
-rw-r--r--dom/base/Highlight.cpp170
-rw-r--r--dom/base/Highlight.h197
-rw-r--r--dom/base/HighlightRegistry.cpp216
-rw-r--r--dom/base/HighlightRegistry.h157
-rw-r--r--dom/base/IDTracker.cpp263
-rw-r--r--dom/base/IDTracker.h194
-rw-r--r--dom/base/IdentifierMapEntry.h230
-rw-r--r--dom/base/IdleDeadline.cpp76
-rw-r--r--dom/base/IdleDeadline.h54
-rw-r--r--dom/base/IdleRequest.cpp61
-rw-r--r--dom/base/IdleRequest.h51
-rw-r--r--dom/base/IframeSandboxKeywordList.h35
-rw-r--r--dom/base/ImageEncoder.cpp454
-rw-r--r--dom/base/ImageEncoder.h126
-rw-r--r--dom/base/ImageTracker.cpp161
-rw-r--r--dom/base/ImageTracker.h71
-rw-r--r--dom/base/InProcessBrowserChildMessageManager.cpp294
-rw-r--r--dom/base/InProcessBrowserChildMessageManager.h129
-rw-r--r--dom/base/IndexedDBHelper.sys.mjs253
-rw-r--r--dom/base/IntlUtils.cpp95
-rw-r--r--dom/base/IntlUtils.h51
-rw-r--r--dom/base/JSExecutionContext.cpp304
-rw-r--r--dom/base/JSExecutionContext.h171
-rw-r--r--dom/base/Link.cpp542
-rw-r--r--dom/base/Link.h154
-rw-r--r--dom/base/LinkStyle.cpp325
-rw-r--r--dom/base/LinkStyle.h287
-rw-r--r--dom/base/Location.cpp653
-rw-r--r--dom/base/Location.h135
-rw-r--r--dom/base/LocationBase.cpp274
-rw-r--r--dom/base/LocationBase.h65
-rw-r--r--dom/base/LocationHelper.sys.mjs51
-rw-r--r--dom/base/MaybeCrossOriginObject.cpp481
-rw-r--r--dom/base/MaybeCrossOriginObject.h354
-rw-r--r--dom/base/MessageBroadcaster.cpp38
-rw-r--r--dom/base/MessageBroadcaster.h50
-rw-r--r--dom/base/MessageListenerManager.cpp48
-rw-r--r--dom/base/MessageListenerManager.h52
-rw-r--r--dom/base/MessageManagerCallback.h69
-rw-r--r--dom/base/MessageManagerGlobal.cpp52
-rw-r--r--dom/base/MessageManagerGlobal.h118
-rw-r--r--dom/base/MessageSender.cpp31
-rw-r--r--dom/base/MessageSender.h32
-rw-r--r--dom/base/MimeType.cpp347
-rw-r--r--dom/base/MimeType.h77
-rw-r--r--dom/base/MozQueryInterface.cpp92
-rw-r--r--dom/base/MozQueryInterface.h54
-rw-r--r--dom/base/MutationObservers.cpp250
-rw-r--r--dom/base/MutationObservers.h150
-rw-r--r--dom/base/NameSpaceConstants.h32
-rw-r--r--dom/base/Navigator.cpp2316
-rw-r--r--dom/base/Navigator.h306
-rw-r--r--dom/base/NodeInfo.cpp183
-rw-r--r--dom/base/NodeInfo.h308
-rw-r--r--dom/base/NodeInfoInlines.h102
-rw-r--r--dom/base/NodeIterator.cpp212
-rw-r--r--dom/base/NodeIterator.h83
-rw-r--r--dom/base/NodeUbiReporting.cpp77
-rw-r--r--dom/base/NodeUbiReporting.h89
-rw-r--r--dom/base/ParentProcessMessageManager.cpp31
-rw-r--r--dom/base/ParentProcessMessageManager.h49
-rw-r--r--dom/base/PlacesBookmark.h52
-rw-r--r--dom/base/PlacesBookmarkAddition.h62
-rw-r--r--dom/base/PlacesBookmarkChanged.h29
-rw-r--r--dom/base/PlacesBookmarkGuid.h49
-rw-r--r--dom/base/PlacesBookmarkKeyword.h53
-rw-r--r--dom/base/PlacesBookmarkMoved.h61
-rw-r--r--dom/base/PlacesBookmarkRemoved.h60
-rw-r--r--dom/base/PlacesBookmarkTags.h54
-rw-r--r--dom/base/PlacesBookmarkTime.h53
-rw-r--r--dom/base/PlacesBookmarkTitle.h54
-rw-r--r--dom/base/PlacesBookmarkUrl.h47
-rw-r--r--dom/base/PlacesEvent.cpp37
-rw-r--r--dom/base/PlacesEvent.h86
-rw-r--r--dom/base/PlacesFavicon.h50
-rw-r--r--dom/base/PlacesHistoryCleared.h42
-rw-r--r--dom/base/PlacesObservers.cpp392
-rw-r--r--dom/base/PlacesObservers.h65
-rw-r--r--dom/base/PlacesPurgeCaches.h39
-rw-r--r--dom/base/PlacesRanking.h39
-rw-r--r--dom/base/PlacesVisit.h66
-rw-r--r--dom/base/PlacesVisitRemoved.h69
-rw-r--r--dom/base/PlacesVisitTitle.h51
-rw-r--r--dom/base/PlacesWeakCallbackWrapper.cpp41
-rw-r--r--dom/base/PlacesWeakCallbackWrapper.h43
-rw-r--r--dom/base/PointerLockManager.cpp442
-rw-r--r--dom/base/PointerLockManager.h82
-rw-r--r--dom/base/PopoverData.cpp27
-rw-r--r--dom/base/PopoverData.h105
-rw-r--r--dom/base/PopupBlocker.cpp438
-rw-r--r--dom/base/PopupBlocker.h117
-rw-r--r--dom/base/Pose.cpp63
-rw-r--r--dom/base/Pose.h67
-rw-r--r--dom/base/PostMessageEvent.cpp306
-rw-r--r--dom/base/PostMessageEvent.h126
-rw-r--r--dom/base/ProcessMessageManager.cpp42
-rw-r--r--dom/base/ProcessMessageManager.h56
-rw-r--r--dom/base/ProcessSelector.sys.mjs58
-rw-r--r--dom/base/RadioGroupManager.cpp181
-rw-r--r--dom/base/RadioGroupManager.h58
-rw-r--r--dom/base/RangeBoundary.h467
-rw-r--r--dom/base/RangeUtils.cpp233
-rw-r--r--dom/base/RangeUtils.h127
-rw-r--r--dom/base/RemoteOuterWindowProxy.cpp172
-rw-r--r--dom/base/ResizeObserver.cpp604
-rw-r--r--dom/base/ResizeObserver.h321
-rw-r--r--dom/base/ResizeObserverController.cpp238
-rw-r--r--dom/base/ResizeObserverController.h144
-rw-r--r--dom/base/ResponsiveImageSelector.cpp730
-rw-r--r--dom/base/ResponsiveImageSelector.h204
-rw-r--r--dom/base/RustTypes.h40
-rw-r--r--dom/base/SameProcessMessageQueue.cpp60
-rw-r--r--dom/base/SameProcessMessageQueue.h49
-rw-r--r--dom/base/ScreenLuminance.cpp19
-rw-r--r--dom/base/ScreenLuminance.h49
-rw-r--r--dom/base/ScreenOrientation.cpp894
-rw-r--r--dom/base/ScreenOrientation.h113
-rw-r--r--dom/base/ScriptableContentIterator.cpp189
-rw-r--r--dom/base/ScriptableContentIterator.h35
-rw-r--r--dom/base/ScrollingMetrics.cpp124
-rw-r--r--dom/base/ScrollingMetrics.h51
-rw-r--r--dom/base/Selection.cpp4118
-rw-r--r--dom/base/Selection.h1103
-rw-r--r--dom/base/SelectionChangeEventDispatcher.cpp175
-rw-r--r--dom/base/SelectionChangeEventDispatcher.h69
-rw-r--r--dom/base/SerializedStackHolder.cpp154
-rw-r--r--dom/base/SerializedStackHolder.h81
-rw-r--r--dom/base/ShadowIncludingTreeIterator.h118
-rw-r--r--dom/base/ShadowRoot.cpp877
-rw-r--r--dom/base/ShadowRoot.h316
-rw-r--r--dom/base/SlowScriptDebug.sys.mjs24
-rw-r--r--dom/base/StaticRange.cpp134
-rw-r--r--dom/base/StaticRange.h140
-rw-r--r--dom/base/StorageAccessPermissionRequest.cpp161
-rw-r--r--dom/base/StorageAccessPermissionRequest.h79
-rw-r--r--dom/base/StructuredCloneBlob.cpp255
-rw-r--r--dom/base/StructuredCloneBlob.h89
-rw-r--r--dom/base/StructuredCloneHolder.cpp1682
-rw-r--r--dom/base/StructuredCloneHolder.h394
-rw-r--r--dom/base/StructuredCloneTags.h164
-rw-r--r--dom/base/StructuredCloneTester.cpp92
-rw-r--r--dom/base/StructuredCloneTester.h66
-rw-r--r--dom/base/StyleSheetList.cpp46
-rw-r--r--dom/base/StyleSheetList.h68
-rw-r--r--dom/base/StyledRange.cpp11
-rw-r--r--dom/base/StyledRange.h24
-rw-r--r--dom/base/SubtleCrypto.cpp125
-rw-r--r--dom/base/SubtleCrypto.h111
-rw-r--r--dom/base/SyncMessageSender.h23
-rw-r--r--dom/base/TestUtils.cpp50
-rw-r--r--dom/base/TestUtils.h30
-rw-r--r--dom/base/Text.cpp157
-rw-r--r--dom/base/Text.h61
-rw-r--r--dom/base/TextInputProcessor.cpp1857
-rw-r--r--dom/base/TextInputProcessor.h251
-rw-r--r--dom/base/ThirdPartyUtil.cpp529
-rw-r--r--dom/base/ThirdPartyUtil.h57
-rw-r--r--dom/base/Timeout.cpp87
-rw-r--r--dom/base/Timeout.h198
-rw-r--r--dom/base/TimeoutBudgetManager.cpp35
-rw-r--r--dom/base/TimeoutBudgetManager.h31
-rw-r--r--dom/base/TimeoutExecutor.cpp257
-rw-r--r--dom/base/TimeoutExecutor.h89
-rw-r--r--dom/base/TimeoutHandler.cpp178
-rw-r--r--dom/base/TimeoutHandler.h104
-rw-r--r--dom/base/TimeoutManager.cpp1338
-rw-r--r--dom/base/TimeoutManager.h254
-rw-r--r--dom/base/TreeIterator.h147
-rw-r--r--dom/base/TreeOrderedArray.h41
-rw-r--r--dom/base/TreeOrderedArrayInlines.h55
-rw-r--r--dom/base/TreeWalker.cpp329
-rw-r--r--dom/base/TreeWalker.h79
-rw-r--r--dom/base/UIDirectionManager.cpp92
-rw-r--r--dom/base/UIDirectionManager.h30
-rw-r--r--dom/base/UseCounter.h80
-rw-r--r--dom/base/UseCounters.conf409
-rw-r--r--dom/base/UseCountersWorker.conf77
-rw-r--r--dom/base/UserActivation.cpp127
-rw-r--r--dom/base/UserActivation.h86
-rw-r--r--dom/base/ViewportMetaData.cpp115
-rw-r--r--dom/base/ViewportMetaData.h48
-rw-r--r--dom/base/VisualViewport.cpp331
-rw-r--r--dom/base/VisualViewport.h110
-rw-r--r--dom/base/WindowDestroyedEvent.cpp154
-rw-r--r--dom/base/WindowDestroyedEvent.h38
-rw-r--r--dom/base/WindowFeatures.cpp241
-rw-r--r--dom/base/WindowFeatures.h130
-rw-r--r--dom/base/WindowNamedPropertiesHandler.cpp278
-rw-r--r--dom/base/WindowNamedPropertiesHandler.h63
-rw-r--r--dom/base/WindowProxyHolder.h77
-rw-r--r--dom/base/XPathGenerator.cpp192
-rw-r--r--dom/base/XPathGenerator.h30
-rw-r--r--dom/base/ZLibHelper.h46
-rw-r--r--dom/base/components.conf33
-rw-r--r--dom/base/crashtests/1024428-1.html11
-rw-r--r--dom/base/crashtests/1027461-1.html9
-rw-r--r--dom/base/crashtests/1027461-inner.xhtml2
-rw-r--r--dom/base/crashtests/1029710.html11
-rw-r--r--dom/base/crashtests/1154598.xhtml9
-rw-r--r--dom/base/crashtests/1157995.html9
-rw-r--r--dom/base/crashtests/1158412.html22
-rw-r--r--dom/base/crashtests/116848-1.html30
-rw-r--r--dom/base/crashtests/1181619.html14
-rw-r--r--dom/base/crashtests/1228882.html6
-rw-r--r--dom/base/crashtests/1230422.html28
-rw-r--r--dom/base/crashtests/1251361.html33
-rw-r--r--dom/base/crashtests/1281715.html24
-rw-r--r--dom/base/crashtests/1291535-iframe.html4
-rw-r--r--dom/base/crashtests/1291535.html1
-rw-r--r--dom/base/crashtests/1304437.html13
-rw-r--r--dom/base/crashtests/1324209.html27
-rw-r--r--dom/base/crashtests/1324500.html4
-rw-r--r--dom/base/crashtests/1326194-1.html20
-rw-r--r--dom/base/crashtests/1326194-2.html20
-rw-r--r--dom/base/crashtests/1332939.html16
-rw-r--r--dom/base/crashtests/1341693.html13
-rw-r--r--dom/base/crashtests/1352453.html22
-rw-r--r--dom/base/crashtests/1353529-inner.html12
-rw-r--r--dom/base/crashtests/1353529.xhtml8
-rw-r--r--dom/base/crashtests/1368327-iframe.html10
-rw-r--r--dom/base/crashtests/1368327.html24
-rw-r--r--dom/base/crashtests/1369363.xhtml16
-rw-r--r--dom/base/crashtests/1370072.html18
-rw-r--r--dom/base/crashtests/1370737.html41
-rw-r--r--dom/base/crashtests/1370968-inner.xhtml6
-rw-r--r--dom/base/crashtests/1370968.html14
-rw-r--r--dom/base/crashtests/1373750.html14
-rw-r--r--dom/base/crashtests/1377826.html5
-rw-r--r--dom/base/crashtests/1383478.html49
-rw-r--r--dom/base/crashtests/1383780.html21
-rw-r--r--dom/base/crashtests/1385272-1.html29
-rw-r--r--dom/base/crashtests/1393806.html17
-rw-r--r--dom/base/crashtests/1396466.html20
-rw-r--r--dom/base/crashtests/1397795.html23
-rw-r--r--dom/base/crashtests/1400701.html15
-rw-r--r--dom/base/crashtests/1403377.html18
-rw-r--r--dom/base/crashtests/1405771.html20
-rw-r--r--dom/base/crashtests/1406109-1.html10
-rw-r--r--dom/base/crashtests/1411473.html12
-rw-r--r--dom/base/crashtests/1413815.html20
-rw-r--r--dom/base/crashtests/1419799.html17
-rw-r--r--dom/base/crashtests/1419902.html23
-rw-r--r--dom/base/crashtests/1422883.html10
-rw-r--r--dom/base/crashtests/1428053.html23
-rw-r--r--dom/base/crashtests/1441029.html1
-rw-r--r--dom/base/crashtests/1445670.html20
-rw-r--r--dom/base/crashtests/1449601.html16
-rw-r--r--dom/base/crashtests/1458016.html12
-rw-r--r--dom/base/crashtests/1459688.html6
-rw-r--r--dom/base/crashtests/1460794.html19
-rw-r--r--dom/base/crashtests/1462548.html13
-rw-r--r--dom/base/crashtests/149320-1.html16
-rw-r--r--dom/base/crashtests/1505811.html24
-rw-r--r--dom/base/crashtests/1505875.html10
-rw-r--r--dom/base/crashtests/1508845.html17
-rw-r--r--dom/base/crashtests/1516289.html14
-rw-r--r--dom/base/crashtests/1516560.html3
-rw-r--r--dom/base/crashtests/1517025.html5
-rw-r--r--dom/base/crashtests/1528675.html28
-rw-r--r--dom/base/crashtests/1529203-1.html17
-rw-r--r--dom/base/crashtests/1529203-2.html21
-rw-r--r--dom/base/crashtests/1529203-3.html21
-rw-r--r--dom/base/crashtests/1555786.html23
-rw-r--r--dom/base/crashtests/1566310.html24
-rw-r--r--dom/base/crashtests/1577191.html15
-rw-r--r--dom/base/crashtests/1588259.html21
-rw-r--r--dom/base/crashtests/1611853.html19
-rw-r--r--dom/base/crashtests/1619322.html17
-rw-r--r--dom/base/crashtests/1623918.html16
-rw-r--r--dom/base/crashtests/1656925.html18
-rw-r--r--dom/base/crashtests/1665792.html13
-rw-r--r--dom/base/crashtests/1681729-inner1.html1
-rw-r--r--dom/base/crashtests/1681729-inner2.html1
-rw-r--r--dom/base/crashtests/1681729.html30
-rw-r--r--dom/base/crashtests/1693049.html30
-rw-r--r--dom/base/crashtests/1697256.html14
-rw-r--r--dom/base/crashtests/1697525.html6
-rw-r--r--dom/base/crashtests/1700237.html31
-rw-r--r--dom/base/crashtests/1712198.html9
-rw-r--r--dom/base/crashtests/1728670-1-child.html12
-rw-r--r--dom/base/crashtests/1728670-1.html3
-rw-r--r--dom/base/crashtests/1757923.html22
-rw-r--r--dom/base/crashtests/1766472.html20
-rw-r--r--dom/base/crashtests/1780790.html15
-rw-r--r--dom/base/crashtests/1811939.html7
-rw-r--r--dom/base/crashtests/1822717-module.js56
-rw-r--r--dom/base/crashtests/1822717.html1
-rw-r--r--dom/base/crashtests/1838484.html9
-rw-r--r--dom/base/crashtests/205225-1.html9
-rw-r--r--dom/base/crashtests/231475-1.html12
-rw-r--r--dom/base/crashtests/244933-1.html13
-rw-r--r--dom/base/crashtests/275912-1.html2
-rw-r--r--dom/base/crashtests/293388-1.html26
-rw-r--r--dom/base/crashtests/325730-1.html27
-rw-r--r--dom/base/crashtests/326618-1.html14
-rw-r--r--dom/base/crashtests/326646-1.html22
-rw-r--r--dom/base/crashtests/326865-1.html12
-rw-r--r--dom/base/crashtests/327571-1.html22
-rw-r--r--dom/base/crashtests/327694.html17
-rw-r--r--dom/base/crashtests/327695-1.html10
-rw-r--r--dom/base/crashtests/329481-1.xhtml12
-rw-r--r--dom/base/crashtests/336381-1.xhtml29
-rw-r--r--dom/base/crashtests/336715-1.xhtml40
-rw-r--r--dom/base/crashtests/338391-1.xhtml33
-rw-r--r--dom/base/crashtests/338674-1.xhtml36
-rw-r--r--dom/base/crashtests/340733-1.html28
-rw-r--r--dom/base/crashtests/343730-1.xhtml35
-rw-r--r--dom/base/crashtests/343850-1.xhtml28
-rw-r--r--dom/base/crashtests/343889-1.html18
-rw-r--r--dom/base/crashtests/344434-1.xhtml24
-rw-r--r--dom/base/crashtests/344882-1.html33
-rw-r--r--dom/base/crashtests/345837-1.xhtml35
-rw-r--r--dom/base/crashtests/346381-1.html16
-rw-r--r--dom/base/crashtests/349355-1.html41
-rw-r--r--dom/base/crashtests/359432-1.xhtml27
-rw-r--r--dom/base/crashtests/360599-1.html25
-rw-r--r--dom/base/crashtests/366200-1.xhtml34
-rw-r--r--dom/base/crashtests/369219-1.xhtml19
-rw-r--r--dom/base/crashtests/369413-1.html12
-rw-r--r--dom/base/crashtests/371124-1-inner.html21
-rw-r--r--dom/base/crashtests/371124-1.html9
-rw-r--r--dom/base/crashtests/371124-2-inner.html10
-rw-r--r--dom/base/crashtests/371124-2.html9
-rw-r--r--dom/base/crashtests/371466-1.xhtml24
-rw-r--r--dom/base/crashtests/372554-1.html22
-rw-r--r--dom/base/crashtests/375399-1-inner.xhtml12
-rw-r--r--dom/base/crashtests/375399-1.html11
-rw-r--r--dom/base/crashtests/377360-1.xhtml19
-rw-r--r--dom/base/crashtests/377960-1.html12
-rw-r--r--dom/base/crashtests/377960-2.html7
-rw-r--r--dom/base/crashtests/384663-1-inner.xhtml18
-rw-r--r--dom/base/crashtests/384663-1.html9
-rw-r--r--dom/base/crashtests/386000-1.html36
-rw-r--r--dom/base/crashtests/386794-1.html17
-rw-r--r--dom/base/crashtests/387460-1-inner.xhtml22
-rw-r--r--dom/base/crashtests/387460-1.html9
-rw-r--r--dom/base/crashtests/399712-1.html29
-rw-r--r--dom/base/crashtests/400763-1.html7
-rw-r--r--dom/base/crashtests/407818.html5
-rw-r--r--dom/base/crashtests/410860-1.xml8
-rw-r--r--dom/base/crashtests/411882-1.xhtml1
-rw-r--r--dom/base/crashtests/416734-1.html13
-rw-r--r--dom/base/crashtests/417852-1.html13
-rw-r--r--dom/base/crashtests/418928-1.html10
-rw-r--r--dom/base/crashtests/420620-1.html29
-rw-r--r--dom/base/crashtests/424276-1.html17
-rw-r--r--dom/base/crashtests/426987-1.html7
-rw-r--r--dom/base/crashtests/43040-1.html19
-rw-r--r--dom/base/crashtests/439206-1.html19
-rw-r--r--dom/base/crashtests/443538-1.svg7
-rw-r--r--dom/base/crashtests/448615-1.html13
-rw-r--r--dom/base/crashtests/450383-1.html9
-rw-r--r--dom/base/crashtests/450385-1.html11
-rw-r--r--dom/base/crashtests/458637-1-inner.xhtml4
-rw-r--r--dom/base/crashtests/458637-1.html29
-rw-r--r--dom/base/crashtests/462947.html13
-rw-r--r--dom/base/crashtests/467392.html4
-rw-r--r--dom/base/crashtests/472593-1.html7
-rw-r--r--dom/base/crashtests/474041-1.svg17
-rw-r--r--dom/base/crashtests/476526.html10
-rw-r--r--dom/base/crashtests/483818-1.html14
-rw-r--r--dom/base/crashtests/490760-1.xhtml25
-rw-r--r--dom/base/crashtests/493281-1.html7
-rw-r--r--dom/base/crashtests/493281-2.html12
-rw-r--r--dom/base/crashtests/494810-1.html15
-rw-r--r--dom/base/crashtests/499006-1.html26
-rw-r--r--dom/base/crashtests/499006-2.html35
-rw-r--r--dom/base/crashtests/502617.html13
-rw-r--r--dom/base/crashtests/504224.html29
-rw-r--r--dom/base/crashtests/509536-1.html17
-rw-r--r--dom/base/crashtests/522516-1.html10
-rw-r--r--dom/base/crashtests/529670.html18
-rw-r--r--dom/base/crashtests/535926-1.html28
-rw-r--r--dom/base/crashtests/543645.html14
-rw-r--r--dom/base/crashtests/551631-1.html22
-rw-r--r--dom/base/crashtests/552651.html25
-rw-r--r--dom/base/crashtests/552651.xml2
-rw-r--r--dom/base/crashtests/554230-1.xhtml15
-rw-r--r--dom/base/crashtests/558973.html17
-rw-r--r--dom/base/crashtests/564079-1.html10
-rw-r--r--dom/base/crashtests/564114.html11
-rw-r--r--dom/base/crashtests/565125-1.html27
-rw-r--r--dom/base/crashtests/575462.svg27
-rw-r--r--dom/base/crashtests/582601.html12
-rw-r--r--dom/base/crashtests/590395-1.html5
-rw-r--r--dom/base/crashtests/593302-1.html29
-rw-r--r--dom/base/crashtests/593302-2.html12
-rw-r--r--dom/base/crashtests/595606-1.html17
-rw-r--r--dom/base/crashtests/595606-2.html18
-rw-r--r--dom/base/crashtests/601247.html8
-rw-r--r--dom/base/crashtests/603531.html18
-rw-r--r--dom/base/crashtests/604262-1.html8
-rw-r--r--dom/base/crashtests/605672-1.svg17
-rw-r--r--dom/base/crashtests/606729-1.html1
-rw-r--r--dom/base/crashtests/607222.html21
-rw-r--r--dom/base/crashtests/609560-1.xhtml31
-rw-r--r--dom/base/crashtests/610571-1.html26
-rw-r--r--dom/base/crashtests/612018-1.html21
-rw-r--r--dom/base/crashtests/628599-1.html31
-rw-r--r--dom/base/crashtests/637116.html29
-rw-r--r--dom/base/crashtests/637214-1.svg26
-rw-r--r--dom/base/crashtests/637214-2.svg26
-rw-r--r--dom/base/crashtests/642022-1.html4
-rw-r--r--dom/base/crashtests/646184.html17
-rw-r--r--dom/base/crashtests/658845-1.svg3
-rw-r--r--dom/base/crashtests/666869.html17
-rw-r--r--dom/base/crashtests/667336-1.html4
-rw-r--r--dom/base/crashtests/675516.xhtml31
-rw-r--r--dom/base/crashtests/675621-1.html7
-rw-r--r--dom/base/crashtests/677194.html6
-rw-r--r--dom/base/crashtests/679459.html21
-rw-r--r--dom/base/crashtests/679689-1.html2
-rw-r--r--dom/base/crashtests/682463.html20
-rw-r--r--dom/base/crashtests/693212.xhtml16
-rw-r--r--dom/base/crashtests/693811-1.html14
-rw-r--r--dom/base/crashtests/693811-2.html16
-rw-r--r--dom/base/crashtests/693811-3.html4
-rw-r--r--dom/base/crashtests/693894.html8
-rw-r--r--dom/base/crashtests/695867.html9
-rw-r--r--dom/base/crashtests/697643.html5
-rw-r--r--dom/base/crashtests/698974-1.html4
-rw-r--r--dom/base/crashtests/700090-1.html32
-rw-r--r--dom/base/crashtests/700090-2.html29
-rw-r--r--dom/base/crashtests/700512-worker.js7
-rw-r--r--dom/base/crashtests/700512.html11
-rw-r--r--dom/base/crashtests/706283-1.html6
-rw-r--r--dom/base/crashtests/709384.html5
-rw-r--r--dom/base/crashtests/709954.html21
-rw-r--r--dom/base/crashtests/713417-1.html23
-rw-r--r--dom/base/crashtests/713417-2.html26
-rw-r--r--dom/base/crashtests/715056.html21
-rw-r--r--dom/base/crashtests/729431-1.xhtml36
-rw-r--r--dom/base/crashtests/741163-1.html7
-rw-r--r--dom/base/crashtests/745495.html19
-rw-r--r--dom/base/crashtests/752226-1.html4
-rw-r--r--dom/base/crashtests/752226-2.html4
-rw-r--r--dom/base/crashtests/766426.html32
-rw-r--r--dom/base/crashtests/771639.html16
-rw-r--r--dom/base/crashtests/786854.html4
-rw-r--r--dom/base/crashtests/815043.html8
-rw-r--r--dom/base/crashtests/815276.html6
-rw-r--r--dom/base/crashtests/815477.html15
-rw-r--r--dom/base/crashtests/815500.html14
-rw-r--r--dom/base/crashtests/816253.html31
-rw-r--r--dom/base/crashtests/819014.html22
-rw-r--r--dom/base/crashtests/822723.html20
-rw-r--r--dom/base/crashtests/824719.html26
-rw-r--r--dom/base/crashtests/827190.html13
-rw-r--r--dom/base/crashtests/828054.html19
-rw-r--r--dom/base/crashtests/828903-iframe.html46
-rw-r--r--dom/base/crashtests/828903.html28
-rw-r--r--dom/base/crashtests/829428.html8
-rw-r--r--dom/base/crashtests/830098.html14
-rw-r--r--dom/base/crashtests/831287.html11
-rw-r--r--dom/base/crashtests/832644.html8
-rw-r--r--dom/base/crashtests/836890.html19
-rw-r--r--dom/base/crashtests/838489-1.html11
-rw-r--r--dom/base/crashtests/838489-2.html16
-rw-r--r--dom/base/crashtests/841205.html25
-rw-r--r--dom/base/crashtests/844404.html23
-rw-r--r--dom/base/crashtests/845093-1.html10
-rw-r--r--dom/base/crashtests/845093-2.html7
-rw-r--r--dom/base/crashtests/847127.html19
-rw-r--r--dom/base/crashtests/849601.html38
-rw-r--r--dom/base/crashtests/849727.html9
-rw-r--r--dom/base/crashtests/849732.html18
-rw-r--r--dom/base/crashtests/851353-1.html25
-rw-r--r--dom/base/crashtests/852381.html19
-rw-r--r--dom/base/crashtests/863950.html18
-rw-r--r--dom/base/crashtests/864448.html22
-rw-r--r--dom/base/crashtests/886213.html22
-rw-r--r--dom/base/crashtests/898906.html14
-rw-r--r--dom/base/crashtests/90613-1.html7
-rw-r--r--dom/base/crashtests/930250.html8
-rw-r--r--dom/base/crashtests/942979.html42
-rw-r--r--dom/base/crashtests/973401.html20
-rw-r--r--dom/base/crashtests/978646.html15
-rw-r--r--dom/base/crashtests/crashtests.list270
-rw-r--r--dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown-worker.js4
-rw-r--r--dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown.html36
-rw-r--r--dom/base/crashtests/file_504224.html7
-rw-r--r--dom/base/crashtests/module-with-syntax-error.js1
-rw-r--r--dom/base/crashtests/structured_clone_container_throws.html9
-rw-r--r--dom/base/crashtests/xhr-with-pagehide-1-helper.html21
-rw-r--r--dom/base/crashtests/xhr-with-pagehide-1.html21
-rw-r--r--dom/base/crashtests/xhr_empty_datauri.html5
-rw-r--r--dom/base/crashtests/xhr_html_nullresponse.html5
-rw-r--r--dom/base/domerr.msg136
-rw-r--r--dom/base/fuzztest/FuzzStructuredClone.cpp70
-rw-r--r--dom/base/fuzztest/moz.build23
-rwxr-xr-xdom/base/gen-usecounters.py74
-rw-r--r--dom/base/moz.build630
-rw-r--r--dom/base/mozAutoDocUpdate.h51
-rw-r--r--dom/base/mozIDOMWindow.idl16
-rw-r--r--dom/base/nsAttrName.h179
-rw-r--r--dom/base/nsAttrValue.cpp1972
-rw-r--r--dom/base/nsAttrValue.h581
-rw-r--r--dom/base/nsAttrValueInlines.h273
-rw-r--r--dom/base/nsAttrValueOrString.cpp29
-rw-r--r--dom/base/nsAttrValueOrString.h85
-rw-r--r--dom/base/nsCCUncollectableMarker.cpp493
-rw-r--r--dom/base/nsCCUncollectableMarker.h46
-rw-r--r--dom/base/nsCaseTreatment.h15
-rw-r--r--dom/base/nsChildContentList.h83
-rw-r--r--dom/base/nsContentAreaDragDrop.cpp875
-rw-r--r--dom/base/nsContentAreaDragDrop.h83
-rw-r--r--dom/base/nsContentCID.h195
-rw-r--r--dom/base/nsContentCreatorFunctions.h69
-rw-r--r--dom/base/nsContentList.cpp1181
-rw-r--r--dom/base/nsContentList.h655
-rw-r--r--dom/base/nsContentListDeclarations.h61
-rw-r--r--dom/base/nsContentPermissionHelper.cpp871
-rw-r--r--dom/base/nsContentPermissionHelper.h232
-rw-r--r--dom/base/nsContentPolicy.cpp173
-rw-r--r--dom/base/nsContentPolicy.h43
-rw-r--r--dom/base/nsContentPolicyUtils.h323
-rw-r--r--dom/base/nsContentSink.cpp907
-rw-r--r--dom/base/nsContentSink.h261
-rw-r--r--dom/base/nsContentTypeParser.cpp28
-rw-r--r--dom/base/nsContentTypeParser.h23
-rw-r--r--dom/base/nsContentUtils.cpp11190
-rw-r--r--dom/base/nsContentUtils.h3680
-rw-r--r--dom/base/nsCopySupport.cpp908
-rw-r--r--dom/base/nsCopySupport.h116
-rw-r--r--dom/base/nsDOMAttributeMap.cpp405
-rw-r--r--dom/base/nsDOMAttributeMap.h176
-rw-r--r--dom/base/nsDOMCID.h28
-rw-r--r--dom/base/nsDOMCaretPosition.cpp58
-rw-r--r--dom/base/nsDOMCaretPosition.h91
-rw-r--r--dom/base/nsDOMDataChannel.cpp510
-rw-r--r--dom/base/nsDOMDataChannel.h130
-rw-r--r--dom/base/nsDOMDataChannelDeclarations.h26
-rw-r--r--dom/base/nsDOMJSUtils.h25
-rw-r--r--dom/base/nsDOMMutationObserver.cpp1097
-rw-r--r--dom/base/nsDOMMutationObserver.h869
-rw-r--r--dom/base/nsDOMNavigationTiming.cpp659
-rw-r--r--dom/base/nsDOMNavigationTiming.h269
-rw-r--r--dom/base/nsDOMString.h18
-rw-r--r--dom/base/nsDOMTokenList.cpp366
-rw-r--r--dom/base/nsDOMTokenList.h98
-rw-r--r--dom/base/nsDOMWindowUtils.cpp4941
-rw-r--r--dom/base/nsDOMWindowUtils.h117
-rw-r--r--dom/base/nsDataDocumentContentPolicy.cpp177
-rw-r--r--dom/base/nsDataDocumentContentPolicy.h39
-rw-r--r--dom/base/nsDeprecatedOperationList.h61
-rw-r--r--dom/base/nsDocElementCreatedNotificationRunner.h31
-rw-r--r--dom/base/nsDocumentWarningList.h18
-rw-r--r--dom/base/nsFocusManager.cpp5443
-rw-r--r--dom/base/nsFocusManager.h1027
-rw-r--r--dom/base/nsFrameLoader.cpp3925
-rw-r--r--dom/base/nsFrameLoader.h574
-rw-r--r--dom/base/nsFrameLoaderOwner.cpp400
-rw-r--r--dom/base/nsFrameLoaderOwner.h132
-rw-r--r--dom/base/nsFrameMessageManager.cpp1657
-rw-r--r--dom/base/nsFrameMessageManager.h375
-rw-r--r--dom/base/nsGlobalWindow.h17
-rw-r--r--dom/base/nsGlobalWindowCommands.cpp1196
-rw-r--r--dom/base/nsGlobalWindowCommands.h41
-rw-r--r--dom/base/nsGlobalWindowInner.cpp7912
-rw-r--r--dom/base/nsGlobalWindowInner.h1616
-rw-r--r--dom/base/nsGlobalWindowOuter.cpp7600
-rw-r--r--dom/base/nsGlobalWindowOuter.h1236
-rw-r--r--dom/base/nsHistory.cpp289
-rw-r--r--dom/base/nsHistory.h74
-rw-r--r--dom/base/nsIAnimationObserver.h55
-rw-r--r--dom/base/nsIContent.h800
-rw-r--r--dom/base/nsIContentInlines.h244
-rw-r--r--dom/base/nsIContentPolicy.idl616
-rw-r--r--dom/base/nsIDOMRequestService.idl21
-rw-r--r--dom/base/nsIDocumentObserver.h120
-rw-r--r--dom/base/nsIDroppedLinkHandler.idl89
-rw-r--r--dom/base/nsIEventSourceEventService.idl35
-rw-r--r--dom/base/nsIGlobalObject.cpp420
-rw-r--r--dom/base/nsIGlobalObject.h304
-rw-r--r--dom/base/nsIImageLoadingContent.idl163
-rw-r--r--dom/base/nsIMessageManager.idl20
-rw-r--r--dom/base/nsIMutationObserver.h432
-rw-r--r--dom/base/nsINode.cpp3703
-rw-r--r--dom/base/nsINode.h2470
-rw-r--r--dom/base/nsINodeList.h54
-rw-r--r--dom/base/nsIObjectLoadingContent.idl101
-rw-r--r--dom/base/nsIScriptChannel.idl75
-rw-r--r--dom/base/nsIScriptContext.h93
-rw-r--r--dom/base/nsIScriptGlobalObject.h84
-rw-r--r--dom/base/nsIScriptObjectPrincipal.h40
-rw-r--r--dom/base/nsIScriptableContentIterator.idl74
-rw-r--r--dom/base/nsISelectionController.idl352
-rw-r--r--dom/base/nsISelectionDisplay.idl37
-rw-r--r--dom/base/nsISelectionListener.idl53
-rw-r--r--dom/base/nsISizeOfEventTarget.h42
-rw-r--r--dom/base/nsISlowScriptDebug.idl35
-rw-r--r--dom/base/nsImageLoadingContent.cpp1873
-rw-r--r--dom/base/nsImageLoadingContent.h611
-rw-r--r--dom/base/nsJSEnvironment.cpp2396
-rw-r--r--dom/base/nsJSEnvironment.h219
-rw-r--r--dom/base/nsJSUtils.cpp222
-rw-r--r--dom/base/nsJSUtils.h250
-rw-r--r--dom/base/nsLineBreaker.cpp553
-rw-r--r--dom/base/nsLineBreaker.h285
-rw-r--r--dom/base/nsMappedAttributeElement.cpp32
-rw-r--r--dom/base/nsMappedAttributeElement.h49
-rw-r--r--dom/base/nsMappedAttributes.cpp264
-rw-r--r--dom/base/nsMappedAttributes.h115
-rw-r--r--dom/base/nsMimeTypeArray.cpp100
-rw-r--r--dom/base/nsMimeTypeArray.h114
-rw-r--r--dom/base/nsNameSpaceManager.cpp261
-rw-r--r--dom/base/nsNameSpaceManager.h83
-rw-r--r--dom/base/nsNoDataProtocolContentPolicy.cpp66
-rw-r--r--dom/base/nsNoDataProtocolContentPolicy.h39
-rw-r--r--dom/base/nsNodeInfoManager.cpp402
-rw-r--r--dom/base/nsNodeInfoManager.h181
-rw-r--r--dom/base/nsObjectLoadingContent.cpp2247
-rw-r--r--dom/base/nsObjectLoadingContent.h566
-rw-r--r--dom/base/nsOpenURIInFrameParams.cpp93
-rw-r--r--dom/base/nsOpenURIInFrameParams.h40
-rw-r--r--dom/base/nsPIDOMWindow.h1188
-rw-r--r--dom/base/nsPIDOMWindowInlines.h88
-rw-r--r--dom/base/nsPIWindowRoot.h96
-rw-r--r--dom/base/nsPluginArray.cpp154
-rw-r--r--dom/base/nsPluginArray.h134
-rw-r--r--dom/base/nsPropertyTable.cpp286
-rw-r--r--dom/base/nsPropertyTable.h166
-rw-r--r--dom/base/nsQueryContentEventResult.cpp234
-rw-r--r--dom/base/nsQueryContentEventResult.h44
-rw-r--r--dom/base/nsRange.cpp3216
-rw-r--r--dom/base/nsRange.h439
-rw-r--r--dom/base/nsSandboxFlags.h138
-rw-r--r--dom/base/nsScreen.cpp232
-rw-r--r--dom/base/nsScreen.h143
-rw-r--r--dom/base/nsStructuredCloneContainer.cpp162
-rw-r--r--dom/base/nsStructuredCloneContainer.h42
-rw-r--r--dom/base/nsStubAnimationObserver.cpp9
-rw-r--r--dom/base/nsStubAnimationObserver.h17
-rw-r--r--dom/base/nsStubDocumentObserver.cpp19
-rw-r--r--dom/base/nsStubDocumentObserver.h35
-rw-r--r--dom/base/nsStubMutationObserver.cpp201
-rw-r--r--dom/base/nsStubMutationObserver.h83
-rw-r--r--dom/base/nsStyledElement.cpp219
-rw-r--r--dom/base/nsStyledElement.h99
-rw-r--r--dom/base/nsSyncLoadService.cpp363
-rw-r--r--dom/base/nsSyncLoadService.h70
-rw-r--r--dom/base/nsTextFragment.cpp538
-rw-r--r--dom/base/nsTextFragment.h316
-rw-r--r--dom/base/nsTextFragmentGeneric.h65
-rw-r--r--dom/base/nsTextFragmentGenericFwd.h20
-rw-r--r--dom/base/nsTextFragmentImpl.h36
-rw-r--r--dom/base/nsTextFragmentSSE2.cpp10
-rw-r--r--dom/base/nsTextFragmentVMX.cpp101
-rw-r--r--dom/base/nsTextNode.cpp246
-rw-r--r--dom/base/nsTextNode.h66
-rw-r--r--dom/base/nsTraversal.cpp66
-rw-r--r--dom/base/nsTraversal.h49
-rw-r--r--dom/base/nsTreeSanitizer.cpp2603
-rw-r--r--dom/base/nsTreeSanitizer.h408
-rw-r--r--dom/base/nsViewportInfo.cpp53
-rw-r--r--dom/base/nsViewportInfo.h160
-rw-r--r--dom/base/nsWindowMemoryReporter.cpp936
-rw-r--r--dom/base/nsWindowMemoryReporter.h178
-rw-r--r--dom/base/nsWindowRoot.cpp389
-rw-r--r--dom/base/nsWindowRoot.h98
-rw-r--r--dom/base/nsWindowSizes.h239
-rw-r--r--dom/base/nsWrapperCache.cpp146
-rw-r--r--dom/base/nsWrapperCache.h646
-rw-r--r--dom/base/nsWrapperCacheInlines.h98
-rw-r--r--dom/base/rust/Cargo.toml14
-rw-r--r--dom/base/rust/cbindgen.toml40
-rw-r--r--dom/base/rust/lib.rs198
-rw-r--r--dom/base/test/.eslintrc.js12
-rw-r--r--dom/base/test/345339_iframe.html29
-rw-r--r--dom/base/test/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--dom/base/test/FAIL.html1
-rw-r--r--dom/base/test/PASS.html1
-rw-r--r--dom/base/test/accesscontrol.resource7
-rw-r--r--dom/base/test/accesscontrol.resource^headers^5
-rw-r--r--dom/base/test/audio.oggbin0 -> 14293 bytes
-rw-r--r--dom/base/test/badContentType.eventsource5
-rw-r--r--dom/base/test/badContentType.eventsource^headers^1
-rw-r--r--dom/base/test/badHTTPResponseCode.eventsource5
-rw-r--r--dom/base/test/badHTTPResponseCode.eventsource^headers^2
-rw-r--r--dom/base/test/badMessageEvent.eventsource4
-rw-r--r--dom/base/test/badMessageEvent.eventsource^headers^1
-rw-r--r--dom/base/test/badMessageEvent2.eventsource5
-rw-r--r--dom/base/test/badMessageEvent2.eventsource^headers^1
-rw-r--r--dom/base/test/browser.ini112
-rw-r--r--dom/base/test/browser_aboutnewtab_process_selection.js137
-rw-r--r--dom/base/test/browser_blocking_image.js183
-rw-r--r--dom/base/test/browser_bug1011748.js31
-rw-r--r--dom/base/test/browser_bug1058164.js238
-rw-r--r--dom/base/test/browser_bug1303838.js368
-rw-r--r--dom/base/test/browser_bug1554070.js49
-rw-r--r--dom/base/test/browser_bug1691214.js122
-rw-r--r--dom/base/test/browser_bug1703472.js68
-rw-r--r--dom/base/test/browser_bug902350.js56
-rw-r--r--dom/base/test/browser_chromeutils_getalldomprocesses.js62
-rw-r--r--dom/base/test/browser_chromeutils_isdomobject.js115
-rw-r--r--dom/base/test/browser_data_documents_aboutmemory.js20
-rw-r--r--dom/base/test/browser_force_process_selector.js38
-rw-r--r--dom/base/test/browser_form_validity_popup_submit.js55
-rw-r--r--dom/base/test/browser_inputStream_structuredClone.js72
-rw-r--r--dom/base/test/browser_messagemanager_loadprocessscript.js193
-rw-r--r--dom/base/test/browser_messagemanager_targetframeloader.js36
-rw-r--r--dom/base/test/browser_messagemanager_unload.js131
-rw-r--r--dom/base/test/browser_multiple_popups.html63
-rw-r--r--dom/base/test/browser_multiple_popups.js296
-rw-r--r--dom/base/test/browser_outline_refocus.js65
-rw-r--r--dom/base/test/browser_page_load_event_telemetry.js46
-rw-r--r--dom/base/test/browser_pagehide_on_tab_close.js21
-rw-r--r--dom/base/test/browser_promiseDocumentFlushed.js292
-rw-r--r--dom/base/test/browser_refresh_content.js127
-rw-r--r--dom/base/test/browser_state_notifications.js193
-rw-r--r--dom/base/test/browser_timeout_throttling_with_audio_playback.js73
-rw-r--r--dom/base/test/browser_use_counters.js348
-rw-r--r--dom/base/test/browser_user_input_handling_delay.js82
-rw-r--r--dom/base/test/browser_user_input_handling_delay_aboutblank.js58
-rw-r--r--dom/base/test/browser_user_input_handling_delay_bfcache.js107
-rw-r--r--dom/base/test/browser_user_input_handling_delay_invisible_iframe.js78
-rw-r--r--dom/base/test/browser_user_input_handling_delay_reload_ticks.js54
-rw-r--r--dom/base/test/browser_xml_toggle.js24
-rw-r--r--dom/base/test/bug1576154.sjs8
-rw-r--r--dom/base/test/bug1739957.sjs10
-rw-r--r--dom/base/test/bug282547.sjs8
-rw-r--r--dom/base/test/bug298064-subframe.html24
-rw-r--r--dom/base/test/bug313646.txt1
-rw-r--r--dom/base/test/bug382113_object.html6
-rw-r--r--dom/base/test/bug403852_fileOpener.js24
-rw-r--r--dom/base/test/bug419132.html22
-rw-r--r--dom/base/test/bug426308-redirect.sjs4
-rw-r--r--dom/base/test/bug435425.sjs25
-rw-r--r--dom/base/test/bug435425_redirect.sjs4
-rw-r--r--dom/base/test/bug444322.js0
-rw-r--r--dom/base/test/bug444322.txt0
-rw-r--r--dom/base/test/bug444546.sjs21
-rw-r--r--dom/base/test/bug455629-helper.svg6
-rw-r--r--dom/base/test/bug457746.sjs10
-rw-r--r--dom/base/test/bug461735-post-redirect.js3
-rw-r--r--dom/base/test/bug461735-redirect1.sjs8
-rw-r--r--dom/base/test/bug461735-redirect2.sjs8
-rw-r--r--dom/base/test/bug466080.sjs14
-rw-r--r--dom/base/test/bug466409-empty.css0
-rw-r--r--dom/base/test/bug466409-page.html12
-rw-r--r--dom/base/test/bug475156.sjs23
-rw-r--r--dom/base/test/bug482935.sjs12
-rw-r--r--dom/base/test/bug540854.sjs21
-rw-r--r--dom/base/test/bug578096LoadChromeScript.js20
-rw-r--r--dom/base/test/bug638112-response.txtbin0 -> 247 bytes
-rw-r--r--dom/base/test/bug638112.sjs24
-rw-r--r--dom/base/test/bug696301-script-1.js3
-rw-r--r--dom/base/test/bug696301-script-1.js^headers^1
-rw-r--r--dom/base/test/bug696301-script-2.js3
-rw-r--r--dom/base/test/bug704320.sjs396
-rw-r--r--dom/base/test/bug704320_counter.sjs96
-rw-r--r--dom/base/test/bug819051.sjs9
-rw-r--r--dom/base/test/chrome.ini42
-rw-r--r--dom/base/test/chrome/bug418986-1.js88
-rw-r--r--dom/base/test/chrome/bug421622-referer.sjs9
-rw-r--r--dom/base/test/chrome/bug884693.sjs8
-rw-r--r--dom/base/test/chrome/chrome.ini84
-rw-r--r--dom/base/test/chrome/clonedoc/chrome.manifest1
-rw-r--r--dom/base/test/chrome/clonedoc/content/doc.xml4
-rw-r--r--dom/base/test/chrome/custom_element_ep.js14
-rw-r--r--dom/base/test/chrome/file_bug1139964.xhtml60
-rw-r--r--dom/base/test/chrome/file_bug1209621.xhtml85
-rw-r--r--dom/base/test/chrome/file_bug549682.xhtml214
-rw-r--r--dom/base/test/chrome/file_bug616841.xhtml63
-rw-r--r--dom/base/test/chrome/file_bug816340.xhtml70
-rw-r--r--dom/base/test/chrome/file_bug990812-1.xhtml61
-rw-r--r--dom/base/test/chrome/file_bug990812-2.xhtml56
-rw-r--r--dom/base/test/chrome/file_bug990812-3.xhtml68
-rw-r--r--dom/base/test/chrome/file_bug990812-4.xhtml63
-rw-r--r--dom/base/test/chrome/file_bug990812-5.xhtml74
-rw-r--r--dom/base/test/chrome/file_bug990812.xhtml55
-rw-r--r--dom/base/test/chrome/file_document-element-inserted-inner.xhtml1
-rw-r--r--dom/base/test/chrome/file_document-element-inserted.xhtml3
-rw-r--r--dom/base/test/chrome/file_title.xhtml1
-rw-r--r--dom/base/test/chrome/fileconstructor_file.pngbin0 -> 95 bytes
-rw-r--r--dom/base/test/chrome/frame_custom_element_content.html5
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.html3
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.js4
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.js^headers^1
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.html3
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.js4
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.js^headers^1
-rw-r--r--dom/base/test/chrome/test_bug1063837.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml46
-rw-r--r--dom/base/test/chrome/test_bug1139964.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug120684.xhtml80
-rw-r--r--dom/base/test/chrome/test_bug1209621.xhtml34
-rw-r--r--dom/base/test/chrome/test_bug1339722.html86
-rw-r--r--dom/base/test/chrome/test_bug1346936.html61
-rw-r--r--dom/base/test/chrome/test_bug206691.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug289714.xhtml33
-rw-r--r--dom/base/test/chrome/test_bug339494.xhtml62
-rw-r--r--dom/base/test/chrome/test_bug357450.xhtml56
-rw-r--r--dom/base/test/chrome/test_bug380418.html37
-rw-r--r--dom/base/test/chrome/test_bug380418.html^headers^4
-rw-r--r--dom/base/test/chrome/test_bug383430.html38
-rw-r--r--dom/base/test/chrome/test_bug418986-1.xhtml25
-rw-r--r--dom/base/test/chrome/test_bug421622.xhtml34
-rw-r--r--dom/base/test/chrome/test_bug429785.xhtml55
-rw-r--r--dom/base/test/chrome/test_bug430050.xhtml50
-rw-r--r--dom/base/test/chrome/test_bug467123.xhtml44
-rw-r--r--dom/base/test/chrome/test_bug473284.xhtml83
-rw-r--r--dom/base/test/chrome/test_bug549682.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug571390.xhtml42
-rw-r--r--dom/base/test/chrome/test_bug616841.xhtml30
-rw-r--r--dom/base/test/chrome/test_bug635835.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug682305.html150
-rw-r--r--dom/base/test/chrome/test_bug683852.xhtml87
-rw-r--r--dom/base/test/chrome/test_bug752226-3.xhtml28
-rw-r--r--dom/base/test/chrome/test_bug752226-4.xhtml28
-rw-r--r--dom/base/test/chrome/test_bug765993.html61
-rw-r--r--dom/base/test/chrome/test_bug780199.xhtml51
-rw-r--r--dom/base/test/chrome/test_bug780529.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug800386.xhtml65
-rw-r--r--dom/base/test/chrome/test_bug816340.xhtml30
-rw-r--r--dom/base/test/chrome/test_bug884693.xhtml82
-rw-r--r--dom/base/test/chrome/test_bug914381.html58
-rw-r--r--dom/base/test/chrome/test_bug990812.xhtml42
-rw-r--r--dom/base/test/chrome/test_chromeOuterWindowID.xhtml137
-rw-r--r--dom/base/test/chrome/test_custom_element_content.xhtml55
-rw-r--r--dom/base/test/chrome/test_custom_element_ep.xhtml41
-rw-r--r--dom/base/test/chrome/test_document-element-inserted.xhtml54
-rw-r--r--dom/base/test/chrome/test_domparsing.xhtml145
-rw-r--r--dom/base/test/chrome/test_fileconstructor.xhtml86
-rw-r--r--dom/base/test/chrome/test_getElementsWithGrid.html121
-rw-r--r--dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml37
-rw-r--r--dom/base/test/chrome/test_nsITextInputProcessor.xhtml29
-rw-r--r--dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml91
-rw-r--r--dom/base/test/chrome/test_range_getClientRectsAndTexts.html74
-rw-r--r--dom/base/test/chrome/test_swapFrameLoaders.xhtml25
-rw-r--r--dom/base/test/chrome/test_title.xhtml29
-rw-r--r--dom/base/test/chrome/test_windowroot.xhtml18
-rw-r--r--dom/base/test/chrome/title_window.xhtml197
-rw-r--r--dom/base/test/chrome/window_chromeOuterWindowID.xhtml14
-rw-r--r--dom/base/test/chrome/window_nsITextInputProcessor.xhtml4750
-rw-r--r--dom/base/test/chrome/window_swapFrameLoaders.xhtml225
-rw-r--r--dom/base/test/common_postMessages.js393
-rw-r--r--dom/base/test/copypaste.js553
-rw-r--r--dom/base/test/delayedServerEvents.sjs56
-rw-r--r--dom/base/test/dummy.html9
-rw-r--r--dom/base/test/embed_bug455472.html1
-rw-r--r--dom/base/test/empty.html0
-rw-r--r--dom/base/test/eventsource.resource22
-rw-r--r--dom/base/test/eventsource.resource^headers^3
-rw-r--r--dom/base/test/eventsource_message.sjs12
-rw-r--r--dom/base/test/eventsource_reconnect.sjs18
-rw-r--r--dom/base/test/eventsource_redirect.resource2
-rw-r--r--dom/base/test/eventsource_redirect.resource^headers^3
-rw-r--r--dom/base/test/eventsource_redirect_to.resource4
-rw-r--r--dom/base/test/eventsource_redirect_to.resource^headers^3
-rw-r--r--dom/base/test/eventsource_worker.js6
-rw-r--r--dom/base/test/fake_plugin.tst1
-rw-r--r--dom/base/test/file1_setting_opener.html1
-rw-r--r--dom/base/test/file2_setting_opener.html1
-rw-r--r--dom/base/test/file3_setting_opener.html1
-rw-r--r--dom/base/test/file4_setting_opener.html1
-rw-r--r--dom/base/test/file_audioLoop.html2
-rw-r--r--dom/base/test/file_audioLoopInIframe.html2
-rw-r--r--dom/base/test/file_blocking_image.html10
-rw-r--r--dom/base/test/file_browser_refresh_content.html41
-rw-r--r--dom/base/test/file_browser_refresh_expired_resource.sjs13
-rw-r--r--dom/base/test/file_browser_refresh_iframe.sjs13
-rw-r--r--dom/base/test/file_browser_refresh_image.sjs38
-rw-r--r--dom/base/test/file_browser_refresh_non_cacheable.sjs6
-rw-r--r--dom/base/test/file_bug1008126_worker.js151
-rw-r--r--dom/base/test/file_bug1011748_OK.sjs4
-rw-r--r--dom/base/test/file_bug1011748_redirect.sjs5
-rw-r--r--dom/base/test/file_bug1091883_frame.html13
-rw-r--r--dom/base/test/file_bug1091883_subframe.html6
-rw-r--r--dom/base/test/file_bug1091883_target.html13
-rw-r--r--dom/base/test/file_bug1100912.html150
-rw-r--r--dom/base/test/file_bug1198095.js38
-rw-r--r--dom/base/test/file_bug1250148.sjs73
-rw-r--r--dom/base/test/file_bug1268962.sjs92
-rw-r--r--dom/base/test/file_bug1274806.html33
-rw-r--r--dom/base/test/file_bug1303838.html34
-rw-r--r--dom/base/test/file_bug1303838_target.html21
-rw-r--r--dom/base/test/file_bug1303838_target_bar.html1
-rw-r--r--dom/base/test/file_bug1303838_target_baz.html1
-rw-r--r--dom/base/test/file_bug1303838_target_foo.html1
-rw-r--r--dom/base/test/file_bug1303838_target_ibar.html1
-rw-r--r--dom/base/test/file_bug1303838_target_ibaz.html1
-rw-r--r--dom/base/test/file_bug1303838_target_ifoo.html1
-rw-r--r--dom/base/test/file_bug1303838_with_iframe.html13
-rw-r--r--dom/base/test/file_bug1554070_1.html14
-rw-r--r--dom/base/test/file_bug1554070_2.html13
-rw-r--r--dom/base/test/file_bug1639328.html8
-rw-r--r--dom/base/test/file_bug1691214.html4
-rw-r--r--dom/base/test/file_bug1700871.html18
-rw-r--r--dom/base/test/file_bug1703472.html6
-rw-r--r--dom/base/test/file_bug28293.sjs4
-rw-r--r--dom/base/test/file_bug326337.xml1
-rw-r--r--dom/base/test/file_bug326337_inner.html44
-rw-r--r--dom/base/test/file_bug326337_outer.html15
-rw-r--r--dom/base/test/file_bug357450.js74
-rw-r--r--dom/base/test/file_bug416317.xhtml1468
-rw-r--r--dom/base/test/file_bug426646-1.html37
-rw-r--r--dom/base/test/file_bug426646-2.html65
-rw-r--r--dom/base/test/file_bug428847-1.xhtml4
-rw-r--r--dom/base/test/file_bug428847-2.xhtml4
-rw-r--r--dom/base/test/file_bug498897.css1
-rw-r--r--dom/base/test/file_bug498897.html23
-rw-r--r--dom/base/test/file_bug498897.html^headers^1
-rw-r--r--dom/base/test/file_bug503473-frame.sjs22
-rw-r--r--dom/base/test/file_bug503481.sjs54
-rw-r--r--dom/base/test/file_bug503481b_inner.html62
-rw-r--r--dom/base/test/file_bug518104.js3
-rw-r--r--dom/base/test/file_bug541937.html7
-rw-r--r--dom/base/test/file_bug541937.xhtml12
-rw-r--r--dom/base/test/file_bug557892.html25
-rw-r--r--dom/base/test/file_bug562137.txt1
-rw-r--r--dom/base/test/file_bug590812-ref.xhtml3
-rw-r--r--dom/base/test/file_bug590812.xml1
-rw-r--r--dom/base/test/file_bug590870.html16
-rw-r--r--dom/base/test/file_bug601803a.html22
-rw-r--r--dom/base/test/file_bug601803b.html11
-rw-r--r--dom/base/test/file_bug604660-1.xml3
-rw-r--r--dom/base/test/file_bug604660-2.xsl19
-rw-r--r--dom/base/test/file_bug604660-3.js1
-rw-r--r--dom/base/test/file_bug604660-4.js1
-rw-r--r--dom/base/test/file_bug604660-5.xml2
-rw-r--r--dom/base/test/file_bug604660-6.xsl9
-rw-r--r--dom/base/test/file_bug622088.sjs5
-rw-r--r--dom/base/test/file_bug622088_inner.html38
-rw-r--r--dom/base/test/file_bug675121.sjs19
-rw-r--r--dom/base/test/file_bug687859-16.jsbin0 -> 64 bytes
-rw-r--r--dom/base/test/file_bug687859-16.js^headers^1
-rw-r--r--dom/base/test/file_bug687859-bom.js1
-rw-r--r--dom/base/test/file_bug687859-bom.js^headers^1
-rw-r--r--dom/base/test/file_bug687859-charset.js1
-rw-r--r--dom/base/test/file_bug687859-http.js1
-rw-r--r--dom/base/test/file_bug687859-http.js^headers^1
-rw-r--r--dom/base/test/file_bug687859-inherit.js1
-rw-r--r--dom/base/test/file_bug692434.xml1
-rw-r--r--dom/base/test/file_bug704320_preload_attr.html32
-rw-r--r--dom/base/test/file_bug704320_preload_common.js32
-rw-r--r--dom/base/test/file_bug704320_preload_reuse.html31
-rw-r--r--dom/base/test/file_bug704320_redirect.html10
-rw-r--r--dom/base/test/file_bug707142_baseline.json1
-rw-r--r--dom/base/test/file_bug707142_bom.json1
-rw-r--r--dom/base/test/file_bug707142_utf-16.jsonbin0 -> 32 bytes
-rw-r--r--dom/base/test/file_bug708620-2.html4
-rw-r--r--dom/base/test/file_bug708620.html7
-rw-r--r--dom/base/test/file_bug753278.html1
-rw-r--r--dom/base/test/file_bug769117.html16
-rw-r--r--dom/base/test/file_bug782342.txt1
-rw-r--r--dom/base/test/file_bug787778.sjs7
-rw-r--r--dom/base/test/file_bug869432.eventsource4
-rw-r--r--dom/base/test/file_bug869432.eventsource^headers^3
-rw-r--r--dom/base/test/file_bug902350.html19
-rw-r--r--dom/base/test/file_bug902350_frame.html14
-rw-r--r--dom/base/test/file_bug907892.html12
-rw-r--r--dom/base/test/file_bug945152.jarbin0 -> 92275 bytes
-rw-r--r--dom/base/test/file_bug945152_worker.js99
-rw-r--r--dom/base/test/file_change_policy_redirect.html10
-rw-r--r--dom/base/test/file_current_inner_window.html24
-rw-r--r--dom/base/test/file_delazification_strategy.html10
-rw-r--r--dom/base/test/file_delazification_strategy.js91
-rw-r--r--dom/base/test/file_domwindowutils_animation.html217
-rw-r--r--dom/base/test/file_domwindowutils_dynamic_toolbar.html23
-rw-r--r--dom/base/test/file_empty.html1
-rw-r--r--dom/base/test/file_explicit_user_agent.sjs6
-rw-r--r--dom/base/test/file_external_script.html11
-rw-r--r--dom/base/test/file_external_script.xhtml11
-rw-r--r--dom/base/test/file_focus_design_mode_inner.html32
-rw-r--r--dom/base/test/file_focus_display_none_xorigin_iframe_inner.html15
-rw-r--r--dom/base/test/file_focus_shadow_dom.html999
-rw-r--r--dom/base/test/file_general_document.html10
-rw-r--r--dom/base/test/file_history_document_open.html1
-rw-r--r--dom/base/test/file_htmlserializer_1.html44
-rw-r--r--dom/base/test/file_htmlserializer_1_bodyonly.html43
-rw-r--r--dom/base/test/file_htmlserializer_1_format.html57
-rw-r--r--dom/base/test/file_htmlserializer_1_linebreak.html47
-rw-r--r--dom/base/test/file_htmlserializer_1_links.html47
-rw-r--r--dom/base/test/file_htmlserializer_1_nested_body.html47
-rw-r--r--dom/base/test/file_htmlserializer_1_no_body.html5
-rw-r--r--dom/base/test/file_htmlserializer_1_noflag.html47
-rw-r--r--dom/base/test/file_htmlserializer_1_noformatpre.html51
-rw-r--r--dom/base/test/file_htmlserializer_1_raw.html45
-rw-r--r--dom/base/test/file_htmlserializer_1_sibling_body.html47
-rw-r--r--dom/base/test/file_htmlserializer_1_sibling_body_only_body.html43
-rw-r--r--dom/base/test/file_htmlserializer_1_wrap.html52
-rw-r--r--dom/base/test/file_htmlserializer_2.html22
-rw-r--r--dom/base/test/file_htmlserializer_2_basic.html24
-rw-r--r--dom/base/test/file_htmlserializer_ipv6.html5
-rw-r--r--dom/base/test/file_htmlserializer_ipv6_out.html6
-rw-r--r--dom/base/test/file_inline_script.html11
-rw-r--r--dom/base/test/file_inline_script.xhtml11
-rw-r--r--dom/base/test/file_js_cache.html10
-rw-r--r--dom/base/test/file_js_cache.js5
-rw-r--r--dom/base/test/file_js_cache_module.html13
-rw-r--r--dom/base/test/file_js_cache_save_after_load.html10
-rw-r--r--dom/base/test/file_js_cache_save_after_load.js15
-rw-r--r--dom/base/test/file_js_cache_syntax_error.html10
-rw-r--r--dom/base/test/file_js_cache_syntax_error.js1
-rw-r--r--dom/base/test/file_js_cache_with_sri.html12
-rw-r--r--dom/base/test/file_location_href_unknown_protocol.html15
-rw-r--r--dom/base/test/file_lock_orientation_with_pending_fullscreen.html22
-rw-r--r--dom/base/test/file_messagemanager_unload.html6
-rw-r--r--dom/base/test/file_module_js_cache.html10
-rw-r--r--dom/base/test/file_module_js_cache.js6
-rw-r--r--dom/base/test/file_module_js_cache_no_module.html10
-rw-r--r--dom/base/test/file_module_js_cache_with_sri.html12
-rw-r--r--dom/base/test/file_mozfiledataurl_img.jpgbin0 -> 2711 bytes
-rw-r--r--dom/base/test/file_navigator_resolve_identity_xrays.xhtml29
-rw-r--r--dom/base/test/file_receiveMessage.html13
-rw-r--r--dom/base/test/file_restrictedEventSource.sjs69
-rw-r--r--dom/base/test/file_sandbox_and_document_uri.html34
-rw-r--r--dom/base/test/file_script.js1
-rw-r--r--dom/base/test/file_script_module_dynamic_and_element.html10
-rw-r--r--dom/base/test/file_script_module_dynamic_and_element.js19
-rw-r--r--dom/base/test/file_script_module_dynamic_and_element_imported_1.js6
-rw-r--r--dom/base/test/file_script_module_dynamic_and_element_imported_2.js1
-rw-r--r--dom/base/test/file_script_module_dynamic_and_element_imported_3.js1
-rw-r--r--dom/base/test/file_script_module_dynamic_and_static.html10
-rw-r--r--dom/base/test/file_script_module_dynamic_and_static.js10
-rw-r--r--dom/base/test/file_script_module_dynamic_and_static_imported_1.js4
-rw-r--r--dom/base/test/file_script_module_dynamic_and_static_imported_2.js4
-rw-r--r--dom/base/test/file_script_module_dynamic_and_static_imported_3.js1
-rw-r--r--dom/base/test/file_script_module_dynamic_import.html10
-rw-r--r--dom/base/test/file_script_module_dynamic_import.js4
-rw-r--r--dom/base/test/file_script_module_dynamic_import_imported.js1
-rw-r--r--dom/base/test/file_script_module_element_and_dynamic.html10
-rw-r--r--dom/base/test/file_script_module_element_and_dynamic.js10
-rw-r--r--dom/base/test/file_script_module_element_and_dynamic_imported_1.js12
-rw-r--r--dom/base/test/file_script_module_element_and_dynamic_imported_2.js1
-rw-r--r--dom/base/test/file_script_module_element_and_dynamic_imported_3.js1
-rw-r--r--dom/base/test/file_script_module_element_and_import.html10
-rw-r--r--dom/base/test/file_script_module_element_and_import.js8
-rw-r--r--dom/base/test/file_script_module_element_and_import_imported_1.js12
-rw-r--r--dom/base/test/file_script_module_element_and_import_imported_2.js1
-rw-r--r--dom/base/test/file_script_module_element_and_import_imported_3.js1
-rw-r--r--dom/base/test/file_script_module_frames_dynamic.html24
-rw-r--r--dom/base/test/file_script_module_frames_dynamic_load.html19
-rw-r--r--dom/base/test/file_script_module_frames_dynamic_load.js4
-rw-r--r--dom/base/test/file_script_module_frames_dynamic_save.html19
-rw-r--r--dom/base/test/file_script_module_frames_dynamic_save.js4
-rw-r--r--dom/base/test/file_script_module_frames_dynamic_shared.js1
-rw-r--r--dom/base/test/file_script_module_frames_element.html24
-rw-r--r--dom/base/test/file_script_module_frames_element_load.html19
-rw-r--r--dom/base/test/file_script_module_frames_element_save.html19
-rw-r--r--dom/base/test/file_script_module_frames_element_shared.js1
-rw-r--r--dom/base/test/file_script_module_frames_import.html24
-rw-r--r--dom/base/test/file_script_module_frames_import_load.html19
-rw-r--r--dom/base/test/file_script_module_frames_import_load.js4
-rw-r--r--dom/base/test/file_script_module_frames_import_save.html19
-rw-r--r--dom/base/test/file_script_module_frames_import_save.js4
-rw-r--r--dom/base/test/file_script_module_frames_import_shared.js1
-rw-r--r--dom/base/test/file_script_module_frames_relay.js22
-rw-r--r--dom/base/test/file_script_module_import.html10
-rw-r--r--dom/base/test/file_script_module_import.js4
-rw-r--r--dom/base/test/file_script_module_import_and_element.html10
-rw-r--r--dom/base/test/file_script_module_import_and_element.js17
-rw-r--r--dom/base/test/file_script_module_import_and_element_imported_1.js6
-rw-r--r--dom/base/test/file_script_module_import_and_element_imported_2.js1
-rw-r--r--dom/base/test/file_script_module_import_and_element_imported_3.js1
-rw-r--r--dom/base/test/file_script_module_import_imported.js1
-rw-r--r--dom/base/test/file_script_module_import_multi.html10
-rw-r--r--dom/base/test/file_script_module_import_multi.js6
-rw-r--r--dom/base/test/file_script_module_import_multi_elems.html10
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_1.js14
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_2.js6
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_imported_once_1.js1
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_imported_once_2.js1
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_imported_once_3.js1
-rw-r--r--dom/base/test/file_script_module_import_multi_elems_imported_twice.js3
-rw-r--r--dom/base/test/file_script_module_import_multi_imported_once.js4
-rw-r--r--dom/base/test/file_script_module_import_multi_imported_twice.js1
-rw-r--r--dom/base/test/file_script_module_single.html10
-rw-r--r--dom/base/test/file_script_module_single.js8
-rw-r--r--dom/base/test/file_script_module_sri_basic.html11
-rw-r--r--dom/base/test/file_script_module_sri_basic.js1
-rw-r--r--dom/base/test/file_script_module_sri_basic_prep.html11
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem.html12
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem.js4
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_nopreload.html10
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_nopreload.js20
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_nopreload_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_nopreload_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_dynamic_elem_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_elem_dynamic.html12
-rw-r--r--dom/base/test/file_script_module_sri_elem_dynamic.js4
-rw-r--r--dom/base/test/file_script_module_sri_elem_dynamic_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_elem_dynamic_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_1.html12
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_1.js1
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_1_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_2.html12
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_2.js1
-rw-r--r--dom/base/test/file_script_module_sri_elem_elem_2_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_elem_import.html12
-rw-r--r--dom/base/test/file_script_module_sri_elem_import.js4
-rw-r--r--dom/base/test/file_script_module_sri_elem_import_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_elem_import_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_fallback.html11
-rw-r--r--dom/base/test/file_script_module_sri_fallback.js1
-rw-r--r--dom/base/test/file_script_module_sri_fallback_failure.html15
-rw-r--r--dom/base/test/file_script_module_sri_fallback_failure.js1
-rw-r--r--dom/base/test/file_script_module_sri_fallback_failure_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_fallback_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_import_elem.html12
-rw-r--r--dom/base/test/file_script_module_sri_import_elem.js4
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_nopreload.html10
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_nopreload.js18
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_nopreload_imported.js3
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_nopreload_prep.html10
-rw-r--r--dom/base/test/file_script_module_sri_import_elem_prep.html10
-rw-r--r--dom/base/test/file_script_module_static_and_dynamic.html10
-rw-r--r--dom/base/test/file_script_module_static_and_dynamic.js8
-rw-r--r--dom/base/test/file_script_module_static_and_dynamic_imported_1.js4
-rw-r--r--dom/base/test/file_script_module_static_and_dynamic_imported_2.js6
-rw-r--r--dom/base/test/file_script_module_static_and_dynamic_imported_3.js1
-rw-r--r--dom/base/test/file_serializer_noscript.html1
-rw-r--r--dom/base/test/file_setname.html8
-rw-r--r--dom/base/test/file_settimeout_inner.html1
-rw-r--r--dom/base/test/file_suppressed_events_and_scrolling.html30
-rw-r--r--dom/base/test/file_suppressed_events_inner.html16
-rw-r--r--dom/base/test/file_suppressed_events_middle.html10
-rw-r--r--dom/base/test/file_suppressed_events_top.html79
-rw-r--r--dom/base/test/file_suppressed_events_top_modalstate.html79
-rw-r--r--dom/base/test/file_suppressed_events_top_xhr.html82
-rw-r--r--dom/base/test/file_timer_flood.html19
-rw-r--r--dom/base/test/file_title.xhtml1
-rw-r--r--dom/base/test/file_toScreenRect.html48
-rw-r--r--dom/base/test/file_use_counter_bfcache.html38
-rw-r--r--dom/base/test/file_use_counter_bfcache_helper.html40
-rw-r--r--dom/base/test/file_use_counter_outer.html17
-rw-r--r--dom/base/test/file_use_counter_outer_display_none.html16
-rw-r--r--dom/base/test/file_use_counter_style.html14
-rw-r--r--dom/base/test/file_use_counter_svg_currentScale.svg17
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern.svg16
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_data.svg13
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_definition.svg12
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_internal.svg24
-rw-r--r--dom/base/test/file_use_counter_svg_getElementById.svg22
-rw-r--r--dom/base/test/file_viewport_metrics_on_landscape_content.html65
-rw-r--r--dom/base/test/file_viewport_scroll_quirks.html1
-rw-r--r--dom/base/test/file_viewport_scroll_xml.xml1
-rw-r--r--dom/base/test/file_webaudio_startstop.html36
-rw-r--r--dom/base/test/file_window_close.html68
-rw-r--r--dom/base/test/file_window_close_2.html4
-rw-r--r--dom/base/test/file_window_focus_by_close_and_open.html13
-rw-r--r--dom/base/test/file_x-frame-options_main.html44
-rw-r--r--dom/base/test/file_x-frame-options_page.sjs67
-rw-r--r--dom/base/test/file_xhtmlserializer_1.xhtml60
-rw-r--r--dom/base/test/file_xhtmlserializer_1_bodyonly.xhtml56
-rw-r--r--dom/base/test/file_xhtmlserializer_1_format.xhtml71
-rw-r--r--dom/base/test/file_xhtmlserializer_1_linebreak.xhtml65
-rw-r--r--dom/base/test/file_xhtmlserializer_1_links.xhtml65
-rw-r--r--dom/base/test/file_xhtmlserializer_1_nested_body.xhtml65
-rw-r--r--dom/base/test/file_xhtmlserializer_1_no_body.xhtml10
-rw-r--r--dom/base/test/file_xhtmlserializer_1_noflag.xhtml65
-rw-r--r--dom/base/test/file_xhtmlserializer_1_noformatpre.xhtml69
-rw-r--r--dom/base/test/file_xhtmlserializer_1_raw.xhtml60
-rw-r--r--dom/base/test/file_xhtmlserializer_1_sibling_body.xhtml65
-rw-r--r--dom/base/test/file_xhtmlserializer_1_sibling_body_only_body.xhtml56
-rw-r--r--dom/base/test/file_xhtmlserializer_1_wrap.xhtml70
-rw-r--r--dom/base/test/file_xhtmlserializer_2.xhtml30
-rw-r--r--dom/base/test/file_xhtmlserializer_2_basic.xhtml31
-rw-r--r--dom/base/test/file_xhtmlserializer_2_enthtml.xhtml55
-rw-r--r--dom/base/test/file_xhtmlserializer_2_entw3c.xhtml55
-rw-r--r--dom/base/test/file_xhtmlserializer_2_latin1.xhtml41
-rw-r--r--dom/base/test/file_youtube_flash_embed.html65
-rw-r--r--dom/base/test/fmm/browser.ini1
-rw-r--r--dom/base/test/fmm/browser_frame_message_manager_cache.js23
-rw-r--r--dom/base/test/forRemoval.resource24
-rw-r--r--dom/base/test/forRemoval.resource^headers^3
-rw-r--r--dom/base/test/formReset.html15
-rw-r--r--dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml108
-rw-r--r--dom/base/test/fullscreen/browser.ini40
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-api-keys.js218
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js127
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js128
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js141
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js122
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation.js118
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation-history.js100
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation-race.js162
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation.js142
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-newtab.js91
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-sizemode.js225
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js101
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-tab-close.js65
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-window-open-race.js73
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js215
-rw-r--r--dom/base/test/fullscreen/chrome.ini10
-rw-r--r--dom/base/test/fullscreen/dummy_page.html10
-rw-r--r--dom/base/test/fullscreen/file_MozDomFullscreen.html8
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api-keys.html41
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api-race.html8
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api.html340
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-async.html50
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-backdrop.html107
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html22
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-bug-1798219.html14
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-denied-inner.html24
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-denied.html171
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html58
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-esc-exit.html63
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-event-order.html50
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html34
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-featurePolicy.html90
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-focus-inner.html24
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-focus.html67
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-hidden.html56
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-inner.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-middle.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-top.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-lenient-setters.html61
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-multiple-inner.html25
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-multiple.html67
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-navigation.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-nested.html130
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-newtab.html4
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-prefixed.html153
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-resize.html39
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-rollback.html140
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-scrollbar.html147
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-selector.html183
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-shadowdom.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-single.html78
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-sub-iframe.html53
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-svg-element.html49
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-table.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-top-layer.html160
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-utils.js87
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html36
-rw-r--r--dom/base/test/fullscreen/file_fullscreen_meta_viewport.html12
-rw-r--r--dom/base/test/fullscreen/fullscreen.xhtml27
-rw-r--r--dom/base/test/fullscreen/fullscreen_helpers.js174
-rw-r--r--dom/base/test/fullscreen/head.js65
-rw-r--r--dom/base/test/fullscreen/mochitest.ini51
-rw-r--r--dom/base/test/fullscreen/moz.build17
-rw-r--r--dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml46
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api-race.html177
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html167
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api.html150
-rw-r--r--dom/base/test/fullscreen/test_fullscreen.xhtml37
-rw-r--r--dom/base/test/fullscreen/test_fullscreen_meta_viewport.html33
-rw-r--r--dom/base/test/fullscreen/test_fullscreen_modal.html68
-rw-r--r--dom/base/test/green.pngbin0 -> 255 bytes
-rw-r--r--dom/base/test/gtest/TestContentUtils.cpp146
-rw-r--r--dom/base/test/gtest/TestMimeType.cpp816
-rw-r--r--dom/base/test/gtest/TestParser.cpp52
-rw-r--r--dom/base/test/gtest/TestPlainTextSerializer.cpp309
-rw-r--r--dom/base/test/gtest/TestScheduler.cpp348
-rw-r--r--dom/base/test/gtest/TestXMLSerializerNoBreakLink.cpp69
-rw-r--r--dom/base/test/gtest/TestXPathGenerator.cpp150
-rw-r--r--dom/base/test/gtest/moz.build21
-rw-r--r--dom/base/test/head.js15
-rw-r--r--dom/base/test/iframe1_bug1640766.html20
-rw-r--r--dom/base/test/iframe1_bug426646.html1
-rw-r--r--dom/base/test/iframe1_bug431701.html1
-rw-r--r--dom/base/test/iframe2_bug1640766.html10
-rw-r--r--dom/base/test/iframe2_bug426646.html1
-rw-r--r--dom/base/test/iframe2_bug431701.html1
-rw-r--r--dom/base/test/iframe3_bug431701.html1
-rw-r--r--dom/base/test/iframe4_bug431701.xml1
-rw-r--r--dom/base/test/iframe5_bug431701.xml1
-rw-r--r--dom/base/test/iframe6_bug431701.xml1
-rw-r--r--dom/base/test/iframe7_bug431701.xml1
-rw-r--r--dom/base/test/iframe_bug962251.html25
-rw-r--r--dom/base/test/iframe_bug976673.html26
-rw-r--r--dom/base/test/iframe_main_bug1022229.html33
-rw-r--r--dom/base/test/iframe_meta_refresh.sjs92
-rw-r--r--dom/base/test/iframe_postMessage_solidus.html15
-rw-r--r--dom/base/test/iframe_postMessages.html14
-rw-r--r--dom/base/test/iframe_sandbox_bug1022229.html13
-rw-r--r--dom/base/test/iframe_shared_compartment2a.html2
-rw-r--r--dom/base/test/iframe_shared_compartment2b.html3
-rw-r--r--dom/base/test/intersectionobserver_cross_domain_iframe.html24
-rw-r--r--dom/base/test/intersectionobserver_iframe.html17
-rw-r--r--dom/base/test/intersectionobserver_window.html46
-rw-r--r--dom/base/test/invalid_accesscontrol.resource7
-rw-r--r--dom/base/test/invalid_accesscontrol.resource^headers^4
-rw-r--r--dom/base/test/jsmodules/.eslintrc.js7
-rw-r--r--dom/base/test/jsmodules/chrome.ini53
-rw-r--r--dom/base/test/jsmodules/iframe_extractIntroType.html14
-rw-r--r--dom/base/test/jsmodules/importmaps/chrome.ini29
-rw-r--r--dom/base/test/jsmodules/importmaps/external_importMap.js5
-rw-r--r--dom/base/test/jsmodules/importmaps/insert_a_base_element.js4
-rw-r--r--dom/base/test/jsmodules/importmaps/module_simpleExport.js1
-rw-r--r--dom/base/test/jsmodules/importmaps/module_simpleImportMap.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/module_simpleImportMap_dir.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap_https.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/module_sortedImportMap.js4
-rw-r--r--dom/base/test/jsmodules/importmaps/moz.build7
-rw-r--r--dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js1
-rw-r--r--dom/base/test/jsmodules/importmaps/scope1/module_simpleImportMap.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js1
-rw-r--r--dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleImportMap.js2
-rw-r--r--dom/base/test/jsmodules/importmaps/test_dynamic_import_reject_importMap.html46
-rw-r--r--dom/base/test/jsmodules/importmaps/test_externalImportMap.html43
-rw-r--r--dom/base/test/jsmodules/importmaps/test_import_meta_resolve_importMap.html49
-rw-r--r--dom/base/test/jsmodules/importmaps/test_inline_module_reject_importMap.html61
-rw-r--r--dom/base/test/jsmodules/importmaps/test_load_importMap_with_base.html51
-rw-r--r--dom/base/test/jsmodules/importmaps/test_load_importMap_with_base2.html51
-rw-r--r--dom/base/test/jsmodules/importmaps/test_module_script_reject_importMap.html45
-rw-r--r--dom/base/test/jsmodules/importmaps/test_parse_importMap_failed.html40
-rw-r--r--dom/base/test/jsmodules/importmaps/test_reject_multiple_importMaps.html64
-rw-r--r--dom/base/test/jsmodules/importmaps/test_simpleImportMap.html62
-rw-r--r--dom/base/test/jsmodules/importmaps/test_sortedImportMap.html62
-rw-r--r--dom/base/test/jsmodules/module_badImport.js1
-rw-r--r--dom/base/test/jsmodules/module_badSyntax.js3
-rw-r--r--dom/base/test/jsmodules/module_cyclic1.js8
-rw-r--r--dom/base/test/jsmodules/module_cyclic2.js8
-rw-r--r--dom/base/test/jsmodules/module_cyclic3.js8
-rw-r--r--dom/base/test/jsmodules/module_extractIntroType.js5
-rw-r--r--dom/base/test/jsmodules/module_large1.js78
-rw-r--r--dom/base/test/jsmodules/module_large2.js78
-rw-r--r--dom/base/test/jsmodules/module_large3.js78
-rw-r--r--dom/base/test/jsmodules/module_missingImport.js1
-rw-r--r--dom/base/test/jsmodules/module_multiImports.js4
-rw-r--r--dom/base/test/jsmodules/module_multiLargeImports.js4
-rw-r--r--dom/base/test/jsmodules/module_setRan.js2
-rw-r--r--dom/base/test/jsmodules/module_simple1.js1
-rw-r--r--dom/base/test/jsmodules/module_simple2.js1
-rw-r--r--dom/base/test/jsmodules/module_simple3.js1
-rw-r--r--dom/base/test/jsmodules/module_simpleExport.js1
-rw-r--r--dom/base/test/jsmodules/module_simpleImport.js2
-rw-r--r--dom/base/test/jsmodules/module_testSyntax.js3
-rw-r--r--dom/base/test/jsmodules/moz.build7
-rw-r--r--dom/base/test/jsmodules/script_simple2.js1
-rw-r--r--dom/base/test/jsmodules/test_asyncInlineModules.html36
-rw-r--r--dom/base/test/jsmodules/test_cyclicImport.html18
-rw-r--r--dom/base/test/jsmodules/test_dynamicImportErrorMessage.html16
-rw-r--r--dom/base/test/jsmodules/test_importIntroType.html22
-rw-r--r--dom/base/test/jsmodules/test_importNotFound.html27
-rw-r--r--dom/base/test/jsmodules/test_importResolveFailed.html21
-rw-r--r--dom/base/test/jsmodules/test_import_meta_resolve.html65
-rw-r--r--dom/base/test/jsmodules/test_importedModuleMemoization.html30
-rw-r--r--dom/base/test/jsmodules/test_linkErrorInCommon1.html32
-rw-r--r--dom/base/test/jsmodules/test_linkErrorInCommon2.html32
-rw-r--r--dom/base/test/jsmodules/test_moduleNotFound.html24
-rw-r--r--dom/base/test/jsmodules/test_moduleParsedAsModule.html23
-rw-r--r--dom/base/test/jsmodules/test_moduleScriptsRun.html19
-rw-r--r--dom/base/test/jsmodules/test_multiAsyncImports.html30
-rw-r--r--dom/base/test/jsmodules/test_multiModuleImports.html28
-rw-r--r--dom/base/test/jsmodules/test_multiModuleLargeImports.html28
-rw-r--r--dom/base/test/jsmodules/test_multiTopLevelImports.html30
-rw-r--r--dom/base/test/jsmodules/test_multiTopLevelLargeImports.html30
-rw-r--r--dom/base/test/jsmodules/test_scriptInsertedModule.html20
-rw-r--r--dom/base/test/jsmodules/test_scriptModuleOrder.html30
-rw-r--r--dom/base/test/jsmodules/test_scriptNotParsedAsModule.html23
-rw-r--r--dom/base/test/jsmodules/test_simpleImport.html16
-rw-r--r--dom/base/test/jsmodules/test_syntaxError.html30
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorAsync.html30
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInline.html34
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html34
-rw-r--r--dom/base/test/jsmodules/test_topLevelIntroType.html21
-rw-r--r--dom/base/test/jsmodules/test_toplevelModuleMemoization.html30
-rw-r--r--dom/base/test/jsmodules/test_typeAttrCaseInsensitive.html19
-rw-r--r--dom/base/test/meta_viewport/mochitest.ini54
-rw-r--r--dom/base/test/meta_viewport/moz.build9
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport0.html41
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport1.html41
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport2.html41
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport3.html43
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport4.html42
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport5.html24
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport6.html47
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport7.html71
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport8.html27
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_height.html24
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_width.html24
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_height_and_initial_scale_1.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_device_height.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_initial_scale_1.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_0_5.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_1.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width.html24
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width_and_fixed_height.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_change_content_among_multiple.html52
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_change_name.html47
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_change_name_among_multiple.html44
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_device_width.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_0_5.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_2.html31
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_empty_content_and_valid_content_tags.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_fit.html34
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_fit_multiple.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_fixed_width_and_zero_display_width.html29
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_initial_scale_0_5.html33
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_initial_scale_2.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_initial_scale_with_trailing_characters.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_insert_before_existing_tag.html32
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0.html28
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0_5.html29
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_2.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_multiple_tags.html28
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_negative_height.html27
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_no_height.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_valid_height.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_no_content_and_valid_content_tags.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_no_width_and_negative_height.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_no_width_and_valid_height.html25
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_remove_node.html34
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_remove_node_from_multiple.html44
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_removing_content_attribute.html33
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_replace_content.html39
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_tiny_display_size.html28
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_negative_height.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_no_height.html26
-rw-r--r--dom/base/test/meta_viewport/test_meta_viewport_width_with_trailing_characters.html26
-rw-r--r--dom/base/test/meta_viewport/viewport_helpers.js44
-rw-r--r--dom/base/test/mochitest.ini1111
-rw-r--r--dom/base/test/moz.build44
-rw-r--r--dom/base/test/object_bug353334.html1
-rw-r--r--dom/base/test/object_bug455472.html1
-rw-r--r--dom/base/test/red.pngbin0 -> 87 bytes
-rw-r--r--dom/base/test/referrerHelper.js343
-rw-r--r--dom/base/test/referrer_change_server.sjs166
-rw-r--r--dom/base/test/referrer_header.sjs6
-rw-r--r--dom/base/test/referrer_helper.js133
-rw-r--r--dom/base/test/referrer_testserver.sjs693
-rw-r--r--dom/base/test/reftest/mixed-bmp-png.icobin0 -> 17542 bytes
-rw-r--r--dom/base/test/reftest/reftest.list7
-rw-r--r--dom/base/test/reftest/test_bug1525662-ref.html15
-rw-r--r--dom/base/test/reftest/test_bug1525662.txt7
-rw-r--r--dom/base/test/reftest/test_bug920877-ref.html20
-rw-r--r--dom/base/test/reftest/test_bug920877.html38
-rw-r--r--dom/base/test/reftest/test_xmlPrettyPrint_csp-ref.xml4
-rw-r--r--dom/base/test/reftest/test_xmlPrettyPrint_csp.xml4
-rw-r--r--dom/base/test/reftest/test_xmlPrettyPrint_csp.xml^headers^1
-rw-r--r--dom/base/test/script-1_bug597345.sjs22
-rw-r--r--dom/base/test/script-2_bug597345.js1
-rw-r--r--dom/base/test/script_bug1238440.js33
-rw-r--r--dom/base/test/script_bug602838.sjs43
-rw-r--r--dom/base/test/script_postmessages_fileList.js26
-rw-r--r--dom/base/test/send_gzip_content.sjs46
-rw-r--r--dom/base/test/slow.sjs15
-rw-r--r--dom/base/test/somedatas.resource16
-rw-r--r--dom/base/test/somedatas.resource^headers^3
-rw-r--r--dom/base/test/test_EventSource_redirects.html53
-rw-r--r--dom/base/test/test_Image_constructor.html32
-rw-r--r--dom/base/test/test_NodeIterator_basics_filters.xhtml178
-rw-r--r--dom/base/test/test_NodeIterator_mutations_1.xhtml204
-rw-r--r--dom/base/test/test_NodeIterator_mutations_2.html112
-rw-r--r--dom/base/test/test_NodeIterator_mutations_3.html160
-rw-r--r--dom/base/test/test_anchor_area_referrer.html127
-rw-r--r--dom/base/test/test_anchor_area_referrer_changing.html66
-rw-r--r--dom/base/test/test_anchor_area_referrer_invalid.html74
-rw-r--r--dom/base/test/test_anchor_area_referrer_rel.html50
-rw-r--r--dom/base/test/test_anchor_target_blank_referrer.html134
-rw-r--r--dom/base/test/test_anonymousContent_api.html56
-rw-r--r--dom/base/test/test_anonymousContent_append_after_reflow.html40
-rw-r--r--dom/base/test/test_anonymousContent_canvas.html59
-rw-r--r--dom/base/test/test_anonymousContent_insert.html45
-rw-r--r--dom/base/test/test_anonymousContent_manipulate_content.html74
-rw-r--r--dom/base/test/test_anonymousContent_set_style.html35
-rw-r--r--dom/base/test/test_anonymousContent_style_csp.html28
-rw-r--r--dom/base/test/test_anonymousContent_style_csp.html^headers^1
-rw-r--r--dom/base/test/test_anonymousContent_xul_window.xhtml23
-rw-r--r--dom/base/test/test_appname_override.html26
-rw-r--r--dom/base/test/test_async_setTimeout_stack.html60
-rw-r--r--dom/base/test/test_async_setTimeout_stack_across_globals.html60
-rw-r--r--dom/base/test/test_base.xhtml39
-rw-r--r--dom/base/test/test_blockParsing.html134
-rw-r--r--dom/base/test/test_blocking_image.html94
-rw-r--r--dom/base/test/test_bug1008126.html62
-rw-r--r--dom/base/test/test_bug1016960.html30
-rw-r--r--dom/base/test/test_bug1022229.html33
-rw-r--r--dom/base/test/test_bug1025933.html41
-rw-r--r--dom/base/test/test_bug1037687.html32
-rw-r--r--dom/base/test/test_bug1037687_subframe.html46
-rw-r--r--dom/base/test/test_bug1043106.html44
-rw-r--r--dom/base/test/test_bug1057176.html32
-rw-r--r--dom/base/test/test_bug1060938.html44
-rw-r--r--dom/base/test/test_bug1064481.html24
-rw-r--r--dom/base/test/test_bug1070015.html53
-rw-r--r--dom/base/test/test_bug1075702.html77
-rw-r--r--dom/base/test/test_bug1091883.html89
-rw-r--r--dom/base/test/test_bug1100912.html35
-rw-r--r--dom/base/test/test_bug1101364.html93
-rw-r--r--dom/base/test/test_bug1118689.html40
-rw-r--r--dom/base/test/test_bug1120222.html31
-rw-r--r--dom/base/test/test_bug1126851.html44
-rw-r--r--dom/base/test/test_bug116083.html120
-rw-r--r--dom/base/test/test_bug1163743.html44
-rw-r--r--dom/base/test/test_bug1165501.html51
-rw-r--r--dom/base/test/test_bug1187157.html30
-rw-r--r--dom/base/test/test_bug1198095.html71
-rw-r--r--dom/base/test/test_bug1222633.html112
-rw-r--r--dom/base/test/test_bug1222633_link_update.html129
-rw-r--r--dom/base/test/test_bug1238440.html88
-rw-r--r--dom/base/test/test_bug1250148.html64
-rw-r--r--dom/base/test/test_bug1259588.html13
-rw-r--r--dom/base/test/test_bug1268962.html104
-rw-r--r--dom/base/test/test_bug1274806.html31
-rw-r--r--dom/base/test/test_bug1281963.html56
-rw-r--r--dom/base/test/test_bug1295852.html23
-rw-r--r--dom/base/test/test_bug1307730.html44
-rw-r--r--dom/base/test/test_bug1308069.html87
-rw-r--r--dom/base/test/test_bug1314032.html38
-rw-r--r--dom/base/test/test_bug1318303.html49
-rw-r--r--dom/base/test/test_bug1375050.html33
-rw-r--r--dom/base/test/test_bug1381710.html40
-rw-r--r--dom/base/test/test_bug1399605.html32
-rw-r--r--dom/base/test/test_bug1404385.html31
-rw-r--r--dom/base/test/test_bug1406102.html41
-rw-r--r--dom/base/test/test_bug1421568.html48
-rw-r--r--dom/base/test/test_bug1433073.html89
-rw-r--r--dom/base/test/test_bug1472427.html89
-rw-r--r--dom/base/test/test_bug1499169.html32
-rw-r--r--dom/base/test/test_bug1576154.html36
-rw-r--r--dom/base/test/test_bug1632975.html54
-rw-r--r--dom/base/test/test_bug1639328.html68
-rw-r--r--dom/base/test/test_bug1640766.html67
-rw-r--r--dom/base/test/test_bug1648887.html34
-rw-r--r--dom/base/test/test_bug166235.html156
-rw-r--r--dom/base/test/test_bug1667316.html153
-rw-r--r--dom/base/test/test_bug1730284.html71
-rw-r--r--dom/base/test/test_bug1739957.html40
-rw-r--r--dom/base/test/test_bug1784187.html38
-rw-r--r--dom/base/test/test_bug1799354.html77
-rw-r--r--dom/base/test/test_bug199959.html39
-rw-r--r--dom/base/test/test_bug218236.html139
-rw-r--r--dom/base/test/test_bug218277.html28
-rw-r--r--dom/base/test/test_bug238409.html45
-rw-r--r--dom/base/test/test_bug254337.html42
-rw-r--r--dom/base/test/test_bug270145.xhtml51
-rw-r--r--dom/base/test/test_bug276037-1.html105
-rw-r--r--dom/base/test/test_bug276037-2.xhtml106
-rw-r--r--dom/base/test/test_bug282547.html104
-rw-r--r--dom/base/test/test_bug28293.html86
-rw-r--r--dom/base/test/test_bug28293.xhtml87
-rw-r--r--dom/base/test/test_bug298064.html32
-rw-r--r--dom/base/test/test_bug300992.html45
-rw-r--r--dom/base/test/test_bug311681.xml103
-rw-r--r--dom/base/test/test_bug313646.html62
-rw-r--r--dom/base/test/test_bug320799.html74
-rw-r--r--dom/base/test/test_bug322317.html33
-rw-r--r--dom/base/test/test_bug326337.html35
-rw-r--r--dom/base/test/test_bug331959.html151
-rw-r--r--dom/base/test/test_bug333064.html59
-rw-r--r--dom/base/test/test_bug333198.html84
-rw-r--r--dom/base/test/test_bug333673.html30
-rw-r--r--dom/base/test/test_bug337631.html99
-rw-r--r--dom/base/test/test_bug338541.xhtml49
-rw-r--r--dom/base/test/test_bug338583.html665
-rw-r--r--dom/base/test/test_bug338679.html82
-rw-r--r--dom/base/test/test_bug339494.html59
-rw-r--r--dom/base/test/test_bug339494.xhtml58
-rw-r--r--dom/base/test/test_bug343596.html51
-rw-r--r--dom/base/test/test_bug345339.html93
-rw-r--r--dom/base/test/test_bug346485.html77
-rw-r--r--dom/base/test/test_bug352728.html122
-rw-r--r--dom/base/test/test_bug352728.xhtml188
-rw-r--r--dom/base/test/test_bug353334.html67
-rw-r--r--dom/base/test/test_bug355026.html29
-rw-r--r--dom/base/test/test_bug357450.html41
-rw-r--r--dom/base/test/test_bug357450.xhtml39
-rw-r--r--dom/base/test/test_bug357450_svg.xhtml46
-rw-r--r--dom/base/test/test_bug357509.html36
-rw-r--r--dom/base/test/test_bug358660.html37
-rw-r--r--dom/base/test/test_bug362391.xhtml75
-rw-r--r--dom/base/test/test_bug364092.xhtml46
-rw-r--r--dom/base/test/test_bug364413.xhtml48
-rw-r--r--dom/base/test/test_bug366944.html49
-rw-r--r--dom/base/test/test_bug366946.html79
-rw-r--r--dom/base/test/test_bug367164.html47
-rw-r--r--dom/base/test/test_bug368972.html120
-rw-r--r--dom/base/test/test_bug371576-2.html32
-rw-r--r--dom/base/test/test_bug371576-3.html29
-rw-r--r--dom/base/test/test_bug371576-4.html21
-rw-r--r--dom/base/test/test_bug371576-5.html36
-rw-r--r--dom/base/test/test_bug372086.html96
-rw-r--r--dom/base/test/test_bug372964-2.html58
-rw-r--r--dom/base/test/test_bug372964.html144
-rw-r--r--dom/base/test/test_bug373181.xhtml17
-rw-r--r--dom/base/test/test_bug375314-2.html151
-rw-r--r--dom/base/test/test_bug375314.html160
-rw-r--r--dom/base/test/test_bug378969.html47
-rw-r--r--dom/base/test/test_bug380418.html34
-rw-r--r--dom/base/test/test_bug380418.html^headers^4
-rw-r--r--dom/base/test/test_bug382113.html35
-rw-r--r--dom/base/test/test_bug382871.html46
-rw-r--r--dom/base/test/test_bug384003.xhtml84
-rw-r--r--dom/base/test/test_bug390219.html38
-rw-r--r--dom/base/test/test_bug390735.html28
-rw-r--r--dom/base/test/test_bug392318.html44
-rw-r--r--dom/base/test/test_bug392511.html53
-rw-r--r--dom/base/test/test_bug393968.html41
-rw-r--r--dom/base/test/test_bug395915.html43
-rw-r--r--dom/base/test/test_bug397234.html42
-rw-r--r--dom/base/test/test_bug398243.html56
-rw-r--r--dom/base/test/test_bug401662.html51
-rw-r--r--dom/base/test/test_bug402150.html24
-rw-r--r--dom/base/test/test_bug402150.html^headers^1
-rw-r--r--dom/base/test/test_bug403841.html29
-rw-r--r--dom/base/test/test_bug403852.html66
-rw-r--r--dom/base/test/test_bug403868.xml85
-rw-r--r--dom/base/test/test_bug405182.html47
-rw-r--r--dom/base/test/test_bug409380.html378
-rw-r--r--dom/base/test/test_bug410229.html108
-rw-r--r--dom/base/test/test_bug413974.html35
-rw-r--r--dom/base/test/test_bug414190.html70
-rw-r--r--dom/base/test/test_bug415860.html240
-rw-r--r--dom/base/test/test_bug416317-1.html32
-rw-r--r--dom/base/test/test_bug416317-2.html32
-rw-r--r--dom/base/test/test_bug416383.html43
-rw-r--r--dom/base/test/test_bug417255.html60
-rw-r--r--dom/base/test/test_bug417384.html50
-rw-r--r--dom/base/test/test_bug418214.html105
-rw-r--r--dom/base/test/test_bug418986-1.html24
-rw-r--r--dom/base/test/test_bug419132.html48
-rw-r--r--dom/base/test/test_bug419527.xhtml68
-rw-r--r--dom/base/test/test_bug420609.xhtml34
-rw-r--r--dom/base/test/test_bug420700.html35
-rw-r--r--dom/base/test/test_bug421602.html53
-rw-r--r--dom/base/test/test_bug422403-1.html204
-rw-r--r--dom/base/test/test_bug422403-2.xhtml296
-rw-r--r--dom/base/test/test_bug422537.html55
-rw-r--r--dom/base/test/test_bug424212.html35
-rw-r--r--dom/base/test/test_bug424359-1.html213
-rw-r--r--dom/base/test/test_bug424359-2.html301
-rw-r--r--dom/base/test/test_bug426308.html42
-rw-r--r--dom/base/test/test_bug426646.html41
-rw-r--r--dom/base/test/test_bug428847.html33
-rw-r--r--dom/base/test/test_bug431082.html51
-rw-r--r--dom/base/test/test_bug431701.html119
-rw-r--r--dom/base/test/test_bug431833.html51
-rw-r--r--dom/base/test/test_bug433533.html300
-rw-r--r--dom/base/test/test_bug433662.html31
-rw-r--r--dom/base/test/test_bug435425.html430
-rw-r--r--dom/base/test/test_bug444322.html2588
-rw-r--r--dom/base/test/test_bug444546.html160
-rw-r--r--dom/base/test/test_bug444722.html65
-rw-r--r--dom/base/test/test_bug448993.html46
-rw-r--r--dom/base/test/test_bug450160.html100
-rw-r--r--dom/base/test/test_bug451376.html86
-rw-r--r--dom/base/test/test_bug453521.html36
-rw-r--r--dom/base/test/test_bug453736.html58
-rw-r--r--dom/base/test/test_bug454325.html147
-rw-r--r--dom/base/test/test_bug454326.html135
-rw-r--r--dom/base/test/test_bug455472.html41
-rw-r--r--dom/base/test/test_bug455629.html63
-rw-r--r--dom/base/test/test_bug456262.html39
-rw-r--r--dom/base/test/test_bug457746.html38
-rw-r--r--dom/base/test/test_bug459424.html31
-rw-r--r--dom/base/test/test_bug461555.html46
-rw-r--r--dom/base/test/test_bug461735.html50
-rw-r--r--dom/base/test/test_bug465767.html42
-rw-r--r--dom/base/test/test_bug466080.html150
-rw-r--r--dom/base/test/test_bug466409.html34
-rw-r--r--dom/base/test/test_bug466751.xhtml40
-rw-r--r--dom/base/test/test_bug469020.html128
-rw-r--r--dom/base/test/test_bug469304.html187
-rw-r--r--dom/base/test/test_bug473162-1.html30
-rw-r--r--dom/base/test/test_bug473162-2.html33
-rw-r--r--dom/base/test/test_bug475156.html301
-rw-r--r--dom/base/test/test_bug482935.html72
-rw-r--r--dom/base/test/test_bug484396.html48
-rw-r--r--dom/base/test/test_bug493881.html31
-rw-r--r--dom/base/test/test_bug493881.js100
-rw-r--r--dom/base/test/test_bug498240.html254
-rw-r--r--dom/base/test/test_bug498433.html104
-rw-r--r--dom/base/test/test_bug498897.html97
-rw-r--r--dom/base/test/test_bug499656.html57
-rw-r--r--dom/base/test/test_bug499656.xhtml57
-rw-r--r--dom/base/test/test_bug500937.html54
-rw-r--r--dom/base/test/test_bug503473.html37
-rw-r--r--dom/base/test/test_bug503481.html69
-rw-r--r--dom/base/test/test_bug503481b.html22
-rw-r--r--dom/base/test/test_bug51034.html42
-rw-r--r--dom/base/test/test_bug513194.html28
-rw-r--r--dom/base/test/test_bug5141.html30
-rw-r--r--dom/base/test/test_bug514487.html49
-rw-r--r--dom/base/test/test_bug515401.html141
-rw-r--r--dom/base/test/test_bug518104.html41
-rw-r--r--dom/base/test/test_bug527896.html61
-rw-r--r--dom/base/test/test_bug540854.html47
-rw-r--r--dom/base/test/test_bug541937.html118
-rw-r--r--dom/base/test/test_bug544642.html42
-rw-r--r--dom/base/test/test_bug545644.html42
-rw-r--r--dom/base/test/test_bug545644.xhtml49
-rw-r--r--dom/base/test/test_bug548463.html66
-rw-r--r--dom/base/test/test_bug553896.xhtml69
-rw-r--r--dom/base/test/test_bug557892.html34
-rw-r--r--dom/base/test/test_bug558726.html40
-rw-r--r--dom/base/test/test_bug559526.html93
-rw-r--r--dom/base/test/test_bug560780.html99
-rw-r--r--dom/base/test/test_bug562137.html32
-rw-r--r--dom/base/test/test_bug562169-1.html44
-rw-r--r--dom/base/test/test_bug562169-2.html29
-rw-r--r--dom/base/test/test_bug562652.html51
-rw-r--r--dom/base/test/test_bug564047.html31
-rw-r--r--dom/base/test/test_bug564863-2.xhtml159
-rw-r--r--dom/base/test/test_bug564863.xhtml305
-rw-r--r--dom/base/test/test_bug567350.html24
-rw-r--r--dom/base/test/test_bug574596.html94
-rw-r--r--dom/base/test/test_bug578096.html49
-rw-r--r--dom/base/test/test_bug585978.html38
-rw-r--r--dom/base/test/test_bug587931.html102
-rw-r--r--dom/base/test/test_bug588990.html332
-rw-r--r--dom/base/test/test_bug590812.html48
-rw-r--r--dom/base/test/test_bug590870.html44
-rw-r--r--dom/base/test/test_bug592366.html59
-rw-r--r--dom/base/test/test_bug592829.html40
-rw-r--r--dom/base/test/test_bug597345.html27
-rw-r--r--dom/base/test/test_bug599295.html47
-rw-r--r--dom/base/test/test_bug599588.html39
-rw-r--r--dom/base/test/test_bug601803.html35
-rw-r--r--dom/base/test/test_bug602838.html68
-rw-r--r--dom/base/test/test_bug604592.html37
-rw-r--r--dom/base/test/test_bug604660.html77
-rw-r--r--dom/base/test/test_bug605982.html34
-rw-r--r--dom/base/test/test_bug606729.html52
-rw-r--r--dom/base/test/test_bug614058.html29
-rw-r--r--dom/base/test/test_bug622088.html96
-rw-r--r--dom/base/test/test_bug622117.html49
-rw-r--r--dom/base/test/test_bug622246.html49
-rw-r--r--dom/base/test/test_bug625722.html39
-rw-r--r--dom/base/test/test_bug626262.html54
-rw-r--r--dom/base/test/test_bug628938.html239
-rw-r--r--dom/base/test/test_bug631615.html39
-rw-r--r--dom/base/test/test_bug638112.html46
-rw-r--r--dom/base/test/test_bug647518.html45
-rw-r--r--dom/base/test/test_bug650001.html30
-rw-r--r--dom/base/test/test_bug650776.html109
-rw-r--r--dom/base/test/test_bug650784.html37
-rw-r--r--dom/base/test/test_bug656283.html58
-rw-r--r--dom/base/test/test_bug664916.html39
-rw-r--r--dom/base/test/test_bug666604.html149
-rw-r--r--dom/base/test/test_bug675121.html45
-rw-r--r--dom/base/test/test_bug675166.html57
-rw-r--r--dom/base/test/test_bug682463.html156
-rw-r--r--dom/base/test/test_bug682554.html30
-rw-r--r--dom/base/test/test_bug682592.html178
-rw-r--r--dom/base/test/test_bug684671.html45
-rw-r--r--dom/base/test/test_bug685798.html45
-rw-r--r--dom/base/test/test_bug686449.xhtml79
-rw-r--r--dom/base/test/test_bug687859.html33
-rw-r--r--dom/base/test/test_bug690056.html54
-rw-r--r--dom/base/test/test_bug692434.html44
-rw-r--r--dom/base/test/test_bug693615.html41
-rw-r--r--dom/base/test/test_bug693875.html34
-rw-r--r--dom/base/test/test_bug694754.xhtml70
-rw-r--r--dom/base/test/test_bug696301-1.html78
-rw-r--r--dom/base/test/test_bug696301-2.html80
-rw-r--r--dom/base/test/test_bug698381.html55
-rw-r--r--dom/base/test/test_bug698384.html61
-rw-r--r--dom/base/test/test_bug704063.html56
-rw-r--r--dom/base/test/test_bug704320-1.html90
-rw-r--r--dom/base/test/test_bug704320-2.html90
-rw-r--r--dom/base/test/test_bug704320_policyset.html104
-rw-r--r--dom/base/test/test_bug704320_policyset2.html45
-rw-r--r--dom/base/test/test_bug704320_preload.html136
-rw-r--r--dom/base/test/test_bug707142.html51
-rw-r--r--dom/base/test/test_bug708620.html41
-rw-r--r--dom/base/test/test_bug711047.html16
-rw-r--r--dom/base/test/test_bug711180.html25
-rw-r--r--dom/base/test/test_bug719533.html27
-rw-r--r--dom/base/test/test_bug726364.html48
-rw-r--r--dom/base/test/test_bug737087.html37
-rw-r--r--dom/base/test/test_bug737565.html64
-rw-r--r--dom/base/test/test_bug737612.html29
-rw-r--r--dom/base/test/test_bug738108.html39
-rw-r--r--dom/base/test/test_bug744830.html132
-rw-r--r--dom/base/test/test_bug749367.html29
-rw-r--r--dom/base/test/test_bug750096.html44
-rw-r--r--dom/base/test/test_bug753278.html46
-rw-r--r--dom/base/test/test_bug761120.html41
-rw-r--r--dom/base/test/test_bug769117.html55
-rw-r--r--dom/base/test/test_bug782342.html85
-rw-r--r--dom/base/test/test_bug787778.html25
-rw-r--r--dom/base/test/test_bug789315.html49
-rw-r--r--dom/base/test/test_bug789856.html42
-rw-r--r--dom/base/test/test_bug809003.html47
-rw-r--r--dom/base/test/test_bug810494.html46
-rw-r--r--dom/base/test/test_bug811701.html48
-rw-r--r--dom/base/test/test_bug811701.xhtml52
-rw-r--r--dom/base/test/test_bug813919.html46
-rw-r--r--dom/base/test/test_bug814576.html41
-rw-r--r--dom/base/test/test_bug819051.html59
-rw-r--r--dom/base/test/test_bug820909.html87
-rw-r--r--dom/base/test/test_bug864595.html34
-rw-r--r--dom/base/test/test_bug868999.html39
-rw-r--r--dom/base/test/test_bug869000.html37
-rw-r--r--dom/base/test/test_bug869002.html32
-rw-r--r--dom/base/test/test_bug869006.html37
-rw-r--r--dom/base/test/test_bug876282.html45
-rw-r--r--dom/base/test/test_bug891952.html61
-rw-r--r--dom/base/test/test_bug894874.html45
-rw-r--r--dom/base/test/test_bug895974.html69
-rw-r--r--dom/base/test/test_bug907892.html49
-rw-r--r--dom/base/test/test_bug913761.html40
-rw-r--r--dom/base/test/test_bug922681.html113
-rw-r--r--dom/base/test/test_bug927196.html56
-rw-r--r--dom/base/test/test_bug945152.html58
-rw-r--r--dom/base/test/test_bug962251.html244
-rw-r--r--dom/base/test/test_bug976673.html105
-rw-r--r--dom/base/test/test_bug982153.html29
-rw-r--r--dom/base/test/test_bug999456.html32
-rw-r--r--dom/base/test/test_caretPositionFromPoint.html131
-rw-r--r--dom/base/test/test_change_policy.html134
-rw-r--r--dom/base/test/test_clearTimeoutIntervalNoArg.html14
-rw-r--r--dom/base/test/test_clipboard_nbsp.html116
-rw-r--r--dom/base/test/test_constructor-assignment.html61
-rw-r--r--dom/base/test/test_constructor.html61
-rw-r--r--dom/base/test/test_content_iterator_post_order.html875
-rw-r--r--dom/base/test/test_content_iterator_pre_order.html869
-rw-r--r--dom/base/test/test_content_iterator_subtree.html690
-rw-r--r--dom/base/test/test_copyimage.html87
-rw-r--r--dom/base/test/test_copypaste.html125
-rw-r--r--dom/base/test/test_copypaste.xhtml112
-rw-r--r--dom/base/test/test_copypaste_disabled.html116
-rw-r--r--dom/base/test/test_createHTMLDocument.html52
-rw-r--r--dom/base/test/test_current_inner_window.html61
-rw-r--r--dom/base/test/test_custom_element.html29
-rw-r--r--dom/base/test/test_custom_element_reflector.html27
-rw-r--r--dom/base/test/test_data_uri.html189
-rw-r--r--dom/base/test/test_delazification_strategy.html175
-rw-r--r--dom/base/test/test_document.all_iteration.html11
-rw-r--r--dom/base/test/test_document.all_unqualified.html35
-rw-r--r--dom/base/test/test_document_constructor.html31
-rw-r--r--dom/base/test/test_document_importNode_document.html32
-rw-r--r--dom/base/test/test_document_wireframe.html354
-rw-r--r--dom/base/test/test_domparser_null_char.html27
-rw-r--r--dom/base/test/test_domparsing.html84
-rw-r--r--dom/base/test/test_domrequest.html233
-rw-r--r--dom/base/test/test_domrequesthelper.xhtml547
-rw-r--r--dom/base/test/test_domwindowutils.html128
-rw-r--r--dom/base/test/test_element.matches.html28
-rw-r--r--dom/base/test/test_elementTraversal.html111
-rw-r--r--dom/base/test/test_element_closest.html84
-rw-r--r--dom/base/test/test_embed_xorigin_document.html103
-rw-r--r--dom/base/test/test_encodeToStringWithMaxLength.html60
-rw-r--r--dom/base/test/test_encodeToStringWithRequiresReinitAfterOutput.html87
-rw-r--r--dom/base/test/test_eventsource_event_listener_leaks.html40
-rw-r--r--dom/base/test/test_eventsourceservice_basic.html69
-rw-r--r--dom/base/test/test_eventsourceservice_reconnect_error.html72
-rw-r--r--dom/base/test/test_eventsourceservice_status_error.html67
-rw-r--r--dom/base/test/test_eventsourceservice_worker.html61
-rw-r--r--dom/base/test/test_explicit_user_agent.html64
-rw-r--r--dom/base/test/test_find.html204
-rw-r--r--dom/base/test/test_find_bug1601118.html61
-rw-r--r--dom/base/test/test_find_bug1654683.html30
-rw-r--r--dom/base/test/test_find_nac.html13
-rw-r--r--dom/base/test/test_focus_design_mode.html62
-rw-r--r--dom/base/test/test_focus_display_none_xorigin_iframe.html134
-rw-r--r--dom/base/test/test_focus_keyboard_event.html48
-rw-r--r--dom/base/test/test_focus_scroll_padding_tab.html74
-rw-r--r--dom/base/test/test_focus_scrollable_fieldset.html60
-rw-r--r--dom/base/test/test_focus_scrollable_input.html56
-rw-r--r--dom/base/test/test_focus_shadow_dom.html36
-rw-r--r--dom/base/test/test_focus_shadow_dom_root.html41
-rw-r--r--dom/base/test/test_fragment_sanitization.xhtml98
-rw-r--r--dom/base/test/test_getAttribute_after_createAttribute.html15
-rw-r--r--dom/base/test/test_getElementById.html58
-rw-r--r--dom/base/test/test_getLastOverWindowPointerLocationInCSSPixels.html83
-rw-r--r--dom/base/test/test_getTranslationNodes.html225
-rw-r--r--dom/base/test/test_getTranslationNodes_limit.html31
-rw-r--r--dom/base/test/test_gsp-qualified.html38
-rw-r--r--dom/base/test/test_gsp-quirks.html27
-rw-r--r--dom/base/test/test_gsp-standards.html27
-rw-r--r--dom/base/test/test_history_document_open.html37
-rw-r--r--dom/base/test/test_history_state_null.html25
-rw-r--r--dom/base/test/test_html_colors_quirks.html711
-rw-r--r--dom/base/test/test_html_colors_standards.html712
-rw-r--r--dom/base/test/test_htmlcopyencoder.html195
-rw-r--r--dom/base/test/test_htmlcopyencoder.xhtml179
-rw-r--r--dom/base/test/test_iframe_event_listener_leaks.html41
-rw-r--r--dom/base/test/test_iframe_referrer.html107
-rw-r--r--dom/base/test/test_iframe_referrer_changing.html50
-rw-r--r--dom/base/test/test_iframe_referrer_invalid.html81
-rw-r--r--dom/base/test/test_innersize_scrollport.html38
-rw-r--r--dom/base/test/test_input_vsync_alignment_inner_event_loop.html49
-rw-r--r--dom/base/test/test_input_vsync_alignment_input_while_vsync.html47
-rw-r--r--dom/base/test/test_input_vsync_alignment_lower_than_normal.html51
-rw-r--r--dom/base/test/test_integer_attr_with_leading_zero.html64
-rw-r--r--dom/base/test/test_intersectionobservers.html1221
-rw-r--r--dom/base/test/test_link_prefetch.html220
-rw-r--r--dom/base/test/test_link_preload.html220
-rw-r--r--dom/base/test/test_link_stylesheet.html221
-rw-r--r--dom/base/test/test_location_href_unknown_protocol.html27
-rw-r--r--dom/base/test/test_lock_orientation_after_fullscreen.html58
-rw-r--r--dom/base/test/test_lock_orientation_with_pending_fullscreen.html126
-rw-r--r--dom/base/test/test_messagePort.html114
-rw-r--r--dom/base/test/test_messagemanager_send_principal.html108
-rw-r--r--dom/base/test/test_meta_refresh_referrer.html98
-rw-r--r--dom/base/test/test_mozMatchesSelector.html14
-rw-r--r--dom/base/test/test_mutationobservers.html862
-rw-r--r--dom/base/test/test_named_frames.html38
-rw-r--r--dom/base/test/test_navigatorPrefOverride.html54
-rw-r--r--dom/base/test/test_navigator_cookieEnabled.html124
-rw-r--r--dom/base/test/test_navigator_hardwareConcurrency.html42
-rw-r--r--dom/base/test/test_navigator_language.html213
-rw-r--r--dom/base/test/test_navigator_resolve_identity_xrays.xhtml39
-rw-r--r--dom/base/test/test_nested_event_loop_spin_and_idle_tasks.html34
-rw-r--r--dom/base/test/test_nodelist_holes.html42
-rw-r--r--dom/base/test/test_openDialogChromeOnly.html38
-rw-r--r--dom/base/test/test_open_null_features.html54
-rw-r--r--dom/base/test/test_pasting_svg_image.html99
-rw-r--r--dom/base/test/test_pdf_print.html62
-rw-r--r--dom/base/test/test_plugin_freezing.html68
-rw-r--r--dom/base/test/test_postMessage_originAttributes.html53
-rw-r--r--dom/base/test/test_postMessage_solidus.html93
-rw-r--r--dom/base/test/test_postMessages_broadcastChannel.html167
-rw-r--r--dom/base/test/test_postMessages_messagePort.html114
-rw-r--r--dom/base/test/test_postMessages_window.html124
-rw-r--r--dom/base/test/test_postMessages_workers.html111
-rw-r--r--dom/base/test/test_processing_instruction_update_stylesheet.xhtml46
-rw-r--r--dom/base/test/test_progress_events_for_gzip_data.html44
-rw-r--r--dom/base/test/test_pushState_structuredclone.html51
-rw-r--r--dom/base/test/test_range_bounds.html305
-rw-r--r--dom/base/test/test_reentrant_flush.html60
-rw-r--r--dom/base/test/test_root_iframe.html27
-rw-r--r--dom/base/test/test_sandbox_and_document_uri.html31
-rw-r--r--dom/base/test/test_sandboxed_blob_uri.html22
-rw-r--r--dom/base/test/test_sanitize_xhr.html35
-rw-r--r--dom/base/test/test_screen_orientation.html86
-rw-r--r--dom/base/test/test_script_loader_crossorigin_data_url.html38
-rw-r--r--dom/base/test/test_script_loader_js_cache.html264
-rw-r--r--dom/base/test/test_script_loader_js_cache_frames.html202
-rw-r--r--dom/base/test/test_script_loader_js_cache_module.html537
-rw-r--r--dom/base/test/test_script_loader_js_cache_module_sri.html425
-rw-r--r--dom/base/test/test_sendQueryContentAndSelectionSetEvent.html253
-rw-r--r--dom/base/test/test_sendSelectionSetEvent_with_same_range.html100
-rw-r--r--dom/base/test/test_serializer_noscript.html38
-rw-r--r--dom/base/test/test_setInterval_from_start.html57
-rw-r--r--dom/base/test/test_setInterval_uncatchable_exception.html55
-rw-r--r--dom/base/test/test_setTimeoutWith0.html22
-rw-r--r--dom/base/test/test_settimeout_extra_arguments.html12
-rw-r--r--dom/base/test/test_settimeout_inner.html59
-rw-r--r--dom/base/test/test_setting_opener.html125
-rw-r--r--dom/base/test/test_shared_compartment1.html77
-rw-r--r--dom/base/test/test_shared_compartment2.html47
-rw-r--r--dom/base/test/test_structuredclone_backref.html32
-rw-r--r--dom/base/test/test_structuredclone_error.html23
-rw-r--r--dom/base/test/test_style_cssText.html85
-rw-r--r--dom/base/test/test_suppressed_events_and_scrolling.html47
-rw-r--r--dom/base/test/test_suppressed_events_nested_iframe.html71
-rw-r--r--dom/base/test/test_suppressed_microtasks.html62
-rw-r--r--dom/base/test/test_text_wholeText.html232
-rw-r--r--dom/base/test/test_textnode_normalize_in_selection.html201
-rw-r--r--dom/base/test/test_textnode_split_in_selection.html221
-rw-r--r--dom/base/test/test_timeout_clamp.html163
-rw-r--r--dom/base/test/test_timer_flood.html118
-rw-r--r--dom/base/test/test_title.html52
-rw-r--r--dom/base/test/test_toScreenRect.html9
-rw-r--r--dom/base/test/test_treewalker_nextsibling.xml96
-rw-r--r--dom/base/test/test_urgent_start.html269
-rw-r--r--dom/base/test/test_user_select.html357
-rw-r--r--dom/base/test/test_viewport_metrics_on_landscape_content.html20
-rw-r--r--dom/base/test/test_viewport_scroll.html89
-rw-r--r--dom/base/test/test_viewsource_forbidden_in_object.html62
-rw-r--r--dom/base/test/test_w3element_traversal.html148
-rw-r--r--dom/base/test/test_w3element_traversal.xhtml149
-rw-r--r--dom/base/test/test_w3element_traversal_svg.html107
-rw-r--r--dom/base/test/test_warning_for_blocked_cross_site_request.html129
-rw-r--r--dom/base/test/test_window_close.html93
-rw-r--r--dom/base/test/test_window_constructor.html36
-rw-r--r--dom/base/test/test_window_content.html28
-rw-r--r--dom/base/test/test_window_cross_origin_props.html101
-rw-r--r--dom/base/test/test_window_define_nonconfigurable.html115
-rw-r--r--dom/base/test/test_window_define_symbol.html29
-rw-r--r--dom/base/test/test_window_element_enumeration.html70
-rw-r--r--dom/base/test/test_window_enumeration.html33
-rw-r--r--dom/base/test/test_window_extensible.html46
-rw-r--r--dom/base/test/test_window_focus_by_close_and_open.html34
-rw-r--r--dom/base/test/test_window_indexing.html139
-rw-r--r--dom/base/test/test_window_keys.html28
-rw-r--r--dom/base/test/test_window_named_frame_enumeration.html96
-rw-r--r--dom/base/test/test_window_own_props.html29
-rw-r--r--dom/base/test/test_window_proto.html17
-rw-r--r--dom/base/test/test_writable-replaceable.html49
-rw-r--r--dom/base/test/test_x-frame-options.html195
-rw-r--r--dom/base/test/test_youtube_flash_embed.html32
-rw-r--r--dom/base/test/unit/1_original.xml3
-rw-r--r--dom/base/test/unit/1_result.xml3
-rw-r--r--dom/base/test/unit/2_original.xml13
-rw-r--r--dom/base/test/unit/2_result_1.xml13
-rw-r--r--dom/base/test/unit/2_result_2.xml14
-rw-r--r--dom/base/test/unit/2_result_3.xml23
-rw-r--r--dom/base/test/unit/2_result_4.xml21
-rw-r--r--dom/base/test/unit/3_original.xml4
-rw-r--r--dom/base/test/unit/3_result.xml7
-rw-r--r--dom/base/test/unit/3_result_2.xml7
-rw-r--r--dom/base/test/unit/4_original.xml32
-rw-r--r--dom/base/test/unit/4_result_1.xml32
-rw-r--r--dom/base/test/unit/4_result_2.xml7
-rw-r--r--dom/base/test/unit/4_result_3.xml4
-rw-r--r--dom/base/test/unit/4_result_4.xml4
-rw-r--r--dom/base/test/unit/4_result_5.xml46
-rw-r--r--dom/base/test/unit/4_result_6.xml48
-rw-r--r--dom/base/test/unit/empty_document.xml3
-rw-r--r--dom/base/test/unit/head_utilities.js69
-rw-r--r--dom/base/test/unit/head_xml.js152
-rw-r--r--dom/base/test/unit/isequalnode_data.xml150
-rw-r--r--dom/base/test/unit/nodelist_data_1.xml58
-rw-r--r--dom/base/test/unit/nodelist_data_2.xhtml45
-rw-r--r--dom/base/test/unit/test_blockParsing.js113
-rw-r--r--dom/base/test/unit/test_bug553888.js57
-rw-r--r--dom/base/test/unit/test_bug737966.js16
-rw-r--r--dom/base/test/unit/test_cancelPrefetch.js149
-rw-r--r--dom/base/test/unit/test_chromeutils_base64.js140
-rw-r--r--dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js40
-rw-r--r--dom/base/test/unit/test_chromeutils_shallowclone.js60
-rw-r--r--dom/base/test/unit/test_delete_range.xml125
-rw-r--r--dom/base/test/unit/test_error_codes.js64
-rw-r--r--dom/base/test/unit/test_generate_xpath.js85
-rw-r--r--dom/base/test/unit/test_htmlserializer.js70
-rw-r--r--dom/base/test/unit/test_isequalnode.js390
-rw-r--r--dom/base/test/unit/test_js_dev_error_interceptor.js53
-rw-r--r--dom/base/test/unit/test_nodelist.js345
-rw-r--r--dom/base/test/unit/test_normalize.js100
-rw-r--r--dom/base/test/unit/test_range.js465
-rw-r--r--dom/base/test/unit/test_serializers_entities.js99
-rw-r--r--dom/base/test/unit/test_serializers_entities_in_attr.js108
-rw-r--r--dom/base/test/unit/test_structuredcloneholder.js159
-rw-r--r--dom/base/test/unit/test_thirdpartyutil.js99
-rw-r--r--dom/base/test/unit/test_treewalker.js23
-rw-r--r--dom/base/test/unit/test_xhr_document.js45
-rw-r--r--dom/base/test/unit/test_xhr_origin_attributes.js53
-rw-r--r--dom/base/test/unit/test_xhr_standalone.js19
-rw-r--r--dom/base/test/unit/test_xml_parser.js48
-rw-r--r--dom/base/test/unit/test_xml_serializer.js421
-rw-r--r--dom/base/test/unit/test_xmlserializer.js179
-rw-r--r--dom/base/test/unit/xpcshell.ini70
-rw-r--r--dom/base/test/unit_ipc/test_bug553888_wrap.js3
-rw-r--r--dom/base/test/unit_ipc/test_xhr_document_ipc.js3
-rw-r--r--dom/base/test/unit_ipc/xpcshell.ini11
-rw-r--r--dom/base/test/useractivation/file_clipboard_common.js505
-rw-r--r--dom/base/test/useractivation/file_empty.html0
-rw-r--r--dom/base/test/useractivation/file_iframe_check_user_activation.html22
-rw-r--r--dom/base/test/useractivation/file_iframe_consume_user_activation.html15
-rw-r--r--dom/base/test/useractivation/file_iframe_user_activated.html14
-rw-r--r--dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html20
-rw-r--r--dom/base/test/useractivation/mochitest.ini23
-rw-r--r--dom/base/test/useractivation/moz.build9
-rw-r--r--dom/base/test/useractivation/test_clipboard_editor.html31
-rw-r--r--dom/base/test/useractivation/test_clipboard_noeditor.html29
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_async_callback.html83
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_mouse_event.html98
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_pointer_event.html122
-rw-r--r--dom/base/test/useractivation/test_useractivation_has_been_activated.html115
-rw-r--r--dom/base/test/useractivation/test_useractivation_key_events.html91
-rw-r--r--dom/base/test/useractivation/test_useractivation_sandbox_transient.html90
-rw-r--r--dom/base/test/useractivation/test_useractivation_scrollbar.html135
-rw-r--r--dom/base/test/useractivation/test_useractivation_transient.html155
-rw-r--r--dom/base/test/useractivation/test_useractivation_transient_consuming.html151
-rw-r--r--dom/base/test/variable_style_sheet.sjs18
-rw-r--r--dom/base/test/w3element_traversal.svg70
-rw-r--r--dom/base/test/wholeTexty-helper.xml6
-rw-r--r--dom/base/test/worker_postMessages.js73
-rw-r--r--dom/base/usecounters.py89
2173 files changed, 310829 insertions, 0 deletions
diff --git a/dom/base/AbstractRange.cpp b/dom/base/AbstractRange.cpp
new file mode 100644
index 0000000000..791d00c029
--- /dev/null
+++ b/dom/base/AbstractRange.cpp
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/AbstractRange.h"
+#include "mozilla/dom/AbstractRangeBinding.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/dom/Selection.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGkAtoms.h"
+#include "nsINode.h"
+#include "nsRange.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ nsRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
+ nsRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ nsRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, nsRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ StaticRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
+ StaticRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ StaticRange* aRange);
+template nsresult AbstractRange::SetStartAndEndInternal(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, StaticRange* aRange);
+template bool AbstractRange::MaybeCacheToReuse(nsRange& aInstance);
+template bool AbstractRange::MaybeCacheToReuse(StaticRange& aInstance);
+
+bool AbstractRange::sHasShutDown = false;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractRange)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractRange)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractRange)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractRange)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
+ // mStart and mEnd may depend on or be depended on some other members in
+ // concrete classes so that they should be unlinked in sub classes.
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mSelections.Clear();
+ // Unregistering of the common inclusive ancestors would by design
+ // also happen when the actual implementations unlink `mStart`/`mEnd`.
+ // This may introduce additional overhead which is not needed when unlinking,
+ // therefore this is done here beforehand.
+ if (tmp->mRegisteredClosestCommonInclusiveAncestor) {
+ tmp->UnregisterClosestCommonInclusiveAncestor(
+ tmp->mRegisteredClosestCommonInclusiveAncestor, true);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!tmp->isInList(),
+ "Shouldn't be registered now that we're unlinking");
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStart)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEnd)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegisteredClosestCommonInclusiveAncestor)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void AbstractRange::MarkDescendants(const nsINode& aNode) {
+ // Set NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection on
+ // aNode's descendants unless aNode is already marked as a range common
+ // ancestor or a descendant of one, in which case all of our descendants have
+ // the bit set already.
+ if (!aNode.IsMaybeSelected()) {
+ // don't set the Descendant bit on |aNode| itself
+ nsINode* node = aNode.GetNextNode(&aNode);
+ while (node) {
+ node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ node = node->GetNextNode(&aNode);
+ } else {
+ // optimize: skip this sub-tree since it's marked already.
+ node = node->GetNextNonChildNode(&aNode);
+ }
+ }
+ }
+}
+
+void AbstractRange::UnmarkDescendants(const nsINode& aNode) {
+ // Unset NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection
+ // on aNode's descendants unless aNode is a descendant of another range common
+ // ancestor. Also, exclude descendants of range common ancestors (but not the
+ // common ancestor itself).
+ if (!aNode
+ .IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ // we know |aNode| doesn't have any bit set
+ nsINode* node = aNode.GetNextNode(&aNode);
+ while (node) {
+ node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ node = node->GetNextNode(&aNode);
+ } else {
+ // We found an ancestor of an overlapping range, skip its descendants.
+ node = node->GetNextNonChildNode(&aNode);
+ }
+ }
+ }
+}
+
+// NOTE: If you need to change default value of members of AbstractRange,
+// update nsRange::Create(nsINode* aNode) and ClearForReuse() too.
+AbstractRange::AbstractRange(nsINode* aNode, bool aIsDynamicRange)
+ : mRegisteredClosestCommonInclusiveAncestor(nullptr),
+ mIsPositioned(false),
+ mIsGenerated(false),
+ mCalledByJS(false),
+ mIsDynamicRange(aIsDynamicRange) {
+ Init(aNode);
+}
+
+AbstractRange::~AbstractRange() = default;
+
+void AbstractRange::Init(nsINode* aNode) {
+ MOZ_ASSERT(aNode, "range isn't in a document!");
+ mOwner = aNode->OwnerDoc();
+}
+
+// static
+void AbstractRange::Shutdown() {
+ sHasShutDown = true;
+ if (nsTArray<RefPtr<nsRange>>* cachedRanges = nsRange::sCachedRanges) {
+ nsRange::sCachedRanges = nullptr;
+ cachedRanges->Clear();
+ delete cachedRanges;
+ }
+ if (nsTArray<RefPtr<StaticRange>>* cachedRanges =
+ StaticRange::sCachedRanges) {
+ StaticRange::sCachedRanges = nullptr;
+ cachedRanges->Clear();
+ delete cachedRanges;
+ }
+}
+
+// static
+template <class RangeType>
+bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
+ static const size_t kMaxRangeCache = 64;
+
+ // If the instance is not used by JS and the cache is not yet full, we
+ // should reuse it. Otherwise, delete it.
+ if (sHasShutDown || aInstance.GetWrapperMaybeDead() || aInstance.GetFlags() ||
+ (RangeType::sCachedRanges &&
+ RangeType::sCachedRanges->Length() == kMaxRangeCache)) {
+ return false;
+ }
+
+ aInstance.ClearForReuse();
+
+ if (!RangeType::sCachedRanges) {
+ RangeType::sCachedRanges = new nsTArray<RefPtr<RangeType>>(16);
+ }
+ RangeType::sCachedRanges->AppendElement(&aInstance);
+ return true;
+}
+
+nsINode* AbstractRange::GetClosestCommonInclusiveAncestor() const {
+ return mIsPositioned ? nsContentUtils::GetClosestCommonInclusiveAncestor(
+ mStart.Container(), mEnd.Container())
+ : nullptr;
+}
+
+// static
+template <typename SPT, typename SRT, typename EPT, typename ERT,
+ typename RangeType>
+nsresult AbstractRange::SetStartAndEndInternal(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange) {
+ if (NS_WARN_IF(!aStartBoundary.IsSet()) ||
+ NS_WARN_IF(!aEndBoundary.IsSet())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsINode* newStartRoot =
+ RangeUtils::ComputeRootNode(aStartBoundary.Container());
+ if (!newStartRoot) {
+ return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+ }
+ if (!aStartBoundary.IsSetAndValid()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ if (aStartBoundary.Container() == aEndBoundary.Container()) {
+ if (!aEndBoundary.IsSetAndValid()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+ // XXX: Offsets - handle this more efficiently.
+ // If the end offset is less than the start offset, this should be
+ // collapsed at the end offset.
+ if (*aStartBoundary.Offset(
+ RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOffsets) >
+ *aEndBoundary.Offset(
+ RangeBoundaryBase<EPT, ERT>::OffsetFilter::kValidOffsets)) {
+ aRange->DoSetRange(aEndBoundary, aEndBoundary, newStartRoot);
+ } else {
+ aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
+ }
+ return NS_OK;
+ }
+
+ nsINode* newEndRoot = RangeUtils::ComputeRootNode(aEndBoundary.Container());
+ if (!newEndRoot) {
+ return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+ }
+ if (!aEndBoundary.IsSetAndValid()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ // If they have different root, this should be collapsed at the end point.
+ if (newStartRoot != newEndRoot) {
+ aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
+ return NS_OK;
+ }
+
+ const Maybe<int32_t> pointOrder =
+ nsContentUtils::ComparePoints(aStartBoundary, aEndBoundary);
+ if (!pointOrder) {
+ // Safely return a value but also detected this in debug builds.
+ MOZ_ASSERT_UNREACHABLE();
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // If the end point is before the start point, this should be collapsed at
+ // the end point.
+ if (*pointOrder == 1) {
+ aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
+ return NS_OK;
+ }
+
+ // Otherwise, set the range as specified.
+ aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
+ return NS_OK;
+}
+
+bool AbstractRange::IsInSelection(const Selection& aSelection) const {
+ return mSelections.Contains(&aSelection);
+}
+
+void AbstractRange::RegisterSelection(Selection& aSelection) {
+ if (IsInSelection(aSelection)) {
+ return;
+ }
+ bool isFirstSelection = mSelections.IsEmpty();
+ mSelections.AppendElement(&aSelection);
+ if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor) {
+ nsINode* commonAncestor = GetClosestCommonInclusiveAncestor();
+ MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
+ RegisterClosestCommonInclusiveAncestor(commonAncestor);
+ }
+}
+
+const nsTArray<WeakPtr<Selection>>& AbstractRange::GetSelections() const {
+ return mSelections;
+}
+
+void AbstractRange::UnregisterSelection(const Selection& aSelection) {
+ mSelections.RemoveElement(&aSelection);
+ if (mSelections.IsEmpty() && mRegisteredClosestCommonInclusiveAncestor) {
+ UnregisterClosestCommonInclusiveAncestor(
+ mRegisteredClosestCommonInclusiveAncestor, false);
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mRegisteredClosestCommonInclusiveAncestor,
+ "How can we have a registered common ancestor when we "
+ "just unregistered?");
+ MOZ_DIAGNOSTIC_ASSERT(
+ !isInList(),
+ "Shouldn't be registered if we have no "
+ "mRegisteredClosestCommonInclusiveAncestor after unregistering");
+ }
+}
+
+void AbstractRange::RegisterClosestCommonInclusiveAncestor(nsINode* aNode) {
+ MOZ_ASSERT(aNode, "bad arg");
+
+ MOZ_DIAGNOSTIC_ASSERT(IsInAnySelection(),
+ "registering range not in selection");
+
+ mRegisteredClosestCommonInclusiveAncestor = aNode;
+
+ MarkDescendants(*aNode);
+
+ UniquePtr<LinkedList<AbstractRange>>& ranges =
+ aNode->GetClosestCommonInclusiveAncestorRangesPtr();
+ if (!ranges) {
+ ranges = MakeUnique<LinkedList<AbstractRange>>();
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!isInList());
+ ranges->insertBack(this);
+ aNode->SetClosestCommonInclusiveAncestorForRangeInSelection();
+}
+
+void AbstractRange::UnregisterClosestCommonInclusiveAncestor(
+ nsINode* aNode, bool aIsUnlinking) {
+ MOZ_ASSERT(aNode, "bad arg");
+ NS_ASSERTION(aNode->IsClosestCommonInclusiveAncestorForRangeInSelection(),
+ "wrong node");
+ MOZ_DIAGNOSTIC_ASSERT(aNode == mRegisteredClosestCommonInclusiveAncestor,
+ "wrong node");
+ LinkedList<AbstractRange>* ranges =
+ aNode->GetExistingClosestCommonInclusiveAncestorRanges();
+ MOZ_ASSERT(ranges);
+
+ mRegisteredClosestCommonInclusiveAncestor = nullptr;
+
+#ifdef DEBUG
+ bool found = false;
+ for (AbstractRange* range : *ranges) {
+ if (range == this) {
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found,
+ "We should be in the list on our registered common ancestor");
+#endif // DEBUG
+
+ remove();
+
+ // We don't want to waste time unmarking flags on nodes that are
+ // being unlinked anyway.
+ if (!aIsUnlinking && ranges->isEmpty()) {
+ aNode->ClearClosestCommonInclusiveAncestorForRangeInSelection();
+ UnmarkDescendants(*aNode);
+ }
+}
+
+void AbstractRange::UpdateCommonAncestorIfNecessary() {
+ nsINode* oldCommonAncestor = mRegisteredClosestCommonInclusiveAncestor;
+ nsINode* newCommonAncestor = GetClosestCommonInclusiveAncestor();
+ if (newCommonAncestor != oldCommonAncestor) {
+ if (oldCommonAncestor) {
+ UnregisterClosestCommonInclusiveAncestor(oldCommonAncestor, false);
+ }
+ if (newCommonAncestor) {
+ RegisterClosestCommonInclusiveAncestor(newCommonAncestor);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsPositioned, "unexpected disconnected nodes");
+ mSelections.Clear();
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mRegisteredClosestCommonInclusiveAncestor,
+ "How can we have a registered common ancestor when we "
+ "didn't register ourselves?");
+ MOZ_DIAGNOSTIC_ASSERT(!isInList(),
+ "Shouldn't be registered if we have no "
+ "mRegisteredClosestCommonInclusiveAncestor");
+ }
+ }
+}
+
+nsINode* AbstractRange::GetParentObject() const { return mOwner; }
+
+JSObject* AbstractRange::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_CRASH("Must be overridden");
+}
+
+void AbstractRange::ClearForReuse() {
+ mOwner = nullptr;
+ mStart = RangeBoundary();
+ mEnd = RangeBoundary();
+ mIsPositioned = false;
+ mIsGenerated = false;
+ mCalledByJS = false;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/AbstractRange.h b/dom/base/AbstractRange.h
new file mode 100644
index 0000000000..c70aaf19ec
--- /dev/null
+++ b/dom/base/AbstractRange.h
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AbstractRange_h
+#define mozilla_dom_AbstractRange_h
+
+#include <cstdint>
+#include <ostream>
+#include "ErrorList.h"
+#include "js/RootingAPI.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIContent;
+class nsINode;
+class nsRange;
+struct JSContext;
+
+namespace mozilla::dom {
+class Document;
+class Selection;
+class StaticRange;
+
+class AbstractRange : public nsISupports,
+ public nsWrapperCache,
+ // For linking together selection-associated ranges.
+ public mozilla::LinkedListElement<AbstractRange> {
+ protected:
+ explicit AbstractRange(nsINode* aNode, bool aIsDynamicRange);
+ virtual ~AbstractRange();
+
+ public:
+ AbstractRange() = delete;
+ explicit AbstractRange(const AbstractRange& aOther) = delete;
+
+ /**
+ * Called when the process is shutting down.
+ */
+ static void Shutdown();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange)
+
+ const RangeBoundary& StartRef() const { return mStart; }
+ const RangeBoundary& EndRef() const { return mEnd; }
+
+ nsIContent* GetChildAtStartOffset() const {
+ return mStart.GetChildAtOffset();
+ }
+ nsIContent* GetChildAtEndOffset() const { return mEnd.GetChildAtOffset(); }
+ bool IsPositioned() const { return mIsPositioned; }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ nsINode* GetClosestCommonInclusiveAncestor() const;
+
+ // WebIDL
+
+ // If Range is created from JS, it's initialized with Document.createRange()
+ // and it collaps the range to start of the Document. Therefore, the
+ // following WebIDL methods are called only when `mIsPositioned` is true.
+ // So, it does not make sense to take `ErrorResult` as their parameter
+ // since its destruction cost may appear in profile. If you create range
+ // object from C++ and needs to check whether it's positioned, should call
+ // `IsPositioned()` directly.
+
+ nsINode* GetStartContainer() const { return mStart.Container(); }
+ nsINode* GetEndContainer() const { return mEnd.Container(); }
+
+ Document* GetComposedDocOfContainers() const {
+ return mStart.Container() ? mStart.Container()->GetComposedDoc() : nullptr;
+ }
+
+ // FYI: Returns 0 if it's not positioned.
+ uint32_t StartOffset() const {
+ return static_cast<uint32_t>(
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
+ }
+
+ // FYI: Returns 0 if it's not positioned.
+ uint32_t EndOffset() const {
+ return static_cast<uint32_t>(
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
+ }
+ bool Collapsed() const {
+ return !mIsPositioned || (mStart.Container() == mEnd.Container() &&
+ StartOffset() == EndOffset());
+ }
+
+ nsINode* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasEqualBoundaries(const AbstractRange& aOther) const {
+ return (mStart == aOther.mStart) && (mEnd == aOther.mEnd);
+ }
+ bool IsDynamicRange() const { return mIsDynamicRange; }
+ bool IsStaticRange() const { return !mIsDynamicRange; }
+ inline nsRange* AsDynamicRange();
+ inline const nsRange* AsDynamicRange() const;
+ inline StaticRange* AsStaticRange();
+ inline const StaticRange* AsStaticRange() const;
+
+ /**
+ * Return true if this range is part of a Selection object
+ * and isn't detached.
+ */
+ bool IsInAnySelection() const { return !mSelections.IsEmpty(); }
+
+ MOZ_CAN_RUN_SCRIPT void RegisterSelection(
+ mozilla::dom::Selection& aSelection);
+
+ void UnregisterSelection(const mozilla::dom::Selection& aSelection);
+
+ /**
+ * Returns a list of all Selections the range is associated with.
+ */
+ const nsTArray<WeakPtr<Selection>>& GetSelections() const;
+
+ /**
+ * Return true if this range is in |aSelection|.
+ */
+ bool IsInSelection(const mozilla::dom::Selection& aSelection) const;
+
+ protected:
+ template <typename SPT, typename SRT, typename EPT, typename ERT,
+ typename RangeType>
+ static nsresult SetStartAndEndInternal(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange);
+
+ template <class RangeType>
+ static bool MaybeCacheToReuse(RangeType& aInstance);
+
+ void Init(nsINode* aNode);
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const AbstractRange& aRange) {
+ if (aRange.Collapsed()) {
+ aStream << "{ mStart=mEnd=" << aRange.mStart;
+ } else {
+ aStream << "{ mStart=" << aRange.mStart << ", mEnd=" << aRange.mEnd;
+ }
+ return aStream << ", mIsGenerated="
+ << (aRange.mIsGenerated ? "true" : "false")
+ << ", mCalledByJS="
+ << (aRange.mIsPositioned ? "true" : "false")
+ << ", mIsDynamicRange="
+ << (aRange.mIsDynamicRange ? "true" : "false") << " }";
+ }
+
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void RegisterClosestCommonInclusiveAncestor(nsINode* aNode);
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void UnregisterClosestCommonInclusiveAncestor(nsINode* aNode,
+ bool aIsUnlinking);
+
+ void UpdateCommonAncestorIfNecessary();
+
+ static void MarkDescendants(const nsINode& aNode);
+ static void UnmarkDescendants(const nsINode& aNode);
+
+ private:
+ void ClearForReuse();
+
+ protected:
+ RefPtr<Document> mOwner;
+ RangeBoundary mStart;
+ RangeBoundary mEnd;
+
+ // A Range can be part of multiple |Selection|s. This is a very rare use case.
+ AutoTArray<WeakPtr<Selection>, 1> mSelections;
+ // mRegisteredClosestCommonInclusiveAncestor is only non-null when the range
+ // IsInAnySelection().
+ nsCOMPtr<nsINode> mRegisteredClosestCommonInclusiveAncestor;
+
+ // `true` if `mStart` and `mEnd` are set for StaticRange or set and valid
+ // for nsRange.
+ bool mIsPositioned;
+
+ // Used by nsRange, but this should have this for minimizing the size.
+ bool mIsGenerated;
+ // Used by nsRange, but this should have this for minimizing the size.
+ bool mCalledByJS;
+
+ // true if this is an `nsRange` object.
+ const bool mIsDynamicRange;
+
+ static bool sHasShutDown;
+};
+
+} // namespace mozilla::dom
+
+#endif // #ifndef mozilla_dom_AbstractRange_h
diff --git a/dom/base/AncestorIterator.h b/dom/base/AncestorIterator.h
new file mode 100644
index 0000000000..d8a56267fd
--- /dev/null
+++ b/dom/base/AncestorIterator.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Implementation of some generic iterators over ancestor nodes.
+ *
+ * Note that these keep raw pointers to the nodes they iterate from, and as
+ * such the DOM should not be mutated while they're in use. There are debug
+ * assertions (via nsMutationGuard) that check this in debug builds.
+ */
+
+#ifndef mozilla_dom_AncestorIterator_h
+#define mozilla_dom_AncestorIterator_h
+
+#include "nsINode.h"
+#include "nsIContentInlines.h"
+#include "FilteredNodeIterator.h"
+
+namespace mozilla::dom {
+
+#ifdef DEBUG
+# define MUTATION_GUARD(class_name_) \
+ nsMutationGuard mMutationGuard; \
+ ~class_name_() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
+#else
+# define MUTATION_GUARD(class_name_)
+#endif
+
+#define DEFINE_ANCESTOR_ITERATOR(name_, method_) \
+ class Inclusive##name_ { \
+ using Self = Inclusive##name_; \
+ \
+ public: \
+ explicit Inclusive##name_(const nsINode& aNode) \
+ : mCurrent(const_cast<nsINode*>(&aNode)) {} \
+ Self& begin() { return *this; } \
+ std::nullptr_t end() const { return nullptr; } \
+ bool operator!=(std::nullptr_t) const { return !!mCurrent; } \
+ void operator++() { mCurrent = mCurrent->method_(); } \
+ nsINode* operator*() { return mCurrent; } \
+ \
+ MUTATION_GUARD(Inclusive##name_) \
+ \
+ protected: \
+ explicit Inclusive##name_(nsINode* aCurrent) : mCurrent(aCurrent) {} \
+ nsINode* mCurrent; \
+ }; \
+ class name_ : public Inclusive##name_ { \
+ public: \
+ using Super = Inclusive##name_; \
+ explicit name_(const nsINode& aNode) \
+ : Inclusive##name_(aNode.method_()) {} \
+ }; \
+ template <typename T> \
+ class name_##OfTypeIterator : public FilteredNodeIterator<T, name_> { \
+ public: \
+ explicit name_##OfTypeIterator(const nsINode& aNode) \
+ : FilteredNodeIterator<T, name_>(aNode) {} \
+ }; \
+ template <typename T> \
+ class Inclusive##name_##OfTypeIterator \
+ : public FilteredNodeIterator<T, Inclusive##name_> { \
+ public: \
+ explicit Inclusive##name_##OfTypeIterator(const nsINode& aNode) \
+ : FilteredNodeIterator<T, Inclusive##name_>(aNode) {} \
+ };
+
+DEFINE_ANCESTOR_ITERATOR(Ancestors, GetParentNode)
+DEFINE_ANCESTOR_ITERATOR(FlatTreeAncestors, GetFlattenedTreeParentNode)
+
+#undef MUTATION_GUARD
+
+} // namespace mozilla::dom
+
+template <typename T>
+inline mozilla::dom::AncestorsOfTypeIterator<T> nsINode::AncestorsOfType()
+ const {
+ return mozilla::dom::AncestorsOfTypeIterator<T>(*this);
+}
+
+template <typename T>
+inline mozilla::dom::InclusiveAncestorsOfTypeIterator<T>
+nsINode::InclusiveAncestorsOfType() const {
+ return mozilla::dom::InclusiveAncestorsOfTypeIterator<T>(*this);
+}
+
+template <typename T>
+inline mozilla::dom::FlatTreeAncestorsOfTypeIterator<T>
+nsINode::FlatTreeAncestorsOfType() const {
+ return mozilla::dom::FlatTreeAncestorsOfTypeIterator<T>(*this);
+}
+
+template <typename T>
+inline mozilla::dom::InclusiveFlatTreeAncestorsOfTypeIterator<T>
+nsINode::InclusiveFlatTreeAncestorsOfType() const {
+ return mozilla::dom::InclusiveFlatTreeAncestorsOfTypeIterator<T>(*this);
+}
+
+template <typename T>
+inline T* nsINode::FirstAncestorOfType() const {
+ return *(AncestorsOfType<T>());
+}
+
+#endif // mozilla_dom_AncestorIterator.h
diff --git a/dom/base/AnchorAreaFormRelValues.cpp b/dom/base/AnchorAreaFormRelValues.cpp
new file mode 100644
index 0000000000..66914341ef
--- /dev/null
+++ b/dom/base/AnchorAreaFormRelValues.cpp
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/AnchorAreaFormRelValues.h"
+
+namespace mozilla::dom {
+
+// static
+const DOMTokenListSupportedToken
+ AnchorAreaFormRelValues::sSupportedRelValues[] = {"noreferrer", "noopener",
+ "opener", nullptr};
+
+} // namespace mozilla::dom
diff --git a/dom/base/AnchorAreaFormRelValues.h b/dom/base/AnchorAreaFormRelValues.h
new file mode 100644
index 0000000000..b55890359f
--- /dev/null
+++ b/dom/base/AnchorAreaFormRelValues.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AnchorAreaFormRelValues_h__
+#define mozilla_dom_AnchorAreaFormRelValues_h__
+
+#include "mozilla/dom/DOMTokenListSupportedTokens.h"
+
+namespace mozilla::dom {
+
+class AnchorAreaFormRelValues {
+ protected:
+ static const DOMTokenListSupportedToken sSupportedRelValues[];
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AnchorAreaFormRelValues_h__
diff --git a/dom/base/AnimationFrameProvider.cpp b/dom/base/AnimationFrameProvider.cpp
new file mode 100644
index 0000000000..ba2ee8849b
--- /dev/null
+++ b/dom/base/AnimationFrameProvider.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/AnimationFrameProvider.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+FrameRequest::FrameRequest(FrameRequestCallback& aCallback, int32_t aHandle)
+ : mCallback(&aCallback), mHandle(aHandle) {
+ LogFrameRequestCallback::LogDispatch(mCallback);
+}
+
+FrameRequest::~FrameRequest() = default;
+
+nsresult FrameRequestManager::Schedule(FrameRequestCallback& aCallback,
+ int32_t* aHandle) {
+ if (mCallbackCounter == INT32_MAX) {
+ // Can't increment without overflowing; bail out
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ int32_t newHandle = ++mCallbackCounter;
+
+ mCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
+
+ *aHandle = newHandle;
+ return NS_OK;
+}
+
+bool FrameRequestManager::Cancel(int32_t aHandle) {
+ // mCallbacks is stored sorted by handle
+ if (mCallbacks.RemoveElementSorted(aHandle)) {
+ return true;
+ }
+
+ Unused << mCanceledCallbacks.put(aHandle);
+ return false;
+}
+
+void FrameRequestManager::Unlink() { mCallbacks.Clear(); }
+
+void FrameRequestManager::Traverse(nsCycleCollectionTraversalCallback& aCB) {
+ for (auto& i : mCallbacks) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCB,
+ "FrameRequestManager::mCallbacks[i]");
+ aCB.NoteXPCOMChild(i.mCallback);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/AnimationFrameProvider.h b/dom/base/AnimationFrameProvider.h
new file mode 100644
index 0000000000..bbc01910ee
--- /dev/null
+++ b/dom/base/AnimationFrameProvider.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AnimationFrameProvider_h
+#define mozilla_dom_AnimationFrameProvider_h
+
+#include "mozilla/dom/AnimationFrameProviderBinding.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+struct FrameRequest {
+ FrameRequest(FrameRequestCallback& aCallback, int32_t aHandle);
+ ~FrameRequest();
+
+ // Comparator operators to allow RemoveElementSorted with an
+ // integer argument on arrays of FrameRequest
+ bool operator==(int32_t aHandle) const { return mHandle == aHandle; }
+ bool operator<(int32_t aHandle) const { return mHandle < aHandle; }
+
+ RefPtr<FrameRequestCallback> mCallback;
+ int32_t mHandle;
+};
+
+class FrameRequestManager {
+ public:
+ FrameRequestManager() = default;
+ ~FrameRequestManager() = default;
+
+ nsresult Schedule(FrameRequestCallback& aCallback, int32_t* aHandle);
+ bool Cancel(int32_t aHandle);
+
+ bool IsEmpty() const { return mCallbacks.IsEmpty(); }
+
+ bool IsCanceled(int32_t aHandle) const {
+ return !mCanceledCallbacks.empty() && mCanceledCallbacks.has(aHandle);
+ }
+
+ void Take(nsTArray<FrameRequest>& aCallbacks) {
+ aCallbacks = std::move(mCallbacks);
+ mCanceledCallbacks.clear();
+ }
+
+ void Unlink();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCB);
+
+ private:
+ nsTArray<FrameRequest> mCallbacks;
+
+ // The set of frame request callbacks that were canceled but which we failed
+ // to find in mFrameRequestCallbacks.
+ HashSet<int32_t> mCanceledCallbacks;
+
+ /**
+ * The current frame request callback handle
+ */
+ int32_t mCallbackCounter = 0;
+};
+
+inline void ImplCycleCollectionUnlink(FrameRequestManager& aField) {
+ aField.Unlink();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, FrameRequestManager& aField,
+ const char* aName, uint32_t aFlags) {
+ aField.Traverse(aCallback);
+}
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/AnonymousContent.cpp b/dom/base/AnonymousContent.cpp
new file mode 100644
index 0000000000..03849d4faf
--- /dev/null
+++ b/dom/base/AnonymousContent.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AnonymousContent.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/AnonymousContentBinding.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIFrame.h"
+#include "nsStyledElement.h"
+#include "HTMLCanvasElement.h"
+
+namespace mozilla::dom {
+
+// Ref counting and cycle collection
+NS_IMPL_CYCLE_COLLECTION(AnonymousContent, mContentNode)
+
+AnonymousContent::AnonymousContent(already_AddRefed<Element> aContentNode)
+ : mContentNode(aContentNode) {
+ MOZ_ASSERT(mContentNode);
+}
+
+AnonymousContent::~AnonymousContent() = default;
+
+void AnonymousContent::SetTextContentForElement(const nsAString& aElementId,
+ const nsAString& aText,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->SetTextContent(aText, aRv);
+}
+
+void AnonymousContent::GetTextContentForElement(const nsAString& aElementId,
+ DOMString& aText,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->GetTextContent(aText, aRv);
+}
+
+void AnonymousContent::SetAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName,
+ const nsAString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->SetAttribute(aName, aValue, aSubjectPrincipal, aRv);
+}
+
+void AnonymousContent::GetAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName,
+ DOMString& aValue,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->GetAttribute(aName, aValue);
+}
+
+void AnonymousContent::RemoveAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->RemoveAttribute(aName, aRv);
+}
+
+already_AddRefed<nsISupports> AnonymousContent::GetCanvasContext(
+ const nsAString& aElementId, const nsAString& aContextId,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ if (!element->IsHTMLElement(nsGkAtoms::canvas)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> context;
+
+ HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(element);
+ canvas->GetContext(aContextId, getter_AddRefs(context));
+
+ return context.forget();
+}
+
+already_AddRefed<Animation> AnonymousContent::SetAnimationForElement(
+ JSContext* aContext, const nsAString& aElementId,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ return element->Animate(aContext, aKeyframes, aOptions, aRv);
+}
+
+void AnonymousContent::SetCutoutRectsForElement(
+ const nsAString& aElementId, const Sequence<OwningNonNull<DOMRect>>& aRects,
+ ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsRegion cutOutRegion;
+ for (const auto& r : aRects) {
+ CSSRect rect(r->X(), r->Y(), r->Width(), r->Height());
+ cutOutRegion.OrWith(CSSRect::ToAppUnits(rect));
+ }
+
+ element->SetProperty(nsGkAtoms::cutoutregion, new nsRegion(cutOutRegion),
+ nsINode::DeleteProperty<nsRegion>);
+
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (frame) {
+ frame->SchedulePaint();
+ }
+}
+
+Element* AnonymousContent::GetElementById(const nsAString& aElementId) {
+ // This can be made faster in the future if needed.
+ RefPtr<nsAtom> elementId = NS_Atomize(aElementId);
+ for (nsIContent* node = mContentNode; node;
+ node = node->GetNextNode(mContentNode)) {
+ if (!node->IsElement()) {
+ continue;
+ }
+ nsAtom* id = node->AsElement()->GetID();
+ if (id && id == elementId) {
+ return node->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+bool AnonymousContent::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return AnonymousContent_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+void AnonymousContent::GetComputedStylePropertyValue(
+ const nsAString& aElementId, const nsACString& aPropertyName,
+ nsACString& aResult, ErrorResult& aRv) {
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ if (!element->OwnerDoc()->GetPresShell()) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ RefPtr<nsComputedDOMStyle> cs = new nsComputedDOMStyle(
+ element, PseudoStyleType::NotPseudo, element->OwnerDoc(),
+ nsComputedDOMStyle::StyleType::All);
+ aRv = cs->GetPropertyValue(aPropertyName, aResult);
+}
+
+void AnonymousContent::GetTargetIdForEvent(Event& aEvent, DOMString& aResult) {
+ nsCOMPtr<Element> el = do_QueryInterface(aEvent.GetOriginalTarget());
+ if (el && el->IsInNativeAnonymousSubtree() && mContentNode->Contains(el)) {
+ aResult.SetKnownLiveAtom(el->GetID(), DOMString::eTreatNullAsNull);
+ return;
+ }
+
+ aResult.SetNull();
+}
+
+void AnonymousContent::SetStyle(const nsACString& aProperty,
+ const nsACString& aValue, ErrorResult& aRv) {
+ if (!mContentNode->IsHTMLElement()) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsGenericHTMLElement* element = nsGenericHTMLElement::FromNode(mContentNode);
+ nsCOMPtr<nsICSSDeclaration> declaration = element->Style();
+ declaration->SetProperty(aProperty, aValue, ""_ns, IgnoreErrors());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/AnonymousContent.h b/dom/base/AnonymousContent.h
new file mode 100644
index 0000000000..5096605a16
--- /dev/null
+++ b/dom/base/AnonymousContent.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AnonymousContent_h
+#define mozilla_dom_AnonymousContent_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class Animation;
+class DOMRect;
+class DOMString;
+class Element;
+class Event;
+template <typename T>
+class Sequence;
+class UnrestrictedDoubleOrAnonymousKeyframeAnimationOptions;
+class UnrestrictedDoubleOrKeyframeAnimationOptions;
+
+class AnonymousContent final {
+ public:
+ // Ref counting and cycle collection
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnonymousContent)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnonymousContent)
+
+ explicit AnonymousContent(already_AddRefed<Element> aContentNode);
+ Element& ContentNode() { return *mContentNode; }
+
+ Element* GetElementById(const nsAString& aElementId);
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ // WebIDL methods
+ void SetTextContentForElement(const nsAString& aElementId,
+ const nsAString& aText, ErrorResult& aRv);
+
+ void GetTextContentForElement(const nsAString& aElementId, DOMString& aText,
+ ErrorResult& aRv);
+
+ void SetAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName, const nsAString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void GetAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName, DOMString& aValue,
+ ErrorResult& aRv);
+
+ void RemoveAttributeForElement(const nsAString& aElementId,
+ const nsAString& aName, ErrorResult& aRv);
+
+ already_AddRefed<nsISupports> GetCanvasContext(const nsAString& aElementId,
+ const nsAString& aContextId,
+ ErrorResult& aRv);
+
+ already_AddRefed<Animation> SetAnimationForElement(
+ JSContext* aContext, const nsAString& aElementId,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError);
+
+ void SetCutoutRectsForElement(const nsAString& aElementId,
+ const Sequence<OwningNonNull<DOMRect>>& aRects,
+ ErrorResult& aError);
+
+ void GetComputedStylePropertyValue(const nsAString& aElementId,
+ const nsACString& aPropertyName,
+ nsACString& aResult, ErrorResult& aRv);
+
+ void GetTargetIdForEvent(Event& aEvent, DOMString& aResult);
+
+ void SetStyle(const nsACString& aProperty, const nsACString& aValue,
+ ErrorResult& aRv);
+
+ private:
+ ~AnonymousContent();
+ RefPtr<Element> mContentNode;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AnonymousContent_h
diff --git a/dom/base/Attr.cpp b/dom/base/Attr.cpp
new file mode 100644
index 0000000000..7ac76be544
--- /dev/null
+++ b/dom/base/Attr.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's Attr node.
+ */
+
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/AttrBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsError.h"
+#include "nsUnicharUtils.h"
+#include "nsDOMString.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "nsGkAtoms.h"
+#include "nsCOMArray.h"
+#include "nsNameSpaceManager.h"
+#include "nsTextNode.h"
+#include "mozAutoDocUpdate.h"
+#include "nsWrapperCacheInlines.h"
+#include "NodeUbiReporting.h"
+
+namespace mozilla::dom {
+
+//----------------------------------------------------------------------
+bool Attr::sInitialized;
+
+Attr::Attr(nsDOMAttributeMap* aAttrMap,
+ already_AddRefed<dom::NodeInfo>&& aNodeInfo, const nsAString& aValue)
+ : nsINode(std::move(aNodeInfo)), mAttrMap(aAttrMap), mValue(aValue) {
+ MOZ_ASSERT(mNodeInfo, "We must get a nodeinfo here!");
+ MOZ_ASSERT(mNodeInfo->NodeType() == ATTRIBUTE_NODE, "Wrong nodeType");
+
+ // We don't add a reference to our content. It will tell us
+ // to drop our reference when it goes away.
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Attr)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Attr)
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttrMap)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Attr)
+ nsINode::Unlink(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAttrMap)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Attr)
+ Element* ownerElement = tmp->GetElement();
+ if (tmp->HasKnownLiveWrapper()) {
+ if (ownerElement) {
+ // The attribute owns the element via attribute map so we can
+ // mark it when the attribute is certainly alive.
+ mozilla::dom::FragmentOrElement::MarkNodeChildren(ownerElement);
+ }
+ return true;
+ }
+ if (ownerElement &&
+ mozilla::dom::FragmentOrElement::CanSkip(ownerElement, true)) {
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Attr)
+ return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Attr)
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// QueryInterface implementation for Attr
+NS_INTERFACE_TABLE_HEAD(Attr)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(Attr, nsINode, EventTarget)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Attr)
+ NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
+ new nsNodeSupportsWeakRefTearoff(this))
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Attr)
+
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY(Attr,
+ LastRelease(),
+ Destroy())
+
+NS_IMPL_DOMARENA_DESTROY(Attr)
+
+void Attr::SetMap(nsDOMAttributeMap* aMap) {
+ if (mAttrMap && !aMap && sInitialized) {
+ // We're breaking a relationship with content and not getting a new one,
+ // need to locally cache value. GetValue() does that.
+ GetValue(mValue);
+ }
+
+ mAttrMap = aMap;
+}
+
+Element* Attr::GetElement() const {
+ if (!mAttrMap) {
+ return nullptr;
+ }
+ nsIContent* content = mAttrMap->GetContent();
+ return content ? content->AsElement() : nullptr;
+}
+
+nsresult Attr::SetOwnerDocument(Document* aDocument) {
+ NS_ASSERTION(aDocument, "Missing document");
+
+ Document* doc = OwnerDoc();
+ NS_ASSERTION(doc != aDocument, "bad call to Attr::SetOwnerDocument");
+ doc->RemoveAllPropertiesFor(this);
+
+ RefPtr<dom::NodeInfo> newNodeInfo = aDocument->NodeInfoManager()->GetNodeInfo(
+ mNodeInfo->NameAtom(), mNodeInfo->GetPrefixAtom(),
+ mNodeInfo->NamespaceID(), ATTRIBUTE_NODE);
+ NS_ASSERTION(newNodeInfo, "GetNodeInfo lies");
+ mNodeInfo.swap(newNodeInfo);
+
+ return NS_OK;
+}
+
+void Attr::GetName(nsAString& aName) { aName = NodeName(); }
+
+void Attr::GetValue(nsAString& aValue) {
+ Element* element = GetElement();
+ if (element) {
+ RefPtr<nsAtom> nameAtom = mNodeInfo->NameAtom();
+ element->GetAttr(mNodeInfo->NamespaceID(), nameAtom, aValue);
+ } else {
+ aValue = mValue;
+ }
+}
+
+void Attr::SetValue(const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal,
+ ErrorResult& aRv) {
+ Element* element = GetElement();
+ if (!element) {
+ mValue = aValue;
+ return;
+ }
+
+ RefPtr<nsAtom> nameAtom = mNodeInfo->NameAtom();
+ aRv = element->SetAttr(mNodeInfo->NamespaceID(), nameAtom,
+ mNodeInfo->GetPrefixAtom(), aValue,
+ aTriggeringPrincipal, true);
+}
+
+void Attr::SetValue(const nsAString& aValue, ErrorResult& aRv) {
+ SetValue(aValue, nullptr, aRv);
+}
+
+bool Attr::Specified() const { return true; }
+
+Element* Attr::GetOwnerElement() { return GetElement(); }
+
+void Attr::GetNodeValueInternal(nsAString& aNodeValue) { GetValue(aNodeValue); }
+
+void Attr::SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError) {
+ SetValue(aNodeValue, nullptr, aError);
+}
+
+nsresult Attr::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
+ nsAutoString value;
+ const_cast<Attr*>(this)->GetValue(value);
+ *aResult = new (aNodeInfo->NodeInfoManager())
+ Attr(nullptr, do_AddRef(aNodeInfo), value);
+
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsIURI* Attr::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
+ Element* parent = GetElement();
+
+ return parent ? parent->GetBaseURI(aTryUseXHRDocBaseURI)
+ : OwnerDoc()->GetBaseURI(aTryUseXHRDocBaseURI);
+}
+
+void Attr::GetTextContentInternal(nsAString& aTextContent,
+ OOMReporter& aError) {
+ GetValue(aTextContent);
+}
+
+void Attr::SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) {
+ SetNodeValueInternal(aTextContent, aError);
+}
+
+void Attr::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ aVisitor.mCanHandle = true;
+}
+
+void Attr::Initialize() { sInitialized = true; }
+
+void Attr::Shutdown() { sInitialized = false; }
+
+JSObject* Attr::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return Attr_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void Attr::ConstructUbiNode(void* storage) {
+ JS::ubi::Concrete<Attr>::construct(storage, this);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Attr.h b/dom/base/Attr.h
new file mode 100644
index 0000000000..c921221fe4
--- /dev/null
+++ b/dom/base/Attr.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's Attr node.
+ */
+
+#ifndef mozilla_dom_Attr_h
+#define mozilla_dom_Attr_h
+
+#include "mozilla/Attributes.h"
+#include "nsDOMAttributeMap.h"
+#include "nsINode.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStubMutationObserver.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+
+class Document;
+
+// Attribute helper class used to wrap up an attribute with a dom
+// object that implements the DOM Attr interface.
+class Attr final : public nsINode {
+ virtual ~Attr() = default;
+
+ public:
+ Attr(nsDOMAttributeMap* aAttrMap, already_AddRefed<dom::NodeInfo>&& aNodeInfo,
+ const nsAString& aValue);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL_DELETECYCLECOLLECTABLE
+
+ NS_DECL_DOMARENA_DESTROY
+
+ NS_IMPL_FROMNODE_HELPER(Attr, IsAttr())
+
+ // nsINode interface
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ OOMReporter& aError) override;
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) override;
+ virtual void GetNodeValueInternal(nsAString& aNodeValue) override;
+ virtual void SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError) override;
+
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
+ void ConstructUbiNode(void* storage) override;
+
+ nsDOMAttributeMap* GetMap() { return mAttrMap; }
+
+ void SetMap(nsDOMAttributeMap* aMap);
+
+ Element* GetElement() const;
+
+ /**
+ * Called when our ownerElement is moved into a new document.
+ * Updates the nodeinfo of this node.
+ */
+ nsresult SetOwnerDocument(Document* aDocument);
+
+ // nsINode interface
+ nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
+ nsIURI* GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
+
+ static void Initialize();
+ static void Shutdown();
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(Attr)
+
+ // WebIDL
+ virtual JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetName(nsAString& aName);
+ void GetValue(nsAString& aValue);
+
+ void SetValue(const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal,
+ ErrorResult& aRv);
+ void SetValue(const nsAString& aValue, ErrorResult& aRv);
+
+ bool Specified() const;
+
+ // XPCOM GetNamespaceURI() is OK
+ // XPCOM GetPrefix() is OK
+ // XPCOM GetLocalName() is OK
+
+ Element* GetOwnerElement();
+
+ protected:
+ virtual Element* GetNameSpaceElement() override { return GetElement(); }
+
+ static bool sInitialized;
+
+ private:
+ RefPtr<nsDOMAttributeMap> mAttrMap;
+ nsString mValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_Attr_h */
diff --git a/dom/base/AttrArray.cpp b/dom/base/AttrArray.cpp
new file mode 100644
index 0000000000..226639c1a8
--- /dev/null
+++ b/dom/base/AttrArray.cpp
@@ -0,0 +1,547 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Storage of the children and attributes of a DOM node; storage for
+ * the two is unified to minimize footprint.
+ */
+
+#include "AttrArray.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsMappedAttributeElement.h"
+#include "nsString.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsMappedAttributes.h"
+#include "nsUnicharUtils.h"
+#include "nsContentUtils.h" // nsAutoScriptBlocker
+
+using mozilla::CheckedUint32;
+using mozilla::dom::Document;
+
+AttrArray::Impl::~Impl() {
+ for (InternalAttr& attr : NonMappedAttrs()) {
+ attr.~InternalAttr();
+ }
+
+ NS_IF_RELEASE(mMappedAttrs);
+}
+
+const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName,
+ int32_t aNamespaceID) const {
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName)) {
+ return &attr.mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+ } else {
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName, aNamespaceID)) {
+ return &attr.mValue;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue* AttrArray::GetAttr(const nsAString& aLocalName) const {
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName)) {
+ return &attr.mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue* AttrArray::GetAttr(const nsAString& aName,
+ nsCaseTreatment aCaseSensitive) const {
+ // Check whether someone is being silly and passing non-lowercase
+ // attr names.
+ if (aCaseSensitive == eIgnoreCase &&
+ nsContentUtils::StringContainsASCIIUpper(aName)) {
+ // Try again with a lowercased name, but make sure we can't reenter this
+ // block by passing eCaseSensitive for aCaseSensitive.
+ nsAutoString lowercase;
+ nsContentUtils::ASCIIToLower(aName, lowercase);
+ return GetAttr(lowercase, eCaseMatters);
+ }
+
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.QualifiedNameEquals(aName)) {
+ return &attr.mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetAttr(aName);
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue* AttrArray::AttrAt(uint32_t aPos) const {
+ NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return &mImpl->NonMappedAttrs()[aPos].mValue;
+ }
+
+ return mImpl->mMappedAttrs->AttrAt(aPos - nonmapped);
+}
+
+template <typename Name>
+inline nsresult AttrArray::AddNewAttribute(Name* aName, nsAttrValue& aValue) {
+ MOZ_ASSERT(!mImpl || mImpl->mCapacity >= mImpl->mAttrCount);
+ if (!mImpl || mImpl->mCapacity == mImpl->mAttrCount) {
+ if (!GrowBy(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
+ new (&attr.mName) nsAttrName(aName);
+ new (&attr.mValue) nsAttrValue();
+ attr.mValue.SwapValueWith(aValue);
+ return NS_OK;
+}
+
+nsresult AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
+ bool* aHadValue) {
+ *aHadValue = false;
+
+ for (InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName)) {
+ attr.mValue.SwapValueWith(aValue);
+ *aHadValue = true;
+ return NS_OK;
+ }
+ }
+
+ return AddNewAttribute(aLocalName, aValue);
+}
+
+nsresult AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
+ nsAttrValue& aValue, bool* aHadValue) {
+ int32_t namespaceID = aName->NamespaceID();
+ nsAtom* localName = aName->NameAtom();
+ if (namespaceID == kNameSpaceID_None) {
+ return SetAndSwapAttr(localName, aValue, aHadValue);
+ }
+
+ *aHadValue = false;
+ for (InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(localName, namespaceID)) {
+ attr.mName.SetTo(aName);
+ attr.mValue.SwapValueWith(aValue);
+ *aHadValue = true;
+ return NS_OK;
+ }
+ }
+
+ return AddNewAttribute(aName, aValue);
+}
+
+nsresult AttrArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
+ NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ mImpl->mBuffer[aPos].mValue.SwapValueWith(aValue);
+ mImpl->mBuffer[aPos].~InternalAttr();
+
+ memmove(mImpl->mBuffer + aPos, mImpl->mBuffer + aPos + 1,
+ (mImpl->mAttrCount - aPos - 1) * sizeof(InternalAttr));
+
+ --mImpl->mAttrCount;
+
+ return NS_OK;
+ }
+
+ if (MappedAttrCount() == 1) {
+ // We're removing the last mapped attribute. Can't swap in this
+ // case; have to copy.
+ aValue.SetTo(*mImpl->mMappedAttrs->AttrAt(0));
+ NS_RELEASE(mImpl->mMappedAttrs);
+
+ return NS_OK;
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(nullptr, nullptr, false);
+
+ mapped->RemoveAttrAt(aPos - nonmapped, aValue);
+
+ return MakeMappedUnique(mapped);
+}
+
+mozilla::dom::BorrowedAttrInfo AttrArray::AttrInfoAt(uint32_t aPos) const {
+ NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ InternalAttr& attr = mImpl->mBuffer[aPos];
+ return BorrowedAttrInfo(&attr.mName, &attr.mValue);
+ }
+
+ return BorrowedAttrInfo(mImpl->mMappedAttrs->NameAt(aPos - nonmapped),
+ mImpl->mMappedAttrs->AttrAt(aPos - nonmapped));
+}
+
+const nsAttrName* AttrArray::AttrNameAt(uint32_t aPos) const {
+ NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return &mImpl->mBuffer[aPos].mName;
+ }
+
+ return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
+}
+
+const nsAttrName* AttrArray::GetSafeAttrNameAt(uint32_t aPos) const {
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return &mImpl->mBuffer[aPos].mName;
+ }
+
+ if (aPos >= AttrCount()) {
+ return nullptr;
+ }
+
+ return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
+}
+
+const nsAttrName* AttrArray::GetExistingAttrNameFromQName(
+ const nsAString& aName) const {
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.QualifiedNameEquals(aName)) {
+ return &attr.mName;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetExistingAttrNameFromQName(aName);
+ }
+
+ return nullptr;
+}
+
+int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName,
+ int32_t aNamespaceID) const {
+ if (!mImpl) {
+ return -1;
+ }
+
+ int32_t idx;
+ if (mImpl->mMappedAttrs && aNamespaceID == kNameSpaceID_None) {
+ idx = mImpl->mMappedAttrs->IndexOfAttr(aLocalName);
+ if (idx >= 0) {
+ return NonMappedAttrCount() + idx;
+ }
+ }
+
+ uint32_t i = 0;
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ // Note that here we don't check for AttrSlotIsTaken() in the loop
+ // condition for the sake of performance because comparing aLocalName
+ // against null would fail in the loop body (since Equals() just compares
+ // the raw pointer value of aLocalName to what AttrSlotIsTaken() would be
+ // checking.
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName)) {
+ return i;
+ }
+ ++i;
+ }
+ } else {
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ if (attr.mName.Equals(aLocalName, aNamespaceID)) {
+ return i;
+ }
+ ++i;
+ }
+ }
+
+ return -1;
+}
+
+nsresult AttrArray::SetAndSwapMappedAttr(nsAtom* aLocalName,
+ nsAttrValue& aValue,
+ nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet,
+ bool* aHadValue) {
+ bool willAdd = true;
+ if (mImpl && mImpl->mMappedAttrs) {
+ willAdd = !mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(aContent, aSheet, willAdd);
+
+ mapped->SetAndSwapAttr(aLocalName, aValue, aHadValue);
+
+ return MakeMappedUnique(mapped);
+}
+
+nsresult AttrArray::DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet) {
+ MOZ_ASSERT(mImpl && mImpl->mMappedAttrs, "Should have mapped attrs here!");
+ if (aSheet == mImpl->mMappedAttrs->GetStyleSheet()) {
+ return NS_OK;
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(nullptr, nullptr, false);
+
+ mapped->DropStyleSheetReference();
+ mapped->SetStyleSheet(aSheet);
+
+ return MakeMappedUnique(mapped);
+}
+
+nsresult AttrArray::DoUpdateMappedAttrRuleMapper(
+ nsMappedAttributeElement& aElement) {
+ MOZ_ASSERT(mImpl && mImpl->mMappedAttrs, "Should have mapped attrs here!");
+
+ // First two args don't matter if the assert holds.
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(nullptr, nullptr, false);
+
+ mapped->SetRuleMapper(aElement.GetAttributeMappingFunction());
+
+ return MakeMappedUnique(mapped);
+}
+
+void AttrArray::Compact() {
+ if (!mImpl) {
+ return;
+ }
+
+ if (!mImpl->mAttrCount && !mImpl->mMappedAttrs) {
+ mImpl.reset();
+ return;
+ }
+
+ // Nothing to do.
+ if (mImpl->mAttrCount == mImpl->mCapacity) {
+ return;
+ }
+
+ Impl* oldImpl = mImpl.release();
+ Impl* impl = static_cast<Impl*>(
+ realloc(oldImpl, Impl::AllocationSizeForAttributes(oldImpl->mAttrCount)));
+ if (!impl) {
+ mImpl.reset(oldImpl);
+ return;
+ }
+ impl->mCapacity = impl->mAttrCount;
+ mImpl.reset(impl);
+}
+
+uint32_t AttrArray::DoGetMappedAttrCount() const {
+ MOZ_ASSERT(mImpl && mImpl->mMappedAttrs);
+ return static_cast<uint32_t>(mImpl->mMappedAttrs->Count());
+}
+
+nsresult AttrArray::ForceMapped(nsMappedAttributeElement* aContent,
+ Document* aDocument) {
+ nsHTMLStyleSheet* sheet = aDocument->GetAttributeStyleSheet();
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(aContent, sheet, false, 0);
+ return MakeMappedUnique(mapped);
+}
+
+void AttrArray::ClearMappedServoStyle() {
+ if (mImpl && mImpl->mMappedAttrs) {
+ mImpl->mMappedAttrs->ClearServoStyle();
+ }
+}
+
+nsMappedAttributes* AttrArray::GetModifiableMapped(
+ nsMappedAttributeElement* aContent, nsHTMLStyleSheet* aSheet,
+ bool aWillAddAttr, int32_t aAttrCount) {
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->Clone(aWillAddAttr);
+ }
+
+ MOZ_ASSERT(aContent, "Trying to create modifiable without content");
+
+ nsMapRuleToAttributesFunc mapRuleFunc =
+ aContent->GetAttributeMappingFunction();
+ return new (aAttrCount) nsMappedAttributes(aSheet, mapRuleFunc);
+}
+
+nsresult AttrArray::MakeMappedUnique(nsMappedAttributes* aAttributes) {
+ NS_ASSERTION(aAttributes, "missing attributes");
+
+ if (!mImpl && !GrowBy(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!aAttributes->GetStyleSheet()) {
+ // This doesn't currently happen, but it could if we do loading right
+
+ RefPtr<nsMappedAttributes> mapped(aAttributes);
+ mapped.swap(mImpl->mMappedAttrs);
+
+ return NS_OK;
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ aAttributes->GetStyleSheet()->UniqueMappedAttributes(aAttributes);
+ NS_ENSURE_TRUE(mapped, NS_ERROR_OUT_OF_MEMORY);
+
+ if (mapped != aAttributes) {
+ // Reset the stylesheet of aAttributes so that it doesn't spend time
+ // trying to remove itself from the hash. There is no risk that aAttributes
+ // is in the hash since it will always have come from GetModifiableMapped,
+ // which never returns maps that are in the hash (such hashes are by
+ // nature not modifiable).
+ aAttributes->DropStyleSheetReference();
+ }
+ mapped.swap(mImpl->mMappedAttrs);
+
+ return NS_OK;
+}
+
+const nsMappedAttributes* AttrArray::GetMapped() const {
+ return mImpl ? mImpl->mMappedAttrs : nullptr;
+}
+
+nsresult AttrArray::EnsureCapacityToClone(const AttrArray& aOther) {
+ MOZ_ASSERT(!mImpl,
+ "AttrArray::EnsureCapacityToClone requires the array be empty "
+ "when called");
+
+ uint32_t attrCount = aOther.NonMappedAttrCount();
+ if (!attrCount) {
+ return NS_OK;
+ }
+
+ // No need to use a CheckedUint32 because we are cloning. We know that we
+ // have already allocated an AttrArray of this size.
+ mImpl.reset(
+ static_cast<Impl*>(malloc(Impl::AllocationSizeForAttributes(attrCount))));
+ NS_ENSURE_TRUE(mImpl, NS_ERROR_OUT_OF_MEMORY);
+
+ mImpl->mMappedAttrs = nullptr;
+ mImpl->mCapacity = attrCount;
+ mImpl->mAttrCount = 0;
+
+ return NS_OK;
+}
+
+bool AttrArray::GrowBy(uint32_t aGrowSize) {
+ const uint32_t kLinearThreshold = 16;
+ const uint32_t kLinearGrowSize = 4;
+
+ CheckedUint32 capacity = mImpl ? mImpl->mCapacity : 0;
+ CheckedUint32 minCapacity = capacity;
+ minCapacity += aGrowSize;
+ if (!minCapacity.isValid()) {
+ return false;
+ }
+
+ if (capacity.value() <= kLinearThreshold) {
+ do {
+ capacity += kLinearGrowSize;
+ if (!capacity.isValid()) {
+ return false;
+ }
+ } while (capacity.value() < minCapacity.value());
+ } else {
+ uint32_t shift = mozilla::CeilingLog2(minCapacity.value());
+ if (shift >= 32) {
+ return false;
+ }
+ capacity = 1u << shift;
+ }
+
+ CheckedUint32 sizeInBytes = capacity.value();
+ sizeInBytes *= sizeof(InternalAttr);
+ if (!sizeInBytes.isValid()) {
+ return false;
+ }
+
+ sizeInBytes += sizeof(Impl);
+ if (!sizeInBytes.isValid()) {
+ return false;
+ }
+
+ MOZ_ASSERT(sizeInBytes.value() ==
+ Impl::AllocationSizeForAttributes(capacity.value()));
+
+ const bool needToInitialize = !mImpl;
+ Impl* oldImpl = mImpl.release();
+ Impl* newImpl = static_cast<Impl*>(realloc(oldImpl, sizeInBytes.value()));
+ if (!newImpl) {
+ mImpl.reset(oldImpl);
+ return false;
+ }
+
+ mImpl.reset(newImpl);
+
+ // Set initial counts if we didn't have a buffer before
+ if (needToInitialize) {
+ mImpl->mMappedAttrs = nullptr;
+ mImpl->mAttrCount = 0;
+ }
+
+ mImpl->mCapacity = capacity.value();
+ return true;
+}
+
+size_t AttrArray::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mImpl) {
+ // Don't add the size taken by *mMappedAttrs because it's shared.
+
+ n += aMallocSizeOf(mImpl.get());
+
+ for (const InternalAttr& attr : NonMappedAttrs()) {
+ n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+
+ return n;
+}
+
+int32_t AttrArray::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
+ AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const {
+ NS_ASSERTION(aName, "Must have attr name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
+ NS_ASSERTION(aValues, "Null value array");
+
+ const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
+ if (val) {
+ for (int32_t i = 0; aValues[i]; ++i) {
+ if (val->Equals(aValues[i], aCaseSensitive)) {
+ return i;
+ }
+ }
+ return ATTR_VALUE_NO_MATCH;
+ }
+ return ATTR_MISSING;
+}
diff --git a/dom/base/AttrArray.h b/dom/base/AttrArray.h
new file mode 100644
index 0000000000..47e91793ce
--- /dev/null
+++ b/dom/base/AttrArray.h
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Storage of the attributes of a DOM node.
+ */
+
+#ifndef AttrArray_h___
+#define AttrArray_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Span.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+
+#include "nscore.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsCaseTreatment.h"
+
+class nsINode;
+class nsIContent;
+class nsMappedAttributes;
+class nsHTMLStyleSheet;
+class nsRuleWalker;
+class nsMappedAttributeElement;
+
+class AttrArray {
+ using BorrowedAttrInfo = mozilla::dom::BorrowedAttrInfo;
+
+ public:
+ AttrArray() = default;
+ ~AttrArray() = default;
+
+ bool HasAttrs() const { return NonMappedAttrCount() || MappedAttrCount(); }
+
+ uint32_t AttrCount() const {
+ return NonMappedAttrCount() + MappedAttrCount();
+ }
+
+ const nsAttrValue* GetAttr(const nsAtom* aLocalName,
+ int32_t aNamespaceID = kNameSpaceID_None) const;
+ // As above but using a string attr name and always using
+ // kNameSpaceID_None. This is always case-sensitive.
+ const nsAttrValue* GetAttr(const nsAString& aName) const;
+ // Get an nsAttrValue by qualified name. Can optionally do
+ // ASCII-case-insensitive name matching.
+ const nsAttrValue* GetAttr(const nsAString& aName,
+ nsCaseTreatment aCaseSensitive) const;
+ const nsAttrValue* AttrAt(uint32_t aPos) const;
+ // SetAndSwapAttr swaps the current attribute value with aValue.
+ // If the attribute was unset, an empty value will be swapped into aValue
+ // and aHadValue will be set to false. Otherwise, aHadValue will be set to
+ // true.
+ nsresult SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
+ bool* aHadValue);
+ nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue,
+ bool* aHadValue);
+
+ // Remove the attr at position aPos. The value of the attr is placed in
+ // aValue; any value that was already in aValue is destroyed.
+ nsresult RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue);
+
+ // Returns attribute name at given position, *not* out-of-bounds safe
+ const nsAttrName* AttrNameAt(uint32_t aPos) const;
+
+ // Returns the attribute info at a given position, *not* out-of-bounds safe
+ BorrowedAttrInfo AttrInfoAt(uint32_t aPos) const;
+
+ // Returns attribute name at given position or null if aPos is out-of-bounds
+ const nsAttrName* GetSafeAttrNameAt(uint32_t aPos) const;
+
+ const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const;
+ int32_t IndexOfAttr(const nsAtom* aLocalName,
+ int32_t aNamespaceID = kNameSpaceID_None) const;
+
+ // SetAndSwapMappedAttr swaps the current attribute value with aValue.
+ // If the attribute was unset, an empty value will be swapped into aValue
+ // and aHadValue will be set to false. Otherwise, aHadValue will be set to
+ // true.
+ nsresult SetAndSwapMappedAttr(nsAtom* aLocalName, nsAttrValue& aValue,
+ nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet, bool* aHadValue);
+ nsresult SetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet) {
+ if (!mImpl || !mImpl->mMappedAttrs) {
+ return NS_OK;
+ }
+ return DoSetMappedAttrStyleSheet(aSheet);
+ }
+
+ // Update the rule mapping function on our mapped attributes, if we have any.
+ // We take a nsMappedAttributeElement, not a nsMapRuleToAttributesFunc,
+ // because the latter is defined in a header we can't include here.
+ nsresult UpdateMappedAttrRuleMapper(nsMappedAttributeElement& aElement) {
+ if (!mImpl || !mImpl->mMappedAttrs) {
+ return NS_OK;
+ }
+ return DoUpdateMappedAttrRuleMapper(aElement);
+ }
+
+ void Compact();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ bool HasMappedAttrs() const { return MappedAttrCount(); }
+ const nsMappedAttributes* GetMapped() const;
+
+ // Force this to have mapped attributes, even if those attributes are empty.
+ nsresult ForceMapped(nsMappedAttributeElement* aContent,
+ mozilla::dom::Document* aDocument);
+
+ // Clear the servo declaration block on the mapped attributes, if any
+ // Will assert off main thread
+ void ClearMappedServoStyle();
+
+ // Increases capacity (if necessary) to have enough space to accomodate the
+ // unmapped attributes of |aOther|.
+ nsresult EnsureCapacityToClone(const AttrArray& aOther);
+
+ enum AttrValuesState { ATTR_MISSING = -1, ATTR_VALUE_NO_MATCH = -2 };
+ using AttrValuesArray = nsStaticAtom* const;
+ int32_t FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
+ AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const;
+
+ inline bool GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
+ nsAString& aResult) const {
+ MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
+
+ const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
+ if (val) {
+ val->ToString(aResult);
+ return true;
+ }
+ return false;
+ }
+
+ inline bool HasAttr(int32_t aNameSpaceID, const nsAtom* aName) const {
+ return GetAttr(aName, aNameSpaceID) != nullptr;
+ }
+
+ inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ NS_ASSERTION(aName, "Must have attr name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
+
+ const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
+ return val && val->Equals(aValue, aCaseSensitive);
+ }
+
+ inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ NS_ASSERTION(aName, "Must have attr name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
+ NS_ASSERTION(aValue, "Null value atom");
+
+ const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
+ return val && val->Equals(aValue, aCaseSensitive);
+ }
+
+ struct InternalAttr {
+ nsAttrName mName;
+ nsAttrValue mValue;
+ };
+
+ private:
+ AttrArray(const AttrArray& aOther) = delete;
+ AttrArray& operator=(const AttrArray& aOther) = delete;
+
+ uint32_t NonMappedAttrCount() const { return mImpl ? mImpl->mAttrCount : 0; }
+
+ uint32_t MappedAttrCount() const {
+ return mImpl && mImpl->mMappedAttrs ? DoGetMappedAttrCount() : 0;
+ }
+
+ uint32_t DoGetMappedAttrCount() const;
+
+ // Returns a non-null zero-refcount object.
+ nsMappedAttributes* GetModifiableMapped(nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet,
+ bool aWillAddAttr,
+ int32_t aAttrCount = 1);
+ nsresult MakeMappedUnique(nsMappedAttributes* aAttributes);
+
+ bool GrowBy(uint32_t aGrowSize);
+
+ // Tries to create an attribute, growing the buffer if needed, with the given
+ // name and value.
+ //
+ // The value is moved from the argument.
+ //
+ // `Name` can be anything you construct a `nsAttrName` with (either an atom or
+ // a NodeInfo pointer).
+ template <typename Name>
+ nsresult AddNewAttribute(Name*, nsAttrValue&);
+
+ /**
+ * Guts of SetMappedAttrStyleSheet for the rare case when we have mapped attrs
+ */
+ nsresult DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet);
+
+ /**
+ * Guts of UpdateMappedAttrRuleMapper for the case when we have mapped attrs.
+ */
+ nsresult DoUpdateMappedAttrRuleMapper(nsMappedAttributeElement& aElement);
+
+#ifdef _MSC_VER
+// Disable MSVC warning 'nonstandard extension used: zero-sized array in
+// struct/union'
+# pragma warning(push)
+# pragma warning(disable : 4200)
+#endif
+ class Impl {
+ public:
+ constexpr static size_t AllocationSizeForAttributes(uint32_t aAttrCount) {
+ return sizeof(Impl) + aAttrCount * sizeof(InternalAttr);
+ }
+
+ auto NonMappedAttrs() const {
+ return mozilla::Span<const InternalAttr>{mBuffer, mAttrCount};
+ }
+
+ auto NonMappedAttrs() {
+ return mozilla::Span<InternalAttr>{mBuffer, mAttrCount};
+ }
+
+ Impl(const Impl&) = delete;
+ Impl(Impl&&) = delete;
+ ~Impl();
+
+ uint32_t mAttrCount;
+ uint32_t mCapacity; // In number of InternalAttrs
+
+ // Manually refcounted.
+ nsMappedAttributes* mMappedAttrs;
+
+ // Allocated in the same buffer as `Impl`.
+ InternalAttr mBuffer[0];
+ };
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+ mozilla::Span<InternalAttr> NonMappedAttrs() {
+ return mImpl ? mImpl->NonMappedAttrs() : mozilla::Span<InternalAttr>();
+ }
+
+ mozilla::Span<const InternalAttr> NonMappedAttrs() const {
+ return mImpl ? mImpl->NonMappedAttrs()
+ : mozilla::Span<const InternalAttr>();
+ }
+
+ mozilla::UniquePtr<Impl> mImpl;
+};
+
+#endif
diff --git a/dom/base/AutoPrintEventDispatcher.h b/dom/base/AutoPrintEventDispatcher.h
new file mode 100644
index 0000000000..6b5999021b
--- /dev/null
+++ b/dom/base/AutoPrintEventDispatcher.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AutoPrintEventDispatcher_h
+#define mozilla_dom_AutoPrintEventDispatcher_h
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsContentUtils.h"
+#include "nsIPrintSettings.h"
+
+namespace mozilla::dom {
+
+class AutoPrintEventDispatcher {
+ // NOTE(emilio): For fission iframes, we dispatch this event in
+ // RecvCloneDocumentTreeIntoSelf.
+ static void CollectInProcessSubdocuments(
+ Document& aDoc, nsTArray<nsCOMPtr<Document>>& aDocs) {
+ auto recurse = [&aDocs](Document& aSubDoc) {
+ aDocs.AppendElement(&aSubDoc);
+ CollectInProcessSubdocuments(aSubDoc, aDocs);
+ return CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(recurse);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void DispatchEvent(bool aBefore) {
+ for (auto& doc : mDocuments) {
+ nsContentUtils::DispatchTrustedEvent(
+ doc, doc->GetWindow(), aBefore ? u"beforeprint"_ns : u"afterprint"_ns,
+ CanBubble::eNo, Cancelable::eNo, nullptr);
+ if (RefPtr<nsPresContext> presContext = doc->GetPresContext()) {
+ presContext->EmulateMedium(aBefore ? nsGkAtoms::print : nullptr);
+ // Ensure media query listeners fire.
+ doc->FlushPendingNotifications(FlushType::Style);
+ }
+ }
+ }
+
+ public:
+ MOZ_CAN_RUN_SCRIPT explicit AutoPrintEventDispatcher(Document& aDoc) {
+ if (!aDoc.IsStaticDocument()) {
+ mDocuments.AppendElement(&aDoc);
+ CollectInProcessSubdocuments(aDoc, mDocuments);
+ }
+
+ DispatchEvent(true);
+ }
+
+ MOZ_CAN_RUN_SCRIPT ~AutoPrintEventDispatcher() { DispatchEvent(false); }
+
+ AutoTArray<nsCOMPtr<Document>, 8> mDocuments;
+ const nsSize mPageSize;
+ nsRect mVisibleAreaToRestore;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/AutoSuppressEventHandlingAndSuspend.h b/dom/base/AutoSuppressEventHandlingAndSuspend.h
new file mode 100644
index 0000000000..6f23fce475
--- /dev/null
+++ b/dom/base/AutoSuppressEventHandlingAndSuspend.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_AutoSuppressEventHandlingAndSuspend_h
+#define dom_base_AutoSuppressEventHandlingAndSuspend_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/Document.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+/**
+ * Suppresses event handling and suspends for all in-process documents in a
+ * BrowsingContext subtree.
+ */
+class MOZ_RAII AutoSuppressEventHandling : public AutoWalkBrowsingContextGroup {
+ public:
+ AutoSuppressEventHandling() = default;
+
+ explicit AutoSuppressEventHandling(BrowsingContext* aContext) {
+ if (aContext) {
+ SuppressBrowsingContext(aContext);
+ }
+ }
+
+ ~AutoSuppressEventHandling();
+
+ protected:
+ virtual void SuppressDocument(Document* aDocument) override;
+ void UnsuppressDocument(Document* aDocument) override;
+};
+
+/**
+ * Suppresses event handling and suspends the active inner window for all
+ * in-process documents in a BrowsingContextGroup. This should be used while
+ * spinning the event loop for a synchronous operation (like `window.open()`)
+ * which affects operations in any other window in the same BrowsingContext
+ * group.
+ */
+class MOZ_RAII AutoSuppressEventHandlingAndSuspend
+ : private AutoSuppressEventHandling {
+ public:
+ explicit AutoSuppressEventHandlingAndSuspend(BrowsingContextGroup* aGroup) {
+ if (aGroup) {
+ SuppressBrowsingContextGroup(aGroup);
+ }
+ }
+
+ ~AutoSuppressEventHandlingAndSuspend();
+
+ protected:
+ void SuppressDocument(Document* aDocument) override;
+
+ private:
+ AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> mWindows;
+};
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/AutocompleteFieldList.h b/dom/base/AutocompleteFieldList.h
new file mode 100644
index 0000000000..7856a77772
--- /dev/null
+++ b/dom/base/AutocompleteFieldList.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file contains the list of field names that are used in @autocomplete
+ * attribute for <input>, <select> and <textarea> controls. It is designed
+ * to be used as inline input through the magic of C preprocessing.
+ *
+ * The first argument to AUTOCOMPLETE_* macro is the identifier for the token
+ * The second argument is the string value of the token
+ */
+
+#ifndef AUTOCOMPLETE_FIELD_NAME
+#define AUTOCOMPLETE_FIELD_NAME(name_, value_)
+#define DEFINED_AUTOCOMPLETE_FIELD_NAME
+#endif
+
+#ifndef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+#define AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(name_, value_)
+#define DEFINED_AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+#endif
+
+#ifndef AUTOCOMPLETE_CONTACT_FIELD_NAME
+#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_)
+#define DEFINED_AUTOCOMPLETE_CONTACT_FIELD_NAME
+#endif
+
+#ifndef AUTOCOMPLETE_FIELD_HINT
+#define AUTOCOMPLETE_FIELD_HINT(name_, value_)
+#define DEFINED_AUTOCOMPLETE_FIELD_HINT
+#endif
+
+#ifndef AUTOCOMPLETE_FIELD_CONTACT_HINT
+#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_)
+#define DEFINED_AUTOCOMPLETE_FIELD_CONTACT_HINT
+#endif
+
+#ifndef AUTOCOMPLETE_CATEGORY
+#define AUTOCOMPLETE_CATEGORY(name_, value_)
+#define DEFINED_AUTOCOMPLETE_CATEGORY
+#endif
+
+#ifndef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_)
+#define DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#endif
+
+#ifndef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_)
+#define DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#endif
+
+//-----------------------------------------------------
+// Unsupported list
+
+// Unsupported field names
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(HONORIFIX_PREFIX, "honorifix-prefix")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(HONORIFIX_SUFFIX, "honorifix-suffix")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(NICKNAME, "nickname")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ORGANIZATION_TITLE, "organization-title")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ADDRESS_LEVEL4, "address-level4")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_GIVEN_NAME, "cc-given-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_ADDITIONAL_NAME, "cc-additional-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_FAMILY_NAME, "cc-family-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_CSC, "cc-csc")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_TYPE, "cc-type")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TRANSACTION_CURRENCY,
+ "transaction-currency")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TRANSACTION_AMOUNT, "transaction-amount")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ONE_TIME_CODE, "one-time-code")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(LANGUAGE, "language")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY, "bday")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_DAY, "bday-day")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_MONTH, "bday-month")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_YEAR, "bday-year")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(SEX, "sex")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(URL, "url")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(PHOTO, "photo")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TEL_EXTENSION, "tel-extension")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(IMPP, "impp")
+
+// Unsupported contact types
+AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(FAX, "fax")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(PAGER, "pager")
+
+//-----------------------------------------------------
+// Valid list
+
+AUTOCOMPLETE_FIELD_NAME(OFF, "off")
+AUTOCOMPLETE_FIELD_NAME(ON, "on")
+
+// Name types
+AUTOCOMPLETE_FIELD_NAME(NAME, "name")
+AUTOCOMPLETE_FIELD_NAME(HONORIFIX_PREFIX, "honorifix-prefix")
+AUTOCOMPLETE_FIELD_NAME(GIVEN_NAME, "given-name")
+AUTOCOMPLETE_FIELD_NAME(ADDITIONAL_NAME, "additional-name")
+AUTOCOMPLETE_FIELD_NAME(FAMILY_NAME, "family-name")
+AUTOCOMPLETE_FIELD_NAME(HONORIFIX_SUFFIX, "honorifix-suffix")
+AUTOCOMPLETE_FIELD_NAME(NICKNAME, "nickname")
+AUTOCOMPLETE_FIELD_NAME(ORGANIZATION_TITLE, "organization-title")
+
+// Login types
+AUTOCOMPLETE_FIELD_NAME(USERNAME, "username")
+AUTOCOMPLETE_FIELD_NAME(NEW_PASSWORD, "new-password")
+AUTOCOMPLETE_FIELD_NAME(CURRENT_PASSWORD, "current-password")
+AUTOCOMPLETE_FIELD_NAME(ONE_TIME_CODE, "one-time-code")
+
+// Address types
+AUTOCOMPLETE_FIELD_NAME(ORGANIZATION, "organization")
+AUTOCOMPLETE_FIELD_NAME(STREET_ADDRESS, "street-address")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LINE1, "address-line1")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LINE2, "address-line2")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LINE3, "address-line3")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL4, "address-level4")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL3, "address-level3")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL2, "address-level2")
+AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL1, "address-level1")
+AUTOCOMPLETE_FIELD_NAME(COUNTRY, "country")
+AUTOCOMPLETE_FIELD_NAME(COUNTRY_NAME, "country-name")
+AUTOCOMPLETE_FIELD_NAME(POSTAL_CODE, "postal-code")
+
+// Credit Card types
+AUTOCOMPLETE_FIELD_NAME(CC_NAME, "cc-name")
+AUTOCOMPLETE_FIELD_NAME(CC_GIVEN_NAME, "cc-given-name")
+AUTOCOMPLETE_FIELD_NAME(CC_ADDITIONAL_NAME, "cc-additional-name")
+AUTOCOMPLETE_FIELD_NAME(CC_FAMILY_NAME, "cc-family-name")
+AUTOCOMPLETE_FIELD_NAME(CC_NUMBER, "cc-number")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP, "cc-exp")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP_MONTH, "cc-exp-month")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP_YEAR, "cc-exp-year")
+AUTOCOMPLETE_FIELD_NAME(CC_CSC, "cc-csc")
+AUTOCOMPLETE_FIELD_NAME(CC_TYPE, "cc-type")
+
+// Transaction types
+AUTOCOMPLETE_FIELD_NAME(TRANSACTION_CURRENCY, "transaction-currency")
+AUTOCOMPLETE_FIELD_NAME(TRANSACTION_AMOUNT, "transaction-amount")
+
+// Additional field types
+AUTOCOMPLETE_FIELD_NAME(LANGUAGE, "language")
+AUTOCOMPLETE_FIELD_NAME(BDAY, "bday")
+AUTOCOMPLETE_FIELD_NAME(BDAY_DAY, "bday-day")
+AUTOCOMPLETE_FIELD_NAME(BDAY_MONTH, "bday-month")
+AUTOCOMPLETE_FIELD_NAME(BDAY_YEAR, "bday-year")
+AUTOCOMPLETE_FIELD_NAME(SEX, "sex")
+AUTOCOMPLETE_FIELD_NAME(URL, "url")
+AUTOCOMPLETE_FIELD_NAME(PHOTO, "photo")
+
+// Field for which we don't want to automatically persist the value e.g. in
+// session/form history.
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(OFF, "off")
+// passwords:
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(CURRENT_PASSWORD, "current-password")
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(NEW_PASSWORD, "new-password")
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(ONE_TIME_CODE, "one-time-code")
+// credit card numbers
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(CC_NUMBER, "cc-number")
+AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(CC_CSC, "cc-csc")
+
+// Contact category types
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL, "tel")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_COUNTRY_CODE, "tel-country-code")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_NATIONAL, "tel-national")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_AREA_CODE, "tel-area-code")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL, "tel-local")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL_PREFIX, "tel-local-prefix")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL_SUFFIX, "tel-local-suffix")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_EXTENSION, "tel-extension")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(EMAIL, "email")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(IMPP, "impp")
+
+AUTOCOMPLETE_FIELD_HINT(SHIPPING, "shipping")
+AUTOCOMPLETE_FIELD_HINT(BILLING, "billing")
+
+AUTOCOMPLETE_FIELD_CONTACT_HINT(HOME, "home")
+AUTOCOMPLETE_FIELD_CONTACT_HINT(WORK, "work")
+AUTOCOMPLETE_FIELD_CONTACT_HINT(MOBILE, "mobile")
+AUTOCOMPLETE_FIELD_CONTACT_HINT(FAX, "fax")
+AUTOCOMPLETE_FIELD_CONTACT_HINT(PAGER, "pager")
+
+AUTOCOMPLETE_CATEGORY(NORMAL, "normal")
+AUTOCOMPLETE_CATEGORY(CONTACT, "contact")
+//-----------------------------------------------------
+
+#ifdef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#undef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_FIELD_NAME
+#undef AUTOCOMPLETE_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_FIELD_NAME
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+#undef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_CONTACT_FIELD_NAME
+#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_CONTACT_FIELD_NAME
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_FIELD_HINT
+#undef AUTOCOMPLETE_FIELD_HINT
+#undef DEFINED_AUTOCOMPLETE_FIELD_HINT
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_FIELD_CONTACT_HINT
+#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
+#undef DEFINED_AUTOCOMPLETE_FIELD_CONTACT_HINT
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_CATEGORY
+#undef AUTOCOMPLETE_CATEGORY
+#undef DEFINED_AUTOCOMPLETE_CATEGORY
+#endif
diff --git a/dom/base/BarProps.cpp b/dom/base/BarProps.cpp
new file mode 100644
index 0000000000..6b72817320
--- /dev/null
+++ b/dom/base/BarProps.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BarProps.h"
+#include "mozilla/dom/BarPropBinding.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "nsIWebBrowserChrome.h"
+
+namespace mozilla::dom {
+
+//
+// Basic (virtual) BarProp class implementation
+//
+BarProp::BarProp(nsGlobalWindowInner* aWindow) : mDOMWindow(aWindow) {}
+
+BarProp::~BarProp() = default;
+
+nsPIDOMWindowInner* BarProp::GetParentObject() const { return mDOMWindow; }
+
+JSObject* BarProp::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BarProp_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BarProp, mDOMWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BarProp)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BarProp)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BarProp)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+bool BarProp::GetVisibleByIsPopup() {
+ // For web content, return the value defined by the spec, instead of
+ // the actual visibility of each bar.
+ //
+ // If this browsing context is created by requesting a popup, all
+ // `BarProp.visible` should return false.
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ if (!bc || bc->IsDiscarded()) {
+ return true;
+ }
+ bc = bc->Top();
+ bool isPopup = bc->GetIsPopupRequested();
+ return !isPopup;
+}
+
+bool BarProp::GetVisibleByFlag(uint32_t aChromeFlag, CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (aCallerType != CallerType::System) {
+ return GetVisibleByIsPopup();
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetBrowserChrome();
+ NS_ENSURE_TRUE(browserChrome, false);
+
+ uint32_t chromeFlags;
+
+ if (NS_FAILED(browserChrome->GetChromeFlags(&chromeFlags))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ return (chromeFlags & aChromeFlag);
+}
+
+void BarProp::SetVisibleByFlag(bool aVisible, uint32_t aChromeFlag,
+ CallerType aCallerType, ErrorResult& aRv) {
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetBrowserChrome();
+ NS_ENSURE_TRUE_VOID(browserChrome);
+
+ if (aCallerType != CallerType::System) {
+ return;
+ }
+
+ uint32_t chromeFlags;
+
+ if (NS_FAILED(browserChrome->GetChromeFlags(&chromeFlags))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (aVisible)
+ chromeFlags |= aChromeFlag;
+ else
+ chromeFlags &= ~aChromeFlag;
+
+ if (NS_FAILED(browserChrome->SetChromeFlags(chromeFlags))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+already_AddRefed<nsIWebBrowserChrome> BarProp::GetBrowserChrome() {
+ if (!mDOMWindow) {
+ return nullptr;
+ }
+
+ return mDOMWindow->GetWebBrowserChrome();
+}
+
+BrowsingContext* BarProp::GetBrowsingContext() {
+ if (!mDOMWindow) {
+ return nullptr;
+ }
+
+ return mDOMWindow->GetBrowsingContext();
+}
+
+//
+// MenubarProp class implementation
+//
+
+MenubarProp::MenubarProp(nsGlobalWindowInner* aWindow) : BarProp(aWindow) {}
+
+MenubarProp::~MenubarProp() = default;
+
+bool MenubarProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_MENUBAR,
+ aCallerType, aRv);
+}
+
+void MenubarProp::SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) {
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_MENUBAR,
+ aCallerType, aRv);
+}
+
+//
+// ToolbarProp class implementation
+//
+
+ToolbarProp::ToolbarProp(nsGlobalWindowInner* aWindow) : BarProp(aWindow) {}
+
+ToolbarProp::~ToolbarProp() = default;
+
+bool ToolbarProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_TOOLBAR,
+ aCallerType, aRv);
+}
+
+void ToolbarProp::SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) {
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_TOOLBAR,
+ aCallerType, aRv);
+}
+
+//
+// LocationbarProp class implementation
+//
+
+LocationbarProp::LocationbarProp(nsGlobalWindowInner* aWindow)
+ : BarProp(aWindow) {}
+
+LocationbarProp::~LocationbarProp() = default;
+
+bool LocationbarProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_LOCATIONBAR,
+ aCallerType, aRv);
+}
+
+void LocationbarProp::SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) {
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_LOCATIONBAR,
+ aCallerType, aRv);
+}
+
+//
+// PersonalbarProp class implementation
+//
+
+PersonalbarProp::PersonalbarProp(nsGlobalWindowInner* aWindow)
+ : BarProp(aWindow) {}
+
+PersonalbarProp::~PersonalbarProp() = default;
+
+bool PersonalbarProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR,
+ aCallerType, aRv);
+}
+
+void PersonalbarProp::SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) {
+ BarProp::SetVisibleByFlag(
+ aVisible, nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR, aCallerType, aRv);
+}
+
+//
+// StatusbarProp class implementation
+//
+
+StatusbarProp::StatusbarProp(nsGlobalWindowInner* aWindow) : BarProp(aWindow) {}
+
+StatusbarProp::~StatusbarProp() = default;
+
+bool StatusbarProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_STATUSBAR,
+ aCallerType, aRv);
+}
+
+void StatusbarProp::SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) {
+ return BarProp::SetVisibleByFlag(
+ aVisible, nsIWebBrowserChrome::CHROME_STATUSBAR, aCallerType, aRv);
+}
+
+//
+// ScrollbarsProp class implementation
+//
+
+ScrollbarsProp::ScrollbarsProp(nsGlobalWindowInner* aWindow)
+ : BarProp(aWindow) {}
+
+ScrollbarsProp::~ScrollbarsProp() = default;
+
+bool ScrollbarsProp::GetVisible(CallerType aCallerType, ErrorResult& aRv) {
+ if (aCallerType != CallerType::System) {
+ return BarProp::GetVisibleByIsPopup();
+ }
+
+ if (!mDOMWindow) {
+ return true;
+ }
+
+ nsIDocShell* ds = mDOMWindow->GetDocShell();
+ if (!ds) {
+ return true;
+ }
+
+ ScrollbarPreference pref = nsDocShell::Cast(ds)->ScrollbarPreference();
+ return pref != ScrollbarPreference::Never;
+}
+
+void ScrollbarsProp::SetVisible(bool aVisible, CallerType, ErrorResult&) {
+ /* Do nothing */
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/BarProps.h b/dom/base/BarProps.h
new file mode 100644
index 0000000000..f7c4c8b95a
--- /dev/null
+++ b/dom/base/BarProps.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* BarProps are the collection of little properties of DOM windows whose
+ only property of their own is "visible". They describe the window
+ chrome which can be made visible or not through JavaScript by setting
+ the appropriate property (window.menubar.visible)
+*/
+
+#ifndef mozilla_dom_BarProps_h
+#define mozilla_dom_BarProps_h
+
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BrowsingContext.h"
+
+class nsGlobalWindowInner;
+class nsIWebBrowserChrome;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+// Script "BarProp" object
+class BarProp : public nsISupports, public nsWrapperCache {
+ public:
+ explicit BarProp(nsGlobalWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(BarProp)
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ virtual bool GetVisible(CallerType aCallerType, ErrorResult& aRv) = 0;
+ virtual void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) = 0;
+
+ protected:
+ virtual ~BarProp();
+
+ bool GetVisibleByIsPopup();
+ bool GetVisibleByFlag(uint32_t aChromeFlag, CallerType aCallerType,
+ ErrorResult& aRv);
+ void SetVisibleByFlag(bool aVisible, uint32_t aChromeFlag,
+ CallerType aCallerType, ErrorResult& aRv);
+
+ already_AddRefed<nsIWebBrowserChrome> GetBrowserChrome();
+
+ BrowsingContext* GetBrowsingContext();
+
+ RefPtr<nsGlobalWindowInner> mDOMWindow;
+};
+
+// Script "menubar" object
+class MenubarProp final : public BarProp {
+ public:
+ explicit MenubarProp(nsGlobalWindowInner* aWindow);
+ virtual ~MenubarProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+// Script "toolbar" object
+class ToolbarProp final : public BarProp {
+ public:
+ explicit ToolbarProp(nsGlobalWindowInner* aWindow);
+ virtual ~ToolbarProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+// Script "locationbar" object
+class LocationbarProp final : public BarProp {
+ public:
+ explicit LocationbarProp(nsGlobalWindowInner* aWindow);
+ virtual ~LocationbarProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+// Script "personalbar" object
+class PersonalbarProp final : public BarProp {
+ public:
+ explicit PersonalbarProp(nsGlobalWindowInner* aWindow);
+ virtual ~PersonalbarProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+// Script "statusbar" object
+class StatusbarProp final : public BarProp {
+ public:
+ explicit StatusbarProp(nsGlobalWindowInner* aWindow);
+ virtual ~StatusbarProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+// Script "scrollbars" object
+class ScrollbarsProp final : public BarProp {
+ public:
+ explicit ScrollbarsProp(nsGlobalWindowInner* aWindow);
+ virtual ~ScrollbarsProp();
+
+ bool GetVisible(CallerType aCallerType, ErrorResult& aRv) override;
+ void SetVisible(bool aVisible, CallerType aCallerType,
+ ErrorResult& aRv) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_BarProps_h */
diff --git a/dom/base/BindContext.cpp b/dom/base/BindContext.cpp
new file mode 100644
index 0000000000..a4088ff174
--- /dev/null
+++ b/dom/base/BindContext.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/BrowsingContext.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+bool BindContext::AllowsAutoFocus() const {
+ if (!StaticPrefs::browser_autofocus()) {
+ return false;
+ }
+ if (!InUncomposedDoc()) {
+ return false;
+ }
+ if (mDoc.IsBeingUsedAsImage()) {
+ return false;
+ }
+ return IsSameOriginAsTop();
+}
+
+bool BindContext::IsSameOriginAsTop() const {
+ BrowsingContext* browsingContext = mDoc.GetBrowsingContext();
+ if (!browsingContext) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow();
+ if (!topWindow) {
+ // If we don't have a DOMWindow, We are not in same origin.
+ return false;
+ }
+
+ Document* topLevelDocument = topWindow->GetExtantDoc();
+ if (!topLevelDocument) {
+ return false;
+ }
+
+ return NS_SUCCEEDED(nsContentUtils::CheckSameOrigin(topLevelDocument, &mDoc));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/BindContext.h b/dom/base/BindContext.h
new file mode 100644
index 0000000000..4ad0d7d9eb
--- /dev/null
+++ b/dom/base/BindContext.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* State that is passed down to BindToTree. */
+
+#ifndef mozilla_dom_BindContext_h__
+#define mozilla_dom_BindContext_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsINode.h"
+
+namespace mozilla::dom {
+
+class Document;
+
+struct MOZ_STACK_CLASS BindContext final {
+ // The document that owns the tree we're getting bound to.
+ //
+ // This is mostly an optimization to avoid silly pointer-chases to get the
+ // OwnerDoc().
+ Document& OwnerDoc() const { return mDoc; }
+
+ // Whether we're getting connected.
+ //
+ // https://dom.spec.whatwg.org/#connected
+ bool InComposedDoc() const { return mInComposedDoc; }
+
+ // Whether we're getting bound to the document tree.
+ //
+ // https://dom.spec.whatwg.org/#in-a-document-tree
+ bool InUncomposedDoc() const { return mInUncomposedDoc; }
+
+ Document* GetComposedDoc() const { return mInComposedDoc ? &mDoc : nullptr; }
+
+ Document* GetUncomposedDoc() const {
+ return mInUncomposedDoc ? &mDoc : nullptr;
+ }
+
+ // Whether our subtree root is changing as a result of this operation.
+ bool SubtreeRootChanges() const { return mSubtreeRootChanges; }
+
+ // Autofocus is allowed only if the is in the same origin as the top level
+ // document.
+ // https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute:same-origin
+ // In addition, the document should not be already loaded and the
+ // "browser.autofocus" preference should be 'true'.
+ bool AllowsAutoFocus() const;
+
+ // This constructor should be used for regular appends to content.
+ explicit BindContext(nsINode& aParent)
+ : mDoc(*aParent.OwnerDoc()),
+ mInComposedDoc(aParent.IsInComposedDoc()),
+ mInUncomposedDoc(aParent.IsInUncomposedDoc()),
+ mSubtreeRootChanges(true) {}
+
+ // When re-binding a shadow host into a tree, we re-bind all the shadow tree
+ // from the root. In that case, the shadow tree contents remain within the
+ // same subtree root. So children should avoid doing silly things like adding
+ // themselves to the ShadowRoot's id table twice or what not.
+ //
+ // This constructor is only meant to be used in that situation.
+ explicit BindContext(ShadowRoot& aShadowRoot)
+ : mDoc(*aShadowRoot.OwnerDoc()),
+ mInComposedDoc(aShadowRoot.IsInComposedDoc()),
+ mInUncomposedDoc(false),
+ mSubtreeRootChanges(false) {}
+
+ // This constructor is meant to be used when inserting native-anonymous
+ // children into a subtree.
+ enum ForNativeAnonymous { ForNativeAnonymous };
+ BindContext(Element& aParentElement, enum ForNativeAnonymous)
+ : mDoc(*aParentElement.OwnerDoc()),
+ mInComposedDoc(aParentElement.IsInComposedDoc()),
+ mInUncomposedDoc(aParentElement.IsInUncomposedDoc()),
+ mSubtreeRootChanges(true) {
+ MOZ_ASSERT(mInComposedDoc, "Binding NAC in a disconnected subtree?");
+ }
+
+ private:
+ // Returns true iff the document is in the same origin as the top level
+ // document.
+ bool IsSameOriginAsTop() const;
+
+ Document& mDoc;
+
+ const bool mInComposedDoc;
+ const bool mInUncomposedDoc;
+
+ // Whether the bind operation will change the subtree root of the content
+ // we're binding.
+ const bool mSubtreeRootChanges;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/BodyConsumer.cpp b/dom/base/BodyConsumer.cpp
new file mode 100644
index 0000000000..6782fe36e5
--- /dev/null
+++ b/dom/base/BodyConsumer.cpp
@@ -0,0 +1,835 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BodyConsumer.h"
+
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/BodyUtil.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/FileCreatorHelper.h"
+#include "mozilla/dom/MutableBlobStreamListener.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TaskQueue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIStreamLoader.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsIInputStream.h"
+
+// Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
+// replaced by FileCreatorHelper#CreateFileW.
+#ifdef CreateFile
+# undef CreateFile
+#endif
+
+namespace mozilla::dom {
+
+namespace {
+
+class BeginConsumeBodyRunnable final : public Runnable {
+ public:
+ BeginConsumeBodyRunnable(BodyConsumer* aConsumer,
+ ThreadSafeWorkerRef* aWorkerRef)
+ : Runnable("BeginConsumeBodyRunnable"),
+ mBodyConsumer(aConsumer),
+ mWorkerRef(aWorkerRef) {}
+
+ NS_IMETHOD
+ Run() override {
+ mBodyConsumer->BeginConsumeBodyMainThread(mWorkerRef);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<BodyConsumer> mBodyConsumer;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+/*
+ * Called on successfully reading the complete stream.
+ */
+class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable {
+ RefPtr<BodyConsumer> mBodyConsumer;
+ nsresult mStatus;
+ uint32_t mLength;
+ uint8_t* mResult;
+
+ public:
+ ContinueConsumeBodyRunnable(BodyConsumer* aBodyConsumer,
+ WorkerPrivate* aWorkerPrivate, nsresult aStatus,
+ uint32_t aLength, uint8_t* aResult)
+ : MainThreadWorkerRunnable(aWorkerPrivate),
+ mBodyConsumer(aBodyConsumer),
+ mStatus(aStatus),
+ mLength(aLength),
+ mResult(aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mBodyConsumer->ContinueConsumeBody(mStatus, mLength, mResult);
+ return true;
+ }
+};
+
+// ControlRunnable used to complete the releasing of resources on the worker
+// thread when already shutting down.
+class AbortConsumeBodyControlRunnable final
+ : public MainThreadWorkerControlRunnable {
+ RefPtr<BodyConsumer> mBodyConsumer;
+
+ public:
+ AbortConsumeBodyControlRunnable(BodyConsumer* aBodyConsumer,
+ WorkerPrivate* aWorkerPrivate)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mBodyConsumer(aBodyConsumer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mBodyConsumer->ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr,
+ true /* shutting down */);
+ return true;
+ }
+};
+
+/*
+ * In case of failure to create a stream pump or dispatch stream completion to
+ * worker, ensure we cleanup properly. Thread agnostic.
+ */
+class MOZ_STACK_CLASS AutoFailConsumeBody final {
+ public:
+ AutoFailConsumeBody(BodyConsumer* aBodyConsumer,
+ ThreadSafeWorkerRef* aWorkerRef)
+ : mBodyConsumer(aBodyConsumer), mWorkerRef(aWorkerRef) {}
+
+ ~AutoFailConsumeBody() {
+ AssertIsOnMainThread();
+
+ if (!mBodyConsumer) {
+ return;
+ }
+
+ // Web Worker
+ if (mWorkerRef) {
+ RefPtr<AbortConsumeBodyControlRunnable> r =
+ new AbortConsumeBodyControlRunnable(mBodyConsumer,
+ mWorkerRef->Private());
+ if (!r->Dispatch()) {
+ MOZ_CRASH("We are going to leak");
+ }
+ return;
+ }
+
+ // Main-thread
+ mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr);
+ }
+
+ void DontFail() { mBodyConsumer = nullptr; }
+
+ private:
+ RefPtr<BodyConsumer> mBodyConsumer;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+/*
+ * Called on successfully reading the complete stream for Blob.
+ */
+class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable {
+ RefPtr<BodyConsumer> mBodyConsumer;
+ RefPtr<BlobImpl> mBlobImpl;
+
+ public:
+ ContinueConsumeBlobBodyRunnable(BodyConsumer* aBodyConsumer,
+ WorkerPrivate* aWorkerPrivate,
+ BlobImpl* aBlobImpl)
+ : MainThreadWorkerRunnable(aWorkerPrivate),
+ mBodyConsumer(aBodyConsumer),
+ mBlobImpl(aBlobImpl) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mBlobImpl);
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mBodyConsumer->ContinueConsumeBlobBody(mBlobImpl);
+ return true;
+ }
+};
+
+// ControlRunnable used to complete the releasing of resources on the worker
+// thread when already shutting down.
+class AbortConsumeBlobBodyControlRunnable final
+ : public MainThreadWorkerControlRunnable {
+ RefPtr<BodyConsumer> mBodyConsumer;
+
+ public:
+ AbortConsumeBlobBodyControlRunnable(BodyConsumer* aBodyConsumer,
+ WorkerPrivate* aWorkerPrivate)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mBodyConsumer(aBodyConsumer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mBodyConsumer->ContinueConsumeBlobBody(nullptr, true /* shutting down */);
+ return true;
+ }
+};
+
+class ConsumeBodyDoneObserver final : public nsIStreamLoaderObserver,
+ public MutableBlobStorageCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ ConsumeBodyDoneObserver(BodyConsumer* aBodyConsumer,
+ ThreadSafeWorkerRef* aWorkerRef)
+ : mBodyConsumer(aBodyConsumer), mWorkerRef(aWorkerRef) {}
+
+ NS_IMETHOD
+ OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCtxt,
+ nsresult aStatus, uint32_t aResultLength,
+ const uint8_t* aResult) override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The loading is completed. Let's nullify the pump before continuing the
+ // consuming of the body.
+ mBodyConsumer->NullifyConsumeBodyPump();
+
+ uint8_t* nonconstResult = const_cast<uint8_t*>(aResult);
+
+ // Main-thread.
+ if (!mWorkerRef) {
+ mBodyConsumer->ContinueConsumeBody(aStatus, aResultLength,
+ nonconstResult);
+ // The caller is responsible for data.
+ return NS_SUCCESS_ADOPTED_DATA;
+ }
+
+ // Web Worker.
+ {
+ RefPtr<ContinueConsumeBodyRunnable> r = new ContinueConsumeBodyRunnable(
+ mBodyConsumer, mWorkerRef->Private(), aStatus, aResultLength,
+ nonconstResult);
+ if (r->Dispatch()) {
+ // The caller is responsible for data.
+ return NS_SUCCESS_ADOPTED_DATA;
+ }
+ }
+
+ // The worker is shutting down. Let's use a control runnable to complete the
+ // shutting down procedure.
+
+ RefPtr<AbortConsumeBodyControlRunnable> r =
+ new AbortConsumeBodyControlRunnable(mBodyConsumer,
+ mWorkerRef->Private());
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We haven't taken ownership of the data.
+ return NS_OK;
+ }
+
+ virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
+ BlobImpl* aBlobImpl, nsresult aRv) override {
+ // On error.
+ if (NS_FAILED(aRv)) {
+ OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr);
+ return;
+ }
+
+ // The loading is completed. Let's nullify the pump before continuing the
+ // consuming of the body.
+ mBodyConsumer->NullifyConsumeBodyPump();
+
+ mBodyConsumer->OnBlobResult(aBlobImpl, mWorkerRef);
+ }
+
+ private:
+ ~ConsumeBodyDoneObserver() = default;
+
+ RefPtr<BodyConsumer> mBodyConsumer;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+NS_IMPL_ISUPPORTS(ConsumeBodyDoneObserver, nsIStreamLoaderObserver)
+
+} // namespace
+
+/* static */ already_AddRefed<Promise> BodyConsumer::Create(
+ nsIGlobalObject* aGlobal, nsISerialEventTarget* aMainThreadEventTarget,
+ nsIInputStream* aBodyStream, AbortSignalImpl* aSignalImpl,
+ ConsumeType aType, const nsACString& aBodyBlobURISpec,
+ const nsAString& aBodyLocalPath, const nsACString& aBodyMimeType,
+ const nsACString& aMixedCaseMimeType,
+ MutableBlobStorage::MutableBlobStorageType aBlobStorageType,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aBodyStream);
+ MOZ_ASSERT(aMainThreadEventTarget);
+
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<BodyConsumer> consumer =
+ new BodyConsumer(aMainThreadEventTarget, aGlobal, aBodyStream, promise,
+ aType, aBodyBlobURISpec, aBodyLocalPath, aBodyMimeType,
+ aMixedCaseMimeType, aBlobStorageType);
+
+ RefPtr<ThreadSafeWorkerRef> workerRef;
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<StrongWorkerRef> strongWorkerRef =
+ StrongWorkerRef::Create(workerPrivate, "BodyConsumer", [consumer]() {
+ consumer->mConsumePromise = nullptr;
+ consumer->mBodyConsumed = true;
+ consumer->ReleaseObject();
+ consumer->ShutDownMainThreadConsuming();
+ });
+ if (NS_WARN_IF(!strongWorkerRef)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ workerRef = new ThreadSafeWorkerRef(strongWorkerRef);
+ } else {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!os)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ aRv = os->AddObserver(consumer, DOM_WINDOW_DESTROYED_TOPIC, true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = os->AddObserver(consumer, DOM_WINDOW_FROZEN_TOPIC, true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> r = new BeginConsumeBodyRunnable(consumer, workerRef);
+ aRv = aMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (aSignalImpl) {
+ consumer->Follow(aSignalImpl);
+ }
+
+ return promise.forget();
+}
+
+void BodyConsumer::ReleaseObject() {
+ AssertIsOnTargetThread();
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
+ }
+ }
+
+ mGlobal = nullptr;
+
+ Unfollow();
+}
+
+BodyConsumer::BodyConsumer(
+ nsISerialEventTarget* aMainThreadEventTarget,
+ nsIGlobalObject* aGlobalObject, nsIInputStream* aBodyStream,
+ Promise* aPromise, ConsumeType aType, const nsACString& aBodyBlobURISpec,
+ const nsAString& aBodyLocalPath, const nsACString& aBodyMimeType,
+ const nsACString& aMixedCaseMimeType,
+ MutableBlobStorage::MutableBlobStorageType aBlobStorageType)
+ : mTargetThread(NS_GetCurrentThread()),
+ mMainThreadEventTarget(aMainThreadEventTarget),
+ mBodyStream(aBodyStream),
+ mBlobStorageType(aBlobStorageType),
+ mBodyMimeType(aBodyMimeType),
+ mMixedCaseMimeType(aMixedCaseMimeType),
+ mBodyBlobURISpec(aBodyBlobURISpec),
+ mBodyLocalPath(aBodyLocalPath),
+ mGlobal(aGlobalObject),
+ mConsumeType(aType),
+ mConsumePromise(aPromise),
+ mBodyConsumed(false),
+ mShuttingDown(false) {
+ MOZ_ASSERT(aMainThreadEventTarget);
+ MOZ_ASSERT(aBodyStream);
+ MOZ_ASSERT(aPromise);
+}
+
+BodyConsumer::~BodyConsumer() = default;
+
+void BodyConsumer::AssertIsOnTargetThread() const {
+ MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread);
+}
+
+namespace {
+
+class FileCreationHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static void Create(Promise* aPromise, BodyConsumer* aConsumer,
+ ThreadSafeWorkerRef* aWorkerRef) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPromise);
+
+ RefPtr<FileCreationHandler> handler =
+ new FileCreationHandler(aConsumer, aWorkerRef);
+ aPromise->AppendNativeHandler(handler);
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aValue.isObject())) {
+ mConsumer->OnBlobResult(nullptr, mWorkerRef);
+ return;
+ }
+
+ RefPtr<Blob> blob;
+ if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
+ mConsumer->OnBlobResult(nullptr, mWorkerRef);
+ return;
+ }
+
+ mConsumer->OnBlobResult(blob->Impl(), mWorkerRef);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ AssertIsOnMainThread();
+
+ mConsumer->OnBlobResult(nullptr, mWorkerRef);
+ }
+
+ private:
+ FileCreationHandler(BodyConsumer* aConsumer, ThreadSafeWorkerRef* aWorkerRef)
+ : mConsumer(aConsumer), mWorkerRef(aWorkerRef) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aConsumer);
+ }
+
+ ~FileCreationHandler() = default;
+
+ RefPtr<BodyConsumer> mConsumer;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+NS_IMPL_ISUPPORTS0(FileCreationHandler)
+
+} // namespace
+
+nsresult BodyConsumer::GetBodyLocalFile(nsIFile** aFile) const {
+ AssertIsOnMainThread();
+
+ if (!mBodyLocalPath.Length()) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = file->InitWithPath(mBodyLocalPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ bool isDir;
+ rv = file->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isDir) {
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+/*
+ * BeginConsumeBodyMainThread() will automatically reject the consume promise
+ * and clean up on any failures, so there is no need for callers to do so,
+ * reflected in a lack of error return code.
+ */
+void BodyConsumer::BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWorkerRef) {
+ AssertIsOnMainThread();
+
+ AutoFailConsumeBody autoReject(this, aWorkerRef);
+
+ if (mShuttingDown) {
+ // We haven't started yet, but we have been terminated. AutoFailConsumeBody
+ // will dispatch a runnable to release resources.
+ return;
+ }
+
+ if (mConsumeType == CONSUME_BLOB) {
+ nsresult rv;
+
+ // If we're trying to consume a blob, and the request was for a blob URI,
+ // then just consume that URI's blob instance.
+ if (!mBodyBlobURISpec.IsEmpty()) {
+ RefPtr<BlobImpl> blobImpl;
+ rv = NS_GetBlobForBlobURISpec(mBodyBlobURISpec, getter_AddRefs(blobImpl));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !blobImpl) {
+ return;
+ }
+ autoReject.DontFail();
+ DispatchContinueConsumeBlobBody(blobImpl, aWorkerRef);
+ return;
+ }
+
+ // If we're trying to consume a blob, and the request was for a local
+ // file, then generate and return a File blob.
+ nsCOMPtr<nsIFile> file;
+ rv = GetBodyLocalFile(getter_AddRefs(file));
+ if (!NS_WARN_IF(NS_FAILED(rv)) && file && !aWorkerRef) {
+ ChromeFilePropertyBag bag;
+ CopyUTF8toUTF16(mBodyMimeType, bag.mType);
+
+ ErrorResult error;
+ RefPtr<Promise> promise =
+ FileCreatorHelper::CreateFile(mGlobal, file, bag, true, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return;
+ }
+
+ autoReject.DontFail();
+ FileCreationHandler::Create(promise, this, aWorkerRef);
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv =
+ NS_NewInputStreamPump(getter_AddRefs(pump), mBodyStream.forget(), 0, 0,
+ false, mMainThreadEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<ConsumeBodyDoneObserver> p =
+ new ConsumeBodyDoneObserver(this, aWorkerRef);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ if (mConsumeType == CONSUME_BLOB) {
+ listener = new MutableBlobStreamListener(mBlobStorageType, mBodyMimeType, p,
+ mMainThreadEventTarget);
+ } else {
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), p);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ listener = loader;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Now that everything succeeded, we can assign the pump to a pointer that
+ // stays alive for the lifetime of the BodyConsumer.
+ mConsumeBodyPump = pump;
+
+ // It is ok for retargeting to fail and reads to happen on the main thread.
+ autoReject.DontFail();
+
+ // Try to retarget, otherwise fall back to main thread.
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
+ if (rr) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(sts.forget(), "BodyConsumer STS Delivery Queue");
+ rv = rr->RetargetDeliveryTo(queue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Retargeting failed");
+ }
+ }
+}
+
+/*
+ * OnBlobResult() is called when a blob body is ready to be consumed (when its
+ * network transfer completes in BeginConsumeBodyRunnable or its local File has
+ * been wrapped by FileCreationHandler). The blob is sent to the target thread
+ * and ContinueConsumeBody is called.
+ */
+void BodyConsumer::OnBlobResult(BlobImpl* aBlobImpl,
+ ThreadSafeWorkerRef* aWorkerRef) {
+ AssertIsOnMainThread();
+
+ DispatchContinueConsumeBlobBody(aBlobImpl, aWorkerRef);
+}
+
+void BodyConsumer::DispatchContinueConsumeBlobBody(
+ BlobImpl* aBlobImpl, ThreadSafeWorkerRef* aWorkerRef) {
+ AssertIsOnMainThread();
+
+ // Main-thread.
+ if (!aWorkerRef) {
+ if (aBlobImpl) {
+ ContinueConsumeBlobBody(aBlobImpl);
+ } else {
+ ContinueConsumeBody(NS_ERROR_DOM_ABORT_ERR, 0, nullptr);
+ }
+ return;
+ }
+
+ // Web Worker.
+ if (aBlobImpl) {
+ RefPtr<ContinueConsumeBlobBodyRunnable> r =
+ new ContinueConsumeBlobBodyRunnable(this, aWorkerRef->Private(),
+ aBlobImpl);
+
+ if (r->Dispatch()) {
+ return;
+ }
+ } else {
+ RefPtr<ContinueConsumeBodyRunnable> r = new ContinueConsumeBodyRunnable(
+ this, aWorkerRef->Private(), NS_ERROR_DOM_ABORT_ERR, 0, nullptr);
+
+ if (r->Dispatch()) {
+ return;
+ }
+ }
+
+ // The worker is shutting down. Let's use a control runnable to complete the
+ // shutting down procedure.
+
+ RefPtr<AbortConsumeBlobBodyControlRunnable> r =
+ new AbortConsumeBlobBodyControlRunnable(this, aWorkerRef->Private());
+
+ Unused << NS_WARN_IF(!r->Dispatch());
+}
+
+/*
+ * ContinueConsumeBody() is to be called on the target thread whenever the
+ * final result of the fetch is known. The fetch promise is resolved or
+ * rejected based on whether the fetch succeeded, and the body can be
+ * converted into the expected type of JS object.
+ */
+void BodyConsumer::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength,
+ uint8_t* aResult, bool aShuttingDown) {
+ AssertIsOnTargetThread();
+
+ // This makes sure that we free the data correctly.
+ auto autoFree = mozilla::MakeScopeExit([&] { free(aResult); });
+
+ if (mBodyConsumed) {
+ return;
+ }
+ mBodyConsumed = true;
+
+ MOZ_ASSERT(mConsumePromise);
+ RefPtr<Promise> localPromise = std::move(mConsumePromise);
+
+ RefPtr<BodyConsumer> self = this;
+ auto autoReleaseObject =
+ mozilla::MakeScopeExit([self] { self->ReleaseObject(); });
+
+ if (aShuttingDown) {
+ // If shutting down, we don't want to resolve any promise.
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ // Per
+ // https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream
+ // Decoding errors should reject with a TypeError
+ if (aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
+ localPromise->MaybeRejectWithTypeError<MSG_DOM_DECODING_FAILED>();
+ } else if (aStatus == NS_ERROR_DOM_WRONG_TYPE_ERR) {
+ localPromise->MaybeRejectWithTypeError<MSG_FETCH_BODY_WRONG_TYPE>();
+ } else {
+ localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+ }
+
+ // Don't warn here since we warned above.
+ if (NS_FAILED(aStatus)) {
+ return;
+ }
+
+ // Finish successfully consuming body according to type.
+ MOZ_ASSERT(aResult);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ localPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+ ErrorResult error;
+
+ switch (mConsumeType) {
+ case CONSUME_ARRAYBUFFER: {
+ JS::Rooted<JSObject*> arrayBuffer(cx);
+ BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
+ error);
+
+ if (!error.Failed()) {
+ JS::Rooted<JS::Value> val(cx);
+ val.setObjectOrNull(arrayBuffer);
+
+ localPromise->MaybeResolve(val);
+ // ArrayBuffer takes over ownership.
+ aResult = nullptr;
+ }
+ break;
+ }
+ case CONSUME_BLOB: {
+ MOZ_CRASH("This should not happen.");
+ break;
+ }
+ case CONSUME_FORMDATA: {
+ nsCString data;
+ data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
+ aResult = nullptr;
+
+ RefPtr<dom::FormData> fd = BodyUtil::ConsumeFormData(
+ mGlobal, mBodyMimeType, mMixedCaseMimeType, data, error);
+ if (!error.Failed()) {
+ localPromise->MaybeResolve(fd);
+ }
+ break;
+ }
+ case CONSUME_TEXT:
+ // fall through handles early exit.
+ case CONSUME_JSON: {
+ nsString decoded;
+ if (NS_SUCCEEDED(
+ BodyUtil::ConsumeText(aResultLength, aResult, decoded))) {
+ if (mConsumeType == CONSUME_TEXT) {
+ localPromise->MaybeResolve(decoded);
+ } else {
+ JS::Rooted<JS::Value> json(cx);
+ BodyUtil::ConsumeJson(cx, &json, decoded, error);
+ if (!error.Failed()) {
+ localPromise->MaybeResolve(json);
+ }
+ }
+ };
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected consume body type");
+ }
+
+ error.WouldReportJSException();
+ if (error.Failed()) {
+ localPromise->MaybeReject(std::move(error));
+ }
+}
+
+void BodyConsumer::ContinueConsumeBlobBody(BlobImpl* aBlobImpl,
+ bool aShuttingDown) {
+ AssertIsOnTargetThread();
+ MOZ_ASSERT(mConsumeType == CONSUME_BLOB);
+
+ if (mBodyConsumed) {
+ return;
+ }
+ mBodyConsumed = true;
+
+ MOZ_ASSERT(mConsumePromise);
+ RefPtr<Promise> localPromise = std::move(mConsumePromise);
+
+ if (!aShuttingDown) {
+ RefPtr<dom::Blob> blob = dom::Blob::Create(mGlobal, aBlobImpl);
+ if (NS_WARN_IF(!blob)) {
+ localPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ localPromise->MaybeResolve(blob);
+ }
+
+ ReleaseObject();
+}
+
+void BodyConsumer::ShutDownMainThreadConsuming() {
+ if (!NS_IsMainThread()) {
+ RefPtr<BodyConsumer> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "BodyConsumer::ShutDownMainThreadConsuming",
+ [self]() { self->ShutDownMainThreadConsuming(); });
+
+ mMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ // We need this because maybe, mConsumeBodyPump has not been created yet. We
+ // must be sure that we don't try to do it.
+ mShuttingDown = true;
+
+ if (mConsumeBodyPump) {
+ mConsumeBodyPump->CancelWithReason(
+ NS_BINDING_ABORTED, "BodyConsumer::ShutDownMainThreadConsuming"_ns);
+ mConsumeBodyPump = nullptr;
+ }
+}
+
+NS_IMETHODIMP BodyConsumer::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
+ (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0));
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (SameCOMIdentity(aSubject, window)) {
+ ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr);
+ }
+
+ return NS_OK;
+}
+
+void BodyConsumer::RunAbortAlgorithm() {
+ AssertIsOnTargetThread();
+ ShutDownMainThreadConsuming();
+ ContinueConsumeBody(NS_ERROR_DOM_ABORT_ERR, 0, nullptr);
+}
+
+NS_IMPL_ISUPPORTS(BodyConsumer, nsIObserver, nsISupportsWeakReference)
+
+} // namespace mozilla::dom
diff --git a/dom/base/BodyConsumer.h b/dom/base/BodyConsumer.h
new file mode 100644
index 0000000000..459f2ce940
--- /dev/null
+++ b/dom/base/BodyConsumer.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BodyConsumer_h
+#define mozilla_dom_BodyConsumer_h
+
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/MutableBlobStorage.h"
+#include "nsIInputStreamPump.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+class nsIThread;
+
+namespace mozilla::dom {
+
+class Promise;
+class ThreadSafeWorkerRef;
+
+// In order to keep alive the object all the time, we use a ThreadSafeWorkerRef,
+// if created on workers.
+class BodyConsumer final : public nsIObserver,
+ public nsSupportsWeakReference,
+ public AbortFollower {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ enum ConsumeType {
+ CONSUME_ARRAYBUFFER,
+ CONSUME_BLOB,
+ CONSUME_FORMDATA,
+ CONSUME_JSON,
+ CONSUME_TEXT,
+ };
+
+ /**
+ * Returns a promise which will be resolved when the body is completely
+ * consumed and converted to the wanted type (See ConsumeType).
+ *
+ * @param aGlobal the global to construct the Promise.
+ * @param aMainThreadEventTarget the main-thread event target. The reading
+ * needs to start on the main-thread because of nsIInputStreamPump.
+ * @param aBodyStream the stream to read.
+ * @param aSignalImpl an AbortSignal object. Optional.
+ * @param aType the consume type.
+ * @param aBodyBlobURISpec this is used only if the consume type is
+ * CONSUME_BLOB. Optional.
+ * @param aBodyLocalPath local path in case the blob is created from a local
+ * file. Used only by CONSUME_BLOB. Optional.
+ * @param aBodyMimeType the mime-type for blob. Used only by CONSUME_BLOB.
+ * Optional.
+ * @param aMixedCaseMimeType is needed to get mixed case multipart
+ * boundary value to FormDataParser.
+ * @param aBlobStorageType Blobs can be saved in temporary file. This is the
+ * type of blob storage to use. Used only by CONSUME_BLOB.
+ * @param aRv An ErrorResult.
+ */
+ static already_AddRefed<Promise> Create(
+ nsIGlobalObject* aGlobal, nsISerialEventTarget* aMainThreadEventTarget,
+ nsIInputStream* aBodyStream, AbortSignalImpl* aSignalImpl,
+ ConsumeType aType, const nsACString& aBodyBlobURISpec,
+ const nsAString& aBodyLocalPath, const nsACString& aBodyMimeType,
+ const nsACString& aMixedCaseMimeType,
+ MutableBlobStorage::MutableBlobStorageType aBlobStorageType,
+ ErrorResult& aRv);
+
+ void ReleaseObject();
+
+ void BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWorkerRef);
+
+ void OnBlobResult(BlobImpl* aBlobImpl,
+ ThreadSafeWorkerRef* aWorkerRef = nullptr);
+
+ void ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult,
+ bool aShuttingDown = false);
+
+ void ContinueConsumeBlobBody(BlobImpl* aBlobImpl, bool aShuttingDown = false);
+
+ void DispatchContinueConsumeBlobBody(BlobImpl* aBlobImpl,
+ ThreadSafeWorkerRef* aWorkerRef);
+
+ void ShutDownMainThreadConsuming();
+
+ void NullifyConsumeBodyPump() {
+ mShuttingDown = true;
+ mConsumeBodyPump = nullptr;
+ }
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ private:
+ BodyConsumer(nsISerialEventTarget* aMainThreadEventTarget,
+ nsIGlobalObject* aGlobalObject, nsIInputStream* aBodyStream,
+ Promise* aPromise, ConsumeType aType,
+ const nsACString& aBodyBlobURISpec,
+ const nsAString& aBodyLocalPath, const nsACString& aBodyMimeType,
+ const nsACString& aMixedCaseMimeType,
+ MutableBlobStorage::MutableBlobStorageType aBlobStorageType);
+
+ ~BodyConsumer();
+
+ nsresult GetBodyLocalFile(nsIFile** aFile) const;
+
+ void AssertIsOnTargetThread() const;
+
+ nsCOMPtr<nsIThread> mTargetThread;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+
+ // This is nullified when the consuming of the body starts.
+ nsCOMPtr<nsIInputStream> mBodyStream;
+
+ MutableBlobStorage::MutableBlobStorageType mBlobStorageType;
+ nsCString mBodyMimeType;
+ nsCString mMixedCaseMimeType;
+
+ nsCString mBodyBlobURISpec;
+ nsString mBodyLocalPath;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // Touched on the main-thread only.
+ nsCOMPtr<nsIInputStreamPump> mConsumeBodyPump;
+
+ // Only ever set once, always on target thread.
+ ConsumeType mConsumeType;
+ RefPtr<Promise> mConsumePromise;
+
+ // touched only on the target thread.
+ bool mBodyConsumed;
+
+ // touched only on the main-thread.
+ bool mShuttingDown;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_BodyConsumer_h
diff --git a/dom/base/BodyUtil.cpp b/dom/base/BodyUtil.cpp
new file mode 100644
index 0000000000..206fa52ebf
--- /dev/null
+++ b/dom/base/BodyUtil.cpp
@@ -0,0 +1,474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BodyUtil.h"
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/MimeType.h"
+#include "nsCRT.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsDOMString.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsURLHelper.h"
+
+#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
+#include "js/JSON.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// Reads over a CRLF and positions start after it.
+static bool PushOverLine(nsACString::const_iterator& aStart,
+ const nsACString::const_iterator& aEnd) {
+ if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
+ ++aStart; // advance to after CRLF
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
+ * This does not respect any encoding specified per entry, using UTF-8
+ * throughout. This is as the Fetch spec states in the consume body algorithm.
+ * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
+ * unlike Necko we do not have to deal with receiving incomplete chunks of data.
+ *
+ * This parser will fail the entire parse on any invalid entry, so it will
+ * never return a partially filled FormData.
+ * The content-disposition header is used to figure out the name and filename
+ * entries. The inclusion of the filename parameter decides if the entry is
+ * inserted into the FormData as a string or a File.
+ *
+ * File blobs are copies of the underlying data string since we cannot adopt
+ * char* chunks embedded within the larger body without significant effort.
+ * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
+ * friends to figure out if Fetch ends up copying big blobs to see if this is
+ * worth optimizing.
+ */
+class MOZ_STACK_CLASS FormDataParser {
+ private:
+ RefPtr<FormData> mFormData;
+ nsCString mMimeType;
+ nsCString mMixedCaseMimeType;
+ nsCString mData;
+
+ // Entry state, reset in START_PART.
+ nsCString mName;
+ nsCString mFilename;
+ nsCString mContentType;
+
+ enum {
+ START_PART,
+ PARSE_HEADER,
+ PARSE_BODY,
+ } mState;
+
+ nsIGlobalObject* mParentObject;
+
+ // Reads over a boundary and sets start to the position after the end of the
+ // boundary. Returns false if no boundary is found immediately.
+ bool PushOverBoundary(const nsACString& aBoundaryString,
+ nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd) {
+ // We copy the end iterator to keep the original pointing to the real end
+ // of the string.
+ nsACString::const_iterator end(aEnd);
+ const char* beginning = aStart.get();
+ if (FindInReadable(aBoundaryString, aStart, end)) {
+ // We either should find the body immediately, or after 2 chars with the
+ // 2 chars being '-', everything else is failure.
+ if ((aStart.get() - beginning) == 0) {
+ aStart.advance(aBoundaryString.Length());
+ return true;
+ }
+
+ if ((aStart.get() - beginning) == 2) {
+ if (*(--aStart) == '-' && *(--aStart) == '-') {
+ aStart.advance(aBoundaryString.Length() + 2);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool ParseHeader(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd, bool* aWasEmptyHeader) {
+ nsAutoCString headerName, headerValue;
+ if (!FetchUtil::ExtractHeader(aStart, aEnd, headerName, headerValue,
+ aWasEmptyHeader)) {
+ return false;
+ }
+ if (*aWasEmptyHeader) {
+ return true;
+ }
+
+ if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
+ bool seenFormData = false;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(headerValue, ';').ToRange()) {
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ if (token.EqualsLiteral("form-data")) {
+ seenFormData = true;
+ continue;
+ }
+
+ if (seenFormData && StringBeginsWith(token, "name="_ns)) {
+ mName = StringTail(token, token.Length() - 5);
+ mName.Trim(" \"");
+ continue;
+ }
+
+ if (seenFormData && StringBeginsWith(token, "filename="_ns)) {
+ mFilename = StringTail(token, token.Length() - 9);
+ mFilename.Trim(" \"");
+ continue;
+ }
+ }
+
+ if (mName.IsVoid()) {
+ // Could not parse a valid entry name.
+ return false;
+ }
+ } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
+ mContentType = headerValue;
+ }
+
+ return true;
+ }
+
+ // The end of a body is marked by a CRLF followed by the boundary. So the
+ // CRLF is part of the boundary and not the body, but any prior CRLFs are
+ // part of the body. This will position the iterator at the beginning of the
+ // boundary (after the CRLF).
+ bool ParseBody(const nsACString& aBoundaryString,
+ nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd) {
+ const char* beginning = aStart.get();
+
+ // Find the boundary marking the end of the body.
+ nsACString::const_iterator end(aEnd);
+ if (!FindInReadable(aBoundaryString, aStart, end)) {
+ return false;
+ }
+
+ // We found a boundary, strip the just prior CRLF, and consider
+ // everything else the body section.
+ if (aStart.get() - beginning < 2) {
+ // Only the first entry can have a boundary right at the beginning. Even
+ // an empty body will have a CRLF before the boundary. So this is
+ // a failure.
+ return false;
+ }
+
+ // Check that there is a CRLF right before the boundary.
+ aStart.advance(-2);
+
+ // Skip optional hyphens.
+ if (*aStart == '-' && *(aStart.get() + 1) == '-') {
+ if (aStart.get() - beginning < 2) {
+ return false;
+ }
+
+ aStart.advance(-2);
+ }
+
+ if (*aStart != nsCRT::CR || *(aStart.get() + 1) != nsCRT::LF) {
+ return false;
+ }
+
+ nsAutoCString body(beginning, aStart.get() - beginning);
+
+ // Restore iterator to after the \r\n as we promised.
+ // We do not need to handle the extra hyphens case since our boundary
+ // parser in PushOverBoundary()
+ aStart.advance(2);
+
+ if (!mFormData) {
+ mFormData = new FormData();
+ }
+
+ NS_ConvertUTF8toUTF16 name(mName);
+
+ if (mFilename.IsVoid()) {
+ ErrorResult rv;
+ mFormData->Append(name, NS_ConvertUTF8toUTF16(body), rv);
+ MOZ_ASSERT(!rv.Failed());
+ } else {
+ // Unfortunately we've to copy the data first since all our strings are
+ // going to free it. We also need fallible alloc, so we can't just use
+ // ToNewCString().
+ char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
+ nsCString::const_iterator bodyIter, bodyEnd;
+ body.BeginReading(bodyIter);
+ body.EndReading(bodyEnd);
+ char* p = copy;
+ while (bodyIter != bodyEnd) {
+ *p++ = *bodyIter++;
+ }
+ p = nullptr;
+
+ RefPtr<Blob> file = File::CreateMemoryFileWithCustomLastModified(
+ mParentObject, reinterpret_cast<void*>(copy), body.Length(),
+ NS_ConvertUTF8toUTF16(mFilename), NS_ConvertUTF8toUTF16(mContentType),
+ /* aLastModifiedDate */ 0);
+ if (NS_WARN_IF(!file)) {
+ return false;
+ }
+
+ Optional<nsAString> dummy;
+ ErrorResult rv;
+ mFormData->Append(name, *file, dummy, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public:
+ FormDataParser(const nsACString& aMimeType,
+ const nsACString& aMixedCaseMimeType, const nsACString& aData,
+ nsIGlobalObject* aParent)
+ : mMimeType(aMimeType),
+ mMixedCaseMimeType(aMixedCaseMimeType),
+ mData(aData),
+ mState(START_PART),
+ mParentObject(aParent) {}
+
+ bool Parse() {
+ if (mData.IsEmpty()) {
+ return false;
+ }
+
+ // Determine boundary from mimetype.
+ UniquePtr<CMimeType> parsed = CMimeType::Parse(mMixedCaseMimeType);
+ if (!parsed) {
+ return false;
+ }
+
+ nsAutoCString boundaryString;
+ if (!parsed->GetParameterValue("boundary"_ns, boundaryString)) {
+ return false;
+ }
+
+ nsACString::const_iterator start, end;
+ mData.BeginReading(start);
+ // This should ALWAYS point to the end of data.
+ // Helpers make copies.
+ mData.EndReading(end);
+
+ while (start != end) {
+ switch (mState) {
+ case START_PART:
+ mName.SetIsVoid(true);
+ mFilename.SetIsVoid(true);
+ mContentType = "text/plain"_ns;
+
+ // MUST start with boundary.
+ if (!PushOverBoundary(boundaryString, start, end)) {
+ return false;
+ }
+
+ if (start != end && *start == '-') {
+ // End of data.
+ if (!mFormData) {
+ mFormData = new FormData();
+ }
+ return true;
+ }
+
+ if (!PushOverLine(start, end)) {
+ return false;
+ }
+ mState = PARSE_HEADER;
+ break;
+
+ case PARSE_HEADER:
+ bool emptyHeader;
+ if (!ParseHeader(start, end, &emptyHeader)) {
+ return false;
+ }
+
+ if (emptyHeader && !PushOverLine(start, end)) {
+ return false;
+ }
+
+ mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
+ break;
+
+ case PARSE_BODY:
+ if (mName.IsVoid()) {
+ NS_WARNING(
+ "No content-disposition header with a valid name was "
+ "found. Failing at body parse.");
+ return false;
+ }
+
+ if (!ParseBody(boundaryString, start, end)) {
+ return false;
+ }
+
+ mState = START_PART;
+ break;
+
+ default:
+ MOZ_CRASH("Invalid case");
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Should never reach here.");
+ return false;
+ }
+
+ already_AddRefed<FormData> GetFormData() { return mFormData.forget(); }
+};
+} // namespace
+
+// static
+void BodyUtil::ConsumeArrayBuffer(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aValue,
+ uint32_t aInputLength, uint8_t* aInput,
+ ErrorResult& aRv) {
+ JS::Rooted<JSObject*> arrayBuffer(aCx);
+ arrayBuffer = JS::NewArrayBufferWithContents(aCx, aInputLength,
+ reinterpret_cast<void*>(aInput));
+ if (!arrayBuffer) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aValue.set(arrayBuffer);
+}
+
+// static
+already_AddRefed<Blob> BodyUtil::ConsumeBlob(nsIGlobalObject* aParent,
+ const nsString& aMimeType,
+ uint32_t aInputLength,
+ uint8_t* aInput,
+ ErrorResult& aRv) {
+ RefPtr<Blob> blob = Blob::CreateMemoryBlob(
+ aParent, reinterpret_cast<void*>(aInput), aInputLength, aMimeType);
+
+ if (!blob) {
+ aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+ return nullptr;
+ }
+ return blob.forget();
+}
+
+// static
+already_AddRefed<FormData> BodyUtil::ConsumeFormData(
+ nsIGlobalObject* aParent, const nsCString& aMimeType,
+ const nsACString& aMixedCaseMimeType, const nsCString& aStr,
+ ErrorResult& aRv) {
+ constexpr auto formDataMimeType = "multipart/form-data"_ns;
+
+ // Allow semicolon separated boundary/encoding suffix like
+ // multipart/form-data; boundary= but disallow multipart/form-datafoobar.
+ bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
+
+ if (isValidFormDataMimeType &&
+ aMimeType.Length() > formDataMimeType.Length()) {
+ isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
+ }
+
+ if (isValidFormDataMimeType) {
+ FormDataParser parser(aMimeType, aMixedCaseMimeType, aStr, aParent);
+ if (!parser.Parse()) {
+ aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
+ return nullptr;
+ }
+
+ RefPtr<FormData> fd = parser.GetFormData();
+ MOZ_ASSERT(fd);
+ return fd.forget();
+ }
+
+ constexpr auto urlDataMimeType = "application/x-www-form-urlencoded"_ns;
+ bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
+
+ if (isValidUrlEncodedMimeType &&
+ aMimeType.Length() > urlDataMimeType.Length()) {
+ isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
+ }
+
+ if (isValidUrlEncodedMimeType) {
+ RefPtr<FormData> fd = new FormData(aParent);
+ DebugOnly<bool> status = URLParams::Parse(
+ aStr, [&fd](const nsAString& aName, const nsAString& aValue) {
+ ErrorResult rv;
+ fd->Append(aName, aValue, rv);
+ MOZ_ASSERT(!rv.Failed());
+ return true;
+ });
+ MOZ_ASSERT(status);
+
+ return fd.forget();
+ }
+
+ aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
+ return nullptr;
+}
+
+// static
+nsresult BodyUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
+ nsString& aText) {
+ nsresult rv =
+ UTF_8_ENCODING->DecodeWithBOMRemoval(Span(aInput, aInputLength), aText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+// static
+void BodyUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ const nsString& aStr, ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ JS::Rooted<JS::Value> json(aCx);
+ if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
+ if (!JS_IsExceptionPending(aCx)) {
+ aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> exn(aCx);
+ DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
+ MOZ_ASSERT(gotException);
+
+ JS_ClearPendingException(aCx);
+ aRv.ThrowJSException(aCx, exn);
+ return;
+ }
+
+ aValue.set(json);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/BodyUtil.h b/dom/base/BodyUtil.h
new file mode 100644
index 0000000000..b7b2ff94fd
--- /dev/null
+++ b/dom/base/BodyUtil.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BodyUtil_h
+#define mozilla_dom_BodyUtil_h
+
+#include "nsString.h"
+#include "nsError.h"
+
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class BodyUtil final {
+ private:
+ BodyUtil() = delete;
+
+ public:
+ /**
+ * Creates an array buffer from an array, assigning the result to |aValue|.
+ * The array buffer takes ownership of |aInput|, which must be allocated
+ * by |malloc|.
+ */
+ static void ConsumeArrayBuffer(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aValue,
+ uint32_t aInputLength, uint8_t* aInput,
+ ErrorResult& aRv);
+
+ /**
+ * Creates an in-memory blob from an array. The blob takes ownership of
+ * |aInput|, which must be allocated by |malloc|.
+ */
+ static already_AddRefed<Blob> ConsumeBlob(nsIGlobalObject* aParent,
+ const nsString& aMimeType,
+ uint32_t aInputLength,
+ uint8_t* aInput, ErrorResult& aRv);
+
+ /**
+ * Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
+ * and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
+ */
+ static already_AddRefed<FormData> ConsumeFormData(
+ nsIGlobalObject* aParent, const nsCString& aMimeType,
+ const nsACString& aMixedCaseMimeType, const nsCString& aStr,
+ ErrorResult& aRv);
+
+ /**
+ * UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
+ * once this method returns.
+ */
+ static nsresult ConsumeText(uint32_t aInputLength, uint8_t* aInput,
+ nsString& aText);
+
+ /**
+ * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
+ * Sets |aRv| to a syntax error if |aStr| contains invalid data.
+ */
+ static void ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ const nsString& aStr, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BodyUtil_h
diff --git a/dom/base/BorrowedAttrInfo.cpp b/dom/base/BorrowedAttrInfo.cpp
new file mode 100644
index 0000000000..48aec4800d
--- /dev/null
+++ b/dom/base/BorrowedAttrInfo.cpp
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BorrowedAttrInfo.h"
+
+namespace mozilla::dom {
+
+BorrowedAttrInfo::BorrowedAttrInfo(const nsAttrName* aName,
+ const nsAttrValue* aValue)
+ : mName(aName), mValue(aValue) {
+ MOZ_ASSERT_IF(mName, mValue);
+}
+
+BorrowedAttrInfo::BorrowedAttrInfo(const BorrowedAttrInfo& aOther)
+ : mName(aOther.mName), mValue(aOther.mValue) {
+ MOZ_ASSERT_IF(mName, mValue);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/BorrowedAttrInfo.h b/dom/base/BorrowedAttrInfo.h
new file mode 100644
index 0000000000..6f975baf5a
--- /dev/null
+++ b/dom/base/BorrowedAttrInfo.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef BorrowedAttrInfo_h__
+#define BorrowedAttrInfo_h__
+
+#include "mozilla/Assertions.h"
+
+class nsAttrName;
+class nsAttrValue;
+
+namespace mozilla::dom {
+
+/**
+ * Struct that stores info on an attribute. The name and value must either both
+ * be null or both be non-null.
+ *
+ * Note that, just as the pointers returned by GetAttrNameAt, the pointers that
+ * this struct hold are only valid until the element or its attributes are
+ * mutated (directly or via script).
+ */
+struct BorrowedAttrInfo {
+ BorrowedAttrInfo() : mName(nullptr), mValue(nullptr) {}
+
+ BorrowedAttrInfo(const nsAttrName* aName, const nsAttrValue* aValue);
+
+ BorrowedAttrInfo(const BorrowedAttrInfo& aOther);
+
+ const nsAttrName* mName;
+ const nsAttrValue* mValue;
+
+ explicit operator bool() const { return mName != nullptr; }
+};
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/base/CCGCScheduler.cpp b/dom/base/CCGCScheduler.cpp
new file mode 100644
index 0000000000..260290b3db
--- /dev/null
+++ b/dom/base/CCGCScheduler.cpp
@@ -0,0 +1,1076 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CCGCScheduler.h"
+
+#include "js/GCAPI.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/PerfStats.h"
+#include "nsRefreshDriver.h"
+
+/*
+ * GC Scheduling from Firefox
+ * ==========================
+ *
+ * See also GC Scheduling from SpiderMonkey's perspective here:
+ * https://searchfox.org/mozilla-central/source/js/src/gc/Scheduling.h
+ *
+ * From Firefox's perspective GCs can start in 5 different ways:
+ *
+ * * The JS engine just starts doing a GC for its own reasons (see above).
+ * Firefox finds out about these via a callback in nsJSEnvironment.cpp
+ * * PokeGC()
+ * * PokeFullGC()
+ * * PokeShrinkingGC()
+ * * memory-pressure GCs (via a listener in nsJSEnvironment.cpp).
+ *
+ * PokeGC
+ * ------
+ *
+ * void CCGCScheduler::PokeGC(JS::GCReason aReason, JSObject* aObj,
+ * TimeDuration aDelay)
+ *
+ * PokeGC provides a way for callers to say "Hey, there may be some memory
+ * associated with this object (via Zone) you can collect." PokeGC will:
+ * * add the zone to a set,
+ * * set flags including what kind of GC to run (SetWantMajorGC),
+ * * then creates the mGCRunner with a short delay.
+ *
+ * The delay can allow other calls to PokeGC to add their zones so they can
+ * be collected together.
+ *
+ * See below for what happens when mGCRunner fires.
+ *
+ * PokeFullGC
+ * ----------
+ *
+ * void CCGCScheduler::PokeFullGC()
+ *
+ * PokeFullGC will create a timer that will initiate a "full" (all zones)
+ * collection. This is usually used after a regular collection if a full GC
+ * seems like a good idea (to collect inter-zone references).
+ *
+ * When the timer fires it will:
+ * * set flags (SetWantMajorGC),
+ * * start the mGCRunner with zero delay.
+ *
+ * See below for when mGCRunner fires.
+ *
+ * PokeShrinkingGC
+ * ---------------
+ *
+ * void CCGCScheduler::PokeShrinkingGC()
+ *
+ * PokeShrinkingGC is called when Firefox's user is inactive.
+ * Like PokeFullGC, PokeShrinkingGC uses a timer, but the timeout is longer
+ * which should prevent the ShrinkingGC from starting if the user only
+ * glances away for a brief time. When the timer fires it will:
+ *
+ * * set flags (SetWantMajorGC),
+ * * create the mGCRunner.
+ *
+ * There is a check if the user is still inactive in GCRunnerFired), if the
+ * user has become active the shrinking GC is canceled and either a regular
+ * GC (if requested, see mWantAtLeastRegularGC) or no GC is run.
+ *
+ * When mGCRunner fires
+ * --------------------
+ *
+ * When mGCRunner fires it calls GCRunnerFired. This starts in the
+ * WaitToMajorGC state:
+ *
+ * * If this is a parent process it jumps to the next state
+ * * If this is a content process it will ask the parent if now is a good
+ * time to do a GC. (MayGCNow)
+ * * kill the mGCRunner
+ * * Exit
+ *
+ * Meanwhile the parent process will queue GC requests so that not too many
+ * are running in parallel overwhelming the CPU cores (see
+ * IdleSchedulerParent).
+ *
+ * When the promise from MayGCNow is resolved it will set some
+ * state (NoteReadyForMajorGC) and restore the mGCRunner.
+ *
+ * When the mGCRunner runs a second time (or this is the parent process and
+ * which jumped over the above logic. It will be in the StartMajorGC state.
+ * It will initiate the GC for real, usually. If it's a shrinking GC and the
+ * user is now active again it may abort. See GCRunnerFiredDoGC().
+ *
+ * The runner will then run the first slice of the garbage collection.
+ * Later slices are also run by the runner, the final slice kills the runner
+ * from the GC callback in nsJSEnvironment.cpp.
+ *
+ * There is additional logic in the code to handle concurrent requests of
+ * various kinds.
+ */
+
+namespace geckoprofiler::markers {
+struct CCIntervalMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("CC");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter, bool aIsStart,
+ const mozilla::ProfilerString8View& aReason,
+ uint32_t aForgetSkippableBeforeCC, uint32_t aSuspectedAtCCStart,
+ uint32_t aRemovedPurples, const mozilla::CycleCollectorResults& aResults,
+ mozilla::TimeDuration aMaxSliceTime) {
+ if (aIsStart) {
+ aWriter.StringProperty("mReason", aReason);
+ aWriter.IntProperty("mSuspected", aSuspectedAtCCStart);
+ aWriter.IntProperty("mForgetSkippable", aForgetSkippableBeforeCC);
+ aWriter.IntProperty("mRemovedPurples", aRemovedPurples);
+ } else {
+ aWriter.TimeDoubleMsProperty("mMaxSliceTime",
+ aMaxSliceTime.ToMilliseconds());
+ aWriter.IntProperty("mSlices", aResults.mNumSlices);
+
+ aWriter.BoolProperty("mAnyManual", aResults.mAnyManual);
+ aWriter.BoolProperty("mForcedGC", aResults.mForcedGC);
+ aWriter.BoolProperty("mMergedZones", aResults.mMergedZones);
+ aWriter.IntProperty("mVisitedRefCounted", aResults.mVisitedRefCounted);
+ aWriter.IntProperty("mVisitedGCed", aResults.mVisitedGCed);
+ aWriter.IntProperty("mFreedRefCounted", aResults.mFreedRefCounted);
+ aWriter.IntProperty("mFreedGCed", aResults.mFreedGCed);
+ aWriter.IntProperty("mFreedJSZones", aResults.mFreedJSZones);
+ }
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ using MS = mozilla::MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ schema.AddStaticLabelValue(
+ "Description",
+ "Summary data for the core part of a cycle collection, possibly "
+ "encompassing a set of incremental slices. The main thread is not "
+ "blocked for the entire major CC interval, only for the individual "
+ "slices.");
+ schema.AddKeyLabelFormatSearchable("mReason", "Reason", MS::Format::String,
+ MS::Searchable::Searchable);
+ schema.AddKeyLabelFormat("mMaxSliceTime", "Max Slice Time",
+ MS::Format::Duration);
+ schema.AddKeyLabelFormat("mSuspected", "Suspected Objects",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mSlices", "Number of Slices",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mAnyManual", "Manually Triggered",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mForcedGC", "GC Forced", MS::Format::Integer);
+ schema.AddKeyLabelFormat("mMergedZones", "Zones Merged",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mForgetSkippable", "Forget Skippables",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mVisitedRefCounted", "Refcounted Objects Visited",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mVisitedGCed", "GC Objects Visited",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mFreedRefCounted", "Refcounted Objects Freed",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mFreedGCed", "GC Objects Freed",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mCollectedGCZones", "JS Zones Freed",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("mRemovedPurples",
+ "Objects Removed From Purple Buffer",
+ MS::Format::Integer);
+ return schema;
+ }
+};
+} // namespace geckoprofiler::markers
+
+namespace mozilla {
+
+void CCGCScheduler::NoteGCBegin(JS::GCReason aReason) {
+ // Treat all GC as incremental here; non-incremental GC will just appear to
+ // be one slice.
+ mInIncrementalGC = true;
+ mReadyForMajorGC = !mAskParentBeforeMajorGC;
+
+ // Tell the parent process that we've started a GC (it might not know if
+ // we hit a threshold in the JS engine).
+ using mozilla::ipc::IdleSchedulerChild;
+ IdleSchedulerChild* child = IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (child) {
+ child->StartedGC();
+ }
+
+ // The reason might have come from mMajorReason, mEagerMajorGCReason, or
+ // in the case of an internally-generated GC, it might come from the
+ // internal logic (and be passed in here). It's easier to manage a single
+ // reason state variable, so merge all sources into mMajorGCReason.
+ MOZ_ASSERT(aReason != JS::GCReason::NO_REASON);
+ mMajorGCReason = aReason;
+ mEagerMajorGCReason = JS::GCReason::NO_REASON;
+}
+
+void CCGCScheduler::NoteGCEnd() {
+ mMajorGCReason = JS::GCReason::NO_REASON;
+ mEagerMajorGCReason = JS::GCReason::NO_REASON;
+ mEagerMinorGCReason = JS::GCReason::NO_REASON;
+
+ mInIncrementalGC = false;
+ mCCBlockStart = TimeStamp();
+ mReadyForMajorGC = !mAskParentBeforeMajorGC;
+ mWantAtLeastRegularGC = false;
+ mNeedsFullCC = CCReason::GC_FINISHED;
+ mHasRunGC = true;
+ mIsCompactingOnUserInactive = false;
+
+ mCleanupsSinceLastGC = 0;
+ mCCollectedWaitingForGC = 0;
+ mCCollectedZonesWaitingForGC = 0;
+ mLikelyShortLivingObjectsNeedingGC = 0;
+
+ using mozilla::ipc::IdleSchedulerChild;
+ IdleSchedulerChild* child = IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (child) {
+ child->DoneGC();
+ }
+}
+
+void CCGCScheduler::NoteGCSliceEnd(TimeStamp aStart, TimeStamp aEnd) {
+ if (mMajorGCReason == JS::GCReason::NO_REASON) {
+ // Internally-triggered GCs do not wait for the parent's permission to
+ // proceed. This flag won't be checked during an incremental GC anyway,
+ // but it better reflects reality.
+ mReadyForMajorGC = true;
+ }
+
+ // Subsequent slices should be INTER_SLICE_GC unless they are triggered by
+ // something else that provides its own reason.
+ mMajorGCReason = JS::GCReason::INTER_SLICE_GC;
+
+ MOZ_ASSERT(aEnd >= aStart);
+ TimeDuration sliceDuration = aEnd - aStart;
+ PerfStats::RecordMeasurement(PerfStats::Metric::MajorGC, sliceDuration);
+
+ // Compute how much GC time was spent in predicted-to-be-idle time. In the
+ // unlikely event that the slice started after the deadline had already
+ // passed, treat the entire slice as non-idle.
+ TimeDuration nonIdleDuration;
+ bool startedIdle = mTriggeredGCDeadline.isSome() &&
+ !mTriggeredGCDeadline->IsNull() &&
+ *mTriggeredGCDeadline > aStart;
+ if (!startedIdle) {
+ nonIdleDuration = sliceDuration;
+ } else {
+ if (*mTriggeredGCDeadline < aEnd) {
+ // Overran the idle deadline.
+ nonIdleDuration = aEnd - *mTriggeredGCDeadline;
+ }
+ }
+
+ PerfStats::RecordMeasurement(PerfStats::Metric::NonIdleMajorGC,
+ nonIdleDuration);
+
+ // Note the GC_SLICE_DURING_IDLE previously had a different definition: it was
+ // a histogram of percentages of externally-triggered slices. It is now a
+ // histogram of percentages of all slices. That means that now you might have
+ // a 4ms internal slice (0% during idle) followed by a 16ms external slice
+ // (15ms during idle), whereas before this would show up as a single record of
+ // a single slice with 75% of its time during idle (15 of 20ms).
+ TimeDuration idleDuration = sliceDuration - nonIdleDuration;
+ uint32_t percent =
+ uint32_t(idleDuration.ToSeconds() / sliceDuration.ToSeconds() * 100);
+ Telemetry::Accumulate(Telemetry::GC_SLICE_DURING_IDLE, percent);
+
+ mTriggeredGCDeadline.reset();
+}
+
+void CCGCScheduler::NoteCCBegin(CCReason aReason, TimeStamp aWhen,
+ uint32_t aNumForgetSkippables,
+ uint32_t aSuspected, uint32_t aRemovedPurples) {
+ CycleCollectorResults ignoredResults;
+ PROFILER_MARKER(
+ "CC", GCCC, MarkerOptions(MarkerTiming::IntervalStart(aWhen)),
+ CCIntervalMarker,
+ /* aIsStart */ true,
+ ProfilerString8View::WrapNullTerminatedString(CCReasonToString(aReason)),
+ aNumForgetSkippables, aSuspected, aRemovedPurples, ignoredResults,
+ TimeDuration());
+
+ mIsCollectingCycles = true;
+}
+
+void CCGCScheduler::NoteCCEnd(const CycleCollectorResults& aResults,
+ TimeStamp aWhen,
+ mozilla::TimeDuration aMaxSliceTime) {
+ mCCollectedWaitingForGC += aResults.mFreedGCed;
+ mCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
+
+ PROFILER_MARKER("CC", GCCC, MarkerOptions(MarkerTiming::IntervalEnd(aWhen)),
+ CCIntervalMarker, /* aIsStart */ false, nullptr, 0, 0, 0,
+ aResults, aMaxSliceTime);
+
+ mIsCollectingCycles = false;
+ mLastCCEndTime = aWhen;
+ mNeedsFullCC = CCReason::NO_REASON;
+}
+
+void CCGCScheduler::NoteWontGC() {
+ mReadyForMajorGC = !mAskParentBeforeMajorGC;
+ mMajorGCReason = JS::GCReason::NO_REASON;
+ mEagerMajorGCReason = JS::GCReason::NO_REASON;
+ mWantAtLeastRegularGC = false;
+ // Don't clear the WantFullGC state, we will do a full GC the next time a
+ // GC happens for any other reason.
+}
+
+bool CCGCScheduler::GCRunnerFired(TimeStamp aDeadline) {
+ MOZ_ASSERT(!mDidShutdown, "GCRunner still alive during shutdown");
+
+ GCRunnerStep step = GetNextGCRunnerAction(aDeadline);
+ switch (step.mAction) {
+ case GCRunnerAction::None:
+ KillGCRunner();
+ return false;
+
+ case GCRunnerAction::MinorGC:
+ JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(),
+ step.mReason);
+ NoteMinorGCEnd();
+ return HasMoreIdleGCRunnerWork();
+
+ case GCRunnerAction::WaitToMajorGC: {
+ MOZ_ASSERT(!mHaveAskedParent, "GCRunner alive after asking the parent");
+ RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
+ CCGCScheduler::MayGCNow(step.mReason);
+ if (!mbPromise) {
+ // We can GC now.
+ break;
+ }
+
+ mHaveAskedParent = true;
+ KillGCRunner();
+ mbPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this](bool aMayGC) {
+ mHaveAskedParent = false;
+ if (aMayGC) {
+ if (!NoteReadyForMajorGC()) {
+ // Another GC started and maybe completed while waiting.
+ return;
+ }
+ // Recreate the GC runner with a 0 delay. The new runner will
+ // continue in idle time.
+ KillGCRunner();
+ EnsureGCRunner(0);
+ } else if (!InIncrementalGC()) {
+ // We should kill the GC runner since we're done with it, but
+ // only if there's no incremental GC.
+ KillGCRunner();
+ NoteWontGC();
+ }
+ },
+ [this](mozilla::ipc::ResponseRejectReason r) {
+ mHaveAskedParent = false;
+ if (!InIncrementalGC()) {
+ KillGCRunner();
+ NoteWontGC();
+ }
+ });
+
+ return true;
+ }
+
+ case GCRunnerAction::StartMajorGC:
+ case GCRunnerAction::GCSlice:
+ break;
+ }
+
+ return GCRunnerFiredDoGC(aDeadline, step);
+}
+
+bool CCGCScheduler::GCRunnerFiredDoGC(TimeStamp aDeadline,
+ const GCRunnerStep& aStep) {
+ // Run a GC slice, possibly the first one of a major GC.
+ nsJSContext::IsShrinking is_shrinking = nsJSContext::NonShrinkingGC;
+ if (!InIncrementalGC() && aStep.mReason == JS::GCReason::USER_INACTIVE) {
+ bool do_gc = mWantAtLeastRegularGC;
+
+ if (!mUserIsActive) {
+ if (!nsRefreshDriver::IsRegularRateTimerTicking()) {
+ mIsCompactingOnUserInactive = true;
+ is_shrinking = nsJSContext::ShrinkingGC;
+ do_gc = true;
+ } else {
+ // Poke again to restart the timer.
+ PokeShrinkingGC();
+ }
+ }
+
+ if (!do_gc) {
+ using mozilla::ipc::IdleSchedulerChild;
+ IdleSchedulerChild* child =
+ IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (child) {
+ child->DoneGC();
+ }
+ NoteWontGC();
+ KillGCRunner();
+ return true;
+ }
+ }
+
+ // Note that we are triggering the following GC slice and recording whether
+ // it started in idle time, for use in the callback at the end of the slice.
+ mTriggeredGCDeadline = Some(aDeadline);
+
+ MOZ_ASSERT(mActiveIntersliceGCBudget);
+ TimeStamp startTimeStamp = TimeStamp::Now();
+ js::SliceBudget budget = ComputeInterSliceGCBudget(aDeadline, startTimeStamp);
+ nsJSContext::RunIncrementalGCSlice(aStep.mReason, is_shrinking, budget);
+
+ // If the GC doesn't have any more work to do on the foreground thread (and
+ // e.g. is waiting for background sweeping to finish) then return false to
+ // make IdleTaskRunner postpone the next call a bit.
+ JSContext* cx = dom::danger::GetJSContext();
+ return JS::IncrementalGCHasForegroundWork(cx);
+}
+
+RefPtr<CCGCScheduler::MayGCPromise> CCGCScheduler::MayGCNow(
+ JS::GCReason reason) {
+ using namespace mozilla::ipc;
+
+ // We ask the parent if we should GC for GCs that aren't too timely,
+ // with the exception of MEM_PRESSURE, in that case we ask the parent
+ // because GCing on too many processes at the same time when under
+ // memory pressure could be a very bad experience for the user.
+ switch (reason) {
+ case JS::GCReason::PAGE_HIDE:
+ case JS::GCReason::MEM_PRESSURE:
+ case JS::GCReason::USER_INACTIVE:
+ case JS::GCReason::FULL_GC_TIMER:
+ case JS::GCReason::CC_FINISHED: {
+ if (XRE_IsContentProcess()) {
+ IdleSchedulerChild* child =
+ IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (child) {
+ return child->MayGCNow();
+ }
+ }
+ // The parent process doesn't ask IdleSchedulerParent if it can GC.
+ break;
+ }
+ default:
+ break;
+ }
+
+ // We use synchronous task dispatch here to avoid a trip through the event
+ // loop if we're on the parent process or it's a GC reason that does not
+ // require permission to GC.
+ RefPtr<MayGCPromise::Private> p = MakeRefPtr<MayGCPromise::Private>(__func__);
+ p->UseSynchronousTaskDispatch(__func__);
+ p->Resolve(true, __func__);
+ return p;
+}
+
+void CCGCScheduler::RunNextCollectorTimer(JS::GCReason aReason,
+ mozilla::TimeStamp aDeadline) {
+ if (mDidShutdown) {
+ return;
+ }
+
+ // When we're in an incremental GC, we should always have an sGCRunner, so do
+ // not check CC timers. The CC timers won't do anything during a GC.
+ MOZ_ASSERT_IF(InIncrementalGC(), mGCRunner);
+
+ RefPtr<IdleTaskRunner> runner;
+ if (mGCRunner) {
+ SetWantMajorGC(aReason);
+ runner = mGCRunner;
+ } else if (mCCRunner) {
+ runner = mCCRunner;
+ }
+
+ if (runner) {
+ runner->SetIdleDeadline(aDeadline);
+ runner->Run();
+ }
+}
+
+void CCGCScheduler::PokeShrinkingGC() {
+ if (mShrinkingGCTimer || mDidShutdown) {
+ return;
+ }
+
+ NS_NewTimerWithFuncCallback(
+ &mShrinkingGCTimer,
+ [](nsITimer* aTimer, void* aClosure) {
+ CCGCScheduler* s = static_cast<CCGCScheduler*>(aClosure);
+ s->KillShrinkingGCTimer();
+ if (!s->mUserIsActive) {
+ if (!nsRefreshDriver::IsRegularRateTimerTicking()) {
+ s->SetWantMajorGC(JS::GCReason::USER_INACTIVE);
+ if (!s->mHaveAskedParent) {
+ s->EnsureGCRunner(0);
+ }
+ } else {
+ s->PokeShrinkingGC();
+ }
+ }
+ },
+ this, StaticPrefs::javascript_options_compact_on_user_inactive_delay(),
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "ShrinkingGCTimerFired");
+}
+
+void CCGCScheduler::PokeFullGC() {
+ if (!mFullGCTimer && !mDidShutdown) {
+ NS_NewTimerWithFuncCallback(
+ &mFullGCTimer,
+ [](nsITimer* aTimer, void* aClosure) {
+ CCGCScheduler* s = static_cast<CCGCScheduler*>(aClosure);
+ s->KillFullGCTimer();
+
+ // Even if the GC is denied by the parent process, because we've
+ // set that we want a full GC we will get one eventually.
+ s->SetNeedsFullGC();
+ s->SetWantMajorGC(JS::GCReason::FULL_GC_TIMER);
+ if (!s->mHaveAskedParent) {
+ s->EnsureGCRunner(0);
+ }
+ },
+ this, StaticPrefs::javascript_options_gc_delay_full(),
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "FullGCTimerFired");
+ }
+}
+
+void CCGCScheduler::PokeGC(JS::GCReason aReason, JSObject* aObj,
+ TimeDuration aDelay) {
+ MOZ_ASSERT(aReason != JS::GCReason::NO_REASON);
+ MOZ_ASSERT(aReason != JS::GCReason::EAGER_NURSERY_COLLECTION);
+
+ if (mDidShutdown) {
+ return;
+ }
+
+ // If a post-CC GC was pending, then we'll make sure one is happening.
+ mNeedsGCAfterCC = false;
+
+ if (aObj) {
+ JS::Zone* zone = JS::GetObjectZone(aObj);
+ CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
+ } else if (aReason != JS::GCReason::CC_FINISHED) {
+ SetNeedsFullGC();
+ }
+
+ if (mGCRunner || mHaveAskedParent) {
+ // There's already a GC runner, or there will be, so just return.
+ return;
+ }
+
+ SetWantMajorGC(aReason);
+
+ if (mCCRunner) {
+ // Make sure CC is called regardless of the size of the purple buffer, and
+ // GC after it.
+ EnsureCCThenGC(CCReason::GC_WAITING);
+ return;
+ }
+
+ // Wait for javascript.options.gc_delay (or delay_first) then start
+ // looking for idle time to run the initial GC slice.
+ static bool first = true;
+ TimeDuration delay =
+ aDelay ? aDelay
+ : TimeDuration::FromMilliseconds(
+ first ? StaticPrefs::javascript_options_gc_delay_first()
+ : StaticPrefs::javascript_options_gc_delay());
+ first = false;
+ EnsureGCRunner(delay);
+}
+
+void CCGCScheduler::PokeMinorGC(JS::GCReason aReason) {
+ MOZ_ASSERT(aReason != JS::GCReason::NO_REASON);
+
+ if (mDidShutdown) {
+ return;
+ }
+
+ SetWantEagerMinorGC(aReason);
+
+ if (mGCRunner || mHaveAskedParent || mCCRunner) {
+ // There's already a runner, or there will be, so just return.
+ return;
+ }
+
+ // Immediately start looking for idle time to run the minor GC.
+ EnsureGCRunner(0);
+}
+
+void CCGCScheduler::EnsureGCRunner(TimeDuration aDelay) {
+ if (mGCRunner) {
+ return;
+ }
+
+ TimeDuration minimumBudget = nsRefreshDriver::IsInHighRateMode()
+ ? TimeDuration::FromMilliseconds(1)
+ : mActiveIntersliceGCBudget;
+
+ // Wait at most the interslice GC delay before forcing a run.
+ mGCRunner = IdleTaskRunner::Create(
+ [this](TimeStamp aDeadline) { return GCRunnerFired(aDeadline); },
+ "CCGCScheduler::EnsureGCRunner", aDelay,
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay_interslice()),
+ minimumBudget, true, [this] { return mDidShutdown; },
+ [this](uint32_t) {
+ PROFILER_MARKER_UNTYPED("GC Interrupt", GCCC);
+ mInterruptRequested = true;
+ });
+}
+
+// nsJSEnvironmentObserver observes the user-interaction-inactive notifications
+// and triggers a shrinking a garbage collection if the user is still inactive
+// after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set.
+void CCGCScheduler::UserIsInactive() {
+ mUserIsActive = false;
+ if (StaticPrefs::javascript_options_compact_on_user_inactive()) {
+ PokeShrinkingGC();
+ }
+}
+
+void CCGCScheduler::UserIsActive() {
+ mUserIsActive = true;
+ KillShrinkingGCTimer();
+ if (mIsCompactingOnUserInactive) {
+ mozilla::dom::AutoJSAPI jsapi;
+ jsapi.Init();
+ JS::AbortIncrementalGC(jsapi.cx());
+ }
+ MOZ_ASSERT(!mIsCompactingOnUserInactive);
+}
+
+void CCGCScheduler::KillShrinkingGCTimer() {
+ if (mShrinkingGCTimer) {
+ mShrinkingGCTimer->Cancel();
+ NS_RELEASE(mShrinkingGCTimer);
+ }
+}
+
+void CCGCScheduler::KillFullGCTimer() {
+ if (mFullGCTimer) {
+ mFullGCTimer->Cancel();
+ NS_RELEASE(mFullGCTimer);
+ }
+}
+
+void CCGCScheduler::KillGCRunner() {
+ // If we're in an incremental GC then killing the timer is only okay if
+ // we're shutting down.
+ MOZ_ASSERT(!(InIncrementalGC() && !mDidShutdown));
+ if (mGCRunner) {
+ mGCRunner->Cancel();
+ mGCRunner = nullptr;
+ }
+}
+
+void CCGCScheduler::EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget) {
+ MOZ_ASSERT(!mDidShutdown);
+
+ TimeDuration minimumBudget = nsRefreshDriver::IsInHighRateMode()
+ ? TimeDuration::FromMilliseconds(1)
+ : aBudget;
+
+ if (!mCCRunner) {
+ mCCRunner = IdleTaskRunner::Create(
+ CCRunnerFired, "EnsureCCRunner::CCRunnerFired", 0, aDelay,
+ minimumBudget, true, [this] { return mDidShutdown; });
+ } else {
+ mCCRunner->SetMinimumUsefulBudget(minimumBudget.ToMilliseconds());
+ nsIEventTarget* target = mozilla::GetCurrentSerialEventTarget();
+ if (target) {
+ mCCRunner->SetTimer(aDelay, target);
+ }
+ }
+}
+
+void CCGCScheduler::MaybePokeCC(TimeStamp aNow, uint32_t aSuspectedCCObjects) {
+ if (mCCRunner || mDidShutdown) {
+ return;
+ }
+
+ CCReason reason = ShouldScheduleCC(aNow, aSuspectedCCObjects);
+ if (reason != CCReason::NO_REASON) {
+ // We can kill some objects before running forgetSkippable.
+ nsCycleCollector_dispatchDeferredDeletion();
+
+ if (!mCCRunner) {
+ InitCCRunnerStateMachine(CCRunnerState::ReducePurple, reason);
+ }
+ EnsureCCRunner(kCCSkippableDelay, kForgetSkippableSliceDuration);
+ }
+}
+
+void CCGCScheduler::KillCCRunner() {
+ UnblockCC();
+ DeactivateCCRunner();
+ if (mCCRunner) {
+ mCCRunner->Cancel();
+ mCCRunner = nullptr;
+ }
+}
+
+void CCGCScheduler::KillAllTimersAndRunners() {
+ KillShrinkingGCTimer();
+ KillCCRunner();
+ KillFullGCTimer();
+ KillGCRunner();
+}
+
+js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
+ TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime,
+ TimeStamp aNow, bool* aPreferShorterSlices) const {
+ *aPreferShorterSlices =
+ aDeadline.IsNull() || (aDeadline - aNow) < kICCSliceBudget;
+
+ TimeDuration baseBudget =
+ aDeadline.IsNull() ? kICCSliceBudget : aDeadline - aNow;
+
+ if (aPrevSliceEndTime.IsNull()) {
+ // The first slice gets the standard slice time.
+ return js::SliceBudget(js::TimeBudget(baseBudget));
+ }
+
+ // Only run a limited slice if we're within the max running time.
+ MOZ_ASSERT(aNow >= aCCBeginTime);
+ TimeDuration runningTime = aNow - aCCBeginTime;
+ if (runningTime >= kMaxICCDuration) {
+ return js::SliceBudget::unlimited();
+ }
+
+ const TimeDuration maxSlice =
+ TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
+
+ // Try to make up for a delay in running this slice.
+ MOZ_ASSERT(aNow >= aPrevSliceEndTime);
+ double sliceDelayMultiplier =
+ (aNow - aPrevSliceEndTime) / kICCIntersliceDelay;
+ TimeDuration delaySliceBudget =
+ std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
+
+ // Increase slice budgets up to |maxSlice| as we approach
+ // half way through the ICC, to avoid large sync CCs.
+ double percentToHalfDone =
+ std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
+ TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
+
+ // Note: We may have already overshot the deadline, in which case
+ // baseBudget will be negative and we will end up returning
+ // laterSliceBudget.
+ return js::SliceBudget(js::TimeBudget(
+ std::max({delaySliceBudget, laterSliceBudget, baseBudget})));
+}
+
+js::SliceBudget CCGCScheduler::ComputeInterSliceGCBudget(TimeStamp aDeadline,
+ TimeStamp aNow) {
+ // We use longer budgets when the CC has been locked out but the CC has
+ // tried to run since that means we may have a significant amount of
+ // garbage to collect and it's better to GC in several longer slices than
+ // in a very long one.
+ TimeDuration budget =
+ aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow;
+ if (!mCCBlockStart) {
+ return CreateGCSliceBudget(budget, !aDeadline.IsNull(), false);
+ }
+
+ TimeDuration blockedTime = aNow - mCCBlockStart;
+ TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
+ double percentOfBlockedTime =
+ std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
+ TimeDuration extendedBudget =
+ maxSliceGCBudget.MultDouble(percentOfBlockedTime);
+ if (budget >= extendedBudget) {
+ return CreateGCSliceBudget(budget, !aDeadline.IsNull(), false);
+ }
+
+ // If the budget is being extended, do not allow it to be interrupted.
+ auto result = js::SliceBudget(js::TimeBudget(extendedBudget), nullptr);
+ result.idle = !aDeadline.IsNull();
+ result.extended = true;
+ return result;
+}
+
+CCReason CCGCScheduler::ShouldScheduleCC(TimeStamp aNow,
+ uint32_t aSuspectedCCObjects) const {
+ if (!mHasRunGC) {
+ return CCReason::NO_REASON;
+ }
+
+ // Don't run consecutive CCs too often.
+ if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
+ if (aNow - mLastCCEndTime < kCCDelay) {
+ return CCReason::NO_REASON;
+ }
+ }
+
+ // If GC hasn't run recently and forget skippable only cycle was run,
+ // don't start a new cycle too soon.
+ if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
+ !mLastForgetSkippableCycleEndTime.IsNull()) {
+ if (aNow - mLastForgetSkippableCycleEndTime <
+ kTimeBetweenForgetSkippableCycles) {
+ return CCReason::NO_REASON;
+ }
+ }
+
+ return IsCCNeeded(aNow, aSuspectedCCObjects);
+}
+
+CCRunnerStep CCGCScheduler::AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
+ uint32_t aSuspectedCCObjects) {
+ struct StateDescriptor {
+ // When in this state, should we first check to see if we still have
+ // enough reason to CC?
+ bool mCanAbortCC;
+
+ // If we do decide to abort the CC, should we still try to forget
+ // skippables one more time?
+ bool mTryFinalForgetSkippable;
+ };
+
+ // The state descriptors for Inactive and Canceled will never actually be
+ // used. We will never call this function while Inactive, and Canceled is
+ // handled specially at the beginning.
+ constexpr StateDescriptor stateDescriptors[] = {
+ {false, false}, /* CCRunnerState::Inactive */
+ {false, false}, /* CCRunnerState::ReducePurple */
+ {true, true}, /* CCRunnerState::CleanupChildless */
+ {true, false}, /* CCRunnerState::CleanupContentUnbinder */
+ {false, false}, /* CCRunnerState::CleanupDeferred */
+ {false, false}, /* CCRunnerState::StartCycleCollection */
+ {false, false}, /* CCRunnerState::CycleCollecting */
+ {false, false}}; /* CCRunnerState::Canceled */
+ static_assert(
+ ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
+ "need one state descriptor per state");
+ const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
+
+ // Make sure we initialized the state machine.
+ MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
+
+ if (mDidShutdown) {
+ return {CCRunnerAction::StopRunning, Yield};
+ }
+
+ if (mCCRunnerState == CCRunnerState::Canceled) {
+ // When we cancel a cycle, there may have been a final ForgetSkippable.
+ return {CCRunnerAction::StopRunning, Yield};
+ }
+
+ if (InIncrementalGC()) {
+ if (mCCBlockStart.IsNull()) {
+ BlockCC(aNow);
+
+ // If we have reached the CycleCollecting state, then ignore CC timer
+ // fires while incremental GC is running. (Running ICC during an IGC
+ // would cause us to synchronously finish the GC, which is bad.)
+ //
+ // If we have not yet started cycle collecting, then reset our state so
+ // that we run forgetSkippable often enough before CC. Because of reduced
+ // mCCDelay, forgetSkippable will be called just a few times.
+ //
+ // The kMaxCCLockedoutTime limit guarantees that we end up calling
+ // forgetSkippable and CycleCollectNow eventually.
+
+ if (mCCRunnerState != CCRunnerState::CycleCollecting) {
+ mCCRunnerState = CCRunnerState::ReducePurple;
+ mCCRunnerEarlyFireCount = 0;
+ mCCDelay = kCCDelay / int64_t(3);
+ }
+ return {CCRunnerAction::None, Yield};
+ }
+
+ if (GetCCBlockedTime(aNow) < kMaxCCLockedoutTime) {
+ return {CCRunnerAction::None, Yield};
+ }
+
+ // Locked out for too long, so proceed and finish the incremental GC
+ // synchronously.
+ }
+
+ // For states that aren't just continuations of previous states, check
+ // whether a CC is still needed (after doing various things to reduce the
+ // purple buffer).
+ if (desc.mCanAbortCC &&
+ IsCCNeeded(aNow, aSuspectedCCObjects) == CCReason::NO_REASON) {
+ // If we don't pass the threshold for wanting to cycle collect, stop now
+ // (after possibly doing a final ForgetSkippable).
+ mCCRunnerState = CCRunnerState::Canceled;
+ NoteForgetSkippableOnlyCycle(aNow);
+
+ // Preserve the previous code's idea of when to check whether a
+ // ForgetSkippable should be fired.
+ if (desc.mTryFinalForgetSkippable &&
+ ShouldForgetSkippable(aSuspectedCCObjects)) {
+ // The Canceled state will make us StopRunning after this action is
+ // performed (see conditional at top of function).
+ return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
+ }
+
+ return {CCRunnerAction::StopRunning, Yield};
+ }
+
+ if (mEagerMinorGCReason != JS::GCReason::NO_REASON && !aDeadline.IsNull()) {
+ return {CCRunnerAction::MinorGC, Continue, mEagerMinorGCReason};
+ }
+
+ switch (mCCRunnerState) {
+ // ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
+ // for some amount of time (kCCDelay, or less if incremental GC blocked
+ // this CC) while firing regular ForgetSkippable actions before continuing
+ // on.
+ case CCRunnerState::ReducePurple:
+ ++mCCRunnerEarlyFireCount;
+ if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
+ mCCRunnerState = CCRunnerState::CleanupChildless;
+ }
+
+ if (ShouldForgetSkippable(aSuspectedCCObjects)) {
+ return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
+ }
+
+ if (aDeadline.IsNull()) {
+ return {CCRunnerAction::None, Yield};
+ }
+
+ // If we're called during idle time, try to find some work to do by
+ // advancing to the next state, effectively bypassing some possible forget
+ // skippable calls.
+ mCCRunnerState = CCRunnerState::CleanupChildless;
+
+ // Continue on to CleanupChildless, but only after checking IsCCNeeded
+ // again.
+ return {CCRunnerAction::None, Continue};
+
+ // CleanupChildless: do a stronger ForgetSkippable that removes nodes with
+ // no children in the cycle collector graph. This state is split into 3
+ // parts; the other Cleanup* actions will happen within the same callback
+ // (unless the ForgetSkippable shrinks the purple buffer enough for the CC
+ // to be skipped entirely.)
+ case CCRunnerState::CleanupChildless:
+ mCCRunnerState = CCRunnerState::CleanupContentUnbinder;
+ return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
+
+ // CleanupContentUnbinder: continuing cleanup, clear out the content
+ // unbinder.
+ case CCRunnerState::CleanupContentUnbinder:
+ if (aDeadline.IsNull()) {
+ // Non-idle (waiting) callbacks skip the rest of the cleanup, but still
+ // wait for another fire before the actual CC.
+ mCCRunnerState = CCRunnerState::StartCycleCollection;
+ return {CCRunnerAction::None, Yield};
+ }
+
+ // Running in an idle callback.
+
+ // The deadline passed, so go straight to CC in the next slice.
+ if (aNow >= aDeadline) {
+ mCCRunnerState = CCRunnerState::StartCycleCollection;
+ return {CCRunnerAction::None, Yield};
+ }
+
+ mCCRunnerState = CCRunnerState::CleanupDeferred;
+ return {CCRunnerAction::CleanupContentUnbinder, Continue};
+
+ // CleanupDeferred: continuing cleanup, do deferred deletion.
+ case CCRunnerState::CleanupDeferred:
+ MOZ_ASSERT(!aDeadline.IsNull(),
+ "Should only be in CleanupDeferred state when idle");
+
+ // Our efforts to avoid a CC have failed. Let the timer fire once more
+ // to trigger a CC.
+ mCCRunnerState = CCRunnerState::StartCycleCollection;
+ if (aNow >= aDeadline) {
+ // The deadline passed, go straight to CC in the next slice.
+ return {CCRunnerAction::None, Yield};
+ }
+
+ return {CCRunnerAction::CleanupDeferred, Yield};
+
+ // StartCycleCollection: start actually doing cycle collection slices.
+ case CCRunnerState::StartCycleCollection:
+ // We are in the final timer fire and still meet the conditions for
+ // triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
+ // any, because that will allow us to include the GC time in the CC pause.
+ mCCRunnerState = CCRunnerState::CycleCollecting;
+ [[fallthrough]];
+
+ // CycleCollecting: continue running slices until done.
+ case CCRunnerState::CycleCollecting: {
+ CCRunnerStep step{CCRunnerAction::CycleCollect, Yield};
+ step.mParam.mCCReason = mCCReason;
+ mCCReason = CCReason::SLICE; // Set reason for following slices.
+ return step;
+ }
+
+ default:
+ MOZ_CRASH("Unexpected CCRunner state");
+ };
+}
+
+GCRunnerStep CCGCScheduler::GetNextGCRunnerAction(TimeStamp aDeadline) const {
+ if (InIncrementalGC()) {
+ MOZ_ASSERT(mMajorGCReason != JS::GCReason::NO_REASON);
+ return {GCRunnerAction::GCSlice, mMajorGCReason};
+ }
+
+ // Service a non-eager GC request first, even if it requires waiting.
+ if (mMajorGCReason != JS::GCReason::NO_REASON) {
+ return {mReadyForMajorGC ? GCRunnerAction::StartMajorGC
+ : GCRunnerAction::WaitToMajorGC,
+ mMajorGCReason};
+ }
+
+ // Now for eager requests, which are ignored unless we're idle.
+ if (!aDeadline.IsNull()) {
+ if (mEagerMajorGCReason != JS::GCReason::NO_REASON) {
+ return {mReadyForMajorGC ? GCRunnerAction::StartMajorGC
+ : GCRunnerAction::WaitToMajorGC,
+ mEagerMajorGCReason};
+ }
+
+ if (mEagerMinorGCReason != JS::GCReason::NO_REASON) {
+ return {GCRunnerAction::MinorGC, mEagerMinorGCReason};
+ }
+ }
+
+ return {GCRunnerAction::None, JS::GCReason::NO_REASON};
+}
+
+js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget(
+ TimeStamp aStartTimeStamp, TimeStamp aDeadline) {
+ if (mForgetSkippableFrequencyStartTime.IsNull()) {
+ mForgetSkippableFrequencyStartTime = aStartTimeStamp;
+ } else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime >
+ kOneMinute) {
+ TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute;
+
+ // If we had forget skippables only at the beginning of the interval, we
+ // still want to use the whole time, minute or more, for frequency
+ // calculation. mLastForgetSkippableEndTime is needed if forget skippable
+ // takes enough time to push the interval to be over a minute.
+ TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime);
+
+ // Duration in minutes.
+ double duration =
+ (endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
+ uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
+ frequencyPerMinute);
+ mForgetSkippableCounter = 0;
+ mForgetSkippableFrequencyStartTime = aStartTimeStamp;
+ }
+ ++mForgetSkippableCounter;
+
+ TimeDuration budgetTime =
+ aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
+ return js::SliceBudget(budgetTime);
+}
+
+} // namespace mozilla
diff --git a/dom/base/CCGCScheduler.h b/dom/base/CCGCScheduler.h
new file mode 100644
index 0000000000..3d35af8da4
--- /dev/null
+++ b/dom/base/CCGCScheduler.h
@@ -0,0 +1,541 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/SliceBudget.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/IdleTaskRunner.h"
+#include "mozilla/MainThreadIdlePeriod.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/ipc/IdleSchedulerChild.h"
+#include "nsCycleCollector.h"
+#include "nsJSEnvironment.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+
+static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f);
+
+// The amount of time we wait between a request to CC (after GC ran)
+// and doing the actual CC.
+static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6);
+
+static const TimeDuration kCCSkippableDelay =
+ TimeDuration::FromMilliseconds(250);
+
+// In case the cycle collector isn't run at all, we don't want forget skippables
+// to run too often. So limit the forget skippable cycle to start at earliest 2
+// seconds after the end of the previous cycle.
+static const TimeDuration kTimeBetweenForgetSkippableCycles =
+ TimeDuration::FromSeconds(2);
+
+// ForgetSkippable is usually fast, so we can use small budgets.
+// This isn't a real budget but a hint to IdleTaskRunner whether there
+// is enough time to call ForgetSkippable.
+static const TimeDuration kForgetSkippableSliceDuration =
+ TimeDuration::FromMilliseconds(2);
+
+// Maximum amount of time that should elapse between incremental CC slices
+static const TimeDuration kICCIntersliceDelay =
+ TimeDuration::FromMilliseconds(64);
+
+// Time budget for an incremental CC slice when using timer to run it.
+static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3);
+// Minimum budget for an incremental CC slice when using idle time to run it.
+static const TimeDuration kIdleICCSliceBudget =
+ TimeDuration::FromMilliseconds(2);
+
+// Maximum total duration for an ICC
+static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2);
+
+// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
+// objects in the purple buffer.
+static const TimeDuration kCCForced = kOneMinute * 2;
+static const uint32_t kCCForcedPurpleLimit = 10;
+
+// Don't allow an incremental GC to lock out the CC for too long.
+static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
+
+// Trigger a CC if the purple buffer exceeds this size when we check it.
+static const uint32_t kCCPurpleLimit = 200;
+
+// Actions performed by the GCRunner state machine.
+enum class GCRunnerAction {
+ MinorGC, // Run a minor GC (nursery collection)
+ WaitToMajorGC, // We want to start a new major GC
+ StartMajorGC, // The parent says we may begin our major GC
+ GCSlice, // Run a single slice of a major GC
+ None
+};
+
+struct GCRunnerStep {
+ GCRunnerAction mAction;
+ JS::GCReason mReason;
+};
+
+// Actions that are output from the CCRunner state machine.
+enum class CCRunnerAction {
+ // Do nothing.
+ None,
+
+ // We crossed an eager minor GC threshold in the middle of an incremental CC,
+ // and we have some idle time.
+ MinorGC,
+
+ // Various cleanup actions.
+ ForgetSkippable,
+ CleanupContentUnbinder,
+ CleanupDeferred,
+
+ // Do the actual cycle collection (build the graph etc).
+ CycleCollect,
+
+ // All done.
+ StopRunning
+};
+
+enum CCRunnerYield { Continue, Yield };
+
+enum CCRunnerForgetSkippableRemoveChildless {
+ KeepChildless = false,
+ RemoveChildless = true
+};
+
+struct CCRunnerStep {
+ // The action the scheduler is instructing the caller to perform.
+ CCRunnerAction mAction;
+
+ // Whether to stop processing actions for this invocation of the timer
+ // callback.
+ CCRunnerYield mYield;
+
+ union ActionData {
+ // If the action is ForgetSkippable, then whether to remove childless nodes
+ // or not.
+ CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
+
+ // If the action is CycleCollect, the reason for the collection.
+ CCReason mCCReason;
+
+ // If the action is MinorGC, the reason for the GC.
+ JS::GCReason mReason;
+
+ MOZ_IMPLICIT ActionData(CCRunnerForgetSkippableRemoveChildless v)
+ : mRemoveChildless(v) {}
+ MOZ_IMPLICIT ActionData(CCReason v) : mCCReason(v) {}
+ MOZ_IMPLICIT ActionData(JS::GCReason v) : mReason(v) {}
+ ActionData() = default;
+ } mParam;
+};
+
+class CCGCScheduler {
+ public:
+ CCGCScheduler()
+ : mAskParentBeforeMajorGC(XRE_IsContentProcess()),
+ mReadyForMajorGC(!mAskParentBeforeMajorGC),
+ mInterruptRequested(false) {}
+
+ static bool CCRunnerFired(TimeStamp aDeadline);
+
+ // Parameter setting
+
+ void SetActiveIntersliceGCBudget(TimeDuration aDuration) {
+ mActiveIntersliceGCBudget = aDuration;
+ }
+
+ // State retrieval
+
+ TimeDuration GetCCBlockedTime(TimeStamp aNow) const {
+ MOZ_ASSERT(mInIncrementalGC);
+ MOZ_ASSERT(!mCCBlockStart.IsNull());
+ return aNow - mCCBlockStart;
+ }
+
+ bool InIncrementalGC() const { return mInIncrementalGC; }
+
+ TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; }
+
+ bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const {
+ return mCleanupsSinceLastGC < aN;
+ }
+
+ bool NeedsFullGC() const { return mNeedsFullGC; }
+
+ // Requests
+ void PokeGC(JS::GCReason aReason, JSObject* aObj, TimeDuration aDelay = 0);
+ void PokeShrinkingGC();
+ void PokeFullGC();
+ void MaybePokeCC(TimeStamp aNow, uint32_t aSuspectedCCObjects);
+ void PokeMinorGC(JS::GCReason aReason);
+
+ void UserIsInactive();
+ void UserIsActive();
+ bool IsUserActive() const { return mUserIsActive; }
+
+ void KillShrinkingGCTimer();
+ void KillFullGCTimer();
+ void KillGCRunner();
+ void KillCCRunner();
+ void KillAllTimersAndRunners();
+
+ js::SliceBudget CreateGCSliceBudget(mozilla::TimeDuration aDuration,
+ bool isIdle, bool isExtended) {
+ auto budget = js::SliceBudget(aDuration, &mInterruptRequested);
+ budget.idle = isIdle;
+ budget.extended = isExtended;
+ return budget;
+ }
+
+ /*
+ * aDelay is the delay before the first time the idle task runner runs.
+ * Then it runs every
+ * StaticPrefs::javascript_options_gc_delay_interslice()
+ */
+ void EnsureGCRunner(TimeDuration aDelay);
+
+ void EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget);
+
+ // State modification
+
+ void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; }
+
+ void SetWantMajorGC(JS::GCReason aReason) {
+ MOZ_ASSERT(aReason != JS::GCReason::NO_REASON);
+
+ // If the GC being requested is not a shrinking GC set this flag.
+ // If/when the shrinking GC timer fires but the user is active we check
+ // this flag before canceling the GC, so as not to cancel the
+ // non-shrinking GC being requested here.
+ if (aReason != JS::GCReason::USER_INACTIVE) {
+ mWantAtLeastRegularGC = true;
+ }
+
+ // Force full GCs when called from reftests so that we collect dead zones
+ // that have not been scheduled for collection.
+ if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
+ SetNeedsFullGC();
+ }
+
+ // USER_INACTIVE trumps everything,
+ // FULL_GC_TIMER trumps everything except USER_INACTIVE,
+ // all other reasons just use the latest reason.
+ switch (aReason) {
+ case JS::GCReason::USER_INACTIVE:
+ mMajorGCReason = aReason;
+ break;
+ case JS::GCReason::FULL_GC_TIMER:
+ if (mMajorGCReason != JS::GCReason::USER_INACTIVE) {
+ mMajorGCReason = aReason;
+ }
+ break;
+ default:
+ if (mMajorGCReason != JS::GCReason::USER_INACTIVE &&
+ mMajorGCReason != JS::GCReason::FULL_GC_TIMER) {
+ mMajorGCReason = aReason;
+ }
+ break;
+ }
+ }
+
+ void SetWantEagerMinorGC(JS::GCReason aReason) {
+ if (mEagerMinorGCReason == JS::GCReason::NO_REASON) {
+ mEagerMinorGCReason = aReason;
+ }
+ }
+
+ // Ensure that the current runner does a cycle collection, and trigger a GC
+ // after it finishes.
+ void EnsureCCThenGC(CCReason aReason) {
+ MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
+ MOZ_ASSERT(aReason != CCReason::NO_REASON);
+ mNeedsFullCC = aReason;
+ mNeedsGCAfterCC = true;
+ }
+
+ // Returns false if we started and finished a major GC while waiting for a
+ // response.
+ [[nodiscard]] bool NoteReadyForMajorGC() {
+ if (mMajorGCReason == JS::GCReason::NO_REASON || InIncrementalGC()) {
+ return false;
+ }
+ mReadyForMajorGC = true;
+ return true;
+ }
+
+ // Starting a major GC (incremental or non-incremental).
+ void NoteGCBegin(JS::GCReason aReason);
+
+ // Major GC completed.
+ void NoteGCEnd();
+
+ // A timer fired, but then decided not to run a GC.
+ void NoteWontGC();
+
+ void NoteMinorGCEnd() { mEagerMinorGCReason = JS::GCReason::NO_REASON; }
+
+ // This is invoked when we reach the actual cycle collection portion of the
+ // overall cycle collection.
+ void NoteCCBegin(CCReason aReason, TimeStamp aWhen,
+ uint32_t aNumForgetSkippables, uint32_t aSuspected,
+ uint32_t aRemovedPurples);
+
+ // This is invoked when the whole process of collection is done -- i.e., CC
+ // preparation (eg ForgetSkippables) in addition to the CC itself. There
+ // really ought to be a separate name for the overall CC as opposed to the
+ // actual cycle collection portion.
+ void NoteCCEnd(const CycleCollectorResults& aResults, TimeStamp aWhen,
+ mozilla::TimeDuration aMaxSliceTime);
+
+ // A single slice has completed.
+ void NoteGCSliceEnd(TimeStamp aStart, TimeStamp aEnd);
+
+ bool GCRunnerFired(TimeStamp aDeadline);
+ bool GCRunnerFiredDoGC(TimeStamp aDeadline, const GCRunnerStep& aStep);
+
+ using MayGCPromise =
+ MozPromise<bool, mozilla::ipc::ResponseRejectReason, true>;
+
+ // Returns null if we shouldn't GC now (eg a GC is already running).
+ static RefPtr<MayGCPromise> MayGCNow(JS::GCReason reason);
+
+ // Check all of the various collector timers/runners and see if they are
+ // waiting to fire. This does not check the Full GC Timer, as that's a
+ // more expensive collection we run on a long timer.
+ void RunNextCollectorTimer(JS::GCReason aReason,
+ mozilla::TimeStamp aDeadline);
+
+ // When we decide to do a cycle collection but we're in the middle of an
+ // incremental GC, the CC is "locked out" until the GC completes -- unless
+ // the wait is too long, and we decide to finish the incremental GC early.
+ void BlockCC(TimeStamp aNow) {
+ MOZ_ASSERT(mInIncrementalGC);
+ MOZ_ASSERT(mCCBlockStart.IsNull());
+ mCCBlockStart = aNow;
+ }
+
+ void UnblockCC() { mCCBlockStart = TimeStamp(); }
+
+ // Returns the number of purple buffer items that were processed and removed.
+ uint32_t NoteForgetSkippableComplete(TimeStamp aNow,
+ uint32_t aSuspectedBeforeForgetSkippable,
+ uint32_t aSuspectedCCObjects) {
+ mLastForgetSkippableEndTime = aNow;
+ mPreviousSuspectedCount = aSuspectedCCObjects;
+ mCleanupsSinceLastGC++;
+ return aSuspectedBeforeForgetSkippable - aSuspectedCCObjects;
+ }
+
+ // Test if we are in the NoteCCBegin .. NoteCCEnd interval.
+ bool IsCollectingCycles() const { return mIsCollectingCycles; }
+
+ // The CC was abandoned without running a slice, so we only did forget
+ // skippables. Prevent running another cycle soon.
+ void NoteForgetSkippableOnlyCycle(TimeStamp aNow) {
+ mLastForgetSkippableCycleEndTime = aNow;
+ }
+
+ void Shutdown() {
+ mDidShutdown = true;
+ KillAllTimersAndRunners();
+ }
+
+ // Scheduling
+
+ // Return a budget along with a boolean saying whether to prefer to run short
+ // slices and stop rather than continuing to the next phase of cycle
+ // collection.
+ js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
+ TimeStamp aCCBeginTime,
+ TimeStamp aPrevSliceEndTime,
+ TimeStamp aNow,
+ bool* aPreferShorterSlices) const;
+
+ js::SliceBudget ComputeInterSliceGCBudget(TimeStamp aDeadline,
+ TimeStamp aNow);
+
+ bool ShouldForgetSkippable(uint32_t aSuspectedCCObjects) const {
+ // Only do a forget skippable if there are more than a few new objects
+ // or we're doing the initial forget skippables.
+ return ((mPreviousSuspectedCount + 100) <= aSuspectedCCObjects) ||
+ mCleanupsSinceLastGC < kMajorForgetSkippableCalls;
+ }
+
+ // There is reason to suspect that there may be a significant amount of
+ // garbage to cycle collect: either we just finished a GC, or the purple
+ // buffer is getting really big, or it's getting somewhat big and it has been
+ // too long since the last CC.
+ CCReason IsCCNeeded(TimeStamp aNow, uint32_t aSuspectedCCObjects) const {
+ if (mNeedsFullCC != CCReason::NO_REASON) {
+ return mNeedsFullCC;
+ }
+ if (aSuspectedCCObjects > kCCPurpleLimit) {
+ return CCReason::MANY_SUSPECTED;
+ }
+ if (aSuspectedCCObjects > kCCForcedPurpleLimit && mLastCCEndTime &&
+ aNow - mLastCCEndTime > kCCForced) {
+ return CCReason::TIMED;
+ }
+ return CCReason::NO_REASON;
+ }
+
+ mozilla::CCReason ShouldScheduleCC(TimeStamp aNow,
+ uint32_t aSuspectedCCObjects) const;
+
+ // If we collected a substantial amount of cycles, poke the GC since more
+ // objects might be unreachable now.
+ bool NeedsGCAfterCC() const {
+ return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 ||
+ mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC;
+ }
+
+ bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
+ int32_t numEarlyTimerFires =
+ std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
+
+ return aCurrentFireCount >= numEarlyTimerFires;
+ }
+
+ enum class CCRunnerState {
+ Inactive,
+ ReducePurple,
+ CleanupChildless,
+ CleanupContentUnbinder,
+ CleanupDeferred,
+ StartCycleCollection,
+ CycleCollecting,
+ Canceled,
+ NumStates
+ };
+
+ void InitCCRunnerStateMachine(CCRunnerState initialState, CCReason aReason) {
+ if (mCCRunner) {
+ return;
+ }
+
+ MOZ_ASSERT(mCCReason == CCReason::NO_REASON);
+ mCCReason = aReason;
+
+ // The state machine should always have been deactivated after the previous
+ // collection, however far that collection may have gone.
+ MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive,
+ "DeactivateCCRunner should have been called");
+ mCCRunnerState = initialState;
+
+ // Currently, there are only two entry points to the non-Inactive part of
+ // the state machine.
+ if (initialState == CCRunnerState::ReducePurple) {
+ mCCDelay = kCCDelay;
+ mCCRunnerEarlyFireCount = 0;
+ } else if (initialState == CCRunnerState::CycleCollecting) {
+ // Nothing needed.
+ } else {
+ MOZ_CRASH("Invalid initial state");
+ }
+ }
+
+ void DeactivateCCRunner() {
+ mCCRunnerState = CCRunnerState::Inactive;
+ mCCReason = CCReason::NO_REASON;
+ }
+
+ bool HasMoreIdleGCRunnerWork() const {
+ return mMajorGCReason != JS::GCReason::NO_REASON ||
+ mEagerMajorGCReason != JS::GCReason::NO_REASON ||
+ mEagerMinorGCReason != JS::GCReason::NO_REASON;
+ }
+
+ GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline) const;
+
+ CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
+ uint32_t aSuspectedCCObjects);
+
+ // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
+ // time ago, if an incremental GC needed to be finished.
+ js::SliceBudget ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp,
+ TimeStamp aDeadline);
+
+ private:
+ // State
+
+ // An incremental GC is in progress, which blocks the CC from running for its
+ // duration (or until it goes too long and is finished synchronously.)
+ bool mInIncrementalGC = false;
+
+ // Whether to ask the parent process if now is a good time to GC (false for
+ // the parent process.)
+ const bool mAskParentBeforeMajorGC;
+
+ // We've asked the parent process if now is a good time to GC (do not ask
+ // again).
+ bool mHaveAskedParent = false;
+
+ // The parent process is ready for us to do a major GC.
+ bool mReadyForMajorGC;
+
+ // Set when the IdleTaskRunner requests the current task be interrupted.
+ // Cleared when the GC slice budget has detected the interrupt request.
+ js::SliceBudget::InterruptRequestFlag mInterruptRequested;
+
+ // When a shrinking GC has been requested but we back-out, if this is true
+ // we run a non-shrinking GC.
+ bool mWantAtLeastRegularGC = false;
+
+ // When the CC started actually waiting for the GC to finish. This will be
+ // set to non-null at a later time than mCCLockedOut.
+ TimeStamp mCCBlockStart;
+
+ bool mDidShutdown = false;
+
+ TimeStamp mLastForgetSkippableEndTime;
+ uint32_t mForgetSkippableCounter = 0;
+ TimeStamp mForgetSkippableFrequencyStartTime;
+ TimeStamp mLastCCEndTime;
+ TimeStamp mLastForgetSkippableCycleEndTime;
+
+ CCRunnerState mCCRunnerState = CCRunnerState::Inactive;
+ int32_t mCCRunnerEarlyFireCount = 0;
+ TimeDuration mCCDelay = kCCDelay;
+
+ // Prevent the very first CC from running before we have GC'd and set the
+ // gray bits.
+ bool mHasRunGC = false;
+
+ mozilla::CCReason mNeedsFullCC = CCReason::NO_REASON;
+ bool mNeedsFullGC = true;
+ bool mNeedsGCAfterCC = false;
+ uint32_t mPreviousSuspectedCount = 0;
+
+ uint32_t mCleanupsSinceLastGC = UINT32_MAX;
+
+ // If the GC runner triggers a GC slice, this will be set to the idle deadline
+ // or the null timestamp if non-idle. It will be Nothing at the end of an
+ // internally-triggered slice.
+ mozilla::Maybe<TimeStamp> mTriggeredGCDeadline;
+
+ RefPtr<IdleTaskRunner> mGCRunner;
+ RefPtr<IdleTaskRunner> mCCRunner;
+ nsITimer* mShrinkingGCTimer = nullptr;
+ nsITimer* mFullGCTimer = nullptr;
+
+ mozilla::CCReason mCCReason = mozilla::CCReason::NO_REASON;
+ JS::GCReason mMajorGCReason = JS::GCReason::NO_REASON;
+ JS::GCReason mEagerMajorGCReason = JS::GCReason::NO_REASON;
+ JS::GCReason mEagerMinorGCReason = JS::GCReason::NO_REASON;
+
+ bool mIsCompactingOnUserInactive = false;
+ bool mIsCollectingCycles = false;
+ bool mUserIsActive = true;
+
+ public:
+ uint32_t mCCollectedWaitingForGC = 0;
+ uint32_t mCCollectedZonesWaitingForGC = 0;
+ uint32_t mLikelyShortLivingObjectsNeedingGC = 0;
+
+ // Configuration parameters
+
+ TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
+};
+
+} // namespace mozilla
diff --git a/dom/base/CORSMode.h b/dom/base/CORSMode.h
new file mode 100644
index 0000000000..25ad0437ba
--- /dev/null
+++ b/dom/base/CORSMode.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CORSMode_h_
+#define CORSMode_h_
+
+#include <stdint.h>
+
+namespace mozilla {
+
+enum CORSMode : uint8_t {
+ /**
+ * The default of not using CORS to validate cross-origin loads.
+ */
+ CORS_NONE,
+
+ /**
+ * Validate cross-site loads using CORS, but do not send any credentials
+ * (cookies, HTTP auth logins, etc) along with the request.
+ */
+ CORS_ANONYMOUS,
+
+ /**
+ * Validate cross-site loads using CORS, and send credentials such as cookies
+ * and HTTP auth logins along with the request.
+ */
+ CORS_USE_CREDENTIALS
+};
+
+constexpr auto kFirstCORSMode = CORS_NONE;
+constexpr auto kLastCORSMode = CORS_USE_CREDENTIALS;
+
+} // namespace mozilla
+
+#endif /* CORSMode_h_ */
diff --git a/dom/base/CallState.h b/dom/base/CallState.h
new file mode 100644
index 0000000000..e9c312dbe4
--- /dev/null
+++ b/dom/base/CallState.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CallState_h
+#define mozilla_CallState_h
+
+namespace mozilla {
+
+// An enum class to be used for returned value of callback functions. If Stop
+// is returned from the callback function, caller stops calling further
+// children. If Continue is returned then caller will keep calling further
+// children.
+enum class CallState {
+ Continue,
+ Stop,
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CallSate_h
diff --git a/dom/base/CharacterData.cpp b/dom/base/CharacterData.cpp
new file mode 100644
index 0000000000..da197aaacb
--- /dev/null
+++ b/dom/base/CharacterData.cpp
@@ -0,0 +1,591 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for DOM Core's Comment, DocumentType, Text,
+ * CDATASection and ProcessingInstruction nodes.
+ */
+
+#include "mozilla/dom/CharacterData.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Document.h"
+#include "nsReadableUtils.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "nsCOMPtr.h"
+#include "nsDOMString.h"
+#include "nsChangeHint.h"
+#include "nsCOMArray.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIContentInlines.h"
+#include "nsTextNode.h"
+#include "nsBidiUtils.h"
+#include "PLDHashTable.h"
+#include "mozilla/Sprintf.h"
+#include "nsWindowSizes.h"
+#include "nsWrapperCacheInlines.h"
+
+#if defined(ACCESSIBILITY) && defined(DEBUG)
+# include "nsAccessibilityService.h"
+#endif
+
+namespace mozilla::dom {
+
+CharacterData::CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
+ : nsIContent(std::move(aNodeInfo)) {
+ MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE ||
+ mNodeInfo->NodeType() == CDATA_SECTION_NODE ||
+ mNodeInfo->NodeType() == COMMENT_NODE ||
+ mNodeInfo->NodeType() == PROCESSING_INSTRUCTION_NODE ||
+ mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE,
+ "Bad NodeType in aNodeInfo");
+}
+
+CharacterData::~CharacterData() {
+ MOZ_ASSERT(!IsInUncomposedDoc(),
+ "Please remove this from the document properly");
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ }
+}
+
+Element* CharacterData::GetNameSpaceElement() {
+ return Element::FromNodeOrNull(GetParentNode());
+}
+
+// Note, _INHERITED macro isn't used here since nsINode implementations are
+// rather special.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CharacterData)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CharacterData)
+ return Element::CanSkip(tmp, aRemovingAllowed);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CharacterData)
+ return Element::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CharacterData)
+ return Element::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits
+// we should traverse should be added here or in nsINode::Traverse.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CharacterData)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[40];
+ SprintfLiteral(name, "CharacterData (len=%d)", tmp->mText.GetLength());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CharacterData, tmp->mRefCnt.get())
+ }
+
+ if (!nsIContent::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// We purposefully don't UNLINK_BEGIN_INHERITED here.
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CharacterData)
+ nsIContent::Unlink(tmp);
+
+ if (nsContentSlots* slots = tmp->GetExistingContentSlots()) {
+ slots->Unlink(*tmp);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN(CharacterData)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(CharacterData)
+NS_INTERFACE_MAP_END_INHERITING(nsIContent)
+
+void CharacterData::GetNodeValueInternal(nsAString& aNodeValue) {
+ GetData(aNodeValue);
+}
+
+void CharacterData::SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError) {
+ aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(),
+ aNodeValue.Length(), true);
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of CharacterData
+
+void CharacterData::SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) {
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
+ return SetNodeValue(aTextContent, aError);
+}
+
+void CharacterData::GetData(nsAString& aData) const {
+ if (mText.Is2b()) {
+ aData.Truncate();
+ mText.AppendTo(aData);
+ } else {
+ // Must use Substring() since nsDependentCString() requires null
+ // terminated strings.
+
+ const char* data = mText.Get1b();
+
+ if (data) {
+ CopyASCIItoUTF16(Substring(data, data + mText.GetLength()), aData);
+ } else {
+ aData.Truncate();
+ }
+ }
+}
+
+void CharacterData::SetData(const nsAString& aData, ErrorResult& aRv) {
+ nsresult rv = SetTextInternal(0, mText.GetLength(), aData.BeginReading(),
+ aData.Length(), true);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void CharacterData::SubstringData(uint32_t aStart, uint32_t aCount,
+ nsAString& aReturn, ErrorResult& rv) {
+ aReturn.Truncate();
+
+ uint32_t textLength = mText.GetLength();
+ if (aStart > textLength) {
+ rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ uint32_t amount = aCount;
+ if (amount > textLength - aStart) {
+ amount = textLength - aStart;
+ }
+
+ if (mText.Is2b()) {
+ aReturn.Assign(mText.Get2b() + aStart, amount);
+ } else {
+ // Must use Substring() since nsDependentCString() requires null
+ // terminated strings.
+
+ const char* data = mText.Get1b() + aStart;
+ CopyASCIItoUTF16(Substring(data, data + amount), aReturn);
+ }
+}
+
+//----------------------------------------------------------------------
+
+void CharacterData::AppendData(const nsAString& aData, ErrorResult& aRv) {
+ InsertData(mText.GetLength(), aData, aRv);
+}
+
+void CharacterData::InsertData(uint32_t aOffset, const nsAString& aData,
+ ErrorResult& aRv) {
+ nsresult rv =
+ SetTextInternal(aOffset, 0, aData.BeginReading(), aData.Length(), true);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void CharacterData::DeleteData(uint32_t aOffset, uint32_t aCount,
+ ErrorResult& aRv) {
+ nsresult rv = SetTextInternal(aOffset, aCount, nullptr, 0, true);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void CharacterData::ReplaceData(uint32_t aOffset, uint32_t aCount,
+ const nsAString& aData, ErrorResult& aRv) {
+ nsresult rv = SetTextInternal(aOffset, aCount, aData.BeginReading(),
+ aData.Length(), true);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+nsresult CharacterData::SetTextInternal(
+ uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer,
+ uint32_t aLength, bool aNotify,
+ CharacterDataChangeInfo::Details* aDetails) {
+ MOZ_ASSERT(aBuffer || !aLength, "Null buffer passed to SetTextInternal!");
+
+ // sanitize arguments
+ uint32_t textLength = mText.GetLength();
+ if (aOffset > textLength) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ if (aCount > textLength - aOffset) {
+ aCount = textLength - aOffset;
+ }
+
+ uint32_t endOffset = aOffset + aCount;
+
+ // Make sure the text fragment can hold the new data.
+ if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ bool haveMutationListeners =
+ aNotify && nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED, this);
+
+ RefPtr<nsAtom> oldValue;
+ if (haveMutationListeners) {
+ oldValue = GetCurrentValueAtom();
+ }
+
+ if (aNotify) {
+ CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
+ aLength, aDetails};
+ MutationObservers::NotifyCharacterDataWillChange(this, info);
+ }
+
+ Directionality oldDir = eDir_NotSet;
+ bool dirAffectsAncestor =
+ (NodeType() == TEXT_NODE &&
+ TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir,
+ aOffset));
+
+ if (aOffset == 0 && endOffset == textLength) {
+ // Replacing whole text or old text was empty.
+ // If this is marked as "maybe modified frequently", the text should be
+ // stored as char16_t since converting char* to char16_t* is expensive.
+ bool ok = mText.SetTo(aBuffer, aLength, true,
+ HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ } else if (aOffset == textLength) {
+ // Appending to existing.
+ bool ok = mText.Append(aBuffer, aLength, !mText.IsBidi(),
+ HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ // Merging old and new
+
+ bool bidi = mText.IsBidi();
+
+ // Allocate new buffer
+ const uint32_t newLength = textLength - aCount + aLength;
+ // Use nsString and not nsAutoString so that we get a nsStringBuffer which
+ // can be just AddRefed in nsTextFragment.
+ nsString to;
+ to.SetCapacity(newLength);
+
+ // Copy over appropriate data
+ if (aOffset) {
+ mText.AppendTo(to, 0, aOffset);
+ }
+ if (aLength) {
+ to.Append(aBuffer, aLength);
+ if (!bidi) {
+ bidi = HasRTLChars(Span(aBuffer, aLength));
+ }
+ }
+ if (endOffset != textLength) {
+ mText.AppendTo(to, endOffset, textLength - endOffset);
+ }
+
+ // If this is marked as "maybe modified frequently", the text should be
+ // stored as char16_t since converting char* to char16_t* is expensive.
+ // Use char16_t also when we have bidi characters.
+ bool use2b = HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY) || bidi;
+ bool ok = mText.SetTo(to, false, use2b);
+ mText.SetBidi(bidi);
+
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
+
+ if (document && mText.IsBidi()) {
+ // If we found bidi characters in mText.SetTo() above, indicate that the
+ // document contains bidi characters.
+ document->SetBidiEnabled();
+ }
+
+ if (dirAffectsAncestor) {
+ // dirAffectsAncestor being true implies that we have a text node, see
+ // above.
+ MOZ_ASSERT(NodeType() == TEXT_NODE);
+ TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify);
+ }
+
+ // Notify observers
+ if (aNotify) {
+ CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
+ aLength, aDetails};
+ MutationObservers::NotifyCharacterDataChanged(this, info);
+
+ if (haveMutationListeners) {
+ InternalMutationEvent mutation(true, eLegacyCharacterDataModified);
+
+ mutation.mPrevAttrValue = oldValue;
+ if (aLength > 0) {
+ nsAutoString val;
+ mText.AppendTo(val);
+ mutation.mNewAttrValue = NS_Atomize(val);
+ }
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation);
+ }
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of nsIContent
+
+#ifdef MOZ_DOM_LIST
+void CharacterData::ToCString(nsAString& aBuf, int32_t aOffset,
+ int32_t aLen) const {
+ if (mText.Is2b()) {
+ const char16_t* cp = mText.Get2b() + aOffset;
+ const char16_t* end = cp + aLen;
+
+ while (cp < end) {
+ char16_t ch = *cp++;
+ if (ch == '&') {
+ aBuf.AppendLiteral("&amp;");
+ } else if (ch == '<') {
+ aBuf.AppendLiteral("&lt;");
+ } else if (ch == '>') {
+ aBuf.AppendLiteral("&gt;");
+ } else if ((ch < ' ') || (ch >= 127)) {
+ aBuf.AppendPrintf("\\u%04x", ch);
+ } else {
+ aBuf.Append(ch);
+ }
+ }
+ } else {
+ unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset;
+ const unsigned char* end = cp + aLen;
+
+ while (cp < end) {
+ char16_t ch = *cp++;
+ if (ch == '&') {
+ aBuf.AppendLiteral("&amp;");
+ } else if (ch == '<') {
+ aBuf.AppendLiteral("&lt;");
+ } else if (ch == '>') {
+ aBuf.AppendLiteral("&gt;");
+ } else if ((ch < ' ') || (ch >= 127)) {
+ aBuf.AppendPrintf("\\u%04x", ch);
+ } else {
+ aBuf.Append(ch);
+ }
+ }
+ }
+}
+#endif
+
+nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) {
+ MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
+ "Must have content or document parent!");
+ MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
+ "Must have the same owner document");
+ MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
+ MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
+ MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
+ // Note that as we recurse into the kids, they'll have a non-null parent. So
+ // only assert if our parent is _changing_ while we have a parent.
+ MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
+ "Already have a parent. Unbind first!");
+
+ const bool hadParent = !!GetParentNode();
+
+ if (aParent.IsInNativeAnonymousSubtree()) {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+ if (IsRootOfNativeAnonymousSubtree()) {
+ aParent.SetMayHaveAnonymousChildren();
+ } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
+ SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
+ }
+
+ // Set parent
+ mParent = &aParent;
+ if (!hadParent && aParent.IsContent()) {
+ SetParentIsContent(true);
+ NS_ADDREF(mParent);
+ }
+ MOZ_ASSERT(!!GetParent() == aParent.IsContent());
+
+ if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
+ // We no longer need to track the subtree pointer (and in fact we'll assert
+ // if we do this any later).
+ ClearSubtreeRootPointer();
+ SetIsConnected(aParent.IsInComposedDoc());
+
+ if (aParent.IsInUncomposedDoc()) {
+ SetIsInDocument();
+ } else {
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ MOZ_ASSERT(aParent.IsContent() &&
+ aParent.AsContent()->GetContainingShadow());
+ ExtendedContentSlots()->mContainingShadow =
+ aParent.AsContent()->GetContainingShadow();
+ }
+
+ if (IsInComposedDoc() && mText.IsBidi()) {
+ aContext.OwnerDoc().SetBidiEnabled();
+ }
+
+ // Clear the lazy frame construction bits.
+ UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ } else {
+ // If we're not in the doc and not in a shadow tree,
+ // update our subtree pointer.
+ SetSubtreeRootPointer(aParent.SubtreeRoot());
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+
+ UpdateEditableState(false);
+
+ // Ensure we only do these once, in the case we move the shadow host around.
+ if (aContext.SubtreeRootChanges()) {
+ HandleShadowDOMRelatedInsertionSteps(hadParent);
+ }
+
+ MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
+ MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
+ MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
+ MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
+ MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
+ MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
+ MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
+ MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
+ return NS_OK;
+}
+
+void CharacterData::UnbindFromTree(bool aNullParent) {
+ // Unset frame flags; if we need them again later, they'll get set again.
+ UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE);
+
+ HandleShadowDOMRelatedRemovalSteps(aNullParent);
+
+ if (aNullParent) {
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ } else {
+ mParent = nullptr;
+ }
+ SetParentIsContent(false);
+ }
+ ClearInDocument();
+ SetIsConnected(false);
+
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // Begin keeping track of our subtree root.
+ SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+ }
+
+ if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) {
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ slots->mContainingShadow = nullptr;
+ }
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+
+#if defined(ACCESSIBILITY) && defined(DEBUG)
+ MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
+ "An accessible for this element still exists!");
+#endif
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of the nsIContent interface text functions
+
+nsresult CharacterData::SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) {
+ return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify);
+}
+
+nsresult CharacterData::AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) {
+ return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify);
+}
+
+bool CharacterData::TextIsOnlyWhitespace() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!ThreadSafeTextIsOnlyWhitespace()) {
+ UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
+ SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
+ return false;
+ }
+
+ SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
+ return true;
+}
+
+bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const {
+ // FIXME: should this method take content language into account?
+ if (mText.Is2b()) {
+ // The fragment contains non-8bit characters and such characters
+ // are never considered whitespace.
+ //
+ // FIXME(emilio): This is not quite true in presence of the
+ // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on
+ // anonymous nodes, so should be fine...
+ return false;
+ }
+
+ if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) {
+ return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE);
+ }
+
+ const char* cp = mText.Get1b();
+ const char* end = cp + mText.GetLength();
+
+ while (cp < end) {
+ char ch = *cp;
+
+ // NOTE(emilio): If you ever change the definition of "whitespace" here, you
+ // need to change it too in RestyleManager::CharacterDataChanged.
+ if (!dom::IsSpaceCharacter(ch)) {
+ return false;
+ }
+
+ ++cp;
+ }
+
+ return true;
+}
+
+already_AddRefed<nsAtom> CharacterData::GetCurrentValueAtom() {
+ nsAutoString val;
+ GetData(val);
+ return NS_Atomize(val);
+}
+
+void CharacterData::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
+ *aNodeSize += mText.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/CharacterData.h b/dom/base/CharacterData.h
new file mode 100644
index 0000000000..8e008b134b
--- /dev/null
+++ b/dom/base/CharacterData.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for DOM Core's Comment, DocumentType, Text,
+ * CDATASection, and ProcessingInstruction nodes.
+ */
+
+#ifndef mozilla_dom_CharacterData_h
+#define mozilla_dom_CharacterData_h
+
+#include "mozilla/Attributes.h"
+#include "nsIContent.h"
+#include "nsIMutationObserver.h"
+
+#include "nsTextFragment.h"
+#include "nsError.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+class Element;
+class HTMLSlotElement;
+} // namespace mozilla::dom
+
+#define CHARACTER_DATA_FLAG_BIT(n_) \
+ NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Data node specific flags
+enum {
+ // This bit is set to indicate that if the text node changes to
+ // non-whitespace, we may need to create a frame for it. This bit must
+ // not be set on nodes that already have a frame.
+ NS_CREATE_FRAME_IF_NON_WHITESPACE = CHARACTER_DATA_FLAG_BIT(0),
+
+ // This bit is set to indicate that if the text node changes to
+ // whitespace, we may need to reframe it (or its ancestors).
+ NS_REFRAME_IF_WHITESPACE = CHARACTER_DATA_FLAG_BIT(1),
+
+ // This bit is set to indicate that we have a cached
+ // TextIsOnlyWhitespace value
+ NS_CACHED_TEXT_IS_ONLY_WHITESPACE = CHARACTER_DATA_FLAG_BIT(2),
+
+ // This bit is only meaningful if the NS_CACHED_TEXT_IS_ONLY_WHITESPACE
+ // bit is set, and if so it indicates whether we're only whitespace or
+ // not.
+ NS_TEXT_IS_ONLY_WHITESPACE = CHARACTER_DATA_FLAG_BIT(3),
+
+ // This bit is set if there is a NewlineProperty attached to the node
+ // (used by nsTextFrame).
+ NS_HAS_NEWLINE_PROPERTY = CHARACTER_DATA_FLAG_BIT(4),
+
+ // This bit is set if there is a FlowLengthProperty attached to the node
+ // (used by nsTextFrame).
+ NS_HAS_FLOWLENGTH_PROPERTY = CHARACTER_DATA_FLAG_BIT(5),
+
+ // This bit is set if the node may be modified frequently. This is typically
+ // specified if the instance is in <input> or <textarea>.
+ NS_MAYBE_MODIFIED_FREQUENTLY = CHARACTER_DATA_FLAG_BIT(6),
+
+ // This bit is set if the node may be masked because of being in a password
+ // field.
+ NS_MAYBE_MASKED = CHARACTER_DATA_FLAG_BIT(7),
+};
+
+// Make sure we have enough space for those bits
+ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 8);
+
+#undef CHARACTER_DATA_FLAG_BIT
+
+namespace mozilla::dom {
+
+class CharacterData : public nsIContent {
+ public:
+ // We want to avoid the overhead of extra function calls for
+ // refcounting when we're not doing refcount logging, so we can't
+ // NS_DECL_ISUPPORTS_INHERITED.
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(CharacterData, nsIContent);
+
+ NS_DECL_ADDSIZEOFEXCLUDINGTHIS
+
+ explicit CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo);
+
+ void MarkAsMaybeModifiedFrequently() {
+ SetFlags(NS_MAYBE_MODIFIED_FREQUENTLY);
+ }
+ void MarkAsMaybeMasked() { SetFlags(NS_MAYBE_MASKED); }
+
+ NS_IMPL_FROMNODE_HELPER(CharacterData, IsCharacterData())
+
+ void GetNodeValueInternal(nsAString& aNodeValue) override;
+ void SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError) override;
+
+ void GetTextContentInternal(nsAString& aTextContent, OOMReporter&) final {
+ GetNodeValue(aTextContent);
+ }
+
+ void SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) final;
+
+ // Implementation for nsIContent
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+
+ void UnbindFromTree(bool aNullParent = true) override;
+
+ const nsTextFragment* GetText() override { return &mText; }
+ uint32_t TextLength() const final { return TextDataLength(); }
+
+ const nsTextFragment& TextFragment() const { return mText; }
+ uint32_t TextDataLength() const { return mText.GetLength(); }
+
+ /**
+ * Set the text to the given value. If aNotify is true then
+ * the document is notified of the content change.
+ */
+ nsresult SetText(const char16_t* aBuffer, uint32_t aLength, bool aNotify);
+ /**
+ * Append the given value to the current text. If aNotify is true then
+ * the document is notified of the content change.
+ */
+ nsresult SetText(const nsAString& aStr, bool aNotify) {
+ return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
+ }
+
+ /**
+ * Append the given value to the current text. If aNotify is true then
+ * the document is notified of the content change.
+ */
+ nsresult AppendText(const char16_t* aBuffer, uint32_t aLength, bool aNotify);
+
+ bool TextIsOnlyWhitespace() final;
+ bool ThreadSafeTextIsOnlyWhitespace() const final;
+
+ /**
+ * Append the text content to aResult.
+ */
+ void AppendTextTo(nsAString& aResult) const { mText.AppendTo(aResult); }
+
+ /**
+ * Append the text content to aResult.
+ */
+ [[nodiscard]] bool AppendTextTo(nsAString& aResult,
+ const fallible_t& aFallible) const {
+ return mText.AppendTo(aResult, aFallible);
+ }
+
+ void SaveSubtreeState() final {}
+
+#ifdef MOZ_DOM_LIST
+ void ToCString(nsAString& aBuf, int32_t aOffset, int32_t aLen) const;
+
+ void List(FILE* out, int32_t aIndent) const override {}
+
+ void DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const override {}
+#endif
+
+ nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const override {
+ RefPtr<CharacterData> result = CloneDataNode(aNodeInfo, true);
+ result.forget(aResult);
+
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+ }
+
+ // WebIDL API
+ void GetData(nsAString& aData) const;
+ virtual void SetData(const nsAString& aData, ErrorResult& rv);
+ // nsINode::Length() returns the right thing for our length attribute
+ void SubstringData(uint32_t aStart, uint32_t aCount, nsAString& aReturn,
+ ErrorResult& rv);
+ void AppendData(const nsAString& aData, ErrorResult& rv);
+ void InsertData(uint32_t aOffset, const nsAString& aData, ErrorResult& rv);
+ void DeleteData(uint32_t aOffset, uint32_t aCount, ErrorResult& rv);
+ void ReplaceData(uint32_t aOffset, uint32_t aCount, const nsAString& aData,
+ ErrorResult& rv);
+
+ //----------------------------------------
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_INHERITED(CharacterData,
+ nsIContent)
+
+ /**
+ * Compare two CharacterData nodes for text equality.
+ */
+ [[nodiscard]] bool TextEquals(const CharacterData* aOther) const {
+ return mText.TextEquals(aOther->mText);
+ }
+
+ protected:
+ virtual ~CharacterData();
+
+ Element* GetNameSpaceElement() final;
+
+ nsresult SetTextInternal(
+ uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer,
+ uint32_t aLength, bool aNotify,
+ CharacterDataChangeInfo::Details* aDetails = nullptr);
+
+ /**
+ * Method to clone this node. This needs to be overriden by all derived
+ * classes. If aCloneText is true the text content will be cloned too.
+ *
+ * @param aOwnerDocument the ownerDocument of the clone
+ * @param aCloneText if true the text content will be cloned too
+ * @return the clone
+ */
+ virtual already_AddRefed<CharacterData> CloneDataNode(
+ dom::NodeInfo* aNodeInfo, bool aCloneText) const = 0;
+
+ nsTextFragment mText;
+
+ private:
+ already_AddRefed<nsAtom> GetCurrentValueAtom();
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_CharacterData_h */
diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp
new file mode 100644
index 0000000000..36d8434e82
--- /dev/null
+++ b/dom/base/ChildIterator.cpp
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIFrame.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla::dom {
+
+FlattenedChildIterator::FlattenedChildIterator(const nsIContent* aParent,
+ bool aStartAtBeginning)
+ : mParent(aParent), mOriginalParent(aParent), mIsFirst(aStartAtBeginning) {
+ if (!mParent->IsElement()) {
+ // TODO(emilio): I think it probably makes sense to only allow constructing
+ // FlattenedChildIterators with Element.
+ return;
+ }
+
+ if (ShadowRoot* shadow = mParent->AsElement()->GetShadowRoot()) {
+ mParent = shadow;
+ mShadowDOMInvolved = true;
+ return;
+ }
+
+ if (const auto* slot = HTMLSlotElement::FromNode(mParent)) {
+ if (!slot->AssignedNodes().IsEmpty()) {
+ mParentAsSlot = slot;
+ if (!aStartAtBeginning) {
+ mIndexInInserted = slot->AssignedNodes().Length();
+ }
+ mShadowDOMInvolved = true;
+ }
+ }
+}
+
+nsIContent* FlattenedChildIterator::GetNextChild() {
+ // If we're already in the inserted-children array, look there first
+ if (mParentAsSlot) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes =
+ mParentAsSlot->AssignedNodes();
+ if (mIsFirst) {
+ mIsFirst = false;
+ MOZ_ASSERT(mIndexInInserted == 0);
+ mChild = assignedNodes[0]->AsContent();
+ return mChild;
+ }
+ MOZ_ASSERT(mIndexInInserted <= assignedNodes.Length());
+ if (mIndexInInserted + 1 >= assignedNodes.Length()) {
+ mIndexInInserted = assignedNodes.Length();
+ return nullptr;
+ }
+ mChild = assignedNodes[++mIndexInInserted]->AsContent();
+ return mChild;
+ }
+
+ if (mIsFirst) { // at the beginning of the child list
+ mChild = mParent->GetFirstChild();
+ mIsFirst = false;
+ } else if (mChild) { // in the middle of the child list
+ mChild = mChild->GetNextSibling();
+ }
+
+ return mChild;
+}
+
+bool FlattenedChildIterator::Seek(const nsIContent* aChildToFind) {
+ if (!mParentAsSlot && aChildToFind->GetParent() == mParent &&
+ !aChildToFind->IsRootOfNativeAnonymousSubtree()) {
+ // Fast path: just point ourselves to aChildToFind, which is a
+ // normal DOM child of ours.
+ mChild = const_cast<nsIContent*>(aChildToFind);
+ mIndexInInserted = 0;
+ mIsFirst = false;
+ return true;
+ }
+
+ // Can we add more fast paths here based on whether the parent of aChildToFind
+ // is a This version can take shortcuts that the two-argument version
+ // can't, so can be faster (and in fact cshadow insertion point or content
+ // insertion point?
+
+ // It would be nice to assert that we find aChildToFind, but bz thinks that
+ // we might not find aChildToFind when called from ContentInserted
+ // if first-letter frames are about.
+ while (nsIContent* child = GetNextChild()) {
+ if (child == aChildToFind) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsIContent* FlattenedChildIterator::GetPreviousChild() {
+ if (mIsFirst) { // at the beginning of the child list
+ return nullptr;
+ }
+ if (mParentAsSlot) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes =
+ mParentAsSlot->AssignedNodes();
+ MOZ_ASSERT(mIndexInInserted <= assignedNodes.Length());
+ if (mIndexInInserted == 0) {
+ mIsFirst = true;
+ return nullptr;
+ }
+ mChild = assignedNodes[--mIndexInInserted]->AsContent();
+ return mChild;
+ }
+ if (mChild) { // in the middle of the child list
+ mChild = mChild->GetPreviousSibling();
+ } else { // at the end of the child list
+ mChild = mParent->GetLastChild();
+ }
+ if (!mChild) {
+ mIsFirst = true;
+ }
+
+ return mChild;
+}
+
+nsIContent* AllChildrenIterator::Get() const {
+ switch (mPhase) {
+ case eAtMarkerKid: {
+ Element* marker = nsLayoutUtils::GetMarkerPseudo(Parent());
+ MOZ_ASSERT(marker, "No content marker frame at eAtMarkerKid phase");
+ return marker;
+ }
+
+ case eAtBeforeKid: {
+ Element* before = nsLayoutUtils::GetBeforePseudo(Parent());
+ MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
+ return before;
+ }
+
+ case eAtFlatTreeKids:
+ return FlattenedChildIterator::Get();
+
+ case eAtAnonKids:
+ return mAnonKids[mAnonKidsIdx];
+
+ case eAtAfterKid: {
+ Element* after = nsLayoutUtils::GetAfterPseudo(Parent());
+ MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
+ return after;
+ }
+
+ default:
+ return nullptr;
+ }
+}
+
+bool AllChildrenIterator::Seek(const nsIContent* aChildToFind) {
+ if (mPhase == eAtBegin || mPhase == eAtMarkerKid) {
+ Element* markerPseudo = nsLayoutUtils::GetMarkerPseudo(Parent());
+ if (markerPseudo && markerPseudo == aChildToFind) {
+ mPhase = eAtMarkerKid;
+ return true;
+ }
+ mPhase = eAtBeforeKid;
+ }
+ if (mPhase == eAtBeforeKid) {
+ Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(Parent());
+ if (beforePseudo && beforePseudo == aChildToFind) {
+ return true;
+ }
+ mPhase = eAtFlatTreeKids;
+ }
+
+ if (mPhase == eAtFlatTreeKids) {
+ if (FlattenedChildIterator::Seek(aChildToFind)) {
+ return true;
+ }
+ mPhase = eAtAnonKids;
+ }
+
+ nsIContent* child = nullptr;
+ do {
+ child = GetNextChild();
+ } while (child && child != aChildToFind);
+
+ return child == aChildToFind;
+}
+
+void AllChildrenIterator::AppendNativeAnonymousChildren() {
+ nsContentUtils::AppendNativeAnonymousChildren(Parent(), mAnonKids, mFlags);
+}
+
+nsIContent* AllChildrenIterator::GetNextChild() {
+ if (mPhase == eAtBegin) {
+ mPhase = eAtMarkerKid;
+ if (Element* markerContent = nsLayoutUtils::GetMarkerPseudo(Parent())) {
+ return markerContent;
+ }
+ }
+
+ if (mPhase == eAtMarkerKid) {
+ mPhase = eAtBeforeKid;
+ if (Element* beforeContent = nsLayoutUtils::GetBeforePseudo(Parent())) {
+ return beforeContent;
+ }
+ }
+
+ if (mPhase == eAtBeforeKid) {
+ // Advance into our explicit kids.
+ mPhase = eAtFlatTreeKids;
+ }
+
+ if (mPhase == eAtFlatTreeKids) {
+ if (nsIContent* kid = FlattenedChildIterator::GetNextChild()) {
+ return kid;
+ }
+ mPhase = eAtAnonKids;
+ }
+
+ if (mPhase == eAtAnonKids) {
+ if (mAnonKids.IsEmpty()) {
+ MOZ_ASSERT(mAnonKidsIdx == UINT32_MAX);
+ AppendNativeAnonymousChildren();
+ mAnonKidsIdx = 0;
+ } else {
+ if (mAnonKidsIdx == UINT32_MAX) {
+ mAnonKidsIdx = 0;
+ } else {
+ mAnonKidsIdx++;
+ }
+ }
+
+ if (mAnonKidsIdx < mAnonKids.Length()) {
+ return mAnonKids[mAnonKidsIdx];
+ }
+
+ mPhase = eAtAfterKid;
+ if (Element* afterContent = nsLayoutUtils::GetAfterPseudo(Parent())) {
+ return afterContent;
+ }
+ }
+
+ mPhase = eAtEnd;
+ return nullptr;
+}
+
+nsIContent* AllChildrenIterator::GetPreviousChild() {
+ if (mPhase == eAtEnd) {
+ MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
+ mPhase = eAtAnonKids;
+ Element* afterContent = nsLayoutUtils::GetAfterPseudo(Parent());
+ if (afterContent) {
+ mPhase = eAtAfterKid;
+ return afterContent;
+ }
+ }
+
+ if (mPhase == eAtAfterKid) {
+ mPhase = eAtAnonKids;
+ }
+
+ if (mPhase == eAtAnonKids) {
+ if (mAnonKids.IsEmpty()) {
+ AppendNativeAnonymousChildren();
+ mAnonKidsIdx = mAnonKids.Length();
+ }
+
+ // If 0 then it turns into UINT32_MAX, which indicates the iterator is
+ // before the anonymous children.
+ --mAnonKidsIdx;
+ if (mAnonKidsIdx < mAnonKids.Length()) {
+ return mAnonKids[mAnonKidsIdx];
+ }
+ mPhase = eAtFlatTreeKids;
+ }
+
+ if (mPhase == eAtFlatTreeKids) {
+ if (nsIContent* kid = FlattenedChildIterator::GetPreviousChild()) {
+ return kid;
+ }
+
+ Element* beforeContent = nsLayoutUtils::GetBeforePseudo(Parent());
+ if (beforeContent) {
+ mPhase = eAtBeforeKid;
+ return beforeContent;
+ }
+ }
+
+ if (mPhase == eAtFlatTreeKids || mPhase == eAtBeforeKid) {
+ Element* markerContent = nsLayoutUtils::GetMarkerPseudo(Parent());
+ if (markerContent) {
+ mPhase = eAtMarkerKid;
+ return markerContent;
+ }
+ }
+
+ mPhase = eAtBegin;
+ return nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ChildIterator.h b/dom/base/ChildIterator.h
new file mode 100644
index 0000000000..af6b867883
--- /dev/null
+++ b/dom/base/ChildIterator.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ChildIterator_h
+#define ChildIterator_h
+
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include <stdint.h>
+
+class nsIContent;
+
+namespace mozilla::dom {
+
+// Iterates over the flattened children of a node, that is, the regular DOM
+// child nodes of a given DOM node, with assigned nodes as slot children, and
+// shadow host children replaced by their shadow root.
+//
+// The iterator can be initialized to start at the end by providing false for
+// aStartAtBeginning in order to start iterating in reverse from the last child.
+class FlattenedChildIterator {
+ public:
+ explicit FlattenedChildIterator(const nsIContent* aParent,
+ bool aStartAtBeginning = true);
+
+ nsIContent* GetNextChild();
+
+ // Looks for aChildToFind respecting insertion points until aChildToFind is
+ // found. This can be O(1) instead of O(N) in many cases.
+ bool Seek(const nsIContent* aChildToFind);
+
+ // Returns the current target of this iterator (which might be an explicit
+ // child of the node, or a node assigned to a slot.
+ nsIContent* Get() const { return mChild; }
+
+ // Returns the original parent we were initialized with.
+ const nsIContent* Parent() const { return mOriginalParent; }
+
+ // The inverse of GetNextChild. Properly steps in and out of insertion
+ // points.
+ nsIContent* GetPreviousChild();
+
+ bool ShadowDOMInvolved() const { return mShadowDOMInvolved; }
+
+ protected:
+ // The parent of the children being iterated. For shadow hosts this will point
+ // to its shadow root.
+ const nsIContent* mParent;
+
+ // If parent is a slot element with assigned slots, this points to the parent
+ // as HTMLSlotElement, otherwise, it's null.
+ const HTMLSlotElement* mParentAsSlot = nullptr;
+
+ const nsIContent* mOriginalParent = nullptr;
+
+ // The current child.
+ nsIContent* mChild = nullptr;
+
+ // A flag to let us know that we haven't started iterating yet.
+ bool mIsFirst = false;
+
+ // The index of the current element in the slot assigned nodes. One-past the
+ // end to represent the last position.
+ uint32_t mIndexInInserted = 0u;
+
+ // For certain optimizations, nsCSSFrameConstructor needs to know if the child
+ // list of the element that we're iterating matches its .childNodes.
+ bool mShadowDOMInvolved = false;
+};
+
+/**
+ * AllChildrenIterator traverses the children of an element including before /
+ * after content and shadow DOM. The iterator can be initialized to start at
+ * the end by providing false for aStartAtBeginning in order to start iterating
+ * in reverse from the last child.
+ *
+ * Note: it assumes that no mutation of the DOM or frame tree takes place during
+ * iteration, and will break horribly if that is not true.
+ */
+class AllChildrenIterator : private FlattenedChildIterator {
+ public:
+ AllChildrenIterator(const nsIContent* aNode, uint32_t aFlags,
+ bool aStartAtBeginning = true)
+ : FlattenedChildIterator(aNode, aStartAtBeginning),
+ mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0),
+ mFlags(aFlags),
+ mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) {}
+
+#ifdef DEBUG
+ AllChildrenIterator(AllChildrenIterator&&) = default;
+
+ AllChildrenIterator& operator=(AllChildrenIterator&& aOther) {
+ // Explicitly call the destructor to ensure the assertion in the destructor
+ // is checked.
+ this->~AllChildrenIterator();
+ new (this) AllChildrenIterator(std::move(aOther));
+ return *this;
+ }
+
+ ~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
+#endif
+
+ // Returns the current target the iterator is at, or null if the iterator
+ // doesn't point to any child node (either eAtBegin or eAtEnd phase).
+ nsIContent* Get() const;
+
+ // Seeks the given node in children of a parent element, starting from
+ // the current iterator's position, and sets the iterator at the given child
+ // node if it was found.
+ bool Seek(const nsIContent* aChildToFind);
+
+ nsIContent* GetNextChild();
+ nsIContent* GetPreviousChild();
+
+ enum IteratorPhase {
+ eAtBegin,
+ eAtMarkerKid,
+ eAtBeforeKid,
+ eAtFlatTreeKids,
+ eAtAnonKids,
+ eAtAfterKid,
+ eAtEnd
+ };
+ IteratorPhase Phase() const { return mPhase; }
+
+ private:
+ // Helpers.
+ void AppendNativeAnonymousChildren();
+
+ // mAnonKids is an array of native anonymous children, mAnonKidsIdx is index
+ // in the array. If mAnonKidsIdx < mAnonKids.Length() and mPhase is
+ // eAtAnonKids then the iterator points at a child at mAnonKidsIdx index. If
+ // mAnonKidsIdx == mAnonKids.Length() then the iterator is somewhere after
+ // the last native anon child. If mAnonKidsIdx == UINT32_MAX then the iterator
+ // is somewhere before the first native anon child.
+ nsTArray<nsIContent*> mAnonKids;
+ uint32_t mAnonKidsIdx;
+
+ uint32_t mFlags;
+ IteratorPhase mPhase;
+#ifdef DEBUG
+ // XXX we should really assert there are no frame tree changes as well, but
+ // there's no easy way to do that.
+ nsMutationGuard mMutationGuard;
+#endif
+};
+
+/**
+ * StyleChildrenIterator traverses the children of the element from the
+ * perspective of the style system, particularly the children we need to
+ * traverse during restyle.
+ *
+ * At present, this is identical to AllChildrenIterator with
+ * (eAllChildren | eSkipDocumentLevelNativeAnonymousContent). We used to have
+ * detect and skip any native anonymous children that are used to implement some
+ * special magic in here that went away, but we keep the separate class so
+ * we can reintroduce special magic back if needed.
+ *
+ * Note: it assumes that no mutation of the DOM or frame tree takes place during
+ * iteration, and will break horribly if that is not true.
+ *
+ * We require this to be memmovable since Rust code can create and move
+ * StyleChildrenIterators.
+ */
+class MOZ_NEEDS_MEMMOVABLE_MEMBERS StyleChildrenIterator
+ : private AllChildrenIterator {
+ public:
+ static nsIContent* GetParent(const nsIContent& aContent) {
+ nsINode* node = aContent.GetFlattenedTreeParentNodeForStyle();
+ return node && node->IsContent() ? node->AsContent() : nullptr;
+ }
+
+ explicit StyleChildrenIterator(const nsIContent* aContent,
+ bool aStartAtBeginning = true)
+ : AllChildrenIterator(
+ aContent,
+ nsIContent::eAllChildren |
+ nsIContent::eSkipDocumentLevelNativeAnonymousContent,
+ aStartAtBeginning) {
+ MOZ_COUNT_CTOR(StyleChildrenIterator);
+ }
+
+ StyleChildrenIterator(StyleChildrenIterator&& aOther)
+ : AllChildrenIterator(std::move(aOther)) {
+ MOZ_COUNT_CTOR(StyleChildrenIterator);
+ }
+
+ StyleChildrenIterator& operator=(StyleChildrenIterator&& aOther) = default;
+
+ MOZ_COUNTED_DTOR(StyleChildrenIterator)
+
+ using AllChildrenIterator::GetNextChild;
+ using AllChildrenIterator::GetPreviousChild;
+ using AllChildrenIterator::Seek;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/ChildProcessMessageManager.h b/dom/base/ChildProcessMessageManager.h
new file mode 100644
index 0000000000..251aab2616
--- /dev/null
+++ b/dom/base/ChildProcessMessageManager.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChildProcessMessageManager_h
+#define mozilla_dom_ChildProcessMessageManager_h
+
+#include "mozilla/dom/SyncMessageSender.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+namespace mozilla::dom {
+
+class ChildProcessMessageManager final : public SyncMessageSender {
+ public:
+ explicit ChildProcessMessageManager(ipc::MessageManagerCallback* aCallback)
+ : SyncMessageSender(aCallback, MessageManagerFlags::MM_PROCESSMANAGER |
+ MessageManagerFlags::MM_OWNSCALLBACK) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return ChildProcessMessageManager_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ protected:
+ virtual ~ChildProcessMessageManager() { mozilla::DropJSObjects(this); }
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ChildProcessMessageManager_h
diff --git a/dom/base/ChromeMessageBroadcaster.cpp b/dom/base/ChromeMessageBroadcaster.cpp
new file mode 100644
index 0000000000..ee2749f337
--- /dev/null
+++ b/dom/base/ChromeMessageBroadcaster.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "AccessCheck.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+
+namespace mozilla::dom {
+
+JSObject* ChromeMessageBroadcaster::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(nsContentUtils::IsSystemCaller(aCx));
+
+ return ChromeMessageBroadcaster_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ChromeMessageBroadcaster.h b/dom/base/ChromeMessageBroadcaster.h
new file mode 100644
index 0000000000..2a02901289
--- /dev/null
+++ b/dom/base/ChromeMessageBroadcaster.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChromeMessageBroadcaster_h
+#define mozilla_dom_ChromeMessageBroadcaster_h
+
+#include "mozilla/dom/MessageBroadcaster.h"
+
+namespace mozilla::dom {
+
+/**
+ * Implementation for the WebIDL ChromeMessageBroadcaster interface. Used for
+ * window and group message managers.
+ */
+class ChromeMessageBroadcaster final : public MessageBroadcaster {
+ public:
+ explicit ChromeMessageBroadcaster(MessageManagerFlags aFlags)
+ : ChromeMessageBroadcaster(nullptr, aFlags) {
+ MOZ_ASSERT(!(aFlags & ~(MessageManagerFlags::MM_GLOBAL |
+ MessageManagerFlags::MM_OWNSCALLBACK)));
+ }
+ explicit ChromeMessageBroadcaster(MessageBroadcaster* aParentManager)
+ : ChromeMessageBroadcaster(aParentManager, MessageManagerFlags::MM_NONE) {
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // FrameScriptLoader
+ void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
+ bool aRunInGlobalScope, mozilla::ErrorResult& aError) {
+ LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
+ }
+ void RemoveDelayedFrameScript(const nsAString& aURL) {
+ RemoveDelayedScript(aURL);
+ }
+ void GetDelayedFrameScripts(JSContext* aCx,
+ nsTArray<nsTArray<JS::Value>>& aScripts,
+ mozilla::ErrorResult& aError) {
+ GetDelayedScripts(aCx, aScripts, aError);
+ }
+
+ private:
+ ChromeMessageBroadcaster(MessageBroadcaster* aParentManager,
+ MessageManagerFlags aFlags)
+ : MessageBroadcaster(aParentManager,
+ aFlags | MessageManagerFlags::MM_CHROME) {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ChromeMessageBroadcaster_h
diff --git a/dom/base/ChromeMessageSender.cpp b/dom/base/ChromeMessageSender.cpp
new file mode 100644
index 0000000000..851675c408
--- /dev/null
+++ b/dom/base/ChromeMessageSender.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChromeMessageSender.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+
+namespace mozilla::dom {
+
+JSObject* ChromeMessageSender::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(nsContentUtils::IsSystemCaller(aCx));
+
+ return ChromeMessageSender_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ChromeMessageSender.h b/dom/base/ChromeMessageSender.h
new file mode 100644
index 0000000000..f90c325943
--- /dev/null
+++ b/dom/base/ChromeMessageSender.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChromeMessageSender_h
+#define mozilla_dom_ChromeMessageSender_h
+
+#include "mozilla/dom/MessageSender.h"
+
+namespace mozilla::dom {
+
+class MessageBroadcaster;
+
+class ChromeMessageSender final : public MessageSender {
+ public:
+ explicit ChromeMessageSender(MessageBroadcaster* aParentManager)
+ : MessageSender(nullptr, aParentManager, MessageManagerFlags::MM_CHROME) {
+ // This is a bit hackish, we wait until the child process is running before
+ // attaching to the parent manager (see MessageSender::InitWithCallback).
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // FrameScriptLoader
+ void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
+ bool aRunInGlobalScope, mozilla::ErrorResult& aError) {
+ LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
+ }
+ void RemoveDelayedFrameScript(const nsAString& aURL) {
+ RemoveDelayedScript(aURL);
+ }
+ void GetDelayedFrameScripts(JSContext* aCx,
+ nsTArray<nsTArray<JS::Value>>& aScripts,
+ mozilla::ErrorResult& aError) {
+ GetDelayedScripts(aCx, aScripts, aError);
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ChromeMessageSender_h
diff --git a/dom/base/ChromeNodeList.cpp b/dom/base/ChromeNodeList.cpp
new file mode 100644
index 0000000000..86df0ce526
--- /dev/null
+++ b/dom/base/ChromeNodeList.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChromeNodeList.h"
+
+#include <new>
+#include <utility>
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeNodeListBinding.h"
+#include "mozilla/dom/Document.h"
+#include "nsCOMPtr.h"
+#include "nsINode.h"
+#include "nsISupports.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+already_AddRefed<ChromeNodeList> ChromeNodeList::Constructor(
+ const GlobalObject& aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ Document* root = win ? win->GetExtantDoc() : nullptr;
+ RefPtr<ChromeNodeList> list = new ChromeNodeList(root);
+ return list.forget();
+}
+
+JSObject* ChromeNodeList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ChromeNodeList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ChromeNodeList::Append(nsINode& aNode, ErrorResult& aError) {
+ if (!aNode.IsContent()) {
+ // nsINodeList deals with nsIContent objects only, so need to
+ // filter out other nodes for now.
+ aError.ThrowTypeError("The node passed in is not a ChildNode");
+ return;
+ }
+
+ AppendElement(aNode.AsContent());
+}
+
+void ChromeNodeList::Remove(nsINode& aNode, ErrorResult& aError) {
+ if (!aNode.IsContent()) {
+ aError.ThrowTypeError("The node passed in is not a ChildNode");
+ return;
+ }
+
+ RemoveElement(aNode.AsContent());
+}
diff --git a/dom/base/ChromeNodeList.h b/dom/base/ChromeNodeList.h
new file mode 100644
index 0000000000..61e0aa6582
--- /dev/null
+++ b/dom/base/ChromeNodeList.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsContentList.h"
+
+class JSObject;
+class nsINode;
+struct JSContext;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class GlobalObject;
+
+class ChromeNodeList final : public nsSimpleContentList {
+ public:
+ explicit ChromeNodeList(nsINode* aOwner) : nsSimpleContentList(aOwner) {}
+
+ static already_AddRefed<ChromeNodeList> Constructor(
+ const GlobalObject& aGlobal);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Append(nsINode& aNode, ErrorResult& aError);
+ void Remove(nsINode& aNode, ErrorResult& aError);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp
new file mode 100644
index 0000000000..8804b73e39
--- /dev/null
+++ b/dom/base/ChromeUtils.cpp
@@ -0,0 +1,1895 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeUtils.h"
+
+#include "JSOracleParent.h"
+#include "js/CallAndConstruct.h" // JS::Call
+#include "js/CharacterEncoding.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_SetProperty, JS_SetPropertyById, JS::IdVector
+#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
+#include "js/SavedFrameAPI.h"
+#include "js/Value.h" // JS::Value, JS::StringValue
+#include "jsfriendapi.h"
+#include "WrapperFactory.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FormAutofillNative.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/PerformanceMetricsCollector.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcInfo.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollingMetrics.h"
+#include "mozilla/SharedStyleSheetCache.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/InProcessParent.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/dom/MediaSessionBinding.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Record.h"
+#include "mozilla/dom/ReportingHeader.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ipc/UtilityProcessHost.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/WheelHandlingHelper.h"
+#include "IOActivityMonitor.h"
+#include "nsNativeTheme.h"
+#include "nsThreadUtils.h"
+#include "mozJSModuleLoader.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "nsIException.h"
+#include "VsyncSource.h"
+
+namespace mozilla::dom {
+
+/* static */
+void ChromeUtils::NondeterministicGetWeakMapKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
+ if (!aMap.isObject()) {
+ aRetval.setUndefined();
+ } else {
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> objRet(cx);
+ JS::Rooted<JSObject*> mapObj(cx, &aMap.toObject());
+ if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &objRet)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::NondeterministicGetWeakSetKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aSet,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
+ if (!aSet.isObject()) {
+ aRetval.setUndefined();
+ } else {
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> objRet(cx);
+ JS::Rooted<JSObject*> setObj(cx, &aSet.toObject());
+ if (!JS_NondeterministicGetWeakSetKeys(cx, setObj, &objRet)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
+ const ArrayBufferViewOrArrayBuffer& aSource,
+ const Base64URLEncodeOptions& aOptions,
+ nsACString& aResult, ErrorResult& aRv) {
+ size_t length = 0;
+ uint8_t* data = nullptr;
+ if (aSource.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aSource.GetAsArrayBuffer();
+ buffer.ComputeState();
+ length = buffer.Length();
+ data = buffer.Data();
+ } else if (aSource.IsArrayBufferView()) {
+ const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+ view.ComputeState();
+ length = view.Length();
+ data = view.Data();
+ } else {
+ MOZ_CRASH("Uninitialized union: expected buffer or view");
+ }
+
+ auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include
+ : Base64URLEncodePaddingPolicy::Omit;
+ nsresult rv = mozilla::Base64URLEncode(length, data, paddingPolicy, aResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResult.Truncate();
+ aRv.Throw(rv);
+ }
+}
+
+/* static */
+void ChromeUtils::Base64URLDecode(GlobalObject& aGlobal,
+ const nsACString& aString,
+ const Base64URLDecodeOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ Base64URLDecodePaddingPolicy paddingPolicy;
+ switch (aOptions.mPadding) {
+ case Base64URLDecodePadding::Require:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Require;
+ break;
+
+ case Base64URLDecodePadding::Ignore:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Ignore;
+ break;
+
+ case Base64URLDecodePadding::Reject:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Reject;
+ break;
+
+ default:
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ FallibleTArray<uint8_t> data;
+ nsresult rv = mozilla::Base64URLDecode(aString, paddingPolicy, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ JS::Rooted<JSObject*> buffer(
+ aGlobal.Context(),
+ ArrayBuffer::Create(aGlobal.Context(), data.Length(), data.Elements()));
+ if (NS_WARN_IF(!buffer)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aRetval.set(buffer);
+}
+
+/* static */
+void ChromeUtils::ReleaseAssert(GlobalObject& aGlobal, bool aCondition,
+ const nsAString& aMessage) {
+ // If the condition didn't fail, which is the likely case, immediately return.
+ if (MOZ_LIKELY(aCondition)) {
+ return;
+ }
+
+ // Extract the current stack from the JS runtime to embed in the crash reason.
+ nsAutoString filename;
+ uint32_t lineNo = 0;
+
+ if (nsCOMPtr<nsIStackFrame> location = GetCurrentJSStack(1)) {
+ location->GetFilename(aGlobal.Context(), filename);
+ lineNo = location->GetLineNumber(aGlobal.Context());
+ } else {
+ filename.Assign(u"<unknown>"_ns);
+ }
+
+ // Convert to utf-8 for adding as the MozCrashReason.
+ NS_ConvertUTF16toUTF8 filenameUtf8(filename);
+ NS_ConvertUTF16toUTF8 messageUtf8(aMessage);
+
+ // Actually crash.
+ MOZ_CRASH_UNSAFE_PRINTF("Failed ChromeUtils.releaseAssert(\"%s\") @ %s:%u",
+ messageUtf8.get(), filenameUtf8.get(), lineNo);
+}
+
+/* static */
+void ChromeUtils::AddProfilerMarker(
+ GlobalObject& aGlobal, const nsACString& aName,
+ const ProfilerMarkerOptionsOrDouble& aOptions,
+ const Optional<nsACString>& aText) {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ MarkerOptions options;
+
+ MarkerCategory category = ::geckoprofiler::category::JS;
+
+ DOMHighResTimeStamp startTime = 0;
+ uint64_t innerWindowId = 0;
+ if (aOptions.IsDouble()) {
+ startTime = aOptions.GetAsDouble();
+ } else {
+ const ProfilerMarkerOptions& opt = aOptions.GetAsProfilerMarkerOptions();
+ startTime = opt.mStartTime;
+ innerWindowId = opt.mInnerWindowId;
+
+ if (opt.mCaptureStack) {
+ // If we will be capturing a stack, change the category of the
+ // ChromeUtils.addProfilerMarker label automatically added by the webidl
+ // binding from DOM to PROFILER so that this function doesn't appear in
+ // the marker stack.
+ JSContext* cx = aGlobal.Context();
+ ProfilingStack* stack = js::GetContextProfilingStackIfEnabled(cx);
+ if (MOZ_LIKELY(stack)) {
+ uint32_t sp = stack->stackPointer;
+ if (MOZ_LIKELY(sp > 0)) {
+ js::ProfilingStackFrame& frame = stack->frames[sp - 1];
+ if (frame.isLabelFrame() && "ChromeUtils"_ns.Equals(frame.label()) &&
+ "addProfilerMarker"_ns.Equals(frame.dynamicString())) {
+ frame.setLabelCategory(JS::ProfilingCategoryPair::PROFILER);
+ }
+ }
+ }
+
+ options.Set(MarkerStack::Capture());
+ }
+#define BEGIN_CATEGORY(name, labelAsString, color) \
+ if (opt.mCategory.Equals(labelAsString)) { \
+ category = ::geckoprofiler::category::name; \
+ } else
+#define SUBCATEGORY(supercategory, name, labelAsString)
+#define END_CATEGORY
+ MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY)
+#undef BEGIN_CATEGORY
+#undef SUBCATEGORY
+#undef END_CATEGORY
+ {
+ category = ::geckoprofiler::category::OTHER;
+ }
+ }
+ if (startTime) {
+ RefPtr<Performance> performance;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (ownerWindow) {
+ performance = ownerWindow->GetPerformance();
+ }
+ } else {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ if (workerPrivate) {
+ performance = workerPrivate->GlobalScope()->GetPerformance();
+ }
+ }
+
+ if (performance) {
+ options.Set(MarkerTiming::IntervalUntilNowFrom(
+ performance->CreationTimeStamp() +
+ TimeDuration::FromMilliseconds(startTime)));
+ } else {
+ options.Set(MarkerTiming::IntervalUntilNowFrom(
+ TimeStamp::ProcessCreation() +
+ TimeDuration::FromMilliseconds(startTime)));
+ }
+ }
+
+ if (innerWindowId) {
+ options.Set(MarkerInnerWindowId(innerWindowId));
+ } else {
+ options.Set(MarkerInnerWindowIdFromJSContext(aGlobal.Context()));
+ }
+
+ {
+ AUTO_PROFILER_STATS(ChromeUtils_AddProfilerMarker);
+ if (aText.WasPassed()) {
+ profiler_add_marker(aName, category, std::move(options),
+ ::geckoprofiler::markers::TextMarker{},
+ aText.Value());
+ } else {
+ profiler_add_marker(aName, category, std::move(options));
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::GetXPCOMErrorName(GlobalObject& aGlobal, uint32_t aErrorCode,
+ nsACString& aRetval) {
+ GetErrorName((nsresult)aErrorCode, aRetval);
+}
+
+/* static */
+void ChromeUtils::WaiveXrays(GlobalObject& aGlobal, JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> value(aGlobal.Context(), aVal);
+ if (!xpc::WrapperFactory::WaiveXrayAndWrap(aGlobal.Context(), &value)) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ } else {
+ aRetval.set(value);
+ }
+}
+
+/* static */
+void ChromeUtils::UnwaiveXrays(GlobalObject& aGlobal,
+ JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ if (!aVal.isObject()) {
+ aRetval.set(aVal);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aGlobal.Context(),
+ js::UncheckedUnwrap(&aVal.toObject()));
+ if (!JS_WrapObject(aGlobal.Context(), &obj)) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ } else {
+ aRetval.setObject(*obj);
+ }
+}
+
+/* static */
+void ChromeUtils::GetClassName(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aObj, bool aUnwrap,
+ nsAString& aRetval) {
+ JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
+ if (aUnwrap) {
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ }
+
+ aRetval = NS_ConvertUTF8toUTF16(nsDependentCString(JS::GetClass(obj)->name));
+}
+
+/* static */
+bool ChromeUtils::IsDOMObject(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
+ bool aUnwrap) {
+ JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
+ if (aUnwrap) {
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ }
+
+ return mozilla::dom::IsDOMObject(obj);
+}
+
+/* static */
+void ChromeUtils::ShallowClone(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aObj,
+ JS::Handle<JSObject*> aTarget,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+
+ auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });
+
+ JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
+ JS::RootedVector<JS::Value> values(cx);
+ JS::RootedVector<jsid> valuesIds(cx);
+
+ {
+ // cx represents our current Realm, so it makes sense to use it for the
+ // CheckedUnwrapDynamic call. We do want CheckedUnwrapDynamic, in case
+ // someone is shallow-cloning a Window.
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapDynamic(aObj, cx));
+ if (!obj) {
+ js::ReportAccessDenied(cx);
+ return;
+ }
+
+ if (js::IsScriptedProxy(obj)) {
+ JS_ReportErrorASCII(cx, "Shallow cloning a proxy object is not allowed");
+ return;
+ }
+
+ JSAutoRealm ar(cx, obj);
+
+ if (!JS_Enumerate(cx, obj, &ids) || !values.reserve(ids.length()) ||
+ !valuesIds.reserve(ids.length())) {
+ return;
+ }
+
+ JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
+ JS::Rooted<JS::PropertyKey> id(cx);
+ for (jsid idVal : ids) {
+ id = idVal;
+ if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) {
+ continue;
+ }
+ if (desc.isNothing() || desc->isAccessorDescriptor()) {
+ continue;
+ }
+ valuesIds.infallibleAppend(id);
+ values.infallibleAppend(desc->value());
+ }
+ }
+
+ JS::Rooted<JSObject*> obj(cx);
+ {
+ Maybe<JSAutoRealm> ar;
+ if (aTarget) {
+ // Our target could be anything, so we want CheckedUnwrapDynamic here.
+ // "cx" represents the current Realm when we were called from bindings, so
+ // we can just use that.
+ JS::Rooted<JSObject*> target(cx, js::CheckedUnwrapDynamic(aTarget, cx));
+ if (!target) {
+ js::ReportAccessDenied(cx);
+ return;
+ }
+ ar.emplace(cx, target);
+ }
+
+ obj = JS_NewPlainObject(cx);
+ if (!obj) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(cx);
+ JS::Rooted<JS::PropertyKey> id(cx);
+ for (uint32_t i = 0; i < valuesIds.length(); i++) {
+ id = valuesIds[i];
+ value = values[i];
+
+ JS_MarkCrossZoneId(cx, id);
+ if (!JS_WrapValue(cx, &value) ||
+ !JS_SetPropertyById(cx, obj, id, value)) {
+ return;
+ }
+ }
+ }
+
+ if (aTarget && !JS_WrapObject(cx, &obj)) {
+ return;
+ }
+
+ cleanup.release();
+ aRetval.set(obj);
+}
+
+namespace {
+class IdleDispatchRunnable final : public IdleRunnable,
+ public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ IdleDispatchRunnable(nsIGlobalObject* aParent, IdleRequestCallback& aCallback)
+ : IdleRunnable("ChromeUtils::IdleDispatch"),
+ mCallback(&aCallback),
+ mParent(aParent) {}
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
+ // See bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ if (mCallback) {
+ CancelTimer();
+
+ auto deadline = mDeadline - TimeStamp::ProcessCreation();
+
+ ErrorResult rv;
+ RefPtr<IdleDeadline> idleDeadline =
+ new IdleDeadline(mParent, mTimedOut, deadline.ToMilliseconds());
+
+ RefPtr<IdleRequestCallback> callback(std::move(mCallback));
+ MOZ_ASSERT(!mCallback);
+ callback->Call(*idleDeadline, "ChromeUtils::IdleDispatch handler");
+ mParent = nullptr;
+ }
+ return NS_OK;
+ }
+
+ void SetDeadline(TimeStamp aDeadline) override { mDeadline = aDeadline; }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ mTimedOut = true;
+ SetDeadline(TimeStamp::Now());
+ return Run();
+ }
+
+ void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override {
+ MOZ_ASSERT(aTarget);
+ MOZ_ASSERT(!mTimer);
+ NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aDelay,
+ nsITimer::TYPE_ONE_SHOT, aTarget);
+ }
+
+ protected:
+ virtual ~IdleDispatchRunnable() { CancelTimer(); }
+
+ private:
+ void CancelTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ }
+
+ RefPtr<IdleRequestCallback> mCallback;
+ nsCOMPtr<nsIGlobalObject> mParent;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ TimeStamp mDeadline{};
+ bool mTimedOut = false;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(IdleDispatchRunnable, IdleRunnable,
+ nsITimerCallback)
+} // anonymous namespace
+
+/* static */
+void ChromeUtils::IdleDispatch(const GlobalObject& aGlobal,
+ IdleRequestCallback& aCallback,
+ const IdleRequestOptions& aOptions,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ auto runnable = MakeRefPtr<IdleDispatchRunnable>(global, aCallback);
+
+ if (aOptions.mTimeout.WasPassed()) {
+ aRv = NS_DispatchToCurrentThreadQueue(
+ runnable.forget(), aOptions.mTimeout.Value(), EventQueuePriority::Idle);
+ } else {
+ aRv = NS_DispatchToCurrentThreadQueue(runnable.forget(),
+ EventQueuePriority::Idle);
+ }
+}
+
+/* static */
+void ChromeUtils::Import(const GlobalObject& aGlobal,
+ const nsACString& aResourceURI,
+ const Optional<JS::Handle<JSObject*>>& aTargetObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ RefPtr moduleloader = mozJSModuleLoader::Get();
+ MOZ_ASSERT(moduleloader);
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("ChromeUtils::Import",
+ OTHER, aResourceURI);
+
+ JSContext* cx = aGlobal.Context();
+
+ JS::Rooted<JSObject*> global(cx);
+ JS::Rooted<JSObject*> exports(cx);
+ nsresult rv = moduleloader->Import(cx, aResourceURI, &global, &exports);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ // Import() on the component loader can return NS_OK while leaving an
+ // exception on the JSContext. Check for that case.
+ if (JS_IsExceptionPending(cx)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ if (aTargetObj.WasPassed()) {
+ if (!JS_AssignObject(cx, aTargetObj.Value(), exports)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ if (!JS_WrapObject(cx, &exports)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aRetval.set(exports);
+}
+
+static mozJSModuleLoader* GetContextualESLoader(
+ const Optional<bool>& aLoadInDevToolsLoader, JSObject* aGlobal) {
+ RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader();
+ // We should load the module in the DevTools loader if:
+ // - ChromeUtils.importESModule's `loadInDevToolsLoader` option is true, or,
+ // - if the callsite is from a module loaded in the DevTools loader and
+ // `loadInDevToolsLoader` isn't an explicit false.
+ bool shouldUseDevToolsLoader =
+ (aLoadInDevToolsLoader.WasPassed() && aLoadInDevToolsLoader.Value()) ||
+ (devToolsModuleloader && !aLoadInDevToolsLoader.WasPassed() &&
+ devToolsModuleloader->IsLoaderGlobal(aGlobal));
+ if (shouldUseDevToolsLoader) {
+ return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+ }
+ return mozJSModuleLoader::Get();
+}
+
+/* static */
+void ChromeUtils::ImportESModule(
+ const GlobalObject& aGlobal, const nsAString& aResourceURI,
+ const ImportESModuleOptionsDictionary& aOptions,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
+ RefPtr moduleloader =
+ GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get());
+ MOZ_ASSERT(moduleloader);
+
+ NS_ConvertUTF16toUTF8 registryLocation(aResourceURI);
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
+ "ChromeUtils::ImportESModule", OTHER, registryLocation);
+
+ JSContext* cx = aGlobal.Context();
+
+ JS::Rooted<JSObject*> moduleNamespace(cx);
+ nsresult rv =
+ moduleloader->ImportESModule(cx, registryLocation, &moduleNamespace);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+
+ if (!JS_WrapObject(cx, &moduleNamespace)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aRetval.set(moduleNamespace);
+}
+
+namespace lazy_getter {
+
+static const size_t SLOT_ID = 0;
+static const size_t SLOT_URI = 1;
+static const size_t SLOT_PARAMS = 1;
+
+static const size_t PARAM_INDEX_TARGET = 0;
+static const size_t PARAM_INDEX_LAMBDA = 1;
+static const size_t PARAMS_COUNT = 2;
+
+static bool ExtractArgs(JSContext* aCx, JS::CallArgs& aArgs,
+ JS::MutableHandle<JSObject*> aCallee,
+ JS::MutableHandle<JSObject*> aThisObj,
+ JS::MutableHandle<jsid> aId) {
+ aCallee.set(&aArgs.callee());
+
+ JS::Handle<JS::Value> thisv = aArgs.thisv();
+ if (!thisv.isObject()) {
+ JS_ReportErrorASCII(aCx, "Invalid target object");
+ return false;
+ }
+
+ aThisObj.set(&thisv.toObject());
+
+ JS::Rooted<JS::Value> id(aCx,
+ js::GetFunctionNativeReserved(aCallee, SLOT_ID));
+ MOZ_ALWAYS_TRUE(JS_ValueToId(aCx, id, aId));
+ return true;
+}
+
+static bool JSLazyGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> unused(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &unused, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> paramsVal(
+ aCx, js::GetFunctionNativeReserved(callee, SLOT_PARAMS));
+ if (paramsVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+ // Avoid calling the lambda multiple times, in case of:
+ // * the getter function is retrieved from property descriptor and called
+ // * the lambda gets the property again
+ // * the getter function throws and accessed again
+ js::SetFunctionNativeReserved(callee, SLOT_PARAMS, JS::UndefinedHandleValue);
+
+ JS::Rooted<JSObject*> paramsObj(aCx, &paramsVal.toObject());
+
+ JS::Rooted<JS::Value> targetVal(aCx);
+ JS::Rooted<JS::Value> lambdaVal(aCx);
+ if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_TARGET, &targetVal)) {
+ return false;
+ }
+ if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_LAMBDA, &lambdaVal)) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> targetObj(aCx, &targetVal.toObject());
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS::Call(aCx, targetObj, lambdaVal, JS::HandleValueArray::empty(),
+ &value)) {
+ return false;
+ }
+
+ if (!JS_DefinePropertyById(aCx, targetObj, id, value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ args.rval().set(value);
+ return true;
+}
+
+static bool DefineLazyGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::Value> aName,
+ JS::Handle<JSObject*> aLambda) {
+ JS::Rooted<jsid> id(aCx);
+ if (!JS_ValueToId(aCx, aName, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSLazyGetter, 0, 0, id)));
+ if (!getter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ JS::RootedVector<JS::Value> params(aCx);
+ if (!params.resize(PARAMS_COUNT)) {
+ return false;
+ }
+ params[PARAM_INDEX_TARGET].setObject(*aTarget);
+ params[PARAM_INDEX_LAMBDA].setObject(*aLambda);
+ JS::Rooted<JSObject*> paramsObj(aCx, JS::NewArrayObject(aCx, params));
+ if (!paramsObj) {
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, aName);
+ js::SetFunctionNativeReserved(getter, SLOT_PARAMS,
+ JS::ObjectValue(*paramsObj));
+
+ return JS_DefinePropertyById(aCx, aTarget, id, getter, nullptr,
+ JSPROP_ENUMERATE);
+}
+
+enum class ModuleType { JSM, ESM };
+
+static bool ModuleGetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
+ ModuleType aType) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> thisObj(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JSString*> moduleURI(
+ aCx, js::GetFunctionNativeReserved(callee, SLOT_URI).toString());
+ JS::UniqueChars bytes = JS_EncodeStringToUTF8(aCx, moduleURI);
+ if (!bytes) {
+ return false;
+ }
+ nsDependentCString uri(bytes.get());
+
+ RefPtr moduleloader =
+ aType == ModuleType::JSM
+ ? mozJSModuleLoader::Get()
+ : GetContextualESLoader(
+ Optional<bool>(),
+ JS::GetNonCCWObjectGlobal(js::UncheckedUnwrap(thisObj)));
+ MOZ_ASSERT(moduleloader);
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (aType == ModuleType::JSM) {
+ JS::Rooted<JSObject*> moduleGlobal(aCx);
+ JS::Rooted<JSObject*> moduleExports(aCx);
+ nsresult rv = moduleloader->Import(aCx, uri, &moduleGlobal, &moduleExports);
+ if (NS_FAILED(rv)) {
+ Throw(aCx, rv);
+ return false;
+ }
+
+ // JSM's exports is from the same realm.
+ if (!JS_GetPropertyById(aCx, moduleExports, id, &value)) {
+ return false;
+ }
+ } else {
+ JS::Rooted<JSObject*> moduleNamespace(aCx);
+ nsresult rv = moduleloader->ImportESModule(aCx, uri, &moduleNamespace);
+ if (NS_FAILED(rv)) {
+ Throw(aCx, rv);
+ return false;
+ }
+
+ // ESM's namespace is from the module's realm.
+ {
+ JSAutoRealm ar(aCx, moduleNamespace);
+ if (!JS_GetPropertyById(aCx, moduleNamespace, id, &value)) {
+ return false;
+ }
+ }
+ if (!JS_WrapValue(aCx, &value)) {
+ return false;
+ }
+ }
+
+ if (!JS_DefinePropertyById(aCx, thisObj, id, value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ args.rval().set(value);
+ return true;
+}
+
+static bool JSModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleGetterImpl(aCx, aArgc, aVp, ModuleType::JSM);
+}
+
+static bool ESModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleGetterImpl(aCx, aArgc, aVp, ModuleType::ESM);
+}
+
+static bool ModuleSetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> thisObj(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+ return false;
+ }
+
+ return JS_DefinePropertyById(aCx, thisObj, id, args.get(0), JSPROP_ENUMERATE);
+}
+
+static bool JSModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleSetterImpl(aCx, aArgc, aVp);
+}
+
+static bool ESModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleSetterImpl(aCx, aArgc, aVp);
+}
+
+static bool DefineJSModuleGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ const nsAString& aId,
+ const nsAString& aResourceURI) {
+ JS::Rooted<JS::Value> uri(aCx);
+ JS::Rooted<JS::Value> idValue(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!xpc::NonVoidStringToJsval(aCx, aResourceURI, &uri) ||
+ !xpc::NonVoidStringToJsval(aCx, aId, &idValue) ||
+ !JS_ValueToId(aCx, idValue, &id)) {
+ return false;
+ }
+ idValue = js::IdToValue(id);
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSModuleGetter, 0, 0, id)));
+
+ JS::Rooted<JSObject*> setter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSModuleSetter, 0, 0, id)));
+
+ if (!getter || !setter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, idValue);
+ js::SetFunctionNativeReserved(setter, SLOT_ID, idValue);
+
+ js::SetFunctionNativeReserved(getter, SLOT_URI, uri);
+
+ return JS_DefinePropertyById(aCx, aTarget, id, getter, setter,
+ JSPROP_ENUMERATE);
+}
+
+static bool DefineESModuleGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::Handle<JS::Value> aResourceURI) {
+ JS::Rooted<JS::Value> idVal(aCx, JS::StringValue(aId.toString()));
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
+ aCx, ESModuleGetter, 0, 0, aId)));
+
+ JS::Rooted<JSObject*> setter(
+ aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
+ aCx, ESModuleSetter, 0, 0, aId)));
+
+ if (!getter || !setter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, idVal);
+ js::SetFunctionNativeReserved(setter, SLOT_ID, idVal);
+
+ js::SetFunctionNativeReserved(getter, SLOT_URI, aResourceURI);
+
+ return JS_DefinePropertyById(aCx, aTarget, aId, getter, setter,
+ JSPROP_ENUMERATE);
+}
+
+} // namespace lazy_getter
+
+/* static */
+void ChromeUtils::DefineLazyGetter(const GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::Value> aName,
+ JS::Handle<JSObject*> aLambda,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ if (!lazy_getter::DefineLazyGetter(cx, aTarget, aName, aLambda)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+}
+
+/* static */
+void ChromeUtils::DefineModuleGetter(const GlobalObject& global,
+ JS::Handle<JSObject*> target,
+ const nsAString& id,
+ const nsAString& resourceURI,
+ ErrorResult& aRv) {
+ if (!lazy_getter::DefineJSModuleGetter(global.Context(), target, id,
+ resourceURI)) {
+ aRv.NoteJSContextException(global.Context());
+ }
+}
+
+/* static */
+void ChromeUtils::DefineESModuleGetters(const GlobalObject& global,
+ JS::Handle<JSObject*> target,
+ JS::Handle<JSObject*> modules,
+ ErrorResult& aRv) {
+ JSContext* cx = global.Context();
+
+ JS::Rooted<JS::IdVector> props(cx, JS::IdVector(cx));
+ if (!JS_Enumerate(cx, modules, &props)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ JS::Rooted<JS::PropertyKey> prop(cx);
+ JS::Rooted<JS::Value> resourceURIVal(cx);
+ for (JS::PropertyKey tmp : props) {
+ prop = tmp;
+
+ if (!prop.isString()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!JS_GetPropertyById(cx, modules, prop, &resourceURIVal)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ if (!lazy_getter::DefineESModuleGetter(cx, target, prop, resourceURIVal)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::OriginAttributesToSuffix(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ nsCString& aSuffix)
+
+{
+ OriginAttributes attrs(aAttrs);
+ attrs.CreateSuffix(aSuffix);
+}
+
+/* static */
+bool ChromeUtils::OriginAttributesMatchPattern(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ const dom::OriginAttributesPatternDictionary& aPattern) {
+ OriginAttributes attrs(aAttrs);
+ OriginAttributesPattern pattern(aPattern);
+ return pattern.Matches(attrs);
+}
+
+/* static */
+void ChromeUtils::CreateOriginAttributesFromOrigin(
+ dom::GlobalObject& aGlobal, const nsAString& aOrigin,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
+ OriginAttributes attrs;
+ nsAutoCString suffix;
+ if (!attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(aOrigin), suffix)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aAttrs = attrs;
+}
+
+/* static */
+void ChromeUtils::CreateOriginAttributesFromOriginSuffix(
+ dom::GlobalObject& aGlobal, const nsAString& aSuffix,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
+ OriginAttributes attrs;
+ nsAutoCString suffix;
+ if (!attrs.PopulateFromSuffix(NS_ConvertUTF16toUTF8(aSuffix))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aAttrs = attrs;
+}
+
+/* static */
+void ChromeUtils::FillNonDefaultOriginAttributes(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ dom::OriginAttributesDictionary& aNewAttrs) {
+ aNewAttrs = aAttrs;
+}
+
+/* static */
+bool ChromeUtils::IsOriginAttributesEqual(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB) {
+ return IsOriginAttributesEqual(aA, aB);
+}
+
+/* static */
+bool ChromeUtils::IsOriginAttributesEqual(
+ const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB) {
+ return aA == aB;
+}
+
+/* static */
+void ChromeUtils::GetBaseDomainFromPartitionKey(dom::GlobalObject& aGlobal,
+ const nsAString& aPartitionKey,
+ nsAString& aBaseDomain,
+ ErrorResult& aRv) {
+ nsString scheme;
+ nsString pkBaseDomain;
+ int32_t port;
+
+ if (!mozilla::OriginAttributes::ParsePartitionKey(aPartitionKey, scheme,
+ pkBaseDomain, port)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aBaseDomain = pkBaseDomain;
+}
+
+/* static */
+void ChromeUtils::GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
+ const nsAString& aURL,
+ nsAString& aPartitionKey,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
+ if (NS_SUCCEEDED(rv) && uri->SchemeIs("chrome")) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPartitionKey.Truncate();
+ aRv.Throw(rv);
+ return;
+ }
+
+ mozilla::OriginAttributes attrs;
+ attrs.SetPartitionKey(uri);
+
+ aPartitionKey = attrs.mPartitionKey;
+}
+
+#ifdef NIGHTLY_BUILD
+/* static */
+void ChromeUtils::GetRecentJSDevError(GlobalObject& aGlobal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ aRetval.setUndefined();
+ auto runtime = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(runtime);
+
+ auto cx = aGlobal.Context();
+ if (!runtime->GetRecentDevError(cx, aRetval)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+}
+
+/* static */
+void ChromeUtils::ClearRecentJSDevError(GlobalObject&) {
+ auto runtime = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(runtime);
+
+ runtime->ClearRecentDevError();
+}
+#endif // NIGHTLY_BUILD
+
+void ChromeUtils::ClearStyleSheetCacheByPrincipal(GlobalObject&,
+ nsIPrincipal* aForPrincipal) {
+ SharedStyleSheetCache::Clear(aForPrincipal);
+}
+
+void ChromeUtils::ClearStyleSheetCacheByBaseDomain(
+ GlobalObject&, const nsACString& aBaseDomain) {
+ SharedStyleSheetCache::Clear(nullptr, &aBaseDomain);
+}
+
+void ChromeUtils::ClearStyleSheetCache(GlobalObject&) {
+ SharedStyleSheetCache::Clear();
+}
+
+#define PROCTYPE_TO_WEBIDL_CASE(_procType, _webidl) \
+ case mozilla::ProcType::_procType: \
+ return WebIDLProcType::_webidl
+
+static WebIDLProcType ProcTypeToWebIDL(mozilla::ProcType aType) {
+ // Max is the value of the last enum, not the length, so add one.
+ static_assert(
+ WebIDLProcTypeValues::Count == static_cast<size_t>(ProcType::Max) + 1,
+ "In order for this static cast to be okay, "
+ "WebIDLProcType must match ProcType exactly");
+
+ // These must match the similar ones in E10SUtils.sys.mjs, RemoteTypes.h,
+ // ProcInfo.h and ChromeUtils.webidl
+ switch (aType) {
+ PROCTYPE_TO_WEBIDL_CASE(Web, Web);
+ PROCTYPE_TO_WEBIDL_CASE(WebIsolated, WebIsolated);
+ PROCTYPE_TO_WEBIDL_CASE(File, File);
+ PROCTYPE_TO_WEBIDL_CASE(Extension, Extension);
+ PROCTYPE_TO_WEBIDL_CASE(PrivilegedAbout, Privilegedabout);
+ PROCTYPE_TO_WEBIDL_CASE(PrivilegedMozilla, Privilegedmozilla);
+ PROCTYPE_TO_WEBIDL_CASE(WebCOOPCOEP, WithCoopCoep);
+ PROCTYPE_TO_WEBIDL_CASE(WebServiceWorker, WebServiceWorker);
+
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ PROCTYPE_TO_WEBIDL_CASE(procinfo_typename, webidl_typename);
+#define SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# define SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#include "mozilla/GeckoProcessTypes.h"
+#undef SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# undef SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#undef GECKO_PROCESS_TYPE
+
+ PROCTYPE_TO_WEBIDL_CASE(Preallocated, Preallocated);
+ PROCTYPE_TO_WEBIDL_CASE(Unknown, Unknown);
+ }
+
+ MOZ_ASSERT(false, "Unhandled case in ProcTypeToWebIDL");
+ return WebIDLProcType::Unknown;
+}
+
+#undef PROCTYPE_TO_WEBIDL_CASE
+
+/* static */
+already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ // This function will use IPDL to enable threads info on macOS
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1529023
+ if (!XRE_IsParentProcess()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ // Prepare the JS promise that will hold our response.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+
+ // Get a list of processes to examine and pre-fill them with available info.
+ // Note that this is subject to race conditions: just because we have a
+ // process in the list doesn't mean that the process will still be alive when
+ // we attempt to get its information. Followup code MUST be able to fail
+ // gracefully on some processes and still return whichever information is
+ // available.
+
+ // Get all the content parents.
+ // Note that this array includes even the long dead content parents, so we
+ // might have some garbage, especially with Fission.
+ // SAFETY NOTE: `contentParents` is only valid if used synchronously.
+ // Anything else and you may end up dealing with dangling pointers.
+ nsTArray<ContentParent*> contentParents;
+ ContentParent::GetAll(contentParents);
+
+ // Prepare our background request.
+ // We reserve one more slot for the browser process itself.
+ nsTArray<ProcInfoRequest> requests(contentParents.Length() + 1);
+ // Requesting process info for the browser process itself.
+ requests.EmplaceBack(
+ /* aPid = */ base::GetCurrentProcId(),
+ /* aProcessType = */ ProcType::Browser,
+ /* aOrigin = */ ""_ns,
+ /* aWindowInfo = */ nsTArray<WindowInfo>(),
+ /* aUtilityInfo = */ nsTArray<UtilityInfo>());
+
+ // First handle non-ContentParent processes.
+ mozilla::ipc::GeckoChildProcessHost::GetAll(
+ [&requests](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
+ base::ProcessId childPid = aGeckoProcess->GetChildProcessId();
+ if (childPid == 0) {
+ // Something went wrong with this process, it may be dead already,
+ // fail gracefully.
+ return;
+ }
+ mozilla::ProcType type = mozilla::ProcType::Unknown;
+
+ switch (aGeckoProcess->GetProcessType()) {
+ case GeckoProcessType::GeckoProcessType_Content: {
+ // These processes are handled separately.
+ return;
+ }
+
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ case GeckoProcessType::GeckoProcessType_##enum_name: { \
+ type = mozilla::ProcType::procinfo_typename; \
+ break; \
+ }
+#define SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# define SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#include "mozilla/GeckoProcessTypes.h"
+#ifndef MOZ_ENABLE_FORKSERVER
+# undef SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#undef SKIP_PROCESS_TYPE_CONTENT
+#undef GECKO_PROCESS_TYPE
+ default:
+ // Leave the default Unknown value in |type|.
+ break;
+ }
+
+ // Attach utility actor information to the process.
+ nsTArray<UtilityInfo> utilityActors;
+ if (aGeckoProcess->GetProcessType() ==
+ GeckoProcessType::GeckoProcessType_Utility) {
+ RefPtr<mozilla::ipc::UtilityProcessManager> upm =
+ mozilla::ipc::UtilityProcessManager::GetSingleton();
+ if (!utilityActors.AppendElements(upm->GetActors(aGeckoProcess),
+ fallible)) {
+ NS_WARNING("Error adding actors");
+ return;
+ }
+ }
+
+ requests.EmplaceBack(
+ /* aPid = */ childPid,
+ /* aProcessType = */ type,
+ /* aOrigin = */ ""_ns,
+ /* aWindowInfo = */ nsTArray<WindowInfo>(), // Without a
+ // ContentProcess, no
+ // DOM windows.
+ /* aUtilityInfo = */ std::move(utilityActors),
+ /* aChild = */ 0 // Without a ContentProcess, no ChildId.
+#ifdef XP_MACOSX
+ ,
+ /* aChildTask = */ aGeckoProcess->GetChildTask()
+#endif // XP_MACOSX
+ );
+ });
+
+ // Now handle ContentParents.
+ for (const auto* contentParent : contentParents) {
+ if (!contentParent || !contentParent->Process()) {
+ // Presumably, the process is dead or dying.
+ continue;
+ }
+ base::ProcessId pid = contentParent->Process()->GetChildProcessId();
+ if (pid == 0) {
+ // Presumably, the process is dead or dying.
+ continue;
+ }
+ if (contentParent->Process()->GetProcessType() !=
+ GeckoProcessType::GeckoProcessType_Content) {
+ // We're probably racing against a process changing type.
+ // We'll get it in the next call, skip it for the moment.
+ continue;
+ }
+
+ // Since this code is executed synchronously on the main thread,
+ // processes cannot die while we're in this loop.
+ mozilla::ProcType type = mozilla::ProcType::Unknown;
+
+ // Convert the remoteType into a ProcType.
+ // Ideally, the remoteType should be strongly typed
+ // upstream, this would make the conversion less brittle.
+ const nsAutoCString remoteType(contentParent->GetRemoteType());
+ if (StringBeginsWith(remoteType, FISSION_WEB_REMOTE_TYPE)) {
+ // WARNING: Do not change the order, as
+ // `DEFAULT_REMOTE_TYPE` is a prefix of
+ // `FISSION_WEB_REMOTE_TYPE`.
+ type = mozilla::ProcType::WebIsolated;
+ } else if (StringBeginsWith(remoteType, SERVICEWORKER_REMOTE_TYPE)) {
+ type = mozilla::ProcType::WebServiceWorker;
+ } else if (StringBeginsWith(remoteType,
+ WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
+ type = mozilla::ProcType::WebCOOPCOEP;
+ } else if (remoteType == FILE_REMOTE_TYPE) {
+ type = mozilla::ProcType::File;
+ } else if (remoteType == EXTENSION_REMOTE_TYPE) {
+ type = mozilla::ProcType::Extension;
+ } else if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
+ type = mozilla::ProcType::PrivilegedAbout;
+ } else if (remoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) {
+ type = mozilla::ProcType::PrivilegedMozilla;
+ } else if (remoteType == PREALLOC_REMOTE_TYPE) {
+ type = mozilla::ProcType::Preallocated;
+ } else if (StringBeginsWith(remoteType, DEFAULT_REMOTE_TYPE)) {
+ type = mozilla::ProcType::Web;
+ } else {
+ MOZ_CRASH_UNSAFE_PRINTF("Unknown remoteType '%s'", remoteType.get());
+ }
+
+ // By convention, everything after '=' is the origin.
+ nsAutoCString origin;
+ nsACString::const_iterator cursor;
+ nsACString::const_iterator end;
+ remoteType.BeginReading(cursor);
+ remoteType.EndReading(end);
+ if (FindCharInReadable('=', cursor, end)) {
+ origin = Substring(++cursor, end);
+ }
+
+ // Attach DOM window information to the process.
+ nsTArray<WindowInfo> windows;
+ for (const auto& browserParentWrapperKey :
+ contentParent->ManagedPBrowserParent()) {
+ for (const auto& windowGlobalParentWrapperKey :
+ browserParentWrapperKey->ManagedPWindowGlobalParent()) {
+ // WindowGlobalParent is the only immediate subclass of
+ // PWindowGlobalParent.
+ auto* windowGlobalParent =
+ static_cast<WindowGlobalParent*>(windowGlobalParentWrapperKey);
+
+ nsString documentTitle;
+ windowGlobalParent->GetDocumentTitle(documentTitle);
+ WindowInfo* window = windows.EmplaceBack(
+ fallible,
+ /* aOuterWindowId = */ windowGlobalParent->OuterWindowId(),
+ /* aDocumentURI = */ windowGlobalParent->GetDocumentURI(),
+ /* aDocumentTitle = */ std::move(documentTitle),
+ /* aIsProcessRoot = */ windowGlobalParent->IsProcessRoot(),
+ /* aIsInProcess = */ windowGlobalParent->IsInProcess());
+ if (!window) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ }
+ }
+ requests.EmplaceBack(
+ /* aPid = */ pid,
+ /* aProcessType = */ type,
+ /* aOrigin = */ origin,
+ /* aWindowInfo = */ std::move(windows),
+ /* aUtilityInfo = */ nsTArray<UtilityInfo>(),
+ /* aChild = */ contentParent->ChildID()
+#ifdef XP_MACOSX
+ ,
+ /* aChildTask = */ contentParent->Process()->GetChildTask()
+#endif // XP_MACOSX
+ );
+ }
+
+ // Now place background request.
+ RefPtr<nsISerialEventTarget> target =
+ global->EventTargetFor(TaskCategory::Performance);
+ mozilla::GetProcInfo(std::move(requests))
+ ->Then(
+ target, __func__,
+ [target,
+ domPromise](const HashMap<base::ProcessId, ProcInfo>& aSysProcInfo) {
+ ParentProcInfoDictionary parentInfo;
+ if (aSysProcInfo.count() == 0) {
+ // For some reason, we couldn't get *any* info.
+ // Maybe a sandboxing issue?
+ domPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ nsTArray<ChildProcInfoDictionary> childrenInfo(
+ aSysProcInfo.count() - 1);
+ for (auto iter = aSysProcInfo.iter(); !iter.done(); iter.next()) {
+ const auto& sysProcInfo = iter.get().value();
+ nsresult rv;
+ if (sysProcInfo.type == ProcType::Browser) {
+ rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, &parentInfo);
+ if (NS_FAILED(rv)) {
+ // Failing to copy? That's probably not something from we can
+ // (or should) try to recover gracefully.
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ MOZ_ASSERT(sysProcInfo.childId == 0);
+ MOZ_ASSERT(sysProcInfo.origin.IsEmpty());
+ } else {
+ mozilla::dom::ChildProcInfoDictionary* childInfo =
+ childrenInfo.AppendElement(fallible);
+ if (!childInfo) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, childInfo);
+ if (NS_FAILED(rv)) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ // Copy Firefox info.
+ childInfo->mChildID = sysProcInfo.childId;
+ childInfo->mOrigin = sysProcInfo.origin;
+ childInfo->mType = ProcTypeToWebIDL(sysProcInfo.type);
+
+ for (const auto& source : sysProcInfo.windows) {
+ auto* dest = childInfo->mWindows.AppendElement(fallible);
+ if (!dest) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ dest->mOuterWindowId = source.outerWindowId;
+ dest->mDocumentURI = source.documentURI;
+ dest->mDocumentTitle = source.documentTitle;
+ dest->mIsProcessRoot = source.isProcessRoot;
+ dest->mIsInProcess = source.isInProcess;
+ }
+
+ if (sysProcInfo.type == ProcType::Utility) {
+ for (const auto& source : sysProcInfo.utilityActors) {
+ auto* dest =
+ childInfo->mUtilityActors.AppendElement(fallible);
+ if (!dest) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ dest->mActorName = source.actorName;
+ }
+ }
+ }
+ }
+
+ // Attach the children to the parent.
+ mozilla::dom::Sequence<mozilla::dom::ChildProcInfoDictionary>
+ children(std::move(childrenInfo));
+ parentInfo.mChildren = std::move(children);
+ domPromise->MaybeResolve(parentInfo);
+ },
+ [domPromise](nsresult aRv) { domPromise->MaybeReject(aRv); });
+ MOZ_ASSERT(domPromise);
+
+ // sending back the promise instance
+ return domPromise.forget();
+}
+
+/* static */
+bool ChromeUtils::VsyncEnabled(GlobalObject& aGlobal) {
+ return mozilla::gfx::VsyncSource::GetFastestVsyncRate().isSome();
+}
+
+/* static */
+already_AddRefed<Promise> ChromeUtils::RequestPerformanceMetrics(
+ GlobalObject& aGlobal, ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Creating a JS promise
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+ RefPtr<nsISerialEventTarget> target =
+ global->EventTargetFor(TaskCategory::Performance);
+
+ // requesting metrics, that will be returned into the promise
+ PerformanceMetricsCollector::RequestMetrics()->Then(
+ target, __func__,
+ [domPromise,
+ target](nsTArray<dom::PerformanceInfoDictionary>&& aResults) {
+ domPromise->MaybeResolve(std::move(aResults));
+ },
+ [domPromise](const nsresult& aRv) { domPromise->MaybeReject(aRv); });
+
+ // sending back the promise instance
+ return domPromise.forget();
+}
+
+void ChromeUtils::SetPerfStatsCollectionMask(GlobalObject& aGlobal,
+ uint64_t aMask) {
+ PerfStats::SetCollectionMask(static_cast<PerfStats::MetricMask>(aMask));
+}
+
+already_AddRefed<Promise> ChromeUtils::CollectPerfStats(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ // Creating a JS promise
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PerfStats::PerfStatsPromise> extPromise =
+ PerfStats::CollectPerfStatsJSON();
+
+ extPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const nsCString& aResult) {
+ promise->MaybeResolve(NS_ConvertUTF8toUTF16(aResult));
+ },
+ [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });
+
+ return promise.forget();
+}
+
+constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;
+
+/* static */
+void ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal,
+ nsIPrincipal* aPrincipal,
+ JS::MutableHandle<JSObject*> aRetval) {
+ JSContext* cx = aGlobal.Context();
+
+ auto* principals = nsJSPrincipals::get(aPrincipal);
+
+ JS::StackCapture captureMode(JS::FirstSubsumedFrame(cx, principals));
+
+ JS::Rooted<JSObject*> frame(cx);
+ if (!JS::CaptureCurrentStack(cx, &frame, std::move(captureMode))) {
+ JS_ClearPendingException(cx);
+ aRetval.set(nullptr);
+ return;
+ }
+
+ // FirstSubsumedFrame gets us a stack which stops at the first principal which
+ // is subsumed by the given principal. That means that we may have a lot of
+ // privileged frames that we don't care about at the top of the stack, though.
+ // We need to filter those out to get the frame we actually want.
+ aRetval.set(
+ js::GetFirstSubsumedSavedFrame(cx, principals, frame, kSkipSelfHosted));
+}
+
+/* static */
+void ChromeUtils::CreateError(const GlobalObject& aGlobal,
+ const nsAString& aMessage,
+ JS::Handle<JSObject*> aStack,
+ JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) {
+ if (aStack && !JS::IsMaybeWrappedSavedFrame(aStack)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });
+
+ JS::Rooted<JSObject*> retVal(cx);
+ {
+ JS::Rooted<JSString*> fileName(cx, JS_GetEmptyString(cx));
+ uint32_t line = 0;
+ uint32_t column = 0;
+
+ Maybe<JSAutoRealm> ar;
+ JS::Rooted<JSObject*> stack(cx);
+ if (aStack) {
+ stack = UncheckedUnwrap(aStack);
+ ar.emplace(cx, stack);
+
+ JSPrincipals* principals =
+ JS::GetRealmPrincipals(js::GetContextRealm(cx));
+ if (JS::GetSavedFrameLine(cx, principals, stack, &line) !=
+ JS::SavedFrameResult::Ok ||
+ JS::GetSavedFrameColumn(cx, principals, stack, &column) !=
+ JS::SavedFrameResult::Ok ||
+ JS::GetSavedFrameSource(cx, principals, stack, &fileName) !=
+ JS::SavedFrameResult::Ok) {
+ return;
+ }
+ }
+
+ JS::Rooted<JSString*> message(cx);
+ {
+ JS::Rooted<JS::Value> msgVal(cx);
+ if (!xpc::NonVoidStringToJsval(cx, aMessage, &msgVal)) {
+ return;
+ }
+ message = msgVal.toString();
+ }
+
+ JS::Rooted<JS::Value> err(cx);
+ if (!JS::CreateError(cx, JSEXN_ERR, stack, fileName, line, column, nullptr,
+ message, JS::NothingHandleValue, &err)) {
+ return;
+ }
+
+ MOZ_ASSERT(err.isObject());
+ retVal = &err.toObject();
+ }
+
+ if (aStack && !JS_WrapObject(cx, &retVal)) {
+ return;
+ }
+
+ cleanup.release();
+ aRetVal.set(retVal);
+}
+
+/* static */
+already_AddRefed<Promise> ChromeUtils::RequestIOActivity(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false));
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+ mozilla::net::IOActivityMonitor::RequestActivities(domPromise);
+ return domPromise.forget();
+}
+
+/* static */
+bool ChromeUtils::HasReportingHeaderForOrigin(GlobalObject& global,
+ const nsAString& aOrigin,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ return ReportingHeader::HasReportingHeaderForOrigin(
+ NS_ConvertUTF16toUTF8(aOrigin));
+}
+
+/* static */
+PopupBlockerState ChromeUtils::GetPopupControlState(GlobalObject& aGlobal) {
+ switch (PopupBlocker::GetPopupControlState()) {
+ case PopupBlocker::PopupControlState::openAllowed:
+ return PopupBlockerState::OpenAllowed;
+
+ case PopupBlocker::PopupControlState::openControlled:
+ return PopupBlockerState::OpenControlled;
+
+ case PopupBlocker::PopupControlState::openBlocked:
+ return PopupBlockerState::OpenBlocked;
+
+ case PopupBlocker::PopupControlState::openAbused:
+ return PopupBlockerState::OpenAbused;
+
+ case PopupBlocker::PopupControlState::openOverridden:
+ return PopupBlockerState::OpenOverridden;
+
+ default:
+ MOZ_CRASH(
+ "PopupBlocker::PopupControlState and PopupBlockerState are out of "
+ "sync");
+ }
+}
+
+/* static */
+double ChromeUtils::LastExternalProtocolIframeAllowed(GlobalObject& aGlobal) {
+ TimeStamp when = PopupBlocker::WhenLastExternalProtocolIframeAllowed();
+ if (when.IsNull()) {
+ return 0;
+ }
+
+ TimeDuration duration = TimeStamp::Now() - when;
+ return duration.ToMilliseconds();
+}
+
+/* static */
+void ChromeUtils::ResetLastExternalProtocolIframeAllowed(
+ GlobalObject& aGlobal) {
+ PopupBlocker::ResetLastExternalProtocolIframeAllowed();
+}
+
+/* static */
+void ChromeUtils::EndWheelTransaction(GlobalObject& aGlobal) {
+ // This allows us to end the current wheel transaction from the browser
+ // chrome. We do not need to perform any checks before calling
+ // EndTransaction(), as it should do nothing in the case that there is
+ // no current wheel transaction.
+ WheelTransaction::EndTransaction();
+}
+
+/* static */
+void ChromeUtils::RegisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const WindowActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->RegisterWindowActor(aName, aOptions, aRv);
+}
+
+/* static */
+void ChromeUtils::UnregisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->UnregisterWindowActor(aName);
+}
+
+/* static */
+void ChromeUtils::RegisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->RegisterProcessActor(aName, aOptions, aRv);
+}
+
+/* static */
+void ChromeUtils::UnregisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->UnregisterProcessActor(aName);
+}
+
+/* static */
+bool ChromeUtils::IsClassifierBlockingErrorCode(GlobalObject& aGlobal,
+ uint32_t aError) {
+ return net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ static_cast<nsresult>(aError));
+}
+
+/* static */
+void ChromeUtils::PrivateNoteIntentionalCrash(const GlobalObject& aGlobal,
+ ErrorResult& aError) {
+ if (XRE_IsContentProcess()) {
+ NoteIntentionalCrash("tab");
+ return;
+ }
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+/* static */
+nsIDOMProcessChild* ChromeUtils::GetDomProcessChild(const GlobalObject&) {
+ return nsIDOMProcessChild::GetSingleton();
+}
+
+/* static */
+void ChromeUtils::GetAllDOMProcesses(
+ GlobalObject& aGlobal, nsTArray<RefPtr<nsIDOMProcessParent>>& aParents,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.ThrowNotAllowedError(
+ "getAllDOMProcesses() may only be called in the parent process");
+ return;
+ }
+ aParents.Clear();
+ // Always add the parent process nsIDOMProcessParent first
+ aParents.AppendElement(InProcessParent::Singleton());
+
+ // Before adding nsIDOMProcessParent for all the content processes
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ aParents.AppendElement(cp);
+ }
+}
+
+/* static */
+void ChromeUtils::ConsumeInteractionData(
+ GlobalObject& aGlobal, Record<nsString, InteractionData>& aInteractions,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.ThrowNotAllowedError(
+ "consumeInteractionData() may only be called in the parent "
+ "process");
+ return;
+ }
+ EventStateManager::ConsumeInteractionData(aInteractions);
+}
+
+already_AddRefed<Promise> ChromeUtils::CollectScrollingData(
+ GlobalObject& aGlobal, ErrorResult& aRv) {
+ // Creating a JS promise
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ScrollingMetrics::ScrollingMetricsPromise> extPromise =
+ ScrollingMetrics::CollectScrollingMetrics();
+
+ extPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const std::tuple<uint32_t, uint32_t>& aResult) {
+ InteractionData out = {};
+ out.mInteractionTimeInMilliseconds = std::get<0>(aResult);
+ out.mScrollingDistanceInPixels = std::get<1>(aResult);
+ promise->MaybeResolve(out);
+ },
+ [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });
+
+ return promise.forget();
+}
+
+/* static */
+void ChromeUtils::GetFormAutofillConfidences(
+ GlobalObject& aGlobal, const Sequence<OwningNonNull<Element>>& aElements,
+ nsTArray<FormAutofillConfidences>& aResults, ErrorResult& aRv) {
+ FormAutofillNative::GetFormAutofillConfidences(aGlobal, aElements, aResults,
+ aRv);
+}
+
+bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) {
+ nsIFrame* f = aElement.GetPrimaryFrame(FlushType::Frames);
+ if (!f) {
+ return false;
+ }
+ return nsNativeTheme::IsDarkBackground(f);
+}
+
+double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; }
+
+/* static */
+void ChromeUtils::EnsureJSOracleStarted(GlobalObject&) {
+ if (StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
+ JSOracleParent::WithJSOracle([](JSOracleParent* aParent) {});
+ }
+}
+
+/* static */
+unsigned ChromeUtils::AliveUtilityProcesses(const GlobalObject&) {
+ const auto& utilityProcessManager =
+ mozilla::ipc::UtilityProcessManager::GetIfExists();
+ return utilityProcessManager ? utilityProcessManager->AliveProcesses() : 0;
+}
+
+/* static */
+void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
+ nsTArray<nsCString>& aNames) {
+ aNames.Clear();
+ for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) {
+ auto idlName = static_cast<UtilityActorName>(i);
+ aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName));
+ }
+}
+
+std::atomic<uint32_t> ChromeUtils::sDevToolsOpenedCount = 0;
+
+/* static */
+bool ChromeUtils::IsDevToolsOpened() {
+ return ChromeUtils::sDevToolsOpenedCount > 0;
+}
+
+/* static */
+bool ChromeUtils::IsDevToolsOpened(GlobalObject& aGlobal) {
+ return ChromeUtils::IsDevToolsOpened();
+}
+
+/* static */
+void ChromeUtils::NotifyDevToolsOpened(GlobalObject& aGlobal) {
+ ChromeUtils::sDevToolsOpenedCount++;
+}
+
+/* static */
+void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
+ MOZ_ASSERT(ChromeUtils::sDevToolsOpenedCount >= 1);
+ ChromeUtils::sDevToolsOpenedCount--;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h
new file mode 100644
index 0000000000..acb2cffdd2
--- /dev/null
+++ b/dom/base/ChromeUtils.h
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChromeUtils__
+#define mozilla_dom_ChromeUtils__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/Record.h"
+#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
+#include "nsIDOMProcessChild.h"
+#include "nsIDOMProcessParent.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace devtools {
+class HeapSnapshot;
+} // namespace devtools
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class BrowsingContext;
+class Element;
+class IdleRequestCallback;
+struct IdleRequestOptions;
+struct MediaMetadataInit;
+class MozQueryInterface;
+class PrecompiledScript;
+class Promise;
+struct ProcessActorOptions;
+struct WindowActorOptions;
+
+class ChromeUtils {
+ private:
+ // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
+ static void SaveHeapSnapshotShared(GlobalObject& global,
+ const HeapSnapshotBoundaries& boundaries,
+ nsAString& filePath, nsAString& snapshotId,
+ ErrorResult& rv);
+
+ public:
+ // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
+ static uint64_t GetObjectNodeId(GlobalObject& global,
+ JS::Handle<JSObject*> aVal);
+
+ // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
+ static void SaveHeapSnapshot(GlobalObject& global,
+ const HeapSnapshotBoundaries& boundaries,
+ nsAString& filePath, ErrorResult& rv);
+
+ // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
+ static void SaveHeapSnapshotGetId(GlobalObject& global,
+ const HeapSnapshotBoundaries& boundaries,
+ nsAString& snapshotId, ErrorResult& rv);
+
+ // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
+ static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(
+ GlobalObject& global, const nsAString& filePath, ErrorResult& rv);
+
+ static bool IsDevToolsOpened();
+ static bool IsDevToolsOpened(GlobalObject& aGlobal);
+ static void NotifyDevToolsOpened(GlobalObject& aGlobal);
+ static void NotifyDevToolsClosed(GlobalObject& aGlobal);
+
+ static void NondeterministicGetWeakMapKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
+
+ static void NondeterministicGetWeakSetKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aSet,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
+
+ static void Base64URLEncode(GlobalObject& aGlobal,
+ const ArrayBufferViewOrArrayBuffer& aSource,
+ const Base64URLEncodeOptions& aOptions,
+ nsACString& aResult, ErrorResult& aRv);
+
+ static void Base64URLDecode(GlobalObject& aGlobal, const nsACString& aString,
+ const Base64URLDecodeOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+
+ static void ReleaseAssert(GlobalObject& aGlobal, bool aCondition,
+ const nsAString& aMessage);
+
+ static void AddProfilerMarker(GlobalObject& aGlobal, const nsACString& aName,
+ const ProfilerMarkerOptionsOrDouble& aOptions,
+ const Optional<nsACString>& text);
+
+ static void GetXPCOMErrorName(GlobalObject& aGlobal, uint32_t aErrorCode,
+ nsACString& aRetval);
+
+ static void OriginAttributesToSuffix(
+ GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ nsCString& aSuffix);
+
+ static bool OriginAttributesMatchPattern(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ const dom::OriginAttributesPatternDictionary& aPattern);
+
+ static void CreateOriginAttributesFromOrigin(
+ dom::GlobalObject& aGlobal, const nsAString& aOrigin,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv);
+
+ static void CreateOriginAttributesFromOriginSuffix(
+ dom::GlobalObject& aGlobal, const nsAString& aSuffix,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv);
+
+ static void FillNonDefaultOriginAttributes(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ dom::OriginAttributesDictionary& aNewAttrs);
+
+ static bool IsOriginAttributesEqual(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB);
+
+ static bool IsOriginAttributesEqual(
+ const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB);
+
+ static bool IsOriginAttributesEqualIgnoringFPD(
+ const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB) {
+ return aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+ aA.mUserContextId == aB.mUserContextId &&
+ aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
+ }
+
+ static void GetBaseDomainFromPartitionKey(dom::GlobalObject& aGlobal,
+ const nsAString& aPartitionKey,
+ nsAString& aBaseDomain,
+ ErrorResult& aRv);
+
+ static void GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
+ const nsAString& aURL,
+ nsAString& aPartitionKey,
+ ErrorResult& aRv);
+
+ // Implemented in js/xpconnect/loader/ChromeScriptLoader.cpp
+ static already_AddRefed<Promise> CompileScript(
+ GlobalObject& aGlobal, const nsAString& aUrl,
+ const dom::CompileScriptOptionsDictionary& aOptions, ErrorResult& aRv);
+
+ static MozQueryInterface* GenerateQI(const GlobalObject& global,
+ const Sequence<JS::Value>& interfaces);
+
+ static void WaiveXrays(GlobalObject& aGlobal, JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static void UnwaiveXrays(GlobalObject& aGlobal, JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static void GetClassName(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
+ bool aUnwrap, nsAString& aRetval);
+
+ static bool IsDOMObject(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
+ bool aUnwrap);
+
+ static void ShallowClone(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
+ JS::Handle<JSObject*> aTarget,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+
+ static void IdleDispatch(const GlobalObject& global,
+ IdleRequestCallback& callback,
+ const IdleRequestOptions& options, ErrorResult& aRv);
+
+ static void GetRecentJSDevError(GlobalObject& aGlobal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+
+ static void ClearRecentJSDevError(GlobalObject& aGlobal);
+
+ static void ClearStyleSheetCacheByPrincipal(GlobalObject&,
+ nsIPrincipal* aForPrincipal);
+
+ static void ClearStyleSheetCacheByBaseDomain(GlobalObject& aGlobal,
+ const nsACString& aBaseDomain);
+
+ static void ClearStyleSheetCache(GlobalObject& aGlobal);
+
+ static already_AddRefed<Promise> RequestPerformanceMetrics(
+ GlobalObject& aGlobal, ErrorResult& aRv);
+
+ static void SetPerfStatsCollectionMask(GlobalObject& aGlobal, uint64_t aMask);
+
+ static already_AddRefed<Promise> CollectPerfStats(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise> RequestProcInfo(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static bool VsyncEnabled(GlobalObject& aGlobal);
+
+ static void Import(const GlobalObject& aGlobal,
+ const nsACString& aResourceURI,
+ const Optional<JS::Handle<JSObject*>>& aTargetObj,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv);
+
+ static void ImportESModule(const GlobalObject& aGlobal,
+ const nsAString& aResourceURI,
+ const ImportESModuleOptionsDictionary& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+
+ static void DefineLazyGetter(const GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::Value> aName,
+ JS::Handle<JSObject*> aLambda, ErrorResult& aRv);
+
+ static void DefineModuleGetter(const GlobalObject& global,
+ JS::Handle<JSObject*> target,
+ const nsAString& id,
+ const nsAString& resourceURI,
+ ErrorResult& aRv);
+
+ static void DefineESModuleGetters(const GlobalObject& global,
+ JS::Handle<JSObject*> target,
+ JS::Handle<JSObject*> modules,
+ ErrorResult& aRv);
+
+ static void GetCallerLocation(const GlobalObject& global,
+ nsIPrincipal* principal,
+ JS::MutableHandle<JSObject*> aRetval);
+
+ static void CreateError(const GlobalObject& global, const nsAString& message,
+ JS::Handle<JSObject*> stack,
+ JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise> RequestIOActivity(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static bool HasReportingHeaderForOrigin(GlobalObject& global,
+ const nsAString& aOrigin,
+ ErrorResult& aRv);
+
+ static PopupBlockerState GetPopupControlState(GlobalObject& aGlobal);
+
+ static double LastExternalProtocolIframeAllowed(GlobalObject& aGlobal);
+
+ static void ResetLastExternalProtocolIframeAllowed(GlobalObject& aGlobal);
+
+ static void EndWheelTransaction(GlobalObject& aGlobal);
+
+ static void RegisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const WindowActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ static void UnregisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName);
+
+ static void RegisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ static void UnregisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName);
+
+ static bool IsClassifierBlockingErrorCode(GlobalObject& aGlobal,
+ uint32_t aError);
+
+ static void PrivateNoteIntentionalCrash(const GlobalObject& aGlobal,
+ ErrorResult& aError);
+
+ static nsIDOMProcessChild* GetDomProcessChild(const GlobalObject&);
+
+ static void GetAllDOMProcesses(
+ GlobalObject& aGlobal, nsTArray<RefPtr<nsIDOMProcessParent>>& aParents,
+ ErrorResult& aRv);
+
+ static void ConsumeInteractionData(
+ GlobalObject& aGlobal, Record<nsString, InteractionData>& aInteractions,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise> CollectScrollingData(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static void GetFormAutofillConfidences(
+ GlobalObject& aGlobal, const Sequence<OwningNonNull<Element>>& aElements,
+ nsTArray<FormAutofillConfidences>& aResults, ErrorResult& aRv);
+
+ static bool IsDarkBackground(GlobalObject&, Element&);
+
+ static double DateNow(GlobalObject&);
+
+ static void EnsureJSOracleStarted(GlobalObject&);
+
+ static unsigned AliveUtilityProcesses(const GlobalObject&);
+
+ static void GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
+ nsTArray<nsCString>& aNames);
+
+ private:
+ // Number of DevTools session debugging the current process
+ static std::atomic<uint32_t> sDevToolsOpenedCount;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChromeUtils__
diff --git a/dom/base/Comment.cpp b/dom/base/Comment.cpp
new file mode 100644
index 0000000000..d9f5d68bcc
--- /dev/null
+++ b/dom/base/Comment.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementations of DOM Core's Comment node.
+ */
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/CommentBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla;
+using namespace dom;
+
+namespace mozilla::dom {
+
+Comment::~Comment() = default;
+
+already_AddRefed<CharacterData> Comment::CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const {
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ auto* nim = ni->NodeInfoManager();
+ RefPtr<Comment> it = new (nim) Comment(ni.forget());
+ if (aCloneText) {
+ it->mText = mText;
+ }
+
+ return it.forget();
+}
+
+#ifdef DEBUG
+void Comment::List(FILE* out, int32_t aIndent) const {
+ int32_t indx;
+ for (indx = aIndent; --indx >= 0;) fputs(" ", out);
+
+ fprintf(out, "Comment@%p refcount=%" PRIuPTR "<!--", (void*)this,
+ mRefCnt.get());
+
+ nsAutoString tmp;
+ ToCString(tmp, 0, mText.GetLength());
+ fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
+
+ fputs("-->\n", out);
+}
+#endif
+
+/* static */
+already_AddRefed<Comment> Comment::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window || !window->GetDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return window->GetDoc()->CreateComment(aData);
+}
+
+JSObject* Comment::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return Comment_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Comment.h b/dom/base/Comment.h
new file mode 100644
index 0000000000..5ad4d0bab8
--- /dev/null
+++ b/dom/base/Comment.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Comment_h
+#define mozilla_dom_Comment_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CharacterData.h"
+
+namespace mozilla::dom {
+
+class Comment final : public CharacterData {
+ private:
+ void Init() {
+ MOZ_ASSERT(mNodeInfo->NodeType() == COMMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+ }
+
+ virtual ~Comment();
+
+ public:
+ explicit Comment(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : CharacterData(std::move(aNodeInfo)) {
+ Init();
+ }
+
+ explicit Comment(nsNodeInfoManager* aNodeInfoManager)
+ : CharacterData(aNodeInfoManager->GetCommentNodeInfo()) {
+ Init();
+ }
+
+ NS_IMPL_FROMNODE_HELPER(Comment, IsComment())
+
+ // nsISupports
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(Comment, CharacterData)
+
+ virtual already_AddRefed<CharacterData> CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out = stdout, int32_t aIndent = 0,
+ bool aDumpAll = true) const override {
+ return;
+ }
+#endif
+
+ static already_AddRefed<Comment> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ ErrorResult& aRv);
+
+ protected:
+ virtual JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Comment_h
diff --git a/dom/base/CompressionStream.cpp b/dom/base/CompressionStream.cpp
new file mode 100644
index 0000000000..72c3b55d52
--- /dev/null
+++ b/dom/base/CompressionStream.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CompressionStream.h"
+
+#include "js/TypeDecls.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/CompressionStreamBinding.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/TransformStream.h"
+#include "mozilla/dom/TextDecoderStream.h"
+#include "mozilla/dom/TransformerCallbackHelpers.h"
+#include "mozilla/dom/UnionTypes.h"
+
+#include "ZLibHelper.h"
+
+// See the zlib manual in https://www.zlib.net/manual.html or in
+// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
+
+namespace mozilla::dom {
+
+class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+
+ explicit CompressionStreamAlgorithms(CompressionFormat format) {
+ int8_t err = deflateInit2(&mZStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ ZLibWindowBits(format), 8 /* default memLevel */,
+ Z_DEFAULT_STRATEGY);
+ if (err == Z_MEM_ERROR) {
+ MOZ_CRASH("Out of memory");
+ }
+ MOZ_ASSERT(err == Z_OK);
+ }
+
+ // Step 3 of
+ // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
+ // Let transformAlgorithm be an algorithm which takes a chunk argument and
+ // runs the compress and enqueue a chunk algorithm with this and chunk.
+ MOZ_CAN_RUN_SCRIPT
+ void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
+
+ // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
+ // (ExtractSpanFromBufferSource does it)
+ Span<const uint8_t> input = ExtractSpanFromBufferSource(cx, aChunk, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2: Let buffer be the result of compressing chunk with cs's format
+ // and context.
+ // Step 3 - 5: (Done in CompressAndEnqueue)
+ CompressAndEnqueue(cx, input, ZLibFlush::No, aController, aRv);
+ }
+
+ // Step 4 of
+ // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
+ // Let flushAlgorithm be an algorithm which takes no argument and runs the
+ // compress flush and enqueue algorithm with this.
+ MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // https://wicg.github.io/compression/#compress-flush-and-enqueue
+
+ // Step 1: Let buffer be the result of compressing an empty input with cs's
+ // format and context, with the finish flag.
+ // Step 2 - 4: (Done in CompressAndEnqueue)
+ CompressAndEnqueue(cx, Span<const uint8_t>(), ZLibFlush::Yes, aController,
+ aRv);
+ }
+
+ private:
+ // Shared by:
+ // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
+ // https://wicg.github.io/compression/#compress-flush-and-enqueue
+ MOZ_CAN_RUN_SCRIPT void CompressAndEnqueue(
+ JSContext* aCx, Span<const uint8_t> aInput, ZLibFlush aFlush,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) {
+ MOZ_ASSERT_IF(aFlush == ZLibFlush::Yes, !aInput.Length());
+
+ mZStream.avail_in = aInput.Length();
+ mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
+
+ JS::RootedVector<JSObject*> array(aCx);
+
+ do {
+ static uint16_t kBufferSize = 16384;
+ UniquePtr<uint8_t> buffer(
+ static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
+ if (!buffer) {
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+
+ mZStream.avail_out = kBufferSize;
+ mZStream.next_out = buffer.get();
+
+ int8_t err = deflate(&mZStream, aFlush);
+
+ // From the manual: deflate() returns ...
+ switch (err) {
+ case Z_OK:
+ case Z_STREAM_END:
+ case Z_BUF_ERROR:
+ // * Z_OK if some progress has been made
+ // * Z_STREAM_END if all input has been consumed and all output has
+ // been produced (only when flush is set to Z_FINISH)
+ // * Z_BUF_ERROR if no progress is possible (for example avail_in or
+ // avail_out was zero). Note that Z_BUF_ERROR is not fatal, and
+ // deflate() can be called again with more input and more output space
+ // to continue compressing.
+ //
+ // (But of course no input should be given after Z_FINISH)
+ break;
+ case Z_STREAM_ERROR:
+ default:
+ // * Z_STREAM_ERROR if the stream state was inconsistent
+ // (which is fatal)
+ MOZ_ASSERT_UNREACHABLE("Unexpected compression error code");
+ aRv.ThrowTypeError("Unexpected compression error");
+ return;
+ }
+
+ // Stream should end only when flushed and vise versa, see above
+ MOZ_ASSERT_IF(err == Z_STREAM_END, aFlush == ZLibFlush::Yes);
+ MOZ_ASSERT_IF(aFlush == ZLibFlush::Yes, err == Z_STREAM_END);
+
+ // At this point we either exhausted the input or the output buffer
+ MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out);
+
+ size_t written = kBufferSize - mZStream.avail_out;
+ if (!written) {
+ break;
+ }
+
+ // Step 3: If buffer is empty, return.
+ // (We'll implicitly return when the array is empty.)
+
+ // Step 4: Split buffer into one or more non-empty pieces and convert them
+ // into Uint8Arrays.
+ // (The buffer is 'split' by having a fixed sized buffer above.)
+
+ JS::Rooted<JSObject*> view(
+ aCx, nsJSUtils::MoveBufferAsUint8Array(aCx, written, buffer));
+ if (!view || !array.append(view)) {
+ JS_ClearPendingException(aCx);
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+ } while (mZStream.avail_out == 0);
+ // From the manual:
+ // If deflate returns with avail_out == 0, this function must be called
+ // again with the same value of the flush parameter and more output space
+ // (updated avail_out)
+
+ // Step 5: For each Uint8Array array, enqueue array in cs's transform.
+ for (const auto& view : array) {
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
+ aController.Enqueue(aCx, value, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+
+ ~CompressionStreamAlgorithms() override { deflateEnd(&mZStream); };
+
+ z_stream mZStream = {};
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_IMPL_ADDREF_INHERITED(CompressionStreamAlgorithms, TransformerAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(CompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStreamAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CompressionStream, mGlobal, mStream)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CompressionStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CompressionStream)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompressionStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CompressionStream::CompressionStream(nsISupports* aGlobal,
+ TransformStream& aStream)
+ : mGlobal(aGlobal), mStream(&aStream) {}
+
+CompressionStream::~CompressionStream() = default;
+
+JSObject* CompressionStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CompressionStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://wicg.github.io/compression/#dom-compressionstream-compressionstream
+already_AddRefed<CompressionStream> CompressionStream::Constructor(
+ const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) {
+ // Step 1: If format is unsupported in CompressionStream, then throw a
+ // TypeError.
+ // XXX: Skipped as we are using enum for this
+
+ // Step 2 - 4: (Done in CompressionStreamAlgorithms)
+
+ // Step 5: Set this's transform to a new TransformStream.
+
+ // Step 6: Set up this's transform with transformAlgorithm set to
+ // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
+ auto algorithms = MakeRefPtr<CompressionStreamAlgorithms>(aFormat);
+
+ RefPtr<TransformStream> stream =
+ TransformStream::CreateGeneric(aGlobal, *algorithms, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return do_AddRef(new CompressionStream(aGlobal.GetAsSupports(), *stream));
+}
+
+already_AddRefed<ReadableStream> CompressionStream::Readable() const {
+ return do_AddRef(mStream->Readable());
+}
+
+already_AddRefed<WritableStream> CompressionStream::Writable() const {
+ return do_AddRef(mStream->Writable());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/CompressionStream.h b/dom/base/CompressionStream.h
new file mode 100644
index 0000000000..23b5d4355b
--- /dev/null
+++ b/dom/base/CompressionStream.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_COMPRESSIONSTREAM_H_
+#define DOM_COMPRESSIONSTREAM_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class WritableStream;
+class TransformStream;
+
+enum class CompressionFormat : uint8_t;
+
+class CompressionStream final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CompressionStream)
+
+ public:
+ CompressionStream(nsISupports* aGlobal, TransformStream& aStream);
+
+ protected:
+ ~CompressionStream();
+
+ public:
+ nsISupports* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<CompressionStream>
+ Constructor(const GlobalObject& aGlobal, CompressionFormat aFormat,
+ ErrorResult& aRv);
+
+ already_AddRefed<ReadableStream> Readable() const;
+
+ already_AddRefed<WritableStream> Writable() const;
+
+ private:
+ nsCOMPtr<nsISupports> mGlobal;
+
+ RefPtr<TransformStream> mStream;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_COMPRESSIONSTREAM_H_
diff --git a/dom/base/ContentAreaDropListener.sys.mjs b/dom/base/ContentAreaDropListener.sys.mjs
new file mode 100644
index 0000000000..9f7bc500a1
--- /dev/null
+++ b/dom/base/ContentAreaDropListener.sys.mjs
@@ -0,0 +1,329 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This component is used for handling dragover and drop of urls.
+//
+// It checks to see whether a drop of a url is allowed. For instance, a url
+// cannot be dropped if it is not a valid uri or the source of the drag cannot
+// access the uri. This prevents, for example, a source document from tricking
+// the user into dragging a chrome url.
+
+export function ContentAreaDropListener() {}
+
+ContentAreaDropListener.prototype = {
+ classID: Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIDroppedLinkHandler"]),
+
+ _addLink(links, url, name, type) {
+ links.push({ url, name, type });
+ },
+
+ _addLinksFromItem(links, dt, i) {
+ let types = dt.mozTypesAt(i);
+ let type, data;
+
+ type = "text/uri-list";
+ if (types.contains(type)) {
+ data = dt.mozGetDataAt(type, i);
+ if (data) {
+ let urls = data.split("\n");
+ for (let url of urls) {
+ // lines beginning with # are comments
+ if (url.startsWith("#")) {
+ continue;
+ }
+ url = url.replace(/^\s+|\s+$/g, "");
+ this._addLink(links, url, url, type);
+ }
+ return;
+ }
+ }
+
+ type = "text/x-moz-url";
+ if (types.contains(type)) {
+ data = dt.mozGetDataAt(type, i);
+ if (data) {
+ let lines = data.split("\n");
+ for (let i = 0, length = lines.length; i < length; i += 2) {
+ this._addLink(links, lines[i], lines[i + 1], type);
+ }
+ return;
+ }
+ }
+
+ for (let type of ["text/plain", "text/x-moz-text-internal"]) {
+ if (types.contains(type)) {
+ data = dt.mozGetDataAt(type, i);
+ if (data) {
+ let lines = data.replace(/^\s+|\s+$/gm, "").split("\n");
+ if (!lines.length) {
+ return;
+ }
+
+ // For plain text, there are 2 cases:
+ // * if there is at least one URI:
+ // Add all URIs, ignoring non-URI lines, so that all URIs
+ // are opened in tabs.
+ // * if there's no URI:
+ // Add the entire text as a single entry, so that the entire
+ // text is searched.
+ let hasURI = false;
+ // We don't care whether we are in a private context, because we are
+ // only using fixedURI and thus there's no risk to use the wrong
+ // search engine.
+ let flags =
+ Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ for (let line of lines) {
+ let info = Services.uriFixup.getFixupURIInfo(line, flags);
+ if (info.fixedURI) {
+ // Use the original line here, and let the caller decide
+ // whether to perform fixup or not.
+ hasURI = true;
+ this._addLink(links, line, line, type);
+ }
+ }
+
+ if (!hasURI) {
+ this._addLink(links, data, data, type);
+ }
+ return;
+ }
+ }
+ }
+
+ // For shortcuts, we want to check for the file type last, so that the
+ // url pointed to in one of the url types is found first before the file
+ // type, which points to the actual file.
+ let files = dt.files;
+ if (files && i < files.length) {
+ this._addLink(
+ links,
+ PathUtils.toFileURI(files[i].mozFullPath),
+ files[i].name,
+ "application/x-moz-file"
+ );
+ }
+ },
+
+ _getDropLinks(dt) {
+ let links = [];
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ this._addLinksFromItem(links, dt, i);
+ }
+ return links;
+ },
+
+ _validateURI(dataTransfer, uriString, disallowInherit, triggeringPrincipal) {
+ if (!uriString) {
+ return "";
+ }
+
+ // Strip leading and trailing whitespace, then try to create a
+ // URI from the dropped string. If that succeeds, we're
+ // dropping a URI and we need to do a security check to make
+ // sure the source document can load the dropped URI.
+ uriString = uriString.replace(/^\s*|\s*$/g, "");
+
+ // Apply URI fixup so that this validation prevents bad URIs even if the
+ // similar fixup is applied later, especialy fixing typos up will convert
+ // non-URI to URI.
+ // We don't know if the uri comes from a private context, but luckily we
+ // are only using fixedURI, so there's no risk to use the wrong search
+ // engine.
+ let fixupFlags =
+ Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ let info = Services.uriFixup.getFixupURIInfo(uriString, fixupFlags);
+ if (!info.fixedURI || info.keywordProviderName) {
+ // Loading a keyword search should always be fine for all cases.
+ return uriString;
+ }
+ let uri = info.fixedURI;
+
+ let secMan = Services.scriptSecurityManager;
+ let flags = secMan.STANDARD;
+ if (disallowInherit) {
+ flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
+ }
+
+ secMan.checkLoadURIWithPrincipal(triggeringPrincipal, uri, flags);
+
+ // Once we validated, return the URI after fixup, instead of the original
+ // uriString.
+ return uri.spec;
+ },
+
+ _getTriggeringPrincipalFromDataTransfer(
+ aDataTransfer,
+ fallbackToSystemPrincipal
+ ) {
+ let sourceNode = aDataTransfer.mozSourceNode;
+ if (
+ sourceNode &&
+ (sourceNode.localName !== "browser" ||
+ sourceNode.namespaceURI !==
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
+ ) {
+ // Use sourceNode's principal only if the sourceNode is not browser.
+ //
+ // If sourceNode is browser, the actual triggering principal may be
+ // differ than sourceNode's principal, since sourceNode's principal is
+ // top level document's one and the drag may be triggered from a frame
+ // with different principal.
+ if (sourceNode.nodePrincipal) {
+ return sourceNode.nodePrincipal;
+ }
+ }
+
+ // First, fallback to mozTriggeringPrincipalURISpec that is set when the
+ // drop comes from another content process.
+ let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec;
+ if (!principalURISpec) {
+ // Fallback to either system principal or file principal, supposing
+ // the drop comes from outside of the browser, so that drops of file
+ // URIs are always allowed.
+ //
+ // TODO: Investigate and describe the difference between them,
+ // or use only one principal. (Bug 1367038)
+ if (fallbackToSystemPrincipal) {
+ return Services.scriptSecurityManager.getSystemPrincipal();
+ }
+
+ principalURISpec = "file:///";
+ }
+ return Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(principalURISpec),
+ {}
+ );
+ },
+
+ getTriggeringPrincipal(aEvent) {
+ let dataTransfer = aEvent.dataTransfer;
+ return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true);
+ },
+
+ getCsp(aEvent) {
+ let sourceNode = aEvent.dataTransfer.mozSourceNode;
+ if (aEvent.dataTransfer.mozCSP !== null) {
+ return aEvent.dataTransfer.mozCSP;
+ }
+
+ if (
+ sourceNode &&
+ (sourceNode.localName !== "browser" ||
+ sourceNode.namespaceURI !==
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
+ ) {
+ // Use sourceNode's csp only if the sourceNode is not browser.
+ //
+ // If sourceNode is browser, the actual triggering csp may be differ than sourceNode's csp,
+ // since sourceNode's csp is top level document's one and the drag may be triggered from a
+ // frame with different csp.
+ return sourceNode.csp;
+ }
+ return null;
+ },
+
+ canDropLink(aEvent, aAllowSameDocument) {
+ if (this._eventTargetIsDisabled(aEvent)) {
+ return false;
+ }
+
+ let dataTransfer = aEvent.dataTransfer;
+ let types = dataTransfer.types;
+ if (
+ !types.includes("application/x-moz-file") &&
+ !types.includes("text/x-moz-url") &&
+ !types.includes("text/uri-list") &&
+ !types.includes("text/x-moz-text-internal") &&
+ !types.includes("text/plain")
+ ) {
+ return false;
+ }
+
+ if (aAllowSameDocument) {
+ return true;
+ }
+
+ // If this is an external drag, allow drop.
+ let sourceTopWC = dataTransfer.sourceTopWindowContext;
+ if (!sourceTopWC) {
+ return true;
+ }
+
+ // If drag source and drop target are in the same top window, don't allow.
+ let eventWC =
+ aEvent.originalTarget.ownerGlobal.browsingContext.currentWindowContext;
+ if (eventWC && sourceTopWC == eventWC.topWindowContext) {
+ return false;
+ }
+
+ return true;
+ },
+
+ dropLinks(aEvent, aDisallowInherit) {
+ if (aEvent && this._eventTargetIsDisabled(aEvent)) {
+ return [];
+ }
+
+ let dataTransfer = aEvent.dataTransfer;
+ let links = this._getDropLinks(dataTransfer);
+ let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
+ dataTransfer,
+ false
+ );
+
+ for (let link of links) {
+ try {
+ link.url = this._validateURI(
+ dataTransfer,
+ link.url,
+ aDisallowInherit,
+ triggeringPrincipal
+ );
+ } catch (ex) {
+ // Prevent the drop entirely if any of the links are invalid even if
+ // one of them is valid.
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ throw ex;
+ }
+ }
+
+ return links;
+ },
+
+ validateURIsForDrop(aEvent, aURIs, aDisallowInherit) {
+ let dataTransfer = aEvent.dataTransfer;
+ let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
+ dataTransfer,
+ false
+ );
+
+ for (let uri of aURIs) {
+ this._validateURI(
+ dataTransfer,
+ uri,
+ aDisallowInherit,
+ triggeringPrincipal
+ );
+ }
+ },
+
+ queryLinks(aDataTransfer) {
+ return this._getDropLinks(aDataTransfer);
+ },
+
+ _eventTargetIsDisabled(aEvent) {
+ let ownerDoc = aEvent.originalTarget.ownerDocument;
+ if (!ownerDoc || !ownerDoc.defaultView) {
+ return false;
+ }
+
+ return ownerDoc.defaultView.windowUtils.isNodeDisabledForEvents(
+ aEvent.originalTarget
+ );
+ },
+};
diff --git a/dom/base/ContentFrameMessageManager.cpp b/dom/base/ContentFrameMessageManager.cpp
new file mode 100644
index 0000000000..b8bf8a2ced
--- /dev/null
+++ b/dom/base/ContentFrameMessageManager.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentFrameMessageManager.h"
+#include "js/RootingAPI.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+JSObject* ContentFrameMessageManager::GetOrCreateWrapper() {
+ JS::Rooted<JS::Value> val(RootingCx());
+ {
+ // Scope to run ~AutoJSAPI before working with a raw JSObject*.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
+ return nullptr;
+ }
+ }
+ MOZ_ASSERT(val.isObject());
+ return &val.toObject();
+}
diff --git a/dom/base/ContentFrameMessageManager.h b/dom/base/ContentFrameMessageManager.h
new file mode 100644
index 0000000000..49c0676bcb
--- /dev/null
+++ b/dom/base/ContentFrameMessageManager.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ContentFrameMessageManager_h
+#define mozilla_dom_ContentFrameMessageManager_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/MessageManagerGlobal.h"
+#include "nsContentUtils.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+template <typename>
+struct Nullable;
+class WindowProxyHolder;
+
+#define NS_CONTENTFRAMEMESSAGEMANAGER_IID \
+ { \
+ 0x97e192a6, 0xab7a, 0x4c8f, { \
+ 0xb7, 0xdd, 0xf7, 0xec, 0x36, 0x38, 0x71, 0xb5 \
+ } \
+ }
+
+/**
+ * Base class for implementing the WebIDL ContentFrameMessageManager class.
+ */
+class ContentFrameMessageManager : public DOMEventTargetHelper,
+ public MessageManagerGlobal {
+ public:
+ using DOMEventTargetHelper::AddRef;
+ using DOMEventTargetHelper::Release;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTFRAMEMESSAGEMANAGER_IID)
+
+ virtual Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) = 0;
+ virtual already_AddRefed<nsIDocShell> GetDocShell(ErrorResult& aError) = 0;
+ virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() = 0;
+
+ nsFrameMessageManager* GetMessageManager() { return mMessageManager; }
+ void DisconnectMessageManager() {
+ mMessageManager->Disconnect();
+ mMessageManager = nullptr;
+ }
+
+ JSObject* GetOrCreateWrapper();
+
+ protected:
+ explicit ContentFrameMessageManager(nsFrameMessageManager* aMessageManager)
+ : DOMEventTargetHelper(xpc::NativeGlobal(xpc::PrivilegedJunkScope())),
+ MessageManagerGlobal(aMessageManager) {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ContentFrameMessageManager,
+ NS_CONTENTFRAMEMESSAGEMANAGER_IID)
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ContentFrameMessageManager_h
diff --git a/dom/base/ContentIterator.cpp b/dom/base/ContentIterator.cpp
new file mode 100644
index 0000000000..279f1a35c1
--- /dev/null
+++ b/dom/base/ContentIterator.cpp
@@ -0,0 +1,1057 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentIterator.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/Result.h"
+
+#include "nsContentUtils.h"
+#include "nsElementTable.h"
+#include "nsIContent.h"
+#include "nsRange.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+static bool ComparePostMode(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd, nsINode& aNode) {
+ nsINode* parent = aNode.GetParentNode();
+ if (!parent) {
+ return false;
+ }
+
+ // aNode should always be content, as we have a parent, but let's just be
+ // extra careful and check.
+ nsIContent* content =
+ NS_WARN_IF(!aNode.IsContent()) ? nullptr : aNode.AsContent();
+
+ // Post mode: start < node <= end.
+ RawRangeBoundary afterNode(parent, content);
+ const auto isStartLessThanAfterNode = [&]() {
+ const Maybe<int32_t> startComparedToAfterNode =
+ nsContentUtils::ComparePoints(aStart, afterNode);
+ return !NS_WARN_IF(!startComparedToAfterNode) &&
+ (*startComparedToAfterNode < 0);
+ };
+
+ const auto isAfterNodeLessOrEqualToEnd = [&]() {
+ const Maybe<int32_t> afterNodeComparedToEnd =
+ nsContentUtils::ComparePoints(afterNode, aEnd);
+ return !NS_WARN_IF(!afterNodeComparedToEnd) &&
+ (*afterNodeComparedToEnd <= 0);
+ };
+
+ return isStartLessThanAfterNode() && isAfterNodeLessOrEqualToEnd();
+}
+
+static bool ComparePreMode(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd, nsINode& aNode) {
+ nsINode* parent = aNode.GetParentNode();
+ if (!parent) {
+ return false;
+ }
+
+ // Pre mode: start <= node < end.
+ RawRangeBoundary beforeNode(parent, aNode.GetPreviousSibling());
+
+ const auto isStartLessOrEqualToBeforeNode = [&]() {
+ const Maybe<int32_t> startComparedToBeforeNode =
+ nsContentUtils::ComparePoints(aStart, beforeNode);
+ return !NS_WARN_IF(!startComparedToBeforeNode) &&
+ (*startComparedToBeforeNode <= 0);
+ };
+
+ const auto isBeforeNodeLessThanEndNode = [&]() {
+ const Maybe<int32_t> beforeNodeComparedToEnd =
+ nsContentUtils::ComparePoints(beforeNode, aEnd);
+ return !NS_WARN_IF(!beforeNodeComparedToEnd) &&
+ (*beforeNodeComparedToEnd < 0);
+ };
+
+ return isStartLessOrEqualToBeforeNode() && isBeforeNodeLessThanEndNode();
+}
+
+///////////////////////////////////////////////////////////////////////////
+// NodeIsInTraversalRange: returns true if content is visited during
+// the traversal of the range in the specified mode.
+//
+static bool NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
+ const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd) {
+ if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet()) ||
+ NS_WARN_IF(!aNode)) {
+ return false;
+ }
+
+ // If a leaf node contains an end point of the traversal range, it is
+ // always in the traversal range.
+ if (aNode == aStart.Container() || aNode == aEnd.Container()) {
+ if (aNode->IsCharacterData()) {
+ return true; // text node or something
+ }
+ if (!aNode->HasChildren()) {
+ MOZ_ASSERT(
+ aNode != aStart.Container() || aStart.IsStartOfContainer(),
+ "aStart.Container() doesn't have children and not a data node, "
+ "aStart should be at the beginning of its container");
+ MOZ_ASSERT(aNode != aEnd.Container() || aEnd.IsStartOfContainer(),
+ "aEnd.Container() doesn't have children and not a data node, "
+ "aEnd should be at the beginning of its container");
+ return true;
+ }
+ }
+
+ if (aIsPreMode) {
+ return ComparePreMode(aStart, aEnd, *aNode);
+ }
+
+ return ComparePostMode(aStart, aEnd, *aNode);
+}
+
+// Each concreate class of ContentIteratorBase may be owned by another class
+// which may be owned by JS. Therefore, all of them should be in the cycle
+// collection. However, we cannot make non-refcountable classes only with the
+// macros. So, we need to make them cycle collectable without the macros.
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ ContentIteratorBase& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mCurNode, aName, aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mFirst, aName, aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mLast, aName, aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mClosestCommonInclusiveAncestor,
+ aName, aFlags);
+}
+
+void ImplCycleCollectionUnlink(ContentIteratorBase& aField) {
+ ImplCycleCollectionUnlink(aField.mCurNode);
+ ImplCycleCollectionUnlink(aField.mFirst);
+ ImplCycleCollectionUnlink(aField.mLast);
+ ImplCycleCollectionUnlink(aField.mClosestCommonInclusiveAncestor);
+}
+
+ContentIteratorBase::ContentIteratorBase(Order aOrder) : mOrder(aOrder) {}
+
+ContentIteratorBase::~ContentIteratorBase() = default;
+
+/******************************************************
+ * Init routines
+ ******************************************************/
+
+nsresult ContentIteratorBase::Init(nsINode* aRoot) {
+ if (NS_WARN_IF(!aRoot)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (mOrder == Order::Pre) {
+ mFirst = aRoot;
+ mLast = ContentIteratorBase::GetDeepLastChild(aRoot);
+ NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
+ } else {
+ mFirst = ContentIteratorBase::GetDeepFirstChild(aRoot);
+ NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
+ mLast = aRoot;
+ }
+
+ mClosestCommonInclusiveAncestor = aRoot;
+ mCurNode = mFirst;
+ return NS_OK;
+}
+
+nsresult ContentIteratorBase::Init(AbstractRange* aRange) {
+ if (NS_WARN_IF(!aRange)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(!aRange->IsPositioned())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return InitInternal(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw());
+}
+
+nsresult ContentIteratorBase::Init(nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset) {
+ if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStartContainer, aStartOffset,
+ aEndContainer, aEndOffset))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return InitInternal(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+}
+
+nsresult ContentIteratorBase::Init(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd) {
+ if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStart, aEnd))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return InitInternal(aStart, aEnd);
+}
+
+class MOZ_STACK_CLASS ContentIteratorBase::Initializer final {
+ public:
+ Initializer(ContentIteratorBase& aIterator, const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd)
+ : mIterator{aIterator},
+ mStart{aStart},
+ mEnd{aEnd},
+ mStartIsCharacterData{mStart.Container()->IsCharacterData()} {
+ MOZ_ASSERT(mStart.IsSetAndValid());
+ MOZ_ASSERT(mEnd.IsSetAndValid());
+ }
+
+ nsresult Run();
+
+ private:
+ /**
+ * @return may be nullptr.
+ */
+ nsINode* DetermineFirstNode() const;
+
+ /**
+ * @return may be nullptr.
+ */
+ [[nodiscard]] Result<nsINode*, nsresult> DetermineLastNode() const;
+
+ bool IsCollapsedNonCharacterRange() const;
+ bool IsSingleNodeCharacterRange() const;
+
+ ContentIteratorBase& mIterator;
+ const RawRangeBoundary& mStart;
+ const RawRangeBoundary& mEnd;
+ const bool mStartIsCharacterData;
+};
+
+nsresult ContentIteratorBase::InitInternal(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd) {
+ Initializer initializer{*this, aStart, aEnd};
+ return initializer.Run();
+}
+
+bool ContentIteratorBase::Initializer::IsCollapsedNonCharacterRange() const {
+ return !mStartIsCharacterData && mStart == mEnd;
+}
+
+bool ContentIteratorBase::Initializer::IsSingleNodeCharacterRange() const {
+ return mStartIsCharacterData && mStart.Container() == mEnd.Container();
+}
+
+nsresult ContentIteratorBase::Initializer::Run() {
+ // get common content parent
+ mIterator.mClosestCommonInclusiveAncestor =
+ nsContentUtils::GetClosestCommonInclusiveAncestor(mStart.Container(),
+ mEnd.Container());
+ if (NS_WARN_IF(!mIterator.mClosestCommonInclusiveAncestor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Check to see if we have a collapsed range, if so, there is nothing to
+ // iterate over.
+ //
+ // XXX: CharacterDataNodes (text nodes) are currently an exception, since
+ // we always want to be able to iterate text nodes at the end points
+ // of a range.
+
+ if (IsCollapsedNonCharacterRange()) {
+ mIterator.SetEmpty();
+ return NS_OK;
+ }
+
+ if (IsSingleNodeCharacterRange()) {
+ mIterator.mFirst = mStart.Container()->AsContent();
+ mIterator.mLast = mIterator.mFirst;
+ mIterator.mCurNode = mIterator.mFirst;
+
+ return NS_OK;
+ }
+
+ mIterator.mFirst = DetermineFirstNode();
+
+ if (Result<nsINode*, nsresult> lastNode = DetermineLastNode();
+ NS_WARN_IF(lastNode.isErr())) {
+ return lastNode.unwrapErr();
+ } else {
+ mIterator.mLast = lastNode.unwrap();
+ }
+
+ // If either first or last is null, they both have to be null!
+ if (!mIterator.mFirst || !mIterator.mLast) {
+ mIterator.SetEmpty();
+ }
+
+ mIterator.mCurNode = mIterator.mFirst;
+
+ return NS_OK;
+}
+
+nsINode* ContentIteratorBase::Initializer::DetermineFirstNode() const {
+ nsIContent* cChild = nullptr;
+
+ // Try to get the child at our starting point. This might return null if
+ // mStart is immediately after the last node in mStart.Container().
+ if (!mStartIsCharacterData) {
+ cChild = mStart.GetChildAtOffset();
+ }
+
+ if (!cChild) {
+ // No children (possibly a <br> or text node), or index is after last child.
+
+ if (mIterator.mOrder == Order::Pre) {
+ // XXX: In the future, if start offset is after the last
+ // character in the cdata node, should we set mFirst to
+ // the next sibling?
+
+ // Normally we would skip the start node because the start node is outside
+ // of the range in pre mode. However, if aStartOffset == 0, and the node
+ // is a non-container node (e.g. <br>), we don't skip the node in this
+ // case in order to address bug 1215798.
+ bool startIsContainer = true;
+ if (mStart.Container()->IsHTMLElement()) {
+ nsAtom* name = mStart.Container()->NodeInfo()->NameAtom();
+ startIsContainer =
+ nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
+ }
+ if (!mStartIsCharacterData &&
+ (startIsContainer || !mStart.IsStartOfContainer())) {
+ nsINode* const result =
+ ContentIteratorBase::GetNextSibling(mStart.Container());
+ NS_WARNING_ASSERTION(result, "GetNextSibling returned null");
+
+ // Does mFirst node really intersect the range? The range could be
+ // 'degenerate', i.e., not collapsed but still contain no content.
+ if (result &&
+ NS_WARN_IF(!NodeIsInTraversalRange(
+ result, mIterator.mOrder == Order::Pre, mStart, mEnd))) {
+ return nullptr;
+ }
+
+ return result;
+ }
+ return mStart.Container()->AsContent();
+ }
+
+ // post-order
+ if (NS_WARN_IF(!mStart.Container()->IsContent())) {
+ // What else can we do?
+ return nullptr;
+ }
+ return mStart.Container()->AsContent();
+ }
+
+ if (mIterator.mOrder == Order::Pre) {
+ return cChild;
+ }
+
+ // post-order
+ nsINode* const result = ContentIteratorBase::GetDeepFirstChild(cChild);
+ NS_WARNING_ASSERTION(result, "GetDeepFirstChild returned null");
+
+ // Does mFirst node really intersect the range? The range could be
+ // 'degenerate', i.e., not collapsed but still contain no content.
+ if (result && !NodeIsInTraversalRange(result, mIterator.mOrder == Order::Pre,
+ mStart, mEnd)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+Result<nsINode*, nsresult> ContentIteratorBase::Initializer::DetermineLastNode()
+ const {
+ const bool endIsCharacterData = mEnd.Container()->IsCharacterData();
+
+ if (endIsCharacterData || !mEnd.Container()->HasChildren() ||
+ mEnd.IsStartOfContainer()) {
+ if (mIterator.mOrder == Order::Pre) {
+ if (NS_WARN_IF(!mEnd.Container()->IsContent())) {
+ // Not much else to do here...
+ return nullptr;
+ }
+
+ // If the end node is a non-container element and the end offset is 0,
+ // the last element should be the previous node (i.e., shouldn't
+ // include the end node in the range).
+ bool endIsContainer = true;
+ if (mEnd.Container()->IsHTMLElement()) {
+ nsAtom* name = mEnd.Container()->NodeInfo()->NameAtom();
+ endIsContainer =
+ nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
+ }
+ if (!endIsCharacterData && !endIsContainer && mEnd.IsStartOfContainer()) {
+ nsINode* const result = mIterator.PrevNode(mEnd.Container());
+ NS_WARNING_ASSERTION(result, "PrevNode returned null");
+ if (result && result != mIterator.mFirst &&
+ NS_WARN_IF(!NodeIsInTraversalRange(
+ result, mIterator.mOrder == Order::Pre,
+ RawRangeBoundary(mIterator.mFirst, 0u), mEnd))) {
+ return nullptr;
+ }
+
+ return result;
+ }
+
+ return mEnd.Container()->AsContent();
+ }
+
+ // post-order
+ //
+ // XXX: In the future, if end offset is before the first character in the
+ // cdata node, should we set mLast to the prev sibling?
+
+ if (!endIsCharacterData) {
+ nsINode* const result =
+ ContentIteratorBase::GetPrevSibling(mEnd.Container());
+ NS_WARNING_ASSERTION(result, "GetPrevSibling returned null");
+
+ if (!NodeIsInTraversalRange(result, mIterator.mOrder == Order::Pre,
+ mStart, mEnd)) {
+ return nullptr;
+ }
+ return result;
+ }
+ return mEnd.Container()->AsContent();
+ }
+
+ nsIContent* cChild = mEnd.Ref();
+
+ if (NS_WARN_IF(!cChild)) {
+ // No child at offset!
+ MOZ_ASSERT_UNREACHABLE("ContentIterator::ContentIterator");
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ if (mIterator.mOrder == Order::Pre) {
+ nsINode* const result = ContentIteratorBase::GetDeepLastChild(cChild);
+ NS_WARNING_ASSERTION(result, "GetDeepLastChild returned null");
+
+ if (NS_WARN_IF(!NodeIsInTraversalRange(
+ result, mIterator.mOrder == Order::Pre, mStart, mEnd))) {
+ return nullptr;
+ }
+
+ return result;
+ }
+
+ // post-order
+ return cChild;
+}
+
+void ContentIteratorBase::SetEmpty() {
+ mCurNode = nullptr;
+ mFirst = nullptr;
+ mLast = nullptr;
+ mClosestCommonInclusiveAncestor = nullptr;
+}
+
+// static
+nsINode* ContentIteratorBase::GetDeepFirstChild(nsINode* aRoot) {
+ if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
+ return aRoot;
+ }
+
+ return ContentIteratorBase::GetDeepFirstChild(aRoot->GetFirstChild());
+}
+
+// static
+nsIContent* ContentIteratorBase::GetDeepFirstChild(nsIContent* aRoot) {
+ if (NS_WARN_IF(!aRoot)) {
+ return nullptr;
+ }
+
+ nsIContent* node = aRoot;
+ nsIContent* child = node->GetFirstChild();
+
+ while (child) {
+ node = child;
+ child = node->GetFirstChild();
+ }
+
+ return node;
+}
+
+// static
+nsINode* ContentIteratorBase::GetDeepLastChild(nsINode* aRoot) {
+ if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
+ return aRoot;
+ }
+
+ return ContentIteratorBase::GetDeepLastChild(aRoot->GetLastChild());
+}
+
+// static
+nsIContent* ContentIteratorBase::GetDeepLastChild(nsIContent* aRoot) {
+ if (NS_WARN_IF(!aRoot)) {
+ return nullptr;
+ }
+
+ nsIContent* node = aRoot;
+ while (node->HasChildren()) {
+ nsIContent* child = node->GetLastChild();
+ node = child;
+ }
+ return node;
+}
+
+// Get the next sibling, or parent's next sibling, or grandpa's next sibling...
+// static
+nsIContent* ContentIteratorBase::GetNextSibling(nsINode* aNode) {
+ if (NS_WARN_IF(!aNode)) {
+ return nullptr;
+ }
+
+ if (aNode->GetNextSibling()) {
+ return aNode->GetNextSibling();
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ return nullptr;
+ }
+
+ // XXX This is a hack to preserve previous behaviour: This should be fixed
+ // in bug 1404916. If we were positioned on anonymous content, move to
+ // the first child of our parent.
+ if (parent->GetLastChild() && parent->GetLastChild() != aNode) {
+ return parent->GetFirstChild();
+ }
+
+ return ContentIteratorBase::GetNextSibling(parent);
+}
+
+// Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
+// static
+nsIContent* ContentIteratorBase::GetPrevSibling(nsINode* aNode) {
+ if (NS_WARN_IF(!aNode)) {
+ return nullptr;
+ }
+
+ if (aNode->GetPreviousSibling()) {
+ return aNode->GetPreviousSibling();
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ return nullptr;
+ }
+
+ // XXX This is a hack to preserve previous behaviour: This should be fixed
+ // in bug 1404916. If we were positioned on anonymous content, move to
+ // the last child of our parent.
+ if (parent->GetFirstChild() && parent->GetFirstChild() != aNode) {
+ return parent->GetLastChild();
+ }
+
+ return ContentIteratorBase::GetPrevSibling(parent);
+}
+
+nsINode* ContentIteratorBase::NextNode(nsINode* aNode) {
+ nsINode* node = aNode;
+
+ // if we are a Pre-order iterator, use pre-order
+ if (mOrder == Order::Pre) {
+ // if it has children then next node is first child
+ if (node->HasChildren()) {
+ nsIContent* firstChild = node->GetFirstChild();
+ MOZ_ASSERT(firstChild);
+
+ return firstChild;
+ }
+
+ // else next sibling is next
+ return ContentIteratorBase::GetNextSibling(node);
+ }
+
+ // post-order
+ nsINode* parent = node->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ MOZ_ASSERT(parent, "The node is the root node but not the last node");
+ mCurNode = nullptr;
+ return node;
+ }
+
+ if (nsIContent* sibling = node->GetNextSibling()) {
+ // next node is sibling's "deep left" child
+ return ContentIteratorBase::GetDeepFirstChild(sibling);
+ }
+
+ return parent;
+}
+
+nsINode* ContentIteratorBase::PrevNode(nsINode* aNode) {
+ nsINode* node = aNode;
+
+ // if we are a Pre-order iterator, use pre-order
+ if (mOrder == Order::Pre) {
+ nsINode* parent = node->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ MOZ_ASSERT(parent, "The node is the root node but not the first node");
+ mCurNode = nullptr;
+ return aNode;
+ }
+
+ nsIContent* sibling = node->GetPreviousSibling();
+ if (sibling) {
+ return ContentIteratorBase::GetDeepLastChild(sibling);
+ }
+
+ return parent;
+ }
+
+ // post-order
+ if (node->HasChildren()) {
+ return node->GetLastChild();
+ }
+
+ // else prev sibling is previous
+ return ContentIteratorBase::GetPrevSibling(node);
+}
+
+/******************************************************
+ * ContentIteratorBase routines
+ ******************************************************/
+
+void ContentIteratorBase::First() {
+ if (!mFirst) {
+ MOZ_ASSERT(IsDone());
+ mCurNode = nullptr;
+ return;
+ }
+
+ mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
+}
+
+void ContentIteratorBase::Last() {
+ // Note that mLast can be nullptr if SetEmpty() is called in Init()
+ // since at that time, Init() returns NS_OK.
+ if (!mLast) {
+ MOZ_ASSERT(IsDone());
+ mCurNode = nullptr;
+ return;
+ }
+
+ mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
+}
+
+void ContentIteratorBase::Next() {
+ if (IsDone()) {
+ return;
+ }
+
+ if (mCurNode == mLast) {
+ mCurNode = nullptr;
+ return;
+ }
+
+ mCurNode = NextNode(mCurNode);
+}
+
+void ContentIteratorBase::Prev() {
+ if (IsDone()) {
+ return;
+ }
+
+ if (mCurNode == mFirst) {
+ mCurNode = nullptr;
+ return;
+ }
+
+ mCurNode = PrevNode(mCurNode);
+}
+
+// Keeping arrays of indexes for the stack of nodes makes PositionAt
+// interesting...
+nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) {
+ if (NS_WARN_IF(!aCurNode)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // take an early out if this doesn't actually change the position
+ if (mCurNode == aCurNode) {
+ return NS_OK;
+ }
+ mCurNode = aCurNode;
+
+ // Check to see if the node falls within the traversal range.
+
+ RawRangeBoundary first(mFirst, 0u);
+ RawRangeBoundary last(mLast, 0u);
+
+ if (mFirst && mLast) {
+ if (mOrder == Order::Pre) {
+ // In pre we want to record the point immediately before mFirst, which is
+ // the point immediately after mFirst's previous sibling.
+ first = {mFirst->GetParentNode(), mFirst->GetPreviousSibling()};
+
+ // If mLast has no children, then we want to make sure to include it.
+ if (!mLast->HasChildren()) {
+ last = {mLast->GetParentNode(), mLast->AsContent()};
+ }
+ } else {
+ // If the first node has any children, we want to be immediately after the
+ // last. Otherwise we want to be immediately before mFirst.
+ if (mFirst->HasChildren()) {
+ first = {mFirst, mFirst->GetLastChild()};
+ } else {
+ first = {mFirst->GetParentNode(), mFirst->GetPreviousSibling()};
+ }
+
+ // Set the last point immediately after the final node.
+ last = {mLast->GetParentNode(), mLast->AsContent()};
+ }
+ }
+
+ NS_WARNING_ASSERTION(first.IsSetAndValid(), "first is not valid");
+ NS_WARNING_ASSERTION(last.IsSetAndValid(), "last is not valid");
+
+ // The end positions are always in the range even if it has no parent. We
+ // need to allow that or 'iter->Init(root)' would assert in Last() or First()
+ // for example, bug 327694.
+ if (mFirst != mCurNode && mLast != mCurNode &&
+ (NS_WARN_IF(!first.IsSet()) || NS_WARN_IF(!last.IsSet()) ||
+ NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mOrder == Order::Pre, first,
+ last)))) {
+ mCurNode = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/******************************************************
+ * ContentSubtreeIterator init routines
+ ******************************************************/
+
+nsresult ContentSubtreeIterator::Init(nsINode* aRoot) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult ContentSubtreeIterator::Init(AbstractRange* aRange) {
+ MOZ_ASSERT(aRange);
+
+ if (NS_WARN_IF(!aRange->IsPositioned())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mRange = aRange;
+
+ return InitWithRange();
+}
+
+nsresult ContentSubtreeIterator::Init(nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset) {
+ return Init(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+}
+
+nsresult ContentSubtreeIterator::Init(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary) {
+ RefPtr<nsRange> range =
+ nsRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors());
+ if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(range->StartRef() != aStartBoundary) ||
+ NS_WARN_IF(range->EndRef() != aEndBoundary)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mRange = std::move(range);
+
+ return InitWithRange();
+}
+
+void ContentSubtreeIterator::CacheInclusiveAncestorsOfEndContainer() {
+ mInclusiveAncestorsOfEndContainer.Clear();
+ nsINode* const endContainer = mRange->GetEndContainer();
+ nsIContent* endNode =
+ endContainer->IsContent() ? endContainer->AsContent() : nullptr;
+ while (endNode) {
+ mInclusiveAncestorsOfEndContainer.AppendElement(endNode);
+ endNode = endNode->GetParent();
+ }
+}
+
+nsIContent* ContentSubtreeIterator::DetermineCandidateForFirstContent() const {
+ nsINode* startContainer = mRange->GetStartContainer();
+ nsIContent* firstCandidate = nullptr;
+ // find first node in range
+ nsINode* node = nullptr;
+ if (!startContainer->GetChildCount()) {
+ // no children, start at the node itself
+ node = startContainer;
+ } else {
+ nsIContent* child = mRange->GetChildAtStartOffset();
+ MOZ_ASSERT(child ==
+ startContainer->GetChildAt_Deprecated(mRange->StartOffset()));
+ if (!child) {
+ // offset after last child
+ node = startContainer;
+ } else {
+ firstCandidate = child;
+ }
+ }
+
+ if (!firstCandidate) {
+ // then firstCandidate is next node after node
+ firstCandidate = ContentIteratorBase::GetNextSibling(node);
+ }
+
+ if (firstCandidate) {
+ firstCandidate = ContentIteratorBase::GetDeepFirstChild(firstCandidate);
+ }
+
+ return firstCandidate;
+}
+
+nsIContent* ContentSubtreeIterator::DetermineFirstContent() const {
+ nsIContent* firstCandidate = DetermineCandidateForFirstContent();
+ if (!firstCandidate) {
+ return nullptr;
+ }
+
+ // confirm that this first possible contained node is indeed contained. Else
+ // we have a range that does not fully contain any node.
+ const Maybe<bool> isNodeContainedInRange =
+ RangeUtils::IsNodeContainedInRange(*firstCandidate, mRange);
+ MOZ_ALWAYS_TRUE(isNodeContainedInRange);
+ if (!isNodeContainedInRange.value()) {
+ return nullptr;
+ }
+
+ // cool, we have the first node in the range. Now we walk up its ancestors
+ // to find the most senior that is still in the range. That's the real first
+ // node.
+ return GetTopAncestorInRange(firstCandidate);
+}
+
+nsIContent* ContentSubtreeIterator::DetermineCandidateForLastContent() const {
+ nsIContent* lastCandidate{nullptr};
+ nsINode* endContainer = mRange->GetEndContainer();
+ // now to find the last node
+ int32_t offset = mRange->EndOffset();
+ int32_t numChildren = endContainer->GetChildCount();
+
+ nsINode* node = nullptr;
+ if (offset > numChildren) {
+ // Can happen for text nodes
+ offset = numChildren;
+ }
+ if (!offset || !numChildren) {
+ node = endContainer;
+ } else {
+ lastCandidate = mRange->EndRef().Ref();
+ MOZ_ASSERT(lastCandidate == endContainer->GetChildAt_Deprecated(--offset));
+ NS_ASSERTION(lastCandidate,
+ "tree traversal trouble in ContentSubtreeIterator::Init");
+ }
+
+ if (!lastCandidate) {
+ // then lastCandidate is prev node before node
+ lastCandidate = ContentIteratorBase::GetPrevSibling(node);
+ }
+
+ if (lastCandidate) {
+ lastCandidate = ContentIteratorBase::GetDeepLastChild(lastCandidate);
+ }
+
+ return lastCandidate;
+}
+
+nsresult ContentSubtreeIterator::InitWithRange() {
+ MOZ_ASSERT(mRange);
+ MOZ_ASSERT(mRange->IsPositioned());
+
+ // get the start node and offset, convert to nsINode
+ mClosestCommonInclusiveAncestor = mRange->GetClosestCommonInclusiveAncestor();
+ nsINode* startContainer = mRange->GetStartContainer();
+ const int32_t startOffset = mRange->StartOffset();
+ nsINode* endContainer = mRange->GetEndContainer();
+ const int32_t endOffset = mRange->EndOffset();
+ MOZ_ASSERT(mClosestCommonInclusiveAncestor && startContainer && endContainer);
+ // Bug 767169
+ MOZ_ASSERT(uint32_t(startOffset) <= startContainer->Length() &&
+ uint32_t(endOffset) <= endContainer->Length());
+
+ // short circuit when start node == end node
+ if (startContainer == endContainer) {
+ nsINode* child = startContainer->GetFirstChild();
+
+ if (!child || startOffset == endOffset) {
+ // Text node, empty container, or collapsed
+ SetEmpty();
+ return NS_OK;
+ }
+ }
+
+ CacheInclusiveAncestorsOfEndContainer();
+
+ mFirst = DetermineFirstContent();
+ if (!mFirst) {
+ SetEmpty();
+ return NS_OK;
+ }
+
+ mLast = DetermineLastContent();
+ if (!mLast) {
+ SetEmpty();
+ return NS_OK;
+ }
+
+ mCurNode = mFirst;
+
+ return NS_OK;
+}
+
+nsIContent* ContentSubtreeIterator::DetermineLastContent() const {
+ nsIContent* lastCandidate = DetermineCandidateForLastContent();
+ if (!lastCandidate) {
+ return nullptr;
+ }
+
+ // confirm that this last possible contained node is indeed contained. Else
+ // we have a range that does not fully contain any node.
+
+ const Maybe<bool> isNodeContainedInRange =
+ RangeUtils::IsNodeContainedInRange(*lastCandidate, mRange);
+ MOZ_ALWAYS_TRUE(isNodeContainedInRange);
+ if (!isNodeContainedInRange.value()) {
+ return nullptr;
+ }
+
+ // cool, we have the last node in the range. Now we walk up its ancestors to
+ // find the most senior that is still in the range. That's the real first
+ // node.
+ return GetTopAncestorInRange(lastCandidate);
+}
+
+/****************************************************************
+ * ContentSubtreeIterator overrides of ContentIterator routines
+ ****************************************************************/
+
+// we can't call PositionAt in a subtree iterator...
+void ContentSubtreeIterator::First() { mCurNode = mFirst; }
+
+// we can't call PositionAt in a subtree iterator...
+void ContentSubtreeIterator::Last() { mCurNode = mLast; }
+
+void ContentSubtreeIterator::Next() {
+ if (IsDone()) {
+ return;
+ }
+
+ if (mCurNode == mLast) {
+ mCurNode = nullptr;
+ return;
+ }
+
+ nsINode* nextNode = ContentIteratorBase::GetNextSibling(mCurNode);
+ NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!");
+
+ int32_t i = mInclusiveAncestorsOfEndContainer.IndexOf(nextNode);
+ while (i != -1) {
+ // as long as we are finding ancestors of the endpoint of the range,
+ // dive down into their children
+ nextNode = nextNode->GetFirstChild();
+ NS_ASSERTION(nextNode, "Iterator error, expected a child node!");
+
+ // should be impossible to get a null pointer. If we went all the way
+ // down the child chain to the bottom without finding an interior node,
+ // then the previous node should have been the last, which was
+ // was tested at top of routine.
+ i = mInclusiveAncestorsOfEndContainer.IndexOf(nextNode);
+ }
+
+ mCurNode = nextNode;
+}
+
+void ContentSubtreeIterator::Prev() {
+ // Prev should be optimized to use the mStartNodes, just as Next
+ // uses mInclusiveAncestorsOfEndContainer.
+ if (IsDone()) {
+ return;
+ }
+
+ if (mCurNode == mFirst) {
+ mCurNode = nullptr;
+ return;
+ }
+
+ // If any of these function calls return null, so will all succeeding ones,
+ // so mCurNode will wind up set to null.
+ nsINode* prevNode = ContentIteratorBase::GetDeepFirstChild(mCurNode);
+
+ prevNode = PrevNode(prevNode);
+
+ prevNode = ContentIteratorBase::GetDeepLastChild(prevNode);
+
+ mCurNode = GetTopAncestorInRange(prevNode);
+}
+
+nsresult ContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
+ NS_ERROR("Not implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+ * ContentSubtreeIterator helper routines
+ ****************************************************************/
+
+nsIContent* ContentSubtreeIterator::GetTopAncestorInRange(
+ nsINode* aNode) const {
+ if (!aNode || !aNode->GetParentNode()) {
+ return nullptr;
+ }
+
+ // aNode has a parent, so it must be content.
+ nsIContent* content = aNode->AsContent();
+
+ // sanity check: aNode is itself in the range
+ Maybe<bool> isNodeContainedInRange =
+ RangeUtils::IsNodeContainedInRange(*aNode, mRange);
+ NS_ASSERTION(isNodeContainedInRange && isNodeContainedInRange.value(),
+ "aNode isn't in mRange, or something else weird happened");
+ if (!isNodeContainedInRange || !isNodeContainedInRange.value()) {
+ return nullptr;
+ }
+
+ while (content) {
+ nsIContent* parent = content->GetParent();
+ // content always has a parent. If its parent is the root, however --
+ // i.e., either it's not content, or it is content but its own parent is
+ // null -- then we're finished, since we don't go up to the root.
+ //
+ // We have to special-case this because CompareNodeToRange treats the root
+ // node differently -- see bug 765205.
+ if (!parent || !parent->GetParentNode()) {
+ return content;
+ }
+
+ isNodeContainedInRange =
+ RangeUtils::IsNodeContainedInRange(*parent, mRange);
+ MOZ_ALWAYS_TRUE(isNodeContainedInRange);
+ if (!isNodeContainedInRange.value()) {
+ return content;
+ }
+
+ content = parent;
+ }
+
+ MOZ_CRASH("This should only be possible if aNode was null");
+}
+
+} // namespace mozilla
diff --git a/dom/base/ContentIterator.h b/dom/base/ContentIterator.h
new file mode 100644
index 0000000000..4035b66593
--- /dev/null
+++ b/dom/base/ContentIterator.h
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ContentIterator_h
+#define mozilla_ContentIterator_h
+
+#include "mozilla/RangeBoundary.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRange.h"
+#include "nsTArray.h"
+
+class nsIContent;
+class nsINode;
+
+namespace mozilla {
+
+/**
+ * ContentIteratorBase is a base class of PostContentIterator,
+ * PreContentIterator and ContentSubtreeIterator. Making each concrete
+ * classes "final", compiler can avoid virtual calls if they are treated
+ * by the users directly.
+ */
+class ContentIteratorBase {
+ public:
+ ContentIteratorBase() = delete;
+ ContentIteratorBase(const ContentIteratorBase&) = delete;
+ ContentIteratorBase& operator=(const ContentIteratorBase&) = delete;
+ virtual ~ContentIteratorBase();
+
+ /**
+ * Allows to iterate over the inclusive descendants
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant) of
+ * aRoot.
+ */
+ virtual nsresult Init(nsINode* aRoot);
+
+ virtual nsresult Init(dom::AbstractRange* aRange);
+ virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset);
+ virtual nsresult Init(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd);
+
+ virtual void First();
+ virtual void Last();
+ virtual void Next();
+ virtual void Prev();
+
+ nsINode* GetCurrentNode() const { return mCurNode; }
+
+ bool IsDone() const { return !mCurNode; }
+
+ virtual nsresult PositionAt(nsINode* aCurNode);
+
+ protected:
+ enum class Order {
+ Pre, /*!< <https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_(NLR)>.
+ */
+ Post /*!< <https://en.wikipedia.org/wiki/Tree_traversal#Post-order_(LRN)>.
+ */
+ };
+
+ explicit ContentIteratorBase(Order aOrder);
+
+ class Initializer;
+
+ /**
+ * Callers must guarantee that:
+ * - Neither aStartContainer nor aEndContainer is nullptr.
+ * - aStartOffset and aEndOffset are valid for its container.
+ * - The start point and the end point are in document order.
+ */
+ nsresult InitInternal(const RawRangeBoundary& aStart,
+ const RawRangeBoundary& aEnd);
+
+ // Recursively get the deepest first/last child of aRoot. This will return
+ // aRoot itself if it has no children.
+ static nsINode* GetDeepFirstChild(nsINode* aRoot);
+ static nsIContent* GetDeepFirstChild(nsIContent* aRoot);
+ static nsINode* GetDeepLastChild(nsINode* aRoot);
+ static nsIContent* GetDeepLastChild(nsIContent* aRoot);
+
+ // Get the next/previous sibling of aNode, or its parent's, or grandparent's,
+ // etc. Returns null if aNode and all its ancestors have no next/previous
+ // sibling.
+ static nsIContent* GetNextSibling(nsINode* aNode);
+ static nsIContent* GetPrevSibling(nsINode* aNode);
+
+ nsINode* NextNode(nsINode* aNode);
+ nsINode* PrevNode(nsINode* aNode);
+
+ void SetEmpty();
+
+ nsCOMPtr<nsINode> mCurNode;
+ nsCOMPtr<nsINode> mFirst;
+ nsCOMPtr<nsINode> mLast;
+ // See <https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor>.
+ nsCOMPtr<nsINode> mClosestCommonInclusiveAncestor;
+
+ const Order mOrder;
+
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+ ContentIteratorBase&, const char*,
+ uint32_t);
+ friend void ImplCycleCollectionUnlink(ContentIteratorBase&);
+};
+
+/**
+ * A simple iterator class for traversing the content in "close tag" order.
+ */
+class PostContentIterator final : public ContentIteratorBase {
+ public:
+ PostContentIterator() : ContentIteratorBase(Order::Post) {}
+ PostContentIterator(const PostContentIterator&) = delete;
+ PostContentIterator& operator=(const PostContentIterator&) = delete;
+ virtual ~PostContentIterator() = default;
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+ PostContentIterator&, const char*,
+ uint32_t);
+ friend void ImplCycleCollectionUnlink(PostContentIterator&);
+};
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, PostContentIterator& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(
+ aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(PostContentIterator& aField) {
+ ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
+}
+
+/**
+ * A simple iterator class for traversing the content in "start tag" order.
+ */
+class PreContentIterator final : public ContentIteratorBase {
+ public:
+ PreContentIterator() : ContentIteratorBase(Order::Pre) {}
+ PreContentIterator(const PreContentIterator&) = delete;
+ PreContentIterator& operator=(const PreContentIterator&) = delete;
+ virtual ~PreContentIterator() = default;
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+ PreContentIterator&, const char*,
+ uint32_t);
+ friend void ImplCycleCollectionUnlink(PreContentIterator&);
+};
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, PreContentIterator& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(
+ aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(PreContentIterator& aField) {
+ ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
+}
+
+/**
+ * A simple iterator class for traversing the content in "top subtree" order.
+ */
+class ContentSubtreeIterator final : public ContentIteratorBase {
+ public:
+ ContentSubtreeIterator() : ContentIteratorBase(Order::Pre) {}
+ ContentSubtreeIterator(const ContentSubtreeIterator&) = delete;
+ ContentSubtreeIterator& operator=(const ContentSubtreeIterator&) = delete;
+ virtual ~ContentSubtreeIterator() = default;
+
+ /**
+ * Not supported.
+ */
+ virtual nsresult Init(nsINode* aRoot) override;
+
+ virtual nsresult Init(dom::AbstractRange* aRange) override;
+ virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) override;
+ virtual nsresult Init(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary) override;
+
+ void Next() override;
+ void Prev() override;
+ // Must override these because we don't do PositionAt
+ void First() override;
+ // Must override these because we don't do PositionAt
+ void Last() override;
+
+ nsresult PositionAt(nsINode* aCurNode) override;
+
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+ ContentSubtreeIterator&, const char*,
+ uint32_t);
+ friend void ImplCycleCollectionUnlink(ContentSubtreeIterator&);
+
+ private:
+ /**
+ * See <https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor>.
+ */
+ void CacheInclusiveAncestorsOfEndContainer();
+
+ /**
+ * @return may be nullptr.
+ */
+ nsIContent* DetermineCandidateForFirstContent() const;
+
+ /**
+ * @return may be nullptr.
+ */
+ nsIContent* DetermineCandidateForLastContent() const;
+
+ /**
+ * @return may be nullptr.
+ */
+ nsIContent* DetermineFirstContent() const;
+
+ /**
+ * @return may be nullptr.
+ */
+ nsIContent* DetermineLastContent() const;
+
+ /**
+ * Callers must guarantee that mRange isn't nullptr and is positioned.
+ */
+ nsresult InitWithRange();
+
+ // Returns the highest inclusive ancestor of aNode that's in the range
+ // (possibly aNode itself). Returns null if aNode is null, or is not itself
+ // in the range. A node is in the range if (node, 0) comes strictly after
+ // the range endpoint, and (node, node.length) comes strictly before it, so
+ // the range's start and end nodes will never be considered "in" it.
+ nsIContent* GetTopAncestorInRange(nsINode* aNode) const;
+
+ RefPtr<dom::AbstractRange> mRange;
+
+ // See <https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor>.
+ AutoTArray<nsIContent*, 8> mInclusiveAncestorsOfEndContainer;
+};
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ ContentSubtreeIterator& aField, const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mRange, aName, aFlags);
+ ImplCycleCollectionTraverse(
+ aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(ContentSubtreeIterator& aField) {
+ ImplCycleCollectionUnlink(aField.mRange);
+ ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
+}
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_ContentIterator_h
diff --git a/dom/base/ContentProcessMessageManager.cpp b/dom/base/ContentProcessMessageManager.cpp
new file mode 100644
index 0000000000..7fe9f4c2c7
--- /dev/null
+++ b/dom/base/ContentProcessMessageManager.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentProcessMessageManager.h"
+
+#include "nsContentCID.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ipc/SharedMap.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool ContentProcessMessageManager::sWasCreated = false;
+
+ContentProcessMessageManager::ContentProcessMessageManager(
+ nsFrameMessageManager* aMessageManager)
+ : MessageManagerGlobal(aMessageManager), mInitialized(false) {
+ mozilla::HoldJSObjects(this);
+}
+
+ContentProcessMessageManager::~ContentProcessMessageManager() {
+ mozilla::DropJSObjects(this);
+}
+
+ContentProcessMessageManager* ContentProcessMessageManager::Get() {
+ nsCOMPtr<nsIMessageSender> service =
+ do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
+ if (!service) {
+ return nullptr;
+ }
+ sWasCreated = true;
+ return static_cast<ContentProcessMessageManager*>(service.get());
+}
+
+already_AddRefed<mozilla::dom::ipc::SharedMap>
+ContentProcessMessageManager::GetSharedData() {
+ if (ContentChild* child = ContentChild::GetSingleton()) {
+ return do_AddRef(child->SharedData());
+ }
+ auto* ppmm = nsFrameMessageManager::sParentProcessManager;
+ return do_AddRef(ppmm->SharedData()->GetReadOnly());
+}
+
+bool ContentProcessMessageManager::WasCreated() { return sWasCreated; }
+
+void ContentProcessMessageManager::MarkForCC() {
+ MarkScopesForCC();
+ MessageManagerGlobal::MarkForCC();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ContentProcessMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ContentProcessMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ContentProcessMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ tmp->nsMessageManagerScriptExecutor::Trace(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ContentProcessMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ tmp->nsMessageManagerScriptExecutor::Unlink();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ContentProcessMessageManager)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentProcessMessageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ContentProcessMessageManager)
+
+bool ContentProcessMessageManager::Init() {
+ if (mInitialized) {
+ return true;
+ }
+ mInitialized = true;
+
+ return nsMessageManagerScriptExecutor::Init();
+}
+
+JSObject* ContentProcessMessageManager::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ContentProcessMessageManager_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+JSObject* ContentProcessMessageManager::GetOrCreateWrapper() {
+ JS::Rooted<JS::Value> val(RootingCx());
+ {
+ // Scope to run ~AutoJSAPI before working with a raw JSObject*.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
+ return nullptr;
+ }
+ }
+ MOZ_ASSERT(val.isObject());
+ return &val.toObject();
+}
+
+void ContentProcessMessageManager::LoadScript(const nsAString& aURL) {
+ Init();
+ JS::Rooted<JSObject*> messageManager(mozilla::dom::RootingCx(),
+ GetOrCreateWrapper());
+ LoadScriptInternal(messageManager, aURL, true);
+}
+
+void ContentProcessMessageManager::SetInitialProcessData(
+ JS::Handle<JS::Value> aInitialData) {
+ mMessageManager->SetInitialProcessData(aInitialData);
+}
diff --git a/dom/base/ContentProcessMessageManager.h b/dom/base/ContentProcessMessageManager.h
new file mode 100644
index 0000000000..b7c54ba452
--- /dev/null
+++ b/dom/base/ContentProcessMessageManager.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ContentProcessMessageManager_h
+#define mozilla_dom_ContentProcessMessageManager_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/MessageManagerGlobal.h"
+#include "mozilla/dom/MessageManagerCallback.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptContext.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+#include "xpcpublic.h"
+
+class nsFrameMessageManager;
+
+namespace mozilla::dom {
+
+namespace ipc {
+class SharedMap;
+}
+
+/**
+ * This class implements a singleton process message manager for content
+ * processes. Each child process has exactly one instance of this class, which
+ * hosts the process's process scripts, and may exchange messages with its
+ * corresponding ParentProcessMessageManager on the parent side.
+ */
+
+class ContentProcessMessageManager : public nsIMessageSender,
+ public nsMessageManagerScriptExecutor,
+ public nsSupportsWeakReference,
+ public ipc::MessageManagerCallback,
+ public MessageManagerGlobal,
+ public nsWrapperCache {
+ public:
+ explicit ContentProcessMessageManager(nsFrameMessageManager* aMessageManager);
+
+ using ipc::MessageManagerCallback::GetProcessMessageManager;
+ using MessageManagerGlobal::GetProcessMessageManager;
+
+ bool Init();
+
+ static ContentProcessMessageManager* Get();
+ static bool WasCreated();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
+ ContentProcessMessageManager, nsIMessageSender)
+
+ void MarkForCC();
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ JSObject* GetOrCreateWrapper();
+
+ using MessageManagerGlobal::AddMessageListener;
+ using MessageManagerGlobal::AddWeakMessageListener;
+ using MessageManagerGlobal::RemoveMessageListener;
+ using MessageManagerGlobal::RemoveWeakMessageListener;
+
+ // ContentProcessMessageManager
+ void GetInitialProcessData(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aInitialProcessData,
+ ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
+ }
+
+ already_AddRefed<ipc::SharedMap> GetSharedData();
+
+ NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
+
+ nsIGlobalObject* GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+ }
+
+ virtual void LoadScript(const nsAString& aURL);
+
+ bool IsProcessScoped() const override { return true; }
+
+ void SetInitialProcessData(JS::Handle<JS::Value> aInitialData);
+
+ protected:
+ virtual ~ContentProcessMessageManager();
+
+ private:
+ bool mInitialized;
+
+ static bool sWasCreated;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ContentProcessMessageManager_h
diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp
new file mode 100644
index 0000000000..8e24b4567b
--- /dev/null
+++ b/dom/base/Crypto.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Crypto.h"
+#include "js/ScalarType.h"
+#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType
+#include "nsCOMPtr.h"
+#include "nsIRandomGenerator.h"
+#include "nsReadableUtils.h"
+
+#include "mozilla/dom/CryptoBinding.h"
+#include "mozilla/dom/SubtleCrypto.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Crypto)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Crypto)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Crypto)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Crypto, mParent, mSubtle)
+
+Crypto::Crypto(nsIGlobalObject* aParent) : mParent(aParent) {}
+
+Crypto::~Crypto() = default;
+
+/* virtual */
+JSObject* Crypto::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Crypto_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ JS::Rooted<JSObject*> view(aCx, aArray.Obj());
+
+ // Throw if the wrong type of ArrayBufferView is passed in
+ // (Part of the Web Crypto API spec)
+ switch (JS_GetArrayBufferViewType(view)) {
+ case js::Scalar::Int8:
+ case js::Scalar::Uint8:
+ case js::Scalar::Uint8Clamped:
+ case js::Scalar::Int16:
+ case js::Scalar::Uint16:
+ case js::Scalar::Int32:
+ case js::Scalar::Uint32:
+ case js::Scalar::BigInt64:
+ case js::Scalar::BigUint64:
+ break;
+ default:
+ aRv.Throw(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ aArray.ComputeState();
+ uint32_t dataLen = aArray.Length();
+ if (dataLen == 0) {
+ NS_WARNING("ArrayBufferView length is 0, cannot continue");
+ aRetval.set(view);
+ return;
+ } else if (dataLen > 65536) {
+ aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIRandomGenerator> randomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1");
+ if (!randomGenerator) {
+ aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ uint8_t* buf;
+ nsresult rv = randomGenerator->GenerateRandomBytes(dataLen, &buf);
+ if (NS_FAILED(rv) || !buf) {
+ aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ // Copy random bytes to ABV.
+ memcpy(aArray.Data(), buf, dataLen);
+ free(buf);
+
+ aRetval.set(view);
+}
+
+void Crypto::RandomUUID(nsACString& aRetVal) {
+ // NSID_LENGTH == 39 == 36 UUID chars + 2 curly braces + 1 NUL byte
+ static_assert(NSID_LENGTH == 39);
+
+ nsIDToCString uuidString(nsID::GenerateUUID());
+ MOZ_ASSERT(strlen(uuidString.get()) == NSID_LENGTH - 1);
+
+ // Omit the curly braces and NUL.
+ aRetVal = Substring(uuidString.get() + 1, NSID_LENGTH - 3);
+ MOZ_ASSERT(aRetVal.Length() == NSID_LENGTH - 3);
+}
+
+SubtleCrypto* Crypto::Subtle() {
+ if (!mSubtle) {
+ mSubtle = new SubtleCrypto(GetParentObject());
+ }
+ return mSubtle;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Crypto.h b/dom/base/Crypto.h
new file mode 100644
index 0000000000..75e3629b25
--- /dev/null
+++ b/dom/base/Crypto.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Crypto_h
+#define mozilla_dom_Crypto_h
+
+#include "mozilla/dom/SubtleCrypto.h"
+#include "nsIGlobalObject.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Crypto final : public nsISupports, public nsWrapperCache {
+ protected:
+ virtual ~Crypto();
+
+ public:
+ explicit Crypto(nsIGlobalObject* aParent);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Crypto)
+
+ void GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv);
+
+ void RandomUUID(nsACString& aRetVal);
+
+ SubtleCrypto* Subtle();
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mParent;
+ RefPtr<SubtleCrypto> mSubtle;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Crypto_h
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp
new file mode 100644
index 0000000000..d8b40b5bc5
--- /dev/null
+++ b/dom/base/CustomElementRegistry.cpp
@@ -0,0 +1,1587 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CustomElementRegistry.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/HTMLElement.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/ShadowIncludingTreeIterator.h"
+#include "mozilla/dom/XULElementBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/UseCounter.h"
+#include "nsContentUtils.h"
+#include "nsHTMLTags.h"
+#include "jsapi.h"
+#include "js/ForOfIterator.h" // JS::ForOfIterator
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetUCProperty
+#include "xpcprivate.h"
+#include "nsGlobalWindow.h"
+#include "nsNameSpaceManager.h"
+
+namespace mozilla::dom {
+
+//-----------------------------------------------------
+// CustomElementUpgradeReaction
+
+class CustomElementUpgradeReaction final : public CustomElementReaction {
+ public:
+ explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition)
+ : mDefinition(aDefinition) {
+ mIsUpgradeReaction = true;
+ }
+
+ virtual void Traverse(
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDefinition");
+ aCb.NoteNativeChild(
+ mDefinition, NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // We don't really own mDefinition.
+ return aMallocSizeOf(this);
+ }
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
+ CustomElementRegistry::Upgrade(aElement, mDefinition, aRv);
+ }
+
+ const RefPtr<CustomElementDefinition> mDefinition;
+};
+
+//-----------------------------------------------------
+// CustomElementCallbackReaction
+
+class CustomElementCallback {
+ public:
+ CustomElementCallback(Element* aThisObject, ElementCallbackType aCallbackType,
+ CallbackFunction* aCallback,
+ const LifecycleCallbackArgs& aArgs);
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+ void Call();
+
+ static UniquePtr<CustomElementCallback> Create(
+ ElementCallbackType aType, Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition);
+
+ private:
+ // The this value to use for invocation of the callback.
+ RefPtr<Element> mThisObject;
+ RefPtr<CallbackFunction> mCallback;
+ // The type of callback (eCreated, eAttached, etc.)
+ ElementCallbackType mType;
+ // Arguments to be passed to the callback,
+ LifecycleCallbackArgs mArgs;
+};
+
+class CustomElementCallbackReaction final : public CustomElementReaction {
+ public:
+ explicit CustomElementCallbackReaction(
+ UniquePtr<CustomElementCallback> aCustomElementCallback)
+ : mCustomElementCallback(std::move(aCustomElementCallback)) {}
+
+ virtual void Traverse(
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ mCustomElementCallback->Traverse(aCb);
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t n = aMallocSizeOf(this);
+
+ n += mCustomElementCallback->SizeOfIncludingThis(aMallocSizeOf);
+
+ return n;
+ }
+
+ private:
+ virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
+ mCustomElementCallback->Call();
+ }
+
+ UniquePtr<CustomElementCallback> mCustomElementCallback;
+};
+
+//-----------------------------------------------------
+// CustomElementCallback
+
+size_t LifecycleCallbackArgs::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += mOldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += mNewValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += mNamespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ return n;
+}
+
+/* static */
+UniquePtr<CustomElementCallback> CustomElementCallback::Create(
+ ElementCallbackType aType, Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
+ MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null");
+ MOZ_ASSERT(aCustomElement->GetCustomElementData(),
+ "CustomElementData should exist");
+
+ // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
+ CallbackFunction* func = nullptr;
+ switch (aType) {
+ case ElementCallbackType::eConnected:
+ if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) {
+ func = aDefinition->mCallbacks->mConnectedCallback.Value();
+ }
+ break;
+
+ case ElementCallbackType::eDisconnected:
+ if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) {
+ func = aDefinition->mCallbacks->mDisconnectedCallback.Value();
+ }
+ break;
+
+ case ElementCallbackType::eAdopted:
+ if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) {
+ func = aDefinition->mCallbacks->mAdoptedCallback.Value();
+ }
+ break;
+
+ case ElementCallbackType::eAttributeChanged:
+ if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
+ func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
+ }
+ break;
+
+ case ElementCallbackType::eFormAssociated:
+ if (aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
+ .WasPassed()) {
+ func = aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
+ .Value();
+ }
+ break;
+
+ case ElementCallbackType::eFormReset:
+ if (aDefinition->mFormAssociatedCallbacks->mFormResetCallback
+ .WasPassed()) {
+ func =
+ aDefinition->mFormAssociatedCallbacks->mFormResetCallback.Value();
+ }
+ break;
+
+ case ElementCallbackType::eFormDisabled:
+ if (aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
+ .WasPassed()) {
+ func = aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
+ .Value();
+ }
+ break;
+
+ case ElementCallbackType::eGetCustomInterface:
+ MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
+ break;
+ }
+
+ // If there is no such callback, stop.
+ if (!func) {
+ return nullptr;
+ }
+
+ // Add CALLBACK to ELEMENT's callback queue.
+ return MakeUnique<CustomElementCallback>(aCustomElement, aType, func, aArgs);
+}
+
+void CustomElementCallback::Call() {
+ switch (mType) {
+ case ElementCallbackType::eConnected:
+ static_cast<LifecycleConnectedCallback*>(mCallback.get())
+ ->Call(mThisObject);
+ break;
+ case ElementCallbackType::eDisconnected:
+ static_cast<LifecycleDisconnectedCallback*>(mCallback.get())
+ ->Call(mThisObject);
+ break;
+ case ElementCallbackType::eAdopted:
+ static_cast<LifecycleAdoptedCallback*>(mCallback.get())
+ ->Call(mThisObject, mArgs.mOldDocument, mArgs.mNewDocument);
+ break;
+ case ElementCallbackType::eAttributeChanged:
+ static_cast<LifecycleAttributeChangedCallback*>(mCallback.get())
+ ->Call(mThisObject, mArgs.mName, mArgs.mOldValue, mArgs.mNewValue,
+ mArgs.mNamespaceURI);
+ break;
+ case ElementCallbackType::eFormAssociated:
+ static_cast<LifecycleFormAssociatedCallback*>(mCallback.get())
+ ->Call(mThisObject, mArgs.mForm);
+ break;
+ case ElementCallbackType::eFormReset:
+ static_cast<LifecycleFormResetCallback*>(mCallback.get())
+ ->Call(mThisObject);
+ break;
+ case ElementCallbackType::eFormDisabled:
+ static_cast<LifecycleFormDisabledCallback*>(mCallback.get())
+ ->Call(mThisObject, mArgs.mDisabled);
+ break;
+ case ElementCallbackType::eGetCustomInterface:
+ MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
+ break;
+ }
+}
+
+void CustomElementCallback::Traverse(
+ nsCycleCollectionTraversalCallback& aCb) const {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
+ aCb.NoteXPCOMChild(mThisObject);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
+ aCb.NoteXPCOMChild(mCallback);
+}
+
+size_t CustomElementCallback::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ // We don't uniquely own mThisObject.
+
+ // We own mCallback but it doesn't have any special memory reporting we can do
+ // for it other than report its own size.
+ n += aMallocSizeOf(mCallback);
+
+ n += mArgs.SizeOfExcludingThis(aMallocSizeOf);
+
+ return n;
+}
+
+CustomElementCallback::CustomElementCallback(
+ Element* aThisObject, ElementCallbackType aCallbackType,
+ mozilla::dom::CallbackFunction* aCallback,
+ const LifecycleCallbackArgs& aArgs)
+ : mThisObject(aThisObject),
+ mCallback(aCallback),
+ mType(aCallbackType),
+ mArgs(aArgs) {}
+
+//-----------------------------------------------------
+// CustomElementData
+
+CustomElementData::CustomElementData(nsAtom* aType)
+ : CustomElementData(aType, CustomElementData::State::eUndefined) {}
+
+CustomElementData::CustomElementData(nsAtom* aType, State aState)
+ : mState(aState), mType(aType) {}
+
+void CustomElementData::SetCustomElementDefinition(
+ CustomElementDefinition* aDefinition) {
+ // Only allow reset definition to nullptr if the custom element state is
+ // "failed".
+ MOZ_ASSERT(aDefinition ? !mCustomElementDefinition
+ : mState == State::eFailed);
+ MOZ_ASSERT_IF(aDefinition, aDefinition->mType == mType);
+
+ mCustomElementDefinition = aDefinition;
+}
+
+void CustomElementData::AttachedInternals() {
+ MOZ_ASSERT(!mIsAttachedInternals);
+
+ mIsAttachedInternals = true;
+}
+
+CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const {
+ // Per spec, if there is a definition, the custom element state should be
+ // either "failed" (during upgrade) or "customized".
+ MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined);
+
+ return mCustomElementDefinition;
+}
+
+bool CustomElementData::IsFormAssociated() const {
+ // https://html.spec.whatwg.org/#form-associated-custom-element
+ return mCustomElementDefinition &&
+ !mCustomElementDefinition->IsCustomBuiltIn() &&
+ mCustomElementDefinition->mFormAssociated;
+}
+
+void CustomElementData::Traverse(
+ nsCycleCollectionTraversalCallback& aCb) const {
+ for (uint32_t i = 0; i < mReactionQueue.Length(); i++) {
+ if (mReactionQueue[i]) {
+ mReactionQueue[i]->Traverse(aCb);
+ }
+ }
+
+ if (mCustomElementDefinition) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition");
+ aCb.NoteNativeChild(
+ mCustomElementDefinition,
+ NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
+ }
+
+ if (mElementInternals) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mElementInternals");
+ aCb.NoteXPCOMChild(ToSupports(mElementInternals.get()));
+ }
+}
+
+void CustomElementData::Unlink() {
+ mReactionQueue.Clear();
+ if (mElementInternals) {
+ mElementInternals->Unlink();
+ mElementInternals = nullptr;
+ }
+ mCustomElementDefinition = nullptr;
+}
+
+size_t CustomElementData::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ for (auto& reaction : mReactionQueue) {
+ // "reaction" can be null if we're being called indirectly from
+ // InvokeReactions (e.g. due to a reaction causing a memory report to be
+ // captured somehow).
+ if (reaction) {
+ n += reaction->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ return n;
+}
+
+//-----------------------------------------------------
+// CustomElementRegistry
+
+namespace {
+
+class MOZ_RAII AutoConstructionStackEntry final {
+ public:
+ AutoConstructionStackEntry(nsTArray<RefPtr<Element>>& aStack,
+ Element* aElement)
+ : mStack(aStack) {
+ MOZ_ASSERT(aElement->IsHTMLElement() || aElement->IsXULElement());
+
+#ifdef DEBUG
+ mIndex = mStack.Length();
+#endif
+ mStack.AppendElement(aElement);
+ }
+
+ ~AutoConstructionStackEntry() {
+ MOZ_ASSERT(mIndex == mStack.Length() - 1,
+ "Removed element should be the last element");
+ mStack.RemoveLastElement();
+ }
+
+ private:
+ nsTArray<RefPtr<Element>>& mStack;
+#ifdef DEBUG
+ uint32_t mIndex;
+#endif
+};
+
+} // namespace
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
+ tmp->mConstructors.clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
+ for (auto iter = tmp->mConstructors.iter(); !iter.done(); iter.next()) {
+ aCallbacks.Trace(&iter.get().mutableKey(), "mConstructors key", aClosure);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow), mIsCustomDefinitionRunning(false) {
+ MOZ_ASSERT(aWindow);
+
+ mozilla::HoldJSObjects(this);
+}
+
+CustomElementRegistry::~CustomElementRegistry() {
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMETHODIMP
+CustomElementRegistry::RunCustomElementCreationCallback::Run() {
+ ErrorResult er;
+ nsDependentAtomString value(mAtom);
+ mCallback->Call(value, er);
+ MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
+ "chrome JavaScript error in the callback.");
+
+ RefPtr<CustomElementDefinition> definition =
+ mRegistry->mCustomDefinitions.Get(mAtom);
+ MOZ_ASSERT(definition, "Callback should define the definition of type.");
+ MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
+ "Callback should be removed.");
+
+ mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> elements;
+ mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom,
+ &elements);
+ MOZ_ASSERT(elements, "There should be a list");
+
+ for (const auto& key : *elements) {
+ nsCOMPtr<Element> elem = do_QueryReferent(key);
+ if (!elem) {
+ continue;
+ }
+
+ CustomElementRegistry::Upgrade(elem, definition, er);
+ MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
+ "chrome JavaScript error in custom element construction.");
+ }
+
+ return NS_OK;
+}
+
+CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
+ nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom) {
+ CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
+
+ if (!data) {
+ RefPtr<CustomElementCreationCallback> callback;
+ mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
+ if (callback) {
+ mElementCreationCallbacks.Remove(aTypeAtom);
+ mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(aTypeAtom);
+ RefPtr<Runnable> runnable =
+ new RunCustomElementCreationCallback(this, aTypeAtom, callback);
+ nsContentUtils::AddScriptRunner(runnable.forget());
+ data = mCustomDefinitions.GetWeak(aTypeAtom);
+ }
+ }
+
+ if (data && data->mLocalName == aNameAtom &&
+ data->mNamespaceID == aNameSpaceID) {
+ return data;
+ }
+
+ return nullptr;
+}
+
+CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
+ JSContext* aCx, JSObject* aConstructor) const {
+ // We're looking up things that tested true for JS::IsConstructor,
+ // so doing a CheckedUnwrapStatic is fine here.
+ JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrapStatic(aConstructor));
+
+ const auto& ptr = mConstructors.lookup(constructor);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ CustomElementDefinition* definition =
+ mCustomDefinitions.GetWeak(ptr->value());
+ MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
+
+ return definition;
+}
+
+void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName) {
+ // We don't have a use-case for a Custom Element inside NAC, and continuing
+ // here causes performance issues for NAC + XBL anonymous content.
+ if (aElement->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+
+ mozilla::dom::NodeInfo* info = aElement->NodeInfo();
+
+ // Candidate may be a custom element through extension,
+ // in which case the custom element type name will not
+ // match the element tag name. e.g. <button is="x-button">.
+ RefPtr<nsAtom> typeName = aTypeName;
+ if (!typeName) {
+ typeName = info->NameAtom();
+ }
+
+ if (mCustomDefinitions.GetWeak(typeName)) {
+ return;
+ }
+
+ nsTHashSet<RefPtr<nsIWeakReference>>* unresolved =
+ mCandidatesMap.GetOrInsertNew(typeName);
+ nsWeakPtr elem = do_GetWeakReference(aElement);
+ unresolved->Insert(elem);
+}
+
+void CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName) {
+ nsIWeakReference* weak = aElement->GetExistingWeakReference();
+ if (!weak) {
+ return;
+ }
+
+#ifdef DEBUG
+ {
+ nsWeakPtr weakPtr = do_GetWeakReference(aElement);
+ MOZ_ASSERT(
+ weak == weakPtr.get(),
+ "do_GetWeakReference should reuse the existing nsIWeakReference.");
+ }
+#endif
+
+ nsTHashSet<RefPtr<nsIWeakReference>>* candidates = nullptr;
+ if (mCandidatesMap.Get(aTypeName, &candidates)) {
+ MOZ_ASSERT(candidates);
+ candidates->Remove(weak);
+ }
+}
+
+// https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
+/* static */
+void CustomElementRegistry::EnqueueLifecycleCallback(
+ ElementCallbackType aType, Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
+ CustomElementDefinition* definition = aDefinition;
+ if (!definition) {
+ definition = aCustomElement->GetCustomElementDefinition();
+ if (!definition ||
+ definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
+ return;
+ }
+
+ if (!definition->mCallbacks && !definition->mFormAssociatedCallbacks) {
+ // definition has been unlinked. Don't try to mess with it.
+ return;
+ }
+ }
+
+ auto callback =
+ CustomElementCallback::Create(aType, aCustomElement, aArgs, definition);
+ if (!callback) {
+ return;
+ }
+
+ DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup();
+ if (!docGroup) {
+ return;
+ }
+
+ if (aType == ElementCallbackType::eAttributeChanged) {
+ RefPtr<nsAtom> attrName = NS_Atomize(aArgs.mName);
+ if (definition->mObservedAttributes.IsEmpty() ||
+ !definition->mObservedAttributes.Contains(attrName)) {
+ return;
+ }
+ }
+
+ CustomElementReactionsStack* reactionsStack =
+ docGroup->CustomElementReactionsStack();
+ reactionsStack->EnqueueCallbackReaction(aCustomElement, std::move(callback));
+}
+
+namespace {
+
+class CandidateFinder {
+ public:
+ CandidateFinder(nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates,
+ Document* aDoc);
+ nsTArray<nsCOMPtr<Element>> OrderedCandidates();
+
+ private:
+ nsCOMPtr<Document> mDoc;
+ nsInterfaceHashtable<nsPtrHashKey<Element>, Element> mCandidates;
+};
+
+CandidateFinder::CandidateFinder(
+ nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates, Document* aDoc)
+ : mDoc(aDoc), mCandidates(aCandidates.Count()) {
+ MOZ_ASSERT(mDoc);
+ for (const auto& candidate : aCandidates) {
+ nsCOMPtr<Element> elem = do_QueryReferent(candidate);
+ if (!elem) {
+ continue;
+ }
+
+ Element* key = elem.get();
+ mCandidates.InsertOrUpdate(key, elem.forget());
+ }
+}
+
+nsTArray<nsCOMPtr<Element>> CandidateFinder::OrderedCandidates() {
+ if (mCandidates.Count() == 1) {
+ // Fast path for one candidate.
+ auto iter = mCandidates.Iter();
+ nsTArray<nsCOMPtr<Element>> rval({std::move(iter.Data())});
+ iter.Remove();
+ return rval;
+ }
+
+ nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count());
+ for (nsINode* node : ShadowIncludingTreeIterator(*mDoc)) {
+ Element* element = Element::FromNode(node);
+ if (!element) {
+ continue;
+ }
+
+ nsCOMPtr<Element> elem;
+ if (mCandidates.Remove(element, getter_AddRefs(elem))) {
+ orderedElements.AppendElement(std::move(elem));
+ if (mCandidates.Count() == 0) {
+ break;
+ }
+ }
+ }
+
+ return orderedElements;
+}
+
+} // namespace
+
+void CustomElementRegistry::UpgradeCandidates(
+ nsAtom* aKey, CustomElementDefinition* aDefinition, ErrorResult& aRv) {
+ DocGroup* docGroup = mWindow->GetDocGroup();
+ if (!docGroup) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> candidates;
+ if (mCandidatesMap.Remove(aKey, &candidates)) {
+ MOZ_ASSERT(candidates);
+ CustomElementReactionsStack* reactionsStack =
+ docGroup->CustomElementReactionsStack();
+
+ CandidateFinder finder(*candidates, mWindow->GetExtantDoc());
+ for (auto& elem : finder.OrderedCandidates()) {
+ reactionsStack->EnqueueUpgradeReaction(elem, aDefinition);
+ }
+ }
+}
+
+JSObject* CustomElementRegistry::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CustomElementRegistry_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports* CustomElementRegistry::GetParentObject() const { return mWindow; }
+
+DocGroup* CustomElementRegistry::GetDocGroup() const {
+ return mWindow ? mWindow->GetDocGroup() : nullptr;
+}
+
+int32_t CustomElementRegistry::InferNamespace(
+ JSContext* aCx, JS::Handle<JSObject*> constructor) {
+ JS::Rooted<JSObject*> XULConstructor(
+ aCx, XULElement_Binding::GetConstructorObject(aCx));
+
+ JS::Rooted<JSObject*> proto(aCx, constructor);
+ while (proto) {
+ if (proto == XULConstructor) {
+ return kNameSpaceID_XUL;
+ }
+
+ JS_GetPrototype(aCx, proto, &proto);
+ }
+
+ return kNameSpaceID_XHTML;
+}
+
+bool CustomElementRegistry::JSObjectToAtomArray(
+ JSContext* aCx, JS::Handle<JSObject*> aConstructor, const nsString& aName,
+ nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv) {
+ JS::Rooted<JS::Value> iterable(aCx, JS::UndefinedValue());
+ if (!JS_GetUCProperty(aCx, aConstructor, aName.get(), aName.Length(),
+ &iterable)) {
+ aRv.NoteJSContextException(aCx);
+ return false;
+ }
+
+ if (!iterable.isUndefined()) {
+ if (!iterable.isObject()) {
+ aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
+ "sequence");
+ return false;
+ }
+
+ JS::ForOfIterator iter(aCx);
+ if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
+ aRv.NoteJSContextException(aCx);
+ return false;
+ }
+
+ if (!iter.valueIsIterable()) {
+ aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
+ "sequence");
+ return false;
+ }
+
+ JS::Rooted<JS::Value> attribute(aCx);
+ while (true) {
+ bool done;
+ if (!iter.next(&attribute, &done)) {
+ aRv.NoteJSContextException(aCx);
+ return false;
+ }
+ if (done) {
+ break;
+ }
+
+ nsAutoString attrStr;
+ if (!ConvertJSValueToString(aCx, attribute, eStringify, eStringify,
+ attrStr)) {
+ aRv.NoteJSContextException(aCx);
+ return false;
+ }
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aArray.AppendElement(NS_Atomize(attrStr));
+ }
+ }
+
+ return true;
+}
+
+// https://html.spec.whatwg.org/commit-snapshots/b48bb2238269d90ea4f455a52cdf29505aff3df0/#dom-customelementregistry-define
+void CustomElementRegistry::Define(
+ JSContext* aCx, const nsAString& aName,
+ CustomElementConstructor& aFunctionConstructor,
+ const ElementDefinitionOptions& aOptions, ErrorResult& aRv) {
+ JS::Rooted<JSObject*> constructor(aCx, aFunctionConstructor.CallableOrNull());
+
+ // We need to do a dynamic unwrap in order to throw the right exception. We
+ // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap
+ // fails.
+ //
+ // In any case, aCx represents the global we want to be using for the unwrap
+ // here.
+ JS::Rooted<JSObject*> constructorUnwrapped(
+ aCx, js::CheckedUnwrapDynamic(constructor, aCx));
+ if (!constructorUnwrapped) {
+ // If the caller's compartment does not have permission to access the
+ // unwrapped constructor then throw.
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ /**
+ * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
+ * these steps.
+ */
+ if (!JS::IsConstructor(constructorUnwrapped)) {
+ aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
+ return;
+ }
+
+ int32_t nameSpaceID = InferNamespace(aCx, constructor);
+
+ /**
+ * 2. If name is not a valid custom element name, then throw a "SyntaxError"
+ * DOMException and abort these steps.
+ */
+ Document* doc = mWindow->GetExtantDoc();
+ RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
+ if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
+ aRv.ThrowSyntaxError(
+ nsPrintfCString("'%s' is not a valid custom element name",
+ NS_ConvertUTF16toUTF8(aName).get()));
+ return;
+ }
+
+ /**
+ * 3. If this CustomElementRegistry contains an entry with name name, then
+ * throw a "NotSupportedError" DOMException and abort these steps.
+ */
+ if (mCustomDefinitions.GetWeak(nameAtom)) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("'%s' has already been defined as a custom element",
+ NS_ConvertUTF16toUTF8(aName).get()));
+ return;
+ }
+
+ /**
+ * 4. If this CustomElementRegistry contains an entry with constructor
+ * constructor, then throw a "NotSupportedError" DOMException and abort these
+ * steps.
+ */
+ const auto& ptr = mConstructors.lookup(constructorUnwrapped);
+ if (ptr) {
+ MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()),
+ "Definition must be found in mCustomDefinitions");
+ nsAutoCString name;
+ ptr->value()->ToUTF8String(name);
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("'%s' and '%s' have the same constructor",
+ NS_ConvertUTF16toUTF8(aName).get(), name.get()));
+ return;
+ }
+
+ /**
+ * 5. Let localName be name.
+ * 6. Let extends be the value of the extends member of options, or null if
+ * no such member exists.
+ * 7. If extends is not null, then:
+ * 1. If extends is a valid custom element name, then throw a
+ * "NotSupportedError" DOMException.
+ * 2. If the element interface for extends and the HTML namespace is
+ * HTMLUnknownElement (e.g., if extends does not indicate an element
+ * definition in this specification), then throw a "NotSupportedError"
+ * DOMException.
+ * 3. Set localName to extends.
+ *
+ * Special note for XUL elements:
+ *
+ * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
+ * custom built-in element will not be extending from a dashed name.
+ * Step 7.2 is disregarded. But, we do check if the name is a dashed name
+ * (i.e. step 2) given that there is no reason for a custom built-in element
+ * type to take on a non-dashed name.
+ * This also ensures the name of the built-in custom element type can never
+ * be the same as the built-in element name, so we don't break the assumption
+ * elsewhere.
+ */
+ nsAutoString localName(aName);
+ if (aOptions.mExtends.WasPassed()) {
+ doc->SetUseCounter(eUseCounter_custom_CustomizedBuiltin);
+
+ RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
+ if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("'%s' cannot extend a custom element",
+ NS_ConvertUTF16toUTF8(aName).get()));
+ return;
+ }
+
+ if (nameSpaceID == kNameSpaceID_XHTML) {
+ // bgsound and multicol are unknown html element.
+ int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom);
+ if (tag == eHTMLTag_userdefined || tag == eHTMLTag_bgsound ||
+ tag == eHTMLTag_multicol) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+ } else { // kNameSpaceID_XUL
+ // As stated above, ensure the name of the customized built-in element
+ // (the one that goes to the |is| attribute) is a dashed name.
+ if (!nsContentUtils::IsNameWithDash(nameAtom)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+ }
+
+ localName.Assign(aOptions.mExtends.Value());
+ }
+
+ /**
+ * 8. If this CustomElementRegistry's element definition is running flag is
+ * set, then throw a "NotSupportedError" DOMException and abort these steps.
+ */
+ if (mIsCustomDefinitionRunning) {
+ aRv.ThrowNotSupportedError(
+ "Cannot define a custom element while defining another custom element");
+ return;
+ }
+
+ auto callbacksHolder = MakeUnique<LifecycleCallbacks>();
+ auto formAssociatedCallbacksHolder =
+ MakeUnique<FormAssociatedLifecycleCallbacks>();
+ nsTArray<RefPtr<nsAtom>> observedAttributes;
+ AutoTArray<RefPtr<nsAtom>, 2> disabledFeatures;
+ bool formAssociated = false;
+ bool disableInternals = false;
+ bool disableShadow = false;
+ { // Set mIsCustomDefinitionRunning.
+ /**
+ * 9. Set this CustomElementRegistry's element definition is running flag.
+ */
+ AutoRestore<bool> restoreRunning(mIsCustomDefinitionRunning);
+ mIsCustomDefinitionRunning = true;
+
+ /**
+ * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any
+ * exceptions.
+ */
+ // The .prototype on the constructor passed could be an "expando" of a
+ // wrapper. So we should get it from wrapper instead of the underlying
+ // object.
+ JS::Rooted<JS::Value> prototype(aCx);
+ if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ /**
+ * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
+ */
+ if (!prototype.isObject()) {
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>("constructor.prototype");
+ return;
+ }
+
+ /**
+ * 14.3. Let lifecycleCallbacks be a map with the four keys
+ * "connectedCallback", "disconnectedCallback", "adoptedCallback", and
+ * "attributeChangedCallback", each of which belongs to an entry whose
+ * value is null. The 'getCustomInterface' callback is also included
+ * for chrome usage.
+ * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
+ * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
+ * exceptions.
+ * 2. If callbackValue is not undefined, then set the value of the
+ * entry in lifecycleCallbacks with key callbackName to the result
+ * of converting callbackValue to the Web IDL Function callback
+ * type. Rethrow any exceptions from the conversion.
+ */
+ if (!callbacksHolder->Init(aCx, prototype)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ /**
+ * 14.5. If the value of the entry in lifecycleCallbacks with key
+ * "attributeChangedCallback" is not null, then:
+ * 1. Let observedAttributesIterable be Get(constructor,
+ * "observedAttributes"). Rethrow any exceptions.
+ * 2. If observedAttributesIterable is not undefined, then set
+ * observedAttributes to the result of converting
+ * observedAttributesIterable to a sequence<DOMString>. Rethrow
+ * any exceptions from the conversion.
+ */
+ if (callbacksHolder->mAttributeChangedCallback.WasPassed()) {
+ if (!JSObjectToAtomArray(aCx, constructor, u"observedAttributes"_ns,
+ observedAttributes, aRv)) {
+ return;
+ }
+ }
+
+ /**
+ * 14.6. Let disabledFeatures be an empty sequence<DOMString>.
+ * 14.7. Let disabledFeaturesIterable be Get(constructor,
+ * "disabledFeatures"). Rethrow any exceptions.
+ * 14.8. If disabledFeaturesIterable is not undefined, then set
+ * disabledFeatures to the result of converting
+ * disabledFeaturesIterable to a sequence<DOMString>.
+ * Rethrow any exceptions from the conversion.
+ */
+ if (!JSObjectToAtomArray(aCx, constructor, u"disabledFeatures"_ns,
+ disabledFeatures, aRv)) {
+ return;
+ }
+
+ // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
+ // "internals".
+ disableInternals = disabledFeatures.Contains(
+ static_cast<nsStaticAtom*>(nsGkAtoms::internals));
+
+ // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
+ // "shadow".
+ disableShadow = disabledFeatures.Contains(
+ static_cast<nsStaticAtom*>(nsGkAtoms::shadow));
+
+ // 14.11. Let formAssociatedValue be Get(constructor, "formAssociated").
+ // Rethrow any exceptions.
+ JS::Rooted<JS::Value> formAssociatedValue(aCx);
+ if (!JS_GetProperty(aCx, constructor, "formAssociated",
+ &formAssociatedValue)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ // 14.12. Set formAssociated to the result of converting
+ // formAssociatedValue to a boolean. Rethrow any exceptions from
+ // the conversion.
+ if (!ValueToPrimitive<bool, eDefault>(aCx, formAssociatedValue,
+ "formAssociated", &formAssociated)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ /**
+ * 14.13. If formAssociated is true, for each of "formAssociatedCallback",
+ * "formResetCallback", "formDisabledCallback", and
+ * "formStateRestoreCallback" callbackName:
+ * 1. Let callbackValue be ? Get(prototype, callbackName).
+ * 2. If callbackValue is not undefined, then set the value of the
+ * entry in lifecycleCallbacks with key callbackName to the result
+ * of converting callbackValue to the Web IDL Function callback
+ * type. Rethrow any exceptions from the conversion.
+ */
+ if (formAssociated &&
+ !formAssociatedCallbacksHolder->Init(aCx, prototype)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ } // Unset mIsCustomDefinitionRunning
+
+ /**
+ * 15. Let definition be a new custom element definition with name name,
+ * local name localName, constructor constructor, prototype prototype,
+ * observed attributes observedAttributes, and lifecycle callbacks
+ * lifecycleCallbacks.
+ */
+ // Associate the definition with the custom element.
+ RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
+
+ /**
+ * 16. Add definition to this CustomElementRegistry.
+ */
+ if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(
+ nameAtom, localNameAtom, nameSpaceID, &aFunctionConstructor,
+ std::move(observedAttributes), std::move(callbacksHolder),
+ std::move(formAssociatedCallbacksHolder), formAssociated,
+ disableInternals, disableShadow);
+
+ CustomElementDefinition* def = definition.get();
+ mCustomDefinitions.InsertOrUpdate(nameAtom, std::move(definition));
+
+ MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
+ "Number of entries should be the same");
+
+ /**
+ * 17. 18. 19. Upgrade candidates
+ */
+ UpgradeCandidates(nameAtom, def, aRv);
+
+ /**
+ * 20. If this CustomElementRegistry's when-defined promise map contains an
+ * entry with key name:
+ * 1. Let promise be the value of that entry.
+ * 2. Resolve promise with undefined.
+ * 3. Delete the entry with key name from this CustomElementRegistry's
+ * when-defined promise map.
+ */
+ RefPtr<Promise> promise;
+ mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
+ if (promise) {
+ promise->MaybeResolve(def->mConstructor);
+ }
+
+ // Dispatch a "customelementdefined" event for DevTools.
+ {
+ JSString* nameJsStr =
+ JS_NewUCStringCopyN(aCx, aName.BeginReading(), aName.Length());
+
+ JS::Rooted<JS::Value> detail(aCx, JS::StringValue(nameJsStr));
+ RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
+ event->InitCustomEvent(aCx, u"customelementdefined"_ns,
+ /* CanBubble */ true,
+ /* Cancelable */ true, detail);
+ event->SetTrusted(true);
+
+ AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(doc, event);
+ dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
+
+ dispatcher->PostDOMEvent();
+ }
+
+ /**
+ * Clean-up mElementCreationCallbacks (if it exists)
+ */
+ mElementCreationCallbacks.Remove(nameAtom);
+}
+
+void CustomElementRegistry::SetElementCreationCallback(
+ const nsAString& aName, CustomElementCreationCallback& aCallback,
+ ErrorResult& aRv) {
+ RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
+ if (mElementCreationCallbacks.GetWeak(nameAtom) ||
+ mCustomDefinitions.GetWeak(nameAtom)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ RefPtr<CustomElementCreationCallback> callback = &aCallback;
+ mElementCreationCallbacks.InsertOrUpdate(nameAtom, std::move(callback));
+}
+
+void CustomElementRegistry::Upgrade(nsINode& aRoot) {
+ for (nsINode* node : ShadowIncludingTreeIterator(aRoot)) {
+ Element* element = Element::FromNode(node);
+ if (!element) {
+ continue;
+ }
+
+ CustomElementData* ceData = element->GetCustomElementData();
+ if (ceData) {
+ NodeInfo* nodeInfo = element->NodeInfo();
+ nsAtom* typeAtom = ceData->GetCustomElementType();
+ CustomElementDefinition* definition =
+ nsContentUtils::LookupCustomElementDefinition(
+ nodeInfo->GetDocument(), nodeInfo->NameAtom(),
+ nodeInfo->NamespaceID(), typeAtom);
+ if (definition) {
+ nsContentUtils::EnqueueUpgradeReaction(element, definition);
+ }
+ }
+ }
+}
+
+void CustomElementRegistry::Get(
+ const nsAString& aName,
+ OwningCustomElementConstructorOrUndefined& aRetVal) {
+ RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
+ CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom);
+
+ if (!data) {
+ aRetVal.SetUndefined();
+ return;
+ }
+
+ aRetVal.SetAsCustomElementConstructor() = data->mConstructor;
+}
+
+already_AddRefed<Promise> CustomElementRegistry::WhenDefined(
+ const nsAString& aName, ErrorResult& aRv) {
+ // Define a function that lazily creates a Promise and perform some action on
+ // it when creation succeeded. It's needed in multiple cases below, but not in
+ // all of them.
+ auto createPromise = [&](auto&& action) -> already_AddRefed<Promise> {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ action(promise);
+
+ return promise.forget();
+ };
+
+ RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
+ Document* doc = mWindow->GetExtantDoc();
+ uint32_t nameSpaceID =
+ doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
+ if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
+ return createPromise([](const RefPtr<Promise>& promise) {
+ promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+ });
+ }
+
+ if (CustomElementDefinition* definition =
+ mCustomDefinitions.GetWeak(nameAtom)) {
+ return createPromise([&](const RefPtr<Promise>& promise) {
+ promise->MaybeResolve(definition->mConstructor);
+ });
+ }
+
+ return mWhenDefinedPromiseMap.WithEntryHandle(
+ nameAtom, [&](auto&& entry) -> already_AddRefed<Promise> {
+ if (!entry) {
+ return createPromise([&entry](const RefPtr<Promise>& promise) {
+ entry.Insert(promise);
+ });
+ }
+ return do_AddRef(entry.Data());
+ });
+}
+
+namespace {
+
+MOZ_CAN_RUN_SCRIPT
+static void DoUpgrade(Element* aElement, CustomElementDefinition* aDefinition,
+ CustomElementConstructor* aConstructor,
+ ErrorResult& aRv) {
+ if (aDefinition->mDisableShadow && aElement->GetShadowRoot()) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Custom element upgrade to '%s' is disabled because a shadow root "
+ "already exists",
+ NS_ConvertUTF16toUTF8(aDefinition->mType->GetUTF16String()).get()));
+ return;
+ }
+
+ CustomElementData* data = aElement->GetCustomElementData();
+ MOZ_ASSERT(data, "CustomElementData should exist");
+ data->mState = CustomElementData::State::ePrecustomized;
+
+ JS::Rooted<JS::Value> constructResult(RootingCx());
+ // Rethrow the exception since it might actually throw the exception from the
+ // upgrade steps back out to the caller of document.createElement.
+ aConstructor->Construct(&constructResult, aRv, "Custom Element Upgrade",
+ CallbackFunction::eRethrowExceptions);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ Element* element;
+ // constructResult is an ObjectValue because construction with a callback
+ // always forms the return value from a JSObject.
+ if (NS_FAILED(UNWRAP_OBJECT(Element, &constructResult, element)) ||
+ element != aElement) {
+ aRv.ThrowTypeError("Custom element constructor returned a wrong element");
+ return;
+ }
+}
+
+} // anonymous namespace
+
+// https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
+/* static */
+void CustomElementRegistry::Upgrade(Element* aElement,
+ CustomElementDefinition* aDefinition,
+ ErrorResult& aRv) {
+ CustomElementData* data = aElement->GetCustomElementData();
+ MOZ_ASSERT(data, "CustomElementData should exist");
+
+ // Step 1.
+ if (data->mState != CustomElementData::State::eUndefined) {
+ return;
+ }
+
+ // Step 2.
+ aElement->SetCustomElementDefinition(aDefinition);
+
+ // Step 3.
+ data->mState = CustomElementData::State::eFailed;
+
+ // Step 4.
+ if (!aDefinition->mObservedAttributes.IsEmpty()) {
+ uint32_t count = aElement->GetAttrCount();
+ for (uint32_t i = 0; i < count; i++) {
+ mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
+
+ const nsAttrName* name = info.mName;
+ nsAtom* attrName = name->LocalName();
+
+ if (aDefinition->IsInObservedAttributeList(attrName)) {
+ int32_t namespaceID = name->NamespaceID();
+ nsAutoString attrValue, namespaceURI;
+ info.mValue->ToString(attrValue);
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID,
+ namespaceURI);
+
+ LifecycleCallbackArgs args;
+ args.mName = nsDependentAtomString(attrName);
+ args.mOldValue = VoidString();
+ args.mNewValue = attrValue;
+ args.mNamespaceURI =
+ (namespaceURI.IsEmpty() ? VoidString() : namespaceURI);
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, aElement, args,
+ aDefinition);
+ }
+ }
+ }
+
+ // Step 5.
+ if (aElement->IsInComposedDoc()) {
+ nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected,
+ aElement, {}, aDefinition);
+ }
+
+ // Step 6.
+ AutoConstructionStackEntry acs(aDefinition->mConstructionStack, aElement);
+
+ // Step 7 and step 8.
+ DoUpgrade(aElement, aDefinition, MOZ_KnownLive(aDefinition->mConstructor),
+ aRv);
+ if (aRv.Failed()) {
+ MOZ_ASSERT(data->mState == CustomElementData::State::eFailed ||
+ data->mState == CustomElementData::State::ePrecustomized);
+ // Spec doesn't set custom element state to failed here, but without this we
+ // would have inconsistent state on a custom elemet that is failed to
+ // upgrade, see https://github.com/whatwg/html/issues/6929, and
+ // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
+ data->mState = CustomElementData::State::eFailed;
+ aElement->SetCustomElementDefinition(nullptr);
+ // Empty element's custom element reaction queue.
+ data->mReactionQueue.Clear();
+ return;
+ }
+
+ // Step 9.
+ if (data->IsFormAssociated()) {
+ ElementInternals* internals = data->GetElementInternals();
+ MOZ_ASSERT(internals);
+ MOZ_ASSERT(aElement->IsHTMLElement());
+ MOZ_ASSERT(!aDefinition->IsCustomBuiltIn());
+
+ internals->UpdateFormOwner();
+ }
+
+ // Step 10.
+ data->mState = CustomElementData::State::eCustom;
+ aElement->SetDefined(true);
+}
+
+already_AddRefed<nsISupports> CustomElementRegistry::CallGetCustomInterface(
+ Element* aElement, const nsIID& aIID) {
+ MOZ_ASSERT(aElement);
+
+ if (!nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
+ return nullptr;
+ }
+
+ // Try to get our GetCustomInterfaceCallback callback.
+ CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
+ if (!definition || !definition->mCallbacks ||
+ !definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() ||
+ (definition->mLocalName != aElement->NodeInfo()->NameAtom())) {
+ return nullptr;
+ }
+ LifecycleGetCustomInterfaceCallback* func =
+ definition->mCallbacks->mGetCustomInterfaceCallback.Value();
+
+ // Initialize a AutoJSAPI to enter the compartment of the callback.
+ AutoJSAPI jsapi;
+ JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
+ if (!funcGlobal || !jsapi.Init(funcGlobal)) {
+ return nullptr;
+ }
+
+ // Grab our JSContext.
+ JSContext* cx = jsapi.cx();
+
+ // Convert our IID to a JSValue to call our callback.
+ JS::Rooted<JS::Value> jsiid(cx);
+ if (!xpc::ID2JSValue(cx, aIID, &jsiid)) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> customInterface(cx);
+ func->Call(aElement, jsiid, &customInterface);
+ if (!customInterface) {
+ return nullptr;
+ }
+
+ // Wrap our JSObject into a nsISupports through XPConnect
+ nsCOMPtr<nsISupports> wrapper;
+ nsresult rv = nsContentUtils::XPConnect()->WrapJSAggregatedToNative(
+ aElement, cx, customInterface, aIID, getter_AddRefs(wrapper));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return wrapper.forget();
+}
+
+void CustomElementRegistry::TraceDefinitions(JSTracer* aTrc) {
+ for (const RefPtr<CustomElementDefinition>& definition :
+ mCustomDefinitions.Values()) {
+ if (definition && definition->mConstructor) {
+ mozilla::TraceScriptHolder(definition->mConstructor, aTrc);
+ }
+ }
+}
+
+//-----------------------------------------------------
+// CustomElementReactionsStack
+
+void CustomElementReactionsStack::CreateAndPushElementQueue() {
+ MOZ_ASSERT(mRecursionDepth);
+ MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
+
+ // Push a new element queue onto the custom element reactions stack.
+ mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
+ mIsElementQueuePushedForCurrentRecursionDepth = true;
+}
+
+void CustomElementReactionsStack::PopAndInvokeElementQueue() {
+ MOZ_ASSERT(mRecursionDepth);
+ MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
+ MOZ_ASSERT(!mReactionsStack.IsEmpty(), "Reaction stack shouldn't be empty");
+
+ // Pop the element queue from the custom element reactions stack,
+ // and invoke custom element reactions in that queue.
+ const uint32_t lastIndex = mReactionsStack.Length() - 1;
+ ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
+ // Check element queue size in order to reduce function call overhead.
+ if (!elementQueue->IsEmpty()) {
+ // It is still not clear what error reporting will look like in custom
+ // element, see https://github.com/w3c/webcomponents/issues/635.
+ // We usually report the error to entry global in gecko, so just follow the
+ // same behavior here.
+ // This may be null if it's called from parser, see the case of
+ // attributeChangedCallback in
+ // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
+ // In that case, the exception of callback reactions will be automatically
+ // reported in CallSetup.
+ nsIGlobalObject* global = GetEntryGlobal();
+ InvokeReactions(elementQueue, MOZ_KnownLive(global));
+ }
+
+ // InvokeReactions() might create other custom element reactions, but those
+ // new reactions should be already consumed and removed at this point.
+ MOZ_ASSERT(
+ lastIndex == mReactionsStack.Length() - 1,
+ "reactions created by InvokeReactions() should be consumed and removed");
+
+ mReactionsStack.RemoveLastElement();
+ mIsElementQueuePushedForCurrentRecursionDepth = false;
+}
+
+void CustomElementReactionsStack::EnqueueUpgradeReaction(
+ Element* aElement, CustomElementDefinition* aDefinition) {
+ Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
+}
+
+void CustomElementReactionsStack::EnqueueCallbackReaction(
+ Element* aElement,
+ UniquePtr<CustomElementCallback> aCustomElementCallback) {
+ Enqueue(aElement,
+ new CustomElementCallbackReaction(std::move(aCustomElementCallback)));
+}
+
+void CustomElementReactionsStack::Enqueue(Element* aElement,
+ CustomElementReaction* aReaction) {
+ CustomElementData* elementData = aElement->GetCustomElementData();
+ MOZ_ASSERT(elementData, "CustomElementData should exist");
+
+ if (mRecursionDepth) {
+ // If the element queue is not created for current recursion depth, create
+ // and push an element queue to reactions stack first.
+ if (!mIsElementQueuePushedForCurrentRecursionDepth) {
+ CreateAndPushElementQueue();
+ }
+
+ MOZ_ASSERT(!mReactionsStack.IsEmpty());
+ // Add element to the current element queue.
+ mReactionsStack.LastElement()->AppendElement(aElement);
+ elementData->mReactionQueue.AppendElement(aReaction);
+ return;
+ }
+
+ // If the custom element reactions stack is empty, then:
+ // Add element to the backup element queue.
+ MOZ_ASSERT(mReactionsStack.IsEmpty(),
+ "custom element reactions stack should be empty");
+ mBackupQueue.AppendElement(aElement);
+ elementData->mReactionQueue.AppendElement(aReaction);
+
+ if (mIsBackupQueueProcessing) {
+ return;
+ }
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
+ context->DispatchToMicroTask(bqmt.forget());
+}
+
+void CustomElementReactionsStack::InvokeBackupQueue() {
+ // Check backup queue size in order to reduce function call overhead.
+ if (!mBackupQueue.IsEmpty()) {
+ // Upgrade reactions won't be scheduled in backup queue and the exception of
+ // callback reactions will be automatically reported in CallSetup.
+ // If the reactions are invoked from backup queue (in microtask check
+ // point), we don't need to pass global object for error reporting.
+ InvokeReactions(&mBackupQueue, nullptr);
+ }
+ MOZ_ASSERT(
+ mBackupQueue.IsEmpty(),
+ "There are still some reactions in BackupQueue not being consumed!?!");
+}
+
+void CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue,
+ nsIGlobalObject* aGlobal) {
+ // This is used for error reporting.
+ Maybe<AutoEntryScript> aes;
+ if (aGlobal) {
+ aes.emplace(aGlobal, "custom elements reaction invocation");
+ }
+
+ // Note: It's possible to re-enter this method.
+ for (uint32_t i = 0; i < aElementQueue->Length(); ++i) {
+ Element* element = aElementQueue->ElementAt(i);
+ // ElementQueue hold a element's strong reference, it should not be a
+ // nullptr.
+ MOZ_ASSERT(element);
+
+ CustomElementData* elementData = element->GetCustomElementData();
+ if (!elementData || !element->GetOwnerGlobal()) {
+ // This happens when the document is destroyed and the element is already
+ // unlinked, no need to fire the callbacks in this case.
+ continue;
+ }
+
+ auto& reactions = elementData->mReactionQueue;
+ for (uint32_t j = 0; j < reactions.Length(); ++j) {
+ // Transfer the ownership of the entry due to reentrant invocation of
+ // this function.
+ auto reaction(std::move(reactions.ElementAt(j)));
+ if (reaction) {
+ if (!aGlobal && reaction->IsUpgradeReaction()) {
+ nsIGlobalObject* global = element->GetOwnerGlobal();
+ MOZ_ASSERT(!aes);
+ aes.emplace(global, "custom elements reaction invocation");
+ }
+ ErrorResult rv;
+ reaction->Invoke(MOZ_KnownLive(element), rv);
+ if (aes) {
+ JSContext* cx = aes->cx();
+ if (rv.MaybeSetPendingException(cx)) {
+ aes->ReportException();
+ }
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ if (!aGlobal && reaction->IsUpgradeReaction()) {
+ aes.reset();
+ }
+ }
+ MOZ_ASSERT(!rv.Failed());
+ }
+ }
+ reactions.Clear();
+ }
+ aElementQueue->Clear();
+}
+
+//-----------------------------------------------------
+// CustomElementDefinition
+
+NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition, mConstructor, mCallbacks,
+ mFormAssociatedCallbacks, mConstructionStack)
+
+CustomElementDefinition::CustomElementDefinition(
+ nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
+ CustomElementConstructor* aConstructor,
+ nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
+ UniquePtr<LifecycleCallbacks>&& aCallbacks,
+ UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
+ bool aFormAssociated, bool aDisableInternals, bool aDisableShadow)
+ : mType(aType),
+ mLocalName(aLocalName),
+ mNamespaceID(aNamespaceID),
+ mConstructor(aConstructor),
+ mObservedAttributes(std::move(aObservedAttributes)),
+ mCallbacks(std::move(aCallbacks)),
+ mFormAssociatedCallbacks(std::move(aFormAssociatedCallbacks)),
+ mFormAssociated(aFormAssociated),
+ mDisableInternals(aDisableInternals),
+ mDisableShadow(aDisableShadow) {}
+
+} // namespace mozilla::dom
diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h
new file mode 100644
index 0000000000..5cf3285b46
--- /dev/null
+++ b/dom/base/CustomElementRegistry.h
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CustomElementRegistry_h
+#define mozilla_dom_CustomElementRegistry_h
+
+#include "js/GCHashTable.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInternals.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+struct CustomElementData;
+struct ElementDefinitionOptions;
+class CallbackFunction;
+class CustomElementCallback;
+class CustomElementReaction;
+class DocGroup;
+class Promise;
+
+enum class ElementCallbackType {
+ eConnected,
+ eDisconnected,
+ eAdopted,
+ eAttributeChanged,
+ eFormAssociated,
+ eFormReset,
+ eFormDisabled,
+ eGetCustomInterface
+};
+
+struct LifecycleCallbackArgs {
+ // Used by the attribute changed callback.
+ nsString mName;
+ nsString mOldValue;
+ nsString mNewValue;
+ nsString mNamespaceURI;
+
+ // Used by the adopted callback.
+ RefPtr<Document> mOldDocument;
+ RefPtr<Document> mNewDocument;
+
+ // Used by the form associated callback.
+ RefPtr<HTMLFormElement> mForm;
+
+ // Used by the form disabled callback.
+ bool mDisabled;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+};
+
+// Each custom element has an associated callback queue and an element is
+// being created flag.
+struct CustomElementData {
+ // https://dom.spec.whatwg.org/#concept-element-custom-element-state
+ // CustomElementData is only created on the element which is a custom element
+ // or an upgrade candidate, so the state of an element without
+ // CustomElementData is "uncustomized".
+ enum class State { eUndefined, eFailed, eCustom, ePrecustomized };
+
+ explicit CustomElementData(nsAtom* aType);
+ CustomElementData(nsAtom* aType, State aState);
+ ~CustomElementData() = default;
+
+ // Custom element state as described in the custom element spec.
+ State mState;
+ // custom element reaction queue as described in the custom element spec.
+ // There is 1 reaction in reaction queue, when 1) it becomes disconnected,
+ // 2) it’s adopted into a new document, 3) its attributes are changed,
+ // appended, removed, or replaced.
+ // There are 3 reactions in reaction queue when doing upgrade operation,
+ // e.g., create an element, insert a node.
+ AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue;
+
+ void SetCustomElementDefinition(CustomElementDefinition* aDefinition);
+ CustomElementDefinition* GetCustomElementDefinition() const;
+ nsAtom* GetCustomElementType() const { return mType; }
+ void AttachedInternals();
+ bool HasAttachedInternals() const { return mIsAttachedInternals; }
+
+ bool IsFormAssociated() const;
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+ void Unlink();
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ nsAtom* GetIs(const Element* aElement) const {
+ // If mType isn't the same as name atom, this is a customized built-in
+ // element, which has 'is' value set.
+ return aElement->NodeInfo()->NameAtom() == mType ? nullptr : mType.get();
+ }
+
+ ElementInternals* GetElementInternals() const { return mElementInternals; }
+
+ ElementInternals* GetOrCreateElementInternals(HTMLElement* aTarget) {
+ if (!mElementInternals) {
+ mElementInternals = MakeAndAddRef<ElementInternals>(aTarget);
+ }
+ return mElementInternals;
+ }
+
+ private:
+ // Custom element type, for <button is="x-button"> or <x-button>
+ // this would be x-button.
+ RefPtr<nsAtom> mType;
+ RefPtr<CustomElementDefinition> mCustomElementDefinition;
+ RefPtr<ElementInternals> mElementInternals;
+ bool mIsAttachedInternals = false;
+};
+
+#define ALREADY_CONSTRUCTED_MARKER nullptr
+
+// The required information for a custom element as defined in:
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
+struct CustomElementDefinition {
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(CustomElementDefinition)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition)
+
+ CustomElementDefinition(
+ nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
+ CustomElementConstructor* aConstructor,
+ nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
+ UniquePtr<LifecycleCallbacks>&& aCallbacks,
+ UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
+ bool aFormAssociated, bool aDisableInternals, bool aDisableShadow);
+
+ // The type (name) for this custom element, for <button is="x-foo"> or <x-foo>
+ // this would be x-foo.
+ RefPtr<nsAtom> mType;
+
+ // The localname to (e.g. <button is=type> -- this would be button).
+ RefPtr<nsAtom> mLocalName;
+
+ // The namespace for this custom element
+ int32_t mNamespaceID;
+
+ // The custom element constructor.
+ RefPtr<CustomElementConstructor> mConstructor;
+
+ // The list of attributes that this custom element observes.
+ nsTArray<RefPtr<nsAtom>> mObservedAttributes;
+
+ // The lifecycle callbacks to call for this custom element.
+ UniquePtr<LifecycleCallbacks> mCallbacks;
+ UniquePtr<FormAssociatedLifecycleCallbacks> mFormAssociatedCallbacks;
+
+ // If this is true, user agent treats elements associated to this custom
+ // element definition as form-associated custom elements.
+ bool mFormAssociated = false;
+
+ // Determine whether to allow to attachInternals() for this custom element.
+ bool mDisableInternals = false;
+
+ // Determine whether to allow to attachShadow() for this custom element.
+ bool mDisableShadow = false;
+
+ // A construction stack. Use nullptr to represent an "already constructed
+ // marker".
+ nsTArray<RefPtr<Element>> mConstructionStack;
+
+ // See step 6.1.10 of https://dom.spec.whatwg.org/#concept-create-element
+ // which set up the prefix after a custom element is created. However, In
+ // Gecko, the prefix isn't allowed to be changed in NodeInfo, so we store the
+ // prefix information here and propagate to where NodeInfo is assigned to a
+ // custom element instead.
+ nsTArray<RefPtr<nsAtom>> mPrefixStack;
+
+ // This basically is used for distinguishing the custom element constructor
+ // is invoked from document.createElement or directly from JS, i.e.
+ // `new CustomElementConstructor()`.
+ uint32_t mConstructionDepth = 0;
+
+ bool IsCustomBuiltIn() { return mType != mLocalName; }
+
+ bool IsInObservedAttributeList(nsAtom* aName) {
+ if (mObservedAttributes.IsEmpty()) {
+ return false;
+ }
+
+ return mObservedAttributes.Contains(aName);
+ }
+
+ private:
+ ~CustomElementDefinition() = default;
+};
+
+class CustomElementReaction {
+ public:
+ virtual ~CustomElementReaction() = default;
+ MOZ_CAN_RUN_SCRIPT
+ virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0;
+ virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const = 0;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const = 0;
+
+ bool IsUpgradeReaction() { return mIsUpgradeReaction; }
+
+ protected:
+ bool mIsUpgradeReaction = false;
+};
+
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
+class CustomElementReactionsStack {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack)
+
+ CustomElementReactionsStack()
+ : mIsBackupQueueProcessing(false),
+ mRecursionDepth(0),
+ mIsElementQueuePushedForCurrentRecursionDepth(false) {}
+
+ // Hold a strong reference of Element so that it does not get cycle collected
+ // before the reactions in its reaction queue are invoked.
+ // The element reaction queues are stored in CustomElementData.
+ // We need to lookup ElementReactionQueueMap again to get relevant reaction
+ // queue. The choice of 3 for the auto size here is based on running Custom
+ // Elements wpt tests.
+ typedef AutoTArray<RefPtr<Element>, 3> ElementQueue;
+
+ /**
+ * Enqueue a custom element upgrade reaction
+ * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction
+ */
+ void EnqueueUpgradeReaction(Element* aElement,
+ CustomElementDefinition* aDefinition);
+
+ /**
+ * Enqueue a custom element callback reaction
+ * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction
+ */
+ void EnqueueCallbackReaction(
+ Element* aElement,
+ UniquePtr<CustomElementCallback> aCustomElementCallback);
+
+ /**
+ * [CEReactions] Before executing the algorithm's steps.
+ * Increase the current recursion depth, and the element queue is pushed
+ * lazily when we really enqueue reactions.
+ *
+ * @return true if the element queue is pushed for "previous" recursion depth.
+ */
+ bool EnterCEReactions() {
+ bool temp = mIsElementQueuePushedForCurrentRecursionDepth;
+ mRecursionDepth++;
+ // The is-element-queue-pushed flag is initially false when entering a new
+ // recursion level. The original value will be cached in AutoCEReaction
+ // and restored after leaving this recursion level.
+ mIsElementQueuePushedForCurrentRecursionDepth = false;
+ return temp;
+ }
+
+ /**
+ * [CEReactions] After executing the algorithm's steps.
+ * Pop and invoke the element queue if it is created and pushed for current
+ * recursion depth, then decrease the current recursion depth.
+ *
+ * @param aCx JSContext used for handling exception thrown by algorithm's
+ * steps, this could be a nullptr.
+ * aWasElementQueuePushed used for restoring status after leaving
+ * current recursion.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed) {
+ MOZ_ASSERT(mRecursionDepth);
+
+ if (mIsElementQueuePushedForCurrentRecursionDepth) {
+ Maybe<JS::AutoSaveExceptionState> ases;
+ if (aCx) {
+ ases.emplace(aCx);
+ }
+ PopAndInvokeElementQueue();
+ }
+ mRecursionDepth--;
+ // Restore the is-element-queue-pushed flag cached in AutoCEReaction when
+ // leaving the recursion level.
+ mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed;
+
+ MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty());
+ }
+
+ bool IsElementQueuePushedForCurrentRecursionDepth() {
+ MOZ_ASSERT_IF(mIsElementQueuePushedForCurrentRecursionDepth,
+ !mReactionsStack.IsEmpty() &&
+ !mReactionsStack.LastElement()->IsEmpty());
+ return mIsElementQueuePushedForCurrentRecursionDepth;
+ }
+
+ private:
+ ~CustomElementReactionsStack() = default;
+ ;
+
+ /**
+ * Push a new element queue onto the custom element reactions stack.
+ */
+ void CreateAndPushElementQueue();
+
+ /**
+ * Pop the element queue from the custom element reactions stack, and invoke
+ * custom element reactions in that queue.
+ */
+ MOZ_CAN_RUN_SCRIPT void PopAndInvokeElementQueue();
+
+ // The choice of 8 for the auto size here is based on gut feeling.
+ AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack;
+ ElementQueue mBackupQueue;
+ // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
+ bool mIsBackupQueueProcessing;
+
+ MOZ_CAN_RUN_SCRIPT void InvokeBackupQueue();
+
+ /**
+ * Invoke custom element reactions
+ * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal);
+
+ void Enqueue(Element* aElement, CustomElementReaction* aReaction);
+
+ // Current [CEReactions] recursion depth.
+ uint32_t mRecursionDepth;
+ // True if the element queue is pushed into reaction stack for current
+ // recursion depth. This will be cached in AutoCEReaction when entering a new
+ // CEReaction recursion and restored after leaving the recursion.
+ bool mIsElementQueuePushedForCurrentRecursionDepth;
+
+ private:
+ class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable {
+ public:
+ explicit BackupQueueMicroTask(CustomElementReactionsStack* aReactionStack)
+ : MicroTaskRunnable(), mReactionStack(aReactionStack) {
+ MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing,
+ "mIsBackupQueueProcessing should be initially false");
+ mReactionStack->mIsBackupQueueProcessing = true;
+ }
+
+ MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) override {
+ mReactionStack->InvokeBackupQueue();
+ mReactionStack->mIsBackupQueueProcessing = false;
+ }
+
+ private:
+ const RefPtr<CustomElementReactionsStack> mReactionStack;
+ };
+};
+
+class CustomElementRegistry final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
+
+ public:
+ explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
+
+ private:
+ class RunCustomElementCreationCallback : public mozilla::Runnable {
+ public:
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
+ // See bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_DECL_NSIRUNNABLE
+
+ explicit RunCustomElementCreationCallback(
+ CustomElementRegistry* aRegistry, nsAtom* aAtom,
+ CustomElementCreationCallback* aCallback)
+ : mozilla::Runnable(
+ "CustomElementRegistry::RunCustomElementCreationCallback"),
+ mRegistry(aRegistry),
+ mAtom(aAtom),
+ mCallback(aCallback) {}
+
+ private:
+ RefPtr<CustomElementRegistry> mRegistry;
+ RefPtr<nsAtom> mAtom;
+ RefPtr<CustomElementCreationCallback> mCallback;
+ };
+
+ public:
+ /**
+ * Looking up a custom element definition.
+ * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+ */
+ CustomElementDefinition* LookupCustomElementDefinition(nsAtom* aNameAtom,
+ int32_t aNameSpaceID,
+ nsAtom* aTypeAtom);
+
+ CustomElementDefinition* LookupCustomElementDefinition(
+ JSContext* aCx, JSObject* aConstructor) const;
+
+ static void EnqueueLifecycleCallback(ElementCallbackType aType,
+ Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs,
+ CustomElementDefinition* aDefinition);
+
+ /**
+ * Upgrade an element.
+ * https://html.spec.whatwg.org/multipage/scripting.html#upgrades
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition,
+ ErrorResult& aRv);
+
+ /**
+ * To allow native code to call methods of chrome-implemented custom elements,
+ * a helper method may be defined in the custom element called
+ * 'getCustomInterfaceCallback'. This method takes an IID and returns an
+ * object which implements an XPCOM interface.
+ *
+ * This returns null if aElement is not from a chrome document.
+ */
+ static already_AddRefed<nsISupports> CallGetCustomInterface(
+ Element* aElement, const nsIID& aIID);
+
+ /**
+ * Registers an unresolved custom element that is a candidate for
+ * upgrade. |aTypeName| is the name of the custom element type, if it is not
+ * provided, then element name is used. |aTypeName| should be provided
+ * when registering a custom element that extends an existing
+ * element. e.g. <button is="x-button">.
+ */
+ void RegisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName = nullptr);
+
+ /**
+ * Unregister an unresolved custom element that is a candidate for
+ * upgrade when a custom element is removed from tree.
+ */
+ void UnregisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName = nullptr);
+
+ /**
+ * Register an element to be upgraded when the custom element creation
+ * callback is executed.
+ *
+ * To be used when LookupCustomElementDefinition() didn't return a definition,
+ * but with the callback scheduled to be run.
+ */
+ inline void RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName = nullptr) {
+ if (mElementCreationCallbacksUpgradeCandidatesMap.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsAtom> typeName = aTypeName;
+ if (!typeName) {
+ typeName = aElement->NodeInfo()->NameAtom();
+ }
+
+ nsTHashSet<RefPtr<nsIWeakReference>>* elements =
+ mElementCreationCallbacksUpgradeCandidatesMap.Get(typeName);
+
+ // If there isn't a table, there won't be a definition added by the
+ // callback.
+ if (!elements) {
+ return;
+ }
+
+ nsWeakPtr elem = do_GetWeakReference(aElement);
+ elements->Insert(elem);
+ }
+
+ void TraceDefinitions(JSTracer* aTrc);
+
+ private:
+ ~CustomElementRegistry();
+
+ bool JSObjectToAtomArray(JSContext* aCx, JS::Handle<JSObject*> aConstructor,
+ const nsString& aName,
+ nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv);
+
+ void UpgradeCandidates(nsAtom* aKey, CustomElementDefinition* aDefinition,
+ ErrorResult& aRv);
+
+ typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, CustomElementDefinition>
+ DefinitionMap;
+ typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>,
+ CustomElementCreationCallback>
+ ElementCreationCallbackMap;
+ typedef nsClassHashtable<nsRefPtrHashKey<nsAtom>,
+ nsTHashSet<RefPtr<nsIWeakReference>>>
+ CandidateMap;
+ typedef JS::GCHashMap<JS::Heap<JSObject*>, RefPtr<nsAtom>,
+ js::StableCellHasher<JS::Heap<JSObject*>>,
+ js::SystemAllocPolicy>
+ ConstructorMap;
+
+ // Hashtable for custom element definitions in web components.
+ // Custom prototypes are stored in the compartment where definition was
+ // defined.
+ DefinitionMap mCustomDefinitions;
+
+ // Hashtable for chrome-only callbacks that is called *before* we return
+ // a CustomElementDefinition, when the typeAtom matches.
+ // The callbacks are registered with the setElementCreationCallback method.
+ ElementCreationCallbackMap mElementCreationCallbacks;
+
+ // Hashtable for looking up definitions by using constructor as key.
+ // Custom elements' name are stored here and we need to lookup
+ // mCustomDefinitions again to get definitions.
+ ConstructorMap mConstructors;
+
+ typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, Promise>
+ WhenDefinedPromiseMap;
+ WhenDefinedPromiseMap mWhenDefinedPromiseMap;
+
+ // The "upgrade candidates map" from the web components spec. Maps from a
+ // namespace id and local name to a list of elements to upgrade if that
+ // element is registered as a custom element.
+ CandidateMap mCandidatesMap;
+
+ // If an element creation callback is found, the nsTHashtable for the
+ // type is created here, and elements will later be upgraded.
+ CandidateMap mElementCreationCallbacksUpgradeCandidatesMap;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // It is used to prevent reentrant invocations of element definition.
+ bool mIsCustomDefinitionRunning;
+
+ private:
+ int32_t InferNamespace(JSContext* aCx, JS::Handle<JSObject*> constructor);
+
+ public:
+ nsISupports* GetParentObject() const;
+
+ DocGroup* GetDocGroup() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Define(JSContext* aCx, const nsAString& aName,
+ CustomElementConstructor& aFunctionConstructor,
+ const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
+
+ void Get(const nsAString& name,
+ OwningCustomElementConstructorOrUndefined& aRetVal);
+
+ already_AddRefed<Promise> WhenDefined(const nsAString& aName,
+ ErrorResult& aRv);
+
+ // Chrome-only method that give JS an opportunity to only load the custom
+ // element definition script when needed.
+ void SetElementCreationCallback(const nsAString& aName,
+ CustomElementCreationCallback& aCallback,
+ ErrorResult& aRv);
+
+ void Upgrade(nsINode& aRoot);
+};
+
+class MOZ_RAII AutoCEReaction final {
+ public:
+ // JSContext is allowed to be a nullptr if we are guaranteeing that we're
+ // not doing something that might throw but not finish reporting a JS
+ // exception during the lifetime of the AutoCEReaction.
+ AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx)
+ : mReactionsStack(aReactionsStack), mCx(aCx) {
+ mIsElementQueuePushedForPreviousRecursionDepth =
+ mReactionsStack->EnterCEReactions();
+ }
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because this is called from Maybe<>.reset().
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY ~AutoCEReaction() {
+ mReactionsStack->LeaveCEReactions(
+ mCx, mIsElementQueuePushedForPreviousRecursionDepth);
+ }
+
+ private:
+ const RefPtr<CustomElementReactionsStack> mReactionsStack;
+ JSContext* mCx;
+ bool mIsElementQueuePushedForPreviousRecursionDepth;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CustomElementRegistry_h
diff --git a/dom/base/DOMArena.h b/dom/base/DOMArena.h
new file mode 100644
index 0000000000..a25886d6d6
--- /dev/null
+++ b/dom/base/DOMArena.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=78:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#ifndef DOM_Arena_h___
+#define DOM_Arena_h___
+#include "nsISupportsImpl.h"
+#include "mozmemory.h"
+
+#include "mozilla/mozalloc_oom.h" // for mozalloc_handle_oom
+
+#define NS_DECL_DOMARENA_DESTROY void Destroy(void);
+
+#define NS_IMPL_DOMARENA_DESTROY(class) \
+ void class ::Destroy(void) { \
+ if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) { \
+ RefPtr<nsNodeInfoManager> nim = OwnerDoc()->NodeInfoManager(); \
+ RefPtr<DOMArena> arena = \
+ HasFlag(NODE_KEEPS_DOMARENA) \
+ ? nsContentUtils::TakeEntryFromDOMArenaTable(this) \
+ : nullptr; \
+ this->~class(); \
+ MOZ_ASSERT(nim, "nsNodeInfoManager needs to be initialized"); \
+ nim->Free(this); \
+ } else { \
+ delete this; \
+ } \
+ }
+
+namespace mozilla::dom {
+
+class DOMArena {
+ public:
+ friend class DocGroup;
+ DOMArena() {
+ arena_params_t params;
+ params.mMaxDirtyIncreaseOverride = 7;
+ mArenaId = moz_create_arena_with_params(&params);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(DOMArena)
+
+ void* Allocate(size_t aSize) {
+ void* ret = moz_arena_malloc(mArenaId, aSize);
+ if (!ret) {
+ mozalloc_handle_oom(aSize);
+ }
+ return ret;
+ }
+
+ private:
+ ~DOMArena() { moz_dispose_arena(mArenaId); }
+ arena_id_t mArenaId;
+};
+} // namespace mozilla::dom
+#endif
diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp
new file mode 100644
index 0000000000..d3e2fc42d3
--- /dev/null
+++ b/dom/base/DOMException.cpp
@@ -0,0 +1,448 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMException.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Exceptions.h"
+#include "nsContentUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Document.h"
+#include "nsIException.h"
+#include "xpcprivate.h"
+
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/ErrorResult.h"
+
+#include "js/TypeDecls.h"
+#include "js/StructuredClone.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+enum DOM4ErrorTypeCodeMap {
+ /* DOM4 errors from
+ http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#domexception */
+ IndexSizeError = DOMException_Binding::INDEX_SIZE_ERR,
+ HierarchyRequestError = DOMException_Binding::HIERARCHY_REQUEST_ERR,
+ WrongDocumentError = DOMException_Binding::WRONG_DOCUMENT_ERR,
+ InvalidCharacterError = DOMException_Binding::INVALID_CHARACTER_ERR,
+ NoModificationAllowedError =
+ DOMException_Binding::NO_MODIFICATION_ALLOWED_ERR,
+ NotFoundError = DOMException_Binding::NOT_FOUND_ERR,
+ NotSupportedError = DOMException_Binding::NOT_SUPPORTED_ERR,
+ // Can't remove until setNamedItem is removed
+ InUseAttributeError = DOMException_Binding::INUSE_ATTRIBUTE_ERR,
+ InvalidStateError = DOMException_Binding::INVALID_STATE_ERR,
+ SyntaxError = DOMException_Binding::SYNTAX_ERR,
+ InvalidModificationError = DOMException_Binding::INVALID_MODIFICATION_ERR,
+ NamespaceError = DOMException_Binding::NAMESPACE_ERR,
+ InvalidAccessError = DOMException_Binding::INVALID_ACCESS_ERR,
+ TypeMismatchError = DOMException_Binding::TYPE_MISMATCH_ERR,
+ SecurityError = DOMException_Binding::SECURITY_ERR,
+ NetworkError = DOMException_Binding::NETWORK_ERR,
+ AbortError = DOMException_Binding::ABORT_ERR,
+ URLMismatchError = DOMException_Binding::URL_MISMATCH_ERR,
+ QuotaExceededError = DOMException_Binding::QUOTA_EXCEEDED_ERR,
+ TimeoutError = DOMException_Binding::TIMEOUT_ERR,
+ InvalidNodeTypeError = DOMException_Binding::INVALID_NODE_TYPE_ERR,
+ DataCloneError = DOMException_Binding::DATA_CLONE_ERR,
+ EncodingError = 0,
+
+ /* IndexedDB errors
+ http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#exceptions */
+ UnknownError = 0,
+ ConstraintError = 0,
+ DataError = 0,
+ TransactionInactiveError = 0,
+ ReadOnlyError = 0,
+ VersionError = 0,
+
+ /* File API errors http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException */
+ NotReadableError = 0,
+
+ /* FileHandle API errors */
+ FileHandleInactiveError = 0,
+
+ /* WebCrypto errors
+ https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-DataError
+ */
+ OperationError = 0,
+
+ /* Push API errors */
+ NotAllowedError = 0,
+};
+
+#define DOM4_MSG_DEF(name, message, nsresult) \
+ {(nsresult), name, #name, message},
+#define DOM_MSG_DEF(val, message) \
+ {(val), NS_ERROR_GET_CODE(val), #val, message},
+
+static constexpr struct ResultStruct {
+ nsresult mNSResult;
+ uint16_t mCode;
+ const char* mName;
+ const char* mMessage;
+} sDOMErrorMsgMap[] = {
+#include "domerr.msg"
+};
+
+#undef DOM4_MSG_DEF
+#undef DOM_MSG_DEF
+
+static void NSResultToNameAndMessage(nsresult aNSResult, nsCString& aName,
+ nsCString& aMessage, uint16_t* aCode) {
+ aName.Truncate();
+ aMessage.Truncate();
+ *aCode = 0;
+ for (uint32_t idx = 0; idx < ArrayLength(sDOMErrorMsgMap); idx++) {
+ if (aNSResult == sDOMErrorMsgMap[idx].mNSResult) {
+ aName.Rebind(sDOMErrorMsgMap[idx].mName,
+ strlen(sDOMErrorMsgMap[idx].mName));
+ aMessage.Rebind(sDOMErrorMsgMap[idx].mMessage,
+ strlen(sDOMErrorMsgMap[idx].mMessage));
+ *aCode = sDOMErrorMsgMap[idx].mCode;
+ return;
+ }
+ }
+
+ NS_WARNING("Huh, someone is throwing non-DOM errors using the DOM module!");
+}
+
+nsresult NS_GetNameAndMessageForDOMNSResult(nsresult aNSResult,
+ nsACString& aName,
+ nsACString& aMessage,
+ uint16_t* aCode) {
+ nsCString name;
+ nsCString message;
+ uint16_t code = 0;
+ NSResultToNameAndMessage(aNSResult, name, message, &code);
+
+ if (!name.IsEmpty() && !message.IsEmpty()) {
+ aName = name;
+ aMessage = message;
+ if (aCode) {
+ *aCode = code;
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Exception)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(Exception)
+ NS_INTERFACE_MAP_ENTRY(nsIException)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(Exception,
+ (mLocation, mData),
+ (mThrownJSVal))
+
+Exception::Exception(const nsACString& aMessage, nsresult aResult,
+ const nsACString& aName, nsIStackFrame* aLocation,
+ nsISupports* aData)
+ : mMessage(aMessage),
+ mResult(aResult),
+ mName(aName),
+ mData(aData),
+ mHoldingJSVal(false) {
+ if (aLocation) {
+ mLocation = aLocation;
+ } else {
+ mLocation = GetCurrentJSStack();
+ // it is legal for there to be no active JS stack, if C++ code
+ // is operating on a JS-implemented interface pointer without
+ // having been called in turn by JS. This happens in the JS
+ // component loader.
+ }
+}
+
+Exception::Exception(nsCString&& aMessage, nsresult aResult, nsCString&& aName)
+ : mMessage(std::move(aMessage)),
+ mResult(aResult),
+ mName(std::move(aName)),
+ mHoldingJSVal(false) {}
+
+Exception::~Exception() {
+ if (mHoldingJSVal) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::DropJSObjects(this);
+ }
+}
+
+bool Exception::StealJSVal(JS::Value* aVp) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mHoldingJSVal) {
+ *aVp = mThrownJSVal;
+
+ mozilla::DropJSObjects(this);
+ mHoldingJSVal = false;
+ return true;
+ }
+
+ return false;
+}
+
+void Exception::StowJSVal(JS::Value& aVp) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mThrownJSVal = aVp;
+ if (!mHoldingJSVal) {
+ mozilla::HoldJSObjects(this);
+ mHoldingJSVal = true;
+ }
+}
+
+void Exception::GetName(nsAString& aName) {
+ if (!mName.IsEmpty()) {
+ CopyUTF8toUTF16(mName, aName);
+ } else {
+ aName.Truncate();
+
+ const char* name = nullptr;
+ nsXPCException::NameAndFormatForNSResult(mResult, &name, nullptr);
+
+ if (name) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(name), aName);
+ }
+ }
+}
+
+void Exception::GetFilename(JSContext* aCx, nsAString& aFilename) {
+ if (mLocation) {
+ mLocation->GetFilename(aCx, aFilename);
+ return;
+ }
+
+ aFilename.Truncate();
+}
+
+void Exception::ToString(JSContext* aCx, nsACString& _retval) {
+ static const char defaultMsg[] = "<no message>";
+ static const char defaultLocation[] = "<unknown>";
+ static const char format[] = "[Exception... \"%s\" nsresult: \"0x%" PRIx32
+ " (%s)\" location: \"%s\" data: %s]";
+
+ nsCString location;
+
+ if (mLocation) {
+ // we need to free this if it does not fail
+ mLocation->ToString(aCx, location);
+ }
+
+ if (location.IsEmpty()) {
+ location.Assign(defaultLocation);
+ }
+
+ const char* msg = mMessage.IsEmpty() ? nullptr : mMessage.get();
+
+ const char* resultName = mName.IsEmpty() ? nullptr : mName.get();
+ if (!resultName && !nsXPCException::NameAndFormatForNSResult(
+ mResult, &resultName, (!msg) ? &msg : nullptr)) {
+ if (!msg) {
+ msg = defaultMsg;
+ }
+ resultName = "<unknown>";
+ }
+ const char* data = mData ? "yes" : "no";
+
+ _retval.Truncate();
+ _retval.AppendPrintf(format, msg, static_cast<uint32_t>(mResult), resultName,
+ location.get(), data);
+}
+
+JSObject* Exception::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Exception_Binding::Wrap(cx, this, aGivenProto);
+}
+
+void Exception::GetMessageMoz(nsString& retval) {
+ CopyUTF8toUTF16(mMessage, retval);
+}
+
+uint32_t Exception::Result() const { return (uint32_t)mResult; }
+
+uint32_t Exception::SourceId(JSContext* aCx) const {
+ if (mLocation) {
+ return mLocation->GetSourceId(aCx);
+ }
+
+ return 0;
+}
+
+uint32_t Exception::LineNumber(JSContext* aCx) const {
+ if (mLocation) {
+ return mLocation->GetLineNumber(aCx);
+ }
+
+ return 0;
+}
+
+uint32_t Exception::ColumnNumber() const { return 0; }
+
+already_AddRefed<nsIStackFrame> Exception::GetLocation() const {
+ nsCOMPtr<nsIStackFrame> location = mLocation;
+ return location.forget();
+}
+
+nsISupports* Exception::GetData() const { return mData; }
+
+void Exception::GetStack(JSContext* aCx, nsAString& aStack) const {
+ if (mLocation) {
+ mLocation->GetFormattedStack(aCx, aStack);
+ }
+}
+
+void Exception::Stringify(JSContext* aCx, nsString& retval) {
+ nsCString str;
+ ToString(aCx, str);
+ CopyUTF8toUTF16(str, retval);
+}
+
+DOMException::DOMException(nsresult aRv, const nsACString& aMessage,
+ const nsACString& aName, uint16_t aCode,
+ nsIStackFrame* aLocation)
+ : Exception(aMessage, aRv, aName, aLocation, nullptr), mCode(aCode) {}
+DOMException::DOMException(nsresult aRv, nsCString&& aMessage,
+ nsCString&& aName, uint16_t aCode)
+ : Exception(std::move(aMessage), aRv, std::move(aName)), mCode(aCode) {}
+
+void DOMException::ToString(JSContext* aCx, nsACString& aReturn) {
+ aReturn.Truncate();
+
+ static const char defaultMsg[] = "<no message>";
+ static const char defaultLocation[] = "<unknown>";
+ static const char defaultName[] = "<unknown>";
+ static const char format[] =
+ "[Exception... \"%s\" code: \"%d\" nsresult: \"0x%" PRIx32
+ " (%s)\" location: \"%s\"]";
+
+ nsAutoCString location;
+
+ if (location.IsEmpty()) {
+ location = defaultLocation;
+ }
+
+ const char* msg = !mMessage.IsEmpty() ? mMessage.get() : defaultMsg;
+ const char* resultName = !mName.IsEmpty() ? mName.get() : defaultName;
+
+ aReturn.AppendPrintf(format, msg, mCode, static_cast<uint32_t>(mResult),
+ resultName, location.get());
+}
+
+void DOMException::GetName(nsString& retval) { CopyUTF8toUTF16(mName, retval); }
+
+already_AddRefed<DOMException> DOMException::Constructor(
+ GlobalObject& /* unused */, const nsAString& aMessage,
+ const Optional<nsAString>& aName) {
+ nsresult exceptionResult = NS_OK;
+ uint16_t exceptionCode = 0;
+ nsCString name("Error"_ns);
+
+ if (aName.WasPassed()) {
+ CopyUTF16toUTF8(aName.Value(), name);
+ for (uint32_t idx = 0; idx < ArrayLength(sDOMErrorMsgMap); idx++) {
+ if (name.EqualsASCII(sDOMErrorMsgMap[idx].mName)) {
+ exceptionResult = sDOMErrorMsgMap[idx].mNSResult;
+ exceptionCode = sDOMErrorMsgMap[idx].mCode;
+ break;
+ }
+ }
+ }
+
+ RefPtr<DOMException> retval = new DOMException(
+ exceptionResult, NS_ConvertUTF16toUTF8(aMessage), name, exceptionCode);
+ return retval.forget();
+}
+
+JSObject* DOMException::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMException_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<DOMException> DOMException::Create(nsresult aRv) {
+ nsCString name;
+ nsCString message;
+ uint16_t code;
+ NSResultToNameAndMessage(aRv, name, message, &code);
+ RefPtr<DOMException> inst = new DOMException(aRv, message, name, code);
+ return inst.forget();
+}
+
+/* static */
+already_AddRefed<DOMException> DOMException::Create(
+ nsresult aRv, const nsACString& aMessage) {
+ nsCString name;
+ nsCString message;
+ uint16_t code;
+ NSResultToNameAndMessage(aRv, name, message, &code);
+ RefPtr<DOMException> inst = new DOMException(aRv, aMessage, name, code);
+ return inst.forget();
+}
+
+static bool ReadAsCString(JSContext* aCx, JSStructuredCloneReader* aReader,
+ nsCString& aString) {
+ JS::Rooted<JSString*> jsMessage(aCx);
+ if (!JS_ReadString(aReader, &jsMessage)) {
+ return false;
+ }
+ return AssignJSString(aCx, aString, jsMessage);
+}
+
+already_AddRefed<DOMException> DOMException::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint32_t reserved;
+ nsresult rv;
+ nsCString message;
+ nsCString name;
+ uint16_t code;
+
+ if (!JS_ReadBytes(aReader, &reserved, 4) || !JS_ReadBytes(aReader, &rv, 4) ||
+ !ReadAsCString(aCx, aReader, message) ||
+ !ReadAsCString(aCx, aReader, name) || !JS_ReadBytes(aReader, &code, 2)) {
+ return nullptr;
+ };
+
+ return do_AddRef(
+ new DOMException(rv, std::move(message), std::move(name), code));
+}
+
+bool DOMException::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ JS::Rooted<JS::Value> messageValue(aCx);
+ JS::Rooted<JS::Value> nameValue(aCx);
+ if (!NonVoidByteStringToJsval(aCx, mMessage, &messageValue) ||
+ !NonVoidByteStringToJsval(aCx, mName, &nameValue)) {
+ return false;
+ }
+
+ JS::Rooted<JSString*> message(aCx, messageValue.toString());
+ JS::Rooted<JSString*> name(aCx, nameValue.toString());
+
+ static_assert(sizeof(nsresult) == 4);
+
+ // A reserved field. Use this to indicate stack serialization support etc.
+ uint32_t reserved = 0;
+ return JS_WriteBytes(aWriter, &reserved, 4) &&
+ JS_WriteBytes(aWriter, &mResult, 4) &&
+ JS_WriteString(aWriter, message) && JS_WriteString(aWriter, name) &&
+ JS_WriteBytes(aWriter, &mCode, 2);
+};
+
+} // namespace mozilla::dom
diff --git a/dom/base/DOMException.h b/dom/base/DOMException.h
new file mode 100644
index 0000000000..c44cf904a0
--- /dev/null
+++ b/dom/base/DOMException.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMException_h__
+#define mozilla_dom_DOMException_h__
+
+// We intentionally shadow non-virtual methods, but gcc gets confused.
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Woverloaded-virtual"
+#endif
+
+#include <stdint.h>
+#include "js/Value.h"
+#include "jspubtd.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsID.h"
+#include "nsWrapperCache.h"
+#include "nsIException.h"
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+class nsIGlobalObject;
+class nsIStackFrame;
+
+nsresult NS_GetNameAndMessageForDOMNSResult(nsresult aNSResult,
+ nsACString& aName,
+ nsACString& aMessage,
+ uint16_t* aCode = nullptr);
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+#define MOZILLA_EXCEPTION_IID \
+ { \
+ 0x55eda557, 0xeba0, 0x4fe3, { \
+ 0xae, 0x2e, 0xf3, 0x94, 0x49, 0x23, 0x62, 0xd6 \
+ } \
+ }
+
+class Exception : public nsIException, public nsWrapperCache {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_EXCEPTION_IID)
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Exception)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIEXCEPTION
+
+ const nsCString& GetMessageMoz() const { return mMessage; }
+ nsresult GetResult() const { return mResult; }
+ // DOMException wants different ToString behavior, so allow it to override.
+ virtual void ToString(JSContext* aCx, nsACString& aReturn);
+
+ // Cruft used by XPConnect for exceptions originating in JS implemented
+ // components.
+ bool StealJSVal(JS::Value* aVp);
+ void StowJSVal(JS::Value& aVp);
+
+ // WebIDL API
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ void GetMessageMoz(nsString& retval);
+
+ uint32_t Result() const;
+
+ void GetName(nsAString& retval);
+
+ virtual void GetErrorMessage(nsAString& aRetVal) {
+ // Since GetName is non non-virtual and deals with different
+ // member variables in Exception vs. DOMException, have a virtual
+ // method to ensure the right error message creation.
+ nsAutoString name;
+ GetName(name);
+ CreateErrorMessage(name, aRetVal);
+ }
+
+ void GetFilename(JSContext* aCx, nsAString& aFilename);
+
+ uint32_t SourceId(JSContext* aCx) const;
+
+ uint32_t LineNumber(JSContext* aCx) const;
+
+ uint32_t ColumnNumber() const;
+
+ already_AddRefed<nsIStackFrame> GetLocation() const;
+
+ nsISupports* GetData() const;
+
+ void GetStack(JSContext* aCx, nsAString& aStack) const;
+
+ void Stringify(JSContext* aCx, nsString& retval);
+
+ Exception(const nsACString& aMessage, nsresult aResult,
+ const nsACString& aName, nsIStackFrame* aLocation,
+ nsISupports* aData);
+ Exception(nsCString&& aMessage, nsresult aResult, nsCString&& aName);
+
+ protected:
+ virtual ~Exception();
+
+ void CreateErrorMessage(const nsAString& aName, nsAString& aRetVal) {
+ // Create similar error message as what ErrorReport::init does in jsexn.cpp.
+ if (!aName.IsEmpty() && !mMessage.IsEmpty()) {
+ aRetVal.Assign(aName);
+ aRetVal.AppendLiteral(": ");
+ AppendUTF8toUTF16(mMessage, aRetVal);
+ } else if (!aName.IsEmpty()) {
+ aRetVal.Assign(aName);
+ } else if (!mMessage.IsEmpty()) {
+ CopyUTF8toUTF16(mMessage, aRetVal);
+ } else {
+ aRetVal.Truncate();
+ }
+ }
+
+ nsCString mMessage;
+ nsresult mResult;
+ nsCString mName;
+ nsCOMPtr<nsIStackFrame> mLocation;
+ nsCOMPtr<nsISupports> mData;
+
+ bool mHoldingJSVal;
+ JS::Heap<JS::Value> mThrownJSVal;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Exception, MOZILLA_EXCEPTION_IID)
+
+class DOMException : public Exception {
+ public:
+ DOMException(nsresult aRv, const nsACString& aMessage,
+ const nsACString& aName, uint16_t aCode,
+ nsIStackFrame* aLocation = nullptr);
+ DOMException(nsresult aRv, nsCString&& aMessage, nsCString&& aName,
+ uint16_t aCode);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(DOMException, Exception)
+
+ // nsWrapperCache overrides
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMException> Constructor(
+ GlobalObject& /* unused */, const nsAString& aMessage,
+ const Optional<nsAString>& aName);
+
+ uint16_t Code() const { return mCode; }
+
+ // Intentionally shadow the Exception version.
+ void GetName(nsString& retval);
+
+ // Exception overrides
+ void ToString(JSContext* aCx, nsACString& aReturn) override;
+
+ virtual void GetErrorMessage(nsAString& aRetVal) override {
+ // See the comment in Exception::GetErrorMessage.
+ nsAutoString name;
+ GetName(name);
+ CreateErrorMessage(name, aRetVal);
+ }
+
+ static already_AddRefed<DOMException> Create(nsresult aRv);
+
+ static already_AddRefed<DOMException> Create(nsresult aRv,
+ const nsACString& aMessage);
+
+ static already_AddRefed<DOMException> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ protected:
+ virtual ~DOMException() = default;
+
+ uint16_t mCode;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+#endif
diff --git a/dom/base/DOMImplementation.cpp b/dom/base/DOMImplementation.cpp
new file mode 100644
index 0000000000..cc4d3d3da8
--- /dev/null
+++ b/dom/base/DOMImplementation.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMImplementation.h"
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/DOMImplementationBinding.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentType.h"
+#include "nsTextNode.h"
+
+namespace mozilla::dom {
+
+// QueryInterface implementation for DOMImplementation
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMImplementation)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMImplementation, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMImplementation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMImplementation)
+
+DOMImplementation::DOMImplementation(Document* aOwner,
+ nsIGlobalObject* aScriptObject,
+ nsIURI* aDocumentURI, nsIURI* aBaseURI)
+ : mOwner(aOwner),
+ mScriptObject(do_GetWeakReference(aScriptObject)),
+ mDocumentURI(aDocumentURI),
+ mBaseURI(aBaseURI) {
+ MOZ_ASSERT(aOwner);
+}
+
+DOMImplementation::~DOMImplementation() = default;
+
+JSObject* DOMImplementation::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMImplementation_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DocumentType> DOMImplementation::CreateDocumentType(
+ const nsAString& aQualifiedName, const nsAString& aPublicId,
+ const nsAString& aSystemId, ErrorResult& aRv) {
+ if (!mOwner) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ aRv = nsContentUtils::CheckQName(aQualifiedName);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<nsAtom> name = NS_Atomize(aQualifiedName);
+ if (!name) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ // Indicate that there is no internal subset (not just an empty one)
+ RefPtr<DocumentType> docType = NS_NewDOMDocumentType(
+ mOwner->NodeInfoManager(), name, aPublicId, aSystemId, VoidString());
+ return docType.forget();
+}
+
+nsresult DOMImplementation::CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ DocumentType* aDoctype,
+ Document** aDocument) {
+ *aDocument = nullptr;
+
+ nsresult rv;
+ if (!aQualifiedName.IsEmpty()) {
+ const nsString& qName = PromiseFlatString(aQualifiedName);
+ const char16_t* colon;
+ rv = nsContentUtils::CheckQName(qName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (colon && (DOMStringIsNull(aNamespaceURI) ||
+ (Substring(qName.get(), colon).EqualsLiteral("xml") &&
+ !aNamespaceURI.EqualsLiteral(
+ "http://www.w3.org/XML/1998/namespace")))) {
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+ }
+ }
+
+ nsCOMPtr<nsIGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScriptObject);
+
+ NS_ENSURE_STATE(!mScriptObject || scriptHandlingObject);
+
+ nsCOMPtr<Document> doc;
+
+ rv = NS_NewDOMDocument(getter_AddRefs(doc), aNamespaceURI, aQualifiedName,
+ aDoctype, mDocumentURI, mBaseURI,
+ mOwner->NodePrincipal(), true, scriptHandlingObject,
+ DocumentFlavorXML);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // When DOMImplementation's createDocument method is invoked with
+ // namespace set to HTML Namespace use the registry of the associated
+ // document to the new instance.
+
+ if (aNamespaceURI.EqualsLiteral("http://www.w3.org/1999/xhtml")) {
+ doc->SetContentType("application/xhtml+xml"_ns);
+ } else if (aNamespaceURI.EqualsLiteral("http://www.w3.org/2000/svg")) {
+ doc->SetContentType("image/svg+xml"_ns);
+ } else {
+ doc->SetContentType("application/xml"_ns);
+ }
+
+ doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+
+ doc.forget(aDocument);
+ return NS_OK;
+}
+
+already_AddRefed<Document> DOMImplementation::CreateDocument(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ DocumentType* aDoctype, ErrorResult& aRv) {
+ nsCOMPtr<Document> document;
+ aRv = CreateDocument(aNamespaceURI, aQualifiedName, aDoctype,
+ getter_AddRefs(document));
+ return document.forget();
+}
+
+nsresult DOMImplementation::CreateHTMLDocument(const nsAString& aTitle,
+ Document** aDocument) {
+ *aDocument = nullptr;
+
+ NS_ENSURE_STATE(mOwner);
+
+ // Indicate that there is no internal subset (not just an empty one)
+ RefPtr<DocumentType> doctype =
+ NS_NewDOMDocumentType(mOwner->NodeInfoManager(),
+ nsGkAtoms::html, // aName
+ u""_ns, // aPublicId
+ u""_ns, // aSystemId
+ VoidString()); // aInternalSubset
+
+ nsCOMPtr<nsIGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScriptObject);
+
+ NS_ENSURE_STATE(!mScriptObject || scriptHandlingObject);
+
+ nsCOMPtr<Document> doc;
+ nsresult rv =
+ NS_NewDOMDocument(getter_AddRefs(doc), u""_ns, u""_ns, doctype,
+ mDocumentURI, mBaseURI, mOwner->NodePrincipal(), true,
+ scriptHandlingObject, DocumentFlavorLegacyGuess);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+ nsCOMPtr<Element> root =
+ doc->CreateElem(u"html"_ns, nullptr, kNameSpaceID_XHTML);
+ doc->AppendChildTo(root, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ nsCOMPtr<Element> head =
+ doc->CreateElem(u"head"_ns, nullptr, kNameSpaceID_XHTML);
+ root->AppendChildTo(head, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ if (!DOMStringIsNull(aTitle)) {
+ nsCOMPtr<Element> title =
+ doc->CreateElem(u"title"_ns, nullptr, kNameSpaceID_XHTML);
+ head->AppendChildTo(title, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ RefPtr<nsTextNode> titleText =
+ new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
+ rv = titleText->SetText(aTitle, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ title->AppendChildTo(titleText, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ }
+
+ nsCOMPtr<Element> body =
+ doc->CreateElem(u"body"_ns, nullptr, kNameSpaceID_XHTML);
+ root->AppendChildTo(body, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+
+ doc.forget(aDocument);
+ return NS_OK;
+}
+
+already_AddRefed<Document> DOMImplementation::CreateHTMLDocument(
+ const Optional<nsAString>& aTitle, ErrorResult& aRv) {
+ nsCOMPtr<Document> document;
+ aRv = CreateHTMLDocument(aTitle.WasPassed() ? aTitle.Value() : VoidString(),
+ getter_AddRefs(document));
+ return document.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DOMImplementation.h b/dom/base/DOMImplementation.h
new file mode 100644
index 0000000000..b42538741b
--- /dev/null
+++ b/dom/base/DOMImplementation.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMImplementation_h
+#define mozilla_dom_DOMImplementation_h
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIURI.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class Document;
+class DocumentType;
+template <typename T>
+class Optional;
+
+class DOMImplementation final : public nsISupports, public nsWrapperCache {
+ ~DOMImplementation();
+
+ public:
+ DOMImplementation(Document* aOwner, nsIGlobalObject* aScriptObject,
+ nsIURI* aDocumentURI, nsIURI* aBaseURI);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMImplementation)
+
+ Document* GetParentObject() const { return mOwner; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasFeature() { return true; }
+
+ already_AddRefed<DocumentType> CreateDocumentType(
+ const nsAString& aQualifiedName, const nsAString& aPublicId,
+ const nsAString& aSystemId, ErrorResult& aRv);
+
+ already_AddRefed<Document> CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ DocumentType* aDoctype,
+ ErrorResult& aRv);
+
+ already_AddRefed<Document> CreateHTMLDocument(
+ const Optional<nsAString>& aTitle, ErrorResult& aRv);
+
+ private:
+ nsresult CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ DocumentType* aDoctype, Document** aDocument);
+ nsresult CreateHTMLDocument(const nsAString& aTitle, Document** aDocument);
+
+ nsCOMPtr<Document> mOwner;
+ nsWeakPtr mScriptObject;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DOMImplementation_h
diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp
new file mode 100644
index 0000000000..9b34e261c4
--- /dev/null
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -0,0 +1,839 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DOMIntersectionObserver.h"
+#include "nsCSSPropertyID.h"
+#include "nsIFrame.h"
+#include "nsContainerFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsRefreshDriver.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "Units.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserverEntry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserverEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserverEntry)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMIntersectionObserverEntry, mOwner,
+ mRootBounds, mBoundingClientRect,
+ mIntersectionRect, mTarget)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(DOMIntersectionObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserver)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DOMIntersectionObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMIntersectionObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMIntersectionObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ if (tmp->mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
+ ImplCycleCollectionUnlink(
+ tmp->mCallback.as<RefPtr<dom::IntersectionCallback>>());
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMIntersectionObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ if (tmp->mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
+ ImplCycleCollectionTraverse(
+ cb, tmp->mCallback.as<RefPtr<dom::IntersectionCallback>>(), "mCallback",
+ 0);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+DOMIntersectionObserver::DOMIntersectionObserver(
+ already_AddRefed<nsPIDOMWindowInner>&& aOwner,
+ dom::IntersectionCallback& aCb)
+ : mOwner(aOwner),
+ mDocument(mOwner->GetExtantDoc()),
+ mCallback(RefPtr<dom::IntersectionCallback>(&aCb)),
+ mConnected(false) {}
+
+already_AddRefed<DOMIntersectionObserver> DOMIntersectionObserver::Constructor(
+ const GlobalObject& aGlobal, dom::IntersectionCallback& aCb,
+ ErrorResult& aRv) {
+ return Constructor(aGlobal, aCb, IntersectionObserverInit(), aRv);
+}
+
+already_AddRefed<DOMIntersectionObserver> DOMIntersectionObserver::Constructor(
+ const GlobalObject& aGlobal, dom::IntersectionCallback& aCb,
+ const IntersectionObserverInit& aOptions, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ RefPtr<DOMIntersectionObserver> observer =
+ new DOMIntersectionObserver(window.forget(), aCb);
+
+ if (!aOptions.mRoot.IsNull()) {
+ if (aOptions.mRoot.Value().IsElement()) {
+ observer->mRoot = aOptions.mRoot.Value().GetAsElement();
+ } else {
+ MOZ_ASSERT(aOptions.mRoot.Value().IsDocument());
+ observer->mRoot = aOptions.mRoot.Value().GetAsDocument();
+ }
+ }
+
+ if (!observer->SetRootMargin(aOptions.mRootMargin)) {
+ aRv.ThrowSyntaxError("rootMargin must be specified in pixels or percent.");
+ return nullptr;
+ }
+
+ if (aOptions.mThreshold.IsDoubleSequence()) {
+ const Sequence<double>& thresholds =
+ aOptions.mThreshold.GetAsDoubleSequence();
+ observer->mThresholds.SetCapacity(thresholds.Length());
+ for (const auto& thresh : thresholds) {
+ if (thresh < 0.0 || thresh > 1.0) {
+ aRv.ThrowRangeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
+ return nullptr;
+ }
+ observer->mThresholds.AppendElement(thresh);
+ }
+ observer->mThresholds.Sort();
+ } else {
+ double thresh = aOptions.mThreshold.GetAsDouble();
+ if (thresh < 0.0 || thresh > 1.0) {
+ aRv.ThrowRangeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
+ return nullptr;
+ }
+ observer->mThresholds.AppendElement(thresh);
+ }
+
+ return observer.forget();
+}
+
+static void LazyLoadCallback(
+ const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
+ for (const auto& entry : aEntries) {
+ MOZ_ASSERT(entry->Target()->IsHTMLElement(nsGkAtoms::img));
+ if (entry->IsIntersecting()) {
+ static_cast<HTMLImageElement*>(entry->Target())
+ ->StopLazyLoading(HTMLImageElement::StartLoading::Yes);
+ }
+ }
+}
+
+static void ContentVisibilityCallback(
+ const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
+ for (const auto& entry : aEntries) {
+ entry->Target()->SetVisibleForContentVisibility(entry->IsIntersecting());
+
+ if (RefPtr<Document> doc = entry->Target()->GetComposedDoc()) {
+ if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
+ presShell->ScheduleContentRelevancyUpdate(
+ ContentRelevancyReason::Visible);
+ }
+ }
+ }
+}
+
+static LengthPercentage PrefMargin(float aValue, bool aIsPercentage) {
+ return aIsPercentage ? LengthPercentage::FromPercentage(aValue / 100.0f)
+ : LengthPercentage::FromPixels(aValue);
+}
+
+DOMIntersectionObserver::DOMIntersectionObserver(Document& aDocument,
+ NativeCallback aCallback)
+ : mOwner(aDocument.GetInnerWindow()),
+ mDocument(&aDocument),
+ mCallback(aCallback),
+ mConnected(false) {}
+
+already_AddRefed<DOMIntersectionObserver>
+DOMIntersectionObserver::CreateLazyLoadObserver(Document& aDocument) {
+ RefPtr<DOMIntersectionObserver> observer =
+ new DOMIntersectionObserver(aDocument, LazyLoadCallback);
+ observer->mThresholds.AppendElement(0.0f);
+
+#define SET_MARGIN(side_, side_lower_) \
+ observer->mRootMargin.Get(eSide##side_) = PrefMargin( \
+ StaticPrefs::dom_image_lazy_loading_root_margin_##side_lower_(), \
+ StaticPrefs:: \
+ dom_image_lazy_loading_root_margin_##side_lower_##_percentage());
+ SET_MARGIN(Top, top);
+ SET_MARGIN(Right, right);
+ SET_MARGIN(Bottom, bottom);
+ SET_MARGIN(Left, left);
+#undef SET_MARGIN
+
+ return observer.forget();
+}
+
+already_AddRefed<DOMIntersectionObserver>
+DOMIntersectionObserver::CreateContentVisibilityObserver(Document& aDocument) {
+ RefPtr<DOMIntersectionObserver> observer =
+ new DOMIntersectionObserver(aDocument, ContentVisibilityCallback);
+
+ observer->mThresholds.AppendElement(0.0f);
+
+ auto margin = LengthPercentage::FromPercentage(
+ StaticPrefs::layout_css_content_visibility_relevant_content_margin() /
+ 100.0f);
+
+ observer->mRootMargin.Get(eSideTop) = margin;
+ observer->mRootMargin.Get(eSideRight) = margin;
+ observer->mRootMargin.Get(eSideBottom) = margin;
+ observer->mRootMargin.Get(eSideLeft) = margin;
+
+ return observer.forget();
+}
+
+bool DOMIntersectionObserver::SetRootMargin(const nsACString& aString) {
+ return Servo_IntersectionObserverRootMargin_Parse(&aString, &mRootMargin);
+}
+
+nsISupports* DOMIntersectionObserver::GetParentObject() const { return mOwner; }
+
+void DOMIntersectionObserver::GetRootMargin(nsACString& aRetVal) {
+ Servo_IntersectionObserverRootMargin_ToString(&mRootMargin, &aRetVal);
+}
+
+void DOMIntersectionObserver::GetThresholds(nsTArray<double>& aRetVal) {
+ aRetVal = mThresholds.Clone();
+}
+
+void DOMIntersectionObserver::Observe(Element& aTarget) {
+ if (!mObservationTargetSet.EnsureInserted(&aTarget)) {
+ return;
+ }
+ aTarget.RegisterIntersectionObserver(this);
+ mObservationTargets.AppendElement(&aTarget);
+
+ MOZ_ASSERT(mObservationTargets.Length() == mObservationTargetSet.Count());
+
+ Connect();
+ if (mDocument) {
+ if (nsPresContext* pc = mDocument->GetPresContext()) {
+ pc->RefreshDriver()->EnsureIntersectionObservationsUpdateHappens();
+ }
+ }
+}
+
+void DOMIntersectionObserver::Unobserve(Element& aTarget) {
+ if (!mObservationTargetSet.EnsureRemoved(&aTarget)) {
+ return;
+ }
+
+ mObservationTargets.RemoveElement(&aTarget);
+ aTarget.UnregisterIntersectionObserver(this);
+
+ MOZ_ASSERT(mObservationTargets.Length() == mObservationTargetSet.Count());
+
+ if (mObservationTargets.IsEmpty()) {
+ Disconnect();
+ }
+}
+
+void DOMIntersectionObserver::UnlinkTarget(Element& aTarget) {
+ mObservationTargets.RemoveElement(&aTarget);
+ mObservationTargetSet.Remove(&aTarget);
+ if (mObservationTargets.IsEmpty()) {
+ Disconnect();
+ }
+}
+
+void DOMIntersectionObserver::Connect() {
+ if (mConnected) {
+ return;
+ }
+
+ mConnected = true;
+ if (mDocument) {
+ mDocument->AddIntersectionObserver(this);
+ }
+}
+
+void DOMIntersectionObserver::Disconnect() {
+ if (!mConnected) {
+ return;
+ }
+
+ mConnected = false;
+ for (Element* target : mObservationTargets) {
+ target->UnregisterIntersectionObserver(this);
+ }
+ mObservationTargets.Clear();
+ mObservationTargetSet.Clear();
+ if (mDocument) {
+ mDocument->RemoveIntersectionObserver(this);
+ }
+}
+
+void DOMIntersectionObserver::TakeRecords(
+ nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal) {
+ aRetVal = std::move(mQueuedEntries);
+}
+
+static Maybe<nsRect> EdgeInclusiveIntersection(const nsRect& aRect,
+ const nsRect& aOtherRect) {
+ nscoord left = std::max(aRect.x, aOtherRect.x);
+ nscoord top = std::max(aRect.y, aOtherRect.y);
+ nscoord right = std::min(aRect.XMost(), aOtherRect.XMost());
+ nscoord bottom = std::min(aRect.YMost(), aOtherRect.YMost());
+ if (left > right || top > bottom) {
+ return Nothing();
+ }
+ return Some(nsRect(left, top, right - left, bottom - top));
+}
+
+enum class BrowsingContextOrigin { Similar, Different };
+
+// NOTE(emilio): Checking docgroup as per discussion in:
+// https://github.com/w3c/IntersectionObserver/issues/161
+static BrowsingContextOrigin SimilarOrigin(const Element& aTarget,
+ const nsINode* aRoot) {
+ if (!aRoot) {
+ return BrowsingContextOrigin::Different;
+ }
+ return aTarget.OwnerDoc()->GetDocGroup() == aRoot->OwnerDoc()->GetDocGroup()
+ ? BrowsingContextOrigin::Similar
+ : BrowsingContextOrigin::Different;
+}
+
+// NOTE: This returns nullptr if |aDocument| is in another process from the top
+// level content document.
+static const Document* GetTopLevelContentDocumentInThisProcess(
+ const Document& aDocument) {
+ auto* wc = aDocument.GetTopLevelWindowContext();
+ return wc ? wc->GetExtantDoc() : nullptr;
+}
+
+// https://w3c.github.io/IntersectionObserver/#compute-the-intersection
+//
+// TODO(emilio): Proof of this being equivalent to the spec welcome, seems
+// reasonably close.
+//
+// Also, it's unclear to me why the spec talks about browsing context while
+// discarding observations of targets of different documents.
+//
+// Both aRootBounds and the return value are relative to
+// nsLayoutUtils::GetContainingBlockForClientRect(aRoot).
+//
+// In case of out-of-process document, aRemoteDocumentVisibleRect is a rectangle
+// in the out-of-process document's coordinate system.
+static Maybe<nsRect> ComputeTheIntersection(
+ nsIFrame* aTarget, nsIFrame* aRoot, const nsRect& aRootBounds,
+ const Maybe<nsRect>& aRemoteDocumentVisibleRect) {
+ nsIFrame* target = aTarget;
+ // 1. Let intersectionRect be the result of running the
+ // getBoundingClientRect() algorithm on the target.
+ //
+ // `intersectionRect` is kept relative to `target` during the loop.
+ Maybe<nsRect> intersectionRect = Some(nsLayoutUtils::GetAllInFlowRectsUnion(
+ target, target, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+
+ // 2. Let container be the containing block of the target.
+ // (We go through the parent chain and only look at scroll frames)
+ //
+ // FIXME(emilio): Spec uses containing blocks, we use scroll frames, but we
+ // only apply overflow-clipping, not clip-path, so it's ~fine. We do need to
+ // apply clip-path.
+ //
+ // 3. While container is not the intersection root:
+ nsIFrame* containerFrame =
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(target);
+ while (containerFrame && containerFrame != aRoot) {
+ // FIXME(emilio): What about other scroll frames that inherit from
+ // nsHTMLScrollFrame but have a different type, like nsListControlFrame?
+ // This looks bogus in that case, but different bug.
+ if (nsIScrollableFrame* scrollFrame = do_QueryFrame(containerFrame)) {
+ if (containerFrame->GetParent() == aRoot && !aRoot->GetParent()) {
+ // This is subtle: if we're computing the intersection against the
+ // viewport (the root frame), and this is its scroll frame, we really
+ // want to skip this intersection (because we want to account for the
+ // root margin, which is already in aRootBounds).
+ break;
+ }
+ nsRect subFrameRect = scrollFrame->GetScrollPortRect();
+
+ // 3.1 Map intersectionRect to the coordinate space of container.
+ nsRect intersectionRectRelativeToContainer =
+ nsLayoutUtils::TransformFrameRectToAncestor(
+ target, intersectionRect.value(), containerFrame);
+
+ // 3.2 If container has overflow clipping or a css clip-path property,
+ // update intersectionRect by applying container's clip.
+ //
+ // 3.3 is handled, looks like, by this same clipping, given the root
+ // scroll-frame cannot escape the viewport, probably?
+ intersectionRect = EdgeInclusiveIntersection(
+ intersectionRectRelativeToContainer, subFrameRect);
+ if (!intersectionRect) {
+ return Nothing();
+ }
+ target = containerFrame;
+ } else {
+ const auto& disp = *containerFrame->StyleDisplay();
+ auto clipAxes = containerFrame->ShouldApplyOverflowClipping(&disp);
+ // 3.2 TODO: Apply clip-path.
+ if (clipAxes != PhysicalAxes::None) {
+ // 3.1 Map intersectionRect to the coordinate space of container.
+ const nsRect intersectionRectRelativeToContainer =
+ nsLayoutUtils::TransformFrameRectToAncestor(
+ target, intersectionRect.value(), containerFrame);
+ const nsRect clipRect = OverflowAreas::GetOverflowClipRect(
+ intersectionRectRelativeToContainer,
+ containerFrame->GetRectRelativeToSelf(), clipAxes,
+ containerFrame->OverflowClipMargin(clipAxes));
+ intersectionRect = EdgeInclusiveIntersection(
+ intersectionRectRelativeToContainer, clipRect);
+ if (!intersectionRect) {
+ return Nothing();
+ }
+ target = containerFrame;
+ }
+ }
+ containerFrame =
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(containerFrame);
+ }
+ MOZ_ASSERT(intersectionRect);
+
+ // 4. Map intersectionRect to the coordinate space of the intersection root.
+ nsRect intersectionRectRelativeToRoot =
+ nsLayoutUtils::TransformFrameRectToAncestor(
+ target, intersectionRect.value(),
+ nsLayoutUtils::GetContainingBlockForClientRect(aRoot));
+
+ // 5.Update intersectionRect by intersecting it with the root intersection
+ // rectangle.
+ intersectionRect =
+ EdgeInclusiveIntersection(intersectionRectRelativeToRoot, aRootBounds);
+ if (intersectionRect.isNothing()) {
+ return Nothing();
+ }
+ // 6. Map intersectionRect to the coordinate space of the viewport of the
+ // Document containing the target.
+ //
+ // FIXME(emilio): I think this may not be correct if the root is explicit
+ // and in the same document, since then the rectangle may not be relative to
+ // the viewport already (but it's in the same document).
+ nsRect rect = intersectionRect.value();
+ if (aTarget->PresContext() != aRoot->PresContext()) {
+ if (nsIFrame* rootScrollFrame =
+ aTarget->PresShell()->GetRootScrollFrame()) {
+ nsLayoutUtils::TransformRect(aRoot, rootScrollFrame, rect);
+ }
+ }
+
+ // In out-of-process iframes we need to take an intersection with the remote
+ // document visible rect which was already clipped by ancestor document's
+ // viewports.
+ if (aRemoteDocumentVisibleRect) {
+ MOZ_ASSERT(aRoot->PresContext()->IsRootContentDocumentInProcess() &&
+ !aRoot->PresContext()->IsRootContentDocumentCrossProcess());
+
+ intersectionRect =
+ EdgeInclusiveIntersection(rect, *aRemoteDocumentVisibleRect);
+ if (intersectionRect.isNothing()) {
+ return Nothing();
+ }
+ rect = intersectionRect.value();
+ }
+
+ return Some(rect);
+}
+
+struct OopIframeMetrics {
+ nsIFrame* mInProcessRootFrame = nullptr;
+ nsRect mInProcessRootRect;
+ nsRect mRemoteDocumentVisibleRect;
+};
+
+static Maybe<OopIframeMetrics> GetOopIframeMetrics(
+ const Document& aDocument, const Document* aRootDocument) {
+ const Document* rootDoc =
+ nsContentUtils::GetInProcessSubtreeRootDocument(&aDocument);
+ MOZ_ASSERT(rootDoc);
+
+ if (rootDoc->IsTopLevelContentDocument()) {
+ return Nothing();
+ }
+
+ if (aRootDocument &&
+ rootDoc ==
+ nsContentUtils::GetInProcessSubtreeRootDocument(aRootDocument)) {
+ // aRootDoc, if non-null, is either the implicit root
+ // (top-level-content-document) or a same-origin document passed explicitly.
+ //
+ // In the former case, we should've returned above if there are no iframes
+ // in between. This condition handles the explicit, same-origin root
+ // document, when both are embedded in an OOP iframe.
+ return Nothing();
+ }
+
+ PresShell* rootPresShell = rootDoc->GetPresShell();
+ if (!rootPresShell || rootPresShell->IsDestroying()) {
+ return Some(OopIframeMetrics{});
+ }
+
+ nsIFrame* inProcessRootFrame = rootPresShell->GetRootFrame();
+ if (!inProcessRootFrame) {
+ return Some(OopIframeMetrics{});
+ }
+
+ BrowserChild* browserChild = BrowserChild::GetFrom(rootDoc->GetDocShell());
+ if (!browserChild) {
+ return Some(OopIframeMetrics{});
+ }
+
+ if (MOZ_UNLIKELY(browserChild->IsTopLevel())) {
+ // FIXME(bug 1772083): This can be hit, but it's unclear how... When can we
+ // have a top-level BrowserChild for a document that isn't a top-level
+ // content document?
+ MOZ_ASSERT_UNREACHABLE("Top level BrowserChild w/ non-top level Document?");
+ return Nothing();
+ }
+
+ nsRect inProcessRootRect;
+ if (nsIScrollableFrame* scrollFrame =
+ rootPresShell->GetRootScrollFrameAsScrollable()) {
+ inProcessRootRect = scrollFrame->GetScrollPortRect();
+ }
+
+ Maybe<LayoutDeviceRect> remoteDocumentVisibleRect =
+ browserChild->GetTopLevelViewportVisibleRectInSelfCoords();
+ if (!remoteDocumentVisibleRect) {
+ return Some(OopIframeMetrics{});
+ }
+
+ return Some(OopIframeMetrics{
+ inProcessRootFrame,
+ inProcessRootRect,
+ LayoutDeviceRect::ToAppUnits(
+ *remoteDocumentVisibleRect,
+ rootPresShell->GetPresContext()->AppUnitsPerDevPixel()),
+ });
+}
+
+// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
+// step 2.1
+IntersectionInput DOMIntersectionObserver::ComputeInput(
+ const Document& aDocument, const nsINode* aRoot,
+ const StyleRect<LengthPercentage>* aRootMargin) {
+ // 1 - Let rootBounds be observer's root intersection rectangle.
+ // ... but since the intersection rectangle depends on the target, we defer
+ // the inflation until later.
+ // NOTE: |rootRect| and |rootFrame| will be root in the same process. In
+ // out-of-process iframes, they are NOT root ones of the top level content
+ // document.
+ nsRect rootRect;
+ nsIFrame* rootFrame = nullptr;
+ const nsINode* root = aRoot;
+ const bool isImplicitRoot = !aRoot;
+ Maybe<nsRect> remoteDocumentVisibleRect;
+ if (aRoot && aRoot->IsElement()) {
+ if ((rootFrame = aRoot->AsElement()->GetPrimaryFrame())) {
+ nsRect rootRectRelativeToRootFrame;
+ if (nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame)) {
+ // rootRectRelativeToRootFrame should be the content rect of rootFrame,
+ // not including the scrollbars.
+ rootRectRelativeToRootFrame = scrollFrame->GetScrollPortRect();
+ } else {
+ // rootRectRelativeToRootFrame should be the border rect of rootFrame.
+ rootRectRelativeToRootFrame = rootFrame->GetRectRelativeToSelf();
+ }
+ nsIFrame* containingBlock =
+ nsLayoutUtils::GetContainingBlockForClientRect(rootFrame);
+ rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ rootFrame, rootRectRelativeToRootFrame, containingBlock);
+ }
+ } else {
+ MOZ_ASSERT(!aRoot || aRoot->IsDocument());
+ const Document* rootDocument =
+ aRoot ? aRoot->AsDocument()
+ : GetTopLevelContentDocumentInThisProcess(aDocument);
+ root = rootDocument;
+
+ if (rootDocument) {
+ // We're in the same process as the root document, though note that there
+ // could be an out-of-process iframe in between us and the root. Grab the
+ // root frame and the root rect.
+ //
+ // Note that the root rect is always good (we assume no DPI changes in
+ // between the two documents, and we don't need to convert coordinates).
+ //
+ // The root frame however we may need to tweak in the block below, if
+ // there's any OOP iframe in between `rootDocument` and `aDocument`, to
+ // handle the OOP iframe positions.
+ if (PresShell* presShell = rootDocument->GetPresShell()) {
+ rootFrame = presShell->GetRootFrame();
+ // We use the root scrollable frame's scroll port to account the
+ // scrollbars in rootRect, if needed.
+ if (nsIScrollableFrame* scrollFrame =
+ presShell->GetRootScrollFrameAsScrollable()) {
+ rootRect = scrollFrame->GetScrollPortRect();
+ } else if (rootFrame) {
+ rootRect = rootFrame->GetRectRelativeToSelf();
+ }
+ }
+ }
+
+ if (Maybe<OopIframeMetrics> metrics =
+ GetOopIframeMetrics(aDocument, rootDocument)) {
+ rootFrame = metrics->mInProcessRootFrame;
+ if (!rootDocument) {
+ rootRect = metrics->mInProcessRootRect;
+ }
+ remoteDocumentVisibleRect = Some(metrics->mRemoteDocumentVisibleRect);
+ }
+ }
+
+ nsMargin rootMargin; // This root margin is NOT applied in `implicit root`
+ // case, e.g. in out-of-process iframes.
+ if (aRootMargin) {
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ nscoord basis = side == eSideTop || side == eSideBottom
+ ? rootRect.Height()
+ : rootRect.Width();
+ rootMargin.Side(side) = aRootMargin->Get(side).Resolve(
+ basis, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
+ }
+ }
+ return {isImplicitRoot, root, rootFrame,
+ rootRect, rootMargin, remoteDocumentVisibleRect};
+}
+
+// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
+// (steps 2.1 - 2.5)
+IntersectionOutput DOMIntersectionObserver::Intersect(
+ const IntersectionInput& aInput, Element& aTarget,
+ IgnoreContentVisibility aIgnoreContentVisibility) {
+ const bool isSimilarOrigin = SimilarOrigin(aTarget, aInput.mRootNode) ==
+ BrowsingContextOrigin::Similar;
+ nsIFrame* targetFrame = aTarget.GetPrimaryFrame();
+ if (!targetFrame || !aInput.mRootFrame) {
+ return {isSimilarOrigin};
+ }
+
+ // "From the perspective of an IntersectionObserver, the skipped contents
+ // of an element are never intersecting the intersection root. This is
+ // true even if both the root and the target elements are in the skipped
+ // contents."
+ // https://drafts.csswg.org/css-contain/#cv-notes
+ //
+ // Skip the intersection if the element is hidden, unless this is the
+ // DOMIntersectionObserver used specifically to track the visibility of
+ // `content-visibility: auto` elements.
+ if (aIgnoreContentVisibility == IgnoreContentVisibility::No &&
+ targetFrame->IsHiddenByContentVisibilityOnAnyAncestor()) {
+ return {isSimilarOrigin};
+ }
+
+ // 2.2. If the intersection root is not the implicit root, and target is
+ // not in the same Document as the intersection root, skip to step 11.
+ if (!aInput.mIsImplicitRoot &&
+ aInput.mRootNode->OwnerDoc() != aTarget.OwnerDoc()) {
+ return {isSimilarOrigin};
+ }
+
+ // 2.3. If the intersection root is an element and target is not a descendant
+ // of the intersection root in the containing block chain, skip to step 11.
+ //
+ // NOTE(emilio): We also do this if target is the implicit root, pending
+ // clarification in
+ // https://github.com/w3c/IntersectionObserver/issues/456.
+ if (aInput.mRootFrame == targetFrame ||
+ !nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aInput.mRootFrame,
+ targetFrame)) {
+ return {isSimilarOrigin};
+ }
+
+ nsRect rootBounds = aInput.mRootRect;
+ if (isSimilarOrigin) {
+ rootBounds.Inflate(aInput.mRootMargin);
+ }
+
+ // 2.4. Set targetRect to the DOMRectReadOnly obtained by running the
+ // getBoundingClientRect() algorithm on target.
+ nsRect targetRect = targetFrame->GetBoundingClientRect();
+
+ // 2.5. Let intersectionRect be the result of running the compute the
+ // intersection algorithm on target and observer’s intersection root.
+ Maybe<nsRect> intersectionRect =
+ ComputeTheIntersection(targetFrame, aInput.mRootFrame, rootBounds,
+ aInput.mRemoteDocumentVisibleRect);
+
+ return {isSimilarOrigin, rootBounds, targetRect, intersectionRect};
+}
+
+IntersectionOutput DOMIntersectionObserver::Intersect(
+ const IntersectionInput& aInput, const nsRect& aTargetRect) {
+ nsRect rootBounds = aInput.mRootRect;
+ rootBounds.Inflate(aInput.mRootMargin);
+ auto intersectionRect =
+ EdgeInclusiveIntersection(aInput.mRootRect, aTargetRect);
+ if (intersectionRect && aInput.mRemoteDocumentVisibleRect) {
+ intersectionRect = EdgeInclusiveIntersection(
+ *intersectionRect, *aInput.mRemoteDocumentVisibleRect);
+ }
+ return {true, rootBounds, aTargetRect, intersectionRect};
+}
+
+// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
+// (step 2)
+void DOMIntersectionObserver::Update(Document& aDocument,
+ DOMHighResTimeStamp time) {
+ auto input = ComputeInput(aDocument, mRoot, &mRootMargin);
+
+ // If this observer is used to determine content relevancy for
+ // `content-visiblity: auto` content, then do not skip intersection
+ // for content that is hidden by `content-visibility: auto`.
+ IgnoreContentVisibility ignoreContentVisibility =
+ aDocument.GetContentVisibilityObserver() == this
+ ? IgnoreContentVisibility::Yes
+ : IgnoreContentVisibility::No;
+
+ // 2. For each target in observer’s internal [[ObservationTargets]] slot,
+ // processed in the same order that observe() was called on each target:
+ for (Element* target : mObservationTargets) {
+ // 2.1 - 2.4.
+ IntersectionOutput output =
+ Intersect(input, *target, ignoreContentVisibility);
+
+ // 2.5. Let targetArea be targetRect’s area.
+ int64_t targetArea = (int64_t)output.mTargetRect.Width() *
+ (int64_t)output.mTargetRect.Height();
+
+ // 2.6. Let intersectionArea be intersectionRect’s area.
+ int64_t intersectionArea =
+ !output.mIntersectionRect
+ ? 0
+ : (int64_t)output.mIntersectionRect->Width() *
+ (int64_t)output.mIntersectionRect->Height();
+
+ // 2.7. Let isIntersecting be true if targetRect and rootBounds intersect or
+ // are edge-adjacent, even if the intersection has zero area (because
+ // rootBounds or targetRect have zero area); otherwise, let isIntersecting
+ // be false.
+ const bool isIntersecting = output.Intersects();
+
+ // 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea
+ // divided by targetArea. Otherwise, let intersectionRatio be 1 if
+ // isIntersecting is true, or 0 if isIntersecting is false.
+ double intersectionRatio;
+ if (targetArea > 0.0) {
+ intersectionRatio =
+ std::min((double)intersectionArea / (double)targetArea, 1.0);
+ } else {
+ intersectionRatio = isIntersecting ? 1.0 : 0.0;
+ }
+
+ // 2.9 Let thresholdIndex be the index of the first entry in
+ // observer.thresholds whose value is greater than intersectionRatio, or the
+ // length of observer.thresholds if intersectionRatio is greater than or
+ // equal to the last entry in observer.thresholds.
+ int32_t thresholdIndex = -1;
+
+ // If not intersecting, we can just shortcut, as we know that the thresholds
+ // are always between 0 and 1.
+ if (isIntersecting) {
+ thresholdIndex = mThresholds.IndexOfFirstElementGt(intersectionRatio);
+ if (thresholdIndex == 0) {
+ // Per the spec, we should leave threshold at 0 and distinguish between
+ // "less than all thresholds and intersecting" and "not intersecting"
+ // (queuing observer entries as both cases come to pass). However,
+ // neither Chrome nor the WPT tests expect this behavior, so treat these
+ // two cases as one.
+ //
+ // See https://github.com/w3c/IntersectionObserver/issues/432 about
+ // this.
+ thresholdIndex = -1;
+ }
+ }
+
+ // Steps 2.10 - 2.15.
+ if (target->UpdateIntersectionObservation(this, thresholdIndex)) {
+ // See https://github.com/w3c/IntersectionObserver/issues/432 about
+ // why we use thresholdIndex > 0 rather than isIntersecting for the
+ // entry's isIntersecting value.
+ QueueIntersectionObserverEntry(
+ target, time,
+ output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(),
+ output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0,
+ intersectionRatio);
+ }
+ }
+}
+
+void DOMIntersectionObserver::QueueIntersectionObserverEntry(
+ Element* aTarget, DOMHighResTimeStamp time, const Maybe<nsRect>& aRootRect,
+ const nsRect& aTargetRect, const Maybe<nsRect>& aIntersectionRect,
+ bool aIsIntersecting, double aIntersectionRatio) {
+ RefPtr<DOMRect> rootBounds;
+ if (aRootRect.isSome()) {
+ rootBounds = new DOMRect(mOwner);
+ rootBounds->SetLayoutRect(aRootRect.value());
+ }
+ RefPtr<DOMRect> boundingClientRect = new DOMRect(mOwner);
+ boundingClientRect->SetLayoutRect(aTargetRect);
+ RefPtr<DOMRect> intersectionRect = new DOMRect(mOwner);
+ if (aIntersectionRect.isSome()) {
+ intersectionRect->SetLayoutRect(aIntersectionRect.value());
+ }
+ RefPtr<DOMIntersectionObserverEntry> entry = new DOMIntersectionObserverEntry(
+ mOwner, time, rootBounds.forget(), boundingClientRect.forget(),
+ intersectionRect.forget(), aIsIntersecting, aTarget, aIntersectionRatio);
+ mQueuedEntries.AppendElement(entry.forget());
+}
+
+void DOMIntersectionObserver::Notify() {
+ if (!mQueuedEntries.Length()) {
+ return;
+ }
+ Sequence<OwningNonNull<DOMIntersectionObserverEntry>> entries;
+ if (entries.SetCapacity(mQueuedEntries.Length(), mozilla::fallible)) {
+ for (size_t i = 0; i < mQueuedEntries.Length(); ++i) {
+ RefPtr<DOMIntersectionObserverEntry> next = mQueuedEntries[i];
+ *entries.AppendElement(mozilla::fallible) = next;
+ }
+ }
+ mQueuedEntries.Clear();
+
+ if (mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
+ RefPtr<dom::IntersectionCallback> callback(
+ mCallback.as<RefPtr<dom::IntersectionCallback>>());
+ callback->Call(this, entries, *this);
+ } else {
+ mCallback.as<NativeCallback>()(entries);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DOMIntersectionObserver.h b/dom/base/DOMIntersectionObserver.h
new file mode 100644
index 0000000000..a5c4a0302d
--- /dev/null
+++ b/dom/base/DOMIntersectionObserver.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOMIntersectionObserver_h
+#define DOMIntersectionObserver_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/IntersectionObserverBinding.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/Variant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+
+namespace mozilla::dom {
+
+class DOMIntersectionObserver;
+
+class DOMIntersectionObserverEntry final : public nsISupports,
+ public nsWrapperCache {
+ ~DOMIntersectionObserverEntry() = default;
+
+ public:
+ DOMIntersectionObserverEntry(nsISupports* aOwner, DOMHighResTimeStamp aTime,
+ RefPtr<DOMRect> aRootBounds,
+ RefPtr<DOMRect> aBoundingClientRect,
+ RefPtr<DOMRect> aIntersectionRect,
+ bool aIsIntersecting, Element* aTarget,
+ double aIntersectionRatio)
+ : mOwner(aOwner),
+ mTime(aTime),
+ mRootBounds(std::move(aRootBounds)),
+ mBoundingClientRect(std::move(aBoundingClientRect)),
+ mIntersectionRect(std::move(aIntersectionRect)),
+ mIsIntersecting(aIsIntersecting),
+ mTarget(aTarget),
+ mIntersectionRatio(aIntersectionRatio) {}
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMIntersectionObserverEntry)
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return IntersectionObserverEntry_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ DOMHighResTimeStamp Time() const { return mTime; }
+
+ DOMRect* GetRootBounds() { return mRootBounds; }
+
+ DOMRect* BoundingClientRect() { return mBoundingClientRect; }
+
+ DOMRect* IntersectionRect() { return mIntersectionRect; }
+
+ bool IsIntersecting() const { return mIsIntersecting; }
+
+ double IntersectionRatio() const { return mIntersectionRatio; }
+
+ Element* Target() { return mTarget; }
+
+ protected:
+ nsCOMPtr<nsISupports> mOwner;
+ DOMHighResTimeStamp mTime;
+ RefPtr<DOMRect> mRootBounds;
+ RefPtr<DOMRect> mBoundingClientRect;
+ RefPtr<DOMRect> mIntersectionRect;
+ bool mIsIntersecting;
+ RefPtr<Element> mTarget;
+ double mIntersectionRatio;
+};
+
+#define NS_DOM_INTERSECTION_OBSERVER_IID \
+ { \
+ 0x8570a575, 0xe303, 0x4d18, { \
+ 0xb6, 0xb1, 0x4d, 0x2b, 0x49, 0xd8, 0xef, 0x94 \
+ } \
+ }
+
+// An input suitable to compute intersections with multiple targets.
+struct IntersectionInput {
+ // Whether the root is implicit (null, originally).
+ const bool mIsImplicitRoot = false;
+ // The computed root node. For the implicit root, this will be the in-process
+ // root document we can compute coordinates against (along with the remote
+ // document visible rect if appropriate).
+ const nsINode* mRootNode = nullptr;
+ nsIFrame* mRootFrame = nullptr;
+ // The rect of mRootFrame in client coordinates.
+ nsRect mRootRect;
+ // The root margin computed against the root rect.
+ nsMargin mRootMargin;
+ // If this is in an OOP iframe, the visible rect of the OOP frame.
+ Maybe<nsRect> mRemoteDocumentVisibleRect;
+};
+
+struct IntersectionOutput {
+ const bool mIsSimilarOrigin;
+ const nsRect mRootBounds;
+ const nsRect mTargetRect;
+ const Maybe<nsRect> mIntersectionRect;
+
+ bool Intersects() const { return mIntersectionRect.isSome(); }
+};
+
+class DOMIntersectionObserver final : public nsISupports,
+ public nsWrapperCache {
+ virtual ~DOMIntersectionObserver() { Disconnect(); }
+
+ using NativeCallback = void (*)(
+ const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries);
+ DOMIntersectionObserver(Document&, NativeCallback);
+
+ public:
+ DOMIntersectionObserver(already_AddRefed<nsPIDOMWindowInner>&& aOwner,
+ dom::IntersectionCallback& aCb);
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMIntersectionObserver)
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_INTERSECTION_OBSERVER_IID)
+
+ static already_AddRefed<DOMIntersectionObserver> Constructor(
+ const GlobalObject&, dom::IntersectionCallback&, ErrorResult&);
+ static already_AddRefed<DOMIntersectionObserver> Constructor(
+ const GlobalObject&, dom::IntersectionCallback&,
+ const IntersectionObserverInit&, ErrorResult&);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return IntersectionObserver_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ nsISupports* GetParentObject() const;
+
+ nsINode* GetRoot() const { return mRoot; }
+
+ void GetRootMargin(nsACString&);
+ bool SetRootMargin(const nsACString&);
+
+ void GetThresholds(nsTArray<double>& aRetVal);
+ void Observe(Element& aTarget);
+ void Unobserve(Element& aTarget);
+
+ void UnlinkTarget(Element& aTarget);
+ void Disconnect();
+
+ void TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal);
+
+ static IntersectionInput ComputeInput(
+ const Document& aDocument, const nsINode* aRoot,
+ const StyleRect<LengthPercentage>* aRootMargin);
+
+ enum class IgnoreContentVisibility : bool { No, Yes };
+ static IntersectionOutput Intersect(
+ const IntersectionInput&, Element&,
+ IgnoreContentVisibility = IgnoreContentVisibility::No);
+ // Intersects with a given rect, already relative to the root frame.
+ static IntersectionOutput Intersect(const IntersectionInput&, const nsRect&);
+
+ void Update(Document& aDocument, DOMHighResTimeStamp time);
+ MOZ_CAN_RUN_SCRIPT void Notify();
+
+ static already_AddRefed<DOMIntersectionObserver> CreateLazyLoadObserver(
+ Document&);
+ static already_AddRefed<DOMIntersectionObserver>
+ CreateLazyLoadObserverViewport(Document&);
+
+ static already_AddRefed<DOMIntersectionObserver>
+ CreateContentVisibilityObserver(Document&);
+
+ protected:
+ void Connect();
+ void QueueIntersectionObserverEntry(Element* aTarget,
+ DOMHighResTimeStamp time,
+ const Maybe<nsRect>& aRootRect,
+ const nsRect& aTargetRect,
+ const Maybe<nsRect>& aIntersectionRect,
+ bool aIsIntersecting,
+ double aIntersectionRatio);
+
+ nsCOMPtr<nsPIDOMWindowInner> mOwner;
+ RefPtr<Document> mDocument;
+ Variant<RefPtr<dom::IntersectionCallback>, NativeCallback> mCallback;
+ RefPtr<nsINode> mRoot;
+ StyleRect<LengthPercentage> mRootMargin;
+ nsTArray<double> mThresholds;
+
+ // These hold raw pointers which are explicitly cleared by UnlinkTarget().
+ //
+ // We keep a set and an array because we need ordered access, but also
+ // constant time lookup.
+ nsTArray<Element*> mObservationTargets;
+ nsTHashSet<Element*> mObservationTargetSet;
+
+ nsTArray<RefPtr<DOMIntersectionObserverEntry>> mQueuedEntries;
+ bool mConnected;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DOMIntersectionObserver,
+ NS_DOM_INTERSECTION_OBSERVER_IID)
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/DOMMatrix.cpp b/dom/base/DOMMatrix.cpp
new file mode 100644
index 0000000000..4723940bae
--- /dev/null
+++ b/dom/base/DOMMatrix.cpp
@@ -0,0 +1,1027 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMMatrix.h"
+
+#include <cmath>
+#include <cstdint>
+#include <new>
+#include "ErrorList.h"
+#include "js/Conversions.h"
+#include "js/Equality.h"
+#include "js/StructuredClone.h"
+#include "js/Value.h"
+#include "mozilla/Casting.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMMatrixBinding.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/BasePoint4D.h"
+#include "mozilla/gfx/Point.h"
+#include "nsIGlobalObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+
+namespace mozilla::dom {
+
+template <typename T>
+static void SetDataInMatrix(DOMMatrixReadOnly* aMatrix, const T* aData,
+ int aLength, ErrorResult& aRv);
+
+static const double radPerDegree = 2.0 * M_PI / 360.0;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMMatrixReadOnly, mParent)
+
+JSObject* DOMMatrixReadOnly::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMMatrixReadOnly_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup-2d
+static bool ValidateAndFixupMatrix2DInit(DOMMatrix2DInit& aMatrixInit,
+ ErrorResult& aRv) {
+#define ValidateAliases(field, alias, fieldName, aliasName) \
+ if ((field).WasPassed() && (alias).WasPassed() && \
+ !JS::SameValueZero((field).Value(), (alias).Value())) { \
+ aRv.ThrowTypeError<MSG_MATRIX_INIT_CONFLICTING_VALUE>((fieldName), \
+ (aliasName)); \
+ return false; \
+ }
+#define SetFromAliasOrDefault(field, alias, defaultValue) \
+ if (!(field).WasPassed()) { \
+ if ((alias).WasPassed()) { \
+ (field).Construct((alias).Value()); \
+ } else { \
+ (field).Construct(defaultValue); \
+ } \
+ }
+#define ValidateAndSet(field, alias, fieldName, aliasName, defaultValue) \
+ ValidateAliases((field), (alias), fieldName, aliasName); \
+ SetFromAliasOrDefault((field), (alias), (defaultValue));
+
+ ValidateAndSet(aMatrixInit.mM11, aMatrixInit.mA, "m11", "a", 1);
+ ValidateAndSet(aMatrixInit.mM12, aMatrixInit.mB, "m12", "b", 0);
+ ValidateAndSet(aMatrixInit.mM21, aMatrixInit.mC, "m21", "c", 0);
+ ValidateAndSet(aMatrixInit.mM22, aMatrixInit.mD, "m22", "d", 1);
+ ValidateAndSet(aMatrixInit.mM41, aMatrixInit.mE, "m41", "e", 0);
+ ValidateAndSet(aMatrixInit.mM42, aMatrixInit.mF, "m42", "f", 0);
+
+ return true;
+
+#undef ValidateAliases
+#undef SetFromAliasOrDefault
+#undef ValidateAndSet
+}
+
+// https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup
+static bool ValidateAndFixupMatrixInit(DOMMatrixInit& aMatrixInit,
+ ErrorResult& aRv) {
+#define Check3DField(field, fieldName, defaultValue) \
+ if ((field) != (defaultValue)) { \
+ if (!aMatrixInit.mIs2D.WasPassed()) { \
+ aMatrixInit.mIs2D.Construct(false); \
+ return true; \
+ } \
+ if (aMatrixInit.mIs2D.Value()) { \
+ aRv.ThrowTypeError<MSG_MATRIX_INIT_EXCEEDS_2D>(fieldName); \
+ return false; \
+ } \
+ }
+
+ if (!ValidateAndFixupMatrix2DInit(aMatrixInit, aRv)) {
+ return false;
+ }
+
+ Check3DField(aMatrixInit.mM13, "m13", 0);
+ Check3DField(aMatrixInit.mM14, "m14", 0);
+ Check3DField(aMatrixInit.mM23, "m23", 0);
+ Check3DField(aMatrixInit.mM24, "m24", 0);
+ Check3DField(aMatrixInit.mM31, "m31", 0);
+ Check3DField(aMatrixInit.mM32, "m32", 0);
+ Check3DField(aMatrixInit.mM34, "m34", 0);
+ Check3DField(aMatrixInit.mM43, "m43", 0);
+ Check3DField(aMatrixInit.mM33, "m33", 1);
+ Check3DField(aMatrixInit.mM44, "m44", 1);
+
+ if (!aMatrixInit.mIs2D.WasPassed()) {
+ aMatrixInit.mIs2D.Construct(true);
+ }
+ return true;
+
+#undef Check3DField
+}
+
+void DOMMatrixReadOnly::SetDataFromMatrix2DInit(
+ const DOMMatrix2DInit& aMatrixInit) {
+ MOZ_ASSERT(Is2D());
+ mMatrix2D->_11 = aMatrixInit.mM11.Value();
+ mMatrix2D->_12 = aMatrixInit.mM12.Value();
+ mMatrix2D->_21 = aMatrixInit.mM21.Value();
+ mMatrix2D->_22 = aMatrixInit.mM22.Value();
+ mMatrix2D->_31 = aMatrixInit.mM41.Value();
+ mMatrix2D->_32 = aMatrixInit.mM42.Value();
+}
+
+void DOMMatrixReadOnly::SetDataFromMatrixInit(
+ const DOMMatrixInit& aMatrixInit) {
+ const bool is2D = aMatrixInit.mIs2D.Value();
+ MOZ_ASSERT(is2D == Is2D());
+ if (is2D) {
+ SetDataFromMatrix2DInit(aMatrixInit);
+ } else {
+ mMatrix3D->_11 = aMatrixInit.mM11.Value();
+ mMatrix3D->_12 = aMatrixInit.mM12.Value();
+ mMatrix3D->_13 = aMatrixInit.mM13;
+ mMatrix3D->_14 = aMatrixInit.mM14;
+ mMatrix3D->_21 = aMatrixInit.mM21.Value();
+ mMatrix3D->_22 = aMatrixInit.mM22.Value();
+ mMatrix3D->_23 = aMatrixInit.mM23;
+ mMatrix3D->_24 = aMatrixInit.mM24;
+ mMatrix3D->_31 = aMatrixInit.mM31;
+ mMatrix3D->_32 = aMatrixInit.mM32;
+ mMatrix3D->_33 = aMatrixInit.mM33;
+ mMatrix3D->_34 = aMatrixInit.mM34;
+ mMatrix3D->_41 = aMatrixInit.mM41.Value();
+ mMatrix3D->_42 = aMatrixInit.mM42.Value();
+ mMatrix3D->_43 = aMatrixInit.mM43;
+ mMatrix3D->_44 = aMatrixInit.mM44;
+ }
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
+ nsISupports* aParent, const DOMMatrix2DInit& aMatrixInit,
+ ErrorResult& aRv) {
+ DOMMatrix2DInit matrixInit(aMatrixInit);
+ if (!ValidateAndFixupMatrix2DInit(matrixInit, aRv)) {
+ return nullptr;
+ };
+
+ RefPtr<DOMMatrixReadOnly> matrix =
+ new DOMMatrixReadOnly(aParent, /* is2D */ true);
+ matrix->SetDataFromMatrix2DInit(matrixInit);
+ return matrix.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
+ nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv) {
+ DOMMatrixInit matrixInit(aMatrixInit);
+ if (!ValidateAndFixupMatrixInit(matrixInit, aRv)) {
+ return nullptr;
+ };
+
+ RefPtr<DOMMatrixReadOnly> rval =
+ new DOMMatrixReadOnly(aParent, matrixInit.mIs2D.Value());
+ rval->SetDataFromMatrixInit(matrixInit);
+ return rval.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
+ const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
+ ErrorResult& aRv) {
+ RefPtr<DOMMatrixReadOnly> matrix =
+ FromMatrix(aGlobal.GetAsSupports(), aMatrixInit, aRv);
+ return matrix.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat32Array(
+ const GlobalObject& aGlobal, const Float32Array& aArray32,
+ ErrorResult& aRv) {
+ aArray32.ComputeState();
+
+ const int length = aArray32.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrixReadOnly> obj =
+ new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(obj, aArray32.Data(), length, aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat64Array(
+ const GlobalObject& aGlobal, const Float64Array& aArray64,
+ ErrorResult& aRv) {
+ aArray64.ComputeState();
+
+ const int length = aArray64.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrixReadOnly> obj =
+ new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(obj, aArray64.Data(), length, aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
+ aArg,
+ ErrorResult& aRv) {
+ if (!aArg.WasPassed()) {
+ RefPtr<DOMMatrixReadOnly> rval =
+ new DOMMatrixReadOnly(aGlobal.GetAsSupports());
+ return rval.forget();
+ }
+
+ const auto& arg = aArg.Value();
+ if (arg.IsUTF8String()) {
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!win) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return nullptr;
+ }
+ RefPtr<DOMMatrixReadOnly> rval =
+ new DOMMatrixReadOnly(aGlobal.GetAsSupports());
+ rval->SetMatrixValue(arg.GetAsUTF8String(), aRv);
+ return rval.forget();
+ }
+ if (arg.IsDOMMatrixReadOnly()) {
+ RefPtr<DOMMatrixReadOnly> obj = new DOMMatrixReadOnly(
+ aGlobal.GetAsSupports(), arg.GetAsDOMMatrixReadOnly());
+ return obj.forget();
+ }
+
+ const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
+ const int length = sequence.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrixReadOnly> rval =
+ new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(rval, sequence.Elements(), length, aRv);
+ return rval.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint8_t is2D;
+
+ if (!JS_ReadBytes(aReader, &is2D, 1)) {
+ return nullptr;
+ }
+
+ RefPtr<DOMMatrixReadOnly> rval = new DOMMatrixReadOnly(aGlobal, is2D);
+
+ if (!ReadStructuredCloneElements(aReader, rval)) {
+ return nullptr;
+ };
+
+ return rval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Translate(double aTx, double aTy,
+ double aTz) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->TranslateSelf(aTx, aTy, aTz);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Scale(
+ double aScaleX, const Optional<double>& aScaleY, double aScaleZ,
+ double aOriginX, double aOriginY, double aOriginZ) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->ScaleSelf(aScaleX, aScaleY, aScaleZ, aOriginX, aOriginY, aOriginZ);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Scale3d(double aScale,
+ double aOriginX,
+ double aOriginY,
+ double aOriginZ) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->Scale3dSelf(aScale, aOriginX, aOriginY, aOriginZ);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::ScaleNonUniform(
+ double aScaleX, double aScaleY) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->ScaleSelf(aScaleX, Optional<double>(aScaleY), 1, 0, 0, 0);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Rotate(
+ double aRotX, const Optional<double>& aRotY,
+ const Optional<double>& aRotZ) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->RotateSelf(aRotX, aRotY, aRotZ);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::RotateFromVector(
+ double x, double y) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->RotateFromVectorSelf(x, y);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::RotateAxisAngle(
+ double aX, double aY, double aZ, double aAngle) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->RotateAxisAngleSelf(aX, aY, aZ, aAngle);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::SkewX(double aSx) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->SkewXSelf(aSx);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::SkewY(double aSy) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->SkewYSelf(aSy);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Multiply(
+ const DOMMatrixInit& other, ErrorResult& aRv) const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->MultiplySelf(other, aRv);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::FlipX() const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ if (mMatrix3D) {
+ gfx::Matrix4x4Double m;
+ m._11 = -1;
+ retval->mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(m * *mMatrix3D);
+ } else {
+ gfx::MatrixDouble m;
+ m._11 = -1;
+ retval->mMatrix2D =
+ MakeUnique<gfx::MatrixDouble>(mMatrix2D ? m * *mMatrix2D : m);
+ }
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::FlipY() const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ if (mMatrix3D) {
+ gfx::Matrix4x4Double m;
+ m._22 = -1;
+ retval->mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(m * *mMatrix3D);
+ } else {
+ gfx::MatrixDouble m;
+ m._22 = -1;
+ retval->mMatrix2D =
+ MakeUnique<gfx::MatrixDouble>(mMatrix2D ? m * *mMatrix2D : m);
+ }
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Inverse() const {
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->InvertSelf();
+
+ return retval.forget();
+}
+
+bool DOMMatrixReadOnly::Is2D() const { return !mMatrix3D; }
+
+bool DOMMatrixReadOnly::IsIdentity() const {
+ if (mMatrix3D) {
+ return mMatrix3D->IsIdentity();
+ }
+
+ return mMatrix2D->IsIdentity();
+}
+
+already_AddRefed<DOMPoint> DOMMatrixReadOnly::TransformPoint(
+ const DOMPointInit& point) const {
+ RefPtr<DOMPoint> retval = new DOMPoint(mParent);
+
+ if (mMatrix3D) {
+ gfx::Point4D transformedPoint;
+ transformedPoint.x = point.mX;
+ transformedPoint.y = point.mY;
+ transformedPoint.z = point.mZ;
+ transformedPoint.w = point.mW;
+
+ transformedPoint = mMatrix3D->TransformPoint(transformedPoint);
+
+ retval->SetX(transformedPoint.x);
+ retval->SetY(transformedPoint.y);
+ retval->SetZ(transformedPoint.z);
+ retval->SetW(transformedPoint.w);
+ } else if (point.mZ != 0 || point.mW != 1.0) {
+ gfx::Matrix4x4Double tempMatrix(gfx::Matrix4x4Double::From2D(*mMatrix2D));
+
+ gfx::PointDouble4D transformedPoint;
+ transformedPoint.x = point.mX;
+ transformedPoint.y = point.mY;
+ transformedPoint.z = point.mZ;
+ transformedPoint.w = point.mW;
+
+ transformedPoint = tempMatrix.TransformPoint(transformedPoint);
+
+ retval->SetX(transformedPoint.x);
+ retval->SetY(transformedPoint.y);
+ retval->SetZ(transformedPoint.z);
+ retval->SetW(transformedPoint.w);
+ } else {
+ gfx::PointDouble transformedPoint;
+ transformedPoint.x = point.mX;
+ transformedPoint.y = point.mY;
+
+ transformedPoint = mMatrix2D->TransformPoint(transformedPoint);
+
+ retval->SetX(transformedPoint.x);
+ retval->SetY(transformedPoint.y);
+ retval->SetZ(point.mZ);
+ retval->SetW(point.mW);
+ }
+ return retval.forget();
+}
+
+template <typename T>
+void GetDataFromMatrix(const DOMMatrixReadOnly* aMatrix, T* aData) {
+ aData[0] = static_cast<T>(aMatrix->M11());
+ aData[1] = static_cast<T>(aMatrix->M12());
+ aData[2] = static_cast<T>(aMatrix->M13());
+ aData[3] = static_cast<T>(aMatrix->M14());
+ aData[4] = static_cast<T>(aMatrix->M21());
+ aData[5] = static_cast<T>(aMatrix->M22());
+ aData[6] = static_cast<T>(aMatrix->M23());
+ aData[7] = static_cast<T>(aMatrix->M24());
+ aData[8] = static_cast<T>(aMatrix->M31());
+ aData[9] = static_cast<T>(aMatrix->M32());
+ aData[10] = static_cast<T>(aMatrix->M33());
+ aData[11] = static_cast<T>(aMatrix->M34());
+ aData[12] = static_cast<T>(aMatrix->M41());
+ aData[13] = static_cast<T>(aMatrix->M42());
+ aData[14] = static_cast<T>(aMatrix->M43());
+ aData[15] = static_cast<T>(aMatrix->M44());
+}
+
+void DOMMatrixReadOnly::ToFloat32Array(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) const {
+ AutoTArray<float, 16> arr;
+ arr.SetLength(16);
+ GetDataFromMatrix(this, arr.Elements());
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, TypedArrayCreator<Float32Array>(arr), &value)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aResult.set(&value.toObject());
+}
+
+void DOMMatrixReadOnly::ToFloat64Array(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) const {
+ AutoTArray<double, 16> arr;
+ arr.SetLength(16);
+ GetDataFromMatrix(this, arr.Elements());
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, TypedArrayCreator<Float64Array>(arr), &value)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aResult.set(&value.toObject());
+}
+
+void DOMMatrixReadOnly::Stringify(nsAString& aResult, ErrorResult& aRv) {
+ char cbuf[JS::MaximumNumberToStringLength];
+ nsAutoString matrixStr;
+ auto AppendDouble = [&aRv, &cbuf, &matrixStr](double d,
+ bool isLastItem = false) {
+ if (!std::isfinite(d)) {
+ aRv.ThrowInvalidStateError(
+ "Matrix with a non-finite element cannot be stringified.");
+ return false;
+ }
+ JS::NumberToString(d, cbuf);
+ matrixStr.AppendASCII(cbuf);
+ if (!isLastItem) {
+ matrixStr.AppendLiteral(", ");
+ }
+ return true;
+ };
+
+ if (mMatrix3D) {
+ // We can't use AppendPrintf here, because it does locale-specific
+ // formatting of floating-point values.
+ matrixStr.AssignLiteral("matrix3d(");
+ if (!AppendDouble(M11()) || !AppendDouble(M12()) || !AppendDouble(M13()) ||
+ !AppendDouble(M14()) || !AppendDouble(M21()) || !AppendDouble(M22()) ||
+ !AppendDouble(M23()) || !AppendDouble(M24()) || !AppendDouble(M31()) ||
+ !AppendDouble(M32()) || !AppendDouble(M33()) || !AppendDouble(M34()) ||
+ !AppendDouble(M41()) || !AppendDouble(M42()) || !AppendDouble(M43()) ||
+ !AppendDouble(M44(), true)) {
+ return;
+ }
+ matrixStr.AppendLiteral(")");
+ } else {
+ // We can't use AppendPrintf here, because it does locale-specific
+ // formatting of floating-point values.
+ matrixStr.AssignLiteral("matrix(");
+ if (!AppendDouble(A()) || !AppendDouble(B()) || !AppendDouble(C()) ||
+ !AppendDouble(D()) || !AppendDouble(E()) || !AppendDouble(F(), true)) {
+ return;
+ }
+ matrixStr.AppendLiteral(")");
+ }
+
+ aResult = matrixStr;
+}
+
+// https://drafts.fxtf.org/geometry/#structured-serialization
+bool DOMMatrixReadOnly::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ const uint8_t is2D = Is2D();
+
+ if (!JS_WriteBytes(aWriter, &is2D, 1)) {
+ return false;
+ }
+
+ if (is2D == 1) {
+ return JS_WriteDouble(aWriter, mMatrix2D->_11) &&
+ JS_WriteDouble(aWriter, mMatrix2D->_12) &&
+ JS_WriteDouble(aWriter, mMatrix2D->_21) &&
+ JS_WriteDouble(aWriter, mMatrix2D->_22) &&
+ JS_WriteDouble(aWriter, mMatrix2D->_31) &&
+ JS_WriteDouble(aWriter, mMatrix2D->_32);
+ }
+
+ return JS_WriteDouble(aWriter, mMatrix3D->_11) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_12) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_13) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_14) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_21) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_22) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_23) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_24) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_31) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_32) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_33) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_34) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_41) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_42) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_43) &&
+ JS_WriteDouble(aWriter, mMatrix3D->_44);
+}
+
+bool DOMMatrixReadOnly::ReadStructuredCloneElements(
+ JSStructuredCloneReader* aReader, DOMMatrixReadOnly* matrix) {
+ if (matrix->Is2D() == 1) {
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_11));
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_12));
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_21));
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_22));
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_31));
+ JS_ReadDouble(aReader, &(matrix->mMatrix2D->_32));
+ } else {
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_11));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_12));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_13));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_14));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_21));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_22));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_23));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_24));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_31));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_32));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_33));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_34));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_41));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_42));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_43));
+ JS_ReadDouble(aReader, &(matrix->mMatrix3D->_44));
+ }
+
+ return true;
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::FromMatrix(
+ nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv) {
+ DOMMatrixInit matrixInit(aMatrixInit);
+ if (!ValidateAndFixupMatrixInit(matrixInit, aRv)) {
+ return nullptr;
+ };
+
+ RefPtr<DOMMatrix> matrix = new DOMMatrix(aParent, matrixInit.mIs2D.Value());
+ matrix->SetDataFromMatrixInit(matrixInit);
+ return matrix.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::FromMatrix(
+ const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
+ ErrorResult& aRv) {
+ RefPtr<DOMMatrix> matrix =
+ FromMatrix(aGlobal.GetAsSupports(), aMatrixInit, aRv);
+ return matrix.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::FromFloat32Array(
+ const GlobalObject& aGlobal, const Float32Array& aArray32,
+ ErrorResult& aRv) {
+ aArray32.ComputeState();
+
+ const int length = aArray32.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(obj, aArray32.Data(), length, aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::FromFloat64Array(
+ const GlobalObject& aGlobal, const Float64Array& aArray64,
+ ErrorResult& aRv) {
+ aArray64.ComputeState();
+
+ const int length = aArray64.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(obj, aArray64.Data(), length, aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
+ aArg,
+ ErrorResult& aRv) {
+ if (!aArg.WasPassed()) {
+ RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports());
+ return rval.forget();
+ }
+
+ const auto& arg = aArg.Value();
+ if (arg.IsUTF8String()) {
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!win) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return nullptr;
+ }
+ RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports());
+ rval->SetMatrixValue(arg.GetAsUTF8String(), aRv);
+ return rval.forget();
+ }
+ if (arg.IsDOMMatrixReadOnly()) {
+ RefPtr<DOMMatrix> obj =
+ new DOMMatrix(aGlobal.GetAsSupports(), arg.GetAsDOMMatrixReadOnly());
+ return obj.forget();
+ }
+
+ const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
+ const int length = sequence.Length();
+ const bool is2D = length == 6;
+ RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+ SetDataInMatrix(rval, sequence.Elements(), length, aRv);
+ return rval.forget();
+}
+
+template <typename T>
+static void SetDataInMatrix(DOMMatrixReadOnly* aMatrix, const T* aData,
+ int aLength, ErrorResult& aRv) {
+ if (aLength == 16) {
+ aMatrix->SetM11(aData[0]);
+ aMatrix->SetM12(aData[1]);
+ aMatrix->SetM13(aData[2]);
+ aMatrix->SetM14(aData[3]);
+ aMatrix->SetM21(aData[4]);
+ aMatrix->SetM22(aData[5]);
+ aMatrix->SetM23(aData[6]);
+ aMatrix->SetM24(aData[7]);
+ aMatrix->SetM31(aData[8]);
+ aMatrix->SetM32(aData[9]);
+ aMatrix->SetM33(aData[10]);
+ aMatrix->SetM34(aData[11]);
+ aMatrix->SetM41(aData[12]);
+ aMatrix->SetM42(aData[13]);
+ aMatrix->SetM43(aData[14]);
+ aMatrix->SetM44(aData[15]);
+ } else if (aLength == 6) {
+ aMatrix->SetA(aData[0]);
+ aMatrix->SetB(aData[1]);
+ aMatrix->SetC(aData[2]);
+ aMatrix->SetD(aData[3]);
+ aMatrix->SetE(aData[4]);
+ aMatrix->SetF(aData[5]);
+ } else {
+ nsAutoCString lengthStr;
+ lengthStr.AppendInt(aLength);
+ aRv.ThrowTypeError<MSG_MATRIX_INIT_LENGTH_WRONG>(lengthStr);
+ }
+}
+
+already_AddRefed<DOMMatrix> DOMMatrix::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint8_t is2D;
+
+ if (!JS_ReadBytes(aReader, &is2D, 1)) {
+ return nullptr;
+ }
+
+ RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal, is2D);
+
+ if (!ReadStructuredCloneElements(aReader, rval)) {
+ return nullptr;
+ };
+
+ return rval.forget();
+}
+
+void DOMMatrixReadOnly::Ensure3DMatrix() {
+ if (!mMatrix3D) {
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(
+ gfx::Matrix4x4Double::From2D(*mMatrix2D));
+ mMatrix2D = nullptr;
+ }
+}
+
+DOMMatrix* DOMMatrix::MultiplySelf(const DOMMatrixInit& aOtherInit,
+ ErrorResult& aRv) {
+ RefPtr<DOMMatrix> other = FromMatrix(mParent, aOtherInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(other);
+ if (other->IsIdentity()) {
+ return this;
+ }
+
+ if (other->Is2D()) {
+ if (mMatrix3D) {
+ *mMatrix3D = gfx::Matrix4x4Double::From2D(*other->mMatrix2D) * *mMatrix3D;
+ } else {
+ *mMatrix2D = *other->mMatrix2D * *mMatrix2D;
+ }
+ } else {
+ Ensure3DMatrix();
+ *mMatrix3D = *other->mMatrix3D * *mMatrix3D;
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::PreMultiplySelf(const DOMMatrixInit& aOtherInit,
+ ErrorResult& aRv) {
+ RefPtr<DOMMatrix> other = FromMatrix(mParent, aOtherInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(other);
+ if (other->IsIdentity()) {
+ return this;
+ }
+
+ if (other->Is2D()) {
+ if (mMatrix3D) {
+ *mMatrix3D = *mMatrix3D * gfx::Matrix4x4Double::From2D(*other->mMatrix2D);
+ } else {
+ *mMatrix2D = *mMatrix2D * *other->mMatrix2D;
+ }
+ } else {
+ Ensure3DMatrix();
+ *mMatrix3D = *mMatrix3D * *other->mMatrix3D;
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::TranslateSelf(double aTx, double aTy, double aTz) {
+ if (aTx == 0 && aTy == 0 && aTz == 0) {
+ return this;
+ }
+
+ if (mMatrix3D || aTz != 0) {
+ Ensure3DMatrix();
+ mMatrix3D->PreTranslate(aTx, aTy, aTz);
+ } else {
+ mMatrix2D->PreTranslate(aTx, aTy);
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::ScaleSelf(double aScaleX, const Optional<double>& aScaleY,
+ double aScaleZ, double aOriginX,
+ double aOriginY, double aOriginZ) {
+ const double scaleY = aScaleY.WasPassed() ? aScaleY.Value() : aScaleX;
+
+ TranslateSelf(aOriginX, aOriginY, aOriginZ);
+
+ if (mMatrix3D || aScaleZ != 1.0) {
+ Ensure3DMatrix();
+ gfx::Matrix4x4Double m;
+ m._11 = aScaleX;
+ m._22 = scaleY;
+ m._33 = aScaleZ;
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::MatrixDouble m;
+ m._11 = aScaleX;
+ m._22 = scaleY;
+ *mMatrix2D = m * *mMatrix2D;
+ }
+
+ TranslateSelf(-aOriginX, -aOriginY, -aOriginZ);
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::Scale3dSelf(double aScale, double aOriginX,
+ double aOriginY, double aOriginZ) {
+ ScaleSelf(aScale, Optional<double>(aScale), aScale, aOriginX, aOriginY,
+ aOriginZ);
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::RotateFromVectorSelf(double aX, double aY) {
+ const double angle = (aX == 0.0 && aY == 0.0) ? 0 : atan2(aY, aX);
+
+ if (fmod(angle, 2 * M_PI) == 0) {
+ return this;
+ }
+
+ if (mMatrix3D) {
+ RotateAxisAngleSelf(0, 0, 1, angle / radPerDegree);
+ } else {
+ *mMatrix2D = mMatrix2D->PreRotate(angle);
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::RotateSelf(double aRotX, const Optional<double>& aRotY,
+ const Optional<double>& aRotZ) {
+ double rotY;
+ double rotZ;
+ if (!aRotY.WasPassed() && !aRotZ.WasPassed()) {
+ rotZ = aRotX;
+ aRotX = 0;
+ rotY = 0;
+ } else {
+ rotY = aRotY.WasPassed() ? aRotY.Value() : 0;
+ rotZ = aRotZ.WasPassed() ? aRotZ.Value() : 0;
+ }
+
+ if (aRotX != 0 || rotY != 0) {
+ Ensure3DMatrix();
+ }
+
+ if (mMatrix3D) {
+ if (fmod(rotZ, 360) != 0) {
+ mMatrix3D->RotateZ(rotZ * radPerDegree);
+ }
+ if (fmod(rotY, 360) != 0) {
+ mMatrix3D->RotateY(rotY * radPerDegree);
+ }
+ if (fmod(aRotX, 360) != 0) {
+ mMatrix3D->RotateX(aRotX * radPerDegree);
+ }
+ } else if (fmod(rotZ, 360) != 0) {
+ *mMatrix2D = mMatrix2D->PreRotate(rotZ * radPerDegree);
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::RotateAxisAngleSelf(double aX, double aY, double aZ,
+ double aAngle) {
+ if (fmod(aAngle, 360) == 0) {
+ return this;
+ }
+
+ aAngle *= radPerDegree;
+
+ Ensure3DMatrix();
+ gfx::Matrix4x4Double m;
+ m.SetRotateAxisAngle(aX, aY, aZ, aAngle);
+
+ *mMatrix3D = m * *mMatrix3D;
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::SkewXSelf(double aSx) {
+ if (fmod(aSx, 360) == 0) {
+ return this;
+ }
+
+ if (mMatrix3D) {
+ gfx::Matrix4x4Double m;
+ m._21 = tan(aSx * radPerDegree);
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::MatrixDouble m;
+ m._21 = tan(aSx * radPerDegree);
+ *mMatrix2D = m * *mMatrix2D;
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::SkewYSelf(double aSy) {
+ if (fmod(aSy, 360) == 0) {
+ return this;
+ }
+
+ if (mMatrix3D) {
+ gfx::Matrix4x4Double m;
+ m._12 = tan(aSy * radPerDegree);
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::MatrixDouble m;
+ m._12 = tan(aSy * radPerDegree);
+ *mMatrix2D = m * *mMatrix2D;
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::InvertSelf() {
+ if (mMatrix3D) {
+ if (!mMatrix3D->Invert()) {
+ mMatrix3D->SetNAN();
+ }
+ } else if (!mMatrix2D->Invert()) {
+ mMatrix2D = nullptr;
+
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>();
+ mMatrix3D->SetNAN();
+ }
+
+ return this;
+}
+
+DOMMatrixReadOnly* DOMMatrixReadOnly::SetMatrixValue(
+ const nsACString& aTransformList, ErrorResult& aRv) {
+ // An empty string is a no-op.
+ if (aTransformList.IsEmpty()) {
+ return this;
+ }
+
+ gfx::Matrix4x4 transform;
+ bool contains3dTransform = false;
+ if (!ServoCSSParser::ParseTransformIntoMatrix(
+ aTransformList, contains3dTransform, transform)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ if (!contains3dTransform) {
+ mMatrix3D = nullptr;
+ if (!mMatrix2D) {
+ mMatrix2D = MakeUnique<gfx::MatrixDouble>();
+ }
+
+ SetA(transform._11);
+ SetB(transform._12);
+ SetC(transform._21);
+ SetD(transform._22);
+ SetE(transform._41);
+ SetF(transform._42);
+ } else {
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(transform);
+ mMatrix2D = nullptr;
+ }
+
+ return this;
+}
+
+DOMMatrix* DOMMatrix::SetMatrixValue(const nsACString& aTransformList,
+ ErrorResult& aRv) {
+ DOMMatrixReadOnly::SetMatrixValue(aTransformList, aRv);
+ return this;
+}
+
+JSObject* DOMMatrix::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMMatrix_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DOMMatrix.h b/dom/base/DOMMatrix.h
new file mode 100644
index 0000000000..19eb6f8ae4
--- /dev/null
+++ b/dom/base/DOMMatrix.h
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_DOMMATRIX_H_
+#define MOZILLA_DOM_DOMMATRIX_H_
+
+#include <cstring>
+#include <utility>
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIGlobalObject;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class DOMMatrix;
+class DOMPoint;
+template <typename T>
+class Optional;
+class UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly;
+struct DOMPointInit;
+struct DOMMatrixInit;
+struct DOMMatrix2DInit;
+
+class DOMMatrixReadOnly : public nsWrapperCache {
+ public:
+ explicit DOMMatrixReadOnly(nsISupports* aParent)
+ : mParent(aParent), mMatrix2D(new gfx::MatrixDouble()) {}
+
+ DOMMatrixReadOnly(nsISupports* aParent, const DOMMatrixReadOnly& other)
+ : mParent(aParent) {
+ if (other.mMatrix2D) {
+ mMatrix2D = MakeUnique<gfx::MatrixDouble>(*other.mMatrix2D);
+ } else {
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(*other.mMatrix3D);
+ }
+ }
+
+ DOMMatrixReadOnly(nsISupports* aParent, const gfx::Matrix4x4& aMatrix)
+ : mParent(aParent) {
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(aMatrix);
+ }
+
+ DOMMatrixReadOnly(nsISupports* aParent, const gfx::Matrix& aMatrix)
+ : mParent(aParent) {
+ mMatrix2D = MakeUnique<gfx::MatrixDouble>(aMatrix);
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMMatrixReadOnly)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(DOMMatrixReadOnly)
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMMatrixReadOnly> FromMatrix(
+ nsISupports* aParent, const DOMMatrix2DInit& aMatrixInit,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> FromMatrix(
+ nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> FromMatrix(
+ const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> FromFloat32Array(
+ const GlobalObject& aGlobal, const Float32Array& aArray32,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> FromFloat64Array(
+ const GlobalObject& aGlobal, const Float64Array& aArray64,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
+ aArg,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrixReadOnly> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ // clang-format off
+#define GetMatrixMember(entry2D, entry3D, default) \
+ { \
+ if (mMatrix3D) { \
+ return mMatrix3D->entry3D; \
+ } \
+ return mMatrix2D->entry2D; \
+ }
+
+#define Get3DMatrixMember(entry3D, default) \
+ { \
+ if (mMatrix3D) { \
+ return mMatrix3D->entry3D; \
+ } \
+ return default; \
+ }
+
+ double A() const GetMatrixMember(_11, _11, 1.0)
+ double B() const GetMatrixMember(_12, _12, 0)
+ double C() const GetMatrixMember(_21, _21, 0)
+ double D() const GetMatrixMember(_22, _22, 1.0)
+ double E() const GetMatrixMember(_31, _41, 0)
+ double F() const GetMatrixMember(_32, _42, 0)
+
+ double M11() const GetMatrixMember(_11, _11, 1.0)
+ double M12() const GetMatrixMember(_12, _12, 0)
+ double M13() const Get3DMatrixMember(_13, 0)
+ double M14() const Get3DMatrixMember(_14, 0)
+ double M21() const GetMatrixMember(_21, _21, 0)
+ double M22() const GetMatrixMember(_22, _22, 1.0)
+ double M23() const Get3DMatrixMember(_23, 0)
+ double M24() const Get3DMatrixMember(_24, 0)
+ double M31() const Get3DMatrixMember(_31, 0)
+ double M32() const Get3DMatrixMember(_32, 0)
+ double M33() const Get3DMatrixMember(_33, 1.0)
+ double M34() const Get3DMatrixMember(_34, 0)
+ double M41() const GetMatrixMember(_31, _41, 0)
+ double M42() const GetMatrixMember(_32, _42, 0)
+ double M43() const Get3DMatrixMember(_43, 0)
+ double M44() const Get3DMatrixMember(_44, 1.0)
+
+#undef GetMatrixMember
+#undef Get3DMatrixMember
+
+ // Defined here so we can construct DOMMatrixReadOnly objects.
+#define Set2DMatrixMember(entry2D, entry3D) \
+ { \
+ if (mMatrix3D) { \
+ mMatrix3D->entry3D = v; \
+ } else { \
+ mMatrix2D->entry2D = v; \
+ } \
+ }
+
+#define Set3DMatrixMember(entry3D, default) \
+ { \
+ if (mMatrix3D || (v != default)) { \
+ Ensure3DMatrix(); \
+ mMatrix3D->entry3D = v; \
+ } \
+ }
+
+ void SetA(double v) Set2DMatrixMember(_11, _11)
+ void SetB(double v) Set2DMatrixMember(_12, _12)
+ void SetC(double v) Set2DMatrixMember(_21, _21)
+ void SetD(double v) Set2DMatrixMember(_22, _22)
+ void SetE(double v) Set2DMatrixMember(_31, _41)
+ void SetF(double v) Set2DMatrixMember(_32, _42)
+
+ void SetM11(double v) Set2DMatrixMember(_11, _11)
+ void SetM12(double v) Set2DMatrixMember(_12, _12)
+ void SetM13(double v) Set3DMatrixMember(_13, 0)
+ void SetM14(double v) Set3DMatrixMember(_14, 0)
+ void SetM21(double v) Set2DMatrixMember(_21, _21)
+ void SetM22(double v) Set2DMatrixMember(_22, _22)
+ void SetM23(double v) Set3DMatrixMember(_23, 0)
+ void SetM24(double v) Set3DMatrixMember(_24, 0)
+ void SetM31(double v) Set3DMatrixMember(_31, 0)
+ void SetM32(double v) Set3DMatrixMember(_32, 0)
+ void SetM33(double v) Set3DMatrixMember(_33, 1.0)
+ void SetM34(double v) Set3DMatrixMember(_34, 0)
+ void SetM41(double v) Set2DMatrixMember(_31, _41)
+ void SetM42(double v) Set2DMatrixMember(_32, _42)
+ void SetM43(double v) Set3DMatrixMember(_43, 0)
+ void SetM44(double v) Set3DMatrixMember(_44, 1.0)
+ ; // semi-colon here to get clang-format to align properly from here on
+
+#undef Set2DMatrixMember
+#undef Set3DMatrixMember
+ // clang-format on
+
+ already_AddRefed<DOMMatrix> Translate(double aTx, double aTy,
+ double aTz = 0) const;
+ already_AddRefed<DOMMatrix> Scale(double aScaleX,
+ const Optional<double>& aScaleY,
+ double aScaleZ, double aOriginX,
+ double aOriginY, double aOriginZ) const;
+ already_AddRefed<DOMMatrix> Scale3d(double aScale, double aOriginX = 0,
+ double aOriginY = 0,
+ double aOriginZ = 0) const;
+ already_AddRefed<DOMMatrix> ScaleNonUniform(double aScaleX,
+ double aScaleY) const;
+ already_AddRefed<DOMMatrix> Rotate(double aRotX,
+ const Optional<double>& aRotY,
+ const Optional<double>& aRotZ) const;
+ already_AddRefed<DOMMatrix> RotateFromVector(double aX, double aY) const;
+ already_AddRefed<DOMMatrix> RotateAxisAngle(double aX, double aY, double aZ,
+ double aAngle) const;
+ already_AddRefed<DOMMatrix> SkewX(double aSx) const;
+ already_AddRefed<DOMMatrix> SkewY(double aSy) const;
+ already_AddRefed<DOMMatrix> Multiply(const DOMMatrixInit& aOther,
+ ErrorResult& aRv) const;
+ already_AddRefed<DOMMatrix> FlipX() const;
+ already_AddRefed<DOMMatrix> FlipY() const;
+ already_AddRefed<DOMMatrix> Inverse() const;
+
+ bool Is2D() const;
+ bool IsIdentity() const;
+ already_AddRefed<DOMPoint> TransformPoint(const DOMPointInit& aPoint) const;
+ void ToFloat32Array(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) const;
+ void ToFloat64Array(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) const;
+ void Stringify(nsAString& aResult, ErrorResult& aRv);
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+ const gfx::MatrixDouble* GetInternal2D() const {
+ if (Is2D()) {
+ return mMatrix2D.get();
+ }
+ return nullptr;
+ }
+
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+ UniquePtr<gfx::MatrixDouble> mMatrix2D;
+ UniquePtr<gfx::Matrix4x4Double> mMatrix3D;
+
+ virtual ~DOMMatrixReadOnly() = default;
+
+ /**
+ * Sets data from a fully validated and fixed-up matrix init,
+ * where all of its members are properly defined.
+ * The init dictionary's dimension must match the matrix one.
+ */
+ void SetDataFromMatrix2DInit(const DOMMatrix2DInit& aMatrixInit);
+ void SetDataFromMatrixInit(const DOMMatrixInit& aMatrixInit);
+
+ DOMMatrixReadOnly* SetMatrixValue(const nsACString&, ErrorResult&);
+ void Ensure3DMatrix();
+
+ DOMMatrixReadOnly(nsISupports* aParent, bool is2D) : mParent(aParent) {
+ if (is2D) {
+ mMatrix2D = MakeUnique<gfx::MatrixDouble>();
+ } else {
+ mMatrix3D = MakeUnique<gfx::Matrix4x4Double>();
+ }
+ }
+
+ static bool ReadStructuredCloneElements(JSStructuredCloneReader* aReader,
+ DOMMatrixReadOnly* matrix);
+
+ private:
+ DOMMatrixReadOnly() = delete;
+ DOMMatrixReadOnly(const DOMMatrixReadOnly&) = delete;
+ DOMMatrixReadOnly& operator=(const DOMMatrixReadOnly&) = delete;
+};
+
+class DOMMatrix : public DOMMatrixReadOnly {
+ public:
+ explicit DOMMatrix(nsISupports* aParent) : DOMMatrixReadOnly(aParent) {}
+
+ DOMMatrix(nsISupports* aParent, const DOMMatrixReadOnly& other)
+ : DOMMatrixReadOnly(aParent, other) {}
+
+ DOMMatrix(nsISupports* aParent, const gfx::Matrix4x4& aMatrix)
+ : DOMMatrixReadOnly(aParent, aMatrix) {}
+
+ DOMMatrix(nsISupports* aParent, const gfx::Matrix& aMatrix)
+ : DOMMatrixReadOnly(aParent, aMatrix) {}
+
+ static already_AddRefed<DOMMatrix> FromMatrix(
+ nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrix> FromMatrix(
+ const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrix> FromFloat32Array(
+ const GlobalObject& aGlobal, const Float32Array& aArray32,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrix> FromFloat64Array(
+ const GlobalObject& aGlobal, const Float64Array& aArray64,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrix> Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
+ aArg,
+ ErrorResult& aRv);
+
+ static already_AddRefed<DOMMatrix> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMMatrix* MultiplySelf(const DOMMatrixInit& aOther, ErrorResult& aRv);
+ DOMMatrix* PreMultiplySelf(const DOMMatrixInit& aOther, ErrorResult& aRv);
+ DOMMatrix* TranslateSelf(double aTx, double aTy, double aTz = 0);
+ DOMMatrix* ScaleSelf(double aScaleX, const Optional<double>& aScaleY,
+ double aScaleZ, double aOriginX, double aOriginY,
+ double aOriginZ);
+ DOMMatrix* Scale3dSelf(double aScale, double aOriginX = 0,
+ double aOriginY = 0, double aOriginZ = 0);
+ DOMMatrix* RotateSelf(double aRotX, const Optional<double>& aRotY,
+ const Optional<double>& aRotZ);
+ DOMMatrix* RotateFromVectorSelf(double aX, double aY);
+ DOMMatrix* RotateAxisAngleSelf(double aX, double aY, double aZ,
+ double aAngle);
+ DOMMatrix* SkewXSelf(double aSx);
+ DOMMatrix* SkewYSelf(double aSy);
+ DOMMatrix* InvertSelf();
+ DOMMatrix* SetMatrixValue(const nsACString&, ErrorResult&);
+
+ virtual ~DOMMatrix() = default;
+
+ private:
+ DOMMatrix(nsISupports* aParent, bool is2D)
+ : DOMMatrixReadOnly(aParent, is2D) {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /*MOZILLA_DOM_DOMMATRIX_H_*/
diff --git a/dom/base/DOMMozPromiseRequestHolder.h b/dom/base/DOMMozPromiseRequestHolder.h
new file mode 100644
index 0000000000..0f0fc29d4d
--- /dev/null
+++ b/dom/base/DOMMozPromiseRequestHolder.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMMozPromiseRequestHolder_h
+#define mozilla_dom_DOMMozPromiseRequestHolder_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::dom {
+
+/**
+ * This is a helper class that can be used when MozPromises are
+ * being consumed by binding layer code. It effectively creates
+ * a MozPromiseRequestHolder that auto-disconnects when the binding's
+ * global is disconnected.
+ *
+ * It can be used like this:
+ *
+ * RefPtr<Promise>
+ * SomeAsyncAPI(Args& aArgs, ErrorResult& aRv)
+ * {
+ * nsIGlobalObject* global = GetParentObject();
+ * if (!global) {
+ * aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ * return nullptr;
+ * }
+ *
+ * RefPtr<Promise> outer = Promise::Create(global, aRv);
+ * if (aRv.Failed()) {
+ * return nullptr;
+ * }
+ *
+ * RefPtr<DOMMozPromiseRequestHolder> holder =
+ * new DOMMozPromiseRequestHolder(global);
+ *
+ * DoAsyncStuff()->Then(
+ * global->EventTargetFor(TaskCategory::Other), __func__,
+ * [holder, outer] (const Result& aResult) {
+ * holder->Complete();
+ *
+ * // Note, you can access the holder's bound global in
+ * // your reaction handler. Its mostly likely set if
+ * // the handler fires, but you still must check for
+ * // its existence since something could disconnect
+ * // the global between when the MozPromise reaction
+ * // runnable is queued and when it actually runs.
+ * nsIGlobalObject* global = holder->GetParentObject();
+ * NS_ENSURE_TRUE_VOID(global);
+ *
+ * outer->MaybeResolve(aResult);
+ * }, [holder, outer] (nsresult aRv) {
+ * holder->Complete();
+ * outer->MaybeReject(aRv);
+ * })->Track(*holder);
+ *
+ * return outer.forget();
+ * }
+ *
+ * NOTE: Currently this helper class extends DETH. This is only
+ * so that it can bind to the global and receive the
+ * DisconnectFromOwner() method call. In this future the
+ * binding code should be factored out so DETH is not
+ * needed here. See bug 1456893.
+ */
+template <typename PromiseType>
+class DOMMozPromiseRequestHolder final : public DOMEventTargetHelper {
+ MozPromiseRequestHolder<PromiseType> mHolder;
+
+ ~DOMMozPromiseRequestHolder() = default;
+
+ void DisconnectFromOwner() override {
+ mHolder.DisconnectIfExists();
+ DOMEventTargetHelper::DisconnectFromOwner();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ // We are extending DETH to get notified when the global goes
+ // away, but this object should never actually be exposed to
+ // script.
+ MOZ_CRASH("illegal method");
+ }
+
+ public:
+ explicit DOMMozPromiseRequestHolder(nsIGlobalObject* aGlobal)
+ : DOMEventTargetHelper(aGlobal) {
+ MOZ_DIAGNOSTIC_ASSERT(aGlobal);
+ }
+
+ operator MozPromiseRequestHolder<PromiseType>&() { return mHolder; }
+
+ operator const MozPromiseRequestHolder<PromiseType>&() const {
+ return mHolder;
+ }
+
+ void Complete() { mHolder.Complete(); }
+
+ void DisconnectIfExists() { mHolder.DisconnectIfExists(); }
+
+ bool Exists() const { return mHolder.Exists(); }
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(DOMMozPromiseRequestHolder,
+ DOMEventTargetHelper)
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_DOMMozPromiseRequestHolder_h
diff --git a/dom/base/DOMParser.cpp b/dom/base/DOMParser.cpp
new file mode 100644
index 0000000000..27e0650550
--- /dev/null
+++ b/dom/base/DOMParser.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMParser.h"
+
+#include "nsNetUtil.h"
+#include "nsDOMString.h"
+#include "MainThreadUtils.h"
+#include "SystemPrincipal.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIStreamListener.h"
+#include "nsStringStream.h"
+#include "nsCRT.h"
+#include "nsStreamUtils.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsError.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+DOMParser::DOMParser(nsIGlobalObject* aOwner, nsIPrincipal* aDocPrincipal,
+ nsIURI* aDocumentURI, nsIURI* aBaseURI)
+ : mOwner(aOwner),
+ mPrincipal(aDocPrincipal),
+ mDocumentURI(aDocumentURI),
+ mBaseURI(aBaseURI),
+ mForceEnableXULXBL(false),
+ mForceEnableDTD(false) {
+ MOZ_ASSERT(aDocPrincipal);
+ MOZ_ASSERT(aDocumentURI);
+}
+
+DOMParser::~DOMParser() = default;
+
+// QueryInterface implementation for DOMParser
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMParser, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMParser)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMParser)
+
+already_AddRefed<Document> DOMParser::ParseFromString(const nsAString& aStr,
+ SupportedType aType,
+ ErrorResult& aRv) {
+ if (aType == SupportedType::Text_html) {
+ nsCOMPtr<Document> document = SetUpDocument(DocumentFlavorHTML, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Keep the XULXBL state in sync with the XML case.
+ if (mForceEnableXULXBL) {
+ document->ForceEnableXULXBL();
+ }
+
+ if (mForceEnableDTD) {
+ document->ForceSkipDTDSecurityChecks();
+ }
+
+ nsresult rv = nsContentUtils::ParseDocumentHTML(aStr, document, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return document.forget();
+ }
+
+ nsAutoCString utf8str;
+ // Convert from UTF16 to UTF8 using fallible allocations
+ if (!AppendUTF16toUTF8(aStr, utf8str, mozilla::fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ // The new stream holds a reference to the buffer
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), utf8str,
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return ParseFromStream(stream, u"UTF-8"_ns, utf8str.Length(), aType, aRv);
+}
+
+already_AddRefed<Document> DOMParser::ParseFromSafeString(const nsAString& aStr,
+ SupportedType aType,
+ ErrorResult& aRv) {
+ // Create the new document with the same principal as `mOwner`, even if it is
+ // the system principal. This will ensure that nodes from the returned
+ // document are in the same DocGroup as the owner global's document, allowing
+ // nodes to be adopted.
+ nsCOMPtr<nsIPrincipal> docPrincipal = mPrincipal;
+ if (mOwner && mOwner->PrincipalOrNull()) {
+ mPrincipal = mOwner->PrincipalOrNull();
+ }
+
+ RefPtr<Document> ret = ParseFromString(aStr, aType, aRv);
+ mPrincipal = docPrincipal;
+ return ret.forget();
+}
+
+already_AddRefed<Document> DOMParser::ParseFromBuffer(const Uint8Array& aBuf,
+ SupportedType aType,
+ ErrorResult& aRv) {
+ aBuf.ComputeState();
+ return ParseFromBuffer(Span(aBuf.Data(), aBuf.Length()), aType, aRv);
+}
+
+already_AddRefed<Document> DOMParser::ParseFromBuffer(Span<const uint8_t> aBuf,
+ SupportedType aType,
+ ErrorResult& aRv) {
+ // The new stream holds a reference to the buffer
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(
+ getter_AddRefs(stream),
+ Span(reinterpret_cast<const char*>(aBuf.Elements()), aBuf.Length()),
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return ParseFromStream(stream, VoidString(), aBuf.Length(), aType, aRv);
+}
+
+already_AddRefed<Document> DOMParser::ParseFromStream(nsIInputStream* aStream,
+ const nsAString& aCharset,
+ int32_t aContentLength,
+ SupportedType aType,
+ ErrorResult& aRv) {
+ bool svg = (aType == SupportedType::Image_svg_xml);
+
+ // For now, we can only create XML documents.
+ // XXXsmaug Should we create an HTMLDocument (in XHTML mode)
+ // for "application/xhtml+xml"?
+ if (aType != SupportedType::Text_xml &&
+ aType != SupportedType::Application_xml &&
+ aType != SupportedType::Application_xhtml_xml && !svg) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+
+ // Put the nsCOMPtr out here so we hold a ref to the stream as needed
+ nsCOMPtr<nsIInputStream> stream = aStream;
+ if (!NS_InputStreamIsBuffered(stream)) {
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ stream.forget(), 4096);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ stream = bufferedStream;
+ }
+
+ nsCOMPtr<Document> document =
+ SetUpDocument(svg ? DocumentFlavorSVG : DocumentFlavorLegacyGuess, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Create a fake channel
+ nsCOMPtr<nsIChannel> parserChannel;
+ NS_NewInputStreamChannel(
+ getter_AddRefs(parserChannel), mDocumentURI,
+ nullptr, // aStream
+ mPrincipal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER,
+ nsDependentCSubstring(SupportedTypeValues::GetString(aType)));
+ if (NS_WARN_IF(!parserChannel)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!DOMStringIsNull(aCharset)) {
+ parserChannel->SetContentCharset(NS_ConvertUTF16toUTF8(aCharset));
+ }
+
+ // Tell the document to start loading
+ nsCOMPtr<nsIStreamListener> listener;
+
+ // Keep the XULXBL state in sync with the HTML case
+ if (mForceEnableXULXBL) {
+ document->ForceEnableXULXBL();
+ }
+
+ if (mForceEnableDTD) {
+ document->ForceSkipDTDSecurityChecks();
+ }
+
+ // Have to pass false for reset here, else the reset will remove
+ // our event listener. Should that listener addition move to later
+ // than this call?
+ nsresult rv =
+ document->StartDocumentLoad(kLoadAsData, parserChannel, nullptr, nullptr,
+ getter_AddRefs(listener), false);
+
+ if (NS_FAILED(rv) || !listener) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Now start pumping data to the listener
+ nsresult status;
+
+ rv = listener->OnStartRequest(parserChannel);
+ if (NS_FAILED(rv)) parserChannel->Cancel(rv);
+ parserChannel->GetStatus(&status);
+
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
+ rv = listener->OnDataAvailable(parserChannel, stream, 0, aContentLength);
+ if (NS_FAILED(rv)) parserChannel->Cancel(rv);
+ parserChannel->GetStatus(&status);
+ }
+
+ rv = listener->OnStopRequest(parserChannel, status);
+ // Failure returned from OnStopRequest does not affect the final status of
+ // the channel, so we do not need to call Cancel(rv) as we do above.
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return document.forget();
+}
+
+/*static */
+already_AddRefed<DOMParser> DOMParser::Constructor(const GlobalObject& aOwner,
+ ErrorResult& rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIPrincipal> docPrincipal = aOwner.GetSubjectPrincipal();
+ nsCOMPtr<nsIURI> documentURI;
+ nsIURI* baseURI = nullptr;
+ if (docPrincipal->IsSystemPrincipal()) {
+ docPrincipal = NullPrincipal::Create(OriginAttributes());
+ documentURI = docPrincipal->GetURI();
+ } else {
+ // Grab document and base URIs off the window our constructor was
+ // called on. Error out if anything untoward happens.
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aOwner.GetAsSupports());
+ if (!window) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ baseURI = window->GetDocBaseURI();
+ documentURI = window->GetDocumentURI();
+ }
+
+ if (!documentURI) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aOwner.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<DOMParser> domParser =
+ new DOMParser(global, docPrincipal, documentURI, baseURI);
+ return domParser.forget();
+}
+
+// static
+already_AddRefed<DOMParser> DOMParser::CreateWithoutGlobal(ErrorResult& aRv) {
+ nsCOMPtr<nsIPrincipal> docPrincipal =
+ NullPrincipal::Create(OriginAttributes());
+
+ nsCOMPtr<nsIURI> documentURI = docPrincipal->GetURI();
+ if (!documentURI) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<DOMParser> domParser =
+ new DOMParser(nullptr, docPrincipal, documentURI, nullptr);
+ return domParser.forget();
+}
+
+already_AddRefed<Document> DOMParser::SetUpDocument(DocumentFlavor aFlavor,
+ ErrorResult& aRv) {
+ // We should really just use mOwner here, but Document gets confused
+ // if we pass it a scriptHandlingObject that doesn't QI to
+ // nsIScriptGlobalObject, and test_isequalnode.js (an xpcshell test without
+ // a window global) breaks. The correct solution is just to wean Document off
+ // of nsIScriptGlobalObject, but that's a yak to shave another day.
+ nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
+ do_QueryInterface(mOwner);
+
+ // Try to inherit a style backend.
+ NS_ASSERTION(mPrincipal, "Must have principal by now");
+ NS_ASSERTION(mDocumentURI, "Must have document URI by now");
+
+ nsCOMPtr<Document> doc;
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(doc), u""_ns, u""_ns, nullptr,
+ mDocumentURI, mBaseURI, mPrincipal, true,
+ scriptHandlingObject, aFlavor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return doc.forget();
+}
diff --git a/dom/base/DOMParser.h b/dom/base/DOMParser.h
new file mode 100644
index 0000000000..efb4270ac8
--- /dev/null
+++ b/dom/base/DOMParser.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMParser_h_
+#define mozilla_dom_DOMParser_h_
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Document.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Span.h"
+#include "mozilla/dom/DOMParserBinding.h"
+#include "mozilla/dom/TypedArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class DOMParser final : public nsISupports, public nsWrapperCache {
+ typedef mozilla::dom::GlobalObject GlobalObject;
+
+ virtual ~DOMParser();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMParser)
+
+ // WebIDL API
+ static already_AddRefed<DOMParser> Constructor(const GlobalObject& aOwner,
+ mozilla::ErrorResult& rv);
+
+ already_AddRefed<Document> ParseFromString(const nsAString& aStr,
+ SupportedType aType,
+ ErrorResult& aRv);
+
+ // ChromeOnly API
+ already_AddRefed<Document> ParseFromSafeString(const nsAString& aStr,
+ SupportedType aType,
+ ErrorResult& aRv);
+ // Sequence converts to Span, so we can use this overload for both
+ // the Sequence case and our internal uses.
+ already_AddRefed<Document> ParseFromBuffer(Span<const uint8_t> aBuf,
+ SupportedType aType,
+ ErrorResult& aRv);
+
+ already_AddRefed<Document> ParseFromBuffer(const Uint8Array& aBuf,
+ SupportedType aType,
+ ErrorResult& aRv);
+
+ already_AddRefed<Document> ParseFromStream(nsIInputStream* aStream,
+ const nsAString& aCharset,
+ int32_t aContentLength,
+ SupportedType aType,
+ ErrorResult& aRv);
+
+ void ForceEnableXULXBL() {
+ mForceEnableXULXBL = true;
+ ForceEnableDTD();
+ }
+
+ void ForceEnableDTD() { mForceEnableDTD = true; }
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return mozilla::dom::DOMParser_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ // A way to create a non-global-associated DOMParser from C++.
+ static already_AddRefed<DOMParser> CreateWithoutGlobal(ErrorResult& aRv);
+
+ private:
+ DOMParser(nsIGlobalObject* aOwner, nsIPrincipal* aDocPrincipal,
+ nsIURI* aDocumentURI, nsIURI* aBaseURI);
+
+ already_AddRefed<Document> SetUpDocument(DocumentFlavor aFlavor,
+ ErrorResult& aRv);
+
+ nsCOMPtr<nsIGlobalObject> mOwner;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ bool mForceEnableXULXBL;
+ bool mForceEnableDTD;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/DOMPoint.cpp b/dom/base/DOMPoint.cpp
new file mode 100644
index 0000000000..510bb76b07
--- /dev/null
+++ b/dom/base/DOMPoint.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMPoint.h"
+
+#include <cstdint>
+#include "js/StructuredClone.h"
+#include "mozilla/Casting.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMMatrix.h"
+#include "mozilla/dom/DOMPointBinding.h"
+#include "nsIGlobalObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMPointReadOnly, mParent)
+
+already_AddRefed<DOMPointReadOnly> DOMPointReadOnly::FromPoint(
+ const GlobalObject& aGlobal, const DOMPointInit& aParams) {
+ RefPtr<DOMPointReadOnly> obj = new DOMPointReadOnly(
+ aGlobal.GetAsSupports(), aParams.mX, aParams.mY, aParams.mZ, aParams.mW);
+ return obj.forget();
+}
+
+already_AddRefed<DOMPointReadOnly> DOMPointReadOnly::Constructor(
+ const GlobalObject& aGlobal, double aX, double aY, double aZ, double aW) {
+ RefPtr<DOMPointReadOnly> obj =
+ new DOMPointReadOnly(aGlobal.GetAsSupports(), aX, aY, aZ, aW);
+ return obj.forget();
+}
+
+JSObject* DOMPointReadOnly::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMPointReadOnly_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMPoint> DOMPointReadOnly::MatrixTransform(
+ const DOMMatrixInit& aInit, ErrorResult& aRv) {
+ RefPtr<DOMMatrixReadOnly> matrix =
+ DOMMatrixReadOnly::FromMatrix(mParent, aInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ DOMPointInit init;
+ init.mX = this->mX;
+ init.mY = this->mY;
+ init.mZ = this->mZ;
+ init.mW = this->mW;
+ RefPtr<DOMPoint> point = matrix->TransformPoint(init);
+ return point.forget();
+}
+
+// https://drafts.fxtf.org/geometry/#structured-serialization
+bool DOMPointReadOnly::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+#define WriteDouble(d) \
+ JS_WriteUint32Pair(aWriter, (BitwiseCast<uint64_t>(d) >> 32) & 0xffffffff, \
+ BitwiseCast<uint64_t>(d) & 0xffffffff)
+
+ return WriteDouble(mX) && WriteDouble(mY) && WriteDouble(mZ) &&
+ WriteDouble(mW);
+
+#undef WriteDouble
+}
+
+// static
+already_AddRefed<DOMPointReadOnly> DOMPointReadOnly::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<DOMPointReadOnly> retval = new DOMPointReadOnly(aGlobal);
+ if (!retval->ReadStructuredClone(aReader)) {
+ return nullptr;
+ }
+ return retval.forget();
+ ;
+}
+
+bool DOMPointReadOnly::ReadStructuredClone(JSStructuredCloneReader* aReader) {
+ uint32_t high;
+ uint32_t low;
+
+#define ReadDouble(d) \
+ if (!JS_ReadUint32Pair(aReader, &high, &low)) { \
+ return false; \
+ } \
+ (*(d) = BitwiseCast<double>(static_cast<uint64_t>(high) << 32 | low))
+
+ ReadDouble(&mX);
+ ReadDouble(&mY);
+ ReadDouble(&mZ);
+ ReadDouble(&mW);
+
+ return true;
+#undef ReadDouble
+}
+
+already_AddRefed<DOMPoint> DOMPoint::FromPoint(const GlobalObject& aGlobal,
+ const DOMPointInit& aParams) {
+ RefPtr<DOMPoint> obj = new DOMPoint(aGlobal.GetAsSupports(), aParams.mX,
+ aParams.mY, aParams.mZ, aParams.mW);
+ return obj.forget();
+}
+
+already_AddRefed<DOMPoint> DOMPoint::Constructor(const GlobalObject& aGlobal,
+ double aX, double aY,
+ double aZ, double aW) {
+ RefPtr<DOMPoint> obj = new DOMPoint(aGlobal.GetAsSupports(), aX, aY, aZ, aW);
+ return obj.forget();
+}
+
+JSObject* DOMPoint::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMPoint_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<DOMPoint> DOMPoint::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<DOMPoint> retval = new DOMPoint(aGlobal);
+ if (!retval->ReadStructuredClone(aReader)) {
+ return nullptr;
+ }
+ return retval.forget();
+ ;
+}
diff --git a/dom/base/DOMPoint.h b/dom/base/DOMPoint.h
new file mode 100644
index 0000000000..d49c8fd441
--- /dev/null
+++ b/dom/base/DOMPoint.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOMPOINT_H_
+#define MOZILLA_DOMPOINT_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIGlobalObject;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class DOMPoint;
+struct DOMPointInit;
+struct DOMMatrixInit;
+
+class DOMPointReadOnly : public nsWrapperCache {
+ public:
+ explicit DOMPointReadOnly(nsISupports* aParent, double aX = 0.0,
+ double aY = 0.0, double aZ = 0.0, double aW = 1.0)
+ : mParent(aParent), mX(aX), mY(aY), mZ(aZ), mW(aW) {}
+
+ static already_AddRefed<DOMPointReadOnly> FromPoint(
+ const GlobalObject& aGlobal, const DOMPointInit& aParams);
+ static already_AddRefed<DOMPointReadOnly> Constructor(
+ const GlobalObject& aGlobal, double aX, double aY, double aZ, double aW);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMPointReadOnly)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(DOMPointReadOnly)
+
+ double X() const { return mX; }
+ double Y() const { return mY; }
+ double Z() const { return mZ; }
+ double W() const { return mW; }
+
+ already_AddRefed<DOMPoint> MatrixTransform(const DOMMatrixInit& aInit,
+ ErrorResult& aRv);
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ static already_AddRefed<DOMPointReadOnly> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ protected:
+ virtual ~DOMPointReadOnly() = default;
+
+ // Shared implementation of ReadStructuredClone for DOMPoint and
+ // DOMPointReadOnly.
+ bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+ nsCOMPtr<nsISupports> mParent;
+ double mX, mY, mZ, mW;
+};
+
+class DOMPoint final : public DOMPointReadOnly {
+ public:
+ explicit DOMPoint(nsISupports* aParent, double aX = 0.0, double aY = 0.0,
+ double aZ = 0.0, double aW = 1.0)
+ : DOMPointReadOnly(aParent, aX, aY, aZ, aW) {}
+
+ static already_AddRefed<DOMPoint> FromPoint(const GlobalObject& aGlobal,
+ const DOMPointInit& aParams);
+ static already_AddRefed<DOMPoint> Constructor(const GlobalObject& aGlobal,
+ double aX, double aY, double aZ,
+ double aW);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMPoint> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+ using DOMPointReadOnly::ReadStructuredClone;
+
+ void SetX(double aX) { mX = aX; }
+ void SetY(double aY) { mY = aY; }
+ void SetZ(double aZ) { mZ = aZ; }
+ void SetW(double aW) { mW = aW; }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /*MOZILLA_DOMPOINT_H_*/
diff --git a/dom/base/DOMQuad.cpp b/dom/base/DOMQuad.cpp
new file mode 100644
index 0000000000..38acffd049
--- /dev/null
+++ b/dom/base/DOMQuad.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMQuad.h"
+
+#include <algorithm>
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMQuadBinding.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMRectBinding.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "nsIGlobalObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMQuad, mParent, mPoints[0], mPoints[1],
+ mPoints[2], mPoints[3])
+
+DOMQuad::DOMQuad(nsISupports* aParent, CSSPoint aPoints[4]) : mParent(aParent) {
+ for (uint32_t i = 0; i < 4; ++i) {
+ mPoints[i] = new DOMPoint(aParent, aPoints[i].x, aPoints[i].y);
+ }
+}
+
+DOMQuad::DOMQuad(nsISupports* aParent) : mParent(aParent) {}
+
+DOMQuad::~DOMQuad() = default;
+
+JSObject* DOMQuad::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMQuad_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMQuad> DOMQuad::FromRect(const GlobalObject& aGlobal,
+ const DOMRectInit& aInit) {
+ nsISupports* parent = aGlobal.GetAsSupports();
+ RefPtr<DOMQuad> obj = new DOMQuad(parent);
+ obj->mPoints[0] = new DOMPoint(parent, aInit.mX, aInit.mY, 0, 1);
+ obj->mPoints[1] =
+ new DOMPoint(parent, aInit.mX + aInit.mWidth, aInit.mY, 0, 1);
+ obj->mPoints[2] = new DOMPoint(parent, aInit.mX + aInit.mWidth,
+ aInit.mY + aInit.mHeight, 0, 1);
+ obj->mPoints[3] =
+ new DOMPoint(parent, aInit.mX, aInit.mY + aInit.mHeight, 0, 1);
+ return obj.forget();
+}
+
+already_AddRefed<DOMQuad> DOMQuad::FromQuad(const GlobalObject& aGlobal,
+ const DOMQuadInit& aInit) {
+ RefPtr<DOMQuad> obj = new DOMQuad(aGlobal.GetAsSupports());
+ obj->mPoints[0] = DOMPoint::FromPoint(aGlobal, aInit.mP1);
+ obj->mPoints[1] = DOMPoint::FromPoint(aGlobal, aInit.mP2);
+ obj->mPoints[2] = DOMPoint::FromPoint(aGlobal, aInit.mP3);
+ obj->mPoints[3] = DOMPoint::FromPoint(aGlobal, aInit.mP4);
+ return obj.forget();
+}
+
+already_AddRefed<DOMQuad> DOMQuad::Constructor(const GlobalObject& aGlobal,
+ const DOMPointInit& aP1,
+ const DOMPointInit& aP2,
+ const DOMPointInit& aP3,
+ const DOMPointInit& aP4) {
+ RefPtr<DOMQuad> obj = new DOMQuad(aGlobal.GetAsSupports());
+ obj->mPoints[0] = DOMPoint::FromPoint(aGlobal, aP1);
+ obj->mPoints[1] = DOMPoint::FromPoint(aGlobal, aP2);
+ obj->mPoints[2] = DOMPoint::FromPoint(aGlobal, aP3);
+ obj->mPoints[3] = DOMPoint::FromPoint(aGlobal, aP4);
+ return obj.forget();
+}
+
+already_AddRefed<DOMQuad> DOMQuad::Constructor(const GlobalObject& aGlobal,
+ const DOMRectReadOnly& aRect) {
+ CSSPoint points[4];
+ Float x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height();
+ points[0] = CSSPoint(x, y);
+ points[1] = CSSPoint(x + w, y);
+ points[2] = CSSPoint(x + w, y + h);
+ points[3] = CSSPoint(x, y + h);
+ RefPtr<DOMQuad> obj = new DOMQuad(aGlobal.GetAsSupports(), points);
+ return obj.forget();
+}
+
+void DOMQuad::GetHorizontalMinMax(double* aX1, double* aX2) const {
+ double x1, x2;
+ x1 = x2 = Point(0)->X();
+ for (uint32_t i = 1; i < 4; ++i) {
+ double x = Point(i)->X();
+ x1 = NaNSafeMin(x1, x);
+ x2 = NaNSafeMax(x2, x);
+ }
+ *aX1 = x1;
+ *aX2 = x2;
+}
+
+void DOMQuad::GetVerticalMinMax(double* aY1, double* aY2) const {
+ double y1, y2;
+ y1 = y2 = Point(0)->Y();
+ for (uint32_t i = 1; i < 4; ++i) {
+ double y = Point(i)->Y();
+ y1 = NaNSafeMin(y1, y);
+ y2 = NaNSafeMax(y2, y);
+ }
+ *aY1 = y1;
+ *aY2 = y2;
+}
+
+already_AddRefed<DOMRectReadOnly> DOMQuad::GetBounds() const {
+ double x1, x2;
+ double y1, y2;
+
+ GetHorizontalMinMax(&x1, &x2);
+ GetVerticalMinMax(&y1, &y2);
+
+ RefPtr<DOMRectReadOnly> rval =
+ new DOMRectReadOnly(GetParentObject(), x1, y1, x2 - x1, y2 - y1);
+ return rval.forget();
+}
+
+// https://drafts.fxtf.org/geometry/#structured-serialization
+bool DOMQuad::WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const {
+ for (const auto& point : mPoints) {
+ if (!point->WriteStructuredClone(aCx, aWriter)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+already_AddRefed<DOMQuad> DOMQuad::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<DOMQuad> quad = new DOMQuad(aGlobal);
+ for (auto& point : quad->mPoints) {
+ point = DOMPoint::ReadStructuredClone(aCx, aGlobal, aReader);
+ if (!point) {
+ return nullptr;
+ }
+ }
+ return quad.forget();
+}
diff --git a/dom/base/DOMQuad.h b/dom/base/DOMQuad.h
new file mode 100644
index 0000000000..f6ca36f602
--- /dev/null
+++ b/dom/base/DOMQuad.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOMQUAD_H_
+#define MOZILLA_DOMQUAD_H_
+
+#include <cstdint>
+#include "Units.h"
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIGlobalObject;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla::dom {
+
+class DOMRectReadOnly;
+class DOMPoint;
+class GlobalObject;
+struct DOMPointInit;
+struct DOMQuadInit;
+struct DOMRectInit;
+
+class DOMQuad final : public nsWrapperCache {
+ ~DOMQuad();
+
+ public:
+ DOMQuad(nsISupports* aParent, CSSPoint aPoints[4]);
+ explicit DOMQuad(nsISupports* aParent);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMQuad)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(DOMQuad)
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMQuad> FromRect(const GlobalObject& aGlobal,
+ const DOMRectInit& aInit);
+
+ static already_AddRefed<DOMQuad> FromQuad(const GlobalObject& aGlobal,
+ const DOMQuadInit& aInit);
+
+ static already_AddRefed<DOMQuad> Constructor(const GlobalObject& aGlobal,
+ const DOMPointInit& aP1,
+ const DOMPointInit& aP2,
+ const DOMPointInit& aP3,
+ const DOMPointInit& aP4);
+ static already_AddRefed<DOMQuad> Constructor(const GlobalObject& aGlobal,
+ const DOMRectReadOnly& aRect);
+
+ already_AddRefed<DOMRectReadOnly> GetBounds() const;
+ DOMPoint* P1() const { return mPoints[0]; }
+ DOMPoint* P2() const { return mPoints[1]; }
+ DOMPoint* P3() const { return mPoints[2]; }
+ DOMPoint* P4() const { return mPoints[3]; }
+
+ DOMPoint* Point(uint32_t aIndex) const { return mPoints[aIndex]; }
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ static already_AddRefed<DOMQuad> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ protected:
+ void GetHorizontalMinMax(double* aX1, double* aX2) const;
+ void GetVerticalMinMax(double* aY1, double* aY2) const;
+
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<DOMPoint> mPoints[4];
+};
+
+} // namespace mozilla::dom
+
+#endif /*MOZILLA_DOMRECT_H_*/
diff --git a/dom/base/DOMRect.cpp b/dom/base/DOMRect.cpp
new file mode 100644
index 0000000000..d7ed164ec5
--- /dev/null
+++ b/dom/base/DOMRect.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMRect.h"
+
+#include <cmath>
+#include "js/StructuredClone.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Casting.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMRectBinding.h"
+#include "mozilla/dom/DOMRectListBinding.h"
+#include "nsIGlobalObject.h"
+#include "nsRect.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMRectReadOnly, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMRectReadOnly)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMRectReadOnly)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMRectReadOnly)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* DOMRectReadOnly::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(mParent);
+ return DOMRectReadOnly_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMRectReadOnly> DOMRectReadOnly::FromRect(
+ const GlobalObject& aGlobal, const DOMRectInit& aInit) {
+ RefPtr<DOMRectReadOnly> obj = new DOMRectReadOnly(
+ aGlobal.GetAsSupports(), aInit.mX, aInit.mY, aInit.mWidth, aInit.mHeight);
+ return obj.forget();
+}
+
+already_AddRefed<DOMRectReadOnly> DOMRectReadOnly::Constructor(
+ const GlobalObject& aGlobal, double aX, double aY, double aWidth,
+ double aHeight) {
+ RefPtr<DOMRectReadOnly> obj =
+ new DOMRectReadOnly(aGlobal.GetAsSupports(), aX, aY, aWidth, aHeight);
+ return obj.forget();
+}
+
+// https://drafts.fxtf.org/geometry/#structured-serialization
+bool DOMRectReadOnly::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+#define WriteDouble(d) \
+ JS_WriteUint32Pair(aWriter, (BitwiseCast<uint64_t>(d) >> 32) & 0xffffffff, \
+ BitwiseCast<uint64_t>(d) & 0xffffffff)
+
+ return WriteDouble(mX) && WriteDouble(mY) && WriteDouble(mWidth) &&
+ WriteDouble(mHeight);
+
+#undef WriteDouble
+}
+
+// static
+already_AddRefed<DOMRectReadOnly> DOMRectReadOnly::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<DOMRectReadOnly> retval = new DOMRectReadOnly(aGlobal);
+ if (!retval->ReadStructuredClone(aReader)) {
+ return nullptr;
+ }
+ return retval.forget();
+}
+
+bool DOMRectReadOnly::ReadStructuredClone(JSStructuredCloneReader* aReader) {
+ uint32_t high;
+ uint32_t low;
+
+#define ReadDouble(d) \
+ if (!JS_ReadUint32Pair(aReader, &high, &low)) { \
+ return false; \
+ } \
+ (*(d) = BitwiseCast<double>(static_cast<uint64_t>(high) << 32 | low))
+
+ ReadDouble(&mX);
+ ReadDouble(&mY);
+ ReadDouble(&mWidth);
+ ReadDouble(&mHeight);
+
+ return true;
+
+#undef ReadDouble
+}
+
+// -----------------------------------------------------------------------------
+
+JSObject* DOMRect::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(mParent);
+ return DOMRect_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMRect> DOMRect::FromRect(const GlobalObject& aGlobal,
+ const DOMRectInit& aInit) {
+ RefPtr<DOMRect> obj = new DOMRect(aGlobal.GetAsSupports(), aInit.mX, aInit.mY,
+ aInit.mWidth, aInit.mHeight);
+ return obj.forget();
+}
+
+already_AddRefed<DOMRect> DOMRect::Constructor(const GlobalObject& aGlobal,
+ double aX, double aY,
+ double aWidth, double aHeight) {
+ RefPtr<DOMRect> obj =
+ new DOMRect(aGlobal.GetAsSupports(), aX, aY, aWidth, aHeight);
+ return obj.forget();
+}
+
+// static
+already_AddRefed<DOMRect> DOMRect::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<DOMRect> retval = new DOMRect(aGlobal);
+ if (!retval->ReadStructuredClone(aReader)) {
+ return nullptr;
+ }
+ return retval.forget();
+}
+
+// -----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMRectList, mParent, mArray)
+
+NS_INTERFACE_TABLE_HEAD(DOMRectList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE0(DOMRectList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DOMRectList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMRectList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMRectList)
+
+JSObject* DOMRectList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::DOMRectList_Binding::Wrap(cx, this, aGivenProto);
+}
+
+static double RoundFloat(double aValue) { return floor(aValue + 0.5); }
+
+void DOMRect::SetLayoutRect(const nsRect& aLayoutRect) {
+ double scale = 65536.0;
+ // Round to the nearest 1/scale units. We choose scale so it can be
+ // represented exactly by machine floating point.
+ double scaleInv = 1 / scale;
+ double t2pScaled = scale / AppUnitsPerCSSPixel();
+ double x = RoundFloat(aLayoutRect.x * t2pScaled) * scaleInv;
+ double y = RoundFloat(aLayoutRect.y * t2pScaled) * scaleInv;
+ SetRect(x, y, RoundFloat(aLayoutRect.XMost() * t2pScaled) * scaleInv - x,
+ RoundFloat(aLayoutRect.YMost() * t2pScaled) * scaleInv - y);
+}
diff --git a/dom/base/DOMRect.h b/dom/base/DOMRect.h
new file mode 100644
index 0000000000..8cacd9a1dc
--- /dev/null
+++ b/dom/base/DOMRect.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOMRECT_H_
+#define MOZILLA_DOMRECT_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <new>
+#include <utility>
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIGlobalObject;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+struct nsRect;
+
+namespace mozilla::dom {
+
+class GlobalObject;
+struct DOMRectInit;
+
+class DOMRectReadOnly : public nsISupports, public nsWrapperCache {
+ protected:
+ virtual ~DOMRectReadOnly() = default;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMRectReadOnly)
+
+ explicit DOMRectReadOnly(nsISupports* aParent, double aX = 0, double aY = 0,
+ double aWidth = 0, double aHeight = 0)
+ : mParent(aParent), mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight) {}
+
+ nsISupports* GetParentObject() const {
+ MOZ_ASSERT(mParent);
+ return mParent;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMRectReadOnly> FromRect(const GlobalObject& aGlobal,
+ const DOMRectInit& aInit);
+
+ static already_AddRefed<DOMRectReadOnly> Constructor(
+ const GlobalObject& aGlobal, double aX, double aY, double aWidth,
+ double aHeight);
+
+ double X() const { return mX; }
+ double Y() const { return mY; }
+ double Width() const { return mWidth; }
+ double Height() const { return mHeight; }
+
+ double Left() const {
+ double x = X(), w = Width();
+ return NaNSafeMin(x, x + w);
+ }
+ double Top() const {
+ double y = Y(), h = Height();
+ return NaNSafeMin(y, y + h);
+ }
+ double Right() const {
+ double x = X(), w = Width();
+ return NaNSafeMax(x, x + w);
+ }
+ double Bottom() const {
+ double y = Y(), h = Height();
+ return NaNSafeMax(y, y + h);
+ }
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ static already_AddRefed<DOMRectReadOnly> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ protected:
+ // Shared implementation of ReadStructuredClone for DOMRect and
+ // DOMRectReadOnly.
+ bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+ nsCOMPtr<nsISupports> mParent;
+ double mX, mY, mWidth, mHeight;
+};
+
+class DOMRect final : public DOMRectReadOnly {
+ public:
+ explicit DOMRect(nsISupports* aParent, double aX = 0, double aY = 0,
+ double aWidth = 0, double aHeight = 0)
+ : DOMRectReadOnly(aParent, aX, aY, aWidth, aHeight) {}
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(DOMRect, DOMRectReadOnly)
+
+ static already_AddRefed<DOMRect> FromRect(const GlobalObject& aGlobal,
+ const DOMRectInit& aInit);
+
+ static already_AddRefed<DOMRect> Constructor(const GlobalObject& aGlobal,
+ double aX, double aY,
+ double aWidth, double aHeight);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMRect> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+ using DOMRectReadOnly::ReadStructuredClone;
+
+ void SetRect(float aX, float aY, float aWidth, float aHeight) {
+ mX = aX;
+ mY = aY;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ }
+ void SetLayoutRect(const nsRect& aLayoutRect);
+
+ void SetX(double aX) { mX = aX; }
+ void SetY(double aY) { mY = aY; }
+ void SetWidth(double aWidth) { mWidth = aWidth; }
+ void SetHeight(double aHeight) { mHeight = aHeight; }
+
+ static DOMRect* FromSupports(nsISupports* aSupports) {
+ return static_cast<DOMRect*>(aSupports);
+ }
+
+ private:
+ ~DOMRect() = default;
+};
+
+class DOMRectList final : public nsISupports, public nsWrapperCache {
+ ~DOMRectList() = default;
+
+ public:
+ explicit DOMRectList(nsISupports* aParent) : mParent(aParent) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMRectList)
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() { return mParent; }
+
+ void Append(DOMRect* aElement) { mArray.AppendElement(aElement); }
+
+ uint32_t Length() { return mArray.Length(); }
+ DOMRect* Item(uint32_t aIndex) { return mArray.SafeElementAt(aIndex); }
+ DOMRect* IndexedGetter(uint32_t aIndex, bool& aFound) {
+ aFound = aIndex < mArray.Length();
+ if (!aFound) {
+ return nullptr;
+ }
+ return mArray[aIndex];
+ }
+
+ protected:
+ nsTArray<RefPtr<DOMRect> > mArray;
+ nsCOMPtr<nsISupports> mParent;
+};
+
+} // namespace mozilla::dom
+
+#endif /*MOZILLA_DOMRECT_H_*/
diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp
new file mode 100644
index 0000000000..93c1d75d89
--- /dev/null
+++ b/dom/base/DOMRequest.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DOMRequest.h"
+
+#include "DOMException.h"
+#include "nsThreadUtils.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "jsfriendapi.h"
+#include "nsContentUtils.h"
+
+using mozilla::dom::AnyCallback;
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::DOMException;
+using mozilla::dom::DOMRequest;
+using mozilla::dom::DOMRequestService;
+using mozilla::dom::Promise;
+using mozilla::dom::RootingCx;
+
+DOMRequest::DOMRequest(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow),
+ mResult(JS::UndefinedValue()),
+ mDone(false) {}
+
+DOMRequest::DOMRequest(nsIGlobalObject* aGlobal)
+ : DOMEventTargetHelper(aGlobal),
+ mResult(JS::UndefinedValue()),
+ mDone(false) {}
+
+DOMRequest::~DOMRequest() { mozilla::DropJSObjects(this); }
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(DOMRequest,
+ DOMEventTargetHelper,
+ (mError, mPromise),
+ (mResult))
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMRequest)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(DOMRequest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(DOMRequest, DOMEventTargetHelper)
+
+/* virtual */
+JSObject* DOMRequest::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMRequest_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void DOMRequest::FireSuccess(JS::Handle<JS::Value> aResult) {
+ NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
+ NS_ASSERTION(!mError, "mError shouldn't have been set!");
+ NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
+
+ mDone = true;
+ if (aResult.isGCThing()) {
+ RootResultVal();
+ }
+ mResult = aResult;
+
+ FireEvent(u"success"_ns, false, false);
+
+ if (mPromise) {
+ mPromise->MaybeResolve(mResult);
+ }
+}
+
+void DOMRequest::FireError(const nsAString& aError) {
+ NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
+ NS_ASSERTION(!mError, "mError shouldn't have been set!");
+ NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
+
+ mDone = true;
+ // XXX Error code chosen arbitrarily
+ mError = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR,
+ NS_ConvertUTF16toUTF8(aError));
+
+ FireEvent(u"error"_ns, true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
+}
+
+void DOMRequest::FireError(nsresult aError) {
+ NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
+ NS_ASSERTION(!mError, "mError shouldn't have been set!");
+ NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
+
+ mDone = true;
+ mError = DOMException::Create(aError);
+
+ FireEvent(u"error"_ns, true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
+}
+
+void DOMRequest::FireDetailedError(DOMException& aError) {
+ NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
+ NS_ASSERTION(!mError, "mError shouldn't have been set!");
+ NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
+
+ mDone = true;
+ mError = &aError;
+
+ FireEvent(u"error"_ns, true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
+}
+
+void DOMRequest::FireEvent(const nsAString& aType, bool aBubble,
+ bool aCancelable) {
+ if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(aType, aBubble, aCancelable);
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+void DOMRequest::RootResultVal() { mozilla::HoldJSObjects(this); }
+
+void DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aRv) {
+ if (!mPromise) {
+ mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (mDone) {
+ // Since we create mPromise lazily, it's possible that the DOMRequest
+ // object has already fired its success/error event. In that case we
+ // should manually resolve/reject mPromise here. mPromise will take care
+ // of calling the callbacks on |promise| as needed.
+ if (mError) {
+ mPromise->MaybeRejectBrokenly(mError);
+ } else {
+ mPromise->MaybeResolve(mResult);
+ }
+ }
+ }
+
+ // Just use the global of the Promise itself as the callee global.
+ JS::Rooted<JSObject*> global(aCx, mPromise->PromiseObj());
+ global = JS::GetNonCCWObjectGlobal(global);
+ mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv);
+}
+
+NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService)
+
+NS_IMETHODIMP
+DOMRequestService::CreateRequest(mozIDOMWindow* aWindow,
+ DOMRequest** aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(aWindow);
+ auto* win = nsPIDOMWindowInner::From(aWindow);
+ RefPtr<DOMRequest> req = new DOMRequest(win);
+ req.forget(aRequest);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireSuccess(DOMRequest* aRequest,
+ JS::Handle<JS::Value> aResult) {
+ NS_ENSURE_STATE(aRequest);
+ aRequest->FireSuccess(aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireError(DOMRequest* aRequest, const nsAString& aError) {
+ NS_ENSURE_STATE(aRequest);
+ aRequest->FireError(aError);
+
+ return NS_OK;
+}
+
+class FireSuccessAsyncTask : public mozilla::Runnable {
+ FireSuccessAsyncTask(DOMRequest* aRequest, const JS::Value& aResult)
+ : mozilla::Runnable("FireSuccessAsyncTask"),
+ mReq(aRequest),
+ mResult(RootingCx(), aResult) {}
+
+ public:
+ // Due to the fact that initialization can fail during shutdown (since we
+ // can't fetch a js context), set up an initiatization function to make sure
+ // we can return the failure appropriately
+ static nsresult Dispatch(DOMRequest* aRequest, const JS::Value& aResult) {
+ RefPtr<FireSuccessAsyncTask> asyncTask =
+ new FireSuccessAsyncTask(aRequest, aResult);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask));
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Run() override {
+ mReq->FireSuccess(
+ JS::Handle<JS::Value>::fromMarkedLocation(mResult.address()));
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<DOMRequest> mReq;
+ JS::PersistentRooted<JS::Value> mResult;
+};
+
+class FireErrorAsyncTask : public mozilla::Runnable {
+ public:
+ FireErrorAsyncTask(DOMRequest* aRequest, const nsAString& aError)
+ : mozilla::Runnable("FireErrorAsyncTask"),
+ mReq(aRequest),
+ mError(aError) {}
+
+ NS_IMETHOD
+ Run() override {
+ mReq->FireError(mError);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<DOMRequest> mReq;
+ nsString mError;
+};
+
+NS_IMETHODIMP
+DOMRequestService::FireSuccessAsync(DOMRequest* aRequest,
+ JS::Handle<JS::Value> aResult) {
+ NS_ENSURE_STATE(aRequest);
+ return FireSuccessAsyncTask::Dispatch(aRequest, aResult);
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireErrorAsync(DOMRequest* aRequest,
+ const nsAString& aError) {
+ NS_ENSURE_STATE(aRequest);
+ nsCOMPtr<nsIRunnable> asyncTask = new FireErrorAsyncTask(aRequest, aError);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask));
+ return NS_OK;
+}
diff --git a/dom/base/DOMRequest.h b/dom/base/DOMRequest.h
new file mode 100644
index 0000000000..b0e7c23112
--- /dev/null
+++ b/dom/base/DOMRequest.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_domrequest_h__
+#define mozilla_dom_domrequest_h__
+
+#include "nsIDOMRequestService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMRequestBinding.h"
+
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class AnyCallback;
+class Promise;
+
+class DOMRequest : public DOMEventTargetHelper {
+ protected:
+ JS::Heap<JS::Value> mResult;
+ RefPtr<DOMException> mError;
+ RefPtr<Promise> mPromise;
+ bool mDone;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DOMRequest,
+ DOMEventTargetHelper)
+
+ // WrapperCache
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ DOMRequestReadyState ReadyState() const {
+ return mDone ? DOMRequestReadyState::Done : DOMRequestReadyState::Pending;
+ }
+
+ void GetResult(JSContext*, JS::MutableHandle<JS::Value> aRetval) const {
+ NS_ASSERTION(mDone || mResult.isUndefined(),
+ "Result should be undefined when pending");
+ aRetval.set(mResult);
+ }
+
+ DOMException* GetError() const {
+ NS_ASSERTION(mDone || !mError, "Error should be null when pending");
+ return mError;
+ }
+
+ IMPL_EVENT_HANDLER(success)
+ IMPL_EVENT_HANDLER(error)
+
+ void Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback, JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aRv);
+
+ void FireSuccess(JS::Handle<JS::Value> aResult);
+ void FireError(const nsAString& aError);
+ void FireError(nsresult aError);
+ void FireDetailedError(DOMException& aError);
+
+ explicit DOMRequest(nsPIDOMWindowInner* aWindow);
+ explicit DOMRequest(nsIGlobalObject* aGlobal);
+
+ protected:
+ virtual ~DOMRequest();
+
+ void FireEvent(const nsAString& aType, bool aBubble, bool aCancelable);
+
+ void RootResultVal();
+};
+
+class DOMRequestService final : public nsIDOMRequestService {
+ ~DOMRequestService() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMREQUESTSERVICE
+
+ // No one should call this but the factory.
+ static already_AddRefed<DOMRequestService> FactoryCreate() {
+ return MakeAndAddRef<DOMRequestService>();
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#define DOMREQUEST_SERVICE_CONTRACTID "@mozilla.org/dom/dom-request-service;1"
+
+#endif // mozilla_dom_domrequest_h__
diff --git a/dom/base/DOMRequestHelper.sys.mjs b/dom/base/DOMRequestHelper.sys.mjs
new file mode 100644
index 0000000000..832c06c4de
--- /dev/null
+++ b/dom/base/DOMRequestHelper.sys.mjs
@@ -0,0 +1,335 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Helper object for APIs that deal with DOMRequests and Promises.
+ * It allows objects inheriting from it to create and keep track of DOMRequests
+ * and Promises objects in the common scenario where requests are created in
+ * the child, handed out to content and delivered to the parent within an async
+ * message (containing the identifiers of these requests). The parent may send
+ * messages back as answers to different requests and the child will use this
+ * helper to get the right request object. This helper also takes care of
+ * releasing the requests objects when the window goes out of scope.
+ *
+ * DOMRequestIPCHelper also deals with message listeners, allowing to add them
+ * to the child side of frame and process message manager and removing them
+ * when needed.
+ */
+export function DOMRequestIpcHelper() {
+ // _listeners keeps a list of messages for which we added a listener and the
+ // kind of listener that we added (strong or weak). It's an object of this
+ // form:
+ // {
+ // "message1": true,
+ // "messagen": false
+ // }
+ //
+ // where each property is the name of the message and its value is a boolean
+ // that indicates if the listener is weak or not.
+ this._listeners = null;
+ this._requests = null;
+ this._window = null;
+}
+
+DOMRequestIpcHelper.prototype = {
+ /**
+ * An object which "inherits" from DOMRequestIpcHelper and declares its own
+ * queryInterface method MUST implement Ci.nsISupportsWeakReference.
+ */
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISupportsWeakReference",
+ "nsIObserver",
+ ]),
+
+ /**
+ * 'aMessages' is expected to be an array of either:
+ * - objects of this form:
+ * {
+ * name: "messageName",
+ * weakRef: false
+ * }
+ * where 'name' is the message identifier and 'weakRef' a boolean
+ * indicating if the listener should be a weak referred one or not.
+ *
+ * - or only strings containing the message name, in which case the listener
+ * will be added as a strong reference by default.
+ */
+ addMessageListeners(aMessages) {
+ if (!aMessages) {
+ return;
+ }
+
+ if (!this._listeners) {
+ this._listeners = {};
+ }
+
+ if (!Array.isArray(aMessages)) {
+ aMessages = [aMessages];
+ }
+
+ aMessages.forEach(aMsg => {
+ let name = aMsg.name || aMsg;
+ // If the listener is already set and it is of the same type we just
+ // increase the count and bail out. If it is not of the same type,
+ // we throw an exception.
+ if (this._listeners[name] != undefined) {
+ if (!!aMsg.weakRef == this._listeners[name].weakRef) {
+ this._listeners[name].count++;
+ return;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ aMsg.weakRef
+ ? Services.cpmm.addWeakMessageListener(name, this)
+ : Services.cpmm.addMessageListener(name, this);
+ this._listeners[name] = {
+ weakRef: !!aMsg.weakRef,
+ count: 1,
+ };
+ });
+ },
+
+ /**
+ * 'aMessages' is expected to be a string or an array of strings containing
+ * the message names of the listeners to be removed.
+ */
+ removeMessageListeners(aMessages) {
+ if (!this._listeners || !aMessages) {
+ return;
+ }
+
+ if (!Array.isArray(aMessages)) {
+ aMessages = [aMessages];
+ }
+
+ aMessages.forEach(aName => {
+ if (this._listeners[aName] == undefined) {
+ return;
+ }
+
+ // Only remove the listener really when we don't have anybody that could
+ // be waiting on a message.
+ if (!--this._listeners[aName].count) {
+ this._listeners[aName].weakRef
+ ? Services.cpmm.removeWeakMessageListener(aName, this)
+ : Services.cpmm.removeMessageListener(aName, this);
+ delete this._listeners[aName];
+ }
+ });
+ },
+
+ /**
+ * Initialize the helper adding the corresponding listeners to the messages
+ * provided as the second parameter.
+ *
+ * 'aMessages' is expected to be an array of either:
+ *
+ * - objects of this form:
+ * {
+ * name: 'messageName',
+ * weakRef: false
+ * }
+ * where 'name' is the message identifier and 'weakRef' a boolean
+ * indicating if the listener should be a weak referred one or not.
+ *
+ * - or only strings containing the message name, in which case the listener
+ * will be added as a strong referred one by default.
+ */
+ initDOMRequestHelper(aWindow, aMessages) {
+ // Query our required interfaces to force a fast fail if they are not
+ // provided. These calls will throw if the interface is not available.
+ this.QueryInterface(Ci.nsISupportsWeakReference);
+ this.QueryInterface(Ci.nsIObserver);
+
+ if (aMessages) {
+ this.addMessageListeners(aMessages);
+ }
+
+ this._id = this._getRandomId();
+
+ this._window = aWindow;
+ if (this._window) {
+ // We don't use this.innerWindowID, but other classes rely on it.
+ this.innerWindowID = this._window.windowGlobalChild.innerWindowId;
+ }
+
+ this._destroyed = false;
+
+ Services.obs.addObserver(
+ this,
+ "inner-window-destroyed",
+ /* weak-ref */ true
+ );
+ },
+
+ destroyDOMRequestHelper() {
+ if (this._destroyed) {
+ return;
+ }
+
+ this._destroyed = true;
+
+ Services.obs.removeObserver(this, "inner-window-destroyed");
+
+ if (this._listeners) {
+ Object.keys(this._listeners).forEach(aName => {
+ this._listeners[aName].weakRef
+ ? Services.cpmm.removeWeakMessageListener(aName, this)
+ : Services.cpmm.removeMessageListener(aName, this);
+ });
+ }
+
+ this._listeners = null;
+ this._requests = null;
+
+ // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
+ if (this.uninit) {
+ this.uninit();
+ }
+
+ this._window = null;
+ },
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic !== "inner-window-destroyed") {
+ return;
+ }
+
+ let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (wId != this.innerWindowID) {
+ return;
+ }
+
+ this.destroyDOMRequestHelper();
+ },
+
+ getRequestId(aRequest) {
+ if (!this._requests) {
+ this._requests = {};
+ }
+
+ let id = "id" + this._getRandomId();
+ this._requests[id] = aRequest;
+ return id;
+ },
+
+ getPromiseResolverId(aPromiseResolver) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.getRequestId(aPromiseResolver);
+ },
+
+ getRequest(aId) {
+ if (this._requests && this._requests[aId]) {
+ return this._requests[aId];
+ }
+ return undefined;
+ },
+
+ getPromiseResolver(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.getRequest(aId);
+ },
+
+ removeRequest(aId) {
+ if (this._requests && this._requests[aId]) {
+ delete this._requests[aId];
+ }
+ },
+
+ removePromiseResolver(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ this.removeRequest(aId);
+ },
+
+ takeRequest(aId) {
+ if (!this._requests || !this._requests[aId]) {
+ return null;
+ }
+ let request = this._requests[aId];
+ delete this._requests[aId];
+ return request;
+ },
+
+ takePromiseResolver(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.takeRequest(aId);
+ },
+
+ _getRandomId() {
+ return Services.uuid.generateUUID().toString();
+ },
+
+ createRequest() {
+ // If we don't have a valid window object, throw.
+ if (!this._window) {
+ console.error(
+ "DOMRequestHelper trying to create a DOMRequest without a valid window, failing."
+ );
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ return Services.DOMRequest.createRequest(this._window);
+ },
+
+ /**
+ * createPromise() creates a new Promise, with `aPromiseInit` as the
+ * PromiseInit callback. The promise constructor is obtained from the
+ * reference to window owned by this DOMRequestIPCHelper.
+ */
+ createPromise(aPromiseInit) {
+ // If we don't have a valid window object, throw.
+ if (!this._window) {
+ console.error(
+ "DOMRequestHelper trying to create a Promise without a valid window, failing."
+ );
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ return new this._window.Promise(aPromiseInit);
+ },
+
+ /**
+ * createPromiseWithId() creates a new Promise, accepting a callback
+ * which is immediately called with the generated resolverId.
+ */
+ createPromiseWithId(aCallback) {
+ return this.createPromise((aResolve, aReject) => {
+ let resolverId = this.getPromiseResolverId({
+ resolve: aResolve,
+ reject: aReject,
+ });
+ aCallback(resolverId);
+ });
+ },
+
+ forEachRequest(aCallback) {
+ if (!this._requests) {
+ return;
+ }
+
+ Object.keys(this._requests).forEach(aKey => {
+ if (this._window.DOMRequest.isInstance(this.getRequest(aKey))) {
+ aCallback(aKey);
+ }
+ });
+ },
+
+ forEachPromiseResolver(aCallback) {
+ if (!this._requests) {
+ return;
+ }
+
+ Object.keys(this._requests).forEach(aKey => {
+ if (
+ "resolve" in this.getPromiseResolver(aKey) &&
+ "reject" in this.getPromiseResolver(aKey)
+ ) {
+ aCallback(aKey);
+ }
+ });
+ },
+};
diff --git a/dom/base/DOMStringList.cpp b/dom/base/DOMStringList.cpp
new file mode 100644
index 0000000000..f8d42f67d3
--- /dev/null
+++ b/dom/base/DOMStringList.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/DOMStringListBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(DOMStringList)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMStringList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMStringList)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMStringList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+DOMStringList::~DOMStringList() = default;
+
+JSObject* DOMStringList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMStringList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DOMStringList.h b/dom/base/DOMStringList.h
new file mode 100644
index 0000000000..89bb9fc418
--- /dev/null
+++ b/dom/base/DOMStringList.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMStringList_h
+#define mozilla_dom_DOMStringList_h
+
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+
+class DOMStringList : public nsISupports, public nsWrapperCache {
+ protected:
+ virtual ~DOMStringList();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DOMStringList)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return nullptr; }
+
+ void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult) {
+ EnsureFresh();
+ if (aIndex < mNames.Length()) {
+ aFound = true;
+ aResult = mNames[aIndex];
+ } else {
+ aFound = false;
+ }
+ }
+
+ void Item(uint32_t aIndex, nsAString& aResult) {
+ EnsureFresh();
+ if (aIndex < mNames.Length()) {
+ aResult = mNames[aIndex];
+ } else {
+ aResult.SetIsVoid(true);
+ }
+ }
+
+ uint32_t Length() {
+ EnsureFresh();
+ return mNames.Length();
+ }
+
+ bool Contains(const nsAString& aString) {
+ EnsureFresh();
+ return mNames.Contains(aString);
+ }
+
+ bool Add(const nsAString& aName) {
+ // XXXbz(Bug 1631374) mNames should really be a fallible array; otherwise
+ // this return value is meaningless. return mNames.AppendElement(aName) !=
+ // nullptr;
+ mNames.AppendElement(aName);
+ return true;
+ }
+
+ void Clear() { mNames.Clear(); }
+
+ nsTArray<nsString>& StringArray() { return mNames; }
+
+ void CopyList(nsTArray<nsString>& aNames) { aNames = mNames.Clone(); }
+
+ protected:
+ // A method that subclasses can override to modify mNames as needed
+ // before we index into it or return its length or whatnot.
+ virtual void EnsureFresh() {}
+
+ // XXXbz we really want this to be a fallible array, but we end up passing it
+ // to consumers who declare themselves as taking and nsTArray. :(
+ nsTArray<nsString> mNames;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_DOMStringList_h */
diff --git a/dom/base/DOMTokenListSupportedTokens.h b/dom/base/DOMTokenListSupportedTokens.h
new file mode 100644
index 0000000000..1559806abe
--- /dev/null
+++ b/dom/base/DOMTokenListSupportedTokens.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Definitions of supported tokens data types for nsDOMTokenList. This is in a
+ * separate header so Element.h can include it too.
+ */
+
+#ifndef mozilla_dom_DOMTokenListSupportedTokens_h
+#define mozilla_dom_DOMTokenListSupportedTokens_h
+
+namespace mozilla::dom {
+
+// A single supported token.
+typedef const char* const DOMTokenListSupportedToken;
+
+// An array of supported tokens. This should end with a null
+// DOMTokenListSupportedToken to indicate array termination. A null value for
+// the DOMTokenListSupportedTokenArray means there is no definition of supported
+// tokens for the given DOMTokenList. This should generally be a static table,
+// or at least outlive the DOMTokenList whose constructor it's passed to.
+typedef DOMTokenListSupportedToken* DOMTokenListSupportedTokenArray;
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_DOMTokenListSupportedTokens_h
diff --git a/dom/base/DecompressionStream.cpp b/dom/base/DecompressionStream.cpp
new file mode 100644
index 0000000000..54d72ee5f9
--- /dev/null
+++ b/dom/base/DecompressionStream.cpp
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DecompressionStream.h"
+
+#include "js/TypeDecls.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/DecompressionStreamBinding.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/TextDecoderStream.h"
+#include "mozilla/dom/TransformStream.h"
+#include "mozilla/dom/TransformerCallbackHelpers.h"
+
+#include "ZLibHelper.h"
+
+// See the zlib manual in https://www.zlib.net/manual.html or in
+// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
+
+namespace mozilla::dom {
+
+class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+
+ explicit DecompressionStreamAlgorithms(CompressionFormat format) {
+ int8_t err = inflateInit2(&mZStream, ZLibWindowBits(format));
+ if (err == Z_MEM_ERROR) {
+ MOZ_CRASH("Out of memory");
+ }
+ MOZ_ASSERT(err == Z_OK);
+ }
+
+ // Step 3 of
+ // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
+ // Let transformAlgorithm be an algorithm which takes a chunk argument and
+ // runs the compress and enqueue a chunk algorithm with this and chunk.
+ MOZ_CAN_RUN_SCRIPT
+ void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
+
+ // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
+ // (ExtractSpanFromBufferSource does it)
+ Span<const uint8_t> input = ExtractSpanFromBufferSource(cx, aChunk, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2: Let buffer be the result of decompressing chunk with ds's format
+ // and context. If this results in an error, then throw a TypeError.
+ // Step 3 - 5: (Done in CompressAndEnqueue)
+ DecompressAndEnqueue(cx, input, ZLibFlush::No, aController, aRv);
+ }
+
+ // Step 4 of
+ // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
+ // Let flushAlgorithm be an algorithm which takes no argument and runs the
+ // compress flush and enqueue algorithm with this.
+ MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
+ TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController.GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // https://wicg.github.io/compression/#decompress-flush-and-enqueue
+
+ // Step 1: Let buffer be the result of decompressing an empty input with
+ // ds's format and context, with the finish flag.
+ // Step 2 - 4: (Done in CompressAndEnqueue)
+ DecompressAndEnqueue(cx, Span<const uint8_t>(), ZLibFlush::Yes, aController,
+ aRv);
+ }
+
+ private:
+ // Shared by:
+ // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
+ // https://wicg.github.io/compression/#decompress-flush-and-enqueue
+ // All data errors throw TypeError by step 2: If this results in an error,
+ // then throw a TypeError.
+ MOZ_CAN_RUN_SCRIPT void DecompressAndEnqueue(
+ JSContext* aCx, Span<const uint8_t> aInput, ZLibFlush aFlush,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) {
+ MOZ_ASSERT_IF(aFlush == ZLibFlush::Yes, !aInput.Length());
+
+ mZStream.avail_in = aInput.Length();
+ mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
+
+ JS::RootedVector<JSObject*> array(aCx);
+
+ do {
+ static uint16_t kBufferSize = 16384;
+ UniquePtr<uint8_t> buffer(
+ static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
+ if (!buffer) {
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+
+ mZStream.avail_out = kBufferSize;
+ mZStream.next_out = buffer.get();
+
+ int8_t err = inflate(&mZStream, aFlush);
+
+ // From the manual: inflate() returns ...
+ switch (err) {
+ case Z_DATA_ERROR:
+ // Z_DATA_ERROR if the input data was corrupted (input stream not
+ // conforming to the zlib format or incorrect check value, in which
+ // case strm->msg points to a string with a more specific error)
+ aRv.ThrowTypeError("The input data is corrupted: "_ns +
+ nsDependentCString(mZStream.msg));
+ return;
+ case Z_MEM_ERROR:
+ // Z_MEM_ERROR if there was not enough memory
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ case Z_NEED_DICT:
+ // Z_NEED_DICT if a preset dictionary is needed at this point
+ //
+ // From the `deflate` section of
+ // https://wicg.github.io/compression/#supported-formats:
+ // * The FDICT flag is not supported by these APIs, and will error the
+ // stream if set.
+ // And FDICT means preset dictionary per
+ // https://datatracker.ietf.org/doc/html/rfc1950#page-5.
+ aRv.ThrowTypeError(
+ "The stream needs a preset dictionary but such setup is "
+ "unsupported");
+ return;
+ case Z_STREAM_END:
+ // Z_STREAM_END if the end of the compressed data has been reached and
+ // all uncompressed output has been produced
+ //
+ // https://wicg.github.io/compression/#supported-formats has error
+ // conditions for each compression format when additional input comes
+ // after stream end.
+ // Note that additional calls for inflate() immediately emits
+ // Z_STREAM_END after this point.
+ if (mZStream.avail_in > 0) {
+ aRv.ThrowTypeError("Unexpected input after the end of stream");
+ return;
+ }
+ mObservedStreamEnd = true;
+ break;
+ case Z_OK:
+ case Z_BUF_ERROR:
+ // * Z_OK if some progress has been made
+ // * Z_BUF_ERROR if no progress was possible or if there was not
+ // enough room in the output buffer when Z_FINISH is used. Note that
+ // Z_BUF_ERROR is not fatal, and inflate() can be called again with
+ // more input and more output space to continue decompressing.
+ //
+ // (But of course no input should be given after Z_FINISH)
+ break;
+ case Z_STREAM_ERROR:
+ default:
+ // * Z_STREAM_ERROR if the stream state was inconsistent
+ // (which is fatal)
+ MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
+ aRv.ThrowTypeError("Unexpected decompression error");
+ return;
+ }
+
+ // At this point we either exhausted the input or the output buffer
+ MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out);
+
+ size_t written = kBufferSize - mZStream.avail_out;
+ if (!written) {
+ break;
+ }
+
+ // Step 3: If buffer is empty, return.
+ // (We'll implicitly return when the array is empty.)
+
+ // Step 4: Split buffer into one or more non-empty pieces and convert them
+ // into Uint8Arrays.
+ // (The buffer is 'split' by having a fixed sized buffer above.)
+
+ JS::Rooted<JSObject*> view(
+ aCx, nsJSUtils::MoveBufferAsUint8Array(aCx, written, buffer));
+ if (!view || !array.append(view)) {
+ JS_ClearPendingException(aCx);
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+ } while (mZStream.avail_out == 0 && !mObservedStreamEnd);
+ // From the manual:
+ // * It must update next_out and avail_out when avail_out has dropped to
+ // zero.
+ // * inflate() should normally be called until it returns Z_STREAM_END or an
+ // error.
+
+ if (aFlush == ZLibFlush::Yes && !mObservedStreamEnd) {
+ // Step 2 of
+ // https://wicg.github.io/compression/#decompress-flush-and-enqueue
+ // If the end of the compressed input has not been reached, then throw a
+ // TypeError.
+ aRv.ThrowTypeError("The input is ended without reaching the stream end");
+ return;
+ }
+
+ // Step 5: For each Uint8Array array, enqueue array in cs's transform.
+ for (const auto& view : array) {
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
+ aController.Enqueue(aCx, value, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+
+ ~DecompressionStreamAlgorithms() override { inflateEnd(&mZStream); };
+
+ z_stream mZStream = {};
+ bool mObservedStreamEnd = false;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms,
+ TransformerAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream, mGlobal, mStream)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+DecompressionStream::DecompressionStream(nsISupports* aGlobal,
+ TransformStream& aStream)
+ : mGlobal(aGlobal), mStream(&aStream) {}
+
+DecompressionStream::~DecompressionStream() = default;
+
+JSObject* DecompressionStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DecompressionStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream
+already_AddRefed<DecompressionStream> DecompressionStream::Constructor(
+ const GlobalObject& aGlobal, CompressionFormat aFormat, ErrorResult& aRv) {
+ // Step 1: If format is unsupported in DecompressionStream, then throw a
+ // TypeError.
+ // XXX: Skipped as we are using enum for this
+
+ // Step 2 - 4: (Done in DecompressionStreamAlgorithms)
+
+ // Step 5: Set this's transform to a new TransformStream.
+
+ // Step 6: Set up this's transform with transformAlgorithm set to
+ // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
+ auto algorithms = MakeRefPtr<DecompressionStreamAlgorithms>(aFormat);
+
+ RefPtr<TransformStream> stream =
+ TransformStream::CreateGeneric(aGlobal, *algorithms, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return do_AddRef(new DecompressionStream(aGlobal.GetAsSupports(), *stream));
+}
+
+already_AddRefed<ReadableStream> DecompressionStream::Readable() const {
+ return do_AddRef(mStream->Readable());
+};
+
+already_AddRefed<WritableStream> DecompressionStream::Writable() const {
+ return do_AddRef(mStream->Writable());
+};
+
+} // namespace mozilla::dom
diff --git a/dom/base/DecompressionStream.h b/dom/base/DecompressionStream.h
new file mode 100644
index 0000000000..0e8e4d07c1
--- /dev/null
+++ b/dom/base/DecompressionStream.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_DECOMPRESSIONSTREAM_H_
+#define DOM_DECOMPRESSIONSTREAM_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class WritableStream;
+class TransformStream;
+
+enum class CompressionFormat : uint8_t;
+
+class DecompressionStream final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DecompressionStream)
+
+ public:
+ DecompressionStream(nsISupports* aGlobal, TransformStream& aStream);
+
+ protected:
+ ~DecompressionStream();
+
+ public:
+ nsISupports* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<DecompressionStream>
+ Constructor(const GlobalObject& global, CompressionFormat format,
+ ErrorResult& aRv);
+
+ already_AddRefed<ReadableStream> Readable() const;
+
+ already_AddRefed<WritableStream> Writable() const;
+
+ private:
+ nsCOMPtr<nsISupports> mGlobal;
+
+ RefPtr<TransformStream> mStream;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_DECOMPRESSIONSTREAM_H_
diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp
new file mode 100644
index 0000000000..aabd946448
--- /dev/null
+++ b/dom/base/DirectionalityUtils.cpp
@@ -0,0 +1,1271 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Implementation description from https://etherpad.mozilla.org/dir-auto
+
+ Static case
+ ===========
+ When we see a new content node with @dir=auto from the parser, we set the
+ NodeHasDirAuto flag on the node. We won't have enough information to
+ decide the directionality of the node at this point.
+
+ When we bind a new content node to the document, if its parent has either of
+ the NodeAncestorHasDirAuto or NodeHasDirAuto flags, we set the
+ NodeAncestorHasDirAuto flag on the node.
+
+ When a new input with @type=text/search/tel/url/email and @dir=auto is added
+ from the parser, we resolve the directionality based on its @value.
+
+ When a new text node with non-neutral content is appended to a textarea
+ element with NodeHasDirAuto, if the directionality of the textarea element
+ is still unresolved, it is resolved based on the value of the text node.
+ Elements with unresolved directionality behave as LTR.
+
+ When a new text node with non-neutral content is appended to an element that
+ is not a textarea but has either of the NodeAncestorHasDirAuto or
+ NodeHasDirAuto flags, we walk up the parent chain while the
+ NodeAncestorHasDirAuto flag is present, and when we reach an element with
+ NodeHasDirAuto and no resolved directionality, we resolve the directionality
+ based on the contents of the text node and cease walking the parent chain.
+ Note that we should ignore elements with NodeHasDirAuto with resolved
+ directionality, so that the second text node in this example tree doesn't
+ affect the directionality of the div:
+
+ <div dir=auto>
+ <span>foo</span>
+ <span>بار</span>
+ </div>
+
+ The parent chain walk will be aborted if we hit a script or style element, or
+ if we hit an element with @dir=ltr or @dir=rtl.
+
+ I will call this algorithm "upward propagation".
+
+ Each text node should maintain a list of elements which have their
+ directionality determined by the first strong character of that text node.
+ This is useful to make dynamic changes more efficient. One way to implement
+ this is to have a per-document hash table mapping a text node to a set of
+ elements. I'll call this data structure TextNodeDirectionalityMap. The
+ algorithm for appending a new text node above needs to update this data
+ structure.
+
+ *IMPLEMENTATION NOTE*
+ In practice, the implementation uses two per-node properties:
+
+ dirAutoSetBy, which is set on a node with auto-directionality, and points to
+ the textnode that contains the strong character which determines the
+ directionality of the node.
+
+ textNodeDirectionalityMap, which is set on a text node and points to a hash
+ table listing the nodes whose directionality is determined by the text node.
+
+ Handling dynamic changes
+ ========================
+
+ We need to handle the following cases:
+
+ 1. When the value of an input element with @type=text/search/tel/url/email is
+ changed, if it has NodeHasDirAuto, we update the resolved directionality.
+
+ 2. When the dir attribute is changed from something else (including the case
+ where it doesn't exist) to auto on a textarea or an input element with
+ @type=text/search/tel/url/email, we set the NodeHasDirAuto flag and resolve
+ the directionality based on the value of the element.
+
+ 3. When the dir attribute is changed from something else (including the case
+ where it doesn't exist) to auto on any element except case 1 above and the bdi
+ element, we run the following algorithm:
+ * We set the NodeHasDirAuto flag.
+ * If the element doesn't have the NodeAncestorHasDirAuto flag, we set the
+ NodeAncestorHasDirAuto flag on all of its child nodes. (Note that if the
+ element does have NodeAncestorHasDirAuto, all of its children should
+ already have this flag too. We can assert this in debug builds.)
+ * To resolve the directionality of the element, we run the algorithm explained
+ in
+ http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
+ (I'll call this the "downward propagation algorithm".) by walking the child
+ subtree in tree order. Note that an element with @dir=auto should not affect
+ other elements in its document with @dir=auto. So there is no need to walk up
+ the parent chain in this case. TextNodeDirectionalityMap needs to be updated
+ as appropriate.
+
+ 3a. When the dir attribute is set to any valid value on an element that didn't
+ have a valid dir attribute before, this means that any descendant of that
+ element will not affect the directionality of any of its ancestors. So we need
+ to check whether any text node descendants of the element are listed in
+ TextNodeDirectionalityMap, and whether the elements whose direction they set
+ are ancestors of the element. If so, we need to rerun the downward propagation
+ algorithm for those ancestors.
+
+ 4. When the dir attribute is changed from auto to something else (including
+ the case where it gets removed) on a textarea or an input element with
+ @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
+ resolve the directionality based on the directionality of the value of the
+ @dir attribute on element itself or its parent element.
+
+ 5. When the dir attribute is changed from auto to something else (including
+ the case where it gets removed) on any element except case 4 above and the bdi
+ element, we run the following algorithm:
+ * We unset the NodeHasDirAuto flag.
+ * If the element does not have the NodeAncestorHasDirAuto flag, we unset
+ the NodeAncestorHasDirAuto flag on all of its child nodes, except those
+ who are a descendant of another element with NodeHasDirAuto. (Note that if
+ the element has the NodeAncestorHasDirAuto flag, all of its child nodes
+ should still retain the same flag.)
+ * We resolve the directionality of the element based on the value of the @dir
+ attribute on the element itself or its parent element.
+ TextNodeDirectionalityMap needs to be updated as appropriate.
+
+ 5a. When the dir attribute is removed or set to an invalid value on any
+ element (except a bdi element) with the NodeAncestorHasDirAuto flag which
+ previously had a valid dir attribute, it might have a text node descendant
+ that did not previously affect the directionality of any of its ancestors but
+ should now begin to affect them. We run the following algorithm:
+ * Walk up the parent chain from the element.
+ * For any element that appears in the TextNodeDirectionalityMap, remove the
+ element from the map and rerun the downward propagation algorithm
+ (see section 3).
+ * If we reach an element without either of the NodeHasDirAuto or
+ NodeAncestorHasDirAuto flags, abort the parent chain walk.
+
+ 6. When an element with @dir=auto is added to the document, we should handle
+ it similar to the case 2/3 above.
+
+ 7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is
+ removed from the document, we should handle it similar to the case 4/5 above,
+ except that we don't need to handle anything in the child subtree. We should
+ also remove all of the occurrences of that node and its descendants from
+ TextNodeDirectionalityMap. (This is the conceptual description of what needs
+ to happen but in the implementation UnbindFromTree is going to be called on
+ all of the descendants so we don't need to descend into the child subtree).
+
+ 8. When the contents of a text node is changed either from script or by the
+ user, we need to run the following algorithm:
+ * If the change has happened after the first character with strong
+ directionality in the text node, do nothing.
+ * If the text node is a child of a bdi, script or style element, do nothing.
+ * If the text node belongs to a textarea with NodeHasDirAuto, we need to
+ update the directionality of the textarea.
+ * Grab a list of elements affected by this text node from
+ TextNodeDirectionalityMap and re-resolve the directionality of each one of
+ them based on the new contents of the text node.
+ * If the text node does not exist in TextNodeDirectionalityMap, and it has the
+ NodeAncestorHasDirAuto flag set, this could potentially be a text node
+ which is going to start affecting the directionality of its parent @dir=auto
+ elements. In this case, we need to fall back to the (potentially expensive)
+ "upward propagation algorithm". The TextNodeDirectionalityMap data structure
+ needs to be update during this algorithm.
+ * If the new contents of the text node do not have any strong characters, and
+ the old contents used to, and the text node used to exist in
+ TextNodeDirectionalityMap and it has the NodeAncestorHasDirAuto flag set,
+ the elements associated with this text node inside TextNodeDirectionalityMap
+ will now get their directionality from another text node. In this case, for
+ each element in the list retrieved from TextNodeDirectionalityMap, run the
+ downward propagation algorithm (section 3), and remove the text node from
+ TextNodeDirectionalityMap.
+
+ 9. When a new text node is injected into a document, we need to run the
+ following algorithm:
+ * If the contents of the text node do not have any characters with strong
+ direction, do nothing.
+ * If the text node is a child of a bdi, script or style element, do nothing.
+ * If the text node is appended to a textarea element with NodeHasDirAuto, we
+ need to update the directionality of the textarea.
+ * If the text node has NodeAncestorHasDirAuto, we need to run the "upward
+ propagation algorithm". The TextNodeDirectionalityMap data structure needs to
+ be update during this algorithm.
+
+ 10. When a text node is removed from a document, we need to run the following
+ algorithm:
+ * If the contents of the text node do not have any characters with strong
+ direction, do nothing.
+ * If the text node is a child of a bdi, script or style element, do nothing.
+ * If the text node is removed from a textarea element with NodeHasDirAuto,
+ set the directionality to "ltr". (This is what the spec currently says, but
+ I'm filing a spec bug to get it fixed -- the directionality should depend on
+ the parent element here.)
+ * If the text node has NodeAncestorHasDirAuto, we need to look at the list
+ of elements being affected by this text node from TextNodeDirectionalityMap,
+ run the "downward propagation algorithm" (section 3) for each one of them,
+ while updating TextNodeDirectionalityMap along the way.
+
+ 11. If the value of the @dir attribute on a bdi element is changed to an
+ invalid value (or if it's removed), determine the new directionality similar
+ to the case 3 above.
+
+ == Implemention Notes ==
+ When a new node gets bound to the tree, the BindToTree function gets called.
+ The reverse case is UnbindFromTree.
+ When the contents of a text node change, CharacterData::SetTextInternal
+ gets called.
+ */
+
+#include "mozilla/dom/DirectionalityUtils.h"
+
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "nsUnicodeProperties.h"
+#include "nsTextFragment.h"
+#include "nsAttrValue.h"
+#include "nsTextNode.h"
+#include "nsCheapSets.h"
+
+namespace mozilla {
+
+using mozilla::dom::Element;
+using mozilla::dom::HTMLInputElement;
+using mozilla::dom::HTMLSlotElement;
+using mozilla::dom::ShadowRoot;
+
+static nsIContent* GetParentOrHostOrSlot(
+ nsIContent* aContent, bool* aCrossedShadowBoundary = nullptr) {
+ if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
+ if (aCrossedShadowBoundary) {
+ *aCrossedShadowBoundary = true;
+ }
+ return slot;
+ }
+
+ nsIContent* parent = aContent->GetParent();
+ if (parent) {
+ return parent;
+ }
+
+ ShadowRoot* sr = ShadowRoot::FromNode(aContent);
+ if (sr) {
+ if (aCrossedShadowBoundary) {
+ *aCrossedShadowBoundary = true;
+ }
+ return sr->Host();
+ }
+
+ return nullptr;
+}
+
+static bool AncestorChainCrossesShadowBoundary(nsIContent* aDescendant,
+ nsIContent* aAncestor) {
+ bool crossedShadowBoundary = false;
+ nsIContent* content = aDescendant;
+ while (content && content != aAncestor) {
+ content = GetParentOrHostOrSlot(content, &crossedShadowBoundary);
+ if (crossedShadowBoundary) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Returns true if aElement is one of the elements whose text content should not
+ * affect its own direction, nor the direction of ancestors with dir=auto.
+ *
+ * Note that this does not include <bdi>, whose content does affect its own
+ * direction when it has dir=auto (which it has by default), so one needs to
+ * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
+ * It *does* include textarea, because even if a textarea has dir=auto, it has
+ * unicode-bidi: plaintext and is handled automatically in bidi resolution.
+ * It also includes `input`, because it takes the `dir` value from its value
+ * attribute, instead of the child nodes.
+ */
+static bool DoesNotParticipateInAutoDirection(const nsIContent* aContent) {
+ mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo();
+ return ((!aContent->IsHTMLElement() || nodeInfo->Equals(nsGkAtoms::script) ||
+ nodeInfo->Equals(nsGkAtoms::style) ||
+ nodeInfo->Equals(nsGkAtoms::input) ||
+ nodeInfo->Equals(nsGkAtoms::textarea) ||
+ aContent->IsInNativeAnonymousSubtree())) &&
+ !aContent->IsShadowRoot();
+}
+
+/**
+ * Returns true if aElement is one of the element whose text content should not
+ * affect the direction of ancestors with dir=auto (though it may affect its own
+ * direction, e.g. <bdi>)
+ */
+static bool DoesNotAffectDirectionOfAncestors(const Element* aElement) {
+ return (DoesNotParticipateInAutoDirection(aElement) ||
+ aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir());
+}
+
+/**
+ * Returns the directionality of a Unicode character
+ */
+static Directionality GetDirectionFromChar(uint32_t ch) {
+ switch (intl::UnicodeProperties::GetBidiClass(ch)) {
+ case intl::BidiClass::RightToLeft:
+ case intl::BidiClass::RightToLeftArabic:
+ return eDir_RTL;
+
+ case intl::BidiClass::LeftToRight:
+ return eDir_LTR;
+
+ default:
+ return eDir_NotSet;
+ }
+}
+
+inline static bool NodeAffectsDirAutoAncestor(nsIContent* aTextNode) {
+ nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
+ return (parent && !DoesNotParticipateInAutoDirection(parent) &&
+ parent->NodeOrAncestorHasDirAuto() &&
+ !aTextNode->IsInNativeAnonymousSubtree());
+}
+
+Directionality GetDirectionFromText(const char16_t* aText,
+ const uint32_t aLength,
+ uint32_t* aFirstStrong) {
+ const char16_t* start = aText;
+ const char16_t* end = aText + aLength;
+
+ while (start < end) {
+ uint32_t current = start - aText;
+ uint32_t ch = *start++;
+
+ if (start < end && NS_IS_SURROGATE_PAIR(ch, *start)) {
+ ch = SURROGATE_TO_UCS4(ch, *start++);
+ current++;
+ }
+
+ // Just ignore lone surrogates
+ if (!IS_SURROGATE(ch)) {
+ Directionality dir = GetDirectionFromChar(ch);
+ if (dir != eDir_NotSet) {
+ if (aFirstStrong) {
+ *aFirstStrong = current;
+ }
+ return dir;
+ }
+ }
+ }
+
+ if (aFirstStrong) {
+ *aFirstStrong = UINT32_MAX;
+ }
+ return eDir_NotSet;
+}
+
+static Directionality GetDirectionFromText(const char* aText,
+ const uint32_t aLength,
+ uint32_t* aFirstStrong = nullptr) {
+ const char* start = aText;
+ const char* end = aText + aLength;
+
+ while (start < end) {
+ uint32_t current = start - aText;
+ unsigned char ch = (unsigned char)*start++;
+
+ Directionality dir = GetDirectionFromChar(ch);
+ if (dir != eDir_NotSet) {
+ if (aFirstStrong) {
+ *aFirstStrong = current;
+ }
+ return dir;
+ }
+ }
+
+ if (aFirstStrong) {
+ *aFirstStrong = UINT32_MAX;
+ }
+ return eDir_NotSet;
+}
+
+static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode,
+ uint32_t* aFirstStrong = nullptr) {
+ const nsTextFragment* frag = &aTextNode->TextFragment();
+ if (frag->Is2b()) {
+ return GetDirectionFromText(frag->Get2b(), frag->GetLength(), aFirstStrong);
+ }
+
+ return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong);
+}
+
+static nsTextNode* WalkDescendantsAndGetDirectionFromText(
+ nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) {
+ nsIContent* child = aRoot->GetFirstChild();
+ while (child) {
+ if ((child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) ||
+ child->GetAssignedSlot()) {
+ child = child->GetNextNonChildNode(aRoot);
+ continue;
+ }
+
+ if (auto* slot = HTMLSlotElement::FromNode(child)) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
+ nsIContent* assignedNode = assignedNodes[i]->AsContent();
+ if (assignedNode->NodeType() == nsINode::TEXT_NODE) {
+ auto text = static_cast<nsTextNode*>(assignedNode);
+ if (assignedNode != aSkip) {
+ Directionality textNodeDir = GetDirectionFromText(text);
+ if (textNodeDir != eDir_NotSet) {
+ *aDirectionality = textNodeDir;
+ return text;
+ }
+ }
+ } else if (assignedNode->IsElement() &&
+ !DoesNotAffectDirectionOfAncestors(
+ assignedNode->AsElement())) {
+ nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
+ assignedNode, aSkip, aDirectionality);
+ if (text) {
+ return text;
+ }
+ }
+ }
+ }
+
+ if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) {
+ auto text = static_cast<nsTextNode*>(child);
+ Directionality textNodeDir = GetDirectionFromText(text);
+ if (textNodeDir != eDir_NotSet) {
+ *aDirectionality = textNodeDir;
+ return text;
+ }
+ }
+ child = child->GetNextNode(aRoot);
+ }
+
+ return nullptr;
+}
+
+/**
+ * Set the directionality of a node with dir=auto as defined in
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
+ *
+ * @param[in] changedNode If we call this method because the content of a text
+ * node is about to change, pass in the changed node, so that we
+ * know not to return it
+ * @return the text node containing the character that determined the direction
+ */
+static nsTextNode* WalkDescendantsSetDirectionFromText(
+ Element* aElement, bool aNotify, nsINode* aChangedNode = nullptr) {
+ MOZ_ASSERT(aElement, "Must have an element");
+ MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
+
+ if (DoesNotParticipateInAutoDirection(aElement)) {
+ return nullptr;
+ }
+
+ Directionality textNodeDir = eDir_NotSet;
+
+ // Check the text in Shadow DOM.
+ if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) {
+ nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
+ shadowRoot, aChangedNode, &textNodeDir);
+ if (text) {
+ aElement->SetDirectionality(textNodeDir, aNotify);
+ return text;
+ }
+ }
+
+ // Check the text in light DOM.
+ nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
+ aElement, aChangedNode, &textNodeDir);
+ if (text) {
+ aElement->SetDirectionality(textNodeDir, aNotify);
+ return text;
+ }
+
+ // We walked all the descendants without finding a text node with strong
+ // directional characters. Set the directionality to LTR
+ aElement->SetDirectionality(eDir_LTR, aNotify);
+ return nullptr;
+}
+
+class nsTextNodeDirectionalityMap {
+ static void nsTextNodeDirectionalityMapDtor(void* aObject,
+ nsAtom* aPropertyName,
+ void* aPropertyValue,
+ void* aData) {
+ nsINode* textNode = static_cast<nsINode*>(aObject);
+ textNode->ClearHasTextNodeDirectionalityMap();
+
+ nsTextNodeDirectionalityMap* map =
+ reinterpret_cast<nsTextNodeDirectionalityMap*>(aPropertyValue);
+ map->EnsureMapIsClear();
+ delete map;
+ }
+
+ public:
+ explicit nsTextNodeDirectionalityMap(nsINode* aTextNode)
+ : mElementToBeRemoved(nullptr) {
+ MOZ_ASSERT(aTextNode, "Null text node");
+ MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
+ aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
+ nsTextNodeDirectionalityMapDtor);
+ aTextNode->SetHasTextNodeDirectionalityMap();
+ }
+
+ MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap)
+
+ static void nsTextNodeDirectionalityMapPropertyDestructor(
+ void* aObject, nsAtom* aProperty, void* aPropertyValue, void* aData) {
+ nsTextNode* textNode = static_cast<nsTextNode*>(aPropertyValue);
+ nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode);
+ if (map) {
+ map->RemoveEntryForProperty(static_cast<Element*>(aObject));
+ }
+ NS_RELEASE(textNode);
+ }
+
+ void AddEntry(nsTextNode* aTextNode, Element* aElement) {
+ if (!mElements.Contains(aElement)) {
+ mElements.Put(aElement);
+ NS_ADDREF(aTextNode);
+ aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode,
+ nsTextNodeDirectionalityMapPropertyDestructor);
+ aElement->SetHasDirAutoSet();
+ }
+ }
+
+ void RemoveEntry(nsTextNode* aTextNode, Element* aElement) {
+ NS_ASSERTION(mElements.Contains(aElement),
+ "element already removed from map");
+
+ mElements.Remove(aElement);
+ aElement->ClearHasDirAutoSet();
+ aElement->RemoveProperty(nsGkAtoms::dirAutoSetBy);
+ }
+
+ void RemoveEntryForProperty(Element* aElement) {
+ if (mElementToBeRemoved != aElement) {
+ mElements.Remove(aElement);
+ }
+ aElement->ClearHasDirAutoSet();
+ }
+
+ private:
+ nsCheapSet<nsPtrHashKey<Element>> mElements;
+ // Only used for comparison.
+ Element* mElementToBeRemoved;
+
+ static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode) {
+ MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
+ "Must be a text node");
+ nsTextNodeDirectionalityMap* map = nullptr;
+
+ if (aTextNode->HasTextNodeDirectionalityMap()) {
+ map = static_cast<nsTextNodeDirectionalityMap*>(
+ aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
+ }
+
+ return map;
+ }
+
+ static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry,
+ void* aDir) {
+ aEntry->GetKey()->SetDirectionality(
+ *reinterpret_cast<Directionality*>(aDir), true);
+ return OpNext;
+ }
+
+ struct nsTextNodeDirectionalityMapAndElement {
+ nsTextNodeDirectionalityMap* mMap;
+ nsCOMPtr<nsINode> mNode;
+ };
+
+ static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry,
+ void* aData) {
+ // run the downward propagation algorithm
+ // and remove the text node from the map
+ nsTextNodeDirectionalityMapAndElement* data =
+ static_cast<nsTextNodeDirectionalityMapAndElement*>(aData);
+ nsINode* oldTextNode = data->mNode;
+ Element* rootNode = aEntry->GetKey();
+ nsTextNode* newTextNode = nullptr;
+ if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
+ newTextNode =
+ WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode);
+ }
+
+ AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved);
+ data->mMap->mElementToBeRemoved = rootNode;
+ if (newTextNode) {
+ nsINode* oldDirAutoSetBy = static_cast<nsTextNode*>(
+ rootNode->GetProperty(nsGkAtoms::dirAutoSetBy));
+ if (oldDirAutoSetBy == newTextNode) {
+ // We're already registered.
+ return OpNext;
+ }
+ nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
+ } else {
+ rootNode->ClearHasDirAutoSet();
+ rootNode->RemoveProperty(nsGkAtoms::dirAutoSetBy);
+ }
+ return OpRemove;
+ }
+
+ static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry,
+ void* aData) {
+ AutoTArray<Element*, 8>* entries =
+ static_cast<AutoTArray<Element*, 8>*>(aData);
+ entries->AppendElement(aEntry->GetKey());
+ return OpRemove;
+ }
+
+ public:
+ uint32_t UpdateAutoDirection(Directionality aDir) {
+ return mElements.EnumerateEntries(SetNodeDirection, &aDir);
+ }
+
+ void ResetAutoDirection(nsINode* aTextNode) {
+ nsTextNodeDirectionalityMapAndElement data = {this, aTextNode};
+ mElements.EnumerateEntries(ResetNodeDirection, &data);
+ }
+
+ void EnsureMapIsClear() {
+ AutoRestore<Element*> restore(mElementToBeRemoved);
+ AutoTArray<Element*, 8> entries;
+ mElements.EnumerateEntries(TakeEntries, &entries);
+ for (Element* el : entries) {
+ el->ClearHasDirAutoSet();
+ el->RemoveProperty(nsGkAtoms::dirAutoSetBy);
+ }
+ }
+
+ static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement) {
+ if (aTextNode->HasTextNodeDirectionalityMap()) {
+ GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
+ }
+ }
+
+ static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement) {
+ nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
+ if (!map) {
+ map = new nsTextNodeDirectionalityMap(aTextNode);
+ }
+
+ map->AddEntry(aTextNode, aElement);
+ }
+
+ static uint32_t UpdateTextNodeDirection(nsINode* aTextNode,
+ Directionality aDir) {
+ MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
+ "Map missing in UpdateTextNodeDirection");
+ return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
+ }
+
+ static void ResetTextNodeDirection(nsTextNode* aTextNode,
+ nsTextNode* aChangedTextNode) {
+ MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
+ "Map missing in ResetTextNodeDirection");
+ RefPtr<nsTextNode> textNode = aTextNode;
+ GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode);
+ }
+
+ static void EnsureMapIsClearFor(nsINode* aTextNode) {
+ if (aTextNode->HasTextNodeDirectionalityMap()) {
+ GetDirectionalityMap(aTextNode)->EnsureMapIsClear();
+ }
+ }
+};
+
+Directionality RecomputeDirectionality(Element* aElement, bool aNotify) {
+ MOZ_ASSERT(!aElement->HasDirAuto(),
+ "RecomputeDirectionality called with dir=auto");
+
+ if (aElement->HasValidDir()) {
+ return aElement->GetDirectionality();
+ }
+
+ Directionality dir = eDir_LTR;
+
+ // https://html.spec.whatwg.org/multipage/dom.html#the-directionality:
+ //
+ // If the element is an input element whose type attribute is in the
+ // Telephone state, and the dir attribute is not in a defined state
+ // (i.e. it is not present or has an invalid value)
+ //
+ // The directionality of the element is 'ltr'.
+ if (auto* input = HTMLInputElement::FromNode(*aElement)) {
+ if (input->ControlType() == FormControlType::InputTel) {
+ aElement->SetDirectionality(dir, aNotify);
+ return dir;
+ }
+ }
+
+ if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) {
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
+ parent = shadow->GetHost();
+ }
+
+ if (parent && parent->IsElement()) {
+ // If the node doesn't have an explicit dir attribute with a valid value,
+ // the directionality is the same as the parent element (but don't
+ // propagate the parent directionality if it isn't set yet).
+ Directionality parentDir = parent->AsElement()->GetDirectionality();
+ if (parentDir != eDir_NotSet) {
+ dir = parentDir;
+ }
+ }
+ }
+
+ aElement->SetDirectionality(dir, aNotify);
+ return dir;
+}
+
+static void SetDirectionalityOnDescendantsInternal(nsINode* aNode,
+ Directionality aDir,
+ bool aNotify) {
+ if (Element* element = Element::FromNode(aNode)) {
+ if (ShadowRoot* shadow = element->GetShadowRoot()) {
+ SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
+ }
+ }
+
+ for (nsIContent* child = aNode->GetFirstChild(); child;) {
+ if (!child->IsElement()) {
+ child = child->GetNextNode(aNode);
+ continue;
+ }
+
+ Element* element = child->AsElement();
+ if (element->HasValidDir() || element->HasDirAuto() ||
+ element->GetAssignedSlot()) {
+ child = child->GetNextNonChildNode(aNode);
+ continue;
+ }
+ if (ShadowRoot* shadow = element->GetShadowRoot()) {
+ SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
+ }
+
+ if (auto* slot = HTMLSlotElement::FromNode(child)) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
+ nsINode* node = assignedNodes[i];
+ Element* assignedElement =
+ node->IsElement() ? node->AsElement() : nullptr;
+ if (assignedElement && !assignedElement->HasValidDir() &&
+ !assignedElement->HasDirAuto()) {
+ assignedElement->SetDirectionality(aDir, aNotify);
+ SetDirectionalityOnDescendantsInternal(assignedElement, aDir,
+ aNotify);
+ }
+ }
+ }
+
+ element->SetDirectionality(aDir, aNotify);
+
+ child = child->GetNextNode(aNode);
+ }
+}
+
+// We want the public version of this only to acc
+void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
+ bool aNotify) {
+ return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
+}
+
+static void ResetAutoDirection(Element* aElement, bool aNotify) {
+ if (aElement->HasDirAutoSet()) {
+ // If the parent has the DirAutoSet flag, its direction is determined by
+ // some text node descendant.
+ // Remove it from the map and reset its direction by the downward
+ // propagation algorithm
+ nsTextNode* setByNode = static_cast<nsTextNode*>(
+ aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+ if (setByNode) {
+ nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
+ }
+ }
+
+ if (aElement->HasDirAuto()) {
+ nsTextNode* setByNode =
+ WalkDescendantsSetDirectionFromText(aElement, aNotify);
+ if (setByNode) {
+ nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement);
+ }
+ SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
+ aNotify);
+ }
+}
+
+/**
+ * Walk the parent chain of a text node whose dir attribute has been removed and
+ * reset the direction of any of its ancestors which have dir=auto and whose
+ * directionality is determined by a text node descendant.
+ */
+void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
+ nsTextNode* setByNode;
+ nsIContent* parent = GetParentOrHostOrSlot(aElement);
+ while (parent && parent->NodeOrAncestorHasDirAuto()) {
+ if (!parent->IsElement()) {
+ parent = GetParentOrHostOrSlot(parent);
+ continue;
+ }
+
+ Element* parentElement = parent->AsElement();
+ if (parent->HasDirAutoSet()) {
+ // If the parent has the DirAutoSet flag, its direction is determined by
+ // some text node descendant.
+ // Remove it from the map and reset its direction by the downward
+ // propagation algorithm
+ setByNode = static_cast<nsTextNode*>(
+ parent->GetProperty(nsGkAtoms::dirAutoSetBy));
+ if (setByNode) {
+ nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
+ parentElement);
+ }
+ }
+ if (parentElement->HasDirAuto()) {
+ setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify);
+ if (setByNode) {
+ nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement);
+ }
+ SetDirectionalityOnDescendants(
+ parentElement, parentElement->GetDirectionality(), aNotify);
+ break;
+ }
+ parent = GetParentOrHostOrSlot(parent);
+ }
+}
+
+static void RecomputeSlottedNodeDirection(HTMLSlotElement& aSlot,
+ nsINode& aNode) {
+ auto* assignedElement = Element::FromNode(aNode);
+ if (!assignedElement) {
+ return;
+ }
+
+ if (assignedElement->HasValidDir() || assignedElement->HasDirAuto()) {
+ return;
+ }
+
+ // Try to optimize out state changes when possible.
+ if (assignedElement->GetDirectionality() == aSlot.GetDirectionality()) {
+ return;
+ }
+
+ assignedElement->SetDirectionality(aSlot.GetDirectionality(), true);
+ SetDirectionalityOnDescendantsInternal(assignedElement,
+ aSlot.GetDirectionality(), true);
+}
+
+void SlotAssignedNodeChanged(HTMLSlotElement* aSlot,
+ nsIContent& aAssignedNode) {
+ if (!aSlot) {
+ return;
+ }
+
+ if (aSlot->NodeOrAncestorHasDirAuto()) {
+ // The directionality of the assigned node may impact the directionality of
+ // the slot. So recompute everything.
+ SlotStateChanged(aSlot, /* aAllAssignedNodesChanged = */ false);
+ }
+
+ if (aAssignedNode.GetAssignedSlot() == aSlot) {
+ RecomputeSlottedNodeDirection(*aSlot, aAssignedNode);
+ }
+}
+
+void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) {
+ if (!aSlot) {
+ return;
+ }
+
+ Directionality oldDir = aSlot->GetDirectionality();
+
+ if (aSlot->HasDirAuto()) {
+ ResetAutoDirection(aSlot, true);
+ }
+
+ if (aSlot->NodeOrAncestorHasDirAuto()) {
+ WalkAncestorsResetAutoDirection(aSlot, true);
+ }
+
+ if (aAllAssignedNodesChanged || oldDir != aSlot->GetDirectionality()) {
+ for (nsINode* node : aSlot->AssignedNodes()) {
+ RecomputeSlottedNodeDirection(*aSlot, *node);
+ }
+ }
+}
+
+void WalkDescendantsResetAutoDirection(Element* aElement) {
+ nsIContent* child = aElement->GetFirstChild();
+ while (child) {
+ if (child->IsElement() && child->AsElement()->HasDirAuto()) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ if (child->NodeType() == nsINode::TEXT_NODE &&
+ child->HasTextNodeDirectionalityMap()) {
+ nsTextNodeDirectionalityMap::ResetTextNodeDirection(
+ static_cast<nsTextNode*>(child), nullptr);
+ // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child)
+ // since ResetTextNodeDirection may have kept elements in child's
+ // DirectionalityMap.
+ }
+ child = child->GetNextNode(aElement);
+ }
+}
+
+static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot);
+
+static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) {
+ if (aNode->IsElement()) {
+ if (ShadowRoot* sr = aNode->AsElement()->GetShadowRoot()) {
+ sr->SetAncestorHasDirAuto();
+ SetAncestorHasDirAutoOnDescendants(sr);
+ }
+ }
+}
+
+static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) {
+ MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot);
+
+ nsIContent* child = aRoot->GetFirstChild();
+ while (child) {
+ if (child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) {
+ child = child->GetNextNonChildNode(aRoot);
+ continue;
+ }
+
+ // If the child is assigned to a slot, it should inherit the state from
+ // that.
+ if (!child->GetAssignedSlot()) {
+ MaybeSetAncestorHasDirAutoOnShadowDOM(child);
+ child->SetAncestorHasDirAuto();
+ if (auto* slot = HTMLSlotElement::FromNode(child)) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
+ assignedNodes[i]->SetAncestorHasDirAuto();
+ SetAncestorHasDirAutoOnDescendants(assignedNodes[i]);
+ }
+ }
+ }
+ child = child->GetNextNode(aRoot);
+ }
+}
+
+void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
+ // Only test for DoesNotParticipateInAutoDirection -- in other words, if
+ // aElement is a <bdi> which is having its dir attribute set to auto (or
+ // removed or set to an invalid value, which are equivalent to dir=auto for
+ // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
+ // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
+ // being bound to an existing node with dir=auto.
+ if (!DoesNotParticipateInAutoDirection(aElement) &&
+ !aElement->AncestorHasDirAuto()) {
+ SetAncestorHasDirAutoOnDescendants(aElement);
+ }
+
+ nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
+ if (textNode) {
+ nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
+ }
+}
+
+void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) {
+ if (aContent->IsElement()) {
+ if (ShadowRoot* shadowRoot = aContent->AsElement()->GetShadowRoot()) {
+ shadowRoot->ClearAncestorHasDirAuto();
+ WalkDescendantsClearAncestorDirAuto(shadowRoot);
+ }
+ }
+
+ nsIContent* child = aContent->GetFirstChild();
+ while (child) {
+ if (child->GetAssignedSlot()) {
+ // If the child node is assigned to a slot, nodes state is inherited from
+ // the slot, not from element's parent.
+ child = child->GetNextNonChildNode(aContent);
+ continue;
+ }
+ if (child->IsElement()) {
+ if (child->AsElement()->HasDirAuto()) {
+ child = child->GetNextNonChildNode(aContent);
+ continue;
+ }
+
+ if (auto* slot = HTMLSlotElement::FromNode(child)) {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
+ if (assignedNodes[i]->IsElement()) {
+ Element* slottedElement = assignedNodes[i]->AsElement();
+ if (slottedElement->HasDirAuto()) {
+ continue;
+ }
+ }
+
+ nsIContent* content = assignedNodes[i]->AsContent();
+ content->ClearAncestorHasDirAuto();
+ WalkDescendantsClearAncestorDirAuto(content);
+ }
+ }
+ }
+
+ child->ClearAncestorHasDirAuto();
+ child = child->GetNextNode(aContent);
+ }
+}
+
+void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
+ bool aNotify = true) {
+ MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
+ "Must be a text node");
+
+ bool crossedShadowBoundary = false;
+ nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary);
+ while (parent && parent->NodeOrAncestorHasDirAuto()) {
+ if (!parent->IsElement()) {
+ parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
+ continue;
+ }
+
+ Element* parentElement = parent->AsElement();
+ if (DoesNotParticipateInAutoDirection(parentElement) ||
+ parentElement->HasFixedDir()) {
+ break;
+ }
+
+ if (parentElement->HasDirAuto()) {
+ bool resetDirection = false;
+ nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>(
+ parent->GetProperty(nsGkAtoms::dirAutoSetBy));
+
+ if (!parent->HasDirAutoSet()) {
+ // Fast path if parent's direction is not yet set by any descendant
+ MOZ_ASSERT(!directionWasSetByTextNode,
+ "dirAutoSetBy property should be null");
+ resetDirection = true;
+ } else {
+ // If parent's direction is already set, we need to know if
+ // aTextNode is before or after the text node that had set it.
+ // We will walk parent's descendants in tree order starting from
+ // aTextNode to optimize for the most common case where text nodes are
+ // being appended to tree.
+ if (!directionWasSetByTextNode) {
+ resetDirection = true;
+ } else if (directionWasSetByTextNode != aTextNode) {
+ if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary(
+ directionWasSetByTextNode, parent)) {
+ // Need to take the slow path when the path from either the old or
+ // new text node to the dir=auto element crosses shadow boundary.
+ ResetAutoDirection(parentElement, aNotify);
+ return;
+ }
+
+ nsIContent* child = aTextNode->GetNextNode(parent);
+ while (child) {
+ if (child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) {
+ child = child->GetNextNonChildNode(parent);
+ continue;
+ }
+
+ if (child == directionWasSetByTextNode) {
+ // we found the node that set the element's direction after our
+ // text node, so we need to reset the direction
+ resetDirection = true;
+ break;
+ }
+
+ child = child->GetNextNode(parent);
+ }
+ }
+ }
+
+ if (resetDirection) {
+ if (directionWasSetByTextNode) {
+ nsTextNodeDirectionalityMap::RemoveElementFromMap(
+ directionWasSetByTextNode, parentElement);
+ }
+ parentElement->SetDirectionality(aDir, aNotify);
+ nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement);
+ SetDirectionalityOnDescendants(parentElement, aDir, aNotify);
+ }
+
+ // Since we found an element with dir=auto, we can stop walking the
+ // parent chain: none of its ancestors will have their direction set by
+ // any of its descendants.
+ return;
+ }
+ parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
+ }
+}
+
+bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
+ uint32_t aOffset) {
+ if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+ nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
+ return false;
+ }
+
+ uint32_t firstStrong;
+ *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
+ return (aOffset <= firstStrong);
+}
+
+void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
+ bool aNotify) {
+ Directionality newDir = GetDirectionFromText(aTextNode);
+ if (newDir == eDir_NotSet) {
+ if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
+ // This node used to have a strong directional character but no
+ // longer does. ResetTextNodeDirection() will re-resolve the
+ // directionality of any elements whose directionality was
+ // determined by this node.
+ nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
+ }
+ } else {
+ // This node has a strong directional character. If it has a
+ // TextNodeDirectionalityMap property, it already determines the
+ // directionality of some element(s), so call UpdateTextNodeDirection to
+ // reresolve their directionality. If it has no map, or if
+ // UpdateTextNodeDirection returns zero, indicating that the map is
+ // empty, call SetAncestorDirectionIfAuto to find ancestor elements
+ // which should have their directionality determined by this node.
+ if (aTextNode->HasTextNodeDirectionalityMap() &&
+ nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode,
+ newDir)) {
+ return;
+ }
+ SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
+ }
+}
+
+void SetDirectionFromNewTextNode(nsTextNode* aTextNode) {
+ if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+ return;
+ }
+
+ nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
+ if (parent && parent->NodeOrAncestorHasDirAuto()) {
+ aTextNode->SetAncestorHasDirAuto();
+ }
+
+ Directionality dir = GetDirectionFromText(aTextNode);
+ if (dir != eDir_NotSet) {
+ SetAncestorDirectionIfAuto(aTextNode, dir);
+ }
+}
+
+void ResetDirectionSetByTextNode(nsTextNode* aTextNode) {
+ if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+ nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
+ return;
+ }
+
+ Directionality dir = GetDirectionFromText(aTextNode);
+ if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
+ nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
+ }
+}
+
+void SetDirectionalityFromValue(Element* aElement, const nsAString& value,
+ bool aNotify) {
+ Directionality dir =
+ GetDirectionFromText(value.BeginReading(), value.Length());
+ if (dir == eDir_NotSet) {
+ dir = eDir_LTR;
+ }
+
+ if (aElement->GetDirectionality() != dir) {
+ aElement->SetDirectionality(dir, aNotify);
+ }
+}
+
+void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
+ bool hadValidDir, bool hadDirAuto, bool aNotify) {
+ if (aElement->IsHTMLElement(nsGkAtoms::input) ||
+ aElement->IsHTMLElement(nsGkAtoms::textarea)) {
+ return;
+ }
+
+ if (aElement->AncestorHasDirAuto()) {
+ if (!hadValidDir) {
+ // The element is a descendant of an element with dir = auto, is
+ // having its dir attribute set, and previously didn't have a valid dir
+ // attribute.
+ // Check whether any of its text node descendants determine the
+ // direction of any of its ancestors, and redetermine their direction
+ WalkDescendantsResetAutoDirection(aElement);
+ } else if (!aElement->HasValidDir()) {
+ // The element is a descendant of an element with dir = auto and is
+ // having its dir attribute removed or set to an invalid value.
+ // Reset the direction of any of its ancestors whose direction is
+ // determined by a text node descendant
+ WalkAncestorsResetAutoDirection(aElement, aNotify);
+ }
+ } else if (hadDirAuto && !aElement->HasDirAuto()) {
+ // The element isn't a descendant of an element with dir = auto, and is
+ // having its dir attribute set to something other than auto.
+ // Walk the descendant tree and clear the AncestorHasDirAuto flag.
+ //
+ // N.B: For elements other than <bdi> it would be enough to test that the
+ // current value of dir was "auto" in BeforeSetAttr to know that we
+ // were unsetting dir="auto". For <bdi> things are more complicated,
+ // since it behaves like dir="auto" whenever the dir attribute is
+ // empty or invalid, so we would have to check whether the old value
+ // was not either "ltr" or "rtl", and the new value was either "ltr"
+ // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
+ // here is simpler.
+ WalkDescendantsClearAncestorDirAuto(aElement);
+ }
+
+ if (aElement->HasDirAuto()) {
+ WalkDescendantsSetDirAuto(aElement, aNotify);
+ } else {
+ if (aElement->HasDirAutoSet()) {
+ nsTextNode* setByNode = static_cast<nsTextNode*>(
+ aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+ nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
+ }
+ SetDirectionalityOnDescendants(
+ aElement, RecomputeDirectionality(aElement, aNotify), aNotify);
+ }
+}
+
+void SetDirOnBind(Element* aElement, nsIContent* aParent) {
+ // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
+ // ancestors that have dir=auto
+ if (!DoesNotParticipateInAutoDirection(aElement) &&
+ !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent &&
+ aParent->NodeOrAncestorHasDirAuto()) {
+ aElement->SetAncestorHasDirAuto();
+
+ SetAncestorHasDirAutoOnDescendants(aElement);
+
+ if (aElement->GetFirstChild() || aElement->GetShadowRoot()) {
+ // We may also need to reset the direction of an ancestor with dir=auto
+ WalkAncestorsResetAutoDirection(aElement, true);
+ }
+ }
+
+ if (!aElement->HasDirAuto()) {
+ // if the element doesn't have dir=auto, set its own directionality from
+ // the dir attribute or by inheriting from its ancestors.
+ RecomputeDirectionality(aElement, false);
+ }
+}
+
+void ResetDir(Element* aElement) {
+ if (aElement->HasDirAutoSet()) {
+ nsTextNode* setByNode = static_cast<nsTextNode*>(
+ aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+ nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
+ }
+
+ if (!aElement->HasDirAuto()) {
+ RecomputeDirectionality(aElement, false);
+ }
+}
+
+} // end namespace mozilla
diff --git a/dom/base/DirectionalityUtils.h b/dom/base/DirectionalityUtils.h
new file mode 100644
index 0000000000..6dc922e95c
--- /dev/null
+++ b/dom/base/DirectionalityUtils.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DirectionalityUtils_h___
+#define DirectionalityUtils_h___
+
+#include "nscore.h"
+#include "nsStringFwd.h"
+
+class nsIContent;
+class nsINode;
+class nsAttrValue;
+class nsTextNode;
+
+namespace mozilla::dom {
+class Element;
+class HTMLSlotElement;
+} // namespace mozilla::dom
+
+namespace mozilla {
+
+enum Directionality : uint8_t { eDir_NotSet, eDir_RTL, eDir_LTR, eDir_Auto };
+
+/**
+ * Various methods for returning the directionality of a string using the
+ * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
+ *
+ * @param[out] aFirstStrong the offset to the first character in the string with
+ * strong directionality, or UINT32_MAX if there is none (return
+ value is eDir_NotSet).
+ * @return the directionality of the string
+ */
+Directionality GetDirectionFromText(const char16_t* aText,
+ const uint32_t aLength,
+ uint32_t* aFirstStrong = nullptr);
+
+/**
+ * Set the directionality of an element according to the algorithm defined at
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality,
+ * not including elements with auto direction.
+ *
+ * @return the directionality that the element was set to
+ */
+Directionality RecomputeDirectionality(mozilla::dom::Element* aElement,
+ bool aNotify = true);
+
+/**
+ * Set the directionality of any descendants of a node that do not themselves
+ * have a dir attribute.
+ * For performance reasons we walk down the descendant tree in the rare case
+ * of setting the dir attribute, rather than walking up the ancestor tree in
+ * the much more common case of getting the element's directionality.
+ */
+void SetDirectionalityOnDescendants(mozilla::dom::Element* aElement,
+ Directionality aDir, bool aNotify = true);
+
+/**
+ * Walk the descendants of a node in tree order and, for any text node
+ * descendant that determines the directionality of some element and is not a
+ * descendant of another descendant of the original node with dir=auto,
+ * redetermine that element's directionality
+ */
+void WalkDescendantsResetAutoDirection(mozilla::dom::Element* aElement);
+
+/**
+ * In case a slot element was added or removed it may change the directionality
+ * of ancestors or assigned nodes.
+ *
+ * aAllAssignedNodesChanged forces the computation of the state for all of the
+ * assigned descendants.
+ */
+void SlotStateChanged(dom::HTMLSlotElement* aSlot,
+ bool aAllAssignedNodesChanged = true);
+
+/**
+ * When only a single node in a slot is reassigned we can save some work
+ * compared to the above.
+ */
+void SlotAssignedNodeChanged(dom::HTMLSlotElement* aSlot,
+ nsIContent& aAssignedNode);
+
+/**
+ * After setting dir=auto on an element, walk its descendants in tree order.
+ * If the node doesn't have the NODE_ANCESTOR_HAS_DIR_AUTO flag, set the
+ * NODE_ANCESTOR_HAS_DIR_AUTO flag on all of its descendants.
+ * Resolve the directionality of the element by the "downward propagation
+ * algorithm" (defined in section 3 in the comments at the beginning of
+ * DirectionalityUtils.cpp)
+ */
+void WalkDescendantsSetDirAuto(mozilla::dom::Element* aElement,
+ bool aNotify = true);
+
+/**
+ * After unsetting dir=auto on an element, walk its descendants in tree order,
+ * skipping any that have dir=auto themselves, and unset the
+ * NODE_ANCESTOR_HAS_DIR_AUTO flag
+ */
+void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent);
+
+/**
+ * When the contents of a text node are about to change, retrieve the current
+ * directionality of the text
+ *
+ * @return whether the text node affects the directionality of any element
+ */
+bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
+ uint32_t aOffset);
+
+/**
+ * After the contents of a text node have changed, change the directionality
+ * of any elements whose directionality is determined by that node
+ */
+void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
+ bool aNotify);
+
+/**
+ * When a text node is appended to an element, find any ancestors with dir=auto
+ * whose directionality will be determined by the text node
+ */
+void SetDirectionFromNewTextNode(nsTextNode* aTextNode);
+
+/**
+ * When a text node is removed from a document, find any ancestors whose
+ * directionality it determined and redetermine their directionality
+ *
+ * @param aTextNode the text node
+ */
+void ResetDirectionSetByTextNode(nsTextNode* aTextNode);
+
+/**
+ * Set the directionality of an element according to the directionality of the
+ * text in aValue
+ */
+void SetDirectionalityFromValue(mozilla::dom::Element* aElement,
+ const nsAString& aValue, bool aNotify);
+
+/**
+ * Called when setting the dir attribute on an element, immediately after
+ * AfterSetAttr. This is instead of using BeforeSetAttr or AfterSetAttr, because
+ * in AfterSetAttr we don't know the old value, so we can't identify all cases
+ * where we need to walk up or down the document tree and reset the direction;
+ * and in BeforeSetAttr we can't do the walk because this element hasn't had the
+ * value set yet so the results will be wrong.
+ */
+void OnSetDirAttr(mozilla::dom::Element* aElement, const nsAttrValue* aNewValue,
+ bool hadValidDir, bool hadDirAuto, bool aNotify);
+
+/**
+ * Called when binding a new element to the tree, to set the
+ * NodeAncestorHasDirAuto flag and set the direction of the element and its
+ * ancestors if necessary
+ */
+void SetDirOnBind(mozilla::dom::Element* aElement, nsIContent* aParent);
+
+/**
+ * Called when unbinding an element from the tree, to recompute the
+ * directionality of the element if it doesn't have autodirection, and to
+ * clean up any entries in nsTextDirectionalityMap that refer to it.
+ */
+void ResetDir(mozilla::dom::Element* aElement);
+} // end namespace mozilla
+
+#endif /* DirectionalityUtils_h___ */
diff --git a/dom/base/DispatcherTrait.cpp b/dom/base/DispatcherTrait.cpp
new file mode 100644
index 0000000000..aea3e53bf3
--- /dev/null
+++ b/dom/base/DispatcherTrait.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DispatcherTrait.h"
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/SchedulerGroup.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult DispatcherTrait::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ return SchedulerGroup::UnlabeledDispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* DispatcherTrait::EventTargetFor(
+ TaskCategory aCategory) const {
+ return GetMainThreadSerialEventTarget();
+}
+
+AbstractThread* DispatcherTrait::AbstractMainThreadFor(TaskCategory aCategory) {
+ // Return non DocGroup version by default.
+ return AbstractThread::MainThread();
+}
diff --git a/dom/base/DispatcherTrait.h b/dom/base/DispatcherTrait.h
new file mode 100644
index 0000000000..3600cd5ede
--- /dev/null
+++ b/dom/base/DispatcherTrait.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DispatcherTrait_h
+#define mozilla_dom_DispatcherTrait_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/TaskCategory.h"
+#include "ErrorList.h"
+
+class nsIRunnable;
+class nsISerialEventTarget;
+
+namespace mozilla {
+class AbstractThread;
+namespace dom {
+// This trait should be attached to classes like nsIGlobalObject and
+// Document that have a DocGroup attached to them. The methods here
+// should delegate to the DocGroup. We can't use the
+// Dispatcher class directly because it inherits from nsISupports.
+class DispatcherTrait {
+ public:
+ // This method may or may not be safe off of the main thread. For Document it
+ // is safe. For nsIGlobalWindow it is not safe.
+ virtual nsresult Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ // This method may or may not be safe off of the main thread. For Document it
+ // is safe. For nsIGlobalWindow it is not safe. The nsISerialEventTarget can
+ // always be used off the main thread.
+ virtual nsISerialEventTarget* EventTargetFor(TaskCategory aCategory) const;
+
+ // Must be called on the main thread. The AbstractThread can always be used
+ // off the main thread.
+ virtual AbstractThread* AbstractMainThreadFor(TaskCategory aCategory);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DispatcherTrait_h
diff --git a/dom/base/DocGroup.cpp b/dom/base/DocGroup.cpp
new file mode 100644
index 0000000000..16f673d063
--- /dev/null
+++ b/dom/base/DocGroup.cpp
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DocGroup.h"
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/PerformanceUtils.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/WindowContext.h"
+#include "nsDOMMutationObserver.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsIXULRuntime.h"
+#include "nsProxyRelease.h"
+#include "nsThread.h"
+#if defined(XP_WIN)
+# include <processthreadsapi.h> // for GetCurrentProcessId()
+#else
+# include <unistd.h> // for getpid()
+#endif // defined(XP_WIN)
+
+namespace {
+
+#define NS_LABELLINGEVENTTARGET_IID \
+ { \
+ 0x6087fa50, 0xe387, 0x45c8, { \
+ 0xab, 0x72, 0xd2, 0x1f, 0x69, 0xee, 0xd3, 0x15 \
+ } \
+ }
+
+// LabellingEventTarget labels all dispatches with the DocGroup that
+// created it.
+class LabellingEventTarget final : public nsISerialEventTarget,
+ public nsIDirectTaskDispatcher {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_LABELLINGEVENTTARGET_IID)
+
+ explicit LabellingEventTarget(
+ mozilla::PerformanceCounter* aPerformanceCounter)
+ : mPerformanceCounter(aPerformanceCounter),
+ mMainThread(
+ static_cast<nsThread*>(mozilla::GetMainThreadSerialEventTarget())) {
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ private:
+ ~LabellingEventTarget() = default;
+ const RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
+ const RefPtr<nsThread> mMainThread;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(LabellingEventTarget, NS_LABELLINGEVENTTARGET_IID)
+
+} // namespace
+
+NS_IMETHODIMP
+LabellingEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
+ uint32_t aFlags) {
+ return Dispatch(do_AddRef(aRunnable), aFlags);
+}
+
+NS_IMETHODIMP
+LabellingEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) {
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mozilla::SchedulerGroup::LabeledDispatch(
+ mozilla::TaskCategory::Other, std::move(aRunnable), mPerformanceCounter);
+}
+
+NS_IMETHODIMP
+LabellingEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LabellingEventTarget::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LabellingEventTarget::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LabellingEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
+ *aIsOnCurrentThread = NS_IsMainThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+LabellingEventTarget::IsOnCurrentThreadInfallible() {
+ return NS_IsMainThread();
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+//-----------------------------------------------------------------------------
+// We are always running on the main thread, forward to the nsThread's
+// MainThread
+NS_IMETHODIMP
+LabellingEventTarget::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
+ return mMainThread->DispatchDirectTask(std::move(aEvent));
+}
+
+NS_IMETHODIMP LabellingEventTarget::DrainDirectTasks() {
+ return mMainThread->DrainDirectTasks();
+}
+
+NS_IMETHODIMP LabellingEventTarget::HaveDirectTasks(bool* aValue) {
+ return mMainThread->HaveDirectTasks(aValue);
+}
+
+NS_IMPL_ISUPPORTS(LabellingEventTarget, nsIEventTarget, nsISerialEventTarget,
+ nsIDirectTaskDispatcher)
+
+namespace mozilla::dom {
+
+AutoTArray<RefPtr<DocGroup>, 2>* DocGroup::sPendingDocGroups = nullptr;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DocGroup)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DocGroup)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalSlotList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DocGroup)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalSlotList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup)
+
+ // If we still have any documents in this array, they were just unlinked, so
+ // clear out our weak pointers to them.
+ tmp->mDocuments.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+/* static */
+already_AddRefed<DocGroup> DocGroup::Create(
+ BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey) {
+ RefPtr<DocGroup> docGroup = new DocGroup(aBrowsingContextGroup, aKey);
+ docGroup->mEventTarget =
+ new LabellingEventTarget(docGroup->GetPerformanceCounter());
+ return docGroup.forget();
+}
+
+/* static */
+nsresult DocGroup::GetKey(nsIPrincipal* aPrincipal, bool aCrossOriginIsolated,
+ nsACString& aKey) {
+ // Use GetBaseDomain() to handle things like file URIs, IP address URIs,
+ // etc. correctly.
+ nsresult rv = aCrossOriginIsolated ? aPrincipal->GetOrigin(aKey)
+ : aPrincipal->GetSiteOrigin(aKey);
+ if (NS_FAILED(rv)) {
+ aKey.Truncate();
+ }
+
+ return rv;
+}
+
+void DocGroup::SetExecutionManager(JSExecutionManager* aManager) {
+ mExecutionManager = aManager;
+}
+
+mozilla::dom::CustomElementReactionsStack*
+DocGroup::CustomElementReactionsStack() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mReactionsStack) {
+ mReactionsStack = new mozilla::dom::CustomElementReactionsStack();
+ }
+
+ return mReactionsStack;
+}
+
+void DocGroup::AddDocument(Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDocuments.Contains(aDocument));
+ MOZ_ASSERT(mBrowsingContextGroup);
+ // If the document is loaded as data it may not have a container, in which
+ // case it can be difficult to determine the BrowsingContextGroup it's
+ // associated with. XSLT can also add the document to the DocGroup before it
+ // gets a container in some cases, in which case this will be asserted
+ // elsewhere.
+ MOZ_ASSERT_IF(
+ aDocument->GetBrowsingContext(),
+ aDocument->GetBrowsingContext()->Group() == mBrowsingContextGroup);
+ mDocuments.AppendElement(aDocument);
+}
+
+void DocGroup::RemoveDocument(Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDocuments.Contains(aDocument));
+ mDocuments.RemoveElement(aDocument);
+
+ if (mDocuments.IsEmpty()) {
+ mBrowsingContextGroup = nullptr;
+ }
+}
+
+DocGroup::DocGroup(BrowsingContextGroup* aBrowsingContextGroup,
+ const nsACString& aKey)
+ : mKey(aKey),
+ mBrowsingContextGroup(aBrowsingContextGroup),
+ mAgentClusterId(nsID::GenerateUUID()) {
+ // This method does not add itself to
+ // mBrowsingContextGroup->mDocGroups as the caller does it for us.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
+ mArena = new mozilla::dom::DOMArena();
+ }
+
+ mPerformanceCounter = new mozilla::PerformanceCounter("DocGroup:"_ns + aKey);
+}
+
+DocGroup::~DocGroup() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(mDocuments.IsEmpty());
+
+ if (mIframePostMessageQueue) {
+ FlushIframePostMessageQueue();
+ }
+}
+
+RefPtr<PerformanceInfoPromise> DocGroup::ReportPerformanceInfo() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mPerformanceCounter);
+#if defined(XP_WIN)
+ uint32_t pid = GetCurrentProcessId();
+#else
+ uint32_t pid = getpid();
+#endif
+ uint64_t windowID = 0;
+ uint16_t count = 0;
+ uint64_t duration = 0;
+ nsCString host;
+ bool isTopLevel = false;
+ RefPtr<BrowsingContext> top;
+ RefPtr<AbstractThread> mainThread =
+ AbstractMainThreadFor(TaskCategory::Performance);
+
+ for (const auto& document : *this) {
+ if (host.IsEmpty()) {
+ nsCOMPtr<nsIURI> docURI = document->GetDocumentURI();
+ if (!docURI) {
+ continue;
+ }
+
+ docURI->GetHost(host);
+ if (host.IsEmpty()) {
+ host = docURI->GetSpecOrDefault();
+ }
+ }
+
+ BrowsingContext* context = document->GetBrowsingContext();
+ if (!context) {
+ continue;
+ }
+
+ top = context->Top();
+
+ if (!top || !top->GetCurrentWindowContext()) {
+ continue;
+ }
+
+ isTopLevel = context->IsTop();
+ windowID = top->GetCurrentWindowContext()->OuterWindowId();
+ break;
+ };
+
+ MOZ_ASSERT(!host.IsEmpty());
+ duration = mPerformanceCounter->GetExecutionDuration();
+ FallibleTArray<CategoryDispatch> items;
+
+ // now that we have the host and window ids, let's look at the perf counters
+ for (uint32_t index = 0; index < (uint32_t)TaskCategory::Count; index++) {
+ TaskCategory category = static_cast<TaskCategory>(index);
+ count = mPerformanceCounter->GetDispatchCount(DispatchCategory(category));
+ CategoryDispatch item = CategoryDispatch(index, count);
+ if (!items.AppendElement(item, fallible)) {
+ NS_ERROR("Could not complete the operation");
+ break;
+ }
+ }
+
+ if (!isTopLevel && top && top->IsInProcess()) {
+ return PerformanceInfoPromise::CreateAndResolve(
+ PerformanceInfo(host, pid, windowID, duration,
+ mPerformanceCounter->GetID(), false, isTopLevel,
+ PerformanceMemoryInfo(), // Empty memory info
+ items),
+ __func__);
+ }
+
+ MOZ_ASSERT(mainThread);
+ RefPtr<DocGroup> self = this;
+ return CollectMemoryInfo(self, mainThread)
+ ->Then(
+ mainThread, __func__,
+ [self, host, pid, windowID, duration, isTopLevel,
+ items = std::move(items)](const PerformanceMemoryInfo& aMemoryInfo) {
+ PerformanceInfo info =
+ PerformanceInfo(host, pid, windowID, duration,
+ self->mPerformanceCounter->GetID(), false,
+ isTopLevel, aMemoryInfo, items);
+
+ return PerformanceInfoPromise::CreateAndResolve(std::move(info),
+ __func__);
+ },
+ [self](const nsresult rv) {
+ return PerformanceInfoPromise::CreateAndReject(rv, __func__);
+ });
+}
+
+nsresult DocGroup::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (mPerformanceCounter) {
+ mPerformanceCounter->IncrementDispatchCounter(DispatchCategory(aCategory));
+ }
+ return SchedulerGroup::LabeledDispatch(aCategory, std::move(aRunnable),
+ mPerformanceCounter);
+}
+
+nsISerialEventTarget* DocGroup::EventTargetFor(TaskCategory aCategory) const {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDocuments.IsEmpty());
+
+ // Here we have the same event target for every TaskCategory. The
+ // reason for that is that currently TaskCategory isn't used, and
+ // it's unsure if it ever will be (See Bug 1624819).
+ return mEventTarget;
+}
+
+AbstractThread* DocGroup::AbstractMainThreadFor(TaskCategory aCategory) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDocuments.IsEmpty());
+
+ // Here we have the same thread for every TaskCategory. The reason
+ // for that is that currently TaskCategory isn't used, and it's
+ // unsure if it ever will be (See Bug 1624819).
+ return AbstractThread::MainThread();
+}
+
+void DocGroup::SignalSlotChange(HTMLSlotElement& aSlot) {
+ MOZ_ASSERT(!mSignalSlotList.Contains(&aSlot));
+ mSignalSlotList.AppendElement(&aSlot);
+
+ if (!sPendingDocGroups) {
+ // Queue a mutation observer compound microtask.
+ nsDOMMutationObserver::QueueMutationObserverMicroTask();
+ sPendingDocGroups = new AutoTArray<RefPtr<DocGroup>, 2>;
+ }
+
+ sPendingDocGroups->AppendElement(this);
+}
+
+bool DocGroup::TryToLoadIframesInBackground() {
+ return !FissionAutostart() &&
+ StaticPrefs::dom_separate_event_queue_for_post_message_enabled() &&
+ StaticPrefs::dom_cross_origin_iframes_loaded_in_background();
+}
+
+nsresult DocGroup::QueueIframePostMessages(
+ already_AddRefed<nsIRunnable>&& aRunnable, uint64_t aWindowId) {
+ if (DocGroup::TryToLoadIframesInBackground()) {
+ if (!mIframePostMessageQueue) {
+ nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
+ mIframePostMessageQueue = ThrottledEventQueue::Create(
+ target, "Background Loading Iframe PostMessage Queue",
+ nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS);
+ nsresult rv = mIframePostMessageQueue->SetIsPaused(true);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ }
+
+ // Ensure the queue is disabled. Unlike the postMessageEvent queue
+ // in BrowsingContextGroup, this postMessage queue should always
+ // be paused, because if we leave it open, the postMessage may get
+ // dispatched to an unloaded iframe
+ MOZ_ASSERT(mIframePostMessageQueue);
+ MOZ_ASSERT(mIframePostMessageQueue->IsPaused());
+
+ mIframesUsedPostMessageQueue.Insert(aWindowId);
+
+ mIframePostMessageQueue->Dispatch(std::move(aRunnable), NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+void DocGroup::TryFlushIframePostMessages(uint64_t aWindowId) {
+ if (DocGroup::TryToLoadIframesInBackground()) {
+ mIframesUsedPostMessageQueue.Remove(aWindowId);
+ if (mIframePostMessageQueue && mIframesUsedPostMessageQueue.IsEmpty()) {
+ MOZ_ASSERT(mIframePostMessageQueue->IsPaused());
+ nsresult rv = mIframePostMessageQueue->SetIsPaused(true);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ FlushIframePostMessageQueue();
+ }
+ }
+}
+
+void DocGroup::FlushIframePostMessageQueue() {
+ nsCOMPtr<nsIRunnable> event;
+ while ((event = mIframePostMessageQueue->GetEvent())) {
+ Dispatch(TaskCategory::Other, event.forget());
+ }
+}
+
+nsTArray<RefPtr<HTMLSlotElement>> DocGroup::MoveSignalSlotList() {
+ for (const RefPtr<HTMLSlotElement>& slot : mSignalSlotList) {
+ slot->RemovedFromSignalSlotList();
+ }
+ return std::move(mSignalSlotList);
+}
+
+bool DocGroup::IsActive() const {
+ for (Document* doc : mDocuments) {
+ if (doc->IsCurrentActiveDocument()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DocGroup.h b/dom/base/DocGroup.h
new file mode 100644
index 0000000000..fb8c552fbf
--- /dev/null
+++ b/dom/base/DocGroup.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DocGroup_h
+#define DocGroup_h
+
+#include "nsISupportsImpl.h"
+#include "nsIPrincipal.h"
+#include "nsThreadUtils.h"
+#include "nsTHashSet.h"
+#include "nsString.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/PerformanceTypes.h"
+
+namespace mozilla {
+class AbstractThread;
+namespace dom {
+
+class CustomElementReactionsStack;
+class JSExecutionManager;
+
+// Two browsing contexts are considered "related" if they are reachable from one
+// another through window.opener, window.parent, or window.frames. This is the
+// spec concept of a browsing context group.
+//
+// Two browsing contexts are considered "similar-origin" if they can be made to
+// have the same origin by setting document.domain. This is the spec concept of
+// a "unit of similar-origin related browsing contexts"
+//
+// A BrowsingContextGroup is a set of browsing contexts which are all
+// "related". Within a BrowsingContextGroup, browsing contexts are
+// broken into "similar-origin" DocGroups. A DocGroup is a member
+// of exactly one BrowsingContextGroup.
+class DocGroup final {
+ public:
+ typedef nsTArray<Document*>::iterator Iterator;
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DocGroup)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(DocGroup)
+
+ static already_AddRefed<DocGroup> Create(
+ BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey);
+
+ // Returns NS_ERROR_FAILURE and sets |aString| to an empty string if the TLD
+ // service isn't available. Returns NS_OK on success, but may still set
+ // |aString| may still be set to an empty string.
+ [[nodiscard]] static nsresult GetKey(nsIPrincipal* aPrincipal,
+ bool aCrossOriginIsolated,
+ nsACString& aKey);
+
+ bool MatchesKey(const nsACString& aKey) { return aKey == mKey; }
+
+ const nsACString& GetKey() const { return mKey; }
+
+ PerformanceCounter* GetPerformanceCounter() { return mPerformanceCounter; }
+
+ JSExecutionManager* GetExecutionManager() const { return mExecutionManager; }
+ void SetExecutionManager(JSExecutionManager*);
+
+ RefPtr<PerformanceInfoPromise> ReportPerformanceInfo();
+
+ BrowsingContextGroup* GetBrowsingContextGroup() const {
+ return mBrowsingContextGroup;
+ }
+
+ mozilla::dom::DOMArena* ArenaAllocator() { return mArena; }
+
+ mozilla::dom::CustomElementReactionsStack* CustomElementReactionsStack();
+
+ // Adding documents to a DocGroup should be done through
+ // BrowsingContextGroup::AddDocument (which in turn calls
+ // DocGroup::AddDocument).
+ void AddDocument(Document* aDocument);
+
+ // Removing documents from a DocGroup should be done through
+ // BrowsingContextGroup::RemoveDocument(which in turn calls
+ // DocGroup::RemoveDocument).
+ void RemoveDocument(Document* aDocument);
+
+ // Iterators for iterating over every document within the DocGroup
+ Iterator begin() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDocuments.begin();
+ }
+ Iterator end() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDocuments.end();
+ }
+
+ nsresult Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ nsISerialEventTarget* EventTargetFor(TaskCategory aCategory) const;
+
+ AbstractThread* AbstractMainThreadFor(TaskCategory aCategory);
+
+ // Return a pointer that can be continually checked to see if access to this
+ // DocGroup is valid. This pointer should live at least as long as the
+ // DocGroup.
+ bool* GetValidAccessPtr();
+
+ // Append aSlot to the list of signal slot list, and queue a mutation observer
+ // microtask.
+ void SignalSlotChange(HTMLSlotElement& aSlot);
+
+ nsTArray<RefPtr<HTMLSlotElement>> MoveSignalSlotList();
+
+ // List of DocGroups that has non-empty signal slot list.
+ static AutoTArray<RefPtr<DocGroup>, 2>* sPendingDocGroups;
+
+ // Returns true if any of its documents are active but not in the bfcache.
+ bool IsActive() const;
+
+ nsresult QueueIframePostMessages(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint64_t aWindowId);
+
+ void TryFlushIframePostMessages(uint64_t aWindowId);
+
+ static bool TryToLoadIframesInBackground();
+
+ const nsID& AgentClusterId() const { return mAgentClusterId; }
+
+ bool IsEmpty() const { return mDocuments.IsEmpty(); }
+
+ private:
+ DocGroup(BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey);
+
+ ~DocGroup();
+
+ void FlushIframePostMessageQueue();
+ nsCString mKey;
+ nsTArray<Document*> mDocuments;
+ RefPtr<mozilla::dom::CustomElementReactionsStack> mReactionsStack;
+ nsTArray<RefPtr<HTMLSlotElement>> mSignalSlotList;
+ RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
+ RefPtr<BrowsingContextGroup> mBrowsingContextGroup;
+ RefPtr<mozilla::ThrottledEventQueue> mIframePostMessageQueue;
+ nsTHashSet<uint64_t> mIframesUsedPostMessageQueue;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+
+ // non-null if the JS execution for this docgroup is regulated with regards
+ // to worker threads. This should only be used when we are forcing serialized
+ // SAB access.
+ RefPtr<JSExecutionManager> mExecutionManager;
+
+ // Each DocGroup has a persisted agent cluster ID.
+ const nsID mAgentClusterId;
+
+ RefPtr<mozilla::dom::DOMArena> mArena;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // defined(DocGroup_h)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
new file mode 100644
index 0000000000..3a1575b7df
--- /dev/null
+++ b/dom/base/Document.cpp
@@ -0,0 +1,18628 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all our document implementations.
+ */
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+#include "Attr.h"
+#include "ErrorList.h"
+#include "ExpandedPrincipal.h"
+#include "MainThreadUtils.h"
+#include "MobileViewportManager.h"
+#include "NodeUbiReporting.h"
+#include "PLDHashTable.h"
+#include "StorageAccessPermissionRequest.h"
+#include "ThirdPartyUtil.h"
+#include "domstubs.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "js/Value.h"
+#include "jsapi.h"
+#include "mozAutoDocUpdate.h"
+#include "mozIDOMWindow.h"
+#include "mozIThirdPartyUtil.h"
+#include "mozilla/AbstractTimelineMarker.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/ContentBlockingUserInteraction.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DocLoadingTimelineMarker.h"
+#include "mozilla/DocumentStyleRootIterator.h"
+#include "mozilla/EditorBase.h"
+#include "mozilla/EditorCommands.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FullscreenChange.h"
+#include "mozilla/GlobalStyleSheetCache.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/IdentifierMapEntry.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Logging.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/MediaManager.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/PendingFullscreenEvent.h"
+#include "mozilla/PermissionDelegateHandler.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/RejectForeignAllowList.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/ScrollTimelineAnimationTracker.h"
+#include "mozilla/SMILAnimationController.h"
+#include "mozilla/SMILTimeContainer.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Components.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/SizeOfState.h"
+#include "mozilla/Span.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_editor.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_full_screen_api.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_page_load.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TelemetryScalarEnums.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/URLDecorationStripper.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/Unused.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/css/Rule.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CDATASection.h"
+#include "mozilla/dom/CSPDictionariesBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ChromeObserver.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DOMImplementation.h"
+#include "mozilla/dom/DOMIntersectionObserver.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentL10n.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventListenerBinding.h"
+#include "mozilla/dom/FailedCertSecurityInfoBinding.h"
+#include "mozilla/dom/FeaturePolicy.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/HighlightRegistry.h"
+#include "mozilla/dom/HTMLAllCollection.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLCollectionBinding.h"
+#include "mozilla/dom/HTMLDialogElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLMetaElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/NetErrorInfoBinding.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeIterator.h"
+#include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/PWindowGlobalChild.h"
+#include "mozilla/dom/PageTransitionEvent.h"
+#include "mozilla/dom/PageTransitionEventBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PostMessageEvent.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ResizeObserverController.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/SVGDocument.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGUseElement.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ShadowIncludingTreeIterator.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
+#include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
+#include "mozilla/dom/StyleSheetList.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/dom/ToggleEvent.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/dom/TreeOrderedArrayInlines.h"
+#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/WorkerDocumentListener.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/BaseCoord.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/Coord.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/ScaleFactor.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/ipc/IdleSchedulerChild.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/RequestContextService.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsAlgorithm.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsBaseHashtable.h"
+#include "nsBidiUtils.h"
+#include "nsCRT.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsCanvasFrame.h"
+#include "nsCaseTreatment.h"
+#include "nsCharsetSource.h"
+#include "nsCommandManager.h"
+#include "nsCommandParams.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentPermissionHelper.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsDOMAttributeMap.h"
+#include "nsDOMCaretPosition.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDOMString.h"
+#include "nsDeviceContext.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsEffectiveTLDService.h"
+#include "nsError.h"
+#include "nsEscape.h"
+#include "nsFocusManager.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHTMLCSSStyleSheet.h"
+#include "nsHTMLDocument.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsHtml5Module.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsIAsyncShutdown.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIBFCacheEntry.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowserChild.h"
+#include "nsIBrowserUsage.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsICategoryManager.h"
+#include "nsICertOverrideService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentSink.h"
+#include "nsICookieJarSettings.h"
+#include "nsICookieService.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocumentActivity.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIDocumentObserver.h"
+#include "nsIDNSService.h"
+#include "nsIEditingSession.h"
+#include "nsIEditor.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIHTMLCollection.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIInlineSpellChecker.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIMutationObserver.h"
+#include "nsINSSErrorsService.h"
+#include "nsINamed.h"
+#include "nsINodeList.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIObserverService.h"
+#include "nsIPermission.h"
+#include "nsIPrompt.h"
+#include "nsIPropertyBag2.h"
+#include "nsIPublicKeyPinningService.h"
+#include "nsIReferrerInfo.h"
+#include "nsIRefreshURI.h"
+#include "nsIRequest.h"
+#include "nsIRequestContext.h"
+#include "nsIRunnable.h"
+#include "nsISHEntry.h"
+#include "nsIScriptElement.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsISelectionController.h"
+#include "nsISerialEventTarget.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIThread.h"
+#include "nsITimedChannel.h"
+#include "nsITimer.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURIMutator.h"
+#include "nsIVariant.h"
+#include "nsIWeakReference.h"
+#include "nsIWebNavigation.h"
+#include "nsIWidget.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertValidity.h"
+#include "nsIXMLContentSink.h"
+#include "nsIHTMLContentSink.h"
+#include "nsIXULRuntime.h"
+#include "nsImageLoadingContent.h"
+#include "nsImportModule.h"
+#include "nsLanguageAtomService.h"
+#include "nsLayoutUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsNodeInfoManager.h"
+#include "nsObjectLoadingContent.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsPIWindowRoot.h"
+#include "nsPoint.h"
+#include "nsPointerHashKeys.h"
+#include "nsPresContext.h"
+#include "nsQueryFrame.h"
+#include "nsQueryObject.h"
+#include "nsRange.h"
+#include "nsRect.h"
+#include "nsRefreshDriver.h"
+#include "nsSandboxFlags.h"
+#include "nsSerializationHelper.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringFlags.h"
+#include "nsStyleUtil.h"
+#include "nsStringIterator.h"
+#include "nsStyleSheetService.h"
+#include "nsStyleStruct.h"
+#include "nsTextNode.h"
+#include "nsUnicharUtils.h"
+#include "nsWrapperCache.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsXPCOMCID.h"
+#include "nsXULAppAPI.h"
+#include "prthread.h"
+#include "prtime.h"
+#include "prtypes.h"
+#include "xpcpublic.h"
+
+// XXX Must be included after mozilla/Encoding.h
+#include "encoding_rs.h"
+
+#include "mozilla/dom/XULBroadcastManager.h"
+#include "mozilla/dom/XULPersist.h"
+#include "nsIAppWindow.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsXULCommandDispatcher.h"
+#include "nsXULPopupManager.h"
+#include "nsIDocShellTreeOwner.h"
+
+#define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
+#define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
+#define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
+#define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
+
+#define NS_MAX_DOCUMENT_WRITE_DEPTH 20
+
+mozilla::LazyLogModule gPageCacheLog("PageCache");
+mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
+mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
+mozilla::LazyLogModule gUseCountersLog("UseCounters");
+
+namespace mozilla {
+namespace dom {
+
+class Document::HeaderData {
+ public:
+ HeaderData(nsAtom* aField, const nsAString& aData)
+ : mField(aField), mData(aData) {}
+
+ ~HeaderData() {
+ // Delete iteratively to avoid blowing up the stack, though it shouldn't
+ // happen in practice.
+ UniquePtr<HeaderData> next = std::move(mNext);
+ while (next) {
+ next = std::move(next->mNext);
+ }
+ }
+
+ RefPtr<nsAtom> mField;
+ nsString mData;
+ UniquePtr<HeaderData> mNext;
+};
+
+using LinkArray = nsTArray<Link*>;
+
+AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
+ nullptr;
+
+static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
+static LazyLogModule gCspPRLog("CSP");
+LazyLogModule gUserInteractionPRLog("UserInteraction");
+
+static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
+ nsIHttpChannel** aHttpChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ httpChannel.forget(aHttpChannel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
+ if (!multipart) {
+ *aHttpChannel = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> baseChannel;
+ nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ httpChannel = do_QueryInterface(baseChannel);
+ httpChannel.forget(aHttpChannel);
+
+ return NS_OK;
+}
+
+} // namespace dom
+
+#define NAME_NOT_VALID ((nsSimpleContentList*)1)
+
+IdentifierMapEntry::IdentifierMapEntry(
+ const IdentifierMapEntry::DependentAtomOrString* aKey)
+ : mKey(aKey ? *aKey : nullptr) {}
+
+void IdentifierMapEntry::Traverse(
+ nsCycleCollectionTraversalCallback* aCallback) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mNameContentList");
+ aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
+
+ if (mImageElement) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mImageElement element");
+ nsIContent* imageElement = mImageElement;
+ aCallback->NoteXPCOMChild(imageElement);
+ }
+}
+
+bool IdentifierMapEntry::IsEmpty() {
+ return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
+ !mImageElement;
+}
+
+bool IdentifierMapEntry::HasNameElement() const {
+ return mNameContentList && mNameContentList->Length() != 0;
+}
+
+void IdentifierMapEntry::AddContentChangeCallback(
+ Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
+ if (!mChangeCallbacks) {
+ mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
+ }
+
+ ChangeCallback cc = {aCallback, aData, aForImage};
+ mChangeCallbacks->PutEntry(cc);
+}
+
+void IdentifierMapEntry::RemoveContentChangeCallback(
+ Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
+ if (!mChangeCallbacks) return;
+ ChangeCallback cc = {aCallback, aData, aForImage};
+ mChangeCallbacks->RemoveEntry(cc);
+ if (mChangeCallbacks->Count() == 0) {
+ mChangeCallbacks = nullptr;
+ }
+}
+
+void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
+ Element* aNewElement,
+ bool aImageOnly) {
+ if (!mChangeCallbacks) return;
+
+ for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
+ IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
+ // Don't fire image changes for non-image observers, and don't fire element
+ // changes for image observers when an image override is active.
+ if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
+ continue;
+ }
+
+ if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
+ iter.Remove();
+ }
+ }
+}
+
+void IdentifierMapEntry::AddIdElement(Element* aElement) {
+ MOZ_ASSERT(aElement, "Must have element");
+ MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
+
+ size_t index = mIdContentList.Insert(*aElement);
+ if (index == 0) {
+ Element* oldElement = mIdContentList->SafeElementAt(1);
+ FireChangeCallbacks(oldElement, aElement);
+ }
+}
+
+void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
+ MOZ_ASSERT(aElement, "Missing element");
+
+ // This should only be called while the document is in an update.
+ // Assertions near the call to this method guarantee this.
+
+ // This could fire in OOM situations
+ // Only assert this in HTML documents for now as XUL does all sorts of weird
+ // crap.
+ NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
+ mIdContentList->Contains(aElement),
+ "Removing id entry that doesn't exist");
+
+ // XXXbz should this ever Compact() I guess when all the content is gone
+ // we'll just get cleaned up in the natural order of things...
+ Element* currentElement = mIdContentList->SafeElementAt(0);
+ mIdContentList.RemoveElement(*aElement);
+ if (currentElement == aElement) {
+ FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
+ }
+}
+
+void IdentifierMapEntry::SetImageElement(Element* aElement) {
+ Element* oldElement = GetImageIdElement();
+ mImageElement = aElement;
+ Element* newElement = GetImageIdElement();
+ if (oldElement != newElement) {
+ FireChangeCallbacks(oldElement, newElement, true);
+ }
+}
+
+void IdentifierMapEntry::ClearAndNotify() {
+ Element* currentElement = mIdContentList->SafeElementAt(0);
+ mIdContentList.Clear();
+ if (currentElement) {
+ FireChangeCallbacks(currentElement, nullptr);
+ }
+ mNameContentList = nullptr;
+ if (mImageElement) {
+ SetImageElement(nullptr);
+ }
+ mChangeCallbacks = nullptr;
+}
+
+namespace dom {
+
+class SimpleHTMLCollection final : public nsSimpleContentList,
+ public nsIHTMLCollection {
+ public:
+ explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual nsINode* GetParentObject() override {
+ return nsSimpleContentList::GetParentObject();
+ }
+ virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
+ virtual Element* GetElementAt(uint32_t aIndex) override {
+ return mElements.SafeElementAt(aIndex)->AsElement();
+ }
+
+ virtual Element* GetFirstNamedElement(const nsAString& aName,
+ bool& aFound) override {
+ aFound = false;
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ for (uint32_t i = 0; i < mElements.Length(); i++) {
+ MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
+ Element* element = mElements[i]->AsElement();
+ if (element->GetID() == name ||
+ (element->HasName() &&
+ element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
+ aFound = true;
+ return element;
+ }
+ }
+ return nullptr;
+ }
+
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
+ AutoTArray<nsAtom*, 8> atoms;
+ for (uint32_t i = 0; i < mElements.Length(); i++) {
+ MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
+ Element* element = mElements[i]->AsElement();
+
+ nsAtom* id = element->GetID();
+ MOZ_ASSERT(id != nsGkAtoms::_empty);
+ if (id && !atoms.Contains(id)) {
+ atoms.AppendElement(id);
+ }
+
+ if (element->HasName()) {
+ nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
+ MOZ_ASSERT(name && name != nsGkAtoms::_empty);
+ if (name && !atoms.Contains(name)) {
+ atoms.AppendElement(name);
+ }
+ }
+ }
+
+ nsString* names = aNames.AppendElements(atoms.Length());
+ for (uint32_t i = 0; i < atoms.Length(); i++) {
+ atoms[i]->ToString(names[i]);
+ }
+ }
+
+ virtual JSObject* GetWrapperPreserveColorInternal() override {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+ virtual void PreserveWrapperInternal(
+ nsISupports* aScriptObjectHolder) override {
+ nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
+ }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ using nsBaseContentList::Item;
+
+ private:
+ virtual ~SimpleHTMLCollection() = default;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
+ nsIHTMLCollection)
+
+} // namespace dom
+
+void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
+ if (!mNameContentList) {
+ mNameContentList = new dom::SimpleHTMLCollection(aNode);
+ }
+
+ mNameContentList->AppendElement(aElement);
+}
+
+void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
+ if (mNameContentList) {
+ mNameContentList->RemoveElement(aElement);
+ }
+}
+
+bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
+ Element* idElement = GetIdElement();
+ return idElement &&
+ nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
+}
+
+size_t IdentifierMapEntry::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+// Helper structs for the content->subdoc map
+
+class SubDocMapEntry : public PLDHashEntryHdr {
+ public:
+ // Both of these are strong references
+ dom::Element* mKey; // must be first, to look like PLDHashEntryStub
+ dom::Document* mSubDocument;
+};
+
+class OnloadBlocker final : public nsIRequest {
+ public:
+ OnloadBlocker() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+
+ private:
+ ~OnloadBlocker() = default;
+};
+
+NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
+
+NS_IMETHODIMP
+OnloadBlocker::GetName(nsACString& aResult) {
+ aResult.AssignLiteral("about:document-onload-blocker");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OnloadBlocker::IsPending(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OnloadBlocker::GetStatus(nsresult* status) {
+ *status = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+NS_IMETHODIMP
+OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
+NS_IMETHODIMP
+OnloadBlocker::Suspend(void) { return NS_OK; }
+NS_IMETHODIMP
+OnloadBlocker::Resume(void) { return NS_OK; }
+
+NS_IMETHODIMP
+OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
+
+NS_IMETHODIMP
+OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = nsIRequest::LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
+
+// ==================================================================
+
+namespace dom {
+
+ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
+
+Document* ExternalResourceMap::RequestResource(
+ nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
+ Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
+ // If we ever start allowing non-same-origin loads here, we might need to do
+ // something interesting with aRequestingPrincipal even for the hashtable
+ // gets.
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aRequestingNode, "Must have a node");
+ MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
+ *aPendingLoad = nullptr;
+ if (mHaveShutDown) {
+ return nullptr;
+ }
+
+ // First, make sure we strip the ref from aURI.
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
+ if (NS_FAILED(rv) || !clone) {
+ return nullptr;
+ }
+
+ ExternalResource* resource;
+ mMap.Get(clone, &resource);
+ if (resource) {
+ return resource->mDocument;
+ }
+
+ bool loadStartSucceeded =
+ mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
+ if (!loadEntry) {
+ loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
+
+ if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
+ aRequestingNode))) {
+ return false;
+ }
+ }
+
+ RefPtr<PendingLoad> load(loadEntry.Data());
+ load.forget(aPendingLoad);
+ return true;
+ });
+ if (!loadStartSucceeded) {
+ // Make sure we don't thrash things by trying this load again, since
+ // chances are it failed for good reasons (security check, etc).
+ // This must be done outside the WithEntryHandle functor, as it accesses
+ // mPendingLoads.
+ AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
+ }
+
+ return nullptr;
+}
+
+void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
+ nsTArray<RefPtr<Document>> docs(mMap.Count());
+ for (const auto& entry : mMap.Values()) {
+ if (Document* doc = entry->mDocument) {
+ docs.AppendElement(doc);
+ }
+ }
+
+ for (auto& doc : docs) {
+ if (aCallback(*doc) == CallState::Stop) {
+ return;
+ }
+ }
+}
+
+void ExternalResourceMap::Traverse(
+ nsCycleCollectionTraversalCallback* aCallback) const {
+ // mPendingLoads will get cleared out as the requests complete, so
+ // no need to worry about those here.
+ for (const auto& entry : mMap) {
+ ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mExternalResourceMap.mMap entry"
+ "->mDocument");
+ aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mExternalResourceMap.mMap entry"
+ "->mViewer");
+ aCallback->NoteXPCOMChild(resource->mViewer);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mExternalResourceMap.mMap entry"
+ "->mLoadGroup");
+ aCallback->NoteXPCOMChild(resource->mLoadGroup);
+ }
+}
+
+void ExternalResourceMap::HideViewers() {
+ for (const auto& entry : mMap) {
+ nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
+ if (viewer) {
+ viewer->Hide();
+ }
+ }
+}
+
+void ExternalResourceMap::ShowViewers() {
+ for (const auto& entry : mMap) {
+ nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
+ if (viewer) {
+ viewer->Show();
+ }
+ }
+}
+
+void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
+ MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
+
+ if (aFromDoc->IsShowing()) {
+ aToDoc->OnPageShow(true, nullptr);
+ }
+}
+
+nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
+ nsIContentViewer* aViewer,
+ nsILoadGroup* aLoadGroup,
+ Document* aDisplayDocument) {
+ MOZ_ASSERT(aURI, "Unexpected call");
+ MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
+ "Must have both or neither");
+
+ RefPtr<PendingLoad> load;
+ mPendingLoads.Remove(aURI, getter_AddRefs(load));
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<Document> doc;
+ if (aViewer) {
+ doc = aViewer->GetDocument();
+ NS_ASSERTION(doc, "Must have a document");
+
+ doc->SetDisplayDocument(aDisplayDocument);
+
+ // Make sure that hiding our viewer will tear down its presentation.
+ aViewer->SetSticky(false);
+
+ rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ rv = aViewer->Open(nullptr, nullptr);
+ }
+
+ if (NS_FAILED(rv)) {
+ doc = nullptr;
+ aViewer = nullptr;
+ aLoadGroup = nullptr;
+ }
+ }
+
+ ExternalResource* newResource =
+ mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
+
+ newResource->mDocument = doc;
+ newResource->mViewer = aViewer;
+ newResource->mLoadGroup = aLoadGroup;
+ if (doc) {
+ if (nsPresContext* pc = doc->GetPresContext()) {
+ pc->RecomputeBrowsingContextDependentData();
+ }
+ TransferShowingState(aDisplayDocument, doc);
+ }
+
+ const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
+ for (uint32_t i = 0; i < obs.Length(); ++i) {
+ obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
+ nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
+ ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
+ if (map.HaveShutDown()) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv =
+ SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
+
+ // Make sure to do this no matter what
+ nsresult rv2 =
+ map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (NS_FAILED(rv2)) {
+ mTargetListener = nullptr;
+ return rv2;
+ }
+
+ return mTargetListener->OnStartRequest(aRequest);
+}
+
+nsresult ExternalResourceMap::PendingLoad::SetupViewer(
+ nsIRequest* aRequest, nsIContentViewer** aViewer,
+ nsILoadGroup** aLoadGroup) {
+ MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
+ *aViewer = nullptr;
+ *aLoadGroup = nullptr;
+
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+ if (httpChannel) {
+ bool requestSucceeded;
+ if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
+ !requestSucceeded) {
+ // Bail out on this load, since it looks like we have an HTTP error page
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ nsAutoCString type;
+ chan->GetContentType(type);
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ chan->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // Give this document its own loadgroup
+ nsCOMPtr<nsILoadGroup> newLoadGroup =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
+ newLoadGroup->SetLoadGroup(loadGroup);
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
+ new LoadgroupCallbacks(callbacks);
+ newLoadGroup->SetNotificationCallbacks(newCallbacks);
+
+ // This is some serious hackery cribbed from docshell
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
+ nsCString contractId;
+ nsresult rv =
+ catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ do_GetService(contractId.get());
+ NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = docLoaderFactory->CreateInstance(
+ "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
+ getter_AddRefs(listener), getter_AddRefs(viewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
+ if (!parser) {
+ /// We don't want to deal with the various fake documents yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // We can't handle HTML and other weird things here yet.
+ nsIContentSink* sink = parser->GetContentSink();
+ nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
+ if (!xmlSink) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ listener.swap(mTargetListener);
+ viewer.forget(aViewer);
+ newLoadGroup.forget(aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount) {
+ // mTargetListener might be null if SetupViewer or AddExternalResource failed.
+ NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
+ if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
+ return NS_BINDING_ABORTED;
+ }
+ return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatus) {
+ // mTargetListener might be null if SetupViewer or AddExternalResource failed
+ if (mTargetListener) {
+ nsCOMPtr<nsIStreamListener> listener;
+ mTargetListener.swap(listener);
+ return listener->OnStopRequest(aRequest, aStatus);
+ }
+
+ return NS_OK;
+}
+
+nsresult ExternalResourceMap::PendingLoad::StartLoad(
+ nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aRequestingNode, "Must have a node");
+ MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
+
+ nsCOMPtr<nsILoadGroup> loadGroup =
+ aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aPerformanceStorage
+ loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->SetReferrerInfo(aReferrerInfo);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ mURI = aURI;
+
+ return channel->AsyncOpen(this);
+}
+
+NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
+ nsIInterfaceRequestor)
+
+#define IMPL_SHIM(_i) \
+ NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
+
+IMPL_SHIM(nsILoadContext)
+IMPL_SHIM(nsIProgressEventSink)
+IMPL_SHIM(nsIChannelEventSink)
+
+#undef IMPL_SHIM
+
+#define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
+
+#define TRY_SHIM(_i) \
+ PR_BEGIN_MACRO \
+ if (IID_IS(_i)) { \
+ nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
+ if (!real) { \
+ return NS_NOINTERFACE; \
+ } \
+ nsCOMPtr<_i> shim = new _i##Shim(this, real); \
+ shim.forget(aSink); \
+ return NS_OK; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
+ void** aSink) {
+ if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
+ IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
+ return mCallbacks->GetInterface(aIID, aSink);
+ }
+
+ *aSink = nullptr;
+
+ TRY_SHIM(nsILoadContext);
+ TRY_SHIM(nsIProgressEventSink);
+ TRY_SHIM(nsIChannelEventSink);
+
+ return NS_NOINTERFACE;
+}
+
+#undef TRY_SHIM
+#undef IID_IS
+
+ExternalResourceMap::ExternalResource::~ExternalResource() {
+ if (mViewer) {
+ mViewer->Close(nullptr);
+ mViewer->Destroy();
+ }
+}
+
+// ==================================================================
+// =
+// ==================================================================
+
+// If we ever have an nsIDocumentObserver notification for stylesheet title
+// changes we should update the list from that instead of overriding
+// EnsureFresh.
+class DOMStyleSheetSetList final : public DOMStringList {
+ public:
+ explicit DOMStyleSheetSetList(Document* aDocument);
+
+ void Disconnect() { mDocument = nullptr; }
+
+ virtual void EnsureFresh() override;
+
+ protected:
+ Document* mDocument; // Our document; weak ref. It'll let us know if it
+ // dies.
+};
+
+DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
+ : mDocument(aDocument) {
+ NS_ASSERTION(mDocument, "Must have document!");
+}
+
+void DOMStyleSheetSetList::EnsureFresh() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mNames.Clear();
+
+ if (!mDocument) {
+ return; // Spec says "no exceptions", and we have no style sets if we have
+ // no document, for sure
+ }
+
+ size_t count = mDocument->SheetCount();
+ nsAutoString title;
+ for (size_t index = 0; index < count; index++) {
+ StyleSheet* sheet = mDocument->SheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+ sheet->GetTitle(title);
+ if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
+ return;
+ }
+ }
+}
+
+// ==================================================================
+Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
+ : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
+ aEventTarget) {}
+
+Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
+
+void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aSelector);
+
+ // There is no guarantee that this method won't be re-entered when selector
+ // matching is ongoing because "memory-pressure" could be notified immediately
+ // when OOM happens according to the design of nsExpirationTracker.
+ // The perfect solution is to delete the |aSelector| and its
+ // RawServoSelectorList in mTable asynchronously.
+ // We remove these objects synchronously for now because NotifyExpired() will
+ // never be triggered by "memory-pressure" which is not implemented yet in
+ // the stage 2 of mozalloc_handle_oom().
+ // Once these objects are removed asynchronously, we should update the warning
+ // added in mozalloc_handle_oom() as well.
+ RemoveObject(aSelector);
+ mTable.Remove(aSelector->mKey);
+ delete aSelector;
+}
+
+Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
+
+// ==================================================================
+// =
+// ==================================================================
+
+Document::InternalCommandDataHashtable*
+ Document::sInternalCommandDataHashtable = nullptr;
+
+// static
+void Document::Shutdown() {
+ if (sInternalCommandDataHashtable) {
+ sInternalCommandDataHashtable->Clear();
+ delete sInternalCommandDataHashtable;
+ sInternalCommandDataHashtable = nullptr;
+ }
+}
+
+Document::Document(const char* aContentType)
+ : nsINode(nullptr),
+ DocumentOrShadowRoot(this),
+ mCharacterSet(WINDOWS_1252_ENCODING),
+ mCharacterSetSource(0),
+ mParentDocument(nullptr),
+ mCachedRootElement(nullptr),
+ mNodeInfoManager(nullptr),
+#ifdef DEBUG
+ mStyledLinksCleared(false),
+#endif
+ mCachedStateObjectValid(false),
+ mBlockAllMixedContent(false),
+ mBlockAllMixedContentPreloads(false),
+ mUpgradeInsecureRequests(false),
+ mUpgradeInsecurePreloads(false),
+ mDevToolsWatchingDOMMutations(false),
+ mBidiEnabled(false),
+ mMayNeedFontPrefsUpdate(true),
+ mMathMLEnabled(false),
+ mIsInitialDocumentInWindow(false),
+ mIsEverInitialDocumentInWindow(false),
+ mIgnoreDocGroupMismatches(false),
+ mLoadedAsData(false),
+ mAddedToMemoryReportingAsDataDocument(false),
+ mMayStartLayout(true),
+ mHaveFiredTitleChange(false),
+ mIsShowing(false),
+ mVisible(true),
+ mRemovedFromDocShell(false),
+ // mAllowDNSPrefetch starts true, so that we can always reliably && it
+ // with various values that might disable it. Since we never prefetch
+ // unless we get a window, and in that case the docshell value will get
+ // &&-ed in, this is safe.
+ mAllowDNSPrefetch(true),
+ mIsStaticDocument(false),
+ mCreatingStaticClone(false),
+ mHasPrintCallbacks(false),
+ mInUnlinkOrDeletion(false),
+ mHasHadScriptHandlingObject(false),
+ mIsBeingUsedAsImage(false),
+ mChromeRulesEnabled(false),
+ mInChromeDocShell(false),
+ mIsDevToolsDocument(false),
+ mIsSyntheticDocument(false),
+ mHasLinksToUpdateRunnable(false),
+ mFlushingPendingLinkUpdates(false),
+ mMayHaveDOMMutationObservers(false),
+ mMayHaveAnimationObservers(false),
+ mHasCSP(false),
+ mHasUnsafeEvalCSP(false),
+ mHasUnsafeInlineCSP(false),
+ mHasCSPDeliveredThroughHeader(false),
+ mBFCacheDisallowed(false),
+ mHasHadDefaultView(false),
+ mStyleSheetChangeEventsEnabled(false),
+ mDevToolsAnonymousAndShadowEventsEnabled(false),
+ mIsSrcdocDocument(false),
+ mHasDisplayDocument(false),
+ mFontFaceSetDirty(true),
+ mDidFireDOMContentLoaded(true),
+ mFrameRequestCallbacksScheduled(false),
+ mIsTopLevelContentDocument(false),
+ mIsContentDocument(false),
+ mDidCallBeginLoad(false),
+ mEncodingMenuDisabled(false),
+ mLinksEnabled(true),
+ mIsSVGGlyphsDocument(false),
+ mInDestructor(false),
+ mIsGoingAway(false),
+ mInXBLUpdate(false),
+ mStyleSetFilled(false),
+ mQuirkSheetAdded(false),
+ mContentEditableSheetAdded(false),
+ mDesignModeSheetAdded(false),
+ mSSApplicableStateNotificationPending(false),
+ mMayHaveTitleElement(false),
+ mDOMLoadingSet(false),
+ mDOMInteractiveSet(false),
+ mDOMCompleteSet(false),
+ mAutoFocusFired(false),
+ mScrolledToRefAlready(false),
+ mChangeScrollPosWhenScrollingToRef(false),
+ mDelayFrameLoaderInitialization(false),
+ mSynchronousDOMContentLoaded(false),
+ mMaybeServiceWorkerControlled(false),
+ mAllowZoom(false),
+ mValidScaleFloat(false),
+ mValidMinScale(false),
+ mValidMaxScale(false),
+ mWidthStrEmpty(false),
+ mParserAborted(false),
+ mReportedDocumentUseCounters(false),
+ mHasReportedShadowDOMUsage(false),
+ mHasDelayedRefreshEvent(false),
+ mLoadEventFiring(false),
+ mSkipLoadEventAfterClose(false),
+ mDisableCookieAccess(false),
+ mDisableDocWrite(false),
+ mTooDeepWriteRecursion(false),
+ mPendingMaybeEditingStateChanged(false),
+ mHasBeenEditable(false),
+ mHasWarnedAboutZoom(false),
+ mIsRunningExecCommand(false),
+ mSetCompleteAfterDOMContentLoaded(false),
+ mDidHitCompleteSheetCache(false),
+ mUseCountersInitialized(false),
+ mShouldReportUseCounters(false),
+ mShouldSendPageUseCounters(false),
+ mUserHasInteracted(false),
+ mHasUserInteractionTimerScheduled(false),
+ mShouldResistFingerprinting(false),
+ mXMLDeclarationBits(0),
+ mOnloadBlockCount(0),
+ mWriteLevel(0),
+ mContentEditableCount(0),
+ mEditingState(EditingState::eOff),
+ mCompatMode(eCompatibility_FullStandards),
+ mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
+ mAncestorIsLoading(false),
+ mVisibilityState(dom::VisibilityState::Hidden),
+ mType(eUnknown),
+ mDefaultElementType(0),
+ mAllowXULXBL(eTriUnset),
+ mSkipDTDSecurityChecks(false),
+ mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
+ mSandboxFlags(0),
+ mPartID(0),
+ mMarkedCCGeneration(0),
+ mPresShell(nullptr),
+ mSubtreeModifiedDepth(0),
+ mPreloadPictureDepth(0),
+ mEventsSuppressed(0),
+ mIgnoreDestructiveWritesCounter(0),
+ mStaticCloneCount(0),
+ mWindow(nullptr),
+ mBFCacheEntry(nullptr),
+ mInSyncOperationCount(0),
+ mBlockDOMContentLoaded(0),
+ mUpdateNestLevel(0),
+ mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
+ mViewportType(Unknown),
+ mViewportFit(ViewportFitType::Auto),
+ mSubDocuments(nullptr),
+ mHeaderData(nullptr),
+ mServoRestyleRootDirtyBits(0),
+ mThrowOnDynamicMarkupInsertionCounter(0),
+ mIgnoreOpensDuringUnloadCounter(0),
+ mSavedResolution(1.0f),
+ mSavedResolutionBeforeMVM(1.0f),
+ mGeneration(0),
+ mCachedTabSizeGeneration(0),
+ mNextFormNumber(0),
+ mNextControlNumber(0),
+ mPreloadService(this),
+ mShouldNotifyFetchSuccess(false),
+ mShouldNotifyFormOrPasswordRemoved(false) {
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
+
+ SetIsInDocument();
+ SetIsConnected(true);
+
+ // Create these unconditionally, they will be used to warn about the `zoom`
+ // property, even if use counters are disabled.
+ mStyleUseCounters.reset(Servo_UseCounters_Create());
+
+ SetContentType(nsDependentCString(aContentType));
+
+ // Start out mLastStyleSheetSet as null, per spec
+ SetDOMStringToNull(mLastStyleSheetSet);
+
+ // void state used to differentiate an empty source from an unselected source
+ mPreloadPictureFoundSource.SetIsVoid(true);
+
+ RecomputeLanguageFromCharset();
+
+ mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
+ mReferrerInfo = new dom::ReferrerInfo(nullptr);
+}
+
+#ifndef ANDROID
+// unused by GeckoView
+static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
+ if (NS_WARN_IF(!aWin)) {
+ return false;
+ }
+
+ nsIURI* uri = aWin->GetDocumentURI();
+ if (NS_WARN_IF(!uri)) {
+ return false;
+ }
+ // getSpec is an expensive operation, hence we first check the scheme
+ // to see if the caller is actually an about: page.
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString aboutSpec;
+ nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return aboutSpec.EqualsASCII(aSpec);
+}
+#endif
+
+bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
+#ifdef ANDROID
+ // GeckoView uses data URLs for error pages, so for now just check for any
+ // error page
+ return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
+#else
+ return win && IsAboutErrorPage(win, "neterror");
+#endif
+}
+
+bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
+ JSObject* aObject) {
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
+#ifdef ANDROID
+ // GeckoView uses data URLs for error pages, so for now just check for any
+ // error page
+ return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
+#else
+ return win && IsAboutErrorPage(win, "httpsonlyerror");
+#endif
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
+ bool aIsTemporary, ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
+ Promise::ePropagateUserInteraction);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsresult rv = NS_OK;
+ if (NS_WARN_IF(!mFailedChannel)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIURI> failedChannelURI;
+ NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
+ if (!failedChannelURI) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
+ if (!innerURI) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString host;
+ innerURI->GetAsciiHost(host);
+ int32_t port;
+ innerURI->GetPort(&port);
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+ if (NS_WARN_IF(!tsi)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+ if (NS_WARN_IF(!cert)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
+ cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [promise](const mozilla::MozPromise<
+ nsresult, mozilla::ipc::ResponseRejectReason,
+ true>::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ promise->MaybeResolve(aValue.ResolveValue());
+ } else {
+ promise->MaybeRejectWithUndefined();
+ }
+ });
+ return promise.forget();
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsICertOverrideService> overrideService =
+ do_GetService(NS_CERTOVERRIDE_CONTRACTID);
+ if (!overrideService) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
+ rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
+ aIsTemporary);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return promise.forget();
+}
+
+void Document::ReloadWithHttpsOnlyException() {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SendReloadWithHttpsOnlyException();
+ }
+}
+
+void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
+ nsresult rv = NS_OK;
+ if (NS_WARN_IF(!mFailedChannel)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (NS_WARN_IF(!tsi)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsAutoString errorCodeString;
+ rv = tsi->GetErrorCodeString(errorCodeString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ aInfo.mErrorCodeString.Assign(errorCodeString);
+}
+
+bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
+ JSObject* aObject) {
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
+#ifdef ANDROID
+ // GeckoView uses data URLs for error pages, so for now just check for any
+ // error page
+ return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
+#else
+ return win && IsAboutErrorPage(win, "certerror");
+#endif
+}
+
+bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
+
+ if (!principal) {
+ return false;
+ }
+
+ // We allow the privilege SSA to be called from system principal.
+ if (principal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ // We only allow calling the privilege SSA from the content script of the
+ // webcompat extension.
+ if (auto* policy = principal->ContentScriptAddonPolicy()) {
+ nsAutoString addonID;
+ policy->GetId(addonID);
+
+ return addonID.EqualsLiteral("webcompat@mozilla.org");
+ }
+
+ return false;
+}
+
+bool Document::IsErrorPage() const {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
+ return loadInfo && loadInfo->GetLoadErrorPage();
+}
+
+void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
+ ErrorResult& aRv) {
+ nsresult rv = NS_OK;
+ if (NS_WARN_IF(!mFailedChannel)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (NS_WARN_IF(!tsi)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsAutoString errorCodeString;
+ rv = tsi->GetErrorCodeString(errorCodeString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ aInfo.mErrorCodeString.Assign(errorCodeString);
+
+ nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
+ rv = tsi->GetOverridableErrorCategory(&errorCategory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ switch (errorCategory) {
+ case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
+ aInfo.mOverridableErrorCategory =
+ dom::OverridableErrorCategory::Trust_error;
+ break;
+ case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
+ aInfo.mOverridableErrorCategory =
+ dom::OverridableErrorCategory::Domain_mismatch;
+ break;
+ case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
+ aInfo.mOverridableErrorCategory =
+ dom::OverridableErrorCategory::Expired_or_not_yet_valid;
+ break;
+ default:
+ aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
+ break;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ nsCOMPtr<nsIX509CertValidity> validity;
+ rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (NS_WARN_IF(!cert)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ rv = cert->GetValidity(getter_AddRefs(validity));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (NS_WARN_IF(!validity)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ PRTime validityResult;
+ rv = validity->GetNotBefore(&validityResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
+
+ rv = validity->GetNotAfter(&validityResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
+
+ nsAutoString issuerCommonName;
+ nsAutoString certChainPEMString;
+ Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
+ int64_t maxValidity = std::numeric_limits<int64_t>::max();
+ int64_t minValidity = 0;
+ PRTime notBefore, notAfter;
+ nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
+ rv = tsi->GetFailedCertChain(failedCertArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (NS_WARN_IF(failedCertArray.IsEmpty())) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ for (const auto& certificate : failedCertArray) {
+ rv = certificate->GetIssuerCommonName(issuerCommonName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = certificate->GetValidity(getter_AddRefs(validity));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (NS_WARN_IF(!validity)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ rv = validity->GetNotBefore(&notBefore);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = validity->GetNotAfter(&notAfter);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ notBefore = std::max(minValidity, notBefore);
+ notAfter = std::min(maxValidity, notAfter);
+ nsTArray<uint8_t> certArray;
+ rv = certificate->GetRawDER(certArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsAutoString der64;
+ rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
+ certArray.Length(), der64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ if (!certChainStrings.AppendElement(der64, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ aInfo.mIssuerCommonName.Assign(issuerCommonName);
+ aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
+ aInfo.mCertValidityRangeNotBefore =
+ DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
+
+ int32_t errorCode;
+ rv = tsi->GetErrorCode(&errorCode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsCOMPtr<nsINSSErrorsService> nsserr =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ if (NS_WARN_IF(!nsserr)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ nsresult res;
+ rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
+ nsCOMPtr<nsIURI> aURI;
+ mFailedChannel->GetURI(getter_AddRefs(aURI));
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
+ } else {
+ nsCOMPtr<nsISiteSecurityService> sss =
+ do_GetService(NS_SSSERVICE_CONTRACTID);
+ if (NS_WARN_IF(!sss)) {
+ return;
+ }
+ Unused << NS_WARN_IF(
+ NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
+ }
+ nsCOMPtr<nsIPublicKeyPinningService> pkps =
+ do_GetService(NS_PKPSERVICE_CONTRACTID);
+ if (NS_WARN_IF(!pkps)) {
+ return;
+ }
+ Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
+}
+
+bool Document::IsAboutPage() const {
+ return NodePrincipal()->SchemeIs("about");
+}
+
+void Document::ConstructUbiNode(void* storage) {
+ JS::ubi::Concrete<Document>::construct(storage, this);
+}
+
+void Document::LoadEventFired() {
+ // Object used to collect some telemetry data so we don't need to query for it
+ // twice.
+ glean::perf::PageLoadExtra pageLoadEventData;
+
+ // Accumulate timing data located in each document's realm and report to
+ // telemetry.
+ AccumulateJSTelemetry(pageLoadEventData);
+
+ // Collect page load timings
+ AccumulatePageLoadTelemetry(pageLoadEventData);
+
+ // Record page load event
+ RecordPageLoadEventTelemetry(pageLoadEventData);
+
+ // Release the JS bytecode cache from its wait on the load event, and
+ // potentially dispatch the encoding of the bytecode.
+ if (ScriptLoader()) {
+ ScriptLoader()->LoadEventFired();
+ }
+}
+
+static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
+ return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
+}
+
+void Document::RecordPageLoadEventTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryData) {
+ // If the page load time is empty, then the content wasn't something we want
+ // to report (i.e. not a top level document).
+ if (!aEventTelemetryData.loadTime) {
+ return;
+ }
+ MOZ_ASSERT(IsTopLevelContentDocument());
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return;
+ }
+
+ nsAutoCString loadTypeStr;
+ switch (docshell->GetLoadType()) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ loadTypeStr.Append("NORMAL");
+ break;
+ case LOAD_HISTORY:
+ loadTypeStr.Append("HISTORY");
+ break;
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_REFRESH:
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ loadTypeStr.Append("RELOAD");
+ break;
+ case LOAD_LINK:
+ loadTypeStr.Append("LINK");
+ break;
+ case LOAD_STOP_CONTENT:
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ loadTypeStr.Append("STOP");
+ break;
+ case LOAD_ERROR_PAGE:
+ loadTypeStr.Append("ERROR");
+ break;
+ default:
+ loadTypeStr.Append("OTHER");
+ break;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService && mReferrerInfo &&
+ (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
+ nsAutoCString currentBaseDomain, referrerBaseDomain;
+ nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
+ if (referrerURI) {
+ auto result = NS_SUCCEEDED(
+ tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
+ if (result) {
+ bool sameOrigin = false;
+ NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
+ aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
+ }
+ }
+ }
+
+ aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
+
+ // Sending a glean ping must be done on the parent process.
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendRecordPageLoadEvent(aEventTelemetryData);
+ }
+}
+
+void Document::AccumulatePageLoadTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
+ // Interested only in top level documents for real websites that are in the
+ // foreground.
+ if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
+ !GetNavigationTiming() ||
+ !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
+ return;
+ }
+
+ if (!GetChannel()) {
+ return;
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
+ if (!timedChannel) {
+ return;
+ }
+
+ // Default duration is 0, use this to check for bogus negative values.
+ const TimeDuration zeroDuration;
+
+ TimeStamp responseStart;
+ timedChannel->GetResponseStart(&responseStart);
+
+ TimeStamp redirectStart, redirectEnd;
+ timedChannel->GetRedirectStart(&redirectStart);
+ timedChannel->GetRedirectEnd(&redirectEnd);
+
+ uint8_t redirectCount;
+ timedChannel->GetRedirectCount(&redirectCount);
+ if (redirectCount) {
+ aEventTelemetryDataOut.redirectCount =
+ mozilla::Some(static_cast<uint32_t>(redirectCount));
+ }
+
+ if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
+ TimeDuration redirectTime = redirectEnd - redirectStart;
+ if (redirectTime > zeroDuration) {
+ aEventTelemetryDataOut.redirectTime =
+ mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
+ }
+ }
+
+ TimeStamp dnsLookupStart, dnsLookupEnd;
+ timedChannel->GetDomainLookupStart(&dnsLookupStart);
+ timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
+
+ if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
+ TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
+ if (dnsLookupTime > zeroDuration) {
+ aEventTelemetryDataOut.dnsLookupTime =
+ mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
+ }
+ }
+
+ TimeStamp navigationStart =
+ GetNavigationTiming()->GetNavigationStartTimeStamp();
+
+ if (!responseStart || !navigationStart) {
+ return;
+ }
+
+ nsAutoCString dnsKey("Native");
+ nsAutoCString http3Key;
+ nsAutoCString http3WithPriorityKey;
+ nsAutoCString earlyHintKey;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel =
+ do_QueryInterface(GetChannel());
+ if (httpChannel) {
+ bool resolvedByTRR = false;
+ Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
+ if (resolvedByTRR) {
+ if (nsCOMPtr<nsIDNSService> dns =
+ do_GetService(NS_DNSSERVICE_CONTRACTID)) {
+ dns->GetTRRDomainKey(dnsKey);
+ } else {
+ // Failed to get the DNS service.
+ dnsKey = "(fail)"_ns;
+ }
+ aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
+ }
+
+ uint32_t major;
+ uint32_t minor;
+ if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
+ if (major == 3) {
+ http3Key = "http3"_ns;
+ nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
+ nsCString header;
+ if (httpChannel2 &&
+ NS_SUCCEEDED(
+ httpChannel2->GetResponseHeader("priority"_ns, header)) &&
+ !header.IsEmpty()) {
+ http3WithPriorityKey = "with_priority"_ns;
+ } else {
+ http3WithPriorityKey = "without_priority"_ns;
+ }
+ } else if (major == 2) {
+ bool supportHttp3 = false;
+ if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
+ supportHttp3 = false;
+ }
+ if (supportHttp3) {
+ http3Key = "supports_http3"_ns;
+ }
+ }
+
+ aEventTelemetryDataOut.httpVer = mozilla::Some(major);
+ }
+
+ uint32_t earlyHintType = 0;
+ Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
+ if (earlyHintType & LinkStyle::ePRECONNECT) {
+ earlyHintKey.Append("preconnect_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRELOAD) {
+ earlyHintKey.Append("preload_"_ns);
+ earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
+ }
+ }
+
+ TimeStamp asyncOpen;
+ timedChannel->GetAsyncOpen(&asyncOpen);
+ if (asyncOpen) {
+ Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
+ asyncOpen, responseStart);
+ }
+
+ // First Contentful Composite
+ if (TimeStamp firstContentfulComposite =
+ GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
+ navigationStart, firstContentfulComposite);
+
+ if (!http3Key.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
+ navigationStart, firstContentfulComposite);
+ }
+
+ if (!http3WithPriorityKey.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
+ navigationStart, firstContentfulComposite);
+ }
+
+ if (!earlyHintKey.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
+ navigationStart, firstContentfulComposite);
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
+ firstContentfulComposite);
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
+ responseStart, firstContentfulComposite);
+
+ TimeDuration fcpTime = firstContentfulComposite - navigationStart;
+ if (fcpTime > zeroDuration) {
+ aEventTelemetryDataOut.fcpTime =
+ mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
+ }
+ }
+
+ // DOM Content Loaded event
+ if (TimeStamp dclEventStart =
+ GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
+ navigationStart, dclEventStart);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
+ responseStart, dclEventStart);
+ }
+
+ // Load event
+ if (TimeStamp loadEventStart =
+ GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
+ navigationStart, loadEventStart);
+ if (!http3Key.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
+ http3Key, navigationStart, loadEventStart);
+ }
+
+ if (!http3WithPriorityKey.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
+ http3WithPriorityKey, navigationStart,
+ loadEventStart);
+ }
+
+ if (!earlyHintKey.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
+ earlyHintKey, navigationStart,
+ loadEventStart);
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
+ loadEventStart);
+
+ TimeDuration responseTime = responseStart - navigationStart;
+ if (responseTime > zeroDuration) {
+ aEventTelemetryDataOut.responseTime =
+ mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
+ }
+
+ TimeDuration loadTime = loadEventStart - navigationStart;
+ if (loadTime > zeroDuration) {
+ aEventTelemetryDataOut.loadTime =
+ mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
+ }
+ }
+}
+
+void Document::AccumulateJSTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
+ if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
+ return;
+ }
+
+ if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
+ return;
+ }
+
+ AutoJSContext cx;
+ JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
+ JSAutoRealm ar(cx, globalObject);
+ JS::JSTimers timers = JS::GetJSTimers(cx);
+
+ if (!timers.executionTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_EXECUTION_MS,
+ ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
+ aEventTelemetryDataOut.jsExecTime = mozilla::Some(
+ static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
+ }
+
+ if (!timers.delazificationTime.IsZero()) {
+ Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
+ ConvertToUnsignedFromDouble(
+ timers.delazificationTime.ToMilliseconds()));
+ }
+
+ if (!timers.xdrEncodingTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
+ ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
+ }
+
+ if (!timers.baselineCompileTime.IsZero()) {
+ Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
+ ConvertToUnsignedFromDouble(
+ timers.baselineCompileTime.ToMilliseconds()));
+ }
+
+ if (!timers.gcTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_GC_MS,
+ ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
+ }
+
+ if (!timers.protectTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_PROTECT_MS,
+ ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
+ }
+}
+
+Document::~Document() {
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
+ MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
+ "Can't be top-level and a resource doc at the same time");
+
+ NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
+
+ if (IsTopLevelContentDocument()) {
+ RemoveToplevelLoadingDocument(this);
+
+ // don't report for about: pages
+ if (!IsAboutPage()) {
+ // record CSP telemetry on this document
+ if (mHasCSP) {
+ Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
+ }
+ if (mHasUnsafeInlineCSP) {
+ Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
+ }
+ if (mHasUnsafeEvalCSP) {
+ Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
+ }
+
+ if (MOZ_UNLIKELY(mMathMLEnabled)) {
+ ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
+ }
+
+ if (IsHTMLDocument()) {
+ switch (GetCompatibilityMode()) {
+ case eCompatibility_FullStandards:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::FullStandards);
+ break;
+ case eCompatibility_AlmostStandards:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
+ break;
+ case eCompatibility_NavQuirks:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
+ break;
+ }
+ }
+ }
+ }
+
+ mInDestructor = true;
+ mInUnlinkOrDeletion = true;
+
+ mozilla::DropJSObjects(this);
+
+ // Clear mObservers to keep it in sync with the mutationobserver list
+ mObservers.Clear();
+
+ mIntersectionObservers.Clear();
+
+ if (mStyleSheetSetList) {
+ mStyleSheetSetList->Disconnect();
+ }
+
+ if (mAnimationController) {
+ mAnimationController->Disconnect();
+ }
+
+ MOZ_ASSERT(mTimelines.isEmpty());
+
+ mParentDocument = nullptr;
+
+ // Kill the subdocument map, doing this will release its strong
+ // references, if any.
+ delete mSubDocuments;
+ mSubDocuments = nullptr;
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Destroy link map now so we don't waste time removing
+ // links one by one
+ DestroyElementMaps();
+
+ // Invalidate cached array of child nodes
+ InvalidateChildNodes();
+
+ // We should not have child nodes when destructor is called,
+ // since child nodes keep their owner document alive.
+ MOZ_ASSERT(!HasChildren());
+
+ mCachedRootElement = nullptr;
+
+ for (auto& sheets : mAdditionalSheets) {
+ UnlinkStyleSheets(sheets);
+ }
+
+ if (mAttrStyleSheet) {
+ mAttrStyleSheet->SetOwningDocument(nullptr);
+ }
+
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ if (mScriptLoader) {
+ mScriptLoader->DropDocumentReference();
+ }
+
+ if (mCSSLoader) {
+ // Could be null here if Init() failed or if we have been unlinked.
+ mCSSLoader->DropDocumentReference();
+ }
+
+ if (mStyleImageLoader) {
+ mStyleImageLoader->DropDocumentReference();
+ }
+
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->DropDocumentReference();
+ }
+
+ if (mXULPersist) {
+ mXULPersist->DropDocumentReference();
+ }
+
+ if (mPermissionDelegateHandler) {
+ mPermissionDelegateHandler->DropDocumentReference();
+ }
+
+ mHeaderData = nullptr;
+
+ mPendingTitleChangeEvent.Revoke();
+
+ MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
+ "must not have media query lists left");
+
+ if (mNodeInfoManager) {
+ mNodeInfoManager->DropDocumentReference();
+ }
+
+ if (mDocGroup) {
+ MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
+ mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
+ }
+
+ UnlinkOriginalDocumentIfStatic();
+
+ UnregisterFromMemoryReportingForDataDocument();
+}
+
+NS_INTERFACE_TABLE_HEAD(Document)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(Document, Document)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
+ NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
+ NS_INTERFACE_TABLE_END
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Document)
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
+ Document, LastRelease())
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
+ if (Element::CanSkip(tmp, aRemovingAllowed)) {
+ EventListenerManager* elm = tmp->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
+ return Element::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
+ return Element::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ nsAutoCString loadedAsData;
+ if (tmp->IsLoadedAsData()) {
+ loadedAsData.AssignLiteral("data");
+ } else {
+ loadedAsData.AssignLiteral("normal");
+ }
+ uint32_t nsid = tmp->GetDefaultNamespaceID();
+ nsAutoCString uri;
+ if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
+ static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
+ "(xhtml)", "(XLink)", "(XSLT)",
+ "(MathML)", "(RDF)", "(XUL)"};
+ if (nsid < ArrayLength(kNSURIs)) {
+ SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
+ kNSURIs[nsid], uri.get());
+ } else {
+ SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
+ }
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
+ }
+
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ tmp->mExternalResourceMap.Traverse(&cb);
+
+ // Traverse all Document pointer members.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
+
+ // Traverse all Document nsCOMPtrs.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
+
+ DocumentOrShadowRoot::Traverse(tmp, cb);
+
+ for (auto& sheets : tmp->mAdditionalSheets) {
+ tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
+
+ // Traverse all our nsCOMArrays.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
+
+ // Traverse animation components
+ if (tmp->mAnimationController) {
+ tmp->mAnimationController->Traverse(&cb);
+ }
+
+ if (tmp->mSubDocuments) {
+ for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
+ cb.NoteXPCOMChild(entry->mKey);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mSubDocuments entry->mSubDocument");
+ cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
+
+ // We own only the items in mDOMMediaQueryLists that have listeners;
+ // this reference is managed by their AddListener and RemoveListener
+ // methods.
+ for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
+ mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
+ if (mql->HasListeners() &&
+ NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
+ cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
+ }
+ }
+
+ // XXX: This should be not needed once bug 1569185 lands.
+ for (const auto& entry : tmp->mL10nProtoElements) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
+ cb.NoteXPCOMChild(entry.GetKey());
+ CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
+ }
+
+ for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
+ mPendingFrameStaticClones[i].mStaticCloneOf);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
+ tmp->mInUnlinkOrDeletion = true;
+
+ tmp->SetStateObject(nullptr);
+
+ // Clear out our external resources
+ tmp->mExternalResourceMap.Shutdown();
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsINode::Unlink(tmp);
+
+ while (tmp->HasChildren()) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it.
+ // If this code changes, change the corresponding code in Document's
+ // unlink impl and ContentUnbinder::UnbindSubtree.
+ nsCOMPtr<nsIContent> child = tmp->GetLastChild();
+ tmp->DisconnectChild(child);
+ child->UnbindFromTree();
+ }
+
+ tmp->UnlinkOriginalDocumentIfStatic();
+
+ tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
+
+ tmp->SetScriptGlobalObject(nullptr);
+
+ for (auto& sheets : tmp->mAdditionalSheets) {
+ tmp->UnlinkStyleSheets(sheets);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
+
+ if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
+ tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
+ tmp->mDocGroup);
+ }
+ tmp->mDocGroup = nullptr;
+
+ if (tmp->IsTopLevelContentDocument()) {
+ RemoveToplevelLoadingDocument(tmp);
+ }
+
+ tmp->mParentDocument = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
+
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->Disconnect();
+ tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ tmp->mListenerManager = nullptr;
+ }
+
+ if (tmp->mStyleSheetSetList) {
+ tmp->mStyleSheetSetList->Disconnect();
+ tmp->mStyleSheetSetList = nullptr;
+ }
+
+ delete tmp->mSubDocuments;
+ tmp->mSubDocuments = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
+ MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
+ "How did we get here without our presshell going away "
+ "first?");
+
+ DocumentOrShadowRoot::Unlink(tmp);
+
+ // Document has a pretty complex destructor, so we're going to
+ // assume that *most* cycles you actually want to break somewhere
+ // else, and not unlink an awful lot here.
+
+ tmp->mExpandoAndGeneration.OwnerUnlinked();
+
+ if (tmp->mAnimationController) {
+ tmp->mAnimationController->Unlink();
+ }
+
+ tmp->mPendingTitleChangeEvent.Revoke();
+
+ if (tmp->mCSSLoader) {
+ tmp->mCSSLoader->DropDocumentReference();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
+ }
+
+ // We own only the items in mDOMMediaQueryLists that have listeners;
+ // this reference is managed by their AddListener and RemoveListener
+ // methods.
+ for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
+ MediaQueryList* next =
+ static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
+ mql->Disconnect();
+ mql = next;
+ }
+
+ tmp->mPendingFrameStaticClones.Clear();
+
+ tmp->mInUnlinkOrDeletion = false;
+
+ tmp->UnregisterFromMemoryReportingForDataDocument();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsresult Document::Init() {
+ if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // Force initialization.
+ mOnloadBlocker = new OnloadBlocker();
+ mStyleImageLoader = new css::ImageLoader(this);
+
+ mNodeInfoManager = new nsNodeInfoManager(this);
+
+ // mNodeInfo keeps NodeInfoManager alive!
+ mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
+ NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
+ MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+
+ NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
+
+ mCSSLoader = new css::Loader(this);
+ // Assume we're not quirky, until we know otherwise
+ mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
+
+ // If after creation the owner js global is not set for a document
+ // we use the default compartment for this document, instead of creating
+ // wrapper in some random compartment when the document is exposed to js
+ // via some events.
+ nsCOMPtr<nsIGlobalObject> global =
+ xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+ NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+ mScopeObject = do_GetWeakReference(global);
+ MOZ_ASSERT(mScopeObject);
+
+ mScriptLoader = new dom::ScriptLoader(this);
+
+ // we need to create a policy here so getting the policy within
+ // ::Policy() can *always* return a non null policy
+ mFeaturePolicy = new dom::FeaturePolicy(this);
+ mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
+
+ mStyleSet = MakeUnique<ServoStyleSet>(*this);
+
+ RecomputeResistFingerprinting();
+
+ return NS_OK;
+}
+
+void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
+
+void Document::RemoveAllPropertiesFor(nsINode* aNode) {
+ PropertyTable().RemoveAllPropertiesFor(aNode);
+}
+
+void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal;
+ if (aChannel) {
+ // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
+ // nsScriptSecurityManager::GetChannelResultPrincipals.
+ // Note: this should match the uri used for the OnNewURI call in
+ // nsDocShell::CreateContentViewer.
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ securityManager->GetChannelResultPrincipals(
+ aChannel, getter_AddRefs(principal),
+ getter_AddRefs(partitionedPrincipal));
+ }
+ }
+
+ bool equal = principal->Equals(partitionedPrincipal);
+
+ principal = MaybeDowngradePrincipal(principal);
+ if (equal) {
+ partitionedPrincipal = principal;
+ } else {
+ partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
+ }
+
+ ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
+
+ // Note that, since mTiming does not change during a reset, the
+ // navigationStart time remains unchanged and therefore any future new
+ // timeline will have the same global clock time as the old one.
+ mDocumentTimeline = nullptr;
+
+ if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
+ if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
+ mDocumentBaseURI = baseURI.forget();
+ mChromeXHRDocBaseURI = nullptr;
+ }
+ }
+
+ mChannel = aChannel;
+ RecomputeResistFingerprinting();
+}
+
+void Document::DisconnectNodeTree() {
+ // Delete references to sub-documents and kill the subdocument map,
+ // if any. This is not strictly needed, but makes the node tree
+ // teardown a bit faster.
+ delete mSubDocuments;
+ mSubDocuments = nullptr;
+
+ bool oldVal = mInUnlinkOrDeletion;
+ mInUnlinkOrDeletion = true;
+ { // Scope for update
+ MOZ_AUTO_DOC_UPDATE(this, true);
+
+ // Destroy link map now so we don't waste time removing
+ // links one by one
+ DestroyElementMaps();
+
+ // Invalidate cached array of child nodes
+ InvalidateChildNodes();
+
+ while (HasChildren()) {
+ nsMutationGuard::DidMutate();
+ nsCOMPtr<nsIContent> content = GetLastChild();
+ nsIContent* previousSibling = content->GetPreviousSibling();
+ DisconnectChild(content);
+ if (content == mCachedRootElement) {
+ // Immediately clear mCachedRootElement, now that it's been removed
+ // from mChildren, so that GetRootElement() will stop returning this
+ // now-stale value.
+ mCachedRootElement = nullptr;
+ }
+ MutationObservers::NotifyContentRemoved(this, content, previousSibling);
+ content->UnbindFromTree();
+ }
+ MOZ_ASSERT(!mCachedRootElement,
+ "After removing all children, there should be no root elem");
+ }
+ mInUnlinkOrDeletion = oldVal;
+}
+
+void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal) {
+ MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
+ MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
+
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
+
+ mSecurityInfo = nullptr;
+
+ nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+ if (!aLoadGroup || group != aLoadGroup) {
+ mDocumentLoadGroup = nullptr;
+ }
+
+ DisconnectNodeTree();
+
+ // Reset our stylesheets
+ ResetStylesheetsToURI(aURI);
+
+ // Release the listener manager
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ mListenerManager = nullptr;
+ }
+
+ // Release the stylesheets list.
+ mDOMStyleSheets = nullptr;
+
+ // Release our principal after tearing down the document, rather than before.
+ // This ensures that, during teardown, the document and the dying window
+ // (which already nulled out its document pointer and cached the principal)
+ // have matching principals.
+ SetPrincipals(nullptr, nullptr);
+
+ // Clear the original URI so SetDocumentURI sets it.
+ mOriginalURI = nullptr;
+
+ SetDocumentURI(aURI);
+ mChromeXHRDocURI = nullptr;
+ // If mDocumentBaseURI is null, Document::GetBaseURI() returns
+ // mDocumentURI.
+ mDocumentBaseURI = nullptr;
+ mChromeXHRDocBaseURI = nullptr;
+
+ // Check if the current document is the top-level DevTools document.
+ // For inner DevTools frames, mIsDevToolsDocument will be set when
+ // calling SetDocumentParent.
+ if (aURI && aURI->SchemeIs("about") &&
+ aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
+ mIsDevToolsDocument = true;
+ }
+
+ if (aLoadGroup) {
+ mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
+ // there was an assertion here that aLoadGroup was not null. This
+ // is no longer valid: nsDocShell::SetDocument does not create a
+ // load group, and it works just fine
+
+ // XXXbz what does "just fine" mean exactly? And given that there
+ // is no nsDocShell::SetDocument, what is this talking about?
+
+ if (IsContentDocument()) {
+ // Inform the associated request context about this load start so
+ // any of its internal load progress flags gets reset.
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ nsCOMPtr<nsIRequestContext> rc;
+ rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
+ if (rc) {
+ rc->BeginLoad();
+ }
+ }
+ }
+ }
+
+ mLastModified.Truncate();
+ // XXXbz I guess we're assuming that the caller will either pass in
+ // a channel with a useful type or call SetContentType?
+ SetContentType(""_ns);
+ mContentLanguage.Truncate();
+ mBaseTarget.Truncate();
+
+ mXMLDeclarationBits = 0;
+
+ // Now get our new principal
+ if (aPrincipal) {
+ SetPrincipals(aPrincipal, aPartitionedPrincipal);
+ } else {
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
+
+ if (!loadContext && aLoadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ loadContext = do_GetInterface(cbs);
+ }
+
+ MOZ_ASSERT(loadContext,
+ "must have a load context or pass in an explicit principal");
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = securityManager->GetLoadContextContentPrincipal(
+ mDocumentURI, loadContext, getter_AddRefs(principal));
+ if (NS_SUCCEEDED(rv)) {
+ SetPrincipals(principal, principal);
+ }
+ }
+ }
+
+ if (mFontFaceSet) {
+ mFontFaceSet->RefreshStandardFontLoadPrincipal();
+ }
+
+ // Refresh the principal on the realm.
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
+ }
+}
+
+already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
+ nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return nullptr;
+ }
+
+ // We can't load a document with an expanded principal. If we're given one,
+ // automatically downgrade it to the last principal it subsumes (which is the
+ // extension principal, in the case of extension content scripts).
+ auto* basePrin = BasePrincipal::Cast(aPrincipal);
+ if (basePrin->Is<ExpandedPrincipal>()) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Should never try to create a document with "
+ "an expanded principal");
+
+ auto* expanded = basePrin->As<ExpandedPrincipal>();
+ return do_AddRef(expanded->AllowList().LastElement());
+ }
+
+ if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
+ // We basically want the parent document here, but because this is very
+ // early in the load, GetInProcessParentDocument() returns null, so we use
+ // the docshell hierarchy to get this information instead.
+ if (RefPtr<BrowsingContext> parent =
+ mDocumentContainer->GetBrowsingContext()->GetParent()) {
+ auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
+ if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ return nullPrincipal.forget();
+ }
+ }
+ }
+ nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+ return principal.forget();
+}
+
+size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+
+ // lowest index first
+ int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
+
+ size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
+ size_t index = 0;
+ for (; index < count; index++) {
+ auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
+ MOZ_ASSERT(sheet);
+ int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
+ if (sheetDocIndex > newDocIndex) {
+ break;
+ }
+
+ // If the sheet is not owned by the document it can be an author
+ // sheet registered at nsStyleSheetService or an additional author
+ // sheet on the document, which means the new
+ // doc sheet should end up before it.
+ if (sheetDocIndex < 0) {
+ if (sheetService) {
+ auto& authorSheets = *sheetService->AuthorStyleSheets();
+ if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
+ break;
+ }
+ }
+ if (sheet == GetFirstAdditionalAuthorSheet()) {
+ break;
+ }
+ }
+ }
+
+ return index;
+}
+
+void Document::ResetStylesheetsToURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ ClearAdoptedStyleSheets();
+
+ auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
+ for (auto& sheet : Reversed(aSheetList)) {
+ sheet->ClearAssociatedDocumentOrShadowRoot();
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ }
+ }
+ aSheetList.Clear();
+ };
+ ClearSheetList(mStyleSheets);
+ for (auto& sheets : mAdditionalSheets) {
+ ClearSheetList(sheets);
+ }
+ if (mStyleSetFilled) {
+ if (auto* ss = nsStyleSheetService::GetInstance()) {
+ for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
+ MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
+ if (sheet->IsApplicable()) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ }
+ }
+ }
+ }
+
+ // Now reset our inline style and attribute sheets.
+ if (mAttrStyleSheet) {
+ mAttrStyleSheet->Reset();
+ mAttrStyleSheet->SetOwningDocument(this);
+ } else {
+ mAttrStyleSheet = new nsHTMLStyleSheet(this);
+ }
+
+ if (!mStyleAttrStyleSheet) {
+ mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
+ }
+
+ if (mStyleSetFilled) {
+ FillStyleSetDocumentSheets();
+
+ if (mStyleSet->StyleSheetsHaveChanged()) {
+ ApplicableStylesChanged();
+ }
+ }
+}
+
+static void AppendSheetsToStyleSet(
+ ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
+ for (StyleSheet* sheet : Reversed(aSheets)) {
+ aStyleSet->AppendStyleSheet(*sheet);
+ }
+}
+
+void Document::FillStyleSetUserAndUASheets() {
+ // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
+ // ordering.
+
+ // The document will fill in the document sheets when we create the presshell
+ auto* cache = GlobalStyleSheetCache::Singleton();
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ MOZ_ASSERT(sheetService,
+ "should never be creating a StyleSet after the style sheet "
+ "service has gone");
+
+ for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
+ : cache->GetUserContentSheet();
+ if (sheet) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->UASheet());
+
+ if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
+ mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
+ }
+
+ if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
+ mStyleSet->AppendStyleSheet(*cache->SVGSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
+
+ if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
+ mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
+
+ // Only load the full XUL sheet if we'll need it.
+ if (LoadsFullXULStyleSheetUpFront()) {
+ mStyleSet->AppendStyleSheet(*cache->XULSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->FormsSheet());
+ mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
+
+ for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ MOZ_ASSERT(!mQuirkSheetAdded);
+ if (NeedsQuirksSheet()) {
+ mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
+ mQuirkSheetAdded = true;
+ }
+}
+
+void Document::FillStyleSet() {
+ MOZ_ASSERT(!mStyleSetFilled);
+ FillStyleSetUserAndUASheets();
+ FillStyleSetDocumentSheets();
+ mStyleSetFilled = true;
+}
+
+void Document::RemoveContentEditableStyleSheets() {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ bool changed = false;
+ if (mDesignModeSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
+ mDesignModeSheetAdded = false;
+ changed = true;
+ }
+ if (mContentEditableSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
+ mContentEditableSheetAdded = false;
+ changed = true;
+ }
+ if (changed) {
+ MOZ_ASSERT(mStyleSetFilled);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+ MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
+ "Caller should ensure we're being rendered");
+
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ bool changed = false;
+ if (!mContentEditableSheetAdded) {
+ mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
+ mContentEditableSheetAdded = true;
+ changed = true;
+ }
+ if (mDesignModeSheetAdded != aDesignMode) {
+ if (mDesignModeSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
+ } else {
+ mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
+ }
+ mDesignModeSheetAdded = !mDesignModeSheetAdded;
+ changed = true;
+ }
+ if (changed) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::FillStyleSetDocumentSheets() {
+ MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
+ "Style set already has document sheets?");
+
+ // Sheets are added in reverse order to avoid worst-case time complexity when
+ // looking up the index of a sheet.
+ //
+ // Note that usually appending is faster (rebuilds less stuff in the
+ // styleset), but in this case it doesn't matter since we're filling the
+ // styleset from scratch anyway.
+ for (StyleSheet* sheet : Reversed(mStyleSheets)) {
+ if (sheet->IsApplicable()) {
+ mStyleSet->AddDocStyleSheet(*sheet);
+ }
+ }
+
+ EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
+ if (aSheet.IsApplicable()) {
+ mStyleSet->AddDocStyleSheet(aSheet);
+ }
+ });
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
+}
+
+void Document::CompatibilityModeChanged() {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+ CSSLoader()->SetCompatibilityMode(mCompatMode);
+ mStyleSet->CompatibilityModeChanged();
+ if (PresShell* presShell = GetPresShell()) {
+ // Selectors may have become case-sensitive / case-insensitive, the stylist
+ // has already performed the relevant invalidation.
+ presShell->EnsureStyleFlush();
+ }
+ if (!mStyleSetFilled) {
+ MOZ_ASSERT(!mQuirkSheetAdded);
+ return;
+ }
+ if (mQuirkSheetAdded == NeedsQuirksSheet()) {
+ return;
+ }
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ StyleSheet* sheet = cache->QuirkSheet();
+ if (mQuirkSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ } else {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+ mQuirkSheetAdded = !mQuirkSheetAdded;
+ ApplicableStylesChanged();
+}
+
+void Document::SetCompatibilityMode(nsCompatibility aMode) {
+ NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
+ "Bad compat mode for XHTML document!");
+
+ if (mCompatMode == aMode) {
+ return;
+ }
+ mCompatMode = aMode;
+ CompatibilityModeChanged();
+ // Trigger recomputation of the nsViewportInfo the next time it's queried.
+ mViewportType = Unknown;
+}
+
+static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
+ uint32_t aSandboxFlags,
+ nsIChannel* aChannel) {
+ // If the document permits allow-top-navigation and
+ // allow-top-navigation-by-user-activation this will permit all top
+ // navigation.
+ if (aSandboxFlags != SANDBOXED_NONE &&
+ !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
+ !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
+ aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
+ "BothAllowTopNavigationAndUserActivationPresent");
+ }
+ // If the document is sandboxed (via the HTML5 iframe sandbox
+ // attribute) and both the allow-scripts and allow-same-origin
+ // keywords are supplied, the sandboxed document can call into its
+ // parent document and remove its sandboxing entirely - we print a
+ // warning to the web console in this case.
+ if (aSandboxFlags & SANDBOXED_NAVIGATION &&
+ !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
+ !(aSandboxFlags & SANDBOXED_ORIGIN)) {
+ RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
+ MOZ_ASSERT(bc->IsInProcess());
+
+ RefPtr<BrowsingContext> parentBC = bc->GetParent();
+ if (!parentBC || !parentBC->IsInProcess()) {
+ // If parent document is not in process, then by construction it
+ // cannot be same origin.
+ return;
+ }
+
+ // Don't warn if our parent is not the top-level document.
+ if (!parentBC->IsTopContent()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
+ MOZ_ASSERT(parentDocShell);
+
+ nsCOMPtr<nsIChannel> parentChannel;
+ parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
+ if (!parentChannel) {
+ return;
+ }
+ nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
+ nsCOMPtr<nsIURI> iframeUri;
+ parentChannel->GetURI(getter_AddRefs(iframeUri));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ "Iframe Sandbox"_ns, parentDocument,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "BothAllowScriptsAndSameOriginPresent",
+ nsTArray<nsString>(), iframeUri);
+ }
+}
+
+bool Document::IsSynthesized() {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
+ return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
+}
+
+// static
+bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
+ nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
+ return principal && (principal->IsSystemPrincipal() ||
+ principal->GetIsAddonOrExpandedAddonPrincipal());
+}
+
+nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset) {
+ if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p StartDocumentLoad %s", this,
+ uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+
+ MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
+ "Bad readyState");
+ SetReadyStateInternal(READYSTATE_LOADING);
+
+ if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
+ mLoadedAsData = true;
+ SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
+ // We need to disable script & style loading in this case.
+ // We leave them disabled even in EndLoad(), and let anyone
+ // who puts the document on display to worry about enabling.
+
+ // Do not load/process scripts when loading as data
+ ScriptLoader()->SetEnabled(false);
+
+ // styles
+ CSSLoader()->SetEnabled(
+ false); // Do not load/process styles when loading as data
+ } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
+ // Allow CSS, but not scripts
+ ScriptLoader()->SetEnabled(false);
+ }
+
+ mMayStartLayout = false;
+ MOZ_ASSERT(!mReadyForIdle,
+ "We should never hit DOMContentLoaded before this point");
+
+ if (aReset) {
+ Reset(aChannel, aLoadGroup);
+ }
+
+ nsAutoCString contentType;
+ nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
+ if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
+ contentType))) ||
+ NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
+ // XXX this is only necessary for viewsource:
+ nsACString::const_iterator start, end, semicolon;
+ contentType.BeginReading(start);
+ contentType.EndReading(end);
+ semicolon = start;
+ FindCharInReadable(';', semicolon, end);
+ SetContentType(Substring(start, semicolon));
+ }
+
+ RetrieveRelevantHeaders(aChannel);
+
+ mChannel = aChannel;
+ RecomputeResistFingerprinting();
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ mIsSrcdocDocument = true;
+ }
+ }
+
+ if (mChannel) {
+ nsLoadFlags loadFlags;
+ mChannel->GetLoadFlags(&loadFlags);
+ bool isDocument = false;
+ mChannel->GetIsDocument(&isDocument);
+ if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
+ IsSynthesized() && XRE_IsContentProcess()) {
+ ContentChild::UpdateCookieStatus(mChannel);
+ }
+
+ // Store the security info for future use.
+ mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+ }
+
+ // If this document is being loaded by a docshell, copy its sandbox flags
+ // to the document, and store the fullscreen enabled flag. These are
+ // immutable after being set here.
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
+
+ // If this is an error page, don't inherit sandbox flags
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (docShell && !loadInfo->GetLoadErrorPage()) {
+ mSandboxFlags = loadInfo->GetSandboxFlags();
+ WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
+ }
+
+ // Set the opener policy for the top level content document.
+ nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
+ nsILoadInfo::CrossOriginOpenerPolicy policy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ if (IsTopLevelContentDocument() && httpChan &&
+ NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
+ docShell->GetBrowsingContext()) {
+ // Setting the opener policy on a discarded context has no effect.
+ Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
+ }
+
+ // The CSP directives upgrade-insecure-requests as well as
+ // block-all-mixed-content not only apply to the toplevel document,
+ // but also to nested documents. The loadInfo of a subdocument
+ // load already holds the correct flag, so let's just set it here
+ // on the document. Please note that we set the appropriate preload
+ // bits just for the sake of completeness here, because the preloader
+ // does not reach into subdocuments.
+ mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
+ mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
+ mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
+ mBlockAllMixedContentPreloads = mBlockAllMixedContent;
+
+ // HTTPS-Only Mode flags
+ // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
+ // sub-resources and sub-documents.
+ mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+
+ nsresult rv = InitReferrerInfo(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitCOEP(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check CSP navigate-to
+ // We need to enforce the CSP of the document that initiated the load,
+ // which is the CSP to inherit.
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+ if (cspToInherit) {
+ bool allowsNavigateTo = false;
+ rv = cspToInherit->GetAllowsNavigateTo(
+ mDocumentURI, loadInfo->GetIsFormSubmission(),
+ !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
+ true, /* aEnforceWhitelist */
+ &allowsNavigateTo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!allowsNavigateTo) {
+ aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
+ return NS_OK;
+ }
+ }
+
+ rv = InitCSP(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize FeaturePolicy
+ rv = InitFeaturePolicy(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Generally XFO and CSP frame-ancestors is handled within
+ // DocumentLoadListener. However, the DocumentLoadListener can not handle
+ // object and embed. Until then we have to enforce it here (See Bug 1646899).
+ nsContentPolicyType internalContentType =
+ loadInfo->InternalContentPolicyType();
+ if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
+ internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
+ nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
+
+ nsresult status;
+ aChannel->GetStatus(&status);
+ if (status == NS_ERROR_XFO_VIOLATION) {
+ // stop! ERROR page!
+ // But before we have to reset the principal of the document
+ // because the onload() event fires before the error page
+ // is displayed and we do not want the enclosing document
+ // to access the contentDocument.
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
+ // Before calling SetPrincipals() we should ensure that mFontFaceSet
+ // and also GetInnerWindow() is still null at this point, before
+ // we can fix Bug 1614735: Evaluate calls to SetPrincipal
+ // within Document.cpp
+ MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
+ SetPrincipals(nullPrincipal, nullPrincipal);
+ }
+ }
+
+ return NS_OK;
+}
+
+void Document::SetLoadedAsData(bool aLoadedAsData,
+ bool aConsiderForMemoryReporting) {
+ mLoadedAsData = aLoadedAsData;
+ if (aConsiderForMemoryReporting) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (global) {
+ if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
+ nsGlobalWindowInner::Cast(window)
+ ->RegisterDataDocumentForMemoryReporting(this);
+ }
+ }
+ }
+}
+
+nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
+
+void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
+
+nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
+ return mPreloadCSP;
+}
+
+void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
+ mPreloadCSP = aPreloadCSP;
+}
+
+void Document::GetCspJSON(nsString& aJSON) {
+ aJSON.Truncate();
+
+ if (!mCSP) {
+ dom::CSPPolicies jsonPolicies;
+ jsonPolicies.ToJSON(aJSON);
+ return;
+ }
+ mCSP->ToJSON(aJSON);
+}
+
+void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
+ for (uint32_t i = 0; i < aMessages.Length(); ++i) {
+ nsAutoString messageTag;
+ aMessages[i]->GetTag(messageTag);
+
+ nsAutoString category;
+ aMessages[i]->GetCategory(category);
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(category), this,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(messageTag).get());
+ }
+}
+
+void Document::ApplySettingsFromCSP(bool aSpeculative) {
+ nsresult rv = NS_OK;
+ if (!aSpeculative) {
+ // 1) apply settings from regular CSP
+ if (mCSP) {
+ // Set up 'block-all-mixed-content' if not already inherited
+ // from the parent context or set by any other CSP.
+ if (!mBlockAllMixedContent) {
+ bool block = false;
+ rv = mCSP->GetBlockAllMixedContent(&block);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ mBlockAllMixedContent = block;
+ }
+ if (!mBlockAllMixedContentPreloads) {
+ mBlockAllMixedContentPreloads = mBlockAllMixedContent;
+ }
+
+ // Set up 'upgrade-insecure-requests' if not already inherited
+ // from the parent context or set by any other CSP.
+ if (!mUpgradeInsecureRequests) {
+ bool upgrade = false;
+ rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ mUpgradeInsecureRequests = upgrade;
+ }
+ if (!mUpgradeInsecurePreloads) {
+ mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
+ }
+ // Update csp settings in the parent process
+ if (auto* wgc = GetWindowGlobalChild()) {
+ wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
+ mUpgradeInsecureRequests);
+ }
+ }
+ return;
+ }
+
+ // 2) apply settings from speculative csp
+ if (mPreloadCSP) {
+ if (!mBlockAllMixedContentPreloads) {
+ bool block = false;
+ rv = mPreloadCSP->GetBlockAllMixedContent(&block);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ mBlockAllMixedContent = block;
+ }
+ if (!mUpgradeInsecurePreloads) {
+ bool upgrade = false;
+ rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ mUpgradeInsecurePreloads = upgrade;
+ }
+ }
+}
+
+nsresult Document::InitCSP(nsIChannel* aChannel) {
+ MOZ_ASSERT(!mScriptGlobalObject,
+ "CSP must be initialized before mScriptGlobalObject is set!");
+
+ // If this is a data document - no need to set CSP.
+ if (mLoadedAsData) {
+ return NS_OK;
+ }
+
+ // If this is an image, no need to set a CSP. Otherwise SVG images
+ // served with a CSP might block internally applied inline styles.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGE ||
+ loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGESET) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
+
+ // If there is a CSP that needs to be inherited from whatever
+ // global is considered the client of the document fetch then
+ // we query it here from the loadinfo in case the newly created
+ // document needs to inherit the CSP. See:
+ // https://w3c.github.io/webappsec-csp/#initialize-document-csp
+ bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
+ if (inheritedCSP) {
+ mCSP = loadInfo->GetCspToInherit();
+ }
+
+ // If there is no CSP to inherit, then we create a new CSP here so
+ // that history entries always have the right reference in case a
+ // Meta CSP gets dynamically added after the history entry has
+ // already been created.
+ if (!mCSP) {
+ mCSP = new nsCSPContext();
+ }
+
+ // Always overwrite the requesting context of the CSP so that any new
+ // 'self' keyword added to an inherited CSP translates correctly.
+ nsresult rv = mCSP->SetRequestContextWithDocument(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (httpChannel) {
+ Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
+ tCspHeaderValue);
+
+ Unused << httpChannel->GetResponseHeader(
+ "content-security-policy-report-only"_ns, tCspROHeaderValue);
+ }
+ NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
+ NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
+
+ // Check if this is a document from a WebExtension.
+ nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+ auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
+
+ // If there's no CSP to apply, go ahead and return early
+ if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
+ cspROHeaderValue.IsEmpty()) {
+ if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIURI> chanURI;
+ aChannel->GetURI(getter_AddRefs(chanURI));
+ nsAutoCString aspec;
+ chanURI->GetAsciiSpec(aspec);
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("no CSP for document, %s", aspec.get()));
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("Document is an add-on or CSP header specified %p", this));
+
+ // ----- if the doc is an addon, apply its CSP.
+ if (addonPolicy) {
+ mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
+
+ mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
+ // Bug 1548468: Move CSP off ExpandedPrincipal
+ // Currently the LoadInfo holds the source of truth for every resource load
+ // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
+ // (and not from the Client) if the load was triggered by an extension.
+ auto* basePrin = BasePrincipal::Cast(principal);
+ if (basePrin->Is<ExpandedPrincipal>()) {
+ basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
+ }
+ }
+
+ // ----- if there's a full-strength CSP header, apply it.
+ if (!cspHeaderValue.IsEmpty()) {
+ mHasCSPDeliveredThroughHeader = true;
+ rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // ----- if there's a report-only CSP header, apply it.
+ if (!cspROHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // ----- Enforce sandbox policy if supplied in CSP header
+ // The document may already have some sandbox flags set (e.g. if the document
+ // is an iframe with the sandbox attribute set). If we have a CSP sandbox
+ // directive, intersect the CSP sandbox flags with the existing flags. This
+ // corresponds to the _least_ permissive policy.
+ uint32_t cspSandboxFlags = SANDBOXED_NONE;
+ rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Probably the iframe sandbox attribute already caused the creation of a
+ // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
+ // and no one has been created yet.
+ bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
+ !(mSandboxFlags & SANDBOXED_ORIGIN);
+
+ mSandboxFlags |= cspSandboxFlags;
+
+ if (needNewNullPrincipal) {
+ principal = NullPrincipal::CreateWithInheritedAttributes(principal);
+ // Skip setting the content blocking allowlist principal to NullPrincipal.
+ // The principal is only used to enable/disable trackingprotection via
+ // permission and can be shared with the top level sandboxed site.
+ // See Bug 1654546.
+ SetPrincipals(principal, principal);
+ }
+
+ ApplySettingsFromCSP(false);
+ return NS_OK;
+}
+
+static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
+ BrowsingContext* parentContext = aContext->GetParent();
+ if (!parentContext) {
+ return nullptr;
+ }
+
+ WindowContext* windowContext = parentContext->GetCurrentWindowContext();
+ if (!windowContext) {
+ return nullptr;
+ }
+
+ return windowContext->GetDocument();
+}
+
+already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
+ BrowsingContext* browsingContext = GetBrowsingContext();
+ if (!browsingContext) {
+ return nullptr;
+ }
+ if (!browsingContext->IsContentSubframe()) {
+ return nullptr;
+ }
+
+ HTMLIFrameElement* iframe =
+ HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
+ if (iframe) {
+ return do_AddRef(iframe->FeaturePolicy());
+ }
+
+ if (XRE_IsParentProcess()) {
+ return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
+ }
+
+ if (Document* parentDocument =
+ GetInProcessParentDocumentFrom(browsingContext)) {
+ return do_AddRef(parentDocument->FeaturePolicy());
+ }
+
+ WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
+ if (!windowContext) {
+ return nullptr;
+ }
+
+ WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
+ if (!child) {
+ return nullptr;
+ }
+
+ return do_AddRef(child->GetContainerFeaturePolicy());
+}
+
+void Document::InitFeaturePolicy() {
+ MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
+
+ mFeaturePolicy->ResetDeclaredPolicy();
+
+ mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
+
+ RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
+ if (parentPolicy) {
+ // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
+ mFeaturePolicy->InheritPolicy(parentPolicy);
+ mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
+ }
+}
+
+nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
+ InitFeaturePolicy();
+
+ // We don't want to parse the http Feature-Policy header if this pref is off.
+ if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!httpChannel) {
+ return NS_OK;
+ }
+
+ // query the policy from the header
+ nsAutoCString value;
+ rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
+ if (NS_SUCCEEDED(rv)) {
+ mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
+ NodePrincipal(), nullptr);
+ }
+
+ return NS_OK;
+}
+
+void Document::EnsureNotEnteringAndExitFullscreen() {
+ Document::ClearPendingFullscreenRequests(this);
+ if (GetFullscreenElement()) {
+ Document::AsyncExitFullscreen(this);
+ }
+}
+
+void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
+ mCachedURLData = nullptr;
+}
+
+nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
+ MOZ_ASSERT(mReferrerInfo);
+ MOZ_ASSERT(mPreloadReferrerInfo);
+
+ if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
+ // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
+ // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
+ // they have an opaque origin.
+ // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ // At this point the document is not fully created and mParentDocument has
+ // not been set yet,
+ Document* parentDoc = bc->GetEmbedderElement()
+ ? bc->GetEmbedderElement()->OwnerDoc()
+ : nullptr;
+ if (parentDoc) {
+ SetReferrerInfo(parentDoc->GetReferrerInfo());
+ mPreloadReferrerInfo = mReferrerInfo;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
+ "srcdoc without null principal as toplevel!");
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!httpChannel) {
+ return NS_OK;
+ }
+
+ if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
+ SetReferrerInfo(referrerInfo);
+ }
+
+ // Override policy if we get one from Referrerr-Policy header
+ mozilla::dom::ReferrerPolicy policy =
+ nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
+ nsCOMPtr<nsIReferrerInfo> clone =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
+ ->CloneWithNewPolicy(policy);
+ SetReferrerInfo(clone);
+ mPreloadReferrerInfo = mReferrerInfo;
+ return NS_OK;
+}
+
+nsresult Document::InitCOEP(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
+
+ if (!intChannel) {
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy policy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
+ mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
+ mEmbedderPolicy = Some(policy);
+ }
+
+ return NS_OK;
+}
+
+void Document::StopDocumentLoad() {
+ if (mParser) {
+ mParserAborted = true;
+ mParser->Terminate();
+ }
+}
+
+void Document::SetDocumentURI(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
+ mDocumentURI = aURI;
+ nsIURI* newBase = GetDocBaseURI();
+
+ mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
+
+ bool equalBases = false;
+ // Changing just the ref of a URI does not change how relative URIs would
+ // resolve wrt to it, so we can treat the bases as equal as long as they're
+ // equal ignoring the ref.
+ if (oldBase && newBase) {
+ oldBase->EqualsExceptRef(newBase, &equalBases);
+ } else {
+ equalBases = !oldBase && !newBase;
+ }
+
+ // If this is the first time we're setting the document's URI, set the
+ // document's original URI.
+ if (!mOriginalURI) mOriginalURI = mDocumentURI;
+
+ // If changing the document's URI changed the base URI of the document, we
+ // need to refresh the hrefs of all the links on the page.
+ if (!equalBases) {
+ mCachedURLData = nullptr;
+ RefreshLinkHrefs();
+ }
+
+ // Recalculate our base domain
+ mBaseDomain.Truncate();
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
+ }
+
+ // Tell our WindowGlobalParent that the document's URI has been changed.
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SetDocumentURI(mDocumentURI);
+ }
+}
+
+static void GetFormattedTimeString(PRTime aTime,
+ nsAString& aFormattedTimeString) {
+ PRExplodedTime prtime;
+ PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
+ // "MM/DD/YYYY hh:mm:ss"
+ char formatedTime[24];
+ if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
+ prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
+ prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
+ CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
+ } else {
+ // If we for whatever reason failed to find the last modified time
+ // (or even the current time), fall back to what NS4.x returned.
+ aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
+ }
+}
+
+void Document::GetLastModified(nsAString& aLastModified) const {
+ if (!mLastModified.IsEmpty()) {
+ aLastModified.Assign(mLastModified);
+ } else {
+ GetFormattedTimeString(PR_Now(), aLastModified);
+ }
+}
+
+static void IncrementExpandoGeneration(Document& aDoc) {
+ ++aDoc.mExpandoAndGeneration.generation;
+}
+
+void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
+ MOZ_ASSERT(
+ nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
+ "Only put elements that need to be exposed as document['name'] in "
+ "the named table.");
+
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
+
+ // Null for out-of-memory
+ if (entry) {
+ if (!entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ IncrementExpandoGeneration(*this);
+ }
+ entry->AddNameElement(this, aElement);
+ }
+}
+
+void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
+ // Speed up document teardown
+ if (mIdentifierMap.Count() == 0) return;
+
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
+ if (!entry) // Could be false if the element was anonymous, hence never added
+ return;
+
+ entry->RemoveNameElement(aElement);
+ if (!entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ IncrementExpandoGeneration(*this);
+ }
+}
+
+void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
+
+ if (entry) { /* True except on OOM */
+ if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+ !entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ IncrementExpandoGeneration(*this);
+ }
+ entry->AddIdElement(aElement);
+ }
+}
+
+void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
+ NS_ASSERTION(aId, "huhwhatnow?");
+
+ // Speed up document teardown
+ if (mIdentifierMap.Count() == 0) {
+ return;
+ }
+
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
+ if (!entry) // Can be null for XML elements with changing ids.
+ return;
+
+ entry->RemoveIdElement(aElement);
+ if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+ !entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ IncrementExpandoGeneration(*this);
+ }
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+}
+
+void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
+ bool aPreload) {
+ ReferrerPolicyEnum policy =
+ ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
+ // The empty string "" corresponds to no referrer policy, causing a fallback
+ // to a referrer policy defined elsewhere.
+ if (policy == ReferrerPolicy::_empty) {
+ return;
+ }
+
+ MOZ_ASSERT(mReferrerInfo);
+ MOZ_ASSERT(mPreloadReferrerInfo);
+
+ if (aPreload) {
+ mPreloadReferrerInfo =
+ static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
+ ->CloneWithNewPolicy(policy);
+ } else {
+ nsCOMPtr<nsIReferrerInfo> clone =
+ static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
+ ->CloneWithNewPolicy(policy);
+ SetReferrerInfo(clone);
+ }
+}
+
+void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
+ nsIPrincipal* aNewPartitionedPrincipal) {
+ MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
+ if (aNewPrincipal && mAllowDNSPrefetch &&
+ StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
+ if (aNewPrincipal->SchemeIs("https")) {
+ mAllowDNSPrefetch = false;
+ }
+ }
+
+ mCSSLoader->DeregisterFromSheetCache();
+
+ mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
+ mPartitionedPrincipal = aNewPartitionedPrincipal;
+
+ mCachedURLData = nullptr;
+
+ mCSSLoader->RegisterInSheetCache();
+
+#ifdef DEBUG
+ // Validate that the docgroup is set correctly by calling its getter and
+ // triggering its sanity check.
+ //
+ // If we're setting the principal to null, we don't want to perform the check,
+ // as the document is entering an intermediate state where it does not have a
+ // principal. It will be given another real principal shortly which we will
+ // check. It's not unsafe to have a document which has a null principal in the
+ // same docgroup as another document, so this should not be a problem.
+ if (aNewPrincipal) {
+ GetDocGroup();
+ }
+#endif
+}
+
+#ifdef DEBUG
+void Document::AssertDocGroupMatchesKey() const {
+ // Sanity check that we have an up-to-date and accurate docgroup
+ // We only check if the principal when we can get the browsing context.
+
+ // Note that we can be invoked during cycle collection, so we need to handle
+ // the browsingcontext being partially unlinked - normally you shouldn't
+ // null-check `Group()` as it shouldn't return nullptr.
+ if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
+ return;
+ }
+
+ if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
+ MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
+ GetBrowsingContext()->Group());
+
+ // GetKey() can fail, e.g. after the TLD service has shut down.
+ nsAutoCString docGroupKey;
+ nsresult rv = mozilla::dom::DocGroup::GetKey(
+ NodePrincipal(),
+ GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
+ docGroupKey);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
+ }
+ }
+}
+#endif
+
+nsresult Document::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ // Note that this method may be called off the main thread.
+ if (mDocGroup) {
+ return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
+ }
+ return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
+ if (mDocGroup) {
+ return mDocGroup->EventTargetFor(aCategory);
+ }
+ return DispatcherTrait::EventTargetFor(aCategory);
+}
+
+AbstractThread* Document::AbstractMainThreadFor(
+ mozilla::TaskCategory aCategory) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDocGroup) {
+ return mDocGroup->AbstractMainThreadFor(aCategory);
+ }
+ return DispatcherTrait::AbstractMainThreadFor(aCategory);
+}
+
+void Document::NoteScriptTrackingStatus(const nsACString& aURL,
+ bool aIsTracking) {
+ if (aIsTracking) {
+ mTrackingScripts.Insert(aURL);
+ } else {
+ MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
+ }
+}
+
+bool Document::IsScriptTracking(JSContext* aCx) const {
+ JS::AutoFilename filename;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
+ return false;
+ }
+ return mTrackingScripts.Contains(nsDependentCString(filename.get()));
+}
+
+void Document::GetContentType(nsAString& aContentType) {
+ CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
+}
+
+void Document::SetContentType(const nsACString& aContentType) {
+ if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
+ aContentType.EqualsLiteral("application/xhtml+xml")) {
+ mDefaultElementType = kNameSpaceID_XHTML;
+ }
+
+ mCachedEncoder = nullptr;
+ mContentType = aContentType;
+}
+
+bool Document::GetAllowPlugins() {
+ // First, we ask our docshell if it allows plugins.
+ auto* browsingContext = GetBrowsingContext();
+
+ if (browsingContext) {
+ if (!browsingContext->GetAllowPlugins()) {
+ return false;
+ }
+
+ // If the docshell allows plugins, we check whether
+ // we are sandboxed and plugins should not be allowed.
+ if (mSandboxFlags & SANDBOXED_PLUGINS) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Document::HasPendingInitialTranslation() {
+ return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
+}
+
+bool Document::HasPendingL10nMutations() const {
+ return mDocumentL10n && mDocumentL10n->HasPendingMutations();
+}
+
+bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
+ JS::Rooted<JSObject*> object(aCx, aObject);
+ nsCOMPtr<nsIPrincipal> callerPrincipal =
+ nsContentUtils::SubjectPrincipal(aCx);
+ nsGlobalWindowInner* win = xpc::WindowOrNull(object);
+ bool allowed = false;
+ callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
+ &allowed);
+ return allowed;
+}
+
+void Document::LocalizationLinkAdded(Element* aLinkElement) {
+ if (!AllowsL10n()) {
+ return;
+ }
+
+ nsAutoString href;
+ aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+
+ if (!mDocumentL10n) {
+ Element* elem = GetDocumentElement();
+ MOZ_DIAGNOSTIC_ASSERT(elem);
+
+ bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
+ mDocumentL10n = DocumentL10n::Create(this, isSync);
+ if (NS_WARN_IF(!mDocumentL10n)) {
+ return;
+ }
+ }
+
+ mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
+
+ if (mReadyState >= READYSTATE_INTERACTIVE) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(
+ "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
+ &DocumentL10n::TriggerInitialTranslation));
+ } else {
+ if (!mDocumentL10n->mBlockingLayout) {
+ // Our initial translation is going to block layout start. Make sure
+ // we don't fire the load event until after that stops happening and
+ // layout has a chance to start.
+ BlockOnload();
+ mDocumentL10n->mBlockingLayout = true;
+ }
+ }
+}
+
+void Document::LocalizationLinkRemoved(Element* aLinkElement) {
+ if (!AllowsL10n()) {
+ return;
+ }
+
+ if (mDocumentL10n) {
+ nsAutoString href;
+ aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+ uint32_t remaining =
+ mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
+ if (remaining == 0) {
+ if (mDocumentL10n->mBlockingLayout) {
+ mDocumentL10n->mBlockingLayout = false;
+ UnblockOnload(/* aFireSync = */ false);
+ }
+ mDocumentL10n = nullptr;
+ }
+ }
+}
+
+/**
+ * This method should be called once the end of the l10n
+ * resource container has been parsed.
+ *
+ * In XUL this is the end of the first </linkset>,
+ * In XHTML/HTML this is the end of </head>.
+ *
+ * This milestone is used to allow for batch
+ * localization context I/O and building done
+ * once when all resources in the document have been
+ * collected.
+ */
+void Document::OnL10nResourceContainerParsed() {
+ // XXX: This is a scaffolding for where we might inject prefetch
+ // in bug 1717241.
+}
+
+void Document::OnParsingCompleted() {
+ // Let's call it again, in case the resource
+ // container has not been closed, and only
+ // now we're closing the document.
+ OnL10nResourceContainerParsed();
+
+ if (mDocumentL10n) {
+ RefPtr<DocumentL10n> l10n = mDocumentL10n;
+ l10n->TriggerInitialTranslation();
+ }
+}
+
+void Document::InitialTranslationCompleted(bool aL10nCached) {
+ if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
+ // This means we blocked the load event in LocalizationLinkAdded. It's
+ // important that the load blocker removal here be async, because our caller
+ // will notify the content sink after us, and we want the content sync's
+ // work to happen before the load event fires.
+ mDocumentL10n->mBlockingLayout = false;
+ UnblockOnload(/* aFireSync = */ false);
+ }
+
+ mL10nProtoElements.Clear();
+
+ nsXULPrototypeDocument* proto = GetPrototype();
+ if (proto) {
+ proto->SetIsL10nCached(aL10nCached);
+ }
+}
+
+bool Document::AllowsL10n() const {
+ if (IsStaticDocument()) {
+ // We don't allow l10n on static documents, because the nodes are already
+ // cloned translated, and static docs don't get parsed so we never
+ // TriggerInitialTranslation, etc, so a load blocker would keep hanging
+ // forever.
+ return false;
+ }
+ bool allowed = false;
+ NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
+ return allowed;
+}
+
+bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_core_enabled();
+}
+
+bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return aCallerType == dom::CallerType::System ||
+ StaticPrefs::dom_animations_api_core_enabled();
+}
+
+bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_getAnimations_enabled();
+}
+
+bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
+}
+
+bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_timelines_enabled();
+}
+
+DocumentTimeline* Document::Timeline() {
+ if (!mDocumentTimeline) {
+ mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
+ }
+
+ return mDocumentTimeline;
+}
+
+SVGSVGElement* Document::GetSVGRootElement() const {
+ Element* root = GetRootElement();
+ if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
+ return nullptr;
+ }
+ return static_cast<SVGSVGElement*>(root);
+}
+
+/* Return true if the document is in the focused top-level window, and is an
+ * ancestor of the focused DOMWindow. */
+bool Document::HasFocus(ErrorResult& rv) const {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ rv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return false;
+ }
+
+ BrowsingContext* bc = GetBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ if (!fm->IsInActiveWindow(bc)) {
+ return false;
+ }
+
+ return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
+}
+
+bool Document::ThisDocumentHasFocus() const {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ return fm && fm->GetFocusedWindow() &&
+ fm->GetFocusedWindow()->GetExtantDoc() == this;
+}
+
+void Document::GetDesignMode(nsAString& aDesignMode) {
+ if (IsInDesignMode()) {
+ aDesignMode.AssignLiteral("on");
+ } else {
+ aDesignMode.AssignLiteral("off");
+ }
+}
+
+void Document::SetDesignMode(const nsAString& aDesignMode,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
+ SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
+}
+
+static void NotifyEditableStateChange(Document& aDoc) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsMutationGuard g;
+#endif
+ for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
+ node = node->GetNextNode(&aDoc)) {
+ if (auto* element = Element::FromNode(node)) {
+ element->UpdateState(true);
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
+}
+
+void Document::SetDesignMode(const nsAString& aDesignMode,
+ const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ ErrorResult& rv) {
+ if (aSubjectPrincipal.isSome() &&
+ !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
+ rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
+ return;
+ }
+ const bool editableMode = IsInDesignMode();
+ if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
+ SetEditableFlag(!editableMode);
+ // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
+ // state of all descendant elements of it. Update that now.
+ NotifyEditableStateChange(*this);
+ rv = EditingStateChanged();
+ }
+}
+
+nsCommandManager* Document::GetMidasCommandManager() {
+ // check if we have it cached
+ if (mMidasCommandManager) {
+ return mMidasCommandManager;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return nullptr;
+ }
+
+ mMidasCommandManager = docshell->GetCommandManager();
+ return mMidasCommandManager;
+}
+
+// static
+void Document::EnsureInitializeInternalCommandDataHashtable() {
+ if (sInternalCommandDataHashtable) {
+ return;
+ }
+ using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
+ sInternalCommandDataHashtable = new InternalCommandDataHashtable();
+ // clang-format off
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"bold"_ns,
+ InternalCommandData(
+ "cmd_bold",
+ Command::FormatBold,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"italic"_ns,
+ InternalCommandData(
+ "cmd_italic",
+ Command::FormatItalic,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"underline"_ns,
+ InternalCommandData(
+ "cmd_underline",
+ Command::FormatUnderline,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"strikethrough"_ns,
+ InternalCommandData(
+ "cmd_strikethrough",
+ Command::FormatStrikeThrough,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"subscript"_ns,
+ InternalCommandData(
+ "cmd_subscript",
+ Command::FormatSubscript,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"superscript"_ns,
+ InternalCommandData(
+ "cmd_superscript",
+ Command::FormatSuperscript,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"cut"_ns,
+ InternalCommandData(
+ "cmd_cut",
+ Command::Cut,
+ ExecCommandParam::Ignore,
+ CutCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"copy"_ns,
+ InternalCommandData(
+ "cmd_copy",
+ Command::Copy,
+ ExecCommandParam::Ignore,
+ CopyCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"paste"_ns,
+ InternalCommandData(
+ "cmd_paste",
+ Command::Paste,
+ ExecCommandParam::Ignore,
+ PasteCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"delete"_ns,
+ InternalCommandData(
+ "cmd_deleteCharBackward",
+ Command::DeleteCharBackward,
+ ExecCommandParam::Ignore,
+ DeleteCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"forwarddelete"_ns,
+ InternalCommandData(
+ "cmd_deleteCharForward",
+ Command::DeleteCharForward,
+ ExecCommandParam::Ignore,
+ DeleteCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"selectall"_ns,
+ InternalCommandData(
+ "cmd_selectAll",
+ Command::SelectAll,
+ ExecCommandParam::Ignore,
+ SelectAllCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"undo"_ns,
+ InternalCommandData(
+ "cmd_undo",
+ Command::HistoryUndo,
+ ExecCommandParam::Ignore,
+ UndoCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"redo"_ns,
+ InternalCommandData(
+ "cmd_redo",
+ Command::HistoryRedo,
+ ExecCommandParam::Ignore,
+ RedoCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"indent"_ns,
+ InternalCommandData("cmd_indent",
+ Command::FormatIndent,
+ ExecCommandParam::Ignore,
+ IndentCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"outdent"_ns,
+ InternalCommandData(
+ "cmd_outdent",
+ Command::FormatOutdent,
+ ExecCommandParam::Ignore,
+ OutdentCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"backcolor"_ns,
+ InternalCommandData(
+ "cmd_highlight",
+ Command::FormatBackColor,
+ ExecCommandParam::String,
+ HighlightColorStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"hilitecolor"_ns,
+ InternalCommandData(
+ "cmd_highlight",
+ Command::FormatBackColor,
+ ExecCommandParam::String,
+ HighlightColorStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"forecolor"_ns,
+ InternalCommandData(
+ "cmd_fontColor",
+ Command::FormatFontColor,
+ ExecCommandParam::String,
+ FontColorStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"fontname"_ns,
+ InternalCommandData(
+ "cmd_fontFace",
+ Command::FormatFontName,
+ ExecCommandParam::String,
+ FontFaceStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"fontsize"_ns,
+ InternalCommandData(
+ "cmd_fontSize",
+ Command::FormatFontSize,
+ ExecCommandParam::String,
+ FontSizeStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"inserthorizontalrule"_ns,
+ InternalCommandData(
+ "cmd_insertHR",
+ Command::InsertHorizontalRule,
+ ExecCommandParam::Ignore,
+ InsertTagCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"createlink"_ns,
+ InternalCommandData(
+ "cmd_insertLinkNoUI",
+ Command::InsertLink,
+ ExecCommandParam::String,
+ InsertTagCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertimage"_ns,
+ InternalCommandData(
+ "cmd_insertImageNoUI",
+ Command::InsertImage,
+ ExecCommandParam::String,
+ InsertTagCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"inserthtml"_ns,
+ InternalCommandData(
+ "cmd_insertHTML",
+ Command::InsertHTML,
+ ExecCommandParam::String,
+ InsertHTMLCommand::GetInstance,
+ // TODO: Chromium inserts text content of the document fragment
+ // created from the param.
+ // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"inserttext"_ns,
+ InternalCommandData(
+ "cmd_insertText",
+ Command::InsertText,
+ ExecCommandParam::String,
+ InsertPlaintextCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"justifyleft"_ns,
+ InternalCommandData(
+ "cmd_align",
+ Command::FormatJustifyLeft,
+ ExecCommandParam::Ignore, // Will be set to "left"
+ AlignCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"justifyright"_ns,
+ InternalCommandData(
+ "cmd_align",
+ Command::FormatJustifyRight,
+ ExecCommandParam::Ignore, // Will be set to "right"
+ AlignCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"justifycenter"_ns,
+ InternalCommandData(
+ "cmd_align",
+ Command::FormatJustifyCenter,
+ ExecCommandParam::Ignore, // Will be set to "center"
+ AlignCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"justifyfull"_ns,
+ InternalCommandData(
+ "cmd_align",
+ Command::FormatJustifyFull,
+ ExecCommandParam::Ignore, // Will be set to "justify"
+ AlignCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"removeformat"_ns,
+ InternalCommandData(
+ "cmd_removeStyles",
+ Command::FormatRemove,
+ ExecCommandParam::Ignore,
+ RemoveStylesCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"unlink"_ns,
+ InternalCommandData(
+ "cmd_removeLinks",
+ Command::FormatRemoveLink,
+ ExecCommandParam::Ignore,
+ StyleUpdatingCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertorderedlist"_ns,
+ InternalCommandData(
+ "cmd_ol",
+ Command::InsertOrderedList,
+ ExecCommandParam::Ignore,
+ ListCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertunorderedlist"_ns,
+ InternalCommandData(
+ "cmd_ul",
+ Command::InsertUnorderedList,
+ ExecCommandParam::Ignore,
+ ListCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertparagraph"_ns,
+ InternalCommandData(
+ "cmd_insertParagraph",
+ Command::InsertParagraph,
+ ExecCommandParam::Ignore,
+ InsertParagraphCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertlinebreak"_ns,
+ InternalCommandData(
+ "cmd_insertLineBreak",
+ Command::InsertLineBreak,
+ ExecCommandParam::Ignore,
+ InsertLineBreakCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"formatblock"_ns,
+ InternalCommandData(
+ "cmd_paragraphState",
+ Command::FormatBlock,
+ ExecCommandParam::String,
+ ParagraphStateCommand::GetInstance,
+ CommandOnTextEditor::Disabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"styleWithCSS"_ns,
+ InternalCommandData(
+ "cmd_setDocumentUseCSS",
+ Command::SetDocumentUseCSS,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"usecss"_ns, // Legacy command
+ InternalCommandData(
+ "cmd_setDocumentUseCSS",
+ Command::SetDocumentUseCSS,
+ ExecCommandParam::InvertedBoolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"contentReadOnly"_ns,
+ InternalCommandData(
+ "cmd_setDocumentReadOnly",
+ Command::SetDocumentReadOnly,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::Enabled));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"insertBrOnReturn"_ns,
+ InternalCommandData(
+ "cmd_insertBrOnReturn",
+ Command::SetDocumentInsertBROnEnterKeyPress,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"defaultParagraphSeparator"_ns,
+ InternalCommandData(
+ "cmd_defaultParagraphSeparator",
+ Command::SetDocumentDefaultParagraphSeparator,
+ ExecCommandParam::String,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"enableObjectResizing"_ns,
+ InternalCommandData(
+ "cmd_enableObjectResizing",
+ Command::ToggleObjectResizers,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"enableInlineTableEditing"_ns,
+ InternalCommandData(
+ "cmd_enableInlineTableEditing",
+ Command::ToggleInlineTableEditor,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"enableAbsolutePositionEditing"_ns,
+ InternalCommandData(
+ "cmd_enableAbsolutePositionEditing",
+ Command::ToggleAbsolutePositionEditor,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"enableCompatibleJoinSplitDirection"_ns,
+ InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
+ Command::EnableCompatibleJoinSplitNodeDirection,
+ ExecCommandParam::Boolean,
+ SetDocumentStateCommand::GetInstance,
+ CommandOnTextEditor::FallThrough));
+#if 0
+ // with empty string
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"justifynone"_ns,
+ InternalCommandData(
+ "cmd_align",
+ Command::Undefined,
+ ExecCommandParam::Ignore,
+ nullptr,
+ CommandOnTextEditor::Disabled)); // Not implemented yet.
+ // REQUIRED SPECIAL REVIEW special review
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"saveas"_ns,
+ InternalCommandData(
+ "cmd_saveAs",
+ Command::Undefined,
+ ExecCommandParam::Boolean,
+ nullptr,
+ CommandOnTextEditor::FallThrough)); // Not implemented yet.
+ // REQUIRED SPECIAL REVIEW special review
+ sInternalCommandDataHashtable->InsertOrUpdate(
+ u"print"_ns,
+ InternalCommandData(
+ "cmd_print",
+ Command::Undefined,
+ ExecCommandParam::Boolean,
+ nullptr,
+ CommandOnTextEditor::FallThrough)); // Not implemented yet.
+#endif // #if 0
+ // clang-format on
+}
+
+Document::InternalCommandData Document::ConvertToInternalCommand(
+ const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
+ nsAString* aAdjustedValue /* = nullptr */) {
+ MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
+ EnsureInitializeInternalCommandDataHashtable();
+ InternalCommandData commandData;
+ if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
+ return InternalCommandData();
+ }
+ // Ignore if the command is disabled by a corresponding pref due to Gecko
+ // specific.
+ switch (commandData.mCommand) {
+ case Command::SetDocumentReadOnly:
+ if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
+ aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
+ return InternalCommandData();
+ }
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ MOZ_DIAGNOSTIC_ASSERT(
+ aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
+ if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
+ return InternalCommandData();
+ }
+ break;
+ default:
+ break;
+ }
+ if (!aAdjustedValue) {
+ // No further work to do
+ return commandData;
+ }
+ switch (commandData.mExecCommandParam) {
+ case ExecCommandParam::Ignore:
+ // Just have to copy it, no checking
+ switch (commandData.mCommand) {
+ case Command::FormatJustifyLeft:
+ aAdjustedValue->AssignLiteral("left");
+ break;
+ case Command::FormatJustifyRight:
+ aAdjustedValue->AssignLiteral("right");
+ break;
+ case Command::FormatJustifyCenter:
+ aAdjustedValue->AssignLiteral("center");
+ break;
+ case Command::FormatJustifyFull:
+ aAdjustedValue->AssignLiteral("justify");
+ break;
+ default:
+ MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
+ EditorCommandParamType::None);
+ break;
+ }
+ return commandData;
+
+ case ExecCommandParam::Boolean:
+ MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
+ EditorCommandParamType::Bool));
+ // If this is a boolean value and it's not explicitly false (e.g. no
+ // value). We default to "true" (see bug 301490).
+ if (!aValue.LowerCaseEqualsLiteral("false")) {
+ aAdjustedValue->AssignLiteral("true");
+ } else {
+ aAdjustedValue->AssignLiteral("false");
+ }
+ return commandData;
+
+ case ExecCommandParam::InvertedBoolean:
+ MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
+ EditorCommandParamType::Bool));
+ // For old backwards commands we invert the check.
+ if (aValue.LowerCaseEqualsLiteral("false")) {
+ aAdjustedValue->AssignLiteral("true");
+ } else {
+ aAdjustedValue->AssignLiteral("false");
+ }
+ return commandData;
+
+ case ExecCommandParam::String:
+ MOZ_ASSERT(!!(
+ EditorCommand::GetParamType(commandData.mCommand) &
+ (EditorCommandParamType::String | EditorCommandParamType::CString)));
+ switch (commandData.mCommand) {
+ case Command::FormatBlock: {
+ const char16_t* start = aValue.BeginReading();
+ const char16_t* end = aValue.EndReading();
+ if (start != end && *start == '<' && *(end - 1) == '>') {
+ ++start;
+ --end;
+ }
+ // XXX Should we reorder this array with actual usage?
+ static const nsStaticAtom* kFormattableBlockTags[] = {
+ // clang-format off
+ nsGkAtoms::address,
+ nsGkAtoms::blockquote,
+ nsGkAtoms::dd,
+ nsGkAtoms::div,
+ nsGkAtoms::dl,
+ nsGkAtoms::dt,
+ nsGkAtoms::h1,
+ nsGkAtoms::h2,
+ nsGkAtoms::h3,
+ nsGkAtoms::h4,
+ nsGkAtoms::h5,
+ nsGkAtoms::h6,
+ nsGkAtoms::p,
+ nsGkAtoms::pre,
+ // clang-format on
+ };
+ nsAutoString value(nsDependentSubstring(start, end));
+ ToLowerCase(value);
+ const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
+ for (const nsStaticAtom* kTag : kFormattableBlockTags) {
+ if (valueAtom == kTag) {
+ kTag->ToString(*aAdjustedValue);
+ return commandData;
+ }
+ }
+ return InternalCommandData();
+ }
+ case Command::FormatFontSize: {
+ // Per editing spec as of April 23, 2012, we need to reject the value
+ // if it's not a valid floating-point number surrounded by optional
+ // whitespace. Otherwise, we parse it as a legacy font size. For
+ // now, we just parse as a legacy font size regardless (matching
+ // WebKit) -- bug 747879.
+ int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
+ if (!size) {
+ return InternalCommandData();
+ }
+ MOZ_ASSERT(aAdjustedValue->IsEmpty());
+ aAdjustedValue->AppendInt(size);
+ return commandData;
+ }
+ case Command::InsertImage:
+ case Command::InsertLink:
+ if (aValue.IsEmpty()) {
+ // Invalid value, return false
+ return InternalCommandData();
+ }
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ case Command::SetDocumentDefaultParagraphSeparator:
+ if (!aValue.LowerCaseEqualsLiteral("div") &&
+ !aValue.LowerCaseEqualsLiteral("p") &&
+ !aValue.LowerCaseEqualsLiteral("br")) {
+ // Invalid value
+ return InternalCommandData();
+ }
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ default:
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
+ return InternalCommandData();
+ }
+}
+
+Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
+ Document& aDocument, const InternalCommandData& aCommandData)
+ : mCommandData(aCommandData) {
+ // We'll retrieve an editor with current DOM tree and layout information.
+ // However, JS may have already hidden or remove exposed root content of
+ // the editor. Therefore, we need the latest layout information here.
+ aDocument.FlushPendingNotifications(FlushType::Layout);
+ if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
+ mDoNothing = true;
+ return;
+ }
+
+ if (nsPresContext* presContext = aDocument.GetPresContext()) {
+ // Consider context of command handling which is automatically resolved
+ // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
+ // The order is:
+ // 1. TextEditor if there is an active element and it has TextEditor like
+ // <input type="text"> or <textarea>.
+ // 2. HTMLEditor for the document, if there is.
+ // 3. Retarget to the DocShell or nsCommandManager as what we've done.
+ if (aCommandData.IsCutOrCopyCommand()) {
+ // Note that we used to use DocShell to handle `cut` and `copy` command
+ // for dispatching corresponding events for making possible web apps to
+ // implement their own editor without editable elements but supports
+ // standard shortcut keys, etc. In this case, we prefer to use active
+ // element's editor to keep same behavior.
+ mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
+ } else {
+ mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
+ mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
+ if (!mActiveEditor) {
+ mActiveEditor = mHTMLEditor;
+ }
+ }
+ }
+
+ // Then, retrieve editor command class instance which should handle it
+ // and can handle it now.
+ if (!mActiveEditor) {
+ // If the command is available without editor, we should redirect the
+ // command to focused descendant with DocShell.
+ if (aCommandData.IsAvailableOnlyWhenEditable()) {
+ mDoNothing = true;
+ return;
+ }
+ return;
+ }
+
+ // Otherwise, we should use EditorCommand instance (which is singleton
+ // instance) when it's enabled.
+ mEditorCommand = aCommandData.mGetEditorCommandFunc
+ ? aCommandData.mGetEditorCommandFunc()
+ : nullptr;
+ if (!mEditorCommand) {
+ mDoNothing = true;
+ mActiveEditor = nullptr;
+ mHTMLEditor = nullptr;
+ return;
+ }
+
+ if (IsCommandEnabled()) {
+ return;
+ }
+
+ // If the EditorCommand instance is disabled, we should do nothing if
+ // the command requires an editor.
+ if (aCommandData.IsAvailableOnlyWhenEditable()) {
+ // Do nothing if editor specific commands is disabled (bug 760052).
+ mDoNothing = true;
+ return;
+ }
+
+ // Otherwise, we should redirect it to focused descendant with DocShell.
+ mEditorCommand = nullptr;
+ mActiveEditor = nullptr;
+ mHTMLEditor = nullptr;
+}
+
+EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
+ using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
+ switch (mCommandData.mCommandOnTextEditor) {
+ case CommandOnTextEditor::Enabled:
+ return mActiveEditor;
+ case CommandOnTextEditor::Disabled:
+ return mActiveEditor && mActiveEditor->IsTextEditor()
+ ? nullptr
+ : mActiveEditor.get();
+ case CommandOnTextEditor::FallThrough:
+ return mHTMLEditor;
+ }
+ return nullptr;
+}
+
+bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
+ if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
+ // Make sure frames are up to date, since that can affect whether
+ // we're editable.
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+ EditorBase* targetEditor = GetTargetEditor();
+ if (targetEditor && targetEditor->IsTextEditor()) {
+ // FYI: When `disabled` attribute is set, `TextEditor` treats it as
+ // "readonly" too.
+ return !targetEditor->IsReadonly();
+ }
+ return aDocument->IsEditingOn();
+}
+
+bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return false;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
+}
+
+nsresult Document::AutoEditorCommandTarget::DoCommand(
+ nsIPrincipal* aPrincipal) const {
+ MOZ_ASSERT(!DoNothing());
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
+ aPrincipal);
+}
+
+template <typename ParamType>
+nsresult Document::AutoEditorCommandTarget::DoCommandParam(
+ const ParamType& aParam, nsIPrincipal* aPrincipal) const {
+ MOZ_ASSERT(!DoNothing());
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->DoCommandParam(mCommandData.mCommand, aParam,
+ MOZ_KnownLive(*targetEditor), aPrincipal);
+}
+
+nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
+ nsCommandParams& aParams) const {
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
+ MOZ_KnownLive(targetEditor), nullptr);
+}
+
+bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
+ const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "execCommand is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ // if they are requesting UI from us, let's fail since we have no UI
+ if (aShowUI) {
+ return false;
+ }
+
+ // If we're running an execCommand, we should just return false.
+ // https://github.com/w3c/editing/issues/200#issuecomment-575241816
+ if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
+ mIsRunningExecCommand) {
+ return false;
+ }
+
+ // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
+ // this might add some ugly JS dependencies?
+
+ nsAutoString adjustedValue;
+ InternalCommandData commandData =
+ ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
+ break;
+ case Command::EnableCompatibleJoinSplitNodeDirection:
+ // We don't allow to take the legacy behavior back if the new one is
+ // enabled by default.
+ if (StaticPrefs::
+ editor_join_split_direction_compatible_with_the_other_browsers() &&
+ !adjustedValue.EqualsLiteral("true") &&
+ !aSubjectPrincipal.IsSystemPrincipal()) {
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Do security check first.
+ if (commandData.IsCutOrCopyCommand()) {
+ if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
+ // We have rejected the event due to it not being performed in an
+ // input-driven context therefore, we report the error to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "ExecCommandCutCopyDeniedNotInputDriven");
+ return false;
+ }
+ } else if (commandData.IsPasteCommand()) {
+ if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
+ nsGkAtoms::clipboardRead)) {
+ return false;
+ }
+ }
+
+ AutoRunningExecCommandMarker markRunningExecCommand(*this);
+
+ // Next, consider context of command handling which is automatically resolved
+ // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+
+ if (editCommandTarget.DoNothing()) {
+ return false;
+ }
+
+ // If we cannot use EditorCommand instance directly, we need to handle the
+ // command with traditional path (i.e., with DocShell or nsCommandManager).
+ if (!editCommandTarget.IsEditor()) {
+ MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
+
+ // Special case clipboard write commands like Command::Cut and
+ // Command::Copy. For such commands, we need the behaviour from
+ // nsWindowRoot::GetControllers() which is to look at the focused element,
+ // and defer to a focused textbox's controller. The code past taken by
+ // other commands in ExecCommand() always uses the window directly, rather
+ // than deferring to the textbox, which is desireable for most editor
+ // commands, but not these commands (as those should allow copying out of
+ // embedded editors). This behaviour is invoked if we call DoCommand()
+ // directly on the docShell.
+ // XXX This means that we allow web app to pick up selected content in
+ // descendant document and write it into the clipboard when a
+ // descendant document has focus. However, Chromium does not allow
+ // this and this seems that it's not good behavior from point of view
+ // of security. We should treat this issue in another bug.
+ if (commandData.IsCutOrCopyCommand()) {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (!docShell) {
+ return false;
+ }
+ nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return false;
+ }
+ return NS_SUCCEEDED(rv);
+ }
+
+ // Otherwise (currently, only clipboard read commands like Command::Paste),
+ // we don't need to redirect the command to focused subdocument.
+ // Therefore, we should handle it with nsCommandManager as used to be.
+ // It may dispatch only preceding event of editing on non-editable element
+ // to make web apps possible to handle standard shortcut key, etc in
+ // their own editor.
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ // Return false for disabled commands (bug 760052)
+ if (!commandManager->IsCommandEnabled(
+ nsDependentCString(commandData.mXULCommandName), window)) {
+ return false;
+ }
+
+ MOZ_ASSERT(commandData.IsPasteCommand() ||
+ commandData.mCommand == Command::SelectAll);
+ nsresult rv =
+ commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Now, our target is fixed to the editor. So, we can use EditorCommand
+ // in EditorCommandTarget directly.
+
+ EditorCommandParamType paramType =
+ EditorCommand::GetParamType(commandData.mCommand);
+
+ // If we don't have meaningful parameter or the EditorCommand does not
+ // require additional parameter, we can use `DoCommand()`.
+ if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
+ MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
+ nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
+ // "true" or "false" here. So, we can use `DoCommandParam()` which takes
+ // a `bool` value.
+ if (!!(paramType & EditorCommandParamType::Bool)) {
+ MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
+ adjustedValue.EqualsLiteral("false"));
+ nsresult rv = editCommandTarget.DoCommandParam(
+ Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
+ // in this case. However, `paramType` may contain both `String` and
+ // `CString` but in such case, we should use `DoCommandParam()` which
+ // takes `nsAString`. So, we should check whether `paramType` contains
+ // `String` or not first.
+ if (!!(paramType & EditorCommandParamType::String)) {
+ MOZ_ASSERT(!adjustedValue.IsVoid());
+ nsresult rv =
+ editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Finally, `paramType` should have `CString`. We should use
+ // `DoCommandParam()` which takes `nsACString`.
+ if (!!(paramType & EditorCommandParamType::CString)) {
+ NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
+ MOZ_ASSERT(!utf8Value.IsVoid());
+ nsresult rv =
+ editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "Not yet implemented to handle new EditorCommandParamType");
+ return false;
+}
+
+bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandEnabled is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ // cut & copy are always allowed
+ if (commandData.IsCutOrCopyCommand()) {
+ return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
+ }
+
+ // Report false for restricted commands
+ if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+
+ if (editCommandTarget.IsEditor()) {
+ return editCommandTarget.IsCommandEnabled();
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ return commandManager->IsCommandEnabled(
+ nsDependentCString(commandData.mXULCommandName), window);
+}
+
+bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandIndeterm is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ if (commandData.mCommand == Command::DoNothing) {
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return false;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return false;
+ }
+ }
+
+ // If command does not have a state_mixed value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just don't throw regardless.
+ return params->GetBool("state_mixed");
+}
+
+bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandState is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
+ // Per spec, state is supported for styleWithCSS but not useCSS, so we just
+ // return false always.
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return false;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return false;
+ }
+ }
+
+ // handle alignment as a special case (possibly other commands too?)
+ // Alignment is special because the external api is individual
+ // commands but internally we use cmd_align with different
+ // parameters. When getting the state of this command, we need to
+ // return the boolean for this particular alignment rather than the
+ // string of 'which alignment is this?'
+ switch (commandData.mCommand) {
+ case Command::FormatJustifyLeft: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("left");
+ }
+ case Command::FormatJustifyRight: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("right");
+ }
+ case Command::FormatJustifyCenter: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("center");
+ }
+ case Command::FormatJustifyFull: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("justify");
+ }
+ default:
+ break;
+ }
+
+ // If command does not have a state_all value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just succeed and return false regardless.
+ return params->GetBool("state_all");
+}
+
+bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
+ CallerType aCallerType, ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandSupported is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ // Gecko technically supports all the clipboard commands including
+ // cut/copy/paste, but non-privileged content will be unable to call
+ // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
+ // may also be disallowed to be called from non-privileged content.
+ // For that reason, we report the support status of corresponding
+ // command accordingly.
+ if (aCallerType != CallerType::System) {
+ if (commandData.IsPasteCommand()) {
+ return false;
+ }
+ if (commandData.IsCutOrCopyCommand() &&
+ !StaticPrefs::dom_allow_cut_copy()) {
+ // XXXbz should we worry about correctly reporting "true" in the
+ // "restricted, but we're an addon with clipboardWrite permissions" case?
+ // See also nsContentUtils::IsCutCopyAllowed.
+ return false;
+ }
+ }
+
+ // aHTMLCommandName is supported if it can be converted to a Midas command
+ return true;
+}
+
+void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
+ nsAString& aValue, ErrorResult& aRv) {
+ aValue.Truncate();
+
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandValue is only supported on HTML documents");
+ return;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ // Return empty string
+ return;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
+ return;
+ }
+
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return;
+ }
+
+ if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
+ return;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return;
+ }
+ }
+
+ // If command does not have a state_attribute value, this call fails, and
+ // aValue will wind up being the empty string. This is fine -- we want to
+ // return "" in that case anyway (bug 738385), so we just return NS_OK
+ // regardless.
+ nsAutoCString result;
+ params->GetCString("state_attribute", result);
+ CopyUTF8toUTF16(result, aValue);
+}
+
+void Document::MaybeEditingStateChanged() {
+ if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
+ mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ EditingStateChanged();
+ } else if (!mInDestructor) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("Document::MaybeEditingStateChanged", this,
+ &Document::MaybeEditingStateChanged));
+ }
+ }
+}
+
+void Document::NotifyFetchOrXHRSuccess() {
+ if (mShouldNotifyFetchSuccess) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ }
+}
+
+void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
+ mShouldNotifyFetchSuccess = aShouldNotify;
+}
+
+void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
+ mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
+}
+
+void Document::TearingDownEditor() {
+ if (IsEditingOn()) {
+ mEditingState = EditingState::eTearingDown;
+ if (IsHTMLOrXHTML()) {
+ RemoveContentEditableStyleSheets();
+ }
+ }
+}
+
+nsresult Document::TurnEditingOff() {
+ NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isBeingDestroyed = false;
+ docshell->IsBeingDestroyed(&isBeingDestroyed);
+ if (isBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // turn editing off
+ rv = editSession->TearDownEditorOnWindow(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEditingState = EditingState::eOff;
+
+ // Editor resets selection since it is being destroyed. But if focus is
+ // still into editable control, we have to initialize selection again.
+ if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+ if (RefPtr<TextControlElement> textControlElement =
+ TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
+ if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
+ textEditor->ReinitializeSelection(*textControlElement);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
+ nsIDocShell* docShell = aWindow->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+ return docShell->GetPresShell() != nullptr;
+}
+
+HTMLEditor* Document::GetHTMLEditor() const {
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return nullptr;
+ }
+
+ return docshell->GetHTMLEditor();
+}
+
+nsresult Document::EditingStateChanged() {
+ if (mRemovedFromDocShell) {
+ return NS_OK;
+ }
+
+ if (mEditingState == EditingState::eSettingUp ||
+ mEditingState == EditingState::eTearingDown) {
+ // XXX We shouldn't recurse
+ return NS_OK;
+ }
+
+ const bool designMode = IsInDesignMode();
+ EditingState newState =
+ designMode ? EditingState::eDesignMode
+ : (mContentEditableCount > 0 ? EditingState::eContentEditable
+ : EditingState::eOff);
+ if (mEditingState == newState) {
+ // No changes in editing mode.
+ return NS_OK;
+ }
+
+ const bool thisDocumentHasFocus = ThisDocumentHasFocus();
+ if (newState == EditingState::eOff) {
+ // Editing is being turned off.
+ nsAutoScriptBlocker scriptBlocker;
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
+ NotifyEditableStateChange(*this);
+ nsresult rv = TurnEditingOff();
+ // If this document has focus and the editing state of this document
+ // becomes "off", it means that HTMLEditor won't handle any inputs nor
+ // modify the DOM tree. However, HTMLEditor may not receive `blur`
+ // event for this state change since this may occur without focus change.
+ // Therefore, let's notify HTMLEditor of this editing state change.
+ // Note that even if focusedElement is an editable text control element,
+ // it becomes not editable from HTMLEditor point of view since text
+ // control elements are manged by TextEditor.
+ RefPtr<Element> focusedElement =
+ nsFocusManager::GetFocusManager()
+ ? nsFocusManager::GetFocusManager()->GetFocusedElement()
+ : nullptr;
+ DebugOnly<nsresult> rvIgnored =
+ HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
+ htmlEditor, *this, focusedElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
+ "ignored");
+ return rv;
+ }
+
+ // Flush out style changes on our _parent_ document, if any, so that
+ // our check for a presshell won't get stale information.
+ if (mParentDocument) {
+ mParentDocument->FlushPendingNotifications(FlushType::Style);
+ }
+
+ // get editing session, make sure this is a strong reference so the
+ // window can't get deleted during the rest of this call.
+ const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // FlushPendingNotifications might destroy our docshell.
+ bool isBeingDestroyed = false;
+ docshell->IsBeingDestroyed(&isBeingDestroyed);
+ if (isBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
+ if (htmlEditor) {
+ // We might already have an editor if it was set up for mail, let's see
+ // if this is actually the case.
+ uint32_t flags = 0;
+ htmlEditor->GetFlags(&flags);
+ if (flags & nsIEditor::eEditorMailMask) {
+ // We already have a mail editor, then we should not attempt to create
+ // another one.
+ return NS_OK;
+ }
+ }
+
+ if (!HasPresShell(window)) {
+ // We should not make the window editable or setup its editor.
+ // It's probably style=display:none.
+ return NS_OK;
+ }
+
+ bool makeWindowEditable = mEditingState == EditingState::eOff;
+ bool spellRecheckAll = false;
+ bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
+ htmlEditor = nullptr;
+
+ {
+ EditingState oldState = mEditingState;
+ nsAutoEditingState push(this, EditingState::eSettingUp);
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // If we're entering the design mode from non-editable state, put the
+ // selection at the beginning of the document for compatibility reasons.
+ bool collapseSelectionAtBeginningOfDocument =
+ designMode && oldState == EditingState::eOff;
+ // However, mEditingState may be eOff even if there is some
+ // `contenteditable` area and selection has been initialized for it because
+ // mEditingState for `contenteditable` may have been scheduled to modify
+ // when safe. In such case, we should not reinitialize selection.
+ if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
+ Selection* selection =
+ presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
+ NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
+ if (selection && selection->RangeCount()) {
+ // Perhaps, we don't need to check whether the selection is in
+ // an editing host or not because all contents will be editable
+ // in designMode. (And we don't want to make this code so complicated
+ // because of legacy API.)
+ collapseSelectionAtBeginningOfDocument = false;
+ }
+ }
+
+ MOZ_ASSERT(mStyleSetFilled);
+
+ // Before making this window editable, we need to modify UA style sheet
+ // because new style may change whether focused element will be focusable
+ // or not.
+ if (IsHTMLOrXHTML()) {
+ AddContentEditableStyleSheetsToStyleSet(designMode);
+ }
+
+ if (designMode) {
+ // designMode is being turned on (overrides contentEditable).
+ spellRecheckAll = oldState == EditingState::eContentEditable;
+ }
+
+ // Adjust focused element with new style but blur event shouldn't be fired
+ // until mEditingState is modified with newState.
+ nsAutoScriptBlocker scriptBlocker;
+ if (designMode) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
+ bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
+ : !focusedContent->IsFocusable();
+ if (clearFocus) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ fm->ClearFocus(window);
+ // If we need to dispatch blur event, we should put off after
+ // modifying mEditingState since blur event handler may change
+ // designMode state again.
+ putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
+ }
+ }
+ }
+ }
+
+ if (makeWindowEditable) {
+ // Editing is being turned on (through designMode or contentEditable)
+ // Turn on editor.
+ // XXX This can cause flushing which can change the editing state, so make
+ // sure to avoid recursing.
+ rv = editSession->MakeWindowEditable(window, "html", false, false, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // XXX Need to call TearDownEditorOnWindow for all failures.
+ htmlEditor = docshell->GetHTMLEditor();
+ if (!htmlEditor) {
+ // Return NS_OK even though we've failed to create an editor here. This
+ // is so that the setter of designMode on non-HTML documents does not
+ // fail.
+ // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
+ // would detect that we can't support the mimetype if appropriate and
+ // would fall onto the eEditorErrorCantEditMimeType path.
+ return NS_OK;
+ }
+
+ if (collapseSelectionAtBeginningOfDocument) {
+ htmlEditor->BeginningOfDocument();
+ }
+
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::AddScriptBlocker();
+ }
+ }
+
+ mEditingState = newState;
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::RemoveScriptBlocker();
+ // If mEditingState is overwritten by another call and already disabled
+ // the editing, we shouldn't keep making window editable.
+ if (mEditingState == EditingState::eOff) {
+ return NS_OK;
+ }
+ }
+
+ if (makeWindowEditable) {
+ // TODO: We should do this earlier in this method.
+ // Previously, we called `ExecCommand` with `insertBrOnReturn` command
+ // whose argument is false here. Then, if it returns error, we
+ // stopped making it editable. However, after bug 1697078 fixed,
+ // `ExecCommand` returns error only when the document is not XHTML's
+ // nor HTML's. Therefore, we use same error handling for now.
+ if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
+ // Editor setup failed. Editing is not on after all.
+ // XXX Should we reset the editable flag on nodes?
+ editSession->TearDownEditorOnWindow(window);
+ mEditingState = EditingState::eOff;
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ // Set the editor to not insert <br> elements on return when in <p> elements
+ // by default.
+ htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
+ }
+
+ // Resync the editor's spellcheck state.
+ if (spellRecheckAll) {
+ nsCOMPtr<nsISelectionController> selectionController =
+ htmlEditor->GetSelectionController();
+ if (NS_WARN_IF(!selectionController)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
+ nsISelectionController::SELECTION_SPELLCHECK);
+ if (spellCheckSelection) {
+ spellCheckSelection->RemoveAllRanges(IgnoreErrors());
+ }
+ }
+ htmlEditor->SyncRealTimeSpell();
+
+ MaybeDispatchCheckKeyPressEventModelEvent();
+
+ // If this document keeps having focus and the HTMLEditor is in the design
+ // mode, it may not receive `focus` event for this editing state change since
+ // this may occur without a focus change. Therefore, let's notify HTMLEditor
+ // of this editing state change.
+ if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
+ ThisDocumentHasFocus()) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
+ " failed, but ignored");
+ }
+
+ return NS_OK;
+}
+
+// Helper class, used below in ChangeContentEditableCount().
+class DeferredContentEditableCountChangeEvent : public Runnable {
+ public:
+ DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
+ : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
+ mDoc(aDoc),
+ mElement(aElement) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ if (mElement && mElement->OwnerDoc() == mDoc) {
+ RefPtr<Document> doc = std::move(mDoc);
+ RefPtr<Element> element = std::move(mElement);
+ doc->DeferredContentEditableCountChange(element);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<Document> mDoc;
+ RefPtr<Element> mElement;
+};
+
+void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
+ NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
+ "Trying to decrement too much.");
+
+ mContentEditableCount += aChange;
+
+ nsContentUtils::AddScriptRunner(
+ new DeferredContentEditableCountChangeEvent(this, aElement));
+}
+
+void Document::DeferredContentEditableCountChange(Element* aElement) {
+ const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ const bool elementHasFocus =
+ aElement && fm && fm->GetFocusedElement() == aElement;
+ if (elementHasFocus) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ // When contenteditable of aElement is changed and HTMLEditor works with it
+ // or needs to start working with it, HTMLEditor may not receive `focus`
+ // event nor `blur` event because this may occur without a focus change.
+ // Therefore, we need to notify HTMLEditor of this contenteditable attribute
+ // change.
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
+ if (aElement->HasFlag(NODE_IS_EDITABLE)) {
+ if (htmlEditor) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
+ aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
+ "ignored");
+ }
+ } else {
+ DebugOnly<nsresult> rvIgnored =
+ HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
+ htmlEditor, *this, aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
+ "but ignored");
+ }
+ }
+
+ if (mParser ||
+ (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
+ return;
+ }
+
+ EditingState oldState = mEditingState;
+
+ nsresult rv = EditingStateChanged();
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (oldState == mEditingState &&
+ mEditingState == EditingState::eContentEditable) {
+ // We just changed the contentEditable state of a node, we need to reset
+ // the spellchecking state of that node.
+ if (aElement) {
+ if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
+ nsCOMPtr<nsIInlineSpellChecker> spellChecker;
+ rv = htmlEditor->GetInlineSpellChecker(false,
+ getter_AddRefs(spellChecker));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (spellChecker &&
+ aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
+ RefPtr<nsRange> range = nsRange::Create(aElement);
+ IgnoredErrorResult res;
+ range->SelectNode(*aElement, res);
+ if (res.Failed()) {
+ // The node might be detached from the document at this point,
+ // which would cause this call to fail. In this case, we can
+ // safely ignore the contenteditable count change.
+ return;
+ }
+
+ rv = spellChecker->SpellCheckRange(range);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+ }
+ }
+
+ // aElement causes creating new HTMLEditor and the element had and keep
+ // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
+ // need to notify HTMLEditor of it becomes editable.
+ if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
+ fm->GetFocusedElement() == aElement) {
+ if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
+ "ignored");
+ }
+ }
+}
+
+void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
+ // Currently, we need to check only when we're becoming editable for
+ // contenteditable.
+ if (mEditingState != EditingState::eContentEditable) {
+ return;
+ }
+
+ if (mHasBeenEditable) {
+ return;
+ }
+ mHasBeenEditable = true;
+
+ // Dispatch "CheckKeyPressEventModel" event. That is handled only by
+ // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
+ // with proper keypress event for the active web app.
+ WidgetEvent checkEvent(true, eUnidentifiedEvent);
+ checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
+ checkEvent.mFlags.mCancelable = false;
+ checkEvent.mFlags.mBubbles = false;
+ checkEvent.mFlags.mOnlySystemGroupDispatch = true;
+ // Post the event rather than dispatching it synchronously because we need
+ // a call of SetKeyPressEventModel() before first key input. Therefore, we
+ // can avoid paying unnecessary runtime cost for most web apps.
+ (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
+}
+
+void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ presShell->SetKeyPressEventModel(aKeyPressEventModel);
+}
+
+TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
+
+void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
+ MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
+ aFocusTime >= mLastFocusTime);
+ mLastFocusTime = aFocusTime;
+}
+
+void Document::GetReferrer(nsAString& aReferrer) const {
+ aReferrer.Truncate();
+ if (!mReferrerInfo) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
+ if (!referrer) {
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ CopyUTF8toUTF16(uri, aReferrer);
+}
+
+void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
+ aCookie.Truncate(); // clear current cookie in case service fails;
+ // no cookie isn't an error condition.
+
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, then reading cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ aRv.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return;
+ }
+
+ StorageAccess storageAccess = CookieAllowedForDocument(this);
+ if (storageAccess == StorageAccess::eDeny) {
+ return;
+ }
+
+ if (ShouldPartitionStorage(storageAccess) &&
+ !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
+ return;
+ }
+
+ // If the document is a cookie-averse Document... return the empty string.
+ if (IsCookieAverse()) {
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (service) {
+ nsAutoCString cookie;
+ service->GetCookieStringFromDocument(this, cookie);
+ // CopyUTF8toUTF16 doesn't handle error
+ // because it assumes that the input is valid.
+ UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
+ }
+}
+
+void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, then setting cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ aRv.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return;
+ }
+
+ StorageAccess storageAccess = CookieAllowedForDocument(this);
+ if (storageAccess == StorageAccess::eDeny) {
+ return;
+ }
+
+ if (ShouldPartitionStorage(storageAccess) &&
+ !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
+ return;
+ }
+
+ // If the document is a cookie-averse Document... do nothing.
+ if (IsCookieAverse()) {
+ return;
+ }
+
+ if (!mDocumentURI) {
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (!service) {
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 cookie(aCookie);
+ nsresult rv = service->SetCookieStringFromDocument(this, cookie);
+
+ // No warning messages here.
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
+ nsString(aCookie).get());
+ }
+}
+
+ReferrerPolicy Document::GetReferrerPolicy() const {
+ return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
+ : ReferrerPolicy::_empty;
+}
+
+void Document::GetAlinkColor(nsAString& aAlinkColor) {
+ aAlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetALink(aAlinkColor);
+ }
+}
+
+void Document::SetAlinkColor(const nsAString& aAlinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetALink(aAlinkColor);
+ }
+}
+
+void Document::GetLinkColor(nsAString& aLinkColor) {
+ aLinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetLink(aLinkColor);
+ }
+}
+
+void Document::SetLinkColor(const nsAString& aLinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetLink(aLinkColor);
+ }
+}
+
+void Document::GetVlinkColor(nsAString& aVlinkColor) {
+ aVlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetVLink(aVlinkColor);
+ }
+}
+
+void Document::SetVlinkColor(const nsAString& aVlinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetVLink(aVlinkColor);
+ }
+}
+
+void Document::GetBgColor(nsAString& aBgColor) {
+ aBgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetBgColor(aBgColor);
+ }
+}
+
+void Document::SetBgColor(const nsAString& aBgColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetBgColor(aBgColor);
+ }
+}
+
+void Document::GetFgColor(nsAString& aFgColor) {
+ aFgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetText(aFgColor);
+ }
+}
+
+void Document::SetFgColor(const nsAString& aFgColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetText(aFgColor);
+ }
+}
+
+void Document::CaptureEvents() {
+ WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
+}
+
+void Document::ReleaseEvents() {
+ WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
+}
+
+HTMLAllCollection* Document::All() {
+ if (!mAll) {
+ mAll = new HTMLAllCollection(this);
+ }
+ return mAll;
+}
+
+nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
+ if (mIsSrcdocDocument) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ return inStrmChan->GetSrcdocData(aSrcdocData);
+ }
+ }
+ aSrcdocData = VoidString();
+ return NS_OK;
+}
+
+Nullable<WindowProxyHolder> Document::GetDefaultView() const {
+ nsPIDOMWindowOuter* win = GetWindow();
+ if (!win) {
+ return nullptr;
+ }
+ return WindowProxyHolder(win->GetBrowsingContext());
+}
+
+nsIContent* Document::GetUnretargetedFocusedContent(
+ IncludeChromeOnly aIncludeChromeOnly) const {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+ if (!focusedContent) {
+ return nullptr;
+ }
+ // be safe and make sure the element is from this document
+ if (focusedContent->OwnerDoc() != this) {
+ return nullptr;
+ }
+ if (focusedContent->ChromeOnlyAccess() &&
+ aIncludeChromeOnly == IncludeChromeOnly::No) {
+ return focusedContent->FindFirstNonChromeOnlyAccessContent();
+ }
+ return focusedContent;
+}
+
+Element* Document::GetActiveElement() {
+ // Get the focused element.
+ Element* focusedElement = GetRetargetedFocusedElement();
+ if (focusedElement) {
+ return focusedElement;
+ }
+
+ // No focused element anywhere in this document. Try to get the BODY.
+ if (IsHTMLOrXHTML()) {
+ Element* bodyElement = AsHTMLDocument()->GetBody();
+ if (bodyElement) {
+ return bodyElement;
+ }
+ // Special case to handle the transition to XHTML from XUL documents
+ // where there currently isn't a body element, but we need to match the
+ // XUL behavior. This should be removed when bug 1540278 is resolved.
+ if (nsContentUtils::IsChromeDoc(this)) {
+ Element* docElement = GetDocumentElement();
+ if (docElement && docElement->IsXULElement()) {
+ return docElement;
+ }
+ }
+ // Because of IE compatibility, return null when html document doesn't have
+ // a body.
+ return nullptr;
+ }
+
+ // If we couldn't get a BODY, return the root element.
+ return GetDocumentElement();
+}
+
+Element* Document::GetCurrentScript() {
+ nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
+ return el;
+}
+
+void Document::ReleaseCapture() const {
+ // only release the capture if the caller can access it. This prevents a
+ // page from stopping a scrollbar grab for example.
+ nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
+ if (node && nsContentUtils::CanCallerAccess(node)) {
+ PresShell::ReleaseCapturingContent();
+ }
+}
+
+nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
+ if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
+ return mChromeXHRDocBaseURI;
+ }
+
+ return GetDocBaseURI();
+}
+
+void Document::SetBaseURI(nsIURI* aURI) {
+ if (!aURI && !mDocumentBaseURI) {
+ return;
+ }
+
+ // Don't do anything if the URI wasn't actually changed.
+ if (aURI && mDocumentBaseURI) {
+ bool equalBases = false;
+ mDocumentBaseURI->Equals(aURI, &equalBases);
+ if (equalBases) {
+ return;
+ }
+ }
+
+ mDocumentBaseURI = aURI;
+ mCachedURLData = nullptr;
+ RefreshLinkHrefs();
+}
+
+Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
+ const nsAString& aURI) {
+ RefPtr<nsIURI> resolvedURI;
+ MOZ_TRY(
+ NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
+ return OwningNonNull<nsIURI>(std::move(resolvedURI));
+}
+
+nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
+ if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
+ mCachedReferrerInfoForInternalCSSAndSVGResources =
+ ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
+ }
+ return mCachedReferrerInfoForInternalCSSAndSVGResources;
+}
+
+URLExtraData* Document::DefaultStyleAttrURLData() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mCachedURLData) {
+ mCachedURLData = new URLExtraData(
+ GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
+ NodePrincipal());
+ }
+ return mCachedURLData;
+}
+
+void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
+ if (mCharacterSet != aEncoding) {
+ mCharacterSet = aEncoding;
+ mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
+ RecomputeLanguageFromCharset();
+
+ if (nsPresContext* context = GetPresContext()) {
+ context->DocumentCharSetChanged(aEncoding);
+ }
+ }
+}
+
+void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
+ nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
+}
+
+void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
+ aData.Truncate();
+ const HeaderData* data = mHeaderData.get();
+ while (data) {
+ if (data->mField == aHeaderField) {
+ aData = data->mData;
+ break;
+ }
+ data = data->mNext.get();
+ }
+}
+
+void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
+ if (!aHeaderField) {
+ NS_ERROR("null headerField");
+ return;
+ }
+
+ if (!mHeaderData) {
+ if (!aData.IsEmpty()) { // don't bother storing empty string
+ mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
+ }
+ } else {
+ HeaderData* data = mHeaderData.get();
+ UniquePtr<HeaderData>* lastPtr = &mHeaderData;
+ bool found = false;
+ do { // look for existing and replace
+ if (data->mField == aHeaderField) {
+ if (!aData.IsEmpty()) {
+ data->mData.Assign(aData);
+ } else { // don't store empty string
+ // Note that data->mNext is moved to a temporary before the old value
+ // of *lastPtr is deleted.
+ *lastPtr = std::move(data->mNext);
+ }
+ found = true;
+
+ break;
+ }
+ lastPtr = &data->mNext;
+ data = lastPtr->get();
+ } while (data);
+
+ if (!aData.IsEmpty() && !found) {
+ // didn't find, append
+ *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerContentLanguage) {
+ CopyUTF16toUTF8(aData, mContentLanguage);
+ mMayNeedFontPrefsUpdate = true;
+ if (auto* presContext = GetPresContext()) {
+ presContext->ContentLanguageChanged();
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::origin_trial) {
+ mTrials.UpdateFromToken(aData, NodePrincipal());
+ if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
+ InitCOEP(mChannel);
+
+ // If we still don't have a WindowContext, WindowContext::OnNewDocument
+ // will take care of this.
+ if (WindowContext* ctx = GetWindowContext()) {
+ if (mEmbedderPolicy) {
+ Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
+ }
+ }
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
+ SetPreferredStyleSheetSet(aData);
+ }
+
+ if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
+ // We get into this code before we have a script global yet, so get to our
+ // container via mDocumentContainer.
+ if (mDocumentContainer) {
+ // Note: using mDocumentURI instead of mBaseURI here, for consistency
+ // (used to just use the current URI of our webnavigation, but that
+ // should really be the same thing). Note that this code can run
+ // before the current URI of the webnavigation has been updated, so we
+ // can't assert equality here.
+ mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
+ mAllowDNSPrefetch) {
+ // Chromium treats any value other than 'on' (case insensitive) as 'off'.
+ mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
+ }
+
+ if (aHeaderField == nsGkAtoms::handheldFriendly) {
+ mViewportType = Unknown;
+ }
+}
+
+void Document::SetEarlyHints(
+ nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
+ mEarlyHints = std::move(aEarlyHints);
+}
+
+void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
+ NotNull<const Encoding*>& aEncoding,
+ nsHtml5TreeOpExecutor* aExecutor) {
+ if (aChannel) {
+ nsAutoCString charsetVal;
+ nsresult rv = aChannel->GetContentCharset(charsetVal);
+ if (NS_SUCCEEDED(rv)) {
+ const Encoding* preferred = Encoding::ForLabel(charsetVal);
+ if (preferred) {
+ if (aExecutor && preferred == REPLACEMENT_ENCODING) {
+ aExecutor->ComplainAboutBogusProtocolCharset(this, false);
+ }
+ aEncoding = WrapNotNull(preferred);
+ aCharsetSource = kCharsetFromChannel;
+ return;
+ } else if (aExecutor && !charsetVal.IsEmpty()) {
+ aExecutor->ComplainAboutBogusProtocolCharset(this, true);
+ }
+ }
+ }
+}
+
+static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
+#ifdef DEBUG
+ for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
+ const Element* element = Element::FromNode(node);
+ if (!element) {
+ continue;
+ }
+ MOZ_ASSERT(!element->HasServoData());
+ }
+#endif
+}
+
+already_AddRefed<PresShell> Document::CreatePresShell(
+ nsPresContext* aContext, nsViewManager* aViewManager) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
+
+ NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
+
+ AssertNoStaleServoDataIn(*this);
+
+ RefPtr<PresShell> presShell = new PresShell(this);
+ // Note: we don't hold a ref to the shell (it holds a ref to us)
+ mPresShell = presShell;
+
+ if (!mStyleSetFilled) {
+ FillStyleSet();
+ }
+
+ presShell->Init(aContext, aViewManager);
+ if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
+ highlightRegistry->AddHighlightSelectionsToFrameSelection();
+ }
+ // Gaining a shell causes changes in how media queries are evaluated, so
+ // invalidate that.
+ aContext->MediaFeatureValuesChanged(
+ {MediaFeatureChange::kAllChanges},
+ MediaFeatureChangePropagation::JustThisDocument);
+
+ // Make sure to never paint if we belong to an invisible DocShell.
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell && docShell->IsInvisible()) {
+ presShell->SetNeverPainting(true);
+ }
+
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p with PressShell %p and DocShell %p", this,
+ presShell.get(), docShell.get()));
+
+ mExternalResourceMap.ShowViewers();
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ if (mDocumentL10n) {
+ // In case we already accumulated mutations,
+ // we'll trigger the refresh driver now.
+ mDocumentL10n->OnCreatePresShell();
+ }
+
+ if (HasAutoFocusCandidates()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+ // Now that we have a shell, we might have @font-face rules (the presence of a
+ // shell may change which rules apply to us). We don't need to do anything
+ // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
+ // is ready to update we'll flush the font set.
+ MarkUserFontSetDirty();
+
+ // Take the author style disabled state from the top browsing cvontext.
+ // (PageStyleChild.sys.mjs ensures this is up to date.)
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
+ }
+
+ return presShell.forget();
+}
+
+void Document::UpdateFrameRequestCallbackSchedulingState(
+ PresShell* aOldPresShell) {
+ // If this condition changes to depend on some other variable, make sure to
+ // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
+ // that variable can change. Also consider if you should change
+ // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
+ // condition.
+ bool shouldBeScheduled =
+ WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
+ if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
+ // nothing to do
+ return;
+ }
+
+ PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
+ MOZ_RELEASE_ASSERT(presShell);
+
+ nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
+ if (shouldBeScheduled) {
+ rd->ScheduleFrameRequestCallbacks(this);
+ } else {
+ rd->RevokeFrameRequestCallbacks(this);
+ }
+
+ mFrameRequestCallbacksScheduled = shouldBeScheduled;
+}
+
+void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
+ MOZ_ASSERT(aCallbacks.IsEmpty());
+ mFrameRequestManager.Take(aCallbacks);
+ // No need to manually remove ourselves from the refresh driver; it will
+ // handle that part. But we do have to update our state.
+ mFrameRequestCallbacksScheduled = false;
+}
+
+bool Document::ShouldThrottleFrameRequests() const {
+ if (mStaticCloneCount > 0) {
+ // Even if we're not visible, a static clone may be, so run at full speed.
+ return false;
+ }
+
+ if (Hidden()) {
+ // We're not visible (probably in a background tab or the bf cache).
+ return true;
+ }
+
+ if (!mPresShell) {
+ // Can't do anything smarter. We don't run frame requests in documents
+ // without a pres shell anyways.
+ return false;
+ }
+
+ if (!mPresShell->IsActive()) {
+ // The pres shell is not active (we're an invisible OOP iframe or such), so
+ // throttle.
+ return true;
+ }
+
+ if (mPresShell->IsPaintingSuppressed()) {
+ // Historically we have throttled frame requests until we've painted at
+ // least once, so keep doing that.
+ return true;
+ }
+
+ Element* el = GetEmbedderElement();
+ if (!el) {
+ // If we're not in-process, our refresh driver is throttled separately (via
+ // PresShell::SetIsActive, so not much more we can do here.
+ return false;
+ }
+
+ if (!StaticPrefs::layout_throttle_in_process_iframes()) {
+ return false;
+ }
+
+ // Note that because we have to scroll this document into view at least once
+ // to unthrottle it, we will drop one requestAnimationFrame frame when a
+ // document that previously wasn't visible scrolls into view. This is
+ // acceptable / unlikely to be human-perceivable, though we could improve on
+ // it if needed by adding an intersection margin or something of that sort.
+ const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
+ *el->OwnerDoc(), /* aRoot = */ nullptr, /* aMargin = */ nullptr);
+ const IntersectionOutput output =
+ DOMIntersectionObserver::Intersect(input, *el);
+ return !output.Intersects();
+}
+
+void Document::DeletePresShell() {
+ mExternalResourceMap.HideViewers();
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
+ presContext->RefreshDriver()->CancelFlushAutoFocus(this);
+ }
+
+ // When our shell goes away, request that all our images be immediately
+ // discarded, so we don't carry around decoded image data for a document we
+ // no longer intend to paint.
+ ImageTracker()->RequestDiscardAll();
+
+ // Now that we no longer have a shell, we need to forget about any FontFace
+ // objects for @font-face rules that came from the style set. There's no need
+ // to call EnsureStyleFlush either, the shell is going away anyway, so there's
+ // no point on it.
+ MarkUserFontSetDirty();
+
+ if (mResizeObserverController) {
+ mResizeObserverController->ShellDetachedFromDocument();
+ }
+
+ if (IsEditingOn()) {
+ TurnEditingOff();
+ }
+
+ PresShell* oldPresShell = mPresShell;
+ mPresShell = nullptr;
+ UpdateFrameRequestCallbackSchedulingState(oldPresShell);
+
+ ClearStaleServoData();
+ AssertNoStaleServoDataIn(*this);
+
+ mStyleSet->ShellDetachedFromDocument();
+ mStyleSetFilled = false;
+ mQuirkSheetAdded = false;
+ mContentEditableSheetAdded = false;
+ mDesignModeSheetAdded = false;
+}
+
+void Document::DisallowBFCaching(uint32_t aStatus) {
+ NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
+ if (!mBFCacheDisallowed) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SendUpdateBFCacheStatus(aStatus, 0);
+ }
+ }
+ mBFCacheDisallowed = true;
+}
+
+void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
+ MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
+
+ if (mPresShell) {
+ if (aEntry) {
+ mPresShell->StopObservingRefreshDriver();
+ } else if (mBFCacheEntry) {
+ mPresShell->StartObservingRefreshDriver();
+ }
+ }
+ mBFCacheEntry = aEntry;
+}
+
+bool Document::RemoveFromBFCacheSync() {
+ bool removed = false;
+ if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
+ entry->RemoveFromBFCacheSync();
+ removed = true;
+ } else if (!IsCurrentActiveDocument()) {
+ // In the old bfcache implementation while the new page is loading, but
+ // before nsIContentViewer.show() has been called, the previous page doesn't
+ // yet have nsIBFCacheEntry. However, the previous page isn't the current
+ // active document anymore.
+ DisallowBFCaching();
+ removed = true;
+ }
+
+ if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ if (bc->IsInBFCache()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ // IPC is asynchronous but the caller is supposed to check the return
+ // value. The reason for 'Sync' in the method name is that the old
+ // implementation may run scripts. There is Async variant in
+ // the old session history implementation for the cases where
+ // synchronous operation isn't safe.
+ cc->SendRemoveFromBFCache(bc->Top());
+ removed = true;
+ }
+ }
+ }
+ return removed;
+}
+
+static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
+ SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
+
+ NS_RELEASE(e->mKey);
+ if (e->mSubDocument) {
+ e->mSubDocument->SetParentDocument(nullptr);
+ NS_RELEASE(e->mSubDocument);
+ }
+}
+
+static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
+ SubDocMapEntry* e =
+ const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
+
+ e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
+ NS_ADDREF(e->mKey);
+
+ e->mSubDocument = nullptr;
+}
+
+nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
+ NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
+
+ if (!aSubDoc) {
+ // aSubDoc is nullptr, remove the mapping
+
+ if (mSubDocuments) {
+ mSubDocuments->Remove(aElement);
+ }
+ } else {
+ if (!mSubDocuments) {
+ // Create a new hashtable
+
+ static const PLDHashTableOps hash_table_ops = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
+
+ mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
+ }
+
+ // Add a mapping to the hash table
+ auto entry =
+ static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
+
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (entry->mSubDocument) {
+ entry->mSubDocument->SetParentDocument(nullptr);
+
+ // Release the old sub document
+ NS_RELEASE(entry->mSubDocument);
+ }
+
+ entry->mSubDocument = aSubDoc;
+ NS_ADDREF(entry->mSubDocument);
+
+ aSubDoc->SetParentDocument(this);
+ }
+
+ return NS_OK;
+}
+
+Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
+ if (mSubDocuments && aContent->IsElement()) {
+ auto entry = static_cast<SubDocMapEntry*>(
+ mSubDocuments->Search(aContent->AsElement()));
+
+ if (entry) {
+ return entry->mSubDocument;
+ }
+ }
+
+ return nullptr;
+}
+
+Element* Document::GetEmbedderElement() const {
+ // We check if we're the active document in our BrowsingContext
+ // by comparing against its document, rather than checking if the
+ // WindowContext is cached, since mWindow may be null when we're
+ // called (such as in nsPresContext::Init).
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
+ }
+
+ return nullptr;
+}
+
+Element* Document::GetRootElement() const {
+ return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
+ ? mCachedRootElement
+ : GetRootElementInternal();
+}
+
+Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
+
+Element* Document::GetRootElementInternal() const {
+ // We invoke GetRootElement() immediately before the servo traversal, so we
+ // should always have a cache hit from Servo.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Loop backwards because any non-elements, such as doctypes and PIs
+ // are likely to appear before the root element.
+ for (nsIContent* child = GetLastChild(); child;
+ child = child->GetPreviousSibling()) {
+ if (Element* element = Element::FromNode(child)) {
+ const_cast<Document*>(this)->mCachedRootElement = element;
+ return element;
+ }
+ }
+
+ const_cast<Document*>(this)->mCachedRootElement = nullptr;
+ return nullptr;
+}
+
+void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
+ bool aNotify, ErrorResult& aRv) {
+ if (aKid->IsElement() && GetRootElement()) {
+ NS_WARNING("Inserting root element when we already have one");
+ aRv.ThrowHierarchyRequestError("There is already a root element.");
+ return;
+ }
+
+ nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
+}
+
+void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
+ Maybe<mozAutoDocUpdate> updateBatch;
+ if (aKid->IsElement()) {
+ updateBatch.emplace(this, aNotify);
+ // Destroy the link map up front before we mess with the child list.
+ DestroyElementMaps();
+ }
+
+ // Preemptively clear mCachedRootElement, since we may be about to remove it
+ // from our child list, and we don't want to return this maybe-obsolete value
+ // from any GetRootElement() calls that happen inside of RemoveChildNode().
+ // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
+ // GetRootElement() calls until after it's removed the child from mChildren.
+ // Any call before that point would restore this soon-to-be-obsolete cached
+ // answer, and our clearing here would be fruitless.)
+ mCachedRootElement = nullptr;
+ nsINode::RemoveChildNode(aKid, aNotify);
+ MOZ_ASSERT(mCachedRootElement != aKid,
+ "Stale pointer in mCachedRootElement, after we tried to clear it "
+ "(maybe somebody called GetRootElement() too early?)");
+}
+
+void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
+ if (mStyleSetFilled) {
+ mStyleSet->AddDocStyleSheet(aSheet);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
+ mStyleSet->RecordShadowStyleChange(aShadowRoot);
+ ApplicableStylesChanged();
+}
+
+void Document::ApplicableStylesChanged() {
+ // TODO(emilio): if we decide to resolve style in display: none iframes, then
+ // we need to always track style changes and remove the mStyleSetFilled.
+ if (!mStyleSetFilled) {
+ return;
+ }
+
+ MarkUserFontSetDirty();
+ PresShell* ps = GetPresShell();
+ if (!ps) {
+ return;
+ }
+
+ ps->EnsureStyleFlush();
+ nsPresContext* pc = ps->GetPresContext();
+ if (!pc) {
+ return;
+ }
+
+ pc->MarkCounterStylesDirty();
+ pc->MarkFontFeatureValuesDirty();
+ pc->MarkFontPaletteValuesDirty();
+ pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
+}
+
+void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(aSheet);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
+ DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
+
+ if (aSheet.IsApplicable()) {
+ AddStyleSheetToStyleSets(aSheet);
+ }
+}
+
+void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
+ const bool applicable = aSheet.IsApplicable();
+ // If we're actually in the document style sheet list
+ if (StyleOrderIndexOfSheet(aSheet) >= 0) {
+ if (applicable) {
+ AddStyleSheetToStyleSets(aSheet);
+ } else {
+ RemoveStyleSheetFromStyleSets(aSheet);
+ }
+ }
+
+ PostStyleSheetApplicableStateChangeEvent(aSheet);
+
+ if (!mSSApplicableStateNotificationPending) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
+ "Document::NotifyStyleSheetApplicableStateChanged", this,
+ &Document::NotifyStyleSheetApplicableStateChanged);
+ mSSApplicableStateNotificationPending =
+ NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
+ }
+}
+
+void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
+ if (!StyleSheetChangeEventsEnabled()) {
+ return;
+ }
+
+ StyleSheetApplicableStateChangeEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mStylesheet = &aSheet;
+ init.mApplicable = aSheet.IsApplicable();
+
+ RefPtr<StyleSheetApplicableStateChangeEvent> event =
+ StyleSheetApplicableStateChangeEvent::Constructor(
+ this, u"StyleSheetApplicableStateChanged"_ns, init);
+ event->SetTrusted(true);
+ event->SetTarget(this);
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
+ asyncDispatcher->PostDOMEvent();
+}
+
+void Document::NotifyStyleSheetApplicableStateChanged() {
+ mSSApplicableStateNotificationPending = false;
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
+ }
+}
+
+static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ nsIURI* aSheetURI) {
+ for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
+ bool bEqual;
+ nsIURI* uri = aSheets[i]->GetSheetURI();
+
+ if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
+ return i;
+ }
+
+ return -1;
+}
+
+nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) {
+ MOZ_ASSERT(aSheetURI, "null arg");
+
+ // Checking if we have loaded this one already.
+ if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
+ return NS_ERROR_INVALID_ARG;
+
+ // Loading the sheet sync.
+ RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
+
+ css::SheetParsingMode parsingMode;
+ switch (aType) {
+ case Document::eAgentSheet:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case Document::eUserSheet:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case Document::eAuthorSheet:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ MOZ_CRASH("impossible value for aType");
+ }
+
+ auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+
+ RefPtr<StyleSheet> sheet = result.unwrap();
+
+ sheet->SetAssociatedDocumentOrShadowRoot(this);
+ MOZ_ASSERT(sheet->IsApplicable());
+
+ return AddAdditionalStyleSheet(aType, sheet);
+}
+
+nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
+ StyleSheet* aSheet) {
+ if (mAdditionalSheets[aType].Contains(aSheet)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aSheet->IsApplicable()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mAdditionalSheets[aType].AppendElement(aSheet);
+
+ if (mStyleSetFilled) {
+ mStyleSet->AppendStyleSheet(*aSheet);
+ ApplicableStylesChanged();
+ }
+ return NS_OK;
+}
+
+void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) {
+ MOZ_ASSERT(aSheetURI);
+
+ nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
+
+ int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
+ if (i >= 0) {
+ RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
+ sheets.RemoveElementAt(i);
+
+ if (!mIsGoingAway) {
+ MOZ_ASSERT(sheetRef->IsApplicable());
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(*sheetRef);
+ ApplicableStylesChanged();
+ }
+ }
+ sheetRef->ClearAssociatedDocumentOrShadowRoot();
+ }
+}
+
+nsIGlobalObject* Document::GetScopeObject() const {
+ nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
+ return scope;
+}
+
+DocGroup* Document::GetDocGroupOrCreate() {
+ if (!mDocGroup && GetBrowsingContext()) {
+ BrowsingContextGroup* group = GetBrowsingContext()->Group();
+ MOZ_ASSERT(group);
+
+ nsAutoCString docGroupKey;
+ nsresult rv = mozilla::dom::DocGroup::GetKey(
+ NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
+ docGroupKey);
+ if (NS_SUCCEEDED(rv)) {
+ mDocGroup = group->AddDocument(docGroupKey, this);
+ }
+ }
+ return mDocGroup;
+}
+
+void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
+ mScopeObject = do_GetWeakReference(aGlobal);
+ if (aGlobal) {
+ mHasHadScriptHandlingObject = true;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (!window) {
+ return;
+ }
+ BrowsingContextGroup* browsingContextGroup =
+ window->GetBrowsingContextGroup();
+
+ // We should already have the principal, and now that we have been added
+ // to a window, we should be able to join a DocGroup!
+ nsAutoCString docGroupKey;
+ nsresult rv = mozilla::dom::DocGroup::GetKey(
+ NodePrincipal(),
+ browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
+ if (mDocGroup) {
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
+ }
+ MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
+ browsingContextGroup);
+ } else {
+ mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
+
+ MOZ_ASSERT(mDocGroup);
+ }
+
+ MOZ_ASSERT_IF(
+ mNodeInfoManager->GetArenaAllocator(),
+ mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
+ }
+}
+
+bool Document::ContainsEMEContent() {
+ nsPIDOMWindowInner* win = GetInnerWindow();
+ // Note this case is different from checking just media elements in that
+ // it covers when we've created MediaKeys but not associated them with a
+ // media element.
+ return win && win->HasActiveMediaKeysInstance();
+}
+
+bool Document::ContainsMSEContent() {
+ bool containsMSE = false;
+
+ auto check = [&containsMSE](nsISupports* aSupports) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
+ if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
+ RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
+ if (ms) {
+ containsMSE = true;
+ }
+ }
+ };
+
+ EnumerateActivityObservers(check);
+ return containsMSE;
+}
+
+static void NotifyActivityChangedCallback(nsISupports* aSupports) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
+ if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
+ mediaElem->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
+ do_QueryInterface(aSupports));
+ if (objectLoadingContent) {
+ nsObjectLoadingContent* olc =
+ static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
+ olc->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
+ do_QueryInterface(aSupports));
+ if (objectDocumentActivity) {
+ objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
+ } else {
+ nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
+ do_QueryInterface(aSupports));
+ if (imageLoadingContent) {
+ auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
+ ilc->NotifyOwnerDocumentActivityChanged();
+ }
+ }
+}
+
+void Document::NotifyActivityChanged() {
+ EnumerateActivityObservers(NotifyActivityChangedCallback);
+}
+
+bool Document::IsTopLevelWindowInactive() const {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ return !bc->GetIsActiveBrowserWindow();
+ }
+
+ return false;
+}
+
+void Document::SetContainer(nsDocShell* aContainer) {
+ if (aContainer) {
+ mDocumentContainer = aContainer;
+ } else {
+ mDocumentContainer = WeakPtr<nsDocShell>();
+ }
+
+ mInChromeDocShell =
+ aContainer && aContainer->GetBrowsingContext()->IsChrome();
+
+ NotifyActivityChanged();
+
+ // IsTopLevelWindowInactive depends on the docshell, so
+ // update the cached value now that it's available.
+ UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
+ if (!aContainer) {
+ return;
+ }
+
+ BrowsingContext* context = aContainer->GetBrowsingContext();
+ MOZ_ASSERT_IF(context && mDocGroup,
+ context->Group() == mDocGroup->GetBrowsingContextGroup());
+ if (context && context->IsContent()) {
+ SetIsTopLevelContentDocument(context->IsTopContent());
+ SetIsContentDocument(true);
+ } else {
+ SetIsTopLevelContentDocument(false);
+ SetIsContentDocument(false);
+ }
+}
+
+nsISupports* Document::GetContainer() const {
+ return static_cast<nsIDocShell*>(mDocumentContainer);
+}
+
+void Document::SetScriptGlobalObject(
+ nsIScriptGlobalObject* aScriptGlobalObject) {
+ MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
+ mAnimationController->IsPausedByType(
+ SMILTimeContainer::PAUSE_PAGEHIDE |
+ SMILTimeContainer::PAUSE_BEGIN),
+ "Clearing window pointer while animations are unpaused");
+
+ if (mScriptGlobalObject && !aScriptGlobalObject) {
+ // We're detaching from the window. We need to grab a pointer to
+ // our layout history state now.
+ mLayoutHistoryState = GetLayoutHistoryState();
+
+ // Also make sure to remove our onload blocker now if we haven't done it yet
+ if (mOnloadBlockCount != 0) {
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+ }
+ }
+
+ if (GetController().isSome()) {
+ if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
+ loader->ClearCacheForControlledDocument(this);
+ }
+
+ // We may become controlled again if this document comes back out
+ // of bfcache. Clear our state to allow that to happen. Only
+ // clear this flag if we are actually controlled, though, so pages
+ // that were force reloaded don't become controlled when they
+ // come out of bfcache.
+ mMaybeServiceWorkerControlled = false;
+ }
+
+ if (GetWindowContext()) {
+ // The document is about to lose its window, so this is a good time to
+ // send our page use counters, while we still have access to our
+ // WindowContext.
+ //
+ // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
+ // catches some cases of documents losing their window that don't
+ // get in here.)
+ SendPageUseCounters();
+ }
+ }
+
+ // BlockOnload() might be called before mScriptGlobalObject is set.
+ // We may need to add the blocker once mScriptGlobalObject is set.
+ bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
+
+ mScriptGlobalObject = aScriptGlobalObject;
+
+ if (needOnloadBlocker) {
+ EnsureOnloadBlocker();
+ }
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ if (aScriptGlobalObject) {
+ // Go back to using the docshell for the layout history state
+ mLayoutHistoryState = nullptr;
+ SetScopeObject(aScriptGlobalObject);
+ mHasHadDefaultView = true;
+
+ if (mAllowDNSPrefetch) {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+#ifdef DEBUG
+ nsCOMPtr<nsIWebNavigation> webNav =
+ do_GetInterface(aScriptGlobalObject);
+ NS_ASSERTION(SameCOMIdentity(webNav, docShell),
+ "Unexpected container or script global?");
+#endif
+ bool allowDNSPrefetch;
+ docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
+ mAllowDNSPrefetch = allowDNSPrefetch;
+ }
+ }
+
+ // If we are set in a window that is already focused we should remember this
+ // as the time the document gained focus.
+ if (HasFocus(IgnoreErrors())) {
+ SetLastFocusTime(TimeStamp::Now());
+ }
+ }
+
+ // Remember the pointer to our window (or lack there of), to avoid
+ // having to QI every time it's asked for.
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
+ mWindow = window;
+
+ // Now that we know what our window is, we can flush the CSP errors to the
+ // Web Console. We are flushing all messages that occurred and were stored in
+ // the queue prior to this point.
+ if (mCSP) {
+ static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(GetChannel());
+ if (internalChannel) {
+ nsCOMArray<nsISecurityConsoleMessage> messages;
+ DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ SendToConsole(messages);
+ }
+
+ // Set our visibility state, but do not fire the event. This is correct
+ // because either we're coming out of bfcache (in which case IsVisible() will
+ // still test false at this point and no state change will happen) or we're
+ // doing the initial document load and don't want to fire the event for this
+ // change.
+ //
+ // When the visibility is changed, notify it to observers.
+ // Some observers need the notification, for example HTMLMediaElement uses
+ // it to update internal media resource allocation.
+ // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
+ // creation are already done before Document::SetScriptGlobalObject() call.
+ // MediaDecoder decides whether starting decoding is decided based on
+ // document's visibility. When the MediaDecoder is created,
+ // Document::SetScriptGlobalObject() is not yet called and document is
+ // hidden state. Therefore the MediaDecoder decides that decoding is
+ // not yet necessary. But soon after Document::SetScriptGlobalObject()
+ // call, the document becomes not hidden. At the time, MediaDecoder needs
+ // to know it and needs to start updating decoding.
+ UpdateVisibilityState(DispatchVisibilityChange::No);
+
+ // The global in the template contents owner document should be the same.
+ if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
+ mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
+ }
+
+ // Tell the script loader about the new global object.
+ if (mScriptLoader && !IsTemplateContentsOwner()) {
+ mScriptLoader->SetGlobalObject(mScriptGlobalObject);
+ }
+
+ if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
+ mScriptGlobalObject && GetChannel()) {
+ // If we are shift-reloaded, don't associate with a ServiceWorker.
+ if (mDocumentContainer->IsForceReloading()) {
+ NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+ return;
+ }
+
+ mMaybeServiceWorkerControlled = true;
+ }
+}
+
+nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
+ MOZ_ASSERT(!mScriptGlobalObject,
+ "Do not call this when mScriptGlobalObject is set!");
+ if (mHasHadDefaultView) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScopeObject);
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
+ if (win) {
+ nsPIDOMWindowOuter* outer = win->GetOuterWindow();
+ if (!outer || outer->GetCurrentInnerWindow() != win) {
+ NS_WARNING("Wrong inner/outer window combination!");
+ return nullptr;
+ }
+ }
+ return scriptHandlingObject;
+}
+void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
+ NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
+ "Wrong script object!");
+ if (aScriptObject) {
+ SetScopeObject(aScriptObject);
+ mHasHadDefaultView = false;
+ }
+}
+
+nsPIDOMWindowOuter* Document::GetWindowInternal() const {
+ MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
+ // Let's use mScriptGlobalObject. Even if the document is already removed from
+ // the docshell, the outer window might be still obtainable from the it.
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ if (mRemovedFromDocShell) {
+ // The docshell returns the outer window we are done.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
+ if (kungFuDeathGrip) {
+ win = kungFuDeathGrip->GetWindow();
+ }
+ } else {
+ if (nsCOMPtr<nsPIDOMWindowInner> inner =
+ do_QueryInterface(mScriptGlobalObject)) {
+ // mScriptGlobalObject is always the inner window, let's get the outer.
+ win = inner->GetOuterWindow();
+ }
+ }
+
+ return win;
+}
+
+bool Document::InternalAllowXULXBL() {
+ if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
+ mAllowXULXBL = eTriTrue;
+ return true;
+ }
+
+ mAllowXULXBL = eTriFalse;
+ return false;
+}
+
+// Note: We don't hold a reference to the document observer; we assume
+// that it has a live reference to the document.
+void Document::AddObserver(nsIDocumentObserver* aObserver) {
+ NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
+ "Observer already in the list");
+ mObservers.AppendElement(aObserver);
+ AddMutationObserver(aObserver);
+}
+
+bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
+ // If we're in the process of destroying the document (and we're
+ // informing the observers of the destruction), don't remove the
+ // observers from the list. This is not a big deal, since we
+ // don't hold a live reference to the observers.
+ if (!mInDestructor) {
+ RemoveMutationObserver(aObserver);
+ return mObservers.RemoveElement(aObserver);
+ }
+
+ return mObservers.Contains(aObserver);
+}
+
+void Document::BeginUpdate() {
+ ++mUpdateNestLevel;
+ nsContentUtils::AddScriptBlocker();
+ NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
+}
+
+void Document::EndUpdate() {
+ const bool reset = !mPendingMaybeEditingStateChanged;
+ mPendingMaybeEditingStateChanged = true;
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
+
+ --mUpdateNestLevel;
+
+ nsContentUtils::RemoveScriptBlocker();
+
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->MaybeBroadcast();
+ }
+
+ if (reset) {
+ mPendingMaybeEditingStateChanged = false;
+ }
+ MaybeEditingStateChanged();
+}
+
+void Document::BeginLoad() {
+ if (IsEditingOn()) {
+ // Reset() blows away all event listeners in the document, and our
+ // editor relies heavily on those. Midas is turned on, to make it
+ // work, re-initialize it to give it a chance to add its event
+ // listeners again.
+
+ TurnEditingOff();
+ EditingStateChanged();
+ }
+
+ MOZ_ASSERT(!mDidCallBeginLoad);
+ mDidCallBeginLoad = true;
+
+ // Block onload here to prevent having to deal with blocking and
+ // unblocking it while we know the document is loading.
+ BlockOnload();
+ mDidFireDOMContentLoaded = false;
+ BlockDOMContentLoaded();
+
+ if (mScriptLoader) {
+ mScriptLoader->BeginDeferringScripts();
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
+}
+
+void Document::MozSetImageElement(const nsAString& aImageElementId,
+ Element* aElement) {
+ if (aImageElementId.IsEmpty()) return;
+
+ // Hold a script blocker while calling SetImageElement since that can call
+ // out to id-observers
+ nsAutoScriptBlocker scriptBlocker;
+
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
+ if (entry) {
+ entry->SetImageElement(aElement);
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+ }
+}
+
+void Document::DispatchContentLoadedEvents() {
+ // If you add early returns from this method, make sure you're
+ // calling UnblockOnload properly.
+
+ // Unpin references to preloaded images
+ mPreloadingImages.Clear();
+
+ // DOM manipulation after content loaded should not care if the element
+ // came from the preloader.
+ mPreloadedPreconnects.Clear();
+
+ if (mTiming) {
+ mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
+ }
+
+ // Dispatch observer notification to notify observers document is interactive.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal()
+ ? "chrome-document-interactive"
+ : "content-document-interactive",
+ nullptr);
+ }
+
+ // Fire a DOM event notifying listeners that this document has been
+ // loaded (excluding images and other loads initiated by this
+ // document).
+ nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
+ u"DOMContentLoaded"_ns, CanBubble::eYes,
+ Cancelable::eNo);
+
+ if (auto* const window = GetInnerWindow()) {
+ const RefPtr<ServiceWorkerContainer> serviceWorker =
+ window->Navigator()->ServiceWorker();
+
+ // This could cause queued messages from a service worker to get
+ // dispatched on serviceWorker.
+ serviceWorker->StartMessages();
+ }
+
+ if (MayStartLayout()) {
+ MaybeResolveReadyForIdle();
+ }
+
+ nsIDocShell* docShell = GetDocShell();
+
+ if (TimelineConsumers::HasConsumer(docShell)) {
+ TimelineConsumers::AddMarkerForDocShell(
+ docShell,
+ MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
+ }
+
+ if (mTiming) {
+ mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
+ }
+
+ // If this document is a [i]frame, fire a DOMFrameContentLoaded
+ // event on all parent documents notifying that the HTML (excluding
+ // other external files such as images and stylesheets) in a frame
+ // has finished loading.
+
+ // target_frame is the [i]frame element that will be used as the
+ // target for the event. It's the [i]frame whose content is done
+ // loading.
+ nsCOMPtr<Element> target_frame = GetEmbedderElement();
+
+ if (target_frame && target_frame->IsInComposedDoc()) {
+ nsCOMPtr<Document> parent = target_frame->OwnerDoc();
+ while (parent) {
+ RefPtr<Event> event;
+ if (parent) {
+ IgnoredErrorResult ignored;
+ event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
+ }
+
+ if (event) {
+ event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
+
+ event->SetTarget(target_frame);
+ event->SetTrusted(true);
+
+ // To dispatch this event we must manually call
+ // EventDispatcher::Dispatch() on the ancestor document since the
+ // target is not in the same document, so the event would never reach
+ // the ancestor document if we used the normal event
+ // dispatching code.
+
+ WidgetEvent* innerEvent = event->WidgetEventPtr();
+ if (innerEvent) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
+ // TODO: Bug 1506441
+ EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(parent)),
+ context, innerEvent, event, &status);
+ }
+ }
+ }
+
+ parent = parent->GetInProcessParentDocument();
+ }
+ }
+
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner) {
+ inner->NoteDOMContentLoaded();
+ }
+
+ // TODO
+ if (mMaybeServiceWorkerControlled) {
+ using mozilla::dom::ServiceWorkerManager;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ Maybe<ClientInfo> clientInfo = GetClientInfo();
+ if (clientInfo.isSome()) {
+ swm->MaybeCheckNavigationUpdate(clientInfo.ref());
+ }
+ }
+ }
+
+ if (mSetCompleteAfterDOMContentLoaded) {
+ SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
+ mSetCompleteAfterDOMContentLoaded = false;
+ }
+
+ UnblockOnload(true);
+}
+
+void Document::EndLoad() {
+ bool turnOnEditing =
+ mParser && (IsInDesignMode() || mContentEditableCount > 0);
+
+#if defined(DEBUG)
+ // only assert if nothing stopped the load on purpose
+ if (!mParserAborted) {
+ nsContentSecurityUtils::AssertAboutPageHasCSP(this);
+ }
+#endif
+
+ // EndLoad may have been called without a matching call to BeginLoad, in the
+ // case of a failed parse (for example, due to timeout). In such a case, we
+ // still want to execute part of this code to do appropriate cleanup, but we
+ // gate part of it because it is intended to match 1-for-1 with calls to
+ // BeginLoad. We have an explicit flag bit for this purpose, since it's
+ // complicated and error prone to derive this condition from other related
+ // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
+
+ // Part 1: Code that always executes to cleanup end of parsing, whether
+ // that parsing was successful or not.
+
+ // Drop the ref to our parser, if any, but keep hold of the sink so that we
+ // can flush it from FlushPendingNotifications as needed. We might have to
+ // do that to get a StartLayout() to happen.
+ if (mParser) {
+ mWeakSink = do_GetWeakReference(mParser->GetContentSink());
+ mParser = nullptr;
+ }
+
+ // Update the attributes on the PerformanceNavigationTiming before notifying
+ // the onload observers.
+ if (nsPIDOMWindowInner* window = GetInnerWindow()) {
+ if (RefPtr<Performance> performance = window->GetPerformance()) {
+ performance->UpdateNavigationTimingEntry();
+ }
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
+
+ // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
+
+ if (!mDidCallBeginLoad) {
+ return;
+ }
+ mDidCallBeginLoad = false;
+
+ UnblockDOMContentLoaded();
+
+ if (turnOnEditing) {
+ EditingStateChanged();
+ }
+
+ if (!GetWindow()) {
+ // This is a document that's not in a window. For example, this could be an
+ // XMLHttpRequest responseXML document, or a document created via DOMParser
+ // or DOMImplementation. We don't reach this code normally for such
+ // documents (which is not obviously correct), but can reach it via
+ // document.open()/document.close().
+ //
+ // Such documents don't fire load events, but per spec should set their
+ // readyState to "complete" when parsing and all loading of subresources is
+ // done. Parsing is done now, and documents not in a window don't load
+ // subresources, so just go ahead and mark ourselves as complete.
+ SetReadyStateInternal(Document::READYSTATE_COMPLETE,
+ /* updateTimingInformation = */ false);
+
+ // Reset mSkipLoadEventAfterClose just in case.
+ mSkipLoadEventAfterClose = false;
+ }
+}
+
+void Document::UnblockDOMContentLoaded() {
+ MOZ_ASSERT(mBlockDOMContentLoaded);
+ if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
+ return;
+ }
+
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p UnblockDOMContentLoaded", this));
+
+ mDidFireDOMContentLoaded = true;
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
+ }
+
+ MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
+ if (!mSynchronousDOMContentLoaded) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
+ &Document::DispatchContentLoadedEvents);
+ Dispatch(TaskCategory::Other, ev.forget());
+ } else {
+ DispatchContentLoadedEvents();
+ }
+}
+
+void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a scriptblocker");
+ NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
+ (this, aElement, aStateMask));
+}
+
+void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
+ StyleRuleChangeKind) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
+ if (aRule.IsIncompleteImportRule()) {
+ return;
+ }
+
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+static Element* GetCustomContentContainer(PresShell* aPresShell) {
+ if (!aPresShell || !aPresShell->GetCanvasFrame()) {
+ return nullptr;
+ }
+
+ return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
+}
+
+already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
+ Element& aElement, bool aForce, ErrorResult& aRv) {
+ // Clone the node to avoid returning a direct reference.
+ nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ PresShell* shell = GetPresShell();
+ if (aForce && !GetCustomContentContainer(shell)) {
+ FlushPendingNotifications(FlushType::Layout);
+ shell = GetPresShell();
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ auto anonContent =
+ MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
+
+ mAnonymousContents.AppendElement(anonContent);
+
+ if (Element* container = GetCustomContentContainer(shell)) {
+ container->AppendChildTo(&anonContent->ContentNode(), true, IgnoreErrors());
+ shell->GetCanvasFrame()->ShowCustomContentContainer();
+ }
+
+ return anonContent.forget();
+}
+
+static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
+ PresShell* aPresShell) {
+ RefPtr<Element> container = GetCustomContentContainer(aPresShell);
+ if (!container) {
+ return;
+ }
+ container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
+}
+
+void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
+ nsAutoScriptBlocker scriptBlocker;
+
+ auto index = mAnonymousContents.IndexOf(&aContent);
+ if (index == mAnonymousContents.NoIndex) {
+ return;
+ }
+
+ mAnonymousContents.RemoveElementAt(index);
+ RemoveAnonContentFromCanvas(aContent, GetPresShell());
+
+ if (mAnonymousContents.IsEmpty() &&
+ GetCustomContentContainer(GetPresShell())) {
+ GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
+ }
+}
+
+Element* Document::GetAnonRootIfInAnonymousContentContainer(
+ nsINode* aNode) const {
+ if (!aNode->IsInNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell || !presShell->GetCanvasFrame()) {
+ return nullptr;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<Element> customContainer =
+ presShell->GetCanvasFrame()->GetCustomContentContainer();
+ if (!customContainer) {
+ return nullptr;
+ }
+
+ // An arbitrary number of elements can be inserted as children of the custom
+ // container frame. We want the one that was added that contains aNode, so
+ // we need to keep track of the last child separately using |child| here.
+ nsINode* child = aNode;
+ nsINode* parent = aNode->GetParentNode();
+ while (parent && parent->IsInNativeAnonymousSubtree()) {
+ if (parent == customContainer) {
+ return Element::FromNode(child);
+ }
+ child = parent;
+ parent = child->GetParentNode();
+ }
+ return nullptr;
+}
+
+Maybe<ClientInfo> Document::GetClientInfo() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
+ return info;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetClientInfo();
+ }
+
+ return Maybe<ClientInfo>();
+}
+
+Maybe<ClientState> Document::GetClientState() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ClientState> state = orig->GetClientState()) {
+ return state;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetClientState();
+ }
+
+ return Maybe<ClientState>();
+}
+
+Maybe<ServiceWorkerDescriptor> Document::GetController() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
+ return controller;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetController();
+ }
+
+ return Maybe<ServiceWorkerDescriptor>();
+}
+
+//
+// Document interface
+//
+DocumentType* Document::GetDoctype() const {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->NodeType() == DOCUMENT_TYPE_NODE) {
+ return static_cast<DocumentType*>(child);
+ }
+ }
+ return nullptr;
+}
+
+DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
+ if (!mDOMImplementation) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (!uri) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+ if (!scriptObject && hasHadScriptObject) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mDOMImplementation = new DOMImplementation(
+ this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
+ }
+
+ return mDOMImplementation;
+}
+
+bool IsLowercaseASCII(const nsAString& aValue) {
+ int32_t len = aValue.Length();
+ for (int32_t i = 0; i < len; ++i) {
+ char16_t c = aValue[i];
+ if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<Element> Document::CreateElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& rv) {
+ rv = nsContentUtils::CheckQName(aTagName, false);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
+ nsAutoString lcTagName;
+ if (needsLowercase) {
+ nsContentUtils::ASCIIToLower(aTagName, lcTagName);
+ }
+
+ const nsString* is = nullptr;
+ PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+
+ // Check 'pseudo' and throw an exception if it's not one allowed
+ // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
+ if (options.mPseudo.WasPassed()) {
+ Maybe<PseudoStyleType> type =
+ nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
+ if (!type || *type == PseudoStyleType::NotPseudo ||
+ !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
+ rv.ThrowNotSupportedError("Invalid pseudo-element");
+ return nullptr;
+ }
+ pseudoType = *type;
+ }
+ }
+
+ RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
+ nullptr, mDefaultElementType, is);
+
+ if (pseudoType != PseudoStyleType::NotPseudo) {
+ elem->SetPseudoElementType(pseudoType);
+ }
+
+ return elem.forget();
+}
+
+already_AddRefed<Element> Document::CreateElementNS(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
+ mNodeInfoManager, ELEMENT_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ const nsString* is = nullptr;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+ }
+
+ nsCOMPtr<Element> element;
+ rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
+ NOT_FROM_PARSER, is);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return element.forget();
+}
+
+already_AddRefed<Element> Document::CreateXULElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& aRv) {
+ aRv = nsContentUtils::CheckQName(aTagName, false);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ const nsString* is = nullptr;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+ }
+
+ RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
+ if (!elem) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ return elem.forget();
+}
+
+already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
+ RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
+ return text.forget();
+}
+
+already_AddRefed<nsTextNode> Document::CreateTextNode(
+ const nsAString& aData) const {
+ RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
+ // Don't notify; this node is still being created.
+ text->SetText(aData, false);
+ return text.forget();
+}
+
+already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
+ RefPtr<DocumentFragment> frag =
+ new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
+ return frag.forget();
+}
+
+// Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
+already_AddRefed<dom::Comment> Document::CreateComment(
+ const nsAString& aData) const {
+ RefPtr<dom::Comment> comment =
+ new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ comment->SetText(aData, false);
+ return comment.forget();
+}
+
+already_AddRefed<CDATASection> Document::CreateCDATASection(
+ const nsAString& aData, ErrorResult& rv) {
+ if (IsHTMLDocument()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (FindInReadable(u"]]>"_ns, aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<CDATASection> cdata =
+ new (mNodeInfoManager) CDATASection(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ cdata->SetText(aData, false);
+
+ return cdata.forget();
+}
+
+already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
+ const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
+ nsresult res = nsContentUtils::CheckQName(aTarget, false);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ if (FindInReadable(u"?>"_ns, aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<ProcessingInstruction> pi =
+ NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
+
+ return pi.forget();
+}
+
+already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
+ ErrorResult& rv) {
+ if (!mNodeInfoManager) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ nsresult res = nsContentUtils::CheckQName(aName, false);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ nsAutoString name;
+ if (IsHTMLDocument()) {
+ nsContentUtils::ASCIIToLower(aName, name);
+ } else {
+ name = aName;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
+ ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute =
+ new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
+ return attribute.forget();
+}
+
+already_AddRefed<Attr> Document::CreateAttributeNS(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ ErrorResult& rv) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
+ mNodeInfoManager, ATTRIBUTE_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute =
+ new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
+ return attribute.forget();
+}
+
+void Document::ResolveScheduledSVGPresAttrs() {
+ for (SVGElement* svg : mLazySVGPresElements) {
+ svg->UpdateContentDeclarationBlock();
+ }
+ mLazySVGPresElements.Clear();
+}
+
+already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
+ const {
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
+
+ const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
+
+ for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
+ nsWeakPtr weakNode = blockedNodes[i];
+ nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
+ // Consider only nodes to which we have managed to get strong references.
+ // Coping with nullptrs since it's expected for nodes to disappear when
+ // nobody else is referring to them.
+ if (node) {
+ list->AppendElement(node);
+ }
+ }
+
+ return list.forget();
+}
+
+void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
+ aSheetSet.Truncate();
+
+ // Look through our sheets, find the selected set title
+ size_t count = SheetCount();
+ nsAutoString title;
+ for (size_t index = 0; index < count; index++) {
+ StyleSheet* sheet = SheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ if (sheet->Disabled()) {
+ // Disabled sheets don't affect the currently selected set
+ continue;
+ }
+
+ sheet->GetTitle(title);
+
+ if (aSheetSet.IsEmpty()) {
+ aSheetSet = title;
+ } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
+ // Sheets from multiple sets enabled; return null string, per spec.
+ SetDOMStringToNull(aSheetSet);
+ return;
+ }
+ }
+}
+
+void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
+ if (DOMStringIsNull(aSheetSet)) {
+ return;
+ }
+
+ // Must update mLastStyleSheetSet before doing anything else with stylesheets
+ // or CSSLoaders.
+ mLastStyleSheetSet = aSheetSet;
+ EnableStyleSheetsForSetInternal(aSheetSet, true);
+}
+
+void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
+ mPreferredStyleSheetSet = aSheetSet;
+ // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
+ // spec.
+ if (DOMStringIsNull(mLastStyleSheetSet)) {
+ // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
+ // per spec. The idea here is that we're changing our preferred set and
+ // that shouldn't change the value of lastStyleSheetSet. Also, we're
+ // using the Internal version so we can update the CSSLoader and not have
+ // to worry about null strings.
+ EnableStyleSheetsForSetInternal(aSheetSet, true);
+ }
+}
+
+DOMStringList* Document::StyleSheetSets() {
+ if (!mStyleSheetSetList) {
+ mStyleSheetSetList = new DOMStyleSheetSetList(this);
+ }
+ return mStyleSheetSetList;
+}
+
+void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
+ // Per spec, passing in null is a no-op.
+ if (!DOMStringIsNull(aSheetSet)) {
+ // Note: must make sure to not change the CSSLoader's preferred sheet --
+ // that value should be equal to either our lastStyleSheetSet (if that's
+ // non-null) or to our preferredStyleSheetSet. And this method doesn't
+ // change either of those.
+ EnableStyleSheetsForSetInternal(aSheetSet, false);
+ }
+}
+
+void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
+ bool aUpdateCSSLoader) {
+ size_t count = SheetCount();
+ nsAutoString title;
+ for (size_t index = 0; index < count; index++) {
+ StyleSheet* sheet = SheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ sheet->GetTitle(title);
+ if (!title.IsEmpty()) {
+ sheet->SetEnabled(title.Equals(aSheetSet));
+ }
+ }
+ if (aUpdateCSSLoader) {
+ CSSLoader()->DocumentStyleSheetSetChanged();
+ }
+ if (mStyleSet->StyleSheetsHaveChanged()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::GetCharacterSet(nsAString& aCharacterSet) const {
+ nsAutoCString charset;
+ GetDocumentCharacterSet()->Name(charset);
+ CopyASCIItoUTF16(charset, aCharacterSet);
+}
+
+already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
+ ErrorResult& rv) const {
+ nsINode* imported = &aNode;
+
+ switch (imported->NodeType()) {
+ case DOCUMENT_NODE: {
+ break;
+ }
+ case DOCUMENT_FRAGMENT_NODE:
+ case ATTRIBUTE_NODE:
+ case ELEMENT_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case COMMENT_NODE:
+ case DOCUMENT_TYPE_NODE: {
+ return imported->Clone(aDeep, mNodeInfoManager, rv);
+ }
+ default: {
+ NS_WARNING("Don't know how to clone this nodetype for importNode.");
+ }
+ }
+
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+}
+
+already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
+ return nsRange::Create(this, 0, this, 0, rv);
+}
+
+already_AddRefed<NodeIterator> Document::CreateNodeIterator(
+ nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
+ ErrorResult& rv) const {
+ RefPtr<NodeIterator> iterator =
+ new NodeIterator(&aRoot, aWhatToShow, aFilter);
+ return iterator.forget();
+}
+
+already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
+ uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const {
+ RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
+ return walker.forget();
+}
+
+already_AddRefed<Location> Document::GetLocation() const {
+ nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
+
+ if (!w) {
+ return nullptr;
+ }
+
+ return do_AddRef(w->Location());
+}
+
+already_AddRefed<nsIURI> Document::GetDomainURI() {
+ nsIPrincipal* principal = NodePrincipal();
+
+ nsCOMPtr<nsIURI> uri;
+ principal->GetDomain(getter_AddRefs(uri));
+ if (uri) {
+ return uri.forget();
+ }
+ auto* basePrin = BasePrincipal::Cast(principal);
+ basePrin->GetURI(getter_AddRefs(uri));
+ return uri.forget();
+}
+
+void Document::GetDomain(nsAString& aDomain) {
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+
+ if (!uri) {
+ aDomain.Truncate();
+ return;
+ }
+
+ nsAutoCString hostName;
+ nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(hostName, aDomain);
+ } else {
+ // If we can't get the host from the URI (e.g. about:, javascript:,
+ // etc), just return an empty string.
+ aDomain.Truncate();
+ }
+}
+
+void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
+ if (!GetBrowsingContext()) {
+ // If our browsing context is null; disallow setting domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (mSandboxFlags & SANDBOXED_DOMAIN) {
+ // We're sandboxed; disallow setting domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (aDomain.IsEmpty()) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+ if (!uri) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+
+ nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
+ if (!newURI) {
+ // Error: illegal domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
+ WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
+ MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SendSetDocumentDomain(newURI);
+ }
+}
+
+already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
+ const nsACString& aHostString) {
+ if (aHostString.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create new URI
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+ if (!uri) {
+ return nullptr;
+ }
+
+ nsresult rv;
+ rv = NS_MutateURI(uri)
+ .SetUserPass(""_ns)
+ .SetPort(-1) // we want to reset the port number if needed.
+ .SetHostPort(aHostString)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
+ const nsAString& aNewDomain, nsIURI* aOrigHost) {
+ if (NS_WARN_IF(!aOrigHost)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI =
+ CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
+ if (!newURI) {
+ // Error: failed to parse input domain
+ return nullptr;
+ }
+
+ if (!IsValidDomain(aOrigHost, newURI)) {
+ // Error: illegal domain
+ return nullptr;
+ }
+
+ nsAutoCString domain;
+ if (NS_FAILED(newURI->GetAsciiHost(domain))) {
+ return nullptr;
+ }
+
+ return CreateInheritingURIForHost(domain);
+}
+
+/* static */
+bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+ nsAutoCString current;
+ nsAutoCString domain;
+ if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
+ current.Truncate();
+ }
+ if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
+ domain.Truncate();
+ }
+
+ bool ok = current.Equals(domain);
+ if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
+ current.CharAt(current.Length() - domain.Length() - 1) == '.') {
+ // We're golden if the new domain is the current page's base domain or a
+ // subdomain of it.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return false;
+ }
+
+ nsAutoCString currentBaseDomain;
+ ok = NS_SUCCEEDED(
+ tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
+ NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
+ (domain.Length() >= currentBaseDomain.Length()),
+ "uh-oh! slight optimization wasn't valid somehow!");
+ ok = ok && domain.Length() >= currentBaseDomain.Length();
+ }
+
+ return ok;
+}
+
+Element* Document::GetHtmlElement() const {
+ Element* rootElement = GetRootElement();
+ if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
+ return rootElement;
+ return nullptr;
+}
+
+Element* Document::GetHtmlChildElement(nsAtom* aTag) {
+ Element* html = GetHtmlElement();
+ if (!html) return nullptr;
+
+ // Look for the element with aTag inside html. This needs to run
+ // forwards to find the first such element.
+ for (nsIContent* child = html->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(aTag)) return child->AsElement();
+ }
+ return nullptr;
+}
+
+nsGenericHTMLElement* Document::GetBody() {
+ Element* html = GetHtmlElement();
+ if (!html) {
+ return nullptr;
+ }
+
+ for (nsIContent* child = html->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::body) ||
+ child->IsHTMLElement(nsGkAtoms::frameset)) {
+ return static_cast<nsGenericHTMLElement*>(child);
+ }
+ }
+
+ return nullptr;
+}
+
+void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
+ nsCOMPtr<Element> root = GetRootElement();
+
+ // The body element must be either a body tag or a frameset tag. And we must
+ // have a root element to be able to add kids to it.
+ if (!newBody ||
+ !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
+ rv.ThrowHierarchyRequestError(
+ "The new body must be either a body tag or frameset tag.");
+ return;
+ }
+
+ if (!root) {
+ rv.ThrowHierarchyRequestError("No root element.");
+ return;
+ }
+
+ // Use DOM methods so that we pass through the appropriate security checks.
+ nsCOMPtr<Element> currentBody = GetBody();
+ if (currentBody) {
+ root->ReplaceChild(*newBody, *currentBody, rv);
+ } else {
+ root->AppendChild(*newBody, rv);
+ }
+}
+
+HTMLSharedElement* Document::GetHead() {
+ return static_cast<HTMLSharedElement*>(GetHeadElement());
+}
+
+Element* Document::GetTitleElement() {
+ // mMayHaveTitleElement will have been set to true if any HTML or SVG
+ // <title> element has been bound to this document. So if it's false,
+ // we know there is nothing to do here. This avoids us having to search
+ // the whole DOM if someone calls document.title on a large document
+ // without a title.
+ if (!mMayHaveTitleElement) {
+ return nullptr;
+ }
+
+ Element* root = GetRootElement();
+ if (root && root->IsSVGElement(nsGkAtoms::svg)) {
+ // In SVG, the document's title must be a child
+ for (nsIContent* child = root->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsSVGElement(nsGkAtoms::title)) {
+ return child->AsElement();
+ }
+ }
+ return nullptr;
+ }
+
+ // We check the HTML namespace even for non-HTML documents, except SVG. This
+ // matches the spec and the behavior of all tested browsers.
+ for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
+ if (node->IsHTMLElement(nsGkAtoms::title)) {
+ return node->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+void Document::GetTitle(nsAString& aTitle) {
+ aTitle.Truncate();
+
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+
+ if (rootElement->IsXULElement()) {
+ rootElement->GetAttr(nsGkAtoms::title, aTitle);
+ } else if (Element* title = GetTitleElement()) {
+ nsContentUtils::GetNodeTextContent(title, false, aTitle);
+ } else {
+ return;
+ }
+
+ aTitle.CompressWhitespace();
+}
+
+void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+
+ if (rootElement->IsXULElement()) {
+ aRv =
+ rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
+ return;
+ }
+
+ Maybe<mozAutoDocUpdate> updateBatch;
+ nsCOMPtr<Element> title = GetTitleElement();
+ if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
+ if (!title) {
+ // Batch updates so that mutation events don't change "the title
+ // element" under us
+ updateBatch.emplace(this, true);
+ RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
+ NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
+ NOT_FROM_PARSER);
+ if (!title) {
+ return;
+ }
+ rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
+ IgnoreErrors());
+ }
+ } else if (rootElement->IsHTMLElement()) {
+ if (!title) {
+ // Batch updates so that mutation events don't change "the title
+ // element" under us
+ updateBatch.emplace(this, true);
+ Element* head = GetHeadElement();
+ if (!head) {
+ return;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> titleInfo;
+ titleInfo = mNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
+ title = NS_NewHTMLTitleElement(titleInfo.forget());
+ if (!title) {
+ return;
+ }
+
+ head->AppendChildTo(title, true, IgnoreErrors());
+ }
+ } else {
+ return;
+ }
+
+ aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
+}
+
+void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
+ NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
+ "Setting a title while unlinking or destroying the element?");
+ if (mInUnlinkOrDeletion) {
+ return;
+ }
+
+ if (aBoundTitleElement) {
+ mMayHaveTitleElement = true;
+ }
+ if (mPendingTitleChangeEvent.IsPending()) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ RefPtr<nsRunnableMethod<Document, void, false>> event =
+ NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
+ &Document::DoNotifyPossibleTitleChange);
+ if (NS_WARN_IF(NS_FAILED(Dispatch(TaskCategory::Other, do_AddRef(event))))) {
+ return;
+ }
+ mPendingTitleChangeEvent = std::move(event);
+}
+
+void Document::DoNotifyPossibleTitleChange() {
+ if (!mPendingTitleChangeEvent.IsPending()) {
+ return;
+ }
+ // Make sure the pending runnable method is cleared.
+ mPendingTitleChangeEvent.Revoke();
+ mHaveFiredTitleChange = true;
+
+ nsAutoString title;
+ GetTitle(title);
+
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ nsCOMPtr<nsISupports> container =
+ presShell->GetPresContext()->GetContainerWeak();
+ if (container) {
+ if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
+ docShellWin->SetTitle(title);
+ }
+ }
+ }
+
+ if (WindowGlobalChild* child = GetWindowGlobalChild()) {
+ child->SendUpdateDocumentTitle(title);
+ }
+
+ // Fire a DOM event for the title change.
+ nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
+ u"DOMTitleChanged"_ns, CanBubble::eYes,
+ Cancelable::eYes);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
+ }
+}
+
+already_AddRefed<MediaQueryList> Document::MatchMedia(
+ const nsACString& aMediaQueryList, CallerType aCallerType) {
+ RefPtr<MediaQueryList> result =
+ new MediaQueryList(this, aMediaQueryList, aCallerType);
+
+ mDOMMediaQueryLists.insertBack(result);
+
+ return result.forget();
+}
+
+void Document::SetMayStartLayout(bool aMayStartLayout) {
+ mMayStartLayout = aMayStartLayout;
+ if (MayStartLayout()) {
+ // Before starting layout, check whether we're a toplevel chrome
+ // window. If we are, setup some state so that we don't have to restyle
+ // the whole tree after StartLayout.
+ if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
+ // We're the chrome document!
+ win->BeforeStartLayout();
+ }
+ ReadyState state = GetReadyStateEnum();
+ if (state >= READYSTATE_INTERACTIVE) {
+ // DOMContentLoaded has fired already.
+ MaybeResolveReadyForIdle();
+ }
+ }
+
+ MaybeEditingStateChanged();
+}
+
+nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
+ mInitializableFrameLoaders.RemoveElement(aLoader);
+ // Don't even try to initialize.
+ if (mInDestructor) {
+ NS_WARNING(
+ "Trying to initialize a frame loader while"
+ "document is being deleted");
+ return NS_ERROR_FAILURE;
+ }
+
+ mInitializableFrameLoaders.AppendElement(aLoader);
+ if (!mFrameLoaderRunner) {
+ mFrameLoaderRunner =
+ NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
+ nsIRunnable* aFinalizer) {
+ mInitializableFrameLoaders.RemoveElement(aLoader);
+ if (mInDestructor) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LogRunnable::LogDispatch(aFinalizer);
+ mFrameLoaderFinalizers.AppendElement(aFinalizer);
+ if (!mFrameLoaderRunner) {
+ mFrameLoaderRunner =
+ NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+void Document::MaybeInitializeFinalizeFrameLoaders() {
+ if (mDelayFrameLoaderInitialization) {
+ // This method will be recalled when !mDelayFrameLoaderInitialization.
+ mFrameLoaderRunner = nullptr;
+ return;
+ }
+
+ // We're not in an update, but it is not safe to run scripts, so
+ // postpone frameloader initialization and finalization.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ if (!mInDestructor && !mFrameLoaderRunner &&
+ (mInitializableFrameLoaders.Length() ||
+ mFrameLoaderFinalizers.Length())) {
+ mFrameLoaderRunner = NewRunnableMethod(
+ "Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return;
+ }
+ mFrameLoaderRunner = nullptr;
+
+ // Don't use a temporary array for mInitializableFrameLoaders, because
+ // loading a frame may cause some other frameloader to be removed from the
+ // array. But be careful to keep the loader alive when starting the load!
+ while (mInitializableFrameLoaders.Length()) {
+ RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
+ mInitializableFrameLoaders.RemoveElementAt(0);
+ NS_ASSERTION(loader, "null frameloader in the array?");
+ loader->ReallyStartLoading();
+ }
+
+ uint32_t length = mFrameLoaderFinalizers.Length();
+ if (length > 0) {
+ nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
+ std::move(mFrameLoaderFinalizers);
+ for (uint32_t i = 0; i < length; ++i) {
+ LogRunnable::Run run(finalizers[i]);
+ finalizers[i]->Run();
+ }
+ }
+}
+
+void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
+ uint32_t length = mInitializableFrameLoaders.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
+ mInitializableFrameLoaders.RemoveElementAt(i);
+ return;
+ }
+ }
+}
+
+void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
+ mPrototypeDocument = aPrototype;
+ mSynchronousDOMContentLoaded = true;
+}
+
+nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
+ return GetPermissionDelegateHandler();
+}
+
+Document* Document::RequestExternalResource(
+ nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aRequestingNode, "Must have a node");
+ MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
+ if (mDisplayDocument) {
+ return mDisplayDocument->RequestExternalResource(
+ aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
+ }
+
+ return mExternalResourceMap.RequestResource(
+ aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
+}
+
+void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
+ mExternalResourceMap.EnumerateResources(aCallback);
+}
+
+SMILAnimationController* Document::GetAnimationController() {
+ // We create the animation controller lazily because most documents won't want
+ // one and only SVG documents and the like will call this
+ if (mAnimationController) return mAnimationController;
+ // Refuse to create an Animation Controller for data documents.
+ if (mLoadedAsData) return nullptr;
+
+ mAnimationController = new SMILAnimationController(this);
+
+ // If there's a presContext then check the animation mode and pause if
+ // necessary.
+ nsPresContext* context = GetPresContext();
+ if (mAnimationController && context &&
+ context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
+ mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
+ }
+
+ // If we're hidden (or being hidden), notify the newly-created animation
+ // controller. (Skip this check for SVG-as-an-image documents, though,
+ // because they don't get OnPageShow / OnPageHide calls).
+ if (!mIsShowing && !mIsBeingUsedAsImage) {
+ mAnimationController->OnPageHide();
+ }
+
+ return mAnimationController;
+}
+
+PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
+ if (!mPendingAnimationTracker) {
+ mPendingAnimationTracker = new PendingAnimationTracker(this);
+ }
+
+ return mPendingAnimationTracker;
+}
+
+ScrollTimelineAnimationTracker*
+Document::GetOrCreateScrollTimelineAnimationTracker() {
+ if (!mScrollTimelineAnimationTracker) {
+ mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
+ }
+
+ return mScrollTimelineAnimationTracker;
+}
+
+/**
+ * Retrieve the "direction" property of the document.
+ *
+ * @lina 01/09/2001
+ */
+void Document::GetDir(nsAString& aDirection) const {
+ aDirection.Truncate();
+ Element* rootElement = GetHtmlElement();
+ if (rootElement) {
+ static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
+ }
+}
+
+/**
+ * Set the "direction" property of the document.
+ *
+ * @lina 01/09/2001
+ */
+void Document::SetDir(const nsAString& aDirection) {
+ Element* rootElement = GetHtmlElement();
+ if (rootElement) {
+ rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
+ }
+}
+
+nsIHTMLCollection* Document::Images() {
+ if (!mImages) {
+ mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
+ nsGkAtoms::img);
+ }
+ return mImages;
+}
+
+nsIHTMLCollection* Document::Embeds() {
+ if (!mEmbeds) {
+ mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
+ nsGkAtoms::embed);
+ }
+ return mEmbeds;
+}
+
+static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
+ void* aData) {
+ return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
+}
+
+nsIHTMLCollection* Document::Links() {
+ if (!mLinks) {
+ mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
+ }
+ return mLinks;
+}
+
+nsIHTMLCollection* Document::Forms() {
+ if (!mForms) {
+ // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
+ mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
+ nsGkAtoms::form);
+ }
+
+ return mForms;
+}
+
+nsIHTMLCollection* Document::Scripts() {
+ if (!mScripts) {
+ mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
+ nsGkAtoms::script);
+ }
+ return mScripts;
+}
+
+nsIHTMLCollection* Document::Applets() {
+ if (!mApplets) {
+ mApplets = new nsEmptyContentList(this);
+ }
+ return mApplets;
+}
+
+static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
+ void* aData) {
+ return aElement->IsHTMLElement(nsGkAtoms::a) &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
+}
+
+nsIHTMLCollection* Document::Anchors() {
+ if (!mAnchors) {
+ mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
+ }
+ return mAnchors;
+}
+
+mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
+ const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
+ ErrorResult& rv) {
+ MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
+ "XOW should have caught this!");
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> outer =
+ nsPIDOMWindowOuter::GetFromCurrentInner(window);
+ if (!outer) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
+ RefPtr<BrowsingContext> newBC;
+ rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
+ if (!newBC) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(newBC));
+}
+
+Document* Document::Open(const Optional<nsAString>& /* unused */,
+ const Optional<nsAString>& /* unused */,
+ ErrorResult& aError) {
+ // Implements
+ // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
+
+ MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
+ "XOW should have caught this!");
+
+ // Step 1 -- throw if we're an XML document.
+ if (!IsHTMLDocument() || mDisableDocWrite) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Step 2 -- throw if dynamic markup insertion should throw.
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Step 3 -- get the entry document, so we can use it for security checks.
+ nsCOMPtr<Document> callerDoc = GetEntryDocument();
+ if (!callerDoc) {
+ // If we're called from C++ or in some other way without an originating
+ // document we can't do a document.open w/o changing the principal of the
+ // document to something like about:blank (as that's the only sane thing to
+ // do when we don't know the origin of this call), and since we can't
+ // change the principals of a document for security reasons we'll have to
+ // refuse to go ahead with this call.
+
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Step 4 -- make sure we're same-origin (not just same origin-domain) with
+ // the entry document.
+ if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Step 5 -- if we have an active parser with a nonzero script nesting level,
+ // just no-op.
+ if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
+ return this;
+ }
+
+ // Step 6 -- check for open() during unload. Per spec, this is just a check
+ // of the ignore-opens-during-unload counter, but our unload event code
+ // doesn't affect that counter yet (unlike pagehide and beforeunload, which
+ // do), so we check for unload directly.
+ if (ShouldIgnoreOpens()) {
+ return this;
+ }
+
+ RefPtr<nsDocShell> shell(mDocumentContainer);
+ if (shell) {
+ bool inUnload;
+ shell->GetIsInUnload(&inUnload);
+ if (inUnload) {
+ return this;
+ }
+ }
+
+ // At this point we know this is a valid-enough document.open() call
+ // and not a no-op. Increment our use counter.
+ SetUseCounter(eUseCounter_custom_DocumentOpen);
+
+ // Step 7 -- stop existing navigation of our browsing context (and all other
+ // loads it's doing) if we're the active document of our browsing context.
+ // Note that we do not want to stop anything if there is no existing
+ // navigation.
+ if (shell && IsCurrentActiveDocument() &&
+ shell->GetIsAttemptingToNavigate()) {
+ shell->Stop(nsIWebNavigation::STOP_NETWORK);
+
+ // The Stop call may have cancelled the onload blocker request or
+ // prevented it from getting added, so we need to make sure it gets added
+ // to the document again otherwise the document could have a non-zero
+ // onload block count without the onload blocker request being in the
+ // loadgroup.
+ EnsureOnloadBlocker();
+ }
+
+ // Step 8 -- clear event listeners out of our DOM tree
+ for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
+ if (EventListenerManager* elm = node->GetExistingListenerManager()) {
+ elm->RemoveAllListeners();
+ }
+ }
+
+ // Step 9 -- clear event listeners from our window, if we have one.
+ //
+ // Note that we explicitly want the inner window, and only if we're its
+ // document. We want to do this (per spec) even when we're not the "active
+ // document", so we can't go through GetWindow(), because it might forward to
+ // the wrong inner.
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ if (win->GetExtantDoc() == this) {
+ if (EventListenerManager* elm =
+ nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
+ elm->RemoveAllListeners();
+ }
+ }
+ }
+
+ // If we have a parser that has a zero script nesting level, we need to
+ // properly terminate it. We do that after we've removed all the event
+ // listeners (so termination won't trigger event listeners if it does
+ // something to the DOM), but before we remove all elements from the document
+ // (so if termination does modify the DOM in some way we will just blow it
+ // away immediately. See the similar code in WriteCommon that handles the
+ // !IsInsertionPointDefined() case and should stay in sync with this code.
+ if (mParser) {
+ MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
+ "Why didn't we take the early return?");
+ // Make sure we don't re-enter.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ mParser->Terminate();
+ MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
+ }
+
+ // Step 10 -- remove all our DOM kids without firing any mutation events.
+ {
+ // We want to ignore any recursive calls to Open() that happen while
+ // disconnecting the node tree. The spec doesn't say to do this, but the
+ // spec also doesn't envision unload events on subframes firing while we do
+ // this, while all browsers fire them in practice. See
+ // <https://github.com/whatwg/html/issues/4611>.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ DisconnectNodeTree();
+ }
+
+ // Step 11 -- if we're the current document in our docshell, do the
+ // equivalent of pushState() with the new URL we should have.
+ if (shell && IsCurrentActiveDocument()) {
+ nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
+ if (callerDoc != this) {
+ nsCOMPtr<nsIURI> noFragmentURI;
+ nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+ newURI = std::move(noFragmentURI);
+ }
+
+ // UpdateURLAndHistory might do various member-setting, so make sure we're
+ // holding strong refs to all the refcounted args on the stack. We can
+ // assume that our caller is holding on to "this" already.
+ nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
+ bool equalURIs;
+ nsresult rv = currentURI->Equals(newURI, &equalURIs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+ nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
+ rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
+ /* aReplace = */ true, currentURI,
+ equalURIs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ // And use the security info of the caller document as well, since
+ // it's the thing providing our data.
+ mSecurityInfo = callerDoc->GetSecurityInfo();
+
+ // This is not mentioned in the spec, but I think that's a spec bug. See
+ // <https://github.com/whatwg/html/issues/4299>. In any case, since our
+ // URL may be changing away from about:blank here, we really want to unset
+ // this flag no matter what, since only about:blank can be an initial
+ // document.
+ SetIsInitialDocument(false);
+
+ // And let our docloader know that it will need to track our load event.
+ nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
+ }
+
+ // Per spec nothing happens with our URI in other cases, though note
+ // <https://github.com/whatwg/html/issues/4286>.
+
+ // Note that we don't need to do anything here with base URIs per spec.
+ // That said, this might be assuming that we implement
+ // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
+ // correctly, which we don't right now for the about:blank case.
+
+ // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
+ mSkipLoadEventAfterClose = mLoadEventFiring;
+
+ // Preliminary to steps 13-16. Set our ready state to uninitialized before
+ // we do anything else, so we can then proceed to later ready state levels.
+ SetReadyStateInternal(READYSTATE_UNINITIALIZED,
+ /* updateTimingInformation = */ false);
+ // Reset a flag that affects readyState behavior.
+ mSetCompleteAfterDOMContentLoaded = false;
+
+ // Step 13 -- set our compat mode to standards.
+ SetCompatibilityMode(eCompatibility_FullStandards);
+
+ // Step 14 -- create a new parser associated with document. This also does
+ // step 16 implicitly.
+ mParserAborted = false;
+ RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
+ mParser = parser;
+ parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
+ nsresult rv = parser->StartExecutor();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ // Clear out our form control state, because the state of controls
+ // in the pre-open() document should not affect the state of
+ // controls that are now going to be written.
+ mLayoutHistoryState = nullptr;
+
+ if (shell) {
+ // Prepare the docshell and the document viewer for the impending
+ // out-of-band document.write()
+ shell->PrepareForNewContentModel();
+
+ nsCOMPtr<nsIContentViewer> cv;
+ shell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ cv->LoadStart(this);
+ }
+ }
+
+ // Step 15.
+ SetReadyStateInternal(Document::READYSTATE_LOADING,
+ /* updateTimingInformation = */ false);
+
+ // Step 16 happened with step 14 above.
+
+ // Step 17.
+ return this;
+}
+
+void Document::Close(ErrorResult& rv) {
+ if (!IsHTMLDocument()) {
+ // No calling document.close() on XHTML!
+
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (!mParser || !mParser->IsScriptCreated()) {
+ return;
+ }
+
+ ++mWriteLevel;
+ rv = (static_cast<nsHtml5Parser*>(mParser.get()))
+ ->Parse(u""_ns, nullptr, true);
+ --mWriteLevel;
+}
+
+void Document::WriteCommon(const Sequence<nsString>& aText,
+ bool aNewlineTerminate, mozilla::ErrorResult& rv) {
+ // Fast path the common case
+ if (aText.Length() == 1) {
+ WriteCommon(aText[0], aNewlineTerminate, rv);
+ } else {
+ // XXXbz it would be nice if we could pass all the strings to the parser
+ // without having to do all this copying and then ask it to start
+ // parsing....
+ nsString text;
+ for (size_t i = 0; i < aText.Length(); ++i) {
+ text.Append(aText[i]);
+ }
+ WriteCommon(text, aNewlineTerminate, rv);
+ }
+}
+
+void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
+ ErrorResult& aRv) {
+#ifdef DEBUG
+ {
+ // Assert that we do not use or accidentally introduce doc.write()
+ // in system privileged context or in any of our about: pages.
+ nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+ bool isAboutOrPrivContext = principal->IsSystemPrincipal();
+ if (!isAboutOrPrivContext) {
+ if (principal->SchemeIs("about")) {
+ // about:blank inherits the security contetext and this assertion
+ // is only meant for actual about: pages.
+ nsAutoCString host;
+ principal->GetHost(host);
+ isAboutOrPrivContext = !host.EqualsLiteral("blank");
+ }
+ }
+ // Some automated tests use an empty string to kick off some parsing
+ // mechansims, but they do not do any harm since they use an empty string.
+ MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
+ "do not use doc.write in privileged context!");
+ }
+#endif
+
+ mTooDeepWriteRecursion =
+ (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
+ if (NS_WARN_IF(mTooDeepWriteRecursion)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (!IsHTMLDocument() || mDisableDocWrite) {
+ // No calling document.write*() on XHTML!
+
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (mParserAborted) {
+ // Hixie says aborting the parser doesn't undefine the insertion point.
+ // However, since we null out mParser in that case, we track the
+ // theoretically defined insertion point using mParserAborted.
+ return;
+ }
+
+ // Implement Step 4.1 of:
+ // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
+ if (ShouldIgnoreOpens()) {
+ return;
+ }
+
+ void* key = GenerateParserKey();
+ if (mParser && !mParser->IsInsertionPointDefined()) {
+ if (mIgnoreDestructiveWritesCounter) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Events"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
+ return;
+ }
+ // The spec doesn't tell us to ignore opens from here, but we need to
+ // ensure opens are ignored here. See similar code in Open() that handles
+ // the case of an existing parser which is not currently running script and
+ // should stay in sync with this code.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ mParser->Terminate();
+ MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
+ }
+
+ if (!mParser) {
+ if (mIgnoreDestructiveWritesCounter) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Events"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
+ return;
+ }
+
+ Open({}, {}, aRv);
+
+ // If Open() fails, or if it didn't create a parser (as it won't
+ // if the user chose to not discard the current document through
+ // onbeforeunload), don't write anything.
+ if (aRv.Failed() || !mParser) {
+ return;
+ }
+ }
+
+ static constexpr auto new_line = u"\n"_ns;
+
+ ++mWriteLevel;
+
+ // This could be done with less code, but for performance reasons it
+ // makes sense to have the code for two separate Parse() calls here
+ // since the concatenation of strings costs more than we like. And
+ // why pay that price when we don't need to?
+ if (aNewlineTerminate) {
+ aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
+ ->Parse(aText + new_line, key, false);
+ } else {
+ aRv =
+ (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
+ }
+
+ --mWriteLevel;
+
+ mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
+}
+
+void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
+ WriteCommon(aText, false, rv);
+}
+
+void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
+ WriteCommon(aText, true, rv);
+}
+
+void* Document::GenerateParserKey(void) {
+ if (!mScriptLoader) {
+ // If we don't have a script loader, then the parser probably isn't parsing
+ // anything anyway, so just return null.
+ return nullptr;
+ }
+
+ // The script loader provides us with the currently executing script element,
+ // which is guaranteed to be unique per script.
+ nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
+ if (script && mParser && mParser->IsScriptCreated()) {
+ nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
+ if (creatorParser != mParser) {
+ // Make scripts that aren't inserted by the active parser of this document
+ // participate in the context of the script that document.open()ed
+ // this document.
+ return nullptr;
+ }
+ }
+ return script;
+}
+
+/* static */
+bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAtom, void* aData) {
+ MOZ_ASSERT(aElement, "Must have element to work with!");
+
+ if (!aElement->HasName()) {
+ return false;
+ }
+
+ nsString* elementName = static_cast<nsString*>(aData);
+ return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
+ eCaseMatters);
+}
+
+/* static */
+void* Document::UseExistingNameString(nsINode* aRootNode,
+ const nsString* aName) {
+ return const_cast<nsString*>(aName);
+}
+
+nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
+ if (mDocumentURI) {
+ nsAutoCString uri;
+ nsresult rv = mDocumentURI->GetSpec(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(uri, aDocumentURI);
+ } else {
+ aDocumentURI.Truncate();
+ }
+
+ return NS_OK;
+}
+
+// Alias of above
+nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
+
+void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
+ CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
+ aRv = GetDocumentURI(aDocumentURI);
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult res = mChromeXHRDocURI->GetSpec(uri);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ CopyUTF8toUTF16(uri, aDocumentURI);
+}
+
+nsIURI* Document::GetDocumentURIObject() const {
+ if (!mChromeXHRDocURI) {
+ return GetDocumentURI();
+ }
+
+ return mChromeXHRDocURI;
+}
+
+void Document::GetCompatMode(nsString& aCompatMode) const {
+ NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
+ mCompatMode == eCompatibility_AlmostStandards ||
+ mCompatMode == eCompatibility_FullStandards,
+ "mCompatMode is neither quirks nor strict for this document");
+
+ if (mCompatMode == eCompatibility_NavQuirks) {
+ aCompatMode.AssignLiteral("BackCompat");
+ } else {
+ aCompatMode.AssignLiteral("CSS1Compat");
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
+ if (Element* element = Element::FromNode(aNode)) {
+ if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
+ while (true) {
+ RefPtr<Attr> attr;
+ {
+ // Use an iterator to get an arbitrary attribute from the
+ // cache. The iterator must be destroyed before any other
+ // operations on mAttributeCache, to avoid hash table
+ // assertions.
+ auto iter = map->mAttributeCache.ConstIter();
+ if (iter.Done()) {
+ break;
+ }
+ attr = iter.UserData();
+ }
+
+ BlastSubtreeToPieces(attr);
+
+ mozilla::DebugOnly<nsresult> rv =
+ element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
+ attr->NodeInfo()->NameAtom(), false);
+
+ // XXX Should we abort here?
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
+ }
+ }
+
+ if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
+ BlastSubtreeToPieces(shadow);
+ element->UnattachShadow();
+ }
+ }
+
+ while (aNode->HasChildren()) {
+ nsIContent* node = aNode->GetFirstChild();
+ BlastSubtreeToPieces(node);
+ aNode->RemoveChildNode(node, false);
+ }
+}
+
+namespace mozilla::dom {
+
+nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
+ OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
+
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
+ nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
+ }
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ switch (adoptedNode->NodeType()) {
+ case ATTRIBUTE_NODE: {
+ // Remove from ownerElement.
+ OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
+
+ nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ if (ownerElement) {
+ OwningNonNull<Attr> newAttr =
+ ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ break;
+ }
+ case DOCUMENT_FRAGMENT_NODE: {
+ if (adoptedNode->IsShadowRoot()) {
+ rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
+ return nullptr;
+ }
+ [[fallthrough]];
+ }
+ case ELEMENT_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case COMMENT_NODE:
+ case DOCUMENT_TYPE_NODE: {
+ // Don't allow adopting a node's anonymous subtree out from under it.
+ if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // We don't want to adopt an element into its own contentDocument or into
+ // a descendant contentDocument, so we check if the frameElement of this
+ // document or any of its parents is the adopted node or one of its
+ // descendants.
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ while (bc) {
+ nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
+ if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
+ rv.ThrowHierarchyRequestError(
+ "Trying to adopt a node into its own contentDocument or a "
+ "descendant contentDocument.");
+ return nullptr;
+ }
+
+ if (XRE_IsParentProcess()) {
+ bc = bc->Canonical()->GetParentCrossChromeBoundary();
+ } else {
+ bc = bc->GetParent();
+ }
+ }
+
+ // Remove from parent.
+ nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
+ if (parent) {
+ parent->RemoveChildNode(adoptedNode->AsContent(), true);
+ } else {
+ MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
+ }
+
+ break;
+ }
+ case DOCUMENT_NODE: {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ default: {
+ NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
+
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
+ bool sameDocument = oldDocument == this;
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> newScope(cx, nullptr);
+ if (!sameDocument) {
+ newScope = GetWrapper();
+ if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
+ // Make sure cx is in a semi-sane compartment before we call WrapNative.
+ // It's kind of irrelevant, given that we're passing aAllowWrapping =
+ // false, and documents should always insist on being wrapped in an
+ // canonical scope. But we try to pass something sane anyway.
+ JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
+ JSAutoRealm ar(cx, globalObject);
+ JS::Rooted<JS::Value> v(cx);
+ rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
+ /* aAllowWrapping = */ false);
+ if (rv.Failed()) return nullptr;
+ newScope = &v.toObject();
+ }
+ }
+
+ adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
+ if (rv.Failed()) {
+ // Disconnect all nodes from their parents, since some have the old document
+ // as their ownerDocument and some have this as their ownerDocument.
+ nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
+ "Should still be in the document we just got adopted into");
+
+ return adoptedNode;
+}
+
+bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
+
+static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
+ const nsString& aScaleString) {
+ // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
+ if (aScaleString.EqualsLiteral("device-width") ||
+ aScaleString.EqualsLiteral("device-height")) {
+ return Some(LayoutDeviceToScreenScale(10.0f));
+ } else if (aScaleString.EqualsLiteral("yes")) {
+ return Some(LayoutDeviceToScreenScale(1.0f));
+ } else if (aScaleString.EqualsLiteral("no")) {
+ return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
+ } else if (aScaleString.IsEmpty()) {
+ return Nothing();
+ }
+
+ nsresult scaleErrorCode;
+ float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
+ if (NS_FAILED(scaleErrorCode)) {
+ return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
+ }
+
+ if (scale < 0) {
+ return Nothing();
+ }
+ return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
+ ViewportMaxScale()));
+}
+
+void Document::ParseScalesInViewportMetaData(
+ const ViewportMetaData& aViewportMetaData) {
+ Maybe<LayoutDeviceToScreenScale> scale;
+
+ scale = ParseScaleString(aViewportMetaData.mInitialScale);
+ mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
+ mValidScaleFloat = scale.isSome();
+
+ scale = ParseScaleString(aViewportMetaData.mMaximumScale);
+ // Chrome uses '5' for the fallback value of maximum-scale, we might
+ // consider matching it in future.
+ // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
+ mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
+ mValidMaxScale = scale.isSome();
+
+ scale = ParseScaleString(aViewportMetaData.mMinimumScale);
+ mScaleMinFloat = scale.valueOr(ViewportMinScale());
+ mValidMinScale = scale.isSome();
+
+ // Resolve min-zoom and max-zoom values.
+ // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
+ if (mValidMaxScale && mValidMinScale) {
+ mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
+ }
+}
+
+void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+ const nsAString& aHeightString,
+ bool aHasValidScale) {
+ // The width and height properties
+ // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
+ //
+ // The width and height viewport <META> properties are translated into width
+ // and height descriptors, setting the min-width/min-height value to
+ // extend-to-zoom and the max-width/max-height value to the length from the
+ // viewport <META> property as follows:
+ //
+ // 1. Non-negative number values are translated to pixel lengths, clamped to
+ // the range: [1px, 10000px]
+ // 2. Negative number values are dropped
+ // 3. device-width and device-height translate to 100vw and 100vh respectively
+ // 4. Other keywords and unknown values are also dropped
+ mMinWidth = nsViewportInfo::kAuto;
+ mMaxWidth = nsViewportInfo::kAuto;
+ if (!aWidthString.IsEmpty()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ if (aWidthString.EqualsLiteral("device-width")) {
+ mMaxWidth = nsViewportInfo::kDeviceSize;
+ } else {
+ nsresult widthErrorCode;
+ mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
+ if (NS_FAILED(widthErrorCode)) {
+ mMaxWidth = nsViewportInfo::kAuto;
+ } else if (mMaxWidth >= 0.0f) {
+ mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
+ } else {
+ mMaxWidth = nsViewportInfo::kAuto;
+ }
+ }
+ } else if (aHasValidScale) {
+ if (aHeightString.IsEmpty()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ mMaxWidth = nsViewportInfo::kExtendToZoom;
+ }
+ } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ mMaxWidth = nsViewportInfo::kDeviceSize;
+ }
+
+ mMinHeight = nsViewportInfo::kAuto;
+ mMaxHeight = nsViewportInfo::kAuto;
+ if (!aHeightString.IsEmpty()) {
+ mMinHeight = nsViewportInfo::kExtendToZoom;
+ if (aHeightString.EqualsLiteral("device-height")) {
+ mMaxHeight = nsViewportInfo::kDeviceSize;
+ } else {
+ nsresult heightErrorCode;
+ mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
+ if (NS_FAILED(heightErrorCode)) {
+ mMaxHeight = nsViewportInfo::kAuto;
+ } else if (mMaxHeight >= 0.0f) {
+ mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
+ } else {
+ mMaxHeight = nsViewportInfo::kAuto;
+ }
+ }
+ }
+}
+
+nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
+ MOZ_ASSERT(mPresShell);
+
+ // Compute the CSS-to-LayoutDevice pixel scale as the product of the
+ // widget scale and the full zoom.
+ nsPresContext* context = mPresShell->GetPresContext();
+ // When querying the full zoom, get it from the device context rather than
+ // directly from the pres context, because the device context's value can
+ // include an adjustment necessary to keep the number of app units per device
+ // pixel an integer, and we want the adjusted value.
+ float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
+ fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
+ CSSToLayoutDeviceScale layoutDeviceScale =
+ context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
+
+ CSSToScreenScale defaultScale =
+ layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
+
+ // Special behaviour for desktop mode, provided we are not on an about: page,
+ // or fullscreen.
+ const bool fullscreen = Fullscreen();
+ auto* bc = GetBrowsingContext();
+ if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
+ CSSCoord viewportWidth =
+ StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
+ CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
+ float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
+ CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
+ ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
+ return nsViewportInfo(fakeDesktopSize, scaleToFit,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+
+ // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
+ if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsLayoutUtils::AllowZoomingForDocument(this)
+ ? nsViewportInfo::ZoomFlag::AllowZoom
+ : nsViewportInfo::ZoomFlag::DisallowZoom,
+ StaticPrefs::apz_allow_zooming_out()
+ ? nsViewportInfo::ZoomBehaviour::Mobile
+ : nsViewportInfo::ZoomBehaviour::Desktop);
+ }
+
+ // In cases where the width of the CSS viewport is less than or equal to the
+ // width of the display (i.e. width <= device-width) then we disable
+ // double-tap-to-zoom behaviour. See bug 941995 for details.
+
+ switch (mViewportType) {
+ case DisplayWidthHeight:
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ case Unknown: {
+ // We might early exit if the viewport is empty. Even if we don't,
+ // at the end of this case we'll note that it was empty. Later, when
+ // we're using the cached values, this will trigger alternate code paths.
+ if (!mLastModifiedViewportMetaData) {
+ // If the docType specifies that we are on a site optimized for mobile,
+ // then we want to return specially crafted defaults for the viewport
+ // info.
+ if (RefPtr<DocumentType> docType = GetDoctype()) {
+ nsAutoString docId;
+ docType->GetPublicId(docId);
+ if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
+ (docId.Find(u"WML") != -1)) {
+ // We're making an assumption that the docType can't change here
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+ }
+
+ nsAutoString handheldFriendly;
+ GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
+ if (handheldFriendly.EqualsLiteral("true")) {
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+ }
+
+ ViewportMetaData metaData = GetViewportMetaData();
+
+ // Parse initial-scale, minimum-scale and maximum-scale.
+ ParseScalesInViewportMetaData(metaData);
+
+ // Parse width and height properties
+ // This function sets m{Min,Max}{Width,Height}.
+ ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
+ mValidScaleFloat);
+
+ mAllowZoom = true;
+ if ((metaData.mUserScalable.EqualsLiteral("0")) ||
+ (metaData.mUserScalable.EqualsLiteral("no")) ||
+ (metaData.mUserScalable.EqualsLiteral("false"))) {
+ mAllowZoom = false;
+ }
+
+ // Resolve viewport-fit value.
+ // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
+ mViewportFit = ViewportFitType::Auto;
+ if (!metaData.mViewportFit.IsEmpty()) {
+ if (metaData.mViewportFit.EqualsLiteral("contain")) {
+ mViewportFit = ViewportFitType::Contain;
+ } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
+ mViewportFit = ViewportFitType::Cover;
+ }
+ }
+
+ mWidthStrEmpty = metaData.mWidth.IsEmpty();
+
+ mViewportType = Specified;
+ [[fallthrough]];
+ }
+ case Specified:
+ default:
+ LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
+ LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
+ bool effectiveValidMaxScale = mValidMaxScale;
+
+ nsViewportInfo::ZoomFlag effectiveZoomFlag =
+ mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
+ : nsViewportInfo::ZoomFlag::DisallowZoom;
+ if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
+ // If the pref to force user-scalable is enabled, we ignore the values
+ // from the meta-viewport tag for these properties and just assume they
+ // allow the page to be scalable. Note in particular that this code is
+ // in the "Specified" branch of the enclosing switch statement, so that
+ // calls to GetViewportInfo always use the latest value of the
+ // browser_ui_zoom_force_user_scalable pref. Other codepaths that
+ // return nsViewportInfo instances are all consistent with
+ // browser_ui_zoom_force_user_scalable() already.
+ effectiveMinScale = ViewportMinScale();
+ effectiveMaxScale = ViewportMaxScale();
+ effectiveValidMaxScale = true;
+ effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
+ }
+
+ // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
+ auto ComputeExtendZoom = [&]() -> float {
+ if (mValidScaleFloat && effectiveValidMaxScale) {
+ return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
+ }
+ if (mValidScaleFloat) {
+ return mScaleFloat.scale;
+ }
+ if (effectiveValidMaxScale) {
+ return effectiveMaxScale.scale;
+ }
+ return nsViewportInfo::kAuto;
+ };
+
+ // Resolving 'extend-to-zoom'
+ // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
+ float extendZoom = ComputeExtendZoom();
+
+ CSSCoord minWidth = mMinWidth;
+ CSSCoord maxWidth = mMaxWidth;
+ CSSCoord minHeight = mMinHeight;
+ CSSCoord maxHeight = mMaxHeight;
+
+ // aDisplaySize is in screen pixels; convert them to CSS pixels for the
+ // viewport size. We need to use this scaled size for any clamping of
+ // width or height.
+ CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
+
+ // Our min and max width and height values are mostly as specified by
+ // the viewport declaration, but we make an exception for max width.
+ // Max width, if auto, and if there's no initial-scale, will be set
+ // to a default size. This is to support legacy site design with no
+ // viewport declaration, and to do that using the same scheme as
+ // Chrome does, in order to maintain web compatibility. Since the
+ // default size has a complicated calculation, we fixup the maxWidth
+ // value after setting it, above.
+ if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
+ if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
+ bc->InRDMPane()) {
+ // If RDM and touch simulation are active, then use the simulated
+ // screen width to accommodate for cases where the screen width is
+ // larger than the desktop viewport default.
+ maxWidth = nsViewportInfo::Max(
+ displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
+ } else {
+ maxWidth = StaticPrefs::browser_viewport_desktopWidth();
+ }
+ // Divide by fullZoom to stretch CSS pixel size of viewport in order
+ // to keep device pixel size unchanged after full zoom applied.
+ // See bug 974242.
+ maxWidth /= fullZoom;
+
+ // We set minWidth to ExtendToZoom, which will cause our later width
+ // calculation to expand to maxWidth, if scale restrictions allow it.
+ minWidth = nsViewportInfo::kExtendToZoom;
+ }
+
+ // Resolve device-width and device-height first.
+ if (maxWidth == nsViewportInfo::kDeviceSize) {
+ maxWidth = displaySize.width;
+ }
+ if (maxHeight == nsViewportInfo::kDeviceSize) {
+ maxHeight = displaySize.height;
+ }
+ if (extendZoom == nsViewportInfo::kAuto) {
+ if (maxWidth == nsViewportInfo::kExtendToZoom) {
+ maxWidth = nsViewportInfo::kAuto;
+ }
+ if (maxHeight == nsViewportInfo::kExtendToZoom) {
+ maxHeight = nsViewportInfo::kAuto;
+ }
+ if (minWidth == nsViewportInfo::kExtendToZoom) {
+ minWidth = maxWidth;
+ }
+ if (minHeight == nsViewportInfo::kExtendToZoom) {
+ minHeight = maxHeight;
+ }
+ } else {
+ CSSSize extendSize = displaySize / extendZoom;
+ if (maxWidth == nsViewportInfo::kExtendToZoom) {
+ maxWidth = extendSize.width;
+ }
+ if (maxHeight == nsViewportInfo::kExtendToZoom) {
+ maxHeight = extendSize.height;
+ }
+ if (minWidth == nsViewportInfo::kExtendToZoom) {
+ minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
+ }
+ if (minHeight == nsViewportInfo::kExtendToZoom) {
+ minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
+ }
+ }
+
+ // Resolve initial width and height from min/max descriptors
+ // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
+ CSSCoord width = nsViewportInfo::kAuto;
+ if (minWidth != nsViewportInfo::kAuto ||
+ maxWidth != nsViewportInfo::kAuto) {
+ width = nsViewportInfo::Max(
+ minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
+ }
+ CSSCoord height = nsViewportInfo::kAuto;
+ if (minHeight != nsViewportInfo::kAuto ||
+ maxHeight != nsViewportInfo::kAuto) {
+ height = nsViewportInfo::Max(
+ minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
+ }
+
+ // Resolve width value
+ // https://drafts.csswg.org/css-device-adapt/#resolve-width
+ if (width == nsViewportInfo::kAuto) {
+ if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
+ width = displaySize.width;
+ } else {
+ width = height * aDisplaySize.width / aDisplaySize.height;
+ }
+ }
+
+ // Resolve height value
+ // https://drafts.csswg.org/css-device-adapt/#resolve-height
+ if (height == nsViewportInfo::kAuto) {
+ if (aDisplaySize.width == 0) {
+ height = displaySize.height;
+ } else {
+ height = width * aDisplaySize.height / aDisplaySize.width;
+ }
+ }
+ MOZ_ASSERT(width != nsViewportInfo::kAuto &&
+ height != nsViewportInfo::kAuto);
+
+ CSSSize size(width, height);
+
+ CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
+ CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
+ CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
+
+ nsViewportInfo::AutoSizeFlag sizeFlag =
+ nsViewportInfo::AutoSizeFlag::FixedSize;
+ if (mMaxWidth == nsViewportInfo::kDeviceSize ||
+ (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
+ mScaleFloat.scale == 1.0f)) ||
+ (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
+ mMaxHeight < 0)) {
+ sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
+ }
+
+ // FIXME: Resolving width and height should be done above 'Resolve width
+ // value' and 'Resolve height value'.
+ if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
+ size = displaySize;
+ }
+
+ // The purpose of clamping the viewport width to a minimum size is to
+ // prevent page authors from setting it to a ridiculously small value.
+ // If the page is actually being rendered in a very small area (as might
+ // happen in e.g. Android 8's picture-in-picture mode), we don't want to
+ // prevent the viewport from taking on that size.
+ CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
+
+ size.width = clamped(size.width, effectiveMinSize.width,
+ float(kViewportMaxSize.width));
+
+ // Also recalculate the default zoom, if it wasn't specified in the
+ // metadata, and the width is specified.
+ if (!mValidScaleFloat && !mWidthStrEmpty) {
+ CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
+ scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
+ }
+
+ size.height = clamped(size.height, effectiveMinSize.height,
+ float(kViewportMaxSize.height));
+
+ // In cases of user-scalable=no, if we have a positive scale, clamp it to
+ // min and max, and then use the clamped value for the scale, the min, and
+ // the max. If we don't have a positive scale, assert that we are setting
+ // the auto scale flag.
+ if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
+ scaleFloat > CSSToScreenScale(0.0f)) {
+ scaleFloat = scaleMinFloat = scaleMaxFloat =
+ clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
+ }
+ MOZ_ASSERT(
+ scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
+ "If we don't have a positive scale, we should be using auto scale.");
+
+ // We need to perform a conversion, but only if the initial or maximum
+ // scale were set explicitly by the user.
+ if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
+ scaleFloat <= scaleMaxFloat) {
+ CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
+ size.width = std::max(size.width, displaySize.width);
+ size.height = std::max(size.height, displaySize.height);
+ } else if (effectiveValidMaxScale) {
+ CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
+ size.width = std::max(size.width, displaySize.width);
+ size.height = std::max(size.height, displaySize.height);
+ }
+
+ return nsViewportInfo(
+ scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
+ mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
+ : nsViewportInfo::AutoScaleFlag::AutoScale,
+ effectiveZoomFlag, mViewportFit);
+ }
+}
+
+ViewportMetaData Document::GetViewportMetaData() const {
+ return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
+ : ViewportMetaData();
+}
+
+void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
+ mLastModifiedViewportMetaData = std::move(aData);
+ // Trigger recomputation of the nsViewportInfo the next time it's queried.
+ mViewportType = Unknown;
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+}
+
+EventListenerManager* Document::GetOrCreateListenerManager() {
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ SetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager* Document::GetExistingListenerManager() const {
+ return mListenerManager;
+}
+
+void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ aVisitor.mCanHandle = true;
+ // FIXME! This is a hack to make middle mouse paste working also in Editor.
+ // Bug 329119
+ aVisitor.mForceContentDispatch = true;
+
+ // Load events must not propagate to |window| object, see bug 335251.
+ if (aVisitor.mEvent->mMessage != eLoad) {
+ nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
+ aVisitor.SetParentTarget(
+ window ? window->GetTargetForEventTargetChain() : nullptr, false);
+ }
+}
+
+already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
+ CallerType aCallerType,
+ ErrorResult& rv) const {
+ nsPresContext* presContext = GetPresContext();
+
+ // Create event even without presContext.
+ RefPtr<Event> ev =
+ EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
+ nullptr, aEventType, aCallerType);
+ if (!ev) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ WidgetEvent* e = ev->WidgetEventPtr();
+ e->mFlags.mBubbles = false;
+ e->mFlags.mCancelable = false;
+ return ev.forget();
+}
+
+void Document::FlushPendingNotifications(FlushType aType) {
+ mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
+ FlushPendingNotifications(flush);
+}
+
+void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
+ FlushType flushType = aFlush.mFlushType;
+
+ RefPtr<Document> documentOnStack = this;
+
+ // We need to flush the sink for non-HTML documents (because the XML
+ // parser still does insertion with deferred notifications). We
+ // also need to flush the sink if this is a layout-related flush, to
+ // make sure that layout is started as needed. But we can skip that
+ // part if we have no presshell or if it's already done an initial
+ // reflow.
+ if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
+ mPresShell && !mPresShell->DidInitialize())) &&
+ (mParser || mWeakSink)) {
+ nsCOMPtr<nsIContentSink> sink;
+ if (mParser) {
+ sink = mParser->GetContentSink();
+ } else {
+ sink = do_QueryReferent(mWeakSink);
+ if (!sink) {
+ mWeakSink = nullptr;
+ }
+ }
+ // Determine if it is safe to flush the sink notifications
+ // by determining if it safe to flush all the presshells.
+ if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
+ sink->FlushPendingNotifications(flushType);
+ }
+ }
+
+ // Should we be flushing pending binding constructors in here?
+
+ if (flushType <= FlushType::ContentAndNotify) {
+ // Nothing to do here
+ return;
+ }
+
+ // If we have a parent we must flush the parent too to ensure that our
+ // container is reflowed if its size was changed.
+ //
+ // We do it only if the subdocument and the parent can observe each other
+ // synchronously (that is, if we're not cross-origin), to avoid work that is
+ // not observable, and if the parent document has finished loading all its
+ // render-blocking stylesheets and may start laying out the document, to avoid
+ // unnecessary flashes of unstyled content on the parent document. Note that
+ // this last bit means that size-dependent media queries in this document may
+ // produce incorrect results temporarily.
+ //
+ // But if it's not safe to flush ourselves, then don't flush the parent, since
+ // that can cause things like resizes of our frame's widget, which we can't
+ // handle while flushing is unsafe.
+ if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
+ mParentDocument->MayStartLayout() && IsSafeToFlush()) {
+ ChangesToFlush parentFlush = aFlush;
+ if (flushType >= FlushType::Style) {
+ // Since media queries mean that a size change of our container can affect
+ // style, we need to promote a style flush on ourself to a layout flush on
+ // our parent, since we need our container to be the correct size to
+ // determine the correct style.
+ parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
+ }
+ mParentDocument->FlushPendingNotifications(parentFlush);
+ }
+
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->FlushPendingNotifications(aFlush);
+ }
+}
+
+void Document::FlushExternalResources(FlushType aType) {
+ NS_ASSERTION(
+ aType >= FlushType::Style,
+ "should only need to flush for style or higher in external resources");
+ if (GetDisplayDocument()) {
+ return;
+ }
+
+ auto flush = [aType](Document& aDoc) {
+ aDoc.FlushPendingNotifications(aType);
+ return CallState::Continue;
+ };
+
+ EnumerateExternalResources(flush);
+}
+
+void Document::SetXMLDeclaration(const char16_t* aVersion,
+ const char16_t* aEncoding,
+ const int32_t aStandalone) {
+ if (!aVersion || *aVersion == '\0') {
+ mXMLDeclarationBits = 0;
+ return;
+ }
+
+ mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
+
+ if (aEncoding && *aEncoding != '\0') {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
+ }
+
+ if (aStandalone == 1) {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
+ XML_DECLARATION_BITS_STANDALONE_YES;
+ } else if (aStandalone == 0) {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
+ }
+}
+
+void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
+ nsAString& aStandalone) {
+ aVersion.Truncate();
+ aEncoding.Truncate();
+ aStandalone.Truncate();
+
+ if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
+ return;
+ }
+
+ // always until we start supporting 1.1 etc.
+ aVersion.AssignLiteral("1.0");
+
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
+ // This is what we have stored, not necessarily what was written
+ // in the original
+ GetCharacterSet(aEncoding);
+ }
+
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
+ aStandalone.AssignLiteral("yes");
+ } else {
+ aStandalone.AssignLiteral("no");
+ }
+ }
+}
+
+void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
+ mColorSchemeMetaTags.Insert(aMeta);
+ RecomputeColorScheme();
+}
+
+void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
+ mColorSchemeMetaTags.RemoveElement(aMeta);
+ RecomputeColorScheme();
+}
+
+void Document::RecomputeColorScheme() {
+ if (!StaticPrefs::layout_css_color_scheme_enabled()) {
+ return;
+ }
+ auto oldColorScheme = mColorSchemeBits;
+ mColorSchemeBits = 0;
+ const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
+ for (const HTMLMetaElement* el : elements) {
+ nsAutoString content;
+ if (!el->GetAttr(nsGkAtoms::content, content)) {
+ continue;
+ }
+
+ NS_ConvertUTF16toUTF8 contentU8(content);
+ if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
+ break;
+ }
+ }
+
+ if (mColorSchemeBits == oldColorScheme) {
+ return;
+ }
+
+ if (nsPresContext* pc = GetPresContext()) {
+ // This affects system colors, which are inherited, so we need to recascade.
+ pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
+ }
+}
+
+bool Document::IsScriptEnabled() const {
+ // If this document is sandboxed without 'allow-scripts'
+ // script is not enabled
+ if (HasScriptsBlockedBySandbox()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject =
+ do_QueryInterface(GetInnerWindow());
+ if (!globalObject || !globalObject->HasJSGlobal()) {
+ return false;
+ }
+
+ return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
+ .Allowed();
+}
+
+void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
+ PRTime modDate = 0;
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (httpChannel) {
+ nsAutoCString tmp;
+ rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
+
+ if (NS_SUCCEEDED(rv)) {
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
+ if (st == PR_SUCCESS) {
+ modDate = time;
+ }
+ }
+
+ static const char* const headers[] = {
+ "default-style", "content-style-type", "content-language",
+ "content-disposition", "refresh", "x-dns-prefetch-control",
+ "x-frame-options", "origin-trial",
+ // add more http headers if you need
+ // XXXbz don't add content-location support without reading bug
+ // 238654 and its dependencies/dups first.
+ 0};
+
+ nsAutoCString headerVal;
+ const char* const* name = headers;
+ while (*name) {
+ rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
+ if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
+ RefPtr<nsAtom> key = NS_Atomize(*name);
+ SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
+ }
+ ++name;
+ }
+ } else {
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
+ if (fileChannel) {
+ nsCOMPtr<nsIFile> file;
+ fileChannel->GetFile(getter_AddRefs(file));
+ if (file) {
+ PRTime msecs;
+ rv = file->GetLastModifiedTime(&msecs);
+
+ if (NS_SUCCEEDED(rv)) {
+ modDate = msecs * int64_t(PR_USEC_PER_MSEC);
+ }
+ }
+ } else {
+ nsAutoCString contentDisp;
+ rv = aChannel->GetContentDispositionHeader(contentDisp);
+ if (NS_SUCCEEDED(rv)) {
+ SetHeaderData(nsGkAtoms::headerContentDisposition,
+ NS_ConvertASCIItoUTF16(contentDisp));
+ }
+ }
+ }
+
+ mLastModified.Truncate();
+ if (modDate != 0) {
+ GetFormattedTimeString(modDate, mLastModified);
+ }
+}
+
+void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
+ // set any HTTP-EQUIV data into document's header data as well as url
+ nsAutoString header;
+ aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
+ if (!header.IsEmpty()) {
+ // Ignore META REFRESH when document is sandboxed from automatic features.
+ nsContentUtils::ASCIIToLower(header);
+ if (nsGkAtoms::refresh->Equals(header) &&
+ (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
+ return;
+ }
+
+ nsAutoString result;
+ aMetaElement->GetAttr(nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
+ SetHeaderData(fieldAtom, result);
+ }
+ }
+
+ if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ nsGkAtoms::handheldFriendly, eIgnoreCase)) {
+ nsAutoString result;
+ aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ nsContentUtils::ASCIIToLower(result);
+ SetHeaderData(nsGkAtoms::handheldFriendly, result);
+ }
+ }
+}
+
+already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
+ nsAtom* aPrefix,
+ int32_t aNamespaceID,
+ const nsAString* aIs) {
+#ifdef DEBUG
+ nsAutoString qName;
+ if (aPrefix) {
+ aPrefix->ToString(qName);
+ qName.Append(':');
+ }
+ qName.Append(aName);
+
+ // Note: "a:b:c" is a valid name in non-namespaces XML, and
+ // Document::CreateElement can call us with such a name and no prefix,
+ // which would cause an error if we just used true here.
+ bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
+ NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
+ "Don't pass invalid prefixes to Document::CreateElem, "
+ "check caller.");
+#endif
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
+ getter_AddRefs(nodeInfo));
+ NS_ENSURE_TRUE(nodeInfo, nullptr);
+
+ nsCOMPtr<Element> element;
+ nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
+ NOT_FROM_PARSER, aIs);
+ return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
+}
+
+bool Document::IsSafeToFlush() const {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return true;
+ }
+ return presShell->IsSafeToFlush();
+}
+
+void Document::Sanitize() {
+ // Sanitize the document by resetting all (current and former) password fields
+ // and any form fields with autocomplete=off to their default values. We do
+ // this now, instead of when the presentation is restored, to offer some
+ // protection in case there is ever an exploit that allows a cached document
+ // to be accessed from a different document.
+
+ // First locate all input elements, regardless of whether they are
+ // in a form, and reset the password and autocomplete=off elements.
+
+ RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
+
+ nsAutoString value;
+
+ uint32_t length = nodes->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ NS_ASSERTION(nodes->Item(i), "null item in node list!");
+
+ RefPtr<HTMLInputElement> input =
+ HTMLInputElement::FromNodeOrNull(nodes->Item(i));
+ if (!input) continue;
+
+ input->GetAttr(nsGkAtoms::autocomplete, value);
+ if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
+ input->Reset();
+ }
+ }
+
+ // Now locate all _form_ elements that have autocomplete=off and reset them
+ nodes = GetElementsByTagName(u"form"_ns);
+
+ length = nodes->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ // Reset() may change the list dynamically.
+ RefPtr<HTMLFormElement> form =
+ HTMLFormElement::FromNodeOrNull(nodes->Item(i));
+ if (!form) continue;
+
+ form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
+ if (value.LowerCaseEqualsLiteral("off")) form->Reset();
+ }
+}
+
+void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
+ if (!mSubDocuments) {
+ return;
+ }
+
+ // PLDHashTable::Iterator can't handle modifications while iterating so we
+ // copy all entries to an array first before calling any callbacks.
+ AutoTArray<RefPtr<Document>, 8> subdocs;
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ if (Document* subdoc = entry->mSubDocument) {
+ subdocs.AppendElement(subdoc);
+ }
+ }
+ for (auto& subdoc : subdocs) {
+ if (aCallback(*subdoc) == CallState::Stop) {
+ break;
+ }
+ }
+}
+
+void Document::CollectDescendantDocuments(
+ nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
+ if (!mSubDocuments) {
+ return;
+ }
+
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ const Document* subdoc = entry->mSubDocument;
+ if (subdoc) {
+ if (aCallback(subdoc)) {
+ aDescendants.AppendElement(entry->mSubDocument);
+ }
+ subdoc->CollectDescendantDocuments(aDescendants, aCallback);
+ }
+ }
+}
+
+bool Document::CanSavePresentation(nsIRequest* aNewRequest,
+ uint32_t& aBFCacheCombo,
+ bool aIncludeSubdocuments,
+ bool aAllowUnloadListeners) {
+ bool ret = true;
+
+ if (!IsBFCachingAllowed()) {
+ aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
+ ret = false;
+ }
+
+ nsAutoCString uri;
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
+ if (mDocumentURI) {
+ mDocumentURI->GetSpec(uri);
+ }
+ }
+
+ if (EventHandlingSuppressed()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked on event handling suppression", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
+ ret = false;
+ }
+
+ // Do not allow suspended windows to be placed in the
+ // bfcache. This method is also used to verify a document
+ // coming out of the bfcache is ok to restore, though. So
+ // we only want to block suspend windows that aren't also
+ // frozen.
+ nsPIDOMWindowInner* win = GetInnerWindow();
+ if (win && win->IsSuspended() && !win->IsFrozen()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked on suspended Window", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::SUSPENDED;
+ ret = false;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
+ bool thirdParty = false;
+ // Currently some other mobile browsers seem to bfcache only cross-domain
+ // pages, but bfcache those also when there are unload event listeners, so
+ // this is trying to match that behavior as much as possible.
+ bool allowUnloadListeners =
+ aAllowUnloadListeners &&
+ StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
+ (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
+ channel, &thirdParty)) &&
+ thirdParty));
+
+ // Check our event listener manager for unload/beforeunload listeners.
+ nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
+ if (!allowUnloadListeners && piTarget) {
+ EventListenerManager* manager = piTarget->GetExistingListenerManager();
+ if (manager) {
+ if (manager->HasUnloadListeners()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to unload handlers", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
+ ret = false;
+ }
+ if (manager->HasBeforeUnloadListeners()) {
+ if (!mozilla::SessionHistoryInParent() ||
+ !StaticPrefs::
+ docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
+ MOZ_LOG(
+ gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to beforeUnload handlers", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
+ ret = false;
+ }
+ }
+ }
+ }
+
+ // Check if we have pending network requests
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ loadGroup->GetRequests(getter_AddRefs(requests));
+
+ bool hasMore = false;
+
+ // We want to bail out if we have any requests other than aNewRequest (or
+ // in the case when aNewRequest is a part of a multipart response the base
+ // channel the multipart response is coming in on).
+ nsCOMPtr<nsIChannel> baseChannel;
+ nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
+ if (part) {
+ part->GetBaseChannel(getter_AddRefs(baseChannel));
+ }
+
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
+ if (request && request != aNewRequest && request != baseChannel) {
+ // Favicon loads don't need to block caching.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
+ if (li->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ continue;
+ }
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
+ nsAutoCString requestName;
+ request->GetName(requestName);
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Save of %s blocked because document has request %s",
+ uri.get(), requestName.get()));
+ }
+ aBFCacheCombo |= BFCacheStatus::REQUEST;
+ ret = false;
+ }
+ }
+ }
+
+ // Check if we have active GetUserMedia use
+ if (MediaManager::Exists() && win &&
+ MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to GetUserMedia", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
+ ret = false;
+ }
+
+#ifdef MOZ_WEBRTC
+ // Check if we have active PeerConnections
+ if (win && win->HasActivePeerConnections()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to PeerConnection", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
+ ret = false;
+ }
+#endif // MOZ_WEBRTC
+
+ // Don't save presentations for documents containing EME content, so that
+ // CDMs reliably shutdown upon user navigation.
+ if (ContainsEMEContent()) {
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
+ ret = false;
+ }
+
+ // Don't save presentations for documents containing MSE content, to
+ // reduce memory usage.
+ if (ContainsMSEContent()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to MSE use", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
+ ret = false;
+ }
+
+ if (aIncludeSubdocuments && mSubDocuments) {
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ Document* subdoc = entry->mSubDocument;
+
+ uint32_t subDocBFCacheCombo = 0;
+ // The aIgnoreRequest we were passed is only for us, so don't pass it on.
+ bool canCache =
+ subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
+ true, allowUnloadListeners)
+ : false;
+ if (!canCache) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to subdocument blocked", uri.get()));
+ aBFCacheCombo |= subDocBFCacheCombo;
+ ret = false;
+ }
+ }
+ }
+
+ if (!mozilla::BFCacheInParent()) {
+ // BFCache is currently not compatible with remote subframes (bug 1609324)
+ if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
+ for (auto& child : browsingContext->Children()) {
+ if (!child->IsInProcess()) {
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (win) {
+ auto* globalWindow = nsGlobalWindowInner::Cast(win);
+#ifdef MOZ_WEBSPEECH
+ if (globalWindow->HasActiveSpeechSynthesis()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to Speech use", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
+ ret = false;
+ }
+#endif
+ if (globalWindow->HasUsedVR()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to having used VR", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
+ ret = false;
+ }
+
+ if (win->HasActiveLocks()) {
+ MOZ_LOG(
+ gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to having active lock requests", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
+ ret = false;
+ }
+
+ if (win->HasActiveWebTransports()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to WebTransport", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+void Document::Destroy() {
+ // The ContentViewer wants to release the document now. So, tell our content
+ // to drop any references to the document so that it can be destroyed.
+ if (mIsGoingAway) {
+ return;
+ }
+
+ ReportDocumentUseCounters();
+ SetDevToolsWatchingDOMMutations(false);
+
+ mIsGoingAway = true;
+
+ ScriptLoader()->Destroy();
+ SetScriptGlobalObject(nullptr);
+ RemovedFromDocShell();
+
+ bool oldVal = mInUnlinkOrDeletion;
+ mInUnlinkOrDeletion = true;
+
+#ifdef DEBUG
+ uint32_t oldChildCount = GetChildCount();
+#endif
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->DestroyContent();
+ MOZ_ASSERT(child->GetParentNode() == this);
+ }
+ MOZ_ASSERT(oldChildCount == GetChildCount());
+ MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
+
+ mInUnlinkOrDeletion = oldVal;
+
+ mLayoutHistoryState = nullptr;
+
+ if (mOriginalDocument) {
+ mOriginalDocument->mLatestStaticClone = nullptr;
+ }
+
+ if (IsStaticDocument()) {
+ RemoveProperty(nsGkAtoms::printisfocuseddoc);
+ RemoveProperty(nsGkAtoms::printselectionranges);
+ }
+
+ // Shut down our external resource map. We might not need this for
+ // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
+ // tearing down all those frame trees right now is the right thing to do.
+ mExternalResourceMap.Shutdown();
+
+ // Manually break cycles via promise's global object pointer.
+ mReadyForIdle = nullptr;
+ mOrientationPendingPromise = nullptr;
+
+ // To break cycles.
+ mPreloadService.ClearAllPreloads();
+
+ if (mDocumentL10n) {
+ mDocumentL10n->Destroy();
+ }
+}
+
+void Document::RemovedFromDocShell() {
+ mEditingState = EditingState::eOff;
+
+ if (mRemovedFromDocShell) return;
+
+ mRemovedFromDocShell = true;
+ NotifyActivityChanged();
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->SaveSubtreeState();
+ }
+
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ docShell->SynchronizeLayoutHistoryState();
+ }
+}
+
+already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
+ const {
+ nsCOMPtr<nsILayoutHistoryState> state;
+ if (!mScriptGlobalObject) {
+ state = mLayoutHistoryState;
+ } else {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ docShell->GetLayoutHistoryState(getter_AddRefs(state));
+ }
+ }
+
+ return state.forget();
+}
+
+void Document::EnsureOnloadBlocker() {
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ // Check first to see if mOnloadBlocker is in the loadgroup.
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ loadGroup->GetRequests(getter_AddRefs(requests));
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
+ if (request && request == mOnloadBlocker) {
+ return;
+ }
+ }
+
+ // Not in the loadgroup, so add it.
+ loadGroup->AddRequest(mOnloadBlocker, nullptr);
+ }
+ }
+}
+
+void Document::BlockOnload() {
+ if (mDisplayDocument) {
+ mDisplayDocument->BlockOnload();
+ return;
+ }
+
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
+ if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
+ loadGroup->AddRequest(mOnloadBlocker, nullptr);
+ }
+ }
+ ++mOnloadBlockCount;
+}
+
+void Document::UnblockOnload(bool aFireSync) {
+ if (mDisplayDocument) {
+ mDisplayDocument->UnblockOnload(aFireSync);
+ return;
+ }
+
+ --mOnloadBlockCount;
+
+ if (mOnloadBlockCount == 0) {
+ if (mScriptGlobalObject) {
+ // Only manipulate the loadgroup in this case, because if
+ // mScriptGlobalObject is null, it's not ours.
+ if (aFireSync) {
+ // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
+ ++mOnloadBlockCount;
+ DoUnblockOnload();
+ } else {
+ PostUnblockOnloadEvent();
+ }
+ } else if (mIsBeingUsedAsImage) {
+ // To correctly unblock onload for a document that contains an SVG
+ // image, we need to know when all of the SVG document's resources are
+ // done loading, in a way comparable to |window.onload|. We fire this
+ // event to indicate that the SVG should be considered fully loaded.
+ // Because scripting is disabled on SVG-as-image documents, this event
+ // is not accessible to content authors. (See bug 837315.)
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
+ CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+}
+
+class nsUnblockOnloadEvent : public Runnable {
+ public:
+ explicit nsUnblockOnloadEvent(Document* aDoc)
+ : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
+ NS_IMETHOD Run() override {
+ mDoc->DoUnblockOnload();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<Document> mDoc;
+};
+
+void Document::PostUnblockOnloadEvent() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
+ nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
+ if (NS_SUCCEEDED(rv)) {
+ // Stabilize block count so we don't post more events while this one is up
+ ++mOnloadBlockCount;
+ } else {
+ NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
+ }
+}
+
+void Document::DoUnblockOnload() {
+ MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
+ MOZ_ASSERT(mOnloadBlockCount != 0,
+ "Shouldn't have a count of zero here, since we stabilized in "
+ "PostUnblockOnloadEvent");
+
+ --mOnloadBlockCount;
+
+ if (mOnloadBlockCount != 0) {
+ // We blocked again after the last unblock. Nothing to do here. We'll
+ // post a new event when we unblock again.
+ return;
+ }
+
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mScriptGlobalObject) {
+ if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
+ loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+ }
+ }
+}
+
+nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ nsIContent* content = f->GetContent();
+ if (!content) {
+ continue;
+ }
+
+ if (content->OwnerDoc() == this) {
+ return content;
+ }
+ // We must be in a subdocument so jump directly to the root frame.
+ // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
+ // the containing document.
+ f = f->PresContext()->GetPresShell()->GetRootFrame();
+ }
+
+ return nullptr;
+}
+
+void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
+ const nsAString& aType, bool aInFrameSwap,
+ bool aPersisted, bool aOnlySystemGroup) {
+ if (!aDispatchTarget) {
+ return;
+ }
+
+ PageTransitionEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mPersisted = aPersisted;
+ init.mInFrameSwap = aInFrameSwap;
+
+ RefPtr<PageTransitionEvent> event =
+ PageTransitionEvent::Constructor(this, aType, init);
+
+ event->SetTrusted(true);
+ event->SetTarget(this);
+ if (aOnlySystemGroup) {
+ event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
+ }
+ EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
+ nullptr);
+}
+
+void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup) {
+ if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (GetDocumentURI()) {
+ uri = GetDocumentURI()->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
+ }
+
+ const bool inFrameLoaderSwap = !!aDispatchStartTarget;
+ MOZ_DIAGNOSTIC_ASSERT(
+ inFrameLoaderSwap ==
+ (mDocumentContainer && mDocumentContainer->InFrameSwap()));
+
+ Element* root = GetRootElement();
+ if (aPersisted && root) {
+ // Send out notifications that our <link> elements are attached.
+ RefPtr<nsContentList> links =
+ NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
+ }
+ }
+
+ // See Document
+ if (!inFrameLoaderSwap) {
+ if (aPersisted) {
+ ImageTracker()->SetAnimatingState(true);
+ }
+
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = true;
+ mVisible = true;
+
+ UpdateVisibilityState();
+ }
+
+ NotifyActivityChanged();
+
+ auto notifyExternal = [aPersisted](Document& aExternalResource) {
+ aExternalResource.OnPageShow(aPersisted, nullptr);
+ return CallState::Continue;
+ };
+ EnumerateExternalResources(notifyExternal);
+
+ if (mAnimationController) {
+ mAnimationController->OnPageShow();
+ }
+
+ if (!mIsBeingUsedAsImage) {
+ // Dispatch observer notification to notify observers page is shown.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal() ? "chrome-page-shown"
+ : "content-page-shown",
+ nullptr);
+ }
+
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+ DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
+ aPersisted, aOnlySystemGroup);
+ }
+}
+
+static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
+ if (nsPresContext* presContext = aDocument.GetPresContext()) {
+ auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
+ FullscreenEventType::Change, &aDocument, aTarget);
+ presContext->RefreshDriver()->ScheduleFullscreenEvent(
+ std::move(pendingEvent));
+ }
+}
+
+void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup) {
+ if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (GetDocumentURI()) {
+ uri = GetDocumentURI()->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
+ }
+
+ const bool inFrameLoaderSwap = !!aDispatchStartTarget;
+ MOZ_DIAGNOSTIC_ASSERT(
+ inFrameLoaderSwap ==
+ (mDocumentContainer && mDocumentContainer->InFrameSwap()));
+
+ // Send out notifications that our <link> elements are detached,
+ // but only if this is not a full unload.
+ Element* root = GetRootElement();
+ if (aPersisted && root) {
+ RefPtr<nsContentList> links =
+ NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
+ }
+ }
+
+ if (mAnimationController) {
+ mAnimationController->OnPageHide();
+ }
+
+ if (!inFrameLoaderSwap) {
+ if (aPersisted) {
+ // We do not stop the animations (bug 1024343) when the page is refreshing
+ // while being dragged out.
+ ImageTracker()->SetAnimatingState(false);
+ }
+
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = false;
+ mVisible = false;
+ }
+
+ ExitPointerLock();
+
+ if (!mIsBeingUsedAsImage) {
+ // Dispatch observer notification to notify observers page is hidden.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal()
+ ? "chrome-page-hidden"
+ : "content-page-hidden",
+ nullptr);
+ }
+
+ // Now send out a PageHide event.
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+ {
+ PageUnloadingEventTimeStamp timeStamp(this);
+ DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
+ aPersisted, aOnlySystemGroup);
+ }
+ }
+
+ if (!inFrameLoaderSwap) {
+ UpdateVisibilityState();
+ }
+
+ auto notifyExternal = [aPersisted](Document& aExternalResource) {
+ aExternalResource.OnPageHide(aPersisted, nullptr);
+ return CallState::Continue;
+ };
+ EnumerateExternalResources(notifyExternal);
+ NotifyActivityChanged();
+
+ ClearPendingFullscreenRequests(this);
+ if (Fullscreen()) {
+ // If this document was fullscreen, we should exit fullscreen in this
+ // doctree branch. This ensures that if the user navigates while in
+ // fullscreen mode we don't leave its still visible ancestor documents
+ // in fullscreen mode. So exit fullscreen in the document's fullscreen
+ // root document, as this will exit fullscreen in all the root's
+ // descendant documents. Note that documents are removed from the
+ // doctree by the time OnPageHide() is called, so we must store a
+ // reference to the root (in Document::mFullscreenRoot) since we can't
+ // just traverse the doctree to get the root.
+ Document::ExitFullscreenInDocTree(this);
+
+ // Since the document is removed from the doctree before OnPageHide() is
+ // called, ExitFullscreen() can't traverse from the root down to *this*
+ // document, so we must manually call CleanupFullscreenState() below too.
+ // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
+ // so we *must* call it after ExitFullscreen(), not before.
+ // OnPageHide() is called in every hidden (i.e. descendant) document,
+ // so calling CleanupFullscreenState() here will ensure all hidden
+ // documents have their fullscreen state reset.
+ CleanupFullscreenState();
+
+ // The fullscreenchange event is to be queued in the refresh driver,
+ // however a hidden page wouldn't trigger that again, so it makes no
+ // sense to dispatch such event here.
+ }
+}
+
+void Document::WillDispatchMutationEvent(nsINode* aTarget) {
+ NS_ASSERTION(
+ mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
+ "mSubtreeModifiedTargets not cleared after dispatching?");
+ ++mSubtreeModifiedDepth;
+ if (aTarget) {
+ // MayDispatchMutationEvent is often called just before this method,
+ // so it has already appended the node to mSubtreeModifiedTargets.
+ int32_t count = mSubtreeModifiedTargets.Count();
+ if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
+ mSubtreeModifiedTargets.AppendObject(aTarget);
+ }
+ }
+}
+
+void Document::MutationEventDispatched(nsINode* aTarget) {
+ if (--mSubtreeModifiedDepth) {
+ return;
+ }
+
+ int32_t count = mSubtreeModifiedTargets.Count();
+ if (!count) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ if (window &&
+ !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
+ mSubtreeModifiedTargets.Clear();
+ return;
+ }
+
+ nsCOMArray<nsINode> realTargets;
+ for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
+ if (possibleTarget->ChromeOnlyAccess()) {
+ continue;
+ }
+
+ nsINode* commonAncestor = nullptr;
+ int32_t realTargetCount = realTargets.Count();
+ for (int32_t j = 0; j < realTargetCount; ++j) {
+ commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
+ possibleTarget, realTargets[j]);
+ if (commonAncestor) {
+ realTargets.ReplaceObjectAt(commonAncestor, j);
+ break;
+ }
+ }
+ if (!commonAncestor) {
+ realTargets.AppendObject(possibleTarget);
+ }
+ }
+
+ mSubtreeModifiedTargets.Clear();
+
+ for (const nsCOMPtr<nsINode>& target : realTargets) {
+ InternalMutationEvent mutation(true, eLegacySubtreeModified);
+ // MOZ_KnownLive due to bug 1620312
+ AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
+ }
+}
+
+void Document::DestroyElementMaps() {
+#ifdef DEBUG
+ mStyledLinksCleared = true;
+#endif
+ mStyledLinks.Clear();
+ // Notify ID change listeners before clearing the identifier map.
+ for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->ClearAndNotify();
+ }
+ mIdentifierMap.Clear();
+ mComposedShadowRoots.Clear();
+ mResponsiveContent.Clear();
+ IncrementExpandoGeneration(*this);
+}
+
+void Document::RefreshLinkHrefs() {
+ // Get a list of all links we know about. We will reset them, which will
+ // remove them from the document, so we need a copy of what is in the
+ // hashtable.
+ const LinkArray linksToNotify = ToArray(mStyledLinks);
+
+ // Reset all of our styled links.
+ nsAutoScriptBlocker scriptBlocker;
+ for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
+ linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
+ }
+}
+
+nsresult Document::CloneDocHelper(Document* clone) const {
+ clone->mIsStaticDocument = mCreatingStaticClone;
+
+ // Init document
+ nsresult rv = clone->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCreatingStaticClone) {
+ if (mOriginalDocument) {
+ clone->mOriginalDocument = mOriginalDocument;
+ } else {
+ clone->mOriginalDocument = const_cast<Document*>(this);
+ }
+ clone->mOriginalDocument->mLatestStaticClone = clone;
+ clone->mOriginalDocument->mStaticCloneCount++;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+
+ // |mDocumentContainer| is the container of the document that is being
+ // created and not the original container. See CreateStaticClone function().
+ nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
+ if (docLoader) {
+ docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
+ }
+ nsCOMPtr<nsIChannel> channel = GetChannel();
+ nsCOMPtr<nsIURI> uri;
+ if (channel) {
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ } else {
+ uri = Document::GetDocumentURI();
+ }
+ clone->mChannel = channel;
+ clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
+ if (uri) {
+ clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
+ }
+
+ clone->mIsSrcdocDocument = mIsSrcdocDocument;
+ clone->SetContainer(mDocumentContainer);
+
+ // Setup the navigation time. This will be needed by any animations in the
+ // document, even if they are only paused.
+ MOZ_ASSERT(!clone->GetNavigationTiming(),
+ "Navigation time was already set?");
+ if (mTiming) {
+ RefPtr<nsDOMNavigationTiming> timing =
+ mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
+ clone->SetNavigationTiming(timing);
+ }
+ clone->SetCsp(mCSP);
+ }
+
+ // Now ensure that our clone has the same URI, base URI, and principal as us.
+ // We do this after the mCreatingStaticClone block above, because that block
+ // can set the base URI to an incorrect value in cases when base URI
+ // information came from the channel. So we override explicitly, and do it
+ // for all these properties, in case ResetToURI messes with any of the rest of
+ // them.
+ clone->SetDocumentURI(Document::GetDocumentURI());
+ clone->SetChromeXHRDocURI(mChromeXHRDocURI);
+ clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
+ clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
+ clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
+ // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
+ // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
+ // when printed standalone via window.print() (where there won't be a parent
+ // document to grab the URI from).
+ clone->mDocumentBaseURI = GetDocBaseURI();
+ clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
+ clone->mReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
+ clone->mPreloadReferrerInfo = clone->mReferrerInfo;
+
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
+ if (mCreatingStaticClone) {
+ // If we're doing a static clone (print, print preview), then we're going to
+ // be setting a scope object after the clone. It's better to set it only
+ // once, so we don't do that here. However, we do want to act as if there is
+ // a script handling object. So we set mHasHadScriptHandlingObject.
+ clone->mHasHadScriptHandlingObject = true;
+ } else if (scriptObject) {
+ clone->SetScriptHandlingObject(scriptObject);
+ } else {
+ clone->SetScopeObject(GetScopeObject());
+ }
+ // Make the clone a data document
+ clone->SetLoadedAsData(
+ true,
+ /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
+
+ // Misc state
+
+ // State from Document
+ clone->mCharacterSet = mCharacterSet;
+ clone->mCharacterSetSource = mCharacterSetSource;
+ clone->SetCompatibilityMode(mCompatMode);
+ clone->mBidiOptions = mBidiOptions;
+ clone->mContentLanguage = mContentLanguage;
+ clone->SetContentType(GetContentTypeInternal());
+ clone->mSecurityInfo = mSecurityInfo;
+
+ // State from Document
+ clone->mType = mType;
+ clone->mXMLDeclarationBits = mXMLDeclarationBits;
+ clone->mBaseTarget = mBaseTarget;
+
+ return NS_OK;
+}
+
+void Document::NotifyLoading(bool aNewParentIsLoading,
+ const ReadyState& aCurrentState,
+ ReadyState aNewState) {
+ // Mirror the top-level loading state down to all subdocuments
+ bool was_loading = mAncestorIsLoading ||
+ aCurrentState == READYSTATE_LOADING ||
+ aCurrentState == READYSTATE_INTERACTIVE;
+ bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
+ aNewState == READYSTATE_INTERACTIVE; // new value for state
+ bool set_load_state = was_loading != is_loading;
+
+ MOZ_LOG(
+ gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
+ "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
+ "set_load_state: %d",
+ (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
+ (int)aNewState, was_loading, is_loading, set_load_state));
+
+ mAncestorIsLoading = aNewParentIsLoading;
+ if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
+ // Tell our innerwindow (and thus TimeoutManager)
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner) {
+ inner->SetActiveLoadingState(is_loading);
+ }
+ BrowsingContext* context = GetBrowsingContext();
+ if (context) {
+ // Don't use PreOrderWalk to mirror this down; go down one level as a
+ // time so we can set mAncestorIsLoading and take into account the
+ // readystates of the subdocument. In the child process it will call
+ // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
+ // iterate it's children
+ for (auto& child : context->Children()) {
+ MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
+ // Setting ancestor loading on a discarded browsing context has no
+ // effect.
+ Unused << child->SetAncestorLoading(is_loading);
+ }
+ }
+ }
+}
+
+void Document::SetReadyStateInternal(ReadyState aReadyState,
+ bool aUpdateTimingInformation) {
+ if (aReadyState == READYSTATE_UNINITIALIZED) {
+ // Transition back to uninitialized happens only to keep assertions happy
+ // right before readyState transitions to something else. Make this
+ // transition undetectable by Web content.
+ mReadyState = aReadyState;
+ return;
+ }
+
+ if (IsTopLevelContentDocument()) {
+ if (aReadyState == READYSTATE_LOADING) {
+ AddToplevelLoadingDocument(this);
+ } else if (aReadyState == READYSTATE_COMPLETE) {
+ RemoveToplevelLoadingDocument(this);
+ }
+ }
+
+ if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
+ SetLoadingOrRestoredFromBFCacheTimeStampToNow();
+ }
+ NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
+ mReadyState = aReadyState;
+ if (aUpdateTimingInformation && mTiming) {
+ switch (aReadyState) {
+ case READYSTATE_LOADING:
+ mTiming->NotifyDOMLoading(GetDocumentURI());
+ break;
+ case READYSTATE_INTERACTIVE:
+ mTiming->NotifyDOMInteractive(GetDocumentURI());
+ break;
+ case READYSTATE_COMPLETE:
+ mTiming->NotifyDOMComplete(GetDocumentURI());
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
+ break;
+ }
+ }
+ // At the time of loading start, we don't have timing object, record time.
+
+ if (READYSTATE_INTERACTIVE == aReadyState &&
+ NodePrincipal()->IsSystemPrincipal()) {
+ if (!mXULPersist) {
+ mXULPersist = new XULPersist(this);
+ mXULPersist->Init();
+ }
+ if (!mChromeObserver) {
+ mChromeObserver = new ChromeObserver(this);
+ mChromeObserver->Init();
+ }
+ }
+
+ if (aUpdateTimingInformation) {
+ RecordNavigationTiming(aReadyState);
+ }
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
+}
+
+void Document::GetReadyState(nsAString& aReadyState) const {
+ switch (mReadyState) {
+ case READYSTATE_LOADING:
+ aReadyState.AssignLiteral(u"loading");
+ break;
+ case READYSTATE_INTERACTIVE:
+ aReadyState.AssignLiteral(u"interactive");
+ break;
+ case READYSTATE_COMPLETE:
+ aReadyState.AssignLiteral(u"complete");
+ break;
+ default:
+ aReadyState.AssignLiteral(u"uninitialized");
+ }
+}
+
+void Document::SuppressEventHandling(uint32_t aIncrease) {
+ mEventsSuppressed += aIncrease;
+ if (mEventsSuppressed == aIncrease) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
+ }
+ }
+ UpdateFrameRequestCallbackSchedulingState();
+ for (uint32_t i = 0; i < aIncrease; ++i) {
+ ScriptLoader()->AddExecuteBlocker();
+ }
+
+ auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
+ aSubDoc.SuppressEventHandling(aIncrease);
+ return CallState::Continue;
+ };
+
+ EnumerateSubDocuments(suppressInSubDoc);
+}
+
+void Document::NotifyAbortedLoad() {
+ // If we still have outstanding work blocking DOMContentLoaded,
+ // then don't try to change the readystate now, but wait until
+ // they finish and then do so.
+ if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
+ mSetCompleteAfterDOMContentLoaded = true;
+ return;
+ }
+
+ // Otherwise we're fully done at this point, so set the
+ // readystate to complete.
+ if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
+ SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
+ nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (MOZ_UNLIKELY(!fm)) {
+ return;
+ }
+
+ nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
+ for (uint32_t i = 0; i < documents.Length(); ++i) {
+ nsCOMPtr<Document> document = std::move(documents[i]);
+ // NB: Don't bother trying to fire delayed events on documents that were
+ // closed before this event ran.
+ if (!document->EventHandlingSuppressed()) {
+ fm->FireDelayedEvents(document);
+ RefPtr<PresShell> presShell = document->GetPresShell();
+ if (presShell) {
+ // Only fire events for active documents.
+ bool fire = aFireEvents && document->GetInnerWindow() &&
+ document->GetInnerWindow()->IsCurrentInnerWindow();
+ presShell->FireOrClearDelayedEvents(fire);
+ }
+ document->FireOrClearPostMessageEvents(aFireEvents);
+ }
+ }
+}
+
+void Document::PreloadPictureClosed() {
+ MOZ_ASSERT(mPreloadPictureDepth > 0);
+ mPreloadPictureDepth--;
+ if (mPreloadPictureDepth == 0) {
+ mPreloadPictureFoundSource.SetIsVoid(true);
+ }
+}
+
+void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr) {
+ // Nested pictures are not valid syntax, so while we'll eventually load them,
+ // it's not worth tracking sources mixed between nesting levels to preload
+ // them effectively.
+ if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
+ // <picture> selects the first matching source, so if this returns a URI we
+ // needn't consider new sources until a new <picture> is encountered.
+ bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
+ this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
+ aMediaAttr, mPreloadPictureFoundSource);
+ if (found && mPreloadPictureFoundSource.IsVoid()) {
+ // Found an empty source, which counts
+ mPreloadPictureFoundSource.SetIsVoid(false);
+ }
+ }
+}
+
+already_AddRefed<nsIURI> Document::ResolvePreloadImage(
+ nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr, bool* aIsImgSet) {
+ nsString sourceURL;
+ bool isImgSet;
+ if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
+ // We're in a <picture> element and found a URI from a source previous to
+ // this image, use it.
+ sourceURL = mPreloadPictureFoundSource;
+ isImgSet = true;
+ } else {
+ // Otherwise try to use this <img> as a source
+ HTMLImageElement::SelectSourceForTagWithAttrs(
+ this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
+ VoidString(), sourceURL);
+ isImgSet = !aSrcsetAttr.IsEmpty();
+ }
+
+ // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
+ if (sourceURL.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Construct into URI using passed baseURI (the parser may know of base URI
+ // changes that have not reached us)
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
+ this, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ *aIsImgSet = isImgSet;
+
+ // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
+ // this this <picture> share the same <sources> (though this is not valid per
+ // spec)
+ return uri.forget();
+}
+
+void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
+ bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
+ nsContentUtils::CORSModeToLoadImageFlags(
+ Element::StringToCORSMode(aCrossOriginAttr));
+
+ nsContentPolicyType policyType =
+ aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
+ : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
+
+ RefPtr<imgRequestProxy> request;
+
+ nsLiteralString initiator = aEarlyHintPreloaderId
+ ? u"early-hints"_ns
+ : (aLinkPreload ? u"link"_ns : u"img"_ns);
+
+ nsresult rv = nsContentUtils::LoadImage(
+ aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
+ nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
+ policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
+
+ // Pin image-reference to avoid evicting it from the img-cache before
+ // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
+ // unlink
+ if (!aLinkPreload && NS_SUCCEEDED(rv)) {
+ mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
+ }
+}
+
+void Document::MaybePreLoadImage(nsIURI* aUri,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy,
+ bool aIsImgSet, bool aLinkPreload,
+ const TimeStamp& aInitTimestamp) {
+ const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
+ if (aLinkPreload) {
+ // Check if the image was already preloaded in this document to avoid
+ // duplicate preloading.
+ PreloadHashKey key =
+ PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
+ if (!mPreloadService.PreloadExists(key)) {
+ PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
+ aLinkPreload, 0);
+ }
+ return;
+ }
+
+ // Early exit if the img is already present in the img-cache
+ // which indicates that the "real" load has already started and
+ // that we shouldn't preload it.
+ if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
+ return;
+ }
+
+#ifdef NIGHTLY_BUILD
+ Telemetry::Accumulate(
+ Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
+ static_cast<uint32_t>(
+ (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
+#endif
+
+ // Image not in cache - trigger preload
+ PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
+ 0);
+}
+
+void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
+ NS_MutateURI mutator(aOrigURI);
+ if (NS_FAILED(mutator.GetStatus())) {
+ return;
+ }
+
+ // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
+ // which ignores the path and uses only the origin. The other is for the
+ // document mPreloadedPreconnects de-duplication hash. Anonymous vs
+ // non-Anonymous preconnects create different connections on the wire and
+ // therefore should not be considred duplicates of each other and we
+ // normalize the path before putting it in the hash to accomplish that.
+
+ if (aCORSMode == CORS_ANONYMOUS) {
+ mutator.SetPathQueryRef("/anonymous"_ns);
+ } else {
+ mutator.SetPathQueryRef("/"_ns);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mutator.Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ const bool existingEntryFound =
+ mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
+ if (entry) {
+ return true;
+ }
+ entry.Insert(true);
+ return false;
+ });
+ if (existingEntryFound) {
+ return;
+ }
+
+ nsCOMPtr<nsISpeculativeConnect> speculator =
+ mozilla::components::IO::Service();
+ if (!speculator) {
+ return;
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
+ speculator->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
+}
+
+void Document::ForgetImagePreload(nsIURI* aURI) {
+ // Checking count is faster than hashing the URI in the common
+ // case of empty table.
+ if (mPreloadingImages.Count() != 0) {
+ nsCOMPtr<imgIRequest> req;
+ mPreloadingImages.Remove(aURI, getter_AddRefs(req));
+ if (req) {
+ // Make sure to cancel the request so imagelib knows it's gone.
+ req->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+ }
+}
+
+void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
+ bool aNotify) {
+ const DocumentState oldStates = mDocumentState;
+ if (aMaybeChangedStates.HasAtLeastOneOfStates(
+ DocumentState::ALL_LOCALEDIR_BITS)) {
+ mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
+ if (IsDocumentRightToLeft()) {
+ mDocumentState |= DocumentState::RTL_LOCALE;
+ } else {
+ mDocumentState |= DocumentState::LTR_LOCALE;
+ }
+ }
+
+ if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
+ if (ComputeDocumentLWTheme()) {
+ mDocumentState |= DocumentState::LWTHEME;
+ } else {
+ mDocumentState &= ~DocumentState::LWTHEME;
+ }
+ }
+
+ if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
+ if (IsTopLevelWindowInactive()) {
+ mDocumentState |= DocumentState::WINDOW_INACTIVE;
+ } else {
+ mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
+ }
+ }
+
+ const DocumentState changedStates = oldStates ^ mDocumentState;
+ if (aNotify && !changedStates.IsEmpty()) {
+ if (PresShell* ps = GetObservingPresShell()) {
+ ps->DocumentStatesChanged(changedStates);
+ }
+ }
+}
+
+namespace {
+
+/**
+ * Stub for LoadSheet(), since all we want is to get the sheet into
+ * the CSSLoader's style cache
+ */
+class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
+ ~StubCSSLoaderObserver() = default;
+
+ public:
+ NS_IMETHOD
+ StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
+
+} // namespace
+
+SheetPreloadStatus Document::PreloadStyle(
+ nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
+ const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
+ css::StylePreloadKind aKind, uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(aKind != css::StylePreloadKind::None);
+
+ // The CSSLoader will retain this object after we return.
+ nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
+
+ // Charset names are always ASCII.
+ auto result = CSSLoader()->LoadSheet(
+ uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
+ Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
+ if (result.isErr()) {
+ return SheetPreloadStatus::Errored;
+ }
+ RefPtr<StyleSheet> sheet = result.unwrap();
+ if (sheet->IsComplete()) {
+ return SheetPreloadStatus::AlreadyComplete;
+ }
+ return SheetPreloadStatus::InProgress;
+}
+
+RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
+ return CSSLoader()
+ ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
+ .unwrapOr(nullptr);
+}
+
+void Document::ResetDocumentDirection() {
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return;
+ }
+ UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
+}
+
+bool Document::IsDocumentRightToLeft() {
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return false;
+ }
+ // setting the localedir attribute on the root element forces a
+ // specific direction for the document.
+ Element* element = GetRootElement();
+ if (element) {
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
+ nullptr};
+ switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
+ strings, eCaseMatters)) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ break; // otherwise, not a valid value, so fall through
+ }
+ }
+
+ if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
+ !mDocumentURI->SchemeIs("resource")) {
+ return false;
+ }
+
+ return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
+}
+
+class nsDelayedEventDispatcher : public Runnable {
+ public:
+ explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
+ : mozilla::Runnable("nsDelayedEventDispatcher"),
+ mDocuments(std::move(aDocuments)) {}
+ virtual ~nsDelayedEventDispatcher() = default;
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ FireOrClearDelayedEvents(std::move(mDocuments), true);
+ return NS_OK;
+ }
+
+ private:
+ nsTArray<nsCOMPtr<Document>> mDocuments;
+};
+
+static void GetAndUnsuppressSubDocuments(
+ Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
+ if (aDocument.EventHandlingSuppressed() > 0) {
+ aDocument.DecreaseEventSuppression();
+ aDocument.ScriptLoader()->RemoveExecuteBlocker();
+ }
+ aDocuments.AppendElement(&aDocument);
+ auto recurse = [&aDocuments](Document& aSubDoc) {
+ GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
+ return CallState::Continue;
+ };
+ aDocument.EnumerateSubDocuments(recurse);
+}
+
+void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
+ nsTArray<nsCOMPtr<Document>> documents;
+ GetAndUnsuppressSubDocuments(*this, documents);
+
+ for (nsCOMPtr<Document>& doc : documents) {
+ if (!doc->EventHandlingSuppressed()) {
+ if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
+ wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<RefPtr<net::ChannelEventQueue>> queues =
+ std::move(doc->mSuspendedQueues);
+ for (net::ChannelEventQueue* queue : queues) {
+ queue->Resume();
+ }
+
+ // If there have been any events driven by the refresh driver which were
+ // delayed due to events being suppressed in this document, make sure
+ // there is a refresh scheduled soon so the events will run.
+ if (doc->mHasDelayedRefreshEvent) {
+ doc->mHasDelayedRefreshEvent = false;
+
+ if (doc->mPresShell) {
+ nsRefreshDriver* rd =
+ doc->mPresShell->GetPresContext()->RefreshDriver();
+ rd->RunDelayedEventsSoon();
+ }
+ }
+ }
+ }
+
+ if (aFireEvents) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> ded =
+ new nsDelayedEventDispatcher(std::move(documents));
+ Dispatch(TaskCategory::Other, ded.forget());
+ } else {
+ FireOrClearDelayedEvents(std::move(documents), false);
+ }
+}
+
+bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
+ return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
+}
+
+void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(EventHandlingSuppressed());
+ mSuspendedQueues.AppendElement(aQueue);
+}
+
+bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
+ mSuspendedPostMessageEvents.AppendElement(aEvent);
+ return true;
+ }
+ return false;
+}
+
+void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
+ nsTArray<RefPtr<PostMessageEvent>> events =
+ std::move(mSuspendedPostMessageEvents);
+
+ if (aFireEvents) {
+ for (PostMessageEvent* event : events) {
+ event->Run();
+ }
+ }
+}
+
+void Document::SetSuppressedEventListener(EventListener* aListener) {
+ mSuppressedEventListener = aListener;
+ auto setOnSubDocs = [&](Document& aDocument) {
+ aDocument.SetSuppressedEventListener(aListener);
+ return CallState::Continue;
+ };
+ EnumerateSubDocuments(setOnSubDocs);
+}
+
+bool Document::IsActive() const {
+ return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
+ !GetBrowsingContext()->IsInBFCache();
+}
+
+nsISupports* Document::GetCurrentContentSink() {
+ return mParser ? mParser->GetContentSink() : nullptr;
+}
+
+Document* Document::GetTemplateContentsOwner() {
+ if (!mTemplateContentsOwner) {
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+
+ nsCOMPtr<Document> document;
+ nsresult rv = NS_NewDOMDocument(
+ getter_AddRefs(document),
+ u""_ns, // aNamespaceURI
+ u""_ns, // aQualifiedName
+ nullptr, // aDoctype
+ Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
+ true, // aLoadedAsData
+ scriptObject, // aEventObject
+ IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mTemplateContentsOwner = document;
+ NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
+
+ if (!scriptObject) {
+ mTemplateContentsOwner->SetScopeObject(GetScopeObject());
+ }
+
+ mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
+
+ // Set |mTemplateContentsOwner| as the template contents owner of itself so
+ // that it is the template contents owner of nested template elements.
+ mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
+ }
+
+ MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
+ return mTemplateContentsOwner;
+}
+
+// https://html.spec.whatwg.org/#the-autofocus-attribute
+void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
+ BrowsingContext* bc = GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ // If target is not fully active, then return.
+ if (!IsCurrentActiveDocument()) {
+ return;
+ }
+
+ // If target's active sandboxing flag set has the sandboxed automatic features
+ // browsing context flag, then return.
+ if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
+ return;
+ }
+
+ // For each ancestorBC of target's browsing context's ancestor browsing
+ // contexts: if ancestorBC's active document's origin is not same origin with
+ // target's origin, then return.
+ while (bc) {
+ BrowsingContext* parent = bc->GetParent();
+ if (!parent) {
+ break;
+ }
+ // AncestorBC is not the same site
+ if (!parent->IsInProcess()) {
+ return;
+ }
+
+ Document* currentDocument = bc->GetDocument();
+ if (!currentDocument) {
+ return;
+ }
+
+ Document* parentDocument = parent->GetDocument();
+ if (!parentDocument) {
+ return;
+ }
+
+ // Not same origin
+ if (!currentDocument->NodePrincipal()->Equals(
+ parentDocument->NodePrincipal())) {
+ return;
+ }
+
+ bc = parent;
+ }
+ MOZ_ASSERT(bc->IsTop());
+
+ Document* topDocument = bc->GetDocument();
+ MOZ_ASSERT(topDocument);
+ topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
+}
+
+void Document::ScheduleFlushAutoFocusCandidates() {
+ MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
+ rd->ScheduleAutoFocusFlush(this);
+ }
+}
+
+void Document::AppendAutoFocusCandidateToTopDocument(
+ Element* aAutoFocusCandidate) {
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (mAutoFocusFired) {
+ return;
+ }
+
+ if (!HasAutoFocusCandidates()) {
+ // PresShell may be initialized later
+ if (mPresShell && mPresShell->DidInitialize()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+ }
+
+ nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
+ mAutoFocusCandidates.RemoveElement(element);
+ mAutoFocusCandidates.AppendElement(element);
+}
+
+void Document::SetAutoFocusFired() {
+ mAutoFocusCandidates.Clear();
+ mAutoFocusFired = true;
+}
+
+// https://html.spec.whatwg.org/#flush-autofocus-candidates
+void Document::FlushAutoFocusCandidates() {
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (mAutoFocusFired) {
+ return;
+ }
+
+ if (!mPresShell) {
+ return;
+ }
+
+ MOZ_ASSERT(HasAutoFocusCandidates());
+ MOZ_ASSERT(mPresShell->DidInitialize());
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
+ // We should be the top document
+ if (!topWindow) {
+ return;
+ }
+
+#ifdef DEBUG
+ {
+ // Trying to find the top window (equivalent to window.top).
+ nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
+ MOZ_ASSERT(topWindow == top);
+ }
+#endif
+
+ // Don't steal the focus from the user
+ if (topWindow->GetFocusedElement()) {
+ SetAutoFocusFired();
+ return;
+ }
+
+ MOZ_ASSERT(mDocumentURI);
+ nsAutoCString ref;
+ // GetRef never fails
+ nsresult rv = mDocumentURI->GetRef(ref);
+ if (NS_SUCCEEDED(rv) &&
+ nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
+ SetAutoFocusFired();
+ return;
+ }
+
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
+ while (iter.HasMore()) {
+ nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
+ if (!autoFocusElement) {
+ continue;
+ }
+ RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
+ // Get the latest info about the frame and allow scripts
+ // to run which might affect the focusability of this element.
+ autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
+
+ // Above layout flush may cause the PresShell to disappear.
+ if (!mPresShell) {
+ return;
+ }
+
+ // Re-get the element because the ownerDoc() might have changed
+ autoFocusElementDoc = autoFocusElement->OwnerDoc();
+ BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
+ if (!bc) {
+ continue;
+ }
+
+ // If doc is not fully active, then remove element from candidates, and
+ // continue.
+ if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
+ iter.Remove();
+ continue;
+ }
+
+ nsCOMPtr<nsIContentSink> sink =
+ do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
+ if (sink) {
+ nsHtml5TreeOpExecutor* executor =
+ static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
+ if (executor) {
+ // This is a HTML5 document
+ MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
+ // If doc's script-blocking style sheet counter is greater than 0, th
+ // return.
+ if (executor->WaitForPendingSheets()) {
+ // In this case, element is the currently-best candidate, but doc is
+ // not ready for autofocusing. We'll try again next time flush
+ // autofocus candidates is called.
+ ScheduleFlushAutoFocusCandidates();
+ return;
+ }
+ }
+ }
+
+ // The autofocus element could be moved to a different
+ // top level BC.
+ if (bc->Top()->GetDocument() != this) {
+ continue;
+ }
+
+ iter.Remove();
+
+ // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
+ // active documents of each of doc's browsing context's ancestor browsing
+ // contexts.
+ // If any Document in inclusiveAncestorDocuments has non-null target
+ // element, then continue.
+ bool shouldFocus = true;
+ while (bc) {
+ Document* doc = bc->GetDocument();
+ if (!doc) {
+ shouldFocus = false;
+ break;
+ }
+
+ nsIURI* uri = doc->GetDocumentURI();
+ if (!uri) {
+ shouldFocus = false;
+ break;
+ }
+
+ nsAutoCString ref;
+ nsresult rv = uri->GetRef(ref);
+ // If there is an element in the document tree that has an ID equal to
+ // fragment
+ if (NS_SUCCEEDED(rv) &&
+ nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
+ shouldFocus = false;
+ break;
+ }
+ bc = bc->GetParent();
+ }
+
+ if (!shouldFocus) {
+ continue;
+ }
+
+ MOZ_ASSERT(topWindow);
+ if (TryAutoFocusCandidate(*autoFocusElement)) {
+ // We've successfully autofocused an element, don't
+ // need to try to focus the rest.
+ SetAutoFocusFired();
+ break;
+ }
+ }
+
+ if (HasAutoFocusCandidates()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+}
+
+bool Document::TryAutoFocusCandidate(Element& aElement) {
+ const FocusOptions options;
+ if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
+ &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
+ target->Focus(options, CallerType::NonSystem, IgnoreErrors());
+ return true;
+ }
+
+ return false;
+}
+
+void Document::SetScrollToRef(nsIURI* aDocumentURI) {
+ if (!aDocumentURI) {
+ return;
+ }
+
+ nsAutoCString ref;
+
+ // Since all URI's that pass through here aren't URL's we can't
+ // rely on the nsIURI implementation for providing a way for
+ // finding the 'ref' part of the URI, we'll haveto revert to
+ // string routines for finding the data past '#'
+
+ nsresult rv = aDocumentURI->GetSpec(ref);
+ if (NS_FAILED(rv)) {
+ Unused << aDocumentURI->GetRef(mScrollToRef);
+ return;
+ }
+
+ nsReadingIterator<char> start, end;
+
+ ref.BeginReading(start);
+ ref.EndReading(end);
+
+ if (FindCharInReadable('#', start, end)) {
+ ++start; // Skip over the '#'
+
+ mScrollToRef = Substring(start, end);
+ }
+}
+
+void Document::ScrollToRef() {
+ if (mScrolledToRefAlready) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->ScrollToAnchor();
+ }
+ return;
+ }
+
+ if (mScrollToRef.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ nsresult rv = NS_ERROR_FAILURE;
+ // We assume that the bytes are in UTF-8, as it says in the spec:
+ // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
+ NS_ConvertUTF8toUTF16 ref(mScrollToRef);
+ // Check an empty string which might be caused by the UTF-8 conversion
+ if (!ref.IsEmpty()) {
+ // Note that GoToAnchor will handle flushing layout as needed.
+ rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ nsAutoCString buff;
+ const bool unescaped =
+ NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
+ /*aFlags =*/0, buff);
+
+ // This attempt is only necessary if characters were unescaped.
+ if (unescaped) {
+ NS_ConvertUTF8toUTF16 utf16Str(buff);
+ if (!utf16Str.IsEmpty()) {
+ rv = presShell->GoToAnchor(utf16Str,
+ mChangeScrollPosWhenScrollingToRef);
+ }
+ }
+
+ // If UTF-8 URI failed then try to assume the string as a
+ // document's charset.
+ if (NS_FAILED(rv)) {
+ const Encoding* encoding = GetDocumentCharacterSet();
+ rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
+ ref);
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mScrolledToRefAlready = true;
+ }
+ }
+}
+
+void Document::RegisterActivityObserver(nsISupports* aSupports) {
+ if (!mActivityObservers) {
+ mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
+ }
+ mActivityObservers->Insert(aSupports);
+}
+
+bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
+ if (!mActivityObservers) {
+ return false;
+ }
+ return mActivityObservers->EnsureRemoved(aSupports);
+}
+
+void Document::EnumerateActivityObservers(
+ ActivityObserverEnumerator aEnumerator) {
+ if (!mActivityObservers) {
+ return;
+ }
+
+ const auto keyArray =
+ ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
+ for (auto& observer : keyArray) {
+ aEnumerator(observer.get());
+ }
+}
+
+void Document::RegisterPendingLinkUpdate(Link* aLink) {
+ if (aLink->HasPendingLinkUpdate()) {
+ return;
+ }
+
+ aLink->SetHasPendingLinkUpdate();
+
+ if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
+ &Document::FlushPendingLinkUpdates);
+ // Do this work in a second in the worst case.
+ nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
+ EventQueuePriority::Idle);
+ if (NS_FAILED(rv)) {
+ // If during shutdown posting a runnable doesn't succeed, we probably
+ // don't need to update link states.
+ return;
+ }
+ mHasLinksToUpdateRunnable = true;
+ }
+
+ mLinksToUpdate.InfallibleAppend(aLink);
+}
+
+void Document::FlushPendingLinkUpdates() {
+ MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
+ MOZ_ASSERT(mHasLinksToUpdateRunnable);
+ mHasLinksToUpdateRunnable = false;
+
+ auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
+ mFlushingPendingLinkUpdates = true;
+
+ while (!mLinksToUpdate.IsEmpty()) {
+ LinksToUpdateList links(std::move(mLinksToUpdate));
+ for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
+ Link* link = iter.Get();
+ Element* element = link->GetElement();
+ if (element->OwnerDoc() == this) {
+ link->ClearHasPendingLinkUpdate();
+ if (element->IsInComposedDoc()) {
+ element->UpdateLinkState(link->LinkState());
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Retrieves the node in a static-clone document that corresponds to aOrigNode,
+ * which is a node in the original document from which aStaticClone was cloned.
+ */
+static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
+ Document& aStaticClone) {
+ MOZ_ASSERT(aOrigNode);
+
+ // Selections in anonymous subtrees aren't supported.
+ if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
+ return nullptr;
+ }
+
+ // If the node is disconnected, this is a bug in the selection code, but it
+ // can happen with shadow DOM so handle it.
+ if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
+ return nullptr;
+ }
+
+ AutoTArray<Maybe<uint32_t>, 32> indexArray;
+ const nsINode* current = aOrigNode;
+ while (const nsINode* parent = current->GetParentNode()) {
+ Maybe<uint32_t> index = parent->ComputeIndexOf(current);
+ NS_ENSURE_TRUE(index.isSome(), nullptr);
+ indexArray.AppendElement(std::move(index));
+ current = parent;
+ }
+ MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
+ nsINode* correspondingNode = [&]() -> nsINode* {
+ if (current->IsDocument()) {
+ return &aStaticClone;
+ }
+ const auto* shadow = ShadowRoot::FromNode(*current);
+ if (!shadow) {
+ return nullptr;
+ }
+ nsINode* correspondingHost =
+ GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
+ if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
+ return nullptr;
+ }
+ return correspondingHost->AsElement()->GetShadowRoot();
+ }();
+
+ if (NS_WARN_IF(!correspondingNode)) {
+ return nullptr;
+ }
+ for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
+ correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
+ NS_ENSURE_TRUE(correspondingNode, nullptr);
+ }
+ return correspondingNode;
+}
+
+/**
+ * Caches the selection ranges from the source document onto the static clone in
+ * case the "Print Selection Only" functionality is invoked.
+ *
+ * Note that we cannot use the selection obtained from GetOriginalDocument()
+ * since that selection may have mutated after the print was invoked.
+ *
+ * Note also that because nsRange objects point into a specific document's
+ * nodes, we cannot reuse an array of nsRange objects across multiple static
+ * clone documents. For that reason we cache a new array of ranges on each
+ * static clone that we create.
+ *
+ * TODO(emilio): This can be simplified once we don't re-clone from static
+ * documents.
+ *
+ * @param aSourceDoc the document from which we are caching selection ranges
+ * @param aStaticClone the document that will hold the cache
+ * @return true if a selection range was cached
+ */
+static void CachePrintSelectionRanges(const Document& aSourceDoc,
+ Document& aStaticClone) {
+ MOZ_ASSERT(aStaticClone.IsStaticDocument());
+ MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
+ MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
+
+ bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
+
+ // When the user opts to "Print Selection Only", the print code prefers any
+ // selection in the static clone corresponding to the focused frame. If this
+ // is that static clone, flag it for the printing code:
+ const bool isFocusedDoc = [&] {
+ if (sourceDocIsStatic) {
+ return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
+ }
+ nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
+ if (!window) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
+ if (!rootWindow) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(rootWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
+ }();
+ if (isFocusedDoc) {
+ aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
+ reinterpret_cast<void*>(true));
+ }
+
+ const Selection* origSelection = nullptr;
+ const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
+
+ if (sourceDocIsStatic) {
+ origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
+ aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
+ } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
+ origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
+ }
+
+ if (!origSelection && !origRanges) {
+ return;
+ }
+
+ const uint32_t rangeCount =
+ sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
+ auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
+
+ for (const uint32_t i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT_IF(!sourceDocIsStatic,
+ origSelection->RangeCount() == rangeCount);
+ const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
+ : origSelection->GetRangeAt(i);
+ MOZ_ASSERT(range);
+ nsINode* startContainer = range->GetStartContainer();
+ nsINode* endContainer = range->GetEndContainer();
+
+ if (!startContainer || !endContainer) {
+ continue;
+ }
+
+ nsINode* startNode =
+ GetCorrespondingNodeInDocument(startContainer, aStaticClone);
+ nsINode* endNode =
+ GetCorrespondingNodeInDocument(endContainer, aStaticClone);
+
+ if (NS_WARN_IF(!startNode || !endNode)) {
+ continue;
+ }
+
+ RefPtr<nsRange> clonedRange =
+ nsRange::Create(startNode, range->StartOffset(), endNode,
+ range->EndOffset(), IgnoreErrors());
+ if (clonedRange && !clonedRange->Collapsed()) {
+ printRanges->AppendElement(std::move(clonedRange));
+ }
+ }
+
+ if (printRanges->IsEmpty()) {
+ return;
+ }
+
+ aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
+ printRanges.release(),
+ nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
+}
+
+already_AddRefed<Document> Document::CreateStaticClone(
+ nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
+ nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
+ MOZ_ASSERT(!mCreatingStaticClone);
+ MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
+ MOZ_DIAGNOSTIC_ASSERT(aViewer);
+
+ mCreatingStaticClone = true;
+ SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
+ nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
+
+ auto raii = MakeScopeExit([&] {
+ RemoveProperty(nsGkAtoms::adoptedsheetclones);
+ mCreatingStaticClone = false;
+ });
+
+ // Make document use different container during cloning.
+ //
+ // FIXME(emilio): Why is this needed?
+ RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
+ SetContainer(nsDocShell::Cast(aCloneContainer));
+ IgnoredErrorResult rv;
+ nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
+ SetContainer(originalShell);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
+ if (!clonedDoc) {
+ return nullptr;
+ }
+
+ size_t sheetsCount = SheetCount();
+ for (size_t i = 0; i < sheetsCount; ++i) {
+ RefPtr<StyleSheet> sheet = SheetAt(i);
+ if (sheet) {
+ if (sheet->IsApplicable()) {
+ RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
+ NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddStyleSheet(clonedSheet);
+ }
+ }
+ }
+ }
+ clonedDoc->CloneAdoptedSheetsFrom(*this);
+
+ for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
+ auto& sheets = mAdditionalSheets[additionalSheetType(t)];
+ for (StyleSheet* sheet : sheets) {
+ if (sheet->IsApplicable()) {
+ RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
+ NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
+ clonedSheet);
+ }
+ }
+ }
+ }
+
+ // Font faces created with the JS API will not be reflected in the
+ // stylesheets and need to be copied over to the cloned document.
+ if (const FontFaceSet* set = GetFonts()) {
+ set->CopyNonRuleFacesTo(clonedDoc->Fonts());
+ }
+
+ clonedDoc->mReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
+ clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
+ CachePrintSelectionRanges(*this, *clonedDoc);
+
+ // We're done with the clone, embed ourselves into the document viewer and
+ // clone our children. The order here is pretty important, because our
+ // document our document needs to have an owner global before we can create
+ // the frame loaders for subdocuments.
+ aViewer->SetDocument(clonedDoc);
+
+ *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
+
+ auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
+ for (const auto& clone : pendingClones) {
+ RefPtr<Element> element = do_QueryObject(clone.mElement);
+ RefPtr<nsFrameLoader> frameLoader =
+ nsFrameLoader::Create(element, /* aNetworkCreated */ false);
+
+ if (NS_WARN_IF(!frameLoader)) {
+ continue;
+ }
+
+ clone.mElement->SetFrameLoader(frameLoader);
+
+ nsresult rv = frameLoader->FinishStaticClone(
+ clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ return clonedDoc.forget();
+}
+
+void Document::UnlinkOriginalDocumentIfStatic() {
+ if (IsStaticDocument() && mOriginalDocument) {
+ MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
+ mOriginalDocument->mStaticCloneCount--;
+ mOriginalDocument = nullptr;
+ }
+ MOZ_ASSERT(!mOriginalDocument);
+}
+
+nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
+ int32_t* aHandle) {
+ nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ UpdateFrameRequestCallbackSchedulingState();
+ return NS_OK;
+}
+
+void Document::CancelFrameRequestCallback(int32_t aHandle) {
+ if (mFrameRequestManager.Cancel(aHandle)) {
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+}
+
+bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
+ return mFrameRequestManager.IsCanceled(aHandle);
+}
+
+nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
+ // Get the document's current state object. This is the object backing both
+ // history.state and popStateEvent.state.
+ //
+ // mStateObjectContainer may be null; this just means that there's no
+ // current state object.
+
+ if (!mCachedStateObjectValid) {
+ if (mStateObjectContainer) {
+ AutoJSAPI jsapi;
+ // Init with null is "OK" in the sense that it will just fail.
+ if (!jsapi.Init(GetScopeObject())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JS::Rooted<JS::Value> value(jsapi.cx());
+ nsresult rv =
+ mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCachedStateObject = value;
+ if (!value.isNullOrUndefined()) {
+ mozilla::HoldJSObjects(this);
+ }
+ } else {
+ mCachedStateObject = JS::NullValue();
+ }
+ mCachedStateObjectValid = true;
+ }
+
+ aState.set(mCachedStateObject);
+ return NS_OK;
+}
+
+void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
+ mTiming = aTiming;
+ if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
+ mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
+ mLoadingOrRestoredFromBFCacheTimeStamp);
+ }
+
+ // If there's already the DocumentTimeline instance, tell it since the
+ // DocumentTimeline is based on both the navigation start time stamp and the
+ // refresh driver timestamp.
+ if (mDocumentTimeline) {
+ mDocumentTimeline->UpdateLastRefreshDriverTime();
+ }
+}
+
+nsContentList* Document::ImageMapList() {
+ if (!mImageMaps) {
+ mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
+ nsGkAtoms::map);
+ }
+
+ return mImageMaps;
+}
+
+#define DEPRECATED_OPERATION(_op) #_op "Warning",
+static const char* kDeprecationWarnings[] = {
+#include "nsDeprecatedOperationList.h"
+ nullptr};
+#undef DEPRECATED_OPERATION
+
+#define DOCUMENT_WARNING(_op) #_op "Warning",
+static const char* kDocumentWarnings[] = {
+#include "nsDocumentWarningList.h"
+ nullptr};
+#undef DOCUMENT_WARNING
+
+static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
+ switch (aOperation) {
+#define DEPRECATED_OPERATION(_op) \
+ case DeprecatedOperations::e##_op: \
+ return eUseCounter_##_op;
+#include "nsDeprecatedOperationList.h"
+#undef DEPRECATED_OPERATION
+ default:
+ MOZ_CRASH();
+ }
+}
+
+bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
+ return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
+}
+
+void Document::WarnOnceAbout(
+ DeprecatedOperations aOperation, bool asError /* = false */,
+ const nsTArray<nsString>& aParams /* = empty array */) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aOperation)) {
+ return;
+ }
+ mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
+ // Don't count deprecated operations for about pages since those pages
+ // are almost in our control, and we always need to remove uses there
+ // before we remove the operation itself anyway.
+ if (!IsAboutPage()) {
+ const_cast<Document*>(this)->SetUseCounter(
+ OperationToUseCounter(aOperation));
+ }
+ uint32_t flags =
+ asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(
+ flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
+ kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
+}
+
+bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
+ return mDocWarningWarnedAbout[aWarning];
+}
+
+void Document::WarnOnceAbout(
+ DocumentWarnings aWarning, bool asError /* = false */,
+ const nsTArray<nsString>& aParams /* = empty array */) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aWarning)) {
+ return;
+ }
+ mDocWarningWarnedAbout[aWarning] = true;
+ uint32_t flags =
+ asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES,
+ kDocumentWarnings[aWarning], aParams);
+}
+
+mozilla::dom::ImageTracker* Document::ImageTracker() {
+ if (!mImageTracker) {
+ mImageTracker = new mozilla::dom::ImageTracker;
+ }
+ return mImageTracker;
+}
+
+void Document::ScheduleSVGUseElementShadowTreeUpdate(
+ SVGUseElement& aUseElement) {
+ MOZ_ASSERT(aUseElement.IsInComposedDoc());
+
+ if (MOZ_UNLIKELY(mIsStaticDocument)) {
+ // Printing doesn't deal well with dynamic DOM mutations.
+ return;
+ }
+
+ mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
+
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+}
+
+void Document::DoUpdateSVGUseElementShadowTrees() {
+ MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
+
+ do {
+ const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
+ mSVGUseElementsNeedingShadowTreeUpdate);
+ mSVGUseElementsNeedingShadowTreeUpdate.Clear();
+
+ for (const auto& useElement : useElementsToUpdate) {
+ if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
+ // The element was in another <use> shadow tree which we processed
+ // already and also needed an update, and is removed from the document
+ // now, so nothing to do here.
+ MOZ_ASSERT(useElementsToUpdate.Length() > 1);
+ continue;
+ }
+ useElement->UpdateShadowTree();
+ }
+ } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
+}
+
+void Document::NotifyMediaFeatureValuesChanged() {
+ for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
+ imageElement->MediaFeatureValuesChanged();
+ }
+}
+
+already_AddRefed<Touch> Document::CreateTouch(
+ nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
+ int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
+ int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
+ float aRotationAngle, float aForce) {
+ RefPtr<Touch> touch =
+ new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
+ aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
+ return touch.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList() {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ return retval.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList(
+ Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ retval->Append(&aTouch);
+ for (uint32_t i = 0; i < aTouches.Length(); ++i) {
+ retval->Append(aTouches[i].get());
+ }
+ return retval.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList(
+ const Sequence<OwningNonNull<Touch>>& aTouches) {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ for (uint32_t i = 0; i < aTouches.Length(); ++i) {
+ retval->Append(aTouches[i].get());
+ }
+ return retval.forget();
+}
+
+already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
+ float aX, float aY) {
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+ nsPoint pt(x, y);
+
+ FlushPendingNotifications(FlushType::Layout);
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+
+ // XUL docs, unlike HTML, have no frame tree until everything's done loading
+ if (!rootFrame) {
+ return nullptr;
+ }
+
+ nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+ RelativeTo{rootFrame}, pt,
+ {{FrameForPointOption::IgnorePaintSuppression,
+ FrameForPointOption::IgnoreCrossDoc}});
+ if (!ptFrame) {
+ return nullptr;
+ }
+
+ // We require frame-relative coordinates for GetContentOffsetsFromPoint.
+ nsPoint adjustedPoint = pt;
+ if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
+ adjustedPoint) !=
+ nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return nullptr;
+ }
+
+ nsIFrame::ContentOffsets offsets =
+ ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
+
+ nsCOMPtr<nsIContent> node = offsets.content;
+ uint32_t offset = offsets.offset;
+ nsCOMPtr<nsIContent> anonNode = node;
+ bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
+ if (nodeIsAnonymous) {
+ node = ptFrame->GetContent();
+ nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
+ HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
+ nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
+ if (textFrame) {
+ // If the anonymous content node has a child, then we need to make sure
+ // that we get the appropriate child, as otherwise the offset may not be
+ // correct when we construct a range for it.
+ nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
+ if (firstChild) {
+ anonNode = firstChild;
+ }
+
+ if (textArea) {
+ offset =
+ nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
+ }
+
+ node = nonanon;
+ } else {
+ node = nullptr;
+ offset = 0;
+ }
+ }
+
+ RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
+ if (nodeIsAnonymous) {
+ aCaretPos->SetAnonymousContentNode(anonNode);
+ }
+ return aCaretPos.forget();
+}
+
+bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
+ // We rely on correct frame information here, so need to flush frames.
+ FlushPendingNotifications(FlushType::Frames);
+
+ // An element that is the HTML body element is potentially scrollable if all
+ // of the following conditions are true:
+
+ // The element has an associated CSS layout box.
+ nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
+ if (!bodyFrame) {
+ return false;
+ }
+
+ // The element's parent element's computed value of the overflow-x and
+ // overflow-y properties are visible.
+ MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
+ nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
+ if (parentFrame &&
+ parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
+ return false;
+ }
+
+ // The element's computed value of the overflow-x or overflow-y properties is
+ // not visible.
+ return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
+}
+
+Element* Document::GetScrollingElement() {
+ // Keep this in sync with IsScrollingElement.
+ if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
+ RefPtr<HTMLBodyElement> body = GetBodyElement();
+ if (body && !IsPotentiallyScrollable(body)) {
+ return body;
+ }
+
+ return nullptr;
+ }
+
+ return GetRootElement();
+}
+
+bool Document::IsScrollingElement(Element* aElement) {
+ // Keep this in sync with GetScrollingElement.
+ MOZ_ASSERT(aElement);
+
+ if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
+ return aElement == GetRootElement();
+ }
+
+ // In the common case when aElement != body, avoid refcounting.
+ HTMLBodyElement* body = GetBodyElement();
+ if (aElement != body) {
+ return false;
+ }
+
+ // Now we know body is non-null, since aElement is not null. It's the
+ // scrolling element for the document if it itself is not potentially
+ // scrollable.
+ RefPtr<HTMLBodyElement> strongBody(body);
+ return !IsPotentiallyScrollable(strongBody);
+}
+
+class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
+
+ explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
+ const BlockParsingOptions& aOptions)
+ : mPromise(aPromise) {
+ nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
+ if (parser &&
+ (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
+ parser->BlockParser();
+ mParser = do_GetWeakReference(parser);
+ mDocument = aDocument;
+ mDocument->BlockOnload();
+ mDocument->BlockDOMContentLoaded();
+ }
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MaybeUnblockParser();
+
+ mPromise->MaybeResolve(aValue);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MaybeUnblockParser();
+
+ mPromise->MaybeReject(aValue);
+ }
+
+ protected:
+ virtual ~UnblockParsingPromiseHandler() {
+ // If we're being cleaned up by the cycle collector, our mDocument reference
+ // may have been unlinked while our mParser weak reference is still alive.
+ if (mDocument) {
+ MaybeUnblockParser();
+ }
+ }
+
+ private:
+ void MaybeUnblockParser() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
+ if (parser) {
+ MOZ_DIAGNOSTIC_ASSERT(mDocument);
+ nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
+ if (parser == docParser) {
+ parser->UnblockParser();
+ parser->ContinueInterruptedParsingAsync();
+ }
+ }
+ if (mDocument) {
+ // We blocked DOMContentLoaded and load events on this document. Unblock
+ // them. Note that we want to do that no matter what's going on with the
+ // parser state for this document. Maybe someone caused it to stop being
+ // parsed, so CreatorParserOrNull() is returning null, but we still want
+ // to unblock these.
+ mDocument->UnblockDOMContentLoaded();
+ mDocument->UnblockOnload(false);
+ }
+ mParser = nullptr;
+ mDocument = nullptr;
+ }
+
+ nsWeakPtr mParser;
+ RefPtr<Promise> mPromise;
+ RefPtr<Document> mDocument;
+};
+
+NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
+
+already_AddRefed<Promise> Document::BlockParsing(
+ Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
+ RefPtr<Promise> resultPromise =
+ Promise::Create(aPromise.GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseNativeHandler> promiseHandler =
+ new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
+ aPromise.AppendNativeHandler(promiseHandler);
+
+ return resultPromise.forget();
+}
+
+already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
+ if (mFailedChannel) {
+ nsCOMPtr<nsIURI> failedURI;
+ if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
+ return failedURI.forget();
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
+ if (!uri) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
+ if (mIsGoingAway) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ if (!mReadyForIdle) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ mReadyForIdle = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return mReadyForIdle;
+}
+
+void Document::MaybeResolveReadyForIdle() {
+ IgnoredErrorResult rv;
+ Promise* readyPromise = GetDocumentReadyForIdle(rv);
+ if (readyPromise) {
+ readyPromise->MaybeResolveWithUndefined();
+ }
+}
+
+mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
+ // The policy is created when the document is initialized. We _must_ have a
+ // policy here even if the featurePolicy pref is off. If this assertion fails,
+ // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
+ MOZ_ASSERT(mFeaturePolicy);
+ return mFeaturePolicy;
+}
+
+nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
+ // Only chrome documents are allowed to use command dispatcher.
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return nullptr;
+ }
+ if (!mCommandDispatcher) {
+ // Create our command dispatcher and hook it up.
+ mCommandDispatcher = new nsXULCommandDispatcher(this);
+ }
+ return mCommandDispatcher;
+}
+
+void Document::InitializeXULBroadcastManager() {
+ if (mXULBroadcastManager) {
+ return;
+ }
+ mXULBroadcastManager = new XULBroadcastManager(this);
+}
+
+namespace {
+
+class DevToolsMutationObserver final : public nsStubMutationObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+
+ // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
+ // relies on the event firing _before_ the removal happens.
+ // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
+ // data changes right now (maybe intentionally?).
+ // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+
+ DevToolsMutationObserver() = default;
+
+ private:
+ void FireEvent(nsINode* aTarget, const nsAString& aType);
+
+ ~DevToolsMutationObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
+
+void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
+ const nsAString& aType) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
+ ChromeOnlyDispatch::eYes,
+ Composed::eYes);
+}
+
+void DevToolsMutationObserver::AttributeChanged(Element* aElement,
+ int32_t aNamespaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ FireEvent(aElement, u"devtoolsattrmodified"_ns);
+}
+
+void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
+ for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
+ ContentInserted(c);
+ }
+}
+
+void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
+ FireEvent(aChild, u"devtoolschildinserted"_ns);
+}
+
+static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
+
+} // namespace
+
+void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
+ if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
+ return;
+ }
+ mDevToolsWatchingDOMMutations = aValue;
+ if (aValue) {
+ if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
+ sDevToolsMutationObserver = new DevToolsMutationObserver();
+ ClearOnShutdown(&sDevToolsMutationObserver);
+ }
+ AddMutationObserver(sDevToolsMutationObserver);
+ } else if (sDevToolsMutationObserver) {
+ RemoveMutationObserver(sDevToolsMutationObserver);
+ }
+}
+
+void Document::MaybeWarnAboutZoom() {
+ if (mHasWarnedAboutZoom) {
+ return;
+ }
+ const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
+ mStyleUseCounters.get(), eCSSProperty_zoom);
+ if (!usedZoom) {
+ return;
+ }
+
+ mHasWarnedAboutZoom = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
+ this, nsContentUtils::eLAYOUT_PROPERTIES,
+ "ZoomPropertyWarning");
+}
+
+nsIHTMLCollection* Document::Children() {
+ if (!mChildrenCollection) {
+ mChildrenCollection =
+ new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
+ nsGkAtoms::_asterisk, false);
+ }
+
+ return mChildrenCollection;
+}
+
+uint32_t Document::ChildElementCount() { return Children()->Length(); }
+
+// Singleton class to manage the list of fullscreen documents which are the
+// root of a branch which contains fullscreen documents. We maintain this list
+// so that we can easily exit all windows from fullscreen when the user
+// presses the escape key.
+class FullscreenRoots {
+ public:
+ // Adds the root of given document to the manager. Calling this method
+ // with a document whose root is already contained has no effect.
+ static void Add(Document* aDoc);
+
+ // Iterates over every root in the root list, and calls aFunction, passing
+ // each root once to aFunction. It is safe to call Add() and Remove() while
+ // iterating over the list (i.e. in aFunction). Documents that are removed
+ // from the manager during traversal are not traversed, and documents that
+ // are added to the manager during traversal are also not traversed.
+ static void ForEach(void (*aFunction)(Document* aDoc));
+
+ // Removes the root of a specific document from the manager.
+ static void Remove(Document* aDoc);
+
+ // Returns true if all roots added to the list have been removed.
+ static bool IsEmpty();
+
+ private:
+ MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
+ MOZ_COUNTED_DTOR(FullscreenRoots)
+
+ enum : uint32_t { NotFound = uint32_t(-1) };
+ // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
+ static uint32_t Find(Document* aRoot);
+
+ // Returns true if aRoot is in the list of fullscreen roots.
+ static bool Contains(Document* aRoot);
+
+ // Singleton instance of the FullscreenRoots. This is instantiated when a
+ // root is added, and it is deleted when the last root is removed.
+ static FullscreenRoots* sInstance;
+
+ // List of weak pointers to roots.
+ nsTArray<nsWeakPtr> mRoots;
+};
+
+FullscreenRoots* FullscreenRoots::sInstance = nullptr;
+
+/* static */
+void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
+ if (!sInstance) {
+ return;
+ }
+ // Create a copy of the roots array, and iterate over the copy. This is so
+ // that if an element is removed from mRoots we don't mess up our iteration.
+ nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
+ // Call aFunction on all entries.
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
+ // Check that the root isn't in the manager. This is so that new additions
+ // while we were running don't get traversed.
+ if (root && FullscreenRoots::Contains(root)) {
+ aFunction(root);
+ }
+ }
+}
+
+/* static */
+bool FullscreenRoots::Contains(Document* aRoot) {
+ return FullscreenRoots::Find(aRoot) != NotFound;
+}
+
+/* static */
+void FullscreenRoots::Add(Document* aDoc) {
+ nsCOMPtr<Document> root =
+ nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ if (!FullscreenRoots::Contains(root)) {
+ if (!sInstance) {
+ sInstance = new FullscreenRoots();
+ }
+ sInstance->mRoots.AppendElement(do_GetWeakReference(root));
+ }
+}
+
+/* static */
+uint32_t FullscreenRoots::Find(Document* aRoot) {
+ if (!sInstance) {
+ return NotFound;
+ }
+ nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
+ if (otherRoot == aRoot) {
+ return i;
+ }
+ }
+ return NotFound;
+}
+
+/* static */
+void FullscreenRoots::Remove(Document* aDoc) {
+ nsCOMPtr<Document> root =
+ nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ uint32_t index = Find(root);
+ NS_ASSERTION(index != NotFound,
+ "Should only try to remove roots which are still added!");
+ if (index == NotFound || !sInstance) {
+ return;
+ }
+ sInstance->mRoots.RemoveElementAt(index);
+ if (sInstance->mRoots.IsEmpty()) {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+}
+
+/* static */
+bool FullscreenRoots::IsEmpty() { return !sInstance; }
+
+// Any fullscreen change waiting for the widget to finish transition
+// is queued here. This is declared static instead of a member of
+// Document because in the majority of time, there would be at most
+// one document requesting or exiting fullscreen. We shouldn't waste
+// the space to hold for it in every document.
+class PendingFullscreenChangeList {
+ public:
+ PendingFullscreenChangeList() = delete;
+
+ template <typename T>
+ static void Add(UniquePtr<T> aChange) {
+ sList.insertBack(aChange.release());
+ }
+
+ static const FullscreenChange* GetLast() { return sList.getLast(); }
+
+ enum IteratorOption {
+ // When we are committing fullscreen changes or preparing for
+ // that, we generally want to iterate all requests in the same
+ // window with eDocumentsWithSameRoot option.
+ eDocumentsWithSameRoot,
+ // If we are removing a document from the tree, we would only
+ // want to remove the requests from the given document and its
+ // descendants. For that case, use eInclusiveDescendants.
+ eInclusiveDescendants
+ };
+
+ template <typename T>
+ class Iterator {
+ public:
+ explicit Iterator(Document* aDoc, IteratorOption aOption)
+ : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
+ if (mCurrent) {
+ if (aDoc->GetBrowsingContext()) {
+ mRootBCForIteration = aDoc->GetBrowsingContext();
+ if (aOption == eDocumentsWithSameRoot) {
+ RefPtr<BrowsingContext> bc =
+ GetParentIgnoreChromeBoundary(mRootBCForIteration);
+ while (bc) {
+ mRootBCForIteration = bc;
+ bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
+ }
+ }
+ }
+ SkipToNextMatch();
+ }
+ }
+
+ UniquePtr<T> TakeAndNext() {
+ auto thisChange = TakeAndNextInternal();
+ SkipToNextMatch();
+ return thisChange;
+ }
+ bool AtEnd() const { return mCurrent == nullptr; }
+
+ private:
+ already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
+ BrowsingContext* aBC) {
+ // Chrome BrowsingContexts are only available in the parent process, so if
+ // we're in a content process, we only worry about the context tree.
+ if (XRE_IsParentProcess()) {
+ return aBC->Canonical()->GetParentCrossChromeBoundary();
+ }
+ return do_AddRef(aBC->GetParent());
+ }
+
+ UniquePtr<T> TakeAndNextInternal() {
+ FullscreenChange* thisChange = mCurrent;
+ MOZ_ASSERT(thisChange->Type() == T::kType);
+ mCurrent = mCurrent->removeAndGetNext();
+ return WrapUnique(static_cast<T*>(thisChange));
+ }
+ void SkipToNextMatch() {
+ while (mCurrent) {
+ if (mCurrent->Type() == T::kType) {
+ RefPtr<BrowsingContext> bc =
+ mCurrent->Document()->GetBrowsingContext();
+ if (!bc) {
+ // Always automatically drop fullscreen changes which are
+ // from a document detached from the doc shell.
+ UniquePtr<T> change = TakeAndNextInternal();
+ change->MayRejectPromise("Document is not active");
+ continue;
+ }
+ while (bc && bc != mRootBCForIteration) {
+ bc = GetParentIgnoreChromeBoundary(bc);
+ }
+ if (bc) {
+ break;
+ }
+ }
+ // The current one either don't have matched type, or isn't
+ // inside the given subtree, so skip this item.
+ mCurrent = mCurrent->getNext();
+ }
+ }
+
+ FullscreenChange* mCurrent;
+ RefPtr<BrowsingContext> mRootBCForIteration;
+ };
+
+ private:
+ static LinkedList<FullscreenChange> sList;
+};
+
+/* static */
+LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
+
+Document* Document::GetFullscreenRoot() {
+ nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
+ return root;
+}
+
+size_t Document::CountFullscreenElements() const {
+ size_t count = 0;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
+ if (elem->State().HasState(ElementState::FULLSCREEN)) {
+ count++;
+ }
+ }
+ }
+ return count;
+}
+
+void Document::SetFullscreenRoot(Document* aRoot) {
+ mFullscreenRoot = do_GetWeakReference(aRoot);
+}
+
+void Document::TryCancelDialog() {
+ // Check if the document is blocked by modal dialog
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
+ dialog->QueueCancelDialog();
+ break;
+ }
+ }
+}
+
+already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
+ UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
+ RefPtr<Promise> promise = exit->GetPromise();
+ RestorePreviousFullscreenState(std::move(exit));
+ return promise.forget();
+}
+
+static void AskWindowToExitFullscreen(Document* aDoc) {
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ } else {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
+ }
+ }
+}
+
+class nsCallExitFullscreen : public Runnable {
+ public:
+ explicit nsCallExitFullscreen(Document* aDoc)
+ : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
+
+ NS_IMETHOD Run() final {
+ if (!mDoc) {
+ FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
+ } else {
+ AskWindowToExitFullscreen(mDoc);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<Document> mDoc;
+};
+
+/* static */
+void Document::AsyncExitFullscreen(Document* aDoc) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
+ if (aDoc) {
+ aDoc->Dispatch(TaskCategory::Other, exit.forget());
+ } else {
+ NS_DispatchToCurrentThread(exit.forget());
+ }
+}
+
+static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
+ uint32_t count = 0;
+ // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
+ auto subDoc = [&count](Document& aSubDoc) {
+ if (aSubDoc.Fullscreen()) {
+ count++;
+ }
+ return CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(subDoc);
+ return count;
+}
+
+bool Document::IsFullscreenLeaf() {
+ // A fullscreen leaf document is fullscreen, and has no fullscreen
+ // subdocuments.
+ //
+ // FIXME(emilio): This doesn't seem to account for fission iframes, is that
+ // ok?
+ return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
+}
+
+static Document* GetFullscreenLeaf(Document& aDoc) {
+ if (aDoc.IsFullscreenLeaf()) {
+ return &aDoc;
+ }
+ if (!aDoc.Fullscreen()) {
+ return nullptr;
+ }
+ Document* leaf = nullptr;
+ auto recurse = [&leaf](Document& aSubDoc) {
+ leaf = GetFullscreenLeaf(aSubDoc);
+ return leaf ? CallState::Stop : CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(recurse);
+ return leaf;
+}
+
+static Document* GetFullscreenLeaf(Document* aDoc) {
+ if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
+ return leaf;
+ }
+ // Otherwise we could be either in a non-fullscreen doc tree, or we're
+ // below the fullscreen doc. Start the search from the root.
+ Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ return GetFullscreenLeaf(*root);
+}
+
+static CallState ResetFullscreen(Document& aDocument) {
+ if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
+ NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
+ "Should have at most 1 fullscreen subdocument.");
+ aDocument.CleanupFullscreenState();
+ NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
+ DispatchFullscreenChange(aDocument, fsElement);
+ aDocument.EnumerateSubDocuments(ResetFullscreen);
+ }
+ return CallState::Continue;
+}
+
+// Since Document::ExitFullscreenInDocTree() could be called from
+// Element::UnbindFromTree() where it is not safe to synchronously run
+// script. This runnable is the script part of that function.
+class ExitFullscreenScriptRunnable : public Runnable {
+ public:
+ explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
+ : mozilla::Runnable("ExitFullscreenScriptRunnable"),
+ mRoot(aRoot),
+ mLeaf(aLeaf) {}
+
+ NS_IMETHOD Run() override {
+ // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
+ // document since we want this event to follow the same path that
+ // MozDOMFullscreen:Entered was dispatched.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
+ CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
+ // Ensure the window exits fullscreen, as long as we don't have
+ // pending fullscreen requests.
+ if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
+ if (!mRoot->HasPendingFullscreenRequests()) {
+ win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
+ false);
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<Document> mRoot;
+ nsCOMPtr<Document> mLeaf;
+};
+
+/* static */
+void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
+ MOZ_ASSERT(aMaybeNotARootDoc);
+
+ // Unlock the pointer
+ PointerLockManager::Unlock();
+
+ // Resolve all promises which waiting for exit fullscreen.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
+ aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
+ exit->MayResolvePromise();
+ }
+
+ nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
+ if (!root || !root->Fullscreen()) {
+ // If a document was detached before exiting from fullscreen, it is
+ // possible that the root had left fullscreen state. In this case,
+ // we would not get anything from the ResetFullscreen() call. Root's
+ // not being a fullscreen doc also means the widget should have
+ // exited fullscreen state. It means even if we do not return here,
+ // we would actually do nothing below except crashing ourselves via
+ // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
+ // document.
+ return;
+ }
+
+ // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
+ // See ExitFullscreenScriptRunnable::Run for details. We have to
+ // record it here because we don't have such information after we
+ // reset the fullscreen state below.
+ Document* fullscreenLeaf = GetFullscreenLeaf(root);
+
+ // Walk the tree of fullscreen documents, and reset their fullscreen state.
+ ResetFullscreen(*root);
+
+ NS_ASSERTION(!root->Fullscreen(),
+ "Fullscreen root should no longer be a fullscreen doc...");
+
+ // Move the top-level window out of fullscreen mode.
+ FullscreenRoots::Remove(root);
+
+ nsContentUtils::AddScriptRunner(
+ new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
+}
+
+static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
+ NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
+ "Should have at least 1 fullscreen root when fullscreen!");
+
+ if (!GetWindow()) {
+ aExit->MayRejectPromise("No active window");
+ return;
+ }
+ if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
+ aExit->MayRejectPromise("Not in fullscreen mode");
+ return;
+ }
+
+ nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
+ AutoTArray<Element*, 8> exitElements;
+
+ Document* doc = fullScreenDoc;
+ // Collect all subdocuments.
+ for (; doc != this; doc = doc->GetInProcessParentDocument()) {
+ Element* fsElement = doc->GetUnretargetedFullscreenElement();
+ MOZ_ASSERT(fsElement,
+ "Parent document of "
+ "a fullscreen document without fullscreen element?");
+ exitElements.AppendElement(fsElement);
+ }
+ MOZ_ASSERT(doc == this, "Must have reached this doc");
+ // Collect all ancestor documents which we are going to change.
+ for (; doc; doc = doc->GetInProcessParentDocument()) {
+ Element* fsElement = doc->GetUnretargetedFullscreenElement();
+ MOZ_ASSERT(fsElement,
+ "Ancestor of fullscreen document must also be in fullscreen");
+ if (doc != this) {
+ if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
+ if (iframe->FullscreenFlag()) {
+ // If this is an iframe, and it explicitly requested
+ // fullscreen, don't rollback it automatically.
+ break;
+ }
+ }
+ }
+ exitElements.AppendElement(fsElement);
+ if (doc->CountFullscreenElements() > 1) {
+ break;
+ }
+ }
+
+ Document* lastDoc = exitElements.LastElement()->OwnerDoc();
+ size_t fullscreenCount = lastDoc->CountFullscreenElements();
+ if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
+ // If we are fully exiting fullscreen, don't touch anything here,
+ // just wait for the window to get out from fullscreen first.
+ PendingFullscreenChangeList::Add(std::move(aExit));
+ AskWindowToExitFullscreen(this);
+ return;
+ }
+
+ // If fullscreen mode is updated the pointer should be unlocked
+ PointerLockManager::Unlock();
+ // All documents listed in the array except the last one are going to
+ // completely exit from the fullscreen state.
+ for (auto i : IntegerRange(exitElements.Length() - 1)) {
+ exitElements[i]->OwnerDoc()->CleanupFullscreenState();
+ }
+ // The last document will either rollback one fullscreen element, or
+ // completely exit from the fullscreen state as well.
+ Document* newFullscreenDoc;
+ if (fullscreenCount > 1) {
+ DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
+ MOZ_ASSERT(removedFullscreenElement);
+ newFullscreenDoc = lastDoc;
+ } else {
+ lastDoc->CleanupFullscreenState();
+ newFullscreenDoc = lastDoc->GetInProcessParentDocument();
+ }
+ // Dispatch the fullscreenchange event to all document listed. Note
+ // that the loop order is reversed so that events are dispatched in
+ // the tree order as indicated in the spec.
+ for (Element* e : Reversed(exitElements)) {
+ DispatchFullscreenChange(*e->OwnerDoc(), e);
+ }
+ aExit->MayResolvePromise();
+
+ MOZ_ASSERT(newFullscreenDoc,
+ "If we were going to exit from fullscreen on "
+ "all documents in this doctree, we should've asked the window to "
+ "exit first instead of reaching here.");
+ if (fullScreenDoc != newFullscreenDoc &&
+ !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
+ // We've popped so enough off the stack that we've rolled back to
+ // a fullscreen element in a parent document. If this document is
+ // cross origin, dispatch an event to chrome so it knows to show
+ // the warning UI.
+ DispatchFullscreenNewOriginEvent(newFullscreenDoc);
+ }
+}
+
+static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
+ if (nsPresContext* presContext = aDoc->GetPresContext()) {
+ presContext->UpdateViewportScrollStylesOverride();
+ }
+}
+
+static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
+ // When a media element enters the fullscreen, we would like to notify that
+ // to the media controller in order to update its status.
+ if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
+ mediaElem->NotifyFullScreenChanged();
+ }
+}
+
+void Document::CleanupFullscreenState() {
+ while (PopFullscreenElement(UpdateViewport::No)) {
+ // Remove the next one if appropriate
+ }
+
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+ mFullscreenRoot = nullptr;
+
+ // Restore the zoom level that was in place prior to entering fullscreen.
+ if (PresShell* presShell = GetPresShell()) {
+ if (presShell->GetMobileViewportManager()) {
+ presShell->SetResolutionAndScaleTo(
+ mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
+ }
+ }
+}
+
+bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
+ Element* removedElement = TopLayerPop([](Element* element) -> bool {
+ return element->State().HasState(ElementState::FULLSCREEN);
+ });
+
+ if (!removedElement) {
+ return false;
+ }
+
+ MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
+ removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
+ NotifyFullScreenChangedForMediaElement(*removedElement);
+ // Reset iframe fullscreen flag.
+ if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
+ iframe->SetFullscreenFlag(false);
+ }
+ if (aUpdateViewport == UpdateViewport::Yes) {
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+ }
+ return true;
+}
+
+void Document::SetFullscreenElement(Element& aElement) {
+ ElementState statesToAdd = ElementState::FULLSCREEN;
+ if (StaticPrefs::dom_fullscreen_modal() && !IsInChromeDocShell()) {
+ // Don't make the document modal in chrome documents, since we don't want
+ // the browser UI like the context menu / etc to be inert.
+ statesToAdd |= ElementState::MODAL;
+ }
+ aElement.AddStates(statesToAdd);
+ TopLayerPush(aElement);
+ NotifyFullScreenChangedForMediaElement(aElement);
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+}
+
+void Document::TopLayerPush(Element& aElement) {
+ const bool modal = aElement.State().HasState(ElementState::MODAL);
+
+ TopLayerPop(aElement);
+ mTopLayer.AppendElement(do_GetWeakReference(&aElement));
+ NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
+
+ if (modal) {
+ aElement.AddStates(ElementState::TOPMOST_MODAL);
+
+ bool foundExistingModalElement = false;
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element != &aElement &&
+ element->State().HasState(ElementState::TOPMOST_MODAL)) {
+ element->RemoveStates(ElementState::TOPMOST_MODAL);
+ foundExistingModalElement = true;
+ break;
+ }
+ }
+
+ if (!foundExistingModalElement) {
+ Element* root = GetRootElement();
+ MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
+ if (&aElement != root) {
+ // Add inert to the root element so that the inertness is applied to the
+ // entire document.
+ root->AddStates(ElementState::INERT);
+ }
+ }
+ }
+}
+
+void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
+ aDialogElement.AddStates(ElementState::MODAL);
+ TopLayerPush(aDialogElement);
+}
+
+void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
+ DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
+ MOZ_ASSERT(removedElement == &aDialogElement);
+ aDialogElement.RemoveStates(ElementState::MODAL);
+}
+
+Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
+ if (mTopLayer.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Remove the topmost element that qualifies aPredicate; This
+ // is required is because the top layer contains not only
+ // fullscreen elements, but also dialog elements.
+ Element* removedElement = nullptr;
+ for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
+ nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
+ if (element && aPredicate(element)) {
+ removedElement = element;
+ mTopLayer.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ // Pop from the stack null elements (references to elements which have
+ // been GC'd since they were added to the stack) and elements which are
+ // no longer in this document.
+ //
+ // FIXME(emilio): If this loop does something, it'd violate the assertions
+ // from GetTopLayerTop()... What gives?
+ while (!mTopLayer.IsEmpty()) {
+ Element* element = GetTopLayerTop();
+ if (!element || element->GetComposedDoc() != this) {
+ mTopLayer.RemoveLastElement();
+ } else {
+ // The top element of the stack is now an in-doc element. Return here.
+ break;
+ }
+ }
+
+ if (!removedElement) {
+ return nullptr;
+ }
+
+ const bool modal = removedElement->State().HasState(ElementState::MODAL);
+
+ if (modal) {
+ removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
+ bool foundExistingModalElement = false;
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element->State().HasState(ElementState::MODAL)) {
+ element->AddStates(ElementState::TOPMOST_MODAL);
+ foundExistingModalElement = true;
+ break;
+ }
+ }
+ // No more modal elements, make the document not inert anymore.
+ if (!foundExistingModalElement) {
+ Element* root = GetRootElement();
+ if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
+ root->RemoveStates(ElementState::INERT);
+ }
+ }
+ }
+
+ return removedElement;
+}
+
+Element* Document::TopLayerPop(Element& aElement) {
+ auto predictFunc = [&aElement](Element* element) {
+ return element == &aElement;
+ };
+ return TopLayerPop(predictFunc);
+}
+
+void Document::GetWireframe(bool aIncludeNodes,
+ Nullable<Wireframe>& aWireframe) {
+ FlushPendingNotifications(FlushType::Layout);
+ GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
+}
+
+void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
+ Nullable<Wireframe>& aWireframe) {
+ using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+ PresShell* shell = GetPresShell();
+ if (!shell) {
+ return;
+ }
+
+ nsPresContext* pc = shell->GetPresContext();
+ if (!pc) {
+ return;
+ }
+
+ nsIFrame* rootFrame = shell->GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ auto& wireframe = aWireframe.SetValue();
+ wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mColor;
+
+ FrameForPointOptions options;
+ options.mBits += FrameForPointOption::IgnoreCrossDoc;
+ options.mBits += FrameForPointOption::IgnorePaintSuppression;
+ options.mBits += FrameForPointOption::OnlyVisible;
+
+ AutoTArray<nsIFrame*, 32> frames;
+ const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
+ nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
+ options);
+
+ // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
+ // something perhaps, but seems hard / like it'd involve at least some extra
+ // copying around, since they don't outlive GetFramesForArea.
+ auto& rects = wireframe.mRects.Construct();
+ if (!rects.SetCapacity(frames.Length(), fallible)) {
+ return;
+ }
+ for (nsIFrame* frame : Reversed(frames)) {
+ auto [rectColor,
+ rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
+ if (frame->IsTextFrame()) {
+ return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
+ WireframeRectType::Text};
+ }
+ if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
+ return {0, WireframeRectType::Image};
+ }
+ if (frame->IsThemed()) {
+ return {0, WireframeRectType::Background};
+ }
+ bool drawImage = false;
+ bool drawColor = false;
+ if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
+ const nscolor color = nsCSSRendering::DetermineBackgroundColor(
+ pc, bgStyle, frame, drawImage, drawColor);
+ if (drawImage &&
+ !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
+ return {color, WireframeRectType::Image};
+ }
+ if (drawColor && !frame->IsCanvasFrame()) {
+ // Canvas frame background already accounted for in mCanvasBackground.
+ return {color, WireframeRectType::Background};
+ }
+ }
+ return {0, WireframeRectType::Unknown};
+ }();
+
+ if (rectType == WireframeRectType::Unknown) {
+ continue;
+ }
+
+ const auto r =
+ CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, frame->GetRectRelativeToSelf(), relativeTo));
+ if ((uint32_t)r.Area() <
+ StaticPrefs::browser_history_wireframeAreaThreshold()) {
+ continue;
+ }
+
+ // Can't really fail because SetCapacity succeeded.
+ auto& taggedRect = *rects.AppendElement(fallible);
+
+ if (aIncludeNodes) {
+ if (nsIContent* c = frame->GetContent()) {
+ taggedRect.mNode.Construct(c);
+ }
+ }
+ taggedRect.mX = r.x;
+ taggedRect.mY = r.y;
+ taggedRect.mWidth = r.width;
+ taggedRect.mHeight = r.height;
+ taggedRect.mColor = rectColor;
+ taggedRect.mType.Construct(rectType);
+ }
+}
+
+Element* Document::GetTopLayerTop() {
+ if (mTopLayer.IsEmpty()) {
+ return nullptr;
+ }
+ uint32_t last = mTopLayer.Length() - 1;
+ nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
+ NS_ASSERTION(element, "Should have a top layer element!");
+ NS_ASSERTION(element->IsInComposedDoc(),
+ "Top layer element should be in doc");
+ NS_ASSERTION(element->OwnerDoc() == this,
+ "Top layer element should be in this doc");
+ return element;
+}
+
+Element* Document::GetUnretargetedFullscreenElement() const {
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ // Per spec, the fullscreen element is the topmost element in the document’s
+ // top layer whose fullscreen flag is set, if any, and null otherwise.
+ if (element && element->State().HasState(ElementState::FULLSCREEN)) {
+ return element;
+ }
+ }
+ return nullptr;
+}
+
+nsTArray<Element*> Document::GetTopLayer() const {
+ nsTArray<Element*> elements;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
+ elements.AppendElement(elem);
+ }
+ }
+ return elements;
+}
+
+bool Document::TopLayerContains(Element& aElement) const {
+ if (mTopLayer.IsEmpty()) {
+ return false;
+ }
+ nsWeakPtr weakElement = do_GetWeakReference(&aElement);
+ return mTopLayer.Contains(weakElement);
+}
+
+void Document::HideAllPopoversUntil(nsINode& aEndpoint,
+ bool aFocusPreviousElement,
+ bool aFireEvents) {
+ auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
+ this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
+ HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
+ }
+ };
+
+ if (&aEndpoint == this) {
+ closeAllOpenPopovers();
+ return;
+ }
+
+ // https://github.com/whatwg/html/pull/9198
+ auto needRepeatingHide = [&]() {
+ auto autoList = AutoPopoverList();
+ return autoList.Contains(&aEndpoint) &&
+ &aEndpoint != autoList.LastElement();
+ };
+
+ MOZ_ASSERT((&aEndpoint)->IsElement() &&
+ (&aEndpoint)->AsElement()->IsAutoPopover());
+ bool repeatingHide = false;
+ bool fireEvents = aFireEvents;
+ do {
+ RefPtr<const Element> lastToHide = nullptr;
+ bool foundEndpoint = false;
+ for (const Element* popover : AutoPopoverList()) {
+ if (popover == &aEndpoint) {
+ foundEndpoint = true;
+ } else if (foundEndpoint) {
+ lastToHide = popover;
+ break;
+ }
+ }
+
+ if (!foundEndpoint) {
+ closeAllOpenPopovers();
+ return;
+ }
+
+ while (lastToHide && lastToHide->IsPopoverOpen()) {
+ RefPtr<Element> topmost = GetTopmostAutoPopover();
+ if (!topmost) {
+ break;
+ }
+ HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
+ }
+
+ repeatingHide = needRepeatingHide();
+ if (repeatingHide) {
+ fireEvents = false;
+ }
+ } while (repeatingHide);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void
+Document::HideAllPopoversWithoutRunningScript() {
+ return HideAllPopoversUntil(*this, false, false);
+}
+
+void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
+ bool aFireEvents, ErrorResult& aRv) {
+ RefPtr<nsGenericHTMLElement> popoverHTMLEl =
+ nsGenericHTMLElement::FromNode(aPopover);
+ NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
+
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+
+ bool wasHiding = popoverHTMLEl->GetPopoverData()->IsHiding();
+ popoverHTMLEl->GetPopoverData()->SetIsHiding(true);
+ auto restoreIsHiding = MakeScopeExit([&]() {
+ if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
+ popoverData->SetIsHiding(wasHiding);
+ }
+ });
+
+ if (popoverHTMLEl->IsAutoPopover()) {
+ HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, aFireEvents);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ // TODO: we can't always guarantee:
+ // The last item in document's auto popover list is popoverHTMLEl.
+ // See, https://github.com/whatwg/html/issues/9197
+ // If popoverHTMLEl is not on top, hide popovers again without firing
+ // events.
+ if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
+ HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
+ "popoverHTMLEl should be on top of auto popover list");
+ }
+ }
+
+ auto* data = popoverHTMLEl->GetPopoverData();
+ MOZ_ASSERT(data, "Should have popover data");
+ data->SetInvoker(nullptr);
+
+ // Fire beforetoggle event and re-check popover validity.
+ if (aFireEvents && !wasHiding) {
+ // Intentionally ignore the return value here as only on open event for
+ // beforetoggle the cancelable attribute is initialized to true.
+ popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
+ PopoverVisibilityState::Hidden,
+ u"beforetoggle"_ns);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ }
+
+ RemovePopoverFromTopLayer(aPopover);
+
+ popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
+ popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
+ PopoverVisibilityState::Hidden);
+
+ // Queue popover toggle event task.
+ if (aFireEvents) {
+ popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
+ }
+
+ if (aFocusPreviousElement) {
+ popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
+ } else {
+ popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
+ }
+}
+
+nsTArray<Element*> Document::AutoPopoverList() const {
+ nsTArray<Element*> elements;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
+ if (element && element->IsAutoPopover()) {
+ elements.AppendElement(element);
+ }
+ }
+ }
+ return elements;
+}
+
+Element* Document::GetTopmostAutoPopover() const {
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element->IsAutoPopover()) {
+ return element;
+ }
+ }
+ return nullptr;
+}
+
+void Document::AddToAutoPopoverList(Element& aElement) {
+ MOZ_ASSERT(aElement.IsAutoPopover());
+ TopLayerPush(aElement);
+}
+
+void Document::RemoveFromAutoPopoverList(Element& aElement) {
+ MOZ_ASSERT(aElement.IsAutoPopover());
+ TopLayerPop(aElement);
+}
+
+void Document::AddPopoverToTopLayer(Element& aElement) {
+ MOZ_ASSERT(aElement.GetPopoverData());
+ TopLayerPush(aElement);
+}
+
+void Document::RemovePopoverFromTopLayer(Element& aElement) {
+ MOZ_ASSERT(aElement.GetPopoverData());
+ TopLayerPop(aElement);
+}
+
+// Returns true if aDoc browsing context is focused.
+bool IsInFocusedTab(Document* aDoc) {
+ BrowsingContext* bc = aDoc->GetBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return false;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
+ // by retaining the old code path for the parent process.
+ nsIDocShell* docshell = aDoc->GetDocShell();
+ if (!docshell) {
+ return false;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ if (!rootItem) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+ if (!rootWin) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
+ activeWindow = fm->GetActiveWindow();
+ if (!activeWindow) {
+ return false;
+ }
+
+ return activeWindow == rootWin;
+ }
+
+ return fm->GetActiveBrowsingContext() == bc->Top();
+}
+
+// Returns true if aDoc browsing context is focused and is also active.
+bool IsInActiveTab(Document* aDoc) {
+ if (!IsInFocusedTab(aDoc)) {
+ return false;
+ }
+
+ BrowsingContext* bc = aDoc->GetBrowsingContext();
+ MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
+ return bc->IsActive();
+}
+
+void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
+ // Ensure the frame element is the fullscreen element in this document.
+ // If the frame element is already the fullscreen element in this document,
+ // this has no effect.
+ auto request = FullscreenRequest::CreateForRemote(aFrameElement);
+ RequestFullscreen(std::move(request), XRE_IsContentProcess());
+}
+
+void Document::RemoteFrameFullscreenReverted() {
+ UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
+ RestorePreviousFullscreenState(std::move(exit));
+}
+
+static bool HasFullscreenSubDocument(Document& aDoc) {
+ uint32_t count = CountFullscreenSubDocuments(aDoc);
+ NS_ASSERTION(count <= 1,
+ "Fullscreen docs should have at most 1 fullscreen child!");
+ return count >= 1;
+}
+
+// Returns nullptr if a request for Fullscreen API is currently enabled
+// in the given document. Returns a static string indicates the reason
+// why it is not enabled otherwise.
+const char* Document::GetFullscreenError(CallerType aCallerType) {
+ if (!StaticPrefs::full_screen_api_enabled()) {
+ return "FullscreenDeniedDisabled";
+ }
+
+ if (aCallerType == CallerType::System) {
+ // Chrome code can always use the fullscreen API, provided it's not
+ // explicitly disabled.
+ return nullptr;
+ }
+
+ if (!IsVisible()) {
+ return "FullscreenDeniedHidden";
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
+ return "FullscreenDeniedFeaturePolicy";
+ }
+
+ // Ensure that all containing elements are <iframe> and have allowfullscreen
+ // attribute set.
+ BrowsingContext* bc = GetBrowsingContext();
+ if (!bc || !bc->FullscreenAllowed()) {
+ return "FullscreenDeniedContainerNotAllowed";
+ }
+
+ return nullptr;
+}
+
+bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
+ Element* elem = aRequest.Element();
+ // Strictly speaking, this isn't part of the fullscreen element ready
+ // check in the spec, but per steps in the spec, when an element which
+ // is already the fullscreen element requests fullscreen, nothing
+ // should change and no event should be dispatched, but we still need
+ // to resolve the returned promise.
+ Element* fullscreenElement = GetUnretargetedFullscreenElement();
+ if (elem == fullscreenElement) {
+ aRequest.MayResolvePromise();
+ return false;
+ }
+ if (!elem->IsInComposedDoc()) {
+ aRequest.Reject("FullscreenDeniedNotInDocument");
+ return false;
+ }
+ if (elem->IsPopoverOpen()) {
+ aRequest.Reject("FullscreenDeniedPopoverOpen");
+ return false;
+ }
+ if (elem->OwnerDoc() != this) {
+ aRequest.Reject("FullscreenDeniedMovedDocument");
+ return false;
+ }
+ if (!GetWindow()) {
+ aRequest.Reject("FullscreenDeniedLostWindow");
+ return false;
+ }
+ if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
+ aRequest.Reject(msg);
+ return false;
+ }
+ if (HasFullscreenSubDocument(*this)) {
+ aRequest.Reject("FullscreenDeniedSubDocFullScreen");
+ return false;
+ }
+ if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
+ aRequest.Reject("FullscreenDeniedHTMLDialog");
+ return false;
+ }
+ if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
+ aRequest.Reject("FullscreenDeniedNotFocusedTab");
+ return false;
+ }
+ // Deny requests when a windowed plugin is focused.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
+ aRequest.MayRejectPromise("An unexpected error occurred");
+ return false;
+ }
+ if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
+ fm->GetFocusedElement())) {
+ aRequest.Reject("FullscreenDeniedFocusedPlugin");
+ return false;
+ }
+ return true;
+}
+
+static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsIDocShell* docShell = aDoc->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ return rootItem ? rootItem->GetWindow() : nullptr;
+}
+
+static bool ShouldApplyFullscreenDirectly(Document* aDoc,
+ nsPIDOMWindowOuter* aRootWin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // If we are in the chrome process, and the window has not been in
+ // fullscreen, we certainly need to make that fullscreen first.
+ if (!aRootWin->GetFullScreen()) {
+ return false;
+ }
+ // The iterator not being at end indicates there is still some
+ // pending fullscreen request relates to this document. We have to
+ // push the request to the pending queue so requests are handled
+ // in the correct order.
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iter.AtEnd()) {
+ return false;
+ }
+
+ // Same thing for exits. If we have any pending, we have to push
+ // to the pending queue.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iterExit.AtEnd()) {
+ return false;
+ }
+
+ // We have to apply the fullscreen state directly in this case,
+ // because nsGlobalWindow::SetFullscreenInternal() will do nothing
+ // if it is already in fullscreen. If we do not apply the state but
+ // instead add it to the queue and wait for the window as normal,
+ // we would get stuck.
+ return true;
+}
+
+static bool CheckFullscreenAllowedElementType(const Element* elem) {
+ // Per spec only HTML, <svg>, and <math> should be allowed, but
+ // we also need to allow XUL elements right now.
+ return elem->IsHTMLElement() || elem->IsXULElement() ||
+ elem->IsSVGElement(nsGkAtoms::svg) ||
+ elem->IsMathMLElement(nsGkAtoms::math);
+}
+
+void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
+ bool aApplyFullscreenDirectly) {
+ if (XRE_IsContentProcess()) {
+ RequestFullscreenInContentProcess(std::move(aRequest),
+ aApplyFullscreenDirectly);
+ } else {
+ RequestFullscreenInParentProcess(std::move(aRequest),
+ aApplyFullscreenDirectly);
+ }
+}
+
+void Document::RequestFullscreenInContentProcess(
+ UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ // If we are in the content process, we can apply the fullscreen
+ // state directly only if we have been in DOM fullscreen, because
+ // otherwise we always need to notify the chrome.
+ if (aApplyFullscreenDirectly ||
+ nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
+ ApplyFullscreen(std::move(aRequest));
+ return;
+ }
+
+ if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
+ aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
+ return;
+ }
+
+ // We don't need to check element ready before this point, because
+ // if we called ApplyFullscreen, it would check that for us.
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return;
+ }
+
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ // If we are not the top level process, dispatch an event to make
+ // our parent process go fullscreen first.
+ Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
+ if (!self->HasPendingFullscreenRequests()) {
+ return;
+ }
+ nsContentUtils::DispatchEventOnlyToChrome(
+ self, ToSupports(self), u"MozDOMFullscreen:Request"_ns,
+ CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
+ }));
+}
+
+void Document::RequestFullscreenInParentProcess(
+ UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
+ if (!rootWin) {
+ aRequest->MayRejectPromise("No active window");
+ return;
+ }
+
+ if (aApplyFullscreenDirectly ||
+ ShouldApplyFullscreenDirectly(this, rootWin)) {
+ ApplyFullscreen(std::move(aRequest));
+ return;
+ }
+
+ if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
+ aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
+ return;
+ }
+
+ // See if we're waiting on an exit. If so, just make this one pending.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
+ this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iter.AtEnd()) {
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
+ return;
+ }
+
+ // We don't need to check element ready before this point, because
+ // if we called ApplyFullscreen, it would check that for us.
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return;
+ }
+
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ // Make the window fullscreen.
+ rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
+}
+
+/* static */
+bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
+ bool handled = false;
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
+ Document* doc = request->Document();
+ if (doc->ApplyFullscreen(std::move(request))) {
+ handled = true;
+ }
+ }
+ return handled;
+}
+
+/* static */
+void Document::ClearPendingFullscreenRequests(Document* aDoc) {
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
+ request->MayRejectPromise("Fullscreen request aborted");
+ }
+}
+
+bool Document::HasPendingFullscreenRequests() {
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ return !iter.AtEnd();
+}
+
+bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return false;
+ }
+
+ RefPtr<Document> doc = aRequest->Document();
+ doc->HideAllPopoversWithoutRunningScript();
+
+ // Stash a reference to any existing fullscreen doc, we'll use this later
+ // to detect if the origin which is fullscreen has changed.
+ nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
+
+ // Stores a list of documents which we must dispatch "fullscreenchange"
+ // too. We're required by the spec to dispatch the events in root-to-leaf
+ // order, but we traverse the doctree in a leaf-to-root order, so we save
+ // references to the documents we must dispatch to so that we get the order
+ // as specified.
+ AutoTArray<Document*, 8> changed;
+
+ // Remember the root document, so that if a fullscreen document is hidden
+ // we can reset fullscreen state in the remaining visible fullscreen
+ // documents.
+ Document* fullScreenRootDoc =
+ nsContentUtils::GetInProcessSubtreeRootDocument(this);
+
+ // If a document is already in fullscreen, then unlock the mouse pointer
+ // before setting a new document to fullscreen
+ PointerLockManager::Unlock();
+
+ // Set the fullscreen element. This sets the fullscreen style on the
+ // element, and the fullscreen-ancestor styles on ancestors of the element
+ // in this document.
+ Element* elem = aRequest->Element();
+ SetFullscreenElement(*elem);
+ // Set the iframe fullscreen flag.
+ if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
+ iframe->SetFullscreenFlag(true);
+ }
+ changed.AppendElement(this);
+
+ // Propagate up the document hierarchy, setting the fullscreen element as
+ // the element's container in ancestor documents. This also sets the
+ // appropriate css styles as well. Note we don't propagate down the
+ // document hierarchy, the fullscreen element (or its container) is not
+ // visible there. Stop when we reach the root document.
+ Document* child = this;
+ while (true) {
+ child->SetFullscreenRoot(fullScreenRootDoc);
+
+ // When entering fullscreen, reset the RCD's resolution to the intrinsic
+ // resolution, otherwise the fullscreen content could be sized larger than
+ // the screen (since fullscreen is implemented using position:fixed and
+ // fixed elements are sized to the layout viewport).
+ // This also ensures that things like video controls aren't zoomed in
+ // when in fullscreen mode.
+ if (PresShell* presShell = child->GetPresShell()) {
+ if (RefPtr<MobileViewportManager> manager =
+ presShell->GetMobileViewportManager()) {
+ // Save the previous resolution so it can be restored.
+ child->mSavedResolution = presShell->GetResolution();
+ presShell->SetResolutionAndScaleTo(
+ manager->ComputeIntrinsicResolution(),
+ ResolutionChangeOrigin::MainThreadRestore);
+ }
+ }
+
+ NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
+ "Fullscreen root should be set!");
+ if (child == fullScreenRootDoc) {
+ break;
+ }
+
+ Element* element = child->GetEmbedderElement();
+ if (!element) {
+ // We've reached the root.No more changes need to be made
+ // to the top layer stacks of documents further up the tree.
+ break;
+ }
+
+ Document* parent = child->GetInProcessParentDocument();
+ parent->SetFullscreenElement(*element);
+ changed.AppendElement(parent);
+ child = parent;
+ }
+
+ FullscreenRoots::Add(this);
+
+ // If it is the first entry of the fullscreen, trigger an event so
+ // that the UI can response to this change, e.g. hide chrome, or
+ // notifying parent process to enter fullscreen. Note that chrome
+ // code may also want to listen to MozDOMFullscreen:NewOrigin event
+ // to pop up warning UI.
+ if (!previousFullscreenDoc) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ }
+
+ // The origin which is fullscreen gets changed. Trigger an event so
+ // that the chrome knows to pop up a warning UI. Note that
+ // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
+ // directly as soon as chrome document goes into fullscreen state. Also note
+ // that, in a multi-process browser, the code in content process is
+ // responsible for sending message with the origin to its parent, and the
+ // parent shouldn't rely on this event itself.
+ if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
+ !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
+ DispatchFullscreenNewOriginEvent(this);
+ }
+
+ // Dispatch "fullscreenchange" events. Note that the loop order is
+ // reversed so that events are dispatched in the tree order as
+ // indicated in the spec.
+ for (Document* d : Reversed(changed)) {
+ DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
+ }
+ aRequest->MayResolvePromise();
+ return true;
+}
+
+void Document::ClearOrientationPendingPromise() {
+ mOrientationPendingPromise = nullptr;
+}
+
+bool Document::SetOrientationPendingPromise(Promise* aPromise) {
+ if (mIsGoingAway) {
+ return false;
+ }
+
+ mOrientationPendingPromise = aPromise;
+ return true;
+}
+
+void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
+ dom::VisibilityState oldState = mVisibilityState;
+ mVisibilityState = ComputeVisibilityState();
+ if (oldState != mVisibilityState) {
+ if (aDispatchEvent == DispatchVisibilityChange::Yes) {
+ nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
+ u"visibilitychange"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+ }
+ NotifyActivityChanged();
+ if (mVisibilityState == dom::VisibilityState::Visible) {
+ MaybeActiveMediaComponents();
+ }
+
+ bool visible = !Hidden();
+ for (auto* listener : mWorkerListeners) {
+ listener->OnVisible(visible);
+ }
+ }
+}
+
+void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
+ mWorkerListeners.Insert(aListener);
+ aListener->OnVisible(!Hidden());
+}
+
+void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
+ mWorkerListeners.Remove(aListener);
+}
+
+VisibilityState Document::ComputeVisibilityState() const {
+ // We have to check a few pieces of information here:
+ // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
+ // 2) Do we have an outer window? If not, we're hidden. Note that we don't
+ // want to use GetWindow here because it does weird groveling for windows
+ // in some cases.
+ // 3) Is our outer window background? If so, we're hidden.
+ // Otherwise, we're visible.
+ if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
+ mWindow->GetOuterWindow()->IsBackground()) {
+ return dom::VisibilityState::Hidden;
+ }
+
+ return dom::VisibilityState::Visible;
+}
+
+void Document::PostVisibilityUpdateEvent() {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
+ "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
+ DispatchVisibilityChange::Yes);
+ Dispatch(TaskCategory::Other, event.forget());
+}
+
+void Document::MaybeActiveMediaComponents() {
+ auto* window = GetWindow();
+ if (!window || !window->ShouldDelayMediaFromStart()) {
+ return;
+ }
+ window->ActivateMediaComponents();
+}
+
+void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
+ nsINode::AddSizeOfExcludingThis(aWindowSizes,
+ &aWindowSizes.mDOMSizes.mDOMOtherSize);
+
+ for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
+ AddSizeOfNodeTree(*kid, aWindowSizes);
+ }
+
+ // IMPORTANT: for our ComputedValues measurements, we want to measure
+ // ComputedValues accessible from DOM elements before ComputedValues not
+ // accessible from DOM elements (i.e. accessible only from the frame tree).
+ //
+ // Therefore, the measurement of the Document superclass must happen after
+ // the measurement of DOM nodes (above), because Document contains the
+ // PresShell, which contains the frame tree.
+ if (mPresShell) {
+ mPresShell->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
+
+ aWindowSizes.mPropertyTablesSize +=
+ mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
+
+ if (EventListenerManager* elm = GetExistingListenerManager()) {
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+
+ if (mNodeInfoManager) {
+ mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
+ mDOMMediaQueryLists.sizeOfExcludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+
+ for (const MediaQueryList* mql : mDOMMediaQueryLists) {
+ aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
+ mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
+ }
+
+ DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
+
+ for (auto& sheetArray : mAdditionalSheets) {
+ AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
+ }
+ // Lumping in the loader with the style-sheets size is not ideal,
+ // but most of the things in there are in fact stylesheets, so it
+ // doesn't seem worthwhile to separate it out.
+ // This can be null if we've already been unlinked.
+ if (mCSSLoader) {
+ aWindowSizes.mLayoutStyleSheetsSize +=
+ mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
+ }
+
+ if (mResizeObserverController) {
+ mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
+ aWindowSizes.mState.mMallocSizeOf)
+ : 0;
+
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ mStyledLinks.ShallowSizeOfExcludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mMidasCommandManager
+ // - many!
+}
+
+void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ aWindowSizes.mState.mMallocSizeOf(this);
+ DocAddSizeOfExcludingThis(aWindowSizes);
+}
+
+void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ // This AddSizeOfExcludingThis() overrides the one from nsINode. But
+ // nsDocuments can only appear at the top of the DOM tree, and we use the
+ // specialized DocAddSizeOfExcludingThis() in that case. So this should never
+ // be called.
+ MOZ_CRASH();
+}
+
+/* static */
+void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
+ size_t nodeSize = 0;
+ aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
+
+ // This is where we transfer the nodeSize obtained from
+ // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
+ switch (aNode.NodeType()) {
+ case nsINode::ELEMENT_NODE:
+ aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
+ break;
+ case nsINode::TEXT_NODE:
+ aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
+ break;
+ case nsINode::CDATA_SECTION_NODE:
+ aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
+ break;
+ case nsINode::COMMENT_NODE:
+ aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
+ break;
+ default:
+ aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
+ break;
+ }
+
+ if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+
+ if (aNode.IsContent()) {
+ nsTArray<nsIContent*> anonKids;
+ nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
+ nsIContent::eAllChildren);
+ for (nsIContent* anonKid : anonKids) {
+ AddSizeOfNodeTree(*anonKid, aWindowSizes);
+ }
+
+ if (auto* element = Element::FromNode(aNode)) {
+ if (ShadowRoot* shadow = element->GetShadowRoot()) {
+ AddSizeOfNodeTree(*shadow, aWindowSizes);
+ }
+ }
+ }
+
+ // NOTE(emilio): If you feel smart and want to change this function to use
+ // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
+ // sane way, and kids of <content> won't point to the parent, so we'd never
+ // find the root node where we should stop at.
+ for (nsIContent* kid = aNode.GetFirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ AddSizeOfNodeTree(*kid, aWindowSizes);
+ }
+}
+
+already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& rv) {
+ nsCOMPtr<nsIScriptGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptObjectPrincipal> prin =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!prin) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (!uri) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc;
+ nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
+ nullptr, uri, uri, prin->GetPrincipal(),
+ true, global, DocumentFlavorPlain);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+
+ return doc.forget();
+}
+
+XPathExpression* Document::CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv) {
+ return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
+}
+
+nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
+ return XPathEvaluator()->CreateNSResolver(aNodeResolver);
+}
+
+already_AddRefed<XPathResult> Document::Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv) {
+ return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
+ aType, aResult, rv);
+}
+
+already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
+ nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
+ if (!item) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ item->GetTreeOwner(getter_AddRefs(owner));
+ nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
+ if (!appWin) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShell> appWinShell;
+ appWin->GetDocShell(getter_AddRefs(appWinShell));
+ if (!SameCOMIdentity(appWinShell, item)) {
+ return nullptr;
+ }
+ return appWin.forget();
+}
+
+WindowContext* Document::GetTopLevelWindowContext() const {
+ WindowContext* windowContext = GetWindowContext();
+ return windowContext ? windowContext->TopWindowContext() : nullptr;
+}
+
+Document* Document::GetTopLevelContentDocumentIfSameProcess() {
+ Document* parent;
+
+ if (!mLoadedAsData) {
+ parent = this;
+ } else {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ if (!window) {
+ return nullptr;
+ }
+
+ parent = window->GetExtantDoc();
+ if (!parent) {
+ return nullptr;
+ }
+ }
+
+ do {
+ if (parent->IsTopLevelContentDocument()) {
+ break;
+ }
+
+ // If we ever have a non-content parent before we hit a toplevel content
+ // parent, then we're never going to find one. Just bail.
+ if (!parent->IsContentDocument()) {
+ return nullptr;
+ }
+
+ parent = parent->GetInProcessParentDocument();
+ } while (parent);
+
+ return parent;
+}
+
+const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
+ return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
+}
+
+void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
+ MOZ_ASSERT(IsBeingUsedAsImage());
+ MOZ_ASSERT(aReferencingDocument);
+
+ if (!aReferencingDocument->mShouldReportUseCounters) {
+ // No need to propagate use counters to a document that itself won't report
+ // use counters.
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("PropagateImageUseCounters from %s to %s",
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
+ nsContentUtils::TruncatedURLForDisplay(
+ aReferencingDocument->mDocumentURI)
+ .get()));
+
+ if (aReferencingDocument->IsBeingUsedAsImage()) {
+ NS_WARNING(
+ "Page use counters from nested image documents may not "
+ "propagate to the top-level document (bug 1657805)");
+ }
+
+ SetCssUseCounterBits();
+ aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
+ aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
+}
+
+bool Document::HasScriptsBlockedBySandbox() const {
+ return mSandboxFlags & SANDBOXED_SCRIPTS;
+}
+
+// Some use-counter sanity-checking.
+static_assert(size_t(eUseCounter_EndCSSProperties) -
+ size_t(eUseCounter_FirstCSSProperty) ==
+ size_t(eCSSProperty_COUNT_with_aliases),
+ "We should have the right amount of CSS property use counters");
+static_assert(size_t(eUseCounter_Count) -
+ size_t(eUseCounter_FirstCountedUnknownProperty) ==
+ size_t(CountedUnknownProperty::Count),
+ "We should have the right amount of counted unknown properties"
+ " use counters");
+static_assert(size_t(eUseCounter_Count) * 2 ==
+ size_t(Telemetry::HistogramUseCounterCount),
+ "There should be two histograms (document and page)"
+ " for each use counter");
+
+#define ASSERT_CSS_COUNTER(id_, method_) \
+ static_assert(size_t(eUseCounter_property_##method_) - \
+ size_t(eUseCounter_FirstCSSProperty) == \
+ size_t(id_), \
+ "Order for CSS counters and CSS property id should match");
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+#define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
+#define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+#undef ASSERT_CSS_COUNTER
+
+void Document::SetCssUseCounterBits() {
+ if (StaticPrefs::layout_css_use_counters_enabled()) {
+ for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
+ auto id = nsCSSPropertyID(i);
+ if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
+ SetUseCounter(nsCSSProps::UseCounterFor(id));
+ }
+ }
+ }
+
+ if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
+ for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
+ auto id = CountedUnknownProperty(i);
+ if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
+ id)) {
+ SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
+ }
+ }
+ }
+}
+
+void Document::InitUseCounters() {
+ // We can be called more than once, e.g. when session history navigation shows
+ // us a second time.
+ if (mUseCountersInitialized) {
+ return;
+ }
+ mUseCountersInitialized = true;
+
+ static_assert(Telemetry::HistogramUseCounterCount > 0);
+
+ if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
+ return;
+ }
+
+ // Now we know for sure that we should report use counters from this document.
+ mShouldReportUseCounters = true;
+
+ WindowContext* top = GetWindowContextForPageUseCounters();
+ if (!top) {
+ // This is the case for SVG image documents. They are not displayed in a
+ // window, but we still do want to record document use counters for them.
+ //
+ // Page use counter propagation is handled in PropagateImageUseCounters,
+ // so there is no need to use the cross-process machinery to send them.
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("InitUseCounters for a non-displayed document [%s]",
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
+ return;
+ }
+
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
+ " [from %s]",
+ wgc->InnerWindowId(), top->Id(),
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
+
+ // Inform the parent process that we will send it page use counters later on.
+ wgc->SendExpectPageUseCounters(top);
+ mShouldSendPageUseCounters = true;
+}
+
+// We keep separate counts for individual documents and top-level
+// pages to more accurately track how many web pages might break if
+// certain features were removed. Consider the case of a single
+// HTML document with several SVG images and/or iframes with
+// sub-documents of their own. If we maintained a single set of use
+// counters and all the sub-documents use a particular feature, then
+// telemetry would indicate that we would be breaking N documents if
+// that feature were removed. Whereas with a document/top-level
+// page split, we can see that N documents would be affected, but
+// only a single web page would be affected.
+//
+// The difference between the values of these two histograms and the
+// related use counters below tell us how many pages did *not* use
+// the feature in question. For instance, if we see that a given
+// session has destroyed 30 content documents, but a particular use
+// counter shows only a count of 5, we can infer that the use
+// counter was *not* used in 25 of those 30 documents.
+//
+// We do things this way, rather than accumulating a boolean flag
+// for each use counter, to avoid sending histograms for features
+// that don't get widely used. Doing things in this fashion means
+// smaller telemetry payloads and faster processing on the server
+// side.
+void Document::ReportDocumentUseCounters() {
+ if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
+ return;
+ }
+
+ mReportedDocumentUseCounters = true;
+
+ // Note that a document is being destroyed. See the comment above for how
+ // use counter histograms are interpreted relative to this measurement.
+ // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
+ // WindowGlobalParent::FinishAccumulatingPageUseCounters.
+ Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
+
+ // Ask all of our resource documents to report their own document use
+ // counters.
+ EnumerateExternalResources([](Document& aDoc) {
+ aDoc.ReportDocumentUseCounters();
+ return CallState::Continue;
+ });
+
+ // Copy StyleUseCounters into our document use counters.
+ SetCssUseCounterBits();
+
+ Maybe<nsCString> urlForLogging;
+ const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
+ if (dumpCounters) {
+ urlForLogging.emplace(
+ nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
+ }
+
+ // Report our per-document use counters.
+ for (int32_t c = 0; c < eUseCounter_Count; ++c) {
+ auto uc = static_cast<UseCounter>(c);
+ if (!mUseCounters[uc]) {
+ continue;
+ }
+
+ auto id = static_cast<Telemetry::HistogramID>(
+ Telemetry::HistogramFirstUseCounter + uc * 2);
+ if (dumpCounters) {
+ printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
+ Telemetry::GetHistogramName(id), urlForLogging->get());
+ }
+ Telemetry::Accumulate(id, 1);
+ }
+}
+
+void Document::SendPageUseCounters() {
+ if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
+ return;
+ }
+
+ // Ask all of our resource documents to send their own document use
+ // counters to the parent process to be counted as page use counters.
+ EnumerateExternalResources([](Document& aDoc) {
+ aDoc.SendPageUseCounters();
+ return CallState::Continue;
+ });
+
+ // Send our use counters to the parent process to accumulate them towards the
+ // page use counters for the top-level document.
+ //
+ // We take our own document use counters (those in mUseCounters) and any child
+ // document use counters (those in mChildDocumentUseCounters) that have been
+ // explicitly propagated up to us, which includes resource documents, static
+ // clones, and SVG images.
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ MOZ_ASSERT_UNREACHABLE(
+ "SendPageUseCounters should be called while we still have access "
+ "to our WindowContext");
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ (" > too late to send page use counters"));
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
+ wgc->WindowContext()->Id(),
+ nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
+
+ // Copy StyleUseCounters into our document use counters.
+ SetCssUseCounterBits();
+
+ UseCounters counters = mUseCounters | mChildDocumentUseCounters;
+ wgc->SendAccumulatePageUseCounters(counters);
+}
+
+bool Document::RecomputeResistFingerprinting() {
+ const bool previous = mShouldResistFingerprinting;
+
+ RefPtr<BrowsingContext> opener =
+ GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
+ // If we have a parent or opener document, defer to it only when we have a
+ // null principal (e.g. a sandboxed iframe or a data: uri) or when the
+ // document's principal matches. This means we will defer about:blank,
+ // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
+ // but not cross-origin ones. Cross-origin iframes/popups may inherit a
+ // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
+ auto shouldInheritFrom = [this](Document* aDoc) {
+ return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
+ this->NodePrincipal()->GetIsNullPrincipal());
+ };
+
+ if (shouldInheritFrom(mParentDocument)) {
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s and deferring "
+ "to parent document %s",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
+ mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ } else if (opener && shouldInheritFrom(opener->GetDocument())) {
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
+ "opener document %s",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
+ mShouldResistFingerprinting =
+ opener->GetDocument()->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ } else {
+ bool chromeDoc = nsContentUtils::IsChromeDoc(this);
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s ChromeDoc:%x",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ chromeDoc));
+ mShouldResistFingerprinting =
+ !chromeDoc && nsContentUtils::ShouldResistFingerprinting(
+ mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
+ }
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Finished RecomputeResistFingerprinting with result %x",
+ mShouldResistFingerprinting));
+
+ return previous != mShouldResistFingerprinting;
+}
+
+bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
+ return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
+}
+
+WindowContext* Document::GetWindowContextForPageUseCounters() const {
+ if (mDisplayDocument) {
+ // If we are a resource document, then go through it to find the
+ // top-level document.
+ return mDisplayDocument->GetWindowContextForPageUseCounters();
+ }
+
+ if (mOriginalDocument) {
+ // For static clones (print preview documents), contribute page use counters
+ // towards the original document.
+ return mOriginalDocument->GetWindowContextForPageUseCounters();
+ }
+
+ WindowContext* wc = GetTopLevelWindowContext();
+ if (!wc || !wc->GetBrowsingContext()->IsContent()) {
+ return nullptr;
+ }
+
+ return wc;
+}
+
+void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
+ if (mIntersectionObservers.IsEmpty()) {
+ return;
+ }
+
+ DOMHighResTimeStamp time = 0;
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ if (Performance* perf = win->GetPerformance()) {
+ time = perf->TimeStampToDOMHighResForRendering(aNowTime);
+ }
+ }
+
+ const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
+ mIntersectionObservers);
+ for (const auto& observer : observers) {
+ if (observer) {
+ observer->Update(*this, time);
+ }
+ }
+}
+
+void Document::ScheduleIntersectionObserverNotification() {
+ if (mIntersectionObservers.IsEmpty()) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> notification =
+ NewRunnableMethod("Document::NotifyIntersectionObservers", this,
+ &Document::NotifyIntersectionObservers);
+ Dispatch(TaskCategory::Other, notification.forget());
+}
+
+void Document::NotifyIntersectionObservers() {
+ const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
+ mIntersectionObservers);
+ for (const auto& observer : observers) {
+ if (observer) {
+ // MOZ_KnownLive because the 'observers' array guarantees to keep it
+ // alive.
+ MOZ_KnownLive(observer)->Notify();
+ }
+ }
+}
+
+DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
+ if (!mLazyLoadImageObserver) {
+ mLazyLoadImageObserver =
+ DOMIntersectionObserver::CreateLazyLoadObserver(*this);
+ }
+ return *mLazyLoadImageObserver;
+}
+
+DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() {
+ if (!mContentVisibilityObserver) {
+ mContentVisibilityObserver =
+ DOMIntersectionObserver::CreateContentVisibilityObserver(*this);
+ }
+ return *mContentVisibilityObserver;
+}
+
+void Document::ObserveForContentVisibility(Element& aElement) {
+ EnsureContentVisibilityObserver().Observe(aElement);
+}
+
+void Document::UnobserveForContentVisibility(Element& aElement) {
+ if (mContentVisibilityObserver) {
+ mContentVisibilityObserver->Unobserve(aElement);
+ }
+}
+
+ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
+ if (!mLastRememberedSizeObserver) {
+ mLastRememberedSizeObserver =
+ ResizeObserver::CreateLastRememberedSizeObserver(*this);
+ }
+ return *mLastRememberedSizeObserver;
+}
+
+void Document::ObserveForLastRememberedSize(Element& aElement) {
+ if (NS_WARN_IF(!IsActive())) {
+ return;
+ }
+ // Options are initialized with ResizeObserverBoxOptions::Content_box by
+ // default, which is what we want.
+ static ResizeObserverOptions options;
+ EnsureLastRememberedSizeObserver().Observe(aElement, options);
+}
+
+void Document::UnobserveForLastRememberedSize(Element& aElement) {
+ if (mLastRememberedSizeObserver) {
+ mLastRememberedSizeObserver->Unobserve(aElement);
+ }
+}
+
+void Document::NotifyLayerManagerRecreated() {
+ NotifyActivityChanged();
+ EnumerateSubDocuments([](Document& aSubDoc) {
+ aSubDoc.NotifyLayerManagerRecreated();
+ return CallState::Continue;
+ });
+}
+
+XPathEvaluator* Document::XPathEvaluator() {
+ if (!mXPathEvaluator) {
+ mXPathEvaluator.reset(new dom::XPathEvaluator(this));
+ }
+ return mXPathEvaluator.get();
+}
+
+already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
+ return mCachedEncoder.forget();
+}
+
+void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
+ mCachedEncoder = aEncoder;
+}
+
+nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
+
+nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
+
+void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
+ mStateObjectContainer = scContainer;
+ mCachedStateObject = JS::UndefinedValue();
+ mCachedStateObjectValid = false;
+}
+
+bool Document::ComputeDocumentLWTheme() const {
+ if (!NodePrincipal()->IsSystemPrincipal()) {
+ return false;
+ }
+
+ Element* element = GetRootElement();
+ return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
+ ELEMENT_NODE);
+ MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
+
+ nsCOMPtr<Element> element;
+ DebugOnly<nsresult> rv =
+ NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
+ mozilla::dom::NOT_FROM_PARSER);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
+ return element.forget();
+}
+
+void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
+ BrowsingContext* aContext) {
+ aContext->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
+ if (RefPtr<Document> doc = win->GetExtantDoc()) {
+ SuppressDocument(doc);
+ mDocuments.AppendElement(doc);
+ }
+ }
+ });
+}
+
+void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
+ BrowsingContextGroup* aGroup) {
+ for (const auto& bc : aGroup->Toplevels()) {
+ SuppressBrowsingContext(bc);
+ }
+}
+
+nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
+ SyncOperationBehavior aSyncBehavior)
+ : mSyncBehavior(aSyncBehavior) {
+ mMicroTaskLevel = 0;
+ if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
+ mMicroTaskLevel = ccjs->MicroTaskLevel();
+ ccjs->SetMicroTaskLevel(0);
+ }
+ if (aDoc) {
+ mBrowsingContext = aDoc->GetBrowsingContext();
+ if (InputTaskManager::CanSuspendInputEvent()) {
+ if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
+ SuppressBrowsingContextGroup(bcg);
+ }
+ } else if (mBrowsingContext) {
+ SuppressBrowsingContext(mBrowsingContext->Top());
+ }
+ if (mBrowsingContext &&
+ mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
+ InputTaskManager::CanSuspendInputEvent()) {
+ mBrowsingContext->Group()->IncInputEventSuspensionLevel();
+ }
+ }
+}
+
+void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->TimeoutManager().BeginSyncOperation();
+ }
+ aDoc->SetIsInSyncOperation(true);
+}
+
+void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->TimeoutManager().EndSyncOperation();
+ }
+ aDoc->SetIsInSyncOperation(false);
+}
+
+nsAutoSyncOperation::~nsAutoSyncOperation() {
+ UnsuppressDocuments();
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->SetMicroTaskLevel(mMicroTaskLevel);
+ }
+ if (mBrowsingContext &&
+ mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
+ InputTaskManager::CanSuspendInputEvent()) {
+ mBrowsingContext->Group()->DecInputEventSuspensionLevel();
+ }
+}
+
+void Document::SetIsInSyncOperation(bool aSync) {
+ if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
+ ccjs->UpdateMicroTaskSuppressionGeneration();
+ }
+
+ if (aSync) {
+ ++mInSyncOperationCount;
+ } else {
+ --mInSyncOperationCount;
+ }
+}
+
+gfxUserFontSet* Document::GetUserFontSet() {
+ if (!mFontFaceSet) {
+ return nullptr;
+ }
+
+ return mFontFaceSet->GetImpl();
+}
+
+void Document::FlushUserFontSet() {
+ if (!mFontFaceSetDirty) {
+ return;
+ }
+
+ mFontFaceSetDirty = false;
+
+ if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ nsTArray<nsFontFaceRuleContainer> rules;
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ MOZ_ASSERT(mStyleSetFilled);
+ mStyleSet->AppendFontFaceRules(rules);
+ }
+
+ if (!mFontFaceSet && !rules.IsEmpty()) {
+ mFontFaceSet = FontFaceSet::CreateForDocument(this);
+ }
+
+ bool changed = false;
+ if (mFontFaceSet) {
+ changed = mFontFaceSet->UpdateRules(rules);
+ }
+
+ // We need to enqueue a style change reflow (for later) to
+ // reflect that we're modifying @font-face rules. (However,
+ // without a reflow, nothing will happen to start any downloads
+ // that are needed.)
+ if (changed && presShell) {
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ presContext->UserFontSetUpdated();
+ }
+ }
+ }
+}
+
+void Document::MarkUserFontSetDirty() {
+ if (mFontFaceSetDirty) {
+ return;
+ }
+ mFontFaceSetDirty = true;
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+}
+
+FontFaceSet* Document::Fonts() {
+ if (!mFontFaceSet) {
+ mFontFaceSet = FontFaceSet::CreateForDocument(this);
+ FlushUserFontSet();
+ }
+ return mFontFaceSet;
+}
+
+void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
+ MOZ_ASSERT(!aTimeStamp.IsNull());
+
+ if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
+ mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
+ return;
+ }
+
+ if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
+ // Report to console just once.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
+ nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
+ }
+
+ mLastScrollLinkedEffectDetectionTime = aTimeStamp;
+}
+
+bool Document::HasScrollLinkedEffect() const {
+ if (nsPresContext* pc = GetPresContext()) {
+ return mLastScrollLinkedEffectDetectionTime ==
+ pc->RefreshDriver()->MostRecentRefresh();
+ }
+
+ return false;
+}
+
+void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
+ if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
+ // Setting has user interction on a discarded browsing context has
+ // no effect.
+ Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
+ }
+}
+
+bool Document::GetSHEntryHasUserInteraction() {
+ if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
+ return topWc->GetSHEntryHasUserInteraction();
+ }
+ return false;
+}
+
+void Document::SetUserHasInteracted() {
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
+ ("Document %p has been interacted by user.", this));
+
+ // We maybe need to update the user-interaction permission.
+ MaybeStoreUserInteractionAsPermission();
+
+ // For purposes of reducing irrelevant session history entries on
+ // the back button, we annotate entries with whether they had user
+ // interaction. This is gated on its own flag on the WindowContext
+ // (instead of mUserHasInteracted) to account for the fact that multiple
+ // top-level SH entries can be associated with the same document.
+ // Thus, whenever we create a new SH entry for this document,
+ // this flag is reset.
+ if (!GetSHEntryHasUserInteraction()) {
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsISHEntry> currentEntry;
+ bool oshe;
+ nsresult rv =
+ docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
+ if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
+ currentEntry->SetHasUserInteraction(true);
+ }
+ }
+ SetSHEntryHasUserInteraction(true);
+ }
+
+ if (mUserHasInteracted) {
+ return;
+ }
+
+ mUserHasInteracted = true;
+
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ loadInfo->SetDocumentHasUserInteracted(true);
+ }
+ // Tell the parent process about user interaction
+ if (auto* wgc = GetWindowGlobalChild()) {
+ wgc->SendUpdateDocumentHasUserInteracted(true);
+ }
+
+ MaybeAllowStorageForOpenerAfterUserInteraction();
+}
+
+BrowsingContext* Document::GetBrowsingContext() const {
+ nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
+ return docshell ? docshell->GetBrowsingContext() : nullptr;
+}
+
+void Document::NotifyUserGestureActivation() {
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ WindowContext* windowContext = aBC->GetCurrentWindowContext();
+ if (!windowContext) {
+ return;
+ }
+
+ nsIDocShell* docShell = aBC->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ Document* document = docShell->GetDocument();
+ if (!document) {
+ return;
+ }
+
+ // XXXedgar we probably could just check `IsInProcess()` after fission
+ // enable.
+ if (NodePrincipal()->Equals(document->NodePrincipal())) {
+ windowContext->NotifyUserGestureActivation();
+ }
+ });
+
+ for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
+ if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
+ windowContext->NotifyUserGestureActivation();
+ }
+ }
+ }
+}
+
+bool Document::HasBeenUserGestureActivated() {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->HasBeenUserGestureActivated();
+}
+
+DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
+ if (RefPtr<WindowContext> wc = GetWindowContext()) {
+ if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
+ if (Performance* perf = innerWindow->GetPerformance()) {
+ return perf->GetDOMTiming()->TimeStampToDOMHighRes(
+ wc->GetUserGestureStart());
+ }
+ }
+ }
+
+ NS_WARNING(
+ "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
+ return 0;
+}
+
+void Document::ClearUserGestureActivation() {
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ bc = bc->Top();
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
+ windowContext->NotifyResetUserGestureActivation();
+ }
+ });
+ }
+}
+
+bool Document::HasValidTransientUserGestureActivation() const {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->HasValidTransientUserGestureActivation();
+}
+
+bool Document::ConsumeTransientUserGestureActivation() {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->ConsumeTransientUserGestureActivation();
+}
+
+void Document::SetDocTreeHadMedia() {
+ RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
+ if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
+ }
+}
+
+void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
+ if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
+ return;
+ }
+
+ // This will probably change for project fission, but currently this document
+ // and the opener are on the same process. In the future, we should make this
+ // part async.
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ // We care about first-party tracking resources only.
+ if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
+ cookieBehavior));
+ }
+
+ auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (NS_WARN_IF(!outer)) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
+ if (!openerBC) {
+ // No opener.
+ return;
+ }
+
+ // We want to ensure the following check works for both fission mode and
+ // non-fission mode:
+ // "If the opener is not a 3rd party and if this window is not a 3rd party
+ // with respect to the opener, we should not continue."
+ //
+ // In non-fission mode, the opener and the opened window are in the same
+ // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
+ // In fission mode, if this window is not a 3rd party with respect to the
+ // opener, they must be in the same process, so we can still use
+ // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
+ // party.
+ if (openerBC->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
+ if (NS_WARN_IF(!outerOpener)) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> openerInner =
+ outerOpener->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!openerInner)) {
+ return;
+ }
+
+ RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
+ if (NS_WARN_IF(!openerDocument)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
+ if (NS_WARN_IF(!openerURI)) {
+ return;
+ }
+
+ // If the opener is not a 3rd party and if this window is not
+ // a 3rd party with respect to the opener, we should not continue.
+ if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
+ !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
+ return;
+ }
+ }
+
+ // We don't care when the asynchronous work finishes here.
+ Unused << StorageAccessAPIHelper::AllowAccessFor(
+ NodePrincipal(), openerBC,
+ ContentBlockingNotifier::eOpenerAfterUserInteraction);
+}
+
+namespace {
+
+// Documents can stay alive for days. We don't want to update the permission
+// value at any user-interaction, and, using a timer triggered any X seconds
+// should be good enough. 'X' is taken from
+// privacy.userInteraction.document.interval pref.
+// We also want to store the user-interaction before shutting down, and, for
+// this reason, this class implements nsIAsyncShutdownBlocker interface.
+class UserInteractionTimer final : public Runnable,
+ public nsITimerCallback,
+ public nsIAsyncShutdownBlocker {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit UserInteractionTimer(Document* aDocument)
+ : Runnable("UserInteractionTimer"),
+ mPrincipal(aDocument->NodePrincipal()),
+ mDocument(do_GetWeakReference(aDocument)) {
+ static int32_t userInteractionTimerId = 0;
+ // Blocker names must be unique. Let's create it now because when needed,
+ // the document could be already gone.
+ mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
+ ++userInteractionTimerId, aDocument);
+ }
+
+ // Runnable interface
+
+ NS_IMETHOD
+ Run() override {
+ uint32_t interval =
+ StaticPrefs::privacy_userInteraction_document_interval();
+ if (!interval) {
+ return NS_OK;
+ }
+
+ RefPtr<UserInteractionTimer> self = this;
+ auto raii =
+ MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
+
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+ NS_ENSURE_TRUE(!!phase, NS_OK);
+
+ rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
+ __LINE__, u"UserInteractionTimer shutdown"_ns);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ raii.release();
+ return NS_OK;
+ }
+
+ // nsITimerCallback interface
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override {
+ StoreUserInteraction();
+ return NS_OK;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ using nsINamed::GetName;
+#endif
+
+ // nsIAsyncShutdownBlocker interface
+
+ NS_IMETHOD
+ GetName(nsAString& aName) override {
+ aName = mBlockerName;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ BlockShutdown(nsIAsyncShutdownClient* aClient) override {
+ CancelTimerAndStoreUserInteraction();
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetState(nsIPropertyBag**) override { return NS_OK; }
+
+ private:
+ ~UserInteractionTimer() = default;
+
+ void StoreUserInteraction() {
+ // Remove the shutting down blocker
+ nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+ if (phase) {
+ phase->RemoveBlocker(this);
+ }
+
+ // If the document is not gone, let's reset its timer flag.
+ nsCOMPtr<Document> document = do_QueryReferent(mDocument);
+ if (document) {
+ ContentBlockingUserInteraction::Observe(mPrincipal);
+ document->ResetUserInteractionTimer();
+ }
+ }
+
+ void CancelTimerAndStoreUserInteraction() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ StoreUserInteraction();
+ }
+
+ static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
+ NS_ENSURE_TRUE(!!svc, nullptr);
+
+ nsCOMPtr<nsIAsyncShutdownClient> phase;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return phase.forget();
+ }
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsWeakPtr mDocument;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ nsString mBlockerName;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
+ nsIAsyncShutdownBlocker)
+
+} // namespace
+
+void Document::MaybeStoreUserInteractionAsPermission() {
+ // We care about user-interaction stored only for top-level documents
+ // and documents with access to the Storage Access API
+ if (!IsTopLevelContentDocument()) {
+ bool hasSA;
+ nsresult rv = HasStorageAccessSync(hasSA);
+ if (NS_FAILED(rv) || !hasSA) {
+ return;
+ }
+ }
+
+ if (!mUserHasInteracted) {
+ // First interaction, let's store this info now.
+ ContentBlockingUserInteraction::Observe(NodePrincipal());
+ return;
+ }
+
+ if (mHasUserInteractionTimerScheduled) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
+ nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
+ EventQueuePriority::Idle);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // This value will be reset by the timer.
+ mHasUserInteractionTimerScheduled = true;
+}
+
+void Document::ResetUserInteractionTimer() {
+ mHasUserInteractionTimerScheduled = false;
+}
+
+bool Document::IsExtensionPage() const {
+ return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
+}
+
+void Document::AddResizeObserver(ResizeObserver& aObserver) {
+ if (!mResizeObserverController) {
+ mResizeObserverController = MakeUnique<ResizeObserverController>(this);
+ }
+ mResizeObserverController->AddResizeObserver(aObserver);
+}
+
+void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
+ MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
+ if (MOZ_UNLIKELY(!mResizeObserverController)) {
+ return;
+ }
+ mResizeObserverController->RemoveResizeObserver(aObserver);
+}
+
+PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
+ if (!mPermissionDelegateHandler) {
+ mPermissionDelegateHandler =
+ mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
+ }
+
+ if (!mPermissionDelegateHandler->Initialize()) {
+ mPermissionDelegateHandler = nullptr;
+ }
+
+ return mPermissionDelegateHandler;
+}
+
+void Document::ScheduleResizeObserversNotification() const {
+ if (!mResizeObserverController) {
+ return;
+ }
+
+ mResizeObserverController->ScheduleNotification();
+}
+
+void Document::ClearStaleServoData() {
+ DocumentStyleRootIterator iter(this);
+ while (Element* root = iter.GetNextStyleRoot()) {
+ RestyleManager::ClearServoDataFromSubtree(root);
+ }
+}
+
+Selection* Document::GetSelection(ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ if (!window->IsCurrentInnerWindow()) {
+ return nullptr;
+ }
+
+ return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
+}
+
+void Document::MakeBrowsingContextNonSynthetic() {
+ if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ if (bc->GetSyntheticDocumentContainer()) {
+ Unused << bc->SetSyntheticDocumentContainer(false);
+ }
+ }
+ }
+}
+
+nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
+ // Step 1: check if cookie permissions are available or denied to this
+ // document's principal
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 2: Check if the browser settings determine whether or not this
+ // document has access to its unpartitioned cookies.
+ bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ bool isOnThirdPartySkipList = false;
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+ }
+ bool isThirdPartyTracker =
+ nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ isOnThirdPartySkipList, isThirdPartyTracker);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 3: Check if the location of this call (embedded, top level, same-site)
+ // determines if cookies are permitted or not.
+ Maybe<bool> resultBecauseCallContext =
+ StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
+ false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 4: Check if the permissions for this document determine if if has
+ // access or is denied cookies.
+ Maybe<bool> resultBecausePreviousPermission =
+ StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
+ this, false);
+ if (resultBecausePreviousPermission.isSome()) {
+ if (resultBecausePreviousPermission.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+ // If you get here, we default to not giving you permission.
+ aHasStorageAccess = false;
+ return NS_OK;
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ bool hasStorageAccess;
+ nsresult rv = HasStorageAccessSync(hasStorageAccess);
+ if (NS_FAILED(rv)) {
+ promise->MaybeRejectWithUndefined();
+ } else {
+ promise->MaybeResolve(hasStorageAccess);
+ }
+
+ return promise.forget();
+}
+
+RefPtr<Document::GetContentBlockingEventsPromise>
+Document::GetContentBlockingEvents() {
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ return nullptr;
+ }
+
+ return wgc->SendGetContentBlockingEvents()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](const WindowGlobalChild::GetContentBlockingEventsPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return Document::GetContentBlockingEventsPromise::CreateAndResolve(
+ aValue.ResolveValue(), __func__);
+ }
+
+ return Document::GetContentBlockingEventsPromise::CreateAndReject(
+ false, __func__);
+ });
+}
+
+StorageAccessAPIHelper::PerformPermissionGrant
+Document::CreatePermissionGrantPromise(
+ nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
+ bool aHasUserInteraction, const Maybe<nsCString>& aTopLevelBaseDomain) {
+ MOZ_ASSERT(aInnerWindow);
+ MOZ_ASSERT(aPrincipal);
+ RefPtr<Document> self(this);
+ RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
+ RefPtr<nsIPrincipal> principal(aPrincipal);
+
+ return [inner, self, principal, aHasUserInteraction, aTopLevelBaseDomain]() {
+ // Create the user prompt
+ RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
+ p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
+ Private(__func__);
+ RefPtr<StorageAccessPermissionRequest> sapr =
+ StorageAccessPermissionRequest::Create(
+ inner, principal, aTopLevelBaseDomain,
+ // Allow
+ [p] {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
+ p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
+ },
+ // Block
+ [p] {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
+ p->Reject(false, __func__);
+ });
+
+ using PromptResult = ContentPermissionRequestBase::PromptResult;
+ PromptResult pr = sapr->CheckPromptPrefs();
+
+ if (pr == PromptResult::Pending) {
+ // We're about to show a prompt, record the request attempt
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
+ }
+
+ // Try to auto-grant the storage access so the user doesn't see the prompt.
+ self->AutomaticStorageAccessPermissionCanBeGranted(aHasUserInteraction)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the autogrant check didn't fail, call this function
+ [p, pr, sapr, inner](
+ const Document::AutomaticStorageAccessPermissionGrantPromise::
+ ResolveOrRejectValue& aValue) -> void {
+ // Make a copy because we can't modified copy-captured lambda
+ // variables.
+ PromptResult pr2 = pr;
+
+ // If the user didn't already click "allow" and we can autogrant,
+ // do that!
+ bool storageAccessCanBeGrantedAutomatically =
+ aValue.IsResolve() && aValue.ResolveValue();
+ bool autoGrant = false;
+ if (pr2 == PromptResult::Pending &&
+ storageAccessCanBeGrantedAutomatically) {
+ pr2 = PromptResult::Granted;
+ autoGrant = true;
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::
+ AllowAutomatically);
+ }
+
+ // If we can complete the permission request, do so.
+ if (pr2 != PromptResult::Pending) {
+ MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
+ pr2 == PromptResult::Denied);
+ if (pr2 == PromptResult::Granted) {
+ StorageAccessAPIHelper::StorageAccessPromptChoices choice =
+ StorageAccessAPIHelper::eAllow;
+ if (autoGrant) {
+ choice = StorageAccessAPIHelper::eAllowAutoGrant;
+ }
+ if (!autoGrant) {
+ p->Resolve(choice, __func__);
+ } else {
+ sapr->MaybeDelayAutomaticGrants()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [p, choice] { p->Resolve(choice, __func__); },
+ [p] { p->Reject(false, __func__); });
+ }
+ return;
+ }
+ p->Reject(false, __func__);
+ return;
+ }
+
+ // If we get here, the auto-decision failed and we need to
+ // wait for the user prompt to complete.
+ sapr->RequestDelayedTask(
+ inner->EventTargetFor(TaskCategory::Other),
+ ContentPermissionRequestBase::DelayedTaskType::Request);
+ });
+
+ return p;
+ };
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 0: Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (!HasValidTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ nsLiteralCString("requestStorageAccess"),
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "RequestStorageAccessUserGesture");
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Get a pointer to the inner window- We need this for convenience sake
+ RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 1: Check if the principal calling this has a permission that lets
+ // them use cookies or forbids them from using cookies.
+ // This is outside of the spec of the StorageAccess API, but makes the return
+ // values to have proper semantics.
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 2: Check if the browser settings always allow or deny cookies.
+ // We should always return a resolved promise if the cookieBehavior is ACCEPT.
+ // This is outside of the spec of the StorageAccess API, but makes the return
+ // values to have proper semantics.
+ bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ bool isOnThirdPartySkipList = false;
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+ }
+ bool isThirdPartyTracker =
+ nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ isOnThirdPartySkipList, isThirdPartyTracker);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 3: Check if the Document calling requestStorageAccess has anything to
+ // gain from storage access. It should be embedded, non-null, etc.
+ Maybe<bool> resultBecauseCallContext =
+ StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
+ true);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 4: Check if we already allowed or denied storage access for this
+ // document's storage key.
+ Maybe<bool> resultBecausePreviousPermission =
+ StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
+ this, true);
+ if (resultBecausePreviousPermission.isSome()) {
+ if (resultBecausePreviousPermission.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Get pointers to some objects that will be used in the async portion
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ RefPtr<nsGlobalWindowOuter> outer =
+ nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (!outer) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ RefPtr<Document> self(this);
+
+ // Consume user activation before entering the async part of this method.
+ // This prevents usage of other transient activation-gated APIs.
+ ConsumeTransientUserGestureActivation();
+
+ // Step 5. Start an async call to request storage access. This will either
+ // perform an automatic decision or notify the user, then perform some follow
+ // on work changing state to reflect the result of the API. If it resolves,
+ // the request was granted. If it rejects it was denied.
+ StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ this, inner, bc, NodePrincipal(), true,
+ ContentBlockingNotifier::eStorageAccessAPI, true)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ self->NotifyUserGestureActivation();
+ promise->MaybeResolveWithUndefined();
+ },
+ [promise] {
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ });
+
+ return promise.forget();
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
+ const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 0: Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ nsLiteralCString("requestStorageAccess"),
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "RequestStorageAccessUserGesture");
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 1: Check if the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> thirdPartyURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ bool isThirdPartyDocument;
+ rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ bool isOnRejectForeignAllowList =
+ RejectForeignAllowList::Check(thirdPartyURI);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 2: Check that this Document is same-site to the top, and check that
+ // we have user activation if we require it.
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(
+ this, aRequireUserActivation);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 3: Get some useful variables that can be captured by the lambda for
+ // the asynchronous portion
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ RefPtr<nsGlobalWindowOuter> outer =
+ nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (!outer) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ thirdPartyURI, NodePrincipal()->OriginAttributesRef());
+ if (!principal) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ RefPtr<Document> self(this);
+ bool hasUserActivation = HasValidTransientUserGestureActivation();
+
+ // Consume user activation before entering the async part of this method.
+ // This prevents usage of other transient activation-gated APIs.
+ ConsumeTransientUserGestureActivation();
+
+ // Step 4a: Start the async part of this function. Check the cookie
+ // permission, but this can't be done in this process. We needs the cookie
+ // permission of the URL as if it were embedded on this page, so we need to
+ // make this check in the ContentParent.
+ StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
+ GetBrowsingContext(), principal)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, thirdPartyURI, bc, principal, hasUserActivation, self,
+ promise](Maybe<bool> cookieResult) {
+ // Handle the result of the cookie permission check that took place
+ // in the ContentParent.
+ if (cookieResult.isSome()) {
+ if (cookieResult.value()) {
+ return MozPromise<int, bool, true>::CreateAndResolve(true,
+ __func__);
+ }
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ }
+
+ // Step 4b: Check for the existing storage access permission
+ nsAutoCString type;
+ bool ok =
+ AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
+ if (!ok) {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ }
+ if (AntiTrackingUtils::CheckStoragePermission(
+ self->NodePrincipal(), type,
+ nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
+ return MozPromise<int, bool, true>::CreateAndResolve(true,
+ __func__);
+ }
+
+ // Step 4c: Try to request storage access, either automatically or
+ // with a user-prompt. This is the part that is async in the
+ // typical requestStorageAccess function.
+ return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ self, inner, bc, principal, hasUserActivation,
+ ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
+ true);
+ },
+ // If the IPC rejects, we should reject our promise here which will
+ // cause a rejection of the promise we already returned
+ [promise]() {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the previous handlers resolved, we should reinstate user
+ // activation and resolve the promise we returned in Step 5.
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ self->NotifyUserGestureActivation();
+ promise->MaybeResolveWithUndefined();
+ },
+ // If the previous handler rejected, we should reject the promise
+ // returned by this function.
+ [promise] {
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ });
+
+ // Step 5: While the async stuff is happening, we should return the promise so
+ // our caller can continue executing.
+ return promise.forget();
+}
+
+already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
+ const nsAString& aSerializedSite, ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (!ConsumeTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> siteURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ bool isCrossSiteArgument;
+ rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ if (!isCrossSiteArgument) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if this party has broad cookie permissions.
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if browser settings preclude this document getting storage
+ // access under the provided site
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check that this Document is same-site to the top
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
+
+ // Test if the permission this is requesting is already set
+ nsCOMPtr<nsIPrincipal> argumentPrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ siteURI, NodePrincipal()->OriginAttributesRef());
+ if (!argumentPrincipal) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ nsCString originNoSuffix;
+ rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ RefPtr<Document> self(this);
+ cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, siteURI,
+ self](const ContentChild::TestStorageAccessPermissionPromise::
+ ResolveValueType& aResult) {
+ if (aResult) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllow, __func__);
+ }
+ // Get a grant for the storage access permission that will be set
+ // when this is completed in the embedding context
+ nsCString serializedSite;
+ RefPtr<nsEffectiveTLDService> etld =
+ nsEffectiveTLDService::GetInstance();
+ if (!etld) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ nsresult rv = etld->GetSite(siteURI, serializedSite);
+ if (NS_FAILED(rv)) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ return self->CreatePermissionGrantPromise(
+ self->GetInnerWindow(), self->NodePrincipal(), true,
+ Some(serializedSite))();
+ },
+ [](const ContentChild::TestStorageAccessPermissionPromise::
+ RejectValueType& aResult) {
+ return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
+ CreateAndReject(false, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, principal, siteURI](int result) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (!cc) {
+ // TODO(bug 1778561): Make this work in non-content processes.
+ promise->MaybeRejectWithUndefined();
+ return;
+ }
+ // Set a permission in the parent process that this document wants
+ // storage access under the argument's site, resolving our returned
+ // promise on success
+ cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](bool success) {
+ if (success) {
+ promise->MaybeResolveWithUndefined();
+ } else {
+ promise->MaybeRejectWithUndefined();
+ }
+ },
+ [promise](mozilla::ipc::ResponseRejectReason reason) {
+ promise->MaybeRejectWithUndefined();
+ });
+ },
+ [promise](bool result) { promise->MaybeRejectWithUndefined(); });
+
+ // Return the promise that is resolved in the async handler above
+ return promise.forget();
+}
+
+already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
+ const nsAString& aSerializedOrigin, ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Check that the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> argumentURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ bool isCrossSiteArgument;
+ rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ if (!isCrossSiteArgument) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if browser settings preclude this document getting storage
+ // access under the provided site
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(argumentURI);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check that this Document is same-site to the top
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Create principal of the embedded site requesting storage access
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ argumentURI, NodePrincipal()->OriginAttributesRef());
+ if (!principal) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Get versions of these objects that we can use in lambdas for callbacks
+ RefPtr<Document> self(this);
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+
+ // Test that the permission was set by a call to RequestStorageAccessUnderSite
+ // from a top level document that is same-site with the argument
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (!cc) {
+ // TODO(bug 1778561): Make this work in non-content processes.
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, bc, self, principal](bool success) {
+ if (success) {
+ // If that resolved with true, check that we don't already have a
+ // permission that gives cookie access.
+ return StorageAccessAPIHelper::
+ AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
+ principal);
+ }
+ return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ },
+ [](mozilla::ipc::ResponseRejectReason reason) {
+ return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
+ // Handle the result of the cookie permission check that took place
+ // in the ContentParent.
+ if (cookieResult.isSome()) {
+ if (cookieResult.value()) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllowAutoGrant, __func__);
+ }
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+
+ // Check for the existing storage access permission
+ nsAutoCString type;
+ bool ok =
+ AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
+ if (!ok) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ if (AntiTrackingUtils::CheckStoragePermission(
+ self->NodePrincipal(), type,
+ nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllowAutoGrant, __func__);
+ }
+
+ // Try to request storage access, ignoring the final checks.
+ // We ignore the final checks because this is where the "grant"
+ // either by prompt doorhanger or autogrant takes place. We already
+ // gathered an equivalent grant in requestStorageAccessUnderSite.
+ return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ self, inner, bc, principal, true,
+ ContentBlockingNotifier::eStorageAccessAPI, false);
+ },
+ // If the IPC rejects, we should reject our promise here which will
+ // cause a rejection of the promise we already returned
+ [promise]() {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the previous handlers resolved, we should reinstate user
+ // activation and resolve the promise we returned in Step 5.
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ promise->MaybeResolveWithUndefined();
+ },
+ // If the previous handler rejected, we should reject the promise
+ // returned by this function.
+ [promise] { promise->MaybeRejectWithUndefined(); });
+
+ return promise.forget();
+}
+
+RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
+Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
+ // requestStorageAccessForOrigin may not require user activation. If we don't
+ // have user activation at this point we should always show the prompt.
+ if (!hasUserActivation ||
+ !StaticPrefs::privacy_antitracking_enableWebcompat()) {
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
+ false, __func__);
+ }
+ if (XRE_IsContentProcess()) {
+ // In the content process, we need to ask the parent process to compute
+ // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
+ // isn't accessible in the content process.
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+
+ return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ContentChild::
+ AutomaticStorageAccessPermissionCanBeGrantedPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return AutomaticStorageAccessPermissionGrantPromise::
+ CreateAndResolve(aValue.ResolveValue(), __func__);
+ }
+
+ return AutomaticStorageAccessPermissionGrantPromise::
+ CreateAndReject(false, __func__);
+ });
+ }
+
+ if (XRE_IsParentProcess()) {
+ // In the parent process, we can directly compute this.
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
+ AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
+ __func__);
+ }
+
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+}
+
+bool Document::AutomaticStorageAccessPermissionCanBeGranted(
+ nsIPrincipal* aPrincipal) {
+ if (!StaticPrefs::dom_storage_access_auto_grants()) {
+ return false;
+ }
+
+ if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
+ "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
+ if (NS_WARN_IF(!bu)) {
+ return false;
+ }
+
+ uint32_t uniqueDomainsVisitedInPast24Hours = 0;
+ nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
+ &uniqueDomainsVisitedInPast24Hours);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
+ AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
+ if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
+ return false;
+ }
+ size_t originsThirdPartyHasAccessTo =
+ maybeOriginsThirdPartyHasAccessTo.value();
+
+ // one percent of the number of top-levels origins visited in the current
+ // session (but not to exceed 24 hours), or the value of the
+ // dom.storage_access.max_concurrent_auto_grants preference, whichever is
+ // higher.
+ size_t maxConcurrentAutomaticGrants = std::max(
+ std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
+ StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
+ 0);
+
+ return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
+}
+
+void Document::RecordNavigationTiming(ReadyState aReadyState) {
+ if (!XRE_IsContentProcess()) {
+ return;
+ }
+ if (!IsTopLevelContentDocument()) {
+ return;
+ }
+ // If we dont have the timing yet (mostly because the doc is still loading),
+ // get it from docshell.
+ RefPtr<nsDOMNavigationTiming> timing = mTiming;
+ if (!timing) {
+ if (!mDocumentContainer) {
+ return;
+ }
+ timing = mDocumentContainer->GetNavigationTiming();
+ if (!timing) {
+ return;
+ }
+ }
+ TimeStamp startTime = timing->GetNavigationStartTimeStamp();
+ switch (aReadyState) {
+ case READYSTATE_LOADING:
+ if (!mDOMLoadingSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
+ startTime);
+ mDOMLoadingSet = true;
+ }
+ break;
+ case READYSTATE_INTERACTIVE:
+ if (!mDOMInteractiveSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
+ startTime);
+ mDOMInteractiveSet = true;
+ }
+ break;
+ case READYSTATE_COMPLETE:
+ if (!mDOMCompleteSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
+ startTime);
+ mDOMCompleteSet = true;
+ }
+ break;
+ default:
+ NS_WARNING("Unexpected ReadyState value");
+ break;
+ }
+}
+
+bool Document::ModuleScriptsEnabled() {
+ return nsContentUtils::IsChromeDoc(this) ||
+ StaticPrefs::dom_moduleScripts_enabled();
+}
+
+bool Document::ImportMapsEnabled() {
+ return nsContentUtils::IsChromeDoc(this) ||
+ StaticPrefs::dom_importMaps_enabled();
+}
+
+void Document::ReportShadowDOMUsage() {
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ WindowContext* wc = inner->GetWindowContext();
+ if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
+ return;
+ }
+
+ WindowContext* topWc = wc->TopWindowContext();
+ if (topWc->GetHasReportedShadowDOMUsage()) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
+}
+
+// static
+bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
+ return StaticPrefs::dom_storage_access_enabled() &&
+ (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
+}
+
+bool Document::StorageAccessSandboxed() const {
+ return Document::StorageAccessSandboxed(GetSandboxFlags());
+}
+
+bool Document::GetCachedSizes(nsTabSizes* aSizes) {
+ if (mCachedTabSizeGeneration == 0 ||
+ GetGeneration() != mCachedTabSizeGeneration) {
+ return false;
+ }
+ aSizes->mDom += mCachedTabSizes.mDom;
+ aSizes->mStyle += mCachedTabSizes.mStyle;
+ aSizes->mOther += mCachedTabSizes.mOther;
+ return true;
+}
+
+void Document::SetCachedSizes(nsTabSizes* aSizes) {
+ mCachedTabSizes.mDom = aSizes->mDom;
+ mCachedTabSizes.mStyle = aSizes->mStyle;
+ mCachedTabSizes.mOther = aSizes->mOther;
+ mCachedTabSizeGeneration = GetGeneration();
+}
+
+already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
+ nsAutoString contentLang;
+ GetContentLanguage(contentLang);
+ contentLang.StripWhitespace();
+
+ // Content-Language may be a comma-separated list of language codes,
+ // in which case the HTML5 spec says to treat it as unknown
+ if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
+ return NS_Atomize(contentLang);
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
+ RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
+ if (!lang) {
+ lang = mLanguageFromCharset;
+ }
+ return lang.forget();
+}
+
+const LangGroupFontPrefs* Document::GetFontPrefsForLang(
+ nsAtom* aLanguage, bool* aNeedsToCache) const {
+ nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
+ return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
+}
+
+void Document::DoCacheAllKnownLangPrefs() {
+ MOZ_ASSERT(mMayNeedFontPrefsUpdate);
+ RefPtr<nsAtom> lang = GetLanguageForStyle();
+ StaticPresData* data = StaticPresData::Get();
+ data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
+ data->GetFontPrefsForLang(nsGkAtoms::x_math);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
+ data->GetFontPrefsForLang(nsGkAtoms::Unicode);
+ for (const auto& key : mLanguagesUsed) {
+ data->GetFontPrefsForLang(key);
+ }
+ mMayNeedFontPrefsUpdate = false;
+}
+
+void Document::RecomputeLanguageFromCharset() {
+ nsLanguageAtomService* service = nsLanguageAtomService::GetService();
+ RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
+ if (language == nsGkAtoms::Unicode) {
+ language = service->GetLocaleLanguage();
+ }
+
+ if (language == mLanguageFromCharset) {
+ return;
+ }
+
+ mMayNeedFontPrefsUpdate = true;
+ mLanguageFromCharset = std::move(language);
+}
+
+nsICookieJarSettings* Document::CookieJarSettings() {
+ // If we are here, this is probably a javascript: URL document. In any case,
+ // we must have a nsCookieJarSettings. Let's create it.
+ if (!mCookieJarSettings) {
+ Document* inProcessParent = GetInProcessParentDocument();
+
+ if (inProcessParent) {
+ mCookieJarSettings = net::CookieJarSettings::Create(
+ inProcessParent->CookieJarSettings()->GetCookieBehavior(),
+ mozilla::net::CookieJarSettings::Cast(
+ inProcessParent->CookieJarSettings())
+ ->GetPartitionKey(),
+ inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
+ inProcessParent->CookieJarSettings()
+ ->GetIsOnContentBlockingAllowList(),
+ inProcessParent->CookieJarSettings()
+ ->GetShouldResistFingerprinting());
+
+ // Inherit the fingerprinting random key from the parent.
+ nsTArray<uint8_t> randomKey;
+ nsresult rv = inProcessParent->CookieJarSettings()
+ ->GetFingerprintingRandomizationKey(randomKey);
+
+ if (NS_SUCCEEDED(rv)) {
+ net::CookieJarSettings::Cast(mCookieJarSettings)
+ ->SetFingerprintingRandomizationKey(randomKey);
+ }
+ } else {
+ mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
+ }
+
+ if (auto* wgc = GetWindowGlobalChild()) {
+ net::CookieJarSettingsArgs csArgs;
+ net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
+ // Update cookie settings in the parent process
+ if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
+ NS_WARNING(
+ "Failed to update document's cookie jar settings on the "
+ "WindowGlobalParent");
+ }
+ }
+ }
+
+ return mCookieJarSettings;
+}
+
+bool Document::HasStorageAccessPermissionGranted() {
+ // The HasStoragePermission flag in LoadInfo remains fixed when
+ // it is set in the parent process, so we need to check the cache
+ // to see if the permission is granted afterwards.
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner && inner->HasStorageAccessPermissionGranted()) {
+ return true;
+ }
+
+ if (!mChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
+}
+
+bool Document::HasStorageAccessPermissionGrantedByAllowList() {
+ // We only care about if the document gets the storage permission via the
+ // allow list here. So we don't check the storage access cache in the inner
+ // window.
+
+ if (!mChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ return loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+}
+
+nsIPrincipal* Document::EffectiveStoragePrincipal() const {
+ if (!StaticPrefs::
+ privacy_partition_always_partition_third_party_non_cookie_storage()) {
+ return EffectiveCookiePrincipal();
+ }
+
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (!inner) {
+ return NodePrincipal();
+ }
+
+ // Return our cached storage principal if one exists.
+ if (mActiveStoragePrincipal) {
+ return mActiveStoragePrincipal;
+ }
+
+ // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
+ // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
+ // fail // browser/base/content/test/performance/browser_startup.js. To avoid
+ // that, we short-circuit the check here by allowing storage access to system
+ // and addon principles, avoiding the test-failure.
+ nsIPrincipal* principal = NodePrincipal();
+ if (principal && (principal->IsSystemPrincipal() ||
+ principal->GetIsAddonOrExpandedAddonPrincipal())) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
+ if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ StorageAccess storageAccess = StorageAllowedForDocument(this);
+ if (!ShouldPartitionStorage(storageAccess) ||
+ !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
+ nsGlobalWindowInner::Cast(inner),
+ StoragePrincipalHelper::eForeignPartitionedPrincipal,
+ getter_AddRefs(mActiveStoragePrincipal))));
+ return mActiveStoragePrincipal;
+}
+
+nsIPrincipal* Document::EffectiveCookiePrincipal() const {
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (!inner) {
+ return NodePrincipal();
+ }
+
+ // Return our cached storage principal if one exists.
+ if (mActiveCookiePrincipal) {
+ return mActiveCookiePrincipal;
+ }
+
+ // We use the lower-level ContentBlocking API here to ensure this
+ // check doesn't send notifications.
+ uint32_t rejectedReason = 0;
+ if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
+ return mActiveCookiePrincipal = NodePrincipal();
+ }
+
+ // Let's use the storage principal only if we need to partition the cookie
+ // jar. When the permission is granted, access will be different and the
+ // normal principal will be used.
+ if (ShouldPartitionStorage(rejectedReason) &&
+ !StoragePartitioningEnabled(
+ rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
+ return mActiveCookiePrincipal = NodePrincipal();
+ }
+
+ return mActiveCookiePrincipal = mPartitionedPrincipal;
+}
+
+nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
+ // If the document is sandboxed document or data: document, we should
+ // get URI of the parent document.
+ for (const Document* document = this;
+ document && document->IsContentDocument();
+ document = document->GetInProcessParentDocument()) {
+ // The document URI may be about:blank even if it comes from actual web
+ // site. Therefore, we need to check the URI of its principal.
+ nsIPrincipal* principal = document->NodePrincipal();
+ if (principal->GetIsNullPrincipal()) {
+ continue;
+ }
+ return principal;
+ }
+ return nullptr;
+}
+
+void Document::SetIsInitialDocument(bool aIsInitialDocument) {
+ mIsInitialDocumentInWindow = aIsInitialDocument;
+
+ if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
+ mIsEverInitialDocumentInWindow = aIsInitialDocument;
+ }
+
+ // Asynchronously tell the parent process that we are, or are no longer, the
+ // initial document. This happens async.
+ if (auto* wgc = GetWindowGlobalChild()) {
+ wgc->SendSetIsInitialDocument(aIsInitialDocument);
+ }
+}
+
+// static
+void Document::AddToplevelLoadingDocument(Document* aDoc) {
+ MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
+ // Currently we're interested in foreground documents only, so bail out early.
+ if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ if (!sLoadingForegroundTopLevelContentDocument) {
+ sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendRunningPrioritizedOperation();
+ }
+ }
+ if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
+ sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
+ }
+}
+
+// static
+void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
+ MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
+ if (sLoadingForegroundTopLevelContentDocument) {
+ sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
+ if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
+ delete sLoadingForegroundTopLevelContentDocument;
+ sLoadingForegroundTopLevelContentDocument = nullptr;
+
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendPrioritizedOperationDone();
+ }
+ }
+ }
+}
+
+ColorScheme Document::DefaultColorScheme() const {
+ return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
+}
+
+ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
+ if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
+ aIgnoreRFP == IgnoreRFP::No) {
+ return ColorScheme::Light;
+ }
+
+ if (nsPresContext* pc = GetPresContext()) {
+ if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
+ return *scheme;
+ }
+ }
+
+ // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
+ // intentionally, to make chrome documents in content docshells (like about
+ // pages) use the content color scheme.
+ if (IsInChromeDocShell()) {
+ return LookAndFeel::ColorSchemeForChrome();
+ }
+ return LookAndFeel::PreferredColorSchemeForContent();
+}
+
+bool Document::HasRecentlyStartedForegroundLoads() {
+ if (!sLoadingForegroundTopLevelContentDocument) {
+ return false;
+ }
+
+ for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
+ ++i) {
+ Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
+ // A page loaded in foreground could be in background now.
+ if (!doc->IsInBackgroundWindow()) {
+ nsPIDOMWindowInner* win = doc->GetInnerWindow();
+ if (win) {
+ Performance* perf = win->GetPerformance();
+ if (perf &&
+ perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ // Didn't find any loading foreground documents, just clear the array.
+ delete sLoadingForegroundTopLevelContentDocument;
+ sLoadingForegroundTopLevelContentDocument = nullptr;
+
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendPrioritizedOperationDone();
+ }
+ return false;
+}
+
+void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
+ nsFrameLoader* aStaticCloneOf) {
+ PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
+ clone->mElement = aElement;
+ clone->mStaticCloneOf = aStaticCloneOf;
+}
+
+bool Document::ShouldAvoidNativeTheme() const {
+ return StaticPrefs::widget_non_native_theme_enabled() &&
+ (!IsInChromeDocShell() || XRE_IsContentProcess());
+}
+
+bool Document::UseRegularPrincipal() const {
+ return EffectiveStoragePrincipal() == NodePrincipal();
+}
+
+bool Document::HasThirdPartyChannel() {
+ nsCOMPtr<nsIChannel> channel = GetChannel();
+ if (channel) {
+ // We assume that the channel is a third-party by default.
+ bool thirdParty = true;
+
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ components::ThirdPartyUtil::Service();
+ if (!thirdPartyUtil) {
+ return thirdParty;
+ }
+
+ // Check that if the channel is a third-party to its parent.
+ nsresult rv =
+ thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
+ if (NS_FAILED(rv)) {
+ // Assume third-party in case of failure
+ thirdParty = true;
+ }
+
+ return thirdParty;
+ }
+
+ if (mParentDocument) {
+ return mParentDocument->HasThirdPartyChannel();
+ }
+
+ return false;
+}
+
+bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
+ if (!(IsContentDocument() || IsResourceDoc())) {
+ return false;
+ }
+
+ if (!aAllowExtensionURIs &&
+ NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
+ return false;
+ }
+
+ return !NodePrincipal()->SchemeIs("about") &&
+ !NodePrincipal()->SchemeIs("chrome") &&
+ !NodePrincipal()->SchemeIs("resource");
+}
+
+void Document::GetConnectedShadowRoots(
+ nsTArray<RefPtr<ShadowRoot>>& aOut) const {
+ AppendToArray(aOut, mComposedShadowRoots);
+}
+
+bool Document::HasPictureInPictureChildElement() const {
+ return mPictureInPictureChildElementCount > 0;
+}
+
+void Document::EnableChildElementInPictureInPictureMode() {
+ mPictureInPictureChildElementCount++;
+ MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
+}
+
+void Document::DisableChildElementInPictureInPictureMode() {
+ mPictureInPictureChildElementCount--;
+ MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
+}
+
+void Document::AddMediaElementWithMSE() {
+ if (mMediaElementWithMSECount++ == 0) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
+ }
+ }
+}
+
+void Document::RemoveMediaElementWithMSE() {
+ MOZ_ASSERT(mMediaElementWithMSECount > 0);
+ if (--mMediaElementWithMSECount == 0) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
+ }
+ }
+}
+
+void Document::UnregisterFromMemoryReportingForDataDocument() {
+ if (!mAddedToMemoryReportingAsDataDocument) {
+ return;
+ }
+ mAddedToMemoryReportingAsDataDocument = false;
+ nsIGlobalObject* global = GetScopeObject();
+ if (global) {
+ if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
+ nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
+ this);
+ }
+ }
+}
+void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
+ MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
+ mOOPChildrenLoading.AppendElement(aChild);
+ if (mOOPChildrenLoading.Length() == 1) {
+ // Let's block unload so that we're blocked from going into the BFCache
+ // until the child has actually notified us that it has done loading.
+ BlockOnload();
+ }
+}
+
+void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
+ // aChild will not be in the list if nsDocLoader::Stop() was called, since
+ // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
+ // so we don't need to call DocLoaderIsEmpty in that case.
+ if (mOOPChildrenLoading.RemoveElement(aChild)) {
+ if (mOOPChildrenLoading.IsEmpty()) {
+ UnblockOnload(false);
+ }
+ RefPtr<nsDocLoader> docLoader(mDocumentContainer);
+ if (docLoader) {
+ docLoader->OOPChildrenLoadingIsEmpty();
+ }
+ }
+}
+
+void Document::ClearOOPChildrenLoading() {
+ nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
+ mOOPChildrenLoading.SwapElements(oopChildrenLoading);
+ if (!oopChildrenLoading.IsEmpty()) {
+ UnblockOnload(false);
+ }
+}
+
+HighlightRegistry& Document::HighlightRegistry() {
+ if (!mHighlightRegistry) {
+ mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
+ }
+ return *mHighlightRegistry;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Document.h b/dom/base/Document.h
new file mode 100644
index 0000000000..073c6ab179
--- /dev/null
+++ b/dom/base/Document.h
@@ -0,0 +1,5628 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Document_h___
+#define mozilla_dom_Document_h___
+
+#include <bitset>
+#include <cstddef>
+#include <cstdint>
+#include <new>
+#include <utility>
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "Units.h"
+#include "imgIRequest.h"
+#include "js/RootingAPI.h"
+#include "js/friend/DOMProxy.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/BitSet.h"
+#include "mozilla/OriginTrials.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/CallState.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/FunctionRef.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PointerLockManager.h"
+#include "mozilla/PreloadService.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/SegmentedVector.h"
+#include "mozilla/StorageAccessAPIHelper.h"
+#include "mozilla/TaskCategory.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UseCounter.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/css/StylePreloadKind.h"
+#include "mozilla/dom/AnimationFrameProvider.h"
+#include "mozilla/dom/DispatcherTrait.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/TreeOrderedArray.h"
+#include "mozilla/dom/ViewportMetaData.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "nsAtom.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsCompatibility.h"
+#include "nsContentListDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsExpirationTracker.h"
+#include "nsGkAtoms.h"
+#include "nsHashKeys.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentViewer.h"
+#include "nsID.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsINode.h"
+#include "nsIObserver.h"
+#include "nsIParser.h"
+#include "nsIPrincipal.h"
+#include "nsIProgressEventSink.h"
+#include "nsIRadioGroupContainer.h"
+#include "nsIReferrerInfo.h"
+#include "nsIRequestObserver.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIStreamListener.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsLiteralString.h"
+#include "nsPIDOMWindow.h"
+#include "nsPropertyTable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "nsTLiteralString.h"
+#include "nsTObserverArray.h"
+#include "nsThreadUtils.h"
+#include "nsURIHashKey.h"
+#include "nsViewportInfo.h"
+#include "nsWeakReference.h"
+#include "nsWindowSizes.h"
+#include "nsXULElement.h"
+#include "nscore.h"
+
+// XXX We need to include this here to ensure that DefaultDeleter for Servo
+// types is specialized before the template is instantiated. Probably, this
+// should be included at some other place already that's generated by cbindgen.
+#include "mozilla/ServoBindingTypes.h"
+
+// windows.h #defines CreateEvent
+#ifdef CreateEvent
+# undef CreateEvent
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+# include "mozilla/dom/DocumentBinding.h"
+#else
+namespace mozilla {
+namespace dom {
+class ElementCreationOptionsOrString;
+} // namespace dom
+} // namespace mozilla
+#endif // MOZILLA_INTERNAL_API
+
+class InfallibleAllocPolicy;
+class JSObject;
+class JSTracer;
+class PLDHashTable;
+class gfxUserFontSet;
+class mozIDOMWindowProxy;
+class nsCachableElementsByNameNodeList;
+class nsCommandManager;
+class nsContentList;
+class nsCycleCollectionTraversalCallback;
+class nsDOMCaretPosition;
+class nsDOMNavigationTiming;
+class nsDocShell;
+class nsFrameLoader;
+class nsFrameLoaderOwner;
+class nsGenericHTMLElement;
+class nsGlobalWindowInner;
+class nsHTMLCSSStyleSheet;
+class nsHTMLDocument;
+class nsHTMLStyleSheet;
+class nsHtml5TreeOpExecutor;
+class nsIAppWindow;
+class nsIAsyncVerifyRedirectCallback;
+class nsIBFCacheEntry;
+class nsIContent;
+class nsIContentSecurityPolicy;
+class nsIContentSink;
+class nsICookieJarSettings;
+class nsIDOMXULCommandDispatcher;
+class nsIDocShell;
+class nsIDocShellTreeItem;
+class nsIDocumentEncoder;
+class nsIDocumentObserver;
+class nsIEventTarget;
+class nsIFrame;
+class nsIGlobalObject;
+class nsIHTMLCollection;
+class nsIInputStream;
+class nsILayoutHistoryState;
+class nsIObjectLoadingContent;
+class nsIPermissionDelegateHandler;
+class nsIRadioVisitor;
+class nsIRequest;
+class nsIRunnable;
+class nsIScriptGlobalObject;
+class nsISecurityConsoleMessage;
+class nsISerialEventTarget;
+class nsIStructuredCloneContainer;
+class nsIVariant;
+class nsNodeInfoManager;
+class nsPIWindowRoot;
+class nsPresContext;
+class nsRange;
+class nsSimpleContentList;
+class nsTextNode;
+class nsViewManager;
+class nsXULPrototypeDocument;
+struct JSContext;
+struct nsFont;
+
+namespace mozilla {
+class AbstractThread;
+class StyleSheet;
+class EditorBase;
+class EditorCommand;
+class Encoding;
+class ErrorResult;
+class EventListenerManager;
+class FullscreenExit;
+class FullscreenRequest;
+class HTMLEditor;
+struct LangGroupFontPrefs;
+class PendingAnimationTracker;
+class PermissionDelegateHandler;
+class PresShell;
+class ScrollTimelineAnimationTracker;
+class ServoStyleSet;
+enum class StyleOrigin : uint8_t;
+class SMILAnimationController;
+enum class StyleCursorKind : uint8_t;
+class SVGContextPaint;
+enum class ColorScheme : uint8_t;
+enum class StyleRuleChangeKind : uint32_t;
+struct StyleSelectorList;
+struct StyleUseCounters;
+template <typename>
+class OwningNonNull;
+struct URLExtraData;
+
+namespace css {
+class Loader;
+class ImageLoader;
+class Rule;
+} // namespace css
+
+namespace dom {
+class AnonymousContent;
+class Attr;
+class XULBroadcastManager;
+class XULPersist;
+class BrowserBridgeChild;
+class ChromeObserver;
+class ClientInfo;
+class ClientState;
+class CDATASection;
+class Comment;
+class CSSImportRule;
+class DocumentL10n;
+class DocumentFragment;
+class DocumentTimeline;
+class DocumentType;
+class DOMImplementation;
+class DOMIntersectionObserver;
+class DOMStringList;
+class Event;
+class EventListener;
+struct FailedCertSecurityInfo;
+class FeaturePolicy;
+class FontFaceSet;
+class FrameRequestCallback;
+class ImageTracker;
+class HighlightRegistry;
+class HTMLAllCollection;
+class HTMLBodyElement;
+class HTMLInputElement;
+class HTMLMetaElement;
+class HTMLDialogElement;
+class HTMLSharedElement;
+class HTMLImageElement;
+struct LifecycleCallbackArgs;
+class Link;
+class Location;
+class MediaQueryList;
+struct NetErrorInfo;
+class NodeFilter;
+class NodeInfo;
+class NodeIterator;
+enum class OrientationType : uint8_t;
+class ProcessingInstruction;
+class Promise;
+class ScriptLoader;
+class Selection;
+class ServiceWorkerDescriptor;
+class ShadowRoot;
+class SVGDocument;
+class SVGElement;
+class SVGSVGElement;
+class SVGUseElement;
+class ImageDocument;
+class Touch;
+class TouchList;
+class TreeWalker;
+enum class ViewportFitType : uint8_t;
+class WindowContext;
+class WindowGlobalChild;
+class WindowProxyHolder;
+struct Wireframe;
+class WorkerDocumentListener;
+class XPathEvaluator;
+class XPathExpression;
+class XPathNSResolver;
+class XPathResult;
+class BrowsingContext;
+
+class nsUnblockOnloadEvent;
+
+template <typename, typename>
+class CallbackObjectHolder;
+
+enum class CallerType : uint32_t;
+
+enum BFCacheStatus {
+ NOT_ALLOWED = 1 << 0, // Status 0
+ EVENT_HANDLING_SUPPRESSED = 1 << 1, // Status 1
+ SUSPENDED = 1 << 2, // Status 2
+ UNLOAD_LISTENER = 1 << 3, // Status 3
+ REQUEST = 1 << 4, // Status 4
+ ACTIVE_GET_USER_MEDIA = 1 << 5, // Status 5
+ ACTIVE_PEER_CONNECTION = 1 << 6, // Status 6
+ CONTAINS_EME_CONTENT = 1 << 7, // Status 7
+ CONTAINS_MSE_CONTENT = 1 << 8, // Status 8
+ HAS_ACTIVE_SPEECH_SYNTHESIS = 1 << 9, // Status 9
+ HAS_USED_VR = 1 << 10, // Status 10
+ CONTAINS_REMOTE_SUBFRAMES = 1 << 11, // Status 11
+ NOT_ONLY_TOPLEVEL_IN_BCG = 1 << 12, // Status 12
+ ABOUT_PAGE = 1 << 13, // Status 13
+ RESTORING = 1 << 14, // Status 14
+ BEFOREUNLOAD_LISTENER = 1 << 15, // Status 15
+ ACTIVE_LOCK = 1 << 16, // Status 16
+ ACTIVE_WEBTRANSPORT = 1 << 17, // Status 17
+};
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::net {
+class ChannelEventQueue;
+class EarlyHintConnectArgs;
+} // namespace mozilla::net
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_IDOCUMENT_IID \
+ { \
+ 0xce1f7627, 0x7109, 0x4977, { \
+ 0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa \
+ } \
+ }
+
+namespace mozilla::dom {
+
+class Document;
+class DOMStyleSheetSetList;
+class ResizeObserver;
+class ResizeObserverController;
+class PostMessageEvent;
+
+#define DEPRECATED_OPERATION(_op) e##_op,
+enum class DeprecatedOperations : uint16_t {
+#include "nsDeprecatedOperationList.h"
+ eDeprecatedOperationCount
+};
+#undef DEPRECATED_OPERATION
+
+class ExternalResourceMap {
+ using SubDocEnumFunc = FunctionRef<CallState(Document&)>;
+
+ public:
+ /**
+ * A class that represents an external resource load that has begun but
+ * doesn't have a document yet. Observers can be registered on this object,
+ * and will be notified after the document is created. Observers registered
+ * after the document has been created will NOT be notified. When observers
+ * are notified, the subject will be the newly-created document, the topic
+ * will be "external-resource-document-created", and the data will be null.
+ * If document creation fails for some reason, observers will still be
+ * notified, with a null document pointer.
+ */
+ class ExternalResourceLoad : public nsISupports {
+ public:
+ virtual ~ExternalResourceLoad() = default;
+
+ void AddObserver(nsIObserver* aObserver) {
+ MOZ_ASSERT(aObserver, "Must have observer");
+ mObservers.AppendElement(aObserver);
+ }
+
+ const nsTArray<nsCOMPtr<nsIObserver>>& Observers() { return mObservers; }
+
+ protected:
+ AutoTArray<nsCOMPtr<nsIObserver>, 8> mObservers;
+ };
+
+ ExternalResourceMap();
+
+ /**
+ * Request an external resource document. This does exactly what
+ * Document::RequestExternalResource is documented to do.
+ */
+ Document* RequestResource(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aRequestingNode,
+ Document* aDisplayDocument,
+ ExternalResourceLoad** aPendingLoad);
+
+ /**
+ * Enumerate the resource documents. See
+ * Document::EnumerateExternalResources.
+ */
+ void EnumerateResources(SubDocEnumFunc aCallback);
+
+ /**
+ * Traverse ourselves for cycle-collection
+ */
+ void Traverse(nsCycleCollectionTraversalCallback* aCallback) const;
+
+ /**
+ * Shut ourselves down (used for cycle-collection unlink), as well
+ * as for document destruction.
+ */
+ void Shutdown() {
+ mPendingLoads.Clear();
+ mMap.Clear();
+ mHaveShutDown = true;
+ }
+
+ bool HaveShutDown() const { return mHaveShutDown; }
+
+ // Needs to be public so we can traverse them sanely
+ struct ExternalResource {
+ ~ExternalResource();
+ RefPtr<Document> mDocument;
+ nsCOMPtr<nsIContentViewer> mViewer;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ };
+
+ // Hide all our viewers
+ void HideViewers();
+
+ // Show all our viewers
+ void ShowViewers();
+
+ protected:
+ class PendingLoad : public ExternalResourceLoad, public nsIStreamListener {
+ ~PendingLoad() = default;
+
+ public:
+ explicit PendingLoad(Document* aDisplayDocument)
+ : mDisplayDocument(aDisplayDocument) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ /**
+ * Start aURI loading. This will perform the necessary security checks and
+ * so forth.
+ */
+ nsresult StartLoad(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aRequestingNode);
+ /**
+ * Set up an nsIContentViewer based on aRequest. This is guaranteed to
+ * put null in *aViewer and *aLoadGroup on all failures.
+ */
+ nsresult SetupViewer(nsIRequest* aRequest, nsIContentViewer** aViewer,
+ nsILoadGroup** aLoadGroup);
+
+ private:
+ RefPtr<Document> mDisplayDocument;
+ nsCOMPtr<nsIStreamListener> mTargetListener;
+ nsCOMPtr<nsIURI> mURI;
+ };
+ friend class PendingLoad;
+
+ class LoadgroupCallbacks final : public nsIInterfaceRequestor {
+ ~LoadgroupCallbacks() = default;
+
+ public:
+ explicit LoadgroupCallbacks(nsIInterfaceRequestor* aOtherCallbacks)
+ : mCallbacks(aOtherCallbacks) {}
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ private:
+ // The only reason it's safe to hold a strong ref here without leaking is
+ // that the notificationCallbacks on a loadgroup aren't the docshell itself
+ // but a shim that holds a weak reference to the docshell.
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+
+ // Use shims for interfaces that docshell implements directly so that we
+ // don't hand out references to the docshell. The shims should all allow
+ // getInterface back on us, but other than that each one should only
+ // implement one interface.
+
+ // XXXbz I wish we could just derive the _allcaps thing from _i
+#define DECL_SHIM(_i, _allcaps) \
+ class _i##Shim final : public nsIInterfaceRequestor, public _i { \
+ ~_i##Shim() {} \
+ \
+ public: \
+ _i##Shim(nsIInterfaceRequestor* aIfreq, _i* aRealPtr) \
+ : mIfReq(aIfreq), mRealPtr(aRealPtr) { \
+ NS_ASSERTION(mIfReq, "Expected non-null here"); \
+ NS_ASSERTION(mRealPtr, "Expected non-null here"); \
+ } \
+ NS_DECL_ISUPPORTS \
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mIfReq->) \
+ NS_FORWARD_##_allcaps(mRealPtr->) private \
+ : nsCOMPtr<nsIInterfaceRequestor> mIfReq; \
+ nsCOMPtr<_i> mRealPtr; \
+ };
+
+ DECL_SHIM(nsILoadContext, NSILOADCONTEXT)
+ DECL_SHIM(nsIProgressEventSink, NSIPROGRESSEVENTSINK)
+ DECL_SHIM(nsIChannelEventSink, NSICHANNELEVENTSINK)
+#undef DECL_SHIM
+ };
+
+ /**
+ * Add an ExternalResource for aURI. aViewer and aLoadGroup might be null
+ * when this is called if the URI didn't result in an XML document. This
+ * function makes sure to remove the pending load for aURI, if any, from our
+ * hashtable, and to notify its observers, if any.
+ */
+ nsresult AddExternalResource(nsIURI* aURI, nsIContentViewer* aViewer,
+ nsILoadGroup* aLoadGroup,
+ Document* aDisplayDocument);
+
+ nsClassHashtable<nsURIHashKey, ExternalResource> mMap;
+ nsRefPtrHashtable<nsURIHashKey, PendingLoad> mPendingLoads;
+ bool mHaveShutDown;
+};
+
+// The current status for a preload.
+enum class SheetPreloadStatus : uint8_t {
+ // There's no need to preload anything, the sheet is already in-memory.
+ AlreadyComplete,
+ // The load is in-progress. There's no guarantee that a load was started, it
+ // could be coalesced with other redundant loads.
+ InProgress,
+ // Something went wrong, and we errored out.
+ Errored,
+};
+
+//----------------------------------------------------------------------
+
+// Document interface. This is implemented by all document objects in
+// Gecko.
+class Document : public nsINode,
+ public DocumentOrShadowRoot,
+ public nsSupportsWeakReference,
+ public nsIRadioGroupContainer,
+ public nsIScriptObjectPrincipal,
+ public DispatcherTrait,
+ public SupportsWeakPtr {
+ friend class DocumentOrShadowRoot;
+
+ protected:
+ explicit Document(const char* aContentType);
+ virtual ~Document();
+
+ Document(const Document&) = delete;
+ Document& operator=(const Document&) = delete;
+
+ public:
+ using ExternalResourceLoad = dom::ExternalResourceMap::ExternalResourceLoad;
+ using ReferrerPolicyEnum = dom::ReferrerPolicy;
+ using AdoptedStyleSheetCloneCache =
+ nsRefPtrHashtable<nsPtrHashKey<const StyleSheet>, StyleSheet>;
+
+ // nsINode overrides the new operator for DOM Arena allocation.
+ // to use the default one, we need to bring it back again
+ void* operator new(size_t aSize) { return ::operator new(aSize); }
+
+ /**
+ * Called when XPCOM shutdown.
+ */
+ static void Shutdown();
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_ADDSIZEOFEXCLUDINGTHIS
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Document,
+ nsINode)
+
+#define NS_DOCUMENT_NOTIFY_OBSERVERS(func_, params_) \
+ do { \
+ for (RefPtr obs : mObservers.ForwardRange()) { \
+ if (obs->IsCallbackEnabled(nsIMutationObserver::k##func_)) { \
+ obs->func_ params_; \
+ } \
+ } \
+ /* FIXME(emilio): Apparently we can keep observing from the BFCache? That \
+ looks bogus. */ \
+ if (PresShell* presShell = GetObservingPresShell()) { \
+ presShell->func_ params_; \
+ } \
+ } while (0)
+
+ // nsIRadioGroupContainer
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor) final {
+ return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor);
+ }
+
+ void SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio) final {
+ DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
+ }
+
+ HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) final {
+ return DocumentOrShadowRoot::GetCurrentRadioButton(aName);
+ }
+
+ NS_IMETHOD
+ GetNextRadioButton(const nsAString& aName, const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut) final {
+ return DocumentOrShadowRoot::GetNextRadioButton(aName, aPrevious,
+ aFocusedRadio, aRadioOut);
+ }
+ void AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio) final {
+ DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio);
+ }
+ void RemoveFromRadioGroup(const nsAString& aName,
+ HTMLInputElement* aRadio) final {
+ DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
+ }
+ uint32_t GetRequiredRadioCount(const nsAString& aName) const final {
+ return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
+ }
+ void RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded) final {
+ DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
+ }
+ bool GetValueMissingState(const nsAString& aName) const final {
+ return DocumentOrShadowRoot::GetValueMissingState(aName);
+ }
+ void SetValueMissingState(const nsAString& aName, bool aValue) final {
+ return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
+ }
+
+ nsIPrincipal* EffectiveCookiePrincipal() const;
+
+ nsIPrincipal* EffectiveStoragePrincipal() const;
+
+ // nsIScriptObjectPrincipal
+ nsIPrincipal* GetPrincipal() final { return NodePrincipal(); }
+
+ nsIPrincipal* GetEffectiveCookiePrincipal() final {
+ return EffectiveCookiePrincipal();
+ }
+
+ nsIPrincipal* GetEffectiveStoragePrincipal() final {
+ return EffectiveStoragePrincipal();
+ }
+
+ // You should probably not be using this function, since it performs no checks
+ // to ensure that the partitioned principal should really be used here. It is
+ // only designed to be used in very specific circumstances, such as when
+ // inheriting the document/storage principal.
+ nsIPrincipal* PartitionedPrincipal() final { return mPartitionedPrincipal; }
+
+ // Gets the appropriate principal to check the URI against a blocklist /
+ // allowlist.
+ nsIPrincipal* GetPrincipalForPrefBasedHacks() const;
+
+ void ClearActiveCookieAndStoragePrincipals() {
+ mActiveStoragePrincipal = nullptr;
+ mActiveCookiePrincipal = nullptr;
+ }
+
+ // EventTarget
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+ EventListenerManager* GetOrCreateListenerManager() override;
+ EventListenerManager* GetExistingListenerManager() const override;
+
+ // This helper class must be set when we dispatch beforeunload and unload
+ // events in order to avoid unterminate sync XHRs.
+ class MOZ_RAII PageUnloadingEventTimeStamp {
+ RefPtr<Document> mDocument;
+ bool mSet;
+
+ public:
+ explicit PageUnloadingEventTimeStamp(Document* aDocument)
+ : mDocument(aDocument), mSet(false) {
+ MOZ_ASSERT(aDocument);
+ if (mDocument->mPageUnloadingEventTimeStamp.IsNull()) {
+ mDocument->SetPageUnloadingEventTimeStamp();
+ mSet = true;
+ }
+ }
+
+ ~PageUnloadingEventTimeStamp() {
+ if (mSet) {
+ mDocument->CleanUnloadEventsTimeStamp();
+ }
+ }
+ };
+
+ /**
+ * Let the document know that we're starting to load data into it.
+ * @param aCommand The parser command. Must not be null.
+ * XXXbz It's odd to have that here.
+ * @param aChannel The channel the data will come from. The channel must be
+ * able to report its Content-Type.
+ * @param aLoadGroup The loadgroup this document should use from now on.
+ * Note that the document might not be the only thing using
+ * this loadgroup.
+ * @param aContainer The container this document is in. This may be null.
+ * XXXbz maybe we should make it more explicit (eg make the
+ * container an nsIWebNavigation or nsIDocShell or
+ * something)?
+ * @param [out] aDocListener the listener to pump data from the channel into.
+ * Generally this will be the parser this document
+ * sets up, or some sort of data-handler for media
+ * documents.
+ * @param aReset whether the document should call Reset() on itself. If this
+ * is false, the document will NOT set its principal to the
+ * channel's owner, will not clear any event listeners that are
+ * already set on it, etc.
+ *
+ * Once this has been called, the document will return false for
+ * MayStartLayout() until SetMayStartLayout(true) is called on it. Making
+ * sure this happens is the responsibility of the caller of
+ * StartDocumentLoad().
+ *
+ * This function has an implementation, and does some setup, but does NOT set
+ * *aDocListener; this is the job of subclasses.
+ */
+ virtual nsresult StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset) = 0;
+ void StopDocumentLoad();
+
+ virtual void SetSuppressParserErrorElement(bool aSuppress) {}
+ virtual bool SuppressParserErrorElement() { return false; }
+
+ virtual void SetSuppressParserErrorConsoleMessages(bool aSuppress) {}
+ virtual bool SuppressParserErrorConsoleMessages() { return false; }
+
+ // nsINode
+ void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
+ bool aNotify, ErrorResult& aRv) override;
+ void RemoveChildNode(nsIContent* aKid, bool aNotify) final;
+ nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult CloneDocHelper(Document* clone) const;
+
+ Document* GetLatestStaticClone() const { return mLatestStaticClone; }
+
+ /**
+ * Signal that the document title may have changed
+ * (see Document::GetTitle).
+ * @param aBoundTitleElement true if an HTML or SVG <title> element
+ * has just been bound to the document.
+ */
+ virtual void NotifyPossibleTitleChange(bool aBoundTitleElement);
+
+ /**
+ * Return the URI for the document. May return null. If it ever stops being
+ * able to return null, we can make sure nsINode::GetBaseURI/GetBaseURIObject
+ * also never return null.
+ *
+ * The value returned corresponds to the "document's address" in
+ * HTML5. As such, it may change over the lifetime of the document, for
+ * instance as a result of the user navigating to a fragment identifier on
+ * the page, or as a result to a call to pushState() or replaceState().
+ *
+ * https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address
+ */
+ nsIURI* GetDocumentURI() const { return mDocumentURI; }
+
+ /**
+ * Return the original URI of the document. This is the same as the
+ * document's URI unless that has changed from its original value (for
+ * example, due to history.pushState() or replaceState() being invoked on the
+ * document).
+ *
+ * This method corresponds to the "creation URL" in HTML5 and, once set,
+ * doesn't change over the lifetime of the document.
+ *
+ * https://html.spec.whatwg.org/multipage/webappapis.html#creation-url
+ */
+ nsIURI* GetOriginalURI() const { return mOriginalURI; }
+
+ /**
+ * Return the base domain of the document. This has been computed using
+ * mozIThirdPartyUtil::GetBaseDomain() and can be used for third-party
+ * checks. When the URI of the document changes, this value is recomputed.
+ */
+ nsCString GetBaseDomain() const { return mBaseDomain; }
+
+ /**
+ * Set the URI for the document. This also sets the document's original URI,
+ * if it's null.
+ */
+ void SetDocumentURI(nsIURI* aURI);
+
+ /**
+ * Set the URI for the document loaded via XHR, when accessed from
+ * chrome privileged script.
+ */
+ void SetChromeXHRDocURI(nsIURI* aURI) { mChromeXHRDocURI = aURI; }
+
+ /**
+ * Set the base URI for the document loaded via XHR, when accessed from
+ * chrome privileged script.
+ */
+ void SetChromeXHRDocBaseURI(nsIURI* aURI) { mChromeXHRDocBaseURI = aURI; }
+
+ /**
+ * The CSP in general is stored in the ClientInfo, but we also cache
+ * the CSP on the document so subresources loaded within a document
+ * can query that cached CSP instead of having to deserialize the CSP
+ * from the Client.
+ *
+ * Please note that at the time of CSP parsing the Client is not
+ * available yet, hence we sync CSP of document and Client when the
+ * Client becomes available within nsGlobalWindowInner::EnsureClientSource().
+ */
+ nsIContentSecurityPolicy* GetCsp() const;
+ void SetCsp(nsIContentSecurityPolicy* aCSP);
+
+ nsIContentSecurityPolicy* GetPreloadCsp() const;
+ void SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP);
+
+ void GetCspJSON(nsString& aJSON);
+
+ /**
+ * Set referrer policy and upgrade-insecure-requests flags
+ */
+ void ApplySettingsFromCSP(bool aSpeculative);
+
+ already_AddRefed<nsIParser> CreatorParserOrNull() {
+ nsCOMPtr<nsIParser> parser = mParser;
+ return parser.forget();
+ }
+
+ /**
+ * ReferrerInfo getter for Document.webidl.
+ */
+ nsIReferrerInfo* ReferrerInfo() const { return GetReferrerInfo(); }
+
+ nsIReferrerInfo* GetReferrerInfo() const { return mReferrerInfo; }
+
+ nsIReferrerInfo* GetPreloadReferrerInfo() const {
+ return mPreloadReferrerInfo;
+ }
+ /**
+ * Return the referrer policy of the document. Return "default" if there's no
+ * valid meta referrer tag found in the document.
+ * Referrer policy should be inherited from parent if the iframe is srcdoc
+ */
+ ReferrerPolicyEnum GetReferrerPolicy() const;
+
+ /**
+ * GetReferrerPolicy() for Document.webidl.
+ */
+ ReferrerPolicyEnum ReferrerPolicy() const { return GetReferrerPolicy(); }
+
+ /**
+ * If true, this flag indicates that all mixed content subresource
+ * loads for this document (and also embeded browsing contexts) will
+ * be blocked.
+ */
+ bool GetBlockAllMixedContent(bool aPreload) const {
+ if (aPreload) {
+ return mBlockAllMixedContentPreloads;
+ }
+ return mBlockAllMixedContent;
+ }
+
+ /**
+ * If true, this flag indicates that all subresource loads for this
+ * document need to be upgraded from http to https.
+ * This flag becomes true if the CSP of the document itself, or any
+ * of the document's ancestors up to the toplevel document makes use
+ * of the CSP directive 'upgrade-insecure-requests'.
+ */
+ bool GetUpgradeInsecureRequests(bool aPreload) const {
+ if (aPreload) {
+ return mUpgradeInsecurePreloads;
+ }
+ return mUpgradeInsecureRequests;
+ }
+
+ void SetReferrerInfo(nsIReferrerInfo*);
+
+ /*
+ * Referrer policy from <meta name="referrer" content=`policy`>
+ * will have higher priority than referrer policy from Referrer-Policy
+ * header. So override the old ReferrerInfo if we get one from meta
+ */
+ void UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
+ bool aPreload);
+
+ /**
+ * Set the principals responsible for this document. Chances are, you do not
+ * want to be using this.
+ */
+ void SetPrincipals(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal);
+
+ /**
+ * Returns true if exempt from HTTPS-Only Mode upgrade.
+ */
+ uint32_t HttpsOnlyStatus() const { return mHttpsOnlyStatus; }
+
+ /**
+ * Return the LoadGroup for the document. May return null.
+ */
+ already_AddRefed<nsILoadGroup> GetDocumentLoadGroup() const {
+ nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+ return group.forget();
+ }
+
+ /**
+ * Return the fallback base URL for this document, as defined in the HTML
+ * specification. Note that this can return null if there is no document URI.
+ *
+ * XXXbz: This doesn't implement the bits for about:blank yet.
+ */
+ nsIURI* GetFallbackBaseURI() const {
+ if (mIsSrcdocDocument && mParentDocument) {
+ return mParentDocument->GetDocBaseURI();
+ }
+ return mDocumentURI;
+ }
+
+ /**
+ * Return the referrer from document URI as defined in the Referrer Policy
+ * specification.
+ * https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
+ * While document is an iframe srcdoc document, let document be document's
+ * browsing context's browsing context container's node document.
+ * Then referrer should be document's URL
+ */
+
+ nsIURI* GetDocumentURIAsReferrer() const {
+ if (mIsSrcdocDocument && mParentDocument) {
+ return mParentDocument->GetDocumentURIAsReferrer();
+ }
+ return mDocumentURI;
+ }
+
+ /**
+ * Return the base URI for relative URIs in the document (the document uri
+ * unless it's overridden by SetBaseURI, HTML <base> tags, etc.). The
+ * returned URI could be null if there is no document URI. If the document is
+ * a srcdoc document and has no explicit base URL, return the parent
+ * document's base URL.
+ */
+ nsIURI* GetDocBaseURI() const {
+ if (mDocumentBaseURI) {
+ return mDocumentBaseURI;
+ }
+ return GetFallbackBaseURI();
+ }
+
+ nsIURI* GetBaseURI(bool aTryUseXHRDocBaseURI = false) const final;
+
+ void SetBaseURI(nsIURI* aURI);
+
+ /**
+ * Resolves a URI based on the document's base URI.
+ */
+ Result<OwningNonNull<nsIURI>, nsresult> ResolveWithBaseURI(
+ const nsAString& aURI);
+
+ /**
+ * Return the URL data which style system needs for resolving url value.
+ * This method attempts to use the cached object in mCachedURLData, but
+ * if the base URI, document URI, or principal has changed since last
+ * call to this function, or the function is called the first time for
+ * the document, a new one is created.
+ */
+ URLExtraData* DefaultStyleAttrURLData();
+ nsIReferrerInfo* ReferrerInfoForInternalCSSAndSVGResources();
+
+ /**
+ * Get/Set the base target of a link in a document.
+ */
+ void GetBaseTarget(nsAString& aBaseTarget) const {
+ aBaseTarget = mBaseTarget;
+ }
+
+ void SetBaseTarget(const nsString& aBaseTarget) { mBaseTarget = aBaseTarget; }
+
+ /**
+ * Return a standard name for the document's character set.
+ */
+ NotNull<const Encoding*> GetDocumentCharacterSet() const {
+ return mCharacterSet;
+ }
+
+ /**
+ * Set the document's character encoding.
+ */
+ void SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding);
+
+ int32_t GetDocumentCharacterSetSource() const { return mCharacterSetSource; }
+
+ // This method MUST be called before SetDocumentCharacterSet if
+ // you're planning to call both.
+ void SetDocumentCharacterSetSource(int32_t aCharsetSource) {
+ mCharacterSetSource = aCharsetSource;
+ }
+
+ /**
+ * Get the Content-Type of this document.
+ */
+ void GetContentType(nsAString& aContentType);
+
+ /**
+ * Set the Content-Type of this document.
+ */
+ void SetContentType(const nsACString& aContentType);
+
+ /**
+ * Return the language of this document.
+ */
+ void GetContentLanguage(nsAString& aContentLanguage) const {
+ CopyASCIItoUTF16(mContentLanguage, aContentLanguage);
+ }
+
+ // The states BidiEnabled and MathMLEnabled should persist across multiple
+ // views (screen, print) of the same document.
+
+ /**
+ * Check if the document contains bidi data.
+ * If so, we have to apply the Unicode Bidi Algorithm.
+ */
+ bool GetBidiEnabled() const { return mBidiEnabled; }
+
+ /**
+ * Indicate the document contains bidi data.
+ * Currently, we cannot disable bidi, because once bidi is enabled,
+ * it affects a frame model irreversibly, and plays even though
+ * the document no longer contains bidi data.
+ */
+ void SetBidiEnabled() { mBidiEnabled = true; }
+
+ void SetMathMLEnabled() { mMathMLEnabled = true; }
+
+ /**
+ * Ask this document whether it's the initial document in its window.
+ */
+ bool IsInitialDocument() const { return mIsInitialDocumentInWindow; }
+
+ /**
+ * Ask this document whether it has ever been a initial document in its
+ * window.
+ */
+ bool IsEverInitialDocument() const { return mIsEverInitialDocumentInWindow; }
+
+ /**
+ * Tell this document that it's the initial document in its window. See
+ * comments on mIsInitialDocumentInWindow for when this should be called.
+ */
+ void SetIsInitialDocument(bool aIsInitialDocument);
+
+ void SetLoadedAsData(bool aLoadedAsData, bool aConsiderForMemoryReporting);
+
+ TimeStamp GetLoadingOrRestoredFromBFCacheTimeStamp() const {
+ return mLoadingOrRestoredFromBFCacheTimeStamp;
+ }
+ void SetLoadingOrRestoredFromBFCacheTimeStampToNow() {
+ mLoadingOrRestoredFromBFCacheTimeStamp = TimeStamp::Now();
+ }
+
+ /**
+ * Normally we assert if a runnable labeled with one DocGroup touches data
+ * from another DocGroup. Calling IgnoreDocGroupMismatches() on a document
+ * means that we can touch that document from any DocGroup without asserting.
+ */
+ void IgnoreDocGroupMismatches() { mIgnoreDocGroupMismatches = true; }
+
+ /**
+ * Get the bidi options for this document.
+ * @see nsBidiUtils.h
+ */
+ uint32_t GetBidiOptions() const { return mBidiOptions; }
+
+ /**
+ * Set the bidi options for this document. This just sets the bits;
+ * callers are expected to take action as needed if they want this
+ * change to actually change anything immediately.
+ * @see nsBidiUtils.h
+ */
+ void SetBidiOptions(uint32_t aBidiOptions) { mBidiOptions = aBidiOptions; }
+
+ /**
+ * Set CSP flag for this document.
+ */
+ void SetHasCSP(bool aHasCSP) { mHasCSP = aHasCSP; }
+
+ /**
+ * Set unsafe-inline CSP flag for this document.
+ */
+ void SetHasUnsafeInlineCSP(bool aHasUnsafeInlineCSP) {
+ mHasUnsafeInlineCSP = aHasUnsafeInlineCSP;
+ }
+
+ /**
+ * Set unsafe-eval CSP flag for this document.
+ */
+ void SetHasUnsafeEvalCSP(bool aHasUnsafeEvalCSP) {
+ mHasUnsafeEvalCSP = aHasUnsafeEvalCSP;
+ }
+
+ /**
+ * Returns true if the document holds a CSP
+ * delivered through an HTTP Header.
+ */
+ bool GetHasCSPDeliveredThroughHeader() {
+ return mHasCSPDeliveredThroughHeader;
+ }
+
+ /**
+ * Return a promise which resolves to the content blocking events.
+ */
+ using GetContentBlockingEventsPromise = MozPromise<uint32_t, bool, true>;
+ [[nodiscard]] RefPtr<GetContentBlockingEventsPromise>
+ GetContentBlockingEvents();
+
+ /**
+ * Get the sandbox flags for this document.
+ * @see nsSandboxFlags.h for the possible flags
+ */
+ uint32_t GetSandboxFlags() const { return mSandboxFlags; }
+
+ Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> GetEmbedderPolicy() const {
+ return mEmbedderPolicy;
+ }
+
+ void SetEmbedderPolicy(
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP) {
+ mEmbedderPolicy = aCOEP;
+ }
+
+ /**
+ * Get string representation of sandbox flags (null if no flags are set)
+ */
+ void GetSandboxFlagsAsString(nsAString& aFlags);
+
+ /**
+ * Set the sandbox flags for this document.
+ * @see nsSandboxFlags.h for the possible flags
+ */
+ void SetSandboxFlags(uint32_t sandboxFlags) { mSandboxFlags = sandboxFlags; }
+
+ /**
+ * Called when the document was decoded as UTF-8 and decoder encountered no
+ * errors.
+ */
+ void EnableEncodingMenu() { mEncodingMenuDisabled = false; }
+
+ /**
+ * Called to disable client access to cookies through the document.cookie API
+ * from user JavaScript code.
+ */
+ void DisableCookieAccess() { mDisableCookieAccess = true; }
+
+ void SetLinkHandlingEnabled(bool aValue) { mLinksEnabled = aValue; }
+ bool LinkHandlingEnabled() { return mLinksEnabled; }
+
+ /**
+ * Set compatibility mode for this document
+ */
+ void SetCompatibilityMode(nsCompatibility aMode);
+
+ /**
+ * Called to disable client access to document.write() API from user
+ * JavaScript code.
+ */
+ void SetDocWriteDisabled(bool aDisabled) { mDisableDocWrite = aDisabled; }
+
+ /**
+ * Whether a document.write() call is in progress.
+ */
+ bool IsWriting() const { return mWriteLevel != uint32_t(0); }
+
+ /**
+ * Access HTTP header data (this may also get set from other
+ * sources, like HTML META tags).
+ */
+ void GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const;
+ void SetHeaderData(nsAtom* aheaderField, const nsAString& aData);
+
+ /**
+ * Set Early Hint data, moves the arrays into the function, leaving the
+ * passed variables empty
+ */
+ void SetEarlyHints(nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints);
+ const nsTArray<net::EarlyHintConnectArgs>& GetEarlyHints() const {
+ return mEarlyHints;
+ }
+
+ /**
+ * Create a new presentation shell that will use aContext for its
+ * presentation context (presentation contexts <b>must not</b> be
+ * shared among multiple presentation shells). The caller of this
+ * method is responsible for calling BeginObservingDocument() on the
+ * presshell if the presshell should observe document mutations.
+ */
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<PresShell> CreatePresShell(
+ nsPresContext* aContext, nsViewManager* aViewManager);
+ void DeletePresShell();
+
+ PresShell* GetPresShell() const {
+ return GetBFCacheEntry() ? nullptr : mPresShell;
+ }
+
+ inline PresShell* GetObservingPresShell() const;
+
+ // Return whether the presshell for this document is safe to flush.
+ bool IsSafeToFlush() const;
+
+ inline nsPresContext* GetPresContext() const;
+
+ bool HasShellOrBFCacheEntry() const { return mPresShell || mBFCacheEntry; }
+
+ // Instead using this method, what you probably want is
+ // RemoveFromBFCacheSync() as we do in MessagePort and BroadcastChannel.
+ void DisallowBFCaching(uint32_t aStatus = BFCacheStatus::NOT_ALLOWED);
+
+ bool IsBFCachingAllowed() const { return !mBFCacheDisallowed; }
+
+ // Accepts null to clear the BFCache entry too.
+ void SetBFCacheEntry(nsIBFCacheEntry* aEntry);
+
+ nsIBFCacheEntry* GetBFCacheEntry() const { return mBFCacheEntry; }
+
+ // Removes this document from the BFCache, if it is cached, and returns
+ // true if it was.
+ bool RemoveFromBFCacheSync();
+
+ /**
+ * Return the parent document of this document. Will return null
+ * unless this document is within a compound document and has a
+ * parent. Note that this parent chain may cross chrome boundaries.
+ */
+ Document* GetInProcessParentDocument() const { return mParentDocument; }
+
+ /**
+ * Set the parent document of this document.
+ */
+ void SetParentDocument(Document* aParent) {
+ mParentDocument = aParent;
+ if (aParent) {
+ RecomputeResistFingerprinting();
+ mIgnoreDocGroupMismatches = aParent->mIgnoreDocGroupMismatches;
+ if (!mIsDevToolsDocument) {
+ mIsDevToolsDocument = mParentDocument->IsDevToolsDocument();
+ }
+ }
+ }
+
+ void SetCurrentContextPaint(const SVGContextPaint* aContextPaint) {
+ mCurrentContextPaint = aContextPaint;
+ }
+
+ const SVGContextPaint* GetCurrentContextPaint() const {
+ return mCurrentContextPaint;
+ }
+
+ /**
+ * Are plugins allowed in this document ?
+ */
+ bool GetAllowPlugins();
+
+ /**
+ * Set the sub document for aContent to aSubDoc.
+ */
+ nsresult SetSubDocumentFor(Element* aContent, Document* aSubDoc);
+
+ /**
+ * Get the sub document for aContent
+ */
+ Document* GetSubDocumentFor(nsIContent* aContent) const;
+
+ /**
+ * Get the content node for which this document is a sub document.
+ */
+ Element* GetEmbedderElement() const;
+
+ /**
+ * Return the doctype for this document.
+ */
+ DocumentType* GetDoctype() const;
+
+ /**
+ * Return the root element for this document.
+ */
+ Element* GetRootElement() const;
+
+ Selection* GetSelection(ErrorResult& aRv);
+
+ void MakeBrowsingContextNonSynthetic();
+ nsresult HasStorageAccessSync(bool& aHasStorageAccess);
+ already_AddRefed<Promise> HasStorageAccess(ErrorResult& aRv);
+
+ StorageAccessAPIHelper::PerformPermissionGrant CreatePermissionGrantPromise(
+ nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
+ bool aHasUserInteraction, const Maybe<nsCString>& aTopLevelBaseDomain);
+
+ already_AddRefed<Promise> RequestStorageAccess(ErrorResult& aRv);
+
+ already_AddRefed<Promise> RequestStorageAccessForOrigin(
+ const nsAString& aThirdPartyOrigin, const bool aRequireUserInteraction,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> RequestStorageAccessUnderSite(
+ const nsAString& aSerializedSite, ErrorResult& aRv);
+ already_AddRefed<Promise> CompleteStorageAccessRequestFromSite(
+ const nsAString& aSerializedOrigin, ErrorResult& aRv);
+
+ bool UseRegularPrincipal() const;
+
+ /**
+ * Gets the event target to dispatch key events to if there is no focused
+ * content in the document.
+ */
+ virtual Element* GetUnfocusedKeyEventTarget();
+
+ /**
+ * Retrieve information about the viewport as a data structure.
+ * This will return information in the viewport META data section
+ * of the document. This can be used in lieu of ProcessViewportInfo(),
+ * which places the viewport information in the document header instead
+ * of returning it directly.
+ *
+ * @param aDisplaySize size of the on-screen display area for this
+ * document, in device pixels.
+ *
+ * NOTE: If the site is optimized for mobile (via the doctype), this
+ * will return viewport information that specifies default information.
+ */
+ nsViewportInfo GetViewportInfo(const ScreenIntSize& aDisplaySize);
+
+ void SetMetaViewportData(UniquePtr<ViewportMetaData> aData);
+
+ // Returns a ViewportMetaData for this document.
+ ViewportMetaData GetViewportMetaData() const;
+
+ /**
+ * True iff this doc will ignore manual character encoding overrides.
+ */
+ virtual bool WillIgnoreCharsetOverride() { return true; }
+
+ /**
+ * Return whether the document was created by a srcdoc iframe.
+ */
+ bool IsSrcdocDocument() const { return mIsSrcdocDocument; }
+
+ /**
+ * Sets whether the document was created by a srcdoc iframe.
+ */
+ void SetIsSrcdocDocument(bool aIsSrcdocDocument) {
+ mIsSrcdocDocument = aIsSrcdocDocument;
+ }
+
+ /*
+ * Gets the srcdoc string from within the channel (assuming both exist).
+ * Returns a void string if this isn't a srcdoc document or if
+ * the channel has not been set.
+ */
+ nsresult GetSrcdocData(nsAString& aSrcdocData);
+
+ already_AddRefed<AnonymousContent> InsertAnonymousContent(
+ Element& aElement, bool aForce, ErrorResult& aError);
+ void RemoveAnonymousContent(AnonymousContent& aContent);
+ /**
+ * If aNode is a descendant of anonymous content inserted by
+ * InsertAnonymousContent, this method returns the root element of the
+ * inserted anonymous content (in other words, the clone of the aElement
+ * that was passed to InsertAnonymousContent).
+ */
+ Element* GetAnonRootIfInAnonymousContentContainer(nsINode* aNode) const;
+ nsTArray<RefPtr<AnonymousContent>>& GetAnonymousContents() {
+ return mAnonymousContents;
+ }
+
+ TimeStamp GetPageUnloadingEventTimeStamp() const {
+ if (!mParentDocument) {
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ TimeStamp parentTimeStamp(
+ mParentDocument->GetPageUnloadingEventTimeStamp());
+ if (parentTimeStamp.IsNull()) {
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ if (!mPageUnloadingEventTimeStamp ||
+ parentTimeStamp < mPageUnloadingEventTimeStamp) {
+ return parentTimeStamp;
+ }
+
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ void NotifyLayerManagerRecreated();
+
+ /**
+ * Add an SVG element to the list of elements that need
+ * their mapped attributes resolved to a Servo declaration block.
+ *
+ * These are weak pointers, please manually unschedule them when an element
+ * is removed.
+ */
+ void ScheduleSVGForPresAttrEvaluation(SVGElement* aSVG) {
+ mLazySVGPresElements.Insert(aSVG);
+ }
+
+ // Unschedule an element scheduled by ScheduleFrameRequestCallback (e.g. for
+ // when it is destroyed)
+ void UnscheduleSVGForPresAttrEvaluation(SVGElement* aSVG) {
+ mLazySVGPresElements.Remove(aSVG);
+ }
+
+ // Resolve all SVG pres attrs scheduled in ScheduleSVGForPresAttrEvaluation
+ void ResolveScheduledSVGPresAttrs();
+
+ Maybe<ClientInfo> GetClientInfo() const;
+ Maybe<ClientState> GetClientState() const;
+ Maybe<ServiceWorkerDescriptor> GetController() const;
+
+ // Returns the size of the mBlockedNodesByClassifier array.
+ //
+ // This array contains nodes that have been blocked to prevent user tracking,
+ // fingerprinting, cryptomining, etc. They most likely have had their
+ // nsIChannel canceled by the URL classifier (Safebrowsing).
+ //
+ // A script can subsequently use GetBlockedNodesByClassifier()
+ // to get a list of references to these nodes.
+ //
+ // Note:
+ // This expresses how many tracking nodes have been blocked for this document
+ // since its beginning, not how many of them are still around in the DOM tree.
+ // Weak references to blocked nodes are added in the mBlockedNodesByClassifier
+ // array but they are not removed when those nodes are removed from the tree
+ // or even garbage collected.
+ long BlockedNodeByClassifierCount() const {
+ return mBlockedNodesByClassifier.Length();
+ }
+
+ //
+ // Returns strong references to mBlockedNodesByClassifier. (Document.h)
+ //
+ // This array contains nodes that have been blocked to prevent
+ // user tracking. They most likely have had their nsIChannel
+ // canceled by the URL classifier (Safebrowsing).
+ //
+ already_AddRefed<nsSimpleContentList> BlockedNodesByClassifier() const;
+
+ // Helper method that returns true if the document has storage-access sandbox
+ // flag.
+ bool StorageAccessSandboxed() const;
+
+ // Helper method that returns true if storage access API is enabled and
+ // the passed flag has storage-access sandbox flag.
+ static bool StorageAccessSandboxed(uint32_t aSandboxFlags);
+
+ // Returns the cookie jar settings for this and sub contexts.
+ nsICookieJarSettings* CookieJarSettings();
+
+ // Returns whether this document has the storage access permission.
+ bool HasStorageAccessPermissionGranted();
+
+ // Returns whether the storage access permission of the document is granted by
+ // the allow list.
+ bool HasStorageAccessPermissionGrantedByAllowList();
+
+ // Increments the document generation.
+ inline void Changed() { ++mGeneration; }
+
+ // Returns the current generation.
+ inline int32_t GetGeneration() const { return mGeneration; }
+
+ // Adds cached sizes values to aSizes if there's any
+ // cached value and if the document generation hasn't
+ // changed since the cache was created.
+ // Returns true if sizes were added.
+ bool GetCachedSizes(nsTabSizes* aSizes);
+
+ // Sets the cache sizes for the current generation.
+ void SetCachedSizes(nsTabSizes* aSizes);
+
+ /**
+ * Should be called when an element's editable changes as a result of
+ * changing its contentEditable attribute/property.
+ *
+ * The change should be +1 if the contentEditable attribute/property was
+ * changed to true, -1 if it was changed to false.
+ */
+ void ChangeContentEditableCount(Element*, int32_t aChange);
+ MOZ_CAN_RUN_SCRIPT void DeferredContentEditableCountChange(Element*);
+
+ enum class EditingState : int8_t {
+ eTearingDown = -2,
+ eSettingUp = -1,
+ eOff = 0,
+ eDesignMode,
+ eContentEditable
+ };
+
+ /**
+ * Returns the editing state of the document (not editable, contentEditable or
+ * designMode).
+ */
+ EditingState GetEditingState() const { return mEditingState; }
+
+ /**
+ * Returns whether the document is editable.
+ */
+ bool IsEditingOn() const {
+ return GetEditingState() == EditingState::eDesignMode ||
+ GetEditingState() == EditingState::eContentEditable;
+ }
+
+ class MOZ_STACK_CLASS nsAutoEditingState {
+ public:
+ nsAutoEditingState(Document* aDoc, EditingState aState)
+ : mDoc(aDoc), mSavedState(aDoc->mEditingState) {
+ aDoc->mEditingState = aState;
+ }
+ ~nsAutoEditingState() { mDoc->mEditingState = mSavedState; }
+
+ private:
+ RefPtr<Document> mDoc;
+ EditingState mSavedState;
+ };
+ friend class nsAutoEditingState;
+
+ /**
+ * Set the editing state of the document. Don't use this if you want
+ * to enable/disable editing, call EditingStateChanged() or
+ * SetDesignMode().
+ */
+ void SetEditingState(EditingState aState) { mEditingState = aState; }
+
+ /**
+ * Called when this Document's editor is destroyed.
+ */
+ void TearingDownEditor();
+
+ void SetKeyPressEventModel(uint16_t aKeyPressEventModel);
+
+ // Gets the next form number.
+ //
+ // Used by nsContentUtils::GenerateStateKey to get a unique number for each
+ // parser inserted form element.
+ int32_t GetNextFormNumber() { return mNextFormNumber++; }
+
+ // Gets the next form control number.
+ //
+ // Used by nsContentUtils::GenerateStateKey to get a unique number for each
+ // parser inserted form control element.
+ int32_t GetNextControlNumber() { return mNextControlNumber++; }
+
+ PreloadService& Preloads() { return mPreloadService; }
+
+ bool HasThirdPartyChannel();
+
+ bool ShouldIncludeInTelemetry(bool aAllowExtensionURIs);
+
+ void AddMediaElementWithMSE();
+ void RemoveMediaElementWithMSE();
+
+ void DoNotifyPossibleTitleChange();
+
+ void InitFeaturePolicy();
+ nsresult InitFeaturePolicy(nsIChannel* aChannel);
+
+ void EnsureNotEnteringAndExitFullscreen();
+
+ protected:
+ friend class nsUnblockOnloadEvent;
+
+ nsresult InitCSP(nsIChannel* aChannel);
+ nsresult InitCOEP(nsIChannel* aChannel);
+
+ nsresult InitReferrerInfo(nsIChannel* aChannel);
+
+ void PostUnblockOnloadEvent();
+
+ void DoUnblockOnload();
+
+ void RetrieveRelevantHeaders(nsIChannel* aChannel);
+
+ void TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
+ NotNull<const Encoding*>& aEncoding,
+ nsHtml5TreeOpExecutor* aExecutor);
+
+ MOZ_CAN_RUN_SCRIPT void DispatchContentLoadedEvents();
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DispatchPageTransition(
+ EventTarget* aDispatchTarget, const nsAString& aType, bool aInFrameSwap,
+ bool aPersisted, bool aOnlySystemGroup);
+
+ // Call this before the document does something that will unbind all content.
+ // That will stop us from doing a lot of work as each element is removed.
+ void DestroyElementMaps();
+
+ Element* GetRootElementInternal() const;
+
+ void SetPageUnloadingEventTimeStamp() {
+ MOZ_ASSERT(!mPageUnloadingEventTimeStamp);
+ mPageUnloadingEventTimeStamp = TimeStamp::NowLoRes();
+ }
+
+ void CleanUnloadEventsTimeStamp() {
+ MOZ_ASSERT(mPageUnloadingEventTimeStamp);
+ mPageUnloadingEventTimeStamp = TimeStamp();
+ }
+
+ /**
+ * Clears any Servo element data stored on Elements in the document.
+ */
+ void ClearStaleServoData();
+
+ /**
+ * Do the tree-disconnection that ResetToURI and document.open need to do.
+ */
+ void DisconnectNodeTree();
+
+ /**
+ * MaybeDispatchCheckKeyPressEventModelEvent() dispatches
+ * "CheckKeyPressEventModel" event to check whether we should dispatch
+ * keypress events in confluent model or split model. This should be
+ * called only when mEditingState is changed to eDesignMode or
+ * eConentEditable at first time.
+ */
+ void MaybeDispatchCheckKeyPressEventModelEvent();
+
+ /* Midas implementation */
+ nsCommandManager* GetMidasCommandManager();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult TurnEditingOff();
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because this is called from all sorts
+ // of places, and I'm pretty sure the exact ExecCommand call it
+ // makes cannot actually run script.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult EditingStateChanged();
+
+ void MaybeEditingStateChanged();
+
+ private:
+ class SelectorCacheKey {
+ public:
+ explicit SelectorCacheKey(const nsACString& aString) : mKey(aString) {
+ MOZ_COUNT_CTOR(SelectorCacheKey);
+ }
+
+ nsCString mKey;
+ nsExpirationState mState;
+
+ nsExpirationState* GetExpirationState() { return &mState; }
+
+ MOZ_COUNTED_DTOR(SelectorCacheKey)
+ };
+
+ class SelectorCacheKeyDeleter;
+
+ public:
+ class SelectorCache final : public nsExpirationTracker<SelectorCacheKey, 4> {
+ public:
+ using SelectorList = UniquePtr<StyleSelectorList>;
+ using Table = nsTHashMap<nsCStringHashKey, SelectorList>;
+
+ explicit SelectorCache(nsIEventTarget* aEventTarget);
+ void NotifyExpired(SelectorCacheKey*) final;
+
+ // We do not call MarkUsed because it would just slow down lookups and
+ // because we're OK expiring things after a few seconds even if they're
+ // being used. Returns whether we actually had an entry for aSelector.
+ //
+ // If we have an entry and the selector list returned has a null
+ // StyleSelectorList*, that indicates that aSelector has already been
+ // parsed and is not a syntactically valid selector.
+ template <typename F>
+ StyleSelectorList* GetListOrInsertFrom(const nsACString& aSelector,
+ F&& aFrom) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mTable.LookupOrInsertWith(aSelector, std::forward<F>(aFrom)).get();
+ }
+
+ ~SelectorCache();
+
+ private:
+ Table mTable;
+ };
+
+ SelectorCache& GetSelectorCache() {
+ if (!mSelectorCache) {
+ mSelectorCache =
+ MakeUnique<SelectorCache>(EventTargetFor(TaskCategory::Other));
+ }
+ return *mSelectorCache;
+ }
+ // Get the root <html> element, or return null if there isn't one (e.g.
+ // if the root isn't <html>)
+ Element* GetHtmlElement() const;
+ // Returns the first child of GetHtmlContent which has the given tag,
+ // or nullptr if that doesn't exist.
+ Element* GetHtmlChildElement(nsAtom* aTag);
+ // Get the canonical <body> element, or return null if there isn't one (e.g.
+ // if the root isn't <html> or if the <body> isn't there)
+ HTMLBodyElement* GetBodyElement();
+ // Get the canonical <head> element, or return null if there isn't one (e.g.
+ // if the root isn't <html> or if the <head> isn't there)
+ Element* GetHeadElement() { return GetHtmlChildElement(nsGkAtoms::head); }
+ // Get the "body" in the sense of document.body: The first <body> or
+ // <frameset> that's a child of a root <html>
+ nsGenericHTMLElement* GetBody();
+ // Set the "body" in the sense of document.body.
+ void SetBody(nsGenericHTMLElement* aBody, ErrorResult& rv);
+ // Get the "head" element in the sense of document.head.
+ HTMLSharedElement* GetHead();
+
+ ServoStyleSet* StyleSetForPresShellOrMediaQueryEvaluation() const {
+ return mStyleSet.get();
+ }
+
+ // ShadowRoot has APIs that can change styles. This notifies the shell that
+ // stlyes applicable in the shadow tree have potentially changed.
+ void RecordShadowStyleChange(ShadowRoot&);
+
+ // Needs to be called any time the applicable style can has changed, in order
+ // to schedule a style flush and setup all the relevant state.
+ void ApplicableStylesChanged();
+
+ // Whether we filled the style set with any style sheet. Only meant to be used
+ // from DocumentOrShadowRoot::Traverse.
+ bool StyleSetFilled() const { return mStyleSetFilled; }
+
+ /**
+ * Accessors to the collection of stylesheets owned by this document.
+ * Style sheets are ordered, most significant last.
+ */
+
+ void InsertSheetAt(size_t aIndex, StyleSheet&);
+
+ /**
+ * Add a stylesheet to the document
+ *
+ * TODO(emilio): This is only used by parts of editor that are no longer in
+ * use by m-c or c-c, so remove.
+ */
+ void AddStyleSheet(StyleSheet* aSheet) {
+ MOZ_ASSERT(aSheet);
+ InsertSheetAt(SheetCount(), *aSheet);
+ }
+
+ /**
+ * Notify the document that the applicable state of the sheet changed
+ * and that observers should be notified and style sets updated
+ */
+ void StyleSheetApplicableStateChanged(StyleSheet&);
+
+ void PostStyleSheetApplicableStateChangeEvent(StyleSheet&);
+
+ enum additionalSheetType {
+ eAgentSheet,
+ eUserSheet,
+ eAuthorSheet,
+ AdditionalSheetTypeCount
+ };
+
+ nsresult LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI);
+ nsresult AddAdditionalStyleSheet(additionalSheetType aType,
+ StyleSheet* aSheet);
+ void RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* sheetURI);
+
+ StyleSheet* GetFirstAdditionalAuthorSheet() {
+ return mAdditionalSheets[eAuthorSheet].SafeElementAt(0);
+ }
+
+ /**
+ * Returns the index that aSheet should be inserted at to maintain document
+ * ordering.
+ */
+ size_t FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet);
+
+ /**
+ * Get this document's CSSLoader. This is guaranteed to not return null.
+ */
+ css::Loader* CSSLoader() const { return mCSSLoader; }
+
+ /**
+ * Get this document's StyleImageLoader. This is guaranteed to not return
+ * null.
+ */
+ css::ImageLoader* StyleImageLoader() const { return mStyleImageLoader; }
+
+ /**
+ * Get the channel that was passed to StartDocumentLoad or Reset for this
+ * document. Note that this may be null in some cases (eg if
+ * StartDocumentLoad or Reset were never called)
+ */
+ nsIChannel* GetChannel() const { return mChannel; }
+
+ /**
+ * Get this document's attribute stylesheet. May return null if
+ * there isn't one.
+ */
+ nsHTMLStyleSheet* GetAttributeStyleSheet() const { return mAttrStyleSheet; }
+
+ /**
+ * Get this document's inline style sheet. May return null if there
+ * isn't one
+ */
+ nsHTMLCSSStyleSheet* GetInlineStyleSheet() const {
+ return mStyleAttrStyleSheet;
+ }
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject);
+
+ /**
+ * Get/set the object from which the context for the event/script handling can
+ * be got. Normally GetScriptHandlingObject() returns the same object as
+ * GetScriptGlobalObject(), but if the document is loaded as data,
+ * non-null may be returned, even if GetScriptGlobalObject() returns null.
+ * aHasHadScriptHandlingObject is set true if document has had the object
+ * for event/script handling. Do not process any events/script if the method
+ * returns null, but aHasHadScriptHandlingObject is true.
+ */
+ nsIScriptGlobalObject* GetScriptHandlingObject(
+ bool& aHasHadScriptHandlingObject) const {
+ aHasHadScriptHandlingObject = mHasHadScriptHandlingObject;
+ return mScriptGlobalObject ? mScriptGlobalObject.get()
+ : GetScriptHandlingObjectInternal();
+ }
+ void SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject);
+
+ /**
+ * Get the object that is used as the scope for all of the content
+ * wrappers whose owner document is this document. Unlike the script global
+ * object, this will only return null when the global object for this
+ * document is truly gone. Use this object when you're trying to find a
+ * content wrapper in XPConnect.
+ */
+ nsIGlobalObject* GetScopeObject() const;
+ void SetScopeObject(nsIGlobalObject* aGlobal);
+
+ /**
+ * Return the window containing the document (the outer window).
+ */
+ nsPIDOMWindowOuter* GetWindow() const {
+ return mWindow ? mWindow->GetOuterWindow() : GetWindowInternal();
+ }
+
+ bool IsInBackgroundWindow() const {
+ auto* outer = mWindow ? mWindow->GetOuterWindow() : nullptr;
+ return outer && outer->IsBackground();
+ }
+
+ /**
+ * Return the inner window used as the script compilation scope for
+ * this document. If you're not absolutely sure you need this, use
+ * GetWindow().
+ */
+ nsPIDOMWindowInner* GetInnerWindow() const {
+ return mRemovedFromDocShell ? nullptr : mWindow;
+ }
+
+ /**
+ * Return the outer window ID.
+ */
+ uint64_t OuterWindowID() const {
+ nsPIDOMWindowOuter* window = GetWindow();
+ return window ? window->WindowID() : 0;
+ }
+
+ /**
+ * Return the inner window ID.
+ */
+ uint64_t InnerWindowID() const {
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ return window ? window->WindowID() : 0;
+ }
+
+ /**
+ * Return WindowGlobalChild that is associated with the inner window.
+ */
+ WindowGlobalChild* GetWindowGlobalChild() {
+ return GetInnerWindow() ? GetInnerWindow()->GetWindowGlobalChild()
+ : nullptr;
+ }
+
+ /**
+ * Return WindowContext associated with the inner window.
+ */
+ WindowContext* GetWindowContext() const {
+ return GetInnerWindow() ? GetInnerWindow()->GetWindowContext() : nullptr;
+ }
+
+ bool IsTopLevelWindowInactive() const;
+
+ /**
+ * Get the script loader for this document
+ */
+ dom::ScriptLoader* ScriptLoader() { return mScriptLoader; }
+
+ /**
+ * Add/Remove an element to the document's id and name hashes
+ */
+ void AddToIdTable(Element* aElement, nsAtom* aId);
+ void RemoveFromIdTable(Element* aElement, nsAtom* aId);
+ void AddToNameTable(Element* aElement, nsAtom* aName);
+ void RemoveFromNameTable(Element* aElement, nsAtom* aName);
+
+ /**
+ * Returns all elements in the top layer in the insertion order.
+ */
+ nsTArray<Element*> GetTopLayer() const;
+
+ bool TopLayerContains(Element&) const;
+
+ // Do the "fullscreen element ready check" from the fullscreen spec.
+ // It returns true if the given element is allowed to go into fullscreen.
+ // It is responsive to dispatch "fullscreenerror" event when necessary.
+ bool FullscreenElementReadyCheck(FullscreenRequest&);
+
+ /**
+ * When this is called on content process, this asynchronously requests that
+ * the document make aElement the fullscreen element, and move into fullscreen
+ * mode. The current fullscreen element (if any) is pushed onto the top layer,
+ * and it can be returned to fullscreen status by calling
+ * RestorePreviousFullscreenState().
+ * If on chrome process, this is synchronously.
+ *
+ * Note that requesting fullscreen in a document also makes the element which
+ * contains this document in this document's parent document fullscreen. i.e.
+ * the <iframe> or <browser> that contains this document is also mode
+ * fullscreen. This happens recursively in all ancestor documents.
+ */
+ void RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
+ bool aApplyFullscreenDirectly = false);
+
+ private:
+ void RequestFullscreenInContentProcess(UniquePtr<FullscreenRequest> aRequest,
+ bool aApplyFullscreenDirectly);
+ void RequestFullscreenInParentProcess(UniquePtr<FullscreenRequest> aRequest,
+ bool aApplyFullscreenDirectly);
+
+ // Pushes aElement onto the top layer
+ void TopLayerPush(Element&);
+
+ // Removes the topmost element for which aPredicate returns true from the top
+ // layer. The removed element, if any, is returned.
+ Element* TopLayerPop(FunctionRef<bool(Element*)> aPredicate);
+
+ // Removes the given element from the top layer. The removed element, if any,
+ // is returned.
+ Element* TopLayerPop(Element&);
+
+ MOZ_CAN_RUN_SCRIPT bool TryAutoFocusCandidate(Element& aElement);
+
+ public:
+ // Removes all the elements with fullscreen flag set from the top layer, and
+ // clears their fullscreen flag.
+ void CleanupFullscreenState();
+
+ // Pops the fullscreen element from the top layer and clears its
+ // fullscreen flag. Returns whether there was any fullscreen element.
+ enum class UpdateViewport : bool { No, Yes };
+ bool PopFullscreenElement(UpdateViewport = UpdateViewport::Yes);
+
+ // Pushes the given element into the top of top layer and set fullscreen
+ // flag.
+ void SetFullscreenElement(Element&);
+
+ // Whether we has pending fullscreen request.
+ bool HasPendingFullscreenRequests();
+
+ // Cancel the dialog element if the document is blocked by the dialog
+ void TryCancelDialog();
+
+ void AddModalDialog(HTMLDialogElement&);
+ void RemoveModalDialog(HTMLDialogElement&);
+
+ /**
+ * Called when a frame in a child process has entered fullscreen or when a
+ * fullscreen frame in a child process changes to another origin.
+ * aFrameElement is the frame element which contains the child-process
+ * fullscreen document.
+ */
+ void RemoteFrameFullscreenChanged(Element* aFrameElement);
+
+ /**
+ * Called when a frame in a remote child document has rolled back fullscreen
+ * so that all its top layer are empty; we must continue the
+ * rollback in this parent process' doc tree branch which is fullscreen.
+ * Note that only one branch of the document tree can have its documents in
+ * fullscreen state at one time. We're in inconsistent state if a
+ * fullscreen document has a parent and that parent isn't fullscreen. We
+ * preserve this property across process boundaries.
+ */
+ void RemoteFrameFullscreenReverted();
+
+ /**
+ * Restores the previous fullscreen element to fullscreen status. If there
+ * is no former fullscreen element, this exits fullscreen, moving the
+ * top-level browser window out of fullscreen mode.
+ */
+ void RestorePreviousFullscreenState(UniquePtr<FullscreenExit>);
+
+ /**
+ * Returns true if this document is a fullscreen leaf document, i.e. it
+ * is in fullscreen mode and has no fullscreen children.
+ */
+ bool IsFullscreenLeaf();
+
+ /**
+ * Returns the document which is at the root of this document's branch
+ * in the in-process document tree. Returns nullptr if the document isn't
+ * fullscreen.
+ */
+ Document* GetFullscreenRoot();
+
+ size_t CountFullscreenElements() const;
+
+ /**
+ * Sets the fullscreen root to aRoot. This stores a weak reference to aRoot
+ * in this document.
+ */
+ void SetFullscreenRoot(Document* aRoot);
+
+ /**
+ * Synchronously cleans up the fullscreen state on the given document.
+ *
+ * Calling this without performing fullscreen transition could lead
+ * to undesired effect (the transition happens after document state
+ * flips), hence it should only be called either by nsGlobalWindow
+ * when we have performed the transition, or when it is necessary to
+ * clean up the state immediately. Otherwise, AsyncExitFullscreen()
+ * should be called instead.
+ *
+ * aDocument must not be null.
+ */
+ static void ExitFullscreenInDocTree(Document* aDocument);
+
+ /**
+ * Ask the document to exit fullscreen state asynchronously.
+ *
+ * Different from ExitFullscreenInDocTree(), this allows the window
+ * to perform fullscreen transition first if any.
+ *
+ * If aDocument is null, it will exit fullscreen from all documents
+ * in all windows.
+ */
+ static void AsyncExitFullscreen(Document* aDocument);
+
+ /**
+ * Handles any pending fullscreen in aDocument or its subdocuments.
+ *
+ * Returns whether there is any fullscreen request handled.
+ */
+ static bool HandlePendingFullscreenRequests(Document* aDocument);
+
+ /**
+ * Clear pending fullscreen in aDocument.
+ */
+ static void ClearPendingFullscreenRequests(Document* aDocument);
+
+ // ScreenOrientation related APIs
+
+ void ClearOrientationPendingPromise();
+ bool SetOrientationPendingPromise(Promise* aPromise);
+ Promise* GetOrientationPendingPromise() const {
+ return mOrientationPendingPromise;
+ }
+
+ //----------------------------------------------------------------------
+
+ // Document notification API's
+
+ /**
+ * Add a new observer of document change notifications. Whenever
+ * content is changed, appended, inserted or removed the observers are
+ * informed. An observer that is already observing the document must
+ * not be added without being removed first.
+ */
+ void AddObserver(nsIDocumentObserver* aObserver);
+
+ /**
+ * Remove an observer of document change notifications. This will
+ * return false if the observer cannot be found.
+ */
+ bool RemoveObserver(nsIDocumentObserver* aObserver);
+
+ // Observation hooks used to propagate notifications to document observers.
+ // BeginUpdate must be called before any batch of modifications of the
+ // content model or of style data, EndUpdate must be called afterward.
+ // To make this easy and painless, use the mozAutoDocUpdate helper class.
+ void BeginUpdate();
+ void EndUpdate();
+ uint32_t UpdateNestingLevel() { return mUpdateNestLevel; }
+
+ void BeginLoad();
+ virtual void EndLoad();
+
+ enum ReadyState {
+ READYSTATE_UNINITIALIZED = 0,
+ READYSTATE_LOADING = 1,
+ READYSTATE_INTERACTIVE = 3,
+ READYSTATE_COMPLETE = 4
+ };
+ // Set the readystate of the document. If aUpdateTimingInformation is true,
+ // this will record relevant timestamps in the document's performance timing.
+ // Some consumers (document.open is the only one right now, actually) don't
+ // want to do that, though.
+ void SetReadyStateInternal(ReadyState, bool aUpdateTimingInformation = true);
+ ReadyState GetReadyStateEnum() { return mReadyState; }
+
+ void NotifyLoading(bool aNewParentIsLoading, const ReadyState& aCurrentState,
+ ReadyState aNewState);
+
+ void NotifyAbortedLoad();
+
+ // Notify that an element changed state. This must happen under a
+ // scriptblocker but NOT within a begin/end update.
+ void ElementStateChanged(Element*, ElementState);
+
+ // Update a set of document states that may have changed.
+ // This should only be called by callers whose state is also reflected in the
+ // implementation of Document::GetDocumentState.
+ //
+ // aNotify controls whether we notify our DocumentStatesChanged observers.
+ void UpdateDocumentStates(DocumentState aMaybeChangedStates, bool aNotify);
+
+ void ResetDocumentDirection();
+
+ // Observation hooks for style data to propagate notifications
+ // to document observers
+ void RuleChanged(StyleSheet&, css::Rule*, StyleRuleChangeKind);
+ void RuleAdded(StyleSheet&, css::Rule&);
+ void RuleRemoved(StyleSheet&, css::Rule&);
+ void SheetCloned(StyleSheet&) {}
+ void ImportRuleLoaded(CSSImportRule&, StyleSheet&);
+
+ /**
+ * Flush notifications for this document and its parent documents
+ * (since those may affect the layout of this one).
+ */
+ void FlushPendingNotifications(FlushType aType);
+
+ /**
+ * Another variant of the above FlushPendingNotifications. This function
+ * takes a ChangesToFlush to specify whether throttled animations are flushed
+ * or not.
+ * If in doubt, use the above FlushPendingNotifications.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void FlushPendingNotifications(ChangesToFlush aFlush);
+
+ /**
+ * Calls FlushPendingNotifications on any external resources this document
+ * has. If this document has no external resources or is an external resource
+ * itself this does nothing. This should only be called with
+ * aType >= FlushType::Style.
+ */
+ void FlushExternalResources(FlushType aType);
+
+ void AddWorkerDocumentListener(WorkerDocumentListener* aListener);
+ void RemoveWorkerDocumentListener(WorkerDocumentListener* aListener);
+
+ // Triggers an update of <svg:use> element shadow trees.
+ void UpdateSVGUseElementShadowTrees() {
+ if (mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty()) {
+ return;
+ }
+ DoUpdateSVGUseElementShadowTrees();
+ }
+
+ /**
+ * Only to be used inside Gecko, you can't really do anything with the
+ * pointer outside Gecko anyway.
+ */
+ nsNodeInfoManager* NodeInfoManager() const { return mNodeInfoManager; }
+
+ /**
+ * Reset the document using the given channel and loadgroup. This works
+ * like ResetToURI, but also sets the document's channel to aChannel.
+ * The principal of the document will be set from the channel.
+ */
+ virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup);
+
+ /**
+ * Reset this document to aURI, aLoadGroup, aPrincipal and
+ * aPartitionedPrincipal. aURI must not be null. If aPrincipal is null, a
+ * content principal based on aURI will be used.
+ */
+ virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal);
+
+ /**
+ * Set the container (docshell) for this document. Virtual so that
+ * docshell can call it.
+ */
+ virtual void SetContainer(nsDocShell* aContainer);
+
+ /**
+ * Get the container (docshell) for this document.
+ */
+ nsISupports* GetContainer() const;
+
+ /**
+ * Get the container's load context for this document.
+ */
+ nsILoadContext* GetLoadContext() const;
+
+ /**
+ * Get docshell the for this document.
+ */
+ nsIDocShell* GetDocShell() const;
+
+ /**
+ * Set and get XML declaration. If aVersion is null there is no declaration.
+ * aStandalone takes values -1, 0 and 1 indicating respectively that there
+ * was no standalone parameter in the declaration, that it was given as no,
+ * or that it was given as yes.
+ */
+ void SetXMLDeclaration(const char16_t* aVersion, const char16_t* aEncoding,
+ const int32_t aStandalone);
+ void GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
+ nsAString& Standalone);
+
+ /**
+ * Returns the bits for the color-scheme specified by the
+ * <meta name="color-scheme">.
+ */
+ uint8_t GetColorSchemeBits() const { return mColorSchemeBits; }
+
+ /**
+ * Traverses the DOM and computes the supported color schemes as per
+ * https://html.spec.whatwg.org/#meta-color-scheme
+ */
+ void RecomputeColorScheme();
+ void AddColorSchemeMeta(HTMLMetaElement&);
+ void RemoveColorSchemeMeta(HTMLMetaElement&);
+
+ /**
+ * Returns true if this is what HTML 5 calls an "HTML document" (for example
+ * regular HTML document with Content-Type "text/html", image documents and
+ * media documents). Returns false for XHTML and any other documents parsed
+ * by the XML parser.
+ */
+ bool IsHTMLDocument() const { return mType == eHTML; }
+ bool IsHTMLOrXHTML() const { return mType == eHTML || mType == eXHTML; }
+ bool IsImageDocument() const {
+ return MediaDocumentKind() == MediaDocumentKind::Image;
+ }
+ bool IsXMLDocument() const { return !IsHTMLDocument(); }
+ bool IsSVGDocument() const { return mType == eSVG; }
+ bool IsUnstyledDocument() { return IsLoadedAsData(); }
+ bool LoadsFullXULStyleSheetUpFront() {
+ if (IsSVGDocument()) {
+ return false;
+ }
+ return AllowXULXBL();
+ }
+
+ bool IsScriptEnabled() const;
+
+ /**
+ * Returns true if this document was created from a nsXULPrototypeDocument.
+ */
+ bool LoadedFromPrototype() const { return mPrototypeDocument; }
+ /**
+ * Returns the prototype the document was created from, or null if it was not
+ * created from a prototype.
+ */
+ nsXULPrototypeDocument* GetPrototype() const { return mPrototypeDocument; }
+
+ bool IsTopLevelContentDocument() const { return mIsTopLevelContentDocument; }
+ void SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument) {
+ mIsTopLevelContentDocument = aIsTopLevelContentDocument;
+ }
+
+ bool IsContentDocument() const { return mIsContentDocument; }
+ void SetIsContentDocument(bool aIsContentDocument) {
+ mIsContentDocument = aIsContentDocument;
+ }
+
+ void ProcessMETATag(HTMLMetaElement* aMetaElement);
+ /**
+ * Create an element with the specified name, prefix and namespace ID.
+ * Returns null if element name parsing failed.
+ */
+ already_AddRefed<Element> CreateElem(const nsAString& aName, nsAtom* aPrefix,
+ int32_t aNamespaceID,
+ const nsAString* aIs = nullptr);
+
+ /**
+ * Get the security info (i.e. SSL state etc) that the document got
+ * from the channel/document that created the content of the
+ * document.
+ *
+ * @see nsIChannel
+ */
+ nsITransportSecurityInfo* GetSecurityInfo() { return mSecurityInfo; }
+
+ /**
+ * Get the channel that failed to load and resulted in an error page, if it
+ * exists. This is only relevant to error pages.
+ */
+ nsIChannel* GetFailedChannel() const { return mFailedChannel; }
+
+ /**
+ * This function checks if the document that is trying to access
+ * GetNetErrorInfo is a trusted about net error page or not.
+ */
+ static bool CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject);
+
+ /**
+ * This function checks if the document that is trying to access
+ * ReloadWithHttpsOnlyException is a trusted HTTPS only error page.
+ */
+ static bool CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
+ JSObject* aObject);
+
+ /**
+ * Get security info like error code for a failed channel. This
+ * property is only exposed to about:neterror documents.
+ */
+ void GetNetErrorInfo(mozilla::dom::NetErrorInfo& aInfo, ErrorResult& aRv);
+
+ /**
+ * This function checks if the document that is trying to access
+ * GetFailedCertSecurityInfo is a trusted cert error page or not.
+ */
+ static bool CallerIsTrustedAboutCertError(JSContext* aCx, JSObject* aObject);
+
+ /**
+ * This function checks if the privilege storage access api is available for
+ * the caller. We only allow privilege SSA to be called by system principal
+ * and webcompat extension.
+ */
+ static bool CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject);
+
+ /**
+ * Get the security info (i.e. certificate validity, errorCode, etc) for a
+ * failed Channel. This property is only exposed for about:certerror
+ * documents.
+ */
+ void GetFailedCertSecurityInfo(mozilla::dom::FailedCertSecurityInfo& aInfo,
+ ErrorResult& aRv);
+
+ /**
+ * Set the channel that failed to load and resulted in an error page.
+ * This is only relevant to error pages.
+ */
+ void SetFailedChannel(nsIChannel* aChannel) { mFailedChannel = aChannel; }
+
+ /**
+ * Returns the default namespace ID used for elements created in this
+ * document.
+ */
+ int32_t GetDefaultNamespaceID() const { return mDefaultElementType; }
+
+ void RemoveAllProperties();
+ void RemoveAllPropertiesFor(nsINode* aNode);
+
+ nsPropertyTable& PropertyTable() { return mPropertyTable; }
+
+ /**
+ * Sets the ID used to identify this part of the multipart document
+ */
+ void SetPartID(uint32_t aID) { mPartID = aID; }
+
+ /**
+ * Return the ID used to identify this part of the multipart document
+ */
+ uint32_t GetPartID() const { return mPartID; }
+
+ /**
+ * Sanitize the document by resetting all input elements and forms that have
+ * autocomplete=off to their default values.
+ * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Sanitize();
+
+ /**
+ * Enumerate all subdocuments.
+ * The enumerator callback should return CallState::Continue to continue
+ * enumerating, or CallState::Stop to stop. This will never get passed a null
+ * aDocument.
+ */
+ using SubDocEnumFunc = FunctionRef<CallState(Document&)>;
+ void EnumerateSubDocuments(SubDocEnumFunc aCallback);
+
+ /**
+ * Collect all the descendant documents for which |aCalback| returns true.
+ * The callback function must not mutate any state for the given document.
+ */
+ using nsDocTestFunc = bool (*)(const Document* aDocument);
+ void CollectDescendantDocuments(nsTArray<RefPtr<Document>>& aDescendants,
+ nsDocTestFunc aCallback) const;
+
+ /**
+ * Check whether it is safe to cache the presentation of this document
+ * and all of its subdocuments (depending on the 3rd param). This method
+ * checks the following conditions recursively:
+ * - Some document types, such as plugin documents, cannot be safely cached.
+ * - If there are any pending requests, we don't allow the presentation
+ * to be cached. Ideally these requests would be suspended and resumed,
+ * but that is difficult in some cases, such as XMLHttpRequest.
+ * - If there are any beforeunload or unload listeners, we must fire them
+ * for correctness, but this likely puts the document into a state where
+ * it would not function correctly if restored.
+ *
+ * |aNewRequest| should be the request for a new document which will
+ * replace this document in the docshell. The new document's request
+ * will be ignored when checking for active requests. If there is no
+ * request associated with the new document, this parameter may be null.
+ *
+ * |aBFCacheCombo| is used as a bitmask to indicate what the status
+ * combination is when we try to BFCache aNewRequest
+ */
+ virtual bool CanSavePresentation(nsIRequest* aNewRequest,
+ uint32_t& aBFCacheCombo,
+ bool aIncludeSubdocuments,
+ bool aAllowUnloadListeners = true);
+
+ virtual nsresult Init();
+
+ /**
+ * Notify the document that its associated ContentViewer is being destroyed.
+ * This releases circular references so that the document can go away.
+ * Destroy() is only called on documents that have a content viewer.
+ */
+ virtual void Destroy();
+
+ /**
+ * Notify the document that its associated ContentViewer is no longer
+ * the current viewer for the docshell. The document might still
+ * be rendered in "zombie state" until the next document is ready.
+ * The document should save form control state.
+ */
+ void RemovedFromDocShell();
+
+ /**
+ * Get the layout history state that should be used to save and restore state
+ * for nodes in this document. This may return null; if that happens state
+ * saving and restoration is not possible.
+ */
+ already_AddRefed<nsILayoutHistoryState> GetLayoutHistoryState() const;
+
+ /**
+ * Methods that can be used to prevent onload firing while an event that
+ * should block onload is posted. onload is guaranteed to not fire until
+ * either all calls to BlockOnload() have been matched by calls to
+ * UnblockOnload() or the load has been stopped altogether (by the user
+ * pressing the Stop button, say).
+ */
+ void BlockOnload();
+ /**
+ * @param aFireSync whether to fire onload synchronously. If false,
+ * onload will fire asynchronously after all onload blocks have been
+ * removed. It will NOT fire from inside UnblockOnload. If true,
+ * onload may fire from inside UnblockOnload.
+ */
+ void UnblockOnload(bool aFireSync);
+
+ // Only BlockOnload should call this!
+ void AsyncBlockOnload();
+
+ void BlockDOMContentLoaded() { ++mBlockDOMContentLoaded; }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void UnblockDOMContentLoaded();
+
+ /**
+ * Notification that the page has been shown, for documents which are loaded
+ * into a DOM window. This corresponds to the completion of document load,
+ * or to the page's presentation being restored into an existing DOM window.
+ * This notification fires applicable DOM events to the content window. See
+ * PageTransitionEvent.webidl for a description of the |aPersisted|
+ * parameter. If aDispatchStartTarget is null, the pageshow event is
+ * dispatched on the ScriptGlobalObject for this document, otherwise it's
+ * dispatched on aDispatchStartTarget. If |aOnlySystemGroup| is true, the
+ * event is only dispatched to listeners in the system group.
+ * Note: if aDispatchStartTarget isn't null, the showing state of the
+ * document won't be altered.
+ */
+ virtual void OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup = false);
+
+ /**
+ * Notification that the page has been hidden, for documents which are loaded
+ * into a DOM window. This corresponds to the unloading of the document, or
+ * to the document's presentation being saved but removed from an existing
+ * DOM window. This notification fires applicable DOM events to the content
+ * window. See PageTransitionEvent.webidl for a description of the
+ * |aPersisted| parameter. If aDispatchStartTarget is null, the pagehide
+ * event is dispatched on the ScriptGlobalObject for this document,
+ * otherwise it's dispatched on aDispatchStartTarget. If |aOnlySystemGroup| is
+ * true, the event is only dispatched to listeners in the system group.
+ * Note: if aDispatchStartTarget isn't null, the showing state of the
+ * document won't be altered.
+ */
+ void OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup = false);
+
+ /*
+ * We record the set of links in the document that are relevant to
+ * style.
+ */
+ /**
+ * Notification that an element is a link that is relevant to style.
+ */
+ void AddStyleRelevantLink(Link* aLink) {
+ NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!");
+#ifdef DEBUG
+ NS_ASSERTION(!mStyledLinks.Contains(aLink),
+ "Document already knows about this Link!");
+ mStyledLinksCleared = false;
+#endif
+ mStyledLinks.Insert(aLink);
+ }
+
+ /**
+ * Notification that an element is a link and its URI might have been
+ * changed or the element removed. If the element is still a link relevant
+ * to style, then someone must ensure that AddStyleRelevantLink is
+ * (eventually) called on it again.
+ */
+ void ForgetLink(Link* aLink) {
+ NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!");
+#ifdef DEBUG
+ bool linkContained = mStyledLinks.Contains(aLink);
+ NS_ASSERTION(linkContained || mStyledLinksCleared,
+ "Document knows nothing about this Link!");
+#endif
+ mStyledLinks.Remove(aLink);
+ }
+
+ // Refreshes the hrefs of all the links in the document.
+ void RefreshLinkHrefs();
+
+ /**
+ * Support for window.matchMedia()
+ */
+
+ already_AddRefed<MediaQueryList> MatchMedia(const nsACString& aMediaQueryList,
+ CallerType aCallerType);
+
+ LinkedList<MediaQueryList>& MediaQueryLists() { return mDOMMediaQueryLists; }
+
+ /**
+ * Get the compatibility mode for this document
+ */
+ nsCompatibility GetCompatibilityMode() const { return mCompatMode; }
+
+ /**
+ * Check whether we've ever fired a DOMTitleChanged event for this
+ * document.
+ */
+ bool HaveFiredDOMTitleChange() const { return mHaveFiredTitleChange; }
+
+ /**
+ * To batch DOMSubtreeModified, document needs to be informed when
+ * a mutation event might be dispatched, even if the event isn't actually
+ * created because there are no listeners for it.
+ *
+ * @param aTarget is the target for the mutation event.
+ */
+ void MayDispatchMutationEvent(nsINode* aTarget) {
+ if (mSubtreeModifiedDepth > 0) {
+ mSubtreeModifiedTargets.AppendObject(aTarget);
+ }
+ }
+
+ /**
+ * Marks as not-going-to-be-collected for the given generation of
+ * cycle collection.
+ */
+ void MarkUncollectableForCCGeneration(uint32_t aGeneration) {
+ mMarkedCCGeneration = aGeneration;
+ }
+
+ /**
+ * Gets the cycle collector generation this document is marked for.
+ */
+ uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
+
+ /**
+ * Returns whether this document is cookie averse. See
+ * https://html.spec.whatwg.org/multipage/dom.html#cookie-averse-document-object
+ */
+ bool IsCookieAverse() const {
+ // If we are a document that "has no browsing context."
+ if (!GetInnerWindow()) {
+ return true;
+ }
+
+ // If we are a document "whose URL's scheme is not a network scheme."
+ // NB: Explicitly allow file: URIs to store cookies.
+
+ return !NodePrincipal()->SchemeIs("http") &&
+ !NodePrincipal()->SchemeIs("https") &&
+ !NodePrincipal()->SchemeIs("file");
+ }
+
+ bool IsLoadedAsData() { return mLoadedAsData; }
+
+ void SetAddedToMemoryReportAsDataDocument() {
+ mAddedToMemoryReportingAsDataDocument = true;
+ }
+
+ void UnregisterFromMemoryReportingForDataDocument();
+
+ bool MayStartLayout() { return mMayStartLayout; }
+
+ void SetMayStartLayout(bool aMayStartLayout);
+
+ already_AddRefed<nsIDocumentEncoder> GetCachedEncoder();
+
+ void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder);
+
+ // In case of failure, the document really can't initialize the frame loader.
+ nsresult InitializeFrameLoader(nsFrameLoader* aLoader);
+ // In case of failure, the caller must handle the error, for example by
+ // finalizing frame loader asynchronously.
+ nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer);
+ // Removes the frame loader of aShell from the initialization list.
+ void TryCancelFrameLoaderInitialization(nsIDocShell* aShell);
+
+ /**
+ * Check whether this document is a root document that is not an
+ * external resource.
+ */
+ bool IsRootDisplayDocument() const {
+ return !mParentDocument && !mDisplayDocument;
+ }
+
+ bool ChromeRulesEnabled() const { return mChromeRulesEnabled; }
+
+ bool IsInChromeDocShell() const {
+ const Document* root = this;
+ while (const Document* displayDoc = root->GetDisplayDocument()) {
+ root = displayDoc;
+ }
+ return root->mInChromeDocShell;
+ }
+
+ bool IsDevToolsDocument() const { return mIsDevToolsDocument; }
+
+ bool IsBeingUsedAsImage() const { return mIsBeingUsedAsImage; }
+
+ void SetIsBeingUsedAsImage() { mIsBeingUsedAsImage = true; }
+
+ bool IsSVGGlyphsDocument() const { return mIsSVGGlyphsDocument; }
+
+ void SetIsSVGGlyphsDocument() { mIsSVGGlyphsDocument = true; }
+
+ bool IsResourceDoc() const {
+ return IsBeingUsedAsImage() || // Are we a helper-doc for an SVG image?
+ mHasDisplayDocument; // Are we an external resource doc?
+ }
+
+ /**
+ * Get the document for which this document is an external resource. This
+ * will be null if this document is not an external resource. Otherwise,
+ * GetDisplayDocument() will return a non-null document, and
+ * GetDisplayDocument()->GetDisplayDocument() is guaranteed to be null.
+ */
+ Document* GetDisplayDocument() const { return mDisplayDocument; }
+
+ /**
+ * Set the display document for this document. aDisplayDocument must not be
+ * null.
+ */
+ void SetDisplayDocument(Document* aDisplayDocument) {
+ MOZ_ASSERT(!GetPresShell() && !GetContainer() && !GetWindow(),
+ "Shouldn't set mDisplayDocument on documents that already "
+ "have a presentation or a docshell or a window");
+ MOZ_ASSERT(aDisplayDocument, "Must not be null");
+ MOZ_ASSERT(aDisplayDocument != this, "Should be different document");
+ MOZ_ASSERT(!aDisplayDocument->GetDisplayDocument(),
+ "Display documents should not nest");
+ mDisplayDocument = aDisplayDocument;
+ mHasDisplayDocument = !!aDisplayDocument;
+ }
+
+ /**
+ * Request an external resource document for aURI. This will return the
+ * resource document if available. If one is not available yet, it will
+ * start loading as needed, and the pending load object will be returned in
+ * aPendingLoad so that the caller can register an observer to wait for the
+ * load. If this function returns null and doesn't return a pending load,
+ * that means that there is no resource document for this URI and won't be
+ * one in the future.
+ *
+ * @param aURI the URI to get
+ * @param aReferrerInfo the referrerInfo of the request
+ * @param aRequestingNode the node making the request
+ * @param aPendingLoad the pending load for this request, if any
+ */
+ Document* RequestExternalResource(nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad);
+
+ /**
+ * Enumerate the external resource documents associated with this document.
+ * The enumerator callback should return CallState::Continue to continue
+ * enumerating, or CallState::Stop to stop. This callback will never get
+ * passed a null aDocument.
+ */
+ void EnumerateExternalResources(SubDocEnumFunc aCallback);
+
+ dom::ExternalResourceMap& ExternalResourceMap() {
+ return mExternalResourceMap;
+ }
+
+ /**
+ * Return whether the document is currently showing (in the sense of
+ * OnPageShow() having been called already and OnPageHide() not having been
+ * called yet.
+ */
+ bool IsShowing() const { return mIsShowing; }
+ /**
+ * Return whether the document is currently visible (in the sense of
+ * OnPageHide having been called and OnPageShow not yet having been called)
+ */
+ bool IsVisible() const { return mVisible; }
+
+ void SetSuppressedEventListener(EventListener* aListener);
+
+ EventListener* GetSuppressedEventListener() {
+ return mSuppressedEventListener;
+ }
+
+ /**
+ * Return true when this document is active, i.e., an active document
+ * in a content viewer and not in the bfcache.
+ * This does NOT match the "active document" concept in the WHATWG spec -
+ * see IsCurrentActiveDocument.
+ */
+ bool IsActive() const;
+
+ /**
+ * Return true if this is the current active document for its
+ * docshell. Note that a docshell may have multiple active documents
+ * due to the bfcache -- this should be used when you need to
+ * differentiate the *current* active document from any active
+ * documents.
+ */
+ bool IsCurrentActiveDocument() const {
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ return inner && inner->IsCurrentInnerWindow() && inner->GetDoc() == this;
+ }
+
+ /**
+ * Returns whether this document should perform image loads.
+ */
+ bool ShouldLoadImages() const {
+ // We check IsBeingUsedAsImage() so that SVG documents loaded as
+ // images can themselves have data: URL image references.
+ return IsCurrentActiveDocument() || IsBeingUsedAsImage() ||
+ IsStaticDocument();
+ }
+
+ void SetHasPrintCallbacks() {
+ MOZ_DIAGNOSTIC_ASSERT(IsStaticDocument());
+ mHasPrintCallbacks = true;
+ }
+
+ bool HasPrintCallbacks() const { return mHasPrintCallbacks; }
+
+ /**
+ * Register/Unregister the ActivityObserver into mActivityObservers to listen
+ * the document's activity changes such as OnPageHide, visibility, activity.
+ * The ActivityObserver objects can be nsIObjectLoadingContent or
+ * nsIDocumentActivity or HTMLMEdiaElement.
+ */
+ void RegisterActivityObserver(nsISupports* aSupports);
+ bool UnregisterActivityObserver(nsISupports* aSupports);
+ // Enumerate all the observers in mActivityObservers by the aEnumerator.
+ using ActivityObserverEnumerator = FunctionRef<void(nsISupports*)>;
+ void EnumerateActivityObservers(ActivityObserverEnumerator aEnumerator);
+
+ void NotifyActivityChanged();
+
+ // Indicates whether mAnimationController has been (lazily) initialized.
+ // If this returns true, we're promising that GetAnimationController()
+ // will have a non-null return value.
+ bool HasAnimationController() { return !!mAnimationController; }
+
+ // Getter for this document's SMIL Animation Controller. Performs lazy
+ // initialization, if this document supports animation and if
+ // mAnimationController isn't yet initialized.
+ //
+ // If HasAnimationController is true, this is guaranteed to return non-null.
+ SMILAnimationController* GetAnimationController();
+
+ // Gets the tracker for animations that are waiting to start.
+ // Returns nullptr if there is no pending animation tracker for this document
+ // which will be the case if there have never been any CSS animations or
+ // transitions on elements in the document.
+ PendingAnimationTracker* GetPendingAnimationTracker() {
+ return mPendingAnimationTracker;
+ }
+
+ // Gets the tracker for animations that are waiting to start and
+ // creates it if it doesn't already exist. As a result, the return value
+ // will never be nullptr.
+ PendingAnimationTracker* GetOrCreatePendingAnimationTracker();
+
+ // Gets the tracker for scroll-driven animations that are waiting to start.
+ // Returns nullptr if there is no scroll-driven animation tracker for this
+ // document which will be the case if there have never been any scroll-driven
+ // animations in the document.
+ ScrollTimelineAnimationTracker* GetScrollTimelineAnimationTracker() {
+ return mScrollTimelineAnimationTracker;
+ }
+
+ // Gets the tracker for scroll-driven animations that are waiting to start and
+ // creates it if it doesn't already exist. As a result, the return value
+ // will never be nullptr.
+ ScrollTimelineAnimationTracker* GetOrCreateScrollTimelineAnimationTracker();
+
+ /**
+ * Prevents user initiated events from being dispatched to the document and
+ * subdocuments.
+ */
+ void SuppressEventHandling(uint32_t aIncrease = 1);
+
+ /**
+ * Unsuppress event handling.
+ * @param aFireEvents If true, delayed events (focus/blur) will be fired
+ * asynchronously.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void UnsuppressEventHandlingAndFireEvents(
+ bool aFireEvents);
+
+ uint32_t EventHandlingSuppressed() const { return mEventsSuppressed; }
+
+ bool IsEventHandlingEnabled() const {
+ return !EventHandlingSuppressed() && mScriptGlobalObject;
+ }
+
+ bool WouldScheduleFrameRequestCallbacks() const {
+ // If this function changes to depend on some other variable, make sure to
+ // call UpdateFrameRequestCallbackSchedulingState() calls to the places
+ // where that variable can change.
+ return mPresShell && IsEventHandlingEnabled();
+ }
+
+ void DecreaseEventSuppression() {
+ MOZ_ASSERT(mEventsSuppressed);
+ --mEventsSuppressed;
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+
+ /**
+ * Some clipboard commands are unconditionally enabled on some documents, so
+ * as to always dispatch copy / paste events even though you'd normally not be
+ * able to copy.
+ */
+ bool AreClipboardCommandsUnconditionallyEnabled() const;
+
+ /**
+ * Note a ChannelEventQueue which has been suspended on the document's behalf
+ * to prevent XHRs from running content scripts while event handling is
+ * suppressed. The document is responsible for resuming the queue after
+ * event handling is unsuppressed.
+ */
+ void AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue);
+
+ /**
+ * Returns true if a postMessage event should be suspended instead of running.
+ * The document is responsible for running the event later, in the order they
+ * were received.
+ */
+ bool SuspendPostMessageEvent(PostMessageEvent* aEvent);
+
+ /**
+ * Run any suspended postMessage events, or clear them.
+ */
+ void FireOrClearPostMessageEvents(bool aFireEvents);
+
+ void SetHasDelayedRefreshEvent() { mHasDelayedRefreshEvent = true; }
+
+ /**
+ * Flag whether we're about to fire the window's load event for this document.
+ */
+ void SetLoadEventFiring(bool aFiring) { mLoadEventFiring = aFiring; }
+
+ /**
+ * Test whether we should be firing a load event for this document after a
+ * document.close(). This is public and on Document, instead of being private
+ * to Document, because we need to go through the normal docloader logic
+ * for the readystate change to READYSTATE_COMPLETE with the normal timing and
+ * semantics of firing the load event; we just don't want to fire the load
+ * event if this tests true. So we need the docloader to be able to access
+ * this state.
+ *
+ * This method should only be called at the point when the load event is about
+ * to be fired. It resets the "skip" flag, so it is not idempotent.
+ */
+ bool SkipLoadEventAfterClose() {
+ bool skip = mSkipLoadEventAfterClose;
+ mSkipLoadEventAfterClose = false;
+ return skip;
+ }
+
+ /**
+ * Increment https://html.spec.whatwg.org/#ignore-destructive-writes-counter
+ */
+ void IncrementIgnoreDestructiveWritesCounter() {
+ ++mIgnoreDestructiveWritesCounter;
+ }
+
+ /**
+ * Decrement https://html.spec.whatwg.org/#ignore-destructive-writes-counter
+ */
+ void DecrementIgnoreDestructiveWritesCounter() {
+ --mIgnoreDestructiveWritesCounter;
+ }
+
+ bool IsDNSPrefetchAllowed() const { return mAllowDNSPrefetch; }
+
+ /**
+ * Returns true if this document is allowed to contain XUL element and
+ * use non-builtin XBL bindings.
+ */
+ bool AllowXULXBL() {
+ return mAllowXULXBL == eTriTrue ? true
+ : mAllowXULXBL == eTriFalse ? false
+ : InternalAllowXULXBL();
+ }
+
+ /**
+ * Returns true if this document is allowed to load DTDs from UI resources
+ * no matter what.
+ */
+ bool SkipDTDSecurityChecks() { return mSkipDTDSecurityChecks; }
+
+ void ForceEnableXULXBL() { mAllowXULXBL = eTriTrue; }
+
+ void ForceSkipDTDSecurityChecks() { mSkipDTDSecurityChecks = true; }
+
+ /**
+ * Returns the template content owner document that owns the content of
+ * HTMLTemplateElement.
+ */
+ Document* GetTemplateContentsOwner();
+
+ Document* GetTemplateContentsOwnerIfExists() const {
+ return mTemplateContentsOwner.get();
+ }
+
+ bool IsTemplateContentsOwner() const {
+ // Template contents owner documents are the template contents owner of
+ // themselves.
+ return mTemplateContentsOwner == this;
+ }
+
+ /**
+ * Returns true if this document is a static clone of a normal document.
+ *
+ * We create static clones for print preview and printing (possibly other
+ * things in future).
+ *
+ * Note that static documents are also "loaded as data" (if this method
+ * returns true, IsLoadedAsData() will also return true).
+ */
+ bool IsStaticDocument() const { return mIsStaticDocument; }
+
+ /**
+ * Clones the document along with any subdocuments, stylesheet, etc.
+ *
+ * The resulting document and everything it contains (including any
+ * sub-documents) are created purely via cloning. The returned documents and
+ * any sub-documents are "loaded as data" documents to preserve the state as
+ * it was during the clone process (we don't want external resources to load
+ * and replace the cloned resources).
+ *
+ * @param aCloneContainer The container for the clone document.
+ * @param aContentViewer The viewer for the clone document. Must be the viewer
+ * of aCloneContainer, but callers must have a reference
+ * to it already and ensure it's not null.
+ * @param aPrintSettings The print settings for this clone.
+ * @param aOutHasInProcessPrintCallbacks Self-descriptive.
+ */
+ already_AddRefed<Document> CreateStaticClone(
+ nsIDocShell* aCloneContainer, nsIContentViewer* aContentViewer,
+ nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks);
+
+ /**
+ * If this document is a static clone, this returns the original
+ * document.
+ */
+ Document* GetOriginalDocument() const {
+ MOZ_ASSERT(!mOriginalDocument || !mOriginalDocument->GetOriginalDocument());
+ return mOriginalDocument;
+ }
+
+ /**
+ * If this document is a static clone, let the original document know that
+ * we're going away and then release our reference to it.
+ */
+ void UnlinkOriginalDocumentIfStatic();
+
+ /**
+ * These are called by the parser as it encounters <picture> tags, the end of
+ * said tags, and possible picture <source srcset> sources respectively. These
+ * are used to inform ResolvePreLoadImage() calls. Unset attributes are
+ * expected to be marked void.
+ *
+ * NOTE that the parser does not attempt to track the current picture nesting
+ * level or whether the given <source> tag is within a picture -- it is only
+ * guaranteed to order these calls properly with respect to
+ * ResolvePreLoadImage.
+ */
+
+ void PreloadPictureOpened() { mPreloadPictureDepth++; }
+
+ void PreloadPictureClosed();
+
+ void PreloadPictureImageSource(const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr);
+
+ /**
+ * Called by the parser to resolve an image for preloading. The parser will
+ * call the PreloadPicture* functions to inform us of possible <picture>
+ * nesting and possible sources, which are used to inform URL selection
+ * responsive <picture> or <img srcset> images. Unset attributes are expected
+ * to be marked void.
+ * If this image is for <picture> or <img srcset>, aIsImgSet will be set to
+ * true, false otherwise.
+ */
+ already_AddRefed<nsIURI> ResolvePreloadImage(nsIURI* aBaseURI,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ bool* aIsImgSet);
+ /**
+ * Called by nsParser to preload images. Can be removed and code moved
+ * to nsPreloadURIs::PreloadURIs() in file nsParser.cpp whenever the
+ * parser-module is linked with gklayout-module. aCrossOriginAttr should
+ * be a void string if the attr is not present.
+ * aIsImgSet is the value got from calling ResolvePreloadImage, it is true
+ * when this image is for loading <picture> or <img srcset> images.
+ */
+ void MaybePreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
+ bool aLinkPreload, const TimeStamp& aInitTimestamp);
+ void PreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
+ bool aLinkPreload, uint64_t aEarlyHintPreloaderId);
+
+ /**
+ * Called by images to forget an image preload when they start doing
+ * the real load.
+ */
+ void ForgetImagePreload(nsIURI* aURI);
+
+ /**
+ * Called by the parser or the preload service to preload style sheets.
+ * aCrossOriginAttr should be a void string if the attr is not present.
+ */
+ SheetPreloadStatus PreloadStyle(nsIURI* aURI, const Encoding* aEncoding,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy,
+ const nsAString& aIntegrity,
+ css::StylePreloadKind,
+ uint64_t aEarlyHintPreloaderId);
+
+ /**
+ * Called by the chrome registry to load style sheets.
+ *
+ * This always does a synchronous load, and parses as a normal document sheet.
+ */
+ RefPtr<StyleSheet> LoadChromeSheetSync(nsIURI* aURI);
+
+ /**
+ * Returns true if the locale used for the document specifies a direction of
+ * right to left. For chrome documents, this comes from the chrome registry.
+ * This is used to determine the current state for the :-moz-locale-dir
+ * pseudoclass so once can know whether a document is expected to be rendered
+ * left-to-right or right-to-left.
+ */
+ bool IsDocumentRightToLeft();
+
+ /**
+ * Called by Parser for link rel=preconnect
+ */
+ void MaybePreconnect(nsIURI* uri, CORSMode aCORSMode);
+
+ /**
+ * Set the document's pending state object (as serialized using structured
+ * clone).
+ */
+ void SetStateObject(nsIStructuredCloneContainer* scContainer);
+
+ /**
+ * Set the document's pending state object to the same state object as
+ * aDocument.
+ */
+ void SetStateObjectFrom(Document* aDocument) {
+ SetStateObject(aDocument->mStateObjectContainer);
+ }
+
+ /**
+ * Returns true if there is a lightweight theme specified. This is used to
+ * determine the state of the :-moz-lwtheme pseudo-class.
+ */
+ bool ComputeDocumentLWTheme() const;
+ void ResetDocumentLWTheme() {
+ UpdateDocumentStates(DocumentState::LWTHEME, true);
+ }
+
+ // Whether we're a media document or not.
+ enum class MediaDocumentKind {
+ NotMedia,
+ Video,
+ Image,
+ Plugin,
+ };
+
+ virtual enum MediaDocumentKind MediaDocumentKind() const {
+ return MediaDocumentKind::NotMedia;
+ }
+
+ DocumentState GetDocumentState() const { return mDocumentState; }
+
+ nsISupports* GetCurrentContentSink();
+
+ void ElementWithAutoFocusInserted(Element* aAutoFocusCandidate);
+ MOZ_CAN_RUN_SCRIPT void FlushAutoFocusCandidates();
+ void ScheduleFlushAutoFocusCandidates();
+ bool HasAutoFocusCandidates() const {
+ return !mAutoFocusCandidates.IsEmpty();
+ }
+
+ void SetAutoFocusFired();
+
+ void SetScrollToRef(nsIURI* aDocumentURI);
+ MOZ_CAN_RUN_SCRIPT void ScrollToRef();
+ void ResetScrolledToRefAlready() { mScrolledToRefAlready = false; }
+
+ void SetChangeScrollPosWhenScrollingToRef(bool aValue) {
+ mChangeScrollPosWhenScrollingToRef = aValue;
+ }
+
+ using DocumentOrShadowRoot::GetElementById;
+ using DocumentOrShadowRoot::GetElementsByClassName;
+ using DocumentOrShadowRoot::GetElementsByTagName;
+ using DocumentOrShadowRoot::GetElementsByTagNameNS;
+
+ DocumentTimeline* Timeline();
+ LinkedList<DocumentTimeline>& Timelines() { return mTimelines; }
+
+ SVGSVGElement* GetSVGRootElement() const;
+
+ nsresult ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
+ int32_t* aHandle);
+ void CancelFrameRequestCallback(int32_t aHandle);
+
+ /**
+ * Returns true if the handle refers to a callback that was canceled that
+ * we did not find in our list of callbacks (e.g. because it is one of those
+ * in the set of callbacks currently queued to be run).
+ */
+ bool IsCanceledFrameRequestCallback(int32_t aHandle) const;
+
+ /**
+ * Put this document's frame request callbacks into the provided
+ * list, and forget about them.
+ */
+ void TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks);
+
+ /**
+ * @return true if this document's frame request callbacks should be
+ * throttled. We throttle requestAnimationFrame for documents which aren't
+ * visible (e.g. scrolled out of the viewport).
+ */
+ bool ShouldThrottleFrameRequests() const;
+
+ // This returns true when the document tree is being teared down.
+ bool InUnlinkOrDeletion() { return mInUnlinkOrDeletion; }
+
+ dom::ImageTracker* ImageTracker();
+
+ // Adds an element to mResponsiveContent when the element is
+ // added to the tree.
+ void AddResponsiveContent(HTMLImageElement* aContent) {
+ MOZ_ASSERT(aContent);
+ mResponsiveContent.Insert(aContent);
+ }
+
+ // Removes an element from mResponsiveContent when the element is
+ // removed from the tree.
+ void RemoveResponsiveContent(HTMLImageElement* aContent) {
+ MOZ_ASSERT(aContent);
+ mResponsiveContent.Remove(aContent);
+ }
+
+ void ScheduleSVGUseElementShadowTreeUpdate(SVGUseElement&);
+ void UnscheduleSVGUseElementShadowTreeUpdate(SVGUseElement& aElement) {
+ mSVGUseElementsNeedingShadowTreeUpdate.Remove(&aElement);
+ }
+
+ bool SVGUseElementNeedsShadowTreeUpdate(SVGUseElement& aElement) const {
+ return mSVGUseElementsNeedingShadowTreeUpdate.Contains(&aElement);
+ }
+
+ using ShadowRootSet = nsTHashSet<ShadowRoot*>;
+
+ void AddComposedDocShadowRoot(ShadowRoot& aShadowRoot) {
+ mComposedShadowRoots.Insert(&aShadowRoot);
+ }
+
+ void RemoveComposedDocShadowRoot(ShadowRoot& aShadowRoot) {
+ mComposedShadowRoots.Remove(&aShadowRoot);
+ }
+
+ // If you're considering using this, you probably want to use
+ // ShadowRoot::IsComposedDocParticipant instead. This is just for
+ // sanity-checking.
+ bool IsComposedDocShadowRoot(ShadowRoot& aShadowRoot) {
+ return mComposedShadowRoots.Contains(&aShadowRoot);
+ }
+
+ const ShadowRootSet& ComposedShadowRoots() const {
+ return mComposedShadowRoots;
+ }
+
+ // WebIDL method for chrome code.
+ void GetConnectedShadowRoots(nsTArray<RefPtr<ShadowRoot>>&) const;
+
+ // Notifies any responsive content added by AddResponsiveContent upon media
+ // features values changing.
+ void NotifyMediaFeatureValuesChanged();
+
+ nsresult GetStateObject(JS::MutableHandle<JS::Value> aState);
+
+ nsDOMNavigationTiming* GetNavigationTiming() const { return mTiming; }
+
+ void SetNavigationTiming(nsDOMNavigationTiming* aTiming);
+
+ nsContentList* ImageMapList();
+
+ // Add aLink to the set of links that need their status resolved.
+ void RegisterPendingLinkUpdate(Link* aLink);
+
+ // Update state on links in mLinksToUpdate.
+ void FlushPendingLinkUpdates();
+
+ bool HasWarnedAbout(DeprecatedOperations aOperation) const;
+ void WarnOnceAbout(
+ DeprecatedOperations aOperation, bool asError = false,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
+
+#define DOCUMENT_WARNING(_op) e##_op,
+ enum DocumentWarnings {
+#include "nsDocumentWarningList.h"
+ eDocumentWarningCount
+ };
+#undef DOCUMENT_WARNING
+ bool HasWarnedAbout(DocumentWarnings aWarning) const;
+ void WarnOnceAbout(
+ DocumentWarnings aWarning, bool asError = false,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
+
+ // This method may fire a DOM event; if it does so it will happen
+ // synchronously.
+ //
+ // Whether the event fires is controlled by the argument.
+ enum class DispatchVisibilityChange { No, Yes };
+ void UpdateVisibilityState(
+ DispatchVisibilityChange = DispatchVisibilityChange::Yes);
+
+ // Posts an event to call UpdateVisibilityState.
+ void PostVisibilityUpdateEvent();
+
+ bool IsSyntheticDocument() const { return mIsSyntheticDocument; }
+
+ // Adds the size of a given node, which must not be a document node, to the
+ // window sizes passed-in.
+ static void AddSizeOfNodeTree(nsINode&, nsWindowSizes&);
+
+ // Note: Document is a sub-class of nsINode, which has a
+ // SizeOfExcludingThis function. However, because Document objects can
+ // only appear at the top of the DOM tree, we have a specialized measurement
+ // function which returns multiple sizes.
+ virtual void DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const;
+ // DocAddSizeOfIncludingThis doesn't need to be overridden by sub-classes
+ // because Document inherits from nsINode; see the comment above the
+ // declaration of nsINode::SizeOfIncludingThis.
+ virtual void DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
+
+ void ConstructUbiNode(void* storage) override;
+
+ bool MayHaveDOMMutationObservers() { return mMayHaveDOMMutationObservers; }
+
+ void SetMayHaveDOMMutationObservers() { mMayHaveDOMMutationObservers = true; }
+
+ bool MayHaveAnimationObservers() { return mMayHaveAnimationObservers; }
+
+ void SetMayHaveAnimationObservers() { mMayHaveAnimationObservers = true; }
+
+ bool IsInSyncOperation() { return mInSyncOperationCount != 0; }
+
+ void SetIsInSyncOperation(bool aSync);
+
+ bool CreatingStaticClone() const { return mCreatingStaticClone; }
+
+ /**
+ * Creates a new element in the HTML namespace with a local name given by
+ * aTag.
+ */
+ already_AddRefed<Element> CreateHTMLElement(nsAtom* aTag);
+
+ // WebIDL API
+ nsIGlobalObject* GetParentObject() const { return GetScopeObject(); }
+ static already_AddRefed<Document> Constructor(const GlobalObject& aGlobal,
+ ErrorResult& rv);
+ DOMImplementation* GetImplementation(ErrorResult& rv);
+ [[nodiscard]] nsresult GetURL(nsString& retval) const;
+ [[nodiscard]] nsresult GetDocumentURI(nsString& retval) const;
+ // Return the URI for the document.
+ // The returned value may differ if the document is loaded via XHR, and
+ // when accessed from chrome privileged script and
+ // from content privileged script for compatibility.
+ void GetDocumentURIFromJS(nsString& aDocumentURI, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ void GetCompatMode(nsString& retval) const;
+ void GetCharacterSet(nsAString& retval) const;
+ // Skip GetContentType, because our NS_IMETHOD version above works fine here.
+ // GetDoctype defined above
+ Element* GetDocumentElement() const { return GetRootElement(); }
+
+ WindowContext* GetTopLevelWindowContext() const;
+
+ // If the top-level ancestor content document for this document is in the same
+ // process, returns it. Otherwise, returns null. This function is not
+ // Fission-compatible, and should not be used in new code.
+ Document* GetTopLevelContentDocumentIfSameProcess();
+ const Document* GetTopLevelContentDocumentIfSameProcess() const;
+
+ // Returns the associated app window if this is a top-level chrome document,
+ // null otherwise.
+ already_AddRefed<nsIAppWindow> GetAppWindowIfToplevelChrome() const;
+
+ already_AddRefed<Element> CreateElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& rv);
+ already_AddRefed<Element> CreateElementNS(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ const ElementCreationOptionsOrString& aOptions, ErrorResult& rv);
+ already_AddRefed<Element> CreateXULElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& aRv);
+ already_AddRefed<DocumentFragment> CreateDocumentFragment() const;
+ already_AddRefed<nsTextNode> CreateTextNode(const nsAString& aData) const;
+ already_AddRefed<nsTextNode> CreateEmptyTextNode() const;
+ already_AddRefed<Comment> CreateComment(const nsAString& aData) const;
+ already_AddRefed<ProcessingInstruction> CreateProcessingInstruction(
+ const nsAString& target, const nsAString& data, ErrorResult& rv) const;
+ already_AddRefed<nsINode> ImportNode(nsINode& aNode, bool aDeep,
+ ErrorResult& rv) const;
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsINode* AdoptNode(nsINode& aNode,
+ ErrorResult& rv);
+ already_AddRefed<Event> CreateEvent(const nsAString& aEventType,
+ CallerType aCallerType,
+ ErrorResult& rv) const;
+ already_AddRefed<nsRange> CreateRange(ErrorResult& rv);
+ already_AddRefed<NodeIterator> CreateNodeIterator(nsINode& aRoot,
+ uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const;
+ already_AddRefed<TreeWalker> CreateTreeWalker(nsINode& aRoot,
+ uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const;
+ // Deprecated WebIDL bits
+ already_AddRefed<CDATASection> CreateCDATASection(const nsAString& aData,
+ ErrorResult& rv);
+ already_AddRefed<Attr> CreateAttribute(const nsAString& aName,
+ ErrorResult& rv);
+ already_AddRefed<Attr> CreateAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ ErrorResult& rv);
+ void GetInputEncoding(nsAString& aInputEncoding) const;
+ already_AddRefed<Location> GetLocation() const;
+ void GetDomain(nsAString& aDomain);
+ void SetDomain(const nsAString& aDomain, mozilla::ErrorResult& rv);
+ void GetCookie(nsAString& aCookie, mozilla::ErrorResult& rv);
+ void SetCookie(const nsAString& aCookie, mozilla::ErrorResult& rv);
+ void GetReferrer(nsAString& aReferrer) const;
+ void GetLastModified(nsAString& aLastModified) const;
+ void GetReadyState(nsAString& aReadyState) const;
+
+ void GetTitle(nsAString& aTitle);
+ void SetTitle(const nsAString& aTitle, ErrorResult& rv);
+ void GetDir(nsAString& aDirection) const;
+ void SetDir(const nsAString& aDirection);
+ nsIHTMLCollection* Images();
+ nsIHTMLCollection* Embeds();
+ nsIHTMLCollection* Plugins() { return Embeds(); }
+ nsIHTMLCollection* Links();
+ nsIHTMLCollection* Forms();
+ nsIHTMLCollection* Scripts();
+ already_AddRefed<nsContentList> GetElementsByName(const nsAString& aName) {
+ return GetFuncStringContentList<nsCachableElementsByNameNodeList>(
+ this, MatchNameAttribute, nullptr, UseExistingNameString, aName);
+ }
+ Document* Open(const mozilla::dom::Optional<nsAString>& /* unused */,
+ const mozilla::dom::Optional<nsAString>& /* unused */,
+ mozilla::ErrorResult& aError);
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Open(
+ const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
+ mozilla::ErrorResult& rv);
+ void Close(mozilla::ErrorResult& rv);
+ void Write(const mozilla::dom::Sequence<nsString>& aText,
+ mozilla::ErrorResult& rv);
+ void Writeln(const mozilla::dom::Sequence<nsString>& aText,
+ mozilla::ErrorResult& rv);
+ Nullable<WindowProxyHolder> GetDefaultView() const;
+ Element* GetActiveElement();
+ enum class IncludeChromeOnly : bool { No, Yes };
+ // TODO(emilio): Audit callers and remove the default argument, some seem like
+ // they could want the IncludeChromeOnly::Yes version.
+ nsIContent* GetUnretargetedFocusedContent(
+ IncludeChromeOnly = IncludeChromeOnly::No) const;
+ /**
+ * Return true if this document or a subdocument has focus.
+ */
+ bool HasFocus(ErrorResult& rv) const;
+
+ /**
+ * Return true if this document itself has focus.
+ */
+ bool ThisDocumentHasFocus() const;
+
+ void GetDesignMode(nsAString& aDesignMode);
+ void SetDesignMode(const nsAString& aDesignMode,
+ nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& rv);
+ void SetDesignMode(const nsAString& aDesignMode,
+ const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ mozilla::ErrorResult& rv);
+ MOZ_CAN_RUN_SCRIPT
+ bool ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
+ const nsAString& aValue, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT bool QueryCommandEnabled(const nsAString& aHTMLCommandName,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT bool QueryCommandIndeterm(
+ const nsAString& aHTMLCommandName, mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT bool QueryCommandState(const nsAString& aHTMLCommandName,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT bool QueryCommandSupported(
+ const nsAString& aHTMLCommandName, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void QueryCommandValue(const nsAString& aHTMLCommandName,
+ nsAString& aValue,
+ mozilla::ErrorResult& aRv);
+ nsIHTMLCollection* Applets();
+ nsIHTMLCollection* Anchors();
+ TimeStamp LastFocusTime() const;
+ void SetLastFocusTime(const TimeStamp& aFocusTime);
+ // Event handlers are all on nsINode already
+ bool MozSyntheticDocument() const { return IsSyntheticDocument(); }
+ Element* GetCurrentScript();
+ void ReleaseCapture() const;
+ void MozSetImageElement(const nsAString& aImageElementId, Element* aElement);
+ nsIURI* GetDocumentURIObject() const;
+ // Not const because all the fullscreen goop is not const
+ const char* GetFullscreenError(CallerType);
+ bool FullscreenEnabled(CallerType aCallerType) {
+ return !GetFullscreenError(aCallerType);
+ }
+
+ void GetWireframeWithoutFlushing(bool aIncludeNodes, Nullable<Wireframe>&);
+
+ MOZ_CAN_RUN_SCRIPT void GetWireframe(bool aIncludeNodes,
+ Nullable<Wireframe>&);
+
+ // Hides all popovers until the given end point, see
+ // https://html.spec.whatwg.org/multipage/popover.html#hide-all-popovers-until
+ MOZ_CAN_RUN_SCRIPT void HideAllPopoversUntil(nsINode& aEndpoint,
+ bool aFocusPreviousElement,
+ bool aFireEvents);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void HideAllPopoversWithoutRunningScript();
+ // Hides the given popover element, see
+ // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
+ MOZ_CAN_RUN_SCRIPT void HidePopover(Element& popover,
+ bool aFocusPreviousElement,
+ bool aFireEvents, ErrorResult& aRv);
+
+ // Returns a list of all the elements in the Document's top layer whose
+ // popover attribute is in the auto state.
+ // See https://html.spec.whatwg.org/multipage/popover.html#auto-popover-list
+ nsTArray<Element*> AutoPopoverList() const;
+
+ // Return document's auto popover list's last element.
+ // See
+ // https://html.spec.whatwg.org/multipage/popover.html#topmost-auto-popover
+ Element* GetTopmostAutoPopover() const;
+
+ // Adds/removes an element to/from the auto popover list.
+ void AddToAutoPopoverList(Element&);
+ void RemoveFromAutoPopoverList(Element&);
+
+ void AddPopoverToTopLayer(Element&);
+ void RemovePopoverFromTopLayer(Element&);
+
+ Element* GetTopLayerTop();
+ // Return the fullscreen element in the top layer
+ Element* GetUnretargetedFullscreenElement() const;
+ bool Fullscreen() const { return !!GetUnretargetedFullscreenElement(); }
+ already_AddRefed<Promise> ExitFullscreen(ErrorResult&);
+ void ExitPointerLock() { PointerLockManager::Unlock(this); }
+ void GetFgColor(nsAString& aFgColor);
+ void SetFgColor(const nsAString& aFgColor);
+ void GetLinkColor(nsAString& aLinkColor);
+ void SetLinkColor(const nsAString& aLinkColor);
+ void GetVlinkColor(nsAString& aAvlinkColor);
+ void SetVlinkColor(const nsAString& aVlinkColor);
+ void GetAlinkColor(nsAString& aAlinkColor);
+ void SetAlinkColor(const nsAString& aAlinkColor);
+ void GetBgColor(nsAString& aBgColor);
+ void SetBgColor(const nsAString& aBgColor);
+ void Clear() const {
+ // Deprecated
+ }
+ void CaptureEvents();
+ void ReleaseEvents();
+
+ mozilla::dom::HTMLAllCollection* All();
+
+ static bool DocumentSupportsL10n(JSContext* aCx, JSObject* aObject);
+ static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
+ static bool IsWebAnimationsEnabled(CallerType aCallerType);
+ static bool IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
+ JSObject* aObject);
+ static bool AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
+ JSObject* aObject);
+ static bool AreWebAnimationsTimelinesEnabled(JSContext* aCx,
+ JSObject* aObject);
+ // Checks that the caller is either chrome or some addon.
+ static bool IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject);
+
+ bool Hidden() const { return mVisibilityState != VisibilityState::Visible; }
+ dom::VisibilityState VisibilityState() const { return mVisibilityState; }
+
+ private:
+ int32_t mPictureInPictureChildElementCount = 0;
+
+ public:
+ void EnableChildElementInPictureInPictureMode();
+ void DisableChildElementInPictureInPictureMode();
+
+ // True if any child element is being used in picture in picture mode.
+ bool HasPictureInPictureChildElement() const;
+
+ void GetSelectedStyleSheetSet(nsAString& aSheetSet);
+ void SetSelectedStyleSheetSet(const nsAString& aSheetSet);
+ void GetLastStyleSheetSet(nsAString& aSheetSet) {
+ aSheetSet = mLastStyleSheetSet;
+ }
+ const nsString& GetCurrentStyleSheetSet() const {
+ return mLastStyleSheetSet.IsEmpty() ? mPreferredStyleSheetSet
+ : mLastStyleSheetSet;
+ }
+ void SetPreferredStyleSheetSet(const nsAString&);
+ void GetPreferredStyleSheetSet(nsAString& aSheetSet) {
+ aSheetSet = mPreferredStyleSheetSet;
+ }
+ DOMStringList* StyleSheetSets();
+ void EnableStyleSheetsForSet(const nsAString& aSheetSet);
+
+ /**
+ * Retrieve the location of the caret position (DOM node and character
+ * offset within that node), given a point.
+ *
+ * @param aX Horizontal point at which to determine the caret position, in
+ * page coordinates.
+ * @param aY Vertical point at which to determine the caret position, in
+ * page coordinates.
+ */
+ already_AddRefed<nsDOMCaretPosition> CaretPositionFromPoint(float aX,
+ float aY);
+
+ Element* GetScrollingElement();
+ // A way to check whether a given element is what would get returned from
+ // GetScrollingElement. It can be faster than comparing to the return value
+ // of GetScrollingElement() due to being able to avoid flushes in various
+ // cases. This method assumes that null is NOT passed.
+ bool IsScrollingElement(Element* aElement);
+
+ // QuerySelector and QuerySelectorAll already defined on nsINode
+
+ XPathExpression* CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv);
+ nsINode* CreateNSResolver(nsINode& aNodeResolver);
+ already_AddRefed<XPathResult> Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv);
+ // Touch event handlers already on nsINode
+ already_AddRefed<Touch> CreateTouch(nsGlobalWindowInner* aView,
+ EventTarget* aTarget, int32_t aIdentifier,
+ int32_t aPageX, int32_t aPageY,
+ int32_t aScreenX, int32_t aScreenY,
+ int32_t aClientX, int32_t aClientY,
+ int32_t aRadiusX, int32_t aRadiusY,
+ float aRotationAngle, float aForce);
+ already_AddRefed<TouchList> CreateTouchList();
+ already_AddRefed<TouchList> CreateTouchList(
+ Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches);
+ already_AddRefed<TouchList> CreateTouchList(
+ const Sequence<OwningNonNull<Touch>>& aTouches);
+
+ void SetStyleSheetChangeEventsEnabled(bool aValue) {
+ mStyleSheetChangeEventsEnabled = aValue;
+ }
+
+ bool StyleSheetChangeEventsEnabled() const {
+ return mStyleSheetChangeEventsEnabled;
+ }
+
+ void SetDevToolsAnonymousAndShadowEventsEnabled(bool aValue) {
+ mDevToolsAnonymousAndShadowEventsEnabled = aValue;
+ }
+ bool DevToolsAnonymousAndShadowEventsEnabled() const {
+ return mDevToolsAnonymousAndShadowEventsEnabled;
+ }
+
+ already_AddRefed<Promise> BlockParsing(Promise& aPromise,
+ const BlockParsingOptions& aOptions,
+ ErrorResult& aRv);
+
+ already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
+
+ Promise* GetDocumentReadyForIdle(ErrorResult& aRv);
+
+ void BlockUnblockOnloadForSystemOrPDFJS(bool aBlock) {
+ if (aBlock) {
+ BlockOnload();
+ } else {
+ UnblockOnload(/* aFireSync = */ false);
+ }
+ }
+
+ nsIDOMXULCommandDispatcher* GetCommandDispatcher();
+ bool HasXULBroadcastManager() const { return mXULBroadcastManager; };
+ void InitializeXULBroadcastManager();
+ XULBroadcastManager* GetXULBroadcastManager() const {
+ return mXULBroadcastManager;
+ }
+ nsINode* GetPopupRangeParent(ErrorResult& aRv);
+ int32_t GetPopupRangeOffset(ErrorResult& aRv);
+
+ bool DevToolsWatchingDOMMutations() const {
+ return mDevToolsWatchingDOMMutations;
+ }
+ void SetDevToolsWatchingDOMMutations(bool aValue);
+
+ void MaybeWarnAboutZoom();
+
+ // ParentNode
+ nsIHTMLCollection* Children();
+ uint32_t ChildElementCount();
+
+ /**
+ * Asserts IsHTMLOrXHTML, and can't return null.
+ * Defined inline in nsHTMLDocument.h
+ */
+ inline nsHTMLDocument* AsHTMLDocument();
+ inline const nsHTMLDocument* AsHTMLDocument() const;
+
+ /**
+ * Asserts IsSVGDocument, and can't return null.
+ * Defined inline in SVGDocument.h
+ */
+ inline SVGDocument* AsSVGDocument();
+ inline const SVGDocument* AsSVGDocument() const;
+
+ /**
+ * Asserts IsImageDocument, and can't return null.
+ * Defined inline in ImageDocument.h
+ */
+ inline ImageDocument* AsImageDocument();
+ inline const ImageDocument* AsImageDocument() const;
+
+ /*
+ * Given a node, get a weak reference to it and append that reference to
+ * mBlockedNodesByClassifier. Can be used later on to look up a node in it.
+ * (e.g., by the UI)
+ */
+ void AddBlockedNodeByClassifier(nsINode* node) {
+ if (!node) {
+ return;
+ }
+
+ nsWeakPtr weakNode = do_GetWeakReference(node);
+
+ if (weakNode) {
+ mBlockedNodesByClassifier.AppendElement(weakNode);
+ }
+ }
+
+ gfxUserFontSet* GetUserFontSet();
+ void FlushUserFontSet();
+ void MarkUserFontSetDirty();
+ FontFaceSet* GetFonts() { return mFontFaceSet; }
+
+ // FontFaceSource
+ FontFaceSet* GetFonts(ErrorResult&) { return Fonts(); }
+ FontFaceSet* Fonts();
+
+ bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; }
+
+ bool IsSynthesized();
+
+ // Records whether we will track use counters for this document, and if so,
+ // which top-level document that page counters will be accumulated to.
+ //
+ // Informs the parent process that page use counters will be sent once the
+ // document goes away.
+ void InitUseCounters();
+
+ // Reports document use counters via telemetry. This method only has an
+ // effect once per document, and so is called during document destruction.
+ void ReportDocumentUseCounters();
+
+ // Report how lazyload performs for this document.
+ void ReportDocumentLazyLoadCounters();
+
+ // Sends page use counters to the parent process to accumulate against the
+ // top-level document. Must be called while we still have access to our
+ // WindowContext. This method has an effect each time it is called, and we
+ // call it just before the document loses its window.
+ void SendPageUseCounters();
+
+ void SetUseCounter(UseCounter aUseCounter) {
+ mUseCounters[aUseCounter] = true;
+ }
+
+ const StyleUseCounters* GetStyleUseCounters() {
+ return mStyleUseCounters.get();
+ }
+
+ // Propagate our use counters explicitly into the specified referencing
+ // document.
+ //
+ // This is used for SVG image documents, which cannot be enumerated in the
+ // referencing document's ReportUseCounters() like external resource documents
+ // can.
+ void PropagateImageUseCounters(Document* aReferencingDocument);
+
+ // Called to track whether this document has had any interaction.
+ // This is used to track whether we should permit "beforeunload".
+ void SetUserHasInteracted();
+ bool UserHasInteracted() { return mUserHasInteracted; }
+ void ResetUserInteractionTimer();
+
+ // This should be called when this document receives events which are likely
+ // to be user interaction with the document, rather than the byproduct of
+ // interaction with the browser (i.e. a keypress to scroll the view port,
+ // keyboard shortcuts, etc). This is used to decide whether we should
+ // permit autoplay audible media. This also gesture activates all other
+ // content documents in this tab.
+ void NotifyUserGestureActivation();
+
+ // This function is used for mochitest only.
+ void ClearUserGestureActivation();
+
+ // Return true if NotifyUserGestureActivation() has been called on any
+ // document in the document tree.
+ bool HasBeenUserGestureActivated();
+
+ // Reture timestamp of last user gesture in milliseconds relative to
+ // navigation start timestamp.
+ DOMHighResTimeStamp LastUserGestureTimeStamp();
+
+ // Return true if there is transient user gesture activation and it hasn't yet
+ // timed out or hasn't been consumed.
+ bool HasValidTransientUserGestureActivation() const;
+
+ // Return true if HasValidTransientUserGestureActivation() would return true,
+ // and consume the activation.
+ bool ConsumeTransientUserGestureActivation();
+
+ BrowsingContext* GetBrowsingContext() const;
+
+ // This document is a WebExtension page, it might be a background page, a
+ // popup, a visible tab, a visible iframe ...e.t.c.
+ bool IsExtensionPage() const;
+
+ bool HasScriptsBlockedBySandbox() const;
+
+ void ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp);
+ bool HasScrollLinkedEffect() const;
+
+#ifdef DEBUG
+ void AssertDocGroupMatchesKey() const;
+#endif
+
+ DocGroup* GetDocGroup() const {
+#ifdef DEBUG
+ AssertDocGroupMatchesKey();
+#endif
+ return mDocGroup;
+ }
+
+ DocGroup* GetDocGroupOrCreate();
+
+ /**
+ * If we're a sub-document, the parent document's layout can affect our style
+ * and layout (due to the viewport size, viewport units, media queries...).
+ *
+ * This function returns true if our parent document and our child document
+ * can observe each other. If they cannot, then we don't need to synchronously
+ * update the parent document layout every time the child document may need
+ * up-to-date layout information.
+ */
+ bool StyleOrLayoutObservablyDependsOnParentDocumentLayout() const {
+ return GetInProcessParentDocument() &&
+ GetDocGroup() == GetInProcessParentDocument()->GetDocGroup();
+ }
+
+ void AddIntersectionObserver(DOMIntersectionObserver* aObserver) {
+ MOZ_ASSERT(!mIntersectionObservers.Contains(aObserver),
+ "Intersection observer already in the list");
+ mIntersectionObservers.Insert(aObserver);
+ }
+
+ void RemoveIntersectionObserver(DOMIntersectionObserver* aObserver) {
+ mIntersectionObservers.Remove(aObserver);
+ }
+
+ bool HasIntersectionObservers() const {
+ return !mIntersectionObservers.IsEmpty();
+ }
+
+ void UpdateIntersectionObservations(TimeStamp aNowTime);
+ void ScheduleIntersectionObserverNotification();
+ MOZ_CAN_RUN_SCRIPT void NotifyIntersectionObservers();
+
+ DOMIntersectionObserver* GetLazyLoadImageObserver() {
+ return mLazyLoadImageObserver;
+ }
+ DOMIntersectionObserver* GetLazyLoadImageObserverViewport() {
+ return mLazyLoadImageObserverViewport;
+ }
+ DOMIntersectionObserver& EnsureLazyLoadImageObserver();
+
+ DOMIntersectionObserver* GetContentVisibilityObserver() const {
+ return mContentVisibilityObserver;
+ }
+ DOMIntersectionObserver& EnsureContentVisibilityObserver();
+ void ObserveForContentVisibility(Element&);
+ void UnobserveForContentVisibility(Element&);
+
+ ResizeObserver* GetLastRememberedSizeObserver() {
+ return mLastRememberedSizeObserver;
+ }
+ ResizeObserver& EnsureLastRememberedSizeObserver();
+ void ObserveForLastRememberedSize(Element&);
+ void UnobserveForLastRememberedSize(Element&);
+
+ // Dispatch a runnable related to the document.
+ nsresult Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) final;
+
+ nsISerialEventTarget* EventTargetFor(TaskCategory) const override;
+
+ AbstractThread* AbstractMainThreadFor(TaskCategory) override;
+
+ // The URLs passed to this function should match what
+ // JS::DescribeScriptedCaller() returns, since this API is used to
+ // determine whether some code is being called from a tracking script.
+ void NoteScriptTrackingStatus(const nsACString& aURL, bool isTracking);
+ // The JSContext passed to this method represents the context that we want to
+ // determine if it belongs to a tracker.
+ bool IsScriptTracking(JSContext* aCx) const;
+
+ // ResizeObserver usage.
+ void AddResizeObserver(ResizeObserver&);
+ void RemoveResizeObserver(ResizeObserver&);
+ void ScheduleResizeObserversNotification() const;
+
+ // Getter for PermissionDelegateHandler. Performs lazy initialization.
+ PermissionDelegateHandler* GetPermissionDelegateHandler();
+
+ // Notify the document that a fetch or a XHR request has completed
+ // succesfully in this document. This is used by the password manager to infer
+ // whether a form is submitted.
+ void NotifyFetchOrXHRSuccess();
+
+ // Set whether NotifyFetchOrXHRSuccess should dispatch an event.
+ void SetNotifyFetchSuccess(bool aShouldNotify);
+
+ // When this is set, removing a form or a password field from DOM
+ // sends a Chrome-only event. This is now only used by the password manager.
+ void SetNotifyFormOrPasswordRemoved(bool aShouldNotify);
+
+ // This function is used by HTMLFormElement and HTMLInputElement to determin
+ // whether to send an event when it is removed from DOM.
+ bool ShouldNotifyFormOrPasswordRemoved() const {
+ return mShouldNotifyFormOrPasswordRemoved;
+ }
+
+ HTMLEditor* GetHTMLEditor() const;
+
+ /**
+ * Localization
+ *
+ * For more information on DocumentL10n see
+ * intl/l10n/docs/fluent/tutorial.rst
+ */
+
+ public:
+ /**
+ * This is a public method exposed on Document WebIDL
+ * to chrome only documents.
+ */
+ DocumentL10n* GetL10n() const { return mDocumentL10n.get(); }
+
+ /**
+ * Whether there's any async l10n mutation work pending.
+ *
+ * When this turns false, we fire the L10nMutationsFinished event.
+ */
+ bool HasPendingL10nMutations() const;
+
+ /**
+ * This method should be called when the container
+ * of l10n resources parsing is completed.
+ *
+ * It triggers initial async fetch of the resources
+ * as early as possible.
+ *
+ * In HTML case this is </head>.
+ * In XUL case this is </linkset>.
+ */
+ void OnL10nResourceContainerParsed();
+
+ /**
+ * This method should be called when a link element
+ * with rel="localization" is being added to the
+ * l10n resource container element.
+ */
+ void LocalizationLinkAdded(Element* aLinkElement);
+
+ /**
+ * This method should be called when a link element
+ * with rel="localization" is being removed.
+ */
+ void LocalizationLinkRemoved(Element* aLinkElement);
+
+ /**
+ * This method should be called as soon as the
+ * parsing of the document is completed.
+ *
+ * In HTML/XHTML this happens when we finish parsing
+ * the document element.
+ * In XUL it happens at `DoneWalking`, during
+ * `MozBeforeInitialXULLayout`.
+ */
+ void OnParsingCompleted();
+
+ /**
+ * This method is called when the initial translation
+ * of the document is completed.
+ *
+ * It unblocks the load event if translation was blocking it.
+ *
+ * If the `aL10nCached` is set to `true`, and the document has
+ * a prototype, it will set the `isL10nCached` flag on it.
+ */
+ void InitialTranslationCompleted(bool aL10nCached);
+
+ /**
+ * Returns whether the document allows localization.
+ */
+ bool AllowsL10n() const;
+
+ protected:
+ RefPtr<DocumentL10n> mDocumentL10n;
+
+ /**
+ * Return true when you want a document without explicitly specified viewport
+ * dimensions/scale to be treated as if "width=device-width" had in fact been
+ * specified.
+ */
+ virtual bool UseWidthDeviceWidthFallbackViewport() const;
+
+ private:
+ bool IsErrorPage() const;
+
+ // Takes the bits from mStyleUseCounters if appropriate, and sets them in
+ // mUseCounters.
+ void SetCssUseCounterBits();
+
+ void ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+ const nsAString& aHeightString,
+ bool aIsAutoScale);
+
+ // Parse scale values in viewport meta tag for a given |aHeaderField| which
+ // represents the scale property and returns the scale value if it's valid.
+ Maybe<LayoutDeviceToScreenScale> ParseScaleInHeader(nsAtom* aHeaderField);
+
+ // Parse scale values in |aViewportMetaData| and set the values in
+ // mScaleMinFloat, mScaleMaxFloat and mScaleFloat respectively.
+ void ParseScalesInViewportMetaData(const ViewportMetaData& aViewportMetaData);
+
+ // Get parent FeaturePolicy from container. The parent FeaturePolicy is
+ // stored in parent iframe or container's browsingContext (cross process)
+ already_AddRefed<mozilla::dom::FeaturePolicy> GetParentFeaturePolicy();
+
+ public:
+ const OriginTrials& Trials() const { return mTrials; }
+
+ private:
+ void DoCacheAllKnownLangPrefs();
+ void RecomputeLanguageFromCharset();
+ bool GetSHEntryHasUserInteraction();
+
+ void AppendAutoFocusCandidateToTopDocument(Element* aAutoFocusCandidate);
+
+ public:
+ void SetMayNeedFontPrefsUpdate() { mMayNeedFontPrefsUpdate = true; }
+
+ bool MayNeedFontPrefsUpdate() { return mMayNeedFontPrefsUpdate; }
+
+ void SetSHEntryHasUserInteraction(bool aHasInteraction);
+
+ already_AddRefed<nsAtom> GetContentLanguageAsAtomForStyle() const;
+ already_AddRefed<nsAtom> GetLanguageForStyle() const;
+
+ /**
+ * Fetch the user's font preferences for the given aLanguage's
+ * language group.
+ */
+ const LangGroupFontPrefs* GetFontPrefsForLang(
+ nsAtom* aLanguage, bool* aNeedsToCache = nullptr) const;
+
+ void ForceCacheLang(nsAtom* aLanguage) {
+ if (!mLanguagesUsed.EnsureInserted(aLanguage)) {
+ return;
+ }
+ GetFontPrefsForLang(aLanguage);
+ }
+
+ void CacheAllKnownLangPrefs() {
+ if (!mMayNeedFontPrefsUpdate) {
+ return;
+ }
+ DoCacheAllKnownLangPrefs();
+ }
+
+ nsINode* GetServoRestyleRoot() const { return mServoRestyleRoot; }
+
+ uint32_t GetServoRestyleRootDirtyBits() const {
+ MOZ_ASSERT(mServoRestyleRoot);
+ MOZ_ASSERT(mServoRestyleRootDirtyBits);
+ return mServoRestyleRootDirtyBits;
+ }
+
+ void ClearServoRestyleRoot() {
+ mServoRestyleRoot = nullptr;
+ mServoRestyleRootDirtyBits = 0;
+ }
+
+ inline void SetServoRestyleRoot(nsINode* aRoot, uint32_t aDirtyBits);
+ inline void SetServoRestyleRootDirtyBits(uint32_t aDirtyBits);
+
+ bool ShouldThrowOnDynamicMarkupInsertion() {
+ return mThrowOnDynamicMarkupInsertionCounter;
+ }
+
+ void IncrementThrowOnDynamicMarkupInsertionCounter() {
+ ++mThrowOnDynamicMarkupInsertionCounter;
+ }
+
+ void DecrementThrowOnDynamicMarkupInsertionCounter() {
+ MOZ_ASSERT(mThrowOnDynamicMarkupInsertionCounter);
+ --mThrowOnDynamicMarkupInsertionCounter;
+ }
+
+ bool ShouldIgnoreOpens() const { return mIgnoreOpensDuringUnloadCounter; }
+
+ void IncrementIgnoreOpensDuringUnloadCounter() {
+ ++mIgnoreOpensDuringUnloadCounter;
+ }
+
+ void DecrementIgnoreOpensDuringUnloadCounter() {
+ MOZ_ASSERT(mIgnoreOpensDuringUnloadCounter);
+ --mIgnoreOpensDuringUnloadCounter;
+ }
+
+ mozilla::dom::FeaturePolicy* FeaturePolicy() const;
+
+ bool ModuleScriptsEnabled();
+
+ bool ImportMapsEnabled();
+
+ /**
+ * Find the (non-anonymous) content in this document for aFrame. It will
+ * be aFrame's content node if that content is in this document and not
+ * anonymous. Otherwise, when aFrame is in a subdocument, we use the frame
+ * element containing the subdocument containing aFrame, and/or find the
+ * nearest non-anonymous ancestor in this document.
+ * Returns null if there is no such element.
+ */
+ nsIContent* GetContentInThisDocument(nsIFrame* aFrame) const;
+
+ void ReportShadowDOMUsage();
+
+ // Sets flags for media telemetry.
+ void SetDocTreeHadMedia();
+
+ dom::XPathEvaluator* XPathEvaluator();
+
+ void MaybeInitializeFinalizeFrameLoaders();
+
+ void SetDelayFrameLoaderInitialization(bool aDelayFrameLoaderInitialization) {
+ mDelayFrameLoaderInitialization = aDelayFrameLoaderInitialization;
+ }
+
+ void SetPrototypeDocument(nsXULPrototypeDocument* aPrototype);
+
+ nsIPermissionDelegateHandler* PermDelegateHandler();
+
+ // CSS prefers-color-scheme media feature for this document.
+ enum class IgnoreRFP { No, Yes };
+ ColorScheme PreferredColorScheme(IgnoreRFP = IgnoreRFP::No) const;
+ // Returns the initial color-scheme used for this document based on the
+ // color-scheme meta tag.
+ ColorScheme DefaultColorScheme() const;
+
+ static bool HasRecentlyStartedForegroundLoads();
+
+ static bool AutomaticStorageAccessPermissionCanBeGranted(
+ nsIPrincipal* aPrincipal);
+
+ already_AddRefed<Promise> AddCertException(bool aIsTemporary,
+ ErrorResult& aError);
+
+ void ReloadWithHttpsOnlyException();
+
+ // Subframes need to be static cloned after the main document has been
+ // embedded within a script global. A `PendingFrameStaticClone` is a static
+ // clone which has not yet been performed.
+ //
+ // The getter returns a direct reference to an internal array which is
+ // manipulated from within printing code.
+ struct PendingFrameStaticClone {
+ PendingFrameStaticClone() = default;
+ PendingFrameStaticClone(PendingFrameStaticClone&&) = default;
+ PendingFrameStaticClone& operator=(PendingFrameStaticClone&&) = default;
+ ~PendingFrameStaticClone();
+
+ RefPtr<nsFrameLoaderOwner> mElement;
+ RefPtr<nsFrameLoader> mStaticCloneOf;
+ };
+ void AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
+ nsFrameLoader* aStaticCloneOf);
+
+ bool ShouldAvoidNativeTheme() const;
+
+ static bool IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI);
+
+ // Inform a parent document that a BrowserBridgeChild has been created for
+ // an OOP sub-document.
+ // (This is the OOP counterpart to nsDocLoader::ChildEnteringOnload)
+ void OOPChildLoadStarted(BrowserBridgeChild* aChild);
+
+ // Inform a parent document that the BrowserBridgeChild for one of its
+ // OOP sub-documents is done calling its onload handler.
+ // (This is the OOP counterpart to nsDocLoader::ChildDoneWithOnload)
+ void OOPChildLoadDone(BrowserBridgeChild* aChild);
+
+ void ClearOOPChildrenLoading();
+
+ bool HasOOPChildrenLoading() { return !mOOPChildrenLoading.IsEmpty(); }
+
+ void SetDidHitCompleteSheetCache() { mDidHitCompleteSheetCache = true; }
+
+ bool DidHitCompleteSheetCache() const { return mDidHitCompleteSheetCache; }
+
+ /**
+ * Get the `HighlightRegistry` which contains all highlights associated
+ * with this document.
+ */
+ class HighlightRegistry& HighlightRegistry();
+
+ bool ShouldResistFingerprinting(RFPTarget aTarget) const;
+
+ // Recompute the current resist fingerprinting state. Returns true when
+ // the state was changed.
+ bool RecomputeResistFingerprinting();
+
+ protected:
+ // Returns the WindowContext for the document that we will contribute
+ // page use counters to.
+ WindowContext* GetWindowContextForPageUseCounters() const;
+
+ void DoUpdateSVGUseElementShadowTrees();
+
+ already_AddRefed<nsIPrincipal> MaybeDowngradePrincipal(
+ nsIPrincipal* aPrincipal);
+
+ void EnsureOnloadBlocker();
+
+ void SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages);
+
+ // Returns true if the scheme for the url for this document is "about".
+ bool IsAboutPage() const;
+
+ bool ContainsEMEContent();
+ bool ContainsMSEContent();
+
+ /**
+ * Returns the title element of the document as defined by the HTML
+ * specification, or null if there isn't one. For documents whose root
+ * element is an <svg:svg>, this is the first <svg:title> element that's a
+ * child of the root. For other documents, it's the first HTML title element
+ * in the document.
+ */
+ Element* GetTitleElement();
+
+ void RecordNavigationTiming(ReadyState aReadyState);
+
+ // Recomputes the visibility state but doesn't set the new value.
+ dom::VisibilityState ComputeVisibilityState() const;
+
+ // Since we wouldn't automatically play media from non-visited page, we need
+ // to notify window when the page was first visited.
+ void MaybeActiveMediaComponents();
+
+ // Apply the fullscreen state to the document, and trigger related
+ // events. It returns false if the fullscreen element ready check
+ // fails and nothing gets changed.
+ bool ApplyFullscreen(UniquePtr<FullscreenRequest>);
+
+ void RemoveDocStyleSheetsFromStyleSets();
+ void ResetStylesheetsToURI(nsIURI* aURI);
+ void FillStyleSet();
+ void FillStyleSetUserAndUASheets();
+ void FillStyleSetDocumentSheets();
+ void CompatibilityModeChanged();
+ bool NeedsQuirksSheet() const {
+ // SVG documents never load quirk.css.
+ // FIXME(emilio): Can SVG documents be in quirks mode anyway?
+ return mCompatMode == eCompatibility_NavQuirks && !IsSVGDocument();
+ }
+ void AddContentEditableStyleSheetsToStyleSet(bool aDesignMode);
+ void RemoveContentEditableStyleSheets();
+ void AddStyleSheetToStyleSets(StyleSheet&);
+ void RemoveStyleSheetFromStyleSets(StyleSheet&);
+ void NotifyStyleSheetApplicableStateChanged();
+ // Just like EnableStyleSheetsForSet, but doesn't check whether
+ // aSheetSet is null and allows the caller to control whether to set
+ // aSheetSet as the preferred set in the CSSLoader.
+ void EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
+ bool aUpdateCSSLoader);
+
+ already_AddRefed<nsIURI> GetDomainURI();
+ already_AddRefed<nsIURI> CreateInheritingURIForHost(
+ const nsACString& aHostString);
+ already_AddRefed<nsIURI> RegistrableDomainSuffixOfInternal(
+ const nsAString& aHostSuffixString, nsIURI* aOrigHost);
+
+ void WriteCommon(const nsAString& aText, bool aNewlineTerminate,
+ mozilla::ErrorResult& aRv);
+ // A version of WriteCommon used by WebIDL bindings
+ void WriteCommon(const mozilla::dom::Sequence<nsString>& aText,
+ bool aNewlineTerminate, mozilla::ErrorResult& rv);
+
+ void* GenerateParserKey(void);
+
+ private:
+ // ExecCommandParam indicates how HTMLDocument.execCommand() treats given the
+ // parameter.
+ enum class ExecCommandParam : uint8_t {
+ // Always ignore it.
+ Ignore,
+ // Treat the given parameter as-is. If the command requires it, use it.
+ // Otherwise, ignore it.
+ String,
+ // Always treat it as boolean parameter.
+ Boolean,
+ // Always treat it as boolean, but inverted.
+ InvertedBoolean,
+ };
+
+ using GetEditorCommandFunc = mozilla::EditorCommand*();
+
+ struct InternalCommandData {
+ const char* mXULCommandName;
+ mozilla::Command mCommand; // uint8_t
+ // How ConvertToInternalCommand() to treats aValue.
+ // Its callers don't need to check this.
+ ExecCommandParam mExecCommandParam; // uint8_t
+ GetEditorCommandFunc* mGetEditorCommandFunc;
+ enum class CommandOnTextEditor : uint8_t {
+ Disabled,
+ Enabled,
+ FallThrough, // Not disabled, but handled by HTMLEditor if there is one
+ };
+ CommandOnTextEditor mCommandOnTextEditor;
+
+ InternalCommandData()
+ : mXULCommandName(nullptr),
+ mCommand(mozilla::Command::DoNothing),
+ mExecCommandParam(ExecCommandParam::Ignore),
+ mGetEditorCommandFunc(nullptr),
+ mCommandOnTextEditor(CommandOnTextEditor::Disabled) {}
+ InternalCommandData(const char* aXULCommandName, mozilla::Command aCommand,
+ ExecCommandParam aExecCommandParam,
+ GetEditorCommandFunc aGetEditorCommandFunc,
+ CommandOnTextEditor aCommandOnTextEditor)
+ : mXULCommandName(aXULCommandName),
+ mCommand(aCommand),
+ mExecCommandParam(aExecCommandParam),
+ mGetEditorCommandFunc(aGetEditorCommandFunc),
+ mCommandOnTextEditor(aCommandOnTextEditor) {}
+
+ bool IsAvailableOnlyWhenEditable() const {
+ return mCommand != mozilla::Command::Cut &&
+ mCommand != mozilla::Command::Copy &&
+ mCommand != mozilla::Command::Paste &&
+ mCommand != mozilla::Command::SetDocumentReadOnly &&
+ mCommand != mozilla::Command::SelectAll;
+ }
+ bool IsCutOrCopyCommand() const {
+ return mCommand == mozilla::Command::Cut ||
+ mCommand == mozilla::Command::Copy;
+ }
+ bool IsPasteCommand() const { return mCommand == mozilla::Command::Paste; }
+ };
+
+ /**
+ * AutoEditorCommandTarget considers which editor or global command manager
+ * handles given command.
+ */
+ class MOZ_RAII AutoEditorCommandTarget {
+ public:
+ MOZ_CAN_RUN_SCRIPT AutoEditorCommandTarget(
+ Document& aDocument, const InternalCommandData& aCommandData);
+ AutoEditorCommandTarget() = delete;
+ explicit AutoEditorCommandTarget(const AutoEditorCommandTarget& aOther) =
+ delete;
+
+ bool DoNothing() const { return mDoNothing; }
+ MOZ_CAN_RUN_SCRIPT bool IsEditable(Document* aDocument) const;
+ bool IsEditor() const {
+ MOZ_ASSERT_IF(mEditorCommand, mActiveEditor || mHTMLEditor);
+ return !!mEditorCommand;
+ }
+
+ MOZ_CAN_RUN_SCRIPT bool IsCommandEnabled() const;
+ MOZ_CAN_RUN_SCRIPT nsresult DoCommand(nsIPrincipal* aPrincipal) const;
+ template <typename ParamType>
+ MOZ_CAN_RUN_SCRIPT nsresult DoCommandParam(const ParamType& aParam,
+ nsIPrincipal* aPrincipal) const;
+ MOZ_CAN_RUN_SCRIPT nsresult
+ GetCommandStateParams(nsCommandParams& aParams) const;
+
+ private:
+ // The returned editor's life is guaranteed while this instance is alive.
+ EditorBase* GetTargetEditor() const;
+
+ RefPtr<EditorBase> mActiveEditor;
+ RefPtr<HTMLEditor> mHTMLEditor;
+ RefPtr<EditorCommand> mEditorCommand;
+ const InternalCommandData& mCommandData;
+ bool mDoNothing = false;
+ };
+
+ /**
+ * Helper method to initialize sInternalCommandDataHashtable.
+ */
+ static void EnsureInitializeInternalCommandDataHashtable();
+
+ /**
+ * ConvertToInternalCommand() returns a copy of InternalCommandData instance.
+ * Note that if aAdjustedValue is non-nullptr, this method checks whether
+ * aValue is proper value or not unless InternalCommandData::mExecCommandParam
+ * is ExecCommandParam::Ignore. For example, if aHTMLCommandName is
+ * "defaultParagraphSeparator", the value has to be one of "div", "p" or
+ * "br". If aValue is invalid value for InternalCommandData::mCommand, this
+ * returns a copy of instance created with default constructor. I.e., its
+ * mCommand is set to Command::DoNothing. So, this treats aHTMLCommandName
+ * is unsupported in such case.
+ *
+ * @param aHTMLCommandName Command name in HTML, e.g., used by
+ * execCommand().
+ * @param aValue The value which is set to the 3rd parameter
+ * of execCommand().
+ * @param aAdjustedValue [out] Must be empty string if set non-nullptr.
+ * Will be set to adjusted value for executing
+ * the internal command.
+ * @return Returns a copy of instance created with the
+ * default constructor if there is no
+ * corresponding internal command for
+ * aHTMLCommandName or aValue is invalid for
+ * found internal command when aAdjustedValue
+ * is not nullptr. Otherwise, returns a copy of
+ * instance registered in
+ * sInternalCommandDataHashtable.
+ */
+ static InternalCommandData ConvertToInternalCommand(
+ const nsAString& aHTMLCommandName, const nsAString& aValue = u""_ns,
+ nsAString* aAdjustedValue = nullptr);
+
+ /**
+ * AutoRunningExecCommandMarker is AutoRestorer for mIsRunningExecCommand.
+ * Since it's a bit field, not a bool member, therefore, we cannot use
+ * AutoRestorer for it.
+ */
+ class MOZ_STACK_CLASS AutoRunningExecCommandMarker final {
+ public:
+ AutoRunningExecCommandMarker() = delete;
+ explicit AutoRunningExecCommandMarker(const AutoRunningExecCommandMarker&) =
+ delete;
+ // Guaranteeing the document's lifetime with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT explicit AutoRunningExecCommandMarker(
+ Document& aDocument)
+ : mDocument(aDocument),
+ mHasBeenRunning(aDocument.mIsRunningExecCommand) {
+ aDocument.mIsRunningExecCommand = true;
+ }
+ ~AutoRunningExecCommandMarker() {
+ if (!mHasBeenRunning) {
+ mDocument.mIsRunningExecCommand = false;
+ }
+ }
+
+ private:
+ Document& mDocument;
+ bool mHasBeenRunning;
+ };
+
+ // Mapping table from HTML command name to internal command.
+ using InternalCommandDataHashtable =
+ nsTHashMap<nsStringCaseInsensitiveHashKey, InternalCommandData>;
+ static InternalCommandDataHashtable* sInternalCommandDataHashtable;
+
+ mutable std::bitset<static_cast<size_t>(
+ DeprecatedOperations::eDeprecatedOperationCount)>
+ mDeprecationWarnedAbout;
+ mutable std::bitset<eDocumentWarningCount> mDocWarningWarnedAbout;
+
+ // Lazy-initialization to have mDocGroup initialized in prior to the
+ // SelectorCaches.
+ UniquePtr<SelectorCache> mSelectorCache;
+ UniquePtr<ServoStyleSet> mStyleSet;
+
+ protected:
+ // Never ever call this. Only call GetWindow!
+ nsPIDOMWindowOuter* GetWindowInternal() const;
+
+ // Never ever call this. Only call GetScriptHandlingObject!
+ nsIScriptGlobalObject* GetScriptHandlingObjectInternal() const;
+
+ // Never ever call this. Only call AllowXULXBL!
+ bool InternalAllowXULXBL();
+
+ /**
+ * These methods should be called before and after dispatching
+ * a mutation event.
+ * To make this easy and painless, use the mozAutoSubtreeModified helper
+ * class.
+ */
+ void WillDispatchMutationEvent(nsINode* aTarget);
+ void MutationEventDispatched(nsINode* aTarget);
+ friend class mozAutoSubtreeModified;
+
+ virtual Element* GetNameSpaceElement() override { return GetRootElement(); }
+
+ nsCString GetContentTypeInternal() const { return mContentType; }
+
+ // Update our frame request callback scheduling state, if needed. This will
+ // schedule or unschedule them, if necessary, and update
+ // mFrameRequestCallbacksScheduled. aOldShell should only be passed when
+ // mPresShell is becoming null; in that case it will be used to get hold of
+ // the relevant refresh driver.
+ void UpdateFrameRequestCallbackSchedulingState(
+ PresShell* aOldPresShell = nullptr);
+
+ // Helper for GetScrollingElement/IsScrollingElement.
+ bool IsPotentiallyScrollable(HTMLBodyElement* aBody);
+
+ void MaybeAllowStorageForOpenerAfterUserInteraction();
+
+ void MaybeStoreUserInteractionAsPermission();
+
+ // Helpers for GetElementsByName.
+ static bool MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAtom, void* aData);
+ static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
+
+ void MaybeResolveReadyForIdle();
+
+ using AutomaticStorageAccessPermissionGrantPromise =
+ MozPromise<bool, bool, true>;
+ [[nodiscard]] RefPtr<AutomaticStorageAccessPermissionGrantPromise>
+ AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation);
+
+ static void AddToplevelLoadingDocument(Document* aDoc);
+ static void RemoveToplevelLoadingDocument(Document* aDoc);
+ static AutoTArray<Document*, 8>* sLoadingForegroundTopLevelContentDocument;
+ friend class cycleCollection;
+
+ nsCOMPtr<nsIReferrerInfo> mPreloadReferrerInfo;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+ nsString mLastModified;
+
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mChromeXHRDocURI;
+ nsCOMPtr<nsIURI> mDocumentBaseURI;
+ nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
+
+ // The base domain of the document for third-party checks.
+ nsCString mBaseDomain;
+
+ // A lazily-constructed URL data for style system to resolve URL values.
+ RefPtr<URLExtraData> mCachedURLData;
+ nsCOMPtr<nsIReferrerInfo> mCachedReferrerInfoForInternalCSSAndSVGResources;
+
+ nsWeakPtr mDocumentLoadGroup;
+
+ WeakPtr<nsDocShell> mDocumentContainer;
+
+ NotNull<const Encoding*> mCharacterSet;
+ int32_t mCharacterSetSource;
+
+ OriginTrials mTrials;
+
+ // This is just a weak pointer; the parent document owns its children.
+ Document* mParentDocument;
+
+ // A reference to the element last returned from GetRootElement().
+ Element* mCachedRootElement;
+
+ // This is maintained by AutoSetRestoreSVGContextPaint.
+ const SVGContextPaint* mCurrentContextPaint = nullptr;
+
+ // This is a weak reference, but we hold a strong reference to mNodeInfo,
+ // which in turn holds a strong reference to this mNodeInfoManager.
+ nsNodeInfoManager* mNodeInfoManager;
+ RefPtr<css::Loader> mCSSLoader;
+ RefPtr<css::ImageLoader> mStyleImageLoader;
+ RefPtr<nsHTMLStyleSheet> mAttrStyleSheet;
+ RefPtr<nsHTMLCSSStyleSheet> mStyleAttrStyleSheet;
+
+ // Tracking for images in the document.
+ RefPtr<dom::ImageTracker> mImageTracker;
+
+ // A hashtable of ShadowRoots belonging to the composed doc.
+ //
+ // See ShadowRoot::Bind and ShadowRoot::Unbind.
+ ShadowRootSet mComposedShadowRoots;
+
+ using SVGUseElementSet = nsTHashSet<SVGUseElement*>;
+
+ // The set of <svg:use> elements that need a shadow tree reclone because the
+ // tree they map to has changed.
+ SVGUseElementSet mSVGUseElementsNeedingShadowTreeUpdate;
+
+ // The set of all object, embed, video/audio elements or
+ // nsIObjectLoadingContent or DocumentActivity for which this is
+ // the owner document. (They might not be in the document.)
+ //
+ // These are non-owning pointers, the elements are responsible for removing
+ // themselves when they go away.
+ UniquePtr<nsTHashSet<nsISupports*>> mActivityObservers;
+
+ // A hashtable of styled links keyed by address pointer.
+ nsTHashSet<Link*> mStyledLinks;
+#ifdef DEBUG
+ // Indicates whether mStyledLinks was cleared or not. This is used to track
+ // state so we can provide useful assertions to consumers of ForgetLink and
+ // AddStyleRelevantLink.
+ bool mStyledLinksCleared;
+#endif
+
+ // The array of all links that need their status resolved. Links must add
+ // themselves to this set by calling RegisterPendingLinkUpdate when added to a
+ // document.
+ static const size_t kSegmentSize = 128;
+
+ using LinksToUpdateList =
+ SegmentedVector<nsCOMPtr<Link>, kSegmentSize, InfallibleAllocPolicy>;
+
+ LinksToUpdateList mLinksToUpdate;
+
+ // SMIL Animation Controller, lazily-initialized in GetAnimationController
+ RefPtr<SMILAnimationController> mAnimationController;
+
+ // Table of element properties for this document.
+ nsPropertyTable mPropertyTable;
+
+ // Our cached .children collection
+ nsCOMPtr<nsIHTMLCollection> mChildrenCollection;
+
+ // Various DOM lists
+ RefPtr<nsContentList> mImages;
+ RefPtr<nsContentList> mEmbeds;
+ RefPtr<nsContentList> mLinks;
+ RefPtr<nsContentList> mForms;
+ RefPtr<nsContentList> mScripts;
+ nsCOMPtr<nsIHTMLCollection> mApplets;
+ RefPtr<nsContentList> mAnchors;
+
+ // container for per-context fonts (downloadable, SVG, etc.)
+ RefPtr<FontFaceSet> mFontFaceSet;
+
+ // Last time this document or a one of its sub-documents was focused. If
+ // focus has never occurred then mLastFocusTime.IsNull() will be true.
+ TimeStamp mLastFocusTime;
+
+ // Last time we found any scroll linked effect in this document.
+ TimeStamp mLastScrollLinkedEffectDetectionTime;
+
+ DocumentState mDocumentState{DocumentState::LTR_LOCALE};
+
+ RefPtr<Promise> mReadyForIdle;
+
+ RefPtr<mozilla::dom::FeaturePolicy> mFeaturePolicy;
+
+ UniquePtr<ResizeObserverController> mResizeObserverController;
+
+ // Permission Delegate Handler, lazily-initialized in
+ // GetPermissionDelegateHandler
+ RefPtr<PermissionDelegateHandler> mPermissionDelegateHandler;
+
+ bool mCachedStateObjectValid : 1;
+ bool mBlockAllMixedContent : 1;
+ bool mBlockAllMixedContentPreloads : 1;
+ bool mUpgradeInsecureRequests : 1;
+ bool mUpgradeInsecurePreloads : 1;
+ bool mDevToolsWatchingDOMMutations : 1;
+
+ // True if BIDI is enabled.
+ bool mBidiEnabled : 1;
+ // True if we may need to recompute the language prefs for this document.
+ bool mMayNeedFontPrefsUpdate : 1;
+ // True if a MathML element has ever been owned by this document.
+ bool mMathMLEnabled : 1;
+
+ // True if this document is the initial document for a window. This should
+ // basically be true only for documents that exist in newly-opened windows or
+ // documents created to satisfy a GetDocument() on a window when there's no
+ // document in it.
+ bool mIsInitialDocumentInWindow : 1;
+
+ // True if this document has ever been the initial document for a window. This
+ // is useful to determine if a document that was the initial document at one
+ // point, and became non-initial later.
+ bool mIsEverInitialDocumentInWindow : 1;
+
+ bool mIgnoreDocGroupMismatches : 1;
+
+ // True if we're loaded as data and therefor has any dangerous stuff, such
+ // as scripts and plugins, disabled.
+ bool mLoadedAsData : 1;
+
+ // True if the document is considered for memory reporting as a
+ // data document
+ bool mAddedToMemoryReportingAsDataDocument : 1;
+
+ // If true, whoever is creating the document has gotten it to the
+ // point where it's safe to start layout on it.
+ bool mMayStartLayout : 1;
+
+ // True iff we've ever fired a DOMTitleChanged event for this document
+ bool mHaveFiredTitleChange : 1;
+
+ // State for IsShowing(). mIsShowing starts off false. It becomes true when
+ // OnPageShow happens and becomes false when OnPageHide happens. So it's false
+ // before the initial load completes and when we're in bfcache or unloaded,
+ // true otherwise.
+ bool mIsShowing : 1;
+
+ // State for IsVisible(). mVisible starts off true. It becomes false when
+ // OnPageHide happens, and becomes true again when OnPageShow happens. So
+ // it's false only when we're in bfcache or unloaded.
+ bool mVisible : 1;
+
+ // True if our content viewer has been removed from the docshell
+ // (it may still be displayed, but in zombie state). Form control data
+ // has been saved.
+ bool mRemovedFromDocShell : 1;
+
+ // True iff DNS prefetch is allowed for this document. Note that if the
+ // document has no window, DNS prefetch won't be performed no matter what.
+ bool mAllowDNSPrefetch : 1;
+
+ // True when this document is a static clone of a normal document
+ bool mIsStaticDocument : 1;
+
+ // True while this document is being cloned to a static document.
+ bool mCreatingStaticClone : 1;
+
+ // True if this static document has any <canvas> element with a
+ // mozPrintCallback property at the time of the clone.
+ bool mHasPrintCallbacks : 1;
+
+ // True iff the document is being unlinked or deleted.
+ bool mInUnlinkOrDeletion : 1;
+
+ // True if document has ever had script handling object.
+ bool mHasHadScriptHandlingObject : 1;
+
+ // True if we're an SVG document being used as an image.
+ bool mIsBeingUsedAsImage : 1;
+
+ // True if our current document URI's scheme enables privileged CSS rules.
+ bool mChromeRulesEnabled : 1;
+
+ // True if we're loaded in a chrome docshell.
+ bool mInChromeDocShell : 1;
+
+ // True if our current document is a DevTools document. Either the url is
+ // about:devtools-toolbox or the parent document already has
+ // mIsDevToolsDocument set to true.
+ // This is used to avoid applying High Contrast mode to DevTools documents.
+ // See Bug 1575766.
+ bool mIsDevToolsDocument : 1;
+
+ // True is this document is synthetic : stand alone image, video, audio
+ // file, etc.
+ bool mIsSyntheticDocument : 1;
+
+ // True is there is a pending runnable which will call
+ // FlushPendingLinkUpdates().
+ bool mHasLinksToUpdateRunnable : 1;
+
+ // True if we're flushing pending link updates.
+ bool mFlushingPendingLinkUpdates : 1;
+
+ // True if a DOMMutationObserver is perhaps attached to a node in the
+ // document.
+ bool mMayHaveDOMMutationObservers : 1;
+
+ // True if an nsIAnimationObserver is perhaps attached to a node in the
+ // document.
+ bool mMayHaveAnimationObservers : 1;
+
+ // True if a document load has a CSP attached.
+ bool mHasCSP : 1;
+
+ // True if a document load has a CSP with unsafe-eval attached.
+ bool mHasUnsafeEvalCSP : 1;
+
+ // True if a document load has a CSP with unsafe-inline attached.
+ bool mHasUnsafeInlineCSP : 1;
+
+ // True if the document has a CSP delivered throuh a header
+ bool mHasCSPDeliveredThroughHeader : 1;
+
+ // True if DisallowBFCaching has been called on this document.
+ bool mBFCacheDisallowed : 1;
+
+ bool mHasHadDefaultView : 1;
+
+ // Whether style sheet change events will be dispatched for this document
+ bool mStyleSheetChangeEventsEnabled : 1;
+
+ // Whether shadowrootattached/anonymousnodecreated/anonymousnoderemoved events
+ // will be dispatched for this document.
+ bool mDevToolsAnonymousAndShadowEventsEnabled : 1;
+
+ // Whether the document was created by a srcdoc iframe.
+ bool mIsSrcdocDocument : 1;
+
+ // Whether this document has a display document and thus is considered to
+ // be a resource document. Normally this is the same as !!mDisplayDocument,
+ // but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is
+ // valid in the document's destructor.
+ bool mHasDisplayDocument : 1;
+
+ // Is the current mFontFaceSet valid?
+ bool mFontFaceSetDirty : 1;
+
+ // True if we have fired the DOMContentLoaded event, or don't plan to fire one
+ // (e.g. we're not being parsed at all).
+ bool mDidFireDOMContentLoaded : 1;
+
+ // True if we have frame request callbacks scheduled with the refresh driver.
+ // This should generally be updated only via
+ // UpdateFrameRequestCallbackSchedulingState.
+ bool mFrameRequestCallbacksScheduled : 1;
+
+ bool mIsTopLevelContentDocument : 1;
+
+ bool mIsContentDocument : 1;
+
+ // True if we have called BeginLoad and are expecting a paired EndLoad call.
+ bool mDidCallBeginLoad : 1;
+
+ // True if the encoding menu should be disabled.
+ bool mEncodingMenuDisabled : 1;
+
+ // False if we've disabled link handling for elements inside this document,
+ // true otherwise.
+ bool mLinksEnabled : 1;
+
+ // True if this document is for an SVG-in-OpenType font.
+ bool mIsSVGGlyphsDocument : 1;
+
+ // True if the document is being destroyed.
+ bool mInDestructor : 1;
+
+ // True if the document has been detached from its content viewer.
+ bool mIsGoingAway : 1;
+
+ bool mInXBLUpdate : 1;
+
+ // Whether we have filled our style set with all the stylesheets.
+ bool mStyleSetFilled : 1;
+
+ // Whether we have a quirks mode stylesheet in the style set.
+ bool mQuirkSheetAdded : 1;
+
+ // Whether we have a contenteditable.css stylesheet in the style set.
+ bool mContentEditableSheetAdded : 1;
+
+ // Whether we have a designmode.css stylesheet in the style set.
+ bool mDesignModeSheetAdded : 1;
+
+ // Keeps track of whether we have a pending
+ // 'style-sheet-applicable-state-changed' notification.
+ bool mSSApplicableStateNotificationPending : 1;
+
+ // True if this document has ever had an HTML or SVG <title> element
+ // bound to it
+ bool mMayHaveTitleElement : 1;
+
+ bool mDOMLoadingSet : 1;
+ bool mDOMInteractiveSet : 1;
+ bool mDOMCompleteSet : 1;
+ bool mAutoFocusFired : 1;
+
+ bool mScrolledToRefAlready : 1;
+ bool mChangeScrollPosWhenScrollingToRef : 1;
+
+ bool mDelayFrameLoaderInitialization : 1;
+
+ bool mSynchronousDOMContentLoaded : 1;
+
+ // Set to true when the document is possibly controlled by the ServiceWorker.
+ // Used to prevent multiple requests to ServiceWorkerManager.
+ bool mMaybeServiceWorkerControlled : 1;
+
+ // These member variables cache information about the viewport so we don't
+ // have to recalculate it each time.
+ bool mAllowZoom : 1;
+ bool mValidScaleFloat : 1;
+ bool mValidMinScale : 1;
+ bool mValidMaxScale : 1;
+ bool mWidthStrEmpty : 1;
+
+ // Parser aborted. True if the parser of this document was forcibly
+ // terminated instead of letting it finish at its own pace.
+ bool mParserAborted : 1;
+
+ // Whether we have reported document use counters for this document with
+ // Telemetry yet. Normally this is only done at document destruction time,
+ // but for image documents (SVG documents) that are not guaranteed to be
+ // destroyed, we report use counters when the image cache no longer has any
+ // imgRequestProxys pointing to them. We track whether we ever reported use
+ // counters so that we only report them once for the document.
+ bool mReportedDocumentUseCounters : 1;
+
+ bool mHasReportedShadowDOMUsage : 1;
+
+ // Whether an event triggered by the refresh driver was delayed because this
+ // document has suppressed events.
+ bool mHasDelayedRefreshEvent : 1;
+
+ // The HTML spec has a "iframe load in progress" flag, but that doesn't seem
+ // to have the right semantics. See
+ // <https://github.com/whatwg/html/issues/4292>. What we have instead is a
+ // flag that is set while the window's 'load' event is firing if this document
+ // is the window's document.
+ bool mLoadEventFiring : 1;
+
+ // The HTML spec has a "mute iframe load" flag, but that doesn't seem to have
+ // the right semantics. See <https://github.com/whatwg/html/issues/4292>.
+ // What we have instead is a flag that is set if completion of our document
+ // via document.close() should skip firing the load event. Note that this
+ // flag is only relevant for HTML documents, but lives here for reasons that
+ // are documented above on SkipLoadEventAfterClose().
+ bool mSkipLoadEventAfterClose : 1;
+
+ // When false, the .cookies property is completely disabled
+ bool mDisableCookieAccess : 1;
+
+ // When false, the document.write() API is disabled.
+ bool mDisableDocWrite : 1;
+
+ // Has document.write() been called with a recursion depth higher than
+ // allowed?
+ bool mTooDeepWriteRecursion : 1;
+
+ /**
+ * Temporary flag that is set in EndUpdate() to ignore
+ * MaybeEditingStateChanged() script runners from a nested scope.
+ */
+ bool mPendingMaybeEditingStateChanged : 1;
+
+ // mHasBeenEditable is set to true when mEditingState is firstly set to
+ // eDesignMode or eContentEditable.
+ bool mHasBeenEditable : 1;
+
+ // Whether we've warned about the CSS zoom property.
+ //
+ // We don't use the general deprecated operation mechanism for this because we
+ // also record this as a `CountedUnknownProperty`.
+ bool mHasWarnedAboutZoom : 1;
+
+ // While we're handling an execCommand call, set to true.
+ bool mIsRunningExecCommand : 1;
+
+ // True if we should change the readystate to complete after we fire
+ // DOMContentLoaded. This happens when we abort a load and
+ // nsDocumentViewer::EndLoad runs while we still have things blocking
+ // DOMContentLoaded. We wait for those to complete, and then update the
+ // readystate when they finish.
+ bool mSetCompleteAfterDOMContentLoaded : 1;
+
+ // Set the true if a completed cached stylesheet was created for the document.
+ bool mDidHitCompleteSheetCache : 1;
+
+ // Whether we have initialized mShouldReportUseCounters and
+ // mShouldSendPageUseCounters, and sent any needed message to the parent
+ // process to indicate that use counter data will be sent at some later point.
+ bool mUseCountersInitialized : 1;
+
+ // Whether this document should report use counters.
+ bool mShouldReportUseCounters : 1;
+
+ // Whether this document should send page use counters. Set to true after
+ // we've called SendExpectPageUseCounters on the top-level WindowGlobal.
+ bool mShouldSendPageUseCounters : 1;
+
+ // Whether the user has interacted with the document or not:
+ bool mUserHasInteracted : 1;
+
+ // We constantly update the user-interaction anti-tracking permission at any
+ // user-interaction using a timer. This boolean value is set to true when this
+ // timer is scheduled.
+ bool mHasUserInteractionTimerScheduled : 1;
+
+ // Whether we should resist fingerprinting.
+ bool mShouldResistFingerprinting : 1;
+
+ uint8_t mXMLDeclarationBits;
+
+ // NOTE(emilio): Technically, this should be a StyleColorSchemeFlags, but we
+ // use uint8_t to avoid having to include a bunch of style system headers
+ // everywhere.
+ uint8_t mColorSchemeBits = 0;
+
+ // Currently active onload blockers.
+ uint32_t mOnloadBlockCount;
+
+ // Tracks if we are currently processing any document.write calls (either
+ // implicit or explicit). Note that if a write call writes out something which
+ // would block the parser, then mWriteLevel will be incorrect until the parser
+ // finishes processing that script.
+ uint32_t mWriteLevel;
+
+ uint32_t mContentEditableCount;
+ EditingState mEditingState;
+
+ // Compatibility mode
+ nsCompatibility mCompatMode;
+
+ // Our readyState
+ ReadyState mReadyState;
+
+ // Ancestor's loading state
+ bool mAncestorIsLoading;
+
+ // Our visibility state
+ dom::VisibilityState mVisibilityState;
+
+ enum Type {
+ eUnknown, // should never be used
+ eHTML,
+ eXHTML,
+ eGenericXML,
+ eSVG
+ };
+
+ Type mType;
+
+ uint8_t mDefaultElementType;
+
+ enum Tri { eTriUnset = 0, eTriFalse, eTriTrue };
+
+ Tri mAllowXULXBL;
+
+ bool mSkipDTDSecurityChecks;
+
+ // The document's script global object, the object from which the
+ // document can get its script context and scope. This is the
+ // *inner* window object.
+ nsCOMPtr<nsIScriptGlobalObject> mScriptGlobalObject;
+
+ // If mIsStaticDocument is true, mOriginalDocument points to the original
+ // document.
+ RefPtr<Document> mOriginalDocument;
+
+ // The bidi options for this document. What this bitfield means is
+ // defined in nsBidiUtils.h
+ uint32_t mBidiOptions;
+
+ // The sandbox flags on the document. These reflect the value of the sandbox
+ // attribute of the associated IFRAME or CSP-protectable content, if existent.
+ // These are set at load time and are immutable - see nsSandboxFlags.h for the
+ // possible flags.
+ uint32_t mSandboxFlags;
+
+ // The embedder policy obtained from parsing the HTTP response header or from
+ // our opener if this is the initial about:blank document.
+ Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mEmbedderPolicy;
+
+ nsCString mContentLanguage;
+
+ // The channel that got passed to Document::StartDocumentLoad(), if any.
+ nsCOMPtr<nsIChannel> mChannel;
+
+ // The CSP for every load lives in the Client within the LoadInfo. For all
+ // document-initiated subresource loads we can use that cached version of the
+ // CSP so we do not have to deserialize the CSP from the Client all the time.
+ nsCOMPtr<nsIContentSecurityPolicy> mCSP;
+ nsCOMPtr<nsIContentSecurityPolicy> mPreloadCSP;
+
+ private:
+ nsCString mContentType;
+
+ protected:
+ // The document's security info
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+
+ // The channel that failed to load and resulted in an error page.
+ // This only applies to error pages. Might be null.
+ nsCOMPtr<nsIChannel> mFailedChannel;
+
+ // if this document is part of a multipart document,
+ // the ID can be used to distinguish it from the other parts.
+ uint32_t mPartID;
+
+ // Cycle collector generation in which we're certain that this document
+ // won't be collected
+ uint32_t mMarkedCCGeneration;
+
+ PresShell* mPresShell;
+
+ nsCOMArray<nsINode> mSubtreeModifiedTargets;
+ uint32_t mSubtreeModifiedDepth;
+
+ // All images in process of being preloaded. This is a hashtable so
+ // we can remove them as the real image loads start; that way we
+ // make sure to not keep the image load going when no one cares
+ // about it anymore.
+ nsRefPtrHashtable<nsURIHashKey, imgIRequest> mPreloadingImages;
+
+ // A list of preconnects initiated by the preloader. This prevents
+ // the same uri from being used more than once, and allows the dom
+ // builder to not repeat the work of the preloader.
+ nsTHashMap<nsURIHashKey, bool> mPreloadedPreconnects;
+
+ // Current depth of picture elements from parser
+ uint32_t mPreloadPictureDepth;
+
+ // Set if we've found a URL for the current picture
+ nsString mPreloadPictureFoundSource;
+
+ // If we're an external resource document, this will be non-null and will
+ // point to our "display document": the one that all resource lookups should
+ // go to.
+ RefPtr<Document> mDisplayDocument;
+
+ uint32_t mEventsSuppressed;
+
+ // Any XHR ChannelEventQueues that were suspended on this document while
+ // events were suppressed.
+ nsTArray<RefPtr<net::ChannelEventQueue>> mSuspendedQueues;
+
+ // Any postMessage events that were suspended on this document while events
+ // were suppressed.
+ nsTArray<RefPtr<PostMessageEvent>> mSuspendedPostMessageEvents;
+
+ RefPtr<EventListener> mSuppressedEventListener;
+
+ /**
+ * https://html.spec.whatwg.org/#ignore-destructive-writes-counter
+ */
+ uint32_t mIgnoreDestructiveWritesCounter;
+
+ // Count of live static clones of this document.
+ uint32_t mStaticCloneCount;
+
+ // If the document is currently printing (or in print preview) this will point
+ // to the current static clone of this document. This is weak since the clone
+ // also has a reference to this document.
+ WeakPtr<Document> mLatestStaticClone;
+
+ // Array of nodes that have been blocked to prevent user tracking.
+ // They most likely have had their nsIChannel canceled by the URL
+ // classifier. (Safebrowsing)
+ //
+ // Weak nsINode pointers are used to allow nodes to disappear.
+ nsTArray<nsWeakPtr> mBlockedNodesByClassifier;
+
+ // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
+ // updated on every set of mScriptGlobalObject.
+ nsPIDOMWindowInner* mWindow;
+
+ nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
+
+ FrameRequestManager mFrameRequestManager;
+
+ // This object allows us to evict ourself from the back/forward cache. The
+ // pointer is non-null iff we're currently in the bfcache.
+ nsIBFCacheEntry* mBFCacheEntry;
+
+ // Our base target.
+ nsString mBaseTarget;
+
+ nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
+ JS::Heap<JS::Value> mCachedStateObject;
+
+ uint32_t mInSyncOperationCount;
+
+ UniquePtr<dom::XPathEvaluator> mXPathEvaluator;
+
+ nsTArray<RefPtr<AnonymousContent>> mAnonymousContents;
+
+ uint32_t mBlockDOMContentLoaded;
+
+ // Our live MediaQueryLists
+ LinkedList<MediaQueryList> mDOMMediaQueryLists;
+
+ // Array of observers
+ nsTObserverArray<nsIDocumentObserver*> mObservers;
+
+ // Flags for use counters used directly by this document.
+ UseCounters mUseCounters;
+ // Flags for use counters from resource documents, static clones,
+ // and SVG images referenced by this document. Those documents propagate
+ // their use counters up to here, which then count towards the top-level
+ // document's page use counters.
+ UseCounters mChildDocumentUseCounters;
+
+ // The CSS property use counters.
+ UniquePtr<StyleUseCounters> mStyleUseCounters;
+
+ TimeStamp mPageUnloadingEventTimeStamp;
+
+ RefPtr<DocGroup> mDocGroup;
+
+ RefPtr<nsCommandManager> mMidasCommandManager;
+
+ // The set of all the tracking script URLs. URLs are added to this set by
+ // calling NoteScriptTrackingStatus(). Currently we assume that a URL not
+ // existing in the set means the corresponding script isn't a tracking script.
+ nsTHashSet<nsCString> mTrackingScripts;
+
+ // Pointer to our parser if we're currently in the process of being
+ // parsed into.
+ nsCOMPtr<nsIParser> mParser;
+
+ // If the document was created from the the prototype cache there will be a
+ // reference to the prototype document to allow tracing.
+ RefPtr<nsXULPrototypeDocument> mPrototypeDocument;
+
+ // Weak reference to our sink for in case we no longer have a parser. This
+ // will allow us to flush out any pending stuff from the sink even if
+ // EndLoad() has already happened.
+ nsWeakPtr mWeakSink;
+
+ // Our update nesting level
+ uint32_t mUpdateNestLevel;
+
+ // HTTPS-Only Mode Status
+ // Constants are defined at nsILoadInfo::HTTPS_ONLY_*
+ uint32_t mHttpsOnlyStatus;
+
+ enum ViewportType : uint8_t {
+ DisplayWidthHeight,
+ Specified,
+ Unknown,
+ };
+
+ ViewportType mViewportType;
+
+ // viewport-fit described by
+ // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
+ ViewportFitType mViewportFit;
+
+ PLDHashTable* mSubDocuments;
+
+ class HeaderData;
+ UniquePtr<HeaderData> mHeaderData;
+
+ nsTArray<net::EarlyHintConnectArgs> mEarlyHints;
+
+ nsRevocableEventPtr<nsRunnableMethod<Document, void, false>>
+ mPendingTitleChangeEvent;
+
+ RefPtr<nsDOMNavigationTiming> mTiming;
+
+ // Recorded time of change to 'loading' state
+ // or time of the page gets restored from BFCache.
+ TimeStamp mLoadingOrRestoredFromBFCacheTimeStamp;
+
+ // Decided to use nsTObserverArray because it allows us to
+ // remove candidates while iterating them and this is what
+ // the spec defines. We could implement the spec without
+ // using nsTObserverArray, however using nsTObserverArray is more clear.
+ nsTObserverArray<nsWeakPtr> mAutoFocusCandidates;
+
+ nsCString mScrollToRef;
+
+ // Weak reference to the scope object (aka the script global object)
+ // that, unlike mScriptGlobalObject, is never unset once set. This
+ // is a weak reference to avoid leaks due to circular references.
+ nsWeakPtr mScopeObject;
+
+ // Array of intersection observers
+ nsTHashSet<DOMIntersectionObserver*> mIntersectionObservers;
+
+ RefPtr<DOMIntersectionObserver> mLazyLoadImageObserver;
+ // Used to measure how effective the lazyload thresholds are.
+ RefPtr<DOMIntersectionObserver> mLazyLoadImageObserverViewport;
+
+ // Used for detecting when `content-visibility: auto` elements are near
+ // or far from the viewport.
+ RefPtr<DOMIntersectionObserver> mContentVisibilityObserver;
+
+ // ResizeObserver for storing and removing the last remembered size.
+ // @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}
+ RefPtr<ResizeObserver> mLastRememberedSizeObserver;
+
+ // Stack of top layer elements.
+ nsTArray<nsWeakPtr> mTopLayer;
+
+ // The root of the doc tree in which this document is in. This is only
+ // non-null when this document is in fullscreen mode.
+ nsWeakPtr mFullscreenRoot;
+
+ RefPtr<DOMImplementation> mDOMImplementation;
+
+ RefPtr<nsContentList> mImageMaps;
+
+ // A set of responsive images keyed by address pointer.
+ nsTHashSet<HTMLImageElement*> mResponsiveContent;
+
+ RefPtr<DocumentTimeline> mDocumentTimeline;
+ LinkedList<DocumentTimeline> mTimelines;
+
+ RefPtr<dom::ScriptLoader> mScriptLoader;
+
+ // Tracker for animations that are waiting to start.
+ // nullptr until GetOrCreatePendingAnimationTracker is called.
+ RefPtr<PendingAnimationTracker> mPendingAnimationTracker;
+
+ // Tracker for scroll-driven animations that are waiting to start.
+ // nullptr until GetOrCreateScrollTimelineAnimationTracker is called.
+ RefPtr<ScrollTimelineAnimationTracker> mScrollTimelineAnimationTracker;
+
+ // A document "without a browsing context" that owns the content of
+ // HTMLTemplateElement.
+ RefPtr<Document> mTemplateContentsOwner;
+
+ dom::ExternalResourceMap mExternalResourceMap;
+
+ // ScreenOrientation "pending promise" as described by
+ // http://www.w3.org/TR/screen-orientation/
+ RefPtr<Promise> mOrientationPendingPromise;
+
+ nsTArray<RefPtr<nsFrameLoader>> mInitializableFrameLoaders;
+ nsTArray<nsCOMPtr<nsIRunnable>> mFrameLoaderFinalizers;
+ RefPtr<nsRunnableMethod<Document>> mFrameLoaderRunner;
+
+ nsTArray<PendingFrameStaticClone> mPendingFrameStaticClones;
+
+ // The layout history state that should be used by nodes in this
+ // document. We only actually store a pointer to it when:
+ // 1) We have no script global object.
+ // 2) We haven't had Destroy() called on us yet.
+ nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+
+ // The parsed viewport metadata of the last modified <meta name=viewport>
+ // element.
+ UniquePtr<ViewportMetaData> mLastModifiedViewportMetaData;
+
+ // A tree ordered list of all color-scheme meta tags in this document.
+ //
+ // TODO(emilio): There are other meta tags in the spec that have a similar
+ // processing model to color-scheme. We could store all in-document meta tags
+ // here to get sane and fast <meta> element processing.
+ TreeOrderedArray<HTMLMetaElement> mColorSchemeMetaTags;
+
+ // These member variables cache information about the viewport so we don't
+ // have to recalculate it each time.
+ LayoutDeviceToScreenScale mScaleMinFloat;
+ LayoutDeviceToScreenScale mScaleMaxFloat;
+ LayoutDeviceToScreenScale mScaleFloat;
+ CSSToLayoutDeviceScale mPixelRatio;
+
+ CSSCoord mMinWidth;
+ CSSCoord mMaxWidth;
+ CSSCoord mMinHeight;
+ CSSCoord mMaxHeight;
+
+ RefPtr<EventListenerManager> mListenerManager;
+
+ nsCOMPtr<nsIRequest> mOnloadBlocker;
+
+ // Gecko-internal sheets used for extensions and such.
+ // Exposed to privileged script via nsIDOMWindowUtils.loadSheet.
+ nsTArray<RefPtr<StyleSheet>> mAdditionalSheets[AdditionalSheetTypeCount];
+
+ // Member to store out last-selected stylesheet set.
+ nsString mLastStyleSheetSet;
+ nsString mPreferredStyleSheetSet;
+
+ RefPtr<DOMStyleSheetSetList> mStyleSheetSetList;
+
+ // We lazily calculate declaration blocks for SVG elements with mapped
+ // attributes in Servo mode. This list contains all elements which need lazy
+ // resolution.
+ nsTHashSet<SVGElement*> mLazySVGPresElements;
+
+ nsTHashSet<RefPtr<nsAtom>> mLanguagesUsed;
+
+ // TODO(emilio): Is this hot enough to warrant to be cached?
+ RefPtr<nsAtom> mLanguageFromCharset;
+
+ // Restyle root for servo's style system.
+ //
+ // We store this as an nsINode, rather than as an Element, so that we can
+ // store the Document node as the restyle root if the entire document (along
+ // with all document-level native-anonymous content) needs to be restyled.
+ //
+ // We also track which "descendant" bits (normal/animation-only/lazy-fc) the
+ // root corresponds to.
+ nsCOMPtr<nsINode> mServoRestyleRoot;
+ uint32_t mServoRestyleRootDirtyBits;
+
+ // Used in conjunction with the create-an-element-for-the-token algorithm to
+ // prevent custom element constructors from being able to use document.open(),
+ // document.close(), and document.write() when they are invoked by the parser.
+ uint32_t mThrowOnDynamicMarkupInsertionCounter;
+
+ // Count of unload/beforeunload/pagehide operations in progress.
+ uint32_t mIgnoreOpensDuringUnloadCounter;
+
+ nsCOMPtr<nsIDOMXULCommandDispatcher>
+ mCommandDispatcher; // [OWNER] of the focus tracker
+
+ RefPtr<XULBroadcastManager> mXULBroadcastManager;
+ RefPtr<XULPersist> mXULPersist;
+ RefPtr<ChromeObserver> mChromeObserver;
+
+ RefPtr<HTMLAllCollection> mAll;
+
+ nsTHashSet<RefPtr<WorkerDocumentListener>> mWorkerListeners;
+
+ // Pres shell resolution saved before entering fullscreen mode.
+ float mSavedResolution;
+
+ // Pres shell resolution saved before creating a MobileViewportManager.
+ float mSavedResolutionBeforeMVM;
+
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ bool mHasStoragePermission;
+
+ // Document generation. Gets incremented everytime it changes.
+ int32_t mGeneration;
+
+ // Cached TabSizes values for the document.
+ int32_t mCachedTabSizeGeneration;
+ nsTabSizes mCachedTabSizes;
+
+ // This is equal to document's principal but with an isolation key. See
+ // StoragePrincipalHelper.h to know more.
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipal;
+
+ // The cached storage principal for this document.
+ // This is mutable so that we can keep EffectiveStoragePrincipal() const
+ // which is required due to its CloneDocHelper() call site. :-(
+ mutable nsCOMPtr<nsIPrincipal> mActiveStoragePrincipal;
+
+ // The cached cookie principal for this document.
+ // This is mutable so that we can keep EffectiveCookiePrincipal() const
+ // which is required due to its CloneDocHelper() call site. :-(
+ mutable nsCOMPtr<nsIPrincipal> mActiveCookiePrincipal;
+
+ // See GetNextFormNumber and GetNextControlNumber.
+ int32_t mNextFormNumber;
+ int32_t mNextControlNumber;
+
+ uint32_t mMediaElementWithMSECount = 0;
+
+ // Scope preloads per document. This is used by speculative loading as well.
+ PreloadService mPreloadService;
+
+ // See NotifyFetchOrXHRSuccess and SetNotifyFetchSuccess.
+ bool mShouldNotifyFetchSuccess;
+
+ // See SetNotifyFormOrPasswordRemoved and ShouldNotifyFormOrPasswordRemoved.
+ bool mShouldNotifyFormOrPasswordRemoved;
+
+ // Record page load telemetry
+ void RecordPageLoadEventTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryData);
+
+ // Accumulate JS telemetry collected
+ void AccumulateJSTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryDataOut);
+
+ // Accumulate page load metrics
+ void AccumulatePageLoadTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryDataOut);
+
+ // The OOP counterpart to nsDocLoader::mChildrenInOnload.
+ // Not holding strong refs here since we don't actually use the BBCs.
+ nsTArray<const BrowserBridgeChild*> mOOPChildrenLoading;
+
+ // Registry of custom highlight definitions associated with this document.
+ RefPtr<class HighlightRegistry> mHighlightRegistry;
+
+ public:
+ // Needs to be public because the bindings code pokes at it.
+ JS::ExpandoAndGeneration mExpandoAndGeneration;
+
+ bool HasPendingInitialTranslation();
+
+ nsRefPtrHashtable<nsRefPtrHashKey<Element>, nsXULPrototypeElement>
+ mL10nProtoElements;
+
+ float GetSavedResolutionBeforeMVM() { return mSavedResolutionBeforeMVM; }
+ void SetSavedResolutionBeforeMVM(float aResolution) {
+ mSavedResolutionBeforeMVM = aResolution;
+ }
+
+ void LoadEventFired();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID)
+
+/**
+ * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
+ * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
+ * object is deleted.
+ */
+class MOZ_STACK_CLASS mozAutoSubtreeModified {
+ public:
+ /**
+ * @param aSubTreeOwner The document in which a subtree will be modified.
+ * @param aTarget The target of the possible DOMSubtreeModified event.
+ * Can be nullptr, in which case mozAutoSubtreeModified
+ * is just used to batch DOM mutations.
+ */
+ mozAutoSubtreeModified(Document* aSubtreeOwner, nsINode* aTarget) {
+ UpdateTarget(aSubtreeOwner, aTarget);
+ }
+
+ ~mozAutoSubtreeModified() { UpdateTarget(nullptr, nullptr); }
+
+ void UpdateTarget(Document* aSubtreeOwner, nsINode* aTarget) {
+ if (mSubtreeOwner) {
+ mSubtreeOwner->MutationEventDispatched(mTarget);
+ }
+
+ mTarget = aTarget;
+ mSubtreeOwner = aSubtreeOwner;
+ if (mSubtreeOwner) {
+ mSubtreeOwner->WillDispatchMutationEvent(mTarget);
+ }
+ }
+
+ private:
+ nsCOMPtr<nsINode> mTarget;
+ RefPtr<Document> mSubtreeOwner;
+};
+
+enum class SyncOperationBehavior { eSuspendInput, eAllowInput };
+
+class AutoWalkBrowsingContextGroup {
+ public:
+ virtual ~AutoWalkBrowsingContextGroup() = default;
+
+ protected:
+ void SuppressBrowsingContext(BrowsingContext* aContext);
+ void SuppressBrowsingContextGroup(BrowsingContextGroup* aGroup);
+ void UnsuppressDocuments() {
+ for (const auto& doc : mDocuments) {
+ UnsuppressDocument(doc);
+ }
+ }
+ virtual void SuppressDocument(Document* aDocument) = 0;
+ virtual void UnsuppressDocument(Document* aDocument) = 0;
+ AutoTArray<RefPtr<Document>, 16> mDocuments;
+};
+
+class MOZ_RAII nsAutoSyncOperation : private AutoWalkBrowsingContextGroup {
+ public:
+ explicit nsAutoSyncOperation(Document* aDocument,
+ SyncOperationBehavior aSyncBehavior);
+ ~nsAutoSyncOperation();
+
+ protected:
+ void SuppressDocument(Document* aDocument) override;
+ void UnsuppressDocument(Document* aDocument) override;
+
+ private:
+ uint32_t mMicroTaskLevel;
+ const SyncOperationBehavior mSyncBehavior;
+ RefPtr<BrowsingContext> mBrowsingContext;
+};
+
+class MOZ_RAII AutoSetThrowOnDynamicMarkupInsertionCounter final {
+ public:
+ explicit AutoSetThrowOnDynamicMarkupInsertionCounter(Document* aDocument)
+ : mDocument(aDocument) {
+ mDocument->IncrementThrowOnDynamicMarkupInsertionCounter();
+ }
+
+ ~AutoSetThrowOnDynamicMarkupInsertionCounter() {
+ mDocument->DecrementThrowOnDynamicMarkupInsertionCounter();
+ }
+
+ private:
+ Document* mDocument;
+};
+
+class MOZ_RAII IgnoreOpensDuringUnload final {
+ public:
+ explicit IgnoreOpensDuringUnload(Document* aDoc) : mDoc(aDoc) {
+ mDoc->IncrementIgnoreOpensDuringUnloadCounter();
+ }
+
+ ~IgnoreOpensDuringUnload() {
+ mDoc->DecrementIgnoreOpensDuringUnloadCounter();
+ }
+
+ private:
+ Document* mDoc;
+};
+
+bool IsInFocusedTab(Document* aDoc);
+
+// This covers all cases covered by IsInFocusedTab, but also ensures that
+// focused tab is "active" meaning not occluded.
+bool IsInActiveTab(Document* aDoc);
+
+} // namespace mozilla::dom
+
+// XXX These belong somewhere else
+nsresult NS_NewHTMLDocument(mozilla::dom::Document** aInstancePtrResult,
+ bool aLoadedAsData = false);
+
+nsresult NS_NewXMLDocument(mozilla::dom::Document** aInstancePtrResult,
+ bool aLoadedAsData = false,
+ bool aIsPlainDocument = false);
+
+nsresult NS_NewSVGDocument(mozilla::dom::Document** aInstancePtrResult);
+
+nsresult NS_NewImageDocument(mozilla::dom::Document** aInstancePtrResult);
+
+nsresult NS_NewVideoDocument(mozilla::dom::Document** aInstancePtrResult);
+
+// Enum for requesting a particular type of document when creating a doc
+enum DocumentFlavor {
+ DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
+ DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
+ DocumentFlavorSVG, // SVGDocument
+ DocumentFlavorXML, // XMLDocument
+ DocumentFlavorPlain, // Just a Document
+};
+
+// Note: it's the caller's responsibility to create or get aPrincipal as needed
+// -- this method will not attempt to get a principal based on aDocumentURI.
+// Also, both aDocumentURI and aBaseURI must not be null.
+nsresult NS_NewDOMDocument(
+ mozilla::dom::Document** aInstancePtrResult, const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName, mozilla::dom::DocumentType* aDoctype,
+ nsIURI* aDocumentURI, nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
+ bool aLoadedAsData, nsIGlobalObject* aEventObject, DocumentFlavor aFlavor);
+
+inline mozilla::dom::Document* nsINode::GetOwnerDocument() const {
+ mozilla::dom::Document* ownerDoc = OwnerDoc();
+
+ return ownerDoc != this ? ownerDoc : nullptr;
+}
+
+inline nsINode* nsINode::OwnerDocAsNode() const { return OwnerDoc(); }
+
+inline mozilla::dom::Document* nsINode::AsDocument() {
+ MOZ_ASSERT(IsDocument());
+ return static_cast<mozilla::dom::Document*>(this);
+}
+
+inline const mozilla::dom::Document* nsINode::AsDocument() const {
+ MOZ_ASSERT(IsDocument());
+ return static_cast<const mozilla::dom::Document*>(this);
+}
+
+inline nsISupports* ToSupports(mozilla::dom::Document* aDoc) {
+ return static_cast<nsINode*>(aDoc);
+}
+
+#endif /* mozilla_dom_Document_h___ */
diff --git a/dom/base/DocumentFragment.cpp b/dom/base/DocumentFragment.cpp
new file mode 100644
index 0000000000..62cc75fd20
--- /dev/null
+++ b/dom/base/DocumentFragment.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's DocumentFragment.
+ */
+
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/Element.h" // for NS_IMPL_ELEMENT_CLONE
+#include "mozilla/dom/NodeInfo.h"
+#include "nsNodeInfoManager.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsDOMString.h"
+#include "nsContentUtils.h" // for NS_INTERFACE_MAP_ENTRY_TEAROFF
+#include "mozilla/dom/DocumentFragmentBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla::dom {
+
+JSObject* DocumentFragment::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DocumentFragment_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+#ifdef MOZ_DOM_LIST
+void DocumentFragment::List(FILE* out, int32_t aIndent) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) {
+ fputs(" ", out);
+ }
+
+ fprintf(out, "DocumentFragment@%p", (void*)this);
+
+ fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
+ fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
+
+ nsIContent* child = GetFirstChild();
+ if (child) {
+ fputs("\n", out);
+
+ for (; child; child = child->GetNextSibling()) {
+ child->List(out, aIndent + 1);
+ }
+
+ for (indent = aIndent; --indent >= 0;) {
+ fputs(" ", out);
+ }
+ }
+
+ fputs(">\n", out);
+}
+
+void DocumentFragment::DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) {
+ fputs(" ", out);
+ }
+
+ fputs("<DocumentFragment>", out);
+
+ if (aIndent) {
+ fputs("\n", out);
+ }
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ int32_t indent = aIndent ? aIndent + 1 : 0;
+ child->DumpContent(out, indent, aDumpAll);
+ }
+ for (indent = aIndent; --indent >= 0;) {
+ fputs(" ", out);
+ }
+ fputs("</DocumentFragment>", out);
+
+ if (aIndent) {
+ fputs("\n", out);
+ }
+}
+#endif
+
+/* static */
+already_AddRefed<DocumentFragment> DocumentFragment::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window || !window->GetDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return window->GetDoc()->CreateDocumentFragment();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DocumentFragment, FragmentOrElement, mHost)
+
+// QueryInterface implementation for DocumentFragment
+NS_INTERFACE_MAP_BEGIN(DocumentFragment)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(DocumentFragment)
+ NS_INTERFACE_MAP_ENTRY(nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsINode)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
+ new nsNodeSupportsWeakRefTearoff(this))
+ // DOM bindings depend on the identity pointer being the
+ // same as nsINode (which nsIContent inherits).
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF_INHERITED(DocumentFragment, FragmentOrElement)
+NS_IMPL_RELEASE_INHERITED(DocumentFragment, FragmentOrElement)
+
+NS_IMPL_ELEMENT_CLONE(DocumentFragment)
+
+} // namespace mozilla::dom
diff --git a/dom/base/DocumentFragment.h b/dom/base/DocumentFragment.h
new file mode 100644
index 0000000000..4b1c11c480
--- /dev/null
+++ b/dom/base/DocumentFragment.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DocumentFragment_h__
+#define mozilla_dom_DocumentFragment_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "nsStringFwd.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file.
+#include "mozilla/dom/Element.h"
+
+class nsAtom;
+class nsIContent;
+
+namespace mozilla::dom {
+
+class Document;
+class Element;
+
+class DocumentFragment : public FragmentOrElement {
+ private:
+ void Init() {
+ MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_FRAGMENT_NODE &&
+ mNodeInfo->Equals(nsGkAtoms::documentFragmentNodeName,
+ kNameSpaceID_None),
+ "Bad NodeType in aNodeInfo");
+ }
+
+ public:
+ using FragmentOrElement::GetFirstChild;
+ using nsINode::QuerySelector;
+ using nsINode::QuerySelectorAll;
+ // Make sure bindings can see our superclass' protected GetElementById method.
+ using nsINode::GetElementById;
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocumentFragment, FragmentOrElement)
+
+ explicit DocumentFragment(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
+ : FragmentOrElement(std::move(aNodeInfo)), mHost(nullptr) {
+ Init();
+ }
+
+ explicit DocumentFragment(nsNodeInfoManager* aNodeInfoManager)
+ : FragmentOrElement(aNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::documentFragmentNodeName, nullptr, kNameSpaceID_None,
+ DOCUMENT_FRAGMENT_NODE)),
+ mHost(nullptr) {
+ Init();
+ }
+
+ NS_IMPL_FROMNODE_HELPER(DocumentFragment, IsDocumentFragment());
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsresult BindToTree(BindContext&, nsINode& aParent) override {
+ NS_ASSERTION(false, "Trying to bind a fragment to a tree");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void UnbindFromTree(bool aNullParent) override {
+ NS_ASSERTION(false, "Trying to unbind a fragment from a tree");
+ }
+
+ Element* GetNameSpaceElement() override { return nullptr; }
+
+ Element* GetHost() const { return mHost; }
+
+ void SetHost(Element* aHost) { mHost = aHost; }
+
+ void GetInnerHTML(nsAString& aInnerHTML) { GetMarkup(false, aInnerHTML); }
+ void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError) {
+ SetInnerHTMLInternal(aInnerHTML, aError);
+ }
+
+ static already_AddRefed<DocumentFragment> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv);
+
+#ifdef MOZ_DOM_LIST
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const override;
+#endif
+
+ protected:
+ virtual ~DocumentFragment() = default;
+
+ nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+ RefPtr<Element> mHost;
+};
+
+} // namespace mozilla::dom
+
+inline mozilla::dom::DocumentFragment* nsINode::AsDocumentFragment() {
+ MOZ_ASSERT(IsDocumentFragment());
+ return static_cast<mozilla::dom::DocumentFragment*>(this);
+}
+
+inline const mozilla::dom::DocumentFragment* nsINode::AsDocumentFragment()
+ const {
+ MOZ_ASSERT(IsDocumentFragment());
+ return static_cast<const mozilla::dom::DocumentFragment*>(this);
+}
+
+#endif // mozilla_dom_DocumentFragment_h__
diff --git a/dom/base/DocumentInlines.h b/dom/base/DocumentInlines.h
new file mode 100644
index 0000000000..c93189763d
--- /dev/null
+++ b/dom/base/DocumentInlines.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_DocumentInlines_h
+#define mozilla_dom_DocumentInlines_h
+
+#include "mozilla/dom/Document.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+#include "nsStyleSheetService.h"
+
+namespace mozilla::dom {
+
+inline PresShell* Document::GetObservingPresShell() const {
+ return mPresShell && mPresShell->IsObservingDocument() ? mPresShell : nullptr;
+}
+
+inline nsPresContext* Document::GetPresContext() const {
+ PresShell* presShell = GetPresShell();
+ return presShell ? presShell->GetPresContext() : nullptr;
+}
+
+inline HTMLBodyElement* Document::GetBodyElement() {
+ return static_cast<HTMLBodyElement*>(GetHtmlChildElement(nsGkAtoms::body));
+}
+
+inline void Document::SetServoRestyleRoot(nsINode* aRoot, uint32_t aDirtyBits) {
+ MOZ_ASSERT(aRoot);
+
+ MOZ_ASSERT(!mServoRestyleRoot || mServoRestyleRoot == aRoot ||
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ mServoRestyleRoot, aRoot));
+ MOZ_ASSERT(aRoot == aRoot->OwnerDocAsNode() || aRoot->IsElement());
+ mServoRestyleRoot = aRoot;
+ SetServoRestyleRootDirtyBits(aDirtyBits);
+}
+
+// Note: we break this out of SetServoRestyleRoot so that callers can add
+// bits without doing a no-op assignment to the restyle root, which would
+// involve cycle-collected refcount traffic.
+inline void Document::SetServoRestyleRootDirtyBits(uint32_t aDirtyBits) {
+ MOZ_ASSERT(aDirtyBits);
+ MOZ_ASSERT((aDirtyBits & ~Element::kAllServoDescendantBits) == 0);
+ MOZ_ASSERT((aDirtyBits & mServoRestyleRootDirtyBits) ==
+ mServoRestyleRootDirtyBits);
+ MOZ_ASSERT(mServoRestyleRoot);
+ mServoRestyleRootDirtyBits = aDirtyBits;
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_DocumentInlines_h
diff --git a/dom/base/DocumentOrShadowRoot.cpp b/dom/base/DocumentOrShadowRoot.cpp
new file mode 100644
index 0000000000..b43edb0275
--- /dev/null
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DocumentOrShadowRoot.h"
+#include "mozilla/AnimationComparator.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/PointerLockManager.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/StyleSheetList.h"
+#include "nsTHashtable.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsIRadioVisitor.h"
+#include "nsIFormControl.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsWindowSizes.h"
+
+namespace mozilla::dom {
+
+DocumentOrShadowRoot::DocumentOrShadowRoot(ShadowRoot* aShadowRoot)
+ : mAsNode(aShadowRoot), mKind(Kind::ShadowRoot) {
+ MOZ_ASSERT(mAsNode);
+}
+
+DocumentOrShadowRoot::DocumentOrShadowRoot(Document* aDoc)
+ : mAsNode(aDoc), mKind(Kind::Document) {
+ MOZ_ASSERT(mAsNode);
+}
+
+void DocumentOrShadowRoot::AddSizeOfOwnedSheetArrayExcludingThis(
+ nsWindowSizes& aSizes, const nsTArray<RefPtr<StyleSheet>>& aSheets) const {
+ size_t n = 0;
+ n += aSheets.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+ for (StyleSheet* sheet : aSheets) {
+ if (!sheet->GetAssociatedDocumentOrShadowRoot()) {
+ // Avoid over-reporting shared sheets.
+ continue;
+ }
+ n += sheet->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+ }
+
+ if (mKind == Kind::ShadowRoot) {
+ aSizes.mLayoutShadowDomStyleSheetsSize += n;
+ } else {
+ aSizes.mLayoutStyleSheetsSize += n;
+ }
+}
+
+void DocumentOrShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ AddSizeOfOwnedSheetArrayExcludingThis(aSizes, mStyleSheets);
+ aSizes.mDOMSizes.mDOMOtherSize +=
+ mIdentifierMap.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+}
+
+DocumentOrShadowRoot::~DocumentOrShadowRoot() {
+ for (StyleSheet* sheet : mStyleSheets) {
+ sheet->ClearAssociatedDocumentOrShadowRoot();
+ }
+}
+
+StyleSheetList* DocumentOrShadowRoot::StyleSheets() {
+ if (!mDOMStyleSheets) {
+ mDOMStyleSheets = new StyleSheetList(*this);
+ }
+ return mDOMStyleSheets;
+}
+
+void DocumentOrShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
+ aSheet.SetAssociatedDocumentOrShadowRoot(this);
+ mStyleSheets.InsertElementAt(aIndex, &aSheet);
+}
+
+void DocumentOrShadowRoot::RemoveStyleSheet(StyleSheet& aSheet) {
+ auto index = mStyleSheets.IndexOf(&aSheet);
+ if (index == mStyleSheets.NoIndex) {
+ // We should only hit this case if we are unlinking
+ // in which case mStyleSheets should be cleared.
+ MOZ_ASSERT(mKind != Kind::Document ||
+ AsNode().AsDocument()->InUnlinkOrDeletion());
+ MOZ_ASSERT(mStyleSheets.IsEmpty());
+ return;
+ }
+ RefPtr<StyleSheet> sheet = std::move(mStyleSheets[index]);
+ mStyleSheets.RemoveElementAt(index);
+ RemoveSheetFromStylesIfApplicable(*sheet);
+ sheet->ClearAssociatedDocumentOrShadowRoot();
+}
+
+void DocumentOrShadowRoot::RemoveSheetFromStylesIfApplicable(
+ StyleSheet& aSheet) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+ if (mKind == Kind::Document) {
+ AsNode().AsDocument()->RemoveStyleSheetFromStyleSets(aSheet);
+ } else {
+ MOZ_ASSERT(AsNode().IsShadowRoot());
+ static_cast<ShadowRoot&>(AsNode()).RemoveSheetFromStyles(aSheet);
+ }
+}
+
+// https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets
+void DocumentOrShadowRoot::OnSetAdoptedStyleSheets(StyleSheet& aSheet,
+ uint32_t aIndex,
+ ErrorResult& aRv) {
+ Document& doc = *AsNode().OwnerDoc();
+ // 1. If value’s constructed flag is not set, or its constructor document is
+ // not equal to this DocumentOrShadowRoot's node document, throw a
+ // "NotAllowedError" DOMException.
+ if (!aSheet.IsConstructed()) {
+ return aRv.ThrowNotAllowedError(
+ "Adopted style sheet must be created through the Constructable "
+ "StyleSheets API");
+ }
+ if (!aSheet.ConstructorDocumentMatches(doc)) {
+ return aRv.ThrowNotAllowedError(
+ "Adopted style sheet's constructor document must match the "
+ "document or shadow root's node document");
+ }
+
+ auto* shadow = ShadowRoot::FromNode(AsNode());
+ MOZ_ASSERT((mKind == Kind::ShadowRoot) == !!shadow);
+
+ auto existingIndex = mAdoptedStyleSheets.LastIndexOf(&aSheet);
+ // Ensure it's in the backing array at the right index.
+ mAdoptedStyleSheets.InsertElementAt(aIndex, &aSheet);
+ if (existingIndex == mAdoptedStyleSheets.NoIndex) {
+ // common case: we're not already adopting this sheet.
+ aSheet.AddAdopter(*this);
+ } else if (existingIndex < aIndex) {
+ // We're inserting an already-adopted stylesheet in a later position, so
+ // this one should take precedent and we should remove the old one.
+ RemoveSheetFromStylesIfApplicable(aSheet);
+ } else {
+ // The sheet is already at a position later than or equal to the current
+ // one, and is already adopted by us, we have nothing to do here other than
+ // adding to the current list.
+ return;
+ }
+
+ if (aSheet.IsApplicable()) {
+ if (mKind == Kind::Document) {
+ doc.AddStyleSheetToStyleSets(aSheet);
+ } else {
+ shadow->InsertSheetIntoAuthorData(aIndex, aSheet, mAdoptedStyleSheets);
+ }
+ }
+}
+
+void DocumentOrShadowRoot::OnDeleteAdoptedStyleSheets(StyleSheet& aSheet,
+ uint32_t aIndex,
+ ErrorResult&) {
+ MOZ_ASSERT(mAdoptedStyleSheets.ElementAt(aIndex) == &aSheet);
+ mAdoptedStyleSheets.RemoveElementAt(aIndex);
+ auto existingIndex = mAdoptedStyleSheets.LastIndexOf(&aSheet);
+ if (existingIndex != mAdoptedStyleSheets.NoIndex && existingIndex >= aIndex) {
+ // The sheet is still adopted by us and was already later from the one we're
+ // removing, so nothing to do.
+ return;
+ }
+
+ RemoveSheetFromStylesIfApplicable(aSheet);
+ if (existingIndex == mAdoptedStyleSheets.NoIndex) {
+ // The sheet is no longer adopted by us.
+ aSheet.RemoveAdopter(*this);
+ } else if (aSheet.IsApplicable()) {
+ // We need to re-insert the sheet at the right (pre-existing) index.
+ nsINode& node = AsNode();
+ if (mKind == Kind::Document) {
+ node.AsDocument()->AddStyleSheetToStyleSets(aSheet);
+ } else {
+ ShadowRoot::FromNode(node)->InsertSheetIntoAuthorData(
+ existingIndex, aSheet, mAdoptedStyleSheets);
+ }
+ }
+}
+
+void DocumentOrShadowRoot::ClearAdoptedStyleSheets() {
+ auto* shadow = ShadowRoot::FromNode(AsNode());
+ auto* doc = shadow ? nullptr : AsNode().AsDocument();
+ MOZ_ASSERT(shadow || doc);
+ IgnoredErrorResult rv;
+ while (!mAdoptedStyleSheets.IsEmpty()) {
+ if (shadow) {
+ ShadowRoot_Binding::AdoptedStyleSheetsHelpers::RemoveLastElement(shadow,
+ rv);
+ } else {
+ Document_Binding::AdoptedStyleSheetsHelpers::RemoveLastElement(doc, rv);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!rv.Failed(), "Removal doesn't fail");
+ }
+}
+
+void DocumentOrShadowRoot::CloneAdoptedSheetsFrom(
+ const DocumentOrShadowRoot& aSource) {
+ if (aSource.mAdoptedStyleSheets.IsEmpty()) {
+ return;
+ }
+
+ Document& ownerDoc = *AsNode().OwnerDoc();
+ const Document& sourceDoc = *aSource.AsNode().OwnerDoc();
+ auto* clonedSheetMap = static_cast<Document::AdoptedStyleSheetCloneCache*>(
+ sourceDoc.GetProperty(nsGkAtoms::adoptedsheetclones));
+ MOZ_ASSERT(clonedSheetMap);
+
+ // We don't need to care about the reflector (AdoptedStyleSheetsHelpers and
+ // so) because this is only used for static documents.
+ for (const StyleSheet* sheet : aSource.mAdoptedStyleSheets) {
+ RefPtr<StyleSheet> clone = clonedSheetMap->LookupOrInsertWith(
+ sheet, [&] { return sheet->CloneAdoptedSheet(ownerDoc); });
+ MOZ_ASSERT(clone);
+ MOZ_DIAGNOSTIC_ASSERT(clone->ConstructorDocumentMatches(ownerDoc));
+ ErrorResult rv;
+ OnSetAdoptedStyleSheets(*clone, mAdoptedStyleSheets.Length(), rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+}
+
+Element* DocumentOrShadowRoot::GetElementById(
+ const nsAString& aElementId) const {
+ if (MOZ_UNLIKELY(aElementId.IsEmpty())) {
+ ReportEmptyGetElementByIdArg();
+ return nullptr;
+ }
+
+ if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) {
+ return entry->GetIdElement();
+ }
+
+ return nullptr;
+}
+
+Element* DocumentOrShadowRoot::GetElementById(nsAtom* aElementId) const {
+ if (MOZ_UNLIKELY(aElementId == nsGkAtoms::_empty)) {
+ ReportEmptyGetElementByIdArg();
+ return nullptr;
+ }
+
+ if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) {
+ return entry->GetIdElement();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName) {
+ ErrorResult rv;
+ RefPtr<nsContentList> list =
+ GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return list.forget();
+}
+
+already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ ErrorResult& aResult) {
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ aResult = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ aNamespaceURI, nameSpaceId);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ }
+
+ NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!");
+ return NS_GetContentList(&AsNode(), nameSpaceId, aLocalName);
+}
+
+already_AddRefed<nsContentList> DocumentOrShadowRoot::GetElementsByClassName(
+ const nsAString& aClasses) {
+ return nsContentUtils::GetElementsByClassName(&AsNode(), aClasses);
+}
+
+nsINode* DocumentOrShadowRoot::Retarget(nsINode* aNode) const {
+ for (nsINode* cur = aNode; cur; cur = cur->GetContainingShadowHost()) {
+ if (cur->SubtreeRoot() == &AsNode()) {
+ return cur;
+ }
+ }
+ return nullptr;
+}
+
+Element* DocumentOrShadowRoot::GetRetargetedFocusedElement() {
+ auto* content = AsNode().OwnerDoc()->GetUnretargetedFocusedContent();
+ if (!content) {
+ return nullptr;
+ }
+ if (nsINode* retarget = Retarget(content)) {
+ return retarget->AsElement();
+ }
+ return nullptr;
+}
+
+Element* DocumentOrShadowRoot::GetPointerLockElement() {
+ nsCOMPtr<Element> pointerLockedElement =
+ PointerLockManager::GetLockedElement();
+ return Element::FromNodeOrNull(Retarget(pointerLockedElement));
+}
+
+Element* DocumentOrShadowRoot::GetFullscreenElement() const {
+ if (!AsNode().IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ Element* element = AsNode().OwnerDoc()->GetUnretargetedFullscreenElement();
+ NS_ASSERTION(!element || element->State().HasState(ElementState::FULLSCREEN),
+ "Fullscreen element should have fullscreen styles applied");
+ return Element::FromNodeOrNull(Retarget(element));
+}
+
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
+
+// Whether only one node or multiple nodes is requested.
+enum class Multiple {
+ No,
+ Yes,
+};
+
+// Whether we should flush layout or not.
+enum class FlushLayout {
+ No,
+ Yes,
+};
+
+enum class PerformRetargeting {
+ No,
+ Yes,
+};
+
+template <typename NodeOrElement>
+NodeOrElement* CastTo(nsINode*);
+
+template <>
+Element* CastTo<Element>(nsINode* aNode) {
+ return aNode->AsElement();
+}
+
+template <>
+nsINode* CastTo<nsINode>(nsINode* aNode) {
+ return aNode;
+}
+
+template <typename NodeOrElement>
+static void QueryNodesFromRect(DocumentOrShadowRoot& aRoot, const nsRect& aRect,
+ FrameForPointOptions aOptions,
+ FlushLayout aShouldFlushLayout,
+ Multiple aMultiple, ViewportType aViewportType,
+ PerformRetargeting aPerformRetargeting,
+ nsTArray<RefPtr<NodeOrElement>>& aNodes) {
+ static_assert(std::is_same<nsINode, NodeOrElement>::value ||
+ std::is_same<Element, NodeOrElement>::value,
+ "Should returning nodes or elements");
+
+ constexpr bool returningElements =
+ std::is_same<Element, NodeOrElement>::value;
+ const bool retargeting = aPerformRetargeting == PerformRetargeting::Yes;
+
+ nsCOMPtr<Document> doc = aRoot.AsNode().OwnerDoc();
+
+ // Make sure the layout information we get is up-to-date, and
+ // ensure we get a root frame (for everything but XUL)
+ if (aShouldFlushLayout == FlushLayout::Yes) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ // XUL docs, unlike HTML, have no frame tree until everything's done loading
+ if (!rootFrame) {
+ return; // return null to premature XUL callers as a reminder to wait
+ }
+
+ aOptions.mBits += FrameForPointOption::IgnorePaintSuppression;
+ aOptions.mBits += FrameForPointOption::IgnoreCrossDoc;
+
+ AutoTArray<nsIFrame*, 8> frames;
+ nsLayoutUtils::GetFramesForArea({rootFrame, aViewportType}, aRect, frames,
+ aOptions);
+
+ for (nsIFrame* frame : frames) {
+ nsINode* node = doc->GetContentInThisDocument(frame);
+ while (node && node->IsInNativeAnonymousSubtree()) {
+ nsIContent* root = node->GetClosestNativeAnonymousSubtreeRoot();
+ MOZ_ASSERT(root, "content is connected");
+ MOZ_ASSERT(root->IsRootOfNativeAnonymousSubtree(), "wat");
+ if (root == &aRoot.AsNode()) {
+ // If we're in the anonymous subtree root we care about, don't retarget.
+ break;
+ }
+ node = root->GetParentOrShadowHostNode();
+ }
+
+ if (!node) {
+ continue;
+ }
+
+ if (returningElements && !node->IsElement()) {
+ // If this helper is called via ElementsFromPoint, we need to make sure
+ // our frame is an element. Otherwise return whatever the top frame is
+ // even if it isn't the top-painted element.
+ // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so
+ // if 'content' is a child of such an element then we need to manually
+ // defer to the parent here.
+ if (aMultiple == Multiple::Yes && !frame->IsInSVGTextSubtree()) {
+ continue;
+ }
+
+ node = node->GetParent();
+ if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(node)) {
+ node = shadow->Host();
+ }
+ }
+
+ // XXXsmaug There is plenty of unspec'ed behavior here
+ // https://github.com/w3c/webcomponents/issues/735
+ // https://github.com/w3c/webcomponents/issues/736
+ if (retargeting) {
+ node = aRoot.Retarget(node);
+ }
+
+ if (node && node != aNodes.SafeLastElement(nullptr)) {
+ aNodes.AppendElement(CastTo<NodeOrElement>(node));
+ if (aMultiple == Multiple::No) {
+ return;
+ }
+ }
+ }
+}
+
+template <typename NodeOrElement>
+static void QueryNodesFromPoint(DocumentOrShadowRoot& aRoot, float aX, float aY,
+ FrameForPointOptions aOptions,
+ FlushLayout aShouldFlushLayout,
+ Multiple aMultiple, ViewportType aViewportType,
+ PerformRetargeting aPerformRetargeting,
+ nsTArray<RefPtr<NodeOrElement>>& aNodes) {
+ // As per the spec, we return null if either coord is negative.
+ if (!aOptions.mBits.contains(FrameForPointOption::IgnoreRootScrollFrame) &&
+ (aX < 0 || aY < 0)) {
+ return;
+ }
+
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+ nsPoint pt(x, y);
+ QueryNodesFromRect(aRoot, nsRect(pt, nsSize(1, 1)), aOptions,
+ aShouldFlushLayout, aMultiple, aViewportType,
+ aPerformRetargeting, aNodes);
+}
+
+} // namespace
+
+Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) {
+ return ElementFromPointHelper(aX, aY, false, true, ViewportType::Layout);
+}
+
+void DocumentOrShadowRoot::ElementsFromPoint(
+ float aX, float aY, nsTArray<RefPtr<Element>>& aElements) {
+ QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes,
+ ViewportType::Layout, PerformRetargeting::Yes, aElements);
+}
+
+void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY,
+ nsTArray<RefPtr<nsINode>>& aNodes) {
+ QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes,
+ ViewportType::Layout, PerformRetargeting::Yes, aNodes);
+}
+
+nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) {
+ AutoTArray<RefPtr<nsINode>, 1> nodes;
+ QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::No,
+ ViewportType::Layout, PerformRetargeting::Yes, nodes);
+ return nodes.SafeElementAt(0);
+}
+
+Element* DocumentOrShadowRoot::ElementFromPointHelper(
+ float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout,
+ ViewportType aViewportType) {
+ EnumSet<FrameForPointOption> options;
+ if (aIgnoreRootScrollFrame) {
+ options += FrameForPointOption::IgnoreRootScrollFrame;
+ }
+
+ auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No;
+
+ AutoTArray<RefPtr<Element>, 1> elements;
+ QueryNodesFromPoint(*this, aX, aY, options, flush, Multiple::No,
+ aViewportType, PerformRetargeting::Yes, elements);
+ return elements.SafeElementAt(0);
+}
+
+void DocumentOrShadowRoot::NodesFromRect(float aX, float aY, float aTopSize,
+ float aRightSize, float aBottomSize,
+ float aLeftSize,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout, bool aOnlyVisible,
+ float aVisibleThreshold,
+ nsTArray<RefPtr<nsINode>>& aReturn) {
+ // Following the same behavior of elementFromPoint,
+ // we don't return anything if either coord is negative
+ if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) {
+ return;
+ }
+
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize);
+ nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1;
+ nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1;
+
+ nsRect rect(x, y, w, h);
+
+ FrameForPointOptions options;
+ if (aIgnoreRootScrollFrame) {
+ options.mBits += FrameForPointOption::IgnoreRootScrollFrame;
+ }
+ if (aOnlyVisible) {
+ options.mBits += FrameForPointOption::OnlyVisible;
+ options.mVisibleThreshold = aVisibleThreshold;
+ }
+
+ auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No;
+ QueryNodesFromRect(*this, rect, options, flush, Multiple::Yes,
+ ViewportType::Layout, PerformRetargeting::No, aReturn);
+}
+
+Element* DocumentOrShadowRoot::AddIDTargetObserver(nsAtom* aID,
+ IDTargetObserver aObserver,
+ void* aData,
+ bool aForImage) {
+ nsDependentAtomString id(aID);
+
+ if (!CheckGetElementByIdArg(id)) {
+ return nullptr;
+ }
+
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aID);
+ NS_ENSURE_TRUE(entry, nullptr);
+
+ entry->AddContentChangeCallback(aObserver, aData, aForImage);
+ return aForImage ? entry->GetImageIdElement() : entry->GetIdElement();
+}
+
+void DocumentOrShadowRoot::RemoveIDTargetObserver(nsAtom* aID,
+ IDTargetObserver aObserver,
+ void* aData, bool aForImage) {
+ nsDependentAtomString id(aID);
+
+ if (!CheckGetElementByIdArg(id)) {
+ return;
+ }
+
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aID);
+ if (!entry) {
+ return;
+ }
+
+ entry->RemoveContentChangeCallback(aObserver, aData, aForImage);
+}
+
+Element* DocumentOrShadowRoot::LookupImageElement(const nsAString& aId) {
+ if (aId.IsEmpty()) {
+ return nullptr;
+ }
+
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
+ return entry ? entry->GetImageIdElement() : nullptr;
+}
+
+void DocumentOrShadowRoot::ReportEmptyGetElementByIdArg() const {
+ nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc());
+}
+
+void DocumentOrShadowRoot::GetAnimations(
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ // As with Element::GetAnimations we initially flush style here.
+ // This should ensure that there are no subsequent changes to the tree
+ // structure while iterating over the children below.
+ if (Document* doc = AsNode().GetComposedDoc()) {
+ doc->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Style, false /* flush animations */));
+ }
+
+ GetAnimationsOptions options;
+ options.mSubtree = true;
+
+ for (RefPtr<nsIContent> child = AsNode().GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (RefPtr<Element> element = Element::FromNode(child)) {
+ nsTArray<RefPtr<Animation>> result;
+ element->GetAnimationsWithoutFlush(options, result);
+ aAnimations.AppendElements(std::move(result));
+ }
+ }
+
+ aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
+}
+
+int32_t DocumentOrShadowRoot::StyleOrderIndexOfSheet(
+ const StyleSheet& aSheet) const {
+ if (aSheet.IsConstructed()) {
+ // NOTE: constructable sheets can have duplicates, so we need to start
+ // looking from behind.
+ int32_t index = mAdoptedStyleSheets.LastIndexOf(&aSheet);
+ return (index < 0) ? index : index + SheetCount();
+ }
+ return mStyleSheets.IndexOf(&aSheet);
+}
+
+void DocumentOrShadowRoot::TraverseSheetRefInStylesIfApplicable(
+ StyleSheet& aSheet, nsCycleCollectionTraversalCallback& cb) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+ if (mKind == Kind::ShadowRoot) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]");
+ cb.NoteXPCOMChild(&aSheet);
+ } else if (AsNode().AsDocument()->StyleSetFilled()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "mStyleSet->mRawSet.stylist.stylesheets.<origin>[i]");
+ cb.NoteXPCOMChild(&aSheet);
+ }
+}
+
+void DocumentOrShadowRoot::TraverseStyleSheets(
+ nsTArray<RefPtr<StyleSheet>>& aSheets, const char* aEdgeName,
+ nsCycleCollectionTraversalCallback& cb) {
+ MOZ_ASSERT(aEdgeName);
+ MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets);
+ for (StyleSheet* sheet : aSheets) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, aEdgeName);
+ cb.NoteXPCOMChild(sheet);
+ TraverseSheetRefInStylesIfApplicable(*sheet, cb);
+ }
+}
+
+void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp,
+ nsCycleCollectionTraversalCallback& cb) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
+ tmp->TraverseStyleSheets(tmp->mStyleSheets, "mStyleSheets[i]", cb);
+
+ tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
+ tmp->TraverseSheetRefInStylesIfApplicable(aSheet, cb);
+ });
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets);
+
+ for (auto iter = tmp->mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->Traverse(&cb);
+ }
+
+ RadioGroupManager::Traverse(tmp, cb);
+}
+
+void DocumentOrShadowRoot::UnlinkStyleSheets(
+ nsTArray<RefPtr<StyleSheet>>& aSheets) {
+ MOZ_ASSERT(&aSheets != &mAdoptedStyleSheets);
+ for (StyleSheet* sheet : aSheets) {
+ sheet->ClearAssociatedDocumentOrShadowRoot();
+ RemoveSheetFromStylesIfApplicable(*sheet);
+ }
+ aSheets.Clear();
+}
+
+void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets);
+ tmp->UnlinkStyleSheets(tmp->mStyleSheets);
+ tmp->EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
+ aSheet.RemoveAdopter(*tmp);
+ tmp->RemoveSheetFromStylesIfApplicable(aSheet);
+ });
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets);
+ tmp->mIdentifierMap.Clear();
+ RadioGroupManager::Unlink(tmp);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DocumentOrShadowRoot.h b/dom/base/DocumentOrShadowRoot.h
new file mode 100644
index 0000000000..75b6972129
--- /dev/null
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DocumentOrShadowRoot_h__
+#define mozilla_dom_DocumentOrShadowRoot_h__
+
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/IdentifierMapEntry.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/ReverseIterator.h"
+#include "nsClassHashtable.h"
+#include "nsContentListDeclarations.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "RadioGroupManager.h"
+
+class nsContentList;
+class nsCycleCollectionTraversalCallback;
+class nsINode;
+class nsINodeList;
+class nsIRadioVisitor;
+class nsWindowSizes;
+
+namespace mozilla {
+class ErrorResult;
+class StyleSheet;
+class ErrorResult;
+
+namespace dom {
+
+class Animation;
+class Element;
+class Document;
+class DocumentOrShadowRoot;
+class HTMLInputElement;
+class StyleSheetList;
+class ShadowRoot;
+template <typename T>
+class Sequence;
+
+/**
+ * A class meant to be shared by ShadowRoot and Document, that holds a list of
+ * stylesheets.
+ *
+ * TODO(emilio, bug 1418159): In the future this should hold most of the
+ * relevant style state, this should allow us to fix bug 548397.
+ */
+class DocumentOrShadowRoot : public RadioGroupManager {
+ enum class Kind {
+ Document,
+ ShadowRoot,
+ };
+
+ public:
+ // These should always be non-null, but can't use a reference because
+ // dereferencing `this` on initializer lists is UB, apparently, see
+ // bug 1596499.
+ explicit DocumentOrShadowRoot(Document*);
+ explicit DocumentOrShadowRoot(ShadowRoot*);
+
+ // Unusual argument naming is because of cycle collection macros.
+ static void Traverse(DocumentOrShadowRoot* tmp,
+ nsCycleCollectionTraversalCallback& cb);
+ static void Unlink(DocumentOrShadowRoot* tmp);
+
+ nsINode& AsNode() { return *mAsNode; }
+
+ const nsINode& AsNode() const { return *mAsNode; }
+
+ StyleSheet* SheetAt(size_t aIndex) const {
+ return mStyleSheets.SafeElementAt(aIndex);
+ }
+
+ size_t SheetCount() const { return mStyleSheets.Length(); }
+
+ const nsTArray<RefPtr<StyleSheet>>& AdoptedStyleSheets() const {
+ return mAdoptedStyleSheets;
+ }
+
+ /**
+ * Returns an index for the sheet in relative style order.
+ * If there are non-applicable sheets, then this index may
+ * not match 1:1 with the sheet's actual index in the style set.
+ *
+ * Handles sheets from both mStyleSheets and mAdoptedStyleSheets
+ */
+ int32_t StyleOrderIndexOfSheet(const StyleSheet& aSheet) const;
+
+ StyleSheetList* StyleSheets();
+
+ void RemoveStyleSheet(StyleSheet&);
+
+ Element* GetElementById(const nsAString& aElementId) const;
+ Element* GetElementById(nsAtom* aElementId) const;
+
+ /**
+ * This method returns _all_ the elements in this scope which have id
+ * aElementId, if there are any. Otherwise it returns null.
+ *
+ * This is useful for stuff like QuerySelector optimization and such.
+ */
+ inline const nsTArray<Element*>* GetAllElementsForId(
+ const nsAString& aElementId) const;
+
+ already_AddRefed<nsContentList> GetElementsByTagName(
+ const nsAString& aTagName) {
+ return NS_GetContentList(&AsNode(), kNameSpaceID_Unknown, aTagName);
+ }
+
+ already_AddRefed<nsContentList> GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName);
+
+ already_AddRefed<nsContentList> GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ mozilla::ErrorResult&);
+
+ already_AddRefed<nsContentList> GetElementsByClassName(
+ const nsAString& aClasses);
+
+ ~DocumentOrShadowRoot();
+
+ Element* GetPointerLockElement();
+ Element* GetFullscreenElement() const;
+
+ Element* ElementFromPoint(float aX, float aY);
+ nsINode* NodeFromPoint(float aX, float aY);
+
+ void ElementsFromPoint(float aX, float aY, nsTArray<RefPtr<Element>>&);
+ void NodesFromPoint(float aX, float aY, nsTArray<RefPtr<nsINode>>&);
+
+ /**
+ * Helper for elementFromPoint implementation that allows
+ * ignoring the scroll frame and/or avoiding layout flushes.
+ *
+ * @see nsIDOMWindowUtils::elementFromPoint
+ */
+ Element* ElementFromPointHelper(float aX, float aY,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout,
+ ViewportType aViewportType);
+
+ void NodesFromRect(float aX, float aY, float aTopSize, float aRightSize,
+ float aBottomSize, float aLeftSize,
+ bool aIgnoreRootScrollFrame, bool aFlushLayout,
+ bool aOnlyVisible, float aVisibleThreshold,
+ nsTArray<RefPtr<nsINode>>&);
+
+ /**
+ * This gets fired when the element that an id refers to changes.
+ * This fires at difficult times. It is generally not safe to do anything
+ * which could modify the DOM in any way. Use
+ * nsContentUtils::AddScriptRunner.
+ * @return true to keep the callback in the callback set, false
+ * to remove it.
+ */
+ typedef bool (*IDTargetObserver)(Element* aOldElement, Element* aNewelement,
+ void* aData);
+
+ /**
+ * Add an IDTargetObserver for a specific ID. The IDTargetObserver
+ * will be fired whenever the content associated with the ID changes
+ * in the future. If aForImage is true, mozSetImageElement can override
+ * what content is associated with the ID. In that case the IDTargetObserver
+ * will be notified at those times when the result of LookupImageElement
+ * changes.
+ * At most one (aObserver, aData, aForImage) triple can be
+ * registered for each ID.
+ * @return the content currently associated with the ID.
+ */
+ Element* AddIDTargetObserver(nsAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage);
+
+ /**
+ * Remove the (aObserver, aData, aForImage) triple for a specific ID, if
+ * registered.
+ */
+ void RemoveIDTargetObserver(nsAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage);
+
+ /**
+ * Lookup an image element using its associated ID, which is usually provided
+ * by |-moz-element()|. Similar to GetElementById, with the difference that
+ * elements set using mozSetImageElement have higher priority.
+ * @param aId the ID associated the element we want to lookup
+ * @return the element associated with |aId|
+ */
+ Element* LookupImageElement(const nsAString& aElementId);
+
+ /**
+ * Check that aId is not empty and log a message to the console
+ * service if it is.
+ * @returns true if aId looks correct, false otherwise.
+ */
+ inline bool CheckGetElementByIdArg(const nsAString& aId) {
+ if (aId.IsEmpty()) {
+ ReportEmptyGetElementByIdArg();
+ return false;
+ }
+ return true;
+ }
+
+ void ReportEmptyGetElementByIdArg() const;
+
+ // Web Animations
+ MOZ_CAN_RUN_SCRIPT
+ void GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations);
+
+ nsINode* Retarget(nsINode*) const;
+
+ void OnSetAdoptedStyleSheets(StyleSheet&, uint32_t aIndex, ErrorResult&);
+ void OnDeleteAdoptedStyleSheets(StyleSheet&, uint32_t aIndex, ErrorResult&);
+
+ // This is needed because ServoStyleSet / ServoAuthorData don't deal with
+ // duplicate stylesheets (and it's unclear we'd want to support that as it'd
+ // be a bunch of duplicate work), while adopted stylesheets do need to deal
+ // with them.
+ template <typename Callback>
+ void EnumerateUniqueAdoptedStyleSheetsBackToFront(Callback aCallback) {
+ StyleSheetSet set(mAdoptedStyleSheets.Length());
+ for (StyleSheet* sheet : Reversed(mAdoptedStyleSheets)) {
+ if (MOZ_UNLIKELY(!set.EnsureInserted(sheet))) {
+ continue;
+ }
+ aCallback(*sheet);
+ }
+ }
+
+ protected:
+ // Cycle collection helper functions
+ void TraverseSheetRefInStylesIfApplicable(
+ StyleSheet&, nsCycleCollectionTraversalCallback&);
+ void TraverseStyleSheets(nsTArray<RefPtr<StyleSheet>>&, const char*,
+ nsCycleCollectionTraversalCallback&);
+ void UnlinkStyleSheets(nsTArray<RefPtr<StyleSheet>>&);
+
+ using StyleSheetSet = nsTHashSet<const StyleSheet*>;
+ void RemoveSheetFromStylesIfApplicable(StyleSheet&);
+ void ClearAdoptedStyleSheets();
+
+ /**
+ * Clone's the argument's adopted style sheets into this.
+ * This should only be used when cloning a static document for printing.
+ */
+ void CloneAdoptedSheetsFrom(const DocumentOrShadowRoot&);
+
+ void InsertSheetAt(size_t aIndex, StyleSheet& aSheet);
+
+ void AddSizeOfExcludingThis(nsWindowSizes&) const;
+ void AddSizeOfOwnedSheetArrayExcludingThis(
+ nsWindowSizes&, const nsTArray<RefPtr<StyleSheet>>&) const;
+
+ /**
+ * If focused element's subtree root is this document or shadow root, return
+ * focused element, otherwise, get the shadow host recursively until the
+ * shadow host's subtree root is this document or shadow root.
+ */
+ Element* GetRetargetedFocusedElement();
+
+ nsTArray<RefPtr<StyleSheet>> mStyleSheets;
+ RefPtr<StyleSheetList> mDOMStyleSheets;
+
+ /**
+ * Style sheets that are adopted by assinging to the `adoptedStyleSheets`
+ * WebIDL atribute. These can only be constructed stylesheets.
+ */
+ nsTArray<RefPtr<StyleSheet>> mAdoptedStyleSheets;
+
+ /*
+ * mIdentifierMap works as follows for IDs:
+ * 1) Attribute changes affect the table immediately (removing and adding
+ * entries as needed).
+ * 2) Removals from the DOM affect the table immediately
+ * 3) Additions to the DOM always update existing entries for names, and add
+ * new ones for IDs.
+ */
+ nsTHashtable<IdentifierMapEntry> mIdentifierMap;
+
+ // Always non-null, see comment in the constructor as to why a pointer instead
+ // of a reference.
+ nsINode* mAsNode;
+ const Kind mKind;
+};
+
+inline const nsTArray<Element*>* DocumentOrShadowRoot::GetAllElementsForId(
+ const nsAString& aElementId) const {
+ if (aElementId.IsEmpty()) {
+ return nullptr;
+ }
+
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId);
+ return entry ? &entry->GetIdElements() : nullptr;
+}
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/DocumentType.cpp b/dom/base/DocumentType.cpp
new file mode 100644
index 0000000000..21cea2c8e8
--- /dev/null
+++ b/dom/base/DocumentType.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's DocumentType node.
+ */
+
+#include "mozilla/dom/DocumentType.h"
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsDOMString.h"
+#include "nsNodeInfoManager.h"
+#include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/DocumentTypeBinding.h"
+
+already_AddRefed<mozilla::dom::DocumentType> NS_NewDOMDocumentType(
+ nsNodeInfoManager* aNodeInfoManager, nsAtom* aName,
+ const nsAString& aPublicId, const nsAString& aSystemId,
+ const nsAString& aInternalSubset) {
+ MOZ_ASSERT(aName, "Must have a name");
+
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::documentTypeNodeName, nullptr, kNameSpaceID_None,
+ nsINode::DOCUMENT_TYPE_NODE, aName);
+
+ RefPtr<mozilla::dom::DocumentType> docType =
+ new (aNodeInfoManager) mozilla::dom::DocumentType(
+ ni.forget(), aPublicId, aSystemId, aInternalSubset);
+ return docType.forget();
+}
+
+namespace mozilla::dom {
+
+JSObject* DocumentType::WrapNode(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DocumentType_Binding::Wrap(cx, this, aGivenProto);
+}
+
+DocumentType::DocumentType(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset)
+ : CharacterData(std::move(aNodeInfo)),
+ mPublicId(aPublicId),
+ mSystemId(aSystemId),
+ mInternalSubset(aInternalSubset) {
+ MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE,
+ "Bad NodeType in aNodeInfo");
+ MOZ_ASSERT(!IsCharacterData());
+}
+
+DocumentType::~DocumentType() = default;
+
+const nsTextFragment* DocumentType::GetText() { return nullptr; }
+
+void DocumentType::GetName(nsAString& aName) const { aName = NodeName(); }
+
+void DocumentType::GetPublicId(nsAString& aPublicId) const {
+ aPublicId = mPublicId;
+}
+
+void DocumentType::GetSystemId(nsAString& aSystemId) const {
+ aSystemId = mSystemId;
+}
+
+void DocumentType::GetInternalSubset(nsAString& aInternalSubset) const {
+ aInternalSubset = mInternalSubset;
+}
+
+already_AddRefed<CharacterData> DocumentType::CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const {
+ return do_AddRef(new (aNodeInfo->NodeInfoManager()) DocumentType(
+ do_AddRef(aNodeInfo), mPublicId, mSystemId, mInternalSubset));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/DocumentType.h b/dom/base/DocumentType.h
new file mode 100644
index 0000000000..1df422a32b
--- /dev/null
+++ b/dom/base/DocumentType.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's DocumentType node.
+ */
+
+#ifndef DocumentType_h
+#define DocumentType_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CharacterData.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+
+// XXX DocumentType is currently implemented by inheriting the generic
+// CharacterData object, even though DocumentType is not character
+// data. This is done simply for convenience and should be changed if
+// this restricts what should be done for character data.
+
+class DocumentType final : public CharacterData {
+ public:
+ DocumentType(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ const nsAString& aPublicId, const nsAString& aSystemId,
+ const nsAString& aInternalSubset);
+
+ // nsISupports
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(DocumentType, CharacterData)
+
+ // nsINode
+ void GetNodeValueInternal(nsAString& aNodeValue) override {
+ SetDOMStringToNull(aNodeValue);
+ }
+ void SetNodeValueInternal(const nsAString& aNodeValue,
+ mozilla::ErrorResult& aError) override {}
+
+ // nsIContent overrides
+ virtual const nsTextFragment* GetText() override;
+
+ virtual already_AddRefed<CharacterData> CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override;
+
+ // WebIDL API
+ void GetName(nsAString& aName) const;
+ void GetPublicId(nsAString& aPublicId) const;
+ void GetSystemId(nsAString& aSystemId) const;
+ void GetInternalSubset(nsAString& aInternalSubset) const;
+
+ protected:
+ virtual ~DocumentType();
+
+ virtual JSObject* WrapNode(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsString mPublicId;
+ nsString mSystemId;
+ nsString mInternalSubset;
+};
+
+} // namespace mozilla::dom
+
+already_AddRefed<mozilla::dom::DocumentType> NS_NewDOMDocumentType(
+ nsNodeInfoManager* aNodeInfoManager, nsAtom* aName,
+ const nsAString& aPublicId, const nsAString& aSystemId,
+ const nsAString& aInternalSubset);
+
+#endif // DocumentType_h
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
new file mode 100644
index 0000000000..7d6441ac1d
--- /dev/null
+++ b/dom/base/Element.cpp
@@ -0,0 +1,4949 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all element classes; this provides an implementation
+ * of DOM Core's Element, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+
+#include <inttypes.h>
+#include <initializer_list>
+#include <new>
+#include "DOMIntersectionObserver.h"
+#include "DOMMatrix.h"
+#include "ExpandedPrincipal.h"
+#include "PresShellInlines.h"
+#include "jsapi.h"
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AnimationComparator.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/Components.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/FullscreenChange.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PointerLockManager.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/ReflowOutput.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/ScrollOrigin.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/ServoStyleConstsInlines.h"
+#include "mozilla/SizeOfState.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_full_screen_api.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Flex.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/Grid.h"
+#include "mozilla/dom/HTMLDivElement.h"
+#include "mozilla/dom/HTMLElement.h"
+#include "mozilla/dom/HTMLParagraphElement.h"
+#include "mozilla/dom/HTMLPreElement.h"
+#include "mozilla/dom/HTMLSpanElement.h"
+#include "mozilla/dom/HTMLTableCellElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/PointerEventHandler.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Sanitizer.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/XULCommandEvent.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/BaseRect.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsAtom.h"
+#include "nsAttrName.h"
+#include "nsAttrValueInlines.h"
+#include "nsAttrValueOrString.h"
+#include "nsBaseHashtable.h"
+#include "nsBlockFrame.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCompatibility.h"
+#include "nsContainerFrame.h"
+#include "nsContentList.h"
+#include "nsContentListDeclarations.h"
+#include "nsCoord.h"
+#include "nsDOMAttributeMap.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMString.h"
+#include "nsDOMStringMap.h"
+#include "nsDOMTokenList.h"
+#include "nsDocShell.h"
+#include "nsError.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFocusManager.h"
+#include "nsFrameState.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsGridContainerFrame.h"
+#include "nsIAutoCompletePopup.h"
+#include "nsIBrowser.h"
+#include "nsIContentInlines.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsIDOMXULContainerElement.h"
+#include "nsIDOMXULControlElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMXULRadioGroupElement.h"
+#include "nsIDOMXULRelatedElement.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDocShell.h"
+#include "nsIFocusManager.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIIOService.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMemoryReporter.h"
+#include "nsIPrincipal.h"
+#include "nsIScreenManager.h"
+#include "nsIScriptError.h"
+#include "nsIScrollableFrame.h"
+#include "nsISpeculativeConnect.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsLayoutUtils.h"
+#include "nsLineBox.h"
+#include "nsNameSpaceManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPoint.h"
+#include "nsPresContext.h"
+#include "nsQueryFrame.h"
+#include "nsRefPtrHashtable.h"
+#include "nsSize.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+#include "nsStyleStruct.h"
+#include "nsStyledElement.h"
+#include "nsTArray.h"
+#include "nsTextNode.h"
+#include "nsThreadUtils.h"
+#include "nsViewManager.h"
+#include "nsWindowSizes.h"
+
+#include "nsXULElement.h"
+
+#ifdef DEBUG
+# include "nsRange.h"
+#endif
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using mozilla::gfx::Matrix4x4;
+
+namespace mozilla::dom {
+
+// Verify sizes of nodes. We use a template rather than a direct static
+// assert so that the error message actually displays the sizes.
+// On 32 bit systems the actual allocated size varies a bit between
+// OSes/compilers.
+//
+// We need different numbers on certain build types to deal with the owning
+// thread pointer that comes with the non-threadsafe refcount on
+// nsIContent.
+#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+# define EXTRA_DOM_NODE_BYTES 8
+#else
+# define EXTRA_DOM_NODE_BYTES 0
+#endif
+
+#define ASSERT_NODE_SIZE(type, opt_size_64, opt_size_32) \
+ template <int a, int sizeOn64, int sizeOn32> \
+ struct Check##type##Size { \
+ static_assert((sizeof(void*) == 8 && a == sizeOn64) || \
+ (sizeof(void*) == 4 && a <= sizeOn32), \
+ "DOM size changed"); \
+ }; \
+ Check##type##Size<sizeof(type), opt_size_64 + EXTRA_DOM_NODE_BYTES, \
+ opt_size_32 + EXTRA_DOM_NODE_BYTES> \
+ g##type##CES;
+
+// Note that mozjemalloc uses a 16 byte quantum, so 64, 80 and 128 are
+// bucket sizes.
+ASSERT_NODE_SIZE(Element, 128, 80);
+ASSERT_NODE_SIZE(HTMLDivElement, 128, 80);
+ASSERT_NODE_SIZE(HTMLElement, 128, 80);
+ASSERT_NODE_SIZE(HTMLParagraphElement, 128, 80);
+ASSERT_NODE_SIZE(HTMLPreElement, 128, 80);
+ASSERT_NODE_SIZE(HTMLSpanElement, 128, 80);
+ASSERT_NODE_SIZE(HTMLTableCellElement, 128, 80);
+ASSERT_NODE_SIZE(Text, 120, 64);
+
+#undef ASSERT_NODE_SIZE
+#undef EXTRA_DOM_NODE_BYTES
+
+} // namespace mozilla::dom
+
+nsAtom* nsIContent::DoGetID() const {
+ MOZ_ASSERT(HasID(), "Unexpected call");
+ MOZ_ASSERT(IsElement(), "Only elements can have IDs");
+
+ return AsElement()->GetParsedAttr(nsGkAtoms::id)->GetAtomValue();
+}
+
+nsIFrame* nsIContent::GetPrimaryFrame(mozilla::FlushType aType) {
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // Cause a flush, so we get up-to-date frame information.
+ if (aType != mozilla::FlushType::None) {
+ doc->FlushPendingNotifications(aType);
+ }
+
+ auto* frame = GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ if (aType == mozilla::FlushType::Layout) {
+ frame->PresShell()->EnsureReflowIfFrameHasHiddenContent(frame);
+ frame = GetPrimaryFrame();
+ }
+
+ return frame;
+}
+
+namespace mozilla::dom {
+
+nsDOMAttributeMap* Element::Attributes() {
+ nsDOMSlots* slots = DOMSlots();
+ if (!slots->mAttributeMap) {
+ slots->mAttributeMap = new nsDOMAttributeMap(this);
+ }
+
+ return slots->mAttributeMap;
+}
+
+void Element::SetPointerCapture(int32_t aPointerId, ErrorResult& aError) {
+ if (OwnerDoc()->ShouldResistFingerprinting(RFPTarget::Unknown) &&
+ aPointerId != PointerEventHandler::GetSpoofedPointerIdForRFP()) {
+ aError.ThrowNotFoundError("Invalid pointer id");
+ return;
+ }
+ const PointerInfo* pointerInfo =
+ PointerEventHandler::GetPointerInfo(aPointerId);
+ if (!pointerInfo) {
+ aError.ThrowNotFoundError("Invalid pointer id");
+ return;
+ }
+ if (!IsInComposedDoc()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ if (OwnerDoc()->GetPointerLockElement()) {
+ // Throw an exception 'InvalidStateError' while the page has a locked
+ // element.
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ if (!pointerInfo->mActiveState ||
+ pointerInfo->mActiveDocument != OwnerDoc()) {
+ return;
+ }
+ PointerEventHandler::RequestPointerCaptureById(aPointerId, this);
+}
+
+void Element::ReleasePointerCapture(int32_t aPointerId, ErrorResult& aError) {
+ if (OwnerDoc()->ShouldResistFingerprinting(RFPTarget::Unknown) &&
+ aPointerId != PointerEventHandler::GetSpoofedPointerIdForRFP()) {
+ aError.ThrowNotFoundError("Invalid pointer id");
+ return;
+ }
+ if (!PointerEventHandler::GetPointerInfo(aPointerId)) {
+ aError.ThrowNotFoundError("Invalid pointer id");
+ return;
+ }
+ if (HasPointerCapture(aPointerId)) {
+ PointerEventHandler::ReleasePointerCaptureById(aPointerId);
+ }
+}
+
+bool Element::HasPointerCapture(long aPointerId) {
+ PointerCaptureInfo* pointerCaptureInfo =
+ PointerEventHandler::GetPointerCaptureInfo(aPointerId);
+ if (pointerCaptureInfo && pointerCaptureInfo->mPendingElement == this) {
+ return true;
+ }
+ return false;
+}
+
+const nsAttrValue* Element::GetSVGAnimatedClass() const {
+ MOZ_ASSERT(MayHaveClass() && IsSVGElement(), "Unexpected call");
+ return static_cast<const SVGElement*>(this)->GetAnimatedClassName();
+}
+
+NS_IMETHODIMP
+Element::QueryInterface(REFNSIID aIID, void** aInstancePtr) {
+ if (aIID.Equals(NS_GET_IID(Element))) {
+ NS_ADDREF_THIS();
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
+ nsresult rv = FragmentOrElement::QueryInterface(aIID, aInstancePtr);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+ElementState Element::IntrinsicState() const {
+ return IsEditable() ? ElementState::READWRITE : ElementState::READONLY;
+}
+
+void Element::NotifyStateChange(ElementState aStates) {
+ if (aStates.IsEmpty()) {
+ return;
+ }
+
+ if (Document* doc = GetComposedDoc()) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ElementStateChanged(this, aStates);
+ }
+}
+
+void Element::UpdateLinkState(ElementState aState) {
+ MOZ_ASSERT(!aState.HasAtLeastOneOfStates(~ElementState::VISITED_OR_UNVISITED),
+ "Unexpected link state bits");
+ mState = (mState & ~ElementState::VISITED_OR_UNVISITED) | aState;
+}
+
+void Element::UpdateState(bool aNotify) {
+ ElementState oldState = mState;
+ mState =
+ IntrinsicState() | (oldState & ElementState::EXTERNALLY_MANAGED_STATES);
+ if (aNotify) {
+ ElementState changedStates = oldState ^ mState;
+ if (!changedStates.IsEmpty()) {
+ Document* doc = GetComposedDoc();
+ if (doc) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ElementStateChanged(this, changedStates);
+ }
+ }
+ }
+}
+
+} // namespace mozilla::dom
+
+void nsIContent::UpdateEditableState(bool aNotify) {
+ if (IsInNativeAnonymousSubtree()) {
+ // Don't propagate the editable flag into native anonymous subtrees.
+ if (IsRootOfNativeAnonymousSubtree()) {
+ return;
+ }
+
+ // We allow setting the flag on NAC (explicitly, see
+ // nsTextControlFrame::CreateAnonymousContent for example), but not
+ // unsetting it.
+ //
+ // Otherwise, just the act of binding the NAC subtree into our non-anonymous
+ // parent would clear the flag, which is not good. As we shouldn't move NAC
+ // around, this is fine.
+ if (HasFlag(NODE_IS_EDITABLE)) {
+ return;
+ }
+ }
+
+ nsIContent* parent = GetParent();
+ SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
+}
+
+namespace mozilla::dom {
+
+void Element::UpdateEditableState(bool aNotify) {
+ nsIContent::UpdateEditableState(aNotify);
+ if (aNotify) {
+ UpdateState(aNotify);
+ } else {
+ // Avoid calling UpdateState in this very common case, because
+ // this gets called for pretty much every single element on
+ // insertion into the document and UpdateState can be slow for
+ // some kinds of elements even when not notifying.
+ if (IsEditable()) {
+ RemoveStatesSilently(ElementState::READONLY);
+ AddStatesSilently(ElementState::READWRITE);
+ } else {
+ RemoveStatesSilently(ElementState::READWRITE);
+ AddStatesSilently(ElementState::READONLY);
+ }
+ }
+}
+
+Maybe<int32_t> Element::GetTabIndexAttrValue() {
+ const nsAttrValue* attrVal = GetParsedAttr(nsGkAtoms::tabindex);
+ if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
+ return Some(attrVal->GetIntegerValue());
+ }
+
+ return Nothing();
+}
+
+int32_t Element::TabIndex() {
+ Maybe<int32_t> attrVal = GetTabIndexAttrValue();
+ if (attrVal.isSome()) {
+ return attrVal.value();
+ }
+
+ return TabIndexDefault();
+}
+
+void Element::Focus(const FocusOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aError) {
+ const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (MOZ_UNLIKELY(!fm)) {
+ return;
+ }
+ const OwningNonNull<Element> kungFuDeathGrip(*this);
+ // Also other browsers seem to have the hack to not re-focus (and flush) when
+ // the element is already focused.
+ // Until https://github.com/whatwg/html/issues/4512 is clarified, we'll
+ // maintain interoperatibility by not re-focusing, independent of aOptions.
+ // I.e., `focus({ preventScroll: true})` followed by `focus( { preventScroll:
+ // false })` won't re-focus.
+ if (fm->CanSkipFocus(this)) {
+ fm->NotifyOfReFocus(kungFuDeathGrip);
+ fm->NeedsFlushBeforeEventHandling(this);
+ return;
+ }
+ uint32_t fmFlags = nsFocusManager::ProgrammaticFocusFlags(aOptions);
+ if (aCallerType == CallerType::NonSystem) {
+ fmFlags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
+ }
+ aError = fm->SetFocus(kungFuDeathGrip, fmFlags);
+}
+
+void Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) {
+ nsAutoString value;
+ value.AppendInt(aTabIndex);
+
+ SetAttr(nsGkAtoms::tabindex, value, aError);
+}
+
+void Element::SetShadowRoot(ShadowRoot* aShadowRoot) {
+ nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+ MOZ_ASSERT(!aShadowRoot || !slots->mShadowRoot,
+ "We shouldn't clear the shadow root without unbind first");
+ slots->mShadowRoot = aShadowRoot;
+}
+
+void Element::SetLastRememberedBSize(float aBSize) {
+ ExtendedDOMSlots()->mLastRememberedBSize = Some(aBSize);
+}
+
+void Element::SetLastRememberedISize(float aISize) {
+ ExtendedDOMSlots()->mLastRememberedISize = Some(aISize);
+}
+
+void Element::RemoveLastRememberedBSize() {
+ if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
+ slots->mLastRememberedBSize.reset();
+ }
+}
+
+void Element::RemoveLastRememberedISize() {
+ if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
+ slots->mLastRememberedISize.reset();
+ }
+}
+
+void Element::Blur(mozilla::ErrorResult& aError) {
+ if (!ShouldBlur(this)) {
+ return;
+ }
+
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return;
+ }
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow()) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ aError = fm->ClearFocus(win);
+ }
+ }
+}
+
+ElementState Element::StyleStateFromLocks() const {
+ StyleStateLocks locksAndValues = LockedStyleStates();
+ ElementState locks = locksAndValues.mLocks;
+ ElementState values = locksAndValues.mValues;
+ ElementState state = (mState & ~locks) | (locks & values);
+
+ if (state.HasState(ElementState::VISITED)) {
+ return state & ~ElementState::UNVISITED;
+ }
+ if (state.HasState(ElementState::UNVISITED)) {
+ return state & ~ElementState::VISITED;
+ }
+
+ return state;
+}
+
+Element::StyleStateLocks Element::LockedStyleStates() const {
+ StyleStateLocks* locks =
+ static_cast<StyleStateLocks*>(GetProperty(nsGkAtoms::lockedStyleStates));
+ if (locks) {
+ return *locks;
+ }
+ return StyleStateLocks();
+}
+
+void Element::NotifyStyleStateChange(ElementState aStates) {
+ if (RefPtr<Document> doc = GetComposedDoc()) {
+ if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
+ nsAutoScriptBlocker scriptBlocker;
+ presShell->ElementStateChanged(doc, this, aStates);
+ }
+ }
+}
+
+void Element::LockStyleStates(ElementState aStates, bool aEnabled) {
+ StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
+
+ locks->mLocks |= aStates;
+ if (aEnabled) {
+ locks->mValues |= aStates;
+ } else {
+ locks->mValues &= ~aStates;
+ }
+
+ if (aStates.HasState(ElementState::VISITED)) {
+ locks->mLocks &= ~ElementState::UNVISITED;
+ }
+ if (aStates.HasState(ElementState::UNVISITED)) {
+ locks->mLocks &= ~ElementState::VISITED;
+ }
+
+ SetProperty(nsGkAtoms::lockedStyleStates, locks,
+ nsINode::DeleteProperty<StyleStateLocks>);
+ SetHasLockedStyleStates();
+
+ NotifyStyleStateChange(aStates);
+}
+
+void Element::UnlockStyleStates(ElementState aStates) {
+ StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
+
+ locks->mLocks &= ~aStates;
+
+ if (locks->mLocks.IsEmpty()) {
+ RemoveProperty(nsGkAtoms::lockedStyleStates);
+ ClearHasLockedStyleStates();
+ delete locks;
+ } else {
+ SetProperty(nsGkAtoms::lockedStyleStates, locks,
+ nsINode::DeleteProperty<StyleStateLocks>);
+ }
+
+ NotifyStyleStateChange(aStates);
+}
+
+void Element::ClearStyleStateLocks() {
+ StyleStateLocks locks = LockedStyleStates();
+
+ RemoveProperty(nsGkAtoms::lockedStyleStates);
+ ClearHasLockedStyleStates();
+
+ NotifyStyleStateChange(locks.mLocks);
+}
+
+/* virtual */
+nsINode* Element::GetScopeChainParent() const { return OwnerDoc(); }
+
+nsDOMTokenList* Element::ClassList() {
+ Element::nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mClassList) {
+ slots->mClassList = new nsDOMTokenList(this, nsGkAtoms::_class);
+ }
+
+ return slots->mClassList;
+}
+
+nsDOMTokenList* Element::Part() {
+ Element::nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mPart) {
+ slots->mPart = new nsDOMTokenList(this, nsGkAtoms::part);
+ }
+
+ return slots->mPart;
+}
+
+void Element::RecompileScriptEventListeners() {
+ for (uint32_t i = 0, count = mAttrs.AttrCount(); i < count; ++i) {
+ BorrowedAttrInfo attrInfo = mAttrs.AttrInfoAt(i);
+
+ // Eventlistenener-attributes are always in the null namespace
+ if (!attrInfo.mName->IsAtom()) {
+ continue;
+ }
+
+ nsAtom* attr = attrInfo.mName->Atom();
+ if (!IsEventAttributeName(attr)) {
+ continue;
+ }
+
+ nsAutoString value;
+ attrInfo.mValue->ToString(value);
+ SetEventHandler(GetEventNameForAttr(attr), value, true);
+ }
+}
+
+void Element::GetAttributeNames(nsTArray<nsString>& aResult) {
+ uint32_t count = mAttrs.AttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* name = mAttrs.AttrNameAt(i);
+ name->GetQualifiedName(*aResult.AppendElement());
+ }
+}
+
+already_AddRefed<nsIHTMLCollection> Element::GetElementsByTagName(
+ const nsAString& aLocalName) {
+ return NS_GetContentList(this, kNameSpaceID_Unknown, aLocalName);
+}
+
+nsIScrollableFrame* Element::GetScrollFrame(nsIFrame** aFrame,
+ FlushType aFlushType) {
+ nsIFrame* frame = GetPrimaryFrame(aFlushType);
+ if (aFrame) {
+ *aFrame = frame;
+ }
+ if (frame) {
+ if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // It's unclear what to return for SVG frames, so just return null.
+ return nullptr;
+ }
+
+ if (nsIScrollableFrame* scrollFrame = frame->GetScrollTargetFrame()) {
+ MOZ_ASSERT(!OwnerDoc()->IsScrollingElement(this),
+ "How can we have a scrollframe if we're the "
+ "scrollingElement for our document?");
+ return scrollFrame;
+ }
+ }
+
+ Document* doc = OwnerDoc();
+ // Note: This IsScrollingElement() call can flush frames, if we're the body of
+ // a quirks mode document.
+ bool isScrollingElement = doc->IsScrollingElement(this);
+ // Now reget *aStyledFrame if the caller asked for it, because that frame
+ // flush can kill it.
+ if (aFrame) {
+ *aFrame = GetPrimaryFrame(FlushType::None);
+ }
+
+ if (isScrollingElement) {
+ // Our scroll info should map to the root scrollable frame if there is one.
+ if (PresShell* presShell = doc->GetPresShell()) {
+ return presShell->GetRootScrollFrameAsScrollable();
+ }
+ }
+
+ return nullptr;
+}
+
+bool Element::CheckVisibility(const CheckVisibilityOptions& aOptions) {
+ nsIFrame* f =
+ GetPrimaryFrame(aOptions.mFlush ? FlushType::Frames : FlushType::None);
+ if (!f) {
+ // 1. If this does not have an associated box, return false.
+ return false;
+ }
+
+ if (f->IsHiddenByContentVisibilityOnAnyAncestor(
+ nsIFrame::IncludeContentVisibility::Hidden)) {
+ // 2. If a shadow-including ancestor of this has content-visibility: hidden,
+ // return false.
+ return false;
+ }
+
+ if (aOptions.mCheckOpacity && f->Style()->IsInOpacityZeroSubtree()) {
+ // 3. If the checkOpacity dictionary member of options is true, and this, or
+ // a shadow-including ancestor of this, has a computed opacity value of 0,
+ // return false.
+ return false;
+ }
+
+ if (aOptions.mCheckVisibilityCSS && !f->StyleVisibility()->IsVisible()) {
+ // 4. If the checkVisibilityCSS dictionary member of options is true, and
+ // this is invisible, return false.
+ return false;
+ }
+
+ // 5. Return true
+ return true;
+}
+
+void Element::ScrollIntoView(const BooleanOrScrollIntoViewOptions& aObject) {
+ if (aObject.IsScrollIntoViewOptions()) {
+ return ScrollIntoView(aObject.GetAsScrollIntoViewOptions());
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aObject.IsBoolean());
+
+ ScrollIntoViewOptions options;
+ if (aObject.GetAsBoolean()) {
+ options.mBlock = ScrollLogicalPosition::Start;
+ options.mInline = ScrollLogicalPosition::Nearest;
+ } else {
+ options.mBlock = ScrollLogicalPosition::End;
+ options.mInline = ScrollLogicalPosition::Nearest;
+ }
+ return ScrollIntoView(options);
+}
+
+void Element::ScrollIntoView(const ScrollIntoViewOptions& aOptions) {
+ Document* document = GetComposedDoc();
+ if (!document) {
+ return;
+ }
+
+ // Get the presentation shell
+ RefPtr<PresShell> presShell = document->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ const auto ToWhereToScroll =
+ [](ScrollLogicalPosition aPosition) -> WhereToScroll {
+ switch (aPosition) {
+ case ScrollLogicalPosition::Start:
+ return WhereToScroll::Start;
+ case ScrollLogicalPosition::Center:
+ return WhereToScroll::Center;
+ case ScrollLogicalPosition::End:
+ return WhereToScroll::End;
+ case ScrollLogicalPosition::EndGuard_:
+ MOZ_FALLTHROUGH_ASSERT("Unexpected block direction value");
+ case ScrollLogicalPosition::Nearest:
+ break;
+ }
+ return WhereToScroll::Nearest;
+ };
+
+ const auto block = ToWhereToScroll(aOptions.mBlock);
+ const auto inline_ = ToWhereToScroll(aOptions.mInline);
+
+ ScrollFlags scrollFlags =
+ ScrollFlags::ScrollOverflowHidden | ScrollFlags::TriggeredByScript;
+ if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+ scrollFlags |= ScrollFlags::ScrollSmooth;
+ } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+ scrollFlags |= ScrollFlags::ScrollSmoothAuto;
+ }
+
+ // TODO: Propagate whether the axes are logical or not down (via scrollflags).
+ presShell->ScrollContentIntoView(
+ this, ScrollAxis(block, WhenToScroll::Always),
+ ScrollAxis(inline_, WhenToScroll::Always), scrollFlags);
+}
+
+void Element::Scroll(const CSSIntPoint& aScroll,
+ const ScrollOptions& aOptions) {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollToCSSPixels(aScroll, scrollMode);
+ }
+}
+
+void Element::Scroll(double aXScroll, double aYScroll) {
+ // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
+ auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
+ mozilla::ToZeroIfNonfinite(aYScroll));
+
+ Scroll(scrollPos, ScrollOptions());
+}
+
+void Element::Scroll(const ScrollToOptions& aOptions) {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
+ if (aOptions.mLeft.WasPassed()) {
+ scrollPos.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+ }
+ if (aOptions.mTop.WasPassed()) {
+ scrollPos.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+ }
+ Scroll(scrollPos, aOptions);
+ }
+}
+
+void Element::ScrollTo(double aXScroll, double aYScroll) {
+ Scroll(aXScroll, aYScroll);
+}
+
+void Element::ScrollTo(const ScrollToOptions& aOptions) { Scroll(aOptions); }
+
+void Element::ScrollBy(double aXScrollDif, double aYScrollDif) {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ ScrollToOptions options;
+ options.mLeft.Construct(aXScrollDif);
+ options.mTop.Construct(aYScrollDif);
+ ScrollBy(options);
+ }
+}
+
+void Element::ScrollBy(const ScrollToOptions& aOptions) {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ CSSIntPoint scrollDelta;
+ if (aOptions.mLeft.WasPassed()) {
+ scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+ }
+ if (aOptions.mTop.WasPassed()) {
+ scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+ }
+
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollByCSSPixels(scrollDelta, scrollMode);
+ }
+}
+
+int32_t Element::ScrollTop() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ? sf->GetScrollPositionCSSPixels().y.value : 0;
+}
+
+void Element::SetScrollTop(int32_t aScrollTop) {
+ // When aScrollTop is 0, we don't need to flush layout to scroll to that
+ // point; we know 0 is always in range. At least we think so... But we do
+ // need to flush frames so we ensure we find the right scrollable frame if
+ // there is one.
+ //
+ // If aScrollTop is nonzero, we need to flush layout because we need to figure
+ // out what our real scrollTopMax is.
+ FlushType flushType = aScrollTop == 0 ? FlushType::Frames : FlushType::Layout;
+ nsIScrollableFrame* sf = GetScrollFrame(nullptr, flushType);
+ if (sf) {
+ ScrollMode scrollMode =
+ sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
+
+ sf->ScrollToCSSPixels(
+ CSSIntPoint(sf->GetScrollPositionCSSPixels().x, aScrollTop),
+ scrollMode);
+ }
+}
+
+int32_t Element::ScrollLeft() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ? sf->GetScrollPositionCSSPixels().x.value : 0;
+}
+
+void Element::SetScrollLeft(int32_t aScrollLeft) {
+ // We can't assume things here based on the value of aScrollLeft, because
+ // depending on our direction and layout 0 may or may not be in our scroll
+ // range. So we need to flush layout no matter what.
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ ScrollMode scrollMode =
+ sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
+
+ sf->ScrollToCSSPixels(
+ CSSIntPoint(aScrollLeft, sf->GetScrollPositionCSSPixels().y),
+ scrollMode);
+ }
+}
+
+void Element::MozScrollSnap() {
+ nsIScrollableFrame* sf = GetScrollFrame(nullptr, FlushType::None);
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
+int32_t Element::ScrollTopMin() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return 0;
+ }
+ return CSSPixel::FromAppUnits(sf->GetScrollRange().y).Rounded();
+}
+
+int32_t Element::ScrollTopMax() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return 0;
+ }
+ return CSSPixel::FromAppUnits(sf->GetScrollRange().YMost()).Rounded();
+}
+
+int32_t Element::ScrollLeftMin() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return 0;
+ }
+ return CSSPixel::FromAppUnits(sf->GetScrollRange().x).Rounded();
+}
+
+int32_t Element::ScrollLeftMax() {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return 0;
+ }
+ return CSSPixel::FromAppUnits(sf->GetScrollRange().XMost()).Rounded();
+}
+
+static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame) {
+ if (!aFrame || aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return nsSize(0, 0);
+ }
+
+ nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
+ OverflowAreas overflowAreas(paddingRect, paddingRect);
+ // Add the scrollable overflow areas of children (if any) to the paddingRect.
+ // It's important to start with the paddingRect, otherwise if there are no
+ // children the overflow rect will be 0,0,0,0 which will force the point 0,0
+ // to be included in the final rect.
+ nsLayoutUtils::UnionChildOverflow(aFrame, overflowAreas);
+ // Make sure that an empty padding-rect's edges are included, by adding
+ // the padding-rect in again with UnionEdges.
+ nsRect overflowRect =
+ overflowAreas.ScrollableOverflow().UnionEdges(paddingRect);
+ return nsLayoutUtils::GetScrolledRect(aFrame, overflowRect,
+ paddingRect.Size(),
+ aFrame->StyleVisibility()->mDirection)
+ .Size();
+}
+
+int32_t Element::ScrollHeight() {
+ nsIFrame* frame;
+ nsIScrollableFrame* sf = GetScrollFrame(&frame);
+ nscoord height;
+ if (sf) {
+ height = sf->GetScrollRange().Height() + sf->GetScrollPortRect().Height();
+ } else {
+ height = GetScrollRectSizeForOverflowVisibleFrame(frame).height;
+ }
+
+ return nsPresContext::AppUnitsToIntCSSPixels(height);
+}
+
+int32_t Element::ScrollWidth() {
+ nsIFrame* frame;
+ nsIScrollableFrame* sf = GetScrollFrame(&frame);
+ nscoord width;
+ if (sf) {
+ width = sf->GetScrollRange().Width() + sf->GetScrollPortRect().Width();
+ } else {
+ width = GetScrollRectSizeForOverflowVisibleFrame(frame).width;
+ }
+
+ return nsPresContext::AppUnitsToIntCSSPixels(width);
+}
+
+nsRect Element::GetClientAreaRect() {
+ Document* doc = OwnerDoc();
+ nsPresContext* presContext = doc->GetPresContext();
+
+ // We can avoid a layout flush if this is the scrolling element of the
+ // document, we have overlay scrollbars, and we aren't embedded in another
+ // document
+ if (presContext && presContext->UseOverlayScrollbars() &&
+ !doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
+ doc->IsScrollingElement(this)) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ // Ensure up to date dimensions, but don't reflow
+ if (RefPtr<nsViewManager> viewManager = presShell->GetViewManager()) {
+ viewManager->FlushDelayedResize();
+ }
+ return nsRect(nsPoint(), presContext->GetVisibleArea().Size());
+ }
+ }
+
+ nsIFrame* frame;
+ if (nsIScrollableFrame* sf = GetScrollFrame(&frame)) {
+ nsRect scrollPort = sf->GetScrollPortRect();
+
+ if (!sf->IsRootScrollFrameOfDocument()) {
+ MOZ_ASSERT(frame);
+ nsIFrame* scrollableAsFrame = do_QueryFrame(sf);
+ // We want the offset to be relative to `frame`, not `sf`... Except for
+ // the root scroll frame, which is an ancestor of frame rather than a
+ // descendant and thus this wouldn't particularly make sense.
+ if (frame != scrollableAsFrame) {
+ scrollPort.MoveBy(scrollableAsFrame->GetOffsetTo(frame));
+ }
+ }
+
+ // The scroll port value might be expanded to the minimum scale size, we
+ // should limit the size to the ICB in such cases.
+ scrollPort.SizeTo(sf->GetLayoutSize());
+ return scrollPort;
+ }
+
+ if (frame &&
+ // The display check is OK even though we're not looking at the style
+ // frame, because the style frame only differs from "frame" for tables,
+ // and table wrappers have the same display as the table itself.
+ (!frame->StyleDisplay()->IsInlineFlow() ||
+ frame->IsFrameOfType(nsIFrame::eReplaced))) {
+ // Special case code to make client area work even when there isn't
+ // a scroll view, see bug 180552, bug 227567.
+ return frame->GetPaddingRect() - frame->GetPositionIgnoringScrolling();
+ }
+
+ // SVG nodes reach here and just return 0
+ return nsRect(0, 0, 0, 0);
+}
+
+int32_t Element::ScreenX() {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ return frame ? frame->GetScreenRect().x : 0;
+}
+
+int32_t Element::ScreenY() {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ return frame ? frame->GetScreenRect().y : 0;
+}
+
+already_AddRefed<nsIScreen> Element::GetScreen() {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ if (!frame) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenMgr) {
+ return nullptr;
+ }
+ nsPresContext* pc = frame->PresContext();
+ const CSSIntRect rect = frame->GetScreenRect();
+ DesktopRect desktopRect = rect * pc->CSSToDevPixelScale() /
+ pc->DeviceContext()->GetDesktopToDeviceScale();
+ return screenMgr->ScreenForRect(DesktopIntRect::Round(desktopRect));
+}
+
+already_AddRefed<DOMRect> Element::GetBoundingClientRect() {
+ RefPtr<DOMRect> rect = new DOMRect(ToSupports(OwnerDoc()));
+
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ if (!frame) {
+ // display:none, perhaps? Return the empty rect
+ return rect.forget();
+ }
+
+ rect->SetLayoutRect(frame->GetBoundingClientRect());
+ return rect.forget();
+}
+
+already_AddRefed<DOMRectList> Element::GetClientRects() {
+ RefPtr<DOMRectList> rectList = new DOMRectList(this);
+
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ if (!frame) {
+ // display:none, perhaps? Return an empty list
+ return rectList.forget();
+ }
+
+ nsLayoutUtils::RectListBuilder builder(rectList);
+ nsLayoutUtils::GetAllInFlowRects(
+ frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), &builder,
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ return rectList.forget();
+}
+
+//----------------------------------------------------------------------
+
+void Element::AddToIdTable(nsAtom* aId) {
+ NS_ASSERTION(HasID(), "Node doesn't have an ID?");
+ if (IsInShadowTree()) {
+ ShadowRoot* containingShadow = GetContainingShadow();
+ containingShadow->AddToIdTable(this, aId);
+ } else {
+ Document* doc = GetUncomposedDoc();
+ if (doc && !IsInNativeAnonymousSubtree()) {
+ doc->AddToIdTable(this, aId);
+ }
+ }
+}
+
+void Element::RemoveFromIdTable() {
+ if (!HasID()) {
+ return;
+ }
+
+ nsAtom* id = DoGetID();
+ if (IsInShadowTree()) {
+ ShadowRoot* containingShadow = GetContainingShadow();
+ // Check for containingShadow because it may have
+ // been deleted during unlinking.
+ if (containingShadow) {
+ containingShadow->RemoveFromIdTable(this, id);
+ }
+ } else {
+ Document* doc = GetUncomposedDoc();
+ if (doc && !IsInNativeAnonymousSubtree()) {
+ doc->RemoveFromIdTable(this, id);
+ }
+ }
+}
+
+void Element::SetSlot(const nsAString& aName, ErrorResult& aError) {
+ aError = SetAttr(kNameSpaceID_None, nsGkAtoms::slot, aName, true);
+}
+
+void Element::GetSlot(nsAString& aName) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::slot, aName);
+}
+
+// https://dom.spec.whatwg.org/#dom-element-shadowroot
+ShadowRoot* Element::GetShadowRootByMode() const {
+ /**
+ * 1. Let shadow be context object's shadow root.
+ * 2. If shadow is null or its mode is "closed", then return null.
+ */
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (!shadowRoot || shadowRoot->IsClosed()) {
+ return nullptr;
+ }
+
+ /**
+ * 3. Return shadow.
+ */
+ return shadowRoot;
+}
+
+bool Element::CanAttachShadowDOM() const {
+ /**
+ * If context object's namespace is not the HTML namespace,
+ * return false.
+ *
+ * Deviate from the spec here to allow shadow dom attachement to
+ * XUL elements.
+ */
+ if (!IsHTMLElement() &&
+ !(IsXULElement() &&
+ nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal()))) {
+ return false;
+ }
+
+ /**
+ * If context object's local name is not
+ * a valid custom element name, "article", "aside", "blockquote",
+ * "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6",
+ * "header", "main" "nav", "p", "section", or "span",
+ * return false.
+ */
+ nsAtom* nameAtom = NodeInfo()->NameAtom();
+ uint32_t namespaceID = NodeInfo()->NamespaceID();
+ if (!(nsContentUtils::IsCustomElementName(nameAtom, namespaceID) ||
+ nameAtom == nsGkAtoms::article || nameAtom == nsGkAtoms::aside ||
+ nameAtom == nsGkAtoms::blockquote || nameAtom == nsGkAtoms::body ||
+ nameAtom == nsGkAtoms::div || nameAtom == nsGkAtoms::footer ||
+ nameAtom == nsGkAtoms::h1 || nameAtom == nsGkAtoms::h2 ||
+ nameAtom == nsGkAtoms::h3 || nameAtom == nsGkAtoms::h4 ||
+ nameAtom == nsGkAtoms::h5 || nameAtom == nsGkAtoms::h6 ||
+ nameAtom == nsGkAtoms::header || nameAtom == nsGkAtoms::main ||
+ nameAtom == nsGkAtoms::nav || nameAtom == nsGkAtoms::p ||
+ nameAtom == nsGkAtoms::section || nameAtom == nsGkAtoms::span)) {
+ return false;
+ }
+
+ /**
+ * 3. If context object’s local name is a valid custom element name, or
+ * context object’s is value is not null, then:
+ * If definition is not null and definition’s disable shadow is true, then
+ * return false.
+ */
+ // It will always have CustomElementData when the element is a valid custom
+ // element or has is value.
+ if (CustomElementData* ceData = GetCustomElementData()) {
+ CustomElementDefinition* definition = ceData->GetCustomElementDefinition();
+ // If the definition is null, the element possible hasn't yet upgraded.
+ // Fallback to use LookupCustomElementDefinition to find its definition.
+ if (!definition) {
+ definition = nsContentUtils::LookupCustomElementDefinition(
+ NodeInfo()->GetDocument(), nameAtom, namespaceID,
+ ceData->GetCustomElementType());
+ }
+
+ if (definition && definition->mDisableShadow) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// https://dom.spec.whatwg.org/commit-snapshots/1eadf0a4a271acc92013d1c0de8c730ac96204f9/#dom-element-attachshadow
+already_AddRefed<ShadowRoot> Element::AttachShadow(const ShadowRootInit& aInit,
+ ErrorResult& aError) {
+ /**
+ * Step 1, 2, and 3.
+ */
+ if (!CanAttachShadowDOM()) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ /**
+ * 4. If this is a shadow host, then throw a "NotSupportedError" DOMException.
+ */
+ if (GetShadowRoot()) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (StaticPrefs::dom_webcomponents_shadowdom_report_usage()) {
+ OwnerDoc()->ReportShadowDOMUsage();
+ }
+
+ return AttachShadowWithoutNameChecks(aInit.mMode,
+ DelegatesFocus(aInit.mDelegatesFocus),
+ aInit.mSlotAssignment);
+}
+
+already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
+ ShadowRootMode aMode, DelegatesFocus aDelegatesFocus,
+ SlotAssignmentMode aSlotAssignment) {
+ nsAutoScriptBlocker scriptBlocker;
+
+ auto* nim = mNodeInfo->NodeInfoManager();
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+ nim->GetNodeInfo(nsGkAtoms::documentFragmentNodeName, nullptr,
+ kNameSpaceID_None, DOCUMENT_FRAGMENT_NODE);
+
+ // If there are no children, the flat tree is not changing due to the presence
+ // of the shadow root, so we don't need to invalidate style / layout.
+ //
+ // This is a minor optimization, but also works around nasty stuff like
+ // bug 1397876.
+ if (Document* doc = GetComposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->ShadowRootWillBeAttached(*this);
+ }
+ }
+
+ /**
+ * 5. Let shadow be a new shadow root whose node document is
+ * context object's node document, host is context object,
+ * and mode is init's mode.
+ */
+ RefPtr<ShadowRoot> shadowRoot = new (nim) ShadowRoot(
+ this, aMode, aDelegatesFocus, aSlotAssignment, nodeInfo.forget());
+
+ if (NodeOrAncestorHasDirAuto()) {
+ shadowRoot->SetAncestorHasDirAuto();
+ }
+
+ /**
+ * 7. If this’s custom element state is "precustomized" or "custom", then set
+ * shadow’s available to element internals to true.
+ */
+ CustomElementData* ceData = GetCustomElementData();
+ if (ceData && (ceData->mState == CustomElementData::State::ePrecustomized ||
+ ceData->mState == CustomElementData::State::eCustom)) {
+ shadowRoot->SetAvailableToElementInternals();
+ }
+
+ /**
+ * 9. Set context object's shadow root to shadow.
+ */
+ SetShadowRoot(shadowRoot);
+
+ // Dispatch a "shadowrootattached" event for devtools if needed.
+ if (MOZ_UNLIKELY(
+ nim->GetDocument()->DevToolsAnonymousAndShadowEventsEnabled())) {
+ AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+ this, u"shadowrootattached"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes, Composed::eYes);
+ dispatcher->PostDOMEvent();
+ }
+
+ /**
+ * 10. Return shadow.
+ */
+ return shadowRoot.forget();
+}
+
+void Element::AttachAndSetUAShadowRoot(NotifyUAWidgetSetup aNotify,
+ DelegatesFocus aDelegatesFocus) {
+ MOZ_DIAGNOSTIC_ASSERT(!CanAttachShadowDOM(),
+ "Cannot be used to attach UI shadow DOM");
+ if (OwnerDoc()->IsStaticDocument()) {
+ return;
+ }
+
+ if (!GetShadowRoot()) {
+ RefPtr<ShadowRoot> shadowRoot =
+ AttachShadowWithoutNameChecks(ShadowRootMode::Closed, aDelegatesFocus);
+ shadowRoot->SetIsUAWidget();
+ }
+
+ MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
+ if (aNotify == NotifyUAWidgetSetup::Yes) {
+ NotifyUAWidgetSetupOrChange();
+ }
+}
+
+void Element::NotifyUAWidgetSetupOrChange() {
+ MOZ_ASSERT(IsInComposedDoc());
+ Document* doc = OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ return;
+ }
+
+ // Schedule a runnable, ensure the event dispatches before
+ // returning to content script.
+ // This event cause UA Widget to construct or cause onchange callback
+ // of existing UA Widget to run; dispatching this event twice should not cause
+ // UA Widget to re-init.
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "Element::NotifyUAWidgetSetupOrChange::UAWidgetSetupOrChange",
+ [self = RefPtr<Element>(this), doc = RefPtr<Document>(doc)]() {
+ nsContentUtils::DispatchChromeEvent(doc, self,
+ u"UAWidgetSetupOrChange"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+ }));
+}
+
+void Element::NotifyUAWidgetTeardown(UnattachShadowRoot aUnattachShadowRoot) {
+ MOZ_ASSERT(IsInComposedDoc());
+ if (!GetShadowRoot()) {
+ return;
+ }
+ MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
+ if (aUnattachShadowRoot == UnattachShadowRoot::Yes) {
+ UnattachShadow();
+ }
+
+ Document* doc = OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ return;
+ }
+
+ // The runnable will dispatch an event to tear down UA Widget.
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "Element::NotifyUAWidgetTeardownAndUnattachShadow::UAWidgetTeardown",
+ [self = RefPtr<Element>(this), doc = RefPtr<Document>(doc)]() {
+ // Bail out if the element is being collected by CC
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ doc->GetScriptHandlingObject(hasHadScriptObject);
+ if (!scriptObject && hasHadScriptObject) {
+ return;
+ }
+
+ Unused << nsContentUtils::DispatchChromeEvent(
+ doc, self, u"UAWidgetTeardown"_ns, CanBubble::eYes,
+ Cancelable::eNo);
+ }));
+}
+
+void Element::UnattachShadow() {
+ RefPtr<ShadowRoot> shadowRoot = GetShadowRoot();
+ if (!shadowRoot) {
+ return;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (RefPtr<Document> doc = GetComposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->DestroyFramesForAndRestyle(this);
+#ifdef ACCESSIBILITY
+ // We need to notify the accessibility service here explicitly because,
+ // even though we're going to reconstruct the _host_, the shadow root and
+ // its children are never really going to come back. We could plumb that
+ // further down to DestroyFramesForAndRestyle and add a new flag to
+ // nsCSSFrameConstructor::ContentRemoved or such, but this seems simpler
+ // instead.
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRemoved(presShell, shadowRoot);
+ }
+#endif
+ }
+ // ContentRemoved doesn't really run script in the cases we care about (it
+ // can only call ClearFocus when removing iframes and so on...)
+ [&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ fm->ContentRemoved(doc, shadowRoot);
+ }
+ }();
+ }
+ MOZ_ASSERT(!GetPrimaryFrame());
+
+ shadowRoot->Unattach();
+ SetShadowRoot(nullptr);
+}
+
+void Element::GetAttribute(const nsAString& aName, DOMString& aReturn) {
+ const nsAttrValue* val = mAttrs.GetAttr(
+ aName,
+ IsHTMLElement() && IsInHTMLDocument() ? eIgnoreCase : eCaseMatters);
+ if (val) {
+ val->ToString(aReturn);
+ } else {
+ if (IsXULElement()) {
+ // XXX should be SetDOMStringToNull(aReturn);
+ // See bug 232598
+ // aReturn is already empty
+ } else {
+ aReturn.SetNull();
+ }
+ }
+}
+
+bool Element::ToggleAttribute(const nsAString& aName,
+ const Optional<bool>& aForce,
+ nsIPrincipal* aTriggeringPrincipal,
+ ErrorResult& aError) {
+ aError = nsContentUtils::CheckQName(aName, false);
+ if (aError.Failed()) {
+ return false;
+ }
+
+ nsAutoString nameToUse;
+ const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
+ if (!name) {
+ if (aForce.WasPassed() && !aForce.Value()) {
+ return false;
+ }
+ RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
+ if (!nameAtom) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ aError = SetAttr(kNameSpaceID_None, nameAtom, u""_ns, aTriggeringPrincipal,
+ true);
+ return true;
+ }
+ if (aForce.WasPassed() && aForce.Value()) {
+ return true;
+ }
+ // Hold a strong reference here so that the atom or nodeinfo doesn't go
+ // away during UnsetAttr. If it did UnsetAttr would be left with a
+ // dangling pointer as argument without knowing it.
+ nsAttrName tmp(*name);
+
+ aError = UnsetAttr(name->NamespaceID(), name->LocalName(), true);
+ return false;
+}
+
+void Element::SetAttribute(const nsAString& aName, const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal,
+ ErrorResult& aError) {
+ aError = nsContentUtils::CheckQName(aName, false);
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsAutoString nameToUse;
+ const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
+ if (!name) {
+ RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
+ if (!nameAtom) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aError = SetAttr(kNameSpaceID_None, nameAtom, aValue, aTriggeringPrincipal,
+ true);
+ return;
+ }
+
+ aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
+ aValue, aTriggeringPrincipal, true);
+}
+
+void Element::RemoveAttribute(const nsAString& aName, ErrorResult& aError) {
+ const nsAttrName* name = InternalGetAttrNameFromQName(aName);
+
+ if (!name) {
+ // If there is no canonical nsAttrName for this attribute name, then the
+ // attribute does not exist and we can't get its namespace ID and
+ // local name below, so we return early.
+ return;
+ }
+
+ // Hold a strong reference here so that the atom or nodeinfo doesn't go
+ // away during UnsetAttr. If it did UnsetAttr would be left with a
+ // dangling pointer as argument without knowing it.
+ nsAttrName tmp(*name);
+
+ aError = UnsetAttr(name->NamespaceID(), name->LocalName(), true);
+}
+
+Attr* Element::GetAttributeNode(const nsAString& aName) {
+ return Attributes()->GetNamedItem(aName);
+}
+
+already_AddRefed<Attr> Element::SetAttributeNode(Attr& aNewAttr,
+ ErrorResult& aError) {
+ return Attributes()->SetNamedItemNS(aNewAttr, aError);
+}
+
+already_AddRefed<Attr> Element::RemoveAttributeNode(Attr& aAttribute,
+ ErrorResult& aError) {
+ Element* elem = aAttribute.GetElement();
+ if (elem != this) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ nsAutoString nameSpaceURI;
+ aAttribute.NodeInfo()->GetNamespaceURI(nameSpaceURI);
+ return Attributes()->RemoveNamedItemNS(
+ nameSpaceURI, aAttribute.NodeInfo()->LocalName(), aError);
+}
+
+void Element::GetAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName, nsAString& aReturn) {
+ int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
+
+ if (nsid == kNameSpaceID_Unknown) {
+ // Unknown namespace means no attribute.
+ SetDOMStringToNull(aReturn);
+ return;
+ }
+
+ RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
+ bool hasAttr = GetAttr(nsid, name, aReturn);
+ if (!hasAttr) {
+ SetDOMStringToNull(aReturn);
+ }
+}
+
+void Element::SetAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal,
+ ErrorResult& aError) {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ aError = nsContentUtils::GetNodeInfoFromQName(
+ aNamespaceURI, aQualifiedName, mNodeInfo->NodeInfoManager(),
+ ATTRIBUTE_NODE, getter_AddRefs(ni));
+ if (aError.Failed()) {
+ return;
+ }
+
+ aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
+ aValue, aTriggeringPrincipal, true);
+}
+
+already_AddRefed<nsIPrincipal> Element::CreateDevtoolsPrincipal() {
+ // Return an ExpandedPrincipal that subsumes this Element's Principal,
+ // and expands this Element's CSP to allow the actions that devtools
+ // needs to perform.
+ AutoTArray<nsCOMPtr<nsIPrincipal>, 1> allowList = {NodePrincipal()};
+ RefPtr<ExpandedPrincipal> dtPrincipal = ExpandedPrincipal::Create(
+ allowList, NodePrincipal()->OriginAttributesRef());
+
+ if (nsIContentSecurityPolicy* csp = GetCsp()) {
+ RefPtr<nsCSPContext> dtCsp = new nsCSPContext();
+ dtCsp->InitFromOther(static_cast<nsCSPContext*>(csp));
+ dtCsp->SetSkipAllowInlineStyleCheck(true);
+
+ dtPrincipal->SetCsp(dtCsp);
+ }
+
+ return dtPrincipal.forget();
+}
+
+void Element::SetAttributeDevtools(const nsAString& aName,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ // Run this through SetAttribute with a devtools-ready principal.
+ RefPtr<nsIPrincipal> dtPrincipal = CreateDevtoolsPrincipal();
+ SetAttribute(aName, aValue, dtPrincipal, aError);
+}
+
+void Element::SetAttributeDevtoolsNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ // Run this through SetAttributeNS with a devtools-ready principal.
+ RefPtr<nsIPrincipal> dtPrincipal = CreateDevtoolsPrincipal();
+ SetAttributeNS(aNamespaceURI, aLocalName, aValue, dtPrincipal, aError);
+}
+
+void Element::RemoveAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ ErrorResult& aError) {
+ RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
+ int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
+
+ if (nsid == kNameSpaceID_Unknown) {
+ // If the namespace ID is unknown, it means there can't possibly be an
+ // existing attribute. We would need a known namespace ID to pass into
+ // UnsetAttr, so we return early if we don't have one.
+ return;
+ }
+
+ aError = UnsetAttr(nsid, name, true);
+}
+
+Attr* Element::GetAttributeNodeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) {
+ return GetAttributeNodeNSInternal(aNamespaceURI, aLocalName);
+}
+
+Attr* Element::GetAttributeNodeNSInternal(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) {
+ return Attributes()->GetNamedItemNS(aNamespaceURI, aLocalName);
+}
+
+already_AddRefed<Attr> Element::SetAttributeNodeNS(Attr& aNewAttr,
+ ErrorResult& aError) {
+ return Attributes()->SetNamedItemNS(aNewAttr, aError);
+}
+
+already_AddRefed<nsIHTMLCollection> Element::GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ ErrorResult& aError) {
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ aError = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!");
+
+ return NS_GetContentList(this, nameSpaceId, aLocalName);
+}
+
+bool Element::HasAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) const {
+ int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespaceURI, nsContentUtils::IsChromeDoc(OwnerDoc()));
+
+ if (nsid == kNameSpaceID_Unknown) {
+ // Unknown namespace means no attr...
+ return false;
+ }
+
+ RefPtr<nsAtom> name = NS_AtomizeMainThread(aLocalName);
+ return HasAttr(nsid, name);
+}
+
+already_AddRefed<nsIHTMLCollection> Element::GetElementsByClassName(
+ const nsAString& aClassNames) {
+ return nsContentUtils::GetElementsByClassName(this, aClassNames);
+}
+
+Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const {
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ if (slots) {
+ nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElements.Get(aAttr);
+ if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) {
+ // If reflectedTarget's explicitly set attr-element |attrEl| is
+ // a descendant of any of element's shadow-including ancestors, then
+ // return |atrEl|.
+ nsINode* root = SubtreeRoot();
+ nsINode* attrSubtreeRoot = attrEl->SubtreeRoot();
+ do {
+ if (root == attrSubtreeRoot) {
+ return attrEl;
+ }
+ auto* shadow = ShadowRoot::FromNode(root);
+ if (!shadow || !shadow->GetHost()) {
+ break;
+ }
+ root = shadow->GetHost()->SubtreeRoot();
+ } while (true);
+ return nullptr;
+ }
+ }
+
+ const nsAttrValue* value = GetParsedAttr(aAttr);
+ if (!value) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(value->Type() == nsAttrValue::eAtom,
+ "Attribute used for attr associated element must be parsed");
+
+ nsAtom* valueAtom = value->GetAtomValue();
+ if (auto* docOrShadowRoot = GetContainingDocumentOrShadowRoot()) {
+ return docOrShadowRoot->GetElementById(valueAtom);
+ }
+
+ nsINode* root = SubtreeRoot();
+ for (auto* node = root; node; node = node->GetNextNode(root)) {
+ if (node->HasID() && node->AsContent()->GetID() == valueAtom) {
+ return node->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+void Element::ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement) {
+ if (aElement) {
+ nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+ slots->mExplicitlySetAttrElements.InsertOrUpdate(
+ aAttr, do_GetWeakReference(aElement));
+ SetAttr(aAttr, EmptyString(), IgnoreErrors());
+ return;
+ }
+
+ if (auto* slots = GetExistingExtendedDOMSlots()) {
+ slots->mExplicitlySetAttrElements.Remove(aAttr);
+ UnsetAttr(aAttr, IgnoreErrors());
+ }
+}
+
+void Element::GetElementsWithGrid(nsTArray<RefPtr<Element>>& aElements) {
+ nsINode* cur = this;
+ while (cur) {
+ if (cur->IsElement()) {
+ Element* elem = cur->AsElement();
+
+ if (elem->GetPrimaryFrame()) {
+ // See if this has a GridContainerFrame. Use the same method that
+ // nsGridContainerFrame uses, which deals with some edge cases.
+ if (nsGridContainerFrame::GetGridContainerFrame(
+ elem->GetPrimaryFrame())) {
+ aElements.AppendElement(elem);
+ }
+
+ // This element has a frame, so allow the traversal to go through
+ // the children.
+ cur = cur->GetNextNode(this);
+ continue;
+ }
+ }
+
+ // Either this isn't an element, or it has no frame. Continue with the
+ // traversal but ignore all the children.
+ cur = cur->GetNextNonChildNode(this);
+ }
+}
+
+bool Element::HasVisibleScrollbars() {
+ nsIScrollableFrame* scrollFrame = GetScrollFrame();
+ return scrollFrame && (!scrollFrame->GetScrollbarVisibility().isEmpty());
+}
+
+nsresult Element::BindToTree(BindContext& aContext, nsINode& aParent) {
+ MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
+ "Must have content or document parent!");
+ MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
+ "Must have the same owner document");
+ MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
+ MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
+ MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
+ // Note that as we recurse into the kids, they'll have a non-null parent. So
+ // only assert if our parent is _changing_ while we have a parent.
+ MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
+ "Already have a parent. Unbind first!");
+
+ const bool hadParent = !!GetParentNode();
+
+ if (aParent.IsInNativeAnonymousSubtree()) {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+ if (IsRootOfNativeAnonymousSubtree()) {
+ aParent.SetMayHaveAnonymousChildren();
+ } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
+ SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
+ }
+ if (aParent.HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR)) {
+ SetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
+ }
+ aParent.SetFlags(NODE_MAY_HAVE_ELEMENT_CHILDREN);
+
+ // Now set the parent.
+ mParent = &aParent;
+ if (!hadParent && aParent.IsContent()) {
+ SetParentIsContent(true);
+ NS_ADDREF(mParent);
+ }
+ MOZ_ASSERT(!!GetParent() == aParent.IsContent());
+
+ MOZ_ASSERT(!HasAnyOfFlags(Element::kAllServoDescendantBits));
+
+ // Finally, set the document
+ if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
+ // We no longer need to track the subtree pointer (and in fact we'll assert
+ // if we do this any later).
+ ClearSubtreeRootPointer();
+ SetIsConnected(aParent.IsInComposedDoc());
+
+ if (aParent.IsInUncomposedDoc()) {
+ SetIsInDocument();
+ } else {
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ MOZ_ASSERT(aParent.IsContent() &&
+ aParent.AsContent()->GetContainingShadow());
+ ExtendedDOMSlots()->mContainingShadow =
+ aParent.AsContent()->GetContainingShadow();
+ }
+ // Clear the lazy frame construction bits.
+ UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ } else {
+ // If we're not in the doc and not in a shadow tree,
+ // update our subtree pointer.
+ SetSubtreeRootPointer(aParent.SubtreeRoot());
+ }
+
+ if (IsInComposedDoc()) {
+ // Connected callback must be enqueued whenever a custom element becomes
+ // connected.
+ if (CustomElementData* data = GetCustomElementData()) {
+ if (data->mState == CustomElementData::State::eCustom) {
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eConnected, this, {});
+ } else {
+ // Step 7.7.2.2 https://dom.spec.whatwg.org/#concept-node-insert
+ nsContentUtils::TryToUpgradeElement(this);
+ }
+ }
+ }
+
+ // This has to be here, rather than in nsGenericHTMLElement::BindToTree,
+ // because it has to happen after updating the parent pointer, but before
+ // recursively binding the kids.
+ if (IsHTMLElement()) {
+ SetDirOnBind(this, nsIContent::FromNode(aParent));
+ }
+
+ UpdateEditableState(false);
+
+ // Call BindToTree on shadow root children.
+ nsresult rv;
+ if (ShadowRoot* shadowRoot = GetShadowRoot()) {
+ rv = shadowRoot->Bind();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now recurse into our kids. Ensure this happens after binding the shadow
+ // root so that directionality of slots is updated.
+ {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(aContext, *this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+
+ // Ensure we only run this once, in the case we move the ShadowRoot around.
+ if (aContext.SubtreeRootChanges()) {
+ if (HasPartAttribute()) {
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ shadow->PartAdded(*this);
+ }
+ }
+ if (HasID()) {
+ AddToIdTable(DoGetID());
+ }
+ HandleShadowDOMRelatedInsertionSteps(hadParent);
+ }
+
+ if (MayHaveStyle()) {
+ // If MayHaveStyle() is true, we must be an nsStyledElement.
+ static_cast<nsStyledElement*>(this)->ReparseStyleAttribute(
+ /* aForceInDataDoc = */ false);
+ }
+
+ // XXXbz script execution during binding can trigger some of these
+ // postcondition asserts.... But we do want that, since things will
+ // generally be quite broken when that happens.
+ MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
+ MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
+ MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
+ MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
+ MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
+ MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
+ MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
+ MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
+ return NS_OK;
+}
+
+bool WillDetachFromShadowOnUnbind(const Element& aElement, bool aNullParent) {
+ // If our parent still is in a shadow tree by now, and we're not removing
+ // ourselves from it, then we're still going to be in a shadow tree after
+ // this.
+ return aElement.IsInShadowTree() &&
+ (aNullParent || !aElement.GetParent()->IsInShadowTree());
+}
+
+void Element::UnbindFromTree(bool aNullParent) {
+ HandleShadowDOMRelatedRemovalSteps(aNullParent);
+
+ if (HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) &&
+ !IsHTMLElement(nsGkAtoms::datalist)) {
+ if (aNullParent) {
+ UnsetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
+ } else {
+ nsIContent* parent = GetParent();
+ MOZ_ASSERT(parent);
+ if (!parent->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR)) {
+ UnsetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
+ }
+ }
+ }
+
+ const bool detachingFromShadow =
+ WillDetachFromShadowOnUnbind(*this, aNullParent);
+ // Make sure to only remove from the ID table if our subtree root is actually
+ // changing.
+ if (IsInUncomposedDoc() || detachingFromShadow) {
+ RemoveFromIdTable();
+ }
+
+ if (detachingFromShadow && HasPartAttribute()) {
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ shadow->PartRemoved(*this);
+ }
+ }
+
+ // Make sure to unbind this node before doing the kids
+ Document* document = GetComposedDoc();
+
+ if (HasPointerLock()) {
+ PointerLockManager::Unlock();
+ }
+ if (mState.HasState(ElementState::FULLSCREEN)) {
+ // The element being removed is an ancestor of the fullscreen element,
+ // exit fullscreen state.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ OwnerDoc(), nsContentUtils::eDOM_PROPERTIES,
+ "RemovedFullscreenElement");
+ // Fully exit fullscreen.
+ Document::ExitFullscreenInDocTree(OwnerDoc());
+ }
+
+ MOZ_ASSERT_IF(HasServoData(), document);
+ MOZ_ASSERT_IF(HasServoData(), IsInNativeAnonymousSubtree());
+ if (document) {
+ ClearServoData(document);
+ }
+
+ // Ensure that CSS transitions don't continue on an element at a
+ // different place in the tree (even if reinserted before next
+ // animation refresh).
+ //
+ // We need to delete the properties while we're still in document
+ // (if we were in document) so that they can look up the
+ // PendingAnimationTracker on the document and remove their animations,
+ // and so they can find their pres context for dispatching cancel events.
+ //
+ // FIXME(bug 522599): Need a test for this.
+ // FIXME(emilio): Why not clearing the effect set as well?
+ if (auto* data = GetAnimationData()) {
+ data->ClearAllAnimationCollections();
+ }
+
+ if (aNullParent) {
+ if (GetParent()) {
+ RefPtr<nsINode> p;
+ p.swap(mParent);
+ } else {
+ mParent = nullptr;
+ }
+ SetParentIsContent(false);
+ }
+
+#ifdef DEBUG
+ // If we can get access to the PresContext, then we sanity-check that
+ // we're not leaving behind a pointer to ourselves as the PresContext's
+ // cached provider of the viewport's scrollbar styles.
+ if (document) {
+ nsPresContext* presContext = document->GetPresContext();
+ if (presContext) {
+ MOZ_ASSERT(this != presContext->GetViewportScrollStylesOverrideElement(),
+ "Leaving behind a raw pointer to this element (as having "
+ "propagated scrollbar styles) - that's dangerous...");
+ }
+ }
+
+# ifdef ACCESSIBILITY
+ MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
+ "An accessible for this element still exists!");
+# endif
+#endif
+
+ ClearInDocument();
+ SetIsConnected(false);
+ if (HasElementCreatedFromPrototypeAndHasUnmodifiedL10n()) {
+ if (document) {
+ document->mL10nProtoElements.Remove(this);
+ }
+ ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n();
+ }
+
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // Begin keeping track of our subtree root.
+ SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+ }
+
+ if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ slots->mContainingShadow = nullptr;
+ }
+ }
+
+ if (document) {
+ // Disconnected must be enqueued whenever a connected custom element becomes
+ // disconnected.
+ CustomElementData* data = GetCustomElementData();
+ if (data) {
+ if (data->mState == CustomElementData::State::eCustom) {
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eDisconnected, this, {});
+ } else {
+ // Remove an unresolved custom element that is a candidate for upgrade
+ // when a custom element is disconnected.
+ nsContentUtils::UnregisterUnresolvedElement(this);
+ }
+ }
+
+ if (HasLastRememberedBSize() || HasLastRememberedISize()) {
+ // Need to remove the last remembered size at the next ResizeObserver
+ // opportunity, so observe the element. But if already observed, we still
+ // want the callback to be invoked even if the size was already 0x0, so
+ // unobserve it first.
+ document->UnobserveForLastRememberedSize(*this);
+ document->ObserveForLastRememberedSize(*this);
+ }
+ }
+
+ // This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree,
+ // because it has to happen after unsetting the parent pointer, but before
+ // recursively unbinding the kids.
+ if (IsHTMLElement()) {
+ ResetDir(this);
+ }
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ // Note that we pass false for aNullParent here, since we don't want
+ // the kids to forget us.
+ child->UnbindFromTree(false);
+ }
+
+ MutationObservers::NotifyParentChainChanged(this);
+
+ // Unbind children of shadow root.
+ if (ShadowRoot* shadowRoot = GetShadowRoot()) {
+ shadowRoot->Unbind();
+ }
+
+ MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits));
+ MOZ_ASSERT(!document || document->GetServoRestyleRoot() != this);
+}
+
+UniquePtr<SMILAttr> Element::GetAnimatedAttr(int32_t aNamespaceID,
+ nsAtom* aName) {
+ return nullptr;
+}
+
+nsDOMCSSAttributeDeclaration* Element::SMILOverrideStyle() {
+ Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+
+ if (!slots->mSMILOverrideStyle) {
+ slots->mSMILOverrideStyle = new nsDOMCSSAttributeDeclaration(this, true);
+ }
+
+ return slots->mSMILOverrideStyle;
+}
+
+DeclarationBlock* Element::GetSMILOverrideStyleDeclaration() {
+ Element::nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mSMILOverrideStyleDeclaration.get() : nullptr;
+}
+
+void Element::SetSMILOverrideStyleDeclaration(DeclarationBlock& aDeclaration) {
+ ExtendedDOMSlots()->mSMILOverrideStyleDeclaration = &aDeclaration;
+
+ // Only need to request a restyle if we're in a document. (We might not
+ // be in a document, if we're clearing animation effects on a target node
+ // that's been detached since the previous animation sample.)
+ if (Document* doc = GetComposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->RestyleForAnimation(this, RestyleHint::RESTYLE_SMIL);
+ }
+ }
+}
+
+bool Element::IsLabelable() const { return false; }
+
+bool Element::IsInteractiveHTMLContent() const { return false; }
+
+DeclarationBlock* Element::GetInlineStyleDeclaration() const {
+ if (!MayHaveStyle()) {
+ return nullptr;
+ }
+ const nsAttrValue* attrVal = mAttrs.GetAttr(nsGkAtoms::style);
+
+ if (attrVal && attrVal->Type() == nsAttrValue::eCSSDeclaration) {
+ return attrVal->GetCSSDeclarationValue();
+ }
+
+ return nullptr;
+}
+
+const nsMappedAttributes* Element::GetMappedAttributes() const {
+ return mAttrs.GetMapped();
+}
+
+void Element::InlineStyleDeclarationWillChange(MutationClosureData& aData) {
+ MOZ_ASSERT_UNREACHABLE("Element::InlineStyleDeclarationWillChange");
+}
+
+nsresult Element::SetInlineStyleDeclaration(DeclarationBlock& aDeclaration,
+ MutationClosureData& aData) {
+ MOZ_ASSERT_UNREACHABLE("Element::SetInlineStyleDeclaration");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+Element::IsAttributeMapped(const nsAtom* aAttribute) const { return false; }
+
+nsChangeHint Element::GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const {
+ return nsChangeHint(0);
+}
+
+bool Element::FindAttributeDependence(const nsAtom* aAttribute,
+ const MappedAttributeEntry* const aMaps[],
+ uint32_t aMapCount) {
+ for (uint32_t mapindex = 0; mapindex < aMapCount; ++mapindex) {
+ for (const MappedAttributeEntry* map = aMaps[mapindex]; map->attribute;
+ ++map) {
+ if (aAttribute == map->attribute) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+already_AddRefed<mozilla::dom::NodeInfo> Element::GetExistingAttrNameFromQName(
+ const nsAString& aStr) const {
+ const nsAttrName* name = InternalGetAttrNameFromQName(aStr);
+ if (!name) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ if (name->IsAtom()) {
+ nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(
+ name->Atom(), nullptr, kNameSpaceID_None, ATTRIBUTE_NODE);
+ } else {
+ nodeInfo = name->NodeInfo();
+ }
+
+ return nodeInfo.forget();
+}
+
+// static
+bool Element::ShouldBlur(nsIContent* aContent) {
+ // Determine if the current element is focused, if it is not focused
+ // then we should not try to blur
+ Document* document = aContent->GetComposedDoc();
+ if (!document) return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
+ if (!window) return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
+ nsIContent* contentToBlur = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(focusedFrame));
+
+ if (!contentToBlur) {
+ return false;
+ }
+
+ if (contentToBlur == aContent) {
+ return true;
+ }
+
+ ShadowRoot* root = aContent->GetShadowRoot();
+ if (root && root->DelegatesFocus() &&
+ contentToBlur->IsShadowIncludingInclusiveDescendantOf(root)) {
+ return true;
+ }
+ return false;
+}
+
+/* static */
+nsresult Element::DispatchEvent(nsPresContext* aPresContext,
+ WidgetEvent* aEvent, nsIContent* aTarget,
+ bool aFullDispatch, nsEventStatus* aStatus) {
+ MOZ_ASSERT(aTarget, "Must have target");
+ MOZ_ASSERT(aEvent, "Must have source event");
+ MOZ_ASSERT(aStatus, "Null out param?");
+
+ if (!aPresContext) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = aPresContext->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ if (aFullDispatch) {
+ return presShell->HandleEventWithTarget(aEvent, nullptr, aTarget, aStatus);
+ }
+
+ return presShell->HandleDOMEventWithTarget(aTarget, aEvent, aStatus);
+}
+
+/* static */
+nsresult Element::DispatchClickEvent(nsPresContext* aPresContext,
+ WidgetInputEvent* aSourceEvent,
+ nsIContent* aTarget, bool aFullDispatch,
+ const EventFlags* aExtraEventFlags,
+ nsEventStatus* aStatus) {
+ MOZ_ASSERT(aTarget, "Must have target");
+ MOZ_ASSERT(aSourceEvent, "Must have source event");
+ MOZ_ASSERT(aStatus, "Null out param?");
+
+ WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick,
+ aSourceEvent->mWidget, WidgetMouseEvent::eReal);
+ event.mRefPoint = aSourceEvent->mRefPoint;
+ uint32_t clickCount = 1;
+ float pressure = 0;
+ uint32_t pointerId = 0; // Use the default value here.
+ uint16_t inputSource = 0;
+ WidgetMouseEvent* sourceMouseEvent = aSourceEvent->AsMouseEvent();
+ if (sourceMouseEvent) {
+ clickCount = sourceMouseEvent->mClickCount;
+ pressure = sourceMouseEvent->mPressure;
+ pointerId = sourceMouseEvent->pointerId;
+ inputSource = sourceMouseEvent->mInputSource;
+ } else if (aSourceEvent->mClass == eKeyboardEventClass) {
+ event.mFlags.mIsPositionless = true;
+ inputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
+ }
+ event.mPressure = pressure;
+ event.mClickCount = clickCount;
+ event.pointerId = pointerId;
+ event.mInputSource = inputSource;
+ event.mModifiers = aSourceEvent->mModifiers;
+ if (aExtraEventFlags) {
+ // Be careful not to overwrite existing flags!
+ event.mFlags.Union(*aExtraEventFlags);
+ }
+
+ return DispatchEvent(aPresContext, &event, aTarget, aFullDispatch, aStatus);
+}
+
+//----------------------------------------------------------------------
+nsresult Element::LeaveLink(nsPresContext* aPresContext) {
+ if (!aPresContext || !aPresContext->Document()->LinkHandlingEnabled()) {
+ return NS_OK;
+ }
+ nsIDocShell* shell = aPresContext->Document()->GetDocShell();
+ if (!shell) {
+ return NS_OK;
+ }
+ return nsDocShell::Cast(shell)->OnLeaveLink();
+}
+
+void Element::SetEventHandler(nsAtom* aEventName, const nsAString& aValue,
+ bool aDefer) {
+ Document* ownerDoc = OwnerDoc();
+ if (ownerDoc->IsLoadedAsData()) {
+ // Make this a no-op rather than throwing an error to avoid
+ // the error causing problems setting the attribute.
+ return;
+ }
+
+ MOZ_ASSERT(aEventName, "Must have event name!");
+ bool defer = true;
+ EventListenerManager* manager =
+ GetEventListenerManagerForAttr(aEventName, &defer);
+ if (!manager) {
+ return;
+ }
+
+ defer = defer && aDefer; // only defer if everyone agrees...
+ manager->SetEventHandler(aEventName, aValue, defer,
+ !nsContentUtils::IsChromeDoc(ownerDoc), this);
+}
+
+//----------------------------------------------------------------------
+
+const nsAttrName* Element::InternalGetAttrNameFromQName(
+ const nsAString& aStr, nsAutoString* aNameToUse) const {
+ MOZ_ASSERT(!aNameToUse || aNameToUse->IsEmpty());
+ const nsAttrName* val = nullptr;
+ if (IsHTMLElement() && IsInHTMLDocument()) {
+ nsAutoString lower;
+ nsAutoString& outStr = aNameToUse ? *aNameToUse : lower;
+ nsContentUtils::ASCIIToLower(aStr, outStr);
+ val = mAttrs.GetExistingAttrNameFromQName(outStr);
+ if (val) {
+ outStr.Truncate();
+ }
+ } else {
+ val = mAttrs.GetExistingAttrNameFromQName(aStr);
+ if (!val && aNameToUse) {
+ *aNameToUse = aStr;
+ }
+ }
+
+ return val;
+}
+
+bool Element::MaybeCheckSameAttrVal(int32_t aNamespaceID, const nsAtom* aName,
+ const nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners,
+ bool* aOldValueSet) {
+ bool modification = false;
+ *aHasListeners =
+ aNotify && nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+ *aOldValueSet = false;
+
+ // If we have no listeners and aNotify is false, we are almost certainly
+ // coming from the content sink and will almost certainly have no previous
+ // value. Even if we do, setting the value is cheap when we have no
+ // listeners and don't plan to notify. The check for aNotify here is an
+ // optimization, the check for *aHasListeners is a correctness issue.
+ if (*aHasListeners || aNotify) {
+ BorrowedAttrInfo info(GetAttrInfo(aNamespaceID, aName));
+ if (info.mValue) {
+ // Check whether the old value is the same as the new one. Note that we
+ // only need to actually _get_ the old value if we have listeners or
+ // if the element is a custom element (because it may have an
+ // attribute changed callback).
+ if (*aHasListeners || GetCustomElementData()) {
+ // Need to store the old value.
+ //
+ // If the current attribute value contains a pointer to some other data
+ // structure that gets updated in the process of setting the attribute
+ // we'll no longer have the old value of the attribute. Therefore, we
+ // should serialize the attribute value now to keep a snapshot.
+ //
+ // We have to serialize the value anyway in order to create the
+ // mutation event so there's no cost in doing it now.
+ aOldValue.SetToSerialized(*info.mValue);
+ *aOldValueSet = true;
+ }
+ bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
+ if (valueMatches && aPrefix == info.mName->GetPrefix()) {
+ return true;
+ }
+ modification = true;
+ }
+ }
+ *aModType = modification
+ ? static_cast<uint8_t>(MutationEvent_Binding::MODIFICATION)
+ : static_cast<uint8_t>(MutationEvent_Binding::ADDITION);
+ return false;
+}
+
+bool Element::OnlyNotifySameValueSet(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners,
+ bool* aOldValueSet) {
+ if (!MaybeCheckSameAttrVal(aNamespaceID, aName, aPrefix, aValue, aNotify,
+ aOldValue, aModType, aHasListeners,
+ aOldValueSet)) {
+ return false;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ MutationObservers::NotifyAttributeSetToCurrentValue(this, aNamespaceID,
+ aName);
+ return true;
+}
+
+nsresult Element::SetSingleClassFromParser(nsAtom* aSingleClassName) {
+ // Keep this in sync with SetAttr and SetParsedAttr below.
+
+ nsAttrValue value(aSingleClassName);
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, false);
+
+ // In principle, BeforeSetAttr should be called here if a node type
+ // existed that wanted to do something special for class, but there
+ // is no such node type, so calling SetMayHaveClass() directly.
+ SetMayHaveClass();
+
+ return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::_class,
+ nullptr, // prefix
+ nullptr, // old value
+ value, nullptr,
+ static_cast<uint8_t>(MutationEvent_Binding::ADDITION),
+ false, // hasListeners
+ false, // notify
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAString& aValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify) {
+ // Keep this in sync with SetParsedAttr below and SetSingleClassFromParser
+ // above.
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValue oldValue;
+ bool oldValueSet;
+
+ {
+ const nsAttrValueOrString value(aValue);
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners,
+ &oldValueSet)) {
+ OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
+ return NS_OK;
+ }
+ }
+
+ // Hold a script blocker while calling ParseAttribute since that can call
+ // out to id-observers
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(this, aNamespaceID, aName,
+ modType);
+ }
+
+ nsAttrValue attrValue;
+ if (!ParseAttribute(aNamespaceID, aName, aValue, aSubjectPrincipal,
+ attrValue)) {
+ attrValue.SetTo(aValue);
+ }
+
+ BeforeSetAttr(aNamespaceID, aName, &attrValue, aNotify);
+
+ PreIdMaybeChange(aNamespaceID, aName, &attrValue);
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+ oldValueSet ? &oldValue : nullptr, attrValue,
+ aSubjectPrincipal, modType, hasListeners, aNotify,
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetParsedAttr(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix, nsAttrValue& aParsedValue,
+ bool aNotify) {
+ // Keep this in sync with SetAttr and SetSingleClassFromParser above
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValue oldValue;
+ bool oldValueSet;
+
+ {
+ const nsAttrValueOrString value(aParsedValue);
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners,
+ &oldValueSet)) {
+ OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
+ return NS_OK;
+ }
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(this, aNamespaceID, aName,
+ modType);
+ }
+
+ BeforeSetAttr(aNamespaceID, aName, &aParsedValue, aNotify);
+
+ PreIdMaybeChange(aNamespaceID, aName, &aParsedValue);
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+ oldValueSet ? &oldValue : nullptr, aParsedValue,
+ nullptr, modType, hasListeners, aNotify,
+ kCallAfterSetAttr, document, updateBatch);
+}
+
+nsresult Element::SetAttrAndNotify(
+ int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAttrValue* aOldValue, nsAttrValue& aParsedValue,
+ nsIPrincipal* aSubjectPrincipal, uint8_t aModType, bool aFireMutation,
+ bool aNotify, bool aCallAfterSetAttr, Document* aComposedDocument,
+ const mozAutoDocUpdate& aGuard) {
+ nsresult rv;
+ nsMutationGuard::DidMutate();
+
+ // Copy aParsedValue for later use since it will be lost when we call
+ // SetAndSwapMappedAttr below
+ nsAttrValue valueForAfterSetAttr;
+ if (aCallAfterSetAttr || GetCustomElementData()) {
+ valueForAfterSetAttr.SetTo(aParsedValue);
+ }
+
+ bool hadValidDir = false;
+ bool hadDirAuto = false;
+ bool oldValueSet;
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::dir) {
+ hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
+ hadDirAuto = HasDirAuto(); // already takes bdi into account
+ }
+
+ // XXXbz Perhaps we should push up the attribute mapping function
+ // stuff to Element?
+ if (!IsAttributeMapped(aName) ||
+ !SetAndSwapMappedAttribute(aName, aParsedValue, &oldValueSet, &rv)) {
+ rv = mAttrs.SetAndSwapAttr(aName, aParsedValue, &oldValueSet);
+ }
+ } else {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(aName, aPrefix, aNamespaceID,
+ ATTRIBUTE_NODE);
+
+ rv = mAttrs.SetAndSwapAttr(ni, aParsedValue, &oldValueSet);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PostIdMaybeChange(aNamespaceID, aName, &valueForAfterSetAttr);
+
+ // If the old value owns its own data, we know it is OK to keep using it.
+ // oldValue will be null if there was no previously set value
+ const nsAttrValue* oldValue;
+ if (aParsedValue.StoresOwnData()) {
+ if (oldValueSet) {
+ oldValue = &aParsedValue;
+ } else {
+ oldValue = nullptr;
+ }
+ } else {
+ // No need to conditionally assign null here. If there was no previously
+ // set value for the attribute, aOldValue will already be null.
+ oldValue = aOldValue;
+ }
+
+ if (HasElementCreatedFromPrototypeAndHasUnmodifiedL10n() &&
+ aNamespaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::datal10nid || aName == nsGkAtoms::datal10nargs)) {
+ ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n();
+ if (aComposedDocument) {
+ aComposedDocument->mL10nProtoElements.Remove(this);
+ }
+ }
+
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ RefPtr<nsAtom> oldValueAtom;
+ if (oldValue) {
+ oldValueAtom = oldValue->GetAsAtom();
+ } else {
+ // If there is no old value, get the value of the uninitialized
+ // attribute that was swapped with aParsedValue.
+ oldValueAtom = aParsedValue.GetAsAtom();
+ }
+ RefPtr<nsAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
+ nsAutoString ns;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNamespaceID, ns);
+
+ LifecycleCallbackArgs args;
+ args.mName = nsDependentAtomString(aName);
+ args.mOldValue = (aModType == MutationEvent_Binding::ADDITION
+ ? VoidString()
+ : nsDependentAtomString(oldValueAtom));
+ args.mNewValue = nsDependentAtomString(newValueAtom);
+ args.mNamespaceURI = (ns.IsEmpty() ? VoidString() : ns);
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, args, definition);
+ }
+ }
+
+ if (aCallAfterSetAttr) {
+ AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, oldValue,
+ aSubjectPrincipal, aNotify);
+
+ if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ OnSetDirAttr(this, &valueForAfterSetAttr, hadValidDir, hadDirAuto,
+ aNotify);
+ }
+ }
+
+ UpdateState(aNotify);
+
+ if (aNotify) {
+ // Don't pass aOldValue to AttributeChanged since it may not be reliable.
+ // Callers only compute aOldValue under certain conditions which may not
+ // be triggered by all nsIMutationObservers.
+ MutationObservers::NotifyAttributeChanged(
+ this, aNamespaceID, aName, aModType,
+ aParsedValue.StoresOwnData() ? &aParsedValue : nullptr);
+ }
+
+ if (aFireMutation) {
+ InternalMutationEvent mutation(true, eLegacyAttrModified);
+
+ nsAutoString ns;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNamespaceID, ns);
+ Attr* attrNode =
+ GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
+ mutation.mRelatedNode = attrNode;
+
+ mutation.mAttrName = aName;
+ nsAutoString newValue;
+ GetAttr(aNamespaceID, aName, newValue);
+ if (!newValue.IsEmpty()) {
+ mutation.mNewAttrValue = NS_Atomize(newValue);
+ }
+ if (oldValue && !oldValue->IsEmptyString()) {
+ mutation.mPrevAttrValue = oldValue->GetAsAtom();
+ }
+ mutation.mAttrChange = aModType;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation);
+ }
+
+ return NS_OK;
+}
+
+bool Element::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) {
+ if (aAttribute == nsGkAtoms::lang) {
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::_class || aAttribute == nsGkAtoms::part) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::exportparts) {
+ aResult.ParsePartMapping(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::id) {
+ // Store id as an atom. id="" means that the element has no id,
+ // not that it has an emptystring as the id.
+ if (aValue.IsEmpty()) {
+ return false;
+ }
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Element::SetAndSwapMappedAttribute(nsAtom* aName, nsAttrValue& aValue,
+ bool* aValueWasSet, nsresult* aRetval) {
+ *aRetval = NS_OK;
+ return false;
+}
+
+void Element::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::_class && aValue) {
+ // Note: This flag is asymmetrical. It is never unset and isn't exact.
+ // If it is ever made to be exact, we probably need to handle this
+ // similarly to how ids are handled in PreIdMaybeChange and
+ // PostIdMaybeChange.
+ // Note that SetSingleClassFromParser inlines BeforeSetAttr and
+ // calls SetMayHaveClass directly. Making a subclass take action
+ // on the class attribute in a BeforeSetAttr override would
+ // require revising SetSingleClassFromParser.
+ SetMayHaveClass();
+ }
+ }
+}
+
+void Element::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::part) {
+ bool isPart = !!aValue;
+ if (HasPartAttribute() != isPart) {
+ SetHasPartAttribute(isPart);
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ if (isPart) {
+ shadow->PartAdded(*this);
+ } else {
+ shadow->PartRemoved(*this);
+ }
+ }
+ }
+ MOZ_ASSERT(HasPartAttribute() == isPart);
+ } else if (aName == nsGkAtoms::slot && GetParent()) {
+ if (ShadowRoot* shadow = GetParent()->GetShadowRoot()) {
+ shadow->MaybeReassignContent(*this);
+ }
+ }
+ }
+}
+
+void Element::PreIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue) {
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+ return;
+ }
+ RemoveFromIdTable();
+}
+
+void Element::PostIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue) {
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+ return;
+ }
+
+ // id="" means that the element has no id, not that it has an empty
+ // string as the id.
+ if (aValue && !aValue->IsEmptyString()) {
+ SetHasID();
+ AddToIdTable(aValue->GetAtomValue());
+ } else {
+ ClearHasID();
+ }
+}
+
+void Element::OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString& aValue,
+ bool aNotify) {
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ nsAutoString ns;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNamespaceID, ns);
+
+ nsAutoString value(aValue.String());
+ LifecycleCallbackArgs args;
+ args.mName = nsDependentAtomString(aName);
+ args.mOldValue = value;
+ args.mNewValue = value;
+ args.mNamespaceURI = (ns.IsEmpty() ? VoidString() : ns);
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, args, definition);
+ }
+ }
+}
+
+EventListenerManager* Element::GetEventListenerManagerForAttr(nsAtom* aAttrName,
+ bool* aDefer) {
+ *aDefer = true;
+ return GetOrCreateListenerManager();
+}
+
+bool Element::GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
+ nsAString& aResult) const {
+ DOMString str;
+ bool haveAttr = GetAttr(aNameSpaceID, aName, str);
+ str.ToString(aResult);
+ return haveAttr;
+}
+
+int32_t Element::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
+ AttrArray::AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const {
+ return mAttrs.FindAttrValueIn(aNameSpaceID, aName, aValues, aCaseSensitive);
+}
+
+nsresult Element::UnsetAttr(int32_t aNameSpaceID, nsAtom* aName, bool aNotify) {
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+
+ int32_t index = mAttrs.IndexOfAttr(aName, aNameSpaceID);
+ if (index < 0) {
+ return NS_OK;
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, aNotify);
+
+ if (aNotify) {
+ MutationObservers::NotifyAttributeWillChange(
+ this, aNameSpaceID, aName, MutationEvent_Binding::REMOVAL);
+ }
+
+ BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+
+ bool hasMutationListeners =
+ aNotify && nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+
+ PreIdMaybeChange(aNameSpaceID, aName, nullptr);
+
+ // Grab the attr node if needed before we remove it from the attr map
+ RefPtr<Attr> attrNode;
+ if (hasMutationListeners) {
+ nsAutoString ns;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNameSpaceID, ns);
+ attrNode = GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
+ }
+
+ // Clear the attribute out from attribute map.
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots && slots->mAttributeMap) {
+ slots->mAttributeMap->DropAttribute(aNameSpaceID, aName);
+ }
+
+ // The id-handling code, and in the future possibly other code, need to
+ // react to unexpected attribute changes.
+ nsMutationGuard::DidMutate();
+
+ bool hadValidDir = false;
+ bool hadDirAuto = false;
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
+ hadDirAuto = HasDirAuto(); // already takes bdi into account
+ }
+
+ nsAttrValue oldValue;
+ nsresult rv = mAttrs.RemoveAttrAt(index, oldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PostIdMaybeChange(aNameSpaceID, aName, nullptr);
+
+ const CustomElementData* data = GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ CustomElementDefinition* definition = data->GetCustomElementDefinition();
+ MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
+
+ if (definition->IsInObservedAttributeList(aName)) {
+ nsAutoString ns;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNameSpaceID, ns);
+
+ RefPtr<nsAtom> oldValueAtom = oldValue.GetAsAtom();
+ LifecycleCallbackArgs args;
+ args.mName = nsDependentAtomString(aName);
+ args.mOldValue = nsDependentAtomString(oldValueAtom);
+ args.mNewValue = VoidString();
+ args.mNamespaceURI = (ns.IsEmpty() ? VoidString() : ns);
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eAttributeChanged, this, args, definition);
+ }
+ }
+
+ AfterSetAttr(aNameSpaceID, aName, nullptr, &oldValue, nullptr, aNotify);
+
+ UpdateState(aNotify);
+
+ if (aNotify) {
+ // We can always pass oldValue here since there is no new value which could
+ // have corrupted it.
+ MutationObservers::NotifyAttributeChanged(
+ this, aNameSpaceID, aName, MutationEvent_Binding::REMOVAL, &oldValue);
+ }
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ OnSetDirAttr(this, nullptr, hadValidDir, hadDirAuto, aNotify);
+ }
+
+ if (hasMutationListeners) {
+ InternalMutationEvent mutation(true, eLegacyAttrModified);
+
+ mutation.mRelatedNode = attrNode;
+ mutation.mAttrName = aName;
+
+ nsAutoString value;
+ oldValue.ToString(value);
+ if (!value.IsEmpty()) mutation.mPrevAttrValue = NS_Atomize(value);
+ mutation.mAttrChange = MutationEvent_Binding::REMOVAL;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation);
+ }
+
+ return NS_OK;
+}
+
+void Element::DescribeAttribute(uint32_t index,
+ nsAString& aOutDescription) const {
+ // name
+ mAttrs.AttrNameAt(index)->GetQualifiedName(aOutDescription);
+
+ // value
+ aOutDescription.AppendLiteral("=\"");
+ nsAutoString value;
+ mAttrs.AttrAt(index)->ToString(value);
+ for (uint32_t i = value.Length(); i > 0; --i) {
+ if (value[i - 1] == char16_t('"')) value.Insert(char16_t('\\'), i - 1);
+ }
+ aOutDescription.Append(value);
+ aOutDescription.Append('"');
+}
+
+#ifdef MOZ_DOM_LIST
+void Element::ListAttributes(FILE* out) const {
+ uint32_t index, count = mAttrs.AttrCount();
+ for (index = 0; index < count; index++) {
+ nsAutoString attributeDescription;
+ DescribeAttribute(index, attributeDescription);
+
+ fputs(" ", out);
+ fputs(NS_LossyConvertUTF16toASCII(attributeDescription).get(), out);
+ }
+}
+
+void Element::List(FILE* out, int32_t aIndent, const nsCString& aPrefix) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+
+ fputs(aPrefix.get(), out);
+
+ fputs(NS_LossyConvertUTF16toASCII(mNodeInfo->QualifiedName()).get(), out);
+
+ fprintf(out, "@%p", (void*)this);
+
+ ListAttributes(out);
+
+ fprintf(out, " state=[%llx]",
+ static_cast<unsigned long long>(State().GetInternalValue()));
+ fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
+ if (IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ const LinkedList<AbstractRange>* ranges =
+ GetExistingClosestCommonInclusiveAncestorRanges();
+ int32_t count = 0;
+ if (ranges) {
+ // Can't use range-based iteration on a const LinkedList, unfortunately.
+ for (const AbstractRange* r = ranges->getFirst(); r; r = r->getNext()) {
+ ++count;
+ }
+ }
+ fprintf(out, " ranges:%d", count);
+ }
+ fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
+ fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
+
+ nsIContent* child = GetFirstChild();
+ if (child) {
+ fputs("\n", out);
+
+ for (; child; child = child->GetNextSibling()) {
+ child->List(out, aIndent + 1);
+ }
+
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+ }
+
+ fputs(">\n", out);
+}
+
+void Element::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const {
+ int32_t indent;
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+
+ const nsString& buf = mNodeInfo->QualifiedName();
+ fputs("<", out);
+ fputs(NS_LossyConvertUTF16toASCII(buf).get(), out);
+
+ if (aDumpAll) ListAttributes(out);
+
+ fputs(">", out);
+
+ if (aIndent) fputs("\n", out);
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ int32_t indent = aIndent ? aIndent + 1 : 0;
+ child->DumpContent(out, indent, aDumpAll);
+ }
+ for (indent = aIndent; --indent >= 0;) fputs(" ", out);
+ fputs("</", out);
+ fputs(NS_LossyConvertUTF16toASCII(buf).get(), out);
+ fputs(">", out);
+
+ if (aIndent) fputs("\n", out);
+}
+#endif
+
+void Element::Describe(nsAString& aOutDescription, bool aShort) const {
+ aOutDescription.Append(mNodeInfo->QualifiedName());
+ aOutDescription.AppendPrintf("@%p", (void*)this);
+
+ uint32_t index, count = mAttrs.AttrCount();
+ for (index = 0; index < count; index++) {
+ if (aShort) {
+ const nsAttrName* name = mAttrs.AttrNameAt(index);
+ if (!name->Equals(nsGkAtoms::id) && !name->Equals(nsGkAtoms::_class)) {
+ continue;
+ }
+ }
+ aOutDescription.Append(' ');
+ nsAutoString attributeDescription;
+ DescribeAttribute(index, attributeDescription);
+ aOutDescription.Append(attributeDescription);
+ }
+}
+
+bool Element::CheckHandleEventForLinksPrecondition(
+ EventChainVisitor& aVisitor) const {
+ // Make sure we actually are a link
+ if (!IsLink()) {
+ return false;
+ }
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
+ (!aVisitor.mEvent->IsTrusted() &&
+ (aVisitor.mEvent->mMessage != eMouseClick) &&
+ (aVisitor.mEvent->mMessage != eKeyPress) &&
+ (aVisitor.mEvent->mMessage != eLegacyDOMActivate)) ||
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
+ return false;
+ }
+ return true;
+}
+
+void Element::GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor) {
+ // Optimisation: return early if this event doesn't interest us.
+ // IMPORTANT: this switch and the switch below it must be kept in sync!
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseOver:
+ case eFocus:
+ case eMouseOut:
+ case eBlur:
+ break;
+ default:
+ return;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ if (!CheckHandleEventForLinksPrecondition(aVisitor)) {
+ return;
+ }
+
+ // We try to handle everything we can even when the URI is invalid. Though of
+ // course we can't do stuff like updating the status bar, so return early here
+ // instead.
+ nsCOMPtr<nsIURI> absURI = GetHrefURI();
+ if (!absURI) {
+ return;
+ }
+
+ // We do the status bar updates in GetEventTargetParent so that the status bar
+ // gets updated even if the event is consumed before we have a chance to set
+ // it.
+ switch (aVisitor.mEvent->mMessage) {
+ // Set the status bar similarly for mouseover and focus
+ case eMouseOver:
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ [[fallthrough]];
+ case eFocus: {
+ InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
+ if (!focusEvent || !focusEvent->mIsRefocus) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ nsContentUtils::TriggerLink(this, absURI, target,
+ /* click */ false, /* isTrusted */ true);
+ // Make sure any ancestor links don't also TriggerLink
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+ }
+ case eMouseOut:
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ [[fallthrough]];
+ case eBlur: {
+ nsresult rv = LeaveLink(aVisitor.mPresContext);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+ }
+
+ default:
+ // switch not in sync with the optimization switch earlier in this
+ // function
+ MOZ_ASSERT_UNREACHABLE("switch statements not in sync");
+ }
+}
+
+// This dispatches a 'chromelinkclick' CustomEvent to chrome-only listeners,
+// so that frontend can handle middle-clicks and ctrl/cmd/shift/etc.-clicks
+// on links, without getting a call for every single click the user makes.
+// Only supported for click or auxclick events.
+void Element::DispatchChromeOnlyLinkClickEvent(
+ EventChainPostVisitor& aVisitor) {
+ MOZ_ASSERT(aVisitor.mEvent->mMessage == eMouseAuxClick ||
+ aVisitor.mEvent->mMessage == eMouseClick,
+ "DispatchChromeOnlyLinkClickEvent supports only click and "
+ "auxclick source events");
+ Document* doc = OwnerDoc();
+ RefPtr<XULCommandEvent> event =
+ new XULCommandEvent(doc, aVisitor.mPresContext, nullptr);
+ RefPtr<dom::Event> mouseDOMEvent = aVisitor.mDOMEvent;
+ if (!mouseDOMEvent) {
+ mouseDOMEvent = EventDispatcher::CreateEvent(
+ aVisitor.mEvent->mOriginalTarget, aVisitor.mPresContext,
+ aVisitor.mEvent, u""_ns);
+ NS_ADDREF(aVisitor.mDOMEvent = mouseDOMEvent);
+ }
+
+ MouseEvent* mouseEvent = mouseDOMEvent->AsMouseEvent();
+ event->InitCommandEvent(
+ u"chromelinkclick"_ns, /* CanBubble */ true,
+ /* Cancelable */ true, nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
+ 0, mouseEvent->CtrlKey(), mouseEvent->AltKey(), mouseEvent->ShiftKey(),
+ mouseEvent->MetaKey(), mouseEvent->Button(), mouseDOMEvent,
+ mouseEvent->MozInputSource(), IgnoreErrors());
+ // Note: we're always trusted, but the event we pass as the `sourceEvent`
+ // might not be. Frontend code will check that event's trusted property to
+ // make that determination; doing it this way means we don't also start
+ // acting on web-generated custom 'chromelinkclick' events which would
+ // provide additional attack surface for a malicious actor.
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ DispatchEvent(*event);
+}
+
+nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) {
+ // Optimisation: return early if this event doesn't interest us.
+ // IMPORTANT: this switch and the switch below it must be kept in sync!
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown:
+ case eMouseClick:
+ case eMouseAuxClick:
+ case eLegacyDOMActivate:
+ case eKeyPress:
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ if (!CheckHandleEventForLinksPrecondition(aVisitor)) {
+ return NS_OK;
+ }
+
+ // We try to handle ~everything consistently even if the href is invalid
+ // (GetHrefURI() returns null).
+ nsresult rv = NS_OK;
+
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown: {
+ if (!OwnerDoc()->LinkHandlingEnabled()) {
+ break;
+ }
+
+ WidgetMouseEvent* const mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ mouseEvent->mFlags.mMultipleActionsPrevented |=
+ mouseEvent->mButton == MouseButton::ePrimary ||
+ mouseEvent->mButton == MouseButton::eMiddle;
+
+ if (mouseEvent->mButton == MouseButton::ePrimary) {
+ // For avoiding focus popup opened by clicking this link to get blurred,
+ // we need this to get focused now. However, if the mousedown occurs
+ // in editable element in this link, we should not do this because its
+ // editing host will get focus.
+ if (IsInComposedDoc()) {
+ Element* targetElement = Element::FromEventTargetOrNull(
+ aVisitor.mEvent->GetDOMEventTarget());
+ if (targetElement && targetElement->IsInclusiveDescendantOf(this) &&
+ (!targetElement->IsEditable() ||
+ targetElement->GetEditingHost() == this)) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ RefPtr<Element> kungFuDeathGrip(this);
+ fm->SetFocus(kungFuDeathGrip, nsIFocusManager::FLAG_BYMOUSE |
+ nsIFocusManager::FLAG_NOSCROLL);
+ }
+ }
+ }
+
+ if (aVisitor.mPresContext) {
+ EventStateManager::SetActiveManager(
+ aVisitor.mPresContext->EventStateManager(), this);
+ }
+
+ // OK, we're pretty sure we're going to load, so warm up a speculative
+ // connection to be sure we have one ready when we open the channel.
+ if (nsIDocShell* shell = OwnerDoc()->GetDocShell()) {
+ if (nsCOMPtr<nsIURI> absURI = GetHrefURI()) {
+ if (nsCOMPtr<nsISpeculativeConnect> sc =
+ mozilla::components::IO::Service()) {
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(shell);
+ sc->SpeculativeConnect(absURI, NodePrincipal(), ir, false);
+ }
+ }
+ }
+ }
+ } break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ if (!mouseEvent->IsControl() && !mouseEvent->IsMeta() &&
+ !mouseEvent->IsAlt() && !mouseEvent->IsShift()) {
+ // The default action is simply to dispatch DOMActivate
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // DOMActivate event should be trusted since the activation is
+ // actually occurred even if the cause is an untrusted click event.
+ InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
+ actEvent.mDetail = 1;
+
+ rv = EventDispatcher::Dispatch(this, aVisitor.mPresContext, &actEvent,
+ nullptr, &status);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ DispatchChromeOnlyLinkClickEvent(aVisitor);
+ }
+ break;
+ }
+ case eMouseAuxClick: {
+ DispatchChromeOnlyLinkClickEvent(aVisitor);
+ break;
+ }
+ case eLegacyDOMActivate: {
+ if (aVisitor.mEvent->mOriginalTarget == this) {
+ if (nsCOMPtr<nsIURI> absURI = GetHrefURI()) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ const InternalUIEvent* activeEvent = aVisitor.mEvent->AsUIEvent();
+ MOZ_ASSERT(activeEvent);
+ nsContentUtils::TriggerLink(this, absURI, target, /* click */ true,
+ activeEvent->IsTrustable());
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ } break;
+
+ case eKeyPress: {
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if (keyEvent && keyEvent->mKeyCode == NS_VK_RETURN) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = DispatchClickEvent(aVisitor.mPresContext, keyEvent, this, false,
+ nullptr, &status);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ } break;
+
+ default:
+ // switch not in sync with the optimization switch earlier in this
+ // function
+ MOZ_ASSERT_UNREACHABLE("switch statements not in sync");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+void Element::GetLinkTarget(nsAString& aTarget) { aTarget.Truncate(); }
+
+static nsStaticAtom* const sPropertiesToTraverseAndUnlink[] = {
+ nsGkAtoms::dirAutoSetBy, nullptr};
+
+// static
+nsStaticAtom* const* Element::HTMLSVGPropertiesToTraverseAndUnlink() {
+ return sPropertiesToTraverseAndUnlink;
+}
+
+nsresult Element::CopyInnerTo(Element* aDst, ReparseAttributes aReparse) {
+ nsresult rv = aDst->mAttrs.EnsureCapacityToClone(mAttrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const bool reparse = aReparse == ReparseAttributes::Yes;
+
+ uint32_t count = mAttrs.AttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ BorrowedAttrInfo info = mAttrs.AttrInfoAt(i);
+ const nsAttrName* name = info.mName;
+ const nsAttrValue* value = info.mValue;
+ if (value->Type() == nsAttrValue::eCSSDeclaration) {
+ MOZ_ASSERT(name->Equals(nsGkAtoms::style, kNameSpaceID_None));
+ // We still clone CSS attributes, even in the `reparse` (cross-document)
+ // case. https://github.com/w3c/webappsec-csp/issues/212
+ nsAttrValue valueCopy(*value);
+ rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valueCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ value->GetCSSDeclarationValue()->SetImmutable();
+ } else if (reparse) {
+ nsAutoString valStr;
+ value->ToString(valStr);
+ rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsAttrValue valueCopy(*value);
+ rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valueCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ dom::NodeInfo* dstNodeInfo = aDst->NodeInfo();
+ if (CustomElementData* data = GetCustomElementData()) {
+ // The cloned node may be a custom element that may require
+ // enqueing upgrade reaction.
+ if (nsAtom* typeAtom = data->GetCustomElementType()) {
+ aDst->SetCustomElementData(MakeUnique<CustomElementData>(typeAtom));
+ MOZ_ASSERT(dstNodeInfo->NameAtom()->Equals(dstNodeInfo->LocalName()));
+ CustomElementDefinition* definition =
+ nsContentUtils::LookupCustomElementDefinition(
+ dstNodeInfo->GetDocument(), dstNodeInfo->NameAtom(),
+ dstNodeInfo->NamespaceID(), typeAtom);
+ if (definition) {
+ nsContentUtils::EnqueueUpgradeReaction(aDst, definition);
+ }
+ }
+ }
+
+ if (dstNodeInfo->GetDocument()->IsStaticDocument()) {
+ // Propagate :defined state to the static clone.
+ if (State().HasState(ElementState::DEFINED)) {
+ aDst->SetDefined(true);
+ }
+ }
+
+ return NS_OK;
+}
+
+Element* Element::Closest(const nsACString& aSelector, ErrorResult& aResult) {
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("Element::Closest",
+ LAYOUT_SelectorQuery, aSelector);
+ const StyleSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return nullptr;
+ }
+
+ return const_cast<Element*>(Servo_SelectorList_Closest(this, list));
+}
+
+bool Element::Matches(const nsACString& aSelector, ErrorResult& aResult) {
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("Element::Matches",
+ LAYOUT_SelectorQuery, aSelector);
+ const StyleSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return false;
+ }
+
+ return Servo_SelectorList_Matches(this, list);
+}
+
+static const nsAttrValue::EnumTable kCORSAttributeTable[] = {
+ // Order matters here
+ // See ParseCORSValue
+ {"anonymous", CORS_ANONYMOUS},
+ {"use-credentials", CORS_USE_CREDENTIALS},
+ {nullptr, 0}};
+
+/* static */
+void Element::ParseCORSValue(const nsAString& aValue, nsAttrValue& aResult) {
+ DebugOnly<bool> success =
+ aResult.ParseEnumValue(aValue, kCORSAttributeTable, false,
+ // default value is anonymous if aValue is
+ // not a value we understand
+ &kCORSAttributeTable[0]);
+ MOZ_ASSERT(success);
+}
+
+/* static */
+CORSMode Element::StringToCORSMode(const nsAString& aValue) {
+ if (aValue.IsVoid()) {
+ return CORS_NONE;
+ }
+
+ nsAttrValue val;
+ Element::ParseCORSValue(aValue, val);
+ return CORSMode(val.GetEnumValue());
+}
+
+/* static */
+CORSMode Element::AttrValueToCORSMode(const nsAttrValue* aValue) {
+ if (!aValue) {
+ return CORS_NONE;
+ }
+
+ return CORSMode(aValue->GetEnumValue());
+}
+
+/**
+ * Returns nullptr if requests for fullscreen are allowed in the current
+ * context. Requests are only allowed if the user initiated them (like with
+ * a mouse-click or key press), unless this check has been disabled by
+ * setting the pref "full-screen-api.allow-trusted-requests-only" to false
+ * or if the caller is privileged. Feature policy may also deny requests.
+ * If fullscreen is not allowed, a key for the error message is returned.
+ */
+static const char* GetFullscreenError(CallerType aCallerType,
+ Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+
+ // Privileged callers can always request fullscreen
+ if (aCallerType == CallerType::System) {
+ return nullptr;
+ }
+
+ if (nsContentUtils::IsPDFJS(aDocument->GetPrincipal())) {
+ // The built-in pdf viewer can always request fullscreen
+ return nullptr;
+ }
+
+ if (const char* error = aDocument->GetFullscreenError(aCallerType)) {
+ return error;
+ }
+
+ // Bypass user interaction checks if preference is set
+ if (!StaticPrefs::full_screen_api_allow_trusted_requests_only()) {
+ return nullptr;
+ }
+
+ if (!aDocument->ConsumeTransientUserGestureActivation()) {
+ return "FullscreenDeniedNotInputDriven";
+ }
+
+ // Entering full-screen on mouse mouse event is only allowed with left mouse
+ // button
+ if (StaticPrefs::full_screen_api_mouse_event_allow_left_button_only() &&
+ (EventStateManager::sCurrentMouseBtn == MouseButton::eMiddle ||
+ EventStateManager::sCurrentMouseBtn == MouseButton::eSecondary)) {
+ return "FullscreenDeniedMouseEventOnlyLeftBtn";
+ }
+
+ return nullptr;
+}
+
+void Element::SetCapture(bool aRetargetToElement) {
+ // If there is already an active capture, ignore this request. This would
+ // occur if a splitter, frame resizer, etc had already captured and we don't
+ // want to override those.
+ if (!PresShell::GetCapturingContent()) {
+ PresShell::SetCapturingContent(
+ this, CaptureFlags::PreventDragStart |
+ (aRetargetToElement ? CaptureFlags::RetargetToElement
+ : CaptureFlags::None));
+ }
+}
+
+void Element::SetCaptureAlways(bool aRetargetToElement) {
+ PresShell::SetCapturingContent(
+ this, CaptureFlags::PreventDragStart | CaptureFlags::IgnoreAllowedState |
+ (aRetargetToElement ? CaptureFlags::RetargetToElement
+ : CaptureFlags::None));
+}
+
+void Element::ReleaseCapture() {
+ if (PresShell::GetCapturingContent() == this) {
+ PresShell::ReleaseCapturingContent();
+ }
+}
+
+already_AddRefed<Promise> Element::RequestFullscreen(CallerType aCallerType,
+ ErrorResult& aRv) {
+ auto request = FullscreenRequest::Create(this, aCallerType, aRv);
+ RefPtr<Promise> promise = request->GetPromise();
+
+ // Only grant fullscreen requests if this is called from inside a trusted
+ // event handler (i.e. inside an event handler for a user initiated event).
+ // This stops the fullscreen from being abused similar to the popups of old,
+ // and it also makes it harder for bad guys' script to go fullscreen and
+ // spoof the browser chrome/window and phish logins etc.
+ // Note that requests for fullscreen inside a web app's origin are exempt
+ // from this restriction.
+ if (const char* error = GetFullscreenError(aCallerType, OwnerDoc())) {
+ request->Reject(error);
+ } else {
+ OwnerDoc()->RequestFullscreen(std::move(request));
+ }
+ return promise.forget();
+}
+
+void Element::RequestPointerLock(CallerType aCallerType) {
+ PointerLockManager::RequestLock(this, aCallerType);
+}
+
+already_AddRefed<Flex> Element::GetAsFlexContainer() {
+ // We need the flex frame to compute additional info, and use
+ // that annotated version of the frame.
+ nsFlexContainerFrame* flexFrame =
+ nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+
+ if (flexFrame) {
+ RefPtr<Flex> flex = new Flex(this, flexFrame);
+ return flex.forget();
+ }
+ return nullptr;
+}
+
+void Element::GetGridFragments(nsTArray<RefPtr<Grid>>& aResult) {
+ nsGridContainerFrame* frame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+
+ // If we get a nsGridContainerFrame from the prior call,
+ // all the next-in-flow frames will also be nsGridContainerFrames.
+ while (frame) {
+ // Get the existing Grid object, if it exists. This object is
+ // guaranteed to be up-to-date because GetGridFrameWithComputedInfo
+ // will delete an existing one when regenerating grid info.
+ Grid* gridFragment = frame->GetGridFragmentInfo();
+ if (!gridFragment) {
+ // Grid constructor will add itself as a property to frame, and
+ // its unlink method will remove itself if the frame still exists.
+ gridFragment = new Grid(this, frame);
+ }
+ aResult.AppendElement(gridFragment);
+ frame = static_cast<nsGridContainerFrame*>(frame->GetNextInFlow());
+ }
+}
+
+bool Element::HasGridFragments() {
+ return !!nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ GetPrimaryFrame(FlushType::Layout));
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToAncestor(
+ Element& aAncestor) {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+ nsIFrame* ancestorFrame = aAncestor.GetPrimaryFrame();
+
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ // If aAncestor is not actually an ancestor of this (including nullptr),
+ // then the call to GetTransformToAncestor will return the transform
+ // all the way up through the parent chain.
+ transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{primaryFrame},
+ RelativeTo{ancestorFrame},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToParent() {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ nsIFrame* parentFrame = primaryFrame->GetParent();
+ transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{primaryFrame},
+ RelativeTo{parentFrame},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<DOMMatrixReadOnly> Element::GetTransformToViewport() {
+ nsIFrame* primaryFrame = GetPrimaryFrame();
+ Matrix4x4 transform;
+ if (primaryFrame) {
+ transform =
+ nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{primaryFrame},
+ RelativeTo{nsLayoutUtils::GetDisplayRootFrame(primaryFrame)},
+ nsIFrame::IN_CSS_UNITS)
+ .GetMatrix();
+ }
+
+ DOMMatrixReadOnly* matrix = new DOMMatrix(this, transform);
+ RefPtr<DOMMatrixReadOnly> result(matrix);
+ return result.forget();
+}
+
+already_AddRefed<Animation> Element::Animate(
+ JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIGlobalObject> ownerGlobal = GetOwnerGlobal();
+ if (!ownerGlobal) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
+ MOZ_ASSERT(!global.Failed());
+
+ // KeyframeEffect constructor doesn't follow the standard Xray calling
+ // convention and needs to be called in caller's compartment.
+ // This should match to RunConstructorInCallerCompartment attribute in
+ // KeyframeEffect.webidl.
+ RefPtr<KeyframeEffect> effect =
+ KeyframeEffect::Constructor(global, this, aKeyframes, aOptions, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Animation constructor follows the standard Xray calling convention and
+ // needs to be called in the target element's realm.
+ JSAutoRealm ar(aContext, global.Get());
+
+ AnimationTimeline* timeline = OwnerDoc()->Timeline();
+ RefPtr<Animation> animation = Animation::Constructor(
+ global, effect, Optional<AnimationTimeline*>(timeline), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aOptions.IsKeyframeAnimationOptions()) {
+ animation->SetId(aOptions.GetAsKeyframeAnimationOptions().mId);
+ }
+
+ animation->Play(aError, Animation::LimitBehavior::AutoRewind);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return animation.forget();
+}
+
+void Element::GetAnimations(const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ if (Document* doc = GetComposedDoc()) {
+ // We don't need to explicitly flush throttled animations here, since
+ // updating the animation style of elements will never affect the set of
+ // running animations and it's only the set of running animations that is
+ // important here.
+ //
+ // NOTE: Any changes to the flags passed to the following call should
+ // be reflected in the flags passed in DocumentOrShadowRoot::GetAnimations
+ // too.
+ doc->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Style, false /* flush animations */));
+ }
+
+ GetAnimationsWithoutFlush(aOptions, aAnimations);
+}
+
+void Element::GetAnimationsWithoutFlush(
+ const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ Element* elem = this;
+ PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
+ // For animations on generated-content elements, the animations are stored
+ // on the parent element.
+ if (IsGeneratedContentContainerForBefore()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::before;
+ } else if (IsGeneratedContentContainerForAfter()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::after;
+ } else if (IsGeneratedContentContainerForMarker()) {
+ elem = GetParentElement();
+ pseudoType = PseudoStyleType::marker;
+ }
+
+ if (!elem) {
+ return;
+ }
+
+ if (!aOptions.mSubtree ||
+ AnimationUtils::IsSupportedPseudoForAnimations(pseudoType)) {
+ GetAnimationsUnsorted(elem, pseudoType, aAnimations);
+ } else {
+ for (nsIContent* node = this; node; node = node->GetNextNode(this)) {
+ if (!node->IsElement()) {
+ continue;
+ }
+ Element* element = node->AsElement();
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::NotPseudo,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::before,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::after,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, PseudoStyleType::marker,
+ aAnimations);
+ }
+ }
+ aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
+}
+
+/* static */
+void Element::GetAnimationsUnsorted(Element* aElement,
+ PseudoStyleType aPseudoType,
+ nsTArray<RefPtr<Animation>>& aAnimations) {
+ MOZ_ASSERT(aPseudoType == PseudoStyleType::NotPseudo ||
+ AnimationUtils::IsSupportedPseudoForAnimations(aPseudoType),
+ "Unsupported pseudo type");
+ MOZ_ASSERT(aElement, "Null element");
+
+ EffectSet* effects = EffectSet::Get(aElement, aPseudoType);
+ if (!effects) {
+ return;
+ }
+
+ for (KeyframeEffect* effect : *effects) {
+ MOZ_ASSERT(effect && effect->GetAnimation(),
+ "Only effects associated with an animation should be "
+ "added to an element's effect set");
+ Animation* animation = effect->GetAnimation();
+
+ MOZ_ASSERT(animation->IsRelevant(),
+ "Only relevant animations should be added to an element's "
+ "effect set");
+ aAnimations.AppendElement(animation);
+ }
+}
+
+void Element::CloneAnimationsFrom(const Element& aOther) {
+ AnimationTimeline* const timeline = OwnerDoc()->Timeline();
+ MOZ_ASSERT(timeline, "Timeline has not been set on the document yet");
+ // Iterate through all pseudo types and copy the effects from each of the
+ // other element's effect sets into this element's effect set.
+ for (PseudoStyleType pseudoType :
+ {PseudoStyleType::NotPseudo, PseudoStyleType::before,
+ PseudoStyleType::after, PseudoStyleType::marker}) {
+ // If the element has an effect set for this pseudo type (or not pseudo)
+ // then copy the effects and animation properties.
+ if (auto* const effects = EffectSet::Get(&aOther, pseudoType)) {
+ auto* const clonedEffects = EffectSet::GetOrCreate(this, pseudoType);
+ for (KeyframeEffect* const effect : *effects) {
+ auto* animation = effect->GetAnimation();
+ if (animation->AsCSSTransition()) {
+ // Don't clone transitions, for compat with other browsers.
+ continue;
+ }
+ // Clone the effect.
+ RefPtr<KeyframeEffect> clonedEffect = new KeyframeEffect(
+ OwnerDoc(), OwningAnimationTarget{this, pseudoType}, *effect);
+
+ // Clone the animation
+ RefPtr<Animation> clonedAnimation = Animation::ClonePausedAnimation(
+ OwnerDoc()->GetParentObject(), *animation, *clonedEffect,
+ *timeline);
+ if (!clonedAnimation) {
+ continue;
+ }
+ clonedEffects->AddEffect(*clonedEffect);
+ }
+ }
+ }
+}
+
+void Element::GetInnerHTML(nsAString& aInnerHTML, OOMReporter& aError) {
+ GetMarkup(false, aInnerHTML);
+}
+
+void Element::SetInnerHTML(const nsAString& aInnerHTML,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) {
+ SetInnerHTMLInternal(aInnerHTML, aError);
+}
+
+void Element::GetOuterHTML(nsAString& aOuterHTML) {
+ GetMarkup(true, aOuterHTML);
+}
+
+void Element::SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ if (parent->NodeType() == DOCUMENT_NODE) {
+ aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (OwnerDoc()->IsHTMLDocument()) {
+ nsAtom* localName;
+ int32_t namespaceID;
+ if (parent->IsElement()) {
+ localName = parent->NodeInfo()->NameAtom();
+ namespaceID = parent->NodeInfo()->NamespaceID();
+ } else {
+ NS_ASSERTION(
+ parent->NodeType() == DOCUMENT_FRAGMENT_NODE,
+ "How come the parent isn't a document, a fragment or an element?");
+ localName = nsGkAtoms::body;
+ namespaceID = kNameSpaceID_XHTML;
+ }
+ RefPtr<DocumentFragment> fragment = new (OwnerDoc()->NodeInfoManager())
+ DocumentFragment(OwnerDoc()->NodeInfoManager());
+ nsContentUtils::ParseFragmentHTML(
+ aOuterHTML, fragment, localName, namespaceID,
+ OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
+ parent->ReplaceChild(*fragment, *this, aError);
+ return;
+ }
+
+ nsCOMPtr<nsINode> context;
+ if (parent->IsElement()) {
+ context = parent;
+ } else {
+ NS_ASSERTION(
+ parent->NodeType() == DOCUMENT_FRAGMENT_NODE,
+ "How come the parent isn't a document, a fragment or an element?");
+ RefPtr<mozilla::dom::NodeInfo> info =
+ OwnerDoc()->NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::body, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
+ context = NS_NewHTMLBodyElement(info.forget(), FROM_PARSER_FRAGMENT);
+ }
+
+ RefPtr<DocumentFragment> fragment = nsContentUtils::CreateContextualFragment(
+ context, aOuterHTML, true, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ parent->ReplaceChild(*fragment, *this, aError);
+}
+
+enum nsAdjacentPosition { eBeforeBegin, eAfterBegin, eBeforeEnd, eAfterEnd };
+
+void Element::InsertAdjacentHTML(const nsAString& aPosition,
+ const nsAString& aText, ErrorResult& aError) {
+ nsAdjacentPosition position;
+ if (aPosition.LowerCaseEqualsLiteral("beforebegin")) {
+ position = eBeforeBegin;
+ } else if (aPosition.LowerCaseEqualsLiteral("afterbegin")) {
+ position = eAfterBegin;
+ } else if (aPosition.LowerCaseEqualsLiteral("beforeend")) {
+ position = eBeforeEnd;
+ } else if (aPosition.LowerCaseEqualsLiteral("afterend")) {
+ position = eAfterEnd;
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIContent> destination;
+ if (position == eBeforeBegin || position == eAfterEnd) {
+ destination = GetParent();
+ if (!destination) {
+ aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ } else {
+ destination = this;
+ }
+
+ // mozAutoDocUpdate keeps the owner document alive. Therefore, using a raw
+ // pointer here is safe.
+ Document* const doc = OwnerDoc();
+
+ // Needed when insertAdjacentHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, true);
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ // Parse directly into destination if possible
+ if (doc->IsHTMLDocument() && !OwnerDoc()->MayHaveDOMMutationObservers() &&
+ (position == eBeforeEnd || (position == eAfterEnd && !GetNextSibling()) ||
+ (position == eAfterBegin && !GetFirstChild()))) {
+ int32_t oldChildCount = destination->GetChildCount();
+ int32_t contextNs = destination->GetNameSpaceID();
+ nsAtom* contextLocal = destination->NodeInfo()->NameAtom();
+ if (contextLocal == nsGkAtoms::html && contextNs == kNameSpaceID_XHTML) {
+ // For compat with IE6 through IE9. Willful violation of HTML5 as of
+ // 2011-04-06. CreateContextualFragment does the same already.
+ // Spec bug: http://www.w3.org/Bugs/Public/show_bug.cgi?id=12434
+ contextLocal = nsGkAtoms::body;
+ }
+ aError = nsContentUtils::ParseFragmentHTML(
+ aText, destination, contextLocal, contextNs,
+ doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
+ // HTML5 parser has notified, but not fired mutation events.
+ nsContentUtils::FireMutationEventsForDirectParsing(doc, destination,
+ oldChildCount);
+ return;
+ }
+
+ // couldn't parse directly
+ RefPtr<DocumentFragment> fragment = nsContentUtils::CreateContextualFragment(
+ destination, aText, true, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Suppress assertion about node removal mutation events that can't have
+ // listeners anyway, because no one has had the chance to register mutation
+ // listeners on the fragment that comes from the parser.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ switch (position) {
+ case eBeforeBegin:
+ destination->InsertBefore(*fragment, this, aError);
+ break;
+ case eAfterBegin:
+ static_cast<nsINode*>(this)->InsertBefore(*fragment, GetFirstChild(),
+ aError);
+ break;
+ case eBeforeEnd:
+ static_cast<nsINode*>(this)->AppendChild(*fragment, aError);
+ break;
+ case eAfterEnd:
+ destination->InsertBefore(*fragment, GetNextSibling(), aError);
+ break;
+ }
+}
+
+nsINode* Element::InsertAdjacent(const nsAString& aWhere, nsINode* aNode,
+ ErrorResult& aError) {
+ if (aWhere.LowerCaseEqualsLiteral("beforebegin")) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return nullptr;
+ }
+ parent->InsertBefore(*aNode, this, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("afterbegin")) {
+ nsCOMPtr<nsINode> refNode = GetFirstChild();
+ static_cast<nsINode*>(this)->InsertBefore(*aNode, refNode, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("beforeend")) {
+ static_cast<nsINode*>(this)->AppendChild(*aNode, aError);
+ } else if (aWhere.LowerCaseEqualsLiteral("afterend")) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return nullptr;
+ }
+ nsCOMPtr<nsINode> refNode = GetNextSibling();
+ parent->InsertBefore(*aNode, refNode, aError);
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ return aError.Failed() ? nullptr : aNode;
+}
+
+Element* Element::InsertAdjacentElement(const nsAString& aWhere,
+ Element& aElement,
+ ErrorResult& aError) {
+ nsINode* newNode = InsertAdjacent(aWhere, &aElement, aError);
+ MOZ_ASSERT(!newNode || newNode->IsElement());
+
+ return newNode ? newNode->AsElement() : nullptr;
+}
+
+void Element::InsertAdjacentText(const nsAString& aWhere,
+ const nsAString& aData, ErrorResult& aError) {
+ RefPtr<nsTextNode> textNode = OwnerDoc()->CreateTextNode(aData);
+ InsertAdjacent(aWhere, textNode, aError);
+}
+
+TextEditor* Element::GetTextEditorInternal() {
+ TextControlElement* textControlElement = TextControlElement::FromNode(this);
+ return textControlElement ? MOZ_KnownLive(textControlElement)->GetTextEditor()
+ : nullptr;
+}
+
+nsresult Element::SetBoolAttr(nsAtom* aAttr, bool aValue) {
+ if (aValue) {
+ return SetAttr(kNameSpaceID_None, aAttr, u""_ns, true);
+ }
+
+ return UnsetAttr(kNameSpaceID_None, aAttr, true);
+}
+
+void Element::GetEnumAttr(nsAtom* aAttr, const char* aDefault,
+ nsAString& aResult) const {
+ GetEnumAttr(aAttr, aDefault, aDefault, aResult);
+}
+
+void Element::GetEnumAttr(nsAtom* aAttr, const char* aDefaultMissing,
+ const char* aDefaultInvalid,
+ nsAString& aResult) const {
+ const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
+
+ aResult.Truncate();
+
+ if (!attrVal) {
+ if (aDefaultMissing) {
+ AppendASCIItoUTF16(nsDependentCString(aDefaultMissing), aResult);
+ } else {
+ SetDOMStringToNull(aResult);
+ }
+ } else {
+ if (attrVal->Type() == nsAttrValue::eEnum) {
+ attrVal->GetEnumString(aResult, true);
+ } else if (aDefaultInvalid) {
+ AppendASCIItoUTF16(nsDependentCString(aDefaultInvalid), aResult);
+ }
+ }
+}
+
+void Element::SetOrRemoveNullableStringAttr(nsAtom* aName,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ if (DOMStringIsNull(aValue)) {
+ UnsetAttr(aName, aError);
+ } else {
+ SetAttr(aName, aValue, aError);
+ }
+}
+
+Directionality Element::GetComputedDirectionality() const {
+ if (nsIFrame* frame = GetPrimaryFrame()) {
+ return frame->StyleVisibility()->mDirection == StyleDirection::Ltr
+ ? eDir_LTR
+ : eDir_RTL;
+ }
+
+ return GetDirectionality();
+}
+
+float Element::FontSizeInflation() {
+ nsIFrame* frame = GetPrimaryFrame();
+ if (!frame) {
+ return -1.0;
+ }
+
+ if (nsLayoutUtils::FontSizeInflationEnabled(frame->PresContext())) {
+ return nsLayoutUtils::FontSizeInflationFor(frame);
+ }
+
+ return 1.0;
+}
+
+void Element::GetImplementedPseudoElement(nsAString& aPseudo) const {
+ PseudoStyleType pseudoType = GetPseudoElementType();
+ if (pseudoType == PseudoStyleType::NotPseudo) {
+ return SetDOMStringToNull(aPseudo);
+ }
+ nsDependentAtomString pseudo(nsCSSPseudoElements::GetPseudoAtom(pseudoType));
+
+ // We want to use the modern syntax (::placeholder, etc), but the atoms only
+ // contain one semi-colon.
+ MOZ_ASSERT(pseudo.Length() > 2 && pseudo[0] == ':' && pseudo[1] != ':');
+
+ aPseudo.Truncate();
+ aPseudo.SetCapacity(pseudo.Length() + 1);
+ aPseudo.Append(':');
+ aPseudo.Append(pseudo);
+}
+
+ReferrerPolicy Element::GetReferrerPolicyAsEnum() const {
+ if (IsHTMLElement()) {
+ return ReferrerPolicyFromAttr(GetParsedAttr(nsGkAtoms::referrerpolicy));
+ }
+ return ReferrerPolicy::_empty;
+}
+
+ReferrerPolicy Element::ReferrerPolicyFromAttr(
+ const nsAttrValue* aValue) const {
+ if (aValue && aValue->Type() == nsAttrValue::eEnum) {
+ return ReferrerPolicy(aValue->GetEnumValue());
+ }
+ return ReferrerPolicy::_empty;
+}
+
+already_AddRefed<nsDOMStringMap> Element::Dataset() {
+ nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mDataset) {
+ // mDataset is a weak reference so assignment will not AddRef.
+ // AddRef is called before returning the pointer.
+ slots->mDataset = new nsDOMStringMap(this);
+ }
+
+ RefPtr<nsDOMStringMap> ret = slots->mDataset;
+ return ret.forget();
+}
+
+void Element::ClearDataset() {
+ nsDOMSlots* slots = GetExistingDOMSlots();
+
+ MOZ_ASSERT(slots && slots->mDataset,
+ "Slots should exist and dataset should not be null.");
+ slots->mDataset = nullptr;
+}
+
+enum nsPreviousIntersectionThreshold {
+ eUninitialized = -2,
+ eNonIntersecting = -1
+};
+
+static void IntersectionObserverPropertyDtor(void* aObject,
+ nsAtom* aPropertyName,
+ void* aPropertyValue,
+ void* aData) {
+ auto* element = static_cast<Element*>(aObject);
+ auto* observers = static_cast<IntersectionObserverList*>(aPropertyValue);
+ for (DOMIntersectionObserver* observer : observers->Keys()) {
+ observer->UnlinkTarget(*element);
+ }
+ delete observers;
+}
+
+void Element::RegisterIntersectionObserver(DOMIntersectionObserver* aObserver) {
+ IntersectionObserverList* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+
+ if (!observers) {
+ observers = new IntersectionObserverList();
+ observers->InsertOrUpdate(aObserver, eUninitialized);
+ SetProperty(nsGkAtoms::intersectionobserverlist, observers,
+ IntersectionObserverPropertyDtor, /* aTransfer = */ true);
+ return;
+ }
+
+ // Value can be:
+ // -2: Makes sure next calculated threshold always differs, leading to a
+ // notification task being scheduled.
+ // -1: Non-intersecting.
+ // >= 0: Intersecting, valid index of aObserver->mThresholds.
+ observers->LookupOrInsert(aObserver, eUninitialized);
+}
+
+void Element::UnregisterIntersectionObserver(
+ DOMIntersectionObserver* aObserver) {
+ auto* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+ if (observers) {
+ observers->Remove(aObserver);
+ if (observers->IsEmpty()) {
+ RemoveProperty(nsGkAtoms::intersectionobserverlist);
+ }
+ }
+}
+
+void Element::UnlinkIntersectionObservers() {
+ // IntersectionObserverPropertyDtor takes care of the hard work.
+ RemoveProperty(nsGkAtoms::intersectionobserverlist);
+}
+
+bool Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver,
+ int32_t aThreshold) {
+ auto* observers = static_cast<IntersectionObserverList*>(
+ GetProperty(nsGkAtoms::intersectionobserverlist));
+ if (!observers) {
+ return false;
+ }
+ bool updated = false;
+ if (auto entry = observers->Lookup(aObserver)) {
+ updated = entry.Data() != aThreshold;
+ entry.Data() = aThreshold;
+ }
+ return updated;
+}
+
+template <class T>
+void Element::GetCustomInterface(nsGetterAddRefs<T> aResult) {
+ nsCOMPtr<nsISupports> iface = CustomElementRegistry::CallGetCustomInterface(
+ this, NS_GET_TEMPLATE_IID(T));
+ if (iface) {
+ if (NS_SUCCEEDED(CallQueryInterface(iface, static_cast<T**>(aResult)))) {
+ return;
+ }
+ }
+}
+
+void Element::ClearServoData(Document* aDoc) {
+ MOZ_ASSERT(aDoc);
+ if (HasServoData()) {
+ Servo_Element_ClearData(this);
+ } else {
+ UnsetFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME);
+ }
+ // Since this element is losing its servo data, nothing under it may have
+ // servo data either, so we can forget restyles rooted at this element. This
+ // is necessary for correctness, since we invoke ClearServoData in various
+ // places where an element's flattened tree parent changes, and such a change
+ // may also make an element invalid to be used as a restyle root.
+ if (aDoc->GetServoRestyleRoot() == this) {
+ aDoc->ClearServoRestyleRoot();
+ }
+}
+
+bool Element::IsAutoPopover() const {
+ const auto* htmlElement = nsGenericHTMLElement::FromNode(this);
+ return htmlElement &&
+ htmlElement->GetPopoverAttributeState() == PopoverAttributeState::Auto;
+}
+
+bool Element::IsPopoverOpen() const {
+ const auto* htmlElement = nsGenericHTMLElement::FromNode(this);
+ return htmlElement && htmlElement->PopoverOpen();
+}
+
+Element* Element::GetTopmostPopoverAncestor() const {
+ const Element* newPopover = this;
+
+ nsTHashMap<nsPtrHashKey<const Element>, size_t> popoverPositions;
+ size_t index = 0;
+ for (Element* popover : OwnerDoc()->AutoPopoverList()) {
+ popoverPositions.LookupOrInsert(popover, index++);
+ }
+ popoverPositions.LookupOrInsert(newPopover, index);
+
+ Element* topmostPopoverAncestor = nullptr;
+
+ auto checkAncestor = [&](const Element* candidate) {
+ if (!candidate) {
+ return;
+ }
+ Element* candidateAncestor = candidate->GetNearestInclusiveOpenPopover();
+ if (!candidateAncestor) {
+ return;
+ }
+ size_t candidatePosition;
+ if (popoverPositions.Get(candidateAncestor, &candidatePosition)) {
+ size_t topmostPosition;
+ if (!topmostPopoverAncestor ||
+ (popoverPositions.Get(topmostPopoverAncestor, &topmostPosition) &&
+ topmostPosition < candidatePosition)) {
+ topmostPopoverAncestor = candidateAncestor;
+ }
+ }
+ };
+
+ checkAncestor(newPopover->GetFlattenedTreeParentElement());
+
+ // https://github.com/whatwg/html/issues/9160
+ RefPtr<Element> invoker = newPopover->GetPopoverData()->GetInvoker();
+ checkAncestor(invoker);
+
+ return topmostPopoverAncestor;
+}
+
+ElementAnimationData& Element::CreateAnimationData() {
+ MOZ_ASSERT(!GetAnimationData());
+ SetMayHaveAnimations();
+ auto* slots = ExtendedDOMSlots();
+ slots->mAnimations = MakeUnique<ElementAnimationData>();
+ return *slots->mAnimations;
+}
+
+PopoverData& Element::CreatePopoverData() {
+ MOZ_ASSERT(!GetPopoverData());
+ auto* slots = ExtendedDOMSlots();
+ slots->mPopoverData = MakeUnique<PopoverData>();
+ return *slots->mPopoverData;
+}
+
+void Element::ClearPopoverData() {
+ nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ if (slots) {
+ slots->mPopoverData = nullptr;
+ }
+}
+
+void Element::SetCustomElementData(UniquePtr<CustomElementData> aData) {
+ SetHasCustomElementData();
+
+ if (aData->mState != CustomElementData::State::eCustom) {
+ SetDefined(false);
+ }
+
+ nsExtendedDOMSlots* slots = ExtendedDOMSlots();
+ MOZ_ASSERT(!slots->mCustomElementData,
+ "Custom element data may not be changed once set.");
+#if DEBUG
+ // We assert only XUL usage, since web may pass whatever as 'is' value
+ if (NodeInfo()->NamespaceID() == kNameSpaceID_XUL) {
+ nsAtom* name = NodeInfo()->NameAtom();
+ nsAtom* type = aData->GetCustomElementType();
+ // Check to see if the tag name is a dashed name.
+ if (nsContentUtils::IsNameWithDash(name)) {
+ // Assert that a tag name with dashes is always an autonomous custom
+ // element.
+ MOZ_ASSERT(type == name);
+ } else {
+ // Could still be an autonomous custom element with a non-dashed tag name.
+ // Need the check below for sure.
+ if (type != name) {
+ // Assert that the name of the built-in custom element type is always
+ // a dashed name.
+ MOZ_ASSERT(nsContentUtils::IsNameWithDash(type));
+ }
+ }
+ }
+#endif
+ slots->mCustomElementData = std::move(aData);
+}
+
+CustomElementDefinition* Element::GetCustomElementDefinition() const {
+ CustomElementData* data = GetCustomElementData();
+ if (!data) {
+ return nullptr;
+ }
+
+ return data->GetCustomElementDefinition();
+}
+
+void Element::SetCustomElementDefinition(CustomElementDefinition* aDefinition) {
+ CustomElementData* data = GetCustomElementData();
+ MOZ_ASSERT(data);
+
+ data->SetCustomElementDefinition(aDefinition);
+}
+
+already_AddRefed<nsIDOMXULButtonElement> Element::AsXULButton() {
+ nsCOMPtr<nsIDOMXULButtonElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULContainerElement> Element::AsXULContainer() {
+ nsCOMPtr<nsIDOMXULContainerElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULContainerItemElement> Element::AsXULContainerItem() {
+ nsCOMPtr<nsIDOMXULContainerItemElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULControlElement> Element::AsXULControl() {
+ nsCOMPtr<nsIDOMXULControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULMenuListElement> Element::AsXULMenuList() {
+ nsCOMPtr<nsIDOMXULMenuListElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULMultiSelectControlElement>
+Element::AsXULMultiSelectControl() {
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULRadioGroupElement> Element::AsXULRadioGroup() {
+ nsCOMPtr<nsIDOMXULRadioGroupElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULRelatedElement> Element::AsXULRelated() {
+ nsCOMPtr<nsIDOMXULRelatedElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULSelectControlElement> Element::AsXULSelectControl() {
+ nsCOMPtr<nsIDOMXULSelectControlElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIDOMXULSelectControlItemElement>
+Element::AsXULSelectControlItem() {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIBrowser> Element::AsBrowser() {
+ nsCOMPtr<nsIBrowser> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+already_AddRefed<nsIAutoCompletePopup> Element::AsAutoCompletePopup() {
+ nsCOMPtr<nsIAutoCompletePopup> value;
+ GetCustomInterface(getter_AddRefs(value));
+ return value.forget();
+}
+
+nsPresContext* Element::GetPresContext(PresContextFor aFor) {
+ // Get the document
+ Document* doc =
+ (aFor == eForComposedDoc) ? GetComposedDoc() : GetUncomposedDoc();
+ if (doc) {
+ return doc->GetPresContext();
+ }
+
+ return nullptr;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoElementMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoElementMallocEnclosingSizeOf)
+
+void Element::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ FragmentOrElement::AddSizeOfExcludingThis(aSizes, aNodeSize);
+ *aNodeSize += mAttrs.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ if (HasServoData()) {
+ // Measure the ElementData object itself.
+ aSizes.mLayoutElementDataObjects +=
+ aSizes.mState.mMallocSizeOf(mServoData.Get());
+
+ // Measure mServoData, excluding the ComputedValues. This measurement
+ // counts towards the element's size. We use ServoElementMallocSizeOf and
+ // ServoElementMallocEnclosingSizeOf rather than |aState.mMallocSizeOf| to
+ // better distinguish in DMD's output the memory measured within Servo
+ // code.
+ *aNodeSize += Servo_Element_SizeOfExcludingThisAndCVs(
+ ServoElementMallocSizeOf, ServoElementMallocEnclosingSizeOf,
+ &aSizes.mState.mSeenPtrs, this);
+
+ // Now measure just the ComputedValues (and style structs) under
+ // mServoData. This counts towards the relevant fields in |aSizes|.
+ if (auto* style = Servo_Element_GetMaybeOutOfDateStyle(this)) {
+ if (!aSizes.mState.HaveSeenPtr(style)) {
+ style->AddSizeOfIncludingThis(aSizes, &aSizes.mLayoutComputedValuesDom);
+ }
+
+ for (size_t i = 0; i < PseudoStyle::kEagerPseudoCount; i++) {
+ if (auto* style = Servo_Element_GetMaybeOutOfDatePseudoStyle(this, i)) {
+ if (!aSizes.mState.HaveSeenPtr(style)) {
+ style->AddSizeOfIncludingThis(aSizes,
+ &aSizes.mLayoutComputedValuesDom);
+ }
+ }
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+static bool BitsArePropagated(const Element* aElement, uint32_t aBits,
+ nsINode* aRestyleRoot) {
+ const Element* curr = aElement;
+ while (curr) {
+ if (curr == aRestyleRoot) {
+ return true;
+ }
+ if (!curr->HasAllFlags(aBits)) {
+ return false;
+ }
+ nsINode* parentNode = curr->GetParentNode();
+ curr = curr->GetFlattenedTreeParentElementForStyle();
+ MOZ_ASSERT_IF(!curr,
+ parentNode == aElement->OwnerDoc() ||
+ parentNode == parentNode->OwnerDoc()->GetRootElement());
+ }
+ return true;
+}
+#endif
+
+static inline void AssertNoBitsPropagatedFrom(nsINode* aRoot) {
+#ifdef DEBUG
+ if (!aRoot || !aRoot->IsElement()) {
+ return;
+ }
+
+ auto* element = aRoot->GetFlattenedTreeParentElementForStyle();
+ while (element) {
+ MOZ_ASSERT(!element->HasAnyOfFlags(Element::kAllServoDescendantBits));
+ element = element->GetFlattenedTreeParentElementForStyle();
+ }
+#endif
+}
+
+// Sets `aBits` on `aElement` and all of its flattened-tree ancestors up to and
+// including aStopAt or the root element (whichever is encountered first), and
+// as long as `aBitsToStopAt` isn't found anywhere in the chain.
+static inline Element* PropagateBits(Element* aElement, uint32_t aBits,
+ nsINode* aStopAt, uint32_t aBitsToStopAt) {
+ Element* curr = aElement;
+ while (curr && !curr->HasAllFlags(aBitsToStopAt)) {
+ curr->SetFlags(aBits);
+ if (curr == aStopAt) {
+ break;
+ }
+ curr = curr->GetFlattenedTreeParentElementForStyle();
+ }
+
+ if (aBitsToStopAt != aBits && curr) {
+ curr->SetFlags(aBits);
+ }
+
+ return curr;
+}
+
+// Notes that a given element is "dirty" with respect to the given descendants
+// bit (which may be one of dirty descendants, dirty animation descendants, or
+// need frame construction for descendants).
+//
+// This function operates on the dirty element itself, despite the fact that the
+// bits are generally used to describe descendants. This allows restyle roots
+// to be scoped as tightly as possible. On the first call to NoteDirtyElement
+// since the last restyle, we don't set any descendant bits at all, and just set
+// the element as the restyle root.
+//
+// Because the style traversal handles multiple tasks (styling,
+// animation-ticking, and lazy frame construction), there are potentially three
+// separate kinds of dirtiness to track. Rather than maintaining three separate
+// restyle roots, we use a single root, and always bubble it up to be the
+// nearest common ancestor of all the dirty content in the tree. This means that
+// we need to track the types of dirtiness that the restyle root corresponds to,
+// so SetServoRestyleRoot accepts a bitfield along with an element.
+//
+// The overall algorithm is as follows:
+// * When the first dirty element is noted, we just set as the restyle root.
+// * When additional dirty elements are noted, we propagate the given bit up
+// the tree, until we either reach the restyle root or the document root.
+// * If we reach the document root, we then propagate the bits associated with
+// the restyle root up the tree until we cross the path of the new root. Once
+// we find this common ancestor, we record it as the restyle root, and then
+// clear the bits between the new restyle root and the document root.
+// * If we have dirty content beneath multiple "document style traversal roots"
+// (which are the main DOM + each piece of document-level native-anoymous
+// content), we set the restyle root to the nsINode of the document itself.
+// This is the bail-out case where we traverse everything.
+//
+// Note that, since we track a root, we try to optimize the case where an
+// element under the current root is dirtied, that's why we don't trivially use
+// `nsContentUtils::GetCommonFlattenedTreeAncestorForStyle`.
+static void NoteDirtyElement(Element* aElement, uint32_t aBits) {
+ MOZ_ASSERT(aElement->IsInComposedDoc());
+
+ // Check the existing root early on, since it may allow us to short-circuit
+ // before examining the parent chain.
+ Document* doc = aElement->GetComposedDoc();
+ nsINode* existingRoot = doc->GetServoRestyleRoot();
+ if (existingRoot == aElement) {
+ doc->SetServoRestyleRootDirtyBits(doc->GetServoRestyleRootDirtyBits() |
+ aBits);
+ return;
+ }
+
+ nsINode* parent = aElement->GetFlattenedTreeParentNodeForStyle();
+ if (!parent) {
+ // The element is not in the flattened tree, bail.
+ return;
+ }
+
+ if (MOZ_LIKELY(parent->IsElement())) {
+ // If our parent is unstyled, we can inductively assume that it will be
+ // traversed when the time is right, and that the traversal will reach us
+ // when it happens. Nothing left to do.
+ if (!parent->AsElement()->HasServoData()) {
+ return;
+ }
+
+ // Similarly, if our parent already has the bit we're propagating, we can
+ // assume everything is already set up.
+ if (parent->HasAllFlags(aBits)) {
+ return;
+ }
+
+ // If the parent is styled but is display:none, we're done.
+ //
+ // We can't check for a frame here, since <frame> elements inside <frameset>
+ // still need to generate a frame, even if they're display: none. :(
+ //
+ // The servo traversal doesn't keep style data under display: none subtrees,
+ // so in order for it to not need to cleanup each time anything happens in a
+ // display: none subtree, we keep it clean.
+ //
+ // Also, we can't be much more smarter about using the parent's frame in
+ // order to avoid work here, because since the style system keeps style data
+ // in, e.g., subtrees under a leaf frame, missing restyles and such in there
+ // has observable behavior via getComputedStyle, for example.
+ if (Servo_Element_IsDisplayNone(parent->AsElement())) {
+ return;
+ }
+ }
+
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+
+ MOZ_ASSERT(parent->IsElement() || parent == doc);
+
+ // The bit checks below rely on this to arrive to useful conclusions about the
+ // shape of the tree.
+ AssertNoBitsPropagatedFrom(existingRoot);
+
+ // If there's no existing restyle root, or if the root is already aElement,
+ // just note the root+bits and return.
+ if (!existingRoot) {
+ doc->SetServoRestyleRoot(aElement, aBits);
+ return;
+ }
+
+ // There is an existing restyle root - walk up the tree from our element,
+ // propagating bits as we go.
+ const bool reachedDocRoot =
+ !parent->IsElement() ||
+ !PropagateBits(parent->AsElement(), aBits, existingRoot, aBits);
+
+ uint32_t existingBits = doc->GetServoRestyleRootDirtyBits();
+ if (!reachedDocRoot || existingRoot == doc) {
+ // We're a descendant of the existing root. All that's left to do is to
+ // make sure the bit we propagated is also registered on the root.
+ doc->SetServoRestyleRoot(existingRoot, existingBits | aBits);
+ } else {
+ // We reached the root without crossing the pre-existing restyle root. We
+ // now need to find the nearest common ancestor, so climb up from the
+ // existing root, extending bits along the way.
+ Element* rootParent = existingRoot->GetFlattenedTreeParentElementForStyle();
+ // We can stop at the first occurrence of `aBits` in order to find the
+ // common ancestor.
+ if (Element* commonAncestor =
+ PropagateBits(rootParent, existingBits, aElement, aBits)) {
+ MOZ_ASSERT(commonAncestor == aElement ||
+ commonAncestor ==
+ nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
+ aElement, rootParent));
+
+ // We found a common ancestor. Make that the new style root, and clear the
+ // bits between the new style root and the document root.
+ doc->SetServoRestyleRoot(commonAncestor, existingBits | aBits);
+ Element* curr = commonAncestor;
+ while ((curr = curr->GetFlattenedTreeParentElementForStyle())) {
+ MOZ_ASSERT(curr->HasAllFlags(aBits));
+ curr->UnsetFlags(aBits);
+ }
+ AssertNoBitsPropagatedFrom(commonAncestor);
+ } else {
+ // We didn't find a common ancestor element. That means we're descended
+ // from two different document style roots, so the common ancestor is the
+ // document.
+ doc->SetServoRestyleRoot(doc, existingBits | aBits);
+ }
+ }
+
+ // See the comment in Document::SetServoRestyleRoot about the !IsElement()
+ // check there. Same justification here.
+ MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
+ !doc->GetServoRestyleRoot()->IsElement() ||
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ aElement, doc->GetServoRestyleRoot()));
+ MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
+ !doc->GetServoRestyleRoot()->IsElement() || !parent->IsElement() ||
+ BitsArePropagated(parent->AsElement(), aBits,
+ doc->GetServoRestyleRoot()));
+ MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBits);
+}
+
+void Element::NoteDirtySubtreeForServo() {
+ MOZ_ASSERT(IsInComposedDoc());
+ MOZ_ASSERT(HasServoData());
+
+ Document* doc = GetComposedDoc();
+ nsINode* existingRoot = doc->GetServoRestyleRoot();
+ uint32_t existingBits =
+ existingRoot ? doc->GetServoRestyleRootDirtyBits() : 0;
+
+ if (existingRoot && existingRoot->IsElement() && existingRoot != this &&
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ existingRoot->AsElement(), this)) {
+ PropagateBits(
+ existingRoot->AsElement()->GetFlattenedTreeParentElementForStyle(),
+ existingBits, this, existingBits);
+
+ doc->ClearServoRestyleRoot();
+ }
+
+ NoteDirtyElement(this,
+ existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteDirtyForServo() {
+ NoteDirtyElement(this, ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteAnimationOnlyDirtyForServo() {
+ NoteDirtyElement(this,
+ ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO);
+}
+
+void Element::NoteDescendantsNeedFramesForServo() {
+ // Since lazy frame construction can be required for non-element nodes, this
+ // Note() method operates on the parent of the frame-requiring content, unlike
+ // the other Note() methods above (which operate directly on the element that
+ // needs processing).
+ NoteDirtyElement(this, NODE_DESCENDANTS_NEED_FRAMES);
+ SetFlags(NODE_DESCENDANTS_NEED_FRAMES);
+}
+
+double Element::FirstLineBoxBSize() const {
+ const nsBlockFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (!frame) {
+ return 0.0;
+ }
+ nsBlockFrame::ConstLineIterator line = frame->LinesBegin();
+ nsBlockFrame::ConstLineIterator lineEnd = frame->LinesEnd();
+ return line != lineEnd
+ ? nsPresContext::AppUnitsToDoubleCSSPixels(line->BSize())
+ : 0.0;
+}
+
+// static
+nsAtom* Element::GetEventNameForAttr(nsAtom* aAttr) {
+ if (aAttr == nsGkAtoms::onwebkitanimationend) {
+ return nsGkAtoms::onwebkitAnimationEnd;
+ }
+ if (aAttr == nsGkAtoms::onwebkitanimationiteration) {
+ return nsGkAtoms::onwebkitAnimationIteration;
+ }
+ if (aAttr == nsGkAtoms::onwebkitanimationstart) {
+ return nsGkAtoms::onwebkitAnimationStart;
+ }
+ if (aAttr == nsGkAtoms::onwebkittransitionend) {
+ return nsGkAtoms::onwebkitTransitionEnd;
+ }
+ return aAttr;
+}
+
+void Element::RegUnRegAccessKey(bool aDoReg) {
+ // first check to see if we have an access key
+ nsAutoString accessKey;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+ if (accessKey.IsEmpty()) {
+ return;
+ }
+
+ // We have an access key, so get the ESM from the pres context.
+ if (nsPresContext* presContext = GetPresContext(eForComposedDoc)) {
+ EventStateManager* esm = presContext->EventStateManager();
+
+ // Register or unregister as appropriate.
+ if (aDoReg) {
+ esm->RegisterAccessKey(this, (uint32_t)accessKey.First());
+ } else {
+ esm->UnregisterAccessKey(this, (uint32_t)accessKey.First());
+ }
+ }
+}
+
+void Element::SetHTML(const nsAString& aInnerHTML,
+ const SetHTMLOptions& aOptions, ErrorResult& aError) {
+ // Throw for disallowed elements
+ if (IsHTMLElement(nsGkAtoms::script)) {
+ aError.ThrowTypeError("This does not work on <script> elements");
+ return;
+ }
+ if (IsHTMLElement(nsGkAtoms::object)) {
+ aError.ThrowTypeError("This does not work on <object> elements");
+ return;
+ }
+ if (IsHTMLElement(nsGkAtoms::iframe)) {
+ aError.ThrowTypeError("This does not work on <iframe> elements");
+ return;
+ }
+
+ // Keep "this" alive should be guaranteed by the caller, and also the content
+ // of a template element (if this is one) should never been released from this
+ // during this call. Therefore, using raw pointer here is safe.
+ FragmentOrElement* target = this;
+ // Handle template case.
+ if (target->IsTemplateElement()) {
+ DocumentFragment* frag =
+ static_cast<HTMLTemplateElement*>(target)->Content();
+ MOZ_ASSERT(frag);
+ target = frag;
+ }
+
+ // TODO: Avoid parsing and implement a fast-path for non-markup input,
+ // Filed as bug 1731215.
+
+ // mozAutoSubtreeModified keeps the owner document alive. Therefore, using a
+ // raw pointer here is safe.
+ Document* const doc = target->OwnerDoc();
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ target->FireNodeRemovedForChildren();
+
+ // Needed when innerHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, true);
+
+ // Remove childnodes.
+ nsAutoMutationBatch mb(target, true, false);
+ while (target->HasChildren()) {
+ target->RemoveChildNode(target->GetFirstChild(), true);
+ }
+ mb.RemovalDone();
+
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ FragmentOrElement* parseContext = this;
+ if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(parseContext)) {
+ // Fix up the context to be the host of the ShadowRoot. See
+ // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml setter step 1.
+ parseContext = shadowRoot->GetHost();
+ }
+
+ // We MUST NOT cause any requests during parsing, so we'll
+ // create an inert Document and parse into a new DocumentFragment.
+ RefPtr<Document> inertDoc;
+ nsAtom* contextLocalName = parseContext->NodeInfo()->NameAtom();
+ int32_t contextNameSpaceID = parseContext->GetNameSpaceID();
+ ElementCreationOptionsOrString options;
+ RefPtr<DocumentFragment> fragment;
+ if (doc->IsHTMLDocument()) {
+ inertDoc = nsContentUtils::CreateInertHTMLDocument(nullptr);
+ if (!inertDoc) {
+ aError = NS_ERROR_FAILURE;
+ return;
+ }
+ fragment = new (inertDoc->NodeInfoManager())
+ DocumentFragment(inertDoc->NodeInfoManager());
+
+ aError = nsContentUtils::ParseFragmentHTML(aInnerHTML, fragment,
+ contextLocalName,
+ contextNameSpaceID, false, true);
+
+ } else if (doc->IsXMLDocument()) {
+ inertDoc = nsContentUtils::CreateInertXMLDocument(nullptr);
+ if (!inertDoc) {
+ aError = NS_ERROR_FAILURE;
+ return;
+ }
+ fragment = new (inertDoc->NodeInfoManager())
+ DocumentFragment(inertDoc->NodeInfoManager());
+
+ // TODO(freddyb) `nsContentUtils::CreateContextualFragment` is actually
+ // collecting a ton of stacks to get in an (X)HTMLish state.
+ // I'm afraid we might need that too. Ugh.
+ AutoTArray<nsString, 0> emptyTagStack;
+ aError =
+ nsContentUtils::ParseFragmentXML(aInnerHTML, inertDoc, emptyTagStack,
+ true, -1, getter_AddRefs(fragment));
+ }
+
+ if (!aError.Failed()) {
+ // Suppress assertion about node removal mutation events that can't have
+ // listeners anyway, because no one has had the chance to register
+ // mutation listeners on the fragment that comes from the parser.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ int32_t oldChildCount = static_cast<int32_t>(target->GetChildCount());
+
+ RefPtr<Sanitizer> sanitizer;
+ if (!aOptions.mSanitizer.WasPassed()) {
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ if (!global) {
+ aError.ThrowInvalidStateError("Missing owner global.");
+ return;
+ }
+ sanitizer = Sanitizer::New(global, {}, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ } else {
+ sanitizer = &aOptions.mSanitizer.Value();
+ }
+
+ sanitizer->SanitizeFragment(fragment, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ target->AppendChild(*fragment, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ mb.NodesAdded();
+ nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
+ oldChildCount);
+ }
+}
+
+bool Element::Translate() const {
+ if (const auto* parent = Element::FromNodeOrNull(mParent)) {
+ return parent->Translate();
+ }
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Element.h b/dom/base/Element.h
new file mode 100644
index 0000000000..509d789d43
--- /dev/null
+++ b/dom/base/Element.h
@@ -0,0 +1,2288 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all element classes; this provides an implementation
+ * of DOM Core's Element, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#ifndef mozilla_dom_Element_h__
+#define mozilla_dom_Element_h__
+
+#include <cstdio>
+#include <cstdint>
+#include <cstdlib>
+#include <utility>
+#include "AttrArray.h"
+#include "ErrorList.h"
+#include "Units.h"
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/RustCell.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/RustTypes.h"
+#include "mozilla/dom/ShadowRootBinding.h"
+#include "nsAtom.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsCaseTreatment.h"
+#include "nsChangeHint.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsHashKeys.h"
+#include "nsIContent.h"
+#include "nsID.h"
+#include "nsINode.h"
+#include "nsLiteralString.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsTLiteralString.h"
+#include "nscore.h"
+
+class JSObject;
+class mozAutoDocUpdate;
+class nsAttrName;
+class nsAttrValueOrString;
+class nsContentList;
+class nsDOMAttributeMap;
+class nsDOMCSSAttributeDeclaration;
+class nsDOMStringMap;
+class nsDOMTokenList;
+class nsFocusManager;
+class nsGenericHTMLFormControlElementWithState;
+class nsGlobalWindowInner;
+class nsGlobalWindowOuter;
+class nsIAutoCompletePopup;
+class nsIBrowser;
+class nsIDOMXULButtonElement;
+class nsIDOMXULContainerElement;
+class nsIDOMXULContainerItemElement;
+class nsIDOMXULControlElement;
+class nsIDOMXULMenuListElement;
+class nsIDOMXULMultiSelectControlElement;
+class nsIDOMXULRadioGroupElement;
+class nsIDOMXULRelatedElement;
+class nsIDOMXULSelectControlElement;
+class nsIDOMXULSelectControlItemElement;
+class nsIFrame;
+class nsIHTMLCollection;
+class nsIMozBrowserFrame;
+class nsIPrincipal;
+class nsIScreen;
+class nsIScrollableFrame;
+class nsIURI;
+class nsMappedAttributes;
+class nsPresContext;
+class nsWindowSizes;
+struct JSContext;
+struct ServoNodeData;
+template <class E>
+class nsTArray;
+template <class T>
+class nsGetterAddRefs;
+
+namespace mozilla {
+class DeclarationBlock;
+class ErrorResult;
+class OOMReporter;
+class SMILAttr;
+struct MutationClosureData;
+class TextEditor;
+namespace css {
+struct URLValue;
+} // namespace css
+namespace dom {
+struct CheckVisibilityOptions;
+struct CustomElementData;
+struct SetHTMLOptions;
+struct GetAnimationsOptions;
+struct ScrollIntoViewOptions;
+struct ScrollToOptions;
+struct FocusOptions;
+struct ShadowRootInit;
+struct ScrollOptions;
+class Attr;
+class BooleanOrScrollIntoViewOptions;
+class Document;
+class DOMIntersectionObserver;
+class DOMMatrixReadOnly;
+class Element;
+class ElementOrCSSPseudoElement;
+class PopoverData;
+class Promise;
+class Sanitizer;
+class ShadowRoot;
+class UnrestrictedDoubleOrKeyframeAnimationOptions;
+template <typename T>
+class Optional;
+enum class CallerType : uint32_t;
+enum class ReferrerPolicy : uint8_t;
+typedef nsTHashMap<nsRefPtrHashKey<DOMIntersectionObserver>, int32_t>
+ IntersectionObserverList;
+} // namespace dom
+} // namespace mozilla
+
+// Declared here because of include hell.
+extern "C" bool Servo_Element_IsDisplayContents(const mozilla::dom::Element*);
+
+already_AddRefed<nsContentList> NS_GetContentList(nsINode* aRootNode,
+ int32_t aMatchNameSpaceId,
+ const nsAString& aTagname);
+
+#define ELEMENT_FLAG_BIT(n_) \
+ NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Element-specific flags
+enum : uint32_t {
+ // Whether this node has dirty descendants for Servo's style system.
+ ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO = ELEMENT_FLAG_BIT(0),
+ // Whether this node has dirty descendants for animation-only restyle for
+ // Servo's style system.
+ ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO = ELEMENT_FLAG_BIT(1),
+
+ // Whether the element has been snapshotted due to attribute or state changes
+ // by the Servo restyle manager.
+ ELEMENT_HAS_SNAPSHOT = ELEMENT_FLAG_BIT(2),
+
+ // Whether the element has already handled its relevant snapshot.
+ //
+ // Used by the servo restyle process in order to accurately track whether the
+ // style of an element is up-to-date, even during the same restyle process.
+ ELEMENT_HANDLED_SNAPSHOT = ELEMENT_FLAG_BIT(3),
+
+ // If this flag is set on an element, that means that it is a HTML datalist
+ // element or has a HTML datalist element ancestor.
+ ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR = ELEMENT_FLAG_BIT(4),
+
+ // Remaining bits are for subclasses
+ ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 5
+};
+
+#undef ELEMENT_FLAG_BIT
+
+// Make sure we have space for our bits
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
+
+namespace mozilla {
+enum class PseudoStyleType : uint8_t;
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+class EventChainVisitor;
+class EventListenerManager;
+class EventStateManager;
+
+namespace dom {
+
+struct CustomElementDefinition;
+class Animation;
+class CustomElementRegistry;
+class Link;
+class DOMRect;
+class DOMRectList;
+class Flex;
+class Grid;
+
+// IID for the dom::Element interface
+#define NS_ELEMENT_IID \
+ { \
+ 0xc67ed254, 0xfd3b, 0x4b10, { \
+ 0x96, 0xa2, 0xc5, 0x8b, 0x7b, 0x64, 0x97, 0xd1 \
+ } \
+ }
+
+#define REFLECT_DOMSTRING_ATTR(method, attr) \
+ void Get##method(nsAString& aValue) const { \
+ GetAttr(nsGkAtoms::attr, aValue); \
+ } \
+ void Set##method(const nsAString& aValue, ErrorResult& aRv) { \
+ SetAttr(nsGkAtoms::attr, aValue, aRv); \
+ }
+
+class Element : public FragmentOrElement {
+ public:
+#ifdef MOZILLA_INTERNAL_API
+ explicit Element(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : FragmentOrElement(std::move(aNodeInfo)),
+ mState(ElementState::READONLY | ElementState::DEFINED) {
+ MOZ_ASSERT(mNodeInfo->NodeType() == ELEMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+ SetIsElement();
+ }
+
+ ~Element() {
+ NS_ASSERTION(!HasServoData(), "expected ServoData to be cleared earlier");
+ }
+
+#endif // MOZILLA_INTERNAL_API
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ELEMENT_IID)
+
+ NS_DECL_ADDSIZEOFEXCLUDINGTHIS
+
+ NS_IMPL_FROMNODE_HELPER(Element, IsElement())
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ /**
+ * Method to get the full state of this element. See dom/base/rust/lib.rs for
+ * the possible bits that could be set here.
+ */
+ ElementState State() const {
+ // mState is maintained by having whoever might have changed it
+ // call UpdateState() or one of the other mState mutators.
+ return mState;
+ }
+
+ /**
+ * Ask this element to update its state. If aNotify is false, then
+ * state change notifications will not be dispatched; in that
+ * situation it is the caller's responsibility to dispatch them.
+ *
+ * In general, aNotify should only be false if we're guaranteed that
+ * the element can't have a frame no matter what its style is
+ * (e.g. if we're in the middle of adding it to the document or
+ * removing it from the document).
+ */
+ void UpdateState(bool aNotify);
+
+ /**
+ * Method to update mState with link state information. This does not notify.
+ */
+ void UpdateLinkState(ElementState aState);
+
+ /**
+ * Returns the current disabled state of the element.
+ */
+ bool IsDisabled() const { return State().HasState(ElementState::DISABLED); }
+
+ virtual int32_t TabIndexDefault() { return -1; }
+
+ /**
+ * Get tabIndex of this element. If not found, return TabIndexDefault.
+ */
+ int32_t TabIndex();
+
+ /**
+ * Get the parsed value of tabindex attribute.
+ */
+ Maybe<int32_t> GetTabIndexAttrValue();
+
+ /**
+ * Set tabIndex value to this element.
+ */
+ void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError);
+
+ /**
+ * Sets the ShadowRoot binding for this element. The contents of the
+ * binding is rendered in place of this node's children.
+ *
+ * @param aShadowRoot The ShadowRoot to be bound to this element.
+ */
+ void SetShadowRoot(ShadowRoot* aShadowRoot);
+
+ void SetLastRememberedBSize(float aBSize);
+ void SetLastRememberedISize(float aISize);
+ void RemoveLastRememberedBSize();
+ void RemoveLastRememberedISize();
+
+ /**
+ * Make focus on this element.
+ */
+ // TODO: Convert Focus() to MOZ_CAN_RUN_SCRIPT and get rid of the
+ // kungFuDeathGrip in it.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void Focus(const FocusOptions& aOptions,
+ const CallerType aCallerType,
+ ErrorResult& aError);
+
+ /**
+ * Show blur and clear focus.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void Blur(mozilla::ErrorResult& aError);
+
+ /**
+ * The style state of this element. This is the real state of the element
+ * with any style locks applied for pseudo-class inspecting.
+ */
+ ElementState StyleState() const {
+ if (!HasLockedStyleStates()) {
+ return mState;
+ }
+ return StyleStateFromLocks();
+ }
+
+ /**
+ * StyleStateLocks is used to specify which event states should be locked,
+ * and whether they should be locked to on or off.
+ */
+ struct StyleStateLocks {
+ // mLocks tracks which event states should be locked.
+ ElementState mLocks;
+ // mValues tracks if the locked state should be on or off.
+ ElementState mValues;
+ };
+
+ /**
+ * The style state locks applied to this element.
+ */
+ StyleStateLocks LockedStyleStates() const;
+
+ /**
+ * Add a style state lock on this element.
+ * aEnabled is the value to lock the given state bits to.
+ */
+ void LockStyleStates(ElementState aStates, bool aEnabled);
+
+ /**
+ * Remove a style state lock on this element.
+ */
+ void UnlockStyleStates(ElementState aStates);
+
+ /**
+ * Clear all style state locks on this element.
+ */
+ void ClearStyleStateLocks();
+
+ /**
+ * Accessors for the state of our dir attribute.
+ */
+ bool HasDirAuto() const {
+ return State().HasState(ElementState::HAS_DIR_ATTR_LIKE_AUTO);
+ }
+
+ /**
+ * Elements with dir="rtl" or dir="ltr".
+ */
+ bool HasFixedDir() const {
+ return State().HasAtLeastOneOfStates(ElementState::HAS_DIR_ATTR_LTR |
+ ElementState::HAS_DIR_ATTR_RTL);
+ }
+
+ /**
+ * Get the inline style declaration, if any, for this element.
+ */
+ DeclarationBlock* GetInlineStyleDeclaration() const;
+
+ /**
+ * Get the mapped attributes, if any, for this element.
+ */
+ const nsMappedAttributes* GetMappedAttributes() const;
+
+ void ClearMappedServoStyle() { mAttrs.ClearMappedServoStyle(); }
+
+ /**
+ * InlineStyleDeclarationWillChange is called before SetInlineStyleDeclaration
+ * so that the element implementation can access the old style attribute
+ * value.
+ */
+ virtual void InlineStyleDeclarationWillChange(MutationClosureData& aData);
+
+ /**
+ * Set the inline style declaration for this element.
+ */
+ virtual nsresult SetInlineStyleDeclaration(DeclarationBlock& aDeclaration,
+ MutationClosureData& aData);
+
+ /**
+ * Get the SMIL override style declaration for this element. If the
+ * rule hasn't been created, this method simply returns null.
+ */
+ DeclarationBlock* GetSMILOverrideStyleDeclaration();
+
+ /**
+ * Set the SMIL override style declaration for this element. This method will
+ * notify the document's pres context, so that the style changes will be
+ * noticed.
+ */
+ void SetSMILOverrideStyleDeclaration(DeclarationBlock&);
+
+ /**
+ * Returns a new SMILAttr that allows the caller to animate the given
+ * attribute on this element.
+ */
+ virtual UniquePtr<SMILAttr> GetAnimatedAttr(int32_t aNamespaceID,
+ nsAtom* aName);
+
+ /**
+ * Get the SMIL override style for this element. This is a style declaration
+ * that is applied *after* the inline style, and it can be used e.g. to store
+ * animated style values.
+ *
+ * Note: This method is analogous to the 'GetStyle' method in
+ * nsGenericHTMLElement and nsStyledElement.
+ */
+ nsDOMCSSAttributeDeclaration* SMILOverrideStyle();
+
+ /**
+ * Returns if the element is labelable as per HTML specification.
+ */
+ virtual bool IsLabelable() const;
+
+ /**
+ * Returns if the element is interactive content as per HTML specification.
+ */
+ virtual bool IsInteractiveHTMLContent() const;
+
+ /**
+ * Returns |this| as an nsIMozBrowserFrame* if the element is a frame or
+ * iframe element.
+ *
+ * We have this method, rather than using QI, so that we can use it during
+ * the servo traversal, where we can't QI DOM nodes because of non-thread-safe
+ * refcounts.
+ */
+ virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() { return nullptr; }
+
+ /**
+ * Is the attribute named stored in the mapped attributes?
+ *
+ * // XXXbz we use this method in HasAttributeDependentStyle, so svg
+ * returns true here even though it stores nothing in the mapped
+ * attributes.
+ */
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const;
+
+ /**
+ * Get a hint that tells the style system what to do when
+ * an attribute on this node changes, if something needs to happen
+ * in response to the change *other* than the result of what is
+ * mapped into style data via any type of style rule.
+ */
+ virtual nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const;
+
+ inline Directionality GetDirectionality() const {
+ if (HasFlag(NODE_HAS_DIRECTION_RTL)) {
+ return eDir_RTL;
+ }
+
+ if (HasFlag(NODE_HAS_DIRECTION_LTR)) {
+ return eDir_LTR;
+ }
+
+ return eDir_NotSet;
+ }
+
+ inline void SetDirectionality(Directionality aDir, bool aNotify) {
+ UnsetFlags(NODE_ALL_DIRECTION_FLAGS);
+ if (!aNotify) {
+ RemoveStatesSilently(ElementState::DIR_STATES);
+ }
+
+ switch (aDir) {
+ case (eDir_RTL):
+ SetFlags(NODE_HAS_DIRECTION_RTL);
+ if (!aNotify) {
+ AddStatesSilently(ElementState::RTL);
+ }
+ break;
+
+ case (eDir_LTR):
+ SetFlags(NODE_HAS_DIRECTION_LTR);
+ if (!aNotify) {
+ AddStatesSilently(ElementState::LTR);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Only call UpdateState if we need to notify, because we call
+ * SetDirectionality for every element, and UpdateState is very very slow
+ * for some elements.
+ */
+ if (aNotify) {
+ UpdateState(true);
+ }
+ }
+
+ Directionality GetComputedDirectionality() const;
+
+ static const uint32_t kAllServoDescendantBits =
+ ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO |
+ ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO |
+ NODE_DESCENDANTS_NEED_FRAMES;
+
+ /**
+ * Notes that something in the given subtree of this element needs dirtying,
+ * and that all the relevant dirty bits have already been propagated up to the
+ * element.
+ *
+ * This is important because `NoteDirtyForServo` uses the dirty bits to reason
+ * about the shape of the tree, so we can't just call into there.
+ */
+ void NoteDirtySubtreeForServo();
+
+ void NoteDirtyForServo();
+ void NoteAnimationOnlyDirtyForServo();
+ void NoteDescendantsNeedFramesForServo();
+
+ bool HasDirtyDescendantsForServo() const {
+ return HasFlag(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void SetHasDirtyDescendantsForServo() {
+ SetFlags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void UnsetHasDirtyDescendantsForServo() {
+ UnsetFlags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ bool HasAnimationOnlyDirtyDescendantsForServo() const {
+ return HasFlag(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void SetHasAnimationOnlyDirtyDescendantsForServo() {
+ SetFlags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void UnsetHasAnimationOnlyDirtyDescendantsForServo() {
+ UnsetFlags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ bool HasServoData() const { return !!mServoData.Get(); }
+
+ void ClearServoData() { ClearServoData(GetComposedDoc()); }
+ void ClearServoData(Document* aDocument);
+
+ PopoverData* GetPopoverData() const {
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mPopoverData.get() : nullptr;
+ }
+
+ PopoverData& EnsurePopoverData() {
+ if (auto* popoverData = GetPopoverData()) {
+ return *popoverData;
+ }
+ return CreatePopoverData();
+ }
+
+ bool IsAutoPopover() const;
+ bool IsPopoverOpen() const;
+
+ /**
+ * https://html.spec.whatwg.org/multipage/popover.html#topmost-popover-ancestor
+ */
+ mozilla::dom::Element* GetTopmostPopoverAncestor() const;
+
+ ElementAnimationData* GetAnimationData() const {
+ if (!MayHaveAnimations()) {
+ return nullptr;
+ }
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mAnimations.get() : nullptr;
+ }
+
+ ElementAnimationData& EnsureAnimationData() {
+ if (auto* anim = GetAnimationData()) {
+ return *anim;
+ }
+ return CreateAnimationData();
+ }
+
+ private:
+ ElementAnimationData& CreateAnimationData();
+ PopoverData& CreatePopoverData();
+
+ public:
+ void ClearPopoverData();
+
+ /**
+ * Gets the custom element data used by web components custom element.
+ * Custom element data is created at the first attempt to enqueue a callback.
+ *
+ * @return The custom element data or null if none.
+ */
+ CustomElementData* GetCustomElementData() const {
+ if (!HasCustomElementData()) {
+ return nullptr;
+ }
+
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mCustomElementData.get() : nullptr;
+ }
+
+ /**
+ * Sets the custom element data, ownership of the
+ * callback data is taken by this element.
+ *
+ * @param aData The custom element data.
+ */
+ void SetCustomElementData(UniquePtr<CustomElementData> aData);
+
+ /**
+ * Gets the custom element definition used by web components custom element.
+ *
+ * @return The custom element definition or null if element is not a custom
+ * element or custom element is not defined yet.
+ */
+ CustomElementDefinition* GetCustomElementDefinition() const;
+
+ /**
+ * Sets the custom element definition, called when custom element is created
+ * or upgraded.
+ *
+ * @param aDefinition The custom element definition.
+ */
+ virtual void SetCustomElementDefinition(CustomElementDefinition* aDefinition);
+
+ const AttrArray& GetAttrs() const { return mAttrs; }
+
+ void SetDefined(bool aSet) {
+ if (aSet) {
+ AddStates(ElementState::DEFINED);
+ } else {
+ RemoveStates(ElementState::DEFINED);
+ }
+ }
+
+ // AccessibilityRole
+ REFLECT_DOMSTRING_ATTR(Role, role)
+
+ // AriaAttributes
+ REFLECT_DOMSTRING_ATTR(AriaAtomic, aria_atomic)
+ REFLECT_DOMSTRING_ATTR(AriaAutoComplete, aria_autocomplete)
+ REFLECT_DOMSTRING_ATTR(AriaBusy, aria_busy)
+ REFLECT_DOMSTRING_ATTR(AriaChecked, aria_checked)
+ REFLECT_DOMSTRING_ATTR(AriaColCount, aria_colcount)
+ REFLECT_DOMSTRING_ATTR(AriaColIndex, aria_colindex)
+ REFLECT_DOMSTRING_ATTR(AriaColIndexText, aria_colindextext)
+ REFLECT_DOMSTRING_ATTR(AriaColSpan, aria_colspan)
+ REFLECT_DOMSTRING_ATTR(AriaCurrent, aria_current)
+ REFLECT_DOMSTRING_ATTR(AriaDescription, aria_description)
+ REFLECT_DOMSTRING_ATTR(AriaDisabled, aria_disabled)
+ REFLECT_DOMSTRING_ATTR(AriaExpanded, aria_expanded)
+ REFLECT_DOMSTRING_ATTR(AriaHasPopup, aria_haspopup)
+ REFLECT_DOMSTRING_ATTR(AriaHidden, aria_hidden)
+ REFLECT_DOMSTRING_ATTR(AriaInvalid, aria_invalid)
+ REFLECT_DOMSTRING_ATTR(AriaKeyShortcuts, aria_keyshortcuts)
+ REFLECT_DOMSTRING_ATTR(AriaLabel, aria_label)
+ REFLECT_DOMSTRING_ATTR(AriaLevel, aria_level)
+ REFLECT_DOMSTRING_ATTR(AriaLive, aria_live)
+ REFLECT_DOMSTRING_ATTR(AriaModal, aria_modal)
+ REFLECT_DOMSTRING_ATTR(AriaMultiLine, aria_multiline)
+ REFLECT_DOMSTRING_ATTR(AriaMultiSelectable, aria_multiselectable)
+ REFLECT_DOMSTRING_ATTR(AriaOrientation, aria_orientation)
+ REFLECT_DOMSTRING_ATTR(AriaPlaceholder, aria_placeholder)
+ REFLECT_DOMSTRING_ATTR(AriaPosInSet, aria_posinset)
+ REFLECT_DOMSTRING_ATTR(AriaPressed, aria_pressed)
+ REFLECT_DOMSTRING_ATTR(AriaReadOnly, aria_readonly)
+ REFLECT_DOMSTRING_ATTR(AriaRelevant, aria_relevant)
+ REFLECT_DOMSTRING_ATTR(AriaRequired, aria_required)
+ REFLECT_DOMSTRING_ATTR(AriaRoleDescription, aria_roledescription)
+ REFLECT_DOMSTRING_ATTR(AriaRowCount, aria_rowcount)
+ REFLECT_DOMSTRING_ATTR(AriaRowIndex, aria_rowindex)
+ REFLECT_DOMSTRING_ATTR(AriaRowIndexText, aria_rowindextext)
+ REFLECT_DOMSTRING_ATTR(AriaRowSpan, aria_rowspan)
+ REFLECT_DOMSTRING_ATTR(AriaSelected, aria_selected)
+ REFLECT_DOMSTRING_ATTR(AriaSetSize, aria_setsize)
+ REFLECT_DOMSTRING_ATTR(AriaSort, aria_sort)
+ REFLECT_DOMSTRING_ATTR(AriaValueMax, aria_valuemax)
+ REFLECT_DOMSTRING_ATTR(AriaValueMin, aria_valuemin)
+ REFLECT_DOMSTRING_ATTR(AriaValueNow, aria_valuenow)
+ REFLECT_DOMSTRING_ATTR(AriaValueText, aria_valuetext)
+
+ protected:
+ /**
+ * Method to get the _intrinsic_ content state of this element. This is the
+ * state that is independent of the element's presentation. To get the full
+ * the possible bits that could be set here.
+ */
+ virtual ElementState IntrinsicState() const;
+
+ /**
+ * Method to add state bits. This should be called from subclass
+ * constructors to set up our event state correctly at construction
+ * time and other places where we don't want to notify a state
+ * change.
+ */
+ void AddStatesSilently(ElementState aStates) { mState |= aStates; }
+
+ /**
+ * Method to remove state bits. This should be called from subclass
+ * constructors to set up our event state correctly at construction
+ * time and other places where we don't want to notify a state
+ * change.
+ */
+ void RemoveStatesSilently(ElementState aStates) { mState &= ~aStates; }
+
+ already_AddRefed<ShadowRoot> AttachShadowInternal(ShadowRootMode,
+ ErrorResult& aError);
+
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ nsIScrollableFrame* GetScrollFrame(nsIFrame** aStyledFrame = nullptr,
+ FlushType aFlushType = FlushType::Layout);
+
+ private:
+ // Need to allow the ESM, nsGlobalWindow, and the focus manager
+ // and Document to set our state
+ friend class mozilla::EventStateManager;
+ friend class mozilla::dom::Document;
+ friend class ::nsGlobalWindowInner;
+ friend class ::nsGlobalWindowOuter;
+ friend class ::nsFocusManager;
+
+ // Allow CusomtElementRegistry to call AddStates.
+ friend class CustomElementRegistry;
+
+ // Also need to allow Link to call UpdateLinkState.
+ friend class Link;
+
+ void NotifyStateChange(ElementState aStates);
+
+ void NotifyStyleStateChange(ElementState aStates);
+
+ // Style state computed from element's state and style locks.
+ ElementState StyleStateFromLocks() const;
+
+ protected:
+ // Methods for the ESM, nsGlobalWindow, focus manager and Document to
+ // manage state bits.
+ // These will handle setting up script blockers when they notify, so no need
+ // to do it in the callers unless desired. States passed here must only be
+ // those in EXTERNALLY_MANAGED_STATES.
+ void AddStates(ElementState aStates) {
+ MOZ_ASSERT(!aStates.HasAtLeastOneOfStates(ElementState::INTRINSIC_STATES),
+ "Should only be adding externally-managed states here");
+ ElementState old = mState;
+ AddStatesSilently(aStates);
+ NotifyStateChange(old ^ mState);
+ }
+ void RemoveStates(ElementState aStates) {
+ MOZ_ASSERT(!aStates.HasAtLeastOneOfStates(ElementState::INTRINSIC_STATES),
+ "Should only be removing externally-managed states here");
+ ElementState old = mState;
+ RemoveStatesSilently(aStates);
+ NotifyStateChange(old ^ mState);
+ }
+ void ToggleStates(ElementState aStates, bool aNotify) {
+ MOZ_ASSERT(!aStates.HasAtLeastOneOfStates(ElementState::INTRINSIC_STATES),
+ "Should only be removing externally-managed states here");
+ mState ^= aStates;
+ if (aNotify) {
+ NotifyStateChange(aStates);
+ }
+ }
+
+ public:
+ // Public methods to manage state bits in MANUALLY_MANAGED_STATES.
+ void AddManuallyManagedStates(ElementState aStates) {
+ MOZ_ASSERT(ElementState::MANUALLY_MANAGED_STATES.HasAllStates(aStates),
+ "Should only be adding manually-managed states here");
+ AddStates(aStates);
+ }
+ void RemoveManuallyManagedStates(ElementState aStates) {
+ MOZ_ASSERT(ElementState::MANUALLY_MANAGED_STATES.HasAllStates(aStates),
+ "Should only be removing manually-managed states here");
+ RemoveStates(aStates);
+ }
+
+ void UpdateEditableState(bool aNotify) override;
+
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+
+ void UnbindFromTree(bool aNullParent = true) override;
+
+ /**
+ * Normalizes an attribute name and returns it as a nodeinfo if an attribute
+ * with that name exists. This method is intended for character case
+ * conversion if the content object is case insensitive (e.g. HTML). Returns
+ * the nodeinfo of the attribute with the specified name if one exists or
+ * null otherwise.
+ *
+ * @param aStr the unparsed attribute string
+ * @return the node info. May be nullptr.
+ */
+ already_AddRefed<mozilla::dom::NodeInfo> GetExistingAttrNameFromQName(
+ const nsAString& aStr) const;
+
+ /**
+ * Helper for SetAttr/SetParsedAttr. This method will return true if aNotify
+ * is true or there are mutation listeners that must be triggered, the
+ * attribute is currently set, and the new value that is about to be set is
+ * different to the current value. As a perf optimization the new and old
+ * values will not actually be compared if we aren't notifying and we don't
+ * have mutation listeners (in which case it's cheap to just return false
+ * and let the caller go ahead and set the value).
+ * @param aOldValue [out] Set to the old value of the attribute, but only if
+ * there are event listeners. If set, the type of aOldValue will be either
+ * nsAttrValue::eString or nsAttrValue::eAtom.
+ * @param aModType [out] Set to MutationEvent_Binding::MODIFICATION or to
+ * MutationEvent_Binding::ADDITION, but only if this helper returns true
+ * @param aHasListeners [out] Set to true if there are mutation event
+ * listeners listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+ * @param aOldValueSet [out] Indicates whether an old attribute value has been
+ * stored in aOldValue. The bool will be set to true if a value was stored.
+ */
+ bool MaybeCheckSameAttrVal(int32_t aNamespaceID, const nsAtom* aName,
+ const nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue, bool aNotify,
+ nsAttrValue& aOldValue, uint8_t* aModType,
+ bool* aHasListeners, bool* aOldValueSet);
+
+ /**
+ * Notifies mutation listeners if aNotify is true, there are mutation
+ * listeners, and the attribute value is changing.
+ *
+ * @param aNamespaceID The namespace of the attribute
+ * @param aName The local name of the attribute
+ * @param aPrefix The prefix of the attribute
+ * @param aValue The value that the attribute is being changed to
+ * @param aNotify If true, mutation listeners will be notified if they exist
+ * and the attribute value is changing
+ * @param aOldValue [out] Set to the old value of the attribute, but only if
+ * there are event listeners. If set, the type of aOldValue will be either
+ * nsAttrValue::eString or nsAttrValue::eAtom.
+ * @param aModType [out] Set to MutationEvent_Binding::MODIFICATION or to
+ * MutationEvent_Binding::ADDITION, but only if this helper returns true
+ * @param aHasListeners [out] Set to true if there are mutation event
+ * listeners listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+ * @param aOldValueSet [out] Indicates whether an old attribute value has been
+ * stored in aOldValue. The bool will be set to true if a value was stored.
+ */
+ bool OnlyNotifySameValueSet(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue, bool aNotify,
+ nsAttrValue& aOldValue, uint8_t* aModType,
+ bool* aHasListeners, bool* aOldValueSet);
+
+ /**
+ * Sets the class attribute to a value that contains no whitespace.
+ * Assumes that we are not notifying and that the attribute hasn't been
+ * set previously.
+ */
+ nsresult SetSingleClassFromParser(nsAtom* aSingleClassName);
+
+ // aParsedValue receives the old value of the attribute. That's useful if
+ // either the input or output value of aParsedValue is StoresOwnData.
+ nsresult SetParsedAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix,
+ nsAttrValue& aParsedValue, bool aNotify);
+ /**
+ * Get the current value of the attribute. This returns a form that is
+ * suitable for passing back into SetAttr.
+ *
+ * @param aNameSpaceID the namespace of the attr (defaults to
+ kNameSpaceID_None in the overload that omits this arg)
+ * @param aName the name of the attr
+ * @param aResult the value (may legitimately be the empty string) [OUT]
+ * @returns true if the attribute was set (even when set to empty string)
+ * false when not set.
+ * GetAttr is not inlined on purpose, to keep down codesize from all the
+ * inlined nsAttrValue bits for C++ callers.
+ */
+ bool GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
+ nsAString& aResult) const;
+
+ bool GetAttr(const nsAtom* aName, nsAString& aResult) const {
+ return GetAttr(kNameSpaceID_None, aName, aResult);
+ }
+
+ /**
+ * Determine if an attribute has been set (empty string or otherwise).
+ *
+ * @param aNameSpaceId the namespace id of the attribute (defaults to
+ kNameSpaceID_None in the overload that omits this arg)
+ * @param aAttr the attribute name
+ * @return whether an attribute exists
+ */
+ inline bool HasAttr(int32_t aNameSpaceID, const nsAtom* aName) const;
+
+ bool HasAttr(const nsAtom* aAttr) const {
+ return HasAttr(kNameSpaceID_None, aAttr);
+ }
+
+ /**
+ * Determine if an attribute has been set to a non-empty string value. If the
+ * attribute is not set at all, this will return false.
+ *
+ * @param aNameSpaceId the namespace id of the attribute (defaults to
+ * kNameSpaceID_None in the overload that omits this arg)
+ * @param aAttr the attribute name
+ */
+ inline bool HasNonEmptyAttr(int32_t aNameSpaceID, const nsAtom* aName) const;
+
+ bool HasNonEmptyAttr(const nsAtom* aAttr) const {
+ return HasNonEmptyAttr(kNameSpaceID_None, aAttr);
+ }
+
+ /**
+ * Test whether this Element's given attribute has the given value. If the
+ * attribute is not set at all, this will return false.
+ *
+ * @param aNameSpaceID The namespace ID of the attribute. Must not
+ * be kNameSpaceID_Unknown.
+ * @param aName The name atom of the attribute. Must not be null.
+ * @param aValue The value to compare to.
+ * @param aCaseSensitive Whether to do a case-sensitive compare on the value.
+ */
+ inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const;
+
+ /**
+ * Test whether this Element's given attribute has the given value. If the
+ * attribute is not set at all, this will return false.
+ *
+ * @param aNameSpaceID The namespace ID of the attribute. Must not
+ * be kNameSpaceID_Unknown.
+ * @param aName The name atom of the attribute. Must not be null.
+ * @param aValue The value to compare to. Must not be null.
+ * @param aCaseSensitive Whether to do a case-sensitive compare on the value.
+ */
+ bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAtom* aValue, nsCaseTreatment aCaseSensitive) const;
+
+ /**
+ * Check whether this Element's given attribute has one of a given list of
+ * values. If there is a match, we return the index in the list of the first
+ * matching value. If there was no attribute at all, then we return
+ * ATTR_MISSING. If there was an attribute but it didn't match, we return
+ * ATTR_VALUE_NO_MATCH. A non-negative result always indicates a match.
+ *
+ * @param aNameSpaceID The namespace ID of the attribute. Must not
+ * be kNameSpaceID_Unknown.
+ * @param aName The name atom of the attribute. Must not be null.
+ * @param aValues a nullptr-terminated array of pointers to atom values to
+ * test against.
+ * @param aCaseSensitive Whether to do a case-sensitive compare on the values.
+ * @return ATTR_MISSING, ATTR_VALUE_NO_MATCH or the non-negative index
+ * indicating the first value of aValues that matched
+ */
+ using AttrValuesArray = AttrArray::AttrValuesArray;
+ int32_t FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
+ AttrArray::AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const;
+
+ /**
+ * Set attribute values. All attribute values are assumed to have a
+ * canonical string representation that can be used for these
+ * methods. The SetAttr method is assumed to perform a translation
+ * of the canonical form into the underlying content specific
+ * form.
+ *
+ * @param aNameSpaceID the namespace of the attribute
+ * @param aName the name of the attribute
+ * @param aValue the value to set
+ * @param aNotify specifies how whether or not the document should be
+ * notified of the attribute change.
+ */
+ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAString& aValue,
+ bool aNotify) {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
+ }
+ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAString& aValue, bool aNotify) {
+ return SetAttr(aNameSpaceID, aName, aPrefix, aValue, nullptr, aNotify);
+ }
+ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal, bool aNotify) {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, aTriggeringPrincipal,
+ aNotify);
+ }
+
+ /**
+ * Set attribute values. All attribute values are assumed to have a
+ * canonical String representation that can be used for these
+ * methods. The SetAttr method is assumed to perform a translation
+ * of the canonical form into the underlying content specific
+ * form.
+ *
+ * @param aNameSpaceID the namespace of the attribute
+ * @param aName the name of the attribute
+ * @param aPrefix the prefix of the attribute
+ * @param aValue the value to set
+ * @param aMaybeScriptedPrincipal the principal of the scripted caller
+ * responsible for setting the attribute, or null if no scripted caller can be
+ * determined. A null value here does not guarantee that there is no
+ * scripted caller, but a non-null value does guarantee that a scripted
+ * caller with the given principal is directly responsible for the
+ * attribute change.
+ * @param aNotify specifies how whether or not the document should be
+ * notified of the attribute change.
+ */
+ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify);
+
+ /**
+ * Remove an attribute so that it is no longer explicitly specified.
+ *
+ * @param aNameSpaceID the namespace id of the attribute
+ * @param aName the name of the attribute to unset
+ * @param aNotify specifies whether or not the document should be
+ * notified of the attribute change
+ */
+ nsresult UnsetAttr(int32_t aNameSpaceID, nsAtom* aName, bool aNotify);
+
+ /**
+ * Get the namespace / name / prefix of a given attribute.
+ *
+ * @param aIndex the index of the attribute name
+ * @returns The name at the given index, or null if the index is
+ * out-of-bounds.
+ * @note The document returned by NodeInfo()->GetDocument() (if one is
+ * present) is *not* necessarily the owner document of the element.
+ * @note The pointer returned by this function is only valid until the
+ * next call of either GetAttrNameAt or SetAttr on the element.
+ */
+ const nsAttrName* GetAttrNameAt(uint32_t aIndex) const {
+ return mAttrs.GetSafeAttrNameAt(aIndex);
+ }
+
+ /**
+ * Same as above, but does not do out-of-bounds checks!
+ */
+ const nsAttrName* GetUnsafeAttrNameAt(uint32_t aIndex) const {
+ return mAttrs.AttrNameAt(aIndex);
+ }
+
+ /**
+ * Gets the attribute info (name and value) for this element at a given index.
+ */
+ BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const {
+ if (aIndex >= mAttrs.AttrCount()) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+
+ return mAttrs.AttrInfoAt(aIndex);
+ }
+
+ /**
+ * Get the number of all specified attributes.
+ *
+ * @return the number of attributes
+ */
+ uint32_t GetAttrCount() const { return mAttrs.AttrCount(); }
+
+ /**
+ * Get the class list of this element (this corresponds to the value of the
+ * class attribute). This may be null if there are no classes, but that's not
+ * guaranteed (e.g. we could have class="").
+ */
+ const nsAttrValue* GetClasses() const {
+ if (!MayHaveClass()) {
+ return nullptr;
+ }
+
+ if (IsSVGElement()) {
+ if (const nsAttrValue* value = GetSVGAnimatedClass()) {
+ return value;
+ }
+ }
+
+ return GetParsedAttr(nsGkAtoms::_class);
+ }
+
+#ifdef MOZ_DOM_LIST
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override {
+ List(out, aIndent, ""_ns);
+ }
+ virtual void DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const override;
+ void List(FILE* out, int32_t aIndent, const nsCString& aPrefix) const;
+ void ListAttributes(FILE* out) const;
+#endif
+
+ /**
+ * Append to aOutDescription a string describing the element and its
+ * attributes.
+ * If aShort is true, only the id and class attributes will be listed.
+ */
+ void Describe(nsAString& aOutDescription, bool aShort = false) const;
+
+ /*
+ * Attribute Mapping Helpers
+ */
+ struct MappedAttributeEntry {
+ const nsStaticAtom* const attribute;
+ };
+
+ /**
+ * A common method where you can just pass in a list of maps to check
+ * for attribute dependence. Most implementations of
+ * IsAttributeMapped should use this function as a default
+ * handler.
+ */
+ template <size_t N>
+ static bool FindAttributeDependence(
+ const nsAtom* aAttribute, const MappedAttributeEntry* const (&aMaps)[N]) {
+ return FindAttributeDependence(aAttribute, aMaps, N);
+ }
+
+ static nsStaticAtom* const* HTMLSVGPropertiesToTraverseAndUnlink();
+
+ private:
+ void DescribeAttribute(uint32_t index, nsAString& aOutDescription) const;
+
+ static bool FindAttributeDependence(const nsAtom* aAttribute,
+ const MappedAttributeEntry* const aMaps[],
+ uint32_t aMapCount);
+
+ protected:
+ inline bool GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
+ DOMString& aResult) const {
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown,
+ "must have a real namespace ID!");
+ MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
+ const nsAttrValue* val = mAttrs.GetAttr(aName, aNameSpaceID);
+ if (val) {
+ val->ToString(aResult);
+ return true;
+ }
+ // else DOMString comes pre-emptied.
+ return false;
+ }
+
+ public:
+ bool HasAttrs() const { return mAttrs.HasAttrs(); }
+
+ inline bool GetAttr(const nsAString& aName, DOMString& aResult) const {
+ MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
+ const nsAttrValue* val = mAttrs.GetAttr(aName);
+ if (val) {
+ val->ToString(aResult);
+ return true;
+ }
+ // else DOMString comes pre-emptied.
+ return false;
+ }
+
+ void GetTagName(nsAString& aTagName) const { aTagName = NodeName(); }
+ void GetId(nsAString& aId) const {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, aId);
+ }
+ void GetId(DOMString& aId) const {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, aId);
+ }
+ void SetId(const nsAString& aId) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::id, aId, true);
+ }
+ void GetClassName(nsAString& aClassName) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName);
+ }
+ void GetClassName(DOMString& aClassName) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName);
+ }
+ void SetClassName(const nsAString& aClassName) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName, true);
+ }
+
+ nsDOMTokenList* ClassList();
+ nsDOMTokenList* Part();
+
+ nsDOMAttributeMap* Attributes();
+
+ void GetAttributeNames(nsTArray<nsString>& aResult);
+
+ void GetAttribute(const nsAString& aName, nsAString& aReturn) {
+ DOMString str;
+ GetAttribute(aName, str);
+ str.ToString(aReturn);
+ }
+
+ void GetAttribute(const nsAString& aName, DOMString& aReturn);
+ void GetAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName, nsAString& aReturn);
+ bool ToggleAttribute(const nsAString& aName, const Optional<bool>& aForce,
+ nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError);
+ void SetAttribute(const nsAString& aName, const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError);
+ void SetAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName, const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError);
+ void SetAttribute(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aError) {
+ SetAttribute(aName, aValue, nullptr, aError);
+ }
+ /**
+ * This method creates a principal that subsumes this element's NodePrincipal
+ * and which has flags set for elevated permissions that devtools needs to
+ * operate on this element. The principal returned by this method is used by
+ * various devtools methods to permit otherwise blocked operations, without
+ * changing any other restrictions the NodePrincipal might have.
+ */
+ already_AddRefed<nsIPrincipal> CreateDevtoolsPrincipal();
+ void SetAttributeDevtools(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aError);
+ void SetAttributeDevtoolsNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ const nsAString& aValue, ErrorResult& aError);
+
+ void RemoveAttribute(const nsAString& aName, ErrorResult& aError);
+ void RemoveAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName, ErrorResult& aError);
+ bool HasAttribute(const nsAString& aName) const {
+ return InternalGetAttrNameFromQName(aName) != nullptr;
+ }
+ bool HasAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) const;
+ bool HasAttributes() const { return HasAttrs(); }
+ Element* Closest(const nsACString& aSelector, ErrorResult& aResult);
+ bool Matches(const nsACString& aSelector, ErrorResult& aError);
+ already_AddRefed<nsIHTMLCollection> GetElementsByTagName(
+ const nsAString& aQualifiedName);
+ already_AddRefed<nsIHTMLCollection> GetElementsByTagNameNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ ErrorResult& aError);
+ already_AddRefed<nsIHTMLCollection> GetElementsByClassName(
+ const nsAString& aClassNames);
+
+ /**
+ * Returns attribute associated element for the given attribute name, see
+ * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-element
+ */
+ Element* GetAttrAssociatedElement(nsAtom* aAttr) const;
+
+ /**
+ * Sets an attribute element for the given attribute.
+ * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element
+ */
+ void ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement);
+
+ PseudoStyleType GetPseudoElementType() const {
+ nsresult rv = NS_OK;
+ auto raw = GetProperty(nsGkAtoms::pseudoProperty, &rv);
+ if (rv == NS_PROPTABLE_PROP_NOT_THERE) {
+ return PseudoStyleType::NotPseudo;
+ }
+ return PseudoStyleType(reinterpret_cast<uintptr_t>(raw));
+ }
+
+ void SetPseudoElementType(PseudoStyleType aPseudo) {
+ static_assert(sizeof(PseudoStyleType) <= sizeof(uintptr_t),
+ "Need to be able to store this in a void*");
+ MOZ_ASSERT(PseudoStyle::IsPseudoElement(aPseudo));
+ SetProperty(nsGkAtoms::pseudoProperty, reinterpret_cast<void*>(aPseudo));
+ }
+
+ /**
+ * Return an array of all elements in the subtree rooted at this
+ * element that have grid container frames. This does not include
+ * pseudo-elements.
+ */
+ void GetElementsWithGrid(nsTArray<RefPtr<Element>>& aElements);
+
+ /**
+ * Provide a direct way to determine if this Element has visible
+ * scrollbars. Flushes layout.
+ */
+ MOZ_CAN_RUN_SCRIPT bool HasVisibleScrollbars();
+
+ private:
+ /**
+ * Implement the algorithm specified at
+ * https://dom.spec.whatwg.org/#insert-adjacent for both
+ * |insertAdjacentElement()| and |insertAdjacentText()| APIs.
+ */
+ nsINode* InsertAdjacent(const nsAString& aWhere, nsINode* aNode,
+ ErrorResult& aError);
+
+ public:
+ Element* InsertAdjacentElement(const nsAString& aWhere, Element& aElement,
+ ErrorResult& aError);
+
+ void InsertAdjacentText(const nsAString& aWhere, const nsAString& aData,
+ ErrorResult& aError);
+
+ void SetPointerCapture(int32_t aPointerId, ErrorResult& aError);
+ void ReleasePointerCapture(int32_t aPointerId, ErrorResult& aError);
+ bool HasPointerCapture(long aPointerId);
+ void SetCapture(bool aRetargetToElement);
+
+ void SetCaptureAlways(bool aRetargetToElement);
+
+ void ReleaseCapture();
+
+ already_AddRefed<Promise> RequestFullscreen(CallerType, ErrorResult&);
+ void RequestPointerLock(CallerType aCallerType);
+ Attr* GetAttributeNode(const nsAString& aName);
+ already_AddRefed<Attr> SetAttributeNode(Attr& aNewAttr, ErrorResult& aError);
+ already_AddRefed<Attr> RemoveAttributeNode(Attr& aOldAttr,
+ ErrorResult& aError);
+ Attr* GetAttributeNodeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName);
+ already_AddRefed<Attr> SetAttributeNodeNS(Attr& aNewAttr,
+ ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<DOMRectList> GetClientRects();
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<DOMRect> GetBoundingClientRect();
+
+ // Shadow DOM v1
+ already_AddRefed<ShadowRoot> AttachShadow(const ShadowRootInit& aInit,
+ ErrorResult& aError);
+ bool CanAttachShadowDOM() const;
+
+ enum class DelegatesFocus : bool { No, Yes };
+
+ already_AddRefed<ShadowRoot> AttachShadowWithoutNameChecks(
+ ShadowRootMode aMode, DelegatesFocus = DelegatesFocus::No,
+ SlotAssignmentMode aSlotAssignmentMode = SlotAssignmentMode::Named);
+
+ // Attach UA Shadow Root if it is not attached.
+ enum class NotifyUAWidgetSetup : bool { No, Yes };
+ void AttachAndSetUAShadowRoot(NotifyUAWidgetSetup = NotifyUAWidgetSetup::Yes,
+ DelegatesFocus = DelegatesFocus::No);
+
+ // Dispatch an event to UAWidgetsChild, triggering construction
+ // or onchange callback on the existing widget.
+ void NotifyUAWidgetSetupOrChange();
+
+ enum class UnattachShadowRoot {
+ No,
+ Yes,
+ };
+
+ // Dispatch an event to UAWidgetsChild, triggering UA Widget destruction.
+ // and optionally remove the shadow root.
+ void NotifyUAWidgetTeardown(UnattachShadowRoot = UnattachShadowRoot::Yes);
+
+ void UnattachShadow();
+
+ ShadowRoot* GetShadowRootByMode() const;
+ void SetSlot(const nsAString& aName, ErrorResult& aError);
+ void GetSlot(nsAString& aName);
+
+ ShadowRoot* GetShadowRoot() const {
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mShadowRoot.get() : nullptr;
+ }
+
+ const Maybe<float> GetLastRememberedBSize() const {
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mLastRememberedBSize : Nothing();
+ }
+ const Maybe<float> GetLastRememberedISize() const {
+ const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mLastRememberedISize : Nothing();
+ }
+ bool HasLastRememberedBSize() const {
+ return GetLastRememberedBSize().isSome();
+ }
+ bool HasLastRememberedISize() const {
+ return GetLastRememberedISize().isSome();
+ }
+
+ const Maybe<ContentRelevancy> GetContentRelevancy() const {
+ const auto* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mContentRelevancy : Nothing();
+ }
+ void SetContentRelevancy(ContentRelevancy relevancy) {
+ ExtendedDOMSlots()->mContentRelevancy = Some(relevancy);
+ }
+
+ const Maybe<bool> GetVisibleForContentVisibility() const {
+ const auto* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mVisibleForContentVisibility : Nothing();
+ }
+ void SetVisibleForContentVisibility(bool visible) {
+ ExtendedDOMSlots()->mVisibleForContentVisibility = Some(visible);
+ }
+
+ void ClearContentRelevancy() {
+ if (auto* slots = GetExistingExtendedDOMSlots()) {
+ slots->mContentRelevancy.reset();
+ slots->mVisibleForContentVisibility.reset();
+ }
+ }
+
+ // https://drafts.csswg.org/cssom-view-1/#dom-element-checkvisibility
+ MOZ_CAN_RUN_SCRIPT bool CheckVisibility(const CheckVisibilityOptions&);
+
+ private:
+ // DO NOT USE THIS FUNCTION directly in C++. This function is supposed to be
+ // called from JS. Use PresShell::ScrollContentIntoView instead.
+ MOZ_CAN_RUN_SCRIPT void ScrollIntoView(const ScrollIntoViewOptions& aOptions);
+
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ // DO NOT USE THIS FUNCTION directly in C++. This function is supposed to be
+ // called from JS. Use PresShell::ScrollContentIntoView instead.
+ void ScrollIntoView(const BooleanOrScrollIntoViewOptions& aObject);
+ MOZ_CAN_RUN_SCRIPT void Scroll(double aXScroll, double aYScroll);
+ MOZ_CAN_RUN_SCRIPT void Scroll(const ScrollToOptions& aOptions);
+ MOZ_CAN_RUN_SCRIPT void ScrollTo(double aXScroll, double aYScroll);
+ MOZ_CAN_RUN_SCRIPT void ScrollTo(const ScrollToOptions& aOptions);
+ MOZ_CAN_RUN_SCRIPT void ScrollBy(double aXScrollDif, double aYScrollDif);
+ MOZ_CAN_RUN_SCRIPT void ScrollBy(const ScrollToOptions& aOptions);
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollTop();
+ MOZ_CAN_RUN_SCRIPT void SetScrollTop(int32_t aScrollTop);
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollLeft();
+ MOZ_CAN_RUN_SCRIPT void SetScrollLeft(int32_t aScrollLeft);
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollWidth();
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollHeight();
+ MOZ_CAN_RUN_SCRIPT void MozScrollSnap();
+ MOZ_CAN_RUN_SCRIPT int32_t ClientTop() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().y).Rounded();
+ }
+ MOZ_CAN_RUN_SCRIPT int32_t ClientLeft() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().x).Rounded();
+ }
+ MOZ_CAN_RUN_SCRIPT int32_t ClientWidth() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().Width()).Rounded();
+ }
+ MOZ_CAN_RUN_SCRIPT int32_t ClientHeight() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().Height()).Rounded();
+ }
+
+ MOZ_CAN_RUN_SCRIPT int32_t ScreenX();
+ MOZ_CAN_RUN_SCRIPT int32_t ScreenY();
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<nsIScreen> GetScreen();
+
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollTopMin();
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollTopMax();
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollLeftMin();
+ MOZ_CAN_RUN_SCRIPT int32_t ScrollLeftMax();
+
+ MOZ_CAN_RUN_SCRIPT double ClientHeightDouble() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().Height());
+ }
+
+ MOZ_CAN_RUN_SCRIPT double ClientWidthDouble() {
+ return CSSPixel::FromAppUnits(GetClientAreaRect().Width());
+ }
+
+ // This function will return the block size of first line box, no matter if
+ // the box is 'block' or 'inline'. The return unit is pixel. If the element
+ // can't get a primary frame, we will return be zero.
+ double FirstLineBoxBSize() const;
+
+ already_AddRefed<Flex> GetAsFlexContainer();
+ void GetGridFragments(nsTArray<RefPtr<Grid>>& aResult);
+
+ bool HasGridFragments();
+
+ already_AddRefed<DOMMatrixReadOnly> GetTransformToAncestor(
+ Element& aAncestor);
+ already_AddRefed<DOMMatrixReadOnly> GetTransformToParent();
+ already_AddRefed<DOMMatrixReadOnly> GetTransformToViewport();
+
+ already_AddRefed<Animation> Animate(
+ JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ void GetAnimations(const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations);
+
+ void GetAnimationsWithoutFlush(const GetAnimationsOptions& aOptions,
+ nsTArray<RefPtr<Animation>>& aAnimations);
+
+ static void GetAnimationsUnsorted(Element* aElement,
+ PseudoStyleType aPseudoType,
+ nsTArray<RefPtr<Animation>>& aAnimations);
+
+ void CloneAnimationsFrom(const Element& aOther);
+
+ virtual void GetInnerHTML(nsAString& aInnerHTML, OOMReporter& aError);
+ virtual void SetInnerHTML(const nsAString& aInnerHTML,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError);
+ void GetOuterHTML(nsAString& aOuterHTML);
+ void SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError);
+ void InsertAdjacentHTML(const nsAString& aPosition, const nsAString& aText,
+ ErrorResult& aError);
+
+ void SetHTML(const nsAString& aInnerHTML, const SetHTMLOptions& aOptions,
+ ErrorResult& aError);
+
+ //----------------------------------------
+
+ /**
+ * Add a script event listener with the given event handler name
+ * (like onclick) and with the value as JS
+ * @param aEventName the event listener name
+ * @param aValue the JS to attach
+ * @param aDefer indicates if deferred execution is allowed
+ */
+ void SetEventHandler(nsAtom* aEventName, const nsAString& aValue,
+ bool aDefer = true);
+
+ /**
+ * Do whatever needs to be done when the mouse leaves a link
+ */
+ nsresult LeaveLink(nsPresContext* aPresContext);
+
+ static bool ShouldBlur(nsIContent* aContent);
+
+ /**
+ * Method to create and dispatch a left-click event loosely based on
+ * aSourceEvent. If aFullDispatch is true, the event will be dispatched
+ * through the full dispatching of the presshell of the aPresContext; if it's
+ * false the event will be dispatched only as a DOM event.
+ * If aPresContext is nullptr, this does nothing.
+ *
+ * @param aFlags Extra flags for the dispatching event. The true flags
+ * will be respected.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static nsresult DispatchClickEvent(nsPresContext* aPresContext,
+ WidgetInputEvent* aSourceEvent,
+ nsIContent* aTarget, bool aFullDispatch,
+ const EventFlags* aFlags,
+ nsEventStatus* aStatus);
+
+ /**
+ * Method to dispatch aEvent to aTarget. If aFullDispatch is true, the event
+ * will be dispatched through the full dispatching of the presshell of the
+ * aPresContext; if it's false the event will be dispatched only as a DOM
+ * event.
+ * If aPresContext is nullptr, this does nothing.
+ */
+ using nsIContent::DispatchEvent;
+ MOZ_CAN_RUN_SCRIPT
+ static nsresult DispatchEvent(nsPresContext* aPresContext,
+ WidgetEvent* aEvent, nsIContent* aTarget,
+ bool aFullDispatch, nsEventStatus* aStatus);
+
+ bool IsDisplayContents() const {
+ return HasServoData() && Servo_Element_IsDisplayContents(this);
+ }
+
+ /*
+ * https://html.spec.whatwg.org/#being-rendered
+ *
+ * With a gotcha for display contents:
+ * https://github.com/whatwg/html/issues/1837
+ */
+ bool IsRendered() const { return GetPrimaryFrame() || IsDisplayContents(); }
+
+ const nsAttrValue* GetParsedAttr(const nsAtom* aAttr) const {
+ return mAttrs.GetAttr(aAttr);
+ }
+
+ const nsAttrValue* GetParsedAttr(const nsAtom* aAttr,
+ int32_t aNameSpaceID) const {
+ return mAttrs.GetAttr(aAttr, aNameSpaceID);
+ }
+
+ /**
+ * Returns the attribute map, if there is one.
+ *
+ * @return existing attribute map or nullptr.
+ */
+ nsDOMAttributeMap* GetAttributeMap() {
+ nsDOMSlots* slots = GetExistingDOMSlots();
+
+ return slots ? slots->mAttributeMap.get() : nullptr;
+ }
+
+ void RecompileScriptEventListeners();
+
+ /**
+ * Get the attr info for the given namespace ID and attribute name. The
+ * namespace ID must not be kNameSpaceID_Unknown and the name must not be
+ * null. Note that this can only return info on attributes that actually
+ * live on this element (and is only virtual to handle XUL prototypes). That
+ * is, this should only be called from methods that only care about attrs
+ * that effectively live in mAttrs.
+ */
+ BorrowedAttrInfo GetAttrInfo(int32_t aNamespaceID,
+ const nsAtom* aName) const {
+ NS_ASSERTION(aName, "must have attribute name");
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "must have a real namespace ID!");
+
+ int32_t index = mAttrs.IndexOfAttr(aName, aNamespaceID);
+ if (index < 0) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+
+ return mAttrs.AttrInfoAt(index);
+ }
+
+ /**
+ * Parse a string into an nsAttrValue for a CORS attribute. This
+ * never fails. The resulting value is an enumerated value whose
+ * GetEnumValue() returns one of the above constants.
+ */
+ static void ParseCORSValue(const nsAString& aValue, nsAttrValue& aResult);
+
+ /**
+ * Return the CORS mode for a given string
+ */
+ static CORSMode StringToCORSMode(const nsAString& aValue);
+
+ /**
+ * Return the CORS mode for a given nsAttrValue (which may be null,
+ * but if not should have been parsed via ParseCORSValue).
+ */
+ static CORSMode AttrValueToCORSMode(const nsAttrValue* aValue);
+
+ nsINode* GetScopeChainParent() const override;
+
+ /**
+ * Locate a TextEditor rooted at this content node, if there is one.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::TextEditor* GetTextEditorInternal();
+
+ /**
+ * Gets value of boolean attribute. Only works for attributes in null
+ * namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aValue Boolean value of attribute.
+ */
+ bool GetBoolAttr(nsAtom* aAttr) const {
+ return HasAttr(kNameSpaceID_None, aAttr);
+ }
+
+ /**
+ * Sets value of boolean attribute by removing attribute or setting it to
+ * the empty string. Only works for attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aValue Boolean value of attribute.
+ */
+ nsresult SetBoolAttr(nsAtom* aAttr, bool aValue);
+
+ /**
+ * Gets the enum value string of an attribute and using a default value if
+ * the attribute is missing or the string is an invalid enum value.
+ *
+ * @param aType the name of the attribute.
+ * @param aDefault the default value if the attribute is missing or invalid.
+ * @param aResult string corresponding to the value [out].
+ */
+ void GetEnumAttr(nsAtom* aAttr, const char* aDefault,
+ nsAString& aResult) const;
+
+ /**
+ * Gets the enum value string of an attribute and using the default missing
+ * value if the attribute is missing or the default invalid value if the
+ * string is an invalid enum value.
+ *
+ * @param aType the name of the attribute.
+ * @param aDefaultMissing the default value if the attribute is missing. If
+ null and the attribute is missing, aResult will be
+ set to the null DOMString; this only matters for
+ cases in which we're reflecting a nullable string.
+ * @param aDefaultInvalid the default value if the attribute is invalid.
+ * @param aResult string corresponding to the value [out].
+ */
+ void GetEnumAttr(nsAtom* aAttr, const char* aDefaultMissing,
+ const char* aDefaultInvalid, nsAString& aResult) const;
+
+ /**
+ * Unset an attribute.
+ */
+ void UnsetAttr(nsAtom* aAttr, ErrorResult& aError) {
+ aError = UnsetAttr(kNameSpaceID_None, aAttr, true);
+ }
+
+ /**
+ * Set an attribute in the simplest way possible.
+ */
+ void SetAttr(nsAtom* aAttr, const nsAString& aValue, ErrorResult& aError) {
+ aError = SetAttr(kNameSpaceID_None, aAttr, aValue, true);
+ }
+
+ void SetAttr(nsAtom* aAttr, const nsAString& aValue,
+ nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) {
+ aError =
+ SetAttr(kNameSpaceID_None, aAttr, aValue, aTriggeringPrincipal, true);
+ }
+
+ /**
+ * Set a content attribute via a reflecting nullable string IDL
+ * attribute (e.g. a CORS attribute). If DOMStringIsNull(aValue),
+ * this will actually remove the content attribute.
+ */
+ void SetOrRemoveNullableStringAttr(nsAtom* aName, const nsAString& aValue,
+ ErrorResult& aError);
+
+ /**
+ * Retrieve the ratio of font-size-inflated text font size to computed font
+ * size for this element. This will query the element for its primary frame,
+ * and then use this to get font size inflation information about the frame.
+ *
+ * @returns The font size inflation ratio (inflated font size to uninflated
+ * font size) for the primary frame of this element. Returns 1.0
+ * by default if font size inflation is not enabled. Returns -1
+ * if the element does not have a primary frame.
+ *
+ * @note The font size inflation ratio that is returned is actually the
+ * font size inflation data for the element's _primary frame_, not the
+ * element itself, but for most purposes, this should be sufficient.
+ */
+ float FontSizeInflation();
+
+ void GetImplementedPseudoElement(nsAString&) const;
+
+ ReferrerPolicy GetReferrerPolicyAsEnum() const;
+ ReferrerPolicy ReferrerPolicyFromAttr(const nsAttrValue* aValue) const;
+
+ /*
+ * Helpers for .dataset. This is implemented on Element, though only some
+ * sorts of elements expose it to JS as a .dataset property
+ */
+ // Getter, to be called from bindings.
+ already_AddRefed<nsDOMStringMap> Dataset();
+ // Callback for destructor of dataset to ensure to null out our weak pointer
+ // to it.
+ void ClearDataset();
+
+ void RegisterIntersectionObserver(DOMIntersectionObserver* aObserver);
+ void UnregisterIntersectionObserver(DOMIntersectionObserver* aObserver);
+ void UnlinkIntersectionObservers();
+ bool UpdateIntersectionObservation(DOMIntersectionObserver* aObserver,
+ int32_t threshold);
+
+ // A number of methods to cast to various XUL interfaces. They return a
+ // pointer only if the element implements that interface.
+ already_AddRefed<nsIDOMXULButtonElement> AsXULButton();
+ already_AddRefed<nsIDOMXULContainerElement> AsXULContainer();
+ already_AddRefed<nsIDOMXULContainerItemElement> AsXULContainerItem();
+ already_AddRefed<nsIDOMXULControlElement> AsXULControl();
+ already_AddRefed<nsIDOMXULMenuListElement> AsXULMenuList();
+ already_AddRefed<nsIDOMXULMultiSelectControlElement>
+ AsXULMultiSelectControl();
+ already_AddRefed<nsIDOMXULRadioGroupElement> AsXULRadioGroup();
+ already_AddRefed<nsIDOMXULRelatedElement> AsXULRelated();
+ already_AddRefed<nsIDOMXULSelectControlElement> AsXULSelectControl();
+ already_AddRefed<nsIDOMXULSelectControlItemElement> AsXULSelectControlItem();
+ already_AddRefed<nsIBrowser> AsBrowser();
+ already_AddRefed<nsIAutoCompletePopup> AsAutoCompletePopup();
+
+ /**
+ * Get the presentation context for this content node.
+ * @return the presentation context
+ */
+ enum PresContextFor { eForComposedDoc, eForUncomposedDoc };
+ nsPresContext* GetPresContext(PresContextFor aFor);
+
+ /**
+ * The method focuses (or activates) element that accesskey is bound to. It is
+ * called when accesskey is activated.
+ *
+ * @param aKeyCausesActivation - if true then element should be activated
+ * @param aIsTrustedEvent - if true then event that is cause of accesskey
+ * execution is trusted.
+ * @return an error if the element isn't able to handle the accesskey (caller
+ * would look for the next element to handle it).
+ * a boolean indicates whether the focus moves to the element after
+ * the element handles the accesskey.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ virtual Result<bool, nsresult> PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) {
+ return Err(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ protected:
+ /*
+ * Named-bools for use with SetAttrAndNotify to make call sites easier to
+ * read.
+ */
+ static const bool kFireMutationEvent = true;
+ static const bool kDontFireMutationEvent = false;
+ static const bool kNotifyDocumentObservers = true;
+ static const bool kDontNotifyDocumentObservers = false;
+ static const bool kCallAfterSetAttr = true;
+ static const bool kDontCallAfterSetAttr = false;
+
+ /**
+ * Set attribute and (if needed) notify documentobservers and fire off
+ * mutation events. This will send the AttributeChanged notification.
+ * Callers of this method are responsible for calling AttributeWillChange,
+ * since that needs to happen before the new attr value has been set, and
+ * in particular before it has been parsed.
+ *
+ * For the boolean parameters, consider using the named bools above to aid
+ * code readability.
+ *
+ * @param aNamespaceID namespace of attribute
+ * @param aAttribute local-name of attribute
+ * @param aPrefix aPrefix of attribute
+ * @param aOldValue The old value of the attribute to use as a fallback
+ * in the cases where the actual old value (i.e.
+ * its current value) is !StoresOwnData() --- in which
+ * case the current value is probably already useless.
+ * If the current value is StoresOwnData() (or absent),
+ * aOldValue will not be used. aOldValue will only be set
+ * in certain circumstances (there are mutation
+ * listeners, element is a custom element, attribute was
+ * not previously unset). Otherwise it will be null.
+ * @param aParsedValue parsed new value of attribute. Replaced by the
+ * old value of the attribute. This old value is only
+ * useful if either it or the new value is StoresOwnData.
+ * @param aSubjectPrincipal
+ * the principal of the scripted caller responsible for
+ * setting the attribute, or null if no scripted caller
+ * can be determined. A null value here does not
+ * guarantee that there is no scripted caller, but a
+ * non-null value does guarantee that a scripted caller
+ * with the given principal is directly responsible for
+ * the attribute change.
+ * @param aModType MutationEvent_Binding::MODIFICATION or ADDITION. Only
+ * needed if aFireMutation or aNotify is true.
+ * @param aFireMutation should mutation-events be fired?
+ * @param aNotify should we notify document-observers?
+ * @param aCallAfterSetAttr should we call AfterSetAttr?
+ * @param aComposedDocument The current composed document of the element.
+ * @param aGuard For making sure that this is called with a
+ * mozAutoDocUpdate instance, this is here. Specify
+ * an instance of it which you created for the call.
+ */
+ nsresult SetAttrAndNotify(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix, const nsAttrValue* aOldValue,
+ nsAttrValue& aParsedValue,
+ nsIPrincipal* aSubjectPrincipal, uint8_t aModType,
+ bool aFireMutation, bool aNotify,
+ bool aCallAfterSetAttr, Document* aComposedDocument,
+ const mozAutoDocUpdate& aGuard);
+
+ /**
+ * Scroll to a new position using behavior evaluated from CSS and
+ * a CSSOM-View DOM method ScrollOptions dictionary. The scrolling may
+ * be performed asynchronously or synchronously depending on the resolved
+ * scroll-behavior.
+ *
+ * @param aScroll Destination of scroll, in CSS pixels
+ * @param aOptions Dictionary of options to be evaluated
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void Scroll(const CSSIntPoint& aScroll, const ScrollOptions& aOptions);
+
+ /**
+ * Convert an attribute string value to attribute type based on the type of
+ * attribute. Called by SetAttr(). Note that at the moment we only do this
+ * for attributes in the null namespace (kNameSpaceID_None).
+ *
+ * @param aNamespaceID the namespace of the attribute to convert
+ * @param aAttribute the attribute to convert
+ * @param aValue the string value to convert
+ * @param aMaybeScriptedPrincipal the principal of the script setting the
+ * attribute, if one can be determined, or null otherwise. As in
+ * AfterSetAttr, a null value does not guarantee that the attribute was
+ * not set by a scripted caller, but a non-null value guarantees that
+ * the attribute was set by a scripted caller with the given principal.
+ * @param aResult the nsAttrValue [OUT]
+ * @return true if the parsing was successful, false otherwise
+ */
+ virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult);
+
+ /**
+ * Try to set the attribute as a mapped attribute, if applicable. This will
+ * only be called for attributes that are in the null namespace and only on
+ * attributes that returned true when passed to IsAttributeMapped. The
+ * caller will not try to set the attr in any other way if this method
+ * returns true (the value of aRetval does not matter for that purpose).
+ *
+ * @param aName the name of the attribute
+ * @param aValue the nsAttrValue to set. Will be swapped with the existing
+ * value of the attribute if the attribute already exists.
+ * @param [out] aValueWasSet If the attribute was not set previously,
+ * aValue will be swapped with an empty attribute
+ * and aValueWasSet will be set to false. Otherwise,
+ * aValueWasSet will be set to true and aValue will
+ * contain the previous value set.
+ * @param [out] aRetval the nsresult status of the operation, if any.
+ * @return true if the setting was attempted, false otherwise.
+ */
+ virtual bool SetAndSwapMappedAttribute(nsAtom* aName, nsAttrValue& aValue,
+ bool* aValueWasSet, nsresult* aRetval);
+
+ /**
+ * Hook that is called by Element::SetAttr to allow subclasses to
+ * deal with attribute sets. This will only be called after we verify that
+ * we're actually doing an attr set and will be called before
+ * AttributeWillChange and before ParseAttribute and hence before we've set
+ * the new value.
+ *
+ * @param aNamespaceID the namespace of the attr being set
+ * @param aName the localname of the attribute being set
+ * @param aValue the value it's being set to represented as either a string or
+ * a parsed nsAttrValue. Alternatively, if the attr is being removed it
+ * will be null.
+ * @param aNotify Whether we plan to notify document observers.
+ */
+ virtual void BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify);
+
+ /**
+ * Hook that is called by Element::SetAttr to allow subclasses to
+ * deal with attribute sets. This will only be called after we have called
+ * SetAndSwapAttr (that is, after we have actually set the attr). It will
+ * always be called under a scriptblocker.
+ *
+ * @param aNamespaceID the namespace of the attr being set
+ * @param aName the localname of the attribute being set
+ * @param aValue the value it's being set to. If null, the attr is being
+ * removed.
+ * @param aOldValue the value that the attribute had previously. If null,
+ * the attr was not previously set. This argument may not have the
+ * correct value for SVG elements, or other cases in which the
+ * attribute value doesn't store its own data
+ * @param aMaybeScriptedPrincipal the principal of the scripted caller
+ * responsible for setting the attribute, or null if no scripted caller
+ * can be determined, or the attribute is being unset. A null value
+ * here does not guarantee that there is no scripted caller, but a
+ * non-null value does guarantee that a scripted caller with the given
+ * principal is directly responsible for the attribute change.
+ * @param aNotify Whether we plan to notify document observers.
+ */
+ virtual void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ bool aNotify);
+
+ /**
+ * This function shall be called just before the id attribute changes. It will
+ * be called after BeforeSetAttr. If the attribute being changed is not the id
+ * attribute, this function does nothing. Otherwise, it will remove the old id
+ * from the document's id cache.
+ *
+ * This must happen after BeforeSetAttr (rather than during) because the
+ * the subclasses' calls to BeforeSetAttr may notify on state changes. If they
+ * incorrectly determine whether the element had an id, the element may not be
+ * restyled properly.
+ *
+ * @param aNamespaceID the namespace of the attr being set
+ * @param aName the localname of the attribute being set
+ * @param aValue the new id value. Will be null if the id is being unset.
+ */
+ void PreIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue);
+
+ /**
+ * This function shall be called just after the id attribute changes. It will
+ * be called before AfterSetAttr. If the attribute being changed is not the id
+ * attribute, this function does nothing. Otherwise, it will add the new id to
+ * the document's id cache and properly set the ElementHasID flag.
+ *
+ * This must happen before AfterSetAttr (rather than during) because the
+ * the subclasses' calls to AfterSetAttr may notify on state changes. If they
+ * incorrectly determine whether the element now has an id, the element may
+ * not be restyled properly.
+ *
+ * @param aNamespaceID the namespace of the attr being set
+ * @param aName the localname of the attribute being set
+ * @param aValue the new id value. Will be null if the id is being unset.
+ */
+ void PostIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue);
+
+ /**
+ * Usually, setting an attribute to the value that it already has results in
+ * no action. However, in some cases, setting an attribute to its current
+ * value should have the effect of, for example, forcing a reload of
+ * network data. To address that, this function will be called in this
+ * situation to allow the handling of such a case.
+ *
+ * @param aNamespaceID the namespace of the attr being set
+ * @param aName the localname of the attribute being set
+ * @param aValue the value it's being set to represented as either a string or
+ * a parsed nsAttrValue.
+ * @param aNotify Whether we plan to notify document observers.
+ */
+ virtual void OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString& aValue,
+ bool aNotify);
+
+ /**
+ * Hook to allow subclasses to produce a different EventListenerManager if
+ * needed for attachment of attribute-defined handlers
+ */
+ virtual EventListenerManager* GetEventListenerManagerForAttr(
+ nsAtom* aAttrName, bool* aDefer);
+
+ /**
+ * Internal hook for converting an attribute name-string to nsAttrName in
+ * case there is such existing attribute. aNameToUse can be passed to get
+ * name which was used for looking for the attribute (lowercase in HTML).
+ */
+ const nsAttrName* InternalGetAttrNameFromQName(
+ const nsAString& aStr, nsAutoString* aNameToUse = nullptr) const;
+
+ virtual Element* GetNameSpaceElement() override { return this; }
+
+ Attr* GetAttributeNodeNSInternal(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName);
+
+ inline void RegisterActivityObserver();
+ inline void UnregisterActivityObserver();
+
+ /**
+ * Add/remove this element to the documents id cache
+ */
+ void AddToIdTable(nsAtom* aId);
+ void RemoveFromIdTable();
+
+ /**
+ * Functions to carry out event default actions for links of all types
+ * (HTML links, XLinks, SVG "XLinks", etc.)
+ */
+
+ /**
+ * Check that we meet the conditions to handle a link event
+ * and that we are actually on a link.
+ *
+ * @param aVisitor event visitor
+ * @return true if we can handle the link event, false otherwise
+ */
+ bool CheckHandleEventForLinksPrecondition(EventChainVisitor& aVisitor) const;
+
+ /**
+ * Handle status bar updates before they can be cancelled.
+ */
+ void GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor);
+
+ void DispatchChromeOnlyLinkClickEvent(EventChainPostVisitor& aVisitor);
+
+ /**
+ * Handle default actions for link event if the event isn't consumed yet.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult PostHandleEventForLinks(EventChainPostVisitor& aVisitor);
+
+ public:
+ /**
+ * Check if this element is a link. This matches the CSS definition of the
+ * :any-link pseudo-class.
+ */
+ bool IsLink() const {
+ return mState.HasAtLeastOneOfStates(ElementState::VISITED |
+ ElementState::UNVISITED);
+ }
+
+ /**
+ * Get a pointer to the full href URI (fully resolved and canonicalized, since
+ * it's an nsIURI object) for link elements.
+ *
+ * @return A pointer to the URI or null if the element is not a link, or it
+ * has no HREF attribute, or the HREF attribute is an invalid URI.
+ */
+ virtual already_AddRefed<nsIURI> GetHrefURI() const { return nullptr; }
+
+ /**
+ * Get the target of this link element. Consumers should established that
+ * this element is a link (probably using IsLink) before calling this
+ * function (or else why call it?)
+ *
+ * Note: for HTML this gets the value of the 'target' attribute; for XLink
+ * this gets the value of the xlink:_moz_target attribute, or failing that,
+ * the value of xlink:show, converted to a suitably equivalent named target
+ * (e.g. _blank).
+ */
+ virtual void GetLinkTarget(nsAString& aTarget);
+
+ virtual bool Translate() const;
+
+ protected:
+ enum class ReparseAttributes { No, Yes };
+ /**
+ * Copy attributes and state to another element
+ * @param aDest the object to copy to
+ */
+ nsresult CopyInnerTo(Element* aDest,
+ ReparseAttributes = ReparseAttributes::Yes);
+
+ /**
+ * Some event handler content attributes have a different name (e.g. different
+ * case) from the actual event name. This function takes an event handler
+ * content attribute name and returns the corresponding event name, to be used
+ * for adding the actual event listener.
+ */
+ virtual nsAtom* GetEventNameForAttr(nsAtom* aAttr);
+
+ /**
+ * Register/unregister this element to accesskey map if it supports accesskey.
+ */
+ virtual void RegUnRegAccessKey(bool aDoReg);
+
+ private:
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ void AssertInvariantsOnNodeInfoChange();
+#endif
+
+ /**
+ * Slow path for GetClasses, this should only be called for SVG elements.
+ */
+ const nsAttrValue* GetSVGAnimatedClass() const;
+
+ /**
+ * Get this element's client area rect in app units.
+ * @return the frame's client area
+ */
+ MOZ_CAN_RUN_SCRIPT nsRect GetClientAreaRect();
+
+ /**
+ * GetCustomInterface is somewhat like a GetInterface, but it is expected
+ * that the implementation is provided by a custom element or via the
+ * the XBL implements keyword. To use this, create a public method that
+ * wraps a call to GetCustomInterface.
+ */
+ template <class T>
+ void GetCustomInterface(nsGetterAddRefs<T> aResult);
+
+ // Prevent people from doing pointless checks/casts on Element instances.
+ void IsElement() = delete;
+ void AsElement() = delete;
+
+ // Data members
+ ElementState mState;
+ // Per-node data managed by Servo.
+ //
+ // There should not be data on nodes that are not in the flattened tree, or
+ // descendants of display: none elements.
+ mozilla::RustCell<ServoNodeData*> mServoData;
+
+ protected:
+ // Array containing all attributes for this element
+ AttrArray mAttrs;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Element, NS_ELEMENT_IID)
+
+inline bool Element::HasAttr(int32_t aNameSpaceID, const nsAtom* aName) const {
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown,
+ "must have a real namespace ID!");
+
+ return mAttrs.IndexOfAttr(aName, aNameSpaceID) >= 0;
+}
+
+inline bool Element::HasNonEmptyAttr(int32_t aNameSpaceID,
+ const nsAtom* aName) const {
+ MOZ_ASSERT(aNameSpaceID > kNameSpaceID_Unknown, "Must have namespace");
+ MOZ_ASSERT(aName, "Must have attribute name");
+
+ const nsAttrValue* val = mAttrs.GetAttr(aName, aNameSpaceID);
+ return val && !val->IsEmptyString();
+}
+
+inline bool Element::AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ return mAttrs.AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+inline bool Element::AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
+ const nsAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ return mAttrs.AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+inline mozilla::dom::Element* nsINode::AsElement() {
+ MOZ_ASSERT(IsElement());
+ return static_cast<mozilla::dom::Element*>(this);
+}
+
+inline const mozilla::dom::Element* nsINode::AsElement() const {
+ MOZ_ASSERT(IsElement());
+ return static_cast<const mozilla::dom::Element*>(this);
+}
+
+inline mozilla::dom::Element* nsINode::GetParentElement() const {
+ return mozilla::dom::Element::FromNodeOrNull(mParent);
+}
+
+inline mozilla::dom::Element* nsINode::GetPreviousElementSibling() const {
+ nsIContent* previousSibling = GetPreviousSibling();
+ while (previousSibling) {
+ if (previousSibling->IsElement()) {
+ return previousSibling->AsElement();
+ }
+ previousSibling = previousSibling->GetPreviousSibling();
+ }
+
+ return nullptr;
+}
+
+inline mozilla::dom::Element* nsINode::GetAsElementOrParentElement() const {
+ return IsElement() ? const_cast<mozilla::dom::Element*>(AsElement())
+ : GetParentElement();
+}
+
+inline mozilla::dom::Element* nsINode::GetNextElementSibling() const {
+ nsIContent* nextSibling = GetNextSibling();
+ while (nextSibling) {
+ if (nextSibling->IsElement()) {
+ return nextSibling->AsElement();
+ }
+ nextSibling = nextSibling->GetNextSibling();
+ }
+
+ return nullptr;
+}
+
+/**
+ * Macros to implement Clone(). _elementName is the class for which to implement
+ * Clone.
+ */
+#define NS_IMPL_ELEMENT_CLONE(_elementName) \
+ nsresult _elementName::Clone(mozilla::dom::NodeInfo* aNodeInfo, \
+ nsINode** aResult) const { \
+ *aResult = nullptr; \
+ RefPtr<mozilla::dom::NodeInfo> ni(aNodeInfo); \
+ auto* nim = ni->NodeInfoManager(); \
+ RefPtr<_elementName> it = new (nim) _elementName(ni.forget()); \
+ nsresult rv = const_cast<_elementName*>(this)->CopyInnerTo(it); \
+ if (NS_SUCCEEDED(rv)) { \
+ it.forget(aResult); \
+ } \
+ \
+ return rv; \
+ }
+
+#define EXPAND(...) __VA_ARGS__
+#define NS_IMPL_ELEMENT_CLONE_WITH_INIT_HELPER(_elementName, extra_args_) \
+ nsresult _elementName::Clone(mozilla::dom::NodeInfo* aNodeInfo, \
+ nsINode** aResult) const { \
+ *aResult = nullptr; \
+ RefPtr<mozilla::dom::NodeInfo> ni(aNodeInfo); \
+ auto* nim = ni->NodeInfoManager(); \
+ RefPtr<_elementName> it = \
+ new (nim) _elementName(ni.forget() EXPAND extra_args_); \
+ nsresult rv = it->Init(); \
+ nsresult rv2 = const_cast<_elementName*>(this)->CopyInnerTo(it); \
+ if (NS_FAILED(rv2)) { \
+ rv = rv2; \
+ } \
+ if (NS_SUCCEEDED(rv)) { \
+ it.forget(aResult); \
+ } \
+ \
+ return rv; \
+ }
+
+#define NS_IMPL_ELEMENT_CLONE_WITH_INIT(_elementName) \
+ NS_IMPL_ELEMENT_CLONE_WITH_INIT_HELPER(_elementName, ())
+#define NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(_elementName) \
+ NS_IMPL_ELEMENT_CLONE_WITH_INIT_HELPER(_elementName, (, NOT_FROM_PARSER))
+
+#endif // mozilla_dom_Element_h__
diff --git a/dom/base/ElementInlines.h b/dom/base/ElementInlines.h
new file mode 100644
index 0000000000..362f5dd478
--- /dev/null
+++ b/dom/base/ElementInlines.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ElementInlines_h
+#define mozilla_dom_ElementInlines_h
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+
+namespace mozilla::dom {
+
+inline void Element::RegisterActivityObserver() {
+ OwnerDoc()->RegisterActivityObserver(this);
+}
+
+inline void Element::UnregisterActivityObserver() {
+ OwnerDoc()->UnregisterActivityObserver(this);
+}
+
+} // namespace mozilla::dom
+
+inline mozilla::dom::Element* nsINode::GetFlattenedTreeParentElement() const {
+ nsINode* parentNode = GetFlattenedTreeParentNode();
+ if MOZ_LIKELY (parentNode && parentNode->IsElement()) {
+ return parentNode->AsElement();
+ }
+
+ return nullptr;
+}
+
+inline mozilla::dom::Element* nsINode::GetFlattenedTreeParentElementForStyle()
+ const {
+ nsINode* parentNode = GetFlattenedTreeParentNodeForStyle();
+ if (MOZ_LIKELY(parentNode && parentNode->IsElement())) {
+ return parentNode->AsElement();
+ }
+ return nullptr;
+}
+
+#endif // mozilla_dom_ElementInlines_h
diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp
new file mode 100644
index 0000000000..86a8b98758
--- /dev/null
+++ b/dom/base/EventSource.cpp
@@ -0,0 +1,2144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Components.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/EventSource.h"
+#include "mozilla/dom/EventSourceBinding.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/EventSourceEventService.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsPresContext.h"
+#include "nsProxyRelease.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIConsoleService.h"
+#include "nsIObserverService.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsJSUtils.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+#include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/Attributes.h"
+#include "nsError.h"
+#include "mozilla/Encoding.h"
+#include "ReferrerInfo.h"
+
+namespace mozilla::dom {
+
+#ifdef DEBUG
+static LazyLogModule gEventSourceLog("EventSource");
+#endif
+
+#define SPACE_CHAR (char16_t)0x0020
+#define CR_CHAR (char16_t)0x000D
+#define LF_CHAR (char16_t)0x000A
+#define COLON_CHAR (char16_t)0x003A
+
+// Reconnection time related values in milliseconds. The default one is equal
+// to the default value of the pref dom.server-events.default-reconnection-time
+#define MIN_RECONNECTION_TIME_VALUE 500
+#define DEFAULT_RECONNECTION_TIME_VALUE 5000
+#define MAX_RECONNECTION_TIME_VALUE \
+ PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
+
+class EventSourceImpl final : public nsIObserver,
+ public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference,
+ public nsISerialEventTarget,
+ public nsITimerCallback,
+ public nsINamed,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ EventSourceImpl(EventSource* aEventSource,
+ nsICookieJarSettings* aCookieJarSettings);
+
+ enum { CONNECTING = 0U, OPEN = 1U, CLOSED = 2U };
+
+ void Close();
+
+ void Init(nsIPrincipal* aPrincipal, const nsAString& aURL, ErrorResult& aRv);
+
+ nsresult GetBaseURI(nsIURI** aBaseURI);
+
+ void SetupHttpChannel();
+ nsresult SetupReferrerInfo(const nsCOMPtr<Document>& aDocument);
+ nsresult InitChannelAndRequestEventSource(bool aEventTargetAccessAllowed);
+ nsresult ResetConnection();
+ void ResetDecoder();
+ nsresult SetReconnectionTimeout();
+
+ void AnnounceConnection();
+ void DispatchAllMessageEvents();
+ nsresult RestartConnection();
+ void ReestablishConnection();
+ void DispatchFailConnection();
+ void FailConnection();
+
+ nsresult Thaw();
+ nsresult Freeze();
+
+ nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
+ const nsTArray<nsString>& aFormatStrings);
+ nsresult ConsoleError();
+
+ static nsresult StreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount);
+ void ParseSegment(const char* aBuffer, uint32_t aLength);
+ nsresult SetFieldAndClear();
+ void ClearFields();
+ nsresult ResetEvent();
+ nsresult DispatchCurrentMessageEvent();
+ nsresult ParseCharacter(char16_t aChr);
+ nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
+ nsresult OnRedirectVerifyCallback(nsresult result);
+ nsresult ParseURL(const nsAString& aURL);
+ nsresult AddWindowObservers();
+ void RemoveWindowObservers();
+
+ void CloseInternal();
+ void CleanupOnMainThread();
+
+ bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
+ void ReleaseWorkerRef();
+
+ void AssertIsOnTargetThread() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
+ }
+
+ bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
+
+ uint16_t ReadyState() {
+ auto lock = mSharedData.Lock();
+ if (lock->mEventSource) {
+ return lock->mEventSource->mReadyState;
+ }
+ // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
+ // means that the EventSource has been closed.
+ return CLOSED;
+ }
+
+ void SetReadyState(uint16_t aReadyState) {
+ auto lock = mSharedData.Lock();
+ MOZ_ASSERT(lock->mEventSource);
+ MOZ_ASSERT(!mIsShutDown);
+ lock->mEventSource->mReadyState = aReadyState;
+ }
+
+ bool IsClosed() { return ReadyState() == CLOSED; }
+
+ RefPtr<EventSource> GetEventSource() {
+ AssertIsOnTargetThread();
+ auto lock = mSharedData.Lock();
+ return lock->mEventSource;
+ }
+
+ /**
+ * A simple state machine used to manage the event-source's line buffer
+ *
+ * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
+ *
+ * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
+ *
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * Whenever the parser find an empty line or the end-of-file
+ * it dispatches the stacked event.
+ *
+ */
+ enum ParserStatus {
+ PARSE_STATE_OFF = 0,
+ PARSE_STATE_BEGIN_OF_STREAM,
+ PARSE_STATE_CR_CHAR,
+ PARSE_STATE_COMMENT,
+ PARSE_STATE_FIELD_NAME,
+ PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
+ PARSE_STATE_FIELD_VALUE,
+ PARSE_STATE_IGNORE_FIELD_VALUE,
+ PARSE_STATE_BEGIN_OF_LINE
+ };
+
+ // Connection related data members. Should only be accessed on main thread.
+ nsCOMPtr<nsIURI> mSrc;
+ uint32_t mReconnectionTime; // in ms
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsString mOrigin;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ struct Message {
+ nsString mEventName;
+ // We need to be able to distinguish between different states of id field:
+ // 1) is not given at all
+ // 2) is given but is empty
+ // 3) is given and has a value
+ // We can't check for the 1st state with a simple nsString.
+ Maybe<nsString> mLastEventID;
+ nsString mData;
+ };
+
+ // Message related data members. May be set / initialized when initializing
+ // EventSourceImpl on target thread but should only be used on target thread.
+ nsString mLastEventID;
+ UniquePtr<Message> mCurrentMessage;
+ nsDeque<Message> mMessagesToDispatch;
+ ParserStatus mStatus;
+ mozilla::UniquePtr<mozilla::Decoder> mUnicodeDecoder;
+ nsString mLastFieldName;
+ nsString mLastFieldValue;
+
+ // EventSourceImpl internal states.
+ // WorkerRef to keep the worker alive. (accessed on worker thread only)
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ // Whether the window is frozen. May be set on main thread and read on target
+ // thread.
+ Atomic<bool> mFrozen;
+ // There are some messages are going to be dispatched when thaw.
+ bool mGoingToDispatchAllMessages;
+ // Whether the EventSource is run on main thread.
+ const bool mIsMainThread;
+ // Whether the EventSourceImpl is going to be destroyed.
+ Atomic<bool> mIsShutDown;
+
+ class EventSourceServiceNotifier final {
+ public:
+ EventSourceServiceNotifier(RefPtr<EventSourceImpl>&& aEventSourceImpl,
+ uint64_t aHttpChannelId, uint64_t aInnerWindowID)
+ : mEventSourceImpl(std::move(aEventSourceImpl)),
+ mHttpChannelId(aHttpChannelId),
+ mInnerWindowID(aInnerWindowID),
+ mConnectionOpened(false) {
+ AssertIsOnMainThread();
+ mService = EventSourceEventService::GetOrCreate();
+ }
+
+ void ConnectionOpened() {
+ mEventSourceImpl->AssertIsOnTargetThread();
+ mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
+ mConnectionOpened = true;
+ }
+
+ void EventReceived(const nsAString& aEventName,
+ const nsAString& aLastEventID, const nsAString& aData,
+ uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
+ mEventSourceImpl->AssertIsOnTargetThread();
+ mService->EventReceived(mHttpChannelId, mInnerWindowID, aEventName,
+ aLastEventID, aData, aRetry, aTimeStamp);
+ }
+
+ ~EventSourceServiceNotifier() {
+ // It is safe to call this on any thread because
+ // EventSourceConnectionClosed method is thread safe and
+ // NS_ReleaseOnMainThread explicitly releases the service on the main
+ // thread.
+ if (mConnectionOpened) {
+ // We want to notify about connection being closed only if we told
+ // it was ever opened. The check is needed if OnStartRequest is called
+ // on the main thread while close() is called on a worker thread.
+ mService->EventSourceConnectionClosed(mHttpChannelId, mInnerWindowID);
+ }
+ NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
+ mService.forget());
+ }
+
+ private:
+ RefPtr<EventSourceEventService> mService;
+ RefPtr<EventSourceImpl> mEventSourceImpl;
+ uint64_t mHttpChannelId;
+ uint64_t mInnerWindowID;
+ bool mConnectionOpened;
+ };
+
+ struct SharedData {
+ RefPtr<EventSource> mEventSource;
+ UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
+ };
+
+ DataMutex<SharedData> mSharedData;
+
+ // Event Source owner information:
+ // - the script file name
+ // - source code line number and column number where the Event Source object
+ // was constructed.
+ // - the ID of the inner window where the script lives. Note that this may not
+ // be the same as the Event Source owner window.
+ // These attributes are used for error reporting. Should only be accessed on
+ // target thread
+ nsString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ uint64_t mInnerWindowID;
+
+ private:
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ // Pointer to the target thread for checking whether we are
+ // on the target thread. This is intentionally a non-owning
+ // pointer in order not to affect the thread destruction
+ // sequence. This pointer must only be compared for equality
+ // and must not be dereferenced.
+ nsIThread* mTargetThread;
+
+ // prevent bad usage
+ EventSourceImpl(const EventSourceImpl& x) = delete;
+ EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
+ ~EventSourceImpl() {
+ if (IsClosed()) {
+ return;
+ }
+ // If we threw during Init we never called Close
+ SetReadyState(CLOSED);
+ CloseInternal();
+ }
+};
+
+NS_IMPL_ISUPPORTS(EventSourceImpl, nsIObserver, nsIStreamListener,
+ nsIRequestObserver, nsIChannelEventSink,
+ nsIInterfaceRequestor, nsISupportsWeakReference,
+ nsISerialEventTarget, nsIEventTarget,
+ nsIThreadRetargetableStreamListener, nsITimerCallback,
+ nsINamed)
+
+EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
+ nsICookieJarSettings* aCookieJarSettings)
+ : mReconnectionTime(0),
+ mStatus(PARSE_STATE_OFF),
+ mFrozen(false),
+ mGoingToDispatchAllMessages(false),
+ mIsMainThread(NS_IsMainThread()),
+ mIsShutDown(false),
+ mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
+ mScriptLine(0),
+ mScriptColumn(0),
+ mInnerWindowID(0),
+ mCookieJarSettings(aCookieJarSettings),
+ mTargetThread(NS_GetCurrentThread()) {
+ MOZ_ASSERT(aEventSource);
+ SetReadyState(CONNECTING);
+}
+
+class CleanupRunnable final : public WorkerMainThreadRunnable {
+ public:
+ explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
+ : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
+ "EventSource :: Cleanup"_ns),
+ mESImpl(std::move(aEventSourceImpl)) {
+ MOZ_ASSERT(mESImpl);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ MOZ_ASSERT(mESImpl);
+ mESImpl->CleanupOnMainThread();
+ // We want to ensure the shortest possible remaining lifetime
+ // and not depend on the Runnable's destruction.
+ mESImpl = nullptr;
+ return true;
+ }
+
+ protected:
+ RefPtr<EventSourceImpl> mESImpl;
+};
+
+void EventSourceImpl::Close() {
+ if (IsClosed()) {
+ return;
+ }
+
+ SetReadyState(CLOSED);
+ // CloseInternal potentially kills ourself, ensure
+ // to not access any members afterwards.
+ CloseInternal();
+}
+
+void EventSourceImpl::CloseInternal() {
+ AssertIsOnTargetThread();
+ MOZ_ASSERT(IsClosed());
+
+ RefPtr<EventSource> myES;
+ {
+ auto lock = mSharedData.Lock();
+ // We want to ensure to release ourself even if we have
+ // the shutdown case, thus we put aside a pointer
+ // to the EventSource and null it out right now.
+ myES = std::move(lock->mEventSource);
+ lock->mEventSource = nullptr;
+ lock->mServiceNotifier = nullptr;
+ }
+
+ MOZ_ASSERT(!mIsShutDown);
+ if (mIsShutDown) {
+ return;
+ }
+
+ // Invoke CleanupOnMainThread before cleaning any members. It will call
+ // ShutDown, which is supposed to be called before cleaning any members.
+ if (NS_IsMainThread()) {
+ CleanupOnMainThread();
+ } else {
+ ErrorResult rv;
+ // run CleanupOnMainThread synchronously on main thread since it touches
+ // observers and members only can be accessed on main thread.
+ RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
+ runnable->Dispatch(Killing, rv);
+ MOZ_ASSERT(!rv.Failed());
+ ReleaseWorkerRef();
+ }
+
+ while (mMessagesToDispatch.GetSize() != 0) {
+ delete mMessagesToDispatch.PopFront();
+ }
+ mFrozen = false;
+ ResetDecoder();
+ mUnicodeDecoder = nullptr;
+ // Release the object on its owner. Don't access to any members
+ // after it.
+ myES->mESImpl = nullptr;
+}
+
+void EventSourceImpl::CleanupOnMainThread() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsClosed());
+
+ // Call ShutDown before cleaning any members.
+ MOZ_ASSERT(!mIsShutDown);
+ mIsShutDown = true;
+
+ if (mIsMainThread) {
+ RemoveWindowObservers();
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ ResetConnection();
+ mPrincipal = nullptr;
+ mSrc = nullptr;
+}
+
+class InitRunnable final : public WorkerMainThreadRunnable {
+ public:
+ InitRunnable(WorkerPrivate* aWorkerPrivate,
+ RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
+ mESImpl(std::move(aEventSourceImpl)),
+ mURL(aURL),
+ mRv(NS_ERROR_NOT_INITIALIZED) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mESImpl);
+ }
+
+ bool MainThreadRun() override {
+ // Get principal from worker's owner document or from worker.
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ Document* doc = window ? window->GetExtantDoc() : nullptr;
+ nsCOMPtr<nsIPrincipal> principal =
+ doc ? doc->NodePrincipal() : wp->GetPrincipal();
+ if (!principal) {
+ mRv = NS_ERROR_FAILURE;
+ return true;
+ }
+ ErrorResult rv;
+ mESImpl->Init(principal, mURL, rv);
+ mRv = rv.StealNSResult();
+
+ // We want to ensure that EventSourceImpl's lifecycle
+ // does not depend on this Runnable's one.
+ mESImpl = nullptr;
+
+ return true;
+ }
+
+ nsresult ErrorCode() const { return mRv; }
+
+ private:
+ RefPtr<EventSourceImpl> mESImpl;
+ const nsAString& mURL;
+ nsresult mRv;
+};
+
+class ConnectRunnable final : public WorkerMainThreadRunnable {
+ public:
+ explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
+ RefPtr<EventSourceImpl> aEventSourceImpl)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
+ mESImpl(std::move(aEventSourceImpl)) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mESImpl);
+ }
+
+ bool MainThreadRun() override {
+ MOZ_ASSERT(mESImpl);
+ // We are allowed to access the event target since this runnable is
+ // synchronized with the thread the event target lives on.
+ mESImpl->InitChannelAndRequestEventSource(true);
+ // We want to ensure the shortest possible remaining lifetime
+ // and not depend on the Runnable's destruction.
+ mESImpl = nullptr;
+ return true;
+ }
+
+ private:
+ RefPtr<EventSourceImpl> mESImpl;
+};
+
+nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+ // get the src
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> srcURI;
+ rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = srcURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This assignment doesn't require extra synchronization because this function
+ // is only ever called from EventSourceImpl::Init(), which is either called
+ // directly if mEventSource was created on the main thread, or via a
+ // synchronous runnable if it was created on a worker thread.
+ {
+ // We can't use GetEventSource() here because it would modify the refcount,
+ // and that's not allowed off the owning thread.
+ auto lock = mSharedData.Lock();
+ lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
+ }
+ mSrc = srcURI;
+ mOrigin = origin;
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::AddWindowObservers() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mIsMainThread);
+ MOZ_ASSERT(!mIsShutDown);
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+void EventSourceImpl::RemoveWindowObservers() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mIsMainThread);
+ MOZ_ASSERT(IsClosed());
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
+ }
+}
+
+void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(ReadyState() == CONNECTING);
+ mPrincipal = aPrincipal;
+ aRv = ParseURL(aURL);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ // The conditional here is historical and not necessarily sane.
+ if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
+ nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
+ &mScriptColumn);
+ mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
+ }
+
+ if (mIsMainThread) {
+ // we observe when the window freezes and thaws
+ aRv = AddWindowObservers();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ mReconnectionTime =
+ Preferences::GetInt("dom.server-events.default-reconnection-time",
+ DEFAULT_RECONNECTION_TIME_VALUE);
+
+ mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
+ MOZ_ASSERT(mIsMainThread);
+ {
+ auto lock = mSharedData.Lock();
+ if (!lock->mEventSource->GetOwner() ||
+ window != lock->mEventSource->GetOwner()) {
+ return NS_OK;
+ }
+ }
+
+ DebugOnly<nsresult> rv;
+ if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
+ rv = Freeze();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
+ rv = Thaw();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult status;
+ rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(status)) {
+ // EventSource::OnStopRequest will evaluate if it shall either reestablish
+ // or fail the connection
+ return NS_ERROR_ABORT;
+ }
+
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (httpStatus != 200) {
+ DispatchFailConnection();
+ return NS_ERROR_ABORT;
+ }
+
+ nsAutoCString contentType;
+ rv = httpChannel->GetContentType(contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
+ DispatchFailConnection();
+ return NS_ERROR_ABORT;
+ }
+
+ if (!mIsMainThread) {
+ // Try to retarget to worker thread, otherwise fall back to main thread.
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
+ if (rr) {
+ rv = rr->RetargetDeliveryTo(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ NS_WARNING("Retargeting failed");
+ }
+ }
+ }
+
+ {
+ auto lock = mSharedData.Lock();
+ lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
+ this, mHttpChannel->ChannelId(), mInnerWindowID);
+ }
+ rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
+ this, &EventSourceImpl::AnnounceConnection),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mStatus = PARSE_STATE_BEGIN_OF_STREAM;
+ return NS_OK;
+}
+
+// this method parses the characters as they become available instead of
+// buffering them.
+nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ // The EventSourceImpl instance is hold alive on the
+ // synchronously calling stack, so raw pointer is fine here.
+ EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
+ if (!thisObject || !aWriteCount) {
+ NS_WARNING(
+ "EventSource cannot read from stream: no aClosure or aWriteCount");
+ return NS_ERROR_FAILURE;
+ }
+ thisObject->AssertIsOnTargetThread();
+ MOZ_ASSERT(!thisObject->mIsShutDown);
+ thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
+ return;
+ }
+ char16_t buffer[1024];
+ auto dst = Span(buffer);
+ auto src = AsBytes(Span(aBuffer, aLength));
+ // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
+ for (;;) {
+ uint32_t result;
+ size_t read;
+ size_t written;
+ std::tie(result, read, written, std::ignore) =
+ mUnicodeDecoder->DecodeToUTF16(src, dst, false);
+ for (auto c : dst.To(written)) {
+ nsresult rv = ParseCharacter(c);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ if (result == kInputEmpty) {
+ return;
+ }
+ src = src.From(read);
+ }
+}
+
+NS_IMETHODIMP
+EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount) {
+ AssertIsOnTargetThread();
+ NS_ENSURE_ARG_POINTER(aInputStream);
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t totalRead;
+ return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
+ aCount, &totalRead);
+}
+
+NS_IMETHODIMP
+EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ AssertIsOnMainThread();
+
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+ MOZ_ASSERT(mSrc);
+ // "Network errors that prevents the connection from being established in the
+ // first place (e.g. DNS errors), must cause the user agent to asynchronously
+ // reestablish the connection.
+ //
+ // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
+ // response to window.stop() or the user canceling the network connection
+ // manually) must cause the user agent to fail the connection.
+ // There could be additional network errors that are not covered in the above
+ // checks
+ // See Bug 1808511
+ if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
+ aStatusCode != NS_ERROR_NET_TIMEOUT &&
+ aStatusCode != NS_ERROR_NET_RESET &&
+ aStatusCode != NS_ERROR_NET_INTERRUPT &&
+ aStatusCode != NS_ERROR_NET_PARTIAL_TRANSFER &&
+ aStatusCode != NS_ERROR_NET_TIMEOUT_EXTERNAL &&
+ aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
+ DispatchFailConnection();
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv =
+ Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
+ this, &EventSourceImpl::ReestablishConnection),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSourceImpl::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+ nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
+ MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
+
+ nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
+
+ rv =
+ mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
+ if (NS_FAILED(rv) || !isValidScheme) {
+ DispatchFailConnection();
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // update our channel
+
+ mHttpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(mHttpChannel);
+
+ SetupHttpChannel();
+ // The HTTP impl already copies over the referrer info on
+ // redirects, so we don't need to SetupReferrerInfo().
+
+ if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
+ rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
+ AssertIsOnMainThread();
+
+ if (IsClosed()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+
+ // To avoid a data race we may only access the event target if it lives on
+ // the main thread.
+ if (mIsMainThread) {
+ auto lock = mSharedData.Lock();
+ rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ if (lock->mEventSource->GetOwner()) {
+ window = lock->mEventSource->GetOwner()->GetOuterWindow();
+ }
+ }
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+
+ return wwatch->GetPrompt(window, aIID, aResult);
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+EventSourceImpl::IsOnCurrentThread(bool* aResult) {
+ *aResult = IsTargetThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
+
+nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+
+ *aBaseURI = nullptr;
+
+ nsCOMPtr<nsIURI> baseURI;
+
+ // first we try from document->GetBaseURI()
+ nsCOMPtr<Document> doc =
+ mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
+ if (doc) {
+ baseURI = doc->GetBaseURI();
+ }
+
+ // otherwise we get from the doc's principal
+ if (!baseURI) {
+ auto* basePrin = BasePrincipal::Cast(mPrincipal);
+ nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ENSURE_STATE(baseURI);
+
+ baseURI.forget(aBaseURI);
+ return NS_OK;
+}
+
+void EventSourceImpl::SetupHttpChannel() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+ nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ /* set the http request headers */
+
+ rv = mHttpChannel->SetRequestHeader(
+ "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
+
+ if (mLastEventID.IsEmpty()) {
+ return;
+ }
+ NS_ConvertUTF16toUTF8 eventId(mLastEventID);
+ rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
+#ifdef DEBUG
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gEventSourceLog, LogLevel::Warning,
+ ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
+ }
+#endif
+ Unused << rv;
+}
+
+nsresult EventSourceImpl::SetupReferrerInfo(
+ const nsCOMPtr<Document>& aDocument) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+
+ if (aDocument) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
+ nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::InitChannelAndRequestEventSource(
+ const bool aEventTargetAccessAllowed) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+
+ bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
+
+ MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
+
+ nsresult rv = aEventTargetAccessAllowed ? [this]() {
+ // We can't call GetEventSource() because we're not
+ // allowed to touch the refcount off the worker thread
+ // due to an assertion, event if it would have otherwise
+ // been safe.
+ auto lock = mSharedData.Lock();
+ return lock->mEventSource->CheckCurrentGlobalCorrectness();
+ }()
+ : NS_OK;
+ if (NS_FAILED(rv) || !isValidScheme) {
+ DispatchFailConnection();
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<Document> doc;
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ {
+ auto lock = mSharedData.Lock();
+ doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
+ : nullptr;
+
+ if (lock->mEventSource->mWithCredentials) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+
+ // The html spec requires we use fetch cache mode of "no-store". This
+ // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
+ nsLoadFlags loadFlags;
+ loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::INHIBIT_CACHING;
+
+ nsCOMPtr<nsIChannel> channel;
+ // If we have the document, use it
+ if (doc) {
+ MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
+
+ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+ rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
+ nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
+ nullptr, // aPerformanceStorage
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags); // aLoadFlags
+ } else {
+ // otherwise use the principal
+ rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
+ nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
+ mCookieJarSettings,
+ nullptr, // aPerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ loadFlags); // aLoadFlags
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
+
+ SetupHttpChannel();
+ rv = SetupReferrerInfo(doc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
+ mHttpChannel->GetNotificationCallbacks(
+ getter_AddRefs(notificationCallbacks));
+ MOZ_ASSERT(!notificationCallbacks);
+ }
+#endif
+
+ mHttpChannel->SetNotificationCallbacks(this);
+
+ // Start reading from the channel
+ rv = mHttpChannel->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ DispatchFailConnection();
+ return rv;
+ }
+
+ return rv;
+}
+
+void EventSourceImpl::AnnounceConnection() {
+ AssertIsOnTargetThread();
+ if (ReadyState() != CONNECTING) {
+ NS_WARNING("Unexpected mReadyState!!!");
+ return;
+ }
+
+ {
+ auto lock = mSharedData.Lock();
+ if (lock->mServiceNotifier) {
+ lock->mServiceNotifier->ConnectionOpened();
+ }
+ }
+
+ // When a user agent is to announce the connection, the user agent must set
+ // the readyState attribute to OPEN and queue a task to fire a simple event
+ // named open at the EventSource object.
+
+ SetReadyState(OPEN);
+
+ nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ // We can't hold the mutex while dispatching the event because the mutex is
+ // not reentrant, and content might call back into our code.
+ rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+}
+
+nsresult EventSourceImpl::ResetConnection() {
+ AssertIsOnMainThread();
+ if (mHttpChannel) {
+ mHttpChannel->Cancel(NS_ERROR_ABORT);
+ mHttpChannel = nullptr;
+ }
+ return NS_OK;
+}
+
+void EventSourceImpl::ResetDecoder() {
+ AssertIsOnTargetThread();
+ if (mUnicodeDecoder) {
+ UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
+ }
+ mStatus = PARSE_STATE_OFF;
+ ClearFields();
+}
+
+class CallRestartConnection final : public WorkerMainThreadRunnable {
+ public:
+ explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
+ : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
+ "EventSource :: RestartConnection"_ns),
+ mESImpl(std::move(aEventSourceImpl)) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mESImpl);
+ }
+
+ bool MainThreadRun() override {
+ MOZ_ASSERT(mESImpl);
+ mESImpl->RestartConnection();
+ // We want to ensure the shortest possible remaining lifetime
+ // and not depend on the Runnable's destruction.
+ mESImpl = nullptr;
+ return true;
+ }
+
+ protected:
+ RefPtr<EventSourceImpl> mESImpl;
+};
+
+nsresult EventSourceImpl::RestartConnection() {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = ResetConnection();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetReconnectionTimeout();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+void EventSourceImpl::ReestablishConnection() {
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
+ return;
+ }
+
+ nsresult rv;
+ if (mIsMainThread) {
+ rv = RestartConnection();
+ } else {
+ RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
+ ErrorResult result;
+ runnable->Dispatch(Canceling, result);
+ MOZ_ASSERT(!result.Failed());
+ rv = result.StealNSResult();
+ }
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = GetEventSource()->CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ SetReadyState(CONNECTING);
+ ResetDecoder();
+ // We can't hold the mutex while dispatching the event because the mutex is
+ // not reentrant, and content might call back into our code.
+ rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+}
+
+nsresult EventSourceImpl::SetReconnectionTimeout() {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+
+ // the timer will be used whenever the requests are going finished.
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ NS_ENSURE_STATE(mTimer);
+ }
+
+ MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
+ nsITimer::TYPE_ONE_SHOT));
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::PrintErrorOnConsole(
+ const char* aBundleURI, const char* aError,
+ const nsTArray<nsString>& aFormatStrings) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_STATE(bundleService);
+
+ nsCOMPtr<nsIStringBundle> strBundle;
+ nsresult rv =
+ bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> errObj(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Localize the error message
+ nsAutoString message;
+ if (!aFormatStrings.IsEmpty()) {
+ rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
+ } else {
+ rv = strBundle->GetStringFromName(aError, message);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
+ mScriptColumn, nsIScriptError::errorFlag,
+ "Event Source", mInnerWindowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // print the error message directly to the JS console
+ rv = console->LogMessage(errObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::ConsoleError() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mIsShutDown);
+ nsAutoCString targetSpec;
+ nsresult rv = mSrc->GetSpec(targetSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<nsString, 1> formatStrings;
+ CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
+
+ if (ReadyState() == CONNECTING) {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ "connectionFailure", formatStrings);
+ } else {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ "netInterrupt", formatStrings);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void EventSourceImpl::DispatchFailConnection() {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return;
+ }
+ nsresult rv = ConsoleError();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to print to the console error");
+ }
+ rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
+ &EventSourceImpl::FailConnection),
+ NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // if the worker is shutting down, the dispatching of normal WorkerRunnables
+ // fails.
+ return;
+ }
+}
+
+void EventSourceImpl::FailConnection() {
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
+ return;
+ }
+ // Must change state to closed before firing event to content.
+ SetReadyState(CLOSED);
+ // When a user agent is to fail the connection, the user agent must set the
+ // readyState attribute to CLOSED and queue a task to fire a simple event
+ // named error at the EventSource object.
+ nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
+ if (NS_SUCCEEDED(rv)) {
+ // We can't hold the mutex while dispatching the event because the mutex
+ // is not reentrant, and content might call back into our code.
+ rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ }
+ }
+ // Call CloseInternal in the end of function because it may release
+ // EventSourceImpl.
+ CloseInternal();
+}
+
+NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
+
+ if (!mFrozen) {
+ nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("InitChannelAndRequestEventSource() failed");
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP EventSourceImpl::GetName(nsACString& aName) {
+ aName.AssignLiteral("EventSourceImpl");
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::Thaw() {
+ AssertIsOnMainThread();
+ if (IsClosed() || !mFrozen) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
+
+ mFrozen = false;
+ nsresult rv;
+ if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
+ this, &EventSourceImpl::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = true;
+
+ rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitChannelAndRequestEventSource(mIsMainThread);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::Freeze() {
+ AssertIsOnMainThread();
+ if (IsClosed() || mFrozen) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
+ mFrozen = true;
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
+ AssertIsOnTargetThread();
+ MOZ_ASSERT(!mIsShutDown);
+ UniquePtr<Message> message(std::move(mCurrentMessage));
+ ClearFields();
+
+ if (!message || message->mData.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // removes the trailing LF from mData
+ MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
+ "Invalid trailing character! LF was expected instead.");
+ message->mData.SetLength(message->mData.Length() - 1);
+
+ if (message->mEventName.IsEmpty()) {
+ message->mEventName.AssignLiteral("message");
+ }
+
+ mMessagesToDispatch.Push(message.release());
+
+ if (!mGoingToDispatchAllMessages) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
+ this, &EventSourceImpl::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = true;
+
+ return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+void EventSourceImpl::DispatchAllMessageEvents() {
+ AssertIsOnTargetThread();
+ mGoingToDispatchAllMessages = false;
+
+ if (IsClosed() || mFrozen) {
+ return;
+ }
+
+ nsresult rv;
+ AutoJSAPI jsapi;
+ {
+ auto lock = mSharedData.Lock();
+ rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
+ return;
+ }
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ while (mMessagesToDispatch.GetSize() > 0) {
+ UniquePtr<Message> message(mMessagesToDispatch.PopFront());
+
+ if (message->mLastEventID.isSome()) {
+ mLastEventID.Assign(message->mLastEventID.value());
+ }
+
+ if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
+ message->mLastEventID = Some(mLastEventID);
+ }
+
+ {
+ auto lock = mSharedData.Lock();
+ if (lock->mServiceNotifier) {
+ lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
+ message->mData, mReconnectionTime,
+ PR_Now());
+ }
+ }
+
+ // Now we can turn our string into a jsval
+ JS::Rooted<JS::Value> jsData(cx);
+ {
+ JSString* jsString;
+ jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
+ message->mData.Length());
+ NS_ENSURE_TRUE_VOID(jsString);
+
+ jsData.setString(jsString);
+ }
+
+ // create an event that uses the MessageEvent interface,
+ // which does not bubble, is not cancelable, and has no default action
+
+ RefPtr<EventSource> eventSource = GetEventSource();
+ RefPtr<MessageEvent> event =
+ new MessageEvent(eventSource, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
+ Cancelable::eNo, jsData, mOrigin, mLastEventID,
+ nullptr, Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ // We can't hold the mutex while dispatching the event because the mutex is
+ // not reentrant, and content might call back into our code.
+ IgnoredErrorResult err;
+ eventSource->DispatchEvent(*event, err);
+ if (err.Failed()) {
+ NS_WARNING("Failed to dispatch the message event!!!");
+ return;
+ }
+
+ if (IsClosed() || mFrozen) {
+ return;
+ }
+ }
+}
+
+void EventSourceImpl::ClearFields() {
+ AssertIsOnTargetThread();
+ mCurrentMessage = nullptr;
+ mLastFieldName.Truncate();
+ mLastFieldValue.Truncate();
+}
+
+nsresult EventSourceImpl::SetFieldAndClear() {
+ MOZ_ASSERT(!mIsShutDown);
+ AssertIsOnTargetThread();
+ if (mLastFieldName.IsEmpty()) {
+ mLastFieldValue.Truncate();
+ return NS_OK;
+ }
+ if (!mCurrentMessage) {
+ mCurrentMessage = MakeUnique<Message>();
+ }
+ char16_t first_char;
+ first_char = mLastFieldName.CharAt(0);
+
+ // with no case folding performed
+ switch (first_char) {
+ case char16_t('d'):
+ if (mLastFieldName.EqualsLiteral("data")) {
+ // If the field name is "data" append the field value to the data
+ // buffer, then append a single U+000A LINE FEED (LF) character
+ // to the data buffer.
+ mCurrentMessage->mData.Append(mLastFieldValue);
+ mCurrentMessage->mData.Append(LF_CHAR);
+ }
+ break;
+
+ case char16_t('e'):
+ if (mLastFieldName.EqualsLiteral("event")) {
+ mCurrentMessage->mEventName.Assign(mLastFieldValue);
+ }
+ break;
+
+ case char16_t('i'):
+ if (mLastFieldName.EqualsLiteral("id")) {
+ mCurrentMessage->mLastEventID = Some(mLastFieldValue);
+ }
+ break;
+
+ case char16_t('r'):
+ if (mLastFieldName.EqualsLiteral("retry")) {
+ uint32_t newValue = 0;
+ uint32_t i = 0; // we must ensure that there are only digits
+ bool assign = true;
+ for (i = 0; i < mLastFieldValue.Length(); ++i) {
+ if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
+ mLastFieldValue.CharAt(i) > (char16_t)'9') {
+ assign = false;
+ break;
+ }
+ newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
+ ((uint32_t)((char16_t)'0')));
+ }
+
+ if (assign) {
+ if (newValue < MIN_RECONNECTION_TIME_VALUE) {
+ mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
+ } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
+ mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
+ } else {
+ mReconnectionTime = newValue;
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ mLastFieldName.Truncate();
+ mLastFieldValue.Truncate();
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::CheckHealthOfRequestCallback(
+ nsIRequest* aRequestCallback) {
+ // This function could be run on target thread if http channel support
+ // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
+
+ // check if we have been closed or if the request has been canceled
+ // or if we have been frozen
+ if (IsClosed() || mFrozen || !mHttpChannel) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
+ NS_ENSURE_STATE(httpChannel);
+
+ if (httpChannel != mHttpChannel) {
+ NS_WARNING("wrong channel from request callback");
+ return NS_ERROR_ABORT;
+ }
+
+ return NS_OK;
+}
+
+nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
+ AssertIsOnTargetThread();
+ nsresult rv;
+
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
+
+ switch (mStatus) {
+ case PARSE_STATE_OFF:
+ NS_ERROR("Invalid state");
+ return NS_ERROR_FAILURE;
+ break;
+
+ case PARSE_STATE_BEGIN_OF_STREAM:
+ if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+ break;
+
+ case PARSE_STATE_CR_CHAR:
+ if (aChr == CR_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+
+ case PARSE_STATE_COMMENT:
+ if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ }
+
+ break;
+
+ case PARSE_STATE_FIELD_NAME:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
+ } else {
+ mLastFieldName += aChr;
+ }
+
+ break;
+
+ case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == SPACE_CHAR) {
+ mStatus = PARSE_STATE_FIELD_VALUE;
+ } else {
+ mLastFieldValue += aChr;
+ mStatus = PARSE_STATE_FIELD_VALUE;
+ }
+
+ break;
+
+ case PARSE_STATE_FIELD_VALUE:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr != 0) {
+ // Avoid appending the null char to the field value.
+ mLastFieldValue += aChr;
+ } else if (mLastFieldName.EqualsLiteral("id")) {
+ // Ignore the whole id field if aChr is null
+ mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
+ mLastFieldValue.Truncate();
+ }
+
+ break;
+
+ case PARSE_STATE_IGNORE_FIELD_VALUE:
+ if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ }
+ break;
+
+ case PARSE_STATE_BEGIN_OF_LINE:
+ if (aChr == CR_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else if (aChr != 0) {
+ // Avoid appending the null char to the field name.
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+class WorkerRunnableDispatcher final : public WorkerRunnable {
+ RefPtr<EventSourceImpl> mEventSourceImpl;
+
+ public:
+ WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
+ WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIRunnable> aEvent)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mEventSourceImpl(std::move(aImpl)),
+ mEvent(std::move(aEvent)) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return !NS_FAILED(mEvent->Run());
+ }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ // Ensure we drop the RefPtr on the worker thread
+ // and to not keep us alive longer than needed.
+ mEventSourceImpl = nullptr;
+ }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // We don't call WorkerRunnable::PreDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or
+ // transport thread).
+ return true;
+ }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // We don't call WorkerRunnable::PostDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or
+ // transport thread).
+ }
+
+ private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+} // namespace
+
+bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(!mWorkerRef);
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mIsShutDown) {
+ return false;
+ }
+
+ RefPtr<EventSourceImpl> self = this;
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "EventSource", [self]() { self->Close(); });
+
+ if (NS_WARN_IF(!workerRef)) {
+ return false;
+ }
+
+ mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ return true;
+}
+
+void EventSourceImpl::ReleaseWorkerRef() {
+ MOZ_ASSERT(IsClosed());
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+ mWorkerRef = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIEventTarget
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event_ref(aEvent);
+ if (mIsMainThread) {
+ return NS_DispatchToMainThread(event_ref.forget());
+ }
+
+ if (mIsShutDown) {
+ // We want to avoid clutter about errors in our shutdown logs,
+ // so just report NS_OK (we have no explicit return value
+ // for shutdown).
+ return NS_OK;
+ }
+
+ // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
+ // runnable.
+ RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
+ this, mWorkerRef->Private(), event_ref.forget());
+
+ if (!event->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+EventSourceImpl::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
+ return NS_OK;
+}
+////////////////////////////////////////////////////////////////////////////////
+// EventSource
+////////////////////////////////////////////////////////////////////////////////
+
+EventSource::EventSource(nsIGlobalObject* aGlobal,
+ nsICookieJarSettings* aCookieJarSettings,
+ bool aWithCredentials)
+ : DOMEventTargetHelper(aGlobal),
+ mWithCredentials(aWithCredentials),
+ mIsMainThread(NS_IsMainThread()) {
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(aCookieJarSettings);
+ mESImpl = new EventSourceImpl(this, aCookieJarSettings);
+}
+
+EventSource::~EventSource() = default;
+
+nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ // it doesn't bubble, and it isn't cancelable
+ event->InitEvent(aName, false, false);
+ event->SetTrusted(true);
+ ErrorResult rv;
+ DispatchEvent(*event, rv);
+ return rv.StealNSResult();
+}
+
+/* static */
+already_AddRefed<EventSource> EventSource::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aURL,
+ const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
+ if (ownerWindow) {
+ Document* doc = ownerWindow->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ cookieJarSettings = doc->CookieJarSettings();
+ } else {
+ // Worker side.
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ cookieJarSettings = workerPrivate->CookieJarSettings();
+ }
+
+ RefPtr<EventSource> eventSource = new EventSource(
+ global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
+
+ if (NS_IsMainThread()) {
+ // Get principal from document and init EventSourceImpl
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!scriptPrincipal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ if (!principal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ eventSource->mESImpl->Init(principal, aURL, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ eventSource->mESImpl->InitChannelAndRequestEventSource(true);
+ return eventSource.forget();
+ }
+
+ // Worker side.
+ {
+ // Scope for possible failures that need cleanup
+ auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
+
+ RefPtr<InitRunnable> initRunnable =
+ new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
+ initRunnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = initRunnable->ErrorCode();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // In workers we have to keep the worker alive using a WorkerRef in order
+ // to dispatch messages correctly.
+ // Note, initRunnable->Dispatch may have cleared mESImpl.
+ if (!eventSource->mESImpl ||
+ !eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
+ // The worker is already shutting down. Let's return an already closed
+ // object, but marked as Connecting.
+ if (eventSource->mESImpl) {
+ // mESImpl is nulled by this call such that EventSourceImpl is
+ // released before returning the object, otherwise
+ // it will set EventSource to a CLOSED state in its DTOR..
+ eventSource->mESImpl->Close();
+ }
+ eventSource->mReadyState = EventSourceImpl::CONNECTING;
+
+ guardESImpl.release();
+ return eventSource.forget();
+ }
+
+ // Let's connect to the server.
+ RefPtr<ConnectRunnable> connectRunnable =
+ new ConnectRunnable(workerPrivate, eventSource->mESImpl);
+ connectRunnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // End of scope for possible failures
+ guardESImpl.release();
+ }
+
+ return eventSource.forget();
+}
+
+// nsWrapperCache
+JSObject* EventSource::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return EventSource_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void EventSource::Close() {
+ AssertIsOnTargetThread();
+ if (mESImpl) {
+ // Close potentially kills ourself, ensure
+ // to not access any members afterwards.
+ mESImpl->Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+ if (tmp->mESImpl) {
+ // IsCertainlyaliveForCC will return true and cause the cycle
+ // collector to skip this instance when mESImpl is non-null and
+ // points back to ourself.
+ // mESImpl is initialized to be non-null in the constructor
+ // and should have been wiped out in our close function.
+ MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
+ tmp->Close();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+bool EventSource::IsCertainlyAliveForCC() const {
+ // Until we are double linked forth and back, we want to stay alive.
+ if (!mESImpl) {
+ return false;
+ }
+ auto lock = mESImpl->mSharedData.Lock();
+ return lock->mEventSource == this;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
+
+} // namespace mozilla::dom
diff --git a/dom/base/EventSource.h b/dom/base/EventSource.h
new file mode 100644
index 0000000000..22c3f78a37
--- /dev/null
+++ b/dom/base/EventSource.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This implementation has support only for http requests. It is because the
+ * spec has defined event streams only for http. HTTP is required because
+ * this implementation uses some http headers: "Last-Event-ID", "Cache-Control"
+ * and "Accept".
+ */
+
+#ifndef mozilla_dom_EventSource_h
+#define mozilla_dom_EventSource_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsDeque.h"
+#include "nsICookieJarSettings.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct EventSourceInit;
+
+class EventSourceImpl;
+
+class EventSource final : public DOMEventTargetHelper {
+ friend class EventSourceImpl;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(EventSource, DOMEventTargetHelper)
+ virtual bool IsCertainlyAliveForCC() const override;
+
+ // EventTarget
+ void DisconnectFromOwner() override {
+ DOMEventTargetHelper::DisconnectFromOwner();
+ Close();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ static already_AddRefed<EventSource> Constructor(
+ const GlobalObject& aGlobal, const nsAString& aURL,
+ const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv);
+
+ void GetUrl(nsAString& aURL) const {
+ AssertIsOnTargetThread();
+ aURL = mOriginalURL;
+ }
+
+ bool WithCredentials() const {
+ AssertIsOnTargetThread();
+ return mWithCredentials;
+ }
+
+ uint16_t ReadyState() const {
+ AssertIsOnTargetThread();
+ return mReadyState;
+ }
+
+ IMPL_EVENT_HANDLER(open)
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(error)
+
+ void Close();
+
+ private:
+ EventSource(nsIGlobalObject* aGlobal,
+ nsICookieJarSettings* aCookieJarSettings, bool aWithCredentials);
+ virtual ~EventSource();
+ // prevent bad usage
+ EventSource(const EventSource& x) = delete;
+ EventSource& operator=(const EventSource& x) = delete;
+
+ void AssertIsOnTargetThread() const {
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+ }
+
+ nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
+
+ // This EventSourceImpl is created, managed and destroyed
+ // by EventSource.
+ RefPtr<EventSourceImpl> mESImpl;
+ nsString mOriginalURL;
+ Atomic<uint32_t> mReadyState;
+ const bool mWithCredentials;
+ const bool mIsMainThread;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_EventSource_h
diff --git a/dom/base/EventSourceEventService.cpp b/dom/base/EventSourceEventService.cpp
new file mode 100644
index 0000000000..362c5fc658
--- /dev/null
+++ b/dom/base/EventSourceEventService.cpp
@@ -0,0 +1,312 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventSourceEventService.h"
+#include "mozilla/StaticPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+StaticRefPtr<EventSourceEventService> gEventSourceEventService;
+
+} // anonymous namespace
+
+class EventSourceBaseRunnable : public Runnable {
+ public:
+ EventSourceBaseRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID)
+ : Runnable("dom::EventSourceBaseRunnable"),
+ mHttpChannelId(aHttpChannelId),
+ mInnerWindowID(aInnerWindowID) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<EventSourceEventService> service =
+ EventSourceEventService::GetOrCreate();
+ MOZ_ASSERT(service);
+
+ EventSourceEventService::EventSourceListeners listeners;
+
+ service->GetListeners(mInnerWindowID, listeners);
+
+ for (uint32_t i = 0; i < listeners.Length(); ++i) {
+ DoWork(listeners[i]);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ ~EventSourceBaseRunnable() = default;
+
+ virtual void DoWork(nsIEventSourceEventListener* aListener) = 0;
+
+ uint64_t mHttpChannelId;
+ uint64_t mInnerWindowID;
+};
+
+class EventSourceConnectionOpenedRunnable final
+ : public EventSourceBaseRunnable {
+ public:
+ EventSourceConnectionOpenedRunnable(uint64_t aHttpChannelId,
+ uint64_t aInnerWindowID)
+ : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
+
+ private:
+ virtual void DoWork(nsIEventSourceEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->EventSourceConnectionOpened(mHttpChannelId);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EventSourceConnectionOpened failed");
+ }
+};
+
+class EventSourceConnectionClosedRunnable final
+ : public EventSourceBaseRunnable {
+ public:
+ EventSourceConnectionClosedRunnable(uint64_t aHttpChannelId,
+ uint64_t aInnerWindowID)
+ : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
+
+ private:
+ virtual void DoWork(nsIEventSourceEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->EventSourceConnectionClosed(mHttpChannelId);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EventSourceConnectionClosed failed");
+ }
+};
+
+class EventSourceEventRunnable final : public EventSourceBaseRunnable {
+ public:
+ EventSourceEventRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID,
+ const nsAString& aEventName,
+ const nsAString& aLastEventID,
+ const nsAString& aData, uint32_t aRetry,
+ DOMHighResTimeStamp aTimeStamp)
+ : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID),
+ mEventName(aEventName),
+ mLastEventID(aLastEventID),
+ mData(aData),
+ mRetry(aRetry),
+ mTimeStamp(aTimeStamp) {}
+
+ private:
+ virtual void DoWork(nsIEventSourceEventListener* aListener) override {
+ DebugOnly<nsresult> rv = aListener->EventReceived(
+ mHttpChannelId, mEventName, mLastEventID, mData, mRetry, mTimeStamp);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Event op failed");
+ }
+
+ nsString mEventName;
+ nsString mLastEventID;
+ nsString mData;
+ uint32_t mRetry;
+ DOMHighResTimeStamp mTimeStamp;
+};
+
+/* static */
+already_AddRefed<EventSourceEventService>
+EventSourceEventService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gEventSourceEventService) {
+ gEventSourceEventService = new EventSourceEventService();
+ }
+
+ RefPtr<EventSourceEventService> service = gEventSourceEventService.get();
+ return service.forget();
+}
+
+NS_INTERFACE_MAP_BEGIN(EventSourceEventService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEventSourceEventService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIEventSourceEventService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(EventSourceEventService)
+NS_IMPL_RELEASE(EventSourceEventService)
+
+EventSourceEventService::EventSourceEventService() : mCountListeners(0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+}
+
+EventSourceEventService::~EventSourceEventService() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void EventSourceEventService::EventSourceConnectionOpened(
+ uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<EventSourceConnectionOpenedRunnable> runnable =
+ new EventSourceConnectionOpenedRunnable(aHttpChannelId, aInnerWindowID);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void EventSourceEventService::EventSourceConnectionClosed(
+ uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+ RefPtr<EventSourceConnectionClosedRunnable> runnable =
+ new EventSourceConnectionClosedRunnable(aHttpChannelId, aInnerWindowID);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void EventSourceEventService::EventReceived(
+ uint64_t aHttpChannelId, uint64_t aInnerWindowID,
+ const nsAString& aEventName, const nsAString& aLastEventID,
+ const nsAString& aData, uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<EventSourceEventRunnable> runnable =
+ new EventSourceEventRunnable(aHttpChannelId, aInnerWindowID, aEventName,
+ aLastEventID, aData, aRetry, aTimeStamp);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+NS_IMETHODIMP
+EventSourceEventService::AddListener(uint64_t aInnerWindowID,
+ nsIEventSourceEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+ ++mCountListeners;
+
+ WindowListener* listener = mWindows.GetOrInsertNew(aInnerWindowID);
+
+ listener->mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventSourceEventService::RemoveListener(
+ uint64_t aInnerWindowID, nsIEventSourceEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!listener->mListeners.RemoveElement(aListener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The last listener for this window.
+ if (listener->mListeners.IsEmpty()) {
+ mWindows.Remove(aInnerWindowID);
+ }
+
+ MOZ_ASSERT(mCountListeners);
+ --mCountListeners;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventSourceEventService::HasListenerFor(uint64_t aInnerWindowID,
+ bool* aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *aResult = mWindows.Get(aInnerWindowID);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventSourceEventService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WindowListener* listener = mWindows.Get(innerID);
+ if (!listener) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
+ mCountListeners -= listener->mListeners.Length();
+ mWindows.Remove(innerID);
+ }
+
+ // This should not happen.
+ return NS_ERROR_FAILURE;
+}
+
+void EventSourceEventService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gEventSourceEventService) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gEventSourceEventService, "xpcom-shutdown");
+ obs->RemoveObserver(gEventSourceEventService, "inner-window-destroyed");
+ }
+
+ mWindows.Clear();
+ gEventSourceEventService = nullptr;
+ }
+}
+
+bool EventSourceEventService::HasListeners() const { return !!mCountListeners; }
+
+void EventSourceEventService::GetListeners(
+ uint64_t aInnerWindowID,
+ EventSourceEventService::EventSourceListeners& aListeners) const {
+ aListeners.Clear();
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return;
+ }
+
+ aListeners.AppendElements(listener->mListeners);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/EventSourceEventService.h b/dom/base/EventSourceEventService.h
new file mode 100644
index 0000000000..e4e0adec88
--- /dev/null
+++ b/dom/base/EventSourceEventService.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_EventSourceEventService_h
+#define mozilla_dom_EventSourceEventService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+#include "nsIEventSourceEventService.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+class EventSourceEventService final : public nsIEventSourceEventService,
+ public nsIObserver {
+ friend class EventSourceBaseRunnable;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIEVENTSOURCEEVENTSERVICE
+
+ static already_AddRefed<EventSourceEventService> GetOrCreate();
+
+ void EventSourceConnectionOpened(uint64_t aHttpChannelId,
+ uint64_t aInnerWindowID);
+
+ void EventSourceConnectionClosed(uint64_t aHttpChannelId,
+ uint64_t aInnerWindowID);
+
+ void EventReceived(uint64_t aHttpChannelId, uint64_t aInnerWindowID,
+ const nsAString& aEventName, const nsAString& aLastEventID,
+ const nsAString& aData, uint32_t aRetry,
+ DOMHighResTimeStamp aTimeStamp);
+
+ private:
+ EventSourceEventService();
+ ~EventSourceEventService();
+
+ bool HasListeners() const;
+ void Shutdown();
+
+ using EventSourceListeners = nsTArray<nsCOMPtr<nsIEventSourceEventListener>>;
+
+ struct WindowListener {
+ EventSourceListeners mListeners;
+ };
+
+ void GetListeners(uint64_t aInnerWindowID,
+ EventSourceListeners& aListeners) const;
+
+ // Used only on the main-thread.
+ nsClassHashtable<nsUint64HashKey, WindowListener> mWindows;
+
+ Atomic<uint64_t> mCountListeners;
+};
+
+} // namespace mozilla::dom
+
+/**
+ * Casting EventSourceEventService to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports* ToSupports(mozilla::dom::EventSourceEventService* p) {
+ return NS_ISUPPORTS_CAST(nsIEventSourceEventService*, p);
+}
+
+#endif // mozilla_dom_EventSourceEventService_h
diff --git a/dom/base/External.cpp b/dom/base/External.cpp
new file mode 100644
index 0000000000..2ac937cb15
--- /dev/null
+++ b/dom/base/External.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/External.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(External, mParent)
+
+JSObject* External::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return External_Binding::Wrap(aCx, this, aGivenProto);
+};
diff --git a/dom/base/External.h b/dom/base/External.h
new file mode 100644
index 0000000000..9ddda47785
--- /dev/null
+++ b/dom/base/External.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_External_h
+#define mozilla_dom_External_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/ExternalBinding.h"
+
+namespace mozilla::dom {
+
+class External : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(External)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(External)
+
+ explicit External(nsISupports* aParent) : mParent(aParent) {}
+
+ nsISupports* GetParentObject() const { return mParent; }
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ void AddSearchProvider() {}
+ void IsSearchProviderInstalled() {}
+
+ protected:
+ virtual ~External() = default;
+
+ private:
+ nsCOMPtr<nsISupports> mParent;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_External_h
diff --git a/dom/base/FilteredNodeIterator.h b/dom/base/FilteredNodeIterator.h
new file mode 100644
index 0000000000..58d503f799
--- /dev/null
+++ b/dom/base/FilteredNodeIterator.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Implementation of a generic filtered iterator over nodes.
+ */
+
+#ifndef mozilla_dom_FilteredNodeIterator_h
+#define mozilla_dom_FilteredNodeIterator_h
+
+#include "nsINode.h"
+
+namespace mozilla::dom {
+
+template <typename T, typename Iter>
+class FilteredNodeIterator : public Iter {
+ public:
+ explicit FilteredNodeIterator(const nsINode& aNode) : Iter(aNode) {
+ EnsureValid();
+ }
+
+ FilteredNodeIterator& begin() { return *this; }
+ using Iter::end;
+
+ void operator++() {
+ Iter::operator++();
+ EnsureValid();
+ }
+
+ using Iter::operator!=;
+
+ T* operator*() {
+ nsINode* node = Iter::operator*();
+ MOZ_ASSERT(!node || T::FromNode(node));
+ return static_cast<T*>(node);
+ }
+
+ private:
+ void EnsureValid() {
+ while (true) {
+ nsINode* node = Iter::operator*();
+ if (!node || T::FromNode(node)) {
+ return;
+ }
+ Iter::operator++();
+ }
+ }
+
+ FilteredNodeIterator() : Iter() {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_FilteredNodeIterator.h
diff --git a/dom/base/FlushType.h b/dom/base/FlushType.h
new file mode 100644
index 0000000000..04a253c534
--- /dev/null
+++ b/dom/base/FlushType.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_FlushType_h
+#define mozilla_FlushType_h
+
+#include <stdint.h>
+#include "X11UndefineNone.h"
+#include "mozilla/EnumeratedArray.h"
+
+namespace mozilla {
+
+/**
+ * This is the enum used by Document::FlushPendingNotifications to
+ * decide what to flush.
+ *
+ * Please note that if you change these values, you should sync it with the
+ * kFlushTypeNames array below.
+ */
+enum class FlushType : uint8_t {
+ None, /* Actually don't flush anything */
+ Event, /* Flush pending events before notify other observers */
+ Content, /* flush the content model construction */
+ ContentAndNotify, /* As above, plus flush the frame model
+ construction and other nsIMutationObserver
+ notifications. */
+ Style, /* As above, plus flush style reresolution */
+ Frames, /* As above, plus flush frame construction */
+ EnsurePresShellInitAndFrames, /* As above, plus ensure the pres shell is alive
+ */
+ InterruptibleLayout, /* As above, plus flush reflow, but allow it to be
+ interrupted (so an incomplete layout may result) */
+ Layout, /* As above, but layout must run to completion */
+ Display, /* As above, plus flush painting */
+ Count
+};
+
+// Flush type strings that will be displayed in the profiler
+// clang-format off
+const EnumeratedArray<FlushType, FlushType::Count, const char*>
+ kFlushTypeNames = {
+ "",
+ "Event",
+ "Content",
+ "ContentAndNotify",
+ "Style",
+ // As far as the profiler is concerned, EnsurePresShellInitAndFrames and
+ // Frames are the same
+ "Style",
+ "Style",
+ "InterruptibleLayout",
+ "Layout",
+ "Display"
+};
+// clang-format on
+
+struct ChangesToFlush {
+ ChangesToFlush(FlushType aFlushType, bool aFlushAnimations)
+ : mFlushType(aFlushType), mFlushAnimations(aFlushAnimations) {}
+
+ FlushType mFlushType;
+ bool mFlushAnimations;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_FlushType_h
diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp
new file mode 100644
index 0000000000..33be8f2232
--- /dev/null
+++ b/dom/base/FormData.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FormData.h"
+#include "nsIInputStream.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/Encoding.h"
+#include "nsGenericHTMLElement.h"
+#include "nsQueryObject.h"
+
+#include "MultipartBlobImpl.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+FormData::FormData(nsISupports* aOwner, NotNull<const Encoding*> aEncoding,
+ Element* aSubmitter)
+ : HTMLFormSubmission(nullptr, u""_ns, aEncoding),
+ mOwner(aOwner),
+ mSubmitter(aSubmitter) {}
+
+FormData::FormData(const FormData& aFormData)
+ : HTMLFormSubmission(aFormData.mActionURL, aFormData.mTarget,
+ aFormData.mEncoding) {
+ mOwner = aFormData.mOwner;
+ mSubmitter = aFormData.mSubmitter;
+ mFormData = aFormData.mFormData.Clone();
+}
+
+namespace {
+
+already_AddRefed<File> GetOrCreateFileCalledBlob(Blob& aBlob,
+ ErrorResult& aRv) {
+ // If this is file, we can just use it
+ RefPtr<File> file = aBlob.ToFile();
+ if (file) {
+ return file.forget();
+ }
+
+ // Forcing 'blob' as filename
+ file = aBlob.ToFile(u"blob"_ns, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return file.forget();
+}
+
+already_AddRefed<File> GetBlobForFormDataStorage(
+ Blob& aBlob, const Optional<nsAString>& aFilename, ErrorResult& aRv) {
+ // Forcing a filename
+ if (aFilename.WasPassed()) {
+ RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return file.forget();
+ }
+
+ return GetOrCreateFileCalledBlob(aBlob, aRv);
+}
+
+} // namespace
+
+// -------------------------------------------------------------------------
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FormData)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubmitter)
+
+ for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) {
+ ImplCycleCollectionUnlink(tmp->mFormData[i].value);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubmitter)
+
+ for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) {
+ ImplCycleCollectionTraverse(cb, tmp->mFormData[i].value,
+ "mFormData[i].GetAsBlob()", 0);
+ }
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FormData)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FormData)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// -------------------------------------------------------------------------
+// HTMLFormSubmission
+nsresult FormData::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) {
+ MOZ_ASSERT_UNREACHABLE("Shouldn't call FormData::GetEncodedSubmission");
+ return NS_OK;
+}
+
+void FormData::Append(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aRv) {
+ AddNameValuePair(aName, aValue);
+}
+
+void FormData::Append(const nsAString& aName, Blob& aBlob,
+ const Optional<nsAString>& aFilename, ErrorResult& aRv) {
+ RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ AddNameBlobPair(aName, file);
+}
+
+void FormData::Append(const nsAString& aName, Directory* aDirectory) {
+ AddNameDirectoryPair(aName, aDirectory);
+}
+
+void FormData::Append(const FormData& aFormData) {
+ for (uint32_t i = 0; i < aFormData.mFormData.Length(); ++i) {
+ mFormData.AppendElement(aFormData.mFormData[i]);
+ }
+}
+
+void FormData::Delete(const nsAString& aName) {
+ mFormData.RemoveElementsBy([&aName](const auto& formDataItem) {
+ return aName.Equals(formDataItem.name);
+ });
+}
+
+void FormData::Get(const nsAString& aName,
+ Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue) {
+ for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+ if (aName.Equals(mFormData[i].name)) {
+ aOutValue.SetValue() = mFormData[i].value;
+ return;
+ }
+ }
+
+ aOutValue.SetNull();
+}
+
+void FormData::GetAll(const nsAString& aName,
+ nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues) {
+ for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+ if (aName.Equals(mFormData[i].name)) {
+ OwningBlobOrDirectoryOrUSVString* element = aValues.AppendElement();
+ *element = mFormData[i].value;
+ }
+ }
+}
+
+bool FormData::Has(const nsAString& aName) {
+ for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+ if (aName.Equals(mFormData[i].name)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
+ MOZ_ASSERT(aBlob);
+
+ nsAutoString usvName(aName);
+ if (!NormalizeUSVString(usvName)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<File> file;
+ ErrorResult rv;
+ file = GetOrCreateFileCalledBlob(*aBlob, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameFilePair(data, usvName, file);
+ return NS_OK;
+}
+
+nsresult FormData::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) {
+ MOZ_ASSERT(aDirectory);
+
+ nsAutoString usvName(aName);
+ if (!NormalizeUSVString(usvName)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameDirectoryPair(data, usvName, aDirectory);
+ return NS_OK;
+}
+
+FormData::FormDataTuple* FormData::RemoveAllOthersAndGetFirstFormDataTuple(
+ const nsAString& aName) {
+ FormDataTuple* lastFoundTuple = nullptr;
+ uint32_t lastFoundIndex = mFormData.Length();
+ // We have to use this slightly awkward for loop since uint32_t >= 0 is an
+ // error for being always true.
+ for (uint32_t i = mFormData.Length(); i-- > 0;) {
+ if (aName.Equals(mFormData[i].name)) {
+ if (lastFoundTuple) {
+ // The one we found earlier was not the first one, we can remove it.
+ mFormData.RemoveElementAt(lastFoundIndex);
+ }
+
+ lastFoundTuple = &mFormData[i];
+ lastFoundIndex = i;
+ }
+ }
+
+ return lastFoundTuple;
+}
+
+void FormData::Set(const nsAString& aName, Blob& aBlob,
+ const Optional<nsAString>& aFilename, ErrorResult& aRv) {
+ FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
+ if (tuple) {
+ RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SetNameFilePair(tuple, aName, file);
+ } else {
+ Append(aName, aBlob, aFilename, aRv);
+ }
+}
+
+void FormData::Set(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aRv) {
+ FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
+ if (tuple) {
+ SetNameValuePair(tuple, aName, aValue);
+ } else {
+ Append(aName, aValue, aRv);
+ }
+}
+
+uint32_t FormData::GetIterableLength() const { return mFormData.Length(); }
+
+const nsAString& FormData::GetKeyAtIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mFormData.Length());
+ return mFormData[aIndex].name;
+}
+
+const OwningBlobOrDirectoryOrUSVString& FormData::GetValueAtIndex(
+ uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mFormData.Length());
+ return mFormData[aIndex].value;
+}
+
+void FormData::SetNameValuePair(FormDataTuple* aData, const nsAString& aName,
+ const nsAString& aValue) {
+ MOZ_ASSERT(aData);
+ aData->name = aName;
+ aData->value.SetAsUSVString() = aValue;
+}
+
+void FormData::SetNameFilePair(FormDataTuple* aData, const nsAString& aName,
+ File* aFile) {
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aFile);
+
+ aData->name = aName;
+ aData->value.SetAsBlob() = aFile;
+}
+
+void FormData::SetNameDirectoryPair(FormDataTuple* aData,
+ const nsAString& aName,
+ Directory* aDirectory) {
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aDirectory);
+
+ aData->name = aName;
+ aData->value.SetAsDirectory() = aDirectory;
+}
+
+/* virtual */
+JSObject* FormData::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FormData_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata
+/* static */
+already_AddRefed<FormData> FormData::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<NonNull<HTMLFormElement> >& aFormElement,
+ nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) {
+ RefPtr<FormData> formData;
+ // 1. If form is given, then:
+ if (aFormElement.WasPassed()) {
+ // 1.1. If submitter is non-null, then:
+ if (aSubmitter) {
+ nsCOMPtr<nsIFormControl> fc = do_QueryObject(aSubmitter);
+
+ // 1.1.1. If submitter is not a submit button, then throw a TypeError.
+ if (!fc || !fc->IsSubmitControl()) {
+ aRv.ThrowTypeError("The submitter is not a submit button.");
+ return nullptr;
+ }
+
+ // 1.1.2. If submitter's form owner is not this form element, then throw a
+ // "NotFoundError" DOMException.
+ if (fc->GetForm() != &aFormElement.Value()) {
+ aRv.ThrowNotFoundError("The submitter is not owned by this form.");
+ return nullptr;
+ }
+ }
+
+ // 1.2. Let list be the result of constructing the entry list for form and
+ // submitter.
+ formData =
+ new FormData(aGlobal.GetAsSupports(), UTF_8_ENCODING, aSubmitter);
+ aRv = aFormElement.Value().ConstructEntryList(formData);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Step 9. Return a shallow clone of entry list.
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set
+ formData = formData->Clone();
+ } else {
+ formData = new FormData(aGlobal.GetAsSupports());
+ }
+
+ return formData.forget();
+}
+
+// contentTypeWithCharset can be set to the contentType or
+// contentType+charset based on what the spec says.
+// See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+nsresult FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const {
+ FSMultipartFormData fs(nullptr, u""_ns, UTF_8_ENCODING, nullptr);
+ nsresult rv = CopySubmissionDataTo(&fs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fs.GetContentType(aContentTypeWithCharset);
+ aCharset.Truncate();
+ *aContentLength = 0;
+ NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
+
+ return NS_OK;
+}
+
+already_AddRefed<FormData> FormData::Clone() {
+ RefPtr<FormData> formData = new FormData(*this);
+ return formData.forget();
+}
+
+nsresult FormData::CopySubmissionDataTo(
+ HTMLFormSubmission* aFormSubmission) const {
+ MOZ_ASSERT(aFormSubmission, "Must have FormSubmission!");
+ for (size_t i = 0; i < mFormData.Length(); ++i) {
+ if (mFormData[i].value.IsUSVString()) {
+ aFormSubmission->AddNameValuePair(mFormData[i].name,
+ mFormData[i].value.GetAsUSVString());
+ } else if (mFormData[i].value.IsBlob()) {
+ aFormSubmission->AddNameBlobPair(mFormData[i].name,
+ mFormData[i].value.GetAsBlob());
+ } else {
+ MOZ_ASSERT(mFormData[i].value.IsDirectory());
+ aFormSubmission->AddNameDirectoryPair(
+ mFormData[i].name, mFormData[i].value.GetAsDirectory());
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/FormData.h b/dom/base/FormData.h
new file mode 100644
index 0000000000..9078c7782b
--- /dev/null
+++ b/dom/base/FormData.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FormData_h
+#define mozilla_dom_FormData_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormDataBinding.h"
+#include "nsGenericHTMLElement.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class HTMLFormElement;
+class GlobalObject;
+
+class FormData final : public nsISupports,
+ public HTMLFormSubmission,
+ public nsWrapperCache {
+ private:
+ FormData(const FormData& aFormData);
+ ~FormData() = default;
+
+ struct FormDataTuple {
+ nsString name;
+ OwningBlobOrDirectoryOrUSVString value;
+ };
+
+ // Returns the FormDataTuple to modify. This may be null, in which case
+ // no element with aName was found.
+ FormDataTuple* RemoveAllOthersAndGetFirstFormDataTuple(
+ const nsAString& aName);
+
+ void SetNameValuePair(FormDataTuple* aData, const nsAString& aName,
+ const nsAString& aValue);
+
+ void SetNameFilePair(FormDataTuple* aData, const nsAString& aName,
+ File* aFile);
+
+ void SetNameDirectoryPair(FormDataTuple* aData, const nsAString& aName,
+ Directory* aDirectory);
+
+ public:
+ explicit FormData(nsISupports* aOwner = nullptr,
+ NotNull<const Encoding*> aEncoding = UTF_8_ENCODING,
+ Element* aSubmitter = nullptr);
+
+ already_AddRefed<FormData> Clone();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FormData)
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ static already_AddRefed<FormData> Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<NonNull<HTMLFormElement> >& aFormElement,
+ nsGenericHTMLElement* aSubmitter, ErrorResult& aRv);
+
+ void Append(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aRv);
+
+ void Append(const nsAString& aName, Blob& aBlob,
+ const Optional<nsAString>& aFilename, ErrorResult& aRv);
+
+ void Append(const nsAString& aName, Directory* aDirectory);
+
+ void Append(const FormData& aFormData);
+
+ void Delete(const nsAString& aName);
+
+ void Get(const nsAString& aName,
+ Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue);
+
+ void GetAll(const nsAString& aName,
+ nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues);
+
+ bool Has(const nsAString& aName);
+
+ void Set(const nsAString& aName, Blob& aBlob,
+ const Optional<nsAString>& aFilename, ErrorResult& aRv);
+ void Set(const nsAString& aName, const nsAString& aValue, ErrorResult& aRv);
+
+ uint32_t GetIterableLength() const;
+
+ const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
+
+ const OwningBlobOrDirectoryOrUSVString& GetValueAtIndex(
+ uint32_t aIndex) const;
+
+ // HTMLFormSubmission
+ virtual nsresult GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) override;
+
+ virtual nsresult AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) override {
+ nsAutoString usvName(aName);
+ nsAutoString usvValue(aValue);
+ if (!NormalizeUSVString(usvName) || !NormalizeUSVString(usvValue)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameValuePair(data, usvName, usvValue);
+ return NS_OK;
+ }
+
+ virtual nsresult AddNameBlobPair(const nsAString& aName,
+ Blob* aBlob) override;
+
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) override;
+
+ using FormDataEntryCallback =
+ bool (*)(const nsString& aName,
+ const OwningBlobOrDirectoryOrUSVString& aValue, void* aClosure);
+
+ uint32_t Length() const { return mFormData.Length(); }
+
+ // Stops iteration and returns false if any invocation of callback returns
+ // false. Returns true otherwise.
+ bool ForEach(FormDataEntryCallback aFunc, void* aClosure) {
+ for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+ FormDataTuple& tuple = mFormData[i];
+ if (!aFunc(tuple.name, tuple.value, aClosure)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ nsresult GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const;
+
+ nsresult CopySubmissionDataTo(HTMLFormSubmission* aFormSubmission) const;
+
+ Element* GetSubmitterElement() const { return mSubmitter.get(); }
+
+ private:
+ nsCOMPtr<nsISupports> mOwner;
+
+ // Submitter element.
+ RefPtr<Element> mSubmitter;
+
+ nsTArray<FormDataTuple> mFormData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FormData_h
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
new file mode 100644
index 0000000000..dc4e43064a
--- /dev/null
+++ b/dom/base/FragmentOrElement.cpp
@@ -0,0 +1,2054 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all element classes and DocumentFragment.
+ */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/FragmentOrElement.h"
+#include "DOMIntersectionObserver.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/mozInlineSpellChecker.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/dom/Attr.h"
+#include "nsDOMAttributeMap.h"
+#include "nsAtom.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIControllers.h"
+#include "nsIDocumentEncoder.h"
+#include "nsFocusManager.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsNetUtil.h"
+#include "nsIFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsDOMCID.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsContentList.h"
+#include "nsDOMTokenList.h"
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsXULElement.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "nsAttrValueOrString.h"
+#include "nsQueryObject.h"
+#include "nsFrameSelection.h"
+#ifdef DEBUG
+# include "nsRange.h"
+#endif
+
+#include "nsFrameLoader.h"
+#include "nsPIDOMWindow.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsTextFragment.h"
+#include "nsContentCID.h"
+#include "nsWindowSizes.h"
+
+#include "nsIWidget.h"
+
+#include "nsNodeInfoManager.h"
+#include "nsGenericHTMLElement.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "ChildIterator.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/NodeListBinding.h"
+
+#include "nsCCUncollectableMarker.h"
+
+#include "mozAutoDocUpdate.h"
+
+#include "mozilla/Sprintf.h"
+#include "nsDOMMutationObserver.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsCycleCollector.h"
+#include "xpcpublic.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/CORSMode.h"
+
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/SVGUseElement.h"
+
+#include "nsStyledElement.h"
+#include "nsIContentInlines.h"
+#include "nsChildContentList.h"
+#include "mozilla/BloomFilter.h"
+
+#include "NodeUbiReporting.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+int32_t nsIContent::sTabFocusModel = eTabFocus_any;
+bool nsIContent::sTabFocusModelAppliesToXUL = false;
+uint64_t nsMutationGuard::sGeneration = 0;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsIContent)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsIContent)
+ MOZ_ASSERT_UNREACHABLE("Our subclasses don't call us");
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsIContent)
+ MOZ_ASSERT_UNREACHABLE("Our subclasses don't call us");
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN(nsIContent)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // Don't bother to QI to cycle collection, because our CC impl is
+ // not doing anything anyway.
+ NS_INTERFACE_MAP_ENTRY(nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsINode)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
+ new nsNodeSupportsWeakRefTearoff(this))
+ // DOM bindings depend on the identity pointer being the
+ // same as nsINode (which nsIContent inherits).
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(nsIContent)
+
+NS_IMPL_DOMARENA_DESTROY(nsIContent)
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY(
+ nsIContent, LastRelease(), Destroy())
+
+nsIContent* nsIContent::FindFirstNonChromeOnlyAccessContent() const {
+ // This handles also nested native anonymous content.
+ for (const nsIContent* content = this; content;
+ content = content->GetChromeOnlyAccessSubtreeRootParent()) {
+ if (!content->ChromeOnlyAccess()) {
+ // Oops, this function signature allows casting const to
+ // non-const. (Then again, so does GetFirstChild()->GetParent().)
+ return const_cast<nsIContent*>(content);
+ }
+ }
+ return nullptr;
+}
+
+// https://dom.spec.whatwg.org/#dom-slotable-assignedslot
+HTMLSlotElement* nsIContent::GetAssignedSlotByMode() const {
+ /**
+ * Get slotable's assigned slot for the result of
+ * find a slot with open flag UNSET [1].
+ *
+ * [1] https://dom.spec.whatwg.org/#assign-a-slot
+ */
+ HTMLSlotElement* slot = GetAssignedSlot();
+ if (!slot) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(GetParent());
+ MOZ_ASSERT(GetParent()->GetShadowRoot());
+
+ /**
+ * Additional check for open flag SET:
+ * If slotable’s parent’s shadow root's mode is not "open",
+ * then return null.
+ */
+ if (GetParent()->GetShadowRoot()->IsClosed()) {
+ return nullptr;
+ }
+
+ return slot;
+}
+
+nsIContent::IMEState nsIContent::GetDesiredIMEState() {
+ if (!IsEditable()) {
+ // Check for the special case where we're dealing with elements which don't
+ // have the editable flag set, but are readwrite (such as text controls).
+ if (!IsElement() ||
+ !AsElement()->State().HasState(ElementState::READWRITE)) {
+ return IMEState(IMEEnabled::Disabled);
+ }
+ }
+ // NOTE: The content for independent editors (e.g., input[type=text],
+ // textarea) must override this method, so, we don't need to worry about
+ // that here.
+ nsIContent* editableAncestor = GetEditingHost();
+
+ // This is in another editable content, use the result of it.
+ if (editableAncestor && editableAncestor != this) {
+ return editableAncestor->GetDesiredIMEState();
+ }
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return IMEState(IMEEnabled::Disabled);
+ }
+ nsPresContext* pc = doc->GetPresContext();
+ if (!pc) {
+ return IMEState(IMEEnabled::Disabled);
+ }
+ HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(pc);
+ if (!htmlEditor) {
+ return IMEState(IMEEnabled::Disabled);
+ }
+ IMEState state;
+ htmlEditor->GetPreferredIMEState(&state);
+ return state;
+}
+
+bool nsIContent::HasIndependentSelection() const {
+ nsIFrame* frame = GetPrimaryFrame();
+ return (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
+}
+
+dom::Element* nsIContent::GetEditingHost() {
+ // If this isn't editable, return nullptr.
+ if (!IsEditable()) {
+ return nullptr;
+ }
+
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // If this is in designMode, we should return <body>
+ if (IsInDesignMode() && !IsInShadowTree()) {
+ return doc->GetBodyElement();
+ }
+
+ dom::Element* editableParentElement = nullptr;
+ for (dom::Element* parent = GetParentElement();
+ parent && parent->HasFlag(NODE_IS_EDITABLE);
+ parent = editableParentElement->GetParentElement()) {
+ editableParentElement = parent;
+ }
+ return editableParentElement ? editableParentElement
+ : dom::Element::FromNode(this);
+}
+
+nsresult nsIContent::LookupNamespaceURIInternal(
+ const nsAString& aNamespacePrefix, nsAString& aNamespaceURI) const {
+ if (aNamespacePrefix.EqualsLiteral("xml")) {
+ // Special-case for xml prefix
+ aNamespaceURI.AssignLiteral("http://www.w3.org/XML/1998/namespace");
+ return NS_OK;
+ }
+
+ if (aNamespacePrefix.EqualsLiteral("xmlns")) {
+ // Special-case for xmlns prefix
+ aNamespaceURI.AssignLiteral("http://www.w3.org/2000/xmlns/");
+ return NS_OK;
+ }
+
+ RefPtr<nsAtom> name;
+ if (!aNamespacePrefix.IsEmpty()) {
+ name = NS_Atomize(aNamespacePrefix);
+ NS_ENSURE_TRUE(name, NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ name = nsGkAtoms::xmlns;
+ }
+ // Trace up the content parent chain looking for the namespace
+ // declaration that declares aNamespacePrefix.
+ for (Element* element = GetAsElementOrParentElement(); element;
+ element = element->GetParentElement()) {
+ if (element->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI)) {
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsAtom* nsIContent::GetLang() const {
+ for (const Element* element = GetAsElementOrParentElement(); element;
+ element = element->GetParentElement()) {
+ if (!element->GetAttrCount()) {
+ continue;
+ }
+
+ // xml:lang has precedence over lang on HTML elements (see
+ // XHTML1 section C.7).
+ const nsAttrValue* attr =
+ element->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
+ if (!attr && element->SupportsLangAttr()) {
+ attr = element->GetParsedAttr(nsGkAtoms::lang);
+ }
+ if (attr) {
+ MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+ MOZ_ASSERT(attr->GetAtomValue());
+ return attr->GetAtomValue();
+ }
+ }
+
+ return nullptr;
+}
+
+nsIURI* nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
+ if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
+ if (URLExtraData* data = use->GetContentURLData()) {
+ return data->BaseURI();
+ }
+ }
+
+ return OwnerDoc()->GetBaseURI(aTryUseXHRDocBaseURI);
+}
+
+nsIURI* nsIContent::GetBaseURIForStyleAttr() const {
+ if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
+ if (URLExtraData* data = use->GetContentURLData()) {
+ return data->BaseURI();
+ }
+ }
+ // This also ignores the case that SVG inside XBL binding.
+ // But it is probably fine.
+ return OwnerDoc()->GetDocBaseURI();
+}
+
+already_AddRefed<URLExtraData> nsIContent::GetURLDataForStyleAttr(
+ nsIPrincipal* aSubjectPrincipal) const {
+ if (SVGUseElement* use = GetContainingSVGUseShadowHost()) {
+ if (URLExtraData* data = use->GetContentURLData()) {
+ return do_AddRef(data);
+ }
+ }
+ auto* doc = OwnerDoc();
+ if (aSubjectPrincipal && aSubjectPrincipal != NodePrincipal()) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ doc->ReferrerInfoForInternalCSSAndSVGResources();
+ // TODO: Cache this?
+ return MakeAndAddRef<URLExtraData>(doc->GetDocBaseURI(), referrerInfo,
+ aSubjectPrincipal);
+ }
+ return do_AddRef(doc->DefaultStyleAttrURLData());
+}
+
+void nsIContent::ConstructUbiNode(void* storage) {
+ JS::ubi::Concrete<nsIContent>::construct(storage, this);
+}
+
+bool nsIContent::InclusiveDescendantMayNeedSpellchecking(HTMLEditor* aEditor) {
+ // Return true if the node may have elements as children, since those or their
+ // descendants may have spellcheck attributes.
+ return HasFlag(NODE_MAY_HAVE_ELEMENT_CHILDREN) ||
+ mozInlineSpellChecker::ShouldSpellCheckNode(aEditor, this);
+}
+
+//----------------------------------------------------------------------
+
+static inline JSObject* GetJSObjectChild(nsWrapperCache* aCache) {
+ return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor()
+ : nullptr;
+}
+
+static bool NeedsScriptTraverse(nsINode* aNode) {
+ return aNode->PreservingWrapper() && aNode->GetWrapperPreserveColor() &&
+ !aNode->HasKnownLiveWrapperAndDoesNotNeedTracing(aNode);
+}
+
+//----------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAttrChildContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAttrChildContentList)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsAttrChildContentList, mNode)
+
+// If the wrapper is known-live, the list can't be part of a garbage cycle.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsAttrChildContentList)
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsAttrChildContentList)
+ return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsAttrChildContentList)
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_TABLE_HEAD(nsAttrChildContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(nsAttrChildContentList, nsINodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAttrChildContentList)
+NS_INTERFACE_MAP_END
+
+JSObject* nsAttrChildContentList::WrapObject(
+ JSContext* cx, JS::Handle<JSObject*> aGivenProto) {
+ return NodeList_Binding::Wrap(cx, this, aGivenProto);
+}
+
+uint32_t nsAttrChildContentList::Length() {
+ return mNode ? mNode->GetChildCount() : 0;
+}
+
+nsIContent* nsAttrChildContentList::Item(uint32_t aIndex) {
+ if (mNode) {
+ return mNode->GetChildAt_Deprecated(aIndex);
+ }
+
+ return nullptr;
+}
+
+int32_t nsAttrChildContentList::IndexOf(nsIContent* aContent) {
+ if (mNode) {
+ return mNode->ComputeIndexOf_Deprecated(aContent);
+ }
+
+ return -1;
+}
+
+//----------------------------------------------------------------------
+uint32_t nsParentNodeChildContentList::Length() {
+ if (!mIsCacheValid && !ValidateCache()) {
+ return 0;
+ }
+
+ MOZ_ASSERT(mIsCacheValid);
+
+ return mCachedChildArray.Length();
+}
+
+nsIContent* nsParentNodeChildContentList::Item(uint32_t aIndex) {
+ if (!mIsCacheValid && !ValidateCache()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mIsCacheValid);
+
+ return mCachedChildArray.SafeElementAt(aIndex, nullptr);
+}
+
+int32_t nsParentNodeChildContentList::IndexOf(nsIContent* aContent) {
+ if (!mIsCacheValid && !ValidateCache()) {
+ return -1;
+ }
+
+ MOZ_ASSERT(mIsCacheValid);
+
+ return mCachedChildArray.IndexOf(aContent);
+}
+
+bool nsParentNodeChildContentList::ValidateCache() {
+ MOZ_ASSERT(!mIsCacheValid);
+ MOZ_ASSERT(mCachedChildArray.IsEmpty());
+
+ nsINode* parent = GetParentObject();
+ if (!parent) {
+ return false;
+ }
+
+ for (nsIContent* node = parent->GetFirstChild(); node;
+ node = node->GetNextSibling()) {
+ mCachedChildArray.AppendElement(node);
+ }
+ mIsCacheValid = true;
+
+ return true;
+}
+
+//----------------------------------------------------------------------
+
+nsIHTMLCollection* FragmentOrElement::Children() {
+ nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mChildrenList) {
+ slots->mChildrenList =
+ new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
+ nsGkAtoms::_asterisk, false);
+ }
+
+ return slots->mChildrenList;
+}
+
+//----------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff, mNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_AGGREGATED(mNode)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSupportsWeakRefTearoff)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSupportsWeakRefTearoff)
+
+NS_IMETHODIMP
+nsNodeSupportsWeakRefTearoff::GetWeakReference(
+ nsIWeakReference** aInstancePtr) {
+ nsINode::nsSlots* slots = mNode->Slots();
+ if (!slots->mWeakReference) {
+ slots->mWeakReference = new nsNodeWeakReference(mNode);
+ }
+
+ NS_ADDREF(*aInstancePtr = slots->mWeakReference);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+static const size_t MaxDOMSlotSizeAllowed =
+#ifdef HAVE_64BIT_BUILD
+ 128;
+#else
+ 64;
+#endif
+
+static_assert(sizeof(nsINode::nsSlots) <= MaxDOMSlotSizeAllowed,
+ "DOM slots cannot be grown without consideration");
+static_assert(sizeof(FragmentOrElement::nsDOMSlots) <= MaxDOMSlotSizeAllowed,
+ "DOM slots cannot be grown without consideration");
+
+void nsIContent::nsExtendedContentSlots::UnlinkExtendedSlots(nsIContent&) {
+ mContainingShadow = nullptr;
+ mAssignedSlot = nullptr;
+}
+
+void nsIContent::nsExtendedContentSlots::TraverseExtendedSlots(
+ nsCycleCollectionTraversalCallback& aCb) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mContainingShadow");
+ aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mAssignedSlot");
+ aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mAssignedSlot.get()));
+}
+
+nsIContent::nsExtendedContentSlots::nsExtendedContentSlots() = default;
+
+nsIContent::nsExtendedContentSlots::~nsExtendedContentSlots() {
+ MOZ_ASSERT(!mManualSlotAssignment);
+}
+
+size_t nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // For now, nothing to measure here. We don't actually own any of our
+ // members.
+ return 0;
+}
+
+FragmentOrElement::nsDOMSlots::nsDOMSlots()
+ : nsIContent::nsContentSlots(), mDataset(nullptr) {
+ MOZ_COUNT_CTOR(nsDOMSlots);
+}
+
+FragmentOrElement::nsDOMSlots::~nsDOMSlots() {
+ MOZ_COUNT_DTOR(nsDOMSlots);
+
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ }
+}
+
+void FragmentOrElement::nsDOMSlots::Traverse(
+ nsCycleCollectionTraversalCallback& aCb) {
+ nsIContent::nsContentSlots::Traverse(aCb);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mStyle");
+ aCb.NoteXPCOMChild(mStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mAttributeMap");
+ aCb.NoteXPCOMChild(mAttributeMap.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mChildrenList");
+ aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsINodeList*, mChildrenList));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mClassList");
+ aCb.NoteXPCOMChild(mClassList.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mPart");
+ aCb.NoteXPCOMChild(mPart.get());
+}
+
+void FragmentOrElement::nsDOMSlots::Unlink(nsINode& aNode) {
+ nsIContent::nsContentSlots::Unlink(aNode);
+ mStyle = nullptr;
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ mAttributeMap = nullptr;
+ }
+ mChildrenList = nullptr;
+ mClassList = nullptr;
+ mPart = nullptr;
+}
+
+size_t FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ nsExtendedContentSlots* extendedSlots = GetExtendedContentSlots();
+ if (extendedSlots) {
+ if (OwnsExtendedSlots()) {
+ n += aMallocSizeOf(extendedSlots);
+ }
+
+ n += extendedSlots->SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ if (mAttributeMap) {
+ n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ if (mChildrenList) {
+ n += mChildrenList->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - Superclass members (nsINode::nsSlots)
+ // - mStyle
+ // - mDataSet
+ // - mClassList
+
+ // The following member are not measured:
+ // - mControllers: because it is non-owning
+ return n;
+}
+
+FragmentOrElement::nsExtendedDOMSlots::nsExtendedDOMSlots() = default;
+
+FragmentOrElement::nsExtendedDOMSlots::~nsExtendedDOMSlots() = default;
+
+void FragmentOrElement::nsExtendedDOMSlots::UnlinkExtendedSlots(
+ nsIContent& aContent) {
+ nsIContent::nsExtendedContentSlots::UnlinkExtendedSlots(aContent);
+
+ // mShadowRoot will similarly be cleared explicitly from
+ // FragmentOrElement::Unlink.
+ mSMILOverrideStyle = nullptr;
+ mControllers = nullptr;
+ mLabelsList = nullptr;
+ mPopoverData = nullptr;
+ if (mCustomElementData) {
+ mCustomElementData->Unlink();
+ mCustomElementData = nullptr;
+ }
+ if (mAnimations) {
+ mAnimations = nullptr;
+ aContent.ClearMayHaveAnimations();
+ }
+ mExplicitlySetAttrElements.Clear();
+}
+
+void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
+ nsCycleCollectionTraversalCallback& aCb) {
+ nsIContent::nsExtendedContentSlots::TraverseExtendedSlots(aCb);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mSMILOverrideStyle");
+ aCb.NoteXPCOMChild(mSMILOverrideStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mControllers");
+ aCb.NoteXPCOMChild(mControllers);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mLabelsList");
+ aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsINodeList*, mLabelsList));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mShadowRoot");
+ aCb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot));
+
+ if (mCustomElementData) {
+ mCustomElementData->Traverse(aCb);
+ }
+ if (mAnimations) {
+ mAnimations->Traverse(aCb);
+ }
+}
+
+size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n =
+ nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(aMallocSizeOf);
+
+ // We own mSMILOverrideStyle but there seems to be no memory reporting on CSS
+ // declarations? At least report the memory the declaration takes up
+ // directly.
+ if (mSMILOverrideStyle) {
+ n += aMallocSizeOf(mSMILOverrideStyle);
+ }
+
+ // We don't really own mSMILOverrideStyleDeclaration. mSMILOverrideStyle owns
+ // it.
+
+ // We don't seem to have memory reporting for nsXULControllers. At least
+ // report the memory it's using directly.
+ if (mControllers) {
+ n += aMallocSizeOf(mControllers);
+ }
+
+ if (mLabelsList) {
+ n += mLabelsList->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // mShadowRoot should be handled during normal DOM tree memory reporting, just
+ // like kids, siblings, etc.
+
+ if (mCustomElementData) {
+ n += mCustomElementData->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+FragmentOrElement::FragmentOrElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsIContent(std::move(aNodeInfo)) {}
+
+FragmentOrElement::~FragmentOrElement() {
+ MOZ_ASSERT(!IsInUncomposedDoc(),
+ "Please remove this from the document properly");
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ }
+}
+
+static nsINode* FindChromeAccessOnlySubtreeOwner(nsINode* aNode) {
+ if (!aNode->ChromeOnlyAccess()) {
+ return aNode;
+ }
+ return const_cast<nsIContent*>(aNode->GetChromeOnlyAccessSubtreeRootParent());
+}
+
+nsINode* FindChromeAccessOnlySubtreeOwner(EventTarget* aTarget) {
+ nsINode* node = nsINode::FromEventTargetOrNull(aTarget);
+ if (!node) {
+ return nullptr;
+ }
+ return FindChromeAccessOnlySubtreeOwner(node);
+}
+
+void nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ // FIXME! Document how this event retargeting works, Bug 329124.
+ aVisitor.mCanHandle = true;
+ aVisitor.mMayHaveListenerManager = HasListenerManager();
+
+ if (IsInShadowTree()) {
+ aVisitor.mItemInShadowTree = true;
+ }
+
+ // Don't propagate mouseover and mouseout events when mouse is moving
+ // inside chrome access only content.
+ bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
+ aVisitor.mRootOfClosedTree = isAnonForEvents;
+ if ((aVisitor.mEvent->mMessage == eMouseOver ||
+ aVisitor.mEvent->mMessage == eMouseOut ||
+ aVisitor.mEvent->mMessage == ePointerOver ||
+ aVisitor.mEvent->mMessage == ePointerOut) &&
+ // Check if we should stop event propagation when event has just been
+ // dispatched or when we're about to propagate from
+ // chrome access only subtree or if we are about to propagate out of
+ // a shadow root to a shadow root host.
+ ((this == aVisitor.mEvent->mOriginalTarget && !ChromeOnlyAccess()) ||
+ isAnonForEvents)) {
+ nsCOMPtr<nsIContent> relatedTarget = nsIContent::FromEventTargetOrNull(
+ aVisitor.mEvent->AsMouseEvent()->mRelatedTarget);
+ if (relatedTarget && relatedTarget->OwnerDoc() == OwnerDoc()) {
+ // If current target is anonymous for events or we know that related
+ // target is descendant of an element which is anonymous for events,
+ // we may want to stop event propagation.
+ // If this is the original target, aVisitor.mRelatedTargetIsInAnon
+ // must be updated.
+ if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon ||
+ (aVisitor.mEvent->mOriginalTarget == this &&
+ (aVisitor.mRelatedTargetIsInAnon =
+ relatedTarget->ChromeOnlyAccess()))) {
+ nsINode* anonOwner = FindChromeAccessOnlySubtreeOwner(this);
+ if (anonOwner) {
+ nsINode* anonOwnerRelated =
+ FindChromeAccessOnlySubtreeOwner(relatedTarget);
+ if (anonOwnerRelated) {
+ // Note, anonOwnerRelated may still be inside some other
+ // native anonymous subtree. The case where anonOwner is still
+ // inside native anonymous subtree will be handled when event
+ // propagates up in the DOM tree.
+ while (anonOwner != anonOwnerRelated &&
+ anonOwnerRelated->ChromeOnlyAccess()) {
+ anonOwnerRelated =
+ FindChromeAccessOnlySubtreeOwner(anonOwnerRelated);
+ }
+ if (anonOwner == anonOwnerRelated) {
+#ifdef DEBUG_smaug
+ nsCOMPtr<nsIContent> originalTarget =
+ nsIContent::FromEventTargetOrNull(
+ aVisitor.mEvent->mOriginalTarget);
+ nsAutoString ot, ct, rt;
+ if (originalTarget) {
+ originalTarget->NodeInfo()->NameAtom()->ToString(ot);
+ }
+ NodeInfo()->NameAtom()->ToString(ct);
+ relatedTarget->NodeInfo()->NameAtom()->ToString(rt);
+ printf(
+ "Stopping %s propagation:"
+ "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s"
+ "\n\trelatedTarget=%s %s \n%s",
+ (aVisitor.mEvent->mMessage == eMouseOver) ? "mouseover"
+ : "mouseout",
+ NS_ConvertUTF16toUTF8(ot).get(),
+ NS_ConvertUTF16toUTF8(ct).get(),
+ isAnonForEvents
+ ? "(is native anonymous)"
+ : (ChromeOnlyAccess() ? "(is in native anonymous subtree)"
+ : ""),
+ NS_ConvertUTF16toUTF8(rt).get(),
+ relatedTarget->ChromeOnlyAccess()
+ ? "(is in native anonymous subtree)"
+ : "",
+ (originalTarget &&
+ relatedTarget->FindFirstNonChromeOnlyAccessContent() ==
+ originalTarget->FindFirstNonChromeOnlyAccessContent())
+ ? ""
+ : "Wrong event propagation!?!\n");
+#endif
+ aVisitor.SetParentTarget(nullptr, false);
+ // Event should not propagate to non-anon content.
+ aVisitor.mCanHandle = isAnonForEvents;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Event parent is the assigned slot, if node is assigned, or node's parent
+ // otherwise.
+ HTMLSlotElement* slot = GetAssignedSlot();
+ nsIContent* parent = slot ? slot : GetParent();
+
+ // Event may need to be retargeted if this is the root of a native
+ // anonymous content subtree or event is dispatched somewhere inside XBL.
+ if (isAnonForEvents) {
+#ifdef DEBUG
+ // If a DOM event is explicitly dispatched using node.dispatchEvent(), then
+ // all the events are allowed even in the native anonymous content..
+ nsCOMPtr<nsIContent> t =
+ nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
+ NS_ASSERTION(!t || !t->ChromeOnlyAccess() ||
+ aVisitor.mEvent->mClass != eMutationEventClass ||
+ aVisitor.mDOMEvent,
+ "Mutation event dispatched in native anonymous content!?!");
+#endif
+ aVisitor.mEventTargetAtParent = parent;
+ } else if (parent && aVisitor.mOriginalTargetIsInAnon) {
+ nsCOMPtr<nsIContent> content(
+ nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget));
+ if (content &&
+ content->GetClosestNativeAnonymousSubtreeRootParentOrHost() == parent) {
+ aVisitor.mEventTargetAtParent = parent;
+ }
+ }
+
+ if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
+ IsRootOfNativeAnonymousSubtree() && OwnerDoc()->GetWindow()) {
+ aVisitor.SetParentTarget(OwnerDoc()->GetWindow()->GetParentTarget(), true);
+ } else if (parent) {
+ aVisitor.SetParentTarget(parent, false);
+ if (slot) {
+ ShadowRoot* root = slot->GetContainingShadow();
+ if (root && root->IsClosed()) {
+ aVisitor.mParentIsSlotInClosedTree = true;
+ }
+ }
+ } else {
+ aVisitor.SetParentTarget(GetComposedDoc(), false);
+ }
+
+ if (!ChromeOnlyAccess() && !aVisitor.mRelatedTargetRetargetedInCurrentScope) {
+ // We don't support Shadow DOM in native anonymous content yet.
+ aVisitor.mRelatedTargetRetargetedInCurrentScope = true;
+ if (aVisitor.mEvent->mOriginalRelatedTarget) {
+ // https://dom.spec.whatwg.org/#concept-event-dispatch
+ // Step 3.
+ // "Let relatedTarget be the result of retargeting event's relatedTarget
+ // against target if event's relatedTarget is non-null, and null
+ // otherwise."
+ //
+ // This is a bit complicated because the event might be from native
+ // anonymous content, but we need to deal with non-native anonymous
+ // content there.
+ bool initialTarget = this == aVisitor.mEvent->mOriginalTarget;
+ nsCOMPtr<nsINode> originalTargetAsNode;
+ // Use of mOriginalTargetIsInAnon is an optimization here.
+ if (!initialTarget && aVisitor.mOriginalTargetIsInAnon) {
+ originalTargetAsNode =
+ FindChromeAccessOnlySubtreeOwner(aVisitor.mEvent->mOriginalTarget);
+ initialTarget = originalTargetAsNode == this;
+ }
+ if (initialTarget) {
+ nsCOMPtr<nsINode> relatedTargetAsNode =
+ FindChromeAccessOnlySubtreeOwner(
+ aVisitor.mEvent->mOriginalRelatedTarget);
+ if (!originalTargetAsNode) {
+ originalTargetAsNode =
+ nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
+ }
+
+ if (relatedTargetAsNode && originalTargetAsNode) {
+ nsINode* retargetedRelatedTarget = nsContentUtils::Retarget(
+ relatedTargetAsNode, originalTargetAsNode);
+ if (originalTargetAsNode == retargetedRelatedTarget &&
+ retargetedRelatedTarget != relatedTargetAsNode) {
+ // Step 4.
+ // "If target is relatedTarget and target is not event's
+ // relatedTarget, then return true."
+ aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
+ // Old code relies on mTarget to point to the first element which
+ // was not added to the event target chain because of mCanHandle
+ // being false, but in Shadow DOM case mTarget really should
+ // point to a node in Shadow DOM.
+ aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
+ return;
+ }
+
+ // Part of step 5. Retargeting target has happened already higher
+ // up in this method.
+ // "Append to an event path with event, target, targetOverride,
+ // relatedTarget, and false."
+ aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
+ }
+ } else {
+ nsCOMPtr<nsINode> relatedTargetAsNode =
+ FindChromeAccessOnlySubtreeOwner(
+ aVisitor.mEvent->mOriginalRelatedTarget);
+ if (relatedTargetAsNode) {
+ // Step 11.3.
+ // "Let relatedTarget be the result of retargeting event's
+ // relatedTarget against parent if event's relatedTarget is non-null,
+ // and null otherwise.".
+ nsINode* retargetedRelatedTarget =
+ nsContentUtils::Retarget(relatedTargetAsNode, this);
+ nsCOMPtr<nsINode> targetInKnownToBeHandledScope =
+ FindChromeAccessOnlySubtreeOwner(
+ aVisitor.mTargetInKnownToBeHandledScope);
+ // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode,
+ // targetInKnownToBeHandledScope will be null. This may happen when
+ // dispatching event to Window object in a content page and
+ // propagating the event to a chrome Element.
+ if (targetInKnownToBeHandledScope &&
+ IsShadowIncludingInclusiveDescendantOf(
+ targetInKnownToBeHandledScope->SubtreeRoot())) {
+ // Part of step 11.4.
+ // "If target's root is a shadow-including inclusive ancestor of
+ // parent, then"
+ // "...Append to an event path with event, parent, null,
+ // relatedTarget, " and slot-in-closed-tree."
+ aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
+ } else if (this == retargetedRelatedTarget) {
+ // Step 11.5
+ // "Otherwise, if parent and relatedTarget are identical, then set
+ // parent to null."
+ aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
+ // Old code relies on mTarget to point to the first element which
+ // was not added to the event target chain because of mCanHandle
+ // being false, but in Shadow DOM case mTarget really should
+ // point to a node in Shadow DOM.
+ aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
+ return;
+ } else if (targetInKnownToBeHandledScope) {
+ // Note, if targetInKnownToBeHandledScope is null,
+ // mTargetInKnownToBeHandledScope could be Window object in content
+ // page and we're in chrome document in the same process.
+
+ // Step 11.6
+ aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
+ }
+ }
+ }
+ }
+
+ if (aVisitor.mEvent->mClass == eTouchEventClass) {
+ // Retarget touch objects.
+ MOZ_ASSERT(!aVisitor.mRetargetedTouchTargets.isSome());
+ aVisitor.mRetargetedTouchTargets.emplace();
+ WidgetTouchEvent* touchEvent = aVisitor.mEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ Touch* touch = touches[i];
+ EventTarget* originalTarget = touch->mOriginalTarget;
+ EventTarget* touchTarget = originalTarget;
+ nsCOMPtr<nsINode> targetAsNode =
+ nsINode::FromEventTargetOrNull(originalTarget);
+ if (targetAsNode) {
+ EventTarget* retargeted =
+ nsContentUtils::Retarget(targetAsNode, this);
+ if (retargeted) {
+ touchTarget = retargeted;
+ }
+ }
+ aVisitor.mRetargetedTouchTargets->AppendElement(touchTarget);
+ touch->mTarget = touchTarget;
+ }
+ MOZ_ASSERT(aVisitor.mRetargetedTouchTargets->Length() ==
+ touches.Length());
+ }
+ }
+
+ if (slot) {
+ // Inform that we're about to exit the current scope.
+ aVisitor.mRelatedTargetRetargetedInCurrentScope = false;
+ }
+}
+
+bool nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) {
+ bool focusable = IsFocusableInternal(aTabIndex, aWithMouse);
+ // Ensure that the return value and aTabIndex are consistent in the case
+ // we're in userfocusignored context.
+ if (focusable || (aTabIndex && *aTabIndex != -1)) {
+ return focusable;
+ }
+ return false;
+}
+
+Element* nsIContent::GetFocusDelegate(bool aWithMouse,
+ bool aAutofocusOnly) const {
+ const nsIContent* whereToLook = this;
+ if (ShadowRoot* root = GetShadowRoot()) {
+ if (!root->DelegatesFocus()) {
+ // 1. If focusTarget is a shadow host and its shadow root 's delegates
+ // focus is false, then return null.
+ return nullptr;
+ }
+ whereToLook = root;
+ }
+
+ auto IsFocusable = [&](Element* aElement) {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ return frame && frame->IsFocusable(aWithMouse);
+ };
+
+ Element* potentialFocus = nullptr;
+ for (nsINode* node = whereToLook->GetFirstChild(); node;
+ node = node->GetNextNode(whereToLook)) {
+ auto* el = Element::FromNode(*node);
+ if (!el) {
+ continue;
+ }
+
+ const bool autofocus = el->GetBoolAttr(nsGkAtoms::autofocus);
+
+ if (aAutofocusOnly && !autofocus) {
+ continue;
+ }
+ if (autofocus) {
+ if (IsFocusable(el)) {
+ // Found an autofocus candidate.
+ return el;
+ }
+ } else if (!potentialFocus && IsFocusable(el)) {
+ // This element could be the one if we can't find an
+ // autofocus candidate which has the precedence.
+ potentialFocus = el;
+ }
+
+ if (!autofocus && potentialFocus) {
+ // Nothing else to do, we are not looking for more focusable elements
+ // here.
+ continue;
+ }
+
+ if (auto* shadow = el->GetShadowRoot()) {
+ if (shadow->DelegatesFocus()) {
+ if (Element* delegatedFocus = shadow->GetFocusDelegate(aWithMouse)) {
+ if (autofocus) {
+ // This element has autofocus and we found an focus delegates
+ // in its descendants, so use the focus delegates
+ return delegatedFocus;
+ }
+ if (!potentialFocus) {
+ potentialFocus = delegatedFocus;
+ }
+ }
+ }
+ }
+ }
+
+ return potentialFocus;
+}
+
+bool nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
+ if (aTabIndex) {
+ *aTabIndex = -1; // Default, not tabbable
+ }
+ return false;
+}
+
+void nsIContent::SetAssignedSlot(HTMLSlotElement* aSlot) {
+ MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots());
+ ExtendedContentSlots()->mAssignedSlot = aSlot;
+}
+
+static Maybe<uint32_t> DoComputeFlatTreeIndexOf(FlattenedChildIterator& aIter,
+ const nsINode* aPossibleChild) {
+ if (aPossibleChild->GetFlattenedTreeParentNode() != aIter.Parent()) {
+ return Nothing();
+ }
+
+ uint32_t index = 0u;
+ for (nsIContent* child = aIter.GetNextChild(); child;
+ child = aIter.GetNextChild()) {
+ if (child == aPossibleChild) {
+ return Some(index);
+ }
+
+ ++index;
+ }
+
+ return Nothing();
+}
+
+Maybe<uint32_t> nsIContent::ComputeFlatTreeIndexOf(
+ const nsINode* aPossibleChild) const {
+ if (!aPossibleChild) {
+ return Nothing();
+ }
+
+ FlattenedChildIterator iter(this);
+ if (!iter.ShadowDOMInvolved()) {
+ auto index = ComputeIndexOf(aPossibleChild);
+ MOZ_ASSERT(DoComputeFlatTreeIndexOf(iter, aPossibleChild) == index);
+ return index;
+ }
+
+ return DoComputeFlatTreeIndexOf(iter, aPossibleChild);
+}
+
+#ifdef MOZ_DOM_LIST
+void nsIContent::Dump() { List(); }
+#endif
+
+void FragmentOrElement::GetTextContentInternal(nsAString& aTextContent,
+ OOMReporter& aError) {
+ if (!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
+ aError.ReportOOM();
+ }
+}
+
+void FragmentOrElement::SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aError) {
+ aError = nsContentUtils::SetNodeTextContent(this, aTextContent, false);
+}
+
+void FragmentOrElement::DestroyContent() {
+ // Drop any servo data. We do this before the RemovedFromDocument call below
+ // so that it doesn't need to try to keep the style state sane when shuffling
+ // around the flattened tree.
+ //
+ // TODO(emilio): I suspect this can be asserted against instead, with a bit of
+ // effort to avoid calling Document::Destroy with a shell...
+ if (IsElement()) {
+ AsElement()->ClearServoData();
+ }
+
+#ifdef DEBUG
+ uint32_t oldChildCount = GetChildCount();
+#endif
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->DestroyContent();
+ MOZ_ASSERT(child->GetParent() == this,
+ "Mutating the tree during XBL destructors is evil");
+ }
+
+ MOZ_ASSERT(oldChildCount == GetChildCount(),
+ "Mutating the tree during XBL destructors is evil");
+
+ if (ShadowRoot* shadowRoot = GetShadowRoot()) {
+ shadowRoot->DestroyContent();
+ }
+}
+
+void FragmentOrElement::SaveSubtreeState() {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->SaveSubtreeState();
+ }
+
+ // FIXME(bug 1469277): Pretty sure this wants to dig into shadow trees as
+ // well.
+}
+
+//----------------------------------------------------------------------
+
+// Generic DOMNode implementations
+
+void FragmentOrElement::FireNodeInserted(
+ Document* aDoc, nsINode* aParent,
+ const nsTArray<nsCOMPtr<nsIContent>>& aNodes) {
+ for (const nsCOMPtr<nsIContent>& childContent : aNodes) {
+ if (nsContentUtils::HasMutationListeners(
+ childContent, NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeInserted);
+ mutation.mRelatedNode = aParent;
+
+ mozAutoSubtreeModified subtree(aDoc, aParent);
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*childContent, mutation);
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+// nsISupports implementation
+
+#define SUBTREE_UNBINDINGS_PER_RUNNABLE 500
+
+class ContentUnbinder : public Runnable {
+ public:
+ ContentUnbinder() : Runnable("ContentUnbinder") { mLast = this; }
+
+ ~ContentUnbinder() { Run(); }
+
+ void UnbindSubtree(nsIContent* aNode) {
+ if (aNode->NodeType() != nsINode::ELEMENT_NODE &&
+ aNode->NodeType() != nsINode::DOCUMENT_FRAGMENT_NODE) {
+ return;
+ }
+ FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
+ if (container->HasChildren()) {
+ // Invalidate cached array of child nodes
+ container->InvalidateChildNodes();
+
+ while (container->HasChildren()) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call DisconnectChild()
+ // before calling UnbindFromTree, since this last can notify various
+ // observers and they should really see consistent
+ // tree state.
+ // If this code changes, change the corresponding code in
+ // FragmentOrElement's and Document's unlink impls.
+ nsCOMPtr<nsIContent> child = container->GetLastChild();
+ container->DisconnectChild(child);
+ UnbindSubtree(child);
+ child->UnbindFromTree();
+ }
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ nsAutoScriptBlocker scriptBlocker;
+ uint32_t len = mSubtreeRoots.Length();
+ if (len) {
+ for (uint32_t i = 0; i < len; ++i) {
+ UnbindSubtree(mSubtreeRoots[i]);
+ }
+ mSubtreeRoots.Clear();
+ }
+ nsCycleCollector_dispatchDeferredDeletion();
+ if (this == sContentUnbinder) {
+ sContentUnbinder = nullptr;
+ if (mNext) {
+ RefPtr<ContentUnbinder> next;
+ next.swap(mNext);
+ sContentUnbinder = next;
+ next->mLast = mLast;
+ mLast = nullptr;
+ NS_DispatchToCurrentThreadQueue(next.forget(),
+ EventQueuePriority::Idle);
+ }
+ }
+ return NS_OK;
+ }
+
+ static void UnbindAll() {
+ RefPtr<ContentUnbinder> ub = sContentUnbinder;
+ sContentUnbinder = nullptr;
+ while (ub) {
+ ub->Run();
+ ub = ub->mNext;
+ }
+ }
+
+ static void Append(nsIContent* aSubtreeRoot) {
+ if (!sContentUnbinder) {
+ sContentUnbinder = new ContentUnbinder();
+ nsCOMPtr<nsIRunnable> e = sContentUnbinder;
+ NS_DispatchToCurrentThreadQueue(e.forget(), EventQueuePriority::Idle);
+ }
+
+ if (sContentUnbinder->mLast->mSubtreeRoots.Length() >=
+ SUBTREE_UNBINDINGS_PER_RUNNABLE) {
+ sContentUnbinder->mLast->mNext = new ContentUnbinder();
+ sContentUnbinder->mLast = sContentUnbinder->mLast->mNext;
+ }
+ sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot);
+ }
+
+ private:
+ AutoTArray<nsCOMPtr<nsIContent>, SUBTREE_UNBINDINGS_PER_RUNNABLE>
+ mSubtreeRoots;
+ RefPtr<ContentUnbinder> mNext;
+ ContentUnbinder* mLast;
+ static ContentUnbinder* sContentUnbinder;
+};
+
+ContentUnbinder* ContentUnbinder::sContentUnbinder = nullptr;
+
+void FragmentOrElement::ClearContentUnbinder() { ContentUnbinder::UnbindAll(); }
+
+// Note, _INHERITED macro isn't used here since nsINode implementations are
+// rather special.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FragmentOrElement)
+
+// We purposefully don't UNLINK_BEGIN_INHERITED here.
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
+ nsIContent::Unlink(tmp);
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsElement()) {
+ Element* elem = tmp->AsElement();
+ elem->UnlinkIntersectionObservers();
+ }
+
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsStaticAtom* const* props =
+ Element::HTMLSVGPropertiesToTraverseAndUnlink();
+ for (uint32_t i = 0; props[i]; ++i) {
+ tmp->RemoveProperty(props[i]);
+ }
+ }
+ }
+
+ // Unlink child content (and unbind our subtree).
+ if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
+ // Don't allow script to run while we're unbinding everything.
+ nsAutoScriptBlocker scriptBlocker;
+ while (tmp->HasChildren()) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it.
+ // If this code changes, change the corresponding code in Document's
+ // unlink impl and ContentUnbinder::UnbindSubtree.
+ nsCOMPtr<nsIContent> child = tmp->GetLastChild();
+ tmp->DisconnectChild(child);
+ child->UnbindFromTree();
+ }
+ } else if (!tmp->GetParent() && tmp->HasChildren()) {
+ ContentUnbinder::Append(tmp);
+ } /* else {
+ The subtree root will end up to a ContentUnbinder, and that will
+ unbind the child nodes.
+ } */
+
+ if (ShadowRoot* shadowRoot = tmp->GetShadowRoot()) {
+ shadowRoot->Unbind();
+ tmp->ExtendedDOMSlots()->mShadowRoot = nullptr;
+ }
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+void FragmentOrElement::MarkNodeChildren(nsINode* aNode) {
+ JSObject* o = GetJSObjectChild(aNode);
+ if (o) {
+ JS::ExposeObjectToActiveJS(o);
+ }
+
+ EventListenerManager* elm = aNode->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+}
+
+nsINode* FindOptimizableSubtreeRoot(nsINode* aNode) {
+ nsINode* p;
+ while ((p = aNode->GetParentNode())) {
+ if (aNode->UnoptimizableCCNode()) {
+ return nullptr;
+ }
+ aNode = p;
+ }
+
+ if (aNode->UnoptimizableCCNode()) {
+ return nullptr;
+ }
+ return aNode;
+}
+
+StaticAutoPtr<nsTHashSet<nsINode*>> gCCBlackMarkedNodes;
+
+static void ClearBlackMarkedNodes() {
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ for (nsINode* n : *gCCBlackMarkedNodes) {
+ n->SetCCMarkedRoot(false);
+ n->SetInCCBlackTree(false);
+ }
+ gCCBlackMarkedNodes = nullptr;
+}
+
+// static
+void FragmentOrElement::RemoveBlackMarkedNode(nsINode* aNode) {
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ gCCBlackMarkedNodes->Remove(aNode);
+}
+
+static bool IsCertainlyAliveNode(nsINode* aNode, Document* aDoc) {
+ MOZ_ASSERT(aNode->GetComposedDoc() == aDoc);
+
+ // Marked to be in-CC-generation or if the document is an svg image that's
+ // being kept alive by the image cache. (Note that an svg image's internal
+ // SVG document will receive an OnPageHide() call when it gets purged from
+ // the image cache; hence, we use IsVisible() as a hint that the document is
+ // actively being kept alive by the cache.)
+ return nsCCUncollectableMarker::InGeneration(aDoc->GetMarkedCCGeneration()) ||
+ (nsCCUncollectableMarker::sGeneration && aDoc->IsBeingUsedAsImage() &&
+ aDoc->IsVisible());
+}
+
+// static
+bool FragmentOrElement::CanSkipInCC(nsINode* aNode) {
+ // Don't try to optimize anything during shutdown.
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+
+ Document* currentDoc = aNode->GetComposedDoc();
+ if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc)) {
+ return !NeedsScriptTraverse(aNode);
+ }
+
+ // Bail out early if aNode is somewhere in anonymous content,
+ // or otherwise unusual.
+ if (aNode->UnoptimizableCCNode()) {
+ return false;
+ }
+
+ nsINode* root = currentDoc ? static_cast<nsINode*>(currentDoc)
+ : FindOptimizableSubtreeRoot(aNode);
+ if (!root) {
+ return false;
+ }
+
+ // Subtree has been traversed already.
+ if (root->CCMarkedRoot()) {
+ return root->InCCBlackTree() && !NeedsScriptTraverse(aNode);
+ }
+
+ if (!gCCBlackMarkedNodes) {
+ gCCBlackMarkedNodes = new nsTHashSet<nsINode*>(1020);
+ }
+
+ // nodesToUnpurple contains nodes which will be removed
+ // from the purple buffer if the DOM tree is known-live.
+ AutoTArray<nsIContent*, 1020> nodesToUnpurple;
+ // grayNodes need script traverse, so they aren't removed from
+ // the purple buffer, but are marked to be in known-live subtree so that
+ // traverse is faster.
+ AutoTArray<nsINode*, 1020> grayNodes;
+
+ bool foundLiveWrapper = root->HasKnownLiveWrapper();
+ if (root != currentDoc) {
+ currentDoc = nullptr;
+ if (NeedsScriptTraverse(root)) {
+ grayNodes.AppendElement(root);
+ } else if (static_cast<nsIContent*>(root)->IsPurple()) {
+ nodesToUnpurple.AppendElement(static_cast<nsIContent*>(root));
+ }
+ }
+
+ // Traverse the subtree and check if we could know without CC
+ // that it is known-live.
+ // Note, this traverse is non-virtual and inline, so it should be a lot faster
+ // than CC's generic traverse.
+ for (nsIContent* node = root->GetFirstChild(); node;
+ node = node->GetNextNode(root)) {
+ foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper();
+ if (foundLiveWrapper && currentDoc) {
+ // If we can mark the whole document known-live, no need to optimize
+ // so much, since when the next purple node in the document will be
+ // handled, it is fast to check that currentDoc is in CCGeneration.
+ break;
+ }
+ if (NeedsScriptTraverse(node)) {
+ // Gray nodes need real CC traverse.
+ grayNodes.AppendElement(node);
+ } else if (node->IsPurple()) {
+ nodesToUnpurple.AppendElement(node);
+ }
+ }
+
+ root->SetCCMarkedRoot(true);
+ root->SetInCCBlackTree(foundLiveWrapper);
+ gCCBlackMarkedNodes->Insert(root);
+
+ if (!foundLiveWrapper) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is known-live,
+ // we can mark the document to be in CCGeneration.
+ currentDoc->MarkUncollectableForCCGeneration(
+ nsCCUncollectableMarker::sGeneration);
+ } else {
+ for (uint32_t i = 0; i < grayNodes.Length(); ++i) {
+ nsINode* node = grayNodes[i];
+ node->SetInCCBlackTree(true);
+ gCCBlackMarkedNodes->Insert(node);
+ }
+ }
+
+ // Subtree is known-live, we can remove non-gray purple nodes from
+ // purple buffer.
+ for (uint32_t i = 0; i < nodesToUnpurple.Length(); ++i) {
+ nsIContent* purple = nodesToUnpurple[i];
+ // Can't remove currently handled purple node.
+ if (purple != aNode) {
+ purple->RemovePurple();
+ }
+ }
+ return !NeedsScriptTraverse(aNode);
+}
+
+AutoTArray<nsINode*, 1020>* gPurpleRoots = nullptr;
+AutoTArray<nsIContent*, 1020>* gNodesToUnbind = nullptr;
+
+void ClearCycleCollectorCleanupData() {
+ if (gPurpleRoots) {
+ uint32_t len = gPurpleRoots->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsINode* n = gPurpleRoots->ElementAt(i);
+ n->SetIsPurpleRoot(false);
+ }
+ delete gPurpleRoots;
+ gPurpleRoots = nullptr;
+ }
+ if (gNodesToUnbind) {
+ uint32_t len = gNodesToUnbind->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsIContent* c = gNodesToUnbind->ElementAt(i);
+ c->SetIsPurpleRoot(false);
+ ContentUnbinder::Append(c);
+ }
+ delete gNodesToUnbind;
+ gNodesToUnbind = nullptr;
+ }
+}
+
+static bool ShouldClearPurple(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ if (aContent->IsPurple()) {
+ return true;
+ }
+
+ JSObject* o = GetJSObjectChild(aContent);
+ if (o && JS::ObjectIsMarkedGray(o)) {
+ return true;
+ }
+
+ if (aContent->HasListenerManager()) {
+ return true;
+ }
+
+ return aContent->HasProperties();
+}
+
+// If aNode is not optimizable, but is an element
+// with a frame in a document which has currently active presshell,
+// we can act as if it was optimizable. When the primary frame dies, aNode
+// will end up to the purple buffer because of the refcount change.
+bool NodeHasActiveFrame(Document* aCurrentDoc, nsINode* aNode) {
+ return aCurrentDoc->GetPresShell() && aNode->IsElement() &&
+ aNode->AsElement()->GetPrimaryFrame();
+}
+
+// CanSkip checks if aNode is known-live, and if it is, returns true. If aNode
+// is in a known-live DOM tree, CanSkip may also remove other objects from
+// purple buffer and unmark event listeners and user data. If the root of the
+// DOM tree is a document, less optimizations are done since checking the
+// liveness of the current document is usually fast and we don't want slow down
+// such common cases.
+bool FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed) {
+ // Don't try to optimize anything during shutdown.
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+
+ bool unoptimizable = aNode->UnoptimizableCCNode();
+ Document* currentDoc = aNode->GetComposedDoc();
+ if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc) &&
+ (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode))) {
+ MarkNodeChildren(aNode);
+ return true;
+ }
+
+ if (unoptimizable) {
+ return false;
+ }
+
+ nsINode* root = currentDoc ? static_cast<nsINode*>(currentDoc)
+ : FindOptimizableSubtreeRoot(aNode);
+ if (!root) {
+ return false;
+ }
+
+ // Subtree has been traversed already, and aNode has
+ // been handled in a way that doesn't require revisiting it.
+ if (root->IsPurpleRoot()) {
+ return false;
+ }
+
+ // nodesToClear contains nodes which are either purple or
+ // gray.
+ AutoTArray<nsIContent*, 1020> nodesToClear;
+
+ bool foundLiveWrapper = root->HasKnownLiveWrapper();
+ bool domOnlyCycle = false;
+ if (root != currentDoc) {
+ currentDoc = nullptr;
+ if (!foundLiveWrapper) {
+ domOnlyCycle = static_cast<nsIContent*>(root)->OwnedOnlyByTheDOMTree();
+ }
+ if (ShouldClearPurple(static_cast<nsIContent*>(root))) {
+ nodesToClear.AppendElement(static_cast<nsIContent*>(root));
+ }
+ }
+
+ // Traverse the subtree and check if we could know without CC
+ // that it is known-live.
+ // Note, this traverse is non-virtual and inline, so it should be a lot faster
+ // than CC's generic traverse.
+ for (nsIContent* node = root->GetFirstChild(); node;
+ node = node->GetNextNode(root)) {
+ foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper();
+ if (foundLiveWrapper) {
+ domOnlyCycle = false;
+ if (currentDoc) {
+ // If we can mark the whole document live, no need to optimize
+ // so much, since when the next purple node in the document will be
+ // handled, it is fast to check that the currentDoc is in CCGeneration.
+ break;
+ }
+ // No need to put stuff to the nodesToClear array, if we can clear it
+ // already here.
+ if (node->IsPurple() && (node != aNode || aRemovingAllowed)) {
+ node->RemovePurple();
+ }
+ MarkNodeChildren(node);
+ } else {
+ domOnlyCycle = domOnlyCycle && node->OwnedOnlyByTheDOMTree();
+ if (ShouldClearPurple(node)) {
+ // Collect interesting nodes which we can clear if we find that
+ // they are kept alive in a known-live tree or are in a DOM-only cycle.
+ nodesToClear.AppendElement(node);
+ }
+ }
+ }
+
+ if (!currentDoc || !foundLiveWrapper) {
+ root->SetIsPurpleRoot(true);
+ if (domOnlyCycle) {
+ if (!gNodesToUnbind) {
+ gNodesToUnbind = new AutoTArray<nsIContent*, 1020>();
+ }
+ gNodesToUnbind->AppendElement(static_cast<nsIContent*>(root));
+ for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
+ nsIContent* n = nodesToClear[i];
+ if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
+ n->RemovePurple();
+ }
+ }
+ return true;
+ } else {
+ if (!gPurpleRoots) {
+ gPurpleRoots = new AutoTArray<nsINode*, 1020>();
+ }
+ gPurpleRoots->AppendElement(root);
+ }
+ }
+
+ if (!foundLiveWrapper) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is known-live,
+ // we can mark the document to be in CCGeneration.
+ currentDoc->MarkUncollectableForCCGeneration(
+ nsCCUncollectableMarker::sGeneration);
+ MarkNodeChildren(currentDoc);
+ }
+
+ // Subtree is known-live, so we can remove purple nodes from
+ // purple buffer and mark stuff that to be certainly alive.
+ for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
+ nsIContent* n = nodesToClear[i];
+ MarkNodeChildren(n);
+ // Can't remove currently handled purple node,
+ // unless aRemovingAllowed is true.
+ if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
+ n->RemovePurple();
+ }
+ }
+ return true;
+}
+
+bool FragmentOrElement::CanSkipThis(nsINode* aNode) {
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+ if (aNode->HasKnownLiveWrapper()) {
+ return true;
+ }
+ Document* c = aNode->GetComposedDoc();
+ return ((c && IsCertainlyAliveNode(aNode, c)) || aNode->InCCBlackTree()) &&
+ !NeedsScriptTraverse(aNode);
+}
+
+void FragmentOrElement::InitCCCallbacks() {
+ nsCycleCollector_setForgetSkippableCallback(ClearCycleCollectorCleanupData);
+ nsCycleCollector_setBeforeUnlinkCallback(ClearBlackMarkedNodes);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkip(tmp, aRemovingAllowed);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits
+// we should traverse should be added here or in nsINode::Traverse.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ uint32_t nsid = tmp->GetNameSpaceID();
+ nsAtomCString localName(tmp->NodeInfo()->NameAtom());
+ nsAutoCString uri;
+ if (tmp->OwnerDoc()->GetDocumentURI()) {
+ uri = tmp->OwnerDoc()->GetDocumentURI()->GetSpecOrDefault();
+ }
+
+ nsAutoString id;
+ nsAtom* idAtom = tmp->GetID();
+ if (idAtom) {
+ id.AppendLiteral(" id='");
+ id.Append(nsDependentAtomString(idAtom));
+ id.Append('\'');
+ }
+
+ nsAutoString classes;
+ const nsAttrValue* classAttrValue =
+ tmp->IsElement() ? tmp->AsElement()->GetClasses() : nullptr;
+ if (classAttrValue) {
+ classes.AppendLiteral(" class='");
+ nsAutoString classString;
+ classAttrValue->ToString(classString);
+ classString.ReplaceChar(char16_t('\n'), char16_t(' '));
+ classes.Append(classString);
+ classes.Append('\'');
+ }
+
+ nsAutoCString orphan;
+ if (!tmp->IsInComposedDoc()) {
+ orphan.AppendLiteral(" (orphan)");
+ }
+
+ const char* nsuri = nsNameSpaceManager::GetNameSpaceDisplayName(nsid);
+ SprintfLiteral(name, "FragmentOrElement %s %s%s%s%s %s", nsuri,
+ localName.get(), NS_ConvertUTF16toUTF8(id).get(),
+ NS_ConvertUTF16toUTF8(classes).get(), orphan.get(),
+ uri.get());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get())
+ }
+
+ if (!nsIContent::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsElement()) {
+ Element* elem = tmp->AsElement();
+ IntersectionObserverList* observers =
+ static_cast<IntersectionObserverList*>(
+ elem->GetProperty(nsGkAtoms::intersectionobserverlist));
+ if (observers) {
+ for (DOMIntersectionObserver* observer : observers->Keys()) {
+ cb.NoteXPCOMChild(observer);
+ }
+ }
+ }
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsStaticAtom* const* props =
+ Element::HTMLSVGPropertiesToTraverseAndUnlink();
+ for (uint32_t i = 0; props[i]; ++i) {
+ nsISupports* property =
+ static_cast<nsISupports*>(tmp->GetProperty(props[i]));
+ cb.NoteXPCOMChild(property);
+ }
+ }
+ }
+ if (tmp->IsElement()) {
+ Element* element = tmp->AsElement();
+ // Traverse attribute names.
+ uint32_t i;
+ uint32_t attrs = element->GetAttrCount();
+ for (i = 0; i < attrs; i++) {
+ const nsAttrName* name = element->GetUnsafeAttrNameAt(i);
+ if (!name->IsAtom()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAttrs[i]->NodeInfo()");
+ cb.NoteNativeChild(name->NodeInfo(),
+ NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
+ }
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN(FragmentOrElement)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(FragmentOrElement)
+NS_INTERFACE_MAP_END_INHERITING(nsIContent)
+
+//----------------------------------------------------------------------
+
+const nsTextFragment* FragmentOrElement::GetText() { return nullptr; }
+
+uint32_t FragmentOrElement::TextLength() const {
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this returning 0
+ MOZ_ASSERT_UNREACHABLE("called FragmentOrElement::TextLength");
+
+ return 0;
+}
+
+bool FragmentOrElement::TextIsOnlyWhitespace() { return false; }
+
+bool FragmentOrElement::ThreadSafeTextIsOnlyWhitespace() const { return false; }
+
+static inline bool IsVoidTag(nsAtom* aTag) {
+ static const nsAtom* voidElements[] = {
+ nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
+ nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
+ nsGkAtoms::embed, nsGkAtoms::frame, nsGkAtoms::hr,
+ nsGkAtoms::img, nsGkAtoms::input, nsGkAtoms::keygen,
+ nsGkAtoms::link, nsGkAtoms::meta, nsGkAtoms::param,
+ nsGkAtoms::source, nsGkAtoms::track, nsGkAtoms::wbr};
+
+ static mozilla::BitBloomFilter<12, nsAtom> sFilter;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
+ sFilter.add(voidElements[i]);
+ }
+ }
+
+ if (sFilter.mightContain(aTag)) {
+ for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
+ if (aTag == voidElements[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* static */
+bool FragmentOrElement::IsHTMLVoid(nsAtom* aLocalName) {
+ return aLocalName && IsVoidTag(aLocalName);
+}
+
+void FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup) {
+ aMarkup.Truncate();
+
+ Document* doc = OwnerDoc();
+ if (IsInHTMLDocument()) {
+ nsContentUtils::SerializeNodeToMarkup(this, !aIncludeSelf, aMarkup);
+ return;
+ }
+
+ nsAutoString contentType;
+ doc->GetContentType(contentType);
+ bool tryToCacheEncoder = !aIncludeSelf;
+
+ nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
+ if (!docEncoder) {
+ docEncoder = do_createDocumentEncoder(
+ PromiseFlatCString(NS_ConvertUTF16toUTF8(contentType)).get());
+ }
+ if (!docEncoder) {
+ // This could be some type for which we create a synthetic document. Try
+ // again as XML
+ contentType.AssignLiteral("application/xml");
+ docEncoder = do_createDocumentEncoder("application/xml");
+ // Don't try to cache the encoder since it would point to a different
+ // contentType once it has been reinitialized.
+ tryToCacheEncoder = false;
+ }
+
+ NS_ENSURE_TRUE_VOID(docEncoder);
+
+ uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities |
+ // Output DOM-standard newlines
+ nsIDocumentEncoder::OutputLFLineBreak |
+ // Don't do linebreaking that's not present in
+ // the source
+ nsIDocumentEncoder::OutputRaw |
+ // Only check for mozdirty when necessary (bug 599983)
+ nsIDocumentEncoder::OutputIgnoreMozDirty;
+
+ if (IsEditable()) {
+ nsCOMPtr<Element> elem = do_QueryInterface(this);
+ TextEditor* textEditor = elem ? elem->GetTextEditorInternal() : nullptr;
+ if (textEditor && textEditor->OutputsMozDirty()) {
+ flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
+ }
+ }
+
+ DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (aIncludeSelf) {
+ docEncoder->SetNode(this);
+ } else {
+ docEncoder->SetContainerNode(this);
+ }
+ rv = docEncoder->EncodeToString(aMarkup);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (tryToCacheEncoder) {
+ doc->SetCachedEncoder(docEncoder.forget());
+ }
+}
+
+static bool ContainsMarkup(const nsAString& aStr) {
+ // Note: we can't use FindCharInSet because null is one of the characters we
+ // want to search for.
+ const char16_t* start = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+
+ while (start != end) {
+ char16_t c = *start;
+ if (c == char16_t('<') || c == char16_t('&') || c == char16_t('\r') ||
+ c == char16_t('\0')) {
+ return true;
+ }
+ ++start;
+ }
+
+ return false;
+}
+
+void FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML,
+ ErrorResult& aError) {
+ // Keep "this" alive should be guaranteed by the caller, and also the content
+ // of a template element (if this is one) should never been released by from
+ // this during this call. Therefore, using raw pointer here is safe.
+ FragmentOrElement* target = this;
+ // Handle template case.
+ if (target->IsTemplateElement()) {
+ DocumentFragment* frag =
+ static_cast<HTMLTemplateElement*>(target)->Content();
+ MOZ_ASSERT(frag);
+ target = frag;
+ }
+ // Fast-path for strings with no markup. Limit this to short strings, to
+ // avoid ContainsMarkup taking too long. The choice for 100 is based on
+ // gut feeling.
+ //
+ // Don't do this for elements with a weird parser insertion mode, for
+ // instance setting innerHTML = "" on a <html> element should add the
+ // optional <head> and <body> elements.
+ if (!target->HasWeirdParserInsertionMode() && aInnerHTML.Length() < 100 &&
+ !ContainsMarkup(aInnerHTML)) {
+ aError = nsContentUtils::SetNodeTextContent(target, aInnerHTML, false);
+ return;
+ }
+
+ // mozAutoSubtreeModified keeps the owner document alive. Therefore, using a
+ // raw pointer here is safe.
+ Document* const doc = target->OwnerDoc();
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ target->FireNodeRemovedForChildren();
+
+ // Needed when innerHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, true);
+
+ // Remove childnodes.
+ nsAutoMutationBatch mb(target, true, false);
+ while (target->HasChildren()) {
+ target->RemoveChildNode(target->GetFirstChild(), true);
+ }
+ mb.RemovalDone();
+
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ FragmentOrElement* parseContext = this;
+ if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(this)) {
+ // Fix up the context to be the host of the ShadowRoot. See
+ // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml setter step 1.
+ parseContext = shadowRoot->GetHost();
+ }
+
+ if (doc->IsHTMLDocument()) {
+ nsAtom* contextLocalName = parseContext->NodeInfo()->NameAtom();
+ int32_t contextNameSpaceID = parseContext->GetNameSpaceID();
+
+ int32_t oldChildCount = target->GetChildCount();
+ aError = nsContentUtils::ParseFragmentHTML(
+ aInnerHTML, target, contextLocalName, contextNameSpaceID,
+ doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
+ mb.NodesAdded();
+ // HTML5 parser has notified, but not fired mutation events.
+ nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
+ oldChildCount);
+ } else {
+ RefPtr<DocumentFragment> df = nsContentUtils::CreateContextualFragment(
+ parseContext, aInnerHTML, true, aError);
+ if (!aError.Failed()) {
+ // Suppress assertion about node removal mutation events that can't have
+ // listeners anyway, because no one has had the chance to register
+ // mutation listeners on the fragment that comes from the parser.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ target->AppendChild(*df, aError);
+ mb.NodesAdded();
+ }
+ }
+}
+
+void FragmentOrElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
+
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots) {
+ *aNodeSize += slots->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+ }
+}
diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h
new file mode 100644
index 0000000000..bbef051daf
--- /dev/null
+++ b/dom/base/FragmentOrElement.h
@@ -0,0 +1,350 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all element classes as well as nsDocumentFragment. This
+ * provides an implementation of nsINode, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#ifndef FragmentOrElement_h___
+#define FragmentOrElement_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_*
+#include "nsIContent.h" // base class
+#include "nsIHTMLCollection.h"
+#include "nsIWeakReferenceUtils.h"
+
+class ContentUnbinder;
+class nsContentList;
+class nsLabelsNodeList;
+class nsDOMAttributeMap;
+class nsDOMTokenList;
+class nsIControllers;
+class nsICSSDeclaration;
+class nsDOMCSSAttributeDeclaration;
+class nsDOMStringMap;
+class nsIURI;
+
+namespace mozilla {
+class DeclarationBlock;
+enum class ContentRelevancyReason;
+using ContentRelevancy = EnumSet<ContentRelevancyReason, uint8_t>;
+class ElementAnimationData;
+namespace dom {
+struct CustomElementData;
+class Element;
+class PopoverData;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Tearoff to use for nodes to implement nsISupportsWeakReference
+ */
+class nsNodeSupportsWeakRefTearoff final : public nsISupportsWeakReference {
+ public:
+ explicit nsNodeSupportsWeakRefTearoff(nsINode* aNode) : mNode(aNode) {}
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsISupportsWeakReference
+ NS_DECL_NSISUPPORTSWEAKREFERENCE
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsNodeSupportsWeakRefTearoff)
+
+ private:
+ ~nsNodeSupportsWeakRefTearoff() = default;
+
+ nsCOMPtr<nsINode> mNode;
+};
+
+/**
+ * A generic base class for DOM elements and document fragments,
+ * implementing many nsIContent, nsINode and Element methods.
+ */
+namespace mozilla::dom {
+
+class ShadowRoot;
+
+class FragmentOrElement : public nsIContent {
+ public:
+ explicit FragmentOrElement(
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ explicit FragmentOrElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ // We want to avoid the overhead of extra function calls for
+ // refcounting when we're not doing refcount logging, so we can't
+ // NS_DECL_ISUPPORTS_INHERITED.
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FragmentOrElement, nsIContent);
+
+ NS_DECL_ADDSIZEOFEXCLUDINGTHIS
+
+ // nsINode interface methods
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ mozilla::OOMReporter& aError) override;
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aError) override;
+
+ // nsIContent interface methods
+ const nsTextFragment* GetText() override;
+ uint32_t TextLength() const override;
+ bool TextIsOnlyWhitespace() override;
+ bool ThreadSafeTextIsOnlyWhitespace() const override;
+
+ void DestroyContent() override;
+ void SaveSubtreeState() override;
+
+ nsIHTMLCollection* Children();
+ uint32_t ChildElementCount() {
+ if (!HasChildren()) {
+ return 0;
+ }
+ return Children()->Length();
+ }
+
+ public:
+ /**
+ * If there are listeners for DOMNodeInserted event, fires the event on all
+ * aNodes
+ */
+ static void FireNodeInserted(Document* aDoc, nsINode* aParent,
+ const nsTArray<nsCOMPtr<nsIContent>>& aNodes);
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_INHERITED(
+ FragmentOrElement, nsIContent)
+
+ static void ClearContentUnbinder();
+ static bool CanSkip(nsINode* aNode, bool aRemovingAllowed);
+ static bool CanSkipInCC(nsINode* aNode);
+ static bool CanSkipThis(nsINode* aNode);
+ static void RemoveBlackMarkedNode(nsINode* aNode);
+ static void MarkNodeChildren(nsINode* aNode);
+ static void InitCCCallbacks();
+
+ /**
+ * Is the HTML local name a void element?
+ */
+ static bool IsHTMLVoid(nsAtom* aLocalName);
+
+ protected:
+ virtual ~FragmentOrElement();
+
+ /**
+ * Dummy CopyInnerTo so that we can use the same macros for
+ * Elements and DocumentFragments.
+ */
+ nsresult CopyInnerTo(FragmentOrElement* aDest) { return NS_OK; }
+
+ public:
+ /**
+ * There are a set of DOM- and scripting-specific instance variables
+ * that may only be instantiated when a content object is accessed
+ * through the DOM. Rather than burn actual slots in the content
+ * objects for each of these instance variables, we put them off
+ * in a side structure that's only allocated when the content is
+ * accessed through the DOM.
+ */
+
+ class nsExtendedDOMSlots : public nsIContent::nsExtendedContentSlots {
+ public:
+ nsExtendedDOMSlots();
+ ~nsExtendedDOMSlots();
+
+ void TraverseExtendedSlots(nsCycleCollectionTraversalCallback&) final;
+ void UnlinkExtendedSlots(nsIContent&) final;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+ /**
+ * SMIL Overridde style rules (for SMIL animation of CSS properties)
+ * @see Element::GetSMILOverrideStyle
+ */
+ RefPtr<nsDOMCSSAttributeDeclaration> mSMILOverrideStyle;
+
+ /**
+ * Holds any SMIL override style declaration for this element.
+ */
+ RefPtr<DeclarationBlock> mSMILOverrideStyleDeclaration;
+
+ /**
+ * The controllers of the XUL Element.
+ */
+ nsCOMPtr<nsIControllers> mControllers;
+
+ /**
+ * An object implementing the .labels property for this element.
+ */
+ RefPtr<nsLabelsNodeList> mLabelsList;
+
+ /**
+ * ShadowRoot bound to the element.
+ */
+ RefPtr<ShadowRoot> mShadowRoot;
+
+ /**
+ * Web components custom element data.
+ */
+ UniquePtr<CustomElementData> mCustomElementData;
+
+ /**
+ * Web animations data.
+ */
+ UniquePtr<ElementAnimationData> mAnimations;
+
+ /**
+ * PopoverData for the element.
+ */
+ UniquePtr<PopoverData> mPopoverData;
+
+ /**
+ * Last remembered size (in CSS pixels) for the element.
+ * @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}
+ */
+ Maybe<float> mLastRememberedBSize;
+ Maybe<float> mLastRememberedISize;
+
+ /**
+ * Whether the content of this element is relevant for the purposes
+ * of `content-visibility: auto.
+ */
+ Maybe<ContentRelevancy> mContentRelevancy;
+
+ /**
+ * Whether the content of this element is considered visible for
+ * the purposes of `content-visibility: auto.
+ */
+ Maybe<bool> mVisibleForContentVisibility;
+
+ /**
+ * Explicitly set attr-elements, see
+ * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element
+ */
+ nsTHashMap<nsRefPtrHashKey<nsAtom>, nsWeakPtr> mExplicitlySetAttrElements;
+ };
+
+ class nsDOMSlots : public nsIContent::nsContentSlots {
+ public:
+ nsDOMSlots();
+ ~nsDOMSlots();
+
+ void Traverse(nsCycleCollectionTraversalCallback&) final;
+ void Unlink(nsINode&) final;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * The .style attribute (an interface that forwards to the actual
+ * style rules)
+ * @see nsGenericHTMLElement::GetStyle
+ */
+ nsCOMPtr<nsICSSDeclaration> mStyle;
+
+ /**
+ * The .dataset attribute.
+ * @see nsGenericHTMLElement::GetDataset
+ */
+ nsDOMStringMap* mDataset; // [Weak]
+
+ /**
+ * @see Element::Attributes
+ */
+ RefPtr<nsDOMAttributeMap> mAttributeMap;
+
+ /**
+ * An object implementing the .children property for this element.
+ */
+ RefPtr<nsContentList> mChildrenList;
+
+ /**
+ * An object implementing the .classList property for this element.
+ */
+ RefPtr<nsDOMTokenList> mClassList;
+
+ /**
+ * An object implementing the .part property for this element.
+ */
+ RefPtr<nsDOMTokenList> mPart;
+ };
+
+ /**
+ * In case ExtendedDOMSlots is needed before normal DOMSlots, an instance of
+ * FatSlots class, which combines those two slot types, is created.
+ * This way we can avoid extra allocation for ExtendedDOMSlots.
+ * FatSlots is useful for example when creating Custom Elements.
+ */
+ class FatSlots final : public nsDOMSlots, public nsExtendedDOMSlots {
+ public:
+ FatSlots() : nsDOMSlots(), nsExtendedDOMSlots() {
+ MOZ_COUNT_CTOR(FatSlots);
+ SetExtendedContentSlots(this, false);
+ }
+
+ ~FatSlots() final { MOZ_COUNT_DTOR(FatSlots); }
+ };
+
+ protected:
+ void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
+ void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);
+
+ // Override from nsINode
+ nsIContent::nsContentSlots* CreateSlots() override {
+ return new nsDOMSlots();
+ }
+
+ nsIContent::nsExtendedContentSlots* CreateExtendedSlots() final {
+ return new nsExtendedDOMSlots();
+ }
+
+ nsDOMSlots* DOMSlots() { return static_cast<nsDOMSlots*>(Slots()); }
+
+ nsDOMSlots* GetExistingDOMSlots() const {
+ return static_cast<nsDOMSlots*>(GetExistingSlots());
+ }
+
+ nsExtendedDOMSlots* ExtendedDOMSlots() {
+ nsContentSlots* slots = GetExistingContentSlots();
+ if (!slots) {
+ FatSlots* fatSlots = new FatSlots();
+ mSlots = fatSlots;
+ return fatSlots;
+ }
+
+ if (!slots->GetExtendedContentSlots()) {
+ slots->SetExtendedContentSlots(CreateExtendedSlots(), true);
+ }
+
+ return static_cast<nsExtendedDOMSlots*>(slots->GetExtendedContentSlots());
+ }
+
+ const nsExtendedDOMSlots* GetExistingExtendedDOMSlots() const {
+ return static_cast<const nsExtendedDOMSlots*>(
+ GetExistingExtendedContentSlots());
+ }
+
+ nsExtendedDOMSlots* GetExistingExtendedDOMSlots() {
+ return static_cast<nsExtendedDOMSlots*>(GetExistingExtendedContentSlots());
+ }
+
+ friend class ::ContentUnbinder;
+};
+
+} // namespace mozilla::dom
+
+#define NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE \
+ if (NS_SUCCEEDED(rv)) return rv; \
+ \
+ rv = FragmentOrElement::QueryInterface(aIID, aInstancePtr); \
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+
+#endif /* FragmentOrElement_h___ */
diff --git a/dom/base/FromParser.h b/dom/base/FromParser.h
new file mode 100644
index 0000000000..0faa65e542
--- /dev/null
+++ b/dom/base/FromParser.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FromParser_h
+#define mozilla_dom_FromParser_h
+
+namespace mozilla::dom {
+
+/**
+ * Constants for passing as aFromParser
+ */
+enum FromParser {
+ NOT_FROM_PARSER = 0,
+ FROM_PARSER_NETWORK = 1,
+ FROM_PARSER_DOCUMENT_WRITE = 1 << 1,
+ FROM_PARSER_FRAGMENT = 1 << 2,
+ FROM_PARSER_XSLT = 1 << 3
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_FromParser_h
diff --git a/dom/base/FullscreenChange.h b/dom/base/FullscreenChange.h
new file mode 100644
index 0000000000..a3cbbe8748
--- /dev/null
+++ b/dom/base/FullscreenChange.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/*
+ * Struct for holding fullscreen request.
+ */
+
+#ifndef mozilla_FullscreenRequest_h
+#define mozilla_FullscreenRequest_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/PendingFullscreenEvent.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Document.h"
+#include "nsIScriptError.h"
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+
+class FullscreenChange : public LinkedListElement<FullscreenChange> {
+ public:
+ FullscreenChange(const FullscreenChange&) = delete;
+
+ enum ChangeType {
+ eEnter,
+ eExit,
+ };
+
+ ChangeType Type() const { return mType; }
+ dom::Document* Document() const { return mDocument; }
+ dom::Promise* GetPromise() const { return mPromise; }
+
+ void MayResolvePromise() const {
+ if (mPromise) {
+ MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
+ mPromise->MaybeResolveWithUndefined();
+ }
+ }
+
+ void MayRejectPromise(const nsACString& aMessage) {
+ if (mPromise) {
+ MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
+ mPromise->MaybeRejectWithTypeError(aMessage);
+ }
+ }
+ template <int N>
+ void MayRejectPromise(const char (&aMessage)[N]) {
+ MayRejectPromise(nsLiteralCString(aMessage));
+ }
+
+ protected:
+ typedef dom::Promise Promise;
+
+ FullscreenChange(ChangeType aType, dom::Document* aDocument,
+ already_AddRefed<Promise> aPromise)
+ : mType(aType), mDocument(aDocument), mPromise(aPromise) {
+ MOZ_ASSERT(aDocument);
+ }
+
+ ~FullscreenChange() {
+ MOZ_ASSERT_IF(mPromise,
+ mPromise->State() != Promise::PromiseState::Pending);
+ }
+
+ private:
+ ChangeType mType;
+ nsCOMPtr<dom::Document> mDocument;
+ RefPtr<Promise> mPromise;
+};
+
+class FullscreenRequest : public FullscreenChange {
+ public:
+ static const ChangeType kType = eEnter;
+
+ static UniquePtr<FullscreenRequest> Create(dom::Element* aElement,
+ dom::CallerType aCallerType,
+ ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(aElement->GetOwnerGlobal(), aRv);
+ return WrapUnique(
+ new FullscreenRequest(aElement, promise.forget(), aCallerType, true));
+ }
+
+ static UniquePtr<FullscreenRequest> CreateForRemote(dom::Element* aElement) {
+ return WrapUnique(new FullscreenRequest(aElement, nullptr,
+ dom::CallerType::NonSystem, false));
+ }
+
+ MOZ_COUNTED_DTOR(FullscreenRequest)
+
+ dom::Element* Element() const { return mElement; }
+
+ // Reject the fullscreen request with the given reason.
+ // It will dispatch the fullscreenerror event.
+ void Reject(const char* aReason) {
+ if (nsPresContext* presContext = Document()->GetPresContext()) {
+ auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
+ FullscreenEventType::Error, Document(), mElement);
+ presContext->RefreshDriver()->ScheduleFullscreenEvent(
+ std::move(pendingEvent));
+ }
+ MayRejectPromise("Fullscreen request denied");
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ Document(), nsContentUtils::eDOM_PROPERTIES,
+ aReason);
+ }
+
+ private:
+ RefPtr<dom::Element> mElement;
+
+ public:
+ // This value should be true if the fullscreen request is
+ // originated from system code.
+ const dom::CallerType mCallerType;
+ // This value denotes whether we should trigger a NewOrigin event if
+ // requesting fullscreen in its document causes the origin which is
+ // fullscreen to change. We may want *not* to trigger that event if
+ // we're calling RequestFullscreen() as part of a continuation of a
+ // request in a subdocument in different process, whereupon the caller
+ // need to send some notification itself with the real origin.
+ const bool mShouldNotifyNewOrigin;
+
+ private:
+ FullscreenRequest(dom::Element* aElement,
+ already_AddRefed<dom::Promise> aPromise,
+ dom::CallerType aCallerType, bool aShouldNotifyNewOrigin)
+ : FullscreenChange(kType, aElement->OwnerDoc(), std::move(aPromise)),
+ mElement(aElement),
+ mCallerType(aCallerType),
+ mShouldNotifyNewOrigin(aShouldNotifyNewOrigin) {
+ MOZ_COUNT_CTOR(FullscreenRequest);
+ }
+};
+
+class FullscreenExit : public FullscreenChange {
+ public:
+ static const ChangeType kType = eExit;
+
+ static UniquePtr<FullscreenExit> Create(dom::Document* aDoc,
+ ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(aDoc->GetOwnerGlobal(), aRv);
+ return WrapUnique(new FullscreenExit(aDoc, promise.forget()));
+ }
+
+ static UniquePtr<FullscreenExit> CreateForRemote(dom::Document* aDoc) {
+ return WrapUnique(new FullscreenExit(aDoc, nullptr));
+ }
+
+ MOZ_COUNTED_DTOR(FullscreenExit)
+
+ private:
+ FullscreenExit(dom::Document* aDoc, already_AddRefed<Promise> aPromise)
+ : FullscreenChange(kType, aDoc, std::move(aPromise)) {
+ MOZ_COUNT_CTOR(FullscreenExit);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_FullscreenRequest_h
diff --git a/dom/base/FuzzingFunctions.cpp b/dom/base/FuzzingFunctions.cpp
new file mode 100644
index 0000000000..36bcaff987
--- /dev/null
+++ b/dom/base/FuzzingFunctions.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzingFunctions.h"
+
+#include "nsJSEnvironment.h"
+#include "js/GCAPI.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextInputProcessor.h"
+#include "nsFocusManager.h"
+#include "nsIAccessibilityService.h"
+#include "nsPIDOMWindow.h"
+#include "xpcAccessibilityService.h"
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/dom/ContentChild.h"
+#endif
+
+namespace mozilla::dom {
+
+/* static */
+void FuzzingFunctions::GarbageCollect(const GlobalObject&) {
+ nsJSContext::GarbageCollectNow(JS::GCReason::COMPONENT_UTILS,
+ nsJSContext::NonShrinkingGC);
+}
+
+/* static */
+void FuzzingFunctions::GarbageCollectCompacting(const GlobalObject&) {
+ nsJSContext::GarbageCollectNow(JS::GCReason::COMPONENT_UTILS,
+ nsJSContext::ShrinkingGC);
+}
+
+/* static */
+void FuzzingFunctions::Crash(const GlobalObject& aGlobalObject,
+ const nsAString& aKeyValue) {
+ char msgbuf[250];
+
+ SprintfLiteral(msgbuf, "%s", NS_ConvertUTF16toUTF8(aKeyValue).get());
+ if (aKeyValue.Length() >= sizeof(msgbuf)) {
+ // Update the end of a truncated message to '...'.
+ strcpy(&msgbuf[sizeof(msgbuf) - 4], "...");
+ }
+ MOZ_CRASH_UNSAFE_PRINTF("%s", msgbuf);
+}
+
+/* static */
+void FuzzingFunctions::CycleCollect(const GlobalObject&) {
+ nsJSContext::CycleCollectNow(CCReason::API);
+}
+
+void FuzzingFunctions::MemoryPressure(const GlobalObject&) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+}
+
+/* static */
+void FuzzingFunctions::SignalIPCReady(const GlobalObject&) {
+#ifdef FUZZING_SNAPSHOT
+ ContentChild::GetSingleton()->SendSignalFuzzingReady();
+#endif
+}
+
+/* static */
+void FuzzingFunctions::EnableAccessibility(const GlobalObject&,
+ ErrorResult& aRv) {
+ RefPtr<nsIAccessibilityService> a11y;
+ nsresult rv;
+
+ rv = NS_GetAccessibilityService(getter_AddRefs(a11y));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+struct ModifierKey final {
+ Modifier mModifier;
+ KeyNameIndex mKeyNameIndex;
+ bool mLockable;
+
+ ModifierKey(Modifier aModifier, KeyNameIndex aKeyNameIndex, bool aLockable)
+ : mModifier(aModifier),
+ mKeyNameIndex(aKeyNameIndex),
+ mLockable(aLockable) {}
+};
+
+static const ModifierKey kModifierKeys[] = {
+ ModifierKey(MODIFIER_ALT, KEY_NAME_INDEX_Alt, false),
+ ModifierKey(MODIFIER_ALTGRAPH, KEY_NAME_INDEX_AltGraph, false),
+ ModifierKey(MODIFIER_CONTROL, KEY_NAME_INDEX_Control, false),
+ ModifierKey(MODIFIER_FN, KEY_NAME_INDEX_Fn, false),
+ ModifierKey(MODIFIER_META, KEY_NAME_INDEX_Meta, false),
+ ModifierKey(MODIFIER_OS, KEY_NAME_INDEX_OS, false),
+ ModifierKey(MODIFIER_SHIFT, KEY_NAME_INDEX_Shift, false),
+ ModifierKey(MODIFIER_SYMBOL, KEY_NAME_INDEX_Symbol, false),
+ ModifierKey(MODIFIER_CAPSLOCK, KEY_NAME_INDEX_CapsLock, true),
+ ModifierKey(MODIFIER_FNLOCK, KEY_NAME_INDEX_FnLock, true),
+ ModifierKey(MODIFIER_NUMLOCK, KEY_NAME_INDEX_NumLock, true),
+ ModifierKey(MODIFIER_SCROLLLOCK, KEY_NAME_INDEX_ScrollLock, true),
+ ModifierKey(MODIFIER_SYMBOLLOCK, KEY_NAME_INDEX_SymbolLock, true),
+};
+
+/* static */
+Modifiers FuzzingFunctions::ActivateModifiers(
+ TextInputProcessor* aTextInputProcessor, Modifiers aModifiers,
+ nsIWidget* aWidget, ErrorResult& aRv) {
+ MOZ_ASSERT(aTextInputProcessor);
+
+ if (aModifiers == MODIFIER_NONE) {
+ return MODIFIER_NONE;
+ }
+
+ // We don't want to dispatch modifier key event from here. In strictly
+ // speaking, all necessary modifiers should be activated with dispatching
+ // each modifier key event. However, we cannot keep storing
+ // TextInputProcessor instance for multiple SynthesizeKeyboardEvents() calls.
+ // So, if some callers need to emulate modifier key events, they should do
+ // it by themselves.
+ uint32_t flags = nsITextInputProcessor::KEY_NON_PRINTABLE_KEY |
+ nsITextInputProcessor::KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT;
+
+ Modifiers activatedModifiers = MODIFIER_NONE;
+ Modifiers activeModifiers = aTextInputProcessor->GetActiveModifiers();
+ for (const ModifierKey& kModifierKey : kModifierKeys) {
+ if (!(kModifierKey.mModifier & aModifiers)) {
+ continue; // Not requested modifier.
+ }
+ if (kModifierKey.mModifier & activeModifiers) {
+ continue; // Already active, do nothing.
+ }
+ WidgetKeyboardEvent event(true, eVoidEvent, aWidget);
+ // mKeyCode will be computed by TextInputProcessor automatically.
+ event.mKeyNameIndex = kModifierKey.mKeyNameIndex;
+ aRv = aTextInputProcessor->Keydown(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return activatedModifiers;
+ }
+ if (kModifierKey.mLockable) {
+ aRv = aTextInputProcessor->Keyup(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return activatedModifiers;
+ }
+ }
+ activatedModifiers |= kModifierKey.mModifier;
+ }
+ return activatedModifiers;
+}
+
+/* static */
+Modifiers FuzzingFunctions::InactivateModifiers(
+ TextInputProcessor* aTextInputProcessor, Modifiers aModifiers,
+ nsIWidget* aWidget, ErrorResult& aRv) {
+ MOZ_ASSERT(aTextInputProcessor);
+
+ if (aModifiers == MODIFIER_NONE) {
+ return MODIFIER_NONE;
+ }
+
+ // We don't want to dispatch modifier key event from here. In strictly
+ // speaking, all necessary modifiers should be activated with dispatching
+ // each modifier key event. However, we cannot keep storing
+ // TextInputProcessor instance for multiple SynthesizeKeyboardEvents() calls.
+ // So, if some callers need to emulate modifier key events, they should do
+ // it by themselves.
+ uint32_t flags = nsITextInputProcessor::KEY_NON_PRINTABLE_KEY |
+ nsITextInputProcessor::KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT;
+
+ Modifiers inactivatedModifiers = MODIFIER_NONE;
+ Modifiers activeModifiers = aTextInputProcessor->GetActiveModifiers();
+ for (const ModifierKey& kModifierKey : kModifierKeys) {
+ if (!(kModifierKey.mModifier & aModifiers)) {
+ continue; // Not requested modifier.
+ }
+ if (kModifierKey.mModifier & activeModifiers) {
+ continue; // Already active, do nothing.
+ }
+ WidgetKeyboardEvent event(true, eVoidEvent, aWidget);
+ // mKeyCode will be computed by TextInputProcessor automatically.
+ event.mKeyNameIndex = kModifierKey.mKeyNameIndex;
+ if (kModifierKey.mLockable) {
+ aRv = aTextInputProcessor->Keydown(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return inactivatedModifiers;
+ }
+ }
+ aRv = aTextInputProcessor->Keyup(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return inactivatedModifiers;
+ }
+ inactivatedModifiers |= kModifierKey.mModifier;
+ }
+ return inactivatedModifiers;
+}
+
+/* static */
+void FuzzingFunctions::SynthesizeKeyboardEvents(
+ const GlobalObject& aGlobalObject, const nsAString& aKeyValue,
+ const KeyboardEventInit& aDict, ErrorResult& aRv) {
+ // Prepare keyboard event to synthesize first.
+ uint32_t flags = 0;
+ // Don't modify the given dictionary since caller may want to modify
+ // a part of it and call this with it again.
+ WidgetKeyboardEvent event(true, eVoidEvent, nullptr);
+ event.mKeyCode = aDict.mKeyCode;
+ event.mCharCode = 0; // Ignore.
+ event.mKeyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aKeyValue);
+ if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ event.mKeyValue = aKeyValue;
+ }
+ // code value should be empty string or one of valid code value.
+ event.mCodeNameIndex =
+ aDict.mCode.IsEmpty()
+ ? CODE_NAME_INDEX_UNKNOWN
+ : WidgetKeyboardEvent::GetCodeNameIndex(aDict.mCode);
+ if (NS_WARN_IF(event.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) {
+ // Meaning that the code value is specified but it's not a known code
+ // value. TextInputProcessor does not support synthesizing keyboard
+ // events with unknown code value. So, returns error now.
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ event.mLocation = aDict.mLocation;
+ event.mIsRepeat = aDict.mRepeat;
+
+#define SET_MODIFIER(aName, aValue) \
+ if (aDict.m##aName) { \
+ event.mModifiers |= aValue; \
+ }
+
+ SET_MODIFIER(CtrlKey, MODIFIER_CONTROL)
+ SET_MODIFIER(ShiftKey, MODIFIER_SHIFT)
+ SET_MODIFIER(AltKey, MODIFIER_ALT)
+ SET_MODIFIER(MetaKey, MODIFIER_META)
+ SET_MODIFIER(ModifierAltGraph, MODIFIER_ALTGRAPH)
+ SET_MODIFIER(ModifierCapsLock, MODIFIER_CAPSLOCK)
+ SET_MODIFIER(ModifierFn, MODIFIER_FN)
+ SET_MODIFIER(ModifierFnLock, MODIFIER_FNLOCK)
+ SET_MODIFIER(ModifierNumLock, MODIFIER_NUMLOCK)
+ SET_MODIFIER(ModifierOS, MODIFIER_OS)
+ SET_MODIFIER(ModifierScrollLock, MODIFIER_SCROLLLOCK)
+ SET_MODIFIER(ModifierSymbol, MODIFIER_SYMBOL)
+ SET_MODIFIER(ModifierSymbolLock, MODIFIER_SYMBOLLOCK)
+
+#undef SET_MODIFIER
+
+ // If we could distinguish whether the caller specified 0 explicitly or
+ // not, we would skip computing the key location when it's specified
+ // explicitly. However, this caller probably won't test tricky keyboard
+ // events, so, it must be enough even though caller cannot set location
+ // to 0.
+ Maybe<uint32_t> maybeNonStandardLocation;
+ if (!event.mLocation) {
+ maybeNonStandardLocation = mozilla::Some(event.mLocation);
+ }
+
+ // If the key is a printable key and |.code| and/or |.keyCode| value is
+ // not specified as non-zero explicitly, let's assume that the caller
+ // emulates US-English keyboard's behavior (because otherwise, caller
+ // should set both values.
+ if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ if (event.mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ event.mCodeNameIndex =
+ TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(
+ event.mKeyValue, maybeNonStandardLocation);
+ MOZ_ASSERT(event.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ }
+ if (!event.mKeyCode) {
+ event.mKeyCode =
+ TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout(
+ event.mKeyValue, maybeNonStandardLocation);
+ if (!event.mKeyCode) {
+ // Prevent to recompute keyCode in TextInputProcessor.
+ flags |= nsITextInputProcessor::KEY_KEEP_KEYCODE_ZERO;
+ }
+ }
+ }
+ // If the key is a non-printable key, we can compute |.code| value of
+ // usual keyboard of the platform. Note that |.keyCode| value for
+ // non-printable key will be computed by TextInputProcessor. So, we need
+ // to take care only |.code| value here.
+ else if (event.mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ event.mCodeNameIndex =
+ WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(
+ event.mKeyNameIndex, maybeNonStandardLocation);
+ }
+
+ // Synthesize keyboard events in a DOM window which is in-process top one.
+ // For emulating user input, this is better than dispatching the events in
+ // the caller's DOM window because this approach can test the path redirecting
+ // the events to focused subdocument too. However, for now, we cannot
+ // dispatch it via another process without big changes. Therefore, we should
+ // use in-process top window instead. If you need to test the path in the
+ // parent process to, please file a feature request bug.
+ nsCOMPtr<nsPIDOMWindowInner> windowInner =
+ do_QueryInterface(aGlobalObject.GetAsSupports());
+ if (!windowInner) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsPIDOMWindowOuter* inProcessTopWindowOuter =
+ windowInner->GetInProcessScriptableTop();
+ if (NS_WARN_IF(!inProcessTopWindowOuter)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIDocShell* docShell = inProcessTopWindowOuter->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext = docShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ event.mWidget = presContext->GetRootWidget();
+ if (NS_WARN_IF(!event.mWidget)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> inProcessTopWindowInner =
+ inProcessTopWindowOuter->EnsureInnerWindow();
+ if (NS_WARN_IF(!inProcessTopWindowInner)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<TextInputProcessor> textInputProcessor = new TextInputProcessor();
+ bool beganInputTransaction = false;
+ aRv = textInputProcessor->BeginInputTransactionForFuzzing(
+ inProcessTopWindowInner, nullptr, &beganInputTransaction);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (NS_WARN_IF(!beganInputTransaction)) {
+ // This is possible if a keyboard event listener or something tries to
+ // dispatch next keyboard events during dispatching a keyboard event via
+ // TextInputProcessor.
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // First, activate necessary modifiers.
+ // MOZ_KnownLive(event.mWidget) is safe because `event` is an instance in
+ // the stack, and `mWidget` is `nsCOMPtr<nsIWidget>`.
+ Modifiers activatedModifiers = ActivateModifiers(
+ textInputProcessor, event.mModifiers, MOZ_KnownLive(event.mWidget), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Then, dispatch keydown and keypress.
+ aRv = textInputProcessor->Keydown(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Then, dispatch keyup.
+ aRv = textInputProcessor->Keyup(event, flags);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Finally, inactivate some modifiers which are activated by this call.
+ // MOZ_KnownLive(event.mWidget) is safe because `event` is an instance in
+ // the stack, and `mWidget` is `nsCOMPtr<nsIWidget>`.
+ InactivateModifiers(textInputProcessor, activatedModifiers,
+ MOZ_KnownLive(event.mWidget), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Unfortunately, we cannot keep storing modifier state in the
+ // TextInputProcessor since if we store it into a static variable,
+ // we need to take care of resetting it when the caller wants.
+ // However, that makes API more complicated. So, until they need
+ // to want
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/FuzzingFunctions.h b/dom/base/FuzzingFunctions.h
new file mode 100644
index 0000000000..1028803e28
--- /dev/null
+++ b/dom/base/FuzzingFunctions.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FuzzingFunctions
+#define mozilla_dom_FuzzingFunctions
+
+#include "mozilla/EventForwards.h"
+
+class nsIWidget;
+
+namespace mozilla {
+
+class ErrorResult;
+class TextInputProcessor;
+
+namespace dom {
+
+class GlobalObject;
+struct KeyboardEventInit;
+
+class FuzzingFunctions final {
+ public:
+ static void GarbageCollect(const GlobalObject&);
+
+ static void GarbageCollectCompacting(const GlobalObject&);
+
+ static void Crash(const GlobalObject& aGlobalObject,
+ const nsAString& aKeyValue);
+
+ static void CycleCollect(const GlobalObject&);
+
+ static void MemoryPressure(const GlobalObject&);
+
+ static void SignalIPCReady(const GlobalObject&);
+
+ static void EnableAccessibility(const GlobalObject&, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static void SynthesizeKeyboardEvents(
+ const GlobalObject& aGlobalObject, const nsAString& aKeyValue,
+ const KeyboardEventInit& aKeyboardEvent, ErrorResult& aRv);
+
+ private:
+ /**
+ * ActivateModifiers() activates aModifiers in the TextInputProcessor.
+ *
+ * @param aTextInputProcessor The TIP whose modifier state you want to change.
+ * @param aModifiers Modifiers which you want to activate.
+ * @param aWidget The widget which should be set to
+ * WidgetKeyboardEvent.
+ * @param aRv Returns error if TextInputProcessor fails to
+ * dispatch a modifier key event.
+ * @return Modifiers which are activated by the call.
+ */
+ MOZ_CAN_RUN_SCRIPT static Modifiers ActivateModifiers(
+ TextInputProcessor* aTextInputProcessor, Modifiers aModifiers,
+ nsIWidget* aWidget, ErrorResult& aRv);
+
+ /**
+ * InactivateModifiers() inactivates aModifiers in the TextInputProcessor.
+ *
+ * @param aTextInputProcessor The TIP whose modifier state you want to change.
+ * @param aModifiers Modifiers which you want to inactivate.
+ * @param aWidget The widget which should be set to
+ * WidgetKeyboardEvent.
+ * @param aRv Returns error if TextInputProcessor fails to
+ * dispatch a modifier key event.
+ * @return Modifiers which are inactivated by the call.
+ */
+ MOZ_CAN_RUN_SCRIPT static Modifiers InactivateModifiers(
+ TextInputProcessor* aTextInputProcessor, Modifiers aModifiers,
+ nsIWidget* aWidget, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FuzzingFunctions
diff --git a/dom/base/GeneratedImageContent.cpp b/dom/base/GeneratedImageContent.cpp
new file mode 100644
index 0000000000..78e6673220
--- /dev/null
+++ b/dom/base/GeneratedImageContent.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/GeneratedImageContent.h"
+
+#include "nsContentCreatorFunctions.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/Document.h"
+#include "nsNodeInfoManager.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ELEMENT_CLONE(GeneratedImageContent);
+
+already_AddRefed<GeneratedImageContent> GeneratedImageContent::Create(
+ Document& aDocument, uint32_t aContentIndex) {
+ RefPtr<dom::NodeInfo> nodeInfo = aDocument.NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::mozgeneratedcontentimage, nullptr, kNameSpaceID_XHTML,
+ nsINode::ELEMENT_NODE);
+
+ auto* nim = nodeInfo->NodeInfoManager();
+ RefPtr<GeneratedImageContent> image =
+ new (nim) GeneratedImageContent(nodeInfo.forget());
+ image->mIndex = aContentIndex;
+ return image.forget();
+}
+
+already_AddRefed<GeneratedImageContent>
+GeneratedImageContent::CreateForListStyleImage(Document& aDocument) {
+ return Create(aDocument, uint32_t(-1));
+}
+
+JSObject* GeneratedImageContent::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/GeneratedImageContent.h b/dom/base/GeneratedImageContent.h
new file mode 100644
index 0000000000..28132c3c43
--- /dev/null
+++ b/dom/base/GeneratedImageContent.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_GeneratedImageContent_h
+#define dom_base_GeneratedImageContent_h
+
+/* A content node that keeps track of an index in the parent's `content`
+ * property value, used for url() values in the content of a ::before or ::after
+ * pseudo-element. */
+
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla::dom {
+
+class GeneratedImageContent final : public nsGenericHTMLElement {
+ public:
+ static already_AddRefed<GeneratedImageContent> Create(Document&,
+ uint32_t aContentIndex);
+ // An image created from 'list-style-image' for a ::marker pseudo.
+ static already_AddRefed<GeneratedImageContent> CreateForListStyleImage(
+ Document&);
+
+ explicit GeneratedImageContent(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
+ : nsGenericHTMLElement(std::move(aNodeInfo)) {
+ MOZ_ASSERT(IsInNamespace(kNameSpaceID_XHTML),
+ "Someone messed up our nodeinfo");
+ }
+
+ ElementState IntrinsicState() const override {
+ ElementState state = nsGenericHTMLElement::IntrinsicState();
+ if (mBroken) {
+ state |= ElementState::BROKEN;
+ }
+ return state;
+ }
+ nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const final;
+
+ nsresult CopyInnerTo(GeneratedImageContent* aDest) {
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aDest->mIndex = mIndex;
+ return NS_OK;
+ }
+
+ // Is this an image created from 'list-style-image'?
+ bool IsForListStyleImageMarker() const { return Index() == uint32_t(-1); }
+
+ // @note we use -1 for images created from 'list-style-image'
+ uint32_t Index() const { return mIndex; }
+
+ // Notify this image failed to load.
+ void NotifyLoadFailed() {
+ mBroken = true;
+ UpdateState(true);
+ }
+
+ protected:
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ virtual ~GeneratedImageContent() = default;
+ uint32_t mIndex = 0;
+ bool mBroken = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // dom_base_GeneratedImageContent_h
diff --git a/dom/base/GlobalTeardownObserver.cpp b/dom/base/GlobalTeardownObserver.cpp
new file mode 100644
index 0000000000..9f9ac39b47
--- /dev/null
+++ b/dom/base/GlobalTeardownObserver.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GlobalTeardownObserver.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/Document.h"
+
+namespace mozilla {
+
+GlobalTeardownObserver::GlobalTeardownObserver() = default;
+GlobalTeardownObserver::GlobalTeardownObserver(nsIGlobalObject* aGlobalObject,
+ bool aHasOrHasHadOwnerWindow)
+ : mHasOrHasHadOwnerWindow(aHasOrHasHadOwnerWindow) {
+ BindToOwner(aGlobalObject);
+}
+
+GlobalTeardownObserver::~GlobalTeardownObserver() {
+ if (mParentObject) {
+ mParentObject->RemoveGlobalTeardownObserver(this);
+ }
+}
+
+void GlobalTeardownObserver::BindToOwner(nsIGlobalObject* aOwner) {
+ MOZ_ASSERT(!mParentObject);
+
+ if (aOwner) {
+ mParentObject = aOwner;
+ aOwner->AddGlobalTeardownObserver(this);
+ // Let's cache the result of this QI for fast access and off main thread
+ // usage
+ mOwnerWindow =
+ nsCOMPtr<nsPIDOMWindowInner>(do_QueryInterface(aOwner)).get();
+ if (mOwnerWindow) {
+ mHasOrHasHadOwnerWindow = true;
+ }
+ }
+}
+
+void GlobalTeardownObserver::DisconnectFromOwner() {
+ if (mParentObject) {
+ mParentObject->RemoveGlobalTeardownObserver(this);
+ }
+ mOwnerWindow = nullptr;
+ mParentObject = nullptr;
+}
+
+nsresult GlobalTeardownObserver::CheckCurrentGlobalCorrectness() const {
+ NS_ENSURE_STATE(!mHasOrHasHadOwnerWindow || mOwnerWindow);
+
+ // Main-thread.
+ if (mOwnerWindow && !mOwnerWindow->IsCurrentInnerWindow()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_IsMainThread()) {
+ return NS_OK;
+ }
+
+ if (!mParentObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mParentObject->IsDying()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+}; // namespace mozilla
diff --git a/dom/base/GlobalTeardownObserver.h b/dom/base/GlobalTeardownObserver.h
new file mode 100644
index 0000000000..e6e3a56299
--- /dev/null
+++ b/dom/base/GlobalTeardownObserver.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_BASE_GLOBALTEARDOWNOBSERVER_H_
+#define DOM_BASE_GLOBALTEARDOWNOBSERVER_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsID.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsPIDOMWindow.h"
+
+#define NS_GLOBALTEARDOWNOBSERVER_IID \
+ { \
+ 0xc31fddb9, 0xec49, 0x4f24, { \
+ 0x90, 0x16, 0xb5, 0x2b, 0x26, 0x6c, 0xb6, 0x29 \
+ } \
+ }
+
+namespace mozilla {
+
+class GlobalTeardownObserver
+ : public nsISupports,
+ public LinkedListElement<GlobalTeardownObserver> {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_GLOBALTEARDOWNOBSERVER_IID)
+
+ GlobalTeardownObserver();
+ explicit GlobalTeardownObserver(nsIGlobalObject* aGlobalObject,
+ bool aHasOrHasHadOwnerWindow = false);
+
+ nsPIDOMWindowInner* GetOwner() const { return mOwnerWindow; }
+ nsIGlobalObject* GetOwnerGlobal() const { return mParentObject; }
+ bool HasOrHasHadOwner() { return mHasOrHasHadOwnerWindow; }
+
+ void GetParentObject(nsIScriptGlobalObject** aParentObject) {
+ if (mParentObject) {
+ CallQueryInterface(mParentObject, aParentObject);
+ } else {
+ *aParentObject = nullptr;
+ }
+ }
+
+ virtual void DisconnectFromOwner();
+
+ // A global permanently becomes invalid when DisconnectEventTargetObjects() is
+ // called. Normally this means:
+ // - For the main thread, when nsGlobalWindowInner::FreeInnerObjects is
+ // called.
+ // - For a worker thread, when clearing the main event queue. (Which we do
+ // slightly later than when the spec notionally calls for it to be done.)
+ //
+ // A global may also become temporarily invalid when:
+ // - For the main thread, if the window is no longer the WindowProxy's current
+ // inner window due to being placed in the bfcache.
+ nsresult CheckCurrentGlobalCorrectness() const;
+
+ protected:
+ virtual ~GlobalTeardownObserver();
+
+ void BindToOwner(nsIGlobalObject* aOwner);
+
+ private:
+ // The parent global object. The global will clear this when
+ // it is destroyed by calling DisconnectFromOwner().
+ nsIGlobalObject* MOZ_NON_OWNING_REF mParentObject = nullptr;
+ // mParentObject pre QI-ed and cached (inner window)
+ // (it is needed for off main thread access)
+ // It is obtained in BindToOwner and reset in DisconnectFromOwner.
+ nsPIDOMWindowInner* MOZ_NON_OWNING_REF mOwnerWindow = nullptr;
+ bool mHasOrHasHadOwnerWindow = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(GlobalTeardownObserver,
+ NS_GLOBALTEARDOWNOBSERVER_IID)
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/HTMLSplitOnSpacesTokenizer.h b/dom/base/HTMLSplitOnSpacesTokenizer.h
new file mode 100644
index 0000000000..5eea2279ea
--- /dev/null
+++ b/dom/base/HTMLSplitOnSpacesTokenizer.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HTMLSplitOnSpacesTokenizer_h
+#define HTMLSplitOnSpacesTokenizer_h
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+
+typedef nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace>
+ HTMLSplitOnSpacesTokenizer;
+
+#endif
diff --git a/dom/base/Highlight.cpp b/dom/base/Highlight.cpp
new file mode 100644
index 0000000000..efff5b81b8
--- /dev/null
+++ b/dom/base/Highlight.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Highlight.h"
+#include "HighlightRegistry.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/dom/HighlightBinding.h"
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RefPtr.h"
+
+#include "AbstractRange.h"
+#include "Document.h"
+#include "PresShell.h"
+#include "Selection.h"
+
+#include "nsFrameSelection.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Highlight, mRanges, mWindow)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Highlight)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Highlight)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Highlight)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Highlight::Highlight(
+ const Sequence<OwningNonNull<AbstractRange>>& aInitialRanges,
+ nsPIDOMWindowInner* aWindow, ErrorResult& aRv)
+ : mWindow(aWindow) {
+ for (RefPtr<AbstractRange> range : aInitialRanges) {
+ Add(*range, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+already_AddRefed<Highlight> Highlight::Constructor(
+ const GlobalObject& aGlobal,
+ const Sequence<OwningNonNull<AbstractRange>>& aInitialRanges,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.ThrowUnknownError(
+ "There is no window associated to "
+ "this highlight object!");
+ return nullptr;
+ }
+
+ RefPtr<Highlight> highlight = new Highlight(aInitialRanges, window, aRv);
+ return aRv.Failed() ? nullptr : highlight.forget();
+}
+
+void Highlight::AddToHighlightRegistry(HighlightRegistry& aHighlightRegistry,
+ const nsAtom& aHighlightName) {
+ mHighlightRegistries.LookupOrInsert(&aHighlightRegistry)
+ .Insert(&aHighlightName);
+}
+
+void Highlight::RemoveFromHighlightRegistry(
+ HighlightRegistry& aHighlightRegistry, const nsAtom& aHighlightName) {
+ if (auto entry = mHighlightRegistries.Lookup(&aHighlightRegistry)) {
+ auto& highlightNames = entry.Data();
+ highlightNames.Remove(&aHighlightName);
+ if (highlightNames.IsEmpty()) {
+ entry.Remove();
+ }
+ }
+}
+
+already_AddRefed<Selection> Highlight::CreateHighlightSelection(
+ const nsAtom* aHighlightName, nsFrameSelection* aFrameSelection) const {
+ MOZ_ASSERT(aFrameSelection);
+ MOZ_ASSERT(aFrameSelection->GetPresShell());
+ RefPtr<Selection> selection =
+ MakeRefPtr<Selection>(SelectionType::eHighlight, aFrameSelection);
+ selection->SetHighlightName(aHighlightName);
+ AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__);
+ selectionBatcher.AddFrameSelection(aFrameSelection);
+ for (const RefPtr<AbstractRange>& range : mRanges) {
+ if (range->GetComposedDocOfContainers() ==
+ aFrameSelection->GetPresShell()->GetDocument()) {
+ // since this is run in a context guarded by a selection batcher,
+ // no strong reference is needed to keep `range` alive.
+ selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(
+ MOZ_KnownLive(*range));
+ }
+ }
+ return selection.forget();
+}
+
+void Highlight::Add(AbstractRange& aRange, ErrorResult& aRv) {
+ Highlight_Binding::SetlikeHelpers::Add(this, aRange, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (!mRanges.Contains(&aRange)) {
+ mRanges.AppendElement(&aRange);
+ AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__,
+ mHighlightRegistries.Count());
+ for (const RefPtr<HighlightRegistry>& registry :
+ mHighlightRegistries.Keys()) {
+ auto frameSelection = registry->GetFrameSelection();
+ selectionBatcher.AddFrameSelection(frameSelection);
+ // since this is run in a context guarded by a selection batcher,
+ // no strong reference is needed to keep `registry` alive.
+ MOZ_KnownLive(registry)->MaybeAddRangeToHighlightSelection(aRange, *this);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+}
+
+void Highlight::Clear(ErrorResult& aRv) {
+ Highlight_Binding::SetlikeHelpers::Clear(this, aRv);
+ if (!aRv.Failed()) {
+ mRanges.Clear();
+ AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__,
+ mHighlightRegistries.Count());
+
+ for (const RefPtr<HighlightRegistry>& registry :
+ mHighlightRegistries.Keys()) {
+ auto frameSelection = registry->GetFrameSelection();
+ selectionBatcher.AddFrameSelection(frameSelection);
+ // since this is run in a context guarded by a selection batcher,
+ // no strong reference is needed to keep `registry` alive.
+ MOZ_KnownLive(registry)->RemoveHighlightSelection(*this);
+ }
+ }
+}
+
+bool Highlight::Delete(AbstractRange& aRange, ErrorResult& aRv) {
+ if (Highlight_Binding::SetlikeHelpers::Delete(this, aRange, aRv)) {
+ mRanges.RemoveElement(&aRange);
+ AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__,
+ mHighlightRegistries.Count());
+
+ for (const RefPtr<HighlightRegistry>& registry :
+ mHighlightRegistries.Keys()) {
+ auto frameSelection = registry->GetFrameSelection();
+ selectionBatcher.AddFrameSelection(frameSelection);
+ // since this is run in a context guarded by a selection batcher,
+ // no strong reference is needed to keep `registry` alive.
+ MOZ_KnownLive(registry)->MaybeRemoveRangeFromHighlightSelection(aRange,
+ *this);
+ }
+ return true;
+ }
+ return false;
+}
+
+JSObject* Highlight::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Highlight_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Highlight.h b/dom/base/Highlight.h
new file mode 100644
index 0000000000..53c9f7cb7d
--- /dev/null
+++ b/dom/base/Highlight.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Highlight_h
+#define mozilla_dom_Highlight_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/HighlightBinding.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsTHashSet.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsFrameSelection;
+class nsPIDOMWindowInner;
+namespace mozilla {
+class ErrorResult;
+}
+
+namespace mozilla::dom {
+class AbstractRange;
+class Document;
+class HighlightRegistry;
+class Selection;
+
+/**
+ * @brief Representation of a custom `Highlight`.
+ *
+ * A `Highlight` is defined in JS as a collection of `AbstractRange`s.
+ * Furthermore, a custom highlight contains the highlight type and priority.
+ *
+ * A highlight is added to a document using the `HighlightRegistry` interface.
+ * A highlight can be added to a document using different names as well as to
+ * multiple `HighlightRegistries`.
+ * To propagate runtime changes of the highlight to its registries, an
+ * observer pattern is implemented.
+ *
+ * The spec defines this class as a `setlike`. To allow access and iteration
+ * of the setlike contents from C++, the insertion and deletion operations are
+ * overridden and the Ranges are also stored internally in an Array.
+ *
+ * @see https://drafts.csswg.org/css-highlight-api-1/#creation
+ */
+class Highlight final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Highlight)
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT Highlight(
+ const Sequence<OwningNonNull<AbstractRange>>& aInitialRanges,
+ nsPIDOMWindowInner* aWindow, ErrorResult& aRv);
+ ~Highlight() = default;
+
+ public:
+ /**
+ * @brief Adds `this` to `aHighlightRegistry`.
+ *
+ * Highlights must know of all registry objects which contain them, so that
+ * the registries can be notified when a property of the Highlight changes.
+ *
+ * Since a Highlight can be part of a registry using different names,
+ * the name has to be provided as well.
+ */
+ void AddToHighlightRegistry(HighlightRegistry& aHighlightRegistry,
+ const nsAtom& aHighlightName);
+
+ /**
+ * @brief Removes `this` from `aHighlightRegistry`.
+ */
+ void RemoveFromHighlightRegistry(HighlightRegistry& aHighlightRegistry,
+ const nsAtom& aHighlightName);
+
+ /**
+ * @brief Creates a Highlight Selection using the given ranges.
+ */
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Selection> CreateHighlightSelection(
+ const nsAtom* aHighlightName, nsFrameSelection* aFrameSelection) const;
+
+ // WebIDL interface
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<Highlight> Constructor(
+ const GlobalObject& aGlobal,
+ const Sequence<OwningNonNull<AbstractRange>>& aInitialRanges,
+ ErrorResult& aRv);
+
+ /**
+ * @brief Priority of this highlight.
+ *
+ * Priority is used to stack overlapping highlights.
+ */
+ int32_t Priority() const { return mPriority; }
+
+ /**
+ * @brief Set the priority of this Highlight.
+ *
+ * Priority is used to stack overlapping highlights.
+ */
+ void SetPriority(int32_t aPriority) { mPriority = aPriority; }
+
+ /**
+ * @brief The HighlightType of this Highlight (Highlight, Spelling Error,
+ * Grammar Error)
+ */
+ HighlightType Type() const { return mHighlightType; }
+
+ /**
+ * @brief Sets the HighlightType (Highlight, Spelling Error, Grammar Error)
+ */
+ void SetType(HighlightType aHighlightType) {
+ mHighlightType = aHighlightType;
+ }
+
+ /**
+ * @brief Adds a `Range` to this highlight.
+ *
+ * This adds `aRange` both to the setlike data storage and the internal one
+ * needed for iteration, if it is not yet present.
+ *
+ * Also notifies all `HighlightRegistry` instances.
+ */
+ MOZ_CAN_RUN_SCRIPT void Add(AbstractRange& aRange, ErrorResult& aRv);
+
+ /**
+ * @brief Removes all ranges from this highlight.
+ *
+ * This removes all highlights from the setlike data structure as well as from
+ * the internal one.
+ *
+ * Also notifies all `HighlightRegistry` instances.
+ */
+ MOZ_CAN_RUN_SCRIPT void Clear(ErrorResult& aRv);
+
+ /**
+ * @brief Removes `aRange` from this highlight.
+ *
+ * This removes `aRange` from the setlike data structure as well as from the
+ * internal one.
+ *
+ * Also notifies all `HighlightRegistry` instances.
+ *
+ * @return As per spec, returns true if the range was deleted.
+ */
+ MOZ_CAN_RUN_SCRIPT bool Delete(AbstractRange& aRange, ErrorResult& aRv);
+
+ private:
+ RefPtr<nsPIDOMWindowInner> mWindow;
+
+ /**
+ * All Range objects contained in this highlight.
+ */
+ nsTArray<RefPtr<AbstractRange>> mRanges;
+
+ /**
+ * Type of this highlight.
+ * @see HighlightType
+ */
+ HighlightType mHighlightType{HighlightType::Highlight};
+
+ /**
+ * Priority of this highlight.
+ *
+ * If highlights are overlapping, the priority can
+ * be used to prioritize. If the priorities of all
+ * Highlights involved are equal, the highlights are
+ * stacked in order of ther insertion into the
+ * `HighlightRegistry`.
+ */
+ int32_t mPriority{0};
+
+ /**
+ * All highlight registries that contain this Highlight.
+ *
+ * A highlight can be included in several registries
+ * using several names.
+ *
+ * Note: Storing `HighlightRegistry` as raw pointer is safe here
+ * because it unregisters itself from `this` when it is destroyed/CC'd
+ */
+ nsTHashMap<nsPtrHashKey<HighlightRegistry>,
+ nsTHashSet<nsRefPtrHashKey<const nsAtom>>>
+ mHighlightRegistries;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Highlight_h
diff --git a/dom/base/HighlightRegistry.cpp b/dom/base/HighlightRegistry.cpp
new file mode 100644
index 0000000000..c6c37d19c4
--- /dev/null
+++ b/dom/base/HighlightRegistry.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HighlightRegistry.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/CompactPair.h"
+
+#include "Document.h"
+#include "Highlight.h"
+#include "mozilla/dom/HighlightBinding.h"
+#include "PresShell.h"
+
+#include "nsAtom.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsFrameSelection.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(HighlightRegistry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HighlightRegistry)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ for (auto const& iter : tmp->mHighlightsOrdered) {
+ iter.second()->RemoveFromHighlightRegistry(*tmp, *iter.first());
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightsOrdered)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HighlightRegistry)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ for (size_t i = 0; i < tmp->mHighlightsOrdered.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightsOrdered[i].second())
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HighlightRegistry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HighlightRegistry)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HighlightRegistry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+HighlightRegistry::HighlightRegistry(Document* aDocument)
+ : mDocument(aDocument) {}
+
+HighlightRegistry::~HighlightRegistry() {
+ for (auto const& iter : mHighlightsOrdered) {
+ iter.second()->RemoveFromHighlightRegistry(*this, *iter.first());
+ }
+}
+
+JSObject* HighlightRegistry::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return HighlightRegistry_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void HighlightRegistry::MaybeAddRangeToHighlightSelection(
+ AbstractRange& aRange, Highlight& aHighlight) {
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (!frameSelection) {
+ return;
+ }
+ MOZ_ASSERT(frameSelection->GetPresShell());
+ if (!frameSelection->GetPresShell()->GetDocument() ||
+ frameSelection->GetPresShell()->GetDocument() !=
+ aRange.GetComposedDocOfContainers()) {
+ // ranges that belong to a different document must not be added.
+ return;
+ }
+ for (auto const& iter : mHighlightsOrdered) {
+ if (iter.second() != &aHighlight) {
+ continue;
+ }
+
+ const RefPtr<const nsAtom> highlightName = iter.first();
+ frameSelection->AddHighlightSelectionRange(highlightName, aHighlight,
+ aRange);
+ }
+}
+
+void HighlightRegistry::MaybeRemoveRangeFromHighlightSelection(
+ AbstractRange& aRange, Highlight& aHighlight) {
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (!frameSelection) {
+ return;
+ }
+ MOZ_ASSERT(frameSelection->GetPresShell());
+
+ for (auto const& iter : mHighlightsOrdered) {
+ if (iter.second() != &aHighlight) {
+ continue;
+ }
+
+ const RefPtr<const nsAtom> highlightName = iter.first();
+ frameSelection->RemoveHighlightSelectionRange(highlightName, aRange);
+ }
+}
+
+void HighlightRegistry::RemoveHighlightSelection(Highlight& aHighlight) {
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (!frameSelection) {
+ return;
+ }
+ for (auto const& iter : mHighlightsOrdered) {
+ if (iter.second() != &aHighlight) {
+ continue;
+ }
+
+ const RefPtr<const nsAtom> highlightName = iter.first();
+ frameSelection->RemoveHighlightSelection(highlightName);
+ }
+}
+
+void HighlightRegistry::AddHighlightSelectionsToFrameSelection() {
+ if (mHighlightsOrdered.IsEmpty()) {
+ return;
+ }
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (!frameSelection) {
+ return;
+ }
+ for (auto const& iter : mHighlightsOrdered) {
+ RefPtr<const nsAtom> highlightName = iter.first();
+ RefPtr<Highlight> highlight = iter.second();
+ frameSelection->AddHighlightSelection(highlightName, *highlight);
+ }
+}
+
+void HighlightRegistry::Set(const nsAString& aKey, Highlight& aValue,
+ ErrorResult& aRv) {
+ HighlightRegistry_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ RefPtr<nsAtom> highlightNameAtom = NS_AtomizeMainThread(aKey);
+ auto foundIter =
+ std::find_if(mHighlightsOrdered.begin(), mHighlightsOrdered.end(),
+ [&highlightNameAtom](auto const& aElm) {
+ return aElm.first() == highlightNameAtom;
+ });
+ if (foundIter != mHighlightsOrdered.end()) {
+ foundIter->second()->RemoveFromHighlightRegistry(*this, *highlightNameAtom);
+ if (frameSelection) {
+ frameSelection->RemoveHighlightSelection(highlightNameAtom);
+ }
+ foundIter->second() = &aValue;
+ } else {
+ mHighlightsOrdered.AppendElement(
+ CompactPair<RefPtr<const nsAtom>, RefPtr<Highlight>>(highlightNameAtom,
+ &aValue));
+ }
+ aValue.AddToHighlightRegistry(*this, *highlightNameAtom);
+ if (frameSelection) {
+ frameSelection->AddHighlightSelection(highlightNameAtom, aValue);
+ }
+}
+
+void HighlightRegistry::Clear(ErrorResult& aRv) {
+ HighlightRegistry_Binding::MaplikeHelpers::Clear(this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ auto frameSelection = GetFrameSelection();
+ AutoFrameSelectionBatcher batcher(__FUNCTION__);
+ batcher.AddFrameSelection(frameSelection);
+ for (auto const& iter : mHighlightsOrdered) {
+ const RefPtr<const nsAtom>& highlightName = iter.first();
+ const RefPtr<Highlight>& highlight = iter.second();
+ highlight->RemoveFromHighlightRegistry(*this, *highlightName);
+ if (frameSelection) {
+ // The selection batcher makes sure that no script is run in this call.
+ // However, `nsFrameSelection::RemoveHighlightSelection` is marked
+ // `MOZ_CAN_RUN_SCRIPT`, therefore `MOZ_KnownLive` is needed regardless.
+ frameSelection->RemoveHighlightSelection(MOZ_KnownLive(highlightName));
+ }
+ }
+
+ mHighlightsOrdered.Clear();
+}
+
+bool HighlightRegistry::Delete(const nsAString& aKey, ErrorResult& aRv) {
+ if (!HighlightRegistry_Binding::MaplikeHelpers::Delete(this, aKey, aRv)) {
+ return false;
+ }
+ RefPtr<nsAtom> highlightNameAtom = NS_AtomizeMainThread(aKey);
+ auto foundIter =
+ std::find_if(mHighlightsOrdered.cbegin(), mHighlightsOrdered.cend(),
+ [&highlightNameAtom](auto const& aElm) {
+ return aElm.first() == highlightNameAtom;
+ });
+ MOZ_ASSERT(foundIter != mHighlightsOrdered.cend(),
+ "HighlightRegistry: maplike and internal data are out of sync!");
+
+ RefPtr<Highlight> highlight = foundIter->second();
+ mHighlightsOrdered.RemoveElementAt(foundIter);
+
+ if (auto frameSelection = GetFrameSelection()) {
+ frameSelection->RemoveHighlightSelection(highlightNameAtom);
+ }
+ highlight->RemoveFromHighlightRegistry(*this, *highlightNameAtom);
+ return true;
+}
+
+RefPtr<nsFrameSelection> HighlightRegistry::GetFrameSelection() {
+ return RefPtr<nsFrameSelection>(
+ mDocument->GetPresShell() ? mDocument->GetPresShell()->FrameSelection()
+ : nullptr);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/HighlightRegistry.h b/dom/base/HighlightRegistry.h
new file mode 100644
index 0000000000..892efe9e17
--- /dev/null
+++ b/dom/base/HighlightRegistry.h
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HighlightRegistry_h
+#define mozilla_dom_HighlightRegistry_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CompactPair.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsTHashMap.h"
+#include "nsHashtablesFwd.h"
+#include "nsWrapperCache.h"
+
+class nsFrameSelection;
+
+namespace mozilla {
+class ErrorResult;
+}
+namespace mozilla::dom {
+
+class AbstractRange;
+class Document;
+class Highlight;
+
+/**
+ * @brief HighlightRegistry manages all `Highlight`s available to a `Document`.
+ *
+ * This class is exposed via `HighlightRegistry.webidl` and used to
+ * add or remove `Highlight` instances to a document and binding it
+ * to a highlight name.
+ *
+ * The HighlightRegistry idl interface defines this class to be a `maplike`.
+ * To be able to access the members of the maplike without proper support
+ * for iteration from C++, the insertion and deletion operations are
+ * overridden and the data is also held inside of this class.
+ *
+ * @see https://drafts.csswg.org/css-highlight-api-1/#registration
+ */
+class HighlightRegistry final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(HighlightRegistry)
+
+ public:
+ explicit HighlightRegistry(Document* aDocument);
+
+ protected:
+ ~HighlightRegistry();
+
+ public:
+ /**
+ * @brief Adds selections for all highlights to the `FrameSelection`.
+ *
+ * This method is called if highlights are added to the registry before
+ * a `FrameSelection` is available.
+ */
+ MOZ_CAN_RUN_SCRIPT void AddHighlightSelectionsToFrameSelection();
+
+ /**
+ * @brief Adds the Range to the Highlight Selection if it belongs to the same
+ * Document.
+ *
+ * If no Highlight Selection for this highlight exists, it will be created.
+ * This may occur when a Highlight is added to the Registry after the
+ * nsFrameSelection is created.
+ */
+ MOZ_CAN_RUN_SCRIPT void MaybeAddRangeToHighlightSelection(
+ AbstractRange& aRange, Highlight& aHighlight);
+
+ /**
+ * @brief Removes the Range from the Highlight Selection if it belongs to the
+ * same Document.
+ *
+ * @note If the last range of a highlight selection is removed, the selection
+ * itself is *not* removed.
+ */
+ MOZ_CAN_RUN_SCRIPT void MaybeRemoveRangeFromHighlightSelection(
+ AbstractRange& aRange, Highlight& aHighlight);
+
+ /**
+ * @brief Removes the highlight selections associated with the highlight.
+ *
+ * This method is called when the Highlight is cleared
+ * (i.e., all Ranges are removed).
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelection(Highlight& aHighlight);
+
+ // WebIDL interface
+
+ Document* GetParentObject() const { return mDocument; };
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * @brief Adds a new `Highlight` to `this` using `aKey` as highlight name.
+ *
+ * Highlight instances are ordered by insertion.
+ *
+ * This call registers `this` and `aHighlightName` in the highlight given in
+ * `aValue`.
+ *
+ * If a `FrameSelection` is present, a highlight selection is created.
+ */
+ MOZ_CAN_RUN_SCRIPT void Set(const nsAString& aKey, Highlight& aValue,
+ ErrorResult& aRv);
+
+ /**
+ * @brief Removes all highlights from this registry.
+ *
+ * If a `FrameSelection` is present, all highlight selections are removed.
+ */
+ MOZ_CAN_RUN_SCRIPT void Clear(ErrorResult& aRv);
+
+ /**
+ * @brief Removes the highlight named `aKey` from the registry.
+ *
+ * This call removes the combination of `this` and `aKey` from the highlight.
+ * If a `FrameSelection` is present, the highlight selection is removed.
+ *
+ * @return true if `aKey` existed and was deleted.
+ */
+ MOZ_CAN_RUN_SCRIPT bool Delete(const nsAString& aKey, ErrorResult& aRv);
+
+ /**
+ * @brief Get the `FrameSelection` object if available. Can return nullptr.
+ */
+ RefPtr<nsFrameSelection> GetFrameSelection();
+
+ private:
+ /**
+ * Parent document.
+ */
+ RefPtr<Document> mDocument;
+
+ /**
+ * Highlight instances are stored as array of name-value tuples
+ * instead of a hashmap in order to preserve the insertion order.
+ *
+ * This is done
+ * a) to keep the order in sync with the underlying
+ * data structure of the `maplike` interface and
+ * b) because the insertion order defines the stacking order of
+ * of highlights that have the same priority.
+ */
+ nsTArray<CompactPair<RefPtr<const nsAtom>, RefPtr<Highlight>>>
+ mHighlightsOrdered;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_HighlightRegistry_h
diff --git a/dom/base/IDTracker.cpp b/dom/base/IDTracker.cpp
new file mode 100644
index 0000000000..4b36c0387e
--- /dev/null
+++ b/dom/base/IDTracker.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IDTracker.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsAtom.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsIReferrerInfo.h"
+#include "nsEscape.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::dom {
+
+static Element* LookupElement(DocumentOrShadowRoot& aDocOrShadow,
+ const nsAString& aRef, bool aReferenceImage) {
+ if (aReferenceImage) {
+ return aDocOrShadow.LookupImageElement(aRef);
+ }
+ return aDocOrShadow.GetElementById(aRef);
+}
+
+static DocumentOrShadowRoot* FindTreeToWatch(nsIContent& aContent,
+ const nsAString& aID,
+ bool aReferenceImage) {
+ ShadowRoot* shadow = aContent.GetContainingShadow();
+
+ // We allow looking outside an <svg:use> shadow tree for backwards compat.
+ while (shadow && shadow->Host()->IsSVGElement(nsGkAtoms::use)) {
+ // <svg:use> shadow trees are immutable, so we can just early-out if we find
+ // our relevant element instead of having to support watching multiple
+ // trees.
+ if (LookupElement(*shadow, aID, aReferenceImage)) {
+ return shadow;
+ }
+ shadow = shadow->Host()->GetContainingShadow();
+ }
+
+ if (shadow) {
+ return shadow;
+ }
+
+ return aContent.OwnerDoc();
+}
+
+IDTracker::IDTracker() = default;
+
+IDTracker::~IDTracker() { Unlink(); }
+
+void IDTracker::ResetToURIFragmentID(nsIContent* aFromContent, nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ bool aWatch, bool aReferenceImage) {
+ MOZ_ASSERT(aFromContent,
+ "ResetToURIFragmentID() expects non-null content pointer");
+
+ Unlink();
+
+ if (!aURI) return;
+
+ nsAutoCString refPart;
+ aURI->GetRef(refPart);
+ // Unescape %-escapes in the reference. The result will be in the
+ // document charset, hopefully...
+ NS_UnescapeURL(refPart);
+
+ // Get the thing to observe changes to.
+ Document* doc = aFromContent->OwnerDoc();
+ auto encoding = doc->GetDocumentCharacterSet();
+
+ nsAutoString ref;
+ nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
+ if (NS_FAILED(rv) || ref.IsEmpty()) {
+ return;
+ }
+
+ if (aFromContent->IsInNativeAnonymousSubtree()) {
+ // This happens, for example, if aFromContent is part of the content
+ // inserted by a call to Document::InsertAnonymousContent, which we
+ // also want to handle. (It also happens for other native anonymous content
+ // etc.)
+ Element* anonRoot =
+ doc->GetAnonRootIfInAnonymousContentContainer(aFromContent);
+ if (anonRoot) {
+ mElement = nsContentUtils::MatchElementId(anonRoot, ref);
+ // We don't have watching working yet for anonymous content, so bail out
+ // here.
+ return;
+ }
+ }
+
+ bool isEqualExceptRef;
+ rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef);
+ DocumentOrShadowRoot* docOrShadow;
+ if (NS_FAILED(rv) || !isEqualExceptRef) {
+ RefPtr<Document::ExternalResourceLoad> load;
+ doc = doc->RequestExternalResource(aURI, aReferrerInfo, aFromContent,
+ getter_AddRefs(load));
+ docOrShadow = doc;
+ if (!doc) {
+ if (!load || !aWatch) {
+ // Nothing will ever happen here
+ return;
+ }
+
+ DocumentLoadNotification* observer =
+ new DocumentLoadNotification(this, ref);
+ mPendingNotification = observer;
+ load->AddObserver(observer);
+ // Keep going so we set up our watching stuff a bit
+ }
+ } else {
+ docOrShadow = FindTreeToWatch(*aFromContent, ref, aReferenceImage);
+ }
+
+ if (aWatch) {
+ mWatchID = NS_Atomize(ref);
+ }
+
+ mReferencingImage = aReferenceImage;
+ HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, ref);
+}
+
+void IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
+ MOZ_ASSERT(aID);
+
+ if (aWatch) {
+ mWatchID = aID;
+ }
+
+ mReferencingImage = false;
+
+ nsDependentAtomString str(aID);
+ DocumentOrShadowRoot* docOrShadow =
+ FindTreeToWatch(aFrom, str, /* aReferenceImage = */ false);
+ HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, str);
+}
+
+void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot* aDocOrShadow,
+ bool aWatch, const nsString& aRef) {
+ if (aWatch) {
+ mWatchDocumentOrShadowRoot = nullptr;
+ if (aDocOrShadow) {
+ mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
+ mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ return;
+ }
+
+ if (!aDocOrShadow) {
+ return;
+ }
+
+ if (Element* e = LookupElement(*aDocOrShadow, aRef, mReferencingImage)) {
+ mElement = e;
+ }
+}
+
+void IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
+ aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
+ aCB->NoteXPCOMChild(mElement);
+}
+
+void IDTracker::Unlink() {
+ if (mWatchID) {
+ if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
+ docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ }
+ if (mPendingNotification) {
+ mPendingNotification->Clear();
+ mPendingNotification = nullptr;
+ }
+ mWatchDocumentOrShadowRoot = nullptr;
+ mWatchID = nullptr;
+ mElement = nullptr;
+ mReferencingImage = false;
+}
+
+void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
+
+bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
+ void* aData) {
+ IDTracker* p = static_cast<IDTracker*>(aData);
+ if (p->mPendingNotification) {
+ p->mPendingNotification->SetTo(aNewElement);
+ } else {
+ NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!");
+ ChangeNotification* watcher =
+ new ChangeNotification(p, aOldElement, aNewElement);
+ p->mPendingNotification = watcher;
+ nsContentUtils::AddScriptRunner(watcher);
+ }
+ bool keepTracking = p->IsPersistent();
+ if (!keepTracking) {
+ p->mWatchDocumentOrShadowRoot = nullptr;
+ p->mWatchID = nullptr;
+ }
+ return keepTracking;
+}
+
+IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
+ Element* aFrom, Element* aTo)
+ : mozilla::Runnable("IDTracker::ChangeNotification"),
+ Notification(aTarget),
+ mFrom(aFrom),
+ mTo(aTo) {}
+
+IDTracker::ChangeNotification::~ChangeNotification() = default;
+
+void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
+
+void IDTracker::ChangeNotification::Clear() {
+ Notification::Clear();
+ mFrom = nullptr;
+ mTo = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
+NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
+
+NS_IMETHODIMP
+IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
+ "Unexpected topic");
+ if (mTarget) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
+ mTarget->mPendingNotification = nullptr;
+ NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
+ // If we got here, that means we had Reset*() called with
+ // aWatch == true. So keep watching if IsPersistent().
+ mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
+ mTarget->ElementChanged(nullptr, mTarget->mElement);
+ }
+ return NS_OK;
+}
+
+DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
+ if (!mWatchDocumentOrShadowRoot) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
+ mWatchDocumentOrShadowRoot->IsShadowRoot());
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
+ return shadow;
+ }
+ return mWatchDocumentOrShadowRoot->AsDocument();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/IDTracker.h b/dom/base/IDTracker.h
new file mode 100644
index 0000000000..471c9c1ec6
--- /dev/null
+++ b/dom/base/IDTracker.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_IDTracker_h_
+#define mozilla_dom_IDTracker_h_
+
+#include "mozilla/Attributes.h"
+#include "nsIObserver.h"
+#include "nsThreadUtils.h"
+
+class nsAtom;
+class nsIContent;
+class nsINode;
+class nsIURI;
+class nsIReferrerInfo;
+
+namespace mozilla::dom {
+
+class Document;
+class DocumentOrShadowRoot;
+class Element;
+
+/**
+ * Class to track what element is referenced by a given ID.
+ *
+ * To use it, call one of the Reset methods to set it up to watch a given ID.
+ * Call get() anytime to determine the referenced element (which may be null if
+ * the element isn't found). When the element changes, ElementChanged
+ * will be called, so subclass this class if you want to receive that
+ * notification. ElementChanged runs at safe-for-script time, i.e. outside
+ * of the content update. Call Unlink() if you want to stop watching
+ * for changes (get() will then return null).
+ *
+ * By default this is a single-shot tracker --- i.e., when ElementChanged
+ * fires, we will automatically stop tracking. get() will continue to return
+ * the changed-to element.
+ * Override IsPersistent to return true if you want to keep tracking after
+ * the first change.
+ */
+class IDTracker {
+ public:
+ using Element = mozilla::dom::Element;
+
+ IDTracker();
+
+ ~IDTracker();
+
+ /**
+ * Find which element, if any, is referenced.
+ */
+ Element* get() { return mElement; }
+
+ /**
+ * Set up the reference. This can be called multiple times to
+ * change which reference is being tracked, but these changes
+ * do not trigger ElementChanged.
+ * @param aFrom the source element for context
+ * @param aURI the URI containing a hash-reference to the element
+ * @param aReferrerInfo the referrerInfo for loading external resource
+ * @param aWatch if false, then we do not set up the notifications to track
+ * changes, so ElementChanged won't fire and get() will always return the same
+ * value, the current element for the ID.
+ * @param aReferenceImage whether the ID references image elements which are
+ * subject to the document's mozSetImageElement overriding mechanism.
+ */
+ void ResetToURIFragmentID(nsIContent* aFrom, nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo, bool aWatch = true,
+ bool aReferenceImage = false);
+
+ /**
+ * A variation on ResetToURIFragmentID() to set up a reference that consists
+ * of the ID of an element in the same document as aFrom.
+ * @param aFrom the source element for context
+ * @param aID the ID of the element
+ * @param aWatch if false, then we do not set up the notifications to track
+ * changes, so ElementChanged won't fire and get() will always return the same
+ * value, the current element for the ID.
+ */
+ void ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch = true);
+
+ /**
+ * Clears the reference. ElementChanged is not triggered. get() will return
+ * null.
+ */
+ void Unlink();
+
+ void Traverse(nsCycleCollectionTraversalCallback* aCB);
+
+ protected:
+ /**
+ * Override this to be notified of element changes. Don't forget
+ * to call this superclass method to change mElement. This is called
+ * at script-runnable time.
+ */
+ virtual void ElementChanged(Element* aFrom, Element* aTo);
+
+ /**
+ * Override this to convert from a single-shot notification to
+ * a persistent notification.
+ */
+ virtual bool IsPersistent() { return false; }
+
+ /**
+ * Set ourselves up with our new document. Note that aDocument might be
+ * null. Either aWatch must be false or aRef must be empty.
+ */
+ void HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot*, bool aWatch,
+ const nsString& aRef);
+
+ private:
+ static bool Observe(Element* aOldElement, Element* aNewElement, void* aData);
+
+ class Notification : public nsISupports {
+ public:
+ virtual void SetTo(Element* aTo) = 0;
+ virtual void Clear() { mTarget = nullptr; }
+ virtual ~Notification() = default;
+
+ protected:
+ explicit Notification(IDTracker* aTarget) : mTarget(aTarget) {
+ MOZ_ASSERT(aTarget, "Must have a target");
+ }
+ IDTracker* mTarget;
+ };
+
+ class ChangeNotification : public mozilla::Runnable, public Notification {
+ public:
+ ChangeNotification(IDTracker* aTarget, Element* aFrom, Element* aTo);
+
+ // We need to actually declare all of nsISupports, because
+ // Notification inherits from it but doesn't declare it.
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD Run() override {
+ if (mTarget) {
+ mTarget->mPendingNotification = nullptr;
+ mTarget->ElementChanged(mFrom, mTo);
+ }
+ return NS_OK;
+ }
+ void SetTo(Element* aTo) override;
+ void Clear() override;
+
+ protected:
+ virtual ~ChangeNotification();
+
+ RefPtr<Element> mFrom;
+ RefPtr<Element> mTo;
+ };
+ friend class ChangeNotification;
+
+ class DocumentLoadNotification : public Notification, public nsIObserver {
+ public:
+ DocumentLoadNotification(IDTracker* aTarget, const nsString& aRef)
+ : Notification(aTarget) {
+ if (!mTarget->IsPersistent()) {
+ mRef = aRef;
+ }
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ private:
+ virtual ~DocumentLoadNotification() = default;
+
+ virtual void SetTo(Element* aTo) override {}
+
+ nsString mRef;
+ };
+ friend class DocumentLoadNotification;
+
+ DocumentOrShadowRoot* GetWatchDocOrShadowRoot() const;
+
+ RefPtr<nsAtom> mWatchID;
+ nsCOMPtr<nsINode>
+ mWatchDocumentOrShadowRoot; // Always a `DocumentOrShadowRoot`.
+ RefPtr<Element> mElement;
+ RefPtr<Notification> mPendingNotification;
+ bool mReferencingImage = false;
+};
+
+inline void ImplCycleCollectionUnlink(IDTracker& aField) { aField.Unlink(); }
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, IDTracker& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ aField.Traverse(&aCallback);
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_IDTracker_h_ */
diff --git a/dom/base/IdentifierMapEntry.h b/dom/base/IdentifierMapEntry.h
new file mode 100644
index 0000000000..f19cdff172
--- /dev/null
+++ b/dom/base/IdentifierMapEntry.h
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Entry for the Document or ShadowRoot's identifier map.
+ */
+
+#ifndef mozilla_IdentifierMapEntry_h
+#define mozilla_IdentifierMapEntry_h
+
+#include <utility>
+
+#include "PLDHashTable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/TreeOrderedArray.h"
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsContentList.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+class nsIContent;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+
+/**
+ * Right now our identifier map entries contain information for 'name'
+ * and 'id' mappings of a given string. This is so that
+ * nsHTMLDocument::ResolveName only has to do one hash lookup instead
+ * of two. It's not clear whether this still matters for performance.
+ *
+ * We also store the document.all result list here. This is mainly so that
+ * when all elements with the given ID are removed and we remove
+ * the ID's IdentifierMapEntry, the document.all result is released too.
+ * Perhaps the document.all results should have their own hashtable
+ * in nsHTMLDocument.
+ */
+class IdentifierMapEntry : public PLDHashEntryHdr {
+ typedef dom::Document Document;
+ typedef dom::Element Element;
+
+ /**
+ * @see Document::IDTargetObserver, this is just here to avoid include hell.
+ */
+ typedef bool (*IDTargetObserver)(Element* aOldElement, Element* aNewelement,
+ void* aData);
+
+ public:
+ // We use DependentAtomOrString as our external key interface. This allows
+ // consumers to use an nsAString, for example, without forcing a copy.
+ struct DependentAtomOrString final {
+ MOZ_IMPLICIT DependentAtomOrString(nsAtom* aAtom)
+ : mAtom(aAtom), mString(nullptr) {}
+ MOZ_IMPLICIT DependentAtomOrString(const nsAString& aString)
+ : mAtom(nullptr), mString(&aString) {}
+ DependentAtomOrString(const DependentAtomOrString& aOther) = default;
+
+ nsAtom* mAtom;
+ const nsAString* mString;
+ };
+
+ typedef const DependentAtomOrString& KeyType;
+ typedef const DependentAtomOrString* KeyTypePointer;
+
+ explicit IdentifierMapEntry(const DependentAtomOrString* aKey);
+ IdentifierMapEntry(IdentifierMapEntry&& aOther) = default;
+ ~IdentifierMapEntry() = default;
+
+ nsString GetKeyAsString() const {
+ if (mKey.mAtom) {
+ return nsAtomString(mKey.mAtom);
+ }
+
+ return mKey.mString;
+ }
+
+ bool KeyEquals(const KeyTypePointer aOtherKey) const {
+ if (mKey.mAtom) {
+ if (aOtherKey->mAtom) {
+ return mKey.mAtom == aOtherKey->mAtom;
+ }
+
+ return mKey.mAtom->Equals(*aOtherKey->mString);
+ }
+
+ if (aOtherKey->mAtom) {
+ return aOtherKey->mAtom->Equals(mKey.mString);
+ }
+
+ return mKey.mString.Equals(*aOtherKey->mString);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ return aKey->mAtom ? aKey->mAtom->hash() : HashString(*aKey->mString);
+ }
+
+ enum { ALLOW_MEMMOVE = false };
+
+ void AddNameElement(nsINode* aDocument, Element* aElement);
+ void RemoveNameElement(Element* aElement);
+ bool IsEmpty();
+ nsBaseContentList* GetNameContentList() { return mNameContentList; }
+ bool HasNameElement() const;
+
+ /**
+ * Returns the element if we know the element associated with this
+ * id. Otherwise returns null.
+ */
+ Element* GetIdElement() const { return mIdContentList->SafeElementAt(0); }
+
+ /**
+ * Returns the list of all elements associated with this id.
+ */
+ const nsTArray<Element*>& GetIdElements() const { return mIdContentList; }
+
+ /**
+ * If this entry has a non-null image element set (using SetImageElement),
+ * the image element will be returned, otherwise the same as GetIdElement().
+ */
+ Element* GetImageIdElement() {
+ return mImageElement ? mImageElement.get() : GetIdElement();
+ }
+
+ /**
+ * This can fire ID change callbacks.
+ */
+ void AddIdElement(Element* aElement);
+ /**
+ * This can fire ID change callbacks.
+ */
+ void RemoveIdElement(Element* aElement);
+ /**
+ * Set the image element override for this ID. This will be returned by
+ * GetIdElement(true) if non-null.
+ */
+ void SetImageElement(Element* aElement);
+ bool HasIdElementExposedAsHTMLDocumentProperty() const;
+
+ bool HasContentChangeCallback() { return mChangeCallbacks != nullptr; }
+ void AddContentChangeCallback(IDTargetObserver aCallback, void* aData,
+ bool aForImage);
+ void RemoveContentChangeCallback(IDTargetObserver aCallback, void* aData,
+ bool aForImage);
+
+ /**
+ * Remove all elements and notify change listeners.
+ */
+ void ClearAndNotify();
+
+ void Traverse(nsCycleCollectionTraversalCallback* aCallback);
+
+ struct ChangeCallback {
+ IDTargetObserver mCallback;
+ void* mData;
+ bool mForImage;
+ };
+
+ struct ChangeCallbackEntry : public PLDHashEntryHdr {
+ typedef const ChangeCallback KeyType;
+ typedef const ChangeCallback* KeyTypePointer;
+
+ explicit ChangeCallbackEntry(const ChangeCallback* aKey) : mKey(*aKey) {}
+ ChangeCallbackEntry(ChangeCallbackEntry&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return aKey->mCallback == mKey.mCallback && aKey->mData == mKey.mData &&
+ aKey->mForImage == mKey.mForImage;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return HashGeneric(aKey->mCallback, aKey->mData);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ ChangeCallback mKey;
+ };
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ // We use an OwningAtomOrString as our internal key storage. It needs to own
+ // the key string, whether in atom or string form.
+ struct OwningAtomOrString final {
+ OwningAtomOrString(const OwningAtomOrString& aOther) = delete;
+ OwningAtomOrString(OwningAtomOrString&& aOther) = default;
+
+ explicit OwningAtomOrString(const DependentAtomOrString& aOther)
+ // aOther may have a null mString, so jump through a bit of a hoop in
+ // that case. I wish there were a way to just default-initialize
+ // mString in that situation... We could also make mString not const
+ // and only assign to it if aOther.mString is not null, but having it be
+ // const is nice.
+ : mAtom(aOther.mAtom),
+ mString(aOther.mString ? *aOther.mString : u""_ns) {}
+
+ RefPtr<nsAtom> mAtom;
+ nsString mString;
+ };
+
+ IdentifierMapEntry(const IdentifierMapEntry& aOther) = delete;
+ IdentifierMapEntry& operator=(const IdentifierMapEntry& aOther) = delete;
+
+ void FireChangeCallbacks(Element* aOldElement, Element* aNewElement,
+ bool aImageOnly = false);
+
+ OwningAtomOrString mKey;
+ dom::TreeOrderedArray<Element> mIdContentList;
+ RefPtr<nsBaseContentList> mNameContentList;
+ UniquePtr<nsTHashtable<ChangeCallbackEntry> > mChangeCallbacks;
+ RefPtr<Element> mImageElement;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_IdentifierMapEntry_h
diff --git a/dom/base/IdleDeadline.cpp b/dom/base/IdleDeadline.cpp
new file mode 100644
index 0000000000..ac9cbdbe03
--- /dev/null
+++ b/dom/base/IdleDeadline.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/IdleDeadline.h"
+
+#include <algorithm>
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/IdleDeadlineBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(IdleDeadline, mWindow, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleDeadline)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleDeadline)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleDeadline)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+IdleDeadline::IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline)
+ : mWindow(aWindow), mDidTimeout(aDidTimeout), mDeadline(aDeadline) {
+ bool hasHadSHO;
+ mGlobal = aWindow->GetDoc()->GetScriptHandlingObject(hasHadSHO);
+}
+
+IdleDeadline::IdleDeadline(nsIGlobalObject* aGlobal, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline)
+ : mWindow(nullptr),
+ mGlobal(aGlobal),
+ mDidTimeout(aDidTimeout),
+ mDeadline(aDeadline) {}
+
+IdleDeadline::~IdleDeadline() = default;
+
+JSObject* IdleDeadline::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IdleDeadline_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp IdleDeadline::TimeRemaining() {
+ if (mDidTimeout) {
+ return 0.0;
+ }
+
+ if (mWindow) {
+ RefPtr<Performance> performance = mWindow->GetPerformance();
+ if (!performance) {
+ // If there is no performance object the window is partially torn
+ // down, so we can safely say that there is no time remaining.
+ return 0.0;
+ }
+
+ // The web API doesn't expect deadlines > 50ms, but conversion from the
+ // internal API may lead to some rounding errors.
+ return std::min(std::max(mDeadline - performance->Now(), 0.0), 50.0);
+ }
+
+ // If there's no window, we're in a system scope, and can just use
+ // a high-resolution TimeStamp::Now();
+ auto timestamp = TimeStamp::Now() - TimeStamp::ProcessCreation();
+ return std::max(mDeadline - timestamp.ToMilliseconds(), 0.0);
+}
+
+bool IdleDeadline::DidTimeout() const { return mDidTimeout; }
+
+} // namespace mozilla::dom
diff --git a/dom/base/IdleDeadline.h b/dom/base/IdleDeadline.h
new file mode 100644
index 0000000000..2ce06e489d
--- /dev/null
+++ b/dom/base/IdleDeadline.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_IdleDeadline_h
+#define mozilla_dom_IdleDeadline_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class IdleDeadline final : public nsISupports, public nsWrapperCache {
+ public:
+ IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline);
+
+ IdleDeadline(nsIGlobalObject* aGlobal, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline);
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMHighResTimeStamp TimeRemaining();
+ bool DidTimeout() const;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(IdleDeadline)
+
+ private:
+ ~IdleDeadline();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ const bool mDidTimeout;
+ const DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_IdleDeadline_h
diff --git a/dom/base/IdleRequest.cpp b/dom/base/IdleRequest.cpp
new file mode 100644
index 0000000000..bb16d516e9
--- /dev/null
+++ b/dom/base/IdleRequest.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IdleRequest.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+IdleRequest::IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle)
+ : mCallback(aCallback), mHandle(aHandle), mTimeoutHandle(Nothing()) {
+ MOZ_DIAGNOSTIC_ASSERT(mCallback);
+}
+
+IdleRequest::~IdleRequest() = default;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+ if (tmp->isInList()) {
+ tmp->remove();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void IdleRequest::SetTimeoutHandle(int32_t aHandle) {
+ mTimeoutHandle = Some(aHandle);
+}
+
+uint32_t IdleRequest::GetTimeoutHandle() const {
+ MOZ_DIAGNOSTIC_ASSERT(mTimeoutHandle.isSome());
+ return mTimeoutHandle.value();
+}
+
+void IdleRequest::IdleRun(nsPIDOMWindowInner* aWindow,
+ DOMHighResTimeStamp aDeadline, bool aDidTimeout) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(mCallback);
+
+ RefPtr<IdleDeadline> deadline =
+ new IdleDeadline(aWindow, aDidTimeout, aDeadline);
+ RefPtr<IdleRequestCallback> callback(std::move(mCallback));
+ MOZ_ASSERT(!mCallback);
+ callback->Call(*deadline, "requestIdleCallback handler");
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/IdleRequest.h b/dom/base/IdleRequest.h
new file mode 100644
index 0000000000..533284b7ef
--- /dev/null
+++ b/dom/base/IdleRequest.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_idlerequest_h
+#define mozilla_dom_idlerequest_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsICancelableRunnable.h"
+#include "nsString.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class IdleRequestCallback;
+
+class IdleRequest final : public LinkedListElement<RefPtr<IdleRequest>> {
+ public:
+ IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle);
+
+ MOZ_CAN_RUN_SCRIPT
+ void IdleRun(nsPIDOMWindowInner* aWindow, DOMHighResTimeStamp aDeadline,
+ bool aDidTimeout);
+
+ void SetTimeoutHandle(int32_t aHandle);
+ bool HasTimeout() const { return mTimeoutHandle.isSome(); }
+ uint32_t GetTimeoutHandle() const;
+
+ uint32_t Handle() const { return mHandle; }
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(IdleRequest)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(IdleRequest)
+ private:
+ ~IdleRequest();
+
+ RefPtr<IdleRequestCallback> mCallback;
+ const uint32_t mHandle;
+ mozilla::Maybe<int32_t> mTimeoutHandle;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_idlerequest_h
diff --git a/dom/base/IframeSandboxKeywordList.h b/dom/base/IframeSandboxKeywordList.h
new file mode 100644
index 0000000000..1ae81f621e
--- /dev/null
+++ b/dom/base/IframeSandboxKeywordList.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* NOTE: no include guard; this file is meant to maybe be included multiple
+ times. It has a list of the sandbox keywords we support, with their
+ corresponding sandbox flags. */
+
+// Each entry has the sandbox keyword as a string, the corresponding nsGkAtoms
+// atom name, and the corresponding sandbox flags.
+SANDBOX_KEYWORD("allow-same-origin", allowsameorigin, SANDBOXED_ORIGIN)
+SANDBOX_KEYWORD("allow-forms", allowforms, SANDBOXED_FORMS)
+SANDBOX_KEYWORD("allow-scripts", allowscripts,
+ SANDBOXED_SCRIPTS | SANDBOXED_AUTOMATIC_FEATURES)
+SANDBOX_KEYWORD("allow-top-navigation", allowtopnavigation,
+ SANDBOXED_TOPLEVEL_NAVIGATION)
+SANDBOX_KEYWORD("allow-top-navigation-by-user-activation",
+ allowtopnavigationbyuseractivation,
+ SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)
+SANDBOX_KEYWORD("allow-pointer-lock", allowpointerlock, SANDBOXED_POINTER_LOCK)
+SANDBOX_KEYWORD("allow-orientation-lock", alloworientationlock,
+ SANDBOXED_ORIENTATION_LOCK)
+SANDBOX_KEYWORD("allow-popups", allowpopups, SANDBOXED_AUXILIARY_NAVIGATION)
+SANDBOX_KEYWORD("allow-modals", allowmodals, SANDBOXED_MODALS)
+SANDBOX_KEYWORD("allow-popups-to-escape-sandbox", allowpopupstoescapesandbox,
+ SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS)
+SANDBOX_KEYWORD("allow-presentation", allowpresentation, SANDBOXED_PRESENTATION)
+SANDBOX_KEYWORD("allow-storage-access-by-user-activation",
+ allowstorageaccessbyuseractivatetion, SANDBOXED_STORAGE_ACCESS)
+SANDBOX_KEYWORD("allow-downloads", allowdownloads, SANDBOXED_ALLOW_DOWNLOADS)
+SANDBOX_KEYWORD("allow-top-navigation-to-custom-protocols",
+ allowtopnavigationcustomprotocols,
+ SANDBOXED_TOPLEVEL_NAVIGATION_CUSTOM_PROTOCOLS)
diff --git a/dom/base/ImageEncoder.cpp b/dom/base/ImageEncoder.cpp
new file mode 100644
index 0000000000..949db16c43
--- /dev/null
+++ b/dom/base/ImageEncoder.cpp
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageEncoder.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
+#include "mozilla/dom/MemoryBlobImpl.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/layers/CanvasRenderer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
+#include "gfxUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsXPCOMCIDInternal.h"
+#include "YCbCrUtils.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla::dom {
+
+// This class should be placed inside GetBRGADataSourceSurfaceSync(). However,
+// due to B2G ICS uses old complier (C++98/03) which forbids local class as
+// template parameter, we need to move this class outside.
+class SurfaceHelper : public Runnable {
+ public:
+ explicit SurfaceHelper(already_AddRefed<layers::Image> aImage)
+ : Runnable("SurfaceHelper"), mImage(aImage) {}
+
+ // It retrieves a SourceSurface reference and convert color format on main
+ // thread and passes DataSourceSurface to caller thread.
+ NS_IMETHOD Run() override {
+ RefPtr<gfx::SourceSurface> surface = mImage->GetAsSourceSurface();
+
+ if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
+ mDataSourceSurface = surface->GetDataSurface();
+ } else {
+ mDataSourceSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
+ surface, gfx::SurfaceFormat::B8G8R8A8);
+ }
+
+ // It guarantees the reference will be released on main thread.
+ NS_ReleaseOnMainThread("SurfaceHelper::surface", surface.forget());
+ return NS_OK;
+ }
+
+ already_AddRefed<gfx::DataSourceSurface> GetDataSurfaceSafe() {
+ nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ MOZ_ASSERT(mainTarget);
+ SyncRunnable::DispatchToThread(mainTarget, this, false);
+
+ return mDataSourceSurface.forget();
+ }
+
+ private:
+ RefPtr<layers::Image> mImage;
+ RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
+};
+
+// This function returns a DataSourceSurface in B8G8R8A8 format.
+// It uses SourceSurface to do format convert. Because most SourceSurface in
+// image formats should be referenced or dereferenced on main thread, it uses a
+// sync class SurfaceHelper to retrieve SourceSurface and convert to B8G8R8A8 on
+// main thread.
+already_AddRefed<DataSourceSurface> GetBRGADataSourceSurfaceSync(
+ already_AddRefed<layers::Image> aImage) {
+ RefPtr<SurfaceHelper> helper = new SurfaceHelper(std::move(aImage));
+ return helper->GetDataSurfaceSafe();
+}
+
+class EncodingCompleteEvent final : public DiscardableRunnable {
+ virtual ~EncodingCompleteEvent() = default;
+
+ public:
+ explicit EncodingCompleteEvent(
+ EncodeCompleteCallback* aEncodeCompleteCallback)
+ : DiscardableRunnable("EncodingCompleteEvent"),
+ mImgSize(0),
+ mType(),
+ mImgData(nullptr),
+ mEncodeCompleteCallback(aEncodeCompleteCallback),
+ mFailed(false) {
+ if (!NS_IsMainThread() && IsCurrentThreadRunningWorker()) {
+ mCreationEventTarget = GetCurrentSerialEventTarget();
+ } else {
+ mCreationEventTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override {
+ nsresult rv = NS_OK;
+
+ // We want to null out mEncodeCompleteCallback no matter what.
+ RefPtr<EncodeCompleteCallback> callback(std::move(mEncodeCompleteCallback));
+ if (!mFailed) {
+ RefPtr<BlobImpl> blobImpl = new MemoryBlobImpl(mImgData, mImgSize, mType);
+ rv = callback->ReceiveBlobImpl(blobImpl.forget());
+ } else {
+ rv = callback->ReceiveBlobImpl(nullptr);
+ }
+
+ return rv;
+ }
+
+ void SetMembers(void* aImgData, uint64_t aImgSize,
+ const nsAutoString& aType) {
+ mImgData = aImgData;
+ mImgSize = aImgSize;
+ mType = aType;
+ }
+
+ void SetFailed() { mFailed = true; }
+
+ nsIEventTarget* GetCreationThreadEventTarget() {
+ return mCreationEventTarget;
+ }
+
+ bool CanBeDeletedOnAnyThread() {
+ return !mEncodeCompleteCallback ||
+ mEncodeCompleteCallback->CanBeDeletedOnAnyThread();
+ }
+
+ private:
+ uint64_t mImgSize;
+ nsAutoString mType;
+ void* mImgData;
+ nsCOMPtr<nsIEventTarget> mCreationEventTarget;
+ RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
+ bool mFailed;
+};
+
+class EncodingRunnable : public Runnable {
+ virtual ~EncodingRunnable() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(EncodingRunnable, Runnable)
+
+ EncodingRunnable(const nsAString& aType, const nsAString& aOptions,
+ UniquePtr<uint8_t[]> aImageBuffer, layers::Image* aImage,
+ imgIEncoder* aEncoder,
+ EncodingCompleteEvent* aEncodingCompleteEvent,
+ int32_t aFormat, const nsIntSize aSize, bool aUsePlaceholder,
+ bool aUsingCustomOptions)
+ : Runnable("EncodingRunnable"),
+ mType(aType),
+ mOptions(aOptions),
+ mImageBuffer(std::move(aImageBuffer)),
+ mImage(aImage),
+ mEncoder(aEncoder),
+ mEncodingCompleteEvent(aEncodingCompleteEvent),
+ mFormat(aFormat),
+ mSize(aSize),
+ mUsePlaceholder(aUsePlaceholder),
+ mUsingCustomOptions(aUsingCustomOptions) {}
+
+ nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) {
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = ImageEncoder::ExtractDataInternal(
+ mType, mOptions, mImageBuffer.get(), mFormat, mSize, mUsePlaceholder,
+ mImage, nullptr, nullptr, getter_AddRefs(stream), mEncoder);
+
+ // If there are unrecognized custom parse options, we should fall back to
+ // the default values for the encoder without any options at all.
+ if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
+ rv = ImageEncoder::ExtractDataInternal(
+ mType, u""_ns, mImageBuffer.get(), mFormat, mSize, mUsePlaceholder,
+ mImage, nullptr, nullptr, getter_AddRefs(stream), mEncoder);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_ReadInputStreamToBuffer(stream, aImgData, -1, aImgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+ }
+
+ NS_IMETHOD Run() override {
+ uint64_t imgSize;
+ void* imgData = nullptr;
+
+ nsresult rv = ProcessImageData(&imgSize, &imgData);
+ if (NS_FAILED(rv)) {
+ mEncodingCompleteEvent->SetFailed();
+ } else {
+ mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
+ }
+ rv = mEncodingCompleteEvent->GetCreationThreadEventTarget()->Dispatch(
+ mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ if (!mEncodingCompleteEvent->CanBeDeletedOnAnyThread()) {
+ // Better to leak than to crash.
+ Unused << mEncodingCompleteEvent.forget();
+ }
+ return rv;
+ }
+
+ return rv;
+ }
+
+ private:
+ nsAutoString mType;
+ nsAutoString mOptions;
+ UniquePtr<uint8_t[]> mImageBuffer;
+ RefPtr<layers::Image> mImage;
+ nsCOMPtr<imgIEncoder> mEncoder;
+ RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
+ int32_t mFormat;
+ const nsIntSize mSize;
+ bool mUsePlaceholder;
+ bool mUsingCustomOptions;
+};
+
+/* static */
+nsresult ImageEncoder::ExtractData(nsAString& aType, const nsAString& aOptions,
+ const nsIntSize aSize, bool aUsePlaceholder,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::CanvasRenderer* aRenderer,
+ nsIInputStream** aStream) {
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize,
+ aUsePlaceholder, nullptr, aContext, aRenderer,
+ aStream, encoder);
+}
+
+/* static */
+nsresult ImageEncoder::ExtractDataFromLayersImageAsync(
+ nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions,
+ layers::Image* aImage, bool aUsePlaceholder,
+ EncodeCompleteCallback* aEncodeCallback) {
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ RefPtr<EncodingCompleteEvent> completeEvent =
+ new EncodingCompleteEvent(aEncodeCallback);
+
+ nsIntSize size(aImage->GetSize().width, aImage->GetSize().height);
+ nsCOMPtr<nsIRunnable> event =
+ new EncodingRunnable(aType, aOptions, nullptr, aImage, encoder,
+ completeEvent, imgIEncoder::INPUT_FORMAT_HOSTARGB,
+ size, aUsePlaceholder, aUsingCustomOptions);
+ return NS_DispatchBackgroundTask(event.forget());
+}
+
+/* static */
+nsresult ImageEncoder::ExtractDataAsync(
+ nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions,
+ UniquePtr<uint8_t[]> aImageBuffer, int32_t aFormat, const nsIntSize aSize,
+ bool aUsePlaceholder, EncodeCompleteCallback* aEncodeCallback) {
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ RefPtr<EncodingCompleteEvent> completeEvent =
+ new EncodingCompleteEvent(aEncodeCallback);
+
+ nsCOMPtr<nsIRunnable> event = new EncodingRunnable(
+ aType, aOptions, std::move(aImageBuffer), nullptr, encoder, completeEvent,
+ aFormat, aSize, aUsePlaceholder, aUsingCustomOptions);
+ return NS_DispatchBackgroundTask(event.forget());
+}
+
+/*static*/
+nsresult ImageEncoder::GetInputStream(int32_t aWidth, int32_t aHeight,
+ uint8_t* aImageBuffer, int32_t aFormat,
+ imgIEncoder* aEncoder,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** aStream) {
+ nsresult rv =
+ aEncoder->InitFromData(aImageBuffer, aWidth * aHeight * 4, aWidth,
+ aHeight, aWidth * 4, aFormat, aEncoderOptions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<imgIEncoder> encoder(aEncoder);
+ encoder.forget(aStream);
+ return NS_OK;
+}
+
+/* static */
+nsresult ImageEncoder::ExtractDataInternal(
+ const nsAString& aType, const nsAString& aOptions, uint8_t* aImageBuffer,
+ int32_t aFormat, const nsIntSize aSize, bool aUsePlaceholder,
+ layers::Image* aImage, nsICanvasRenderingContextInternal* aContext,
+ layers::CanvasRenderer* aRenderer, nsIInputStream** aStream,
+ imgIEncoder* aEncoder) {
+ if (aSize.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIInputStream> imgStream;
+
+ // get image bytes
+ nsresult rv;
+ if (aImageBuffer && !aUsePlaceholder) {
+ if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = ImageEncoder::GetInputStream(aSize.width, aSize.height, aImageBuffer,
+ aFormat, aEncoder, aOptions,
+ getter_AddRefs(imgStream));
+ } else if (aContext && !aUsePlaceholder) {
+ NS_ConvertUTF16toUTF8 encoderType(aType);
+ rv = aContext->GetInputStream(encoderType.get(), aOptions,
+ getter_AddRefs(imgStream));
+ } else if (aRenderer && !aUsePlaceholder) {
+ MOZ_CRASH("unused?");
+ const NS_ConvertUTF16toUTF8 encoderType(aType);
+ if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const auto snapshot = aRenderer->BorrowSnapshot();
+ if (!snapshot) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ const RefPtr<DataSourceSurface> data = snapshot->mSurf->GetDataSurface();
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ {
+ DataSourceSurface::MappedSurface map;
+ if (!data->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = aEncoder->InitFromData(map.mData, aSize.width * aSize.height * 4,
+ aSize.width, aSize.height, aSize.width * 4,
+ imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions);
+ data->Unmap();
+ }
+ } else if (aImage && !aUsePlaceholder) {
+ // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
+ // Other image formats could have problem to convert format off-main-thread.
+ // So here it uses a help function GetBRGADataSourceSurfaceSync() to convert
+ // format on main thread.
+ if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
+ nsTArray<uint8_t> data;
+ layers::PlanarYCbCrImage* ycbcrImage =
+ static_cast<layers::PlanarYCbCrImage*>(aImage);
+ gfxImageFormat format = SurfaceFormat::A8R8G8B8_UINT32;
+ uint32_t stride = GetAlignedStride<16>(aSize.width, 4);
+ size_t length = BufferSizeFromStrideAndHeight(stride, aSize.height);
+ if (length == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ data.SetCapacity(length);
+
+ ConvertYCbCrToRGB(*ycbcrImage->GetData(), format, aSize, data.Elements(),
+ stride);
+
+ rv = aEncoder->InitFromData(data.Elements(),
+ aSize.width * aSize.height * 4, aSize.width,
+ aSize.height, aSize.width * 4,
+ imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions);
+ } else {
+ if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface;
+ RefPtr<layers::Image> image(aImage);
+ dataSurface = GetBRGADataSourceSurfaceSync(image.forget());
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = aEncoder->InitFromData(map.mData, aSize.width * aSize.height * 4,
+ aSize.width, aSize.height, aSize.width * 4,
+ imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions);
+ dataSurface->Unmap();
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ imgStream = aEncoder;
+ }
+ } else {
+ if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // no context, so we have to encode an empty image
+ // note that if we didn't have a current context, the spec says we're
+ // supposed to just return transparent black pixels of the canvas
+ // dimensions.
+ RefPtr<DataSourceSurface> emptyCanvas =
+ Factory::CreateDataSourceSurfaceWithStride(
+ IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8,
+ 4 * aSize.width, true);
+ if (NS_WARN_IF(!emptyCanvas)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (aUsePlaceholder) {
+ auto size = 4 * aSize.width * aSize.height;
+ auto* data = map.mData;
+ GeneratePlaceholderCanvasData(size, data);
+ }
+ rv = aEncoder->InitFromData(map.mData, aSize.width * aSize.height * 4,
+ aSize.width, aSize.height, aSize.width * 4,
+ imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions);
+ emptyCanvas->Unmap();
+ if (NS_SUCCEEDED(rv)) {
+ imgStream = aEncoder;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ imgStream.forget(aStream);
+ return rv;
+}
+
+/* static */
+already_AddRefed<imgIEncoder> ImageEncoder::GetImageEncoder(nsAString& aType) {
+ // Get an image encoder for the media type.
+ nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
+ NS_ConvertUTF16toUTF8 encoderType(aType);
+ encoderCID += encoderType;
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
+
+ if (!encoder && aType != u"image/png"_ns) {
+ // Unable to create an encoder instance of the specified type. Falling back
+ // to PNG.
+ aType.AssignLiteral("image/png");
+ nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
+ encoder = do_CreateInstance(PNGEncoderCID.get());
+ }
+
+ return encoder.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ImageEncoder.h b/dom/base/ImageEncoder.h
new file mode 100644
index 0000000000..3b15ddd7bd
--- /dev/null
+++ b/dom/base/ImageEncoder.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ImageEncoder_h
+#define ImageEncoder_h
+
+#include "imgIEncoder.h"
+#include "nsError.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/HTMLCanvasElementBinding.h"
+#include "mozilla/UniquePtr.h"
+#include "nsSize.h"
+
+class nsICanvasRenderingContextInternal;
+
+namespace mozilla {
+
+namespace layers {
+class CanvasRenderer;
+class Image;
+} // namespace layers
+
+namespace dom {
+
+class EncodeCompleteCallback;
+class EncodingRunnable;
+
+class ImageEncoder {
+ public:
+ // Extracts data synchronously and gives you a stream containing the image
+ // represented by aContext. aType may change to "image/png" if we had to fall
+ // back to a PNG encoder. A return value of NS_OK implies successful data
+ // extraction. If there are any unrecognized custom parse options in
+ // aOptions, NS_ERROR_INVALID_ARG will be returned. When encountering this
+ // error it is usual to call this function again without any options at all.
+ static nsresult ExtractData(nsAString& aType, const nsAString& aOptions,
+ const nsIntSize aSize, bool aUsePlaceholder,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::CanvasRenderer* aRenderer,
+ nsIInputStream** aStream);
+
+ // Extracts data asynchronously. aType may change to "image/png" if we had to
+ // fall back to a PNG encoder. aOptions are the options to be passed to the
+ // encoder and aUsingCustomOptions specifies whether custom parse options were
+ // used (i.e. by using -moz-parse-options). If there are any unrecognized
+ // custom parse options, we fall back to the default values for the encoder
+ // without any options at all. A return value of NS_OK only implies
+ // successful dispatching of the extraction step to the encoding thread.
+ // aEncodeCallback will be called on main thread when encoding process is
+ // success.
+ // Note: The callback has to set a valid parent for content for the generated
+ // Blob object.
+ static nsresult ExtractDataAsync(nsAString& aType, const nsAString& aOptions,
+ bool aUsingCustomOptions,
+ UniquePtr<uint8_t[]> aImageBuffer,
+ int32_t aFormat, const nsIntSize aSize,
+ bool aUsePlaceholder,
+ EncodeCompleteCallback* aEncodeCallback);
+
+ // Extract an Image asynchronously. Its function is same as ExtractDataAsync
+ // except for the parameters. aImage is the uncompressed data. aEncodeCallback
+ // will be called on main thread when encoding process is success.
+ // Note: The callback has to set a valid parent for content for the generated
+ // Blob object.
+ static nsresult ExtractDataFromLayersImageAsync(
+ nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions,
+ layers::Image* aImage, bool aUsePlaceholder,
+ EncodeCompleteCallback* aEncodeCallback);
+
+ // Gives you a stream containing the image represented by aImageBuffer.
+ // The format is given in aFormat, for example
+ // imgIEncoder::INPUT_FORMAT_HOSTARGB.
+ static nsresult GetInputStream(int32_t aWidth, int32_t aHeight,
+ uint8_t* aImageBuffer, int32_t aFormat,
+ imgIEncoder* aEncoder,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** aStream);
+
+ private:
+ // When called asynchronously, aContext and aRenderer are null.
+ static nsresult ExtractDataInternal(
+ const nsAString& aType, const nsAString& aOptions, uint8_t* aImageBuffer,
+ int32_t aFormat, const nsIntSize aSize, bool aUsePlaceholder,
+ layers::Image* aImage, nsICanvasRenderingContextInternal* aContext,
+ layers::CanvasRenderer* aRenderer, nsIInputStream** aStream,
+ imgIEncoder* aEncoder);
+
+ // Creates and returns an encoder instance of the type specified in aType.
+ // aType may change to "image/png" if no instance of the original type could
+ // be created and we had to fall back to a PNG encoder. A null return value
+ // should be interpreted as NS_IMAGELIB_ERROR_NO_ENCODER and aType is
+ // undefined in this case.
+ static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
+
+ friend class EncodingRunnable;
+ friend class EncoderThreadPoolTerminator;
+};
+
+/**
+ * The callback interface of ExtractDataAsync and
+ * ExtractDataFromLayersImageAsync. ReceiveBlobImpl() is called on main thread
+ * when encoding is complete.
+ */
+class EncodeCompleteCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EncodeCompleteCallback)
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) = 0;
+
+ // CanBeDeletedOnAnyThread is pure virtual, so that whoever extends this class
+ // needs to think how to handle cases like the owning DOM worker thread
+ // shutting down.
+ virtual bool CanBeDeletedOnAnyThread() = 0;
+
+ protected:
+ virtual ~EncodeCompleteCallback() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // ImageEncoder_h
diff --git a/dom/base/ImageTracker.cpp b/dom/base/ImageTracker.cpp
new file mode 100644
index 0000000000..42936082f6
--- /dev/null
+++ b/dom/base/ImageTracker.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* table of images used in a document, for batch locking/unlocking and
+ * animating */
+
+#include "ImageTracker.h"
+
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/Preferences.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla::dom {
+
+ImageTracker::ImageTracker() = default;
+
+ImageTracker::~ImageTracker() { SetLockingState(false); }
+
+nsresult ImageTracker::Add(imgIRequest* aImage) {
+ MOZ_ASSERT(aImage);
+
+ const nsresult rv = mImages.WithEntryHandle(aImage, [&](auto&& entry) {
+ nsresult rv = NS_OK;
+ if (entry) {
+ // The image is already in the hashtable. Increment its count.
+ uint32_t oldCount = entry.Data();
+ MOZ_ASSERT(oldCount > 0, "Entry in the image tracker with count 0!");
+ entry.Data() = oldCount + 1;
+ } else {
+ // A new entry was inserted - set the count to 1.
+ entry.Insert(1);
+
+ // If we're locking images, lock this image too.
+ if (mLocking) {
+ rv = aImage->LockImage();
+ }
+
+ // If we're animating images, request that this image be animated too.
+ if (mAnimating) {
+ nsresult rv2 = aImage->IncrementAnimationConsumers();
+ rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+ }
+ }
+
+ return rv;
+ });
+
+ return rv;
+}
+
+nsresult ImageTracker::Remove(imgIRequest* aImage, uint32_t aFlags) {
+ NS_ENSURE_ARG_POINTER(aImage);
+
+ // Get the old count. It should exist and be > 0.
+ if (auto entry = mImages.Lookup(aImage)) {
+ MOZ_ASSERT(entry.Data() > 0, "Entry in the image tracker with count 0!");
+ // If the count becomes zero, remove it from the tracker.
+ if (--entry.Data() == 0) {
+ entry.Remove();
+ } else {
+ return NS_OK;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Removing image that wasn't in the tracker!");
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ // Now that we're no longer tracking this image, unlock it if we'd
+ // previously locked it.
+ if (mLocking) {
+ rv = aImage->UnlockImage();
+ }
+
+ // If we're animating images, remove our request to animate this one.
+ if (mAnimating) {
+ nsresult rv2 = aImage->DecrementAnimationConsumers();
+ rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+ }
+
+ if (aFlags & REQUEST_DISCARD) {
+ // Request that the image be discarded if nobody else holds a lock on it.
+ // Do this even if !mLocking, because even if we didn't just unlock
+ // this image, it might still be a candidate for discarding.
+ aImage->RequestDiscard();
+ }
+
+ return rv;
+}
+
+void ImageTracker::SetLockingState(bool aLocked) {
+ // If there's no change, there's nothing to do.
+ if (mLocking == aLocked) {
+ return;
+ }
+
+ // Otherwise, iterate over our images and perform the appropriate action.
+ for (imgIRequest* image : mImages.Keys()) {
+ if (aLocked) {
+ image->LockImage();
+ } else {
+ image->UnlockImage();
+ }
+ }
+
+ // Update state.
+ mLocking = aLocked;
+}
+
+void ImageTracker::SetAnimatingState(bool aAnimating) {
+ // If there's no change, there's nothing to do.
+ if (mAnimating == aAnimating) return;
+
+ // Otherwise, iterate over our images and perform the appropriate action.
+ for (imgIRequest* image : mImages.Keys()) {
+ if (aAnimating) {
+ image->IncrementAnimationConsumers();
+ } else {
+ image->DecrementAnimationConsumers();
+ }
+ }
+
+ // Update state.
+ mAnimating = aAnimating;
+}
+
+void ImageTracker::RequestDiscardAll() {
+ for (imgIRequest* image : mImages.Keys()) {
+ image->RequestDiscard();
+ }
+}
+
+void ImageTracker::MediaFeatureValuesChangedAllDocuments(
+ const MediaFeatureChange& aChange) {
+ // Inform every content image used in the document that media feature values
+ // have changed. If the same image is used in multiple places, then we can
+ // end up informing them multiple times. Theme changes are rare though and we
+ // don't bother trying to ensure we only do this once per image.
+ //
+ // Pull the images out into an array and iterate over them, in case the
+ // image notifications do something that ends up modifying the table.
+ nsTArray<nsCOMPtr<imgIContainer>> images;
+ for (imgIRequest* req : mImages.Keys()) {
+ nsCOMPtr<imgIContainer> image;
+ req->GetImage(getter_AddRefs(image));
+ if (!image) {
+ continue;
+ }
+ images.AppendElement(image->Unwrap());
+ }
+ for (imgIContainer* image : images) {
+ image->MediaFeatureValuesChangedAllDocuments(aChange);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ImageTracker.h b/dom/base/ImageTracker.h
new file mode 100644
index 0000000000..d179bf66c8
--- /dev/null
+++ b/dom/base/ImageTracker.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* table of images used in a document, for batch locking/unlocking and
+ * animating */
+
+#ifndef mozilla_dom_ImageTracker
+#define mozilla_dom_ImageTracker
+
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+
+class imgIRequest;
+namespace mozilla {
+struct MediaFeatureChange;
+}
+
+namespace mozilla::dom {
+
+/*
+ * Image Tracking
+ *
+ * Style and content images register their imgIRequests with their document's
+ * image tracker, so that we can efficiently tell all descendant images when
+ * they are and are not visible. When an image is on-screen, we want to call
+ * LockImage() on it so that it doesn't do things like discarding frame data
+ * to save memory. The PresShell informs its document's image tracker whether
+ * its images should be locked or not via SetLockingState().
+ *
+ * See bug 512260.
+ */
+class ImageTracker {
+ public:
+ ImageTracker();
+ ImageTracker(const ImageTracker&) = delete;
+ ImageTracker& operator=(const ImageTracker&) = delete;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageTracker)
+
+ nsresult Add(imgIRequest* aImage);
+
+ enum { REQUEST_DISCARD = 0x1 };
+ nsresult Remove(imgIRequest* aImage, uint32_t aFlags = 0);
+
+ // Makes the images on this document locked/unlocked. By default, the locking
+ // state is unlocked/false.
+ void SetLockingState(bool aLocked);
+ bool GetLockingState() const { return mLocking; }
+
+ // Makes the images on this document capable of having their animation
+ // active or suspended. An Image will animate as long as at least one of its
+ // owning Documents needs it to animate; otherwise it can suspend.
+ void SetAnimatingState(bool aAnimating);
+
+ void RequestDiscardAll();
+ void MediaFeatureValuesChangedAllDocuments(const MediaFeatureChange&);
+
+ private:
+ ~ImageTracker();
+
+ nsTHashMap<nsPtrHashKey<imgIRequest>, uint32_t> mImages;
+ bool mLocking = false;
+ bool mAnimating = true;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ImageTracker
diff --git a/dom/base/InProcessBrowserChildMessageManager.cpp b/dom/base/InProcessBrowserChildMessageManager.cpp
new file mode 100644
index 0000000000..774b2aa1b2
--- /dev/null
+++ b/dom/base/InProcessBrowserChildMessageManager.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "InProcessBrowserChildMessageManager.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsQueryObject.h"
+#include "xpcpublic.h"
+#include "nsIMozBrowserFrame.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/ChromeMessageSender.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+/* static */
+already_AddRefed<InProcessBrowserChildMessageManager>
+InProcessBrowserChildMessageManager::Create(nsDocShell* aShell,
+ nsIContent* aOwner,
+ nsFrameMessageManager* aChrome) {
+ RefPtr<InProcessBrowserChildMessageManager> mm =
+ new InProcessBrowserChildMessageManager(aShell, aOwner, aChrome);
+
+ NS_ENSURE_TRUE(mm->Init(), nullptr);
+
+ if (XRE_IsParentProcess()) {
+ RefPtr<JSActorService> wasvc = JSActorService::GetSingleton();
+ wasvc->RegisterChromeEventTarget(mm);
+ }
+
+ return mm.forget();
+}
+
+bool InProcessBrowserChildMessageManager::DoSendBlockingMessage(
+ const nsAString& aMessage, StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->Flush();
+
+ if (mChromeMessageManager) {
+ RefPtr<nsFrameMessageManager> mm = mChromeMessageManager;
+ RefPtr<nsFrameLoader> fl = GetFrameLoader();
+ mm->ReceiveMessage(mOwner, fl, aMessage, true, &aData, aRetVal,
+ IgnoreErrors());
+ }
+ return true;
+}
+
+class nsAsyncMessageToParent : public nsSameProcessAsyncMessageBase,
+ public SameProcessMessageQueue::Runnable {
+ public:
+ explicit nsAsyncMessageToParent(
+ InProcessBrowserChildMessageManager* aBrowserChild)
+ : nsSameProcessAsyncMessageBase(), mBrowserChild(aBrowserChild) {}
+
+ virtual nsresult HandleMessage() override {
+ RefPtr<nsFrameLoader> fl = mBrowserChild->GetFrameLoader();
+ ReceiveMessage(mBrowserChild->mOwner, fl,
+ mBrowserChild->mChromeMessageManager);
+ return NS_OK;
+ }
+ RefPtr<InProcessBrowserChildMessageManager> mBrowserChild;
+};
+
+nsresult InProcessBrowserChildMessageManager::DoSendAsyncMessage(
+ const nsAString& aMessage, StructuredCloneData& aData) {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ RefPtr<nsAsyncMessageToParent> ev = new nsAsyncMessageToParent(this);
+
+ nsresult rv = ev->Init(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ queue->Push(ev);
+ return NS_OK;
+}
+
+InProcessBrowserChildMessageManager::InProcessBrowserChildMessageManager(
+ nsDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome)
+ : ContentFrameMessageManager(new nsFrameMessageManager(this)),
+ mDocShell(aShell),
+ mLoadingScript(false),
+ mPreventEventsEscaping(false),
+ mOwner(aOwner),
+ mChromeMessageManager(aChrome) {
+ mozilla::HoldJSObjects(this);
+
+ // If owner corresponds to an <iframe mozbrowser>, we'll have to tweak our
+ // GetEventTargetParent implementation.
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
+ if (browserFrame) {
+ mIsBrowserFrame = browserFrame->GetReallyIsBrowser();
+ } else {
+ mIsBrowserFrame = false;
+ }
+}
+
+InProcessBrowserChildMessageManager::~InProcessBrowserChildMessageManager() {
+ if (XRE_IsParentProcess()) {
+ JSActorService::UnregisterChromeEventTarget(this);
+ }
+
+ mozilla::DropJSObjects(this);
+}
+
+// This method isn't automatically forwarded safely because it's notxpcom, so
+// the IDL binding doesn't know what value to return.
+void InProcessBrowserChildMessageManager::MarkForCC() {
+ MarkScopesForCC();
+ MessageManagerGlobal::MarkForCC();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(InProcessBrowserChildMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ InProcessBrowserChildMessageManager, DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(
+ InProcessBrowserChildMessageManager, DOMEventTargetHelper)
+ tmp->nsMessageManagerScriptExecutor::Trace(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
+ InProcessBrowserChildMessageManager, DOMEventTargetHelper)
+ if (XRE_IsParentProcess()) {
+ JSActorService::UnregisterChromeEventTarget(tmp);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
+ tmp->nsMessageManagerScriptExecutor::Unlink();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InProcessBrowserChildMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsIInProcessContentFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(ContentFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(InProcessBrowserChildMessageManager,
+ DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(InProcessBrowserChildMessageManager,
+ DOMEventTargetHelper)
+
+JSObject* InProcessBrowserChildMessageManager::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ContentFrameMessageManager_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void InProcessBrowserChildMessageManager::CacheFrameLoader(
+ nsFrameLoader* aFrameLoader) {
+ mFrameLoader = aFrameLoader;
+}
+
+Nullable<WindowProxyHolder> InProcessBrowserChildMessageManager::GetContent(
+ ErrorResult& aError) {
+ if (!mDocShell) {
+ return nullptr;
+ }
+ return WindowProxyHolder(mDocShell->GetBrowsingContext());
+}
+
+already_AddRefed<nsIEventTarget>
+InProcessBrowserChildMessageManager::GetTabEventTarget() {
+ nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
+ return target.forget();
+}
+
+void InProcessBrowserChildMessageManager::FireUnloadEvent() {
+ // We're called from Document::MaybeInitializeFinalizeFrameLoaders, so it
+ // should be safe to run script.
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ // Don't let the unload event propagate to chrome event handlers.
+ mPreventEventsEscaping = true;
+ DOMEventTargetHelper::DispatchTrustedEvent(u"unload"_ns);
+
+ // Allow events fired during docshell destruction (pagehide, unload) to
+ // propagate to the <browser> element since chrome code depends on this.
+ mPreventEventsEscaping = false;
+}
+
+void InProcessBrowserChildMessageManager::DisconnectEventListeners() {
+ if (mDocShell) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mDocShell->GetWindow()) {
+ win->SetChromeEventHandler(win->GetChromeEventHandler());
+ }
+ }
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ }
+
+ mDocShell = nullptr;
+}
+
+void InProcessBrowserChildMessageManager::Disconnect() {
+ mChromeMessageManager = nullptr;
+ mOwner = nullptr;
+ if (mMessageManager) {
+ static_cast<nsFrameMessageManager*>(mMessageManager.get())->Disconnect();
+ mMessageManager = nullptr;
+ }
+}
+
+NS_IMETHODIMP_(nsIContent*)
+InProcessBrowserChildMessageManager::GetOwnerContent() { return mOwner; }
+
+void InProcessBrowserChildMessageManager::GetEventTargetParent(
+ EventChainPreVisitor& aVisitor) {
+ aVisitor.mForceContentDispatch = true;
+ aVisitor.mCanHandle = true;
+
+ if (mPreventEventsEscaping) {
+ aVisitor.SetParentTarget(nullptr, false);
+ return;
+ }
+
+ if (mIsBrowserFrame &&
+ (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
+ if (mOwner) {
+ if (nsPIDOMWindowInner* innerWindow =
+ mOwner->OwnerDoc()->GetInnerWindow()) {
+ // 'this' is already a "chrome handler", so we consider window's
+ // parent target to be part of that same part of the event path.
+ aVisitor.SetParentTarget(innerWindow->GetParentTarget(), false);
+ }
+ }
+ } else {
+ aVisitor.SetParentTarget(mOwner, false);
+ }
+}
+
+class nsAsyncScriptLoad : public Runnable {
+ public:
+ nsAsyncScriptLoad(InProcessBrowserChildMessageManager* aBrowserChild,
+ const nsAString& aURL, bool aRunInGlobalScope)
+ : mozilla::Runnable("nsAsyncScriptLoad"),
+ mBrowserChild(aBrowserChild),
+ mURL(aURL),
+ mRunInGlobalScope(aRunInGlobalScope) {}
+
+ NS_IMETHOD Run() override {
+ mBrowserChild->LoadFrameScript(mURL, mRunInGlobalScope);
+ return NS_OK;
+ }
+ RefPtr<InProcessBrowserChildMessageManager> mBrowserChild;
+ nsString mURL;
+ bool mRunInGlobalScope;
+};
+
+void InProcessBrowserChildMessageManager::LoadFrameScript(
+ const nsAString& aURL, bool aRunInGlobalScope) {
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(
+ new nsAsyncScriptLoad(this, aURL, aRunInGlobalScope));
+ return;
+ }
+ bool tmp = mLoadingScript;
+ mLoadingScript = true;
+ JS::Rooted<JSObject*> mm(mozilla::dom::RootingCx(), GetOrCreateWrapper());
+ LoadScriptInternal(mm, aURL, !aRunInGlobalScope);
+ mLoadingScript = tmp;
+}
+
+already_AddRefed<nsFrameLoader>
+InProcessBrowserChildMessageManager::GetFrameLoader() {
+ RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(mOwner);
+ RefPtr<nsFrameLoader> fl = owner ? owner->GetFrameLoader() : nullptr;
+ if (!fl) {
+ fl = mFrameLoader;
+ }
+ return fl.forget();
+}
diff --git a/dom/base/InProcessBrowserChildMessageManager.h b/dom/base/InProcessBrowserChildMessageManager.h
new file mode 100644
index 0000000000..b12e84290f
--- /dev/null
+++ b/dom/base/InProcessBrowserChildMessageManager.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInProcessBrowserChildGlobal_h
+#define nsInProcessBrowserChildGlobal_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/MessageManagerCallback.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsDocShell.h"
+#include "nsCOMArray.h"
+#include "nsWeakReference.h"
+
+class nsFrameMessageManager;
+
+namespace mozilla {
+class EventChainPreVisitor;
+
+namespace dom {
+
+/**
+ * This class implements a ContentFrameMessageManager for use by frame loaders
+ * in the parent process. It is bound to a DocShell rather than a BrowserChild,
+ * and does not use any IPC infrastructure for its message passing.
+ */
+
+class InProcessBrowserChildMessageManager final
+ : public ContentFrameMessageManager,
+ public nsMessageManagerScriptExecutor,
+ public nsIInProcessContentFrameMessageManager,
+ public nsSupportsWeakReference,
+ public mozilla::dom::ipc::MessageManagerCallback {
+ using StructuredCloneData = mozilla::dom::ipc::StructuredCloneData;
+
+ private:
+ InProcessBrowserChildMessageManager(nsDocShell* aShell, nsIContent* aOwner,
+ nsFrameMessageManager* aChrome);
+
+ public:
+ static already_AddRefed<InProcessBrowserChildMessageManager> Create(
+ nsDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ InProcessBrowserChildMessageManager, DOMEventTargetHelper)
+
+ void MarkForCC();
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) override;
+ virtual already_AddRefed<nsIDocShell> GetDocShell(
+ ErrorResult& aError) override {
+ return do_AddRef(mDocShell);
+ }
+ virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() override;
+
+ NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
+
+ NS_DECL_NSIINPROCESSCONTENTFRAMEMESSAGEMANAGER
+
+ void CacheFrameLoader(nsFrameLoader* aFrameLoader);
+
+ /**
+ * MessageManagerCallback methods that we override.
+ */
+ virtual bool DoSendBlockingMessage(
+ const nsAString& aMessage, StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) override;
+ virtual nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override;
+
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
+ void LoadFrameScript(const nsAString& aURL, bool aRunInGlobalScope);
+ void FireUnloadEvent();
+ void DisconnectEventListeners();
+ void Disconnect();
+ void SendMessageToParent(const nsString& aMessage, bool aSync,
+ const nsString& aJSON,
+ nsTArray<nsString>* aJSONRetVal);
+ nsFrameMessageManager* GetInnerManager() {
+ return static_cast<nsFrameMessageManager*>(mMessageManager.get());
+ }
+
+ void SetOwner(nsIContent* aOwner) { mOwner = aOwner; }
+ nsFrameMessageManager* GetChromeMessageManager() {
+ return mChromeMessageManager;
+ }
+ void SetChromeMessageManager(nsFrameMessageManager* aParent) {
+ mChromeMessageManager = aParent;
+ }
+
+ already_AddRefed<nsFrameLoader> GetFrameLoader();
+
+ protected:
+ virtual ~InProcessBrowserChildMessageManager();
+
+ RefPtr<nsDocShell> mDocShell;
+ bool mLoadingScript;
+
+ // Is this the message manager for an in-process <iframe mozbrowser>? This
+ // affects where events get sent, so it affects GetEventTargetParent.
+ bool mIsBrowserFrame;
+ bool mPreventEventsEscaping;
+
+ // We keep a strong reference to the frameloader after we've started
+ // teardown. This allows us to dispatch message manager messages during this
+ // time.
+ RefPtr<nsFrameLoader> mFrameLoader;
+
+ public:
+ nsIContent* mOwner;
+ nsFrameMessageManager* mChromeMessageManager;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/IndexedDBHelper.sys.mjs b/dom/base/IndexedDBHelper.sys.mjs
new file mode 100644
index 0000000000..a36b211c0a
--- /dev/null
+++ b/dom/base/IndexedDBHelper.sys.mjs
@@ -0,0 +1,253 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var DEBUG = 0;
+var debug;
+if (DEBUG) {
+ debug = function (s) {
+ dump("-*- IndexedDBHelper: " + s + "\n");
+ };
+} else {
+ debug = function (s) {};
+}
+
+function getErrorName(err) {
+ return (err && err.name) || "UnknownError";
+}
+
+export function IndexedDBHelper() {}
+
+IndexedDBHelper.prototype = {
+ // Close the database
+ close: function close() {
+ if (this._db) {
+ this._db.close();
+ this._db = null;
+ }
+ },
+
+ /**
+ * Open a new database.
+ * User has to provide upgradeSchema.
+ *
+ * @param successCb
+ * Success callback to call once database is open.
+ * @param failureCb
+ * Error callback to call when an error is encountered.
+ */
+ open: function open(aCallback) {
+ if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) {
+ this._waitForOpenCallbacks.add(aCallback);
+ if (this._waitForOpenCallbacks.size !== 1) {
+ return;
+ }
+ }
+
+ let self = this;
+ let invokeCallbacks = err => {
+ for (let callback of self._waitForOpenCallbacks) {
+ callback(err);
+ }
+ self._waitForOpenCallbacks.clear();
+ };
+
+ if (DEBUG) {
+ debug("Try to open database:" + self.dbName + " " + self.dbVersion);
+ }
+ let req;
+ try {
+ req = indexedDB.open(this.dbName, this.dbVersion);
+ } catch (e) {
+ if (DEBUG) {
+ debug("Error opening database: " + self.dbName);
+ }
+ Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e)));
+ return;
+ }
+ req.onsuccess = function (event) {
+ if (DEBUG) {
+ debug("Opened database:" + self.dbName + " " + self.dbVersion);
+ }
+ self._db = event.target.result;
+ self._db.onversionchange = function (event) {
+ if (DEBUG) {
+ debug("WARNING: DB modified from a different window.");
+ }
+ };
+ invokeCallbacks();
+ };
+
+ req.onupgradeneeded = function (aEvent) {
+ if (DEBUG) {
+ debug(
+ "Database needs upgrade:" +
+ self.dbName +
+ aEvent.oldVersion +
+ aEvent.newVersion
+ );
+ debug(
+ "Correct new database version:" +
+ (aEvent.newVersion == this.dbVersion)
+ );
+ }
+
+ let _db = aEvent.target.result;
+ self.upgradeSchema(
+ req.transaction,
+ _db,
+ aEvent.oldVersion,
+ aEvent.newVersion
+ );
+ };
+ req.onerror = function (aEvent) {
+ if (DEBUG) {
+ debug("Failed to open database: " + self.dbName);
+ }
+ invokeCallbacks(getErrorName(aEvent.target.error));
+ };
+ req.onblocked = function (aEvent) {
+ if (DEBUG) {
+ debug("Opening database request is blocked.");
+ }
+ };
+ },
+
+ /**
+ * Use the cached DB or open a new one.
+ *
+ * @param successCb
+ * Success callback to call.
+ * @param failureCb
+ * Error callback to call when an error is encountered.
+ */
+ ensureDB: function ensureDB(aSuccessCb, aFailureCb) {
+ if (this._db) {
+ if (DEBUG) {
+ debug("ensureDB: already have a database, returning early.");
+ }
+ if (aSuccessCb) {
+ Services.tm.dispatchToMainThread(aSuccessCb);
+ }
+ return;
+ }
+ this.open(aError => {
+ if (aError) {
+ aFailureCb && aFailureCb(aError);
+ } else {
+ aSuccessCb && aSuccessCb();
+ }
+ });
+ },
+
+ /**
+ * Start a new transaction.
+ *
+ * @param txn_type
+ * Type of transaction (e.g. "readwrite")
+ * @param store_name
+ * The object store you want to be passed to the callback
+ * @param callback
+ * Function to call when the transaction is available. It will
+ * be invoked with the transaction and the `store' object store.
+ * @param successCb
+ * Success callback to call on a successful transaction commit.
+ * The result is stored in txn.result (in the callback function).
+ * @param failureCb
+ * Error callback to call when an error is encountered.
+ */
+ newTxn: function newTxn(
+ txn_type,
+ store_name,
+ callback,
+ successCb,
+ failureCb
+ ) {
+ this.ensureDB(() => {
+ if (DEBUG) {
+ debug("Starting new transaction" + txn_type);
+ }
+ let txn;
+ try {
+ txn = this._db.transaction(
+ Array.isArray(store_name) ? store_name : this.dbStoreNames,
+ txn_type
+ );
+ } catch (e) {
+ if (DEBUG) {
+ debug("Error starting transaction: " + this.dbName);
+ }
+ failureCb(getErrorName(e));
+ return;
+ }
+ if (DEBUG) {
+ debug("Retrieving object store: " + this.dbName);
+ }
+ let stores;
+ if (Array.isArray(store_name)) {
+ stores = [];
+ for (let i = 0; i < store_name.length; ++i) {
+ stores.push(txn.objectStore(store_name[i]));
+ }
+ } else {
+ stores = txn.objectStore(store_name);
+ }
+
+ txn.oncomplete = function () {
+ if (DEBUG) {
+ debug("Transaction complete. Returning to callback.");
+ }
+ /*
+ * txn.result property is not part of the transaction object returned
+ * by this._db.transaction method called above.
+ * The property is expected to be set in the callback function.
+ * However, it can happen that the property is not set for some reason,
+ * so we have to check if the property exists before calling the
+ * success callback.
+ */
+ if (successCb) {
+ if ("result" in txn) {
+ successCb(txn.result);
+ } else {
+ successCb();
+ }
+ }
+ };
+
+ txn.onabort = function () {
+ if (DEBUG) {
+ debug("Caught error on transaction");
+ }
+ /*
+ * txn.error property is part of the transaction object returned by
+ * this._db.transaction method called above.
+ * The attribute is defined in IDBTranscation WebIDL interface.
+ * It may be null.
+ */
+ if (failureCb) {
+ failureCb(getErrorName(txn.error));
+ }
+ };
+ callback(txn, stores);
+ }, failureCb);
+ },
+
+ /**
+ * Initialize the DB. Does not call open.
+ *
+ * @param aDBName
+ * DB name for the open call.
+ * @param aDBVersion
+ * Current DB version. User has to implement upgradeSchema.
+ * @param aDBStoreName
+ * ObjectStore that is used.
+ */
+ initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) {
+ this.dbName = aDBName;
+ this.dbVersion = aDBVersion;
+ this.dbStoreNames = aDBStoreNames;
+ // Cache the database.
+ this._db = null;
+ this._waitForOpenCallbacks = new Set();
+ },
+};
diff --git a/dom/base/IntlUtils.cpp b/dom/base/IntlUtils.cpp
new file mode 100644
index 0000000000..7292b9d779
--- /dev/null
+++ b/dom/base/IntlUtils.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IntlUtils.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozIMozIntl.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(IntlUtils, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IntlUtils)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IntlUtils)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IntlUtils)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+IntlUtils::IntlUtils(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {}
+
+IntlUtils::~IntlUtils() = default;
+
+JSObject* IntlUtils::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IntlUtils_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void IntlUtils::GetDisplayNames(const Sequence<nsString>& aLocales,
+ const DisplayNameOptions& aOptions,
+ DisplayNameResult& aResult,
+ ErrorResult& aError) {
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome() ||
+ nsContentUtils::IsCallerUAWidget());
+
+ nsCOMPtr<mozIMozIntl> mozIntl = do_GetService("@mozilla.org/mozintl;1");
+ if (!mozIntl) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ aError.MightThrowJSException();
+
+ // Need to enter privileged junk scope since mozIntl implementation is in
+ // chrome JS and passing XBL JS there shouldn't work.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Prepare parameter for getDisplayNames().
+ JS::Rooted<JS::Value> locales(cx);
+ if (!ToJSValue(cx, aLocales, &locales)) {
+ aError.StealExceptionFromJSContext(cx);
+ return;
+ }
+
+ JS::Rooted<JS::Value> options(cx);
+ if (!ToJSValue(cx, aOptions, &options)) {
+ aError.StealExceptionFromJSContext(cx);
+ return;
+ }
+
+ // Now call the method.
+ JS::Rooted<JS::Value> retVal(cx);
+ nsresult rv = mozIntl->GetDisplayNamesDeprecated(locales, options, &retVal);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ if (!retVal.isObject() || !JS_WrapValue(cx, &retVal)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Return the result as DisplayNameResult.
+ if (!aResult.Init(cx, retVal)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+bool IntlUtils::IsAppLocaleRTL() {
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome() ||
+ nsContentUtils::IsCallerUAWidget());
+ return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/IntlUtils.h b/dom/base/IntlUtils.h
new file mode 100644
index 0000000000..51531e6140
--- /dev/null
+++ b/dom/base/IntlUtils.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_IntlUtils_h
+#define mozilla_dom_IntlUtils_h
+
+#include "mozilla/dom/IntlUtilsBinding.h"
+#include "nsWrapperCache.h"
+#include "xpcprivate.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+#ifdef XP_WIN
+# undef GetLocaleInfo
+#endif
+
+class IntlUtils final : public nsISupports, public nsWrapperCache {
+ public:
+ explicit IntlUtils(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(IntlUtils)
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetDisplayNames(const Sequence<nsString>& aLocales,
+ const mozilla::dom::DisplayNameOptions& aOptions,
+ mozilla::dom::DisplayNameResult& aResult,
+ mozilla::ErrorResult& aError);
+
+ void GetLocaleInfo(const Sequence<nsString>& aLocales,
+ mozilla::dom::LocaleInfo& aResult,
+ mozilla::ErrorResult& aError);
+
+ bool IsAppLocaleRTL();
+
+ private:
+ ~IntlUtils();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/base/JSExecutionContext.cpp b/dom/base/JSExecutionContext.cpp
new file mode 100644
index 0000000000..af8eafb332
--- /dev/null
+++ b/dom/base/JSExecutionContext.cpp
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is not a generated file. It contains common utility functions
+ * invoked from the JavaScript code generated from IDL interfaces.
+ * The goal of the utility functions is to cut down on the size of
+ * the generated code itself.
+ */
+
+#include "mozilla/dom/JSExecutionContext.h"
+
+#include <utility>
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/Conversions.h"
+#include "js/experimental/JSStencil.h"
+#include "js/HeapAPI.h"
+#include "js/OffThreadScriptCompilation.h"
+#include "js/ProfilingCategory.h"
+#include "js/Promise.h"
+#include "js/SourceText.h"
+#include "js/Transcoding.h"
+#include "js/Value.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Likely.h"
+#include "nsContentUtils.h"
+#include "nsTPromiseFlatString.h"
+#include "xpcpublic.h"
+
+#if !defined(DEBUG) && !defined(MOZ_ENABLE_JS_DUMP)
+# include "mozilla/StaticPrefs_browser.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static nsresult EvaluationExceptionToNSResult(JSContext* aCx) {
+ if (JS_IsExceptionPending(aCx)) {
+ return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW;
+ }
+ return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE;
+}
+
+JSExecutionContext::JSExecutionContext(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::CompileOptions& aCompileOptions,
+ JS::Handle<JS::Value> aDebuggerPrivateValue,
+ JS::Handle<JSScript*> aDebuggerIntroductionScript)
+ : mAutoProfilerLabel("JSExecutionContext",
+ /* dynamicStr */ nullptr,
+ JS::ProfilingCategoryPair::JS),
+ mCx(aCx),
+ mRealm(aCx, aGlobal),
+ mRetValue(aCx),
+ mScript(aCx),
+ mCompileOptions(aCompileOptions),
+ mDebuggerPrivateValue(aCx, aDebuggerPrivateValue),
+ mDebuggerIntroductionScript(aCx, aDebuggerIntroductionScript),
+ mRv(NS_OK),
+ mSkip(false),
+ mCoerceToString(false),
+ mEncodeBytecode(false)
+#ifdef DEBUG
+ ,
+ mWantsReturnValue(false),
+ mScriptUsed(false)
+#endif
+{
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(CycleCollectedJSContext::Get() &&
+ CycleCollectedJSContext::Get()->MicroTaskLevel());
+ MOZ_ASSERT(mRetValue.isUndefined());
+
+ MOZ_ASSERT(JS_IsGlobalObject(aGlobal));
+ if (MOZ_UNLIKELY(!xpc::Scriptability::Get(aGlobal).Allowed())) {
+ mSkip = true;
+ mRv = NS_OK;
+ }
+}
+
+nsresult JSExecutionContext::JoinOffThread(
+ JS::OffThreadToken** aOffThreadToken) {
+ if (mSkip) {
+ return mRv;
+ }
+
+ MOZ_ASSERT(!mWantsReturnValue);
+
+ JS::Rooted<JS::InstantiationStorage> storage(mCx);
+ RefPtr<JS::Stencil> stencil =
+ JS::FinishOffThreadStencil(mCx, *aOffThreadToken, storage.address());
+ *aOffThreadToken = nullptr; // Mark the token as having been finished.
+ if (!stencil) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+ return InstantiateStencil(std::move(stencil), storage.address());
+}
+
+template <typename Unit>
+nsresult JSExecutionContext::InternalCompile(JS::SourceText<Unit>& aSrcBuf) {
+ if (mSkip) {
+ return mRv;
+ }
+
+ MOZ_ASSERT(aSrcBuf.get());
+ MOZ_ASSERT(mRetValue.isUndefined());
+#ifdef DEBUG
+ mWantsReturnValue = !mCompileOptions.noScriptRval;
+#endif
+
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(mCx, mCompileOptions, aSrcBuf);
+ if (!stencil) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+ return InstantiateStencil(std::move(stencil));
+}
+
+nsresult JSExecutionContext::Compile(JS::SourceText<char16_t>& aSrcBuf) {
+ return InternalCompile(aSrcBuf);
+}
+
+nsresult JSExecutionContext::Compile(JS::SourceText<Utf8Unit>& aSrcBuf) {
+ return InternalCompile(aSrcBuf);
+}
+
+nsresult JSExecutionContext::Compile(const nsAString& aScript) {
+ if (mSkip) {
+ return mRv;
+ }
+
+ const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(),
+ JS::SourceOwnership::Borrowed)) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+ return Compile(srcBuf);
+}
+
+nsresult JSExecutionContext::Decode(mozilla::Vector<uint8_t>& aBytecodeBuf,
+ size_t aBytecodeIndex) {
+ if (mSkip) {
+ return mRv;
+ }
+
+ JS::DecodeOptions decodeOptions(mCompileOptions);
+ decodeOptions.borrowBuffer = true;
+
+ JS::TranscodeRange range(aBytecodeBuf.begin() + aBytecodeIndex,
+ aBytecodeBuf.length() - aBytecodeIndex);
+
+ MOZ_ASSERT(!mWantsReturnValue);
+ RefPtr<JS::Stencil> stencil;
+ JS::TranscodeResult tr =
+ JS::DecodeStencil(mCx, decodeOptions, range, getter_AddRefs(stencil));
+ // These errors are external parameters which should be handled before the
+ // decoding phase, and which are the only reasons why you might want to
+ // fallback on decoding failures.
+ MOZ_ASSERT(tr != JS::TranscodeResult::Failure_BadBuildId);
+ if (tr != JS::TranscodeResult::Ok) {
+ mSkip = true;
+ mRv = NS_ERROR_DOM_JS_DECODING_ERROR;
+ return mRv;
+ }
+
+ return InstantiateStencil(std::move(stencil));
+}
+
+nsresult JSExecutionContext::InstantiateStencil(
+ RefPtr<JS::Stencil>&& aStencil, JS::InstantiationStorage* aStorage) {
+ JS::InstantiateOptions instantiateOptions(mCompileOptions);
+ JS::Rooted<JSScript*> script(
+ mCx, JS::InstantiateGlobalStencil(mCx, instantiateOptions, aStencil,
+ aStorage));
+ if (!script) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+ if (mEncodeBytecode) {
+ if (!JS::StartIncrementalEncoding(mCx, std::move(aStencil))) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+ }
+
+ MOZ_ASSERT(!mScript);
+ mScript.set(script);
+
+ if (instantiateOptions.deferDebugMetadata) {
+ if (!JS::UpdateDebugMetadata(mCx, mScript, instantiateOptions,
+ mDebuggerPrivateValue, nullptr,
+ mDebuggerIntroductionScript, nullptr)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
+
+JSScript* JSExecutionContext::GetScript() {
+#ifdef DEBUG
+ MOZ_ASSERT(!mSkip);
+ MOZ_ASSERT(mScript);
+ mScriptUsed = true;
+#endif
+
+ return MaybeGetScript();
+}
+
+JSScript* JSExecutionContext::MaybeGetScript() { return mScript; }
+
+nsresult JSExecutionContext::ExecScript() {
+ if (mSkip) {
+ return mRv;
+ }
+
+ MOZ_ASSERT(mScript);
+
+ if (!JS_ExecuteScript(mCx, mScript)) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+ return NS_OK;
+}
+
+static bool IsPromiseValue(JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ if (!aValue.isObject()) {
+ return false;
+ }
+
+ // We only care about Promise here, so CheckedUnwrapStatic is fine.
+ JS::Rooted<JSObject*> obj(aCx, js::CheckedUnwrapStatic(&aValue.toObject()));
+ if (!obj) {
+ return false;
+ }
+
+ return JS::IsPromiseObject(obj);
+}
+
+nsresult JSExecutionContext::ExecScript(
+ JS::MutableHandle<JS::Value> aRetValue) {
+ if (mSkip) {
+ aRetValue.setUndefined();
+ return mRv;
+ }
+
+ MOZ_ASSERT(mScript);
+ MOZ_ASSERT(mWantsReturnValue);
+
+ if (!JS_ExecuteScript(mCx, mScript, aRetValue)) {
+ mSkip = true;
+ mRv = EvaluationExceptionToNSResult(mCx);
+ return mRv;
+ }
+
+#ifdef DEBUG
+ mWantsReturnValue = false;
+#endif
+ if (mCoerceToString && IsPromiseValue(mCx, aRetValue)) {
+ // We're a javascript: url and we should treat Promise return values as
+ // undefined.
+ //
+ // Once bug 1477821 is fixed this code might be able to go away, or will
+ // become enshrined in the spec, depending.
+ aRetValue.setUndefined();
+ }
+
+ if (mCoerceToString && !aRetValue.isUndefined()) {
+ JSString* str = JS::ToString(mCx, aRetValue);
+ if (!str) {
+ // ToString can be a function call, so an exception can be raised while
+ // executing the function.
+ mSkip = true;
+ return EvaluationExceptionToNSResult(mCx);
+ }
+ aRetValue.set(JS::StringValue(str));
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/JSExecutionContext.h b/dom/base/JSExecutionContext.h
new file mode 100644
index 0000000000..7bfb42301a
--- /dev/null
+++ b/dom/base/JSExecutionContext.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_BASE_JSEXECUTIONCONTEXT_H_
+#define DOM_BASE_JSEXECUTIONCONTEXT_H_
+
+#include "js/GCVector.h"
+#include "js/OffThreadScriptCompilation.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/experimental/JSStencil.h"
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Vector.h"
+#include "nsStringFwd.h"
+#include "nscore.h"
+
+class nsIScriptContext;
+class nsIScriptElement;
+class nsIScriptGlobalObject;
+class nsXBLPrototypeBinding;
+
+namespace mozilla {
+union Utf8Unit;
+
+namespace dom {
+
+class MOZ_STACK_CLASS JSExecutionContext final {
+ // Register stack annotations for the Gecko profiler.
+ mozilla::AutoProfilerLabel mAutoProfilerLabel;
+
+ JSContext* mCx;
+
+ // Handles switching to our global's realm.
+ JSAutoRealm mRealm;
+
+ // Set to a valid handle if a return value is expected.
+ JS::Rooted<JS::Value> mRetValue;
+
+ // The compiled script.
+ JS::Rooted<JSScript*> mScript;
+
+ // The compilation options applied throughout
+ JS::CompileOptions& mCompileOptions;
+
+ // Debug Metadata: Values managed for the benefit of the debugger when
+ // inspecting code.
+ //
+ // For more details see CompilationAndEvaluation.h, and the comments on
+ // UpdateDebugMetadata
+ JS::Rooted<JS::Value> mDebuggerPrivateValue;
+ JS::Rooted<JSScript*> mDebuggerIntroductionScript;
+
+ // returned value forwarded when we have to interupt the execution eagerly
+ // with mSkip.
+ nsresult mRv;
+
+ // Used to skip upcoming phases in case of a failure. In such case the
+ // result is carried by mRv.
+ bool mSkip;
+
+ // Should the result be serialized before being returned.
+ bool mCoerceToString;
+
+ // Encode the bytecode before it is being executed.
+ bool mEncodeBytecode;
+
+#ifdef DEBUG
+ // Should we set the return value.
+ bool mWantsReturnValue;
+
+ bool mScriptUsed;
+#endif
+
+ private:
+ // Compile a script contained in a SourceText.
+ template <typename Unit>
+ nsresult InternalCompile(JS::SourceText<Unit>& aSrcBuf);
+
+ // Instantiate (on main-thread) a JS::Stencil generated by off-thread or
+ // main-thread parsing or decoding.
+ nsresult InstantiateStencil(RefPtr<JS::Stencil>&& aStencil,
+ JS::InstantiationStorage* aStorage = nullptr);
+
+ public:
+ // Enter compartment in which the code would be executed. The JSContext
+ // must come from an AutoEntryScript.
+ //
+ // The JS engine can associate metadata for the debugger with scripts at
+ // compile time. The optional last arguments here cover that metadata.
+ JSExecutionContext(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::CompileOptions& aCompileOptions,
+ JS::Handle<JS::Value> aDebuggerPrivateValue = JS::UndefinedHandleValue,
+ JS::Handle<JSScript*> aDebuggerIntroductionScript = nullptr);
+
+ JSExecutionContext(const JSExecutionContext&) = delete;
+ JSExecutionContext(JSExecutionContext&&) = delete;
+
+ ~JSExecutionContext() {
+ // This flag is reset when the returned value is extracted.
+ MOZ_ASSERT_IF(!mSkip, !mWantsReturnValue);
+
+ // If encoding was started we expect the script to have been
+ // used when ending the encoding.
+ MOZ_ASSERT_IF(mEncodeBytecode && mScript && mRv == NS_OK, mScriptUsed);
+ }
+
+ // The returned value would be converted to a string if the
+ // |aCoerceToString| is flag set.
+ JSExecutionContext& SetCoerceToString(bool aCoerceToString) {
+ mCoerceToString = aCoerceToString;
+ return *this;
+ }
+
+ // When set, this flag records and encodes the bytecode as soon as it is
+ // being compiled, and before it is being executed. The bytecode can then be
+ // requested by using |JS::FinishIncrementalEncoding| with the mutable
+ // handle |aScript| argument of |CompileAndExec| or |JoinAndExec|.
+ JSExecutionContext& SetEncodeBytecode(bool aEncodeBytecode) {
+ mEncodeBytecode = aEncodeBytecode;
+ return *this;
+ }
+
+ // After getting a notification that an off-thread compile/decode finished,
+ // this function will take the result of the parser and move it to the main
+ // thread.
+ [[nodiscard]] nsresult JoinOffThread(JS::OffThreadToken** aOffThreadToken);
+
+ // Compile a script contained in a SourceText.
+ nsresult Compile(JS::SourceText<char16_t>& aSrcBuf);
+ nsresult Compile(JS::SourceText<mozilla::Utf8Unit>& aSrcBuf);
+
+ // Compile a script contained in a string.
+ nsresult Compile(const nsAString& aScript);
+
+ // Decode a script contained in a buffer.
+ nsresult Decode(mozilla::Vector<uint8_t>& aBytecodeBuf,
+ size_t aBytecodeIndex);
+
+ // Get a successfully compiled script.
+ JSScript* GetScript();
+
+ // Get the compiled script if present, or nullptr.
+ JSScript* MaybeGetScript();
+
+ // Execute the compiled script and ignore the return value.
+ [[nodiscard]] nsresult ExecScript();
+
+ // Execute the compiled script a get the return value.
+ //
+ // Copy the returned value into the mutable handle argument. In case of a
+ // evaluation failure either during the execution or the conversion of the
+ // result to a string, the nsresult is be set to the corresponding result
+ // code and the mutable handle argument remains unchanged.
+ //
+ // The value returned in the mutable handle argument is part of the
+ // compartment given as argument to the JSExecutionContext constructor. If the
+ // caller is in a different compartment, then the out-param value should be
+ // wrapped by calling |JS_WrapValue|.
+ [[nodiscard]] nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif /* DOM_BASE_JSEXECUTIONCONTEXT_H_ */
diff --git a/dom/base/Link.cpp b/dom/base/Link.cpp
new file mode 100644
index 0000000000..46c54bef55
--- /dev/null
+++ b/dom/base/Link.cpp
@@ -0,0 +1,542 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Link.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/SVGAElement.h"
+#include "mozilla/dom/HTMLDNSPrefetch.h"
+#include "mozilla/IHistory.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsLayoutUtils.h"
+#include "nsIURL.h"
+#include "nsIURIMutator.h"
+#include "nsISizeOf.h"
+
+#include "nsEscape.h"
+#include "nsGkAtoms.h"
+#include "nsString.h"
+#include "mozAutoDocUpdate.h"
+
+#include "mozilla/Components.h"
+#include "nsAttrValueInlines.h"
+#include "HTMLLinkElement.h"
+
+namespace mozilla::dom {
+
+Link::Link(Element* aElement)
+ : mElement(aElement),
+ mState(State::NotLink),
+ mNeedsRegistration(false),
+ mRegistered(false),
+ mHasPendingLinkUpdate(false),
+ mHistory(true) {
+ MOZ_ASSERT(mElement, "Must have an element");
+}
+
+Link::Link()
+ : mElement(nullptr),
+ mState(State::NotLink),
+ mNeedsRegistration(false),
+ mRegistered(false),
+ mHasPendingLinkUpdate(false),
+ mHistory(false) {}
+
+Link::~Link() {
+ // !mElement is for mock_Link.
+ MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc());
+ UnregisterFromHistory();
+}
+
+bool Link::ElementHasHref() const {
+ if (mElement->HasAttr(nsGkAtoms::href)) {
+ return true;
+ }
+ if (const auto* svg = SVGAElement::FromNode(*mElement)) {
+ // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
+ // SMIL is fixed to actually mutate DOM attributes rather than faking it.
+ return svg->HasHref();
+ }
+ MOZ_ASSERT(!mElement->IsSVGElement(),
+ "What other SVG element inherits from Link?");
+ return false;
+}
+
+void Link::VisitedQueryFinished(bool aVisited) {
+ MOZ_ASSERT(mRegistered, "Setting the link state of an unregistered Link!");
+
+ auto newState = aVisited ? State::Visited : State::Unvisited;
+
+ // Set our current state as appropriate.
+ mState = newState;
+
+ MOZ_ASSERT(LinkState() == ElementState::VISITED ||
+ LinkState() == ElementState::UNVISITED,
+ "Unexpected state obtained from LinkState()!");
+
+ // Tell the element to update its visited state.
+ mElement->UpdateState(true);
+
+ // Even if the state didn't actually change, we need to repaint in order for
+ // the visited state not to be observable.
+ nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
+ nsChangeHint_RepaintFrame);
+}
+
+ElementState Link::LinkState() const {
+ // We are a constant method, but we are just lazily doing things and have to
+ // track that state. Cast away that constness!
+ //
+ // XXX(emilio): that's evil.
+ Link* self = const_cast<Link*>(this);
+
+ Element* element = self->mElement;
+
+ // If we have not yet registered for notifications and need to,
+ // due to our href changing, register now!
+ if (!mRegistered && mNeedsRegistration && element->IsInComposedDoc() &&
+ !HasPendingLinkUpdate()) {
+ // Only try and register once.
+ self->mNeedsRegistration = false;
+
+ nsCOMPtr<nsIURI> hrefURI(GetURI());
+
+ // Assume that we are not visited until we are told otherwise.
+ self->mState = State::Unvisited;
+
+ // Make sure the href attribute has a valid link (bug 23209).
+ // If we have a good href, register with History if available.
+ if (mHistory && hrefURI) {
+ if (nsCOMPtr<IHistory> history = components::History::Service()) {
+ self->mRegistered = true;
+ history->RegisterVisitedCallback(hrefURI, self);
+ // And make sure we are in the document's link map.
+ element->GetComposedDoc()->AddStyleRelevantLink(self);
+ }
+ }
+ }
+
+ // Otherwise, return our known state.
+ if (mState == State::Visited) {
+ return ElementState::VISITED;
+ }
+
+ if (mState == State::Unvisited) {
+ return ElementState::UNVISITED;
+ }
+
+ return ElementState();
+}
+
+nsIURI* Link::GetURI() const {
+ // If we have this URI cached, use it.
+ if (mCachedURI) {
+ return mCachedURI;
+ }
+
+ // Otherwise obtain it.
+ Link* self = const_cast<Link*>(this);
+ Element* element = self->mElement;
+ mCachedURI = element->GetHrefURI();
+
+ return mCachedURI;
+}
+
+void Link::SetProtocol(const nsAString& aProtocol) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsAString::const_iterator start, end;
+ aProtocol.BeginReading(start);
+ aProtocol.EndReading(end);
+ nsAString::const_iterator iter(start);
+ (void)FindCharInReadable(':', iter, end);
+ nsresult rv = NS_MutateURI(uri)
+ .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ SetHrefAttribute(uri);
+}
+
+void Link::SetPassword(const nsAString& aPassword) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv = NS_MutateURI(uri)
+ .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
+ .Finalize(uri);
+ if (NS_SUCCEEDED(rv)) {
+ SetHrefAttribute(uri);
+ }
+}
+
+void Link::SetUsername(const nsAString& aUsername) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv = NS_MutateURI(uri)
+ .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
+ .Finalize(uri);
+ if (NS_SUCCEEDED(rv)) {
+ SetHrefAttribute(uri);
+ }
+}
+
+void Link::SetHost(const nsAString& aHost) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv =
+ NS_MutateURI(uri).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ SetHrefAttribute(uri);
+}
+
+void Link::SetHostname(const nsAString& aHostname) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv =
+ NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ SetHrefAttribute(uri);
+}
+
+void Link::SetPathname(const nsAString& aPathname) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv = NS_MutateURI(uri)
+ .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ SetHrefAttribute(uri);
+}
+
+void Link::SetSearch(const nsAString& aSearch) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ auto encoding = mElement->OwnerDoc()->GetDocumentCharacterSet();
+ nsresult rv =
+ NS_MutateURI(uri)
+ .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch), encoding)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ SetHrefAttribute(uri);
+}
+
+void Link::SetPort(const nsAString& aPort) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv;
+ nsAutoString portStr(aPort);
+
+ // nsIURI uses -1 as default value.
+ int32_t port = -1;
+ if (!aPort.IsEmpty()) {
+ port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ rv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ SetHrefAttribute(uri);
+}
+
+void Link::SetHash(const nsAString& aHash) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ nsresult rv =
+ NS_MutateURI(uri).SetRef(NS_ConvertUTF16toUTF8(aHash)).Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ SetHrefAttribute(uri);
+}
+
+void Link::GetOrigin(nsAString& aOrigin) {
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ return;
+ }
+
+ nsString origin;
+ nsContentUtils::GetUTFOrigin(uri, origin);
+ aOrigin.Assign(origin);
+}
+
+void Link::GetProtocol(nsAString& _protocol) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (uri) {
+ nsAutoCString scheme;
+ (void)uri->GetScheme(scheme);
+ CopyASCIItoUTF16(scheme, _protocol);
+ }
+ _protocol.Append(char16_t(':'));
+}
+
+void Link::GetUsername(nsAString& aUsername) {
+ aUsername.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ return;
+ }
+
+ nsAutoCString username;
+ uri->GetUsername(username);
+ CopyASCIItoUTF16(username, aUsername);
+}
+
+void Link::GetPassword(nsAString& aPassword) {
+ aPassword.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ return;
+ }
+
+ nsAutoCString password;
+ uri->GetPassword(password);
+ CopyASCIItoUTF16(password, aPassword);
+}
+
+void Link::GetHost(nsAString& _host) {
+ _host.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI should result in an empty string.
+ return;
+ }
+
+ nsAutoCString hostport;
+ nsresult rv = uri->GetHostPort(hostport);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(hostport, _host);
+ }
+}
+
+void Link::GetHostname(nsAString& _hostname) {
+ _hostname.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI should result in an empty string.
+ return;
+ }
+
+ nsContentUtils::GetHostOrIPv6WithBrackets(uri, _hostname);
+}
+
+void Link::GetPathname(nsAString& _pathname) {
+ _pathname.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI should result in an empty string.
+ return;
+ }
+
+ nsAutoCString file;
+ nsresult rv = uri->GetFilePath(file);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(file, _pathname);
+ }
+}
+
+void Link::GetSearch(nsAString& _search) {
+ _search.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+ return;
+ }
+
+ nsAutoCString search;
+ nsresult rv = uri->GetQuery(search);
+ if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
+ _search.Assign(u'?');
+ AppendUTF8toUTF16(search, _search);
+ }
+}
+
+void Link::GetPort(nsAString& _port) {
+ _port.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI should result in an empty string.
+ return;
+ }
+
+ int32_t port;
+ nsresult rv = uri->GetPort(&port);
+ // Note that failure to get the port from the URI is not necessarily a bad
+ // thing. Some URIs do not have a port.
+ if (NS_SUCCEEDED(rv) && port != -1) {
+ nsAutoString portStr;
+ portStr.AppendInt(port, 10);
+ _port.Assign(portStr);
+ }
+}
+
+void Link::GetHash(nsAString& _hash) {
+ _hash.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ // Do not throw! Not having a valid URI should result in an empty
+ // string.
+ return;
+ }
+
+ nsAutoCString ref;
+ nsresult rv = uri->GetRef(ref);
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ _hash.Assign(char16_t('#'));
+ AppendUTF8toUTF16(ref, _hash);
+ }
+}
+
+void Link::ResetLinkState(bool aNotify, bool aHasHref) {
+ // If !mNeedsRegstration, then either we've never registered, or we're
+ // currently registered; in either case, we should remove ourself
+ // from the doc and the history.
+ if (!mNeedsRegistration && mState != State::NotLink) {
+ Document* doc = mElement->GetComposedDoc();
+ if (doc && (mRegistered || mState == State::Visited)) {
+ // Tell the document to forget about this link if we've registered
+ // with it before.
+ doc->ForgetLink(this);
+ }
+ }
+
+ // If we have an href, we should register with the history.
+ //
+ // FIXME(emilio): Do we really want to allow all MathML elements to be
+ // :visited? That seems not great.
+ mNeedsRegistration = aHasHref;
+
+ // If we've cached the URI, reset always invalidates it.
+ UnregisterFromHistory();
+ mCachedURI = nullptr;
+
+ // Update our state back to the default; the default state for links with an
+ // href is unvisited.
+ mState = aHasHref ? State::Unvisited : State::NotLink;
+
+ // We have to be very careful here: if aNotify is false we do NOT
+ // want to call UpdateState, because that will call into LinkState()
+ // and try to start off loads, etc. But ResetLinkState is called
+ // with aNotify false when things are in inconsistent states, so
+ // we'll get confused in that situation. Instead, just silently
+ // update the link state on mElement. Since we might have set the
+ // link state to unvisited, make sure to update with that state if
+ // required.
+ if (aNotify) {
+ mElement->UpdateState(aNotify);
+ } else {
+ if (mState == State::Unvisited) {
+ mElement->UpdateLinkState(ElementState::UNVISITED);
+ } else {
+ mElement->UpdateLinkState(ElementState());
+ }
+ }
+}
+
+void Link::UnregisterFromHistory() {
+ // If we are not registered, we have nothing to do.
+ if (!mRegistered) {
+ return;
+ }
+
+ // And tell History to stop tracking us.
+ if (mHistory && mCachedURI) {
+ if (nsCOMPtr<IHistory> history = components::History::Service()) {
+ history->UnregisterVisitedCallback(mCachedURI, this);
+ mRegistered = false;
+ }
+ }
+}
+
+void Link::SetHrefAttribute(nsIURI* aURI) {
+ NS_ASSERTION(aURI, "Null URI is illegal!");
+
+ // if we change this code to not reserialize we need to do something smarter
+ // in SetProtocol because changing the protocol of an URI can change the
+ // "nature" of the nsIURL/nsIURI implementation.
+ nsAutoCString href;
+ (void)aURI->GetSpec(href);
+ (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
+ NS_ConvertUTF8toUTF16(href), true);
+}
+
+size_t Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const {
+ size_t n = 0;
+
+ if (nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI)) {
+ n += iface->SizeOfIncludingThis(aState.mMallocSizeOf);
+ }
+
+ // The following members don't need to be measured:
+ // - mElement, because it is a pointer-to-self used to avoid QIs
+
+ return n;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Link.h b/dom/base/Link.h
new file mode 100644
index 0000000000..7d547d75ba
--- /dev/null
+++ b/dom/base/Link.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the base class for all link classes.
+ */
+
+#ifndef mozilla_dom_Link_h__
+#define mozilla_dom_Link_h__
+
+#include "nsWrapperCache.h" // For nsWrapperCache::FlagsType
+#include "nsCOMPtr.h"
+#include "mozilla/dom/RustTypes.h"
+
+class nsIURI;
+
+namespace mozilla {
+
+class SizeOfState;
+
+namespace dom {
+
+class Document;
+class Element;
+
+#define MOZILLA_DOM_LINK_IMPLEMENTATION_IID \
+ { \
+ 0xb25edee6, 0xdd35, 0x4f8b, { \
+ 0xab, 0x90, 0x66, 0xd0, 0xbd, 0x3c, 0x22, 0xd5 \
+ } \
+ }
+
+class Link : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_LINK_IMPLEMENTATION_IID)
+
+ enum class State : uint8_t {
+ Unvisited = 0,
+ Visited,
+ NotLink,
+ };
+
+ /**
+ * aElement is the element pointer corresponding to this link.
+ */
+ explicit Link(Element* aElement);
+
+ /**
+ * This constructor is only used for testing.
+ */
+ explicit Link();
+
+ virtual void VisitedQueryFinished(bool aVisited);
+
+ /**
+ * @return ElementState::VISITED if this link is visited,
+ * ElementState::UNVISTED if this link is not visited, or 0 if this
+ * link is not actually a link.
+ */
+ ElementState LinkState() const;
+
+ /**
+ * @return the URI this link is for, if available.
+ */
+ nsIURI* GetURI() const;
+
+ /**
+ * Helper methods for modifying and obtaining parts of the URI of the Link.
+ */
+ void SetProtocol(const nsAString& aProtocol);
+ void SetUsername(const nsAString& aUsername);
+ void SetPassword(const nsAString& aPassword);
+ void SetHost(const nsAString& aHost);
+ void SetHostname(const nsAString& aHostname);
+ void SetPathname(const nsAString& aPathname);
+ void SetSearch(const nsAString& aSearch);
+ void SetPort(const nsAString& aPort);
+ void SetHash(const nsAString& aHash);
+ void GetOrigin(nsAString& aOrigin);
+ void GetProtocol(nsAString& _protocol);
+ void GetUsername(nsAString& aUsername);
+ void GetPassword(nsAString& aPassword);
+ void GetHost(nsAString& _host);
+ void GetHostname(nsAString& _hostname);
+ void GetPathname(nsAString& _pathname);
+ void GetSearch(nsAString& _search);
+ void GetPort(nsAString& _port);
+ void GetHash(nsAString& _hash);
+
+ /**
+ * Invalidates any link caching, and resets the state to the default.
+ *
+ * @param aNotify
+ * true if ResetLinkState should notify the owning document about style
+ * changes or false if it should not.
+ */
+ void ResetLinkState(bool aNotify, bool aHasHref);
+
+ // This method nevers returns a null element.
+ Element* GetElement() const { return mElement; }
+
+ virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState) const;
+
+ virtual bool ElementHasHref() const;
+
+ bool HasPendingLinkUpdate() const { return mHasPendingLinkUpdate; }
+ void SetHasPendingLinkUpdate() { mHasPendingLinkUpdate = true; }
+ void ClearHasPendingLinkUpdate() { mHasPendingLinkUpdate = false; }
+
+ // To ensure correct mHasPendingLinkUpdate handling, we have this method
+ // similar to the one in Element. Overriders must call
+ // ClearHasPendingLinkUpdate().
+ // If you change this, change also the method in nsINode.
+ virtual void NodeInfoChanged(Document* aOldDoc) = 0;
+
+ protected:
+ virtual ~Link();
+
+ nsIURI* GetCachedURI() const { return mCachedURI; }
+ bool HasCachedURI() const { return !!mCachedURI; }
+
+ private:
+ /**
+ * Unregisters from History so this node no longer gets notifications about
+ * changes to visitedness.
+ */
+ void UnregisterFromHistory();
+
+ void SetHrefAttribute(nsIURI* aURI);
+
+ mutable nsCOMPtr<nsIURI> mCachedURI;
+
+ Element* const mElement;
+
+ // TODO(emilio): This ideally could be `State mState : 2`, but the version of
+ // gcc we build on automation with (7 as of this writing) has a useless
+ // warning about all values in the range of the enum not fitting, see
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414.
+ State mState;
+ bool mNeedsRegistration : 1;
+ bool mRegistered : 1;
+ bool mHasPendingLinkUpdate : 1;
+ bool mHistory : 1;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Link, MOZILLA_DOM_LINK_IMPLEMENTATION_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Link_h__
diff --git a/dom/base/LinkStyle.cpp b/dom/base/LinkStyle.cpp
new file mode 100644
index 0000000000..07ab99bca4
--- /dev/null
+++ b/dom/base/LinkStyle.cpp
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A base class which implements nsIStyleSheetLinkingElement and can
+ * be subclassed by various content nodes that want to load
+ * stylesheets (<style>, <link>, processing instructions, etc).
+ */
+
+#include "mozilla/dom/LinkStyle.h"
+
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsUnicharInputStream.h"
+#include "nsContentUtils.h"
+#include "nsStyleUtil.h"
+#include "nsQueryObject.h"
+
+namespace mozilla::dom {
+
+LinkStyle::SheetInfo::SheetInfo(
+ const Document& aDocument, nsIContent* aContent,
+ already_AddRefed<nsIURI> aURI,
+ already_AddRefed<nsIPrincipal> aTriggeringPrincipal,
+ already_AddRefed<nsIReferrerInfo> aReferrerInfo,
+ mozilla::CORSMode aCORSMode, const nsAString& aTitle,
+ const nsAString& aMedia, const nsAString& aIntegrity,
+ const nsAString& aNonce, HasAlternateRel aHasAlternateRel,
+ IsInline aIsInline, IsExplicitlyEnabled aIsExplicitlyEnabled)
+ : mContent(aContent),
+ mURI(aURI),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mReferrerInfo(aReferrerInfo),
+ mCORSMode(aCORSMode),
+ mTitle(aTitle),
+ mMedia(aMedia),
+ mIntegrity(aIntegrity),
+ mNonce(aNonce),
+ mHasAlternateRel(aHasAlternateRel == HasAlternateRel::Yes),
+ mIsInline(aIsInline == IsInline::Yes),
+ mIsExplicitlyEnabled(aIsExplicitlyEnabled) {
+ MOZ_ASSERT(!mIsInline || aContent);
+ MOZ_ASSERT_IF(aContent, aContent->OwnerDoc() == &aDocument);
+ MOZ_ASSERT(mReferrerInfo);
+ MOZ_ASSERT(mIntegrity.IsEmpty() || !mIsInline,
+ "Integrity only applies to <link>");
+}
+
+LinkStyle::SheetInfo::~SheetInfo() = default;
+
+LinkStyle::LinkStyle()
+ : mUpdatesEnabled(true), mLineNumber(1), mColumnNumber(1) {}
+
+LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); }
+
+StyleSheet* LinkStyle::GetSheetForBindings() const {
+ if (mStyleSheet && mStyleSheet->IsComplete()) {
+ return mStyleSheet;
+ }
+ return nullptr;
+}
+
+void LinkStyle::GetTitleAndMediaForElement(const Element& aSelf,
+ nsString& aTitle, nsString& aMedia) {
+ // Only honor title as stylesheet name for elements in the document (that is,
+ // ignore for Shadow DOM), per [1] and [2]. See [3].
+ //
+ // [1]: https://html.spec.whatwg.org/#attr-link-title
+ // [2]: https://html.spec.whatwg.org/#attr-style-title
+ // [3]: https://github.com/w3c/webcomponents/issues/535
+ if (aSelf.IsInUncomposedDoc()) {
+ aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle);
+ aTitle.CompressWhitespace();
+ }
+
+ aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
+ // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
+ // that media queries should be ASCII lowercased during serialization.
+ //
+ // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS
+ // should take care of serializing it properly.
+ nsContentUtils::ASCIIToLower(aMedia);
+}
+
+bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element& aSelf) {
+ // Per
+ // https://html.spec.whatwg.org/multipage/semantics.html#the-style-element:update-a-style-block
+ // step 4, for style elements we should only accept empty and "text/css" type
+ // attribute values.
+ nsAutoString type;
+ aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
+ return type.IsEmpty() || type.LowerCaseEqualsLiteral("text/css");
+}
+
+void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); }
+
+void LinkStyle::Traverse(nsCycleCollectionTraversalCallback& cb) {
+ LinkStyle* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
+}
+
+void LinkStyle::SetStyleSheet(StyleSheet* aStyleSheet) {
+ if (mStyleSheet) {
+ mStyleSheet->SetOwningNode(nullptr);
+ }
+
+ mStyleSheet = aStyleSheet;
+ if (mStyleSheet) {
+ mStyleSheet->SetOwningNode(&AsContent());
+ }
+}
+
+void LinkStyle::GetCharset(nsAString& aCharset) { aCharset.Truncate(); }
+
+static uint32_t ToLinkMask(const nsAString& aLink) {
+ // Keep this in sync with sSupportedRelValues in HTMLLinkElement.cpp
+ uint32_t mask = 0;
+ if (aLink.EqualsLiteral("prefetch")) {
+ mask = LinkStyle::ePREFETCH;
+ } else if (aLink.EqualsLiteral("dns-prefetch")) {
+ mask = LinkStyle::eDNS_PREFETCH;
+ } else if (aLink.EqualsLiteral("stylesheet")) {
+ mask = LinkStyle::eSTYLESHEET;
+ } else if (aLink.EqualsLiteral("next")) {
+ mask = LinkStyle::eNEXT;
+ } else if (aLink.EqualsLiteral("alternate")) {
+ mask = LinkStyle::eALTERNATE;
+ } else if (aLink.EqualsLiteral("preconnect")) {
+ mask = LinkStyle::ePRECONNECT;
+ } else if (aLink.EqualsLiteral("preload")) {
+ mask = LinkStyle::ePRELOAD;
+ } else if (aLink.EqualsLiteral("modulepreload")) {
+ mask = LinkStyle::eMODULE_PRELOAD;
+ }
+
+ return mask;
+}
+
+uint32_t LinkStyle::ParseLinkTypes(const nsAString& aTypes) {
+ uint32_t linkMask = 0;
+ nsAString::const_iterator start, done;
+ aTypes.BeginReading(start);
+ aTypes.EndReading(done);
+ if (start == done) return linkMask;
+
+ nsAString::const_iterator current(start);
+ bool inString = !nsContentUtils::IsHTMLWhitespace(*current);
+ nsAutoString subString;
+
+ while (current != done) {
+ if (nsContentUtils::IsHTMLWhitespace(*current)) {
+ if (inString) {
+ nsContentUtils::ASCIIToLower(Substring(start, current), subString);
+ linkMask |= ToLinkMask(subString);
+ inString = false;
+ }
+ } else {
+ if (!inString) {
+ start = current;
+ inString = true;
+ }
+ }
+ ++current;
+ }
+ if (inString) {
+ nsContentUtils::ASCIIToLower(Substring(start, current), subString);
+ linkMask |= ToLinkMask(subString);
+ }
+ return linkMask;
+}
+
+Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheet(
+ nsICSSLoaderObserver* aObserver) {
+ return DoUpdateStyleSheet(nullptr, nullptr, aObserver, ForceUpdate::No);
+}
+
+Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheetInternal(
+ Document* aOldDocument, ShadowRoot* aOldShadowRoot,
+ ForceUpdate aForceUpdate) {
+ return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr,
+ aForceUpdate);
+}
+
+Result<LinkStyle::Update, nsresult> LinkStyle::DoUpdateStyleSheet(
+ Document* aOldDocument, ShadowRoot* aOldShadowRoot,
+ nsICSSLoaderObserver* aObserver, ForceUpdate aForceUpdate) {
+ nsIContent& thisContent = AsContent();
+ if (thisContent.IsInSVGUseShadowTree()) {
+ // Stylesheets in <use>-cloned subtrees are disabled until we figure out
+ // how they should behave.
+ return Update{};
+ }
+
+ if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
+ MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
+ "ShadowRoot content is never in document, thus "
+ "there should not be a old document and old "
+ "ShadowRoot simultaneously.");
+
+ // We're removing the link element from the document or shadow tree, unload
+ // the stylesheet.
+ //
+ // We want to do this even if updates are disabled, since otherwise a sheet
+ // with a stale linking element pointer will be hanging around -- not good!
+ if (mStyleSheet->IsComplete()) {
+ if (aOldShadowRoot) {
+ aOldShadowRoot->RemoveStyleSheet(*mStyleSheet);
+ } else {
+ aOldDocument->RemoveStyleSheet(*mStyleSheet);
+ }
+ }
+
+ SetStyleSheet(nullptr);
+ }
+
+ Document* doc = thisContent.GetComposedDoc();
+
+ // Loader could be null during unlink, see bug 1425866.
+ if (!doc || !doc->CSSLoader() || !doc->CSSLoader()->GetEnabled()) {
+ return Update{};
+ }
+
+ // When static documents are created, stylesheets are cloned manually.
+ if (!mUpdatesEnabled || doc->IsStaticDocument()) {
+ return Update{};
+ }
+
+ Maybe<SheetInfo> info = GetStyleSheetInfo();
+ if (aForceUpdate == ForceUpdate::No && mStyleSheet && info &&
+ !info->mIsInline && info->mURI) {
+ if (nsIURI* oldURI = mStyleSheet->GetSheetURI()) {
+ bool equal;
+ nsresult rv = oldURI->Equals(info->mURI, &equal);
+ if (NS_SUCCEEDED(rv) && equal) {
+ return Update{};
+ }
+ }
+ }
+
+ if (mStyleSheet) {
+ if (mStyleSheet->IsComplete()) {
+ if (thisContent.IsInShadowTree()) {
+ ShadowRoot* containingShadow = thisContent.GetContainingShadow();
+ // Could be null only during unlink.
+ if (MOZ_LIKELY(containingShadow)) {
+ containingShadow->RemoveStyleSheet(*mStyleSheet);
+ }
+ } else {
+ doc->RemoveStyleSheet(*mStyleSheet);
+ }
+ }
+
+ SetStyleSheet(nullptr);
+ }
+
+ if (!info) {
+ return Update{};
+ }
+
+ if (!info->mURI && !info->mIsInline) {
+ // If href is empty and this is not inline style then just bail
+ return Update{};
+ }
+
+ if (info->mIsInline) {
+ nsAutoString text;
+ if (!nsContentUtils::GetNodeTextContent(&thisContent, false, text,
+ fallible)) {
+ return Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ MOZ_ASSERT(thisContent.NodeInfo()->NameAtom() != nsGkAtoms::link,
+ "<link> is not 'inline', and needs different CSP checks");
+ MOZ_ASSERT(thisContent.IsElement());
+ nsresult rv = NS_OK;
+ if (!nsStyleUtil::CSPAllowsInlineStyle(
+ thisContent.AsElement(), doc, info->mTriggeringPrincipal,
+ mLineNumber, mColumnNumber, text, &rv)) {
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ return Update{};
+ }
+
+ // Parse the style sheet.
+ return doc->CSSLoader()->LoadInlineStyle(*info, text, mLineNumber,
+ aObserver);
+ }
+ if (thisContent.IsElement()) {
+ nsAutoString integrity;
+ thisContent.AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
+ integrity);
+ if (!integrity.IsEmpty()) {
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("LinkStyle::DoUpdateStyleSheet, integrity=%s",
+ NS_ConvertUTF16toUTF8(integrity).get()));
+ }
+ }
+ auto resultOrError = doc->CSSLoader()->LoadStyleLink(*info, aObserver);
+ if (resultOrError.isErr()) {
+ // Don't propagate LoadStyleLink() errors further than this, since some
+ // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
+ // things like a stylesheet load being blocked by the security system.
+ return Update{};
+ }
+ return resultOrError;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/LinkStyle.h b/dom/base/LinkStyle.h
new file mode 100644
index 0000000000..5994b89b5a
--- /dev/null
+++ b/dom/base/LinkStyle.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_LinkStyle_h
+#define mozilla_dom_LinkStyle_h
+
+#include "nsINode.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/Result.h"
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+
+class nsIContent;
+class nsICSSLoaderObserver;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla::dom {
+
+class Document;
+class ShadowRoot;
+
+// https://drafts.csswg.org/cssom/#the-linkstyle-interface
+class LinkStyle {
+ public:
+ enum class ForceUpdate : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class Completed : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class HasAlternateRel : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class IsAlternate : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class IsInline : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class IsExplicitlyEnabled : uint8_t {
+ No,
+ Yes,
+ };
+
+ enum class MediaMatched : uint8_t {
+ Yes,
+ No,
+ };
+
+ struct Update {
+ private:
+ bool mWillNotify;
+ bool mIsAlternate;
+ bool mMediaMatched;
+
+ public:
+ Update() : mWillNotify(false), mIsAlternate(false), mMediaMatched(false) {}
+
+ Update(Completed aCompleted, IsAlternate aIsAlternate,
+ MediaMatched aMediaMatched)
+ : mWillNotify(aCompleted == Completed::No),
+ mIsAlternate(aIsAlternate == IsAlternate::Yes),
+ mMediaMatched(aMediaMatched == MediaMatched::Yes) {}
+
+ bool WillNotify() const { return mWillNotify; }
+
+ bool ShouldBlock() const {
+ if (!mWillNotify) {
+ return false;
+ }
+
+ return !mIsAlternate && mMediaMatched;
+ }
+ };
+
+ static LinkStyle* FromNode(nsINode& aNode) { return aNode.AsLinkStyle(); }
+ static const LinkStyle* FromNode(const nsINode& aNode) {
+ return aNode.AsLinkStyle();
+ }
+
+ static LinkStyle* FromNodeOrNull(nsINode* aNode) {
+ return aNode ? FromNode(*aNode) : nullptr;
+ }
+
+ static const LinkStyle* FromNodeOrNull(const nsINode* aNode) {
+ return aNode ? FromNode(*aNode) : nullptr;
+ }
+
+ enum RelValue {
+ ePREFETCH = 0x00000001,
+ eDNS_PREFETCH = 0x00000002,
+ eSTYLESHEET = 0x00000004,
+ eNEXT = 0x00000008,
+ eALTERNATE = 0x00000010,
+ ePRECONNECT = 0x00000020,
+ // NOTE: 0x40 is unused
+ ePRELOAD = 0x00000080,
+ eMODULE_PRELOAD = 0x00000100
+ };
+
+ // The return value is a bitwise or of 0 or more RelValues.
+ static uint32_t ParseLinkTypes(const nsAString& aTypes);
+
+ void UpdateStyleSheetInternal() {
+ Unused << UpdateStyleSheetInternal(nullptr, nullptr);
+ }
+
+ struct MOZ_STACK_CLASS SheetInfo {
+ nsIContent* mContent;
+ // FIXME(emilio): do these really need to be strong refs?
+ nsCOMPtr<nsIURI> mURI;
+
+ // The principal of the scripted caller that initiated the load, if
+ // available. Otherwise null.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ mozilla::CORSMode mCORSMode;
+ nsString mTitle;
+ nsString mMedia;
+ nsString mIntegrity;
+ nsString mNonce;
+
+ bool mHasAlternateRel;
+ bool mIsInline;
+ IsExplicitlyEnabled mIsExplicitlyEnabled;
+
+ SheetInfo(const mozilla::dom::Document&, nsIContent*,
+ already_AddRefed<nsIURI> aURI,
+ already_AddRefed<nsIPrincipal> aTriggeringPrincipal,
+ already_AddRefed<nsIReferrerInfo> aReferrerInfo,
+ mozilla::CORSMode, const nsAString& aTitle,
+ const nsAString& aMedia, const nsAString& aIntegrity,
+ const nsAString& aNonce, HasAlternateRel, IsInline,
+ IsExplicitlyEnabled);
+
+ ~SheetInfo();
+ };
+
+ virtual nsIContent& AsContent() = 0;
+ virtual Maybe<SheetInfo> GetStyleSheetInfo() = 0;
+
+ /**
+ * Used to make the association between a style sheet and
+ * the element that linked it to the document.
+ *
+ * @param aStyleSheet the style sheet associated with this
+ * element.
+ */
+ void SetStyleSheet(StyleSheet* aStyleSheet);
+
+ /**
+ * Tells this element to update the stylesheet.
+ *
+ * @param aObserver observer to notify once the stylesheet is loaded.
+ * This will be passed to the CSSLoader
+ */
+ Result<Update, nsresult> UpdateStyleSheet(nsICSSLoaderObserver*);
+
+ /**
+ * Tells this element whether to update the stylesheet when the
+ * element's properties change.
+ *
+ * @param aEnableUpdates update on changes or not.
+ */
+ void SetEnableUpdates(bool aEnableUpdates) {
+ mUpdatesEnabled = aEnableUpdates;
+ }
+
+ /**
+ * Gets the charset that the element claims the style sheet is in.
+ * Can return empty string to indicate that we have no charset
+ * information.
+ *
+ * @param aCharset the charset
+ */
+ virtual void GetCharset(nsAString& aCharset);
+
+ // This doesn't entirely belong here since they only make sense for
+ // some types of linking elements, but it's a better place than
+ // anywhere else.
+ void SetLineNumber(uint32_t aLineNumber) { mLineNumber = aLineNumber; }
+
+ /**
+ * Get the line number, as previously set by SetLineNumber.
+ *
+ * @return the line number of this element; or 1 if no line number
+ * was set
+ */
+ uint32_t GetLineNumber() const { return mLineNumber; }
+
+ // This doesn't entirely belong here since they only make sense for
+ // some types of linking elements, but it's a better place than
+ // anywhere else.
+ void SetColumnNumber(uint32_t aColumnNumber) {
+ mColumnNumber = aColumnNumber;
+ }
+
+ /**
+ * Get the column number, as previously set by SetColumnNumber.
+ *
+ * @return the column number of this element; or 1 if no column number
+ * was set
+ */
+ uint32_t GetColumnNumber() const { return mColumnNumber; }
+
+ StyleSheet* GetSheet() const { return mStyleSheet; }
+
+ /** JS can only observe the sheet once fully loaded */
+ StyleSheet* GetSheetForBindings() const;
+
+ protected:
+ LinkStyle();
+ virtual ~LinkStyle();
+
+ // Gets a suitable title and media for SheetInfo out of an element, which
+ // needs to be `this`.
+ //
+ // NOTE(emilio): Needs nsString instead of nsAString because of
+ // CompressWhitespace.
+ static void GetTitleAndMediaForElement(const mozilla::dom::Element&,
+ nsString& aTitle, nsString& aMedia);
+
+ // Returns whether the type attribute specifies the text/css type for style
+ // elements.
+ static bool IsCSSMimeTypeAttributeForStyleElement(const Element&);
+
+ // CC methods
+ void Unlink();
+ void Traverse(nsCycleCollectionTraversalCallback& cb);
+
+ /**
+ * @param aOldDocument should be non-null only if we're updating because we
+ * removed the node from the document.
+ * @param aOldShadowRoot should be non-null only if we're updating because we
+ * removed the node from a shadow tree.
+ * @param aForceUpdate true will force the update even if the URI has not
+ * changed. This should be used in cases when something
+ * about the content that affects the resulting sheet
+ * changed but the URI may not have changed.
+ *
+ * TODO(emilio): Should probably pass a single DocumentOrShadowRoot.
+ */
+ Result<Update, nsresult> UpdateStyleSheetInternal(
+ Document* aOldDocument, ShadowRoot* aOldShadowRoot,
+ ForceUpdate = ForceUpdate::No);
+
+ /**
+ * @param aOldDocument should be non-null only if we're updating because we
+ * removed the node from the document.
+ * @param aOldShadowRoot The ShadowRoot that used to contain the style.
+ * Passed as a parameter because on an update, the node
+ * is removed from the tree before the sheet is removed
+ * from the ShadowRoot.
+ * @param aForceUpdate true will force the update even if the URI has not
+ * changed. This should be used in cases when something
+ * about the content that affects the resulting sheet
+ * changed but the URI may not have changed.
+ */
+ Result<Update, nsresult> DoUpdateStyleSheet(Document* aOldDocument,
+ ShadowRoot* aOldShadowRoot,
+ nsICSSLoaderObserver*,
+ ForceUpdate);
+
+ RefPtr<mozilla::StyleSheet> mStyleSheet;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ bool mUpdatesEnabled;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_LinkStyle_h
diff --git a/dom/base/Location.cpp b/dom/base/Location.cpp
new file mode 100644
index 0000000000..e5ed7e22e4
--- /dev/null
+++ b/dom/base/Location.cpp
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Location.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsDocShellLoadState.h"
+#include "nsIWebNavigation.h"
+#include "nsIOService.h"
+#include "nsIURL.h"
+#include "nsIJARURI.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsEscape.h"
+#include "nsPresContext.h"
+#include "nsError.h"
+#include "nsReadableUtils.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/Likely.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/LocationBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "ReferrerInfo.h"
+
+namespace mozilla::dom {
+
+Location::Location(nsPIDOMWindowInner* aWindow,
+ BrowsingContext* aBrowsingContext)
+ : mInnerWindow(aWindow) {
+ // aBrowsingContext can be null if it gets called after nsDocShell::Destory().
+ if (aBrowsingContext) {
+ mBrowsingContextId = aBrowsingContext->Id();
+ }
+}
+
+Location::~Location() = default;
+
+// QueryInterface implementation for Location
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Location)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Location, mInnerWindow)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Location)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Location)
+
+BrowsingContext* Location::GetBrowsingContext() {
+ RefPtr<BrowsingContext> bc = BrowsingContext::Get(mBrowsingContextId);
+ return bc.get();
+}
+
+already_AddRefed<nsIDocShell> Location::GetDocShell() {
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ return do_AddRef(bc->GetDocShell());
+ }
+ return nullptr;
+}
+
+nsresult Location::GetURI(nsIURI** aURI, bool aGetInnermostURI) {
+ *aURI = nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell(GetDocShell());
+ if (!docShell) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = webNav->GetCurrentURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It is valid for docshell to return a null URI. Don't try to fixup
+ // if this happens.
+ if (!uri) {
+ return NS_OK;
+ }
+
+ if (aGetInnermostURI) {
+ nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(uri));
+ while (jarURI) {
+ jarURI->GetJARFile(getter_AddRefs(uri));
+ jarURI = do_QueryInterface(uri);
+ }
+ }
+
+ NS_ASSERTION(uri, "nsJARURI screwed up?");
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(uri);
+ exposableURI.forget(aURI);
+ return NS_OK;
+}
+
+void Location::GetHash(nsAString& aHash, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aHash.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsAutoCString ref;
+ nsAutoString unicodeRef;
+
+ aRv = uri->GetRef(ref);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!ref.IsEmpty()) {
+ aHash.Assign(char16_t('#'));
+ AppendUTF8toUTF16(ref, aHash);
+ }
+
+ if (aHash == mCachedHash) {
+ // Work around ShareThis stupidly polling location.hash every
+ // 5ms all the time by handing out the same exact string buffer
+ // we handed out last time.
+ aHash = mCachedHash;
+ } else {
+ mCachedHash = aHash;
+ }
+}
+
+void Location::SetHash(const nsAString& aHash, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 hash(aHash);
+ if (hash.IsEmpty() || hash.First() != char16_t('#')) {
+ hash.Insert(char16_t('#'), 0);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ aRv = NS_MutateURI(uri).SetRef(hash).Finalize(uri);
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::GetHost(nsAString& aHost, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aHost.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result;
+
+ result = GetURI(getter_AddRefs(uri), true);
+
+ if (uri) {
+ nsAutoCString hostport;
+
+ result = uri->GetHostPort(hostport);
+
+ if (NS_SUCCEEDED(result)) {
+ AppendUTF8toUTF16(hostport, aHost);
+ }
+ }
+}
+
+void Location::SetHost(const nsAString& aHost, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ aRv =
+ NS_MutateURI(uri).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::GetHostname(nsAString& aHostname,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aHostname.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri), true);
+ if (uri) {
+ nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname);
+ }
+}
+
+void Location::SetHostname(const nsAString& aHostname,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ aRv =
+ NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+nsresult Location::GetHref(nsAString& aHref) {
+ aHref.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
+ return rv;
+ }
+
+ nsAutoCString uriString;
+ rv = uri->GetSpec(uriString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AppendUTF8toUTF16(uriString, aHref);
+ return NS_OK;
+}
+
+void Location::GetOrigin(nsAString& aOrigin, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri), true);
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsAutoString origin;
+ aRv = nsContentUtils::GetUTFOrigin(uri, origin);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aOrigin = origin;
+}
+
+void Location::GetPathname(nsAString& aPathname,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aPathname.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsAutoCString file;
+
+ aRv = uri->GetFilePath(file);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ AppendUTF8toUTF16(file, aPathname);
+}
+
+void Location::SetPathname(const nsAString& aPathname,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsresult rv = NS_MutateURI(uri)
+ .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::GetPort(nsAString& aPort, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aPort.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri), true);
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ int32_t port;
+ nsresult result = uri->GetPort(&port);
+
+ // Don't propagate this exception to caller
+ if (NS_SUCCEEDED(result) && -1 != port) {
+ nsAutoString portStr;
+ portStr.AppendInt(port);
+ aPort.Append(portStr);
+ }
+}
+
+void Location::SetPort(const nsAString& aPort, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed() || !uri)) {
+ return;
+ }
+
+ // perhaps use nsReadingIterators at some point?
+ NS_ConvertUTF16toUTF8 portStr(aPort);
+ const char* buf = portStr.get();
+ int32_t port = -1;
+
+ if (!portStr.IsEmpty() && buf) {
+ if (*buf == ':') {
+ port = atol(buf + 1);
+ } else {
+ port = atol(buf);
+ }
+ }
+
+ aRv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::GetProtocol(nsAString& aProtocol,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aProtocol.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsAutoCString protocol;
+
+ aRv = uri->GetScheme(protocol);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ CopyASCIItoUTF16(protocol, aProtocol);
+ aProtocol.Append(char16_t(':'));
+}
+
+void Location::SetProtocol(const nsAString& aProtocol,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !uri) {
+ return;
+ }
+
+ nsAString::const_iterator start, end;
+ aProtocol.BeginReading(start);
+ aProtocol.EndReading(end);
+ nsAString::const_iterator iter(start);
+ Unused << FindCharInReadable(':', iter, end);
+
+ nsresult rv = NS_MutateURI(uri)
+ .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
+ .Finalize(uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Oh, I wish nsStandardURL returned NS_ERROR_MALFORMED_URI for _all_ the
+ // malformed cases, not just some of them!
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsAutoCString newSpec;
+ aRv = uri->GetSpec(newSpec);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ // We may want a new URI class for the new URI, so recreate it:
+ rv = NS_NewURI(getter_AddRefs(uri), newSpec);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ rv = NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) {
+ // No-op, per spec.
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::GetSearch(nsAString& aSearch, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aSearch.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result = NS_OK;
+
+ result = GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+
+ if (url) {
+ nsAutoCString search;
+
+ result = url->GetQuery(search);
+
+ if (NS_SUCCEEDED(result) && !search.IsEmpty()) {
+ aSearch.Assign(char16_t('?'));
+ AppendUTF8toUTF16(search, aSearch);
+ }
+ }
+}
+
+void Location::SetSearch(const nsAString& aSearch,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (NS_WARN_IF(aRv.Failed()) || !url) {
+ return;
+ }
+
+ aRv =
+ NS_MutateURI(uri).SetQuery(NS_ConvertUTF16toUTF8(aSearch)).Finalize(uri);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SetURI(uri, aSubjectPrincipal, aRv);
+}
+
+void Location::Reload(bool aForceget, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ RefPtr<nsDocShell> docShell(GetDocShell().downcast<nsDocShell>());
+ if (!docShell) {
+ return aRv.Throw(NS_ERROR_FAILURE);
+ }
+
+ if (StaticPrefs::dom_block_reload_from_resize_event_handler()) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
+ if (window && window->IsHandlingResizeEvent()) {
+ // location.reload() was called on a window that is handling a
+ // resize event. Sites do this since Netscape 4.x needed it, but
+ // we don't, and it's a horrible experience for nothing. In stead
+ // of reloading the page, just clear style data and reflow the
+ // page since some sites may use this trick to work around gecko
+ // reflow bugs, and this should have the same effect.
+ RefPtr<Document> doc = window->GetExtantDoc();
+
+ nsPresContext* pcx;
+ if (doc && (pcx = doc->GetPresContext())) {
+ pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW,
+ RestyleHint::RestyleSubtree());
+ }
+ return;
+ }
+ }
+
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ if (!bc || bc->IsDiscarded()) {
+ return;
+ }
+
+ CallerType callerType = aSubjectPrincipal.IsSystemPrincipal()
+ ? CallerType::System
+ : CallerType::NonSystem;
+
+ nsresult rv = bc->CheckLocationChangeRateLimit(callerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ uint32_t reloadFlags = nsIWebNavigation::LOAD_FLAGS_NONE;
+
+ if (aForceget) {
+ reloadFlags = nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY;
+ }
+
+ rv = docShell->Reload(reloadFlags);
+ if (NS_FAILED(rv) && rv != NS_BINDING_ABORTED) {
+ // NS_BINDING_ABORTED is returned when we attempt to reload a POST result
+ // and the user says no at the "do you want to reload?" prompt. Don't
+ // propagate this one back to callers.
+ return aRv.Throw(rv);
+ }
+}
+
+void Location::Assign(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ DoSetHref(aUrl, aSubjectPrincipal, false, aRv);
+}
+
+bool Location::CallerSubsumes(nsIPrincipal* aSubjectPrincipal) {
+ MOZ_ASSERT(aSubjectPrincipal);
+
+ RefPtr<BrowsingContext> bc(GetBrowsingContext());
+ if (MOZ_UNLIKELY(!bc) || MOZ_UNLIKELY(bc->IsDiscarded())) {
+ // Per spec, operations on a Location object with a discarded BC are no-ops,
+ // not security errors, so we need to return true from the access check and
+ // let the caller do its own discarded docShell check.
+ return true;
+ }
+ if (MOZ_UNLIKELY(!bc->IsInProcess())) {
+ return false;
+ }
+
+ // Get the principal associated with the location object. Note that this is
+ // the principal of the page which will actually be navigated, not the
+ // principal of the Location object itself. This is why we need this check
+ // even though we only allow limited cross-origin access to Location objects
+ // in general.
+ nsCOMPtr<nsPIDOMWindowOuter> outer = bc->GetDOMWindow();
+ MOZ_DIAGNOSTIC_ASSERT(outer);
+ if (MOZ_UNLIKELY(!outer)) return false;
+
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(outer);
+ bool subsumes = false;
+ nsresult rv = aSubjectPrincipal->SubsumesConsideringDomain(
+ sop->GetPrincipal(), &subsumes);
+ NS_ENSURE_SUCCESS(rv, false);
+ return subsumes;
+}
+
+JSObject* Location::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Location_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Location.h b/dom/base/Location.h
new file mode 100644
index 0000000000..5fb92beb04
--- /dev/null
+++ b/dom/base/Location.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Location_h
+#define mozilla_dom_Location_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/LocationBase.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsIDocShell;
+class nsIPrincipal;
+class nsIURI;
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+//*****************************************************************************
+// Location: Script "location" object
+//*****************************************************************************
+
+class Location final : public nsISupports,
+ public LocationBase,
+ public nsWrapperCache {
+ public:
+ typedef BrowsingContext::LocationProxy RemoteProxy;
+
+ Location(nsPIDOMWindowInner* aWindow, BrowsingContext* aBrowsingContext);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Location)
+
+ // WebIDL API:
+ void Assign(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void Reload(bool aForceget, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetHref(nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ if (!CallerSubsumes(&aSubjectPrincipal)) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aError = GetHref(aHref);
+ }
+
+ void GetOrigin(nsAString& aOrigin, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetProtocol(nsAString& aProtocol, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetProtocol(const nsAString& aProtocol, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetHost(nsAString& aHost, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetHost(const nsAString& aHost, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetHostname(nsAString& aHostname, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetHostname(const nsAString& aHostname, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetPort(nsAString& aPort, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetPort(const nsAString& aPort, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetPathname(nsAString& aPathname, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetPathname(const nsAString& aPathname, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetSearch(nsAString& aSeach, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetSearch(const nsAString& aSeach, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void GetHash(nsAString& aHash, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetHash(const nsAString& aHash, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mInnerWindow; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Non WebIDL methods:
+
+ nsresult GetHref(nsAString& aHref);
+
+ nsresult ToString(nsAString& aString) { return GetHref(aString); }
+
+ protected:
+ virtual ~Location();
+
+ BrowsingContext* GetBrowsingContext() override;
+ already_AddRefed<nsIDocShell> GetDocShell() override;
+
+ // In the case of jar: uris, we sometimes want the place the jar was
+ // fetched from as the URI instead of the jar: uri itself. Pass in
+ // true for aGetInnermostURI when that's the case.
+ // Note, this method can return NS_OK with a null value for aURL. This happens
+ // if the docShell is null.
+ nsresult GetURI(nsIURI** aURL, bool aGetInnermostURI = false);
+
+ bool CallerSubsumes(nsIPrincipal* aSubjectPrincipal);
+
+ nsString mCachedHash;
+ nsCOMPtr<nsPIDOMWindowInner> mInnerWindow;
+ uint64_t mBrowsingContextId = 0;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Location_h
diff --git a/dom/base/LocationBase.cpp b/dom/base/LocationBase.cpp
new file mode 100644
index 0000000000..fd48c54c7e
--- /dev/null
+++ b/dom/base/LocationBase.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/LocationBase.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScriptContext.h"
+#include "nsDocShellLoadState.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/WindowContext.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<nsDocShellLoadState> LocationBase::CheckURL(
+ nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ RefPtr<BrowsingContext> bc(GetBrowsingContext());
+ if (NS_WARN_IF(!bc)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIURI> sourceURI;
+ ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+
+ // Get security manager.
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (NS_WARN_IF(!ssm)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ // Check to see if URI is allowed. We're not going to worry about a
+ // window ID here because it's not 100% clear which window's id we
+ // would want, and we're throwing a content-visible exception
+ // anyway.
+ nsresult rv = ssm->CheckLoadURIWithPrincipal(
+ &aSubjectPrincipal, aURI, nsIScriptSecurityManager::STANDARD, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoCString spec;
+ aURI->GetSpec(spec);
+ aRv.ThrowTypeError<MSG_URL_NOT_LOADABLE>(spec);
+ return nullptr;
+ }
+
+ // Make the load's referrer reflect changes to the document's URI caused by
+ // push/replaceState, if possible. First, get the document corresponding to
+ // fp. If the document's original URI (i.e. its URI before
+ // push/replaceState) matches the principal's URI, use the document's
+ // current URI as the referrer. If they don't match, use the principal's
+ // URI.
+ //
+ // The triggering principal for this load should be the principal of the
+ // incumbent document (which matches where the referrer information is
+ // coming from) when there is an incumbent document, and the subject
+ // principal otherwise. Note that the URI in the triggering principal
+ // may not match the referrer URI in various cases, notably including
+ // the cases when the incumbent document's document URI was modified
+ // after the document was loaded.
+
+ nsCOMPtr<nsPIDOMWindowInner> incumbent =
+ do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
+ nsCOMPtr<Document> doc = incumbent ? incumbent->GetDoc() : nullptr;
+
+ // Create load info
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+
+ if (!doc) {
+ // No document; just use our subject principal as the triggering principal.
+ loadState->SetTriggeringPrincipal(&aSubjectPrincipal);
+ return loadState.forget();
+ }
+
+ nsCOMPtr<nsIURI> docOriginalURI, docCurrentURI, principalURI;
+ docOriginalURI = doc->GetOriginalURI();
+ docCurrentURI = doc->GetDocumentURI();
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+
+ triggeringPrincipal = doc->NodePrincipal();
+ referrerPolicy = doc->GetReferrerPolicy();
+
+ bool urisEqual = false;
+ if (docOriginalURI && docCurrentURI && principal) {
+ principal->EqualsURI(docOriginalURI, &urisEqual);
+ }
+ if (urisEqual) {
+ referrerInfo = new ReferrerInfo(docCurrentURI, referrerPolicy);
+ } else {
+ principal->CreateReferrerInfo(referrerPolicy, getter_AddRefs(referrerInfo));
+ }
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
+ loadState->SetCsp(doc->GetCsp());
+ if (referrerInfo) {
+ loadState->SetReferrerInfo(referrerInfo);
+ }
+ loadState->SetHasValidUserGestureActivation(
+ doc->HasValidTransientUserGestureActivation());
+
+ return loadState.forget();
+}
+
+void LocationBase::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv, bool aReplace) {
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ if (!bc || bc->IsDiscarded()) {
+ return;
+ }
+
+ CallerType callerType = aSubjectPrincipal.IsSystemPrincipal()
+ ? CallerType::System
+ : CallerType::NonSystem;
+
+ nsresult rv = bc->CheckLocationChangeRateLimit(callerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState =
+ CheckURL(aURI, aSubjectPrincipal, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (aReplace) {
+ loadState->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE);
+ } else {
+ loadState->SetLoadType(LOAD_STOP_CONTENT);
+ }
+
+ // Get the incumbent script's browsing context to set as source.
+ nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
+ nsContentUtils::IncumbentInnerWindow();
+ if (sourceWindow) {
+ WindowContext* context = sourceWindow->GetWindowContext();
+ loadState->SetSourceBrowsingContext(sourceWindow->GetBrowsingContext());
+ loadState->SetHasValidUserGestureActivation(
+ context && context->HasValidTransientUserGestureActivation());
+ }
+
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ loadState->SetFirstParty(true);
+
+ rv = bc->LoadURI(loadState);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (rv == NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI &&
+ net::SchemeIsJavascript(loadState->URI())) {
+ // Per spec[1], attempting to load a javascript: URI into a cross-origin
+ // BrowsingContext is a no-op, and should not raise an exception.
+ // Technically, Location setters run with exceptions enabled should only
+ // throw an exception[2] when the caller is not allowed to navigate[3] the
+ // target browsing context due to sandboxing flags or not being
+ // closely-related enough, though in practice we currently throw for other
+ // reasons as well.
+ //
+ // [1]:
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
+ // [2]:
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
+ // [3]:
+ // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
+ return;
+ }
+ aRv.Throw(rv);
+ return;
+ }
+
+ Document* doc = bc->GetDocument();
+ if (doc && nsContentUtils::IsExternalProtocol(aURI)) {
+ doc->EnsureNotEnteringAndExitFullscreen();
+ }
+}
+
+void LocationBase::SetHref(const nsAString& aHref,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ DoSetHref(aHref, aSubjectPrincipal, false, aRv);
+}
+
+void LocationBase::DoSetHref(const nsAString& aHref,
+ nsIPrincipal& aSubjectPrincipal, bool aReplace,
+ ErrorResult& aRv) {
+ // Get the source of the caller
+ nsCOMPtr<nsIURI> base = GetSourceBaseURL();
+ SetHrefWithBase(aHref, base, aSubjectPrincipal, aReplace, aRv);
+}
+
+void LocationBase::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
+ nsIPrincipal& aSubjectPrincipal,
+ bool aReplace, ErrorResult& aRv) {
+ nsresult result;
+ nsCOMPtr<nsIURI> newUri;
+
+ if (Document* doc = GetEntryDocument()) {
+ result = NS_NewURI(getter_AddRefs(newUri), aHref,
+ doc->GetDocumentCharacterSet(), aBase);
+ } else {
+ result = NS_NewURI(getter_AddRefs(newUri), aHref, nullptr, aBase);
+ }
+
+ if (newUri) {
+ /* Check with the scriptContext if it is currently processing a script tag.
+ * If so, this must be a <script> tag with a location.href in it.
+ * we want to do a replace load, in such a situation.
+ * In other cases, for example if a event handler or a JS timer
+ * had a location.href in it, we want to do a normal load,
+ * so that the new url will be appended to Session History.
+ * This solution is tricky. Hopefully it isn't going to bite
+ * anywhere else. This is part of solution for bug # 39938, 72197
+ */
+ bool inScriptTag = false;
+ nsIScriptContext* scriptContext = nullptr;
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(GetEntryGlobal());
+ if (win) {
+ scriptContext = nsGlobalWindowInner::Cast(win)->GetContextInternal();
+ }
+
+ if (scriptContext) {
+ if (scriptContext->GetProcessingScriptTag()) {
+ // Now check to make sure that the script is running in our window,
+ // since we only want to replace if the location is set by a
+ // <script> tag in the same window. See bug 178729.
+ nsCOMPtr<nsIDocShell> docShell(GetDocShell());
+ nsCOMPtr<nsIScriptGlobalObject> ourGlobal =
+ docShell ? docShell->GetScriptGlobalObject() : nullptr;
+ inScriptTag = (ourGlobal == scriptContext->GetGlobalObject());
+ }
+ }
+
+ SetURI(newUri, aSubjectPrincipal, aRv, aReplace || inScriptTag);
+ return;
+ }
+
+ aRv.Throw(result);
+}
+
+void LocationBase::Replace(const nsAString& aUrl,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ DoSetHref(aUrl, aSubjectPrincipal, true, aRv);
+}
+
+nsIURI* LocationBase::GetSourceBaseURL() {
+ Document* doc = GetEntryDocument();
+
+ // If there's no entry document, we either have no Script Entry Point or one
+ // that isn't a DOM Window. This doesn't generally happen with the DOM, but
+ // can sometimes happen with extension code in certain IPC configurations. If
+ // this happens, try falling back on the current document associated with the
+ // docshell. If that fails, just return null and hope that the caller passed
+ // an absolute URI.
+ if (!doc) {
+ if (nsCOMPtr<nsIDocShell> docShell = GetDocShell()) {
+ nsCOMPtr<nsPIDOMWindowOuter> docShellWin =
+ do_QueryInterface(docShell->GetScriptGlobalObject());
+ if (docShellWin) {
+ doc = docShellWin->GetDoc();
+ }
+ }
+ }
+ return doc ? doc->GetBaseURI() : nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/LocationBase.h b/dom/base/LocationBase.h
new file mode 100644
index 0000000000..c0c57bb1da
--- /dev/null
+++ b/dom/base/LocationBase.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_LocationBase_h
+#define mozilla_dom_LocationBase_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsStringFwd.h"
+
+class nsIDocShell;
+class nsIPrincipal;
+class nsIURI;
+class nsDocShellLoadState;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class BrowsingContext;
+
+//*****************************************************************************
+// Location: Script "location" object
+//*****************************************************************************
+
+class LocationBase {
+ public:
+ // WebIDL API:
+ void Replace(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ void SetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError);
+
+ protected:
+ virtual BrowsingContext* GetBrowsingContext() = 0;
+ virtual already_AddRefed<nsIDocShell> GetDocShell() = 0;
+
+ void SetURI(nsIURI* aURL, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv,
+ bool aReplace = false);
+ void SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
+ nsIPrincipal& aSubjectPrincipal, bool aReplace,
+ ErrorResult& aRv);
+
+ // Helper for Assign/SetHref/Replace
+ void DoSetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+ bool aReplace, ErrorResult& aRv);
+
+ // Get the base URL we should be using for our relative URL
+ // resolution for SetHref/Assign/Replace.
+ nsIURI* GetSourceBaseURL();
+
+ // Check whether it's OK to load the given url with the given subject
+ // principal, and if so construct the right nsDocShellLoadInfo for the load
+ // and return it.
+ already_AddRefed<nsDocShellLoadState> CheckURL(
+ nsIURI* url, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_LocationBase_h
diff --git a/dom/base/LocationHelper.sys.mjs b/dom/base/LocationHelper.sys.mjs
new file mode 100644
index 0000000000..671dd414d6
--- /dev/null
+++ b/dom/base/LocationHelper.sys.mjs
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function isPublic(ap) {
+ let mask = "_nomap";
+ let result = ap.ssid.indexOf(mask, ap.ssid.length - mask.length);
+ return result == -1;
+}
+
+function sort(a, b) {
+ return b.signal - a.signal;
+}
+
+function encode(ap) {
+ return { macAddress: ap.mac, signalStrength: ap.signal };
+}
+
+/**
+ * Shared utility functions for modules dealing with
+ * Location Services.
+ */
+
+export class LocationHelper {
+ static formatWifiAccessPoints(accessPoints) {
+ return accessPoints.filter(isPublic).sort(sort).map(encode);
+ }
+
+ /**
+ * Calculate the distance between 2 points using the Haversine formula.
+ * https://en.wikipedia.org/wiki/Haversine_formula
+ */
+ static distance(p1, p2) {
+ let rad = x => (x * Math.PI) / 180;
+ // Radius of the earth.
+ let R = 6371e3;
+ let lat = rad(p2.lat - p1.lat);
+ let lng = rad(p2.lng - p1.lng);
+
+ let a =
+ Math.sin(lat / 2) * Math.sin(lat / 2) +
+ Math.cos(rad(p1.lat)) *
+ Math.cos(rad(p2.lat)) *
+ Math.sin(lng / 2) *
+ Math.sin(lng / 2);
+
+ let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return R * c;
+ }
+}
diff --git a/dom/base/MaybeCrossOriginObject.cpp b/dom/base/MaybeCrossOriginObject.cpp
new file mode 100644
index 0000000000..5fdcf785f6
--- /dev/null
+++ b/dom/base/MaybeCrossOriginObject.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MaybeCrossOriginObject.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/dom/RemoteObjectProxy.h"
+#include "js/CallAndConstruct.h" // JS::Call
+#include "js/friend/WindowProxy.h" // js::IsWindowProxy
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperties
+#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
+#include "js/Proxy.h"
+#include "js/RootingAPI.h"
+#include "js/WeakMap.h"
+#include "js/Wrapper.h"
+#include "jsfriendapi.h"
+#include "AccessCheck.h"
+#include "nsContentUtils.h"
+
+#ifdef DEBUG
+static bool IsLocation(JSObject* obj) {
+ return strcmp(JS::GetClass(obj)->name, "Location") == 0;
+}
+#endif // DEBUG
+
+namespace mozilla::dom {
+
+/* static */
+bool MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(JSContext* cx,
+ JSObject* obj) {
+ MOZ_ASSERT(!js::IsCrossCompartmentWrapper(obj));
+ // WindowProxy and Window must always be same-Realm, so we can do
+ // our IsPlatformObjectSameOrigin check against either one. But verify that
+ // in case we have a WindowProxy the right things happen.
+ MOZ_ASSERT(js::GetNonCCWObjectRealm(obj) ==
+ // "true" for second arg means to unwrap WindowProxy to
+ // get at the Window.
+ js::GetNonCCWObjectRealm(js::UncheckedUnwrap(obj, true)),
+ "WindowProxy not same-Realm as Window?");
+
+ BasePrincipal* subjectPrincipal =
+ BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
+ BasePrincipal* objectPrincipal =
+ BasePrincipal::Cast(nsContentUtils::ObjectPrincipal(obj));
+
+ // The spec effectively has an EqualsConsideringDomain check here,
+ // because the spec has no concept of asymmetric security
+ // relationships. But we shouldn't ever end up here in the
+ // asymmetric case anyway: That case should end up with Xrays, which
+ // don't call into this code.
+ //
+ // Let's assert that EqualsConsideringDomain and
+ // SubsumesConsideringDomain give the same results and use
+ // EqualsConsideringDomain for the check we actually do, since it's
+ // stricter and more closely matches the spec.
+ //
+ // That said, if the (not very well named)
+ // OriginAttributes::IsRestrictOpenerAccessForFPI() method returns
+ // false, we want to use FastSubsumesConsideringDomainIgnoringFPD
+ // instead of FastEqualsConsideringDomain, because in that case we
+ // still want to treat things which are in different first-party
+ // contexts as same-origin.
+ MOZ_ASSERT(
+ subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal) ==
+ subjectPrincipal->FastSubsumesConsideringDomain(objectPrincipal),
+ "Why are we in an asymmetric case here?");
+ if (OriginAttributes::IsRestrictOpenerAccessForFPI()) {
+ return subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal);
+ }
+
+ return subjectPrincipal->FastSubsumesConsideringDomainIgnoringFPD(
+ objectPrincipal) &&
+ objectPrincipal->FastSubsumesConsideringDomainIgnoringFPD(
+ subjectPrincipal);
+}
+
+bool MaybeCrossOriginObjectMixins::CrossOriginGetOwnPropertyHelper(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const {
+ MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+ "Why did we get called?");
+ // First check for an IDL-defined cross-origin property with the given name.
+ // This corresponds to
+ // https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
+ // step 2.
+ JS::Rooted<JSObject*> holder(cx);
+ if (!EnsureHolder(cx, obj, &holder)) {
+ return false;
+ }
+
+ return JS_GetOwnPropertyDescriptorById(cx, holder, id, desc);
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginPropertyFallback(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) {
+ MOZ_ASSERT(desc.isNothing(), "Why are we being called?");
+
+ // Step 1.
+ if (xpc::IsCrossOriginWhitelistedProp(cx, id)) {
+ // Spec says to return PropertyDescriptor {
+ // [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
+ // [[Configurable]]: true
+ // }.
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::UndefinedValue(), {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+
+ // Step 2.
+ return ReportCrossOriginDenial(cx, id, "access"_ns);
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginGet(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) {
+ // This is fairly similar to BaseProxyHandler::get, but there are some
+ // differences. Most importantly, we want to throw if we have a descriptor
+ // with no getter, while BaseProxyHandler::get returns undefined. The other
+ // big difference is that we don't have to worry about prototypes (ours is
+ // always null).
+
+ // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
+ // compartment, because for the proxies we have here [[GetOwnProperty]] will
+ // do security checks based on the current Realm. Unfortunately,
+ // JS_GetPropertyDescriptorById asserts that compartments match. Luckily, we
+ // know that "obj" is a proxy here, so we can directly call its
+ // getOwnPropertyDescriptor() hook.
+ //
+ // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
+ // the handler and call its getOwnPropertyDescriptor hook directly.
+ MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
+ MOZ_ASSERT(
+ js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
+ "Unexpected proxy");
+ MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+ "Why did we get called?");
+ js::AssertSameCompartment(cx, receiver);
+
+ // Step 1.
+ JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
+ return false;
+ }
+
+ // Step 2.
+ MOZ_ASSERT(desc.isSome(),
+ "Callees should throw in all cases when they are not finding a "
+ "property decriptor");
+ desc->assertComplete();
+
+ // Step 3.
+ if (desc->isDataDescriptor()) {
+ vp.set(desc->value());
+ return true;
+ }
+
+ // Step 4.
+ MOZ_ASSERT(desc->isAccessorDescriptor());
+
+ // Step 5.
+ JS::Rooted<JSObject*> getter(cx);
+ if (!desc->hasGetter() || !(getter = desc->getter())) {
+ // Step 6.
+ return ReportCrossOriginDenial(cx, id, "get"_ns);
+ }
+
+ // Step 7.
+ return JS::Call(cx, receiver, getter, JS::HandleValueArray::empty(), vp);
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::CrossOriginSet(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) {
+ // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
+ // compartment, because for the proxies we have here [[GetOwnProperty]] will
+ // do security checks based on the current Realm. Unfortunately,
+ // JS_GetPropertyDescriptorById asserts that compartments match. Luckily, we
+ // know that "obj" is a proxy here, so we can directly call its
+ // getOwnPropertyDescriptor() hook.
+ //
+ // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
+ // the handler and call its getOwnPropertyDescriptor hook directly.
+ MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
+ MOZ_ASSERT(
+ js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
+ "Unexpected proxy");
+ MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+ "Why did we get called?");
+ js::AssertSameCompartment(cx, receiver);
+ js::AssertSameCompartment(cx, v);
+
+ // Step 1.
+ JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
+ return false;
+ }
+
+ // Step 2.
+ MOZ_ASSERT(desc.isSome(),
+ "Callees should throw in all cases when they are not finding a "
+ "property decriptor");
+ desc->assertComplete();
+
+ // Step 3.
+ JS::Rooted<JSObject*> setter(cx);
+ if (desc->hasSetter() && (setter = desc->setter())) {
+ JS::Rooted<JS::Value> ignored(cx);
+ // Step 3.1.
+ if (!JS::Call(cx, receiver, setter, JS::HandleValueArray(v), &ignored)) {
+ return false;
+ }
+
+ // Step 3.2.
+ return result.succeed();
+ }
+
+ // Step 4.
+ return ReportCrossOriginDenial(cx, id, "set"_ns);
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::EnsureHolder(
+ JSContext* cx, JS::Handle<JSObject*> obj, size_t slot,
+ const CrossOriginProperties& properties,
+ JS::MutableHandle<JSObject*> holder) {
+ MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
+ "Why are we calling this at all in same-origin cases?");
+ // We store the holders in a weakmap stored in obj's slot. Our object is
+ // always a proxy, so we can just go ahead and use GetProxyReservedSlot here.
+ JS::Rooted<JS::Value> weakMapVal(cx, js::GetProxyReservedSlot(obj, slot));
+ if (weakMapVal.isUndefined()) {
+ // Enter the Realm of "obj" when we allocate the WeakMap, since we are going
+ // to store it in a slot on "obj" and in general we may not be
+ // same-compartment with "obj" here.
+ JSAutoRealm ar(cx, obj);
+ JSObject* newMap = JS::NewWeakMapObject(cx);
+ if (!newMap) {
+ return false;
+ }
+ weakMapVal.setObject(*newMap);
+ js::SetProxyReservedSlot(obj, slot, weakMapVal);
+ }
+ MOZ_ASSERT(weakMapVal.isObject(),
+ "How did a non-object else end up in this slot?");
+
+ JS::Rooted<JSObject*> map(cx, &weakMapVal.toObject());
+ MOZ_ASSERT(JS::IsWeakMapObject(map),
+ "How did something else end up in this slot?");
+
+ // We need to be in "map"'s compartment to work with it. Per spec, the key
+ // for this map is supposed to be the pair (current settings, relevant
+ // settings). The current settings corresponds to the current Realm of cx.
+ // The relevant settings corresponds to the Realm of "obj", but since all of
+ // our objects are per-Realm singletons, we are basically using "obj" itself
+ // as part of the key.
+ //
+ // To represent the current settings, we use a dedicated key object of the
+ // current-Realm.
+ //
+ // We can't use the current global, because we can't get a useful
+ // cross-compartment wrapper for it; such wrappers would always go
+ // through a WindowProxy and would not be guarantee to keep pointing to a
+ // single Realm when unwrapped. We want to grab this key before we start
+ // changing Realms.
+ //
+ // Also we can't use arbitrary object (e.g.: Object.prototype), because at
+ // this point those compartments are not same-origin, and don't have access to
+ // each other, and the object retrieved here will be wrapped by a security
+ // wrapper below, and the wrapper will be stored into the cache
+ // (see Compartment::wrap). Those compartments can get access later by
+ // modifying `document.domain`, and wrapping objects after that point
+ // shouldn't result in a security wrapper. Wrap operation looks up the
+ // existing wrapper in the cache, that contains the security wrapper created
+ // here. We should use unique/private object here, so that this doesn't
+ // affect later wrap operation.
+ JS::Rooted<JSObject*> key(cx, JS::GetRealmKeyObject(cx));
+ if (!key) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> holderVal(cx);
+ { // Scope for working with the map
+ JSAutoRealm ar(cx, map);
+ if (!MaybeWrapObject(cx, &key)) {
+ return false;
+ }
+
+ if (!JS::GetWeakMapEntry(cx, map, key, &holderVal)) {
+ return false;
+ }
+ }
+
+ if (holderVal.isObject()) {
+ // We want to do an unchecked unwrap, because the holder (and the current
+ // caller) may actually be more privileged than our map.
+ holder.set(js::UncheckedUnwrap(&holderVal.toObject()));
+
+ // holder might be a dead object proxy if things got nuked.
+ if (!JS_IsDeadWrapper(holder)) {
+ MOZ_ASSERT(js::GetContextRealm(cx) == js::GetNonCCWObjectRealm(holder),
+ "How did we end up with a key/value mismatch?");
+ return true;
+ }
+ }
+
+ // We didn't find a usable holder. Go ahead and allocate one. At this point
+ // we have two options: we could allocate the holder in the current Realm and
+ // store a cross-compartment wrapper for it in the map as needed, or we could
+ // allocate the holder in the Realm of the map and have it hold
+ // cross-compartment references to all the methods it holds, since those
+ // methods need to be in our current Realm. It seems better to allocate the
+ // holder in our current Realm.
+ bool isChrome = xpc::AccessCheck::isChrome(js::GetContextRealm(cx));
+ holder.set(JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
+ if (!holder || !JS_DefineProperties(cx, holder, properties.mAttributes) ||
+ !JS_DefineFunctions(cx, holder, properties.mMethods) ||
+ (isChrome && properties.mChromeOnlyAttributes &&
+ !JS_DefineProperties(cx, holder, properties.mChromeOnlyAttributes)) ||
+ (isChrome && properties.mChromeOnlyMethods &&
+ !JS_DefineFunctions(cx, holder, properties.mChromeOnlyMethods))) {
+ return false;
+ }
+
+ holderVal.setObject(*holder);
+ { // Scope for working with the map
+ JSAutoRealm ar(cx, map);
+
+ // Key is already in the right Realm, but we need to wrap the value.
+ if (!MaybeWrapValue(cx, &holderVal)) {
+ return false;
+ }
+
+ if (!JS::SetWeakMapEntry(cx, map, key, holderVal)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool MaybeCrossOriginObjectMixins::ReportCrossOriginDenial(
+ JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
+ xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
+ return false;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::getPrototype(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> protop) const {
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ protop.set(nullptr);
+ return true;
+ }
+
+ { // Scope for JSAutoRealm
+ JSAutoRealm ar(cx, proxy);
+ protop.set(getSameOriginPrototype(cx));
+ if (!protop) {
+ return false;
+ }
+ }
+
+ return MaybeWrapObject(cx, protop);
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::setPrototype(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> proto,
+ JS::ObjectOpResult& result) const {
+ // Inlined version of
+ // https://tc39.github.io/ecma262/#sec-set-immutable-prototype
+ js::AssertSameCompartment(cx, proto);
+
+ // We have to be careful how we get the prototype. In particular, we do _NOT_
+ // want to enter the Realm of "proxy" to do that, in case we're not
+ // same-origin with it here.
+ JS::Rooted<JSObject*> wrappedProxy(cx, proxy);
+ if (!MaybeWrapObject(cx, &wrappedProxy)) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> currentProto(cx);
+ if (!js::GetObjectProto(cx, wrappedProxy, &currentProto)) {
+ return false;
+ }
+
+ if (currentProto != proto) {
+ return result.failCantSetProto();
+ }
+
+ return result.succeed();
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::getPrototypeIfOrdinary(
+ JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
+ JS::MutableHandle<JSObject*> protop) const {
+ // We have a custom [[GetPrototypeOf]]
+ *isOrdinary = false;
+ return true;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::setImmutablePrototype(
+ JSContext* cx, JS::Handle<JSObject*> proxy, bool* succeeded) const {
+ // We just want to disallow this.
+ *succeeded = false;
+ return true;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::isExtensible(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ bool* extensible) const {
+ // We never allow [[PreventExtensions]] to succeed.
+ *extensible = true;
+ return true;
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::preventExtensions(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const {
+ return result.failCantPreventExtensions();
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::defineProperty(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return ReportCrossOriginDenial(cx, id, "define"_ns);
+ }
+
+ // Enter the Realm of proxy and do the remaining work in there.
+ JSAutoRealm ar(cx, proxy);
+ JS::Rooted<JS::PropertyDescriptor> descCopy(cx, desc);
+ if (!JS_WrapPropertyDescriptor(cx, &descCopy)) {
+ return false;
+ }
+
+ JS_MarkCrossZoneId(cx, id);
+
+ return definePropertySameOrigin(cx, proxy, id, descCopy, result);
+}
+
+template <typename Base>
+bool MaybeCrossOriginObject<Base>::enumerate(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const {
+ // Just get the property keys from ourselves, in whatever Realm we happen to
+ // be in. It's important to not enter the Realm of "proxy" here, because that
+ // would affect the list of keys we claim to have. We wrap the proxy in the
+ // current compartment just to be safe; it doesn't affect behavior as far as
+ // CrossOriginObjectWrapper and MaybeCrossOriginObject are concerned.
+ JS::Rooted<JSObject*> self(cx, proxy);
+ if (!MaybeWrapObject(cx, &self)) {
+ return false;
+ }
+
+ return js::GetPropertyKeys(cx, self, 0, props);
+}
+
+// Force instantiations of the out-of-line template methods we need.
+template class MaybeCrossOriginObject<js::Wrapper>;
+template class MaybeCrossOriginObject<DOMProxyHandler>;
+
+} // namespace mozilla::dom
diff --git a/dom/base/MaybeCrossOriginObject.h b/dom/base/MaybeCrossOriginObject.h
new file mode 100644
index 0000000000..5a75e70dce
--- /dev/null
+++ b/dom/base/MaybeCrossOriginObject.h
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MaybeCrossOriginObject_h
+#define mozilla_dom_MaybeCrossOriginObject_h
+
+/**
+ * Shared infrastructure for WindowProxy and Location objects. These
+ * are the objects that can be accessed cross-origin in the HTML
+ * specification.
+ *
+ * This class can be inherited from by the relevant proxy handlers to
+ * help implement spec algorithms.
+ *
+ * The algorithms this class implements come from
+ * <https://html.spec.whatwg.org/multipage/browsers.html#shared-abstract-operations>,
+ * <https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object>,
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#the-location-interface>.
+ *
+ * The class is templated on its base so we can directly implement the things
+ * that should have identical implementations for WindowProxy and Location. The
+ * templating is needed because WindowProxy needs to be a wrapper and Location
+ * shouldn't be one.
+ */
+
+#include "js/Class.h"
+#include "js/TypeDecls.h"
+#include "nsStringFwd.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla::dom {
+
+/**
+ * "mAttributes" and "mMethods" are the cross-origin attributes and methods we
+ * care about, which should get defined on holders.
+ *
+ * "mChromeOnlyAttributes" and "mChromeOnlyMethods" are the cross-origin
+ * attributes and methods we care about, which should get defined on holders
+ * for the chrome realm, in addition to the properties that are in
+ * "mAttributes" and "mMethods".
+ */
+struct CrossOriginProperties {
+ const JSPropertySpec* mAttributes;
+ const JSFunctionSpec* mMethods;
+ const JSPropertySpec* mChromeOnlyAttributes;
+ const JSFunctionSpec* mChromeOnlyMethods;
+};
+
+// Methods that MaybeCrossOriginObject wants that do not depend on the "Base"
+// template parameter. We can avoid having multiple instantiations of them by
+// pulling them out into this helper class.
+class MaybeCrossOriginObjectMixins {
+ public:
+ /**
+ * Implementation of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#isplatformobjectsameorigin-(-o-)>.
+ * "cx" and "obj" may or may not be same-compartment and even when
+ * same-compartment may not be same-Realm. "obj" can be a WindowProxy, a
+ * Window, or a Location.
+ */
+ static bool IsPlatformObjectSameOrigin(JSContext* cx, JSObject* obj);
+
+ protected:
+ /**
+ * Implementation of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)>.
+ *
+ * "cx" and "obj" are expected to be different-Realm here, and may be
+ * different-compartment. "obj" can be a "WindowProxy" or a "Location" or a
+ * cross-process proxy for one of those.
+ */
+ bool CrossOriginGetOwnPropertyHelper(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const;
+
+ /**
+ * Implementation of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginpropertyfallback-(-p-)>.
+ *
+ * This should be called at the end of getOwnPropertyDescriptor
+ * methods in the cross-origin case.
+ *
+ * "cx" and "obj" are expected to be different-Realm here, and may
+ * be different-compartment. "obj" can be a "WindowProxy" or a
+ * "Location" or a cross-process proxy for one of those.
+ */
+ static bool CrossOriginPropertyFallback(
+ JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc);
+
+ /**
+ * Implementation of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginget-(-o,-p,-receiver-)>.
+ *
+ * "cx" and "obj" are expected to be different-Realm here and may be
+ * different-compartment. "obj" can be a "WindowProxy" or a
+ * "Location" or a cross-process proxy for one of those.
+ *
+ * "receiver" will be in the compartment of "cx". The return value will
+ * be in the compartment of "cx".
+ */
+ static bool CrossOriginGet(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp);
+
+ /**
+ * Implementation of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-)>.
+ *
+ * "cx" and "obj" are expected to be different-Realm here and may be
+ * different-compartment. "obj" can be a "WindowProxy" or a
+ * "Location" or a cross-process proxy for one of those.
+ *
+ * "receiver" and "v" will be in the compartment of "cx".
+ */
+ static bool CrossOriginSet(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result);
+
+ /**
+ * Utility method to ensure a holder for cross-origin properties for the
+ * current global of the JSContext.
+ *
+ * When this is called, "cx" and "obj" are _always_ different-Realm, because
+ * this is only used in cross-origin situations. The "holder" return value is
+ * always in the Realm of "cx".
+ *
+ * "obj" is the object which has space to store the collection of holders in
+ * the given slot.
+ *
+ * "properties" are the cross-origin attributes and methods we care about,
+ * which should get defined on holders.
+ */
+ static bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> obj,
+ size_t slot, const CrossOriginProperties& properties,
+ JS::MutableHandle<JSObject*> holder);
+
+ /**
+ * Ensures we have a holder object for the current Realm. When this is
+ * called, "obj" is guaranteed to not be same-Realm with "cx", because this
+ * is only used for cross-origin cases.
+ *
+ * Subclasses are expected to implement this by calling our static
+ * EnsureHolder with the appropriate arguments.
+ */
+ virtual bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> holder) const = 0;
+
+ /**
+ * Report a cross-origin denial for a property named by aId. Always
+ * returns false, so it can be used as "return
+ * ReportCrossOriginDenial(...);".
+ */
+ static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
+ const nsACString& aAccessType);
+};
+
+// A proxy handler for objects that may be cross-origin objects. Whether they
+// actually _are_ cross-origin objects can change dynamically if document.domain
+// is set.
+template <typename Base>
+class MaybeCrossOriginObject : public Base,
+ public MaybeCrossOriginObjectMixins {
+ protected:
+ template <typename... Args>
+ constexpr MaybeCrossOriginObject(Args&&... aArgs)
+ : Base(std::forward<Args>(aArgs)...) {}
+
+ /**
+ * Implementation of [[GetPrototypeOf]] as defined in
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getprototypeof>
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#location-getprototypeof>.
+ *
+ * Our prototype-storage model looks quite different from the spec's, so we
+ * need to implement some hooks that don't directly map to the spec.
+ *
+ * "proxy" is the WindowProxy or Location involved. It may or may not be
+ * same-compartment with cx.
+ *
+ * "protop" is the prototype value (possibly null). It is guaranteed to be
+ * same-compartment with cx after this function returns successfully.
+ */
+ bool getPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> protop) const final;
+
+ /**
+ * Hook for doing the OrdinaryGetPrototypeOf bits that [[GetPrototypeOf]] does
+ * in the spec. Location and WindowProxy store that information somewhat
+ * differently.
+ *
+ * The prototype should come from the Realm of "cx".
+ */
+ virtual JSObject* getSameOriginPrototype(JSContext* cx) const = 0;
+
+ /**
+ * Implementation of [[SetPrototypeOf]] as defined in
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-setprototypeof>
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#location-setprototypeof>.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with "cx".
+ *
+ * "proto" is the new prototype object (possibly null). It must be
+ * same-compartment with "cx".
+ */
+ bool setPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JSObject*> proto,
+ JS::ObjectOpResult& result) const final;
+
+ /**
+ * Our non-standard getPrototypeIfOrdinary hook.
+ */
+ bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* isOrdinary,
+ JS::MutableHandle<JSObject*> protop) const final;
+
+ /**
+ * Our non-standard setImmutablePrototype hook.
+ */
+ bool setImmutablePrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* succeeded) const final;
+
+ /**
+ * Implementation of [[IsExtensible]] as defined in
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-isextensible>
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#location-isextensible>.
+ */
+ bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* extensible) const final;
+
+ /**
+ * Implementation of [[PreventExtensions]] as defined in
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions>
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#location-preventextensions>.
+ */
+ bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const final;
+
+ /**
+ * Implementation of [[GetOwnProperty]] is completely delegated to subclasses.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with cx.
+ */
+ bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override = 0;
+
+ /**
+ * Implementation of [[DefineOwnProperty]] as defined in
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty>
+ * and
+ * <https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty>.
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with cx.
+ *
+ */
+ bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const final;
+
+ /**
+ * Some of our base classes define _another_ virtual defineProperty, and we
+ * get overloaded-virtual warnings as a result due to us hiding it, if we
+ * don't pull it in here.
+ */
+ using Base::defineProperty;
+
+ /**
+ * Hook for handling the same-origin case in defineProperty.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It will be
+ * same-compartment with cx.
+ *
+ * "desc" is a the descriptor being defined. It will be same-compartment with
+ * cx.
+ */
+ virtual bool definePropertySameOrigin(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const = 0;
+
+ /**
+ * Implementation of [[Get]] is completely delegated to subclasses.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with "cx".
+ *
+ * "receiver" is the receiver ("this") for the get. It will be
+ * same-compartment with "cx"
+ *
+ * "vp" is the return value. It will be same-compartment with "cx".
+ */
+ bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override = 0;
+
+ /**
+ * Implementation of [[Set]] is completely delegated to subclasses.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with "cx".
+ *
+ * "v" is the value being set. It will be same-compartment with "cx".
+ *
+ * "receiver" is the receiver ("this") for the set. It will be
+ * same-compartment with "cx".
+ */
+ bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const override = 0;
+
+ /**
+ * Implementation of [[Delete]] is completely delegated to subclasses.
+ *
+ * "proxy" is the WindowProxy or Location object involved. It may or may not
+ * be same-compartment with "cx".
+ */
+ bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const override = 0;
+
+ /**
+ * Spidermonkey-internal hook for enumerating objects.
+ */
+ bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const final;
+
+ // Cross origin objects should not participate in private fields.
+ virtual bool throwOnPrivateField() const override { return true; }
+
+ /**
+ * Spidermonkey-internal hook used by Object.prototype.toString. Subclasses
+ * need to implement this, because we don't know what className they want.
+ * Except in the cross-origin case, when we could maybe handle it...
+ */
+ const char* className(JSContext* cx,
+ JS::Handle<JSObject*> proxy) const override = 0;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_MaybeCrossOriginObject_h */
diff --git a/dom/base/MessageBroadcaster.cpp b/dom/base/MessageBroadcaster.cpp
new file mode 100644
index 0000000000..f7106fa93b
--- /dev/null
+++ b/dom/base/MessageBroadcaster.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MessageBroadcaster.h"
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla::dom {
+
+MessageBroadcaster::MessageBroadcaster(MessageBroadcaster* aParentManager,
+ MessageManagerFlags aFlags)
+ : MessageListenerManager(nullptr, aParentManager,
+ aFlags | MessageManagerFlags::MM_BROADCASTER) {
+ if (aParentManager) {
+ aParentManager->AddChildManager(this);
+ }
+}
+
+void MessageBroadcaster::ReleaseCachedProcesses() {
+ ContentParent::ReleaseCachedProcesses();
+}
+
+void MessageBroadcaster::AddChildManager(MessageListenerManager* aManager) {
+ mChildManagers.AppendElement(aManager);
+
+ RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
+ RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
+
+ LoadPendingScripts(this, aManager);
+}
+
+void MessageBroadcaster::RemoveChildManager(MessageListenerManager* aManager) {
+ mChildManagers.RemoveElement(aManager);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/MessageBroadcaster.h b/dom/base/MessageBroadcaster.h
new file mode 100644
index 0000000000..8ee5cab870
--- /dev/null
+++ b/dom/base/MessageBroadcaster.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MessageBroadcaster_h
+#define mozilla_dom_MessageBroadcaster_h
+
+#include "mozilla/dom/MessageListenerManager.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+/**
+ * Implementation for the WebIDL MessageBroadcaster interface. Base class for
+ * window and process broadcaster message managers.
+ */
+class MessageBroadcaster : public MessageListenerManager {
+ public:
+ static MessageBroadcaster* From(MessageListenerManager* aManager) {
+ if (aManager->IsBroadcaster()) {
+ return static_cast<MessageBroadcaster*>(aManager);
+ }
+ return nullptr;
+ }
+
+ void BroadcastAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ mozilla::ErrorResult& aError) {
+ DispatchAsyncMessage(aCx, aMessageName, aObj, JS::UndefinedHandleValue,
+ aError);
+ }
+ uint32_t ChildCount() { return mChildManagers.Length(); }
+ MessageListenerManager* GetChildAt(uint32_t aIndex) {
+ return mChildManagers.SafeElementAt(aIndex);
+ }
+ void ReleaseCachedProcesses();
+
+ void AddChildManager(MessageListenerManager* aManager);
+ void RemoveChildManager(MessageListenerManager* aManager);
+
+ protected:
+ MessageBroadcaster(MessageBroadcaster* aParentManager,
+ MessageManagerFlags aFlags);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessageBroadcaster_h
diff --git a/dom/base/MessageListenerManager.cpp b/dom/base/MessageListenerManager.cpp
new file mode 100644
index 0000000000..ffdc630830
--- /dev/null
+++ b/dom/base/MessageListenerManager.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MessageListenerManager.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+
+namespace mozilla::dom {
+
+MessageListenerManager::MessageListenerManager(
+ ipc::MessageManagerCallback* aCallback, MessageBroadcaster* aParentManager,
+ ipc::MessageManagerFlags aFlags)
+ : nsFrameMessageManager(aCallback, aFlags),
+ mParentManager(aParentManager) {}
+
+MessageListenerManager::~MessageListenerManager() = default;
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MessageListenerManager)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsFrameMessageManager)
+NS_IMPL_ADDREF_INHERITED(MessageListenerManager, nsFrameMessageManager)
+NS_IMPL_RELEASE_INHERITED(MessageListenerManager, nsFrameMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MessageListenerManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessageListenerManager,
+ nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MessageListenerManager,
+ nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessageListenerManager,
+ nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentManager)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+void MessageListenerManager::ClearParentManager(bool aRemove) {
+ if (aRemove && mParentManager) {
+ mParentManager->RemoveChildManager(this);
+ }
+ mParentManager = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/MessageListenerManager.h b/dom/base/MessageListenerManager.h
new file mode 100644
index 0000000000..bc1043222c
--- /dev/null
+++ b/dom/base/MessageListenerManager.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MessageListenerManager_h
+#define mozilla_dom_MessageListenerManager_h
+
+#include "nsCycleCollectionNoteChild.h"
+#include "nsFrameMessageManager.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class MessageBroadcaster;
+
+/**
+ * Implementation for the WebIDL MessageListenerManager interface. Base class
+ * for message managers that are exposed to script.
+ */
+class MessageListenerManager : public nsFrameMessageManager,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MessageListenerManager,
+ nsFrameMessageManager)
+
+ MessageBroadcaster* GetParentObject() { return mParentManager; }
+
+ virtual MessageBroadcaster* GetParentManager() override {
+ return mParentManager;
+ }
+
+ /**
+ * If aRemove is true then RemoveChildManager(this) will be called on the
+ * parent manager first.
+ */
+ virtual void ClearParentManager(bool aRemove) override;
+
+ protected:
+ MessageListenerManager(ipc::MessageManagerCallback* aCallback,
+ MessageBroadcaster* aParentManager,
+ MessageManagerFlags aFlags);
+ virtual ~MessageListenerManager();
+
+ RefPtr<MessageBroadcaster> mParentManager;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessageListenerManager_h
diff --git a/dom/base/MessageManagerCallback.h b/dom/base/MessageManagerCallback.h
new file mode 100644
index 0000000000..c2d0750f6e
--- /dev/null
+++ b/dom/base/MessageManagerCallback.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_MessageManagerCallback_h__
+#define dom_base_MessageManagerCallback_h__
+
+#include "nsError.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class ClonedMessageData;
+class ContentChild;
+class ContentParent;
+class ProcessMessageManager;
+
+namespace ipc {
+
+class StructuredCloneData;
+
+class MessageManagerCallback {
+ public:
+ virtual ~MessageManagerCallback() = default;
+
+ virtual bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) {
+ return true;
+ }
+
+ virtual bool DoSendBlockingMessage(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) {
+ return true;
+ }
+
+ virtual nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) {
+ return NS_OK;
+ }
+
+ virtual mozilla::dom::ProcessMessageManager* GetProcessMessageManager()
+ const {
+ return nullptr;
+ }
+
+ virtual void DoGetRemoteType(nsACString& aRemoteType,
+ ErrorResult& aError) const;
+
+ protected:
+ bool BuildClonedMessageData(StructuredCloneData& aData,
+ ClonedMessageData& aClonedData);
+};
+
+void UnpackClonedMessageData(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData);
+
+} // namespace ipc
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/MessageManagerGlobal.cpp b/dom/base/MessageManagerGlobal.cpp
new file mode 100644
index 0000000000..7ddf9fa11c
--- /dev/null
+++ b/dom/base/MessageManagerGlobal.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MessageManagerGlobal.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/Logging.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+namespace mozilla::dom {
+
+void MessageManagerGlobal::Dump(const nsAString& aStr) {
+ if (!nsJSUtils::DumpEnabled()) {
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 cStr(aStr);
+ MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug,
+ ("[MessageManager.Dump] %s", cStr.get()));
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", cStr.get());
+#endif
+#ifdef XP_WIN
+ if (IsDebuggerPresent()) {
+ OutputDebugStringW(PromiseFlatString(aStr).get());
+ }
+#endif
+ fputs(cStr.get(), stdout);
+ fflush(stdout);
+}
+
+void MessageManagerGlobal::Atob(const nsAString& aAsciiString,
+ nsAString& aBase64Data, ErrorResult& aError) {
+ aError = nsContentUtils::Atob(aAsciiString, aBase64Data);
+}
+
+void MessageManagerGlobal::Btoa(const nsAString& aBase64Data,
+ nsAString& aAsciiString, ErrorResult& aError) {
+ aError = nsContentUtils::Btoa(aBase64Data, aAsciiString);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/MessageManagerGlobal.h b/dom/base/MessageManagerGlobal.h
new file mode 100644
index 0000000000..e1262efa69
--- /dev/null
+++ b/dom/base/MessageManagerGlobal.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MessageManagerGlobal_h
+#define mozilla_dom_MessageManagerGlobal_h
+
+#include "nsFrameMessageManager.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla::dom {
+
+/**
+ * Base class for implementing the WebIDL MessageManagerGlobal class.
+ */
+class MessageManagerGlobal {
+ public:
+ // MessageListenerManager
+ void AddMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener, bool aListenWhenClosed,
+ ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->AddMessageListener(aMessageName, aListener,
+ aListenWhenClosed, aError);
+ }
+ void RemoveMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener, ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->RemoveMessageListener(aMessageName, aListener, aError);
+ }
+ void AddWeakMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener, ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->AddWeakMessageListener(aMessageName, aListener, aError);
+ }
+ void RemoveWeakMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener,
+ ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->RemoveWeakMessageListener(aMessageName, aListener, aError);
+ }
+
+ // MessageSender
+ void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ JS::Handle<JS::Value> aTransfers, ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->SendAsyncMessage(aCx, aMessageName, aObj, aTransfers,
+ aError);
+ }
+ already_AddRefed<ProcessMessageManager> GetProcessMessageManager(
+ mozilla::ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ return mMessageManager->GetProcessMessageManager(aError);
+ }
+
+ void GetRemoteType(nsACString& aRemoteType, mozilla::ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->GetRemoteType(aRemoteType, aError);
+ }
+
+ // SyncMessageSender
+ void SendSyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj, nsTArray<JS::Value>& aResult,
+ ErrorResult& aError) {
+ if (!mMessageManager) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ mMessageManager->SendSyncMessage(aCx, aMessageName, aObj, aResult, aError);
+ }
+
+ // MessageManagerGlobal
+ void Dump(const nsAString& aStr);
+ void Atob(const nsAString& aAsciiString, nsAString& aBase64Data,
+ ErrorResult& aError);
+ void Btoa(const nsAString& aBase64Data, nsAString& aAsciiString,
+ ErrorResult& aError);
+
+ void MarkForCC() {
+ if (mMessageManager) {
+ mMessageManager->MarkForCC();
+ }
+ }
+
+ protected:
+ explicit MessageManagerGlobal(nsFrameMessageManager* aMessageManager)
+ : mMessageManager(aMessageManager) {}
+
+ RefPtr<nsFrameMessageManager> mMessageManager;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessageManagerGlobal_h
diff --git a/dom/base/MessageSender.cpp b/dom/base/MessageSender.cpp
new file mode 100644
index 0000000000..0d97f9c3ed
--- /dev/null
+++ b/dom/base/MessageSender.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MessageSender.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+
+namespace mozilla::dom {
+
+void MessageSender::InitWithCallback(ipc::MessageManagerCallback* aCallback) {
+ if (mCallback) {
+ // Initialization should only happen once.
+ return;
+ }
+
+ SetCallback(aCallback);
+
+ // First load parent scripts by adding this to parent manager.
+ if (mParentManager) {
+ mParentManager->AddChildManager(this);
+ }
+
+ for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+ LoadScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i],
+ IgnoreErrors());
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/MessageSender.h b/dom/base/MessageSender.h
new file mode 100644
index 0000000000..51fd2979fe
--- /dev/null
+++ b/dom/base/MessageSender.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MessageSender_h
+#define mozilla_dom_MessageSender_h
+
+#include "mozilla/dom/MessageListenerManager.h"
+
+namespace mozilla::dom {
+
+class MessageBroadcaster;
+
+/**
+ * Implementation for the WebIDL MessageSender interface. Base class for frame
+ * and child process message managers.
+ */
+class MessageSender : public MessageListenerManager {
+ public:
+ void InitWithCallback(ipc::MessageManagerCallback* aCallback);
+
+ protected:
+ MessageSender(ipc::MessageManagerCallback* aCallback,
+ MessageBroadcaster* aParentManager, MessageManagerFlags aFlags)
+ : MessageListenerManager(aCallback, aParentManager, aFlags) {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessageSender_h
diff --git a/dom/base/MimeType.cpp b/dom/base/MimeType.cpp
new file mode 100644
index 0000000000..d9defe3b57
--- /dev/null
+++ b/dom/base/MimeType.cpp
@@ -0,0 +1,347 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MimeType.h"
+#include "nsUnicharUtils.h"
+
+namespace {
+template <typename Char>
+constexpr bool IsHTTPTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+ c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+ c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
+ mozilla::IsAsciiAlphanumeric(c);
+}
+
+template <typename Char>
+constexpr bool IsHTTPQuotedStringTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c);
+}
+
+template <typename Char>
+constexpr bool IsHTTPWhitespace(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || c == 0xA || c == 0xD || c == 0x20;
+}
+} // namespace
+
+template <typename char_type>
+/* static */ mozilla::UniquePtr<TMimeType<char_type>>
+TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
+ // See https://mimesniff.spec.whatwg.org/#parsing-a-mime-type
+
+ // Steps 1-2
+ const char_type* pos = aMimeType.BeginReading();
+ const char_type* end = aMimeType.EndReading();
+ while (pos < end && IsHTTPWhitespace(*pos)) {
+ ++pos;
+ }
+ if (pos == end) {
+ return nullptr;
+ }
+ while (end > pos && IsHTTPWhitespace(*(end - 1))) {
+ --end;
+ }
+
+ // Steps 3-4
+ const char_type* typeStart = pos;
+ while (pos < end && *pos != '/') {
+ if (!IsHTTPTokenPoint(*pos)) {
+ return nullptr;
+ }
+ ++pos;
+ }
+ const char_type* typeEnd = pos;
+ if (typeStart == typeEnd) {
+ return nullptr;
+ }
+
+ // Step 5
+ if (pos == end) {
+ return nullptr;
+ }
+
+ // Step 6
+ ++pos;
+
+ // Step 7-9
+ const char_type* subtypeStart = pos;
+ const char_type* subtypeEnd = nullptr;
+ while (pos < end && *pos != ';') {
+ if (!IsHTTPTokenPoint(*pos)) {
+ // If we hit a whitespace, check that the rest of
+ // the subtype is whitespace, otherwise fail.
+ if (IsHTTPWhitespace(*pos)) {
+ subtypeEnd = pos;
+ ++pos;
+ while (pos < end && *pos != ';') {
+ if (!IsHTTPWhitespace(*pos)) {
+ return nullptr;
+ }
+ ++pos;
+ }
+ break;
+ }
+ return nullptr;
+ }
+ ++pos;
+ }
+ if (subtypeEnd == nullptr) {
+ subtypeEnd = pos;
+ }
+ if (subtypeStart == subtypeEnd) {
+ return nullptr;
+ }
+
+ // Step 10
+ nsTString<char_type> type;
+ nsTString<char_type> subtype;
+ for (const char_type* c = typeStart; c < typeEnd; ++c) {
+ type.Append(ToLowerCaseASCII(*c));
+ }
+ for (const char_type* c = subtypeStart; c < subtypeEnd; ++c) {
+ subtype.Append(ToLowerCaseASCII(*c));
+ }
+ mozilla::UniquePtr<TMimeType<char_type>> mimeType(
+ mozilla::MakeUnique<TMimeType<char_type>>(type, subtype));
+
+ // Step 11
+ while (pos < end) {
+ // Step 11.1
+ ++pos;
+
+ // Step 11.2
+ while (pos < end && IsHTTPWhitespace(*pos)) {
+ ++pos;
+ }
+
+ // Steps 11.3 and 11.4
+ nsTString<char_type> paramName;
+ bool paramNameHadInvalidChars = false;
+ while (pos < end && *pos != ';' && *pos != '=') {
+ if (!IsHTTPTokenPoint(*pos)) {
+ paramNameHadInvalidChars = true;
+ }
+ paramName.Append(ToLowerCaseASCII(*pos));
+ ++pos;
+ }
+
+ // Step 11.5
+ if (pos < end) {
+ if (*pos == ';') {
+ continue;
+ }
+ ++pos;
+ }
+
+ // Step 11.6
+ if (pos == end) {
+ break;
+ }
+
+ // Step 11.7
+ ParameterValue paramValue;
+ bool paramValueHadInvalidChars = false;
+
+ // Step 11.8
+ if (*pos == '"') {
+ // Step 11.8.1
+ ++pos;
+
+ // Step 11.8.2
+ while (true) {
+ // Step 11.8.2.1
+ while (pos < end && *pos != '"' && *pos != '\\') {
+ if (!IsHTTPQuotedStringTokenPoint(*pos)) {
+ paramValueHadInvalidChars = true;
+ }
+ if (!IsHTTPTokenPoint(*pos)) {
+ paramValue.mRequiresQuoting = true;
+ }
+ paramValue.Append(*pos);
+ ++pos;
+ }
+
+ // Step 11.8.2.2
+ if (pos < end && *pos == '\\') {
+ // Step 11.8.2.2.1
+ ++pos;
+
+ // Step 11.8.2.2.2
+ if (pos < end) {
+ if (!IsHTTPQuotedStringTokenPoint(*pos)) {
+ paramValueHadInvalidChars = true;
+ }
+ if (!IsHTTPTokenPoint(*pos)) {
+ paramValue.mRequiresQuoting = true;
+ }
+ paramValue.Append(*pos);
+ ++pos;
+ continue;
+ }
+
+ // Step 11.8.2.2.3
+ paramValue.Append('\\');
+ paramValue.mRequiresQuoting = true;
+ }
+
+ // Step 11.8.2.3
+ break;
+ }
+
+ // Step 11.8.3
+ while (pos < end && *pos != ';') {
+ ++pos;
+ }
+
+ // Step 11.9
+ } else {
+ // Step 11.9.1
+ const char_type* paramValueStart = pos;
+ while (pos < end && *pos != ';') {
+ ++pos;
+ }
+
+ // Step 11.9.2
+ const char_type* paramValueLastChar = pos - 1;
+ while (paramValueLastChar >= paramValueStart &&
+ IsHTTPWhitespace(*paramValueLastChar)) {
+ --paramValueLastChar;
+ }
+
+ // Step 11.9.3
+ if (paramValueStart > paramValueLastChar) {
+ continue;
+ }
+
+ for (const char_type* c = paramValueStart; c <= paramValueLastChar; ++c) {
+ if (!IsHTTPQuotedStringTokenPoint(*c)) {
+ paramValueHadInvalidChars = true;
+ }
+ if (!IsHTTPTokenPoint(*c)) {
+ paramValue.mRequiresQuoting = true;
+ }
+ paramValue.Append(*c);
+ }
+ }
+
+ // Step 11.10
+ if (!paramName.IsEmpty() && !paramNameHadInvalidChars &&
+ !paramValueHadInvalidChars) {
+ // XXX Is the assigned value used anywhere?
+ paramValue = mimeType->mParameters.LookupOrInsertWith(paramName, [&] {
+ mimeType->mParameterNames.AppendElement(paramName);
+ return paramValue;
+ });
+ }
+ }
+
+ // Step 12
+ return mimeType;
+}
+
+template <typename char_type>
+void TMimeType<char_type>::Serialize(nsTSubstring<char_type>& aOutput) const {
+ aOutput.Assign(mType);
+ aOutput.AppendLiteral("/");
+ aOutput.Append(mSubtype);
+ for (uint32_t i = 0; i < mParameterNames.Length(); i++) {
+ auto name = mParameterNames[i];
+ aOutput.AppendLiteral(";");
+ aOutput.Append(name);
+ aOutput.AppendLiteral("=");
+ GetParameterValue(name, aOutput, true);
+ }
+}
+
+template <typename char_type>
+void TMimeType<char_type>::GetFullType(nsTSubstring<char_type>& aOutput) const {
+ aOutput.Assign(mType);
+ aOutput.AppendLiteral("/");
+ aOutput.Append(mSubtype);
+}
+
+template <typename char_type>
+bool TMimeType<char_type>::HasParameter(
+ const nsTSubstring<char_type>& aName) const {
+ return mParameters.Get(aName, nullptr);
+}
+
+template <typename char_type>
+bool TMimeType<char_type>::GetParameterValue(
+ const nsTSubstring<char_type>& aName, nsTSubstring<char_type>& aOutput,
+ bool aAppend) const {
+ if (!aAppend) {
+ aOutput.Truncate();
+ }
+
+ ParameterValue value;
+ if (!mParameters.Get(aName, &value)) {
+ return false;
+ }
+
+ if (value.mRequiresQuoting || value.IsEmpty()) {
+ aOutput.AppendLiteral("\"");
+ const char_type* vcur = value.BeginReading();
+ const char_type* vend = value.EndReading();
+ while (vcur < vend) {
+ if (*vcur == '"' || *vcur == '\\') {
+ aOutput.AppendLiteral("\\");
+ }
+ aOutput.Append(*vcur);
+ vcur++;
+ }
+ aOutput.AppendLiteral("\"");
+ } else {
+ aOutput.Append(value);
+ }
+
+ return true;
+}
+
+template <typename char_type>
+void TMimeType<char_type>::SetParameterValue(
+ const nsTSubstring<char_type>& aName,
+ const nsTSubstring<char_type>& aValue) {
+ mParameters.WithEntryHandle(aName, [&](auto&& entry) {
+ if (!entry) {
+ mParameterNames.AppendElement(aName);
+ }
+ ParameterValue value;
+ value.Append(aValue);
+ entry.InsertOrUpdate(std::move(value));
+ });
+}
+
+template mozilla::UniquePtr<TMimeType<char16_t>> TMimeType<char16_t>::Parse(
+ const nsTSubstring<char16_t>& aMimeType);
+template mozilla::UniquePtr<TMimeType<char>> TMimeType<char>::Parse(
+ const nsTSubstring<char>& aMimeType);
+template void TMimeType<char16_t>::Serialize(
+ nsTSubstring<char16_t>& aOutput) const;
+template void TMimeType<char>::Serialize(nsTSubstring<char>& aOutput) const;
+template void TMimeType<char16_t>::GetFullType(
+ nsTSubstring<char16_t>& aOutput) const;
+template void TMimeType<char>::GetFullType(nsTSubstring<char>& aOutput) const;
+template bool TMimeType<char16_t>::HasParameter(
+ const nsTSubstring<char16_t>& aName) const;
+template bool TMimeType<char>::HasParameter(
+ const nsTSubstring<char>& aName) const;
+template bool TMimeType<char16_t>::GetParameterValue(
+ const nsTSubstring<char16_t>& aName, nsTSubstring<char16_t>& aOutput,
+ bool aAppend) const;
+template bool TMimeType<char>::GetParameterValue(
+ const nsTSubstring<char>& aName, nsTSubstring<char>& aOutput,
+ bool aAppend) const;
+template void TMimeType<char16_t>::SetParameterValue(
+ const nsTSubstring<char16_t>& aName, const nsTSubstring<char16_t>& aValue);
+template void TMimeType<char>::SetParameterValue(
+ const nsTSubstring<char>& aName, const nsTSubstring<char>& aValue);
diff --git a/dom/base/MimeType.h b/dom/base/MimeType.h
new file mode 100644
index 0000000000..3fb252188a
--- /dev/null
+++ b/dom/base/MimeType.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MimeType_h
+#define mozilla_dom_MimeType_h
+
+#include "mozilla/TextUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTHashMap.h"
+#include "nsTArray.h"
+
+template <typename char_type>
+struct HashKeyType;
+template <>
+struct HashKeyType<char16_t> {
+ using HashType = nsStringHashKey;
+};
+template <>
+struct HashKeyType<char> {
+ using HashType = nsCStringHashKey;
+};
+
+template <typename char_type>
+class TMimeType final {
+ private:
+ class ParameterValue : public nsTString<char_type> {
+ public:
+ bool mRequiresQuoting;
+
+ ParameterValue() : mRequiresQuoting(false) {}
+ };
+
+ nsTString<char_type> mType;
+ nsTString<char_type> mSubtype;
+ nsTHashMap<typename HashKeyType<char_type>::HashType, ParameterValue>
+ mParameters;
+ nsTArray<nsTString<char_type>> mParameterNames;
+
+ public:
+ TMimeType(const nsTSubstring<char_type>& aType,
+ const nsTSubstring<char_type>& aSubtype)
+ : mType(aType), mSubtype(aSubtype) {}
+
+ static mozilla::UniquePtr<TMimeType<char_type>> Parse(
+ const nsTSubstring<char_type>& aStr);
+
+ void Serialize(nsTSubstring<char_type>& aStr) const;
+
+ // Returns the `<mType>/<mSubtype>`
+ void GetFullType(nsTSubstring<char_type>& aStr) const;
+
+ // @param aName - the name of the parameter
+ // @return true if the parameter name is found, false otherwise.
+ bool HasParameter(const nsTSubstring<char_type>& aName) const;
+
+ // @param aName - the name of the parameter
+ // @param aOutput - will hold the value of the parameter (quoted if necessary)
+ // @param aAppend - if true, the method will append to the string;
+ // otherwise the string is truncated before appending.
+ // @return true if the parameter name is found, false otherwise.
+ bool GetParameterValue(const nsTSubstring<char_type>& aName,
+ nsTSubstring<char_type>& aOutput,
+ bool aAppend = false) const;
+
+ // @param aName - the name of the parameter
+ // @param aValue - the value of the parameter
+ void SetParameterValue(const nsTSubstring<char_type>& aName,
+ const nsTSubstring<char_type>& aValue);
+};
+
+using MimeType = TMimeType<char16_t>;
+using CMimeType = TMimeType<char>;
+
+#endif // mozilla_dom_MimeType_h
diff --git a/dom/base/MozQueryInterface.cpp b/dom/base/MozQueryInterface.cpp
new file mode 100644
index 0000000000..aa6bc19346
--- /dev/null
+++ b/dom/base/MozQueryInterface.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeUtils.h"
+#include "MozQueryInterface.h"
+#include "xptinfo.h"
+
+#include <string.h>
+
+#include "jsapi.h"
+
+#include "mozilla/ErrorResult.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+constexpr size_t IID_SIZE = sizeof(nsIID);
+
+static_assert(
+ IID_SIZE == 16,
+ "Size of nsID struct changed. Please ensure this code is still valid.");
+
+static int CompareIIDs(const nsIID& aA, const nsIID& aB) {
+ return memcmp((void*)&aA.m0, (void*)&aB.m0, IID_SIZE);
+}
+
+/* static */
+MozQueryInterface* ChromeUtils::GenerateQI(
+ const GlobalObject& aGlobal, const Sequence<JS::Value>& aInterfaces) {
+ JSContext* cx = aGlobal.Context();
+
+ nsTArray<nsIID> ifaces;
+
+ JS::Rooted<JS::Value> iface(cx);
+ for (uint32_t idx = 0; idx < aInterfaces.Length(); ++idx) {
+ iface = aInterfaces[idx];
+
+ // Handle ID objects
+ if (Maybe<nsID> id = xpc::JSValue2ID(cx, iface)) {
+ ifaces.AppendElement(*id);
+ continue;
+ }
+
+ // Accept string valued names
+ if (iface.isString()) {
+ JS::UniqueChars name = JS_EncodeStringToLatin1(cx, iface.toString());
+
+ const nsXPTInterfaceInfo* iinfo = nsXPTInterfaceInfo::ByName(name.get());
+ if (iinfo) {
+ ifaces.AppendElement(iinfo->IID());
+ continue;
+ }
+ }
+
+ // NOTE: We ignore unknown interfaces here because in some cases we try to
+ // pass them in to support multiple platforms.
+ }
+
+ MOZ_ASSERT(!ifaces.Contains(NS_GET_IID(nsISupports), CompareIIDs));
+ ifaces.AppendElement(NS_GET_IID(nsISupports));
+
+ ifaces.Sort(CompareIIDs);
+
+ return new MozQueryInterface(std::move(ifaces));
+}
+
+bool MozQueryInterface::QueriesTo(const nsIID& aIID) const {
+ return mInterfaces.ContainsSorted(aIID, CompareIIDs);
+}
+
+void MozQueryInterface::LegacyCall(JSContext* cx, JS::Handle<JS::Value> thisv,
+ JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv) const {
+ Maybe<nsID> id = xpc::JSValue2ID(cx, aIID);
+ if (id && QueriesTo(*id)) {
+ aResult.set(thisv);
+ } else {
+ aRv.Throw(NS_ERROR_NO_INTERFACE);
+ }
+}
+
+bool MozQueryInterface::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return MozQueryInterface_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/MozQueryInterface.h b/dom/base/MozQueryInterface.h
new file mode 100644
index 0000000000..1dd3d9bfff
--- /dev/null
+++ b/dom/base/MozQueryInterface.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MozQueryInterface
+#define mozilla_dom_MozQueryInterface
+
+/**
+ * This class implements an optimized QueryInterface method for
+ * XPConnect-wrapped JS objects.
+ *
+ * For JavaScript callers, it behaves as an ordinary QueryInterface method,
+ * returning its `this` object or throwing depending on the interface it was
+ * passed.
+ *
+ * For native XPConnect callers, we bypass JSAPI entirely, and directly check
+ * whether the queried interface is in the interfaces list.
+ */
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsID.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class MozQueryInterface final : public NonRefcountedDOMObject {
+ public:
+ explicit MozQueryInterface(nsTArray<nsIID>&& aInterfaces)
+ : mInterfaces(std::move(aInterfaces)) {}
+
+ bool QueriesTo(const nsIID& aIID) const;
+
+ void LegacyCall(JSContext* cx, JS::Handle<JS::Value> thisv,
+ JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aResult, ErrorResult& aRv) const;
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ private:
+ nsTArray<nsIID> mInterfaces;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MozQueryInterface
diff --git a/dom/base/MutationObservers.cpp b/dom/base/MutationObservers.cpp
new file mode 100644
index 0000000000..e3e7c8b02e
--- /dev/null
+++ b/dom/base/MutationObservers.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MutationObservers.h"
+
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "nsIMutationObserver.h"
+#include "mozilla/EventListenerManager.h"
+#include "PLDHashTable.h"
+#include "nsCOMArray.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULElement.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/PresShell.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsDOMMutationObserver.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define NOTIFY_PRESSHELL(notify_) \
+ if (PresShell* presShell = doc->GetObservingPresShell()) { \
+ notify_(presShell); \
+ }
+
+#define NOTIFIER(func_, ...) \
+ [&](nsIMutationObserver* aObserver) { aObserver->func_(__VA_ARGS__); }
+
+template <typename NotifyObserver>
+static inline nsINode* ForEachAncestorObserver(nsINode* aNode,
+ NotifyObserver& aFunc,
+ uint32_t aCallback) {
+ nsINode* last;
+ nsINode* node = aNode;
+ do {
+ mozilla::SafeDoublyLinkedList<nsIMutationObserver>* observers =
+ node->GetMutationObservers();
+ if (observers) {
+ for (auto iter = observers->begin(); iter != observers->end(); ++iter) {
+ if (iter->IsCallbackEnabled(aCallback)) {
+ aFunc(&*iter);
+ }
+ }
+ }
+ last = node;
+ if (!(node = node->GetParentNode())) {
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(last)) {
+ node = shadow->GetHost();
+ }
+ }
+ } while (node);
+ return last;
+}
+
+// Whether to notify to the PresShell about a mutation.
+// For removals, the pres shell gets notified first, since it needs to operate
+// on the "old" DOM shape.
+enum class NotifyPresShell { No, Before, After };
+
+template <NotifyPresShell aNotifyPresShell = NotifyPresShell::After,
+ typename NotifyObserver>
+static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify,
+ uint32_t aCallback) {
+ Document* doc = aNode->OwnerDoc();
+ nsDOMMutationEnterLeave enterLeave(doc);
+
+#ifdef DEBUG
+ const bool wasConnected = aNode->IsInComposedDoc();
+#endif
+ if constexpr (aNotifyPresShell == NotifyPresShell::Before) {
+ if (aNode->IsInComposedDoc()) {
+ NOTIFY_PRESSHELL(aNotify);
+ }
+ }
+ nsINode* last = ForEachAncestorObserver(aNode, aNotify, aCallback);
+ // For non-removals, the pres shell gets notified last, since it needs to
+ // operate on the "final" DOM shape.
+ if constexpr (aNotifyPresShell == NotifyPresShell::After) {
+ if (last == doc) {
+ NOTIFY_PRESSHELL(aNotify);
+ }
+ }
+ MOZ_ASSERT((last == doc) == wasConnected,
+ "If we were connected we should notify all ancestors all the "
+ "way to the document");
+}
+
+#define IMPL_ANIMATION_NOTIFICATION(func_, content_, params_) \
+ PR_BEGIN_MACRO \
+ nsDOMMutationEnterLeave enterLeave(doc); \
+ auto forEach = [&](nsIMutationObserver* aObserver) { \
+ if (nsCOMPtr<nsIAnimationObserver> obs = do_QueryInterface(aObserver)) { \
+ obs->func_ params_; \
+ } \
+ }; \
+ ForEachAncestorObserver(content_, forEach, nsIMutationObserver::k##func_); \
+ PR_END_MACRO
+
+namespace mozilla {
+void MutationObservers::NotifyCharacterDataWillChange(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
+ Notify(aContent, NOTIFIER(CharacterDataWillChange, aContent, aInfo),
+ nsIMutationObserver::kCharacterDataWillChange);
+}
+
+void MutationObservers::NotifyCharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
+ aContent->OwnerDoc()->Changed();
+ Notify(aContent, NOTIFIER(CharacterDataChanged, aContent, aInfo),
+ nsIMutationObserver::kCharacterDataChanged);
+}
+
+void MutationObservers::NotifyAttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ Notify(aElement,
+ NOTIFIER(AttributeWillChange, aElement, aNameSpaceID, aAttribute,
+ aModType),
+ nsIMutationObserver::kAttributeWillChange);
+}
+
+void MutationObservers::NotifyAttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ aElement->OwnerDoc()->Changed();
+ Notify(aElement,
+ NOTIFIER(AttributeChanged, aElement, aNameSpaceID, aAttribute,
+ aModType, aOldValue),
+ nsIMutationObserver::kAttributeChanged);
+}
+
+void MutationObservers::NotifyAttributeSetToCurrentValue(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) {
+ Notify(
+ aElement,
+ NOTIFIER(AttributeSetToCurrentValue, aElement, aNameSpaceID, aAttribute),
+ nsIMutationObserver::kAttributeSetToCurrentValue);
+}
+
+void MutationObservers::NotifyContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent) {
+ aContainer->OwnerDoc()->Changed();
+ Notify(aContainer, NOTIFIER(ContentAppended, aFirstNewContent),
+ nsIMutationObserver::kContentAppended);
+}
+
+void MutationObservers::NotifyContentInserted(nsINode* aContainer,
+ nsIContent* aChild) {
+ MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(),
+ "container must be an nsIContent or an Document");
+ aContainer->OwnerDoc()->Changed();
+ Notify(aContainer, NOTIFIER(ContentInserted, aChild),
+ nsIMutationObserver::kContentInserted);
+}
+
+void MutationObservers::NotifyContentRemoved(nsINode* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(),
+ "container must be an nsIContent or an Document");
+ aContainer->OwnerDoc()->Changed();
+ MOZ_ASSERT(aChild->GetParentNode() == aContainer,
+ "We expect the parent link to be still around at this point");
+ Notify<NotifyPresShell::Before>(
+ aContainer, NOTIFIER(ContentRemoved, aChild, aPreviousSibling),
+ nsIMutationObserver::kContentRemoved);
+}
+
+void MutationObservers::NotifyARIAAttributeDefaultWillChange(
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {
+ Notify<NotifyPresShell::No>(
+ aElement,
+ NOTIFIER(ARIAAttributeDefaultWillChange, aElement, aAttribute, aModType),
+ nsIMutationObserver::kARIAAttributeDefaultWillChange);
+}
+
+void MutationObservers::NotifyARIAAttributeDefaultChanged(
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {
+ Notify<NotifyPresShell::No>(
+ aElement,
+ NOTIFIER(ARIAAttributeDefaultChanged, aElement, aAttribute, aModType),
+ nsIMutationObserver::kARIAAttributeDefaultChanged);
+}
+
+} // namespace mozilla
+
+void MutationObservers::NotifyAnimationMutated(
+ dom::Animation* aAnimation, AnimationMutationType aMutatedType) {
+ MOZ_ASSERT(aAnimation);
+
+ NonOwningAnimationTarget target = aAnimation->GetTargetForAnimation();
+ if (!target) {
+ return;
+ }
+
+ // A pseudo element and its parent element use the same owner doc.
+ Document* doc = target.mElement->OwnerDoc();
+ if (doc->MayHaveAnimationObservers()) {
+ // we use the its parent element as the subject in DOM Mutation Observer.
+ Element* elem = target.mElement;
+ switch (aMutatedType) {
+ case AnimationMutationType::Added:
+ IMPL_ANIMATION_NOTIFICATION(AnimationAdded, elem, (aAnimation));
+ break;
+ case AnimationMutationType::Changed:
+ IMPL_ANIMATION_NOTIFICATION(AnimationChanged, elem, (aAnimation));
+ break;
+ case AnimationMutationType::Removed:
+ IMPL_ANIMATION_NOTIFICATION(AnimationRemoved, elem, (aAnimation));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected mutation type");
+ }
+ }
+}
+
+void MutationObservers::NotifyAnimationAdded(dom::Animation* aAnimation) {
+ NotifyAnimationMutated(aAnimation, AnimationMutationType::Added);
+}
+
+void MutationObservers::NotifyAnimationChanged(dom::Animation* aAnimation) {
+ NotifyAnimationMutated(aAnimation, AnimationMutationType::Changed);
+}
+
+void MutationObservers::NotifyAnimationRemoved(dom::Animation* aAnimation) {
+ NotifyAnimationMutated(aAnimation, AnimationMutationType::Removed);
+}
diff --git a/dom/base/MutationObservers.h b/dom/base/MutationObservers.h
new file mode 100644
index 0000000000..1ca86bdf43
--- /dev/null
+++ b/dom/base/MutationObservers.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_BASE_MUTATIONOBSERVERS_H_
+#define DOM_BASE_MUTATIONOBSERVERS_H_
+
+#include "mozilla/DoublyLinkedList.h"
+#include "nsIContent.h" // for use in inline function (NotifyParentChainChanged)
+#include "nsIMutationObserver.h" // for use in inline function (NotifyParentChainChanged)
+#include "nsINode.h"
+
+class nsAtom;
+class nsAttrValue;
+
+namespace mozilla::dom {
+class Animation;
+class Element;
+
+class MutationObservers {
+ public:
+ /**
+ * Send CharacterDataWillChange notifications to nsIMutationObservers.
+ * @param aContent Node whose data changed
+ * @param aInfo Struct with information details about the change
+ * @see nsIMutationObserver::CharacterDataWillChange
+ */
+ static void NotifyCharacterDataWillChange(nsIContent* aContent,
+ const CharacterDataChangeInfo&);
+
+ /**
+ * Send CharacterDataChanged notifications to nsIMutationObservers.
+ * @param aContent Node whose data changed
+ * @param aInfo Struct with information details about the change
+ * @see nsIMutationObserver::CharacterDataChanged
+ */
+ static void NotifyCharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&);
+
+ /**
+ * Send AttributeWillChange notifications to nsIMutationObservers.
+ * @param aElement Element whose data will change
+ * @param aNameSpaceID Namespace of changing attribute
+ * @param aAttribute Local-name of changing attribute
+ * @param aModType Type of change (add/change/removal)
+ * @see nsIMutationObserver::AttributeWillChange
+ */
+ static void NotifyAttributeWillChange(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType);
+
+ /**
+ * Send AttributeChanged notifications to nsIMutationObservers.
+ * @param aElement Element whose data changed
+ * @param aNameSpaceID Namespace of changed attribute
+ * @param aAttribute Local-name of changed attribute
+ * @param aModType Type of change (add/change/removal)
+ * @param aOldValue If the old value was StoresOwnData() (or absent),
+ * that value, otherwise null
+ * @see nsIMutationObserver::AttributeChanged
+ */
+ static void NotifyAttributeChanged(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue);
+
+ /**
+ * Send AttributeSetToCurrentValue notifications to nsIMutationObservers.
+ * @param aElement Element whose data changed
+ * @param aNameSpaceID Namespace of the attribute
+ * @param aAttribute Local-name of the attribute
+ * @see nsIMutationObserver::AttributeSetToCurrentValue
+ */
+ static void NotifyAttributeSetToCurrentValue(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+
+ /**
+ * Send ContentAppended notifications to nsIMutationObservers
+ * @param aContainer Node into which new child/children were added
+ * @param aFirstNewContent First new child
+ * @see nsIMutationObserver::ContentAppended
+ */
+ static void NotifyContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent);
+
+ /**
+ * Send ContentInserted notifications to nsIMutationObservers
+ * @param aContainer Node into which new child was inserted
+ * @param aChild Newly inserted child
+ * @see nsIMutationObserver::ContentInserted
+ */
+ static void NotifyContentInserted(nsINode* aContainer, nsIContent* aChild);
+ /**
+ * Send ContentRemoved notifications to nsIMutationObservers
+ * @param aContainer Node from which child was removed
+ * @param aChild Removed child
+ * @param aPreviousSibling Previous sibling of the removed child
+ * @see nsIMutationObserver::ContentRemoved
+ */
+ static void NotifyContentRemoved(nsINode* aContainer, nsIContent* aChild,
+ nsIContent* aPreviousSibling);
+
+ /**
+ * Send ParentChainChanged notifications to nsIMutationObservers
+ * @param aContent The piece of content that had its parent changed.
+ * @see nsIMutationObserver::ParentChainChanged
+ */
+ static inline void NotifyParentChainChanged(nsIContent* aContent) {
+ mozilla::SafeDoublyLinkedList<nsIMutationObserver>* observers =
+ aContent->GetMutationObservers();
+ if (observers) {
+ for (auto iter = observers->begin(); iter != observers->end(); ++iter) {
+ if (iter->IsCallbackEnabled(nsIMutationObserver::kParentChainChanged)) {
+ iter->ParentChainChanged(aContent);
+ }
+ }
+ }
+ }
+
+ static void NotifyARIAAttributeDefaultWillChange(
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType);
+ static void NotifyARIAAttributeDefaultChanged(mozilla::dom::Element* aElement,
+ nsAtom* aAttribute,
+ int32_t aModType);
+
+ /**
+ * Notify that an animation is added/changed/removed.
+ * @param aAnimation The animation we added/changed/removed.
+ */
+ static void NotifyAnimationAdded(mozilla::dom::Animation* aAnimation);
+ static void NotifyAnimationChanged(mozilla::dom::Animation* aAnimation);
+ static void NotifyAnimationRemoved(mozilla::dom::Animation* aAnimation);
+
+ private:
+ enum class AnimationMutationType { Added, Changed, Removed };
+ /**
+ * Notify the observers of the target of an animation
+ * @param aAnimation The mutated animation.
+ * @param aMutationType The mutation type of this animation. It could be
+ * Added, Changed, or Removed.
+ */
+ static void NotifyAnimationMutated(mozilla::dom::Animation* aAnimation,
+ AnimationMutationType aMutatedType);
+};
+} // namespace mozilla::dom
+
+#endif // DOM_BASE_MUTATIONOBSERVERS_H_
diff --git a/dom/base/NameSpaceConstants.h b/dom/base/NameSpaceConstants.h
new file mode 100644
index 0000000000..6001575695
--- /dev/null
+++ b/dom/base/NameSpaceConstants.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_NameSpaceConstants_h__
+#define mozilla_dom_NameSpaceConstants_h__
+
+#include <stdint.h>
+
+#define kNameSpaceID_Unknown -1
+// 0 is special at C++, so use a static const int32_t for
+// kNameSpaceID_None to keep if from being cast to pointers
+// Note that the XBL cache assumes (and asserts) that it can treat a
+// single-byte value higher than kNameSpaceID_LastBuiltin specially.
+static const int32_t kNameSpaceID_None = 0;
+#define kNameSpaceID_XMLNS \
+ 1 // not really a namespace, but it needs to play the game
+#define kNameSpaceID_XML 2
+#define kNameSpaceID_XHTML 3
+#define kNameSpaceID_XLink 4
+#define kNameSpaceID_XSLT 5
+#define kNameSpaceID_MathML 6
+#define kNameSpaceID_RDF 7
+#define kNameSpaceID_XUL 8
+#define kNameSpaceID_SVG 9
+#define kNameSpaceID_disabled_MathML 10
+#define kNameSpaceID_disabled_SVG 11
+#define kNameSpaceID_LastBuiltin 11 // last 'built-in' namespace
+
+#endif // mozilla_dom_NameSpaceConstants_h__
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
new file mode 100644
index 0000000000..151d992d6f
--- /dev/null
+++ b/dom/base/Navigator.cpp
@@ -0,0 +1,2316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Needs to be first.
+#include "base/basictypes.h"
+
+#include "Navigator.h"
+#include "nsIXULAppInfo.h"
+#include "nsPluginArray.h"
+#include "nsMimeTypeArray.h"
+#include "mozilla/Components.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/BodyExtractor.h"
+#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/File.h"
+#include "Geolocation.h"
+#include "nsIClassOfService.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsIWebProtocolHandlerRegistrar.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#ifdef FUZZING
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_pdfjs.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/Telemetry.h"
+#include "BatteryManager.h"
+#include "mozilla/dom/CredentialsContainer.h"
+#include "mozilla/dom/Clipboard.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
+#include "mozilla/dom/GamepadServiceTest.h"
+#include "mozilla/dom/MediaCapabilities.h"
+#include "mozilla/dom/MediaSession.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/LockManager.h"
+#include "mozilla/dom/MIDIAccessManager.h"
+#include "mozilla/dom/MIDIOptionsBinding.h"
+#include "mozilla/dom/Permissions.h"
+#include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/TCPSocket.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRDisplayEvent.h"
+#include "mozilla/dom/VRServiceTest.h"
+#include "mozilla/dom/XRSystem.h"
+#include "mozilla/dom/workerinternals/RuntimeService.h"
+#include "mozilla/Hal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "Connection.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "nsGlobalWindow.h"
+#include "nsIPermissionManager.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsRFPService.h"
+#include "nsStringStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICookieManager.h"
+#include "nsICookieService.h"
+#include "nsIHttpChannel.h"
+#ifdef ENABLE_WEBDRIVER
+# include "nsIMarionette.h"
+# include "nsIRemoteAgent.h"
+#endif
+#include "nsStreamUtils.h"
+#include "WidgetUtils.h"
+#include "nsIScriptError.h"
+#include "ReferrerInfo.h"
+#include "mozilla/PermissionDelegateHandler.h"
+
+#include "nsIExternalProtocolHandler.h"
+#include "BrowserChild.h"
+#include "mozilla/ipc/URIUtils.h"
+
+#include "mozilla/dom/MediaDevices.h"
+#include "MediaManager.h"
+
+#include "nsJSUtils.h"
+
+#include "mozilla/dom/Promise.h"
+
+#include "nsIUploadChannel2.h"
+#include "mozilla/dom/FormData.h"
+#include "nsIDocShell.h"
+
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+
+#if defined(XP_WIN)
+# include "mozilla/WindowsVersion.h"
+#endif
+
+#include "mozilla/EMEUtils.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/webgpu/Instance.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/dom/AudioContext.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "AutoplayPolicy.h"
+
+namespace mozilla::dom {
+
+static const nsLiteralCString kVibrationPermissionType = "vibration"_ns;
+
+Navigator::Navigator(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {}
+
+Navigator::~Navigator() { Invalidate(); }
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigator)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Navigator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Navigator)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Navigator)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Navigator)
+ tmp->Invalidate();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharePromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlugins)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCredentials)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaCapabilities)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSession)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddonManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebGpu)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocks)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharePromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXRSystem)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void Navigator::Invalidate() {
+ // Don't clear mWindow here so we know we've got a non-null mWindow
+ // until we're unlinked.
+
+ mPlugins = nullptr;
+
+ mPermissions = nullptr;
+
+ if (mStorageManager) {
+ mStorageManager->Shutdown();
+ mStorageManager = nullptr;
+ }
+
+ // If there is a page transition, make sure delete the geolocation object.
+ if (mGeolocation) {
+ mGeolocation->Shutdown();
+ mGeolocation = nullptr;
+ }
+
+ if (mBatteryManager) {
+ mBatteryManager->Shutdown();
+ mBatteryManager = nullptr;
+ }
+
+ mBatteryPromise = nullptr;
+
+ if (mConnection) {
+ mConnection->Shutdown();
+ mConnection = nullptr;
+ }
+
+ mMediaDevices = nullptr;
+
+ mServiceWorkerContainer = nullptr;
+
+ if (mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager->Shutdown();
+ mMediaKeySystemAccessManager = nullptr;
+ }
+
+ if (mGamepadServiceTest) {
+ mGamepadServiceTest->Shutdown();
+ mGamepadServiceTest = nullptr;
+ }
+
+ mVRGetDisplaysPromises.Clear();
+
+ if (mVRServiceTest) {
+ mVRServiceTest->Shutdown();
+ mVRServiceTest = nullptr;
+ }
+
+ if (mXRSystem) {
+ mXRSystem->Shutdown();
+ mXRSystem = nullptr;
+ }
+
+ mMediaCapabilities = nullptr;
+
+ if (mMediaSession) {
+ mMediaSession->Shutdown();
+ mMediaSession = nullptr;
+ }
+
+ mAddonManager = nullptr;
+
+ mWebGpu = nullptr;
+
+ if (mLocks) {
+ // Unloading a page does not immediately destruct the lock manager actor,
+ // but we want to abort the lock requests as soon as possible. Explicitly
+ // call Shutdown() to do that.
+ mLocks->Shutdown();
+ mLocks = nullptr;
+ }
+
+ mSharePromise = nullptr;
+}
+
+void Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ nsCOMPtr<nsPIDOMWindowInner> window;
+
+ if (mWindow) {
+ window = mWindow;
+ nsIDocShell* docshell = window->GetDocShell();
+ nsString customUserAgent;
+ if (docshell) {
+ docshell->GetBrowsingContext()->GetCustomUserAgent(customUserAgent);
+
+ if (!customUserAgent.IsEmpty()) {
+ aUserAgent = customUserAgent;
+ return;
+ }
+ }
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ nsresult rv = GetUserAgent(
+ mWindow, doc, aCallerType == CallerType::System ? Some(false) : Nothing(),
+ aUserAgent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ }
+}
+
+void Navigator::GetAppCodeName(nsAString& aAppCodeName, ErrorResult& aRv) {
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpProtocolHandler> service(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsAutoCString appName;
+ rv = service->GetAppName(appName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ CopyASCIItoUTF16(appName, aAppCodeName);
+}
+
+void Navigator::GetAppVersion(nsAString& aAppVersion, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+
+ nsresult rv = GetAppVersion(
+ aAppVersion, doc,
+ /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ }
+}
+
+void Navigator::GetAppName(nsAString& aAppName, CallerType aCallerType) const {
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+
+ AppName(aAppName, doc,
+ /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System);
+}
+
+/**
+ * Returns the value of Accept-Languages (HTTP header) as a nsTArray of
+ * languages. The value is set in the preference by the user ("Content
+ * Languages").
+ *
+ * "en", "en-US" and "i-cherokee" and "" are valid languages tokens.
+ *
+ * If there is no valid language, the value of getWebExposedLocales is
+ * used to ensure that locale spoofing is honored and to reduce
+ * fingerprinting.
+ *
+ * See RFC 7231, Section 9.7 "Browser Fingerprinting" and
+ * RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers"
+ * for more detail.
+ */
+/* static */
+void Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aLanguages.Clear();
+
+ // E.g. "de-de, en-us,en".
+ nsAutoString acceptLang;
+ Preferences::GetLocalizedString("intl.accept_languages", acceptLang);
+
+ // Split values on commas.
+ for (nsDependentSubstring lang :
+ nsCharSeparatedTokenizer(acceptLang, ',').ToRange()) {
+ // Replace "_" with "-" to avoid POSIX/Windows "en_US" notation.
+ // NOTE: we should probably rely on the pref being set correctly.
+ if (lang.Length() > 2 && lang[2] == char16_t('_')) {
+ lang.Replace(2, 1, char16_t('-'));
+ }
+
+ // Use uppercase for country part, e.g. "en-US", not "en-us", see BCP47
+ // only uppercase 2-letter country codes, not "zh-Hant", "de-DE-x-goethe".
+ // NOTE: we should probably rely on the pref being set correctly.
+ if (lang.Length() > 2) {
+ int32_t pos = 0;
+ bool first = true;
+ for (const nsAString& code :
+ nsCharSeparatedTokenizer(lang, '-').ToRange()) {
+ if (code.Length() == 2 && !first) {
+ nsAutoString upper(code);
+ ToUpperCase(upper);
+ lang.Replace(pos, code.Length(), upper);
+ }
+
+ pos += code.Length() + 1; // 1 is the separator
+ first = false;
+ }
+ }
+
+ aLanguages.AppendElement(lang);
+ }
+ if (aLanguages.Length() == 0) {
+ nsTArray<nsCString> locales;
+ mozilla::intl::LocaleService::GetInstance()->GetWebExposedLocales(locales);
+ aLanguages.AppendElement(NS_ConvertUTF8toUTF16(locales[0]));
+ }
+}
+
+/**
+ * Returns the first language from GetAcceptLanguages.
+ *
+ * Full details above in GetAcceptLanguages.
+ */
+void Navigator::GetLanguage(nsAString& aLanguage) {
+ nsTArray<nsString> languages;
+ GetLanguages(languages);
+ MOZ_ASSERT(languages.Length() >= 1);
+ aLanguage.Assign(languages[0]);
+}
+
+void Navigator::GetLanguages(nsTArray<nsString>& aLanguages) {
+ GetAcceptLanguages(aLanguages);
+
+ // The returned value is cached by the binding code. The window listens to the
+ // accept languages change and will clear the cache when needed. It has to
+ // take care of dispatching the DOM event already and the invalidation and the
+ // event has to be timed correctly.
+}
+
+void Navigator::GetPlatform(nsAString& aPlatform, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (mWindow) {
+ BrowsingContext* bc = mWindow->GetBrowsingContext();
+ nsString customPlatform;
+ if (bc) {
+ bc->GetCustomPlatform(customPlatform);
+
+ if (!customPlatform.IsEmpty()) {
+ aPlatform = customPlatform;
+ return;
+ }
+ }
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+
+ nsresult rv = GetPlatform(
+ aPlatform, doc,
+ /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ }
+}
+
+void Navigator::GetOscpu(nsAString& aOSCPU, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (aCallerType != CallerType::System) {
+ // If fingerprinting resistance is on, we will spoof this value. See
+ // nsRFPService.h for details about spoofed values.
+ if (nsContentUtils::ShouldResistFingerprinting(GetDocShell(),
+ RFPTarget::NavigatorOscpu)) {
+ aOSCPU.AssignLiteral(SPOOFED_OSCPU);
+ return;
+ }
+
+ nsAutoString override;
+ nsresult rv = Preferences::GetString("general.oscpu.override", override);
+ if (NS_SUCCEEDED(rv)) {
+ aOSCPU = override;
+ return;
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpProtocolHandler> service(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsAutoCString oscpu;
+ rv = service->GetOscpu(oscpu);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ CopyASCIItoUTF16(oscpu, aOSCPU);
+}
+
+void Navigator::GetVendor(nsAString& aVendor) { aVendor.Truncate(); }
+
+void Navigator::GetVendorSub(nsAString& aVendorSub) { aVendorSub.Truncate(); }
+
+void Navigator::GetProduct(nsAString& aProduct) {
+ aProduct.AssignLiteral("Gecko");
+}
+
+void Navigator::GetProductSub(nsAString& aProductSub) {
+ // Legacy build date hardcoded for backward compatibility (bug 776376)
+ aProductSub.AssignLiteral(LEGACY_UA_GECKO_TRAIL);
+}
+
+nsMimeTypeArray* Navigator::GetMimeTypes(ErrorResult& aRv) {
+ auto* plugins = GetPlugins(aRv);
+ if (!plugins) {
+ return nullptr;
+ }
+
+ return plugins->MimeTypeArray();
+}
+
+nsPluginArray* Navigator::GetPlugins(ErrorResult& aRv) {
+ if (!mPlugins) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mPlugins = MakeRefPtr<nsPluginArray>(mWindow);
+ }
+
+ return mPlugins;
+}
+
+bool Navigator::PdfViewerEnabled() {
+ // We ignore pdfjs.disabled when resisting fingerprinting.
+ // See bug 1756280 for an explanation.
+ return !StaticPrefs::pdfjs_disabled() ||
+ nsContentUtils::ShouldResistFingerprinting(GetDocShell(),
+ RFPTarget::Unknown);
+}
+
+Permissions* Navigator::GetPermissions(ErrorResult& aRv) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!mPermissions) {
+ mPermissions = new Permissions(mWindow);
+ }
+
+ return mPermissions;
+}
+
+StorageManager* Navigator::Storage() {
+ MOZ_ASSERT(mWindow);
+
+ if (!mStorageManager) {
+ mStorageManager = new StorageManager(mWindow->AsGlobal());
+ }
+
+ return mStorageManager;
+}
+
+bool Navigator::CookieEnabled() {
+ // Check whether an exception overrides the global cookie behavior
+ // Note that the code for getting the URI here matches that in
+ // nsHTMLDocument::SetCookie.
+ if (!mWindow || !mWindow->GetDocShell()) {
+ return nsICookieManager::GetCookieBehavior(false) !=
+ nsICookieService::BEHAVIOR_REJECT;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow);
+ uint32_t cookieBehavior = loadContext
+ ? nsICookieManager::GetCookieBehavior(
+ loadContext->UsePrivateBrowsing())
+ : nsICookieManager::GetCookieBehavior(false);
+ bool cookieEnabled = cookieBehavior != nsICookieService::BEHAVIOR_REJECT;
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ return cookieEnabled;
+ }
+
+ uint32_t rejectedReason = 0;
+ bool granted = false;
+ nsresult rv = doc->NodePrincipal()->HasFirstpartyStorageAccess(
+ mWindow, &rejectedReason, &granted);
+ if (NS_FAILED(rv)) {
+ // Not a content, so technically can't set cookies, but let's
+ // just return the default value.
+ return cookieEnabled;
+ }
+
+ // We should return true if the cookie is partitioned because the cookie is
+ // still available in this case.
+ if (!granted &&
+ StoragePartitioningEnabled(rejectedReason, doc->CookieJarSettings())) {
+ granted = true;
+ }
+
+ ContentBlockingNotifier::OnDecision(
+ mWindow,
+ granted ? ContentBlockingNotifier::BlockingDecision::eAllow
+ : ContentBlockingNotifier::BlockingDecision::eBlock,
+ rejectedReason);
+ return granted;
+}
+
+bool Navigator::OnLine() { return !NS_IsOffline(); }
+
+void Navigator::GetBuildID(nsAString& aBuildID, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (aCallerType != CallerType::System) {
+ // If fingerprinting resistance is on, we will spoof this value. See
+ // nsRFPService.h for details about spoofed values.
+ if (nsContentUtils::ShouldResistFingerprinting(
+ GetDocShell(), RFPTarget::NavigatorBuildID)) {
+ aBuildID.AssignLiteral(LEGACY_BUILD_ID);
+ return;
+ }
+
+ nsAutoString override;
+ nsresult rv = Preferences::GetString("general.buildID.override", override);
+ if (NS_SUCCEEDED(rv)) {
+ aBuildID = override;
+ return;
+ }
+
+ nsAutoCString host;
+ bool isHTTPS = false;
+ if (mWindow) {
+ nsCOMPtr<Document> doc = mWindow->GetDoc();
+ if (doc) {
+ nsIURI* uri = doc->GetDocumentURI();
+ if (uri) {
+ isHTTPS = uri->SchemeIs("https");
+ if (isHTTPS) {
+ MOZ_ALWAYS_SUCCEEDS(uri->GetHost(host));
+ }
+ }
+ }
+ }
+
+ // Spoof the buildID on pages not loaded from "https://*.mozilla.org".
+ if (!isHTTPS || !StringEndsWith(host, ".mozilla.org"_ns)) {
+ aBuildID.AssignLiteral(LEGACY_BUILD_ID);
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (!appInfo) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ nsAutoCString buildID;
+ nsresult rv = appInfo->GetAppBuildID(buildID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ aBuildID.Truncate();
+ AppendASCIItoUTF16(buildID, aBuildID);
+}
+
+void Navigator::GetDoNotTrack(nsAString& aResult) {
+ bool doNotTrack = StaticPrefs::privacy_donottrackheader_enabled();
+ if (!doNotTrack) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow);
+ doNotTrack = loadContext && loadContext->UseTrackingProtection();
+ }
+
+ if (doNotTrack) {
+ aResult.AssignLiteral("1");
+ } else {
+ aResult.AssignLiteral("unspecified");
+ }
+}
+
+bool Navigator::GlobalPrivacyControl() {
+ return StaticPrefs::privacy_globalprivacycontrol_enabled() &&
+ StaticPrefs::privacy_globalprivacycontrol_functionality_enabled();
+}
+
+uint64_t Navigator::HardwareConcurrency() {
+ workerinternals::RuntimeService* rts =
+ workerinternals::RuntimeService::GetOrCreateService();
+ if (!rts) {
+ return 1;
+ }
+
+ return rts->ClampedHardwareConcurrency(
+ nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting(
+ RFPTarget::NavigatorHWConcurrency));
+}
+
+namespace {
+
+class VibrateWindowListener : public nsIDOMEventListener {
+ public:
+ VibrateWindowListener(nsPIDOMWindowInner* aWindow, Document* aDocument) {
+ mWindow = do_GetWeakReference(aWindow);
+ mDocument = do_GetWeakReference(aDocument);
+
+ constexpr auto visibilitychange = u"visibilitychange"_ns;
+ aDocument->AddSystemEventListener(visibilitychange, this, /* listener */
+ true, /* use capture */
+ false /* wants untrusted */);
+ }
+
+ void RemoveListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ private:
+ virtual ~VibrateWindowListener() = default;
+
+ nsWeakPtr mWindow;
+ nsWeakPtr mDocument;
+};
+
+NS_IMPL_ISUPPORTS(VibrateWindowListener, nsIDOMEventListener)
+
+StaticRefPtr<VibrateWindowListener> gVibrateWindowListener;
+
+static bool MayVibrate(Document* doc) {
+ // Hidden documents cannot start or stop a vibration.
+ return (doc && !doc->Hidden());
+}
+
+NS_IMETHODIMP
+VibrateWindowListener::HandleEvent(Event* aEvent) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
+
+ if (!MayVibrate(doc)) {
+ // It's important that we call CancelVibrate(), not Vibrate() with an
+ // empty list, because Vibrate() will fail if we're no longer focused, but
+ // CancelVibrate() will succeed, so long as nobody else has started a new
+ // vibration pattern.
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
+ hal::CancelVibrate(window);
+ RemoveListener();
+ gVibrateWindowListener = nullptr;
+ // Careful: The line above might have deleted |this|!
+ }
+
+ return NS_OK;
+}
+
+void VibrateWindowListener::RemoveListener() {
+ nsCOMPtr<EventTarget> target = do_QueryReferent(mDocument);
+ if (!target) {
+ return;
+ }
+ constexpr auto visibilitychange = u"visibilitychange"_ns;
+ target->RemoveSystemEventListener(visibilitychange, this,
+ true /* use capture */);
+}
+
+} // namespace
+
+void Navigator::SetVibrationPermission(bool aPermitted, bool aPersistent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsTArray<uint32_t> pattern = std::move(mRequestedVibrationPattern);
+
+ if (!mWindow) {
+ return;
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+
+ if (!MayVibrate(doc)) {
+ return;
+ }
+
+ if (aPermitted) {
+ // Add a listener to cancel the vibration if the document becomes hidden,
+ // and remove the old visibility listener, if there was one.
+ if (!gVibrateWindowListener) {
+ // If gVibrateWindowListener is null, this is the first time we've
+ // vibrated, and we need to register a listener to clear
+ // gVibrateWindowListener on shutdown.
+ ClearOnShutdown(&gVibrateWindowListener);
+ } else {
+ gVibrateWindowListener->RemoveListener();
+ }
+ gVibrateWindowListener = new VibrateWindowListener(mWindow, doc);
+ hal::Vibrate(pattern, mWindow);
+ }
+
+ if (aPersistent) {
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ components::PermissionManager::Service();
+ if (!permMgr) {
+ return;
+ }
+ permMgr->AddFromPrincipal(doc->NodePrincipal(), kVibrationPermissionType,
+ aPermitted ? nsIPermissionManager::ALLOW_ACTION
+ : nsIPermissionManager::DENY_ACTION,
+ nsIPermissionManager::EXPIRE_SESSION, 0);
+ }
+}
+
+bool Navigator::Vibrate(uint32_t aDuration) {
+ AutoTArray<uint32_t, 1> pattern;
+ pattern.AppendElement(aDuration);
+ return Vibrate(pattern);
+}
+
+nsTArray<uint32_t> SanitizeVibratePattern(const nsTArray<uint32_t>& aPattern) {
+ nsTArray<uint32_t> pattern(aPattern.Clone());
+
+ if (pattern.Length() > StaticPrefs::dom_vibrator_max_vibrate_list_len()) {
+ pattern.SetLength(StaticPrefs::dom_vibrator_max_vibrate_list_len());
+ }
+
+ for (size_t i = 0; i < pattern.Length(); ++i) {
+ pattern[i] =
+ std::min(StaticPrefs::dom_vibrator_max_vibrate_ms(), pattern[i]);
+ }
+
+ return pattern;
+}
+
+bool Navigator::Vibrate(const nsTArray<uint32_t>& aPattern) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mWindow) {
+ return false;
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+
+ if (!MayVibrate(doc)) {
+ return false;
+ }
+
+ nsTArray<uint32_t> pattern = SanitizeVibratePattern(aPattern);
+
+ // The spec says we check dom.vibrator.enabled after we've done the sanity
+ // checking on the pattern.
+ if (!StaticPrefs::dom_vibrator_enabled()) {
+ return true;
+ }
+
+ mRequestedVibrationPattern = std::move(pattern);
+
+ PermissionDelegateHandler* permissionHandler =
+ doc->GetPermissionDelegateHandler();
+ if (NS_WARN_IF(!permissionHandler)) {
+ return false;
+ }
+
+ uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ permissionHandler->GetPermission(kVibrationPermissionType, &permission,
+ false);
+
+ if (permission == nsIPermissionManager::DENY_ACTION) {
+ // Abort without observer service or on denied session permission.
+ SetVibrationPermission(false /* permitted */, false /* persistent */);
+ return false;
+ }
+
+ if (permission == nsIPermissionManager::ALLOW_ACTION ||
+ mRequestedVibrationPattern.IsEmpty() ||
+ (mRequestedVibrationPattern.Length() == 1 &&
+ mRequestedVibrationPattern[0] == 0)) {
+ // Always allow cancelling vibration and respect session permissions.
+ SetVibrationPermission(true /* permitted */, false /* persistent */);
+ return true;
+ }
+
+ // Request user permission.
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return true;
+ }
+
+ obs->NotifyObservers(ToSupports(this), "Vibration:Request", nullptr);
+
+ return true;
+}
+
+//*****************************************************************************
+// Pointer Events interface
+//*****************************************************************************
+
+uint32_t Navigator::MaxTouchPoints(CallerType aCallerType) {
+ nsIDocShell* docshell = GetDocShell();
+ BrowsingContext* bc = docshell ? docshell->GetBrowsingContext() : nullptr;
+
+ // Responsive Design Mode overrides the maxTouchPoints property when
+ // touch simulation is enabled.
+ if (bc && bc->InRDMPane()) {
+ return bc->GetMaxTouchPointsOverride();
+ }
+
+ // The maxTouchPoints is going to reveal the detail of users' hardware. So,
+ // we will spoof it into 0 if fingerprinting resistance is on.
+ if (aCallerType != CallerType::System &&
+ nsContentUtils::ShouldResistFingerprinting(GetDocShell(),
+ RFPTarget::Unknown)) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIWidget> widget =
+ widget::WidgetUtils::DOMWindowToWidget(mWindow->GetOuterWindow());
+
+ NS_ENSURE_TRUE(widget, 0);
+ return widget->GetMaxTouchPoints();
+}
+
+//*****************************************************************************
+// Navigator::nsIDOMClientInformation
+//*****************************************************************************
+
+// This list should be kept up-to-date with the spec:
+// https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers
+// If you change this list, please also update the copy in E10SUtils.sys.mjs.
+static const char* const kSafeSchemes[] = {
+ // clang-format off
+ "bitcoin",
+ "ftp",
+ "ftps",
+ "geo",
+ "im",
+ "irc",
+ "ircs",
+ "magnet",
+ "mailto",
+ "matrix",
+ "mms",
+ "news",
+ "nntp",
+ "openpgp4fpr",
+ "sftp",
+ "sip",
+ "sms",
+ "smsto",
+ "ssh",
+ "tel",
+ "urn",
+ "webcal",
+ "wtai",
+ "xmpp",
+ // clang-format on
+};
+
+void Navigator::CheckProtocolHandlerAllowed(const nsAString& aScheme,
+ nsIURI* aHandlerURI,
+ nsIURI* aDocumentURI,
+ ErrorResult& aRv) {
+ auto raisePermissionDeniedHandler = [&] {
+ nsAutoCString spec;
+ aHandlerURI->GetSpec(spec);
+ nsPrintfCString message("Permission denied to add %s as a protocol handler",
+ spec.get());
+ aRv.ThrowSecurityError(message);
+ };
+
+ auto raisePermissionDeniedScheme = [&] {
+ nsPrintfCString message(
+ "Permission denied to add a protocol handler for %s",
+ NS_ConvertUTF16toUTF8(aScheme).get());
+ aRv.ThrowSecurityError(message);
+ };
+
+ if (!aDocumentURI || !aHandlerURI) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsCString spec;
+ aHandlerURI->GetSpec(spec);
+ // If the uri doesn't contain '%s', it won't be a good handler - the %s
+ // gets replaced with the handled URI.
+ if (!FindInReadable("%s"_ns, spec)) {
+ aRv.ThrowSyntaxError("Handler URI does not contain \"%s\".");
+ return;
+ }
+
+ // For security reasons we reject non-http(s) urls (see bug 354316),
+ nsAutoCString docScheme;
+ nsAutoCString handlerScheme;
+ aDocumentURI->GetScheme(docScheme);
+ aHandlerURI->GetScheme(handlerScheme);
+ if ((!docScheme.EqualsLiteral("https") && !docScheme.EqualsLiteral("http")) ||
+ (!handlerScheme.EqualsLiteral("https") &&
+ !handlerScheme.EqualsLiteral("http"))) {
+ raisePermissionDeniedHandler();
+ return;
+ }
+
+ // Should be same-origin:
+ nsAutoCString handlerHost;
+ aHandlerURI->GetHostPort(handlerHost);
+ nsAutoCString documentHost;
+ aDocumentURI->GetHostPort(documentHost);
+ if (!handlerHost.Equals(documentHost) || !handlerScheme.Equals(docScheme)) {
+ raisePermissionDeniedHandler();
+ return;
+ }
+
+ // Having checked the handler URI, check the scheme:
+ nsAutoCString scheme;
+ ToLowerCase(NS_ConvertUTF16toUTF8(aScheme), scheme);
+ if (StringBeginsWith(scheme, "web+"_ns)) {
+ // Check for non-ascii
+ nsReadingIterator<char> iter;
+ nsReadingIterator<char> iterEnd;
+ auto remainingScheme = Substring(scheme, 4 /* web+ */);
+ remainingScheme.BeginReading(iter);
+ remainingScheme.EndReading(iterEnd);
+ // Scheme suffix must be non-empty
+ if (iter == iterEnd) {
+ raisePermissionDeniedScheme();
+ return;
+ }
+ for (; iter != iterEnd; iter++) {
+ if (*iter < 'a' || *iter > 'z') {
+ raisePermissionDeniedScheme();
+ return;
+ }
+ }
+ } else {
+ bool matches = false;
+ for (const char* safeScheme : kSafeSchemes) {
+ if (scheme.Equals(safeScheme)) {
+ matches = true;
+ break;
+ }
+ }
+ if (!matches) {
+ raisePermissionDeniedScheme();
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsCOMPtr<nsIIOService> io = components::IO::Service();
+ if (NS_FAILED(
+ io->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)))) {
+ raisePermissionDeniedScheme();
+ return;
+ }
+
+ // check if we have prefs set saying not to add this.
+ bool defaultExternal =
+ Preferences::GetBool("network.protocol-handler.external-default");
+ nsPrintfCString specificPref("network.protocol-handler.external.%s",
+ scheme.get());
+ if (!Preferences::GetBool(specificPref.get(), defaultExternal)) {
+ raisePermissionDeniedScheme();
+ return;
+ }
+
+ // Check to make sure this isn't already handled internally (we don't
+ // want to let them take over, say "chrome"). In theory, the checks above
+ // should have already taken care of this.
+ nsCOMPtr<nsIExternalProtocolHandler> externalHandler =
+ do_QueryInterface(handler);
+ MOZ_RELEASE_ASSERT(
+ externalHandler,
+ "We should never allow overriding a builtin protocol handler");
+}
+
+void Navigator::RegisterProtocolHandler(const nsAString& aScheme,
+ const nsAString& aURI,
+ ErrorResult& aRv) {
+ if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell() ||
+ !mWindow->GetDoc()) {
+ return;
+ }
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow);
+ if (loadContext->UsePrivateBrowsing()) {
+ // If we're a private window, don't alert the user or webpage. We log to the
+ // console so that web developers have some way to tell what's going wrong.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM"_ns, mWindow->GetDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "RegisterProtocolHandlerPrivateBrowsingWarning");
+ return;
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetDoc();
+
+ // Determine if doc is allowed to assign this handler
+ nsIURI* docURI = doc->GetDocumentURIObject();
+ nsCOMPtr<nsIURI> handlerURI;
+ NS_NewURI(getter_AddRefs(handlerURI), NS_ConvertUTF16toUTF8(aURI),
+ doc->GetDocumentCharacterSet(), docURI);
+ CheckProtocolHandlerAllowed(aScheme, handlerURI, docURI, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Determine a title from the document URI.
+ nsAutoCString docDisplayHostPort;
+ docURI->GetDisplayHostPort(docDisplayHostPort);
+ NS_ConvertASCIItoUTF16 title(docDisplayHostPort);
+
+ if (XRE_IsContentProcess()) {
+ nsAutoString scheme(aScheme);
+ RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(mWindow);
+ browserChild->SendRegisterProtocolHandler(scheme, handlerURI, title,
+ docURI);
+ return;
+ }
+
+ nsCOMPtr<nsIWebProtocolHandlerRegistrar> registrar =
+ do_GetService(NS_WEBPROTOCOLHANDLERREGISTRAR_CONTRACTID);
+ if (registrar) {
+ aRv = registrar->RegisterProtocolHandler(aScheme, handlerURI, title, docURI,
+ mWindow->GetOuterWindow());
+ }
+}
+
+Geolocation* Navigator::GetGeolocation(ErrorResult& aRv) {
+ if (mGeolocation) {
+ return mGeolocation;
+ }
+
+ if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mGeolocation = new Geolocation();
+ if (NS_FAILED(mGeolocation->Init(mWindow))) {
+ mGeolocation = nullptr;
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return mGeolocation;
+}
+
+class BeaconStreamListener final : public nsIStreamListener {
+ ~BeaconStreamListener() = default;
+
+ public:
+ BeaconStreamListener() : mLoadGroup(nullptr) {}
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+};
+
+NS_IMPL_ISUPPORTS(BeaconStreamListener, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+BeaconStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ // release the loadgroup first
+ mLoadGroup = nullptr;
+
+ return NS_ERROR_ABORT;
+}
+
+NS_IMETHODIMP
+BeaconStreamListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BeaconStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+bool Navigator::SendBeacon(const nsAString& aUrl,
+ const Nullable<fetch::BodyInit>& aData,
+ ErrorResult& aRv) {
+ if (aData.IsNull()) {
+ return SendBeaconInternal(aUrl, nullptr, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv);
+ }
+
+ if (aData.Value().IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(
+ &aData.Value().GetAsArrayBufferView());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv);
+ }
+
+ if (aData.Value().IsBlob()) {
+ BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeBlob, aRv);
+ }
+
+ if (aData.Value().IsFormData()) {
+ BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsURLSearchParams()) {
+ BodyExtractor<const URLSearchParams> body(
+ &aData.Value().GetAsURLSearchParams());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ MOZ_CRASH("Invalid data type.");
+ return false;
+}
+
+bool Navigator::SendBeaconInternal(const nsAString& aUrl,
+ BodyExtractorBase* aBody, BeaconType aType,
+ ErrorResult& aRv) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ nsIURI* documentURI = doc->GetDocumentURI();
+ if (!documentURI) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(uri), aUrl, doc, doc->GetDocBaseURI());
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(NS_ConvertUTF16toUTF8(aUrl));
+ return false;
+ }
+
+ // Spec disallows any schemes save for HTTP/HTTPs
+ if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) {
+ aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Beacon",
+ uri->GetSpecOrDefault());
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> in;
+ nsAutoCString contentTypeWithCharset;
+ nsAutoCString charset;
+ uint64_t length = 0;
+ if (aBody) {
+ aRv = aBody->GetAsStream(getter_AddRefs(in), &length,
+ contentTypeWithCharset, charset);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+ }
+
+ nsSecurityFlags securityFlags = nsILoadInfo::SEC_COOKIES_INCLUDE;
+ // Ensure that only streams with content types that are safelisted ignore CORS
+ // rules
+ if (aBody && !contentTypeWithCharset.IsVoid() &&
+ !nsContentUtils::IsCORSSafelistedRequestHeader("content-type"_ns,
+ contentTypeWithCharset)) {
+ securityFlags |= nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ } else {
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri, doc, securityFlags,
+ nsIContentPolicy::TYPE_BEACON);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ // Beacon spec only supports HTTP requests at this time
+ aRv.Throw(NS_ERROR_DOM_BAD_URI);
+ return false;
+ }
+
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (aBody) {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
+ if (!uploadChannel) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length,
+ "POST"_ns, false);
+ } else {
+ rv = httpChannel->SetRequestMethod("POST"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(channel);
+ if (p) {
+ p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Background);
+ }
+
+ // The channel needs to have a loadgroup associated with it, so that we can
+ // cancel the channel and any redirected channels it may create.
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ nsCOMPtr<nsIInterfaceRequestor> callbacks =
+ do_QueryInterface(mWindow->GetDocShell());
+ loadGroup->SetNotificationCallbacks(callbacks);
+ channel->SetLoadGroup(loadGroup);
+
+ RefPtr<BeaconStreamListener> beaconListener = new BeaconStreamListener();
+ rv = channel->AsyncOpen(beaconListener);
+ // do not throw if security checks fail within asyncOpen
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // make the beaconListener hold a strong reference to the loadgroup
+ // which is released in ::OnStartRequest
+ beaconListener->SetLoadGroup(loadGroup);
+
+ return true;
+}
+
+MediaDevices* Navigator::GetMediaDevices(ErrorResult& aRv) {
+ if (!mMediaDevices) {
+ if (!mWindow || !mWindow->GetOuterWindow() ||
+ mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ mMediaDevices = new MediaDevices(mWindow);
+ }
+ return mMediaDevices;
+}
+
+void Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
+ NavigatorUserMediaSuccessCallback& aOnSuccess,
+ NavigatorUserMediaErrorCallback& aOnError,
+ CallerType aCallerType, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mWindow || !mWindow->IsFullyActive()) {
+ aRv.ThrowInvalidStateError("The document is not fully active.");
+ return;
+ }
+ GetMediaDevices(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ MOZ_ASSERT(mMediaDevices);
+ if (Document* doc = mWindow->GetExtantDoc()) {
+ if (!mWindow->IsSecureContext()) {
+ doc->SetUseCounter(eUseCounter_custom_MozGetUserMediaInsec);
+ }
+ }
+ RefPtr<MediaManager::StreamPromise> sp;
+ if (!MediaManager::IsOn(aConstraints.mVideo) &&
+ !MediaManager::IsOn(aConstraints.mAudio)) {
+ sp = MediaManager::StreamPromise::CreateAndReject(
+ MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError,
+ "audio and/or video is required"),
+ __func__);
+ } else {
+ sp = mMediaDevices->GetUserMedia(mWindow, aConstraints, aCallerType);
+ }
+ RefPtr<NavigatorUserMediaSuccessCallback> onsuccess(&aOnSuccess);
+ RefPtr<NavigatorUserMediaErrorCallback> onerror(&aOnError);
+
+ nsWeakPtr weakWindow = nsWeakPtr(do_GetWeakReference(mWindow));
+ sp->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [weakWindow, onsuccess = std::move(onsuccess)](
+ const RefPtr<DOMMediaStream>& aStream) MOZ_CAN_RUN_SCRIPT {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow);
+ if (!window || !window->GetOuterWindow() ||
+ window->GetOuterWindow()->GetCurrentInnerWindow() != window) {
+ return; // Leave Promise pending after navigation by design.
+ }
+ MediaManager::CallOnSuccess(*onsuccess, *aStream);
+ },
+ [weakWindow, onerror = std::move(onerror)](
+ const RefPtr<MediaMgrError>& aError) MOZ_CAN_RUN_SCRIPT {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow);
+ if (!window || !window->GetOuterWindow() ||
+ window->GetOuterWindow()->GetCurrentInnerWindow() != window) {
+ return; // Leave Promise pending after navigation by design.
+ }
+ auto error = MakeRefPtr<MediaStreamError>(window, *aError);
+ MediaManager::CallOnError(*onerror, *error);
+ });
+}
+
+//*****************************************************************************
+// Navigator::nsINavigatorBattery
+//*****************************************************************************
+
+Promise* Navigator::GetBattery(ErrorResult& aRv) {
+ if (mBatteryPromise) {
+ return mBatteryPromise;
+ }
+
+ if (!mWindow || !mWindow->GetDocShell()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<Promise> batteryPromise = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ mBatteryPromise = batteryPromise;
+
+ if (!mBatteryManager) {
+ mBatteryManager = new battery::BatteryManager(mWindow);
+ mBatteryManager->Init();
+ }
+
+ mBatteryPromise->MaybeResolve(mBatteryManager);
+
+ return mBatteryPromise;
+}
+
+//*****************************************************************************
+// Navigator::Share() - Web Share API
+//*****************************************************************************
+
+already_AddRefed<Promise> Navigator::Share(const ShareData& aData,
+ ErrorResult& aRv) {
+ if (!mWindow || !mWindow->IsFullyActive()) {
+ aRv.ThrowInvalidStateError("The document is not fully active.");
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!mWindow->GetDocShell() || !mWindow->GetExtantDoc())) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(),
+ u"web-share"_ns)) {
+ aRv.ThrowNotAllowedError(
+ "Document's Permissions Policy does not allow calling "
+ "share() from this context.");
+ return nullptr;
+ }
+
+ if (mSharePromise) {
+ NS_WARNING("Only one share picker at a time per navigator instance");
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // null checked above
+ Document* doc = mWindow->GetExtantDoc();
+
+ if (StaticPrefs::dom_webshare_requireinteraction() &&
+ !doc->ConsumeTransientUserGestureActivation()) {
+ aRv.ThrowNotAllowedError(
+ "User activation was already consumed "
+ "or share() was not activated by a user gesture.");
+ return nullptr;
+ }
+
+ ValidateShareData(aData, aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // TODO: Process file member, which we don't currently support.
+
+ // If data's url member is present, try to resolve it...
+ nsCOMPtr<nsIURI> url;
+ if (aData.mUrl.WasPassed()) {
+ auto result = doc->ResolveWithBaseURI(aData.mUrl.Value());
+ url = result.unwrap();
+ MOZ_ASSERT(url);
+ }
+
+ // Process the title member...
+ nsCString title;
+ if (aData.mTitle.WasPassed()) {
+ title.Assign(NS_ConvertUTF16toUTF8(aData.mTitle.Value()));
+ } else {
+ title.SetIsVoid(true);
+ }
+
+ // Process the text member...
+ nsCString text;
+ if (aData.mText.WasPassed()) {
+ text.Assign(NS_ConvertUTF16toUTF8(aData.mText.Value()));
+ } else {
+ text.SetIsVoid(true);
+ }
+
+ // Let mSharePromise be a new promise.
+ mSharePromise = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ IPCWebShareData data(title, text, url);
+ auto wgc = mWindow->GetWindowGlobalChild();
+ if (!wgc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Do the share
+ wgc->SendShare(data)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}](
+ PWindowGlobalChild::SharePromise::ResolveOrRejectValue&& aResult) {
+ if (aResult.IsResolve()) {
+ if (NS_SUCCEEDED(aResult.ResolveValue())) {
+ self->mSharePromise->MaybeResolveWithUndefined();
+ } else {
+ self->mSharePromise->MaybeReject(aResult.ResolveValue());
+ }
+ } else if (self->mSharePromise) {
+ // IPC died
+ self->mSharePromise->MaybeReject(NS_BINDING_ABORTED);
+ }
+ self->mSharePromise = nullptr;
+ });
+ return do_AddRef(mSharePromise);
+}
+
+//*****************************************************************************
+// Navigator::CanShare() - Web Share API
+//*****************************************************************************
+bool Navigator::CanShare(const ShareData& aData) {
+ if (!mWindow || !mWindow->IsFullyActive()) {
+ return false;
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(),
+ u"web-share"_ns)) {
+ return false;
+ }
+
+ IgnoredErrorResult rv;
+ ValidateShareData(aData, rv);
+ return !rv.Failed();
+}
+
+void Navigator::ValidateShareData(const ShareData& aData, ErrorResult& aRv) {
+ // TODO: remove this check when we support files share.
+ if (aData.mFiles.WasPassed() && !aData.mFiles.Value().IsEmpty()) {
+ aRv.ThrowTypeError("Passing files is currently not supported.");
+ return;
+ }
+
+ bool titleTextOrUrlPassed = aData.mTitle.WasPassed() ||
+ aData.mText.WasPassed() || aData.mUrl.WasPassed();
+
+ // At least one member must be present.
+ if (!titleTextOrUrlPassed) {
+ aRv.ThrowTypeError(
+ "Must have a title, text, or url member in the ShareData dictionary");
+ return;
+ }
+
+ // If data's url member is present, try to resolve it...
+ nsCOMPtr<nsIURI> url;
+ if (aData.mUrl.WasPassed()) {
+ Document* doc = mWindow->GetExtantDoc();
+ Result<OwningNonNull<nsIURI>, nsresult> result =
+ doc->ResolveWithBaseURI(aData.mUrl.Value());
+ if (NS_WARN_IF(result.isErr())) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(
+ NS_ConvertUTF16toUTF8(aData.mUrl.Value()));
+ return;
+ }
+ url = result.unwrap();
+ // Check that we only share loadable URLs (e.g., http/https).
+ // we also exclude blobs, as it doesn't make sense to share those outside
+ // the context of the browser.
+ const uint32_t flags =
+ nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL |
+ nsIScriptSecurityManager::DISALLOW_SCRIPT;
+ if (NS_FAILED(
+ nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
+ doc->NodePrincipal(), url, flags, doc->InnerWindowID())) ||
+ url->SchemeIs("blob")) {
+ aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Share",
+ url->GetSpecOrDefault());
+ return;
+ }
+ }
+}
+
+static bool ShouldResistFingerprinting(const Document* aDoc,
+ RFPTarget aTarget) {
+ return aDoc ? aDoc->ShouldResistFingerprinting(aTarget)
+ : nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget);
+}
+
+already_AddRefed<LegacyMozTCPSocket> Navigator::MozTCPSocket() {
+ RefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
+ return socket.forget();
+}
+
+void Navigator::GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads,
+ ErrorResult& aRv) {
+ if (!mWindow || !mWindow->IsFullyActive()) {
+ return;
+ }
+ NS_ENSURE_TRUE_VOID(mWindow->GetDocShell());
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(win->GetExtantDoc(),
+ u"gamepad"_ns)) {
+ aRv.ThrowSecurityError(
+ "Document's Permission Policy does not allow calling "
+ "getGamepads() from this context.");
+ return;
+ }
+
+ win->SetHasGamepadEventListener(true);
+ win->GetGamepads(aGamepads);
+}
+
+GamepadServiceTest* Navigator::RequestGamepadServiceTest(ErrorResult& aRv) {
+#ifdef FUZZING
+ if (!StaticPrefs::fuzzing_enabled()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+#else
+ if (!xpc::IsInAutomation()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+#endif
+
+ if (!mGamepadServiceTest) {
+ mGamepadServiceTest = GamepadServiceTest::CreateTestService(mWindow);
+ }
+ return mGamepadServiceTest;
+}
+
+already_AddRefed<Promise> Navigator::GetVRDisplays(ErrorResult& aRv) {
+ if (!mWindow || !mWindow->GetDocShell() || !mWindow->GetExtantDoc()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(),
+ u"vr"_ns)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow));
+ if (!browser) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ FinishGetVRDisplays(true, p);
+ } else {
+ RefPtr<Navigator> self(this);
+ int browserID = browser->ChromeOuterWindowID();
+
+ browser->SendIsWindowSupportingWebVR(browserID)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, p](bool isSupported) {
+ self->FinishGetVRDisplays(isSupported, p);
+ },
+ [p](const mozilla::ipc::ResponseRejectReason) {
+ p->MaybeRejectWithTypeError("Unable to start display enumeration");
+ });
+ }
+
+ return p.forget();
+}
+
+void Navigator::FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p) {
+ if (!isWebVRSupportedInwindow) {
+ // WebVR in this window is not supported, so resolve the promise
+ // with no displays available
+ nsTArray<RefPtr<VRDisplay>> vrDisplaysEmpty;
+ p->MaybeResolve(vrDisplaysEmpty);
+ return;
+ }
+
+ // Since FinishGetVRDisplays can be called asynchronously after an IPC
+ // response, it's possible that the Window can be torn down before this
+ // call. In that case, the Window's cyclic references to VR objects are
+ // also torn down and should not be recreated via
+ // NotifyHasXRSession.
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ if (win->IsDying()) {
+ // The Window has been torn down, so there is no further work that can
+ // be done.
+ p->MaybeRejectWithTypeError(
+ "Unable to return VRDisplays for a closed window.");
+ return;
+ }
+
+ mVRGetDisplaysPromises.AppendElement(p);
+ win->RequestXRPermission();
+}
+
+void Navigator::OnXRPermissionRequestAllow() {
+ // The permission request that results in this callback could have
+ // been instantiated by WebVR, WebXR, or both.
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ bool usingWebXR = false;
+
+ if (mXRSystem) {
+ usingWebXR = mXRSystem->OnXRPermissionRequestAllow();
+ }
+
+ bool rejectWebVR = true;
+ // If WebVR and WebXR both requested permission, only grant it to
+ // WebXR, which takes priority.
+ if (!usingWebXR) {
+ // We pass mWindow's id to RefreshVRDisplays, so NotifyVRDisplaysUpdated
+ // will be called asynchronously, resolving the promises in
+ // mVRGetDisplaysPromises.
+ rejectWebVR = !VRDisplay::RefreshVRDisplays(win->WindowID());
+ }
+ // Even if WebXR took priority, reject requests for WebVR in case they were
+ // made simultaneously and coelesced into a single permission prompt.
+ if (rejectWebVR) {
+ for (auto& p : mVRGetDisplaysPromises) {
+ // Failed to refresh, reject the promise now
+ p->MaybeRejectWithTypeError("Failed to find attached VR displays.");
+ }
+ mVRGetDisplaysPromises.Clear();
+ }
+}
+
+void Navigator::OnXRPermissionRequestCancel() {
+ if (mXRSystem) {
+ mXRSystem->OnXRPermissionRequestCancel();
+ }
+
+ nsTArray<RefPtr<VRDisplay>> vrDisplays;
+ for (auto& p : mVRGetDisplaysPromises) {
+ // Resolve the promise with no vr displays when
+ // the user blocks access.
+ p->MaybeResolve(vrDisplays);
+ }
+ mVRGetDisplaysPromises.Clear();
+}
+
+void Navigator::GetActiveVRDisplays(
+ nsTArray<RefPtr<VRDisplay>>& aDisplays) const {
+ /**
+ * Get only the active VR displays.
+ * GetActiveVRDisplays should only enumerate displays that
+ * are already active without causing any other hardware to be
+ * activated.
+ * We must not call nsGlobalWindow::NotifyHasXRSession here,
+ * as that would cause enumeration and activation of other VR hardware.
+ * Activating VR hardware is intrusive to the end user, as it may
+ * involve physically powering on devices that the user did not
+ * intend to use.
+ */
+ if (!mWindow || !mWindow->GetDocShell()) {
+ return;
+ }
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ nsTArray<RefPtr<VRDisplay>> displays;
+ if (win->UpdateVRDisplays(displays)) {
+ for (auto display : displays) {
+ if (display->IsPresenting()) {
+ aDisplays.AppendElement(display);
+ }
+ }
+ }
+}
+
+void Navigator::NotifyVRDisplaysUpdated() {
+ // Synchronize the VR devices and resolve the promises in
+ // mVRGetDisplaysPromises
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+
+ nsTArray<RefPtr<VRDisplay>> vrDisplays;
+ if (win->UpdateVRDisplays(vrDisplays)) {
+ for (auto p : mVRGetDisplaysPromises) {
+ p->MaybeResolve(vrDisplays);
+ }
+ } else {
+ for (auto p : mVRGetDisplaysPromises) {
+ p->MaybeReject(NS_ERROR_FAILURE);
+ }
+ }
+ mVRGetDisplaysPromises.Clear();
+}
+
+void Navigator::NotifyActiveVRDisplaysChanged() {
+ Navigator_Binding::ClearCachedActiveVRDisplaysValue(this);
+}
+
+VRServiceTest* Navigator::RequestVRServiceTest(ErrorResult& aRv) {
+ if (!xpc::IsInAutomation()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ // Ensure that the Mock VR devices are not released prematurely
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ win->NotifyHasXRSession();
+
+ if (!mVRServiceTest) {
+ mVRServiceTest = VRServiceTest::CreateTestService(mWindow);
+ }
+ return mVRServiceTest;
+}
+
+XRSystem* Navigator::GetXr(ErrorResult& aRv) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ if (!mXRSystem) {
+ mXRSystem = XRSystem::Create(mWindow);
+ }
+ return mXRSystem;
+}
+
+bool Navigator::IsWebVRContentDetected() const {
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ return win->IsVRContentDetected();
+}
+
+bool Navigator::IsWebVRContentPresenting() const {
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ return win->IsVRContentPresenting();
+}
+
+void Navigator::RequestVRPresentation(VRDisplay& aDisplay) {
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
+ win->DispatchVRDisplayActivate(aDisplay.DisplayId(),
+ VRDisplayEventReason::Requested);
+}
+
+already_AddRefed<Promise> Navigator::RequestMIDIAccess(
+ const MIDIOptions& aOptions, ErrorResult& aRv) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ MIDIAccessManager* accessMgr = MIDIAccessManager::Get();
+ return accessMgr->RequestMIDIAccess(mWindow, aOptions, aRv);
+}
+
+network::Connection* Navigator::GetConnection(ErrorResult& aRv) {
+ if (!mConnection) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mConnection = network::Connection::CreateForWindow(
+ mWindow,
+ nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting());
+ }
+
+ return mConnection;
+}
+
+already_AddRefed<ServiceWorkerContainer> Navigator::ServiceWorker() {
+ MOZ_ASSERT(mWindow);
+
+ if (!mServiceWorkerContainer) {
+ mServiceWorkerContainer =
+ ServiceWorkerContainer::Create(mWindow->AsGlobal());
+ }
+
+ RefPtr<ServiceWorkerContainer> ref = mServiceWorkerContainer;
+ return ref.forget();
+}
+
+already_AddRefed<ServiceWorkerContainer> Navigator::ServiceWorkerJS() {
+ if (mWindow->AsGlobal()->GetStorageAccess() ==
+ StorageAccess::ePrivateBrowsing) {
+ SetUseCounter(mWindow->AsGlobal()->GetGlobalJSObject(),
+ eUseCounter_custom_PrivateBrowsingNavigatorServiceWorker);
+ }
+
+ return ServiceWorker();
+}
+
+size_t Navigator::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ // TODO: add SizeOfIncludingThis() to nsMimeTypeArray, bug 674113.
+ // TODO: add SizeOfIncludingThis() to nsPluginArray, bug 674114.
+ // TODO: add SizeOfIncludingThis() to Geolocation, bug 674115.
+ // TODO: add SizeOfIncludingThis() to DesktopNotificationCenter, bug 674116.
+
+ return n;
+}
+
+void Navigator::OnNavigation() {
+ if (!mWindow) {
+ return;
+ }
+
+ // If MediaManager is open let it inform any live streams or pending callbacks
+ MediaManager* manager = MediaManager::GetIfExists();
+ if (manager) {
+ manager->OnNavigation(mWindow->WindowID());
+ }
+}
+
+JSObject* Navigator::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Navigator_Binding::Wrap(cx, this, aGivenProto);
+}
+
+/* static */
+bool Navigator::HasUserMediaSupport(JSContext* cx, JSObject* obj) {
+ // Make enabling peerconnection enable getUserMedia() as well.
+ // Emulate [SecureContext] unless media.devices.insecure.enabled=true
+ return (StaticPrefs::media_navigator_enabled() ||
+ StaticPrefs::media_peerconnection_enabled()) &&
+ (IsSecureContextOrObjectIsFromSecureContext(cx, obj) ||
+ StaticPrefs::media_devices_insecure_enabled());
+}
+
+/* static */
+bool Navigator::HasShareSupport(JSContext* cx, JSObject* obj) {
+ if (!StaticPrefs::dom_webshare_enabled()) {
+ return false;
+ }
+#if defined(XP_WIN) && !defined(__MINGW32__)
+ // The first public build that supports ShareCanceled API
+ return IsWindows10BuildOrLater(18956);
+#else
+ return true;
+#endif
+}
+
+/* static */
+bool Navigator::HasMidiSupport(JSContext* cx, JSObject* obj) {
+ nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(cx);
+
+ // Enable on secure contexts but exclude file schemes.
+ return StaticPrefs::dom_webmidi_enabled() &&
+ IsSecureContextOrObjectIsFromSecureContext(cx, obj) &&
+ !principal->SchemeIs("file");
+}
+
+/* static */
+already_AddRefed<nsPIDOMWindowInner> Navigator::GetWindowFromGlobal(
+ JSObject* aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(aGlobal);
+ return win.forget();
+}
+
+void Navigator::ClearPlatformCache() {
+ Navigator_Binding::ClearCachedPlatformValue(this);
+}
+
+nsresult Navigator::GetPlatform(nsAString& aPlatform, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue) {
+ // If fingerprinting resistance is on, we will spoof this value. See
+ // nsRFPService.h for details about spoofed values.
+ if (ShouldResistFingerprinting(aCallerDoc, RFPTarget::NavigatorPlatform)) {
+ aPlatform.AssignLiteral(SPOOFED_PLATFORM);
+ return NS_OK;
+ }
+ nsAutoString override;
+ nsresult rv =
+ mozilla::Preferences::GetString("general.platform.override", override);
+
+ if (NS_SUCCEEDED(rv)) {
+ aPlatform = override;
+ return NS_OK;
+ }
+ }
+
+#if defined(WIN32)
+ aPlatform.AssignLiteral("Win32");
+#elif defined(XP_MACOSX)
+ // Always return "MacIntel", even on ARM64 macOS like Safari does.
+ aPlatform.AssignLiteral("MacIntel");
+#else
+ nsresult rv;
+ nsCOMPtr<nsIHttpProtocolHandler> service(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString plat;
+ rv = service->GetOscpu(plat);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyASCIItoUTF16(plat, aPlatform);
+#endif
+
+ return NS_OK;
+}
+
+/* static */
+nsresult Navigator::GetAppVersion(nsAString& aAppVersion, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue) {
+ // If fingerprinting resistance is on, we will spoof this value. See
+ // nsRFPService.h for details about spoofed values.
+ if (ShouldResistFingerprinting(aCallerDoc,
+ RFPTarget::NavigatorAppVersion)) {
+ aAppVersion.AssignLiteral(SPOOFED_APPVERSION);
+ return NS_OK;
+ }
+ nsAutoString override;
+ nsresult rv = mozilla::Preferences::GetString("general.appversion.override",
+ override);
+
+ if (NS_SUCCEEDED(rv)) {
+ aAppVersion = override;
+ return NS_OK;
+ }
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpProtocolHandler> service(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString str;
+ rv = service->GetAppVersion(str);
+ CopyASCIItoUTF16(str, aAppVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aAppVersion.AppendLiteral(" (");
+
+ rv = service->GetPlatform(str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AppendASCIItoUTF16(str, aAppVersion);
+ aAppVersion.Append(char16_t(')'));
+
+ return rv;
+}
+
+/* static */
+void Navigator::AppName(nsAString& aAppName, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue) {
+ // If fingerprinting resistance is on, we will spoof this value. See
+ // nsRFPService.h for details about spoofed values.
+ if (ShouldResistFingerprinting(aCallerDoc, RFPTarget::NavigatorAppName)) {
+ aAppName.AssignLiteral(SPOOFED_APPNAME);
+ return;
+ }
+
+ nsAutoString override;
+ nsresult rv =
+ mozilla::Preferences::GetString("general.appname.override", override);
+
+ if (NS_SUCCEEDED(rv)) {
+ aAppName = override;
+ return;
+ }
+ }
+
+ aAppName.AssignLiteral("Netscape");
+}
+
+void Navigator::ClearUserAgentCache() {
+ Navigator_Binding::ClearCachedUserAgentValue(this);
+}
+
+nsresult Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow,
+ Document* aCallerDoc,
+ Maybe<bool> aShouldResistFingerprinting,
+ nsAString& aUserAgent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ /*
+ ResistFingerprinting is migrating to fine-grained control based off
+ either a channel or Principal+OriginAttributes
+
+ This function can be called from Workers, Main Thread, and at least one
+ other (unusual) case.
+
+ For Main Thread, we will generally have a window and an associated
+ Document, for Workers we will not.
+
+ If aShouldResistFingerprinting is provided, we should respect it.
+ If it is not provided, we will use aCallerDoc to determine our behavior.
+ */
+
+ bool shouldResistFingerprinting =
+ aShouldResistFingerprinting.isSome()
+ ? aShouldResistFingerprinting.value()
+ : ShouldResistFingerprinting(aCallerDoc,
+ RFPTarget::NavigatorUserAgent);
+
+ // We will skip the override and pass to httpHandler to get spoofed userAgent
+ // when 'privacy.resistFingerprinting' is true.
+ if (!shouldResistFingerprinting) {
+ nsAutoString override;
+ nsresult rv =
+ mozilla::Preferences::GetString("general.useragent.override", override);
+
+ if (NS_SUCCEEDED(rv)) {
+ aUserAgent = override;
+ return NS_OK;
+ }
+ }
+
+ // When the caller is content and 'privacy.resistFingerprinting' is true,
+ // return a spoofed userAgent which reveals the platform but not the
+ // specific OS version, etc.
+ if (shouldResistFingerprinting) {
+ nsAutoCString spoofedUA;
+ nsRFPService::GetSpoofedUserAgent(spoofedUA, false);
+ CopyASCIItoUTF16(spoofedUA, aUserAgent);
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpProtocolHandler> service(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString ua;
+ rv = service->GetUserAgent(ua);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CopyASCIItoUTF16(ua, aUserAgent);
+
+ if (!aWindow) {
+ return NS_OK;
+ }
+
+ // Copy the User-Agent header from the document channel which has already been
+ // subject to UA overrides.
+ nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(doc->GetChannel());
+ if (httpChannel) {
+ nsAutoCString userAgent;
+ rv = httpChannel->GetRequestHeader("User-Agent"_ns, userAgent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ CopyASCIItoUTF16(userAgent, aUserAgent);
+ }
+ return NS_OK;
+}
+
+static nsCString RequestKeySystemAccessLogString(
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ bool aIsSecureContext) {
+ nsCString str;
+ str.AppendPrintf(
+ "Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=",
+ NS_ConvertUTF16toUTF8(aKeySystem).get());
+ str.Append(MediaKeySystemAccess::ToCString(aConfigs));
+ str.AppendLiteral(") secureContext=");
+ str.AppendInt(aIsSecureContext);
+ return str;
+}
+
+already_AddRefed<Promise> Navigator::RequestMediaKeySystemAccess(
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs, ErrorResult& aRv) {
+ EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs,
+ mWindow->IsSecureContext())
+ .get());
+
+ if (!mWindow->IsSecureContext()) {
+ Document* doc = mWindow->GetExtantDoc();
+ AutoTArray<nsString, 1> params;
+ nsString* uri = params.AppendElement();
+ if (doc) {
+ Unused << doc->GetDocumentURI(*uri);
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Media"_ns,
+ doc, nsContentUtils::eDOM_PROPERTIES,
+ "MediaEMEInsecureContextDeprecatedWarning",
+ params);
+ }
+
+ Document* doc = mWindow->GetExtantDoc();
+ if (doc &&
+ !FeaturePolicyUtils::IsFeatureAllowed(doc, u"encrypted-media"_ns)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<DetailedPromise> promise = DetailedPromise::Create(
+ mWindow->AsGlobal(), aRv, "navigator.requestMediaKeySystemAccess"_ns);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
+ }
+
+ mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs);
+ return promise.forget();
+}
+
+CredentialsContainer* Navigator::Credentials() {
+ if (!mCredentials) {
+ mCredentials = new CredentialsContainer(GetWindow());
+ }
+ return mCredentials;
+}
+
+dom::MediaCapabilities* Navigator::MediaCapabilities() {
+ if (!mMediaCapabilities) {
+ mMediaCapabilities = new dom::MediaCapabilities(GetWindow()->AsGlobal());
+ }
+ return mMediaCapabilities;
+}
+
+dom::MediaSession* Navigator::MediaSession() {
+ if (!mMediaSession) {
+ mMediaSession = new dom::MediaSession(GetWindow());
+ }
+ return mMediaSession;
+}
+
+bool Navigator::HasCreatedMediaSession() const {
+ return mMediaSession != nullptr;
+}
+
+Clipboard* Navigator::Clipboard() {
+ if (!mClipboard) {
+ mClipboard = new dom::Clipboard(GetWindow());
+ }
+ return mClipboard;
+}
+
+AddonManager* Navigator::GetMozAddonManager(ErrorResult& aRv) {
+ if (!mAddonManager) {
+ nsPIDOMWindowInner* win = GetWindow();
+ if (!win) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ mAddonManager = ConstructJSImplementation<AddonManager>(
+ "@mozilla.org/addon-web-api/manager;1", win->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return mAddonManager;
+}
+
+webgpu::Instance* Navigator::Gpu() {
+ if (!mWebGpu) {
+ mWebGpu = webgpu::Instance::Create(GetWindow()->AsGlobal());
+ }
+ return mWebGpu;
+}
+
+dom::LockManager* Navigator::Locks() {
+ if (!mLocks) {
+ mLocks = new dom::LockManager(GetWindow()->AsGlobal());
+ }
+ return mLocks;
+}
+
+/* static */
+bool Navigator::Webdriver() {
+#ifdef ENABLE_WEBDRIVER
+ nsCOMPtr<nsIMarionette> marionette = do_GetService(NS_MARIONETTE_CONTRACTID);
+ if (marionette) {
+ bool marionetteRunning = false;
+ marionette->GetRunning(&marionetteRunning);
+ if (marionetteRunning) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIRemoteAgent> agent = do_GetService(NS_REMOTEAGENT_CONTRACTID);
+ if (agent) {
+ bool remoteAgentRunning = false;
+ agent->GetRunning(&remoteAgentRunning);
+ if (remoteAgentRunning) {
+ return true;
+ }
+ }
+#endif
+
+ return false;
+}
+
+AutoplayPolicy Navigator::GetAutoplayPolicy(AutoplayPolicyMediaType aType) {
+ if (!mWindow) {
+ return AutoplayPolicy::Disallowed;
+ }
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ return AutoplayPolicy::Disallowed;
+ }
+ return media::AutoplayPolicy::GetAutoplayPolicy(aType, *doc);
+}
+
+AutoplayPolicy Navigator::GetAutoplayPolicy(HTMLMediaElement& aElement) {
+ return media::AutoplayPolicy::GetAutoplayPolicy(aElement);
+}
+
+AutoplayPolicy Navigator::GetAutoplayPolicy(AudioContext& aContext) {
+ return media::AutoplayPolicy::GetAutoplayPolicy(aContext);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
new file mode 100644
index 0000000000..cbe8d6bb27
--- /dev/null
+++ b/dom/base/Navigator.h
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Navigator_h
+#define mozilla_dom_Navigator_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/AddonManagerBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsWrapperCache.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/MediaKeySystemAccessManager.h"
+
+class nsPluginArray;
+class nsMimeTypeArray;
+class nsPIDOMWindowInner;
+class nsIDOMNavigatorSystemMessages;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class AddonManager;
+class BodyExtractorBase;
+class Geolocation;
+class systemMessageCallback;
+class MediaDevices;
+struct MediaStreamConstraints;
+class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
+class ServiceWorkerContainer;
+class DOMRequest;
+class CredentialsContainer;
+class Clipboard;
+class LockManager;
+class HTMLMediaElement;
+class AudioContext;
+} // namespace dom
+namespace webgpu {
+class Instance;
+} // namespace webgpu
+} // namespace mozilla
+
+//*****************************************************************************
+// Navigator: Script "navigator" object
+//*****************************************************************************
+
+namespace mozilla::dom {
+
+class Permissions;
+
+namespace battery {
+class BatteryManager;
+} // namespace battery
+
+class Promise;
+
+class Gamepad;
+class GamepadServiceTest;
+class NavigatorUserMediaSuccessCallback;
+class NavigatorUserMediaErrorCallback;
+
+struct MIDIOptions;
+
+nsTArray<uint32_t> SanitizeVibratePattern(const nsTArray<uint32_t>& aPattern);
+
+namespace network {
+class Connection;
+} // namespace network
+
+class LegacyMozTCPSocket;
+class VRDisplay;
+class VRServiceTest;
+class XRSystem;
+class StorageManager;
+class MediaCapabilities;
+class MediaSession;
+struct ShareData;
+class WindowGlobalChild;
+
+class Navigator final : public nsISupports, public nsWrapperCache {
+ public:
+ explicit Navigator(nsPIDOMWindowInner* aInnerWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Navigator)
+
+ void Invalidate();
+ nsPIDOMWindowInner* GetWindow() const { return mWindow; }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * Called when the inner window navigates to a new page.
+ */
+ void OnNavigation();
+
+ void GetProduct(nsAString& aProduct);
+ void GetLanguage(nsAString& aLanguage);
+ void GetAppName(nsAString& aAppName, CallerType aCallerType) const;
+ void GetAppVersion(nsAString& aAppName, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ void GetPlatform(nsAString& aPlatform, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ void GetUserAgent(nsAString& aUserAgent, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ bool OnLine();
+ void CheckProtocolHandlerAllowed(const nsAString& aScheme,
+ nsIURI* aHandlerURI, nsIURI* aDocumentURI,
+ ErrorResult& aRv);
+ void RegisterProtocolHandler(const nsAString& aScheme, const nsAString& aURL,
+ ErrorResult& aRv);
+ nsMimeTypeArray* GetMimeTypes(ErrorResult& aRv);
+ nsPluginArray* GetPlugins(ErrorResult& aRv);
+ bool PdfViewerEnabled();
+ Permissions* GetPermissions(ErrorResult& aRv);
+ void GetDoNotTrack(nsAString& aResult);
+ bool GlobalPrivacyControl();
+ Geolocation* GetGeolocation(ErrorResult& aRv);
+ Promise* GetBattery(ErrorResult& aRv);
+
+ bool CanShare(const ShareData& aData);
+ already_AddRefed<Promise> Share(const ShareData& aData, ErrorResult& aRv);
+
+ static void AppName(nsAString& aAppName, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue);
+
+ static nsresult GetPlatform(nsAString& aPlatform, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue);
+
+ static nsresult GetAppVersion(nsAString& aAppVersion, Document* aCallerDoc,
+ bool aUsePrefOverriddenValue);
+
+ static nsresult GetUserAgent(nsPIDOMWindowInner* aWindow,
+ Document* aCallerDoc,
+ Maybe<bool> aShouldResistFingerprinting,
+ nsAString& aUserAgent);
+
+ // Clears the platform cache by calling:
+ // Navigator_Binding::ClearCachedPlatformValue(this);
+ void ClearPlatformCache();
+
+ // Clears the user agent cache by calling:
+ // Navigator_Binding::ClearCachedUserAgentValue(this);
+ void ClearUserAgentCache();
+
+ bool Vibrate(uint32_t aDuration);
+ bool Vibrate(const nsTArray<uint32_t>& aDuration);
+ void SetVibrationPermission(bool aPermitted, bool aPersistent);
+ uint32_t MaxTouchPoints(CallerType aCallerType);
+ void GetAppCodeName(nsAString& aAppCodeName, ErrorResult& aRv);
+ void GetOscpu(nsAString& aOscpu, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ void GetVendorSub(nsAString& aVendorSub);
+ void GetVendor(nsAString& aVendor);
+ void GetProductSub(nsAString& aProductSub);
+ bool CookieEnabled();
+ void GetBuildID(nsAString& aBuildID, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ bool JavaEnabled() { return false; }
+ uint64_t HardwareConcurrency();
+ bool TaintEnabled() { return false; }
+
+ already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
+ network::Connection* GetConnection(ErrorResult& aRv);
+ MediaDevices* GetMediaDevices(ErrorResult& aRv);
+ MediaDevices* GetExtantMediaDevices() const { return mMediaDevices; };
+
+ void GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads, ErrorResult& aRv);
+ GamepadServiceTest* RequestGamepadServiceTest(ErrorResult& aRv);
+ already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
+ void FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p);
+ void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
+ void OnXRPermissionRequestAllow();
+ void OnXRPermissionRequestCancel();
+ VRServiceTest* RequestVRServiceTest(ErrorResult& aRv);
+ bool IsWebVRContentDetected() const;
+ bool IsWebVRContentPresenting() const;
+ void RequestVRPresentation(VRDisplay& aDisplay);
+ XRSystem* GetXr(ErrorResult& aRv);
+ already_AddRefed<Promise> RequestMIDIAccess(const MIDIOptions& aOptions,
+ ErrorResult& aRv);
+
+ bool SendBeacon(const nsAString& aUrl, const Nullable<fetch::BodyInit>& aData,
+ ErrorResult& aRv);
+
+ void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
+ NavigatorUserMediaSuccessCallback& aOnSuccess,
+ NavigatorUserMediaErrorCallback& aOnError,
+ CallerType aCallerType, ErrorResult& aRv);
+
+ already_AddRefed<ServiceWorkerContainer> ServiceWorker();
+ // NOTE(krosylight): This currently exists solely for use counter purpose,
+ // since Navigator::ServiceWorker is also called by native functions. Remove
+ // this when we don't need the counter.
+ already_AddRefed<ServiceWorkerContainer> ServiceWorkerJS();
+
+ mozilla::dom::CredentialsContainer* Credentials();
+ dom::Clipboard* Clipboard();
+ webgpu::Instance* Gpu();
+ dom::LockManager* Locks();
+
+ static bool Webdriver();
+
+ void GetLanguages(nsTArray<nsString>& aLanguages);
+
+ StorageManager* Storage();
+
+ static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
+
+ dom::MediaCapabilities* MediaCapabilities();
+ dom::MediaSession* MediaSession();
+
+ AddonManager* GetMozAddonManager(ErrorResult& aRv);
+
+ // WebIDL helper methods
+ static bool HasUserMediaSupport(JSContext* /* unused */,
+ JSObject* /* unused */);
+ static bool HasShareSupport(JSContext* /* unused */, JSObject* /* unused */);
+
+ static bool HasMidiSupport(JSContext* /* unused */, JSObject* /* unused */);
+
+ nsPIDOMWindowInner* GetParentObject() const { return GetWindow(); }
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // GetWindowFromGlobal returns the inner window for this global, if
+ // any, else null.
+ static already_AddRefed<nsPIDOMWindowInner> GetWindowFromGlobal(
+ JSObject* aGlobal);
+
+ already_AddRefed<Promise> RequestMediaKeySystemAccess(
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig, ErrorResult& aRv);
+
+ bool HasCreatedMediaSession() const;
+
+ // Following methods are for the Autoplay Policy Detection API.
+ // https://w3c.github.io/autoplay/#autoplay-detection-methods
+ AutoplayPolicy GetAutoplayPolicy(AutoplayPolicyMediaType aType);
+ AutoplayPolicy GetAutoplayPolicy(HTMLMediaElement& aElement);
+ AutoplayPolicy GetAutoplayPolicy(AudioContext& aContext);
+
+ private:
+ void ValidateShareData(const ShareData& aData, ErrorResult& aRv);
+ RefPtr<MediaKeySystemAccessManager> mMediaKeySystemAccessManager;
+
+ public:
+ void NotifyVRDisplaysUpdated();
+ void NotifyActiveVRDisplaysChanged();
+
+ bool TestTrialGatedAttribute() const { return true; }
+
+ private:
+ virtual ~Navigator();
+
+ // This enum helps SendBeaconInternal to apply different behaviors to body
+ // types.
+ enum BeaconType { eBeaconTypeBlob, eBeaconTypeArrayBuffer, eBeaconTypeOther };
+
+ bool SendBeaconInternal(const nsAString& aUrl, BodyExtractorBase* aBody,
+ BeaconType aType, ErrorResult& aRv);
+
+ nsIDocShell* GetDocShell() const {
+ return mWindow ? mWindow->GetDocShell() : nullptr;
+ }
+
+ RefPtr<nsPluginArray> mPlugins;
+ RefPtr<Permissions> mPermissions;
+ RefPtr<Geolocation> mGeolocation;
+ RefPtr<battery::BatteryManager> mBatteryManager;
+ RefPtr<Promise> mBatteryPromise;
+ RefPtr<network::Connection> mConnection;
+ RefPtr<CredentialsContainer> mCredentials;
+ RefPtr<dom::Clipboard> mClipboard;
+ RefPtr<MediaDevices> mMediaDevices;
+ RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<GamepadServiceTest> mGamepadServiceTest;
+ nsTArray<RefPtr<Promise>> mVRGetDisplaysPromises;
+ RefPtr<VRServiceTest> mVRServiceTest;
+ RefPtr<XRSystem> mXRSystem;
+ nsTArray<uint32_t> mRequestedVibrationPattern;
+ RefPtr<StorageManager> mStorageManager;
+ RefPtr<dom::MediaCapabilities> mMediaCapabilities;
+ RefPtr<dom::MediaSession> mMediaSession;
+ RefPtr<AddonManager> mAddonManager;
+ RefPtr<webgpu::Instance> mWebGpu;
+ RefPtr<Promise> mSharePromise; // Web Share API related
+ RefPtr<dom::LockManager> mLocks;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Navigator_h
diff --git a/dom/base/NodeInfo.cpp b/dom/base/NodeInfo.cpp
new file mode 100644
index 0000000000..0a66d4deff
--- /dev/null
+++ b/dom/base/NodeInfo.cpp
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Class that represents a prefix/namespace/localName triple; a single
+ * nodeinfo is shared by all elements in a document that have that
+ * prefix, namespace, and localName.
+ */
+
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Likely.h"
+
+#include "nsNodeInfoManager.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsDOMString.h"
+#include "nsCRT.h"
+#include "nsINode.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/Document.h"
+#include "nsGkAtoms.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla;
+using mozilla::dom::NodeInfo;
+
+NodeInfo::~NodeInfo() {
+ mOwnerManager->RemoveNodeInfo(this);
+
+ // We can't use NS_IF_RELEASE because mName is const.
+ if (mInner.mName) {
+ mInner.mName->Release();
+ }
+ NS_IF_RELEASE(mInner.mPrefix);
+ NS_IF_RELEASE(mInner.mExtraName);
+}
+
+NodeInfo::NodeInfo(nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsAtom* aExtraName,
+ nsNodeInfoManager* aOwnerManager)
+ : mDocument(aOwnerManager->GetDocument()),
+ mInner(aName, aPrefix, aNamespaceID, aNodeType, aExtraName),
+ mOwnerManager(aOwnerManager) {
+ CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName);
+
+ NS_IF_ADDREF(mInner.mName);
+ NS_IF_ADDREF(mInner.mPrefix);
+ NS_IF_ADDREF(mInner.mExtraName);
+
+ // Now compute our cached members.
+
+ // Qualified name. If we have no prefix, use ToString on
+ // mInner.mName so that we get to share its buffer.
+ if (aPrefix) {
+ mQualifiedName = nsDependentAtomString(mInner.mPrefix) + u":"_ns +
+ nsDependentAtomString(mInner.mName);
+ } else {
+ mInner.mName->ToString(mQualifiedName);
+ }
+
+ MOZ_ASSERT_IF(aNodeType != nsINode::ELEMENT_NODE &&
+ aNodeType != nsINode::ATTRIBUTE_NODE &&
+ aNodeType != UINT16_MAX,
+ aNamespaceID == kNameSpaceID_None && !aPrefix);
+
+ switch (aNodeType) {
+ case nsINode::ELEMENT_NODE:
+ case nsINode::ATTRIBUTE_NODE:
+ // Correct the case for HTML
+ if (aNodeType == nsINode::ELEMENT_NODE &&
+ aNamespaceID == kNameSpaceID_XHTML && GetDocument() &&
+ GetDocument()->IsHTMLDocument()) {
+ nsContentUtils::ASCIIToUpper(mQualifiedName, mNodeName);
+ } else {
+ mNodeName = mQualifiedName;
+ }
+ mInner.mName->ToString(mLocalName);
+ break;
+ case nsINode::TEXT_NODE:
+ case nsINode::CDATA_SECTION_NODE:
+ case nsINode::COMMENT_NODE:
+ case nsINode::DOCUMENT_NODE:
+ case nsINode::DOCUMENT_FRAGMENT_NODE:
+ mInner.mName->ToString(mNodeName);
+ SetDOMStringToNull(mLocalName);
+ break;
+ case nsINode::PROCESSING_INSTRUCTION_NODE:
+ case nsINode::DOCUMENT_TYPE_NODE:
+ mInner.mExtraName->ToString(mNodeName);
+ SetDOMStringToNull(mLocalName);
+ break;
+ default:
+ MOZ_ASSERT(aNodeType == UINT16_MAX, "Unknown node type");
+ }
+}
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(NodeInfo)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(NodeInfo)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(NodeInfo)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[72];
+ uint32_t nsid = tmp->NamespaceID();
+ nsAtomCString localName(tmp->NameAtom());
+ const char* nsuri = nsNameSpaceManager::GetNameSpaceDisplayName(nsid);
+ SprintfLiteral(name, "NodeInfo %s %s", nsuri, localName.get());
+
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(NodeInfo, tmp->mRefCnt.get())
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnerManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(NodeInfo)
+ return nsCCUncollectableMarker::sGeneration && tmp->CanSkip();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(NodeInfo)
+ return nsCCUncollectableMarker::sGeneration && tmp->CanSkip();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(NodeInfo)
+ return nsCCUncollectableMarker::sGeneration && tmp->CanSkip();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+void NodeInfo::GetName(nsAString& aName) const {
+ mInner.mName->ToString(aName);
+}
+
+void NodeInfo::GetPrefix(nsAString& aPrefix) const {
+ if (mInner.mPrefix) {
+ mInner.mPrefix->ToString(aPrefix);
+ } else {
+ SetDOMStringToNull(aPrefix);
+ }
+}
+
+void NodeInfo::GetNamespaceURI(nsAString& aNameSpaceURI) const {
+ if (mInner.mNamespaceID > 0) {
+ nsresult rv = nsNameSpaceManager::GetInstance()->GetNameSpaceURI(
+ mInner.mNamespaceID, aNameSpaceURI);
+ // How can we possibly end up with a bogus namespace ID here?
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+ } else {
+ SetDOMStringToNull(aNameSpaceURI);
+ }
+}
+
+bool NodeInfo::NamespaceEquals(const nsAString& aNamespaceURI) const {
+ int32_t nsid = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespaceURI, nsContentUtils::IsChromeDoc(mOwnerManager->GetDocument()));
+
+ return mozilla::dom::NodeInfo::NamespaceEquals(nsid);
+}
+
+void NodeInfo::DeleteCycleCollectable() {
+ RefPtr<nsNodeInfoManager> kungFuDeathGrip = mOwnerManager;
+ mozilla::Unused
+ << kungFuDeathGrip; // Just keeping value alive for longer than this
+ delete this;
+}
+
+bool NodeInfo::CanSkip() {
+ return mDocument && nsCCUncollectableMarker::InGeneration(
+ mDocument->GetMarkedCCGeneration());
+}
diff --git a/dom/base/NodeInfo.h b/dom/base/NodeInfo.h
new file mode 100644
index 0000000000..3060597ffb
--- /dev/null
+++ b/dom/base/NodeInfo.h
@@ -0,0 +1,308 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Class that represents a prefix/namespace/localName triple; a single
+ * nodeinfo is shared by all elements in a document that have that
+ * prefix, namespace, and localName.
+ *
+ * nsNodeInfoManagers are internal objects that manage a list of
+ * NodeInfos, every document object should hold a strong reference to
+ * a nsNodeInfoManager and every NodeInfo also holds a strong reference
+ * to their owning manager. When a NodeInfo is no longer used it will
+ * automatically remove itself from its owner manager, and when all
+ * NodeInfos have been removed from a nsNodeInfoManager and all external
+ * references are released the nsNodeInfoManager deletes itself.
+ */
+
+#ifndef mozilla_dom_NodeInfo_h___
+#define mozilla_dom_NodeInfo_h___
+
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsAtom.h"
+#include "nsHashKeys.h"
+
+class nsNodeInfoManager;
+
+namespace mozilla::dom {
+
+class Document;
+
+class NodeInfo final {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(NodeInfo)
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS_WITH_CUSTOM_DELETE(NodeInfo)
+
+ /*
+ * Get the name from this node as a string, this does not include the prefix.
+ *
+ * For the HTML element "<body>" this will return "body" and for the XML
+ * element "<html:body>" this will return "body".
+ */
+ void GetName(nsAString& aName) const;
+
+ /*
+ * Get the name from this node as an atom, this does not include the prefix.
+ * This function never returns a null atom.
+ *
+ * For the HTML element "<body>" this will return the "body" atom and for
+ * the XML element "<html:body>" this will return the "body" atom.
+ */
+ nsAtom* NameAtom() const { return mInner.mName; }
+
+ /*
+ * Get the qualified name from this node as a string, the qualified name
+ * includes the prefix, if one exists.
+ *
+ * For the HTML element "<body>" this will return "body" and for the XML
+ * element "<html:body>" this will return "html:body".
+ */
+ const nsString& QualifiedName() const { return mQualifiedName; }
+
+ /*
+ * Returns the node's nodeName as defined in DOM Core
+ */
+ const nsString& NodeName() const { return mNodeName; }
+
+ /*
+ * Returns the node's localName as defined in DOM Core
+ */
+ const nsString& LocalName() const { return mLocalName; }
+
+ /*
+ * Get the prefix from this node as a string.
+ *
+ * For the HTML element "<body>" this will return a null string and for
+ * the XML element "<html:body>" this will return the string "html".
+ */
+ void GetPrefix(nsAString& aPrefix) const;
+
+ /*
+ * Get the prefix from this node as an atom.
+ *
+ * For the HTML element "<body>" this will return a null atom and for
+ * the XML element "<html:body>" this will return the "html" atom.
+ */
+ nsAtom* GetPrefixAtom() const { return mInner.mPrefix; }
+
+ /*
+ * Get the namespace URI for a node, if the node has a namespace URI.
+ */
+ void GetNamespaceURI(nsAString& aNameSpaceURI) const;
+
+ /*
+ * Get the namespace ID for a node if the node has a namespace, if not this
+ * returns kNameSpaceID_None.
+ */
+ int32_t NamespaceID() const { return mInner.mNamespaceID; }
+
+ /*
+ * Get the nodetype for the node. Returns the values specified in Node
+ * for Node.nodeType
+ */
+ uint16_t NodeType() const { return mInner.mNodeType; }
+
+ /*
+ * Get the extra name, used by PIs and DocTypes, for the node.
+ */
+ nsAtom* GetExtraName() const { return mInner.mExtraName; }
+
+ /**
+ * Get the owning node info manager. Only to be used inside Gecko, you can't
+ * really do anything with the pointer outside Gecko anyway.
+ */
+ nsNodeInfoManager* NodeInfoManager() const { return mOwnerManager; }
+
+ /*
+ * Utility functions that can be used to check if a nodeinfo holds a specific
+ * name, name and prefix, name and prefix and namespace ID, or just
+ * namespace ID.
+ */
+ inline bool Equals(NodeInfo* aNodeInfo) const;
+
+ bool NameAndNamespaceEquals(NodeInfo* aNodeInfo) const;
+
+ bool Equals(const nsAtom* aNameAtom) const {
+ return mInner.mName == aNameAtom;
+ }
+
+ bool Equals(const nsAtom* aNameAtom, const nsAtom* aPrefixAtom) const {
+ return (mInner.mName == aNameAtom) && (mInner.mPrefix == aPrefixAtom);
+ }
+
+ bool Equals(const nsAtom* aNameAtom, int32_t aNamespaceID) const {
+ return ((mInner.mName == aNameAtom) &&
+ (mInner.mNamespaceID == aNamespaceID));
+ }
+
+ bool Equals(const nsAtom* aNameAtom, const nsAtom* aPrefixAtom,
+ int32_t aNamespaceID) const {
+ return ((mInner.mName == aNameAtom) && (mInner.mPrefix == aPrefixAtom) &&
+ (mInner.mNamespaceID == aNamespaceID));
+ }
+
+ bool NamespaceEquals(int32_t aNamespaceID) const {
+ return mInner.mNamespaceID == aNamespaceID;
+ }
+
+ inline bool Equals(const nsAString& aName) const;
+
+ inline bool Equals(const nsAString& aName, const nsAString& aPrefix) const;
+
+ inline bool Equals(const nsAString& aName, int32_t aNamespaceID) const;
+
+ inline bool Equals(const nsAString& aName, const nsAString& aPrefix,
+ int32_t aNamespaceID) const;
+
+ bool NamespaceEquals(const nsAString& aNamespaceURI) const;
+
+ inline bool QualifiedNameEquals(const nsAtom* aNameAtom) const;
+
+ bool QualifiedNameEquals(const nsAString& aQualifiedName) const {
+ return mQualifiedName == aQualifiedName;
+ }
+
+ /*
+ * Retrieve a pointer to the document that owns this node info.
+ */
+ Document* GetDocument() const { return mDocument; }
+
+ private:
+ NodeInfo() = delete;
+ NodeInfo(const NodeInfo& aOther) = delete;
+
+ // NodeInfo is only constructed by nsNodeInfoManager which is a friend class.
+ // aName and aOwnerManager may not be null.
+ NodeInfo(nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsAtom* aExtraName,
+ nsNodeInfoManager* aOwnerManager);
+
+ ~NodeInfo();
+
+ public:
+ bool CanSkip();
+
+ /**
+ * This method gets called by the cycle collector when it's time to delete
+ * this object.
+ */
+ void DeleteCycleCollectable();
+
+ protected:
+ /*
+ * NodeInfoInner is used for two things:
+ *
+ * 1. as a member in nsNodeInfo for holding the name, prefix and
+ * namespace ID
+ * 2. as the hash key in the hash table in nsNodeInfoManager
+ *
+ * NodeInfoInner does not do any kind of reference counting,
+ * that's up to the user of this class. Since NodeInfoInner is
+ * typically used as a member of NodeInfo, the hash table doesn't
+ * need to delete the keys. When the value (NodeInfo) is deleted
+ * the key is automatically deleted.
+ */
+
+ class NodeInfoInner {
+ public:
+ NodeInfoInner()
+ : mName(nullptr),
+ mPrefix(nullptr),
+ mNamespaceID(kNameSpaceID_Unknown),
+ mNodeType(0),
+ mNameString(nullptr),
+ mExtraName(nullptr),
+ mHash() {}
+ NodeInfoInner(nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsAtom* aExtraName)
+ : mName(aName),
+ mPrefix(aPrefix),
+ mNamespaceID(aNamespaceID),
+ mNodeType(aNodeType),
+ mNameString(nullptr),
+ mExtraName(aExtraName),
+ mHash() {}
+ NodeInfoInner(const nsAString& aTmpName, nsAtom* aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType)
+ : mName(nullptr),
+ mPrefix(aPrefix),
+ mNamespaceID(aNamespaceID),
+ mNodeType(aNodeType),
+ mNameString(&aTmpName),
+ mExtraName(nullptr),
+ mHash() {}
+
+ bool operator==(const NodeInfoInner& aOther) const {
+ if (mPrefix != aOther.mPrefix || mNamespaceID != aOther.mNamespaceID ||
+ mNodeType != aOther.mNodeType || mExtraName != aOther.mExtraName) {
+ return false;
+ }
+
+ if (mName) {
+ if (aOther.mName) {
+ return mName == aOther.mName;
+ }
+ return mName->Equals(*(aOther.mNameString));
+ }
+
+ if (aOther.mName) {
+ return aOther.mName->Equals(*(mNameString));
+ }
+
+ return mNameString->Equals(*(aOther.mNameString));
+ }
+
+ uint32_t Hash() const {
+ if (!mHash) {
+ mHash.emplace(mName ? mName->hash()
+ : mozilla::HashString(*mNameString));
+ }
+ return mHash.value();
+ }
+
+ nsAtom* const MOZ_OWNING_REF mName;
+ nsAtom* MOZ_OWNING_REF mPrefix;
+ int32_t mNamespaceID;
+ uint16_t mNodeType; // As defined by Node.nodeType
+ const nsAString* const mNameString;
+ nsAtom* MOZ_OWNING_REF mExtraName; // Only used by PIs and DocTypes
+ mutable mozilla::Maybe<const uint32_t> mHash;
+ };
+
+ // nsNodeInfoManager needs to pass mInner to the hash table.
+ friend class ::nsNodeInfoManager;
+
+ // This is a non-owning reference, but it's safe since it's set to nullptr
+ // by nsNodeInfoManager::DropDocumentReference when the document is destroyed.
+ Document* MOZ_NON_OWNING_REF mDocument; // Cache of mOwnerManager->mDocument
+
+ NodeInfoInner mInner;
+
+ RefPtr<nsNodeInfoManager> mOwnerManager;
+
+ /*
+ * Members for various functions of mName+mPrefix that we can be
+ * asked to compute.
+ */
+
+ // Qualified name
+ nsString mQualifiedName;
+
+ // nodeName for the node.
+ nsString mNodeName;
+
+ // localName for the node. This is either equal to mInner.mName, or a
+ // void string, depending on mInner.mNodeType.
+ nsString mLocalName;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_NodeInfo_h___ */
diff --git a/dom/base/NodeInfoInlines.h b/dom/base/NodeInfoInlines.h
new file mode 100644
index 0000000000..e7a11e1864
--- /dev/null
+++ b/dom/base/NodeInfoInlines.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_NodeInfoInlines_h___
+#define mozilla_dom_NodeInfoInlines_h___
+
+#include "nsAtom.h"
+#include "nsINode.h"
+#include "nsDOMString.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla::dom {
+
+inline bool NodeInfo::Equals(NodeInfo* aNodeInfo) const {
+ return aNodeInfo == this ||
+ aNodeInfo->Equals(mInner.mName, mInner.mPrefix, mInner.mNamespaceID);
+}
+
+inline bool NodeInfo::NameAndNamespaceEquals(NodeInfo* aNodeInfo) const {
+ return aNodeInfo == this ||
+ aNodeInfo->Equals(mInner.mName, mInner.mNamespaceID);
+}
+
+inline bool NodeInfo::Equals(const nsAString& aName) const {
+ return mInner.mName->Equals(aName);
+}
+
+inline bool NodeInfo::Equals(const nsAString& aName,
+ const nsAString& aPrefix) const {
+ return mInner.mName->Equals(aName) &&
+ (mInner.mPrefix ? mInner.mPrefix->Equals(aPrefix) : aPrefix.IsEmpty());
+}
+
+inline bool NodeInfo::Equals(const nsAString& aName,
+ int32_t aNamespaceID) const {
+ return mInner.mNamespaceID == aNamespaceID && mInner.mName->Equals(aName);
+}
+
+inline bool NodeInfo::Equals(const nsAString& aName, const nsAString& aPrefix,
+ int32_t aNamespaceID) const {
+ return mInner.mName->Equals(aName) && mInner.mNamespaceID == aNamespaceID &&
+ (mInner.mPrefix ? mInner.mPrefix->Equals(aPrefix) : aPrefix.IsEmpty());
+}
+
+inline bool NodeInfo::QualifiedNameEquals(const nsAtom* aNameAtom) const {
+ MOZ_ASSERT(aNameAtom, "Must have name atom");
+ if (!GetPrefixAtom()) {
+ return Equals(aNameAtom);
+ }
+
+ return aNameAtom->Equals(mQualifiedName);
+}
+
+} // namespace mozilla::dom
+
+inline void CheckValidNodeInfo(uint16_t aNodeType, const nsAtom* aName,
+ int32_t aNamespaceID, const nsAtom* aExtraName) {
+ MOZ_ASSERT(
+ aNodeType == nsINode::ELEMENT_NODE ||
+ aNodeType == nsINode::ATTRIBUTE_NODE ||
+ aNodeType == nsINode::TEXT_NODE ||
+ aNodeType == nsINode::CDATA_SECTION_NODE ||
+ aNodeType == nsINode::PROCESSING_INSTRUCTION_NODE ||
+ aNodeType == nsINode::COMMENT_NODE ||
+ aNodeType == nsINode::DOCUMENT_NODE ||
+ aNodeType == nsINode::DOCUMENT_TYPE_NODE ||
+ aNodeType == nsINode::DOCUMENT_FRAGMENT_NODE ||
+ aNodeType == UINT16_MAX,
+ // If a new type is added here, please update nsINode::MAX_NODE_TYPE and
+ // NodeTypeStrings in nsINode.cpp accordingly. Note that UINT16_MAX is
+ // only used for XUL prototype nodeinfos, which are never going to show up
+ // where NodeTypeStrings is used.
+ "Invalid nodeType");
+ MOZ_ASSERT((aNodeType == nsINode::PROCESSING_INSTRUCTION_NODE ||
+ aNodeType == nsINode::DOCUMENT_TYPE_NODE) == !!aExtraName,
+ "Supply aExtraName for and only for PIs and doctypes");
+ MOZ_ASSERT(aNodeType == nsINode::ELEMENT_NODE ||
+ aNodeType == nsINode::ATTRIBUTE_NODE ||
+ aNodeType == UINT16_MAX || aNamespaceID == kNameSpaceID_None,
+ "Only attributes and elements can be in a namespace");
+ MOZ_ASSERT(aName && aName != nsGkAtoms::_empty, "Invalid localName");
+ MOZ_ASSERT(((aNodeType == nsINode::TEXT_NODE) ==
+ (aName == nsGkAtoms::textTagName)) &&
+ ((aNodeType == nsINode::CDATA_SECTION_NODE) ==
+ (aName == nsGkAtoms::cdataTagName)) &&
+ ((aNodeType == nsINode::COMMENT_NODE) ==
+ (aName == nsGkAtoms::commentTagName)) &&
+ ((aNodeType == nsINode::DOCUMENT_NODE) ==
+ (aName == nsGkAtoms::documentNodeName)) &&
+ ((aNodeType == nsINode::DOCUMENT_FRAGMENT_NODE) ==
+ (aName == nsGkAtoms::documentFragmentNodeName)) &&
+ ((aNodeType == nsINode::DOCUMENT_TYPE_NODE) ==
+ (aName == nsGkAtoms::documentTypeNodeName)) &&
+ ((aNodeType == nsINode::PROCESSING_INSTRUCTION_NODE) ==
+ (aName == nsGkAtoms::processingInstructionTagName)),
+ "Wrong localName for nodeType");
+}
+
+#endif /* mozilla_dom_NodeInfoInlines_h___ */
diff --git a/dom/base/NodeIterator.cpp b/dom/base/NodeIterator.cpp
new file mode 100644
index 0000000000..146eae1da9
--- /dev/null
+++ b/dom/base/NodeIterator.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Traversal's NodeIterator
+ */
+
+#include "mozilla/dom/NodeIterator.h"
+
+#include "nsError.h"
+
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/dom/NodeIteratorBinding.h"
+
+namespace mozilla::dom {
+
+/*
+ * NodePointer implementation
+ */
+NodeIterator::NodePointer::NodePointer(nsINode* aNode, bool aBeforeNode)
+ : mNode(aNode), mBeforeNode(aBeforeNode) {}
+
+bool NodeIterator::NodePointer::MoveToNext(nsINode* aRoot) {
+ if (!mNode) return false;
+
+ if (mBeforeNode) {
+ mBeforeNode = false;
+ return true;
+ }
+
+ nsINode* child = mNode->GetFirstChild();
+ if (child) {
+ mNode = child;
+ return true;
+ }
+
+ return MoveForward(aRoot, mNode);
+}
+
+bool NodeIterator::NodePointer::MoveToPrevious(nsINode* aRoot) {
+ if (!mNode) return false;
+
+ if (!mBeforeNode) {
+ mBeforeNode = true;
+ return true;
+ }
+
+ if (mNode == aRoot) return false;
+
+ MoveBackward(mNode->GetParentNode(), mNode->GetPreviousSibling());
+
+ return true;
+}
+
+void NodeIterator::NodePointer::AdjustAfterRemoval(
+ nsINode* aRoot, nsINode* aContainer, nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ // If mNode is null or the root there is nothing to do.
+ if (!mNode || mNode == aRoot) return;
+
+ // check if ancestor was removed
+ if (!mNode->IsInclusiveDescendantOf(aChild)) return;
+
+ if (mBeforeNode) {
+ // Try the next sibling
+ nsINode* nextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
+ : aContainer->GetFirstChild();
+
+ if (nextSibling) {
+ mNode = nextSibling;
+ return;
+ }
+
+ // Next try siblings of ancestors
+ if (MoveForward(aRoot, aContainer)) return;
+
+ // No suitable node was found so try going backwards
+ mBeforeNode = false;
+ }
+
+ MoveBackward(aContainer, aPreviousSibling);
+}
+
+bool NodeIterator::NodePointer::MoveForward(nsINode* aRoot, nsINode* aNode) {
+ while (1) {
+ if (aNode == aRoot) break;
+
+ nsINode* sibling = aNode->GetNextSibling();
+ if (sibling) {
+ mNode = sibling;
+ return true;
+ }
+ aNode = aNode->GetParentNode();
+ }
+
+ return false;
+}
+
+void NodeIterator::NodePointer::MoveBackward(nsINode* aParent, nsINode* aNode) {
+ if (aNode) {
+ do {
+ mNode = aNode;
+ aNode = aNode->GetLastChild();
+ } while (aNode);
+ } else {
+ mNode = aParent;
+ }
+}
+
+/*
+ * Factories, constructors and destructors
+ */
+
+NodeIterator::NodeIterator(nsINode* aRoot, uint32_t aWhatToShow,
+ NodeFilter* aFilter)
+ : nsTraversal(aRoot, aWhatToShow, aFilter), mPointer(mRoot, true) {
+ aRoot->AddMutationObserver(this);
+}
+
+NodeIterator::~NodeIterator() {
+ /* destructor code */
+ if (mRoot) mRoot->RemoveMutationObserver(this);
+}
+
+/*
+ * nsISupports and cycle collection stuff
+ */
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(NodeIterator)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NodeIterator)
+ if (tmp->mRoot) tmp->mRoot->RemoveMutationObserver(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilter)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NodeIterator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilter)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for NodeIterator
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NodeIterator)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(NodeIterator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(NodeIterator)
+
+already_AddRefed<nsINode> NodeIterator::NextOrPrevNode(
+ NodePointer::MoveToMethodType aMove, ErrorResult& aResult) {
+ if (mInAcceptNode) {
+ aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ mWorkingPointer = mPointer;
+
+ struct AutoClear {
+ NodePointer* mPtr;
+ explicit AutoClear(NodePointer* ptr) : mPtr(ptr) {}
+ ~AutoClear() { mPtr->Clear(); }
+ } ac(&mWorkingPointer);
+
+ while ((mWorkingPointer.*aMove)(mRoot)) {
+ nsCOMPtr<nsINode> testNode;
+ int16_t filtered = TestNode(mWorkingPointer.mNode, aResult, &testNode);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ mPointer = mWorkingPointer;
+ return testNode.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void NodeIterator::Detach() {
+ if (mRoot) {
+ mRoot->OwnerDoc()->WarnOnceAbout(DeprecatedOperations::eNodeIteratorDetach);
+ }
+}
+
+/*
+ * nsIMutationObserver interface
+ */
+
+void NodeIterator::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ nsINode* container = aChild->GetParentNode();
+
+ mPointer.AdjustAfterRemoval(mRoot, container, aChild, aPreviousSibling);
+ mWorkingPointer.AdjustAfterRemoval(mRoot, container, aChild,
+ aPreviousSibling);
+}
+
+bool NodeIterator::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return NodeIterator_Binding::Wrap(cx, this, aGivenProto, aReflector);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/NodeIterator.h b/dom/base/NodeIterator.h
new file mode 100644
index 0000000000..0f20734685
--- /dev/null
+++ b/dom/base/NodeIterator.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Traversal's NodeIterator
+ */
+
+#ifndef mozilla_dom_NodeIterator_h
+#define mozilla_dom_NodeIterator_h
+
+#include "nsTraversal.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStubMutationObserver.h"
+
+class nsINode;
+
+namespace mozilla::dom {
+
+class NodeIterator final : public nsStubMutationObserver, public nsTraversal {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NodeIterator(nsINode* aRoot, uint32_t aWhatToShow, NodeFilter* aFilter);
+
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(NodeIterator)
+
+ // WebIDL API
+ nsINode* Root() const { return mRoot; }
+ nsINode* GetReferenceNode() const { return mPointer.mNode; }
+ bool PointerBeforeReferenceNode() const { return mPointer.mBeforeNode; }
+ uint32_t WhatToShow() const { return mWhatToShow; }
+ NodeFilter* GetFilter() { return mFilter; }
+ already_AddRefed<nsINode> NextNode(ErrorResult& aResult) {
+ return NextOrPrevNode(&NodePointer::MoveToNext, aResult);
+ }
+ already_AddRefed<nsINode> PreviousNode(ErrorResult& aResult) {
+ return NextOrPrevNode(&NodePointer::MoveToPrevious, aResult);
+ }
+ void Detach();
+
+ bool WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ private:
+ virtual ~NodeIterator();
+
+ struct NodePointer {
+ NodePointer() : mNode(nullptr), mBeforeNode(false) {}
+ NodePointer(nsINode* aNode, bool aBeforeNode);
+
+ typedef bool (NodePointer::*MoveToMethodType)(nsINode*);
+ bool MoveToNext(nsINode* aRoot);
+ bool MoveToPrevious(nsINode* aRoot);
+
+ bool MoveForward(nsINode* aRoot, nsINode* aNode);
+ void MoveBackward(nsINode* aParent, nsINode* aNode);
+
+ void AdjustAfterRemoval(nsINode* aRoot, nsINode* aContainer,
+ nsIContent* aChild, nsIContent* aPreviousSibling);
+
+ void Clear() { mNode = nullptr; }
+
+ nsINode* mNode;
+ bool mBeforeNode;
+ };
+
+ // Have to return a strong ref, because the act of testing the node can
+ // remove it from the DOM so we're holding the only ref to it.
+ already_AddRefed<nsINode> NextOrPrevNode(NodePointer::MoveToMethodType aMove,
+ ErrorResult& aResult);
+
+ NodePointer mPointer;
+ NodePointer mWorkingPointer;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_NodeIterator_h
diff --git a/dom/base/NodeUbiReporting.cpp b/dom/base/NodeUbiReporting.cpp
new file mode 100644
index 0000000000..cb6eb011ee
--- /dev/null
+++ b/dom/base/NodeUbiReporting.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NodeUbiReporting.h"
+#include "js/UbiNodeUtils.h"
+#include "nsWindowSizes.h"
+
+using JS::ubi::EdgeRange;
+using JS::ubi::SimpleEdgeRange;
+
+const char16_t JS::ubi::Concrete<mozilla::dom::Document>::concreteTypeName[] =
+ u"Document";
+const char16_t JS::ubi::Concrete<nsIContent>::concreteTypeName[] =
+ u"nsIContent";
+const char16_t JS::ubi::Concrete<mozilla::dom::Attr>::concreteTypeName[] =
+ u"Attr";
+
+void JS::ubi::Concrete<nsINode>::construct(void* storage, nsINode* ptr) {
+ // nsINode is abstract, and all of its inherited instances have
+ // an overridden function with instructions to construct ubi::Nodes.
+ // We actually want to call that function and construct from those instances.
+ ptr->ConstructUbiNode(storage);
+}
+
+js::UniquePtr<EdgeRange> JS::ubi::Concrete<nsINode>::edges(
+ JSContext* cx, bool wantNames) const {
+ AutoSuppressGCAnalysis suppress;
+ auto range = js::MakeUnique<SimpleEdgeRange>();
+ if (!range) {
+ return nullptr;
+ }
+ if (get().GetParent()) {
+ char16_t* edgeName = nullptr;
+ if (wantNames) {
+ edgeName = NS_xstrdup(u"Parent Node");
+ }
+ if (!range->addEdge(JS::ubi::Edge(edgeName, get().GetParent()))) {
+ return nullptr;
+ }
+ }
+ for (auto curr = get().GetFirstChild(); curr; curr = curr->GetNextSibling()) {
+ char16_t* edgeName = nullptr;
+ if (wantNames) {
+ edgeName = NS_xstrdup(u"Child Node");
+ }
+ if (!range->addEdge(JS::ubi::Edge(edgeName, curr))) {
+ return nullptr;
+ }
+ }
+ return js::UniquePtr<EdgeRange>(range.release());
+}
+
+JS::ubi::Node::Size JS::ubi::Concrete<nsINode>::size(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ AutoSuppressGCAnalysis suppress;
+ mozilla::SizeOfState sz(mallocSizeOf);
+ nsWindowSizes wn(sz);
+ size_t n = 0;
+ get().AddSizeOfIncludingThis(wn, &n);
+ return n;
+}
+
+const char16_t* JS::ubi::Concrete<nsINode>::descriptiveTypeName() const {
+ return get().NodeName().get();
+}
+
+JS::ubi::Node::Size JS::ubi::Concrete<mozilla::dom::Document>::size(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ AutoSuppressGCAnalysis suppress;
+ mozilla::SizeOfState sz(mallocSizeOf);
+ nsWindowSizes wn(sz);
+ getDoc().DocAddSizeOfIncludingThis(wn);
+ return wn.getTotalSize();
+}
diff --git a/dom/base/NodeUbiReporting.h b/dom/base/NodeUbiReporting.h
new file mode 100644
index 0000000000..be1c4e19c9
--- /dev/null
+++ b/dom/base/NodeUbiReporting.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_NodeUbiReporting_h
+#define dom_NodeUbiReporting_h
+
+#include "Attr.h"
+#include "Document.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsINode.h"
+#include "js/UbiNode.h"
+
+/*
+ * This file defines specializations of JS::ubi::Concrete for DOM nodes
+ * so that the JS memory tools, which operate on the UbiNode graph, can
+ * define subclasses of JS::ubi::Base that represent DOM nodes and
+ * yield the outgoing edges in a DOM node graph for reporting.
+ */
+
+namespace JS::ubi {
+
+// The DOM node base class.
+// This is an abstract class and therefore does not require a concreteTypeName.
+template <>
+class Concrete<nsINode> : public Base {
+ protected:
+ explicit Concrete(nsINode* ptr) : Base(ptr) {}
+
+ public:
+ static void construct(void* storage, nsINode* ptr);
+ Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
+ js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
+
+ nsINode& get() const { return *static_cast<nsINode*>(ptr); }
+ CoarseType coarseType() const final { return CoarseType::DOMNode; }
+ const char16_t* descriptiveTypeName() const override;
+};
+
+template <>
+class Concrete<nsIContent> : public Concrete<nsINode> {
+ protected:
+ explicit Concrete(nsIContent* ptr) : Concrete<nsINode>(ptr) {}
+
+ public:
+ static void construct(void* storage, nsIContent* ptr) {
+ new (storage) Concrete(ptr);
+ }
+ const char16_t* typeName() const override { return concreteTypeName; };
+ static const char16_t concreteTypeName[];
+};
+
+template <>
+class Concrete<mozilla::dom::Document> : public Concrete<nsINode> {
+ protected:
+ explicit Concrete(mozilla::dom::Document* ptr) : Concrete<nsINode>(ptr) {}
+
+ public:
+ static void construct(void* storage, mozilla::dom::Document* ptr) {
+ new (storage) Concrete(ptr);
+ }
+ Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
+
+ mozilla::dom::Document& getDoc() const {
+ return *static_cast<mozilla::dom::Document*>(ptr);
+ }
+ const char16_t* typeName() const override { return concreteTypeName; };
+ static const char16_t concreteTypeName[];
+};
+
+template <>
+class Concrete<mozilla::dom::Attr> : public Concrete<nsINode> {
+ protected:
+ explicit Concrete(mozilla::dom::Attr* ptr) : Concrete<nsINode>(ptr) {}
+
+ public:
+ static void construct(void* storage, mozilla::dom::Attr* ptr) {
+ new (storage) Concrete(ptr);
+ }
+ const char16_t* typeName() const override { return concreteTypeName; };
+ static const char16_t concreteTypeName[];
+};
+
+} // namespace JS::ubi
+
+#endif
diff --git a/dom/base/ParentProcessMessageManager.cpp b/dom/base/ParentProcessMessageManager.cpp
new file mode 100644
index 0000000000..a325bca6be
--- /dev/null
+++ b/dom/base/ParentProcessMessageManager.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "AccessCheck.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+
+namespace mozilla::dom {
+
+ParentProcessMessageManager::ParentProcessMessageManager()
+ : MessageBroadcaster(nullptr, MessageManagerFlags::MM_CHROME |
+ MessageManagerFlags::MM_PROCESSMANAGER) {
+ mozilla::HoldJSObjects(this);
+}
+
+ParentProcessMessageManager::~ParentProcessMessageManager() {
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* ParentProcessMessageManager::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(nsContentUtils::IsSystemCaller(aCx));
+
+ return ParentProcessMessageManager_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ParentProcessMessageManager.h b/dom/base/ParentProcessMessageManager.h
new file mode 100644
index 0000000000..d1cbc5808f
--- /dev/null
+++ b/dom/base/ParentProcessMessageManager.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ParentProcessMessageManager_h
+#define mozilla_dom_ParentProcessMessageManager_h
+
+#include "mozilla/dom/MessageBroadcaster.h"
+
+namespace mozilla::dom {
+
+/**
+ * Implementation for the WebIDL ParentProcessMessageManager interface.
+ * ParentProcessMessageManager is used in a parent process to communicate with
+ * all the child processes.
+ */
+class ParentProcessMessageManager final : public MessageBroadcaster {
+ public:
+ ParentProcessMessageManager();
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // ProcessScriptLoader
+ void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
+ mozilla::ErrorResult& aError) {
+ LoadScript(aUrl, aAllowDelayedLoad, false, aError);
+ }
+ void RemoveDelayedProcessScript(const nsAString& aURL) {
+ RemoveDelayedScript(aURL);
+ }
+ void GetDelayedProcessScripts(JSContext* aCx,
+ nsTArray<nsTArray<JS::Value>>& aScripts,
+ mozilla::ErrorResult& aError) {
+ GetDelayedScripts(aCx, aScripts, aError);
+ }
+
+ // GlobalProcessScriptLoader
+ using nsFrameMessageManager::GetInitialProcessData;
+
+ private:
+ virtual ~ParentProcessMessageManager();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ParentProcessMessageManager_h
diff --git a/dom/base/PlacesBookmark.h b/dom/base/PlacesBookmark.h
new file mode 100644
index 0000000000..f814f19180
--- /dev/null
+++ b/dom/base/PlacesBookmark.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmark_h
+#define mozilla_dom_PlacesBookmark_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmark : public PlacesEvent {
+ public:
+ explicit PlacesBookmark(PlacesEventType aEventType)
+ : PlacesEvent(aEventType) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmark_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmark* AsPlacesBookmark() const override { return this; }
+
+ unsigned short ItemType() { return mItemType; }
+ int64_t Id() { return mId; }
+ int64_t ParentId() { return mParentId; }
+ void GetUrl(nsString& aUrl) { aUrl = mUrl; }
+ void GetGuid(nsCString& aGuid) { aGuid = mGuid; }
+ void GetParentGuid(nsCString& aParentGuid) { aParentGuid = mParentGuid; }
+ uint16_t Source() { return mSource; }
+ bool IsTagging() { return mIsTagging; }
+
+ unsigned short mItemType;
+ int64_t mId;
+ int64_t mParentId;
+ nsString mUrl;
+ nsCString mGuid;
+ nsCString mParentGuid;
+ uint16_t mSource;
+ bool mIsTagging;
+
+ protected:
+ virtual ~PlacesBookmark() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmark_h
diff --git a/dom/base/PlacesBookmarkAddition.h b/dom/base/PlacesBookmarkAddition.h
new file mode 100644
index 0000000000..9416f53f25
--- /dev/null
+++ b/dom/base/PlacesBookmarkAddition.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkAddition_h
+#define mozilla_dom_PlacesBookmarkAddition_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmarkAddition final : public PlacesBookmark {
+ public:
+ explicit PlacesBookmarkAddition()
+ : PlacesBookmark(PlacesEventType::Bookmark_added) {}
+
+ static already_AddRefed<PlacesBookmarkAddition> Constructor(
+ const GlobalObject& aGlobal,
+ const PlacesBookmarkAdditionInit& aInitDict) {
+ RefPtr<PlacesBookmarkAddition> event = new PlacesBookmarkAddition();
+ event->mItemType = aInitDict.mItemType;
+ event->mId = aInitDict.mId;
+ event->mParentId = aInitDict.mParentId;
+ event->mIndex = aInitDict.mIndex;
+ event->mUrl = aInitDict.mUrl;
+ event->mTitle = aInitDict.mTitle;
+ event->mDateAdded = aInitDict.mDateAdded;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkAddition_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const override {
+ return this;
+ }
+
+ int32_t Index() { return mIndex; }
+ void GetTitle(nsString& aTitle) { aTitle = mTitle; }
+ uint64_t DateAdded() { return mDateAdded; }
+
+ int32_t mIndex;
+ nsString mTitle;
+ uint64_t mDateAdded;
+
+ private:
+ ~PlacesBookmarkAddition() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmarkAddition_h
diff --git a/dom/base/PlacesBookmarkChanged.h b/dom/base/PlacesBookmarkChanged.h
new file mode 100644
index 0000000000..3ce6af7c53
--- /dev/null
+++ b/dom/base/PlacesBookmarkChanged.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkChanged_h
+#define mozilla_dom_PlacesBookmarkChanged_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkChanged : public PlacesBookmark {
+ public:
+ explicit PlacesBookmarkChanged(PlacesEventType aEventType)
+ : PlacesBookmark(aEventType) {}
+
+ uint64_t LastModified() { return mLastModified; }
+ uint64_t mLastModified;
+
+ protected:
+ virtual ~PlacesBookmarkChanged() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkGuid.h b/dom/base/PlacesBookmarkGuid.h
new file mode 100644
index 0000000000..1d58b03452
--- /dev/null
+++ b/dom/base/PlacesBookmarkGuid.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkGuid_h
+#define mozilla_dom_PlacesBookmarkGuid_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkGuid final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkGuid()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_guid_changed) {}
+
+ static already_AddRefed<PlacesBookmarkGuid> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkGuidInit& aInitDict) {
+ RefPtr<PlacesBookmarkGuid> event = new PlacesBookmarkGuid();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkGuid_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkGuid* AsPlacesBookmarkGuid() const override {
+ return this;
+ }
+
+ private:
+ ~PlacesBookmarkGuid() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkKeyword.h b/dom/base/PlacesBookmarkKeyword.h
new file mode 100644
index 0000000000..949679aeaa
--- /dev/null
+++ b/dom/base/PlacesBookmarkKeyword.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkKeyword_h
+#define mozilla_dom_PlacesBookmarkKeyword_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkKeyword final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkKeyword()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_keyword_changed) {}
+
+ static already_AddRefed<PlacesBookmarkKeyword> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkKeywordInit& aInitDict) {
+ RefPtr<PlacesBookmarkKeyword> event = new PlacesBookmarkKeyword();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mKeyword = aInitDict.mKeyword;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkKeyword_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkKeyword* AsPlacesBookmarkKeyword() const override {
+ return this;
+ }
+
+ void GetKeyword(nsCString& aKeyword) const { aKeyword = mKeyword; }
+ nsCString mKeyword;
+
+ private:
+ ~PlacesBookmarkKeyword() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkMoved.h b/dom/base/PlacesBookmarkMoved.h
new file mode 100644
index 0000000000..afad925789
--- /dev/null
+++ b/dom/base/PlacesBookmarkMoved.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkMoved_h
+#define mozilla_dom_PlacesBookmarkMoved_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkMoved final : public PlacesBookmark {
+ public:
+ explicit PlacesBookmarkMoved()
+ : PlacesBookmark(PlacesEventType::Bookmark_moved) {}
+
+ static already_AddRefed<PlacesBookmarkMoved> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkMovedInit& aInitDict) {
+ RefPtr<PlacesBookmarkMoved> event = new PlacesBookmarkMoved();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mIndex = aInitDict.mIndex;
+ event->mOldParentGuid = aInitDict.mOldParentGuid;
+ event->mOldIndex = aInitDict.mOldIndex;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkMoved_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkMoved* AsPlacesBookmarkMoved() const override {
+ return this;
+ }
+
+ int32_t Index() { return mIndex; }
+ int32_t OldIndex() { return mOldIndex; }
+ void GetOldParentGuid(nsCString& aOldParentGuid) {
+ aOldParentGuid = mOldParentGuid;
+ }
+
+ int32_t mIndex;
+ int32_t mOldIndex;
+ nsCString mOldParentGuid;
+
+ private:
+ ~PlacesBookmarkMoved() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkRemoved.h b/dom/base/PlacesBookmarkRemoved.h
new file mode 100644
index 0000000000..c8743a7fe4
--- /dev/null
+++ b/dom/base/PlacesBookmarkRemoved.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkRemoved_h
+#define mozilla_dom_PlacesBookmarkRemoved_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkRemoved final : public PlacesBookmark {
+ public:
+ explicit PlacesBookmarkRemoved()
+ : PlacesBookmark(PlacesEventType::Bookmark_removed) {}
+
+ static already_AddRefed<PlacesBookmarkRemoved> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkRemovedInit& aInitDict) {
+ RefPtr<PlacesBookmarkRemoved> event = new PlacesBookmarkRemoved();
+ event->mItemType = aInitDict.mItemType;
+ event->mId = aInitDict.mId;
+ event->mParentId = aInitDict.mParentId;
+ event->mIndex = aInitDict.mIndex;
+ event->mUrl = aInitDict.mUrl;
+ event->mTitle = aInitDict.mTitle;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ event->mIsDescendantRemoval = aInitDict.mIsDescendantRemoval;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkRemoved_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkRemoved* AsPlacesBookmarkRemoved() const override {
+ return this;
+ }
+
+ int32_t Index() { return mIndex; }
+ void GetTitle(nsString& aTitle) { aTitle = mTitle; }
+ bool IsDescendantRemoval() { return mIsDescendantRemoval; }
+
+ int32_t mIndex;
+ nsString mTitle;
+ bool mIsDescendantRemoval;
+
+ private:
+ ~PlacesBookmarkRemoved() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkTags.h b/dom/base/PlacesBookmarkTags.h
new file mode 100644
index 0000000000..560cb2af7c
--- /dev/null
+++ b/dom/base/PlacesBookmarkTags.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkTags_h
+#define mozilla_dom_PlacesBookmarkTags_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkTags final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkTags()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_tags_changed) {}
+
+ static already_AddRefed<PlacesBookmarkTags> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkTagsInit& aInitDict) {
+ RefPtr<PlacesBookmarkTags> event = new PlacesBookmarkTags();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mTags = aInitDict.mTags;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkTags_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkTags* AsPlacesBookmarkTags() const override {
+ return this;
+ }
+
+ void GetTags(nsTArray<nsString>& aTags) const { aTags.Assign(mTags); }
+
+ nsTArray<nsString> mTags;
+
+ private:
+ ~PlacesBookmarkTags() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkTime.h b/dom/base/PlacesBookmarkTime.h
new file mode 100644
index 0000000000..491a0c752e
--- /dev/null
+++ b/dom/base/PlacesBookmarkTime.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkTime_h
+#define mozilla_dom_PlacesBookmarkTime_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkTime final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkTime()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_time_changed) {}
+
+ static already_AddRefed<PlacesBookmarkTime> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkTimeInit& aInitDict) {
+ RefPtr<PlacesBookmarkTime> event = new PlacesBookmarkTime();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mDateAdded = aInitDict.mDateAdded;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkTime_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkTime* AsPlacesBookmarkTime() const override {
+ return this;
+ }
+
+ uint64_t DateAdded() { return mDateAdded; }
+ uint64_t mDateAdded;
+
+ private:
+ ~PlacesBookmarkTime() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkTitle.h b/dom/base/PlacesBookmarkTitle.h
new file mode 100644
index 0000000000..041b6a7c35
--- /dev/null
+++ b/dom/base/PlacesBookmarkTitle.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkTitle_h
+#define mozilla_dom_PlacesBookmarkTitle_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkTitle final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkTitle()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_title_changed) {}
+
+ static already_AddRefed<PlacesBookmarkTitle> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkTitleInit& aInitDict) {
+ RefPtr<PlacesBookmarkTitle> event = new PlacesBookmarkTitle();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mTitle = aInitDict.mTitle;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkTitle_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkTitle* AsPlacesBookmarkTitle() const override {
+ return this;
+ }
+
+ void GetTitle(nsString& aTitle) const { aTitle = mTitle; }
+
+ nsString mTitle;
+
+ private:
+ ~PlacesBookmarkTitle() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesBookmarkUrl.h b/dom/base/PlacesBookmarkUrl.h
new file mode 100644
index 0000000000..0df2e4611d
--- /dev/null
+++ b/dom/base/PlacesBookmarkUrl.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkUrl_h
+#define mozilla_dom_PlacesBookmarkUrl_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkUrl final : public PlacesBookmarkChanged {
+ public:
+ explicit PlacesBookmarkUrl()
+ : PlacesBookmarkChanged(PlacesEventType::Bookmark_url_changed) {}
+
+ static already_AddRefed<PlacesBookmarkUrl> Constructor(
+ const GlobalObject& aGlobal, const PlacesBookmarkUrlInit& aInitDict) {
+ RefPtr<PlacesBookmarkUrl> event = new PlacesBookmarkUrl();
+ event->mId = aInitDict.mId;
+ event->mItemType = aInitDict.mItemType;
+ event->mUrl = aInitDict.mUrl;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mLastModified = aInitDict.mLastModified;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesBookmarkUrl_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkUrl* AsPlacesBookmarkUrl() const override { return this; }
+
+ private:
+ ~PlacesBookmarkUrl() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/PlacesEvent.cpp b/dom/base/PlacesEvent.cpp
new file mode 100644
index 0000000000..d298ffc7fa
--- /dev/null
+++ b/dom/base/PlacesEvent.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PlacesEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PlacesEvent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PlacesEvent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PlacesEvent)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+already_AddRefed<PlacesEvent> PlacesEvent::Constructor(
+ const GlobalObject& aGlobal, PlacesEventType aType, ErrorResult& rv) {
+ RefPtr<PlacesEvent> event = new PlacesEvent(aType);
+ return event.forget();
+}
+
+nsISupports* PlacesEvent::GetParentObject() const { return nullptr; }
+
+JSObject* PlacesEvent::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PlacesEvent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/PlacesEvent.h b/dom/base/PlacesEvent.h
new file mode 100644
index 0000000000..4df74f52c7
--- /dev/null
+++ b/dom/base/PlacesEvent.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesEvent_h
+#define mozilla_dom_PlacesEvent_h
+
+#include "mozilla/dom/PlacesEventBinding.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class PlacesEvent : public nsWrapperCache {
+ public:
+ explicit PlacesEvent(PlacesEventType aType) : mType(aType) {}
+
+ static already_AddRefed<PlacesEvent> Constructor(const GlobalObject& aGlobal,
+ PlacesEventType aType,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlacesEvent)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PlacesEvent)
+
+ nsISupports* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ PlacesEventType Type() const { return mType; }
+
+ virtual const PlacesVisit* AsPlacesVisit() const { return nullptr; }
+ virtual const PlacesBookmark* AsPlacesBookmark() const { return nullptr; }
+ virtual const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkRemoved* AsPlacesBookmarkRemoved() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkMoved* AsPlacesBookmarkMoved() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkGuid* AsPlacesBookmarkGuid() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkKeyword* AsPlacesBookmarkKeyword() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkTags* AsPlacesBookmarkTags() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkTime* AsPlacesBookmarkTime() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkTitle* AsPlacesBookmarkTitle() const {
+ return nullptr;
+ }
+ virtual const PlacesBookmarkUrl* AsPlacesBookmarkUrl() const {
+ return nullptr;
+ }
+ virtual const PlacesFavicon* AsPlacesFavicon() const { return nullptr; }
+ virtual const PlacesVisitTitle* AsPlacesVisitTitle() const { return nullptr; }
+ virtual const PlacesHistoryCleared* AsPlacesHistoryCleared() const {
+ return nullptr;
+ }
+ virtual const PlacesRanking* AsPlacesRanking() const { return nullptr; }
+ virtual const PlacesVisitRemoved* AsPlacesVisitRemoved() const {
+ return nullptr;
+ }
+ virtual const PlacesPurgeCaches* AsPlacesPurgeCaches() const {
+ return nullptr;
+ }
+
+ protected:
+ virtual ~PlacesEvent() = default;
+ PlacesEventType mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesEvent_h
diff --git a/dom/base/PlacesFavicon.h b/dom/base/PlacesFavicon.h
new file mode 100644
index 0000000000..da186e7d16
--- /dev/null
+++ b/dom/base/PlacesFavicon.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesFavicon_h
+#define mozilla_dom_PlacesFavicon_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesFavicon final : public PlacesEvent {
+ public:
+ explicit PlacesFavicon() : PlacesEvent(PlacesEventType::Favicon_changed) {}
+
+ static already_AddRefed<PlacesFavicon> Constructor(
+ const GlobalObject& aGlobal, const PlacesFaviconInit& aInitDict) {
+ RefPtr<PlacesFavicon> event = new PlacesFavicon();
+ event->mUrl = aInitDict.mUrl;
+ event->mPageGuid = aInitDict.mPageGuid;
+ event->mFaviconUrl = aInitDict.mFaviconUrl;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesFavicon_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesFavicon* AsPlacesFavicon() const override { return this; }
+
+ void GetUrl(nsString& aUrl) const { aUrl = mUrl; }
+ void GetPageGuid(nsCString& aPageGuid) const { aPageGuid = mPageGuid; }
+ void GetFaviconUrl(nsString& aFaviconUrl) const { aFaviconUrl = mFaviconUrl; }
+
+ nsString mUrl;
+ nsCString mPageGuid;
+ nsString mFaviconUrl;
+
+ private:
+ ~PlacesFavicon() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesFavicon_h
diff --git a/dom/base/PlacesHistoryCleared.h b/dom/base/PlacesHistoryCleared.h
new file mode 100644
index 0000000000..1068fb7659
--- /dev/null
+++ b/dom/base/PlacesHistoryCleared.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesHistoryCleared_h
+#define mozilla_dom_PlacesHistoryCleared_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesHistoryCleared final : public PlacesEvent {
+ public:
+ explicit PlacesHistoryCleared()
+ : PlacesEvent(PlacesEventType::History_cleared) {}
+
+ static already_AddRefed<PlacesHistoryCleared> Constructor(
+ const GlobalObject& aGlobal) {
+ RefPtr<PlacesHistoryCleared> event = new PlacesHistoryCleared();
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesHistoryCleared_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesHistoryCleared* AsPlacesHistoryCleared() const override {
+ return this;
+ }
+
+ private:
+ ~PlacesHistoryCleared() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesHistoryCleared_h
diff --git a/dom/base/PlacesObservers.cpp b/dom/base/PlacesObservers.cpp
new file mode 100644
index 0000000000..bb92e4a072
--- /dev/null
+++ b/dom/base/PlacesObservers.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PlacesObservers.h"
+
+#include "PlacesWeakCallbackWrapper.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIXPConnect.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla::dom {
+
+template <class T>
+struct Flagged {
+ Flagged(uint32_t aFlags, T&& aValue)
+ : flags(aFlags), value(std::forward<T>(aValue)) {}
+ Flagged(Flagged&& aOther)
+ : Flagged(std::move(aOther.flags), std::move(aOther.value)) {}
+ Flagged(const Flagged& aOther) = default;
+ ~Flagged() = default;
+
+ uint32_t flags;
+ T value;
+};
+
+template <class T>
+using FlaggedArray = nsTArray<Flagged<T>>;
+
+template <class T>
+struct ListenerCollection {
+ static StaticAutoPtr<FlaggedArray<T>> gListeners;
+ static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
+
+ static FlaggedArray<T>* GetListeners(bool aDoNotInit = false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gListeners && !aDoNotInit) {
+ gListeners = new FlaggedArray<T>();
+ ClearOnShutdown(&gListeners);
+ }
+ return gListeners;
+ }
+
+ static FlaggedArray<T>* GetListenersToRemove(bool aDoNotInit = false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gListenersToRemove && !aDoNotInit) {
+ gListenersToRemove = new FlaggedArray<T>();
+ ClearOnShutdown(&gListenersToRemove);
+ }
+ return gListenersToRemove;
+ }
+};
+
+template <class T>
+StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListeners;
+template <class T>
+StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListenersToRemove;
+
+using JSListeners = ListenerCollection<RefPtr<PlacesEventCallback>>;
+using WeakJSListeners = ListenerCollection<WeakPtr<PlacesWeakCallbackWrapper>>;
+using WeakNativeListeners =
+ ListenerCollection<WeakPtr<places::INativePlacesEventCallback>>;
+
+// Even if NotifyListeners is called any timing, we mange the notifications with
+// adding to this queue, then sending in sequence. This avoids sending nested
+// notifications while previous ones are still being sent.
+static nsTArray<Sequence<OwningNonNull<PlacesEvent>>> gNotificationQueue;
+
+uint32_t GetEventTypeFlag(PlacesEventType aEventType) {
+ if (aEventType == PlacesEventType::None) {
+ return 0;
+ }
+ return 1 << ((uint32_t)aEventType - 1);
+}
+
+uint32_t GetFlagsForEventTypes(const nsTArray<PlacesEventType>& aEventTypes) {
+ uint32_t flags = 0;
+ for (PlacesEventType eventType : aEventTypes) {
+ flags |= GetEventTypeFlag(eventType);
+ }
+ return flags;
+}
+
+uint32_t GetFlagsForEvents(
+ const nsTArray<OwningNonNull<PlacesEvent>>& aEvents) {
+ uint32_t flags = 0;
+ for (const PlacesEvent& event : aEvents) {
+ flags |= GetEventTypeFlag(event.Type());
+ }
+ return flags;
+}
+
+template <class TWrapped, class TUnwrapped, class TListenerCollection>
+MOZ_CAN_RUN_SCRIPT void CallListeners(
+ uint32_t aEventFlags, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
+ unsigned long aListenersLengthToCall,
+ const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
+ const std::function<void(TUnwrapped&,
+ const Sequence<OwningNonNull<PlacesEvent>>&)>&
+ aCallListener) {
+ auto& listeners = *TListenerCollection::GetListeners();
+ for (uint32_t i = 0; i < aListenersLengthToCall; i++) {
+ Flagged<TWrapped>& listener = listeners[i];
+ TUnwrapped unwrapped = aUnwrapListener(listener.value);
+ if (!unwrapped) {
+ continue;
+ }
+
+ if ((listener.flags & aEventFlags) == aEventFlags) {
+ aCallListener(unwrapped, aEvents);
+ } else if (listener.flags & aEventFlags) {
+ Sequence<OwningNonNull<PlacesEvent>> filtered;
+ for (const OwningNonNull<PlacesEvent>& event : aEvents) {
+ if (listener.flags & GetEventTypeFlag(event->Type())) {
+ bool success = !!filtered.AppendElement(event, fallible);
+ MOZ_RELEASE_ASSERT(success);
+ }
+ }
+ aCallListener(unwrapped, filtered);
+ }
+ }
+}
+
+void PlacesObservers::AddListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback,
+ ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListeners();
+ Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
+ listeners->AppendElement(pair);
+}
+
+void PlacesObservers::AddListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback,
+ ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListeners();
+ WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
+ MOZ_ASSERT(weakCb.get());
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
+ listeners->AppendElement(flagged);
+}
+
+void PlacesObservers::AddListener(
+ const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListeners();
+ Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
+ listeners->AppendElement(pair);
+}
+
+void PlacesObservers::RemoveListener(
+ GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback, ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListenersToRemove();
+ Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
+ listeners->AppendElement(pair);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(
+ GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListenersToRemove();
+ WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
+ MOZ_ASSERT(weakCb.get());
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags,
+ std::move(weakCb));
+ listeners->AppendElement(flagged);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(
+ const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListenersToRemove();
+ Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
+ listeners->AppendElement(pair);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(uint32_t aFlags,
+ PlacesEventCallback& aCallback) {
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<RefPtr<PlacesEventCallback>>& l = listeners->ElementAt(i);
+ if (!(*l.value == aCallback)) {
+ continue;
+ }
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
+ i--;
+ } else {
+ l.flags &= ~aFlags;
+ }
+ }
+}
+
+void PlacesObservers::RemoveListener(uint32_t aFlags,
+ PlacesWeakCallbackWrapper& aCallback) {
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners->ElementAt(i);
+ RefPtr<PlacesWeakCallbackWrapper> unwrapped = l.value.get();
+ if (unwrapped != &aCallback) {
+ continue;
+ }
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
+ i--;
+ } else {
+ l.flags &= ~aFlags;
+ }
+ }
+}
+
+void PlacesObservers::RemoveListener(
+ uint32_t aFlags, places::INativePlacesEventCallback* aCallback) {
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<places::INativePlacesEventCallback>>& l =
+ listeners->ElementAt(i);
+ RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
+ if (unwrapped != aCallback) {
+ continue;
+ }
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
+ i--;
+ } else {
+ l.flags &= ~aFlags;
+ }
+ }
+}
+
+template <class TWrapped, class TUnwrapped, class TListenerCollection>
+void CleanupListeners(
+ const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
+ const std::function<void(Flagged<TWrapped>&)>& aRemoveListener) {
+ auto& listeners = *TListenerCollection::GetListeners();
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+ Flagged<TWrapped>& listener = listeners[i];
+ TUnwrapped unwrapped = aUnwrapListener(listener.value);
+ if (!unwrapped) {
+ listeners.RemoveElementAt(i);
+ i--;
+ }
+ }
+
+ auto& listenersToRemove = *TListenerCollection::GetListenersToRemove();
+ for (auto& listener : listenersToRemove) {
+ aRemoveListener(listener);
+ }
+ listenersToRemove.Clear();
+}
+
+void PlacesObservers::NotifyListeners(
+ GlobalObject& aGlobal, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
+ ErrorResult& rv) {
+ NotifyListeners(aEvents);
+}
+
+void PlacesObservers::NotifyListeners(
+ const Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
+ MOZ_ASSERT(aEvents.Length() > 0, "Must pass a populated array of events");
+ if (aEvents.Length() == 0) {
+ return;
+ }
+
+#ifdef DEBUG
+ if (!gNotificationQueue.IsEmpty()) {
+ NS_WARNING(
+ "Avoid nested Places notifications if possible, the order of events "
+ "cannot be guaranteed");
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+ Unused << xpc->DebugDumpJSStack(false, false, false);
+ }
+#endif
+
+ gNotificationQueue.AppendElement(aEvents);
+
+ // If gNotificationQueue has only the events we added now, start to notify.
+ // Otherwise, as it already started the notification processing,
+ // rely on the processing.
+ if (gNotificationQueue.Length() == 1) {
+ NotifyNext();
+ }
+}
+
+void PlacesObservers::NotifyNext() {
+ auto events = gNotificationQueue[0];
+ uint32_t flags = GetFlagsForEvents(events);
+
+ // Send up to the number of current listeners, to avoid handling listeners
+ // added during this notification.
+ unsigned long jsListenersLength = JSListeners::GetListeners()->Length();
+ unsigned long weakNativeListenersLength =
+ WeakNativeListeners::GetListeners()->Length();
+ unsigned long weakJSListenersLength =
+ WeakJSListeners::GetListeners()->Length();
+
+ CallListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
+ JSListeners>(
+ flags, events, jsListenersLength, [](auto& cb) { return cb; },
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
+ // some internals of the std::function implementation that we can't
+ // annotate. We handle this by annotating CallListeners and making sure
+ // it holds a strong ref to the callback.
+ [&](auto& cb, const auto& events)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY { MOZ_KnownLive(cb)->Call(events); });
+
+ CallListeners<WeakPtr<places::INativePlacesEventCallback>,
+ RefPtr<places::INativePlacesEventCallback>,
+ WeakNativeListeners>(
+ flags, events, weakNativeListenersLength,
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb, const Sequence<OwningNonNull<PlacesEvent>>& events) {
+ cb->HandlePlacesEvent(events);
+ });
+
+ CallListeners<WeakPtr<PlacesWeakCallbackWrapper>,
+ RefPtr<PlacesWeakCallbackWrapper>, WeakJSListeners>(
+ flags, events, weakJSListenersLength, [](auto& cb) { return cb.get(); },
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
+ // some internals of the std::function implementation that we can't
+ // annotate. We handle this by annotating CallListeners and making sure
+ // it holds a strong ref to the callback.
+ [&](auto& cb, const auto& events) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr<PlacesEventCallback> callback(cb->mCallback);
+ callback->Call(events);
+ });
+
+ gNotificationQueue.RemoveElementAt(0);
+
+ CleanupListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
+ JSListeners>(
+ [](auto& cb) { return cb; },
+ [&](auto& cb) { RemoveListener(cb.flags, *cb.value); });
+ CleanupListeners<WeakPtr<PlacesWeakCallbackWrapper>,
+ RefPtr<PlacesWeakCallbackWrapper>, WeakJSListeners>(
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb) { RemoveListener(cb.flags, *cb.value.get()); });
+ CleanupListeners<WeakPtr<places::INativePlacesEventCallback>,
+ RefPtr<places::INativePlacesEventCallback>,
+ WeakNativeListeners>(
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb) { RemoveListener(cb.flags, cb.value.get()); });
+
+ if (!gNotificationQueue.IsEmpty()) {
+ NotifyNext();
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/PlacesObservers.h b/dom/base/PlacesObservers.h
new file mode 100644
index 0000000000..e7aca305d8
--- /dev/null
+++ b/dom/base/PlacesObservers.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesObservers__
+#define mozilla_dom_PlacesObservers__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PlacesObserversBinding.h"
+#include "mozilla/dom/PlacesEvent.h"
+#include "mozilla/places/INativePlacesEventCallback.h"
+#include "nsIWeakReferenceUtils.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class PlacesObservers {
+ public:
+ static void AddListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback, ErrorResult& rv);
+ static void AddListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback,
+ ErrorResult& rv);
+ static void AddListener(const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback);
+ static void RemoveListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback, ErrorResult& rv);
+ static void RemoveListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback,
+ ErrorResult& rv);
+ static void RemoveListener(const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void NotifyListeners(
+ GlobalObject& aGlobal,
+ const Sequence<OwningNonNull<PlacesEvent>>& aEvents, ErrorResult& rv);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void NotifyListeners(
+ const Sequence<OwningNonNull<PlacesEvent>>& aEvents);
+
+ private:
+ static void RemoveListener(uint32_t aFlags, PlacesEventCallback& aCallback);
+ static void RemoveListener(uint32_t aFlags,
+ PlacesWeakCallbackWrapper& aCallback);
+ static void RemoveListener(uint32_t aFlags,
+ places::INativePlacesEventCallback* aCallback);
+
+ MOZ_CAN_RUN_SCRIPT static void NotifyNext();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesObservers__
diff --git a/dom/base/PlacesPurgeCaches.h b/dom/base/PlacesPurgeCaches.h
new file mode 100644
index 0000000000..6678ee8252
--- /dev/null
+++ b/dom/base/PlacesPurgeCaches.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesPurgeCaches_h
+#define mozilla_dom_PlacesPurgeCaches_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesPurgeCaches final : public PlacesEvent {
+ public:
+ explicit PlacesPurgeCaches() : PlacesEvent(PlacesEventType::Purge_caches) {}
+
+ static already_AddRefed<PlacesPurgeCaches> Constructor(
+ const GlobalObject& aGlobal) {
+ RefPtr<PlacesPurgeCaches> event = new PlacesPurgeCaches();
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesPurgeCaches_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesPurgeCaches* AsPlacesPurgeCaches() const override { return this; }
+
+ private:
+ ~PlacesPurgeCaches() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesPurgeCaches_h
diff --git a/dom/base/PlacesRanking.h b/dom/base/PlacesRanking.h
new file mode 100644
index 0000000000..2fc669dce5
--- /dev/null
+++ b/dom/base/PlacesRanking.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesRanking_h
+#define mozilla_dom_PlacesRanking_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesRanking final : public PlacesEvent {
+ public:
+ explicit PlacesRanking() : PlacesEvent(PlacesEventType::Pages_rank_changed) {}
+
+ static already_AddRefed<PlacesRanking> Constructor(
+ const GlobalObject& aGlobal) {
+ RefPtr<PlacesRanking> event = new PlacesRanking();
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesRanking_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesRanking* AsPlacesRanking() const override { return this; }
+
+ private:
+ ~PlacesRanking() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesRanking_h
diff --git a/dom/base/PlacesVisit.h b/dom/base/PlacesVisit.h
new file mode 100644
index 0000000000..4b3f99218b
--- /dev/null
+++ b/dom/base/PlacesVisit.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesVisit_h
+#define mozilla_dom_PlacesVisit_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesVisit final : public PlacesEvent {
+ public:
+ explicit PlacesVisit() : PlacesEvent(PlacesEventType::Page_visited) {}
+
+ static already_AddRefed<PlacesVisit> Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ RefPtr<PlacesVisit> event = new PlacesVisit();
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesVisit_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesVisit* AsPlacesVisit() const override { return this; }
+
+ void GetUrl(nsString& aUrl) { aUrl = mUrl; }
+ uint64_t VisitId() { return mVisitId; }
+ uint64_t VisitTime() { return mVisitTime; }
+ uint64_t ReferringVisitId() { return mReferringVisitId; }
+ uint64_t TransitionType() { return mTransitionType; }
+ void GetPageGuid(nsTString<char>& aPageGuid) { aPageGuid = mPageGuid; }
+ bool Hidden() { return mHidden; }
+ uint32_t VisitCount() { return mVisitCount; }
+ uint32_t TypedCount() { return mTypedCount; }
+ void GetLastKnownTitle(nsString& aLastKnownTitle) {
+ aLastKnownTitle = mLastKnownTitle;
+ }
+
+ // It's convenient for these to be directly available in C++, so just expose
+ // them. These are generally passed around with const qualifiers anyway, so
+ // it shouldn't be a problem.
+ nsString mUrl;
+ uint64_t mVisitId;
+ uint64_t mVisitTime;
+ uint64_t mReferringVisitId;
+ uint32_t mTransitionType;
+ nsCString mPageGuid;
+ bool mHidden;
+ uint32_t mVisitCount;
+ uint32_t mTypedCount;
+ nsString mLastKnownTitle;
+
+ private:
+ ~PlacesVisit() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesVisit_h
diff --git a/dom/base/PlacesVisitRemoved.h b/dom/base/PlacesVisitRemoved.h
new file mode 100644
index 0000000000..3b11dd5184
--- /dev/null
+++ b/dom/base/PlacesVisitRemoved.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesVisitRemoved_h
+#define mozilla_dom_PlacesVisitRemoved_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesVisitRemoved final : public PlacesEvent {
+ public:
+ explicit PlacesVisitRemoved() : PlacesEvent(PlacesEventType::Page_removed) {}
+
+ static already_AddRefed<PlacesVisitRemoved> Constructor(
+ const GlobalObject& aGlobal, const PlacesVisitRemovedInit& aInitDict) {
+ MOZ_ASSERT(
+ aInitDict.mReason == PlacesVisitRemoved_Binding::REASON_DELETED ||
+ aInitDict.mReason == PlacesVisitRemoved_Binding::REASON_EXPIRED,
+ "The reason should be REASON_DELETED or REASON_EXPIRED");
+ MOZ_ASSERT(
+ !(aInitDict.mIsRemovedFromStore && aInitDict.mIsPartialVisistsRemoval),
+ "isRemovedFromStore and isPartialVisistsRemoval are inconsistent");
+
+ RefPtr<PlacesVisitRemoved> event = new PlacesVisitRemoved();
+ event->mUrl = aInitDict.mUrl;
+ event->mPageGuid = aInitDict.mPageGuid;
+ event->mReason = aInitDict.mReason;
+ event->mTransitionType = aInitDict.mTransitionType;
+ event->mIsRemovedFromStore = aInitDict.mIsRemovedFromStore;
+ event->mIsPartialVisistsRemoval = aInitDict.mIsPartialVisistsRemoval;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesVisitRemoved_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesVisitRemoved* AsPlacesVisitRemoved() const override {
+ return this;
+ }
+
+ void GetUrl(nsString& aUrl) const { aUrl = mUrl; }
+ void GetPageGuid(nsCString& aPageGuid) const { aPageGuid = mPageGuid; }
+ uint16_t Reason() const { return mReason; }
+ uint32_t TransitionType() const { return mTransitionType; }
+ bool IsRemovedFromStore() const { return mIsRemovedFromStore; }
+ bool IsPartialVisistsRemoval() const { return mIsPartialVisistsRemoval; }
+
+ nsString mUrl;
+ nsCString mPageGuid;
+ uint16_t mReason;
+ uint32_t mTransitionType;
+ bool mIsRemovedFromStore;
+ bool mIsPartialVisistsRemoval;
+
+ private:
+ ~PlacesVisitRemoved() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesVisitRemoved_h
diff --git a/dom/base/PlacesVisitTitle.h b/dom/base/PlacesVisitTitle.h
new file mode 100644
index 0000000000..052b061485
--- /dev/null
+++ b/dom/base/PlacesVisitTitle.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesVisitTitle_h
+#define mozilla_dom_PlacesVisitTitle_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesVisitTitle final : public PlacesEvent {
+ public:
+ explicit PlacesVisitTitle()
+ : PlacesEvent(PlacesEventType::Page_title_changed) {}
+
+ static already_AddRefed<PlacesVisitTitle> Constructor(
+ const GlobalObject& aGlobal, const PlacesVisitTitleInit& aInitDict) {
+ RefPtr<PlacesVisitTitle> event = new PlacesVisitTitle();
+ event->mUrl = aInitDict.mUrl;
+ event->mPageGuid = aInitDict.mPageGuid;
+ event->mTitle = aInitDict.mTitle;
+ return event.forget();
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return PlacesVisitTitle_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesVisitTitle* AsPlacesVisitTitle() const override { return this; }
+
+ void GetUrl(nsString& aUrl) const { aUrl = mUrl; }
+ void GetPageGuid(nsCString& aPageGuid) const { aPageGuid = mPageGuid; }
+ void GetTitle(nsString& aTitle) const { aTitle = mTitle; }
+
+ nsString mUrl;
+ nsCString mPageGuid;
+ nsString mTitle;
+
+ private:
+ ~PlacesVisitTitle() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesVisitTitle_h
diff --git a/dom/base/PlacesWeakCallbackWrapper.cpp b/dom/base/PlacesWeakCallbackWrapper.cpp
new file mode 100644
index 0000000000..5e60c174b2
--- /dev/null
+++ b/dom/base/PlacesWeakCallbackWrapper.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PlacesWeakCallbackWrapper.h"
+
+#include "mozilla/dom/ContentProcessMessageManager.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(PlacesWeakCallbackWrapper,
+ mParent, mCallback)
+
+PlacesWeakCallbackWrapper::PlacesWeakCallbackWrapper(
+ nsISupports* aParent, PlacesEventCallback& aCallback)
+ : mParent(do_GetWeakReference(aParent)), mCallback(&aCallback) {}
+
+already_AddRefed<PlacesWeakCallbackWrapper>
+PlacesWeakCallbackWrapper::Constructor(const GlobalObject& aGlobal,
+ PlacesEventCallback& aCallback) {
+ nsCOMPtr<nsISupports> parent = aGlobal.GetAsSupports();
+ RefPtr<PlacesWeakCallbackWrapper> wrapper =
+ new PlacesWeakCallbackWrapper(parent, aCallback);
+ return wrapper.forget();
+}
+
+PlacesWeakCallbackWrapper::~PlacesWeakCallbackWrapper() = default;
+
+nsISupports* PlacesWeakCallbackWrapper::GetParentObject() const {
+ nsCOMPtr<nsISupports> parent = do_QueryReferent(mParent);
+ return parent;
+}
+
+JSObject* PlacesWeakCallbackWrapper::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return PlacesWeakCallbackWrapper_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/PlacesWeakCallbackWrapper.h b/dom/base/PlacesWeakCallbackWrapper.h
new file mode 100644
index 0000000000..0b20276fc4
--- /dev/null
+++ b/dom/base/PlacesWeakCallbackWrapper.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesWeakCallbackWrapper_h
+#define mozilla_dom_PlacesWeakCallbackWrapper_h
+
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/PlacesObserversBinding.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class PlacesWeakCallbackWrapper final : public nsWrapperCache,
+ public SupportsWeakPtr {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlacesWeakCallbackWrapper)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(PlacesWeakCallbackWrapper)
+
+ explicit PlacesWeakCallbackWrapper(nsISupports* aParent,
+ PlacesEventCallback& aCallback);
+
+ static already_AddRefed<PlacesWeakCallbackWrapper> Constructor(
+ const GlobalObject& aGlobal, PlacesEventCallback& aCallback);
+
+ nsISupports* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ protected:
+ friend class PlacesObservers;
+ ~PlacesWeakCallbackWrapper();
+ nsWeakPtr mParent;
+ RefPtr<PlacesEventCallback> mCallback;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_PlacesWeakCallbackWrapper_h
diff --git a/dom/base/PointerLockManager.cpp b/dom/base/PointerLockManager.cpp
new file mode 100644
index 0000000000..4ec9d76abf
--- /dev/null
+++ b/dom/base/PointerLockManager.cpp
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PointerLockManager.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_full_screen_api.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/WindowContext.h"
+#include "nsCOMPtr.h"
+#include "nsSandboxFlags.h"
+
+namespace mozilla {
+
+using mozilla::dom::BrowserChild;
+using mozilla::dom::BrowserParent;
+using mozilla::dom::BrowsingContext;
+using mozilla::dom::CallerType;
+using mozilla::dom::Document;
+using mozilla::dom::Element;
+using mozilla::dom::WindowContext;
+
+// Reference to the pointer locked element.
+static nsWeakPtr sLockedElement;
+
+// Reference to the document which requested pointer lock.
+static nsWeakPtr sLockedDoc;
+
+// Reference to the BrowserParent requested pointer lock.
+static BrowserParent* sLockedRemoteTarget = nullptr;
+
+/* static */
+bool PointerLockManager::sIsLocked = false;
+
+/* static */
+already_AddRefed<dom::Element> PointerLockManager::GetLockedElement() {
+ nsCOMPtr<Element> element = do_QueryReferent(sLockedElement);
+ return element.forget();
+}
+
+/* static */
+already_AddRefed<dom::Document> PointerLockManager::GetLockedDocument() {
+ nsCOMPtr<Document> document = do_QueryReferent(sLockedDoc);
+ return document.forget();
+}
+
+/* static */
+BrowserParent* PointerLockManager::GetLockedRemoteTarget() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return sLockedRemoteTarget;
+}
+
+static void DispatchPointerLockChange(Document* aTarget) {
+ if (!aTarget) {
+ return;
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aTarget, u"pointerlockchange"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eNo);
+ asyncDispatcher->PostDOMEvent();
+}
+
+static void DispatchPointerLockError(Document* aTarget, const char* aMessage) {
+ if (!aTarget) {
+ return;
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aTarget, u"pointerlockerror"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eNo);
+ asyncDispatcher->PostDOMEvent();
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ aTarget, nsContentUtils::eDOM_PROPERTIES,
+ aMessage);
+}
+
+static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock,
+ bool aNoFocusCheck = false) {
+ // Check if pointer lock pref is enabled
+ if (!StaticPrefs::full_screen_api_pointer_lock_enabled()) {
+ return "PointerLockDeniedDisabled";
+ }
+
+ nsCOMPtr<Document> ownerDoc = aElement->OwnerDoc();
+ if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
+ return "PointerLockDeniedInUse";
+ }
+
+ if (!aElement->IsInComposedDoc()) {
+ return "PointerLockDeniedNotInDocument";
+ }
+
+ if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) {
+ return "PointerLockDeniedSandboxed";
+ }
+
+ // Check if the element is in a document with a docshell.
+ if (!ownerDoc->GetContainer()) {
+ return "PointerLockDeniedHidden";
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow();
+ if (!ownerWindow) {
+ return "PointerLockDeniedHidden";
+ }
+ nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow();
+ if (!ownerInnerWindow) {
+ return "PointerLockDeniedHidden";
+ }
+ if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
+ return "PointerLockDeniedHidden";
+ }
+
+ BrowsingContext* bc = ownerDoc->GetBrowsingContext();
+ BrowsingContext* topBC = bc ? bc->Top() : nullptr;
+ WindowContext* topWC = ownerDoc->GetTopLevelWindowContext();
+ if (!topBC || !topBC->IsActive() || !topWC ||
+ topWC != topBC->GetCurrentWindowContext()) {
+ return "PointerLockDeniedHidden";
+ }
+
+ if (!aNoFocusCheck) {
+ if (!IsInActiveTab(ownerDoc)) {
+ return "PointerLockDeniedNotFocused";
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+void PointerLockManager::RequestLock(Element* aElement,
+ CallerType aCallerType) {
+ NS_ASSERTION(aElement,
+ "Must pass non-null element to PointerLockManager::RequestLock");
+
+ RefPtr<Document> doc = aElement->OwnerDoc();
+ nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
+ if (aElement == pointerLockedElement) {
+ DispatchPointerLockChange(doc);
+ return;
+ }
+
+ if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
+ DispatchPointerLockError(doc, msg);
+ return;
+ }
+
+ bool userInputOrSystemCaller =
+ doc->HasValidTransientUserGestureActivation() ||
+ aCallerType == CallerType::System;
+ nsCOMPtr<nsIRunnable> request =
+ new PointerLockRequest(aElement, userInputOrSystemCaller);
+ doc->Dispatch(TaskCategory::Other, request.forget());
+}
+
+/* static */
+void PointerLockManager::Unlock(Document* aDoc) {
+ if (!sIsLocked) {
+ return;
+ }
+
+ nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
+ if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
+ return;
+ }
+ if (!SetPointerLock(nullptr, pointerLockedDoc, StyleCursorKind::Auto)) {
+ return;
+ }
+
+ nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
+ ChangePointerLockedElement(nullptr, pointerLockedDoc, pointerLockedElement);
+
+ if (BrowserChild* browserChild =
+ BrowserChild::GetFrom(pointerLockedDoc->GetDocShell())) {
+ browserChild->SendReleasePointerLock();
+ }
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *pointerLockedElement, u"MozDOMPointerLock:Exited"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+}
+
+/* static */
+void PointerLockManager::ChangePointerLockedElement(
+ Element* aElement, Document* aDocument, Element* aPointerLockedElement) {
+ // aDocument here is not really necessary, as it is the uncomposed
+ // document of both aElement and aPointerLockedElement as far as one
+ // is not nullptr, and they wouldn't both be nullptr in any case.
+ // But since the caller of this function should have known what the
+ // document is, we just don't try to figure out what it should be.
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(aElement != aPointerLockedElement);
+ if (aPointerLockedElement) {
+ MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument);
+ aPointerLockedElement->ClearPointerLock();
+ }
+ if (aElement) {
+ MOZ_ASSERT(aElement->GetComposedDoc() == aDocument);
+ aElement->SetPointerLock();
+ sLockedElement = do_GetWeakReference(aElement);
+ sLockedDoc = do_GetWeakReference(aDocument);
+ NS_ASSERTION(sLockedElement && sLockedDoc,
+ "aElement and this should support weak references!");
+ } else {
+ sLockedElement = nullptr;
+ sLockedDoc = nullptr;
+ }
+ // Retarget all events to aElement via capture or
+ // stop retargeting if aElement is nullptr.
+ PresShell::SetCapturingContent(aElement, CaptureFlags::PointerLock);
+ DispatchPointerLockChange(aDocument);
+}
+
+/* static */
+bool PointerLockManager::StartSetPointerLock(Element* aElement,
+ Document* aDocument) {
+ if (!SetPointerLock(aElement, aDocument, StyleCursorKind::None)) {
+ DispatchPointerLockError(aDocument, "PointerLockDeniedFailedToLock");
+ return false;
+ }
+
+ ChangePointerLockedElement(aElement, aDocument, nullptr);
+ nsContentUtils::DispatchEventOnlyToChrome(
+ aDocument, ToSupports(aElement), u"MozDOMPointerLock:Entered"_ns,
+ CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
+
+ return true;
+}
+
+/* static */
+bool PointerLockManager::SetPointerLock(Element* aElement, Document* aDocument,
+ StyleCursorKind aCursorStyle) {
+ MOZ_ASSERT(!aElement || aElement->OwnerDoc() == aDocument,
+ "We should be either unlocking pointer (aElement is nullptr), "
+ "or locking pointer to an element in this document");
+#ifdef DEBUG
+ if (!aElement) {
+ nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
+ MOZ_ASSERT(pointerLockedDoc == aDocument);
+ }
+#endif
+
+ PresShell* presShell = aDocument->GetPresShell();
+ if (!presShell) {
+ NS_WARNING("SetPointerLock(): No PresShell");
+ if (!aElement) {
+ sIsLocked = false;
+ // If we are unlocking pointer lock, but for some reason the doc
+ // has already detached from the presshell, just ask the event
+ // state manager to release the pointer.
+ EventStateManager::SetPointerLock(nullptr, nullptr);
+ return true;
+ }
+ return false;
+ }
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ if (!presContext) {
+ NS_WARNING("SetPointerLock(): Unable to get PresContext");
+ return false;
+ }
+
+ nsCOMPtr<nsIWidget> widget;
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!NS_WARN_IF(!rootFrame)) {
+ widget = rootFrame->GetNearestWidget();
+ NS_WARNING_ASSERTION(widget,
+ "SetPointerLock(): Unable to find widget in "
+ "presShell->GetRootFrame()->GetNearestWidget();");
+ if (aElement && !widget) {
+ return false;
+ }
+ }
+
+ sIsLocked = !!aElement;
+
+ // Hide the cursor and set pointer lock for future mouse events
+ RefPtr<EventStateManager> esm = presContext->EventStateManager();
+ esm->SetCursor(aCursorStyle, nullptr, {}, Nothing(), widget, true);
+ EventStateManager::SetPointerLock(widget, presContext);
+
+ return true;
+}
+
+/* static */
+bool PointerLockManager::IsInLockContext(BrowsingContext* aContext) {
+ if (!aContext) {
+ return false;
+ }
+
+ nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
+ if (!pointerLockedDoc || !pointerLockedDoc->GetBrowsingContext()) {
+ return false;
+ }
+
+ BrowsingContext* lockTop = pointerLockedDoc->GetBrowsingContext()->Top();
+ BrowsingContext* top = aContext->Top();
+
+ return top == lockTop;
+}
+
+/* static */
+bool PointerLockManager::SetLockedRemoteTarget(BrowserParent* aBrowserParent) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sLockedRemoteTarget) {
+ return sLockedRemoteTarget == aBrowserParent;
+ }
+
+ sLockedRemoteTarget = aBrowserParent;
+ return true;
+}
+
+/* static */
+void PointerLockManager::ReleaseLockedRemoteTarget(
+ BrowserParent* aBrowserParent) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sLockedRemoteTarget == aBrowserParent) {
+ sLockedRemoteTarget = nullptr;
+ }
+}
+
+PointerLockManager::PointerLockRequest::PointerLockRequest(
+ Element* aElement, bool aUserInputOrChromeCaller)
+ : mozilla::Runnable("PointerLockRequest"),
+ mElement(do_GetWeakReference(aElement)),
+ mDocument(do_GetWeakReference(aElement->OwnerDoc())),
+ mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
+
+NS_IMETHODIMP
+PointerLockManager::PointerLockRequest::Run() {
+ nsCOMPtr<Element> element = do_QueryReferent(mElement);
+ nsCOMPtr<Document> document = do_QueryReferent(mDocument);
+
+ const char* error = nullptr;
+ if (!element || !document || !element->GetComposedDoc()) {
+ error = "PointerLockDeniedNotInDocument";
+ } else if (element->GetComposedDoc() != document) {
+ error = "PointerLockDeniedMovedDocument";
+ }
+ if (!error) {
+ nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(sLockedElement);
+ if (element == pointerLockedElement) {
+ DispatchPointerLockChange(document);
+ return NS_OK;
+ }
+ // Note, we must bypass focus change, so pass true as the last parameter!
+ error = GetPointerLockError(element, pointerLockedElement, true);
+ // Another element in the same document is requesting pointer lock,
+ // just grant it without user input check.
+ if (!error && pointerLockedElement) {
+ ChangePointerLockedElement(element, document, pointerLockedElement);
+ return NS_OK;
+ }
+ }
+ // If it is neither user input initiated, nor requested in fullscreen,
+ // it should be rejected.
+ if (!error && !mUserInputOrChromeCaller && !document->Fullscreen()) {
+ error = "PointerLockDeniedNotInputDriven";
+ }
+
+ if (error) {
+ DispatchPointerLockError(document, error);
+ return NS_OK;
+ }
+
+ if (BrowserChild* browserChild =
+ BrowserChild::GetFrom(document->GetDocShell())) {
+ nsWeakPtr e = do_GetWeakReference(element);
+ nsWeakPtr doc = do_GetWeakReference(element->OwnerDoc());
+ nsWeakPtr bc = do_GetWeakReference(browserChild);
+ browserChild->SendRequestPointerLock(
+ [e, doc, bc](const nsCString& aError) {
+ nsCOMPtr<Document> document = do_QueryReferent(doc);
+ if (!aError.IsEmpty()) {
+ DispatchPointerLockError(document, aError.get());
+ return;
+ }
+
+ const char* error = nullptr;
+ auto autoCleanup = MakeScopeExit([&] {
+ if (error) {
+ DispatchPointerLockError(document, error);
+ // If we are failed to set pointer lock, notify parent to stop
+ // redirect mouse event to this process.
+ if (nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryReferent(bc)) {
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SendReleasePointerLock();
+ }
+ }
+ });
+
+ nsCOMPtr<Element> element = do_QueryReferent(e);
+ if (!element || !document || !element->GetComposedDoc()) {
+ error = "PointerLockDeniedNotInDocument";
+ return;
+ }
+
+ if (element->GetComposedDoc() != document) {
+ error = "PointerLockDeniedMovedDocument";
+ return;
+ }
+
+ nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
+ error = GetPointerLockError(element, pointerLockedElement, true);
+ if (error) {
+ return;
+ }
+
+ if (!StartSetPointerLock(element, document)) {
+ error = "PointerLockDeniedFailedToLock";
+ return;
+ }
+ },
+ [doc](mozilla::ipc::ResponseRejectReason) {
+ // IPC layer error
+ nsCOMPtr<Document> document = do_QueryReferent(doc);
+ if (!document) {
+ return;
+ }
+
+ DispatchPointerLockError(document, "PointerLockDeniedFailedToLock");
+ });
+ } else {
+ StartSetPointerLock(element, document);
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/base/PointerLockManager.h b/dom/base/PointerLockManager.h
new file mode 100644
index 0000000000..0b229bd429
--- /dev/null
+++ b/dom/base/PointerLockManager.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_PointerLockManager_h
+#define mozilla_PointerLockManager_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+enum class StyleCursorKind : uint8_t;
+
+namespace dom {
+class BrowsingContext;
+class BrowserParent;
+enum class CallerType : uint32_t;
+class Document;
+class Element;
+} // namespace dom
+
+class PointerLockManager final {
+ public:
+ static void RequestLock(dom::Element* aElement, dom::CallerType aCallerType);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static void Unlock(dom::Document* aDoc = nullptr);
+
+ static bool IsLocked() { return sIsLocked; }
+
+ static already_AddRefed<dom::Element> GetLockedElement();
+
+ static already_AddRefed<dom::Document> GetLockedDocument();
+
+ static dom::BrowserParent* GetLockedRemoteTarget();
+
+ /**
+ * Returns true if aContext and the current pointer locked document
+ * have common top BrowsingContext.
+ * Note that this method returns true only if caller is in the same process
+ * as pointer locked document.
+ */
+ static bool IsInLockContext(mozilla::dom::BrowsingContext* aContext);
+
+ // Set/release pointer lock remote target. Should only be called in parent
+ // process.
+ static bool SetLockedRemoteTarget(dom::BrowserParent* aBrowserParent);
+ static void ReleaseLockedRemoteTarget(dom::BrowserParent* aBrowserParent);
+
+ private:
+ class PointerLockRequest final : public Runnable {
+ public:
+ PointerLockRequest(dom::Element* aElement, bool aUserInputOrChromeCaller);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() final;
+
+ private:
+ nsWeakPtr mElement;
+ nsWeakPtr mDocument;
+ bool mUserInputOrChromeCaller;
+ };
+
+ static void ChangePointerLockedElement(dom::Element* aElement,
+ dom::Document* aDocument,
+ dom::Element* aPointerLockedElement);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static bool StartSetPointerLock(dom::Element* aElement,
+ dom::Document* aDocument);
+
+ MOZ_CAN_RUN_SCRIPT
+ static bool SetPointerLock(dom::Element* aElement, dom::Document* aDocument,
+ StyleCursorKind);
+
+ static bool sIsLocked;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PointerLockManager_h
diff --git a/dom/base/PopoverData.cpp b/dom/base/PopoverData.cpp
new file mode 100644
index 0000000000..2ecca5f43d
--- /dev/null
+++ b/dom/base/PopoverData.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PopoverData.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla::dom {
+
+PopoverToggleEventTask::PopoverToggleEventTask(nsWeakPtr aElement,
+ PopoverVisibilityState aOldState)
+ : Runnable("PopoverToggleEventTask"),
+ mElement(std::move(aElement)),
+ mOldState(aOldState) {}
+
+NS_IMETHODIMP
+PopoverToggleEventTask::Run() {
+ nsCOMPtr<Element> element = do_QueryReferent(mElement);
+ if (auto* htmlElement = nsGenericHTMLElement::FromNode(element)) {
+ MOZ_KnownLive(htmlElement)->RunPopoverToggleEventTask(this, mOldState);
+ }
+ return NS_OK;
+};
+
+} // namespace mozilla::dom
diff --git a/dom/base/PopoverData.h b/dom/base/PopoverData.h
new file mode 100644
index 0000000000..e0afe70560
--- /dev/null
+++ b/dom/base/PopoverData.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PopoverData_h
+#define mozilla_dom_PopoverData_h
+
+#include "Element.h"
+#include "nsINode.h"
+#include "nsIRunnable.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+// https://html.spec.whatwg.org/#attr-popover
+enum class PopoverAttributeState : uint8_t {
+ None,
+ Auto, ///< https://html.spec.whatwg.org/#attr-popover-auto-state
+ Manual, ///< https://html.spec.whatwg.org/#attr-popover-manual-state
+};
+
+enum class PopoverVisibilityState : uint8_t {
+ Hidden,
+ Showing,
+};
+
+class PopoverToggleEventTask : public Runnable {
+ public:
+ explicit PopoverToggleEventTask(nsWeakPtr aElement,
+ PopoverVisibilityState aOldState);
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
+
+ PopoverVisibilityState GetOldState() const { return mOldState; }
+
+ private:
+ nsWeakPtr mElement;
+ PopoverVisibilityState mOldState;
+};
+
+class PopoverData {
+ public:
+ PopoverData() = default;
+ ~PopoverData() = default;
+
+ PopoverAttributeState GetPopoverAttributeState() const { return mState; }
+ void SetPopoverAttributeState(PopoverAttributeState aState) {
+ mState = aState;
+ }
+
+ PopoverVisibilityState GetPopoverVisibilityState() const {
+ return mVisibilityState;
+ }
+ void SetPopoverVisibilityState(PopoverVisibilityState aVisibilityState) {
+ mVisibilityState = aVisibilityState;
+ }
+
+ nsWeakPtr GetPreviouslyFocusedElement() const {
+ return mPreviouslyFocusedElement;
+ }
+ void SetPreviouslyFocusedElement(nsWeakPtr aPreviouslyFocusedElement) {
+ mPreviouslyFocusedElement = aPreviouslyFocusedElement;
+ }
+
+ RefPtr<Element> GetInvoker() const {
+ return do_QueryReferent(mInvokerElement);
+ }
+ void SetInvoker(Element* aInvokerElement) {
+ mInvokerElement =
+ do_GetWeakReference(static_cast<nsINode*>(aInvokerElement));
+ }
+
+ PopoverToggleEventTask* GetToggleEventTask() const { return mTask; }
+ void SetToggleEventTask(PopoverToggleEventTask* aTask) { mTask = aTask; }
+ void ClearToggleEventTask() { mTask = nullptr; }
+
+ bool IsHiding() const { return mIsHiding; }
+ void SetIsHiding(bool aIsHiding) { mIsHiding = aIsHiding; }
+
+ private:
+ PopoverVisibilityState mVisibilityState = PopoverVisibilityState::Hidden;
+ PopoverAttributeState mState = PopoverAttributeState::None;
+ // Popover and dialog don't share mPreviouslyFocusedElement for there are
+ // chances to lose the previously focused element.
+ // See, https://github.com/whatwg/html/issues/9063
+ nsWeakPtr mPreviouslyFocusedElement = nullptr;
+
+ // https://html.spec.whatwg.org/#popover-invoker
+ // Since having a popover invoker only makes a difference if the invoker
+ // is in the document (in another open popover to be precise) we can make
+ // this a weak reference, as if the element goes away it's necessarily not
+ // connected to our document.
+ nsWeakPtr mInvokerElement;
+ bool mIsHiding = false;
+ RefPtr<PopoverToggleEventTask> mTask;
+};
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/PopupBlocker.cpp b/dom/base/PopupBlocker.cpp
new file mode 100644
index 0000000000..7b4112d0ea
--- /dev/null
+++ b/dom/base/PopupBlocker.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Components.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "nsXULPopupManager.h"
+#include "nsIPermissionManager.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+static char* sPopupAllowedEvents;
+
+static PopupBlocker::PopupControlState sPopupControlState =
+ PopupBlocker::openAbused;
+static uint32_t sPopupStatePusherCount = 0;
+
+static TimeStamp sLastAllowedExternalProtocolIFrameTimeStamp;
+
+static uint32_t sOpenPopupSpamCount = 0;
+
+void PopupAllowedEventsChanged() {
+ if (sPopupAllowedEvents) {
+ free(sPopupAllowedEvents);
+ }
+
+ nsAutoCString str;
+ Preferences::GetCString("dom.popup_allowed_events", str);
+
+ // We'll want to do this even if str is empty to avoid looking up
+ // this pref all the time if it's not set.
+ sPopupAllowedEvents = ToNewCString(str);
+}
+
+// return true if eventName is contained within events, delimited by
+// spaces
+bool PopupAllowedForEvent(const char* eventName) {
+ if (!sPopupAllowedEvents) {
+ PopupAllowedEventsChanged();
+
+ if (!sPopupAllowedEvents) {
+ return false;
+ }
+ }
+
+ nsDependentCString events(sPopupAllowedEvents);
+
+ nsCString::const_iterator start, end;
+ nsCString::const_iterator startiter(events.BeginReading(start));
+ events.EndReading(end);
+
+ while (startiter != end) {
+ nsCString::const_iterator enditer(end);
+
+ if (!FindInReadable(nsDependentCString(eventName), startiter, enditer))
+ return false;
+
+ // the match is surrounded by spaces, or at a string boundary
+ if ((startiter == start || *--startiter == ' ') &&
+ (enditer == end || *enditer == ' ')) {
+ return true;
+ }
+
+ // Move on and see if there are other matches. (The delimitation
+ // requirement makes it pointless to begin the next search before
+ // the end of the invalid match just found.)
+ startiter = enditer;
+ }
+
+ return false;
+}
+
+// static
+void OnPrefChange(const char* aPrefName, void*) {
+ nsDependentCString prefName(aPrefName);
+ if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
+ PopupAllowedEventsChanged();
+ }
+}
+
+} // namespace
+
+/* static */
+PopupBlocker::PopupControlState PopupBlocker::PushPopupControlState(
+ PopupBlocker::PopupControlState aState, bool aForce) {
+ MOZ_ASSERT(NS_IsMainThread());
+ PopupBlocker::PopupControlState old = sPopupControlState;
+ if (aState < old || aForce) {
+ sPopupControlState = aState;
+ }
+ return old;
+}
+
+/* static */
+void PopupBlocker::PopPopupControlState(
+ PopupBlocker::PopupControlState aState) {
+ MOZ_ASSERT(NS_IsMainThread());
+ sPopupControlState = aState;
+}
+
+/* static */ PopupBlocker::PopupControlState
+PopupBlocker::GetPopupControlState() {
+ return sPopupControlState;
+}
+
+/* static */
+uint32_t PopupBlocker::GetPopupPermission(nsIPrincipal* aPrincipal) {
+ uint32_t permit = nsIPermissionManager::UNKNOWN_ACTION;
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ components::PermissionManager::Service();
+
+ if (permissionManager) {
+ permissionManager->TestPermissionFromPrincipal(aPrincipal, "popup"_ns,
+ &permit);
+ }
+
+ return permit;
+}
+
+/* static */
+void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount; }
+
+/* static */
+void PopupBlocker::PopupStatePusherDestroyed() {
+ MOZ_ASSERT(sPopupStatePusherCount);
+ --sPopupStatePusherCount;
+}
+
+// static
+PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState(
+ WidgetEvent* aEvent, Event* aDOMEvent) {
+ // generally if an event handler is running, new windows are disallowed.
+ // check for exceptions:
+ PopupBlocker::PopupControlState abuse = PopupBlocker::openBlocked;
+
+ if (aDOMEvent && aDOMEvent->GetWantsPopupControlCheck()) {
+ nsAutoString type;
+ aDOMEvent->GetType(type);
+ if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) {
+ return PopupBlocker::openAllowed;
+ }
+ }
+
+ switch (aEvent->mClass) {
+ case eBasicEventClass:
+ // For these following events only allow popups if they're
+ // triggered while handling user input. See
+ // UserActivation::IsUserInteractionEvent() for details.
+ if (UserActivation::IsHandlingUserInput()) {
+ switch (aEvent->mMessage) {
+ case eFormSelect:
+ if (PopupAllowedForEvent("select")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eFormChange:
+ if (PopupAllowedForEvent("change")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eEditorInputEventClass:
+ // For this following event only allow popups if it's triggered
+ // while handling user input. See
+ // UserActivation::IsUserInteractionEvent() for details.
+ if (UserActivation::IsHandlingUserInput()) {
+ switch (aEvent->mMessage) {
+ case eEditorInput:
+ if (PopupAllowedForEvent("input")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eInputEventClass:
+ // For this following event only allow popups if it's triggered
+ // while handling user input. See
+ // UserActivation::IsUserInteractionEvent() for details.
+ if (UserActivation::IsHandlingUserInput()) {
+ switch (aEvent->mMessage) {
+ case eFormChange:
+ if (PopupAllowedForEvent("change")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eXULCommand:
+ abuse = PopupBlocker::openControlled;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eKeyboardEventClass:
+ if (aEvent->IsTrusted()) {
+ uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ // return key on focused button. see note at eMouseClick.
+ if (key == NS_VK_RETURN) {
+ abuse = PopupBlocker::openAllowed;
+ } else if (PopupAllowedForEvent("keypress")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eKeyUp:
+ // space key on focused button. see note at eMouseClick.
+ if (key == NS_VK_SPACE) {
+ abuse = PopupBlocker::openAllowed;
+ } else if (PopupAllowedForEvent("keyup")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eKeyDown:
+ if (PopupAllowedForEvent("keydown")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eTouchEventClass:
+ if (aEvent->IsTrusted()) {
+ switch (aEvent->mMessage) {
+ case eTouchStart:
+ if (PopupAllowedForEvent("touchstart")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eTouchEnd:
+ if (PopupAllowedForEvent("touchend")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eMouseEventClass:
+ if (aEvent->IsTrusted()) {
+ // Let's ignore MouseButton::eSecondary because that is handled as
+ // context menu.
+ if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary ||
+ aEvent->AsMouseEvent()->mButton == MouseButton::eMiddle) {
+ switch (aEvent->mMessage) {
+ case eMouseUp:
+ if (PopupAllowedForEvent("mouseup")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eMouseDown:
+ if (PopupAllowedForEvent("mousedown")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eMouseClick:
+ /* Click events get special treatment because of their
+ historical status as a more legitimate event handler. If
+ click popups are enabled in the prefs, clear the popup
+ status completely. */
+ if (PopupAllowedForEvent("click")) {
+ abuse = PopupBlocker::openAllowed;
+ }
+ break;
+ case eMouseDoubleClick:
+ if (PopupAllowedForEvent("dblclick")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (aEvent->mMessage == eMouseAuxClick) {
+ // Not eLeftButton
+ // There's not a strong reason to ignore other events (eg eMouseUp)
+ // for non-primary clicks as far as we know, so we could add them if
+ // it becomes a compat issue
+ if (PopupAllowedForEvent("auxclick")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ }
+
+ switch (aEvent->mMessage) {
+ case eContextMenu:
+ if (PopupAllowedForEvent("contextmenu")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case ePointerEventClass:
+ if (aEvent->IsTrusted() &&
+ (aEvent->AsPointerEvent()->mButton == MouseButton::ePrimary ||
+ aEvent->AsPointerEvent()->mButton == MouseButton::eMiddle)) {
+ switch (aEvent->mMessage) {
+ case ePointerUp:
+ if (PopupAllowedForEvent("pointerup")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case ePointerDown:
+ if (PopupAllowedForEvent("pointerdown")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case eFormEventClass:
+ // For these following events only allow popups if they're
+ // triggered while handling user input. See
+ // UserActivation::IsUserInteractionEvent() for details.
+ if (UserActivation::IsHandlingUserInput()) {
+ switch (aEvent->mMessage) {
+ case eFormSubmit:
+ if (PopupAllowedForEvent("submit")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case eFormReset:
+ if (PopupAllowedForEvent("reset")) {
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return abuse;
+}
+
+/* static */
+void PopupBlocker::Initialize() {
+ DebugOnly<nsresult> rv =
+ Preferences::RegisterCallback(OnPrefChange, "dom.popup_allowed_events");
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Failed to observe \"dom.popup_allowed_events\"");
+}
+
+/* static */
+void PopupBlocker::Shutdown() {
+ MOZ_ASSERT(sOpenPopupSpamCount == 0);
+
+ if (sPopupAllowedEvents) {
+ free(sPopupAllowedEvents);
+ }
+
+ Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
+}
+
+/* static */
+bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
+ if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
+ return false;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (sLastAllowedExternalProtocolIFrameTimeStamp.IsNull()) {
+ sLastAllowedExternalProtocolIFrameTimeStamp = now;
+ return true;
+ }
+
+ if ((now - sLastAllowedExternalProtocolIFrameTimeStamp).ToSeconds() <
+ StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
+ return false;
+ }
+
+ sLastAllowedExternalProtocolIFrameTimeStamp = now;
+ return true;
+}
+
+/* static */
+TimeStamp PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
+ return sLastAllowedExternalProtocolIFrameTimeStamp;
+}
+
+/* static */
+void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
+ sLastAllowedExternalProtocolIFrameTimeStamp = TimeStamp();
+}
+
+/* static */
+void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount++; }
+
+/* static */
+void PopupBlocker::UnregisterOpenPopupSpam() {
+ MOZ_ASSERT(sOpenPopupSpamCount);
+ sOpenPopupSpamCount--;
+}
+
+/* static */
+uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount; }
+
+} // namespace mozilla::dom
+
+AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
+ mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce)
+ : mOldState(
+ mozilla::dom::PopupBlocker::PushPopupControlState(aState, aForce)) {
+ mozilla::dom::PopupBlocker::PopupStatePusherCreated();
+}
+
+AutoPopupStatePusherInternal::~AutoPopupStatePusherInternal() {
+ mozilla::dom::PopupBlocker::PopPopupControlState(mOldState);
+ mozilla::dom::PopupBlocker::PopupStatePusherDestroyed();
+}
diff --git a/dom/base/PopupBlocker.h b/dom/base/PopupBlocker.h
new file mode 100644
index 0000000000..bc6a5daf97
--- /dev/null
+++ b/dom/base/PopupBlocker.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PopupBlocker_h
+#define mozilla_dom_PopupBlocker_h
+
+#include <stdint.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+
+class AutoPopupStatePusherInternal;
+class nsIPrincipal;
+
+namespace mozilla {
+class WidgetEvent;
+namespace dom {
+class Event;
+
+class PopupBlocker final {
+ public:
+ // Popup control state enum. The values in this enum must go from most
+ // permissive to least permissive so that it's safe to push state in
+ // all situations. Pushing popup state onto the stack never makes the
+ // current popup state less permissive.
+ // Keep this in sync with PopupBlockerState webidl dictionary!
+ enum PopupControlState {
+ openAllowed = 0, // open that window without worries
+ openControlled, // it's a popup, but allow it
+ openBlocked, // it's a popup, but not from an allowed event
+ openAbused, // it's a popup. disallow it, but allow domain override.
+ openOverridden // disallow window open
+ };
+
+ static PopupControlState PushPopupControlState(PopupControlState aState,
+ bool aForce);
+
+ static void PopPopupControlState(PopupControlState aState);
+
+ static PopupControlState GetPopupControlState();
+
+ static void PopupStatePusherCreated();
+ static void PopupStatePusherDestroyed();
+
+ static uint32_t GetPopupPermission(nsIPrincipal* aPrincipal);
+
+ static PopupBlocker::PopupControlState GetEventPopupControlState(
+ WidgetEvent* aEvent, Event* aDOMEvent = nullptr);
+
+ // Returns if a external protocol iframe is allowed.
+ static bool ConsumeTimerTokenForExternalProtocolIframe();
+
+ // Returns when the last external protocol iframe has been allowed.
+ static TimeStamp WhenLastExternalProtocolIframeAllowed();
+
+ // Reset the last external protocol iframe timestamp.
+ static void ResetLastExternalProtocolIframeAllowed();
+
+ // These method track the number of popup which is considered as a spam popup.
+ static void RegisterOpenPopupSpam();
+ static void UnregisterOpenPopupSpam();
+ static uint32_t GetOpenPopupSpamCount();
+
+ static void Initialize();
+ static void Shutdown();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#ifdef MOZILLA_INTERNAL_API
+# define AUTO_POPUP_STATE_PUSHER AutoPopupStatePusherInternal
+#else
+# define AUTO_POPUP_STATE_PUSHER AutoPopupStatePusherExternal
+#endif
+
+// Helper class that helps with pushing and popping popup control
+// state. Note that this class looks different from within code that's
+// part of the layout library than it does in code outside the layout
+// library. We give the two object layouts different names so the symbols
+// don't conflict, but code should always use the name
+// |AutoPopupStatePusher|.
+class MOZ_RAII AUTO_POPUP_STATE_PUSHER final {
+ public:
+#ifdef MOZILLA_INTERNAL_API
+ explicit AUTO_POPUP_STATE_PUSHER(
+ mozilla::dom::PopupBlocker::PopupControlState aState,
+ bool aForce = false);
+ ~AUTO_POPUP_STATE_PUSHER();
+#else
+ AUTO_POPUP_STATE_PUSHER(nsPIDOMWindowOuter* aWindow,
+ mozilla::dom::PopupBlocker::PopupControlState aState)
+ : mWindow(aWindow), mOldState(openAbused) {
+ if (aWindow) {
+ mOldState = PopupBlocker::PushPopupControlState(aState, false);
+ }
+ }
+
+ ~AUTO_POPUP_STATE_PUSHER() {
+ if (mWindow) {
+ PopupBlocker::PopPopupControlState(mOldState);
+ }
+ }
+#endif
+
+ protected:
+#ifndef MOZILLA_INTERNAL_API
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+#endif
+ mozilla::dom::PopupBlocker::PopupControlState mOldState;
+};
+
+#define AutoPopupStatePusher AUTO_POPUP_STATE_PUSHER
+
+#endif // mozilla_PopupBlocker_h
diff --git a/dom/base/Pose.cpp b/dom/base/Pose.cpp
new file mode 100644
index 0000000000..ddccd476ef
--- /dev/null
+++ b/dom/base/Pose.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/experimental/TypedData.h" // JS_GetFloat32ArrayData
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/Pose.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
+ Pose, (mParent),
+ (mPosition, mLinearVelocity, mLinearAcceleration, mOrientation,
+ mAngularVelocity, mAngularAcceleration))
+
+Pose::Pose(nsISupports* aParent)
+ : mParent(aParent),
+ mPosition(nullptr),
+ mLinearVelocity(nullptr),
+ mLinearAcceleration(nullptr),
+ mOrientation(nullptr),
+ mAngularVelocity(nullptr),
+ mAngularAcceleration(nullptr) {
+ mozilla::HoldJSObjects(this);
+}
+
+Pose::~Pose() { mozilla::DropJSObjects(this); }
+
+nsISupports* Pose::GetParentObject() const { return mParent; }
+
+void Pose::SetFloat32Array(JSContext* aJSContext, nsWrapperCache* creator,
+ JS::MutableHandle<JSObject*> aRetVal,
+ JS::Heap<JSObject*>& aObj, float* aVal,
+ uint32_t aValLength, ErrorResult& aRv) {
+ if (!aVal) {
+ aRetVal.set(nullptr);
+ return;
+ }
+
+ if (!aObj) {
+ aObj = Float32Array::Create(aJSContext, creator, aValLength, aVal);
+ if (!aObj) {
+ aRv.NoteJSContextException(aJSContext);
+ return;
+ }
+ } else {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared = false;
+ JS::Rooted<JSObject*> obj(aJSContext, aObj.get());
+ float* data = JS_GetFloat32ArrayData(obj, &isShared, nogc);
+ if (data) {
+ memcpy(data, aVal, aValLength * sizeof(float));
+ }
+ }
+
+ aRetVal.set(aObj);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Pose.h b/dom/base/Pose.h
new file mode 100644
index 0000000000..803574e28c
--- /dev/null
+++ b/dom/base/Pose.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Pose_h
+#define mozilla_dom_Pose_h
+
+#include "js/RootingAPI.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class Pose : public nsWrapperCache {
+ public:
+ explicit Pose(nsISupports* aParent);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Pose)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Pose)
+
+ nsISupports* GetParentObject() const;
+
+ virtual void GetPosition(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ virtual void GetLinearVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ virtual void GetLinearAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ virtual void GetOrientation(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ virtual void GetAngularVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ virtual void GetAngularAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) = 0;
+ static void SetFloat32Array(JSContext* aJSContext, nsWrapperCache* creator,
+ JS::MutableHandle<JSObject*> aRetVal,
+ JS::Heap<JSObject*>& aObj, float* aVal,
+ uint32_t aValLength, ErrorResult& aRv);
+
+ protected:
+ virtual ~Pose();
+
+ nsCOMPtr<nsISupports> mParent;
+
+ JS::Heap<JSObject*> mPosition;
+ JS::Heap<JSObject*> mLinearVelocity;
+ JS::Heap<JSObject*> mLinearAcceleration;
+ JS::Heap<JSObject*> mOrientation;
+ JS::Heap<JSObject*> mAngularVelocity;
+ JS::Heap<JSObject*> mAngularAcceleration;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Pose_h
diff --git a/dom/base/PostMessageEvent.cpp b/dom/base/PostMessageEvent.cpp
new file mode 100644
index 0000000000..58fa434065
--- /dev/null
+++ b/dom/base/PostMessageEvent.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PostMessageEvent.h"
+
+#include "MessageEvent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIConsoleService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsPresContext.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::dom {
+
+PostMessageEvent::PostMessageEvent(BrowsingContext* aSource,
+ const nsAString& aCallerOrigin,
+ nsGlobalWindowOuter* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal,
+ uint64_t aCallerWindowID, nsIURI* aCallerURI,
+ const nsCString& aScriptLocation,
+ bool aIsFromPrivateWindow,
+ const Maybe<nsID>& aCallerAgentClusterId)
+ : Runnable("dom::PostMessageEvent"),
+ mSource(aSource),
+ mCallerOrigin(aCallerOrigin),
+ mTargetWindow(aTargetWindow),
+ mProvidedPrincipal(aProvidedPrincipal),
+ mCallerWindowID(aCallerWindowID),
+ mCallerAgentClusterId(aCallerAgentClusterId),
+ mCallerURI(aCallerURI),
+ mScriptLocation(Some(aScriptLocation)),
+ mIsFromPrivateWindow(aIsFromPrivateWindow) {}
+
+PostMessageEvent::~PostMessageEvent() = default;
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP PostMessageEvent::Run() {
+ // Note: We don't init this AutoJSAPI with targetWindow, because we do not
+ // want exceptions during message deserialization to trigger error events on
+ // targetWindow.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ // The document URI is just used for the principal mismatch error message
+ // below. Use a stack variable so mCallerURI is not held onto after
+ // this method finishes, regardless of the method outcome.
+ nsCOMPtr<nsIURI> callerURI = std::move(mCallerURI);
+
+ // If we bailed before this point we're going to leak mMessage, but
+ // that's probably better than crashing.
+
+ RefPtr<nsGlobalWindowInner> targetWindow;
+ if (mTargetWindow->IsClosedOrClosing() ||
+ !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) ||
+ targetWindow->IsDying())
+ return NS_OK;
+
+ // If the window's document has suppressed event handling, hand off this event
+ // for running later. We check the top window's document so that when multiple
+ // same-origin windows exist in the same top window, postMessage events will
+ // be delivered in the same order they were posted, regardless of which window
+ // they were posted to.
+ if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
+ targetWindow->GetOuterWindow()->GetInProcessTop()) {
+ if (nsCOMPtr<nsPIDOMWindowInner> topInner =
+ topWindow->GetCurrentInnerWindow()) {
+ if (topInner->GetExtantDoc() &&
+ topInner->GetExtantDoc()->SuspendPostMessageEvent(this)) {
+ return NS_OK;
+ }
+ }
+ }
+
+ JSAutoRealm ar(cx, targetWindow->GetWrapper());
+
+ // Ensure that any origin which might have been provided is the origin of this
+ // window's document. Note that we do this *now* instead of when postMessage
+ // is called because the target window might have been navigated to a
+ // different location between then and now. If this check happened when
+ // postMessage was called, it would be fairly easy for a malicious webpage to
+ // intercept messages intended for another site by carefully timing navigation
+ // of the target window so it changed location after postMessage but before
+ // now.
+ if (mProvidedPrincipal) {
+ // Get the target's origin either from its principal or, in the case the
+ // principal doesn't carry a URI (e.g. the system principal), the target's
+ // document.
+ nsIPrincipal* targetPrin = targetWindow->GetPrincipal();
+ if (NS_WARN_IF(!targetPrin)) return NS_OK;
+
+ // Note: This is contrary to the spec with respect to file: URLs, which
+ // the spec groups into a single origin, but given we intentionally
+ // don't do that in other places it seems better to hold the line for
+ // now. Long-term, we want HTML5 to address this so that we can
+ // be compliant while being safer.
+ if (!targetPrin->Equals(mProvidedPrincipal)) {
+ OriginAttributes sourceAttrs = mProvidedPrincipal->OriginAttributesRef();
+ OriginAttributes targetAttrs = targetPrin->OriginAttributesRef();
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ sourceAttrs.mUserContextId == targetAttrs.mUserContextId,
+ "Target and source should have the same userContextId attribute.");
+
+ nsAutoString providedOrigin, targetOrigin;
+ nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString errorText;
+ nsContentUtils::FormatLocalizedString(
+ errorText, nsContentUtils::eDOM_PROPERTIES,
+ "TargetPrincipalDoesNotMatch", providedOrigin, targetOrigin);
+
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCallerWindowID == 0) {
+ rv = errorObject->Init(
+ errorText, NS_ConvertUTF8toUTF16(mScriptLocation.value()), u""_ns,
+ 0, 0, nsIScriptError::errorFlag, "DOM Window"_ns,
+ mIsFromPrivateWindow, mProvidedPrincipal->IsSystemPrincipal());
+ } else if (callerURI) {
+ rv = errorObject->InitWithSourceURI(errorText, callerURI, u""_ns, 0, 0,
+ nsIScriptError::errorFlag,
+ "DOM Window"_ns, mCallerWindowID);
+ } else {
+ rv = errorObject->InitWithWindowID(
+ errorText, NS_ConvertUTF8toUTF16(mScriptLocation.value()), u""_ns,
+ 0, 0, nsIScriptError::errorFlag, "DOM Window"_ns, mCallerWindowID);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return consoleService->LogMessage(errorObject);
+ }
+ }
+
+ IgnoredErrorResult rv;
+ JS::Rooted<JS::Value> messageData(cx);
+ nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
+ do_QueryObject(targetWindow);
+
+ JS::CloneDataPolicy cloneDataPolicy;
+
+ MOZ_DIAGNOSTIC_ASSERT(targetWindow);
+ if (mCallerAgentClusterId.isSome() && targetWindow->GetDocGroup() &&
+ targetWindow->GetDocGroup()->AgentClusterId().Equals(
+ mCallerAgentClusterId.ref())) {
+ cloneDataPolicy.allowIntraClusterClonableSharedObjects();
+ }
+
+ if (targetWindow->IsSharedMemoryAllowed()) {
+ cloneDataPolicy.allowSharedMemoryObjects();
+ }
+
+ if (mHolder.empty()) {
+ DispatchError(cx, targetWindow, eventTarget);
+ return NS_OK;
+ }
+
+ StructuredCloneHolder* holder;
+ if (mHolder.constructed<StructuredCloneHolder>()) {
+ mHolder.ref<StructuredCloneHolder>().Read(
+ targetWindow->AsGlobal(), cx, &messageData, cloneDataPolicy, rv);
+ holder = &mHolder.ref<StructuredCloneHolder>();
+ } else {
+ MOZ_ASSERT(mHolder.constructed<ipc::StructuredCloneData>());
+ mHolder.ref<ipc::StructuredCloneData>().Read(cx, &messageData, rv);
+ holder = &mHolder.ref<ipc::StructuredCloneData>();
+ }
+ if (NS_WARN_IF(rv.Failed())) {
+ JS_ClearPendingException(cx);
+ DispatchError(cx, targetWindow, eventTarget);
+ return NS_OK;
+ }
+
+ // Create the event
+ RefPtr<MessageEvent> event = new MessageEvent(eventTarget, nullptr, nullptr);
+
+ Nullable<WindowProxyOrMessagePortOrServiceWorker> source;
+ if (mSource) {
+ source.SetValue().SetAsWindowProxy() = mSource;
+ }
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!holder->TakeTransferredPortsAsSequence(ports)) {
+ DispatchError(cx, targetWindow, eventTarget);
+ return NS_OK;
+ }
+
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, messageData, mCallerOrigin, u""_ns,
+ source, ports);
+
+ Dispatch(targetWindow, event);
+ return NS_OK;
+}
+
+void PostMessageEvent::DispatchError(JSContext* aCx,
+ nsGlobalWindowInner* aTargetWindow,
+ mozilla::dom::EventTarget* aEventTarget) {
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mOrigin = mCallerOrigin;
+
+ if (mSource) {
+ init.mSource.SetValue().SetAsWindowProxy() = mSource;
+ }
+
+ RefPtr<Event> event =
+ MessageEvent::Constructor(aEventTarget, u"messageerror"_ns, init);
+ Dispatch(aTargetWindow, event);
+}
+
+void PostMessageEvent::Dispatch(nsGlobalWindowInner* aTargetWindow,
+ Event* aEvent) {
+ // We can't simply call dispatchEvent on the window because doing so ends
+ // up flipping the trusted bit on the event, and we don't want that to
+ // happen because then untrusted content can call postMessage on a chrome
+ // window if it can get a reference to it.
+
+ RefPtr<nsPresContext> presContext =
+ aTargetWindow->GetExtantDoc()->GetPresContext();
+
+ aEvent->SetTrusted(true);
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // TODO: Bug 1506441
+ EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(aTargetWindow)),
+ presContext, internalEvent, aEvent, &status);
+}
+
+void PostMessageEvent::DispatchToTargetThread(ErrorResult& aError) {
+ nsCOMPtr<nsIRunnable> event = this;
+
+ if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() &&
+ !DocGroup::TryToLoadIframesInBackground()) {
+ BrowsingContext* bc = mTargetWindow->GetBrowsingContext();
+ bc = bc ? bc->Top() : nullptr;
+ if (bc && bc->IsLoading()) {
+ // As long as the top level is loading, we can dispatch events to the
+ // queue because the queue will be flushed eventually
+ aError = bc->Group()->QueuePostMessageEvent(event.forget());
+ return;
+ }
+ }
+
+ // XXX Loading iframes in background isn't enabled by default and doesn't
+ // work with Fission at the moment.
+ if (DocGroup::TryToLoadIframesInBackground()) {
+ RefPtr<nsIDocShell> docShell = mTargetWindow->GetDocShell();
+ RefPtr<nsDocShell> dShell = nsDocShell::Cast(docShell);
+
+ // PostMessage that are added to the BrowsingContextGroup are the ones that
+ // can be flushed when the top level document is loaded.
+ // TreadAsBackgroundLoad DocShells are treated specially.
+ if (dShell) {
+ if (!dShell->TreatAsBackgroundLoad()) {
+ BrowsingContext* bc = mTargetWindow->GetBrowsingContext();
+ bc = bc ? bc->Top() : nullptr;
+ if (bc && bc->IsLoading()) {
+ // As long as the top level is loading, we can dispatch events to the
+ // queue because the queue will be flushed eventually
+ aError = bc->Group()->QueuePostMessageEvent(event.forget());
+ return;
+ }
+ } else if (mTargetWindow->GetExtantDoc() &&
+ mTargetWindow->GetExtantDoc()->GetReadyStateEnum() <
+ Document::READYSTATE_COMPLETE) {
+ mozilla::dom::DocGroup* docGroup = mTargetWindow->GetDocGroup();
+ aError = docGroup->QueueIframePostMessages(event.forget(),
+ dShell->GetOuterWindowID());
+ return;
+ }
+ }
+ }
+
+ aError = mTargetWindow->Dispatch(TaskCategory::Other, event.forget());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/PostMessageEvent.h b/dom/base/PostMessageEvent.h
new file mode 100644
index 0000000000..48c4c429c7
--- /dev/null
+++ b/dom/base/PostMessageEvent.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PostMessageEvent_h
+#define mozilla_dom_PostMessageEvent_h
+
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+
+class nsGlobalWindowOuter;
+class nsGlobalWindowInner;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class BrowsingContext;
+class Event;
+class EventTarget;
+
+/**
+ * Class used to represent events generated by calls to Window.postMessage,
+ * which asynchronously creates and dispatches events.
+ */
+class PostMessageEvent final : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ // aCallerWindowID should not be 0.
+ PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
+ nsGlobalWindowOuter* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal, uint64_t aCallerWindowID,
+ nsIURI* aCallerURI, const nsCString& aScriptLocation,
+ const Maybe<nsID>& aCallerAgentClusterId)
+ : PostMessageEvent(aSource, aCallerOrigin, aTargetWindow,
+ aProvidedPrincipal, aCallerWindowID, aCallerURI,
+ aScriptLocation, false, aCallerAgentClusterId) {}
+
+ // To be used when the caller's window lives in a different process.
+ PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
+ nsGlobalWindowOuter* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal, uint64_t aCallerWindowID,
+ nsIURI* aCallerURI, const nsCString& aScriptLocation,
+ bool aIsFromPrivateWindow)
+ : PostMessageEvent(aSource, aCallerOrigin, aTargetWindow,
+ aProvidedPrincipal, aCallerWindowID, aCallerURI,
+ aScriptLocation, aIsFromPrivateWindow, Nothing()) {}
+
+ void Write(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ JS::Handle<JS::Value> aTransfer,
+ const JS::CloneDataPolicy& aClonePolicy, ErrorResult& aError) {
+ mHolder.construct<StructuredCloneHolder>(
+ StructuredCloneHolder::CloningSupported,
+ StructuredCloneHolder::TransferringSupported,
+ JS::StructuredCloneScope::SameProcess);
+ mHolder.ref<StructuredCloneHolder>().Write(aCx, aMessage, aTransfer,
+ aClonePolicy, aError);
+ }
+ void UnpackFrom(const ClonedOrErrorMessageData& aMessageData) {
+ if (aMessageData.type() != ClonedOrErrorMessageData::TClonedMessageData) {
+ return;
+ }
+
+ mHolder.construct<ipc::StructuredCloneData>();
+ // FIXME Want to steal!
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1516349.
+ mHolder.ref<ipc::StructuredCloneData>().CopyFromClonedMessageData(
+ aMessageData);
+ }
+
+ void DispatchToTargetThread(ErrorResult& aError);
+
+ private:
+ PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
+ nsGlobalWindowOuter* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal, uint64_t aCallerWindowID,
+ nsIURI* aCallerURI, const nsCString& aScriptLocation,
+ bool aIsFromPrivateWindow,
+ const Maybe<nsID>& aCallerAgentClusterId);
+ ~PostMessageEvent();
+
+ MOZ_CAN_RUN_SCRIPT void Dispatch(nsGlobalWindowInner* aTargetWindow,
+ Event* aEvent);
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DispatchError(
+ JSContext* aCx, nsGlobalWindowInner* aTargetWindow,
+ mozilla::dom::EventTarget* aEventTarget);
+
+ RefPtr<BrowsingContext> mSource;
+ nsString mCallerOrigin;
+ RefPtr<nsGlobalWindowOuter> mTargetWindow;
+ nsCOMPtr<nsIPrincipal> mProvidedPrincipal;
+ // If the postMessage call was made on a WindowProxy whose Window lives in a
+ // separate process then mHolder will contain a StructuredCloneData, else
+ // it'll contain a StructuredCloneHolder.
+ MaybeOneOf<StructuredCloneHolder, ipc::StructuredCloneData> mHolder;
+ uint64_t mCallerWindowID;
+ const Maybe<nsID> mCallerAgentClusterId;
+ nsCOMPtr<nsIURI> mCallerURI;
+ // if callerURI is null, then we can use script location for reporting errors
+ // to console
+ const Maybe<nsCString> mScriptLocation;
+ // This is only set to a relevant value if mCallerWindowID doesn't contain a
+ // value.
+ bool mIsFromPrivateWindow;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PostMessageEvent_h
diff --git a/dom/base/ProcessMessageManager.cpp b/dom/base/ProcessMessageManager.cpp
new file mode 100644
index 0000000000..393a1954d9
--- /dev/null
+++ b/dom/base/ProcessMessageManager.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "mozilla/dom/ProcessMessageManager.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+ProcessMessageManager::ProcessMessageManager(
+ ipc::MessageManagerCallback* aCallback,
+ ParentProcessMessageManager* aParentManager, MessageManagerFlags aFlags)
+ : MessageSender(aCallback, aParentManager,
+ aFlags | MessageManagerFlags::MM_CHROME |
+ MessageManagerFlags::MM_PROCESSMANAGER),
+ mPid(-1),
+ // aCallback is only specified if this is the in-process manager.
+ mInProcess(!!aCallback) {
+ MOZ_ASSERT(!(aFlags & ~(MessageManagerFlags::MM_GLOBAL |
+ MessageManagerFlags::MM_OWNSCALLBACK)));
+
+ // This is a bit hackish. We attach to the parent manager, but only if we have
+ // a callback (which is only for the in-process message manager). For other
+ // cases we wait until the child process is running (see
+ // MessageSender::InitWithCallback).
+ if (aParentManager && mCallback) {
+ aParentManager->AddChildManager(this);
+ }
+}
+
+JSObject* ProcessMessageManager::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(nsContentUtils::IsSystemCaller(aCx));
+
+ return ProcessMessageManager_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ProcessMessageManager.h b/dom/base/ProcessMessageManager.h
new file mode 100644
index 0000000000..ac7fedb4b9
--- /dev/null
+++ b/dom/base/ProcessMessageManager.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ProcessMessageManager_h
+#define mozilla_dom_ProcessMessageManager_h
+
+#include "mozilla/dom/MessageSender.h"
+
+namespace mozilla::dom {
+
+class ParentProcessMessageManager;
+
+/**
+ * ProcessMessageManager is used in a parent process to communicate with a child
+ * process (or with the process itself in a single-process scenario).
+ */
+class ProcessMessageManager final : public MessageSender {
+ public:
+ ProcessMessageManager(
+ ipc::MessageManagerCallback* aCallback,
+ ParentProcessMessageManager* aParentManager,
+ MessageManagerFlags aFlags = MessageManagerFlags::MM_NONE);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // ProcessScriptLoader
+ void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
+ mozilla::ErrorResult& aError) {
+ LoadScript(aUrl, aAllowDelayedLoad, false, aError);
+ }
+ void RemoveDelayedProcessScript(const nsAString& aURL) {
+ RemoveDelayedScript(aURL);
+ }
+ void GetDelayedProcessScripts(JSContext* aCx,
+ nsTArray<nsTArray<JS::Value>>& aScripts,
+ mozilla::ErrorResult& aError) {
+ GetDelayedScripts(aCx, aScripts, aError);
+ }
+
+ void SetOsPid(int32_t aPid) { mPid = aPid; }
+ int32_t OsPid() const { return mPid; }
+
+ bool IsInProcess() const { return mInProcess; }
+
+ private:
+ int32_t mPid;
+ bool mInProcess;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ProcessMessageManager_h
diff --git a/dom/base/ProcessSelector.sys.mjs b/dom/base/ProcessSelector.sys.mjs
new file mode 100644
index 0000000000..59319f7bf6
--- /dev/null
+++ b/dom/base/ProcessSelector.sys.mjs
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Fills up aProcesses until max and then selects randomly from the available
+// ones.
+export function RandomSelector() {}
+
+RandomSelector.prototype = {
+ classID: Components.ID("{c616fcfd-9737-41f1-aa74-cee72a38f91b}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIContentProcessProvider"]),
+
+ provideProcess(aType, aProcesses, aMaxCount) {
+ if (aProcesses.length < aMaxCount) {
+ return Ci.nsIContentProcessProvider.NEW_PROCESS;
+ }
+
+ return Math.floor(Math.random() * aMaxCount);
+ },
+};
+
+// Fills up aProcesses until max and then selects one from the available
+// ones that host the least number of tabs.
+export function MinTabSelector() {}
+
+MinTabSelector.prototype = {
+ classID: Components.ID("{2dc08eaf-6eef-4394-b1df-a3a927c1290b}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIContentProcessProvider"]),
+
+ provideProcess(aType, aProcesses, aMaxCount) {
+ let min = Number.MAX_VALUE;
+ let candidate = Ci.nsIContentProcessProvider.NEW_PROCESS;
+
+ // The reason for not directly using aProcesses.length here is because if
+ // we keep processes alive for testing but want a test to use only single
+ // content process we can just keep relying on dom.ipc.processCount = 1
+ // this way.
+ let numIters = Math.min(aProcesses.length, aMaxCount);
+
+ for (let i = 0; i < numIters; i++) {
+ let process = aProcesses[i];
+ let tabCount = process.tabCount;
+ if (tabCount < min) {
+ min = tabCount;
+ candidate = i;
+ }
+ }
+
+ // If all current processes have at least one tab and we have not yet
+ // reached the maximum, spawn a new process.
+ if (min > 0 && aProcesses.length < aMaxCount) {
+ return Ci.nsIContentProcessProvider.NEW_PROCESS;
+ }
+
+ // Otherwise we use candidate.
+ return candidate;
+ },
+};
diff --git a/dom/base/RadioGroupManager.cpp b/dom/base/RadioGroupManager.cpp
new file mode 100644
index 0000000000..e7c8a730fa
--- /dev/null
+++ b/dom/base/RadioGroupManager.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RadioGroupManager.h"
+#include "nsIRadioVisitor.h"
+#include "mozilla/dom/HTMLInputElement.h"
+
+namespace mozilla::dom {
+
+/**
+ * A struct that holds all the information about a radio group.
+ */
+struct nsRadioGroupStruct {
+ nsRadioGroupStruct()
+ : mRequiredRadioCount(0), mGroupSuffersFromValueMissing(false) {}
+
+ /**
+ * A strong pointer to the currently selected radio button.
+ */
+ RefPtr<HTMLInputElement> mSelectedRadioButton;
+ nsTArray<RefPtr<HTMLInputElement>> mRadioButtons;
+ uint32_t mRequiredRadioCount;
+ bool mGroupSuffersFromValueMissing;
+};
+
+RadioGroupManager::RadioGroupManager() = default;
+
+void RadioGroupManager::Traverse(RadioGroupManager* tmp,
+ nsCycleCollectionTraversalCallback& cb) {
+ for (const auto& entry : tmp->mRadioGroups) {
+ nsRadioGroupStruct* radioGroup = entry.GetWeak();
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "mRadioGroups entry->mSelectedRadioButton");
+ cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
+
+ uint32_t i, count = radioGroup->mRadioButtons.Length();
+ for (i = 0; i < count; ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "mRadioGroups entry->mRadioButtons[i]");
+ cb.NoteXPCOMChild(ToSupports(radioGroup->mRadioButtons[i]));
+ }
+ }
+}
+
+void RadioGroupManager::Unlink(RadioGroupManager* tmp) {
+ tmp->mRadioGroups.Clear();
+}
+
+nsresult RadioGroupManager::WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+
+ for (size_t i = 0; i < radioGroup->mRadioButtons.Length(); i++) {
+ if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) {
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+void RadioGroupManager::SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mSelectedRadioButton = aRadio;
+}
+
+HTMLInputElement* RadioGroupManager::GetCurrentRadioButton(
+ const nsAString& aName) {
+ return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
+}
+
+nsresult RadioGroupManager::GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut) {
+ *aRadioOut = nullptr;
+
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+
+ // Return the radio button relative to the focused radio button.
+ // If no radio is focused, get the radio relative to the selected one.
+ RefPtr<HTMLInputElement> currentRadio;
+ if (aFocusedRadio) {
+ currentRadio = aFocusedRadio;
+ } else {
+ currentRadio = radioGroup->mSelectedRadioButton;
+ if (!currentRadio) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio);
+ if (index < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t numRadios = static_cast<int32_t>(radioGroup->mRadioButtons.Length());
+ RefPtr<HTMLInputElement> radio;
+ do {
+ if (aPrevious) {
+ if (--index < 0) {
+ index = numRadios - 1;
+ }
+ } else if (++index >= numRadios) {
+ index = 0;
+ }
+ radio = radioGroup->mRadioButtons[index];
+ } while (radio->Disabled() && radio != currentRadio);
+
+ radio.forget(aRadioOut);
+ return NS_OK;
+}
+
+void RadioGroupManager::AddToRadioGroup(const nsAString& aName,
+ HTMLInputElement* aRadio) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mRadioButtons.AppendElement(aRadio);
+
+ if (aRadio->IsRequired()) {
+ radioGroup->mRequiredRadioCount++;
+ }
+}
+
+void RadioGroupManager::RemoveFromRadioGroup(const nsAString& aName,
+ HTMLInputElement* aRadio) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mRadioButtons.RemoveElement(aRadio);
+
+ if (aRadio->IsRequired()) {
+ MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0,
+ "mRequiredRadioCount about to wrap below 0!");
+ radioGroup->mRequiredRadioCount--;
+ }
+}
+
+uint32_t RadioGroupManager::GetRequiredRadioCount(
+ const nsAString& aName) const {
+ nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
+ return radioGroup ? radioGroup->mRequiredRadioCount : 0;
+}
+
+void RadioGroupManager::RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+
+ if (aRequiredAdded) {
+ radioGroup->mRequiredRadioCount++;
+ } else {
+ MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0,
+ "mRequiredRadioCount about to wrap below 0!");
+ radioGroup->mRequiredRadioCount--;
+ }
+}
+
+bool RadioGroupManager::GetValueMissingState(const nsAString& aName) const {
+ nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
+ return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
+}
+
+void RadioGroupManager::SetValueMissingState(const nsAString& aName,
+ bool aValue) {
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mGroupSuffersFromValueMissing = aValue;
+}
+
+nsRadioGroupStruct* RadioGroupManager::GetRadioGroup(
+ const nsAString& aName) const {
+ nsRadioGroupStruct* radioGroup = nullptr;
+ mRadioGroups.Get(aName, &radioGroup);
+ return radioGroup;
+}
+
+nsRadioGroupStruct* RadioGroupManager::GetOrCreateRadioGroup(
+ const nsAString& aName) {
+ return mRadioGroups.GetOrInsertNew(aName);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/RadioGroupManager.h b/dom/base/RadioGroupManager.h
new file mode 100644
index 0000000000..f25bf5e753
--- /dev/null
+++ b/dom/base/RadioGroupManager.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_nsRadioGroupStruct_h
+#define mozilla_dom_nsRadioGroupStruct_h
+
+#include "nsCOMArray.h"
+#include "nsIFormControl.h"
+#include "nsIRadioGroupContainer.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+
+namespace html {
+class nsIRadioVisitor;
+}
+
+namespace dom {
+class HTMLInputElement;
+struct nsRadioGroupStruct;
+
+class RadioGroupManager {
+ public:
+ RadioGroupManager();
+
+ static void Traverse(RadioGroupManager* tmp,
+ nsCycleCollectionTraversalCallback& cb);
+ static void Unlink(RadioGroupManager* tmp);
+
+ // nsIRadioGroupContainer
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName, nsIRadioVisitor* aVisitor);
+ void SetCurrentRadioButton(const nsAString& aName, HTMLInputElement* aRadio);
+ HTMLInputElement* GetCurrentRadioButton(const nsAString& aName);
+ nsresult GetNextRadioButton(const nsAString& aName, const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut);
+ void AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio);
+ void RemoveFromRadioGroup(const nsAString& aName, HTMLInputElement* aRadio);
+ uint32_t GetRequiredRadioCount(const nsAString& aName) const;
+ void RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded);
+ bool GetValueMissingState(const nsAString& aName) const;
+ void SetValueMissingState(const nsAString& aName, bool aValue);
+
+ // for radio group
+ nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
+ nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
+
+ private:
+ nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_nsRadioGroupStruct_h
diff --git a/dom/base/RangeBoundary.h b/dom/base/RangeBoundary.h
new file mode 100644
index 0000000000..2e4ab42397
--- /dev/null
+++ b/dom/base/RangeBoundary.h
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RangeBoundary_h
+#define mozilla_RangeBoundary_h
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
+class nsRange;
+
+namespace mozilla {
+
+template <typename T, typename U>
+class EditorDOMPointBase;
+
+// This class will maintain a reference to the child immediately
+// before the boundary's offset. We try to avoid computing the
+// offset as much as possible and just ensure mRef points to the
+// correct child.
+//
+// mParent
+// |
+// [child0] [child1] [child2]
+// / |
+// mRef mOffset=2
+//
+// If mOffset == 0, mRef is null.
+// For text nodes, mRef will always be null and the offset will
+// be kept up-to-date.
+
+template <typename ParentType, typename RefType>
+class RangeBoundaryBase;
+
+typedef RangeBoundaryBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>
+ RangeBoundary;
+typedef RangeBoundaryBase<nsINode*, nsIContent*> RawRangeBoundary;
+
+/**
+ * There are two ways of ensuring that `mRef` points to the correct node.
+ * In most cases, the `RangeBoundary` is used by an object that is a
+ * `MutationObserver` (i.e. `nsRange`) and replaces its `RangeBoundary`
+ * objects when its parent chain changes.
+ * However, there are Ranges which are not `MutationObserver`s (i.e.
+ * `StaticRange`). `mRef` may become invalid when a DOM mutation happens.
+ * Therefore, it needs to be recomputed using `mOffset` before it is being
+ * accessed.
+ * Because recomputing / validating of `mRef` could be an expensive operation,
+ * it should be ensured that `Ref()` is called as few times as possible, i.e.
+ * only once per method of `RangeBoundaryBase`.
+ *
+ * Furthermore, there are special implications when the `RangeBoundary` is not
+ * used by an `MutationObserver`:
+ * After a DOM mutation, the Boundary may point to something that is not valid
+ * anymore, i.e. the `mOffset` is larger than `Container()->Length()`. In this
+ * case, `Ref()` and `Get*ChildAtOffset()` return `nullptr` as an indication
+ * that this RangeBoundary is not valid anymore. Also, `IsSetAndValid()`
+ * returns false. However, `IsSet()` will still return true.
+ *
+ */
+enum class RangeBoundaryIsMutationObserved { No = 0, Yes = 1 };
+
+// This class has two specializations, one using reference counting
+// pointers and one using raw pointers. This helps us avoid unnecessary
+// AddRef/Release calls.
+template <typename ParentType, typename RefType>
+class RangeBoundaryBase {
+ template <typename T, typename U>
+ friend class RangeBoundaryBase;
+ template <typename T, typename U>
+ friend class EditorDOMPointBase;
+
+ friend nsRange;
+
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+ RangeBoundary&, const char*,
+ uint32_t);
+ friend void ImplCycleCollectionUnlink(RangeBoundary&);
+
+ static const uint32_t kFallbackOffset = 0;
+
+ public:
+ RangeBoundaryBase(nsINode* aContainer, nsIContent* aRef)
+ : mParent(aContainer), mRef(aRef), mIsMutationObserved(true) {
+ if (mRef) {
+ NS_WARNING_ASSERTION(mRef->GetParentNode() == mParent,
+ "Initializing RangeBoundary with invalid value");
+ } else {
+ mOffset.emplace(0);
+ }
+ }
+
+ RangeBoundaryBase(nsINode* aContainer, uint32_t aOffset,
+ RangeBoundaryIsMutationObserved aRangeIsMutationObserver =
+ RangeBoundaryIsMutationObserved::Yes)
+ : mParent(aContainer),
+ mRef(nullptr),
+ mOffset(mozilla::Some(aOffset)),
+ mIsMutationObserved(bool(aRangeIsMutationObserver)) {
+ if (mIsMutationObserved && mParent && mParent->IsContainerNode()) {
+ // Find a reference node
+ if (aOffset == mParent->GetChildCount()) {
+ mRef = mParent->GetLastChild();
+ } else if (aOffset > 0) {
+ mRef = mParent->GetChildAt_Deprecated(aOffset - 1);
+ }
+ NS_WARNING_ASSERTION(mRef || aOffset == 0,
+ "Constructing RangeBoundary with invalid value");
+ }
+ NS_WARNING_ASSERTION(!mRef || mRef->GetParentNode() == mParent,
+ "Constructing RangeBoundary with invalid value");
+ }
+
+ RangeBoundaryBase()
+ : mParent(nullptr), mRef(nullptr), mIsMutationObserved(true) {}
+
+ // Needed for initializing RawRangeBoundary from an existing RangeBoundary.
+ template <typename PT, typename RT>
+ RangeBoundaryBase(const RangeBoundaryBase<PT, RT>& aOther,
+ RangeBoundaryIsMutationObserved aIsMutationObserved)
+ : mParent(aOther.mParent),
+ mRef(aOther.mRef),
+ mOffset(aOther.mOffset),
+ mIsMutationObserved(bool(aIsMutationObserved)) {}
+
+ /**
+ * This method may return `nullptr` in two cases:
+ * 1. `mIsMutationObserved` is true and the boundary points to the first
+ * child of `mParent`.
+ * 2. `mIsMutationObserved` is false and `mOffset` is out of bounds for
+ * `mParent`s child list.
+ * If `mIsMutationObserved` is false, this method may do some significant
+ * computation. Therefore it is advised to call it as seldom as possible.
+ * Code inside of this class should call this method exactly one time and
+ * afterwards refer to `mRef` directly.
+ */
+ nsIContent* Ref() const {
+ if (mIsMutationObserved) {
+ return mRef;
+ }
+ MOZ_ASSERT(mParent);
+ MOZ_ASSERT(mOffset);
+
+ // `mRef` may have become invalid due to some DOM mutation,
+ // which is not monitored here. Therefore, we need to validate `mRef`
+ // manually.
+ if (*mOffset > Container()->Length()) {
+ // offset > child count means that the range boundary has become invalid
+ // due to a DOM mutation.
+ mRef = nullptr;
+ } else if (*mOffset == Container()->Length()) {
+ mRef = mParent->GetLastChild();
+ } else if (*mOffset) {
+ // validate and update `mRef`.
+ // If `ComputeIndexOf()` returns `Nothing`, then `mRef` is not a child of
+ // `mParent` anymore.
+ // If the returned index for `mRef` does not match to `mOffset`, `mRef`
+ // needs to be updated.
+ auto indexOfRefObject = mParent->ComputeIndexOf(mRef);
+ if (indexOfRefObject.isNothing() || *mOffset != *indexOfRefObject + 1) {
+ mRef = mParent->GetChildAt_Deprecated(*mOffset - 1);
+ }
+ } else {
+ mRef = nullptr;
+ }
+ return mRef;
+ }
+
+ nsINode* Container() const { return mParent; }
+
+ /**
+ * This method may return `nullptr` if `mIsMutationObserved` is false and
+ * `mOffset` is out of bounds.
+ */
+ nsIContent* GetChildAtOffset() const {
+ if (!mParent || !mParent->IsContainerNode()) {
+ return nullptr;
+ }
+ nsIContent* const ref = Ref();
+ if (!ref) {
+ if (!mIsMutationObserved && *mOffset != 0) {
+ // This means that this boundary is invalid.
+ // `mOffset` is out of bounds.
+ return nullptr;
+ }
+ MOZ_ASSERT(*Offset(OffsetFilter::kValidOrInvalidOffsets) == 0,
+ "invalid RangeBoundary");
+ return mParent->GetFirstChild();
+ }
+ MOZ_ASSERT(mParent->GetChildAt_Deprecated(
+ *Offset(OffsetFilter::kValidOrInvalidOffsets)) ==
+ ref->GetNextSibling());
+ return ref->GetNextSibling();
+ }
+
+ /**
+ * GetNextSiblingOfChildOffset() returns next sibling of a child at offset.
+ * If this refers after the last child or the container cannot have children,
+ * this returns nullptr with warning.
+ */
+ nsIContent* GetNextSiblingOfChildAtOffset() const {
+ if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
+ return nullptr;
+ }
+ nsIContent* const ref = Ref();
+ if (!ref) {
+ if (!mIsMutationObserved && *mOffset != 0) {
+ // This means that this boundary is invalid.
+ // `mOffset` is out of bounds.
+ return nullptr;
+ }
+ MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets) == 0,
+ "invalid RangeBoundary");
+ nsIContent* firstChild = mParent->GetFirstChild();
+ if (NS_WARN_IF(!firstChild)) {
+ // Already referring the end of the container.
+ return nullptr;
+ }
+ return firstChild->GetNextSibling();
+ }
+ if (NS_WARN_IF(!ref->GetNextSibling())) {
+ // Already referring the end of the container.
+ return nullptr;
+ }
+ return ref->GetNextSibling()->GetNextSibling();
+ }
+
+ /**
+ * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child
+ * at offset. If this refers the first child or the container cannot have
+ * children, this returns nullptr with warning.
+ */
+ nsIContent* GetPreviousSiblingOfChildAtOffset() const {
+ if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
+ return nullptr;
+ }
+ nsIContent* const ref = Ref();
+ if (NS_WARN_IF(!ref)) {
+ // Already referring the start of the container.
+ return nullptr;
+ }
+ return ref;
+ }
+
+ enum class OffsetFilter { kValidOffsets, kValidOrInvalidOffsets };
+
+ /**
+ * @return maybe an offset, depending on aOffsetFilter. If it is:
+ * kValidOffsets: if the offset is valid, it, Nothing{} otherwise.
+ * kValidOrInvalidOffsets: the internally stored offset, even if
+ * invalid, or if not available, a defined
+ * default value. That is, always some value.
+ */
+ Maybe<uint32_t> Offset(const OffsetFilter aOffsetFilter) const {
+ switch (aOffsetFilter) {
+ case OffsetFilter::kValidOffsets: {
+ if (IsSetAndValid()) {
+ MOZ_ASSERT_IF(!mIsMutationObserved, mOffset);
+ if (!mOffset && mIsMutationObserved) {
+ DetermineOffsetFromReference();
+ }
+ }
+ return !mIsMutationObserved && *mOffset > Container()->Length()
+ ? Nothing{}
+ : mOffset;
+ }
+ case OffsetFilter::kValidOrInvalidOffsets: {
+ MOZ_ASSERT_IF(!mIsMutationObserved, mOffset.isSome());
+ if (mOffset.isSome()) {
+ return mOffset;
+ }
+ if (mParent && mIsMutationObserved) {
+ DetermineOffsetFromReference();
+ if (mOffset.isSome()) {
+ return mOffset;
+ }
+ }
+
+ return Some(kFallbackOffset);
+ }
+ }
+
+ // Needed to calm the compiler. There was deliberately no default case added
+ // to the above switch-statement, because it would prevent build-errors when
+ // not all enumerators are handled.
+ MOZ_ASSERT_UNREACHABLE();
+ return Some(kFallbackOffset);
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& aStream,
+ const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary) {
+ aStream << "{ mParent=" << aRangeBoundary.Container();
+ if (aRangeBoundary.Container()) {
+ aStream << " (" << *aRangeBoundary.Container()
+ << ", Length()=" << aRangeBoundary.Container()->Length() << ")";
+ }
+ if (aRangeBoundary.mIsMutationObserved) {
+ aStream << ", mRef=" << aRangeBoundary.mRef;
+ if (aRangeBoundary.mRef) {
+ aStream << " (" << *aRangeBoundary.mRef << ")";
+ }
+ }
+
+ aStream << ", mOffset=" << aRangeBoundary.mOffset;
+ aStream << ", mIsMutationObserved="
+ << (aRangeBoundary.mIsMutationObserved ? "true" : "false") << " }";
+ return aStream;
+ }
+
+ private:
+ void DetermineOffsetFromReference() const {
+ MOZ_ASSERT(mParent);
+ MOZ_ASSERT(mRef);
+ MOZ_ASSERT(mRef->GetParentNode() == mParent);
+ MOZ_ASSERT(mIsMutationObserved);
+ MOZ_ASSERT(mOffset.isNothing());
+
+ if (mRef->IsBeingRemoved()) {
+ // ComputeIndexOf would return nothing because mRef has already been
+ // removed from the child node chain of mParent.
+ return;
+ }
+
+ const Maybe<uint32_t> index = mParent->ComputeIndexOf(mRef);
+ MOZ_ASSERT(*index != UINT32_MAX);
+ mOffset.emplace(MOZ_LIKELY(index.isSome()) ? *index + 1u : 0u);
+ }
+
+ void InvalidateOffset() {
+ MOZ_ASSERT(mParent);
+ MOZ_ASSERT(mParent->IsContainerNode(),
+ "Range is positioned on a text node!");
+ if (!mIsMutationObserved) {
+ // RangeBoundaries that are not used in the context of a
+ // `MutationObserver` use the offset as main source of truth to compute
+ // `mRef`. Therefore, it must not be updated or invalidated.
+ return;
+ }
+ if (!mRef) {
+ MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
+ "Invalidating offset of invalid RangeBoundary?");
+ return;
+ }
+ mOffset.reset();
+ }
+
+ public:
+ bool IsSet() const { return mParent && (mRef || mOffset.isSome()); }
+
+ bool IsSetAndValid() const {
+ if (!IsSet()) {
+ return false;
+ }
+
+ if (mIsMutationObserved && Ref()) {
+ // XXX mRef refers previous sibling of pointing child. Therefore, it
+ // seems odd that this becomes invalid due to its removal. Should we
+ // change RangeBoundaryBase to refer child at offset directly?
+ return Ref()->GetParentNode() == Container() && !Ref()->IsBeingRemoved();
+ }
+
+ MOZ_ASSERT(mOffset.isSome());
+ return *mOffset <= Container()->Length();
+ }
+
+ bool IsStartOfContainer() const {
+ // We're at the first point in the container if we don't have a reference,
+ // and our offset is 0. If we don't have a Ref, we should already have an
+ // offset, so we can just directly fetch it.
+ return mIsMutationObserved ? !Ref() && mOffset.value() == 0
+ : mOffset.value() == 0;
+ }
+
+ bool IsEndOfContainer() const {
+ // We're at the last point in the container if Ref is a pointer to the last
+ // child in Container(), or our Offset() is the same as the length of our
+ // container. If we don't have a Ref, then we should already have an offset,
+ // so we can just directly fetch it.
+ return mIsMutationObserved && Ref()
+ ? !Ref()->GetNextSibling()
+ : mOffset.value() == Container()->Length();
+ }
+
+ // Convenience methods for switching between the two types
+ // of RangeBoundary.
+ RangeBoundaryBase<nsINode*, nsIContent*> AsRaw() const {
+ return RangeBoundaryBase<nsINode*, nsIContent*>(
+ *this, RangeBoundaryIsMutationObserved(mIsMutationObserved));
+ }
+
+ template <typename A, typename B>
+ RangeBoundaryBase& operator=(const RangeBoundaryBase<A, B>& aOther) = delete;
+
+ template <typename A, typename B>
+ RangeBoundaryBase& CopyFrom(
+ const RangeBoundaryBase<A, B>& aOther,
+ RangeBoundaryIsMutationObserved aIsMutationObserved) {
+ // mParent and mRef can be strong pointers, so better to try to avoid any
+ // extra AddRef/Release calls.
+ if (mParent != aOther.mParent) {
+ mParent = aOther.mParent;
+ }
+ if (mRef != aOther.mRef) {
+ mRef = aOther.mRef;
+ }
+ mOffset = aOther.mOffset;
+ mIsMutationObserved = bool(aIsMutationObserved);
+ return *this;
+ }
+
+ bool Equals(const nsINode* aNode, uint32_t aOffset) const {
+ if (mParent != aNode) {
+ return false;
+ }
+
+ const Maybe<uint32_t> offset = Offset(OffsetFilter::kValidOffsets);
+ return offset && (*offset == aOffset);
+ }
+
+ template <typename A, typename B>
+ bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
+ return mParent == aOther.mParent &&
+ (mIsMutationObserved && aOther.mIsMutationObserved && mRef
+ ? mRef == aOther.mRef
+ : Offset(OffsetFilter::kValidOrInvalidOffsets) ==
+ aOther.Offset(
+ RangeBoundaryBase<
+ A, B>::OffsetFilter::kValidOrInvalidOffsets));
+ }
+
+ template <typename A, typename B>
+ bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ ParentType mParent;
+ mutable RefType mRef;
+
+ mutable mozilla::Maybe<uint32_t> mOffset;
+ bool mIsMutationObserved;
+};
+
+template <typename ParentType, typename RefType>
+const uint32_t RangeBoundaryBase<ParentType, RefType>::kFallbackOffset;
+
+inline void ImplCycleCollectionUnlink(RangeBoundary& aField) {
+ ImplCycleCollectionUnlink(aField.mParent);
+ ImplCycleCollectionUnlink(aField.mRef);
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, RangeBoundary& aField,
+ const char* aName, uint32_t aFlags) {
+ ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
+ ImplCycleCollectionTraverse(aCallback, aField.mRef, "mRef", 0);
+}
+
+} // namespace mozilla
+
+#endif // defined(mozilla_RangeBoundary_h)
diff --git a/dom/base/RangeUtils.cpp b/dom/base/RangeUtils.cpp
new file mode 100644
index 0000000000..28283054b8
--- /dev/null
+++ b/dom/base/RangeUtils.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RangeUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/AbstractRange.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary);
+template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary);
+template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+
+template nsresult RangeUtils::CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+
+template nsresult RangeUtils::CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+
+template nsresult RangeUtils::CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RawRangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+
+template nsresult RangeUtils::CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+
+// static
+nsINode* RangeUtils::ComputeRootNode(nsINode* aNode) {
+ if (!aNode) {
+ return nullptr;
+ }
+
+ if (aNode->IsContent()) {
+ if (aNode->NodeInfo()->NameAtom() == nsGkAtoms::documentTypeNodeName) {
+ return nullptr;
+ }
+
+ nsIContent* content = aNode->AsContent();
+
+ // If the node is in a shadow tree then the ShadowRoot is the root.
+ //
+ // FIXME(emilio): Should this be after the NAC check below? We can have NAC
+ // inside Shadow DOM which will peek this path rather than the one below.
+ if (ShadowRoot* containingShadow = content->GetContainingShadow()) {
+ return containingShadow;
+ }
+
+ // If the node is in NAC, then the NAC parent should be the root.
+ if (nsINode* root =
+ content->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
+ return root;
+ }
+ }
+
+ // Elements etc. must be in document or in document fragment,
+ // text nodes in document, in document fragment or in attribute.
+ if (nsINode* root = aNode->GetUncomposedDoc()) {
+ return root;
+ }
+
+ NS_ASSERTION(!aNode->SubtreeRoot()->IsDocument(),
+ "GetUncomposedDoc should have returned a doc");
+
+ // We allow this because of backward compatibility.
+ return aNode->SubtreeRoot();
+}
+
+// static
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+bool RangeUtils::IsValidPoints(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ // Use NS_WARN_IF() only for the cases where the arguments are unexpected.
+ if (NS_WARN_IF(!aStartBoundary.IsSetAndValid()) ||
+ NS_WARN_IF(!aEndBoundary.IsSetAndValid())) {
+ return false;
+ }
+
+ // Otherwise, don't use NS_WARN_IF() for preventing to make console messy.
+ // Instead, check one by one since it is easier to catch the error reason
+ // with debugger.
+
+ if (ComputeRootNode(aStartBoundary.Container()) !=
+ ComputeRootNode(aEndBoundary.Container())) {
+ return false;
+ }
+
+ const Maybe<int32_t> order =
+ nsContentUtils::ComparePoints(aStartBoundary, aEndBoundary);
+ if (!order) {
+ MOZ_ASSERT_UNREACHABLE();
+ return false;
+ }
+
+ return *order != 1;
+}
+
+// static
+Maybe<bool> RangeUtils::IsNodeContainedInRange(nsINode& aNode,
+ AbstractRange* aAbstractRange) {
+ bool nodeIsBeforeRange{false};
+ bool nodeIsAfterRange{false};
+
+ const nsresult rv = CompareNodeToRange(&aNode, aAbstractRange,
+ &nodeIsBeforeRange, &nodeIsAfterRange);
+ if (NS_FAILED(rv)) {
+ return Nothing();
+ }
+
+ return Some(!nodeIsBeforeRange && !nodeIsAfterRange);
+}
+
+// Utility routine to detect if a content node is completely contained in a
+// range If outNodeBefore is returned true, then the node starts before the
+// range does. If outNodeAfter is returned true, then the node ends after the
+// range does. Note that both of the above might be true. If neither are true,
+// the node is contained inside of the range.
+// XXX - callers responsibility to ensure node in same doc as range!
+
+// static
+nsresult RangeUtils::CompareNodeToRange(nsINode* aNode,
+ AbstractRange* aAbstractRange,
+ bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange) {
+ if (NS_WARN_IF(!aAbstractRange) ||
+ NS_WARN_IF(!aAbstractRange->IsPositioned())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return CompareNodeToRangeBoundaries(aNode, aAbstractRange->StartRef(),
+ aAbstractRange->EndRef(),
+ aNodeIsBeforeRange, aNodeIsAfterRange);
+}
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+nsresult RangeUtils::CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange) {
+ MOZ_ASSERT(aNodeIsBeforeRange);
+ MOZ_ASSERT(aNodeIsAfterRange);
+
+ if (NS_WARN_IF(!aNode) ||
+ NS_WARN_IF(!aStartBoundary.IsSet() || !aEndBoundary.IsSet())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // create a pair of dom points that expresses location of node:
+ // NODE(start), NODE(end)
+ // Let incoming range be:
+ // {RANGE(start), RANGE(end)}
+ // if (RANGE(start) <= NODE(start)) and (RANGE(end) => NODE(end))
+ // then the Node is contained (completely) by the Range.
+
+ // gather up the dom point info
+ int32_t nodeStart;
+ uint32_t nodeEnd;
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent) {
+ // can't make a parent/offset pair to represent start or
+ // end of the root node, because it has no parent.
+ // so instead represent it by (node,0) and (node,numChildren)
+ parent = aNode;
+ nodeStart = 0;
+ nodeEnd = aNode->GetChildCount();
+ } else {
+ nodeStart = parent->ComputeIndexOf_Deprecated(aNode);
+ NS_WARNING_ASSERTION(
+ nodeStart >= 0,
+ "aNode has the parent node but it does not have aNode!");
+ nodeEnd = nodeStart + 1u;
+ MOZ_ASSERT(nodeStart < 0 || static_cast<uint32_t>(nodeStart) < nodeEnd,
+ "nodeStart should be less than nodeEnd");
+ }
+
+ // XXX nsContentUtils::ComparePoints() may be expensive. If some callers
+ // just want one of aNodeIsBeforeRange or aNodeIsAfterRange, we can
+ // skip the other comparison.
+
+ // In the ComparePoints calls below we use a container & offset instead of
+ // a range boundary because the range boundary constructor warns if you pass
+ // in a -1 offset and the ComputeIndexOf call above can return -1 if aNode
+ // is native anonymous content. ComparePoints has comments about offsets
+ // being -1 and it seems to deal with it, or at least we aren't aware of any
+ // problems arising because of it. We don't have a better idea how to get
+ // rid of the warning without much larger changes so we do this just to
+ // silence the warning. (Bug 1438996)
+
+ // is RANGE(start) <= NODE(start) ?
+ Maybe<int32_t> order = nsContentUtils::ComparePoints_AllowNegativeOffsets(
+ aStartBoundary.Container(),
+ *aStartBoundary.Offset(
+ RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOrInvalidOffsets),
+ parent, nodeStart);
+ if (NS_WARN_IF(!order)) {
+ return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
+ }
+ *aNodeIsBeforeRange = *order > 0;
+ // is RANGE(end) >= NODE(end) ?
+ order = nsContentUtils::ComparePoints(
+ aEndBoundary.Container(),
+ *aEndBoundary.Offset(
+ RangeBoundaryBase<EPT, ERT>::OffsetFilter::kValidOrInvalidOffsets),
+ parent, nodeEnd);
+
+ if (NS_WARN_IF(!order)) {
+ return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
+ }
+ *aNodeIsAfterRange = *order < 0;
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/base/RangeUtils.h b/dom/base/RangeUtils.h
new file mode 100644
index 0000000000..511f5e5092
--- /dev/null
+++ b/dom/base/RangeUtils.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RangeUtils_h
+#define mozilla_RangeUtils_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/RangeBoundary.h"
+#include "nsIContent.h"
+#include "nsINode.h"
+
+namespace mozilla {
+
+namespace dom {
+class AbstractRange;
+} // namespace dom
+
+class RangeUtils final {
+ typedef dom::AbstractRange AbstractRange;
+
+ public:
+ /**
+ * GetRawRangeBoundaryBefore() and GetRawRangeBoundaryAfter() retrieve
+ * RawRangeBoundary which points before or after aNode.
+ */
+ static const RawRangeBoundary GetRawRangeBoundaryAfter(nsINode* aNode) {
+ MOZ_ASSERT(aNode);
+
+ if (NS_WARN_IF(!aNode->IsContent())) {
+ return RawRangeBoundary();
+ }
+
+ nsINode* parentNode = aNode->GetParentNode();
+ if (!parentNode) {
+ return RawRangeBoundary();
+ }
+ RawRangeBoundary afterNode(parentNode, aNode->AsContent());
+ // If aNode isn't in the child nodes of its parent node, we hit this case.
+ // This may occur when we're called by a mutation observer while aNode is
+ // removed from the parent node.
+ if (NS_WARN_IF(
+ !afterNode.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets))) {
+ return RawRangeBoundary();
+ }
+ return afterNode;
+ }
+
+ static const RawRangeBoundary GetRawRangeBoundaryBefore(nsINode* aNode) {
+ MOZ_ASSERT(aNode);
+
+ if (NS_WARN_IF(!aNode->IsContent())) {
+ return RawRangeBoundary();
+ }
+
+ nsINode* parentNode = aNode->GetParentNode();
+ if (!parentNode) {
+ return RawRangeBoundary();
+ }
+ // If aNode isn't in the child nodes of its parent node, we hit this case.
+ // This may occur when we're called by a mutation observer while aNode is
+ // removed from the parent node.
+ const Maybe<uint32_t> indexInParent = parentNode->ComputeIndexOf(aNode);
+ if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
+ return RawRangeBoundary();
+ }
+ return RawRangeBoundary(parentNode, *indexInParent);
+ }
+
+ /**
+ * Compute the root node of aNode for initializing range classes.
+ * When aNode is in an anonymous subtree, this returns the shadow root or
+ * binding parent. Otherwise, the root node of the document or document
+ * fragment. If this returns nullptr, that means aNode can be neither the
+ * start container nor end container of any range.
+ */
+ static nsINode* ComputeRootNode(nsINode* aNode);
+
+ /**
+ * XXX nsRange should accept 0 - UINT32_MAX as offset. However, users of
+ * nsRange treat offset as int32_t. Additionally, some other internal
+ * APIs like nsINode::ComputeIndexOf_Deprecated() use int32_t. Therefore,
+ * nsRange should accept only 0 - INT32_MAX as valid offset for now.
+ */
+ static bool IsValidOffset(uint32_t aOffset) { return aOffset <= INT32_MAX; }
+
+ /**
+ * Return true if aStartContainer/aStartOffset and aEndContainer/aEndOffset
+ * are valid start and end points for a range. Otherwise, return false.
+ */
+ static bool IsValidPoints(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) {
+ return IsValidPoints(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static bool IsValidPoints(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary);
+
+ /**
+ * The caller needs to ensure aNode is in the same doc like aAbstractRange.
+ */
+ static Maybe<bool> IsNodeContainedInRange(nsINode& aNode,
+ AbstractRange* aAbstractRange);
+
+ /**
+ * Utility routine to detect if a content node starts before a range and/or
+ * ends after a range. If neither it is contained inside the range.
+ * Note that callers responsibility to ensure node in same doc as range.
+ */
+ static nsresult CompareNodeToRange(nsINode* aNode,
+ AbstractRange* aAbstractRange,
+ bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static nsresult CompareNodeToRangeBoundaries(
+ nsINode* aNode, const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, bool* aNodeIsBeforeRange,
+ bool* aNodeIsAfterRange);
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_RangeUtils_h
diff --git a/dom/base/RemoteOuterWindowProxy.cpp b/dom/base/RemoteOuterWindowProxy.cpp
new file mode 100644
index 0000000000..aa561c7e6d
--- /dev/null
+++ b/dom/base/RemoteOuterWindowProxy.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccessCheck.h"
+#include "js/Proxy.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/RemoteObjectProxy.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "xpcprivate.h"
+
+namespace mozilla::dom {
+
+/**
+ * RemoteOuterWindowProxy is the proxy handler for the WindowProxy objects for
+ * Window objects that live in a different process.
+ *
+ * RemoteOuterWindowProxy holds a BrowsingContext, which is cycle collected.
+ * This reference is declared to the cycle collector via NoteChildren().
+ */
+
+class RemoteOuterWindowProxy
+ : public RemoteObjectProxy<BrowsingContext,
+ Window_Binding::sCrossOriginProperties> {
+ public:
+ using Base = RemoteObjectProxy;
+
+ constexpr RemoteOuterWindowProxy()
+ : RemoteObjectProxy(prototypes::id::Window) {}
+
+ // Standard internal methods
+ bool getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const final;
+ bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const final;
+
+ // SpiderMonkey extensions
+ bool getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const final;
+
+ void NoteChildren(JSObject* aProxy,
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ CycleCollectionNoteChild(aCb,
+ static_cast<BrowsingContext*>(GetNative(aProxy)),
+ "JS::GetPrivate(obj)");
+ }
+};
+
+static const RemoteOuterWindowProxy sSingleton;
+
+// Give RemoteOuterWindowProxy 2 reserved slots, like the other wrappers,
+// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+template <>
+const JSClass RemoteOuterWindowProxy::Base::sClass =
+ PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
+
+bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aRetVal) {
+ MOZ_ASSERT(!aContext->GetDocShell(),
+ "Why are we creating a RemoteOuterWindowProxy?");
+
+ sSingleton.GetProxyObject(aCx, aContext, aTransplantTo, aRetVal);
+ return !!aRetVal;
+}
+
+BrowsingContext* GetBrowsingContext(JSObject* aProxy) {
+ MOZ_ASSERT(IsRemoteObjectProxy(aProxy, prototypes::id::Window));
+ return static_cast<BrowsingContext*>(
+ RemoteObjectProxyBase::GetNative(aProxy));
+}
+
+static bool WrapResult(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ BrowsingContext* aResult, JS::PropertyAttributes attrs,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) {
+ JS::Rooted<JS::Value> v(aCx);
+ if (!ToJSValue(aCx, WindowProxyHolder(aResult), &v)) {
+ return false;
+ }
+
+ aDesc.set(Some(JS::PropertyDescriptor::Data(v, attrs)));
+ return true;
+}
+
+bool RemoteOuterWindowProxy::getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
+ BrowsingContext* bc = GetBrowsingContext(aProxy);
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ Span<RefPtr<BrowsingContext>> children = bc->Children();
+ if (index < children.Length()) {
+ return WrapResult(aCx, aProxy, children[index],
+ {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Enumerable},
+ aDesc);
+ }
+ return ReportCrossOriginDenial(aCx, aId, "access"_ns);
+ }
+
+ bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
+ if (!ok || aDesc.isSome()) {
+ return ok;
+ }
+
+ // We don't need the "print" hack that nsOuterWindowProxy has, because pdf
+ // documents are placed in a process based on their principal before the PDF
+ // viewer changes principals around, so are always same-process with things
+ // that are same-origin with their original principal and won't reach this
+ // code in the cases when "print" should be accessible.
+
+ if (aId.isString()) {
+ nsAutoJSString str;
+ if (!str.init(aCx, aId.toString())) {
+ return false;
+ }
+
+ for (BrowsingContext* child : bc->Children()) {
+ if (child->NameEquals(str)) {
+ return WrapResult(aCx, aProxy, child,
+ {JS::PropertyAttribute::Configurable}, aDesc);
+ }
+ }
+ }
+
+ return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
+}
+
+bool AppendIndexedPropertyNames(JSContext* aCx, BrowsingContext* aContext,
+ JS::MutableHandleVector<jsid> aIndexedProps) {
+ int32_t length = aContext->Children().Length();
+ if (!aIndexedProps.reserve(aIndexedProps.length() + length)) {
+ return false;
+ }
+
+ for (int32_t i = 0; i < length; ++i) {
+ aIndexedProps.infallibleAppend(JS::PropertyKey::Int(i));
+ }
+ return true;
+}
+
+bool RemoteOuterWindowProxy::ownPropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const {
+ BrowsingContext* bc = GetBrowsingContext(aProxy);
+
+ // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys:crossoriginownpropertykeys-(-o-)
+ // step 3 to 5
+ if (!AppendIndexedPropertyNames(aCx, bc, aProps)) {
+ return false;
+ }
+
+ // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys:crossoriginownpropertykeys-(-o-)
+ // step 7
+ return RemoteObjectProxy::ownPropertyKeys(aCx, aProxy, aProps);
+}
+
+bool RemoteOuterWindowProxy::getOwnEnumerablePropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const {
+ return AppendIndexedPropertyNames(aCx, GetBrowsingContext(aProxy), aProps);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp
new file mode 100644
index 0000000000..cc9865ee2e
--- /dev/null
+++ b/dom/base/ResizeObserver.cpp
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ResizeObserver.h"
+
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/SVGUtils.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include <limits>
+
+namespace mozilla::dom {
+
+/**
+ * Returns the length of the parent-traversal path (in terms of the number of
+ * nodes) to an unparented/root node from aNode. An unparented/root node is
+ * considered to have a depth of 1, its children have a depth of 2, etc.
+ * aNode is expected to be non-null.
+ * Note: The shadow root is not part of the calculation because the caller,
+ * ResizeObserver, doesn't observe the shadow root, and only needs relative
+ * depths among all the observed targets. In other words, we calculate the
+ * depth of the flattened tree.
+ *
+ * However, these is a spec issue about how to handle shadow DOM case. We
+ * may need to update this function later:
+ * https://github.com/w3c/csswg-drafts/issues/3840
+ *
+ * https://drafts.csswg.org/resize-observer/#calculate-depth-for-node-h
+ */
+static uint32_t GetNodeDepth(nsINode* aNode) {
+ uint32_t depth = 1;
+
+ MOZ_ASSERT(aNode, "Node shouldn't be null");
+
+ // Use GetFlattenedTreeParentNode to bypass the shadow root and cross the
+ // shadow boundary to calculate the node depth without the shadow root.
+ while ((aNode = aNode->GetFlattenedTreeParentNode())) {
+ ++depth;
+ }
+
+ return depth;
+}
+
+static nsSize GetContentRectSize(const nsIFrame& aFrame) {
+ if (const nsIScrollableFrame* f = do_QueryFrame(&aFrame)) {
+ // We return the scrollport rect for compat with other UAs, see bug 1733042.
+ // But the scrollPort includes padding (but not border!), so remove it.
+ nsRect scrollPort = f->GetScrollPortRect();
+ nsMargin padding =
+ aFrame.GetUsedPadding().ApplySkipSides(aFrame.GetSkipSides());
+ scrollPort.Deflate(padding);
+ // This can break in some edge cases like when layout overflows sizes or
+ // what not.
+ NS_ASSERTION(
+ !aFrame.PresContext()->UseOverlayScrollbars() ||
+ scrollPort.Size() == aFrame.GetContentRectRelativeToSelf().Size(),
+ "Wrong scrollport?");
+ return scrollPort.Size();
+ }
+ return aFrame.GetContentRectRelativeToSelf().Size();
+}
+
+/**
+ * Returns |aTarget|'s size in the form of gfx::Size (in pixels).
+ * If the target is an SVG that does not participate in CSS layout,
+ * its width and height are determined from bounding box.
+ *
+ * https://www.w3.org/TR/resize-observer-1/#calculate-box-size
+ */
+static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
+ Element* aTarget, ResizeObserverBoxOptions aBox,
+ const ResizeObserver& aObserver) {
+ nsIFrame* frame = aTarget->GetPrimaryFrame();
+
+ if (!frame) {
+ // TODO: Should this return an empty array instead?
+ // https://github.com/w3c/csswg-drafts/issues/7734
+ return {LogicalPixelSize()};
+ }
+
+ if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Per the spec, this target's SVG size is always its bounding box size no
+ // matter what box option you choose, because SVG elements do not use
+ // standard CSS box model.
+ // TODO: what if the SVG is fragmented?
+ // https://github.com/w3c/csswg-drafts/issues/7736
+ const gfxRect bbox = SVGUtils::GetBBox(frame);
+ gfx::Size size(static_cast<float>(bbox.width),
+ static_cast<float>(bbox.height));
+ const WritingMode wm = frame->GetWritingMode();
+ if (aBox == ResizeObserverBoxOptions::Device_pixel_content_box) {
+ // Per spec, we calculate the inline/block sizes to target’s bounding box
+ // {inline|block} length, in integral device pixels, so we round the final
+ // result.
+ // https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box
+ const LayoutDeviceIntSize snappedSize =
+ RoundedToInt(CSSSize::FromUnknownSize(size) *
+ frame->PresContext()->CSSToDevPixelScale());
+ return {LogicalPixelSize(wm, gfx::Size(snappedSize.ToUnknownSize()))};
+ }
+ return {LogicalPixelSize(wm, size)};
+ }
+
+ // Per the spec, non-replaced inline Elements will always have an empty
+ // content rect. Therefore, we always use the same trivially-empty size
+ // for non-replaced inline elements here, and their IsActive() will
+ // always return false. (So its observation won't be fired.)
+ // TODO: Should we use an empty array instead?
+ // https://github.com/w3c/csswg-drafts/issues/7734
+ if (!frame->IsFrameOfType(nsIFrame::eReplaced) &&
+ frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ return {LogicalPixelSize()};
+ }
+
+ auto GetFrameSize = [aBox](nsIFrame* aFrame) {
+ switch (aBox) {
+ case ResizeObserverBoxOptions::Border_box:
+ return CSSPixel::FromAppUnits(aFrame->GetSize()).ToUnknownSize();
+ case ResizeObserverBoxOptions::Device_pixel_content_box: {
+ // Simply converting from app units to device units is insufficient - we
+ // need to take subpixel snapping into account. Subpixel snapping
+ // happens with respect to the reference frame, so do the dev pixel
+ // conversion with our rectangle positioned relative to the reference
+ // frame, then get the size from there.
+ const auto* referenceFrame = nsLayoutUtils::GetReferenceFrame(aFrame);
+ // GetOffsetToCrossDoc version handles <iframe>s in addition to normal
+ // cases. We don't expect this to tight loop for additional checks to
+ // matter.
+ const auto offset = aFrame->GetOffsetToCrossDoc(referenceFrame);
+ const auto contentSize = GetContentRectSize(*aFrame);
+ // Casting to double here is deliberate to minimize rounding error in
+ // upcoming operations.
+ const auto appUnitsPerDevPixel =
+ static_cast<double>(aFrame->PresContext()->AppUnitsPerDevPixel());
+ // Calculation here is a greatly simplified version of
+ // `NSRectToSnappedRect` as 1) we're not actually drawing (i.e. no draw
+ // target), and 2) transform does not need to be taken into account.
+ gfx::Rect rect{gfx::Float(offset.X() / appUnitsPerDevPixel),
+ gfx::Float(offset.Y() / appUnitsPerDevPixel),
+ gfx::Float(contentSize.Width() / appUnitsPerDevPixel),
+ gfx::Float(contentSize.Height() / appUnitsPerDevPixel)};
+ gfx::Point tl = rect.TopLeft().Round();
+ gfx::Point br = rect.BottomRight().Round();
+
+ rect.SizeTo(gfx::Size(br.x - tl.x, br.y - tl.y));
+ rect.NudgeToIntegers();
+ return rect.Size().ToUnknownSize();
+ }
+ case ResizeObserverBoxOptions::Content_box:
+ default:
+ break;
+ }
+ return CSSPixel::FromAppUnits(GetContentRectSize(*aFrame)).ToUnknownSize();
+ };
+ if (!StaticPrefs::dom_resize_observer_support_fragments() &&
+ !aObserver.HasNativeCallback()) {
+ return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))};
+ }
+ AutoTArray<LogicalPixelSize, 1> size;
+ for (nsIFrame* cur = frame; cur; cur = cur->GetNextContinuation()) {
+ const WritingMode wm = cur->GetWritingMode();
+ size.AppendElement(LogicalPixelSize(wm, GetFrameSize(cur)));
+ }
+ return size;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ResizeObservation)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObservation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObservation)
+ tmp->Unlink(RemoveFromObserver::Yes);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+ResizeObservation::ResizeObservation(Element& aTarget,
+ ResizeObserver& aObserver,
+ ResizeObserverBoxOptions aBox)
+ : mTarget(&aTarget),
+ mObserver(&aObserver),
+ mObservedBox(aBox),
+ mLastReportedSize(
+ {StaticPrefs::dom_resize_observer_last_reported_size_invalid()
+ ? LogicalPixelSize(WritingMode(), gfx::Size(-1, -1))
+ : LogicalPixelSize()}) {
+ aTarget.BindObject(mObserver);
+}
+
+void ResizeObservation::Unlink(RemoveFromObserver aRemoveFromObserver) {
+ ResizeObserver* observer = std::exchange(mObserver, nullptr);
+ nsCOMPtr<Element> target = std::move(mTarget);
+ if (observer && target) {
+ if (aRemoveFromObserver == RemoveFromObserver::Yes) {
+ observer->Unobserve(*target);
+ }
+ target->UnbindObject(observer);
+ }
+}
+
+bool ResizeObservation::IsActive() const {
+ // As detailed in the css-contain specification, if the target is hidden by
+ // `content-visibility` it should not call its ResizeObservation callbacks.
+ nsIFrame* frame = mTarget->GetPrimaryFrame();
+ if (frame && frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
+ return false;
+ }
+
+ return mLastReportedSize !=
+ CalculateBoxSize(mTarget, mObservedBox, *mObserver);
+}
+
+void ResizeObservation::UpdateLastReportedSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mLastReportedSize.Assign(aSize);
+}
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner, mDocument, mActiveTargets,
+ mObservationMap);
+ if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ ImplCycleCollectionTraverse(
+ cb, tmp->mCallback.as<RefPtr<ResizeObserverCallback>>(), "mCallback",
+ 0);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObserver)
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner, mDocument, mActiveTargets,
+ mObservationMap);
+ if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ ImplCycleCollectionUnlink(
+ tmp->mCallback.as<RefPtr<ResizeObserverCallback>>());
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ResizeObserver::ResizeObserver(Document& aDocument, NativeCallback aCallback)
+ : mOwner(aDocument.GetInnerWindow()),
+ mDocument(&aDocument),
+ mCallback(aCallback) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner window");
+ MOZ_ASSERT(mDocument == mOwner->GetExtantDoc());
+}
+
+already_AddRefed<ResizeObserver> ResizeObserver::Constructor(
+ const GlobalObject& aGlobal, ResizeObserverCallback& aCb,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return do_AddRef(new ResizeObserver(std::move(window), doc, aCb));
+}
+
+void ResizeObserver::Observe(Element& aTarget,
+ const ResizeObserverOptions& aOptions) {
+ if (MOZ_UNLIKELY(!mDocument)) {
+ MOZ_ASSERT_UNREACHABLE("How did we call observe() after unlink?");
+ return;
+ }
+
+ // NOTE(emilio): Per spec, this is supposed to happen on construction, but the
+ // spec isn't particularly sane here, see
+ // https://github.com/w3c/csswg-drafts/issues/4518
+ if (mObservationList.isEmpty()) {
+ MOZ_ASSERT(mObservationMap.IsEmpty());
+ mDocument->AddResizeObserver(*this);
+ }
+
+ auto& observation = mObservationMap.LookupOrInsert(&aTarget);
+ if (observation) {
+ if (observation->BoxOptions() == aOptions.mBox) {
+ // Already observed this target and the observed box is the same, so
+ // return.
+ // Note: Based on the spec, we should unobserve it first. However,
+ // calling Unobserve() when we observe the same box will remove original
+ // ResizeObservation and then add a new one, this may cause an unexpected
+ // result because ResizeObservation stores the mLastReportedSize which
+ // should be kept to make sure IsActive() returns the correct result.
+ return;
+ }
+ // Remove the pre-existing entry, but without unregistering ourselves from
+ // the controller.
+ observation->remove();
+ observation = nullptr;
+ }
+
+ observation = new ResizeObservation(aTarget, *this, aOptions.mBox);
+ if (!StaticPrefs::dom_resize_observer_last_reported_size_invalid() &&
+ this == mDocument->GetLastRememberedSizeObserver()) {
+ // Resize observations are initialized with a (0, 0) mLastReportedSize,
+ // this means that the callback won't be called if the element is 0x0.
+ // But we need it called for handling the last remembered size, so set
+ // mLastReportedSize to an invalid size to ensure IsActive() is true
+ // for the current element size.
+ // See https://github.com/w3c/csswg-drafts/issues/3664 about doing this in
+ // the general case, then we won't need this hack for the last remembered
+ // size, and will have consistency with IntersectionObserver.
+ observation->UpdateLastReportedSize(
+ {LogicalPixelSize(WritingMode(), gfx::Size(-1, -1))});
+ MOZ_ASSERT(observation->IsActive());
+ }
+ mObservationList.insertBack(observation);
+
+ // Per the spec, we need to trigger notification in event loop that
+ // contains ResizeObserver observe call even when resize/reflow does
+ // not happen.
+ mDocument->ScheduleResizeObserversNotification();
+}
+
+void ResizeObserver::Unobserve(Element& aTarget) {
+ RefPtr<ResizeObservation> observation;
+ if (!mObservationMap.Remove(&aTarget, getter_AddRefs(observation))) {
+ return;
+ }
+
+ MOZ_ASSERT(!mObservationList.isEmpty(),
+ "If ResizeObservation found for an element, observation list "
+ "must be not empty.");
+ observation->remove();
+ if (mObservationList.isEmpty()) {
+ if (MOZ_LIKELY(mDocument)) {
+ mDocument->RemoveResizeObserver(*this);
+ }
+ }
+}
+
+void ResizeObserver::Disconnect() {
+ const bool registered = !mObservationList.isEmpty();
+ while (auto* observation = mObservationList.popFirst()) {
+ observation->Unlink(ResizeObservation::RemoveFromObserver::No);
+ }
+ MOZ_ASSERT(mObservationList.isEmpty());
+ mObservationMap.Clear();
+ mActiveTargets.Clear();
+ if (registered && MOZ_LIKELY(mDocument)) {
+ mDocument->RemoveResizeObserver(*this);
+ }
+}
+
+void ResizeObserver::GatherActiveObservations(uint32_t aDepth) {
+ mActiveTargets.Clear();
+ mHasSkippedTargets = false;
+
+ for (auto* observation : mObservationList) {
+ if (!observation->IsActive()) {
+ continue;
+ }
+
+ uint32_t targetDepth = GetNodeDepth(observation->Target());
+
+ if (targetDepth > aDepth) {
+ mActiveTargets.AppendElement(observation);
+ } else {
+ mHasSkippedTargets = true;
+ }
+ }
+}
+
+uint32_t ResizeObserver::BroadcastActiveObservations() {
+ uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
+
+ if (!HasActiveObservations()) {
+ return shallowestTargetDepth;
+ }
+
+ Sequence<OwningNonNull<ResizeObserverEntry>> entries;
+
+ for (auto& observation : mActiveTargets) {
+ Element* target = observation->Target();
+
+ auto borderBoxSize =
+ CalculateBoxSize(target, ResizeObserverBoxOptions::Border_box, *this);
+ auto contentBoxSize =
+ CalculateBoxSize(target, ResizeObserverBoxOptions::Content_box, *this);
+ auto devicePixelContentBoxSize = CalculateBoxSize(
+ target, ResizeObserverBoxOptions::Device_pixel_content_box, *this);
+ RefPtr<ResizeObserverEntry> entry =
+ new ResizeObserverEntry(mOwner, *target, borderBoxSize, contentBoxSize,
+ devicePixelContentBoxSize);
+
+ if (!entries.AppendElement(entry.forget(), fallible)) {
+ // Out of memory.
+ break;
+ }
+
+ // Sync the broadcast size of observation so the next size inspection
+ // will be based on the updated size from last delivered observations.
+ switch (observation->BoxOptions()) {
+ case ResizeObserverBoxOptions::Border_box:
+ observation->UpdateLastReportedSize(borderBoxSize);
+ break;
+ case ResizeObserverBoxOptions::Device_pixel_content_box:
+ observation->UpdateLastReportedSize(devicePixelContentBoxSize);
+ break;
+ case ResizeObserverBoxOptions::Content_box:
+ default:
+ observation->UpdateLastReportedSize(contentBoxSize);
+ }
+
+ uint32_t targetDepth = GetNodeDepth(observation->Target());
+
+ if (targetDepth < shallowestTargetDepth) {
+ shallowestTargetDepth = targetDepth;
+ }
+ }
+
+ if (mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ RefPtr<ResizeObserverCallback> callback(
+ mCallback.as<RefPtr<ResizeObserverCallback>>());
+ callback->Call(this, entries, *this);
+ } else {
+ mCallback.as<NativeCallback>()(entries, *this);
+ }
+
+ mActiveTargets.Clear();
+ mHasSkippedTargets = false;
+
+ return shallowestTargetDepth;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mOwner, mTarget,
+ mContentRect, mBorderBoxSize,
+ mContentBoxSize,
+ mDevicePixelContentBoxSize)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverEntry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void ResizeObserverEntry::GetBorderBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mBorderBoxSize);
+}
+
+void ResizeObserverEntry::GetContentBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mContentBoxSize);
+}
+
+void ResizeObserverEntry::GetDevicePixelContentBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mDevicePixelContentBoxSize);
+}
+
+void ResizeObserverEntry::SetBorderBoxSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mBorderBoxSize.Clear();
+ mBorderBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mBorderBoxSize.AppendElement(new ResizeObserverSize(mOwner, size));
+ }
+}
+
+void ResizeObserverEntry::SetContentRectAndSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ nsIFrame* frame = mTarget->GetPrimaryFrame();
+
+ // 1. Update mContentRect.
+ nsMargin padding = frame ? frame->GetUsedPadding() : nsMargin();
+ // Per the spec, we need to use the top-left padding offset as the origin of
+ // our contentRect.
+ gfx::Size sizeForRect;
+ MOZ_DIAGNOSTIC_ASSERT(!aSize.IsEmpty());
+ if (!aSize.IsEmpty()) {
+ const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
+ sizeForRect = aSize[0].PhysicalSize(wm);
+ }
+ nsRect rect(nsPoint(padding.left, padding.top),
+ CSSPixel::ToAppUnits(CSSSize::FromUnknownSize(sizeForRect)));
+ RefPtr<DOMRect> contentRect = new DOMRect(mOwner);
+ contentRect->SetLayoutRect(rect);
+ mContentRect = std::move(contentRect);
+
+ // 2. Update mContentBoxSize.
+ mContentBoxSize.Clear();
+ mContentBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mContentBoxSize.AppendElement(new ResizeObserverSize(mOwner, size));
+ }
+}
+
+void ResizeObserverEntry::SetDevicePixelContentSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mDevicePixelContentBoxSize.Clear();
+ mDevicePixelContentBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mDevicePixelContentBoxSize.AppendElement(
+ new ResizeObserverSize(mOwner, size));
+ }
+}
+
+static void LastRememberedSizeCallback(
+ const Sequence<OwningNonNull<ResizeObserverEntry>>& aEntries,
+ ResizeObserver& aObserver) {
+ for (const auto& entry : aEntries) {
+ Element* target = entry->Target();
+ if (!target->IsInComposedDoc()) {
+ aObserver.Unobserve(*target);
+ target->RemoveLastRememberedBSize();
+ target->RemoveLastRememberedISize();
+ continue;
+ }
+ nsIFrame* frame = target->GetPrimaryFrame();
+ if (!frame) {
+ aObserver.Unobserve(*target);
+ continue;
+ }
+ MOZ_ASSERT(!frame->IsFrameOfType(nsIFrame::eLineParticipant) ||
+ frame->IsFrameOfType(nsIFrame::eReplaced),
+ "Should have unobserved non-replaced inline.");
+ MOZ_ASSERT(!frame->HidesContent(),
+ "Should have unobserved element skipping its contents.");
+ const nsStylePosition* stylePos = frame->StylePosition();
+ const WritingMode wm = frame->GetWritingMode();
+ bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).IsAutoLength();
+ bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).IsAutoLength();
+ MOZ_ASSERT(canUpdateBSize || !target->HasLastRememberedBSize(),
+ "Should have removed the last remembered block size.");
+ MOZ_ASSERT(canUpdateISize || !target->HasLastRememberedISize(),
+ "Should have removed the last remembered inline size.");
+ MOZ_ASSERT(canUpdateBSize || canUpdateISize,
+ "Should have unobserved if we can't update any size.");
+ AutoTArray<RefPtr<ResizeObserverSize>, 1> contentSizeList;
+ entry->GetContentBoxSize(contentSizeList);
+ MOZ_ASSERT(!contentSizeList.IsEmpty());
+ if (canUpdateBSize) {
+ float bSize = 0;
+ for (const auto& current : contentSizeList) {
+ bSize += current->BlockSize();
+ }
+ target->SetLastRememberedBSize(bSize);
+ }
+ if (canUpdateISize) {
+ float iSize = 0;
+ for (const auto& current : contentSizeList) {
+ iSize = std::max(iSize, current->InlineSize());
+ }
+ target->SetLastRememberedISize(iSize);
+ }
+ }
+}
+
+/* static */ already_AddRefed<ResizeObserver>
+ResizeObserver::CreateLastRememberedSizeObserver(Document& aDocument) {
+ return do_AddRef(new ResizeObserver(aDocument, LastRememberedSizeCallback));
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverSize, mOwner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverSize)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverSize)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverSize)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+} // namespace mozilla::dom
diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h
new file mode 100644
index 0000000000..6f1fc5b6cd
--- /dev/null
+++ b/dom/base/ResizeObserver.h
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ResizeObserver_h
+#define mozilla_dom_ResizeObserver_h
+
+#include "gfxPoint.h"
+#include "js/TypeDecls.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ResizeObserverBinding.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class Element;
+
+// The logical size in pixels.
+// Note: if LogicalPixelSize have usages other than ResizeObserver in the
+// future, it might be better to change LogicalSize into a template class, and
+// use it to implement LogicalPixelSize.
+class LogicalPixelSize {
+ public:
+ LogicalPixelSize() = default;
+ LogicalPixelSize(WritingMode aWM, const gfx::Size& aSize) {
+ mSize = aSize;
+ if (aWM.IsVertical()) {
+ std::swap(mSize.width, mSize.height);
+ }
+ }
+
+ gfx::Size PhysicalSize(WritingMode aWM) const {
+ if (!aWM.IsVertical()) {
+ return mSize;
+ }
+ gfx::Size result(mSize);
+ std::swap(result.width, result.height);
+ return result;
+ }
+
+ bool operator==(const LogicalPixelSize& aOther) const {
+ return mSize == aOther.mSize;
+ }
+ bool operator!=(const LogicalPixelSize& aOther) const {
+ return !(*this == aOther);
+ }
+
+ float ISize() const { return mSize.width; }
+ float BSize() const { return mSize.height; }
+ float& ISize() { return mSize.width; }
+ float& BSize() { return mSize.height; }
+
+ private:
+ // |mSize.width| represents inline-size and |mSize.height| represents
+ // block-size.
+ gfx::Size mSize;
+};
+
+// For the internal implementation in ResizeObserver. Normally, this is owned by
+// ResizeObserver.
+class ResizeObservation final : public LinkedListElement<ResizeObservation> {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResizeObservation)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResizeObservation)
+
+ ResizeObservation(Element&, ResizeObserver&, ResizeObserverBoxOptions);
+
+ Element* Target() const { return mTarget; }
+
+ ResizeObserverBoxOptions BoxOptions() const { return mObservedBox; }
+
+ /**
+ * Returns whether the observed target element size differs from the saved
+ * mLastReportedSize.
+ */
+ bool IsActive() const;
+
+ /**
+ * Update current mLastReportedSize to aSize.
+ */
+ void UpdateLastReportedSize(const nsTArray<LogicalPixelSize>& aSize);
+
+ enum class RemoveFromObserver : bool { No, Yes };
+ void Unlink(RemoveFromObserver);
+
+ protected:
+ ~ResizeObservation() { Unlink(RemoveFromObserver::No); };
+
+ nsCOMPtr<Element> mTarget;
+
+ // Weak, observer always outlives us.
+ ResizeObserver* mObserver;
+
+ const ResizeObserverBoxOptions mObservedBox;
+
+ // The latest recorded of observed target.
+ // This will be CSS pixels for border-box/content-box, or device pixels for
+ // device-pixel-content-box.
+ AutoTArray<LogicalPixelSize, 1> mLastReportedSize;
+};
+
+/**
+ * ResizeObserver interfaces and algorithms are based on
+ * https://drafts.csswg.org/resize-observer/#api
+ */
+class ResizeObserver final : public nsISupports, public nsWrapperCache {
+ using NativeCallback = void (*)(
+ const Sequence<OwningNonNull<ResizeObserverEntry>>&, ResizeObserver&);
+ ResizeObserver(Document& aDocument, NativeCallback aCallback);
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver)
+
+ ResizeObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner, Document* aDocument,
+ ResizeObserverCallback& aCb)
+ : mOwner(std::move(aOwner)),
+ mDocument(aDocument),
+ mCallback(RefPtr<ResizeObserverCallback>(&aCb)) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner window");
+ MOZ_ASSERT(mDocument, "Need a non-null doc");
+ MOZ_ASSERT(mDocument == mOwner->GetExtantDoc());
+ }
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return ResizeObserver_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ static already_AddRefed<ResizeObserver> Constructor(
+ const GlobalObject& aGlobal, ResizeObserverCallback& aCb,
+ ErrorResult& aRv);
+
+ void Observe(Element&, const ResizeObserverOptions&);
+ void Unobserve(Element&);
+
+ void Disconnect();
+
+ /**
+ * Gather all observations which have an observed target with size changed
+ * since last BroadcastActiveObservations() in this ResizeObserver.
+ * An observation will be skipped if the depth of its observed target is less
+ * or equal than aDepth. All gathered observations will be added to
+ * mActiveTargets.
+ */
+ void GatherActiveObservations(uint32_t aDepth);
+
+ /**
+ * Returns whether this ResizeObserver has any active observations
+ * since last GatherActiveObservations().
+ */
+ bool HasActiveObservations() const { return !mActiveTargets.IsEmpty(); }
+
+ /**
+ * Returns whether this ResizeObserver has any skipped observations
+ * since last GatherActiveObservations().
+ */
+ bool HasSkippedObservations() const { return mHasSkippedTargets; }
+
+ /**
+ * Returns whether this is an internal ResizeObserver with a native callback.
+ */
+ bool HasNativeCallback() const { return mCallback.is<NativeCallback>(); }
+
+ /**
+ * Invoke the callback function in JavaScript for all active observations
+ * and pass the sequence of ResizeObserverEntry so JavaScript can access them.
+ * The active observations' mLastReportedSize fields will be updated, and
+ * mActiveTargets will be cleared. It also returns the shallowest depth of
+ * elements from active observations or numeric_limits<uint32_t>::max() if
+ * there are not any active observations.
+ */
+ MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations();
+
+ static already_AddRefed<ResizeObserver> CreateLastRememberedSizeObserver(
+ Document&);
+
+ protected:
+ ~ResizeObserver() { Disconnect(); }
+
+ nsCOMPtr<nsPIDOMWindowInner> mOwner;
+ // The window's document at the time of ResizeObserver creation.
+ RefPtr<Document> mDocument;
+ Variant<RefPtr<ResizeObserverCallback>, NativeCallback> mCallback;
+ nsTArray<RefPtr<ResizeObservation>> mActiveTargets;
+ // The spec uses a list to store the skipped targets. However, it seems what
+ // we want is to check if there are any skipped targets (i.e. existence).
+ // Therefore, we use a boolean value to represent the existence of skipped
+ // targets.
+ bool mHasSkippedTargets = false;
+
+ // Combination of HashTable and LinkedList so we can iterate through
+ // the elements of HashTable in order of insertion time, so we can deliver
+ // observations in the correct order
+ // FIXME: it will be nice if we have our own data structure for this in the
+ // future, and mObservationMap should be considered the "owning" storage for
+ // the observations, so it'd be better to drop mObservationList later.
+ nsRefPtrHashtable<nsPtrHashKey<Element>, ResizeObservation> mObservationMap;
+ LinkedList<ResizeObservation> mObservationList;
+};
+
+/**
+ * ResizeObserverEntry is the entry that contains the information for observed
+ * elements. This object is the one that's visible to JavaScript in callback
+ * function that is fired by ResizeObserver.
+ */
+class ResizeObserverEntry final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverEntry)
+
+ ResizeObserverEntry(
+ nsISupports* aOwner, Element& aTarget,
+ const nsTArray<LogicalPixelSize>& aBorderBoxSize,
+ const nsTArray<LogicalPixelSize>& aContentBoxSize,
+ const nsTArray<LogicalPixelSize>& aDevicePixelContentBoxSize)
+ : mOwner(aOwner), mTarget(&aTarget) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner");
+ MOZ_ASSERT(mTarget, "Need a non-null target element");
+
+ SetBorderBoxSize(aBorderBoxSize);
+ SetContentRectAndSize(aContentBoxSize);
+ SetDevicePixelContentSize(aDevicePixelContentBoxSize);
+ }
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return ResizeObserverEntry_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ Element* Target() const { return mTarget; }
+
+ /**
+ * Returns the DOMRectReadOnly of target's content rect so it can be
+ * accessed from JavaScript in callback function of ResizeObserver.
+ */
+ DOMRectReadOnly* ContentRect() const { return mContentRect; }
+
+ /**
+ * Returns target's logical border-box size, content-box size, and
+ * device-pixel-content-box as an array of ResizeObserverSize.
+ */
+ void GetBorderBoxSize(nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
+ void GetContentBoxSize(nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
+ void GetDevicePixelContentBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
+
+ private:
+ ~ResizeObserverEntry() = default;
+
+ // Set borderBoxSize.
+ void SetBorderBoxSize(const nsTArray<LogicalPixelSize>& aSize);
+ // Set contentRect and contentBoxSize.
+ void SetContentRectAndSize(const nsTArray<LogicalPixelSize>& aSize);
+ // Set devicePixelContentBoxSize.
+ void SetDevicePixelContentSize(const nsTArray<LogicalPixelSize>& aSize);
+
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<Element> mTarget;
+
+ RefPtr<DOMRectReadOnly> mContentRect;
+ AutoTArray<RefPtr<ResizeObserverSize>, 1> mBorderBoxSize;
+ AutoTArray<RefPtr<ResizeObserverSize>, 1> mContentBoxSize;
+ AutoTArray<RefPtr<ResizeObserverSize>, 1> mDevicePixelContentBoxSize;
+};
+
+class ResizeObserverSize final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserverSize)
+
+ ResizeObserverSize(nsISupports* aOwner, const LogicalPixelSize& aSize)
+ : mOwner(aOwner), mSize(aSize) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner");
+ }
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return ResizeObserverSize_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ float InlineSize() const { return mSize.ISize(); }
+ float BlockSize() const { return mSize.BSize(); }
+
+ protected:
+ ~ResizeObserverSize() = default;
+
+ nsCOMPtr<nsISupports> mOwner;
+ // The logical size value:
+ // 1. content-box/border-box: in CSS pixels.
+ // 2. device-pixel-content-box: in device pixels.
+ const LogicalPixelSize mSize;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ResizeObserver_h
diff --git a/dom/base/ResizeObserverController.cpp b/dom/base/ResizeObserverController.cpp
new file mode 100644
index 0000000000..3a315cb026
--- /dev/null
+++ b/dom/base/ResizeObserverController.cpp
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ResizeObserverController.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Unused.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+#include <limits>
+
+namespace mozilla::dom {
+
+void ResizeObserverNotificationHelper::WillRefresh(TimeStamp aTime) {
+ MOZ_DIAGNOSTIC_ASSERT(mOwner, "Should've de-registered on-time!");
+ mOwner->Notify();
+ // Note that mOwner may be null / dead here.
+}
+
+nsRefreshDriver* ResizeObserverNotificationHelper::GetRefreshDriver() const {
+ PresShell* presShell = mOwner->GetPresShell();
+ if (MOZ_UNLIKELY(!presShell)) {
+ return nullptr;
+ }
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (MOZ_UNLIKELY(!presContext)) {
+ return nullptr;
+ }
+
+ return presContext->RefreshDriver();
+}
+
+void ResizeObserverNotificationHelper::Register() {
+ if (mRegistered) {
+ return;
+ }
+
+ nsRefreshDriver* refreshDriver = GetRefreshDriver();
+ if (!refreshDriver) {
+ // We maybe navigating away from this page or currently in an iframe with
+ // display: none. Just abort the Register(), no need to do notification.
+ return;
+ }
+
+ refreshDriver->AddRefreshObserver(this, FlushType::Display, "ResizeObserver");
+ mRegistered = true;
+}
+
+void ResizeObserverNotificationHelper::Unregister() {
+ if (!mRegistered) {
+ return;
+ }
+
+ nsRefreshDriver* refreshDriver = GetRefreshDriver();
+ MOZ_RELEASE_ASSERT(
+ refreshDriver,
+ "We should not leave a dangling reference to the observer around");
+
+ bool rv = refreshDriver->RemoveRefreshObserver(this, FlushType::Display);
+ MOZ_DIAGNOSTIC_ASSERT(rv, "Should remove the observer successfully");
+ Unused << rv;
+
+ mRegistered = false;
+}
+
+ResizeObserverNotificationHelper::~ResizeObserverNotificationHelper() {
+ MOZ_RELEASE_ASSERT(!mRegistered, "How can we die when registered?");
+ MOZ_RELEASE_ASSERT(!mOwner, "Forgot to clear weak pointer?");
+}
+
+void ResizeObserverController::ShellDetachedFromDocument() {
+ mResizeObserverNotificationHelper->Unregister();
+}
+
+static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
+ if (BrowsingContext* bc = aDoc.GetBrowsingContext()) {
+ RefPtr<BrowsingContext> top = bc->Top();
+ top->PreOrderWalk([](BrowsingContext* aCur) {
+ if (Document* doc = aCur->GetExtantDocument()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+ });
+ } else {
+ // If there is no browsing context, we just flush this document itself.
+ aDoc.FlushPendingNotifications(FlushType::Layout);
+ }
+}
+
+void ResizeObserverController::Notify() {
+ mResizeObserverNotificationHelper->Unregister();
+ if (mResizeObservers.IsEmpty()) {
+ return;
+ }
+
+ // We may call BroadcastAllActiveObservations(), which might cause mDocument
+ // to be destroyed (and the ResizeObserverController with it).
+ // e.g. if mDocument is in an iframe, and the observer JS removes it from the
+ // parent document and we trigger an unlucky GC/CC (or maybe if the observer
+ // JS navigates to a different URL). Or the author uses elem.offsetTop API,
+ // which could flush style + layout and make the document destroyed if we're
+ // inside an iframe that has suddenly become |display:none| via the author
+ // doing something in their ResizeObserver callback.
+ //
+ // Therefore, we ref-count mDocument here to make sure it and its members
+ // (e.g. mResizeObserverController, which is `this` pointer) are still alive
+ // in the rest of this function because after it goes up, `this` is possible
+ // deleted.
+ RefPtr<Document> doc(mDocument);
+
+ uint32_t shallowestTargetDepth = 0;
+ while (true) {
+ // Flush layout, so that any callback functions' style changes / resizes
+ // get a chance to take effect. The callback functions may do changes in its
+ // sub-documents or ancestors, so flushing layout for the whole browsing
+ // context tree makes sure we don't miss anyone.
+ FlushLayoutForWholeBrowsingContextTree(*doc);
+
+ // To avoid infinite resize loop, we only gather all active observations
+ // that have the depth of observed target element more than current
+ // shallowestTargetDepth.
+ GatherAllActiveObservations(shallowestTargetDepth);
+
+ if (!HasAnyActiveObservations()) {
+ break;
+ }
+
+ DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
+ shallowestTargetDepth = BroadcastAllActiveObservations();
+ NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
+ "shallowestTargetDepth should be getting strictly deeper");
+ }
+
+ if (HasAnySkippedObservations()) {
+ // Per spec, we deliver an error if the document has any skipped
+ // observations. Also, we re-register via ScheduleNotification().
+ RootedDictionary<ErrorEventInit> init(RootingCx());
+
+ init.mMessage.AssignLiteral(
+ "ResizeObserver loop completed with undelivered notifications.");
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
+ MOZ_ASSERT(sgo);
+
+ if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
+ status = nsEventStatus_eIgnore;
+ }
+ } else {
+ // We don't fire error events at any global for non-window JS on the main
+ // thread.
+ }
+
+ // We need to deliver pending notifications in next cycle.
+ ScheduleNotification();
+ }
+}
+
+void ResizeObserverController::GatherAllActiveObservations(uint32_t aDepth) {
+ for (ResizeObserver* observer : mResizeObservers) {
+ observer->GatherActiveObservations(aDepth);
+ }
+}
+
+uint32_t ResizeObserverController::BroadcastAllActiveObservations() {
+ uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
+
+ // Copy the observers as this invokes the callbacks and could register and
+ // unregister observers at will.
+ const auto observers =
+ ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
+ for (auto& observer : observers) {
+ // MOZ_KnownLive because 'observers' is guaranteed to keep it
+ // alive.
+ //
+ // This can go away once
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
+ uint32_t targetDepth =
+ MOZ_KnownLive(observer)->BroadcastActiveObservations();
+ if (targetDepth < shallowestTargetDepth) {
+ shallowestTargetDepth = targetDepth;
+ }
+ }
+
+ return shallowestTargetDepth;
+}
+
+bool ResizeObserverController::HasAnyActiveObservations() const {
+ for (auto& observer : mResizeObservers) {
+ if (observer->HasActiveObservations()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ResizeObserverController::HasAnySkippedObservations() const {
+ for (auto& observer : mResizeObservers) {
+ if (observer->HasSkippedObservations()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ResizeObserverController::ScheduleNotification() {
+ mResizeObserverNotificationHelper->Register();
+}
+
+ResizeObserverController::~ResizeObserverController() {
+ MOZ_RELEASE_ASSERT(
+ !mResizeObserverNotificationHelper->IsRegistered(),
+ "Nothing else should keep a reference to our helper when we go away");
+ mResizeObserverNotificationHelper->DetachFromOwner();
+}
+
+void ResizeObserverController::AddSizeOfIncludingThis(
+ nsWindowSizes& aSizes) const {
+ MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
+ size_t size = mallocSizeOf(this);
+ size += mResizeObservers.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // TODO(emilio): Measure the observers individually or something? They aren't
+ // really owned by us.
+ aSizes.mDOMSizes.mDOMResizeObserverControllerSize += size;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ResizeObserverController.h b/dom/base/ResizeObserverController.h
new file mode 100644
index 0000000000..8a9ff0dbd2
--- /dev/null
+++ b/dom/base/ResizeObserverController.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ResizeObserverController_h
+#define mozilla_dom_ResizeObserverController_h
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ResizeObserver.h"
+#include "mozilla/TimeStamp.h"
+#include "nsRefreshObservers.h"
+#include "nsTObserverArray.h"
+
+class nsRefreshDriver;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace dom {
+
+class ResizeObserverController;
+
+/**
+ * ResizeObserverNotificationHelper will trigger ResizeObserver notifications
+ * by registering with the Refresh Driver.
+ */
+class ResizeObserverNotificationHelper final : public nsARefreshObserver {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ResizeObserverNotificationHelper, override)
+
+ explicit ResizeObserverNotificationHelper(ResizeObserverController* aOwner)
+ : mOwner(aOwner), mRegistered(false) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner");
+ }
+
+ MOZ_CAN_RUN_SCRIPT void WillRefresh(TimeStamp aTime) override;
+
+ nsRefreshDriver* GetRefreshDriver() const;
+
+ void Register();
+
+ void Unregister();
+
+ bool IsRegistered() const { return mRegistered; }
+
+ void DetachFromOwner() { mOwner = nullptr; }
+
+ private:
+ virtual ~ResizeObserverNotificationHelper();
+
+ ResizeObserverController* mOwner;
+ bool mRegistered;
+};
+
+/**
+ * ResizeObserverController contains the list of ResizeObservers and controls
+ * the flow of notification.
+ */
+class ResizeObserverController final {
+ public:
+ explicit ResizeObserverController(Document* aDocument)
+ : mDocument(aDocument),
+ mResizeObserverNotificationHelper(
+ new ResizeObserverNotificationHelper(this)) {
+ MOZ_ASSERT(mDocument, "Need a non-null document");
+ }
+
+ void AddSizeOfIncludingThis(nsWindowSizes&) const;
+
+ void ShellDetachedFromDocument();
+ void AddResizeObserver(ResizeObserver& aObserver) {
+ MOZ_ASSERT(!mResizeObservers.Contains(&aObserver));
+ // Insert internal ResizeObservers before scripted ones, since they may have
+ // observable side-effects and we don't want to expose the insertion time.
+ if (aObserver.HasNativeCallback()) {
+ mResizeObservers.InsertElementAt(0, &aObserver);
+ } else {
+ mResizeObservers.AppendElement(&aObserver);
+ }
+ }
+
+ void RemoveResizeObserver(ResizeObserver& aObserver) {
+ MOZ_ASSERT(mResizeObservers.Contains(&aObserver));
+ mResizeObservers.RemoveElement(&aObserver);
+ }
+
+ /**
+ * Schedule the notification via ResizeObserverNotificationHelper refresh
+ * observer.
+ */
+ void ScheduleNotification();
+
+ /**
+ * Notify all ResizeObservers by gathering and broadcasting all active
+ * observations.
+ */
+ MOZ_CAN_RUN_SCRIPT void Notify();
+
+ PresShell* GetPresShell() const { return mDocument->GetPresShell(); }
+
+ ~ResizeObserverController();
+
+ private:
+ /**
+ * Calls GatherActiveObservations(aDepth) for all ResizeObservers in this
+ * controller. All observations in each ResizeObserver with element's depth
+ * more than aDepth will be gathered.
+ */
+ void GatherAllActiveObservations(uint32_t aDepth);
+
+ /**
+ * Calls BroadcastActiveObservations() for all ResizeObservers in this
+ * controller. It also returns the shallowest depth of observed target
+ * elements with active observations from all ResizeObservers or
+ * numeric_limits<uint32_t>::max() if there aren't any active observations
+ * at all.
+ */
+ MOZ_CAN_RUN_SCRIPT uint32_t BroadcastAllActiveObservations();
+
+ /**
+ * Returns whether there is any ResizeObserver that has active observations.
+ */
+ bool HasAnyActiveObservations() const;
+
+ /**
+ * Returns whether there is any ResizeObserver that has skipped observations.
+ */
+ bool HasAnySkippedObservations() const;
+
+ // Raw pointer is OK because mDocument strongly owns us & hence must outlive
+ // us.
+ Document* const mDocument;
+
+ RefPtr<ResizeObserverNotificationHelper> mResizeObserverNotificationHelper;
+ nsTArray<ResizeObserver*> mResizeObservers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ResizeObserverController_h
diff --git a/dom/base/ResponsiveImageSelector.cpp b/dom/base/ResponsiveImageSelector.cpp
new file mode 100644
index 0000000000..29483e0e55
--- /dev/null
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -0,0 +1,730 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/TextUtils.h"
+#include "nsIURI.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+
+#include "nsCSSProps.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mOwnerNode)
+
+static bool ParseInteger(const nsAString& aString, int32_t& aInt) {
+ nsContentUtils::ParseHTMLIntegerResultFlags parseResult;
+ aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult);
+ return !(parseResult &
+ (nsContentUtils::eParseHTMLInteger_Error |
+ nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput |
+ nsContentUtils::eParseHTMLInteger_NonStandard));
+}
+
+static bool ParseFloat(const nsAString& aString, double& aDouble) {
+ // Check if it is a valid floating-point number first since the result of
+ // nsString.ToDouble() is more lenient than the spec,
+ // https://html.spec.whatwg.org/#valid-floating-point-number
+ nsAString::const_iterator iter, end;
+ aString.BeginReading(iter);
+ aString.EndReading(end);
+
+ if (iter == end) {
+ return false;
+ }
+
+ if (*iter == char16_t('-') && ++iter == end) {
+ return false;
+ }
+
+ if (IsAsciiDigit(*iter)) {
+ for (; iter != end && IsAsciiDigit(*iter); ++iter)
+ ;
+ } else if (*iter == char16_t('.')) {
+ // Do nothing, jumps to fraction part
+ } else {
+ return false;
+ }
+
+ // Fraction
+ if (*iter == char16_t('.')) {
+ ++iter;
+ if (iter == end || !IsAsciiDigit(*iter)) {
+ // U+002E FULL STOP character (.) must be followed by one or more ASCII
+ // digits
+ return false;
+ }
+
+ for (; iter != end && IsAsciiDigit(*iter); ++iter)
+ ;
+ }
+
+ if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) {
+ ++iter;
+ if (*iter == char16_t('-') || *iter == char16_t('+')) {
+ ++iter;
+ }
+
+ if (iter == end || !IsAsciiDigit(*iter)) {
+ // Should have one or more ASCII digits
+ return false;
+ }
+
+ for (; iter != end && IsAsciiDigit(*iter); ++iter)
+ ;
+ }
+
+ if (iter != end) {
+ return false;
+ }
+
+ nsresult rv;
+ aDouble = PromiseFlatString(aString).ToDouble(&rv);
+ return NS_SUCCEEDED(rv);
+}
+
+ResponsiveImageSelector::ResponsiveImageSelector(nsIContent* aContent)
+ : mOwnerNode(aContent), mSelectedCandidateIndex(-1) {}
+
+ResponsiveImageSelector::ResponsiveImageSelector(dom::Document* aDocument)
+ : mOwnerNode(aDocument), mSelectedCandidateIndex(-1) {}
+
+ResponsiveImageSelector::~ResponsiveImageSelector() = default;
+
+void ResponsiveImageSelector::ParseSourceSet(
+ const nsAString& aSrcSet,
+ FunctionRef<void(ResponsiveImageCandidate&&)> aCallback) {
+ nsAString::const_iterator iter, end;
+ aSrcSet.BeginReading(iter);
+ aSrcSet.EndReading(end);
+
+ // Read URL / descriptor pairs
+ while (iter != end) {
+ nsAString::const_iterator url, urlEnd, descriptor;
+
+ // Skip whitespace and commas.
+ // Extra commas at this point are a non-fatal syntax error.
+ for (; iter != end &&
+ (nsContentUtils::IsHTMLWhitespace(*iter) || *iter == char16_t(','));
+ ++iter)
+ ;
+
+ if (iter == end) {
+ break;
+ }
+
+ url = iter;
+
+ // Find end of url
+ for (; iter != end && !nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
+ ;
+
+ // Omit trailing commas from URL.
+ // Multiple commas are a non-fatal error.
+ while (iter != url) {
+ if (*(--iter) != char16_t(',')) {
+ iter++;
+ break;
+ }
+ }
+
+ const nsDependentSubstring& urlStr = Substring(url, iter);
+
+ MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point");
+
+ ResponsiveImageCandidate candidate;
+ if (candidate.ConsumeDescriptors(iter, end)) {
+ candidate.SetURLSpec(urlStr);
+ aCallback(std::move(candidate));
+ }
+ }
+}
+
+// http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
+bool ResponsiveImageSelector::SetCandidatesFromSourceSet(
+ const nsAString& aSrcSet, nsIPrincipal* aTriggeringPrincipal) {
+ ClearSelectedCandidate();
+
+ if (!mOwnerNode || !mOwnerNode->GetBaseURI()) {
+ MOZ_ASSERT(false, "Should not be parsing SourceSet without a document");
+ return false;
+ }
+
+ mCandidates.Clear();
+
+ auto eachCandidate = [&](ResponsiveImageCandidate&& aCandidate) {
+ aCandidate.SetTriggeringPrincipal(
+ nsContentUtils::GetAttrTriggeringPrincipal(
+ Content(), aCandidate.URLString(), aTriggeringPrincipal));
+ AppendCandidateIfUnique(std::move(aCandidate));
+ };
+
+ ParseSourceSet(aSrcSet, eachCandidate);
+
+ bool parsedCandidates = !mCandidates.IsEmpty();
+
+ // Re-add default to end of list
+ MaybeAppendDefaultCandidate();
+
+ return parsedCandidates;
+}
+
+uint32_t ResponsiveImageSelector::NumCandidates(bool aIncludeDefault) {
+ uint32_t candidates = mCandidates.Length();
+
+ // If present, the default candidate is the last item
+ if (!aIncludeDefault && candidates && mCandidates.LastElement().IsDefault()) {
+ candidates--;
+ }
+
+ return candidates;
+}
+
+nsIContent* ResponsiveImageSelector::Content() {
+ return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr;
+}
+
+dom::Document* ResponsiveImageSelector::Document() {
+ return mOwnerNode->OwnerDoc();
+}
+
+void ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString,
+ nsIPrincipal* aPrincipal) {
+ ClearSelectedCandidate();
+
+ // Check if the last element of our candidates is a default
+ if (!mCandidates.IsEmpty() && mCandidates.LastElement().IsDefault()) {
+ mCandidates.RemoveLastElement();
+ }
+
+ mDefaultSourceURL = aURLString;
+ mDefaultSourceTriggeringPrincipal = aPrincipal;
+
+ // Add new default to end of list
+ MaybeAppendDefaultCandidate();
+}
+
+void ResponsiveImageSelector::ClearSelectedCandidate() {
+ mSelectedCandidateIndex = -1;
+ mSelectedCandidateURL = nullptr;
+}
+
+bool ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString& aSizes) {
+ ClearSelectedCandidate();
+
+ NS_ConvertUTF16toUTF8 sizes(aSizes);
+ mServoSourceSizeList.reset(Servo_SourceSizeList_Parse(&sizes));
+ return !!mServoSourceSizeList;
+}
+
+void ResponsiveImageSelector::AppendCandidateIfUnique(
+ ResponsiveImageCandidate&& aCandidate) {
+ int numCandidates = mCandidates.Length();
+
+ // With the exception of Default, which should not be added until we are done
+ // building the list.
+ if (aCandidate.IsDefault()) {
+ return;
+ }
+
+ // Discard candidates with identical parameters, they will never match
+ for (int i = 0; i < numCandidates; i++) {
+ if (mCandidates[i].HasSameParameter(aCandidate)) {
+ return;
+ }
+ }
+
+ mCandidates.AppendElement(std::move(aCandidate));
+}
+
+void ResponsiveImageSelector::MaybeAppendDefaultCandidate() {
+ if (mDefaultSourceURL.IsEmpty()) {
+ return;
+ }
+
+ int numCandidates = mCandidates.Length();
+
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-source-set
+ // step 4.1.3:
+ // If child has a src attribute whose value is not the empty string and source
+ // set does not contain an image source with a density descriptor value of 1,
+ // and no image source with a width descriptor, append child's src attribute
+ // value to source set.
+ for (int i = 0; i < numCandidates; i++) {
+ if (mCandidates[i].IsComputedFromWidth()) {
+ return;
+ } else if (mCandidates[i].Density(this) == 1.0) {
+ return;
+ }
+ }
+
+ ResponsiveImageCandidate defaultCandidate;
+ defaultCandidate.SetParameterDefault();
+ defaultCandidate.SetURLSpec(mDefaultSourceURL);
+ defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal);
+ // We don't use MaybeAppend since we want to keep this even if it can never
+ // match, as it may if the source set changes.
+ mCandidates.AppendElement(std::move(defaultCandidate));
+}
+
+already_AddRefed<nsIURI> ResponsiveImageSelector::GetSelectedImageURL() {
+ SelectImage();
+
+ nsCOMPtr<nsIURI> url = mSelectedCandidateURL;
+ return url.forget();
+}
+
+bool ResponsiveImageSelector::GetSelectedImageURLSpec(nsAString& aResult) {
+ SelectImage();
+
+ if (mSelectedCandidateIndex == -1) {
+ return false;
+ }
+
+ aResult.Assign(mCandidates[mSelectedCandidateIndex].URLString());
+ return true;
+}
+
+double ResponsiveImageSelector::GetSelectedImageDensity() {
+ int bestIndex = GetSelectedCandidateIndex();
+ if (bestIndex < 0) {
+ return 1.0;
+ }
+
+ return mCandidates[bestIndex].Density(this);
+}
+
+nsIPrincipal* ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal() {
+ int bestIndex = GetSelectedCandidateIndex();
+ if (bestIndex < 0) {
+ return nullptr;
+ }
+
+ return mCandidates[bestIndex].TriggeringPrincipal();
+}
+
+bool ResponsiveImageSelector::SelectImage(bool aReselect) {
+ if (!aReselect && mSelectedCandidateIndex != -1) {
+ // Already have selection
+ return false;
+ }
+
+ int oldBest = mSelectedCandidateIndex;
+ ClearSelectedCandidate();
+
+ int numCandidates = mCandidates.Length();
+ if (!numCandidates) {
+ return oldBest != -1;
+ }
+
+ dom::Document* doc = Document();
+ nsPresContext* pctx = doc->GetPresContext();
+ nsCOMPtr<nsIURI> baseURI = mOwnerNode->GetBaseURI();
+
+ if (!pctx || !baseURI) {
+ return oldBest != -1;
+ }
+
+ double displayDensity = pctx->CSSPixelsToDevPixels(1.0f);
+ double overrideDPPX = pctx->GetOverrideDPPX();
+
+ if (overrideDPPX > 0) {
+ displayDensity = overrideDPPX;
+ }
+
+ // Per spec, "In a UA-specific manner, choose one image source"
+ // - For now, select the lowest density greater than displayDensity, otherwise
+ // the greatest density available
+
+ // If the list contains computed width candidates, compute the current
+ // effective image width.
+ double computedWidth = -1;
+ for (int i = 0; i < numCandidates; i++) {
+ if (mCandidates[i].IsComputedFromWidth()) {
+ DebugOnly<bool> computeResult =
+ ComputeFinalWidthForCurrentViewport(&computedWidth);
+ MOZ_ASSERT(computeResult,
+ "Computed candidates not allowed without sizes data");
+ break;
+ }
+ }
+
+ int bestIndex = -1;
+ double bestDensity = -1.0;
+ for (int i = 0; i < numCandidates; i++) {
+ double candidateDensity = (computedWidth == -1)
+ ? mCandidates[i].Density(this)
+ : mCandidates[i].Density(computedWidth);
+ // - If bestIndex is below display density, pick anything larger.
+ // - Otherwise, prefer if less dense than bestDensity but still above
+ // displayDensity.
+ if (bestIndex == -1 ||
+ (bestDensity < displayDensity && candidateDensity > bestDensity) ||
+ (candidateDensity >= displayDensity &&
+ candidateDensity < bestDensity)) {
+ bestIndex = i;
+ bestDensity = candidateDensity;
+ }
+ }
+
+ MOZ_ASSERT(bestIndex >= 0 && bestIndex < numCandidates);
+
+ // Resolve URL
+ nsresult rv;
+ const nsAString& urlStr = mCandidates[bestIndex].URLString();
+ nsCOMPtr<nsIURI> candidateURL;
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(candidateURL),
+ urlStr, doc, baseURI);
+
+ mSelectedCandidateURL = NS_SUCCEEDED(rv) ? candidateURL : nullptr;
+ mSelectedCandidateIndex = bestIndex;
+
+ return mSelectedCandidateIndex != oldBest;
+}
+
+int ResponsiveImageSelector::GetSelectedCandidateIndex() {
+ SelectImage();
+
+ return mSelectedCandidateIndex;
+}
+
+bool ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(
+ double* aWidth) {
+ dom::Document* doc = Document();
+ PresShell* presShell = doc->GetPresShell();
+ nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
+
+ if (!pctx) {
+ return false;
+ }
+ nscoord effectiveWidth =
+ presShell->StyleSet()->EvaluateSourceSizeList(mServoSourceSizeList.get());
+
+ *aWidth =
+ nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
+ return true;
+}
+
+ResponsiveImageCandidate::ResponsiveImageCandidate() {
+ mType = CandidateType::Invalid;
+ mValue.mDensity = 1.0;
+}
+
+void ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString) {
+ mURLString = aURLString;
+}
+
+void ResponsiveImageCandidate::SetTriggeringPrincipal(
+ nsIPrincipal* aPrincipal) {
+ mTriggeringPrincipal = aPrincipal;
+}
+
+void ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth) {
+ mType = CandidateType::ComputedFromWidth;
+ mValue.mWidth = aWidth;
+}
+
+void ResponsiveImageCandidate::SetParameterDefault() {
+ MOZ_ASSERT(!IsValid(), "double setting candidate type");
+
+ mType = CandidateType::Default;
+ // mValue shouldn't actually be used for this type, but set it to default
+ // anyway
+ mValue.mDensity = 1.0;
+}
+
+void ResponsiveImageCandidate::SetParameterInvalid() {
+ mType = CandidateType::Invalid;
+ // mValue shouldn't actually be used for this type, but set it to default
+ // anyway
+ mValue.mDensity = 1.0;
+}
+
+void ResponsiveImageCandidate::SetParameterAsDensity(double aDensity) {
+ MOZ_ASSERT(!IsValid(), "double setting candidate type");
+
+ mType = CandidateType::Density;
+ mValue.mDensity = aDensity;
+}
+
+// Represents all supported descriptors for a ResponsiveImageCandidate, though
+// there is no candidate type that uses all of these. This should generally
+// match the mValue union of ResponsiveImageCandidate.
+struct ResponsiveImageDescriptors {
+ ResponsiveImageDescriptors() : mInvalid(false){};
+
+ Maybe<double> mDensity;
+ Maybe<int32_t> mWidth;
+ // We don't support "h" descriptors yet and they are not spec'd, but the
+ // current spec does specify that they can be silently ignored (whereas
+ // entirely unknown descriptors cause us to invalidate the candidate)
+ //
+ // If we ever start honoring them we should serialize them in
+ // AppendDescriptors.
+ Maybe<int32_t> mFutureCompatHeight;
+ // If this descriptor set is bogus, e.g. a value was added twice (and thus
+ // dropped) or an unknown descriptor was added.
+ bool mInvalid;
+
+ void AddDescriptor(const nsAString& aDescriptor);
+ bool Valid();
+ // Use the current set of descriptors to configure a candidate
+ void FillCandidate(ResponsiveImageCandidate& aCandidate);
+};
+
+// Try to parse a single descriptor from a string. If value already set or
+// unknown, sets invalid flag.
+// This corresponds to the descriptor "Descriptor parser" step in:
+// https://html.spec.whatwg.org/#parse-a-srcset-attribute
+void ResponsiveImageDescriptors::AddDescriptor(const nsAString& aDescriptor) {
+ if (aDescriptor.IsEmpty()) {
+ return;
+ }
+
+ // All currently supported descriptors end with an identifying character.
+ nsAString::const_iterator descStart, descType;
+ aDescriptor.BeginReading(descStart);
+ aDescriptor.EndReading(descType);
+ descType--;
+ const nsDependentSubstring& valueStr = Substring(descStart, descType);
+ if (*descType == char16_t('w')) {
+ int32_t possibleWidth;
+ // If the value is not a valid non-negative integer, it doesn't match this
+ // descriptor, fall through.
+ if (ParseInteger(valueStr, possibleWidth) && possibleWidth >= 0) {
+ if (possibleWidth != 0 && mWidth.isNothing() && mDensity.isNothing()) {
+ mWidth.emplace(possibleWidth);
+ } else {
+ // Valid width descriptor, but width or density were already seen, sizes
+ // support isn't enabled, or it parsed to 0, which is an error per spec
+ mInvalid = true;
+ }
+
+ return;
+ }
+ } else if (*descType == char16_t('h')) {
+ int32_t possibleHeight;
+ // If the value is not a valid non-negative integer, it doesn't match this
+ // descriptor, fall through.
+ if (ParseInteger(valueStr, possibleHeight) && possibleHeight >= 0) {
+ if (possibleHeight != 0 && mFutureCompatHeight.isNothing() &&
+ mDensity.isNothing()) {
+ mFutureCompatHeight.emplace(possibleHeight);
+ } else {
+ // Valid height descriptor, but height or density were already seen, or
+ // it parsed to zero, which is an error per spec
+ mInvalid = true;
+ }
+
+ return;
+ }
+ } else if (*descType == char16_t('x')) {
+ // If the value is not a valid floating point number, it doesn't match this
+ // descriptor, fall through.
+ double possibleDensity = 0.0;
+ if (ParseFloat(valueStr, possibleDensity)) {
+ if (possibleDensity >= 0.0 && mWidth.isNothing() &&
+ mDensity.isNothing() && mFutureCompatHeight.isNothing()) {
+ mDensity.emplace(possibleDensity);
+ } else {
+ // Valid density descriptor, but height or width or density were already
+ // seen, or it parsed to less than zero, which is an error per spec
+ mInvalid = true;
+ }
+
+ return;
+ }
+ }
+
+ // Matched no known descriptor, mark this descriptor set invalid
+ mInvalid = true;
+}
+
+bool ResponsiveImageDescriptors::Valid() {
+ return !mInvalid && !(mFutureCompatHeight.isSome() && mWidth.isNothing());
+}
+
+void ResponsiveImageDescriptors::FillCandidate(
+ ResponsiveImageCandidate& aCandidate) {
+ if (!Valid()) {
+ aCandidate.SetParameterInvalid();
+ } else if (mWidth.isSome()) {
+ MOZ_ASSERT(mDensity.isNothing()); // Shouldn't be valid
+
+ aCandidate.SetParameterAsComputedWidth(*mWidth);
+ } else if (mDensity.isSome()) {
+ MOZ_ASSERT(mWidth.isNothing()); // Shouldn't be valid
+
+ aCandidate.SetParameterAsDensity(*mDensity);
+ } else {
+ // A valid set of descriptors with no density nor width (e.g. an empty set)
+ // becomes 1.0 density, per spec
+ aCandidate.SetParameterAsDensity(1.0);
+ }
+}
+
+bool ResponsiveImageCandidate::ConsumeDescriptors(
+ nsAString::const_iterator& aIter,
+ const nsAString::const_iterator& aIterEnd) {
+ nsAString::const_iterator& iter = aIter;
+ const nsAString::const_iterator& end = aIterEnd;
+
+ bool inParens = false;
+
+ ResponsiveImageDescriptors descriptors;
+
+ // Parse descriptor list.
+ // This corresponds to the descriptor parsing loop from:
+ // https://html.spec.whatwg.org/#parse-a-srcset-attribute
+
+ // Skip initial whitespace
+ for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
+ ;
+
+ nsAString::const_iterator currentDescriptor = iter;
+
+ for (;; iter++) {
+ if (iter == end) {
+ descriptors.AddDescriptor(Substring(currentDescriptor, iter));
+ break;
+ } else if (inParens) {
+ if (*iter == char16_t(')')) {
+ inParens = false;
+ }
+ } else {
+ if (*iter == char16_t(',')) {
+ // End of descriptors, flush current descriptor and advance past comma
+ // before breaking
+ descriptors.AddDescriptor(Substring(currentDescriptor, iter));
+ iter++;
+ break;
+ }
+ if (nsContentUtils::IsHTMLWhitespace(*iter)) {
+ // End of current descriptor, consume it, skip spaces
+ // ("After descriptor" state in spec) before continuing
+ descriptors.AddDescriptor(Substring(currentDescriptor, iter));
+ for (; iter != end && nsContentUtils::IsHTMLWhitespace(*iter); ++iter)
+ ;
+ if (iter == end) {
+ break;
+ }
+ currentDescriptor = iter;
+ // Leave one whitespace so the loop advances to this position next
+ // iteration
+ iter--;
+ } else if (*iter == char16_t('(')) {
+ inParens = true;
+ }
+ }
+ }
+
+ descriptors.FillCandidate(*this);
+
+ return IsValid();
+}
+
+bool ResponsiveImageCandidate::HasSameParameter(
+ const ResponsiveImageCandidate& aOther) const {
+ if (aOther.mType != mType) {
+ return false;
+ }
+
+ if (mType == CandidateType::Default) {
+ return true;
+ }
+
+ if (mType == CandidateType::Density) {
+ return aOther.mValue.mDensity == mValue.mDensity;
+ }
+
+ if (mType == CandidateType::Invalid) {
+ MOZ_ASSERT_UNREACHABLE("Comparing invalid candidates?");
+ return true;
+ }
+
+ if (mType == CandidateType::ComputedFromWidth) {
+ return aOther.mValue.mWidth == mValue.mWidth;
+ }
+
+ MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum");
+ return false;
+}
+
+double ResponsiveImageCandidate::Density(
+ ResponsiveImageSelector* aSelector) const {
+ if (mType == CandidateType::ComputedFromWidth) {
+ double width;
+ if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
+ return 1.0;
+ }
+ return Density(width);
+ }
+
+ // Other types don't need matching width
+ MOZ_ASSERT(mType == CandidateType::Default || mType == CandidateType::Density,
+ "unhandled candidate type");
+ return Density(-1);
+}
+
+void ResponsiveImageCandidate::AppendDescriptors(
+ nsAString& aDescriptors) const {
+ MOZ_ASSERT(IsValid());
+ switch (mType) {
+ case CandidateType::Default:
+ case CandidateType::Invalid:
+ return;
+ case CandidateType::ComputedFromWidth:
+ aDescriptors.Append(' ');
+ aDescriptors.AppendInt(mValue.mWidth);
+ aDescriptors.Append('w');
+ return;
+ case CandidateType::Density:
+ aDescriptors.Append(' ');
+ aDescriptors.AppendFloat(mValue.mDensity);
+ aDescriptors.Append('x');
+ return;
+ }
+}
+
+double ResponsiveImageCandidate::Density(double aMatchingWidth) const {
+ if (mType == CandidateType::Invalid) {
+ MOZ_ASSERT(false, "Getting density for uninitialized candidate");
+ return 1.0;
+ }
+
+ if (mType == CandidateType::Default) {
+ return 1.0;
+ }
+
+ if (mType == CandidateType::Density) {
+ return mValue.mDensity;
+ }
+ if (mType == CandidateType::ComputedFromWidth) {
+ if (aMatchingWidth < 0) {
+ MOZ_ASSERT(
+ false,
+ "Don't expect to have a negative matching width at this point");
+ return 1.0;
+ }
+ double density = double(mValue.mWidth) / aMatchingWidth;
+ MOZ_ASSERT(density > 0.0);
+ return density;
+ }
+
+ MOZ_ASSERT(false, "Unknown candidate type");
+ return 1.0;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ResponsiveImageSelector.h b/dom/base/ResponsiveImageSelector.h
new file mode 100644
index 0000000000..2fee92409d
--- /dev/null
+++ b/dom/base/ResponsiveImageSelector.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_responsiveimageselector_h__
+#define mozilla_dom_responsiveimageselector_h__
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/FunctionRef.h"
+#include "nsISupports.h"
+#include "nsIContent.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsMediaQuery;
+class nsCSSValue;
+
+namespace mozilla::dom {
+
+class ResponsiveImageCandidate;
+
+class ResponsiveImageSelector {
+ friend class ResponsiveImageCandidate;
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResponsiveImageSelector)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResponsiveImageSelector)
+
+ explicit ResponsiveImageSelector(nsIContent* aContent);
+ explicit ResponsiveImageSelector(dom::Document* aDocument);
+
+ // Parses the raw candidates and calls into the callback for each one of them.
+ static void ParseSourceSet(const nsAString& aSrcSet,
+ FunctionRef<void(ResponsiveImageCandidate&&)>);
+
+ // NOTE ABOUT CURRENT SELECTION
+ //
+ // The best candidate is selected lazily when GetSelectedImage*() is
+ // called, or when SelectImage() is called explicitly. This result
+ // is then cached until either invalidated by further Set*() calls,
+ // or explicitly by replaced by SelectImage(aReselect = true).
+ //
+ // Because the selected image depends on external variants like
+ // viewport size and device pixel ratio, the time at which image
+ // selection occurs can affect the result.
+
+ // Given a srcset string, parse and replace current candidates (does not
+ // replace default source)
+ bool SetCandidatesFromSourceSet(const nsAString& aSrcSet,
+ nsIPrincipal* aTriggeringPrincipal = nullptr);
+
+ // Fill the source sizes from a valid sizes descriptor. Returns false if
+ // descriptor is invalid.
+ bool SetSizesFromDescriptor(const nsAString& aSizesDescriptor);
+
+ // Set the default source, treated as the least-precedence 1.0 density source.
+ void SetDefaultSource(const nsAString& aURLString,
+ nsIPrincipal* aPrincipal = nullptr);
+
+ uint32_t NumCandidates(bool aIncludeDefault = true);
+
+ // If this was created for a specific content. May be null if we were only
+ // created for a document.
+ nsIContent* Content();
+
+ // The document we were created for, or the owner document of the content if
+ // we were created for a specific nsIContent.
+ dom::Document* Document();
+
+ // Get the url and density for the selected best candidate. These
+ // implicitly cause an image to be selected if necessary.
+ already_AddRefed<nsIURI> GetSelectedImageURL();
+ // Returns false if there is no selected image
+ bool GetSelectedImageURLSpec(nsAString& aResult);
+ double GetSelectedImageDensity();
+ nsIPrincipal* GetSelectedImageTriggeringPrincipal();
+
+ // Runs image selection now if necessary. If an image has already
+ // been choosen, takes no action unless aReselect is true.
+ //
+ // aReselect - Always re-run selection, replacing the previously
+ // choosen image.
+ // return - true if the selected image result changed.
+ bool SelectImage(bool aReselect = false);
+
+ protected:
+ virtual ~ResponsiveImageSelector();
+
+ private:
+ // Append a candidate unless its selector is duplicated by a higher priority
+ // candidate
+ void AppendCandidateIfUnique(ResponsiveImageCandidate&& aCandidate);
+
+ // Append a default candidate with this URL if necessary. Does not check if
+ // the array already contains one, use SetDefaultSource instead.
+ void MaybeAppendDefaultCandidate();
+
+ // Get index of selected candidate, triggering selection if necessary.
+ int GetSelectedCandidateIndex();
+
+ // Forget currently selected candidate. (See "NOTE ABOUT CURRENT SELECTION"
+ // above.)
+ void ClearSelectedCandidate();
+
+ // Compute a density from a Candidate width. Returns false if sizes were not
+ // specified for this selector.
+ //
+ // aContext is the presContext to use for current viewport sizing, null will
+ // use the associated content's context.
+ bool ComputeFinalWidthForCurrentViewport(double* aWidth);
+
+ nsCOMPtr<nsINode> mOwnerNode;
+ // The cached URL for default candidate.
+ nsString mDefaultSourceURL;
+ nsCOMPtr<nsIPrincipal> mDefaultSourceTriggeringPrincipal;
+ // If this array contains an eCandidateType_Default, it should be the last
+ // element, such that the Setters can preserve/replace it respectively.
+ nsTArray<ResponsiveImageCandidate> mCandidates;
+ int mSelectedCandidateIndex;
+ // The cached resolved URL for mSelectedCandidateIndex, such that we only
+ // resolve the absolute URL at selection time
+ nsCOMPtr<nsIURI> mSelectedCandidateURL;
+
+ // Servo bits.
+ UniquePtr<StyleSourceSizeList> mServoSourceSizeList;
+};
+
+class ResponsiveImageCandidate {
+ public:
+ ResponsiveImageCandidate();
+ ResponsiveImageCandidate(const ResponsiveImageCandidate&) = delete;
+ ResponsiveImageCandidate(ResponsiveImageCandidate&&) = default;
+
+ void SetURLSpec(const nsAString& aURLString);
+ void SetTriggeringPrincipal(nsIPrincipal* aPrincipal);
+ // Set this as a default-candidate. This behaves the same as density 1.0, but
+ // has a differing type such that it can be replaced by subsequent
+ // SetDefaultSource calls.
+ void SetParameterDefault();
+
+ // Set this candidate as a by-density candidate with specified density.
+ void SetParameterAsDensity(double aDensity);
+ void SetParameterAsComputedWidth(int32_t aWidth);
+
+ void SetParameterInvalid();
+
+ // Consume descriptors from a string defined by aIter and aIterEnd, adjusts
+ // aIter to the end of data consumed.
+ // Returns false if descriptors string is invalid, but still parses to the end
+ // of descriptors microsyntax.
+ bool ConsumeDescriptors(nsAString::const_iterator& aIter,
+ const nsAString::const_iterator& aIterEnd);
+
+ // Check if our parameter (which does not include the url) is identical
+ bool HasSameParameter(const ResponsiveImageCandidate& aOther) const;
+
+ const nsAString& URLString() const { return mURLString; }
+ nsIPrincipal* TriggeringPrincipal() const { return mTriggeringPrincipal; }
+
+ // Compute and return the density relative to a selector.
+ double Density(ResponsiveImageSelector* aSelector) const;
+ // If the width is already known. Useful when iterating over candidates to
+ // avoid having each call re-compute the width.
+ double Density(double aMatchingWidth) const;
+
+ // Append the descriptors for this candidate serialized as a string.
+ void AppendDescriptors(nsAString&) const;
+
+ bool IsValid() const { return mType != CandidateType::Invalid; }
+
+ // If this selector is computed from the selector's matching width.
+ bool IsComputedFromWidth() const {
+ return mType == CandidateType::ComputedFromWidth;
+ }
+
+ bool IsDefault() const { return mType == CandidateType::Default; }
+
+ enum class CandidateType : uint8_t {
+ Invalid,
+ Density,
+ // Treated as 1.0 density, but a separate type so we can update the
+ // responsive candidates and default separately
+ Default,
+ ComputedFromWidth
+ };
+
+ CandidateType Type() const { return mType; }
+
+ private:
+ nsString mURLString;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ CandidateType mType;
+ union {
+ double mDensity;
+ int32_t mWidth;
+ } mValue;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_responsiveimageselector_h__
diff --git a/dom/base/RustTypes.h b/dom/base/RustTypes.h
new file mode 100644
index 0000000000..2bb62be902
--- /dev/null
+++ b/dom/base/RustTypes.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RustTypes_h
+#define mozilla_dom_RustTypes_h
+
+#include "nsDebug.h"
+
+#define STATE_HELPERS(Type) \
+ using InternalType = decltype(bits); \
+ \
+ constexpr Type() : bits{0} {} \
+ \
+ explicit constexpr Type(InternalType aBits) : bits{aBits} {} \
+ \
+ bool IsEmpty() const { return bits == 0; } \
+ \
+ bool HasAtLeastOneOfStates(Type aStates) const { \
+ return bool((*this) & aStates); \
+ } \
+ \
+ bool HasState(Type aState) const { \
+ NS_ASSERTION(!(aState.bits & (aState.bits - 1)), \
+ "When calling HasState, " \
+ "argument has to contain only one state!"); \
+ return HasAtLeastOneOfStates(aState); \
+ } \
+ \
+ bool HasAllStates(Type aStates) const { \
+ return ((*this) & aStates) == aStates; \
+ } \
+ \
+ InternalType GetInternalValue() const { return bits; }
+
+#include "mozilla/dom/GeneratedElementDocumentState.h"
+
+#endif
diff --git a/dom/base/SameProcessMessageQueue.cpp b/dom/base/SameProcessMessageQueue.cpp
new file mode 100644
index 0000000000..bfeffd508a
--- /dev/null
+++ b/dom/base/SameProcessMessageQueue.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SameProcessMessageQueue.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+SameProcessMessageQueue* SameProcessMessageQueue::sSingleton;
+
+SameProcessMessageQueue::SameProcessMessageQueue() = default;
+
+SameProcessMessageQueue::~SameProcessMessageQueue() {
+ // This code should run during shutdown, and we should already have pumped the
+ // event loop. So we should only see messages here if someone is sending
+ // messages pretty late in shutdown.
+ NS_WARNING_ASSERTION(mQueue.IsEmpty(),
+ "Shouldn't send messages during shutdown");
+ sSingleton = nullptr;
+}
+
+void SameProcessMessageQueue::Push(Runnable* aRunnable) {
+ mQueue.AppendElement(aRunnable);
+ NS_DispatchToCurrentThread(aRunnable);
+}
+
+void SameProcessMessageQueue::Flush() {
+ const nsTArray<RefPtr<Runnable>> queue = std::move(mQueue);
+ for (size_t i = 0; i < queue.Length(); i++) {
+ queue[i]->Run();
+ }
+}
+
+/* static */
+SameProcessMessageQueue* SameProcessMessageQueue::Get() {
+ if (!sSingleton) {
+ sSingleton = new SameProcessMessageQueue();
+ }
+ return sSingleton;
+}
+
+SameProcessMessageQueue::Runnable::Runnable() : mDispatched(false) {}
+
+NS_IMPL_ISUPPORTS(SameProcessMessageQueue::Runnable, nsIRunnable)
+
+nsresult SameProcessMessageQueue::Runnable::Run() {
+ if (mDispatched) {
+ return NS_OK;
+ }
+
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->mQueue.RemoveElement(this);
+
+ mDispatched = true;
+ return HandleMessage();
+}
diff --git a/dom/base/SameProcessMessageQueue.h b/dom/base/SameProcessMessageQueue.h
new file mode 100644
index 0000000000..b8b79fd323
--- /dev/null
+++ b/dom/base/SameProcessMessageQueue.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SameProcessMessageQueue_h
+#define mozilla_dom_SameProcessMessageQueue_h
+
+#include "nsIRunnable.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+class SameProcessMessageQueue {
+ public:
+ SameProcessMessageQueue();
+ virtual ~SameProcessMessageQueue();
+
+ class Runnable : public nsIRunnable {
+ public:
+ explicit Runnable();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ virtual nsresult HandleMessage() = 0;
+
+ protected:
+ virtual ~Runnable() = default;
+
+ private:
+ bool mDispatched;
+ };
+
+ void Push(Runnable* aRunnable);
+ void Flush();
+
+ static SameProcessMessageQueue* Get();
+
+ private:
+ nsTArray<RefPtr<Runnable>> mQueue;
+ static SameProcessMessageQueue* sSingleton;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SameProcessMessageQueue_h
diff --git a/dom/base/ScreenLuminance.cpp b/dom/base/ScreenLuminance.cpp
new file mode 100644
index 0000000000..54c329b5aa
--- /dev/null
+++ b/dom/base/ScreenLuminance.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScreenLuminance.h"
+#include "nsScreen.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScreenLuminance, mScreen)
+
+JSObject* ScreenLuminance::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ScreenLuminance_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/ScreenLuminance.h b/dom/base/ScreenLuminance.h
new file mode 100644
index 0000000000..5595fd734b
--- /dev/null
+++ b/dom/base/ScreenLuminance.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScreenLuminance_h
+#define mozilla_dom_ScreenLuminance_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsWrapperCache.h"
+
+class nsScreen;
+
+namespace mozilla::dom {
+
+class ScreenLuminance final : public nsWrapperCache {
+ public:
+ // Ref counting and cycle collection
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ScreenLuminance)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(ScreenLuminance)
+
+ // WebIDL methods
+ double Min() const { return mMin; }
+ double Max() const { return mMax; }
+ double MaxAverage() const { return mMaxAverage; }
+ // End WebIDL methods
+
+ ScreenLuminance(nsScreen* aScreen, double aMin, double aMax,
+ double aMaxAverage)
+ : mScreen(aScreen), mMin(aMin), mMax(aMax), mMaxAverage(aMaxAverage) {}
+
+ nsScreen* GetParentObject() const { return mScreen; }
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ virtual ~ScreenLuminance() = default;
+
+ RefPtr<nsScreen> mScreen;
+ double mMin;
+ double mMax;
+ double mMaxAverage;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ScreenLuminance_h
diff --git a/dom/base/ScreenOrientation.cpp b/dom/base/ScreenOrientation.cpp
new file mode 100644
index 0000000000..a00c40465a
--- /dev/null
+++ b/dom/base/ScreenOrientation.cpp
@@ -0,0 +1,894 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScreenOrientation.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsGlobalWindow.h"
+#include "nsSandboxFlags.h"
+#include "nsScreen.h"
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ScreenOrientation, DOMEventTargetHelper,
+ mScreen);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScreenOrientation)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(ScreenOrientation, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ScreenOrientation, DOMEventTargetHelper)
+
+static OrientationType InternalOrientationToType(
+ hal::ScreenOrientation aOrientation) {
+ switch (aOrientation) {
+ case hal::ScreenOrientation::PortraitPrimary:
+ return OrientationType::Portrait_primary;
+ case hal::ScreenOrientation::PortraitSecondary:
+ return OrientationType::Portrait_secondary;
+ case hal::ScreenOrientation::LandscapePrimary:
+ return OrientationType::Landscape_primary;
+ case hal::ScreenOrientation::LandscapeSecondary:
+ return OrientationType::Landscape_secondary;
+ default:
+ MOZ_CRASH("Bad aOrientation value");
+ }
+}
+
+static hal::ScreenOrientation OrientationTypeToInternal(
+ OrientationType aOrientation) {
+ switch (aOrientation) {
+ case OrientationType::Portrait_primary:
+ return hal::ScreenOrientation::PortraitPrimary;
+ case OrientationType::Portrait_secondary:
+ return hal::ScreenOrientation::PortraitSecondary;
+ case OrientationType::Landscape_primary:
+ return hal::ScreenOrientation::LandscapePrimary;
+ case OrientationType::Landscape_secondary:
+ return hal::ScreenOrientation::LandscapeSecondary;
+ default:
+ MOZ_CRASH("Bad aOrientation value");
+ }
+}
+
+ScreenOrientation::ScreenOrientation(nsPIDOMWindowInner* aWindow,
+ nsScreen* aScreen)
+ : DOMEventTargetHelper(aWindow), mScreen(aScreen) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aScreen);
+
+ mAngle = aScreen->GetOrientationAngle();
+ mType = InternalOrientationToType(aScreen->GetOrientationType());
+
+ Document* doc = GetResponsibleDocument();
+ BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
+ if (bc && !bc->IsDiscarded() && !bc->InRDMPane()) {
+ MOZ_ALWAYS_SUCCEEDS(bc->SetCurrentOrientation(mType, mAngle));
+ }
+}
+
+ScreenOrientation::~ScreenOrientation() {
+ if (mTriedToLockDeviceOrientation) {
+ UnlockDeviceOrientation();
+ } else {
+ CleanupFullscreenListener();
+ }
+
+ MOZ_ASSERT(!mFullscreenListener);
+}
+
+class ScreenOrientation::FullscreenEventListener final
+ : public nsIDOMEventListener {
+ ~FullscreenEventListener() = default;
+
+ public:
+ FullscreenEventListener() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+};
+
+class ScreenOrientation::VisibleEventListener final
+ : public nsIDOMEventListener {
+ ~VisibleEventListener() = default;
+
+ public:
+ VisibleEventListener() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+};
+
+class ScreenOrientation::LockOrientationTask final : public nsIRunnable {
+ ~LockOrientationTask();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ LockOrientationTask(ScreenOrientation* aScreenOrientation, Promise* aPromise,
+ hal::ScreenOrientation aOrientationLock,
+ Document* aDocument, bool aIsFullscreen);
+
+ protected:
+ bool OrientationLockContains(OrientationType aOrientationType);
+
+ RefPtr<ScreenOrientation> mScreenOrientation;
+ RefPtr<Promise> mPromise;
+ hal::ScreenOrientation mOrientationLock;
+ WeakPtr<Document> mDocument;
+ bool mIsFullscreen;
+};
+
+NS_IMPL_ISUPPORTS(ScreenOrientation::LockOrientationTask, nsIRunnable)
+
+ScreenOrientation::LockOrientationTask::LockOrientationTask(
+ ScreenOrientation* aScreenOrientation, Promise* aPromise,
+ hal::ScreenOrientation aOrientationLock, Document* aDocument,
+ bool aIsFullscreen)
+ : mScreenOrientation(aScreenOrientation),
+ mPromise(aPromise),
+ mOrientationLock(aOrientationLock),
+ mDocument(aDocument),
+ mIsFullscreen(aIsFullscreen) {
+ MOZ_ASSERT(aScreenOrientation);
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(aDocument);
+}
+
+ScreenOrientation::LockOrientationTask::~LockOrientationTask() = default;
+
+bool ScreenOrientation::LockOrientationTask::OrientationLockContains(
+ OrientationType aOrientationType) {
+ return bool(mOrientationLock & OrientationTypeToInternal(aOrientationType));
+}
+
+NS_IMETHODIMP
+ScreenOrientation::LockOrientationTask::Run() {
+ if (!mPromise) {
+ return NS_OK;
+ }
+
+ if (!mDocument) {
+ mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> owner = mScreenOrientation->GetOwner();
+ if (!owner || !owner->IsFullyActive()) {
+ mPromise->MaybeRejectWithAbortError("The document is not fully active.");
+ return NS_OK;
+ }
+
+ // Step to lock the orientation as defined in the spec.
+ if (mDocument->GetOrientationPendingPromise() != mPromise) {
+ // The document's pending promise is not associated with this task
+ // to lock orientation. There has since been another request to
+ // lock orientation, thus we don't need to do anything. Old promise
+ // should be been rejected.
+ return NS_OK;
+ }
+
+ if (mDocument->Hidden()) {
+ // Active orientation lock is not the document's orientation lock.
+ mPromise->MaybeResolveWithUndefined();
+ mDocument->ClearOrientationPendingPromise();
+ return NS_OK;
+ }
+
+ if (mOrientationLock == hal::ScreenOrientation::None) {
+ mScreenOrientation->UnlockDeviceOrientation();
+ mPromise->MaybeResolveWithUndefined();
+ mDocument->ClearOrientationPendingPromise();
+ return NS_OK;
+ }
+
+ BrowsingContext* bc = mDocument->GetBrowsingContext();
+ if (!bc) {
+ mPromise->MaybeResolveWithUndefined();
+ mDocument->ClearOrientationPendingPromise();
+ return NS_OK;
+ }
+
+ OrientationType previousOrientationType = bc->GetCurrentOrientationType();
+ mScreenOrientation->LockDeviceOrientation(mOrientationLock, mIsFullscreen)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, previousOrientationType](
+ const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
+ if (self->mPromise->State() != Promise::PromiseState::Pending) {
+ // mPromise is already resolved or rejected by
+ // DispatchChangeEventAndResolvePromise() or
+ // AbortInProcessOrientationPromises().
+ return;
+ }
+
+ if (!self->mDocument) {
+ self->mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ if (self->mDocument->GetOrientationPendingPromise() !=
+ self->mPromise) {
+ // mPromise is old promise now and document has new promise by
+ // later `orientation.lock` call. Old promise is already rejected
+ // by AbortInProcessOrientationPromises()
+ return;
+ }
+ if (aValue.IsResolve()) {
+ // LockDeviceOrientation won't change orientation, so change
+ // event isn't fired.
+ if (BrowsingContext* bc = self->mDocument->GetBrowsingContext()) {
+ OrientationType currentOrientationType =
+ bc->GetCurrentOrientationType();
+ if ((previousOrientationType == currentOrientationType &&
+ self->OrientationLockContains(currentOrientationType)) ||
+ (self->mOrientationLock ==
+ hal::ScreenOrientation::Default &&
+ bc->GetCurrentOrientationAngle() == 0)) {
+ // Orientation lock will not cause an orientation change, so
+ // we need to manually resolve the promise here.
+ self->mPromise->MaybeResolveWithUndefined();
+ self->mDocument->ClearOrientationPendingPromise();
+ }
+ }
+ return;
+ }
+ self->mPromise->MaybeReject(aValue.RejectValue());
+ self->mDocument->ClearOrientationPendingPromise();
+ });
+
+ return NS_OK;
+}
+
+already_AddRefed<Promise> ScreenOrientation::Lock(
+ OrientationLockType aOrientation, ErrorResult& aRv) {
+ hal::ScreenOrientation orientation = hal::ScreenOrientation::None;
+
+ switch (aOrientation) {
+ case OrientationLockType::Any:
+ orientation = hal::ScreenOrientation::PortraitPrimary |
+ hal::ScreenOrientation::PortraitSecondary |
+ hal::ScreenOrientation::LandscapePrimary |
+ hal::ScreenOrientation::LandscapeSecondary;
+ break;
+ case OrientationLockType::Natural:
+ orientation |= hal::ScreenOrientation::Default;
+ break;
+ case OrientationLockType::Landscape:
+ orientation = hal::ScreenOrientation::LandscapePrimary |
+ hal::ScreenOrientation::LandscapeSecondary;
+ break;
+ case OrientationLockType::Portrait:
+ orientation = hal::ScreenOrientation::PortraitPrimary |
+ hal::ScreenOrientation::PortraitSecondary;
+ break;
+ case OrientationLockType::Portrait_primary:
+ orientation = hal::ScreenOrientation::PortraitPrimary;
+ break;
+ case OrientationLockType::Portrait_secondary:
+ orientation = hal::ScreenOrientation::PortraitSecondary;
+ break;
+ case OrientationLockType::Landscape_primary:
+ orientation = hal::ScreenOrientation::LandscapePrimary;
+ break;
+ case OrientationLockType::Landscape_secondary:
+ orientation = hal::ScreenOrientation::LandscapeSecondary;
+ break;
+ default:
+ NS_WARNING("Unexpected orientation type");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return LockInternal(orientation, aRv);
+}
+
+// Wait for document entered fullscreen.
+class FullscreenWaitListener final : public nsIDOMEventListener {
+ private:
+ ~FullscreenWaitListener() = default;
+
+ public:
+ FullscreenWaitListener() = default;
+
+ NS_DECL_ISUPPORTS
+
+ // When we have pending fullscreen request, we will wait for the completion or
+ // cancel of it.
+ RefPtr<GenericPromise> Promise(Document* aDocument) {
+ if (aDocument->Fullscreen()) {
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }
+
+ if (NS_FAILED(InstallEventListener(aDocument))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ MOZ_ASSERT(aDocument->HasPendingFullscreenRequests());
+ return mHolder.Ensure(__func__);
+ }
+
+ NS_IMETHODIMP HandleEvent(Event* aEvent) override {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("pagehide")) {
+ mHolder.Reject(NS_ERROR_FAILURE, __func__);
+ CleanupEventListener();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange") ||
+ eventType.EqualsLiteral("fullscreenerror") ||
+ eventType.EqualsLiteral("pagehide"));
+ if (mDocument->Fullscreen()) {
+ mHolder.Resolve(true, __func__);
+ } else {
+ mHolder.Reject(NS_ERROR_FAILURE, __func__);
+ }
+ CleanupEventListener();
+ return NS_OK;
+ }
+
+ private:
+ nsresult InstallEventListener(Document* aDoc) {
+ if (mDocument) {
+ return NS_OK;
+ }
+
+ mDocument = aDoc;
+ nsresult rv = aDoc->AddSystemEventListener(u"fullscreenchange"_ns, this,
+ /* aUseCapture = */ true);
+ if (NS_FAILED(rv)) {
+ CleanupEventListener();
+ return rv;
+ }
+
+ rv = aDoc->AddSystemEventListener(u"fullscreenerror"_ns, this,
+ /* aUseCapture = */ true);
+ if (NS_FAILED(rv)) {
+ CleanupEventListener();
+ return rv;
+ }
+
+ nsPIDOMWindowOuter* window = aDoc->GetWindow();
+ nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+ if (!target) {
+ CleanupEventListener();
+ return NS_ERROR_FAILURE;
+ }
+ rv = target->AddSystemEventListener(u"pagehide"_ns, this,
+ /* aUseCapture = */ true,
+ /* aWantsUntrusted = */ false);
+ if (NS_FAILED(rv)) {
+ CleanupEventListener();
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ void CleanupEventListener() {
+ if (!mDocument) {
+ return;
+ }
+ RefPtr<FullscreenWaitListener> kungFuDeathGrip(this);
+ mDocument->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
+ mDocument->RemoveSystemEventListener(u"fullscreenerror"_ns, this, true);
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+ nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+ if (target) {
+ target->RemoveSystemEventListener(u"pagehide"_ns, this, true);
+ }
+ mDocument = nullptr;
+ }
+
+ MozPromiseHolder<GenericPromise> mHolder;
+ RefPtr<Document> mDocument;
+};
+
+NS_IMPL_ISUPPORTS(FullscreenWaitListener, nsIDOMEventListener)
+
+void ScreenOrientation::AbortInProcessOrientationPromises(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ aBrowsingContext = aBrowsingContext->Top();
+ aBrowsingContext->PreOrderWalk([](BrowsingContext* aContext) {
+ nsIDocShell* docShell = aContext->GetDocShell();
+ if (docShell) {
+ Document* doc = docShell->GetDocument();
+ if (doc) {
+ Promise* promise = doc->GetOrientationPendingPromise();
+ if (promise) {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ doc->ClearOrientationPendingPromise();
+ }
+ }
+ }
+ });
+}
+
+already_AddRefed<Promise> ScreenOrientation::LockInternal(
+ hal::ScreenOrientation aOrientation, ErrorResult& aRv) {
+ // Steps to apply an orientation lock as defined in spec.
+
+ // Step 1.
+ // Let document be this's relevant global object's associated Document.
+
+ Document* doc = GetResponsibleDocument();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Step 2.
+ // If document is not fully active, return a promise rejected with an
+ // "InvalidStateError" DOMException.
+
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (NS_WARN_IF(!owner)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = owner->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(owner);
+ MOZ_ASSERT(go);
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!owner->IsFullyActive()) {
+ p->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return p.forget();
+ }
+
+ // Step 3.
+ // If document has the sandboxed orientation lock browsing context flag set,
+ // or doesn't meet the pre-lock conditions, or locking would be a security
+ // risk, return a promise rejected with a "SecurityError" DOMException and
+ // abort these steps.
+
+ LockPermission perm = GetLockOrientationPermission(true);
+ if (perm == LOCK_DENIED) {
+ p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return p.forget();
+ }
+
+ // Step 4.
+ // If the user agent does not support locking the screen orientation to
+ // orientation, return a promise rejected with a "NotSupportedError"
+ // DOMException and abort these steps.
+
+#if !defined(MOZ_WIDGET_ANDROID) && !defined(XP_WIN)
+ // User agent does not support locking the screen orientation.
+ p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return p.forget();
+#else
+ // Bypass locking screen orientation if preference is false
+ if (!StaticPrefs::dom_screenorientation_allow_lock()) {
+ p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return p.forget();
+ }
+
+ RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
+ bc = bc ? bc->Top() : nullptr;
+ if (!bc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ bc->SetOrientationLock(aOrientation, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ AbortInProcessOrientationPromises(bc);
+ dom::ContentChild::GetSingleton()->SendAbortOtherOrientationPendingPromises(
+ bc);
+
+ if (!doc->SetOrientationPendingPromise(p)) {
+ p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return p.forget();
+ }
+
+ if (perm == LOCK_ALLOWED || doc->Fullscreen()) {
+ nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
+ this, p, aOrientation, doc, perm == FULLSCREEN_LOCK_ALLOWED);
+ aRv = NS_DispatchToMainThread(lockOrientationTask);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return p.forget();
+ }
+
+ MOZ_ASSERT(perm == FULLSCREEN_LOCK_ALLOWED);
+
+ // Full screen state is pending. We have to wait for the completion.
+ RefPtr<FullscreenWaitListener> listener = new FullscreenWaitListener();
+ RefPtr<Promise> promise = p;
+ listener->Promise(doc)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, promise = std::move(promise), aOrientation,
+ document =
+ RefPtr{doc}](const GenericPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
+ self, promise, aOrientation, document, true);
+ nsresult rv = NS_DispatchToMainThread(lockOrientationTask);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+ // Pending full screen request is canceled or causes an error.
+ if (document->GetOrientationPendingPromise() != promise) {
+ // The document's pending promise is not associated with
+ // this promise.
+ return;
+ }
+ // pre-lock conditions aren't matched.
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ document->ClearOrientationPendingPromise();
+ });
+
+ return p.forget();
+#endif
+}
+
+RefPtr<GenericNonExclusivePromise> ScreenOrientation::LockDeviceOrientation(
+ hal::ScreenOrientation aOrientation, bool aIsFullscreen) {
+ if (!GetOwner()) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
+ __func__);
+ }
+
+ nsCOMPtr<EventTarget> target = GetOwner()->GetDoc();
+ // We need to register a listener so we learn when we leave fullscreen
+ // and when we will have to unlock the screen.
+ // This needs to be done before LockScreenOrientation call to make sure
+ // the locking can be unlocked.
+ if (aIsFullscreen && !target) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
+ __func__);
+ }
+
+ // We are fullscreen and lock has been accepted.
+ if (aIsFullscreen) {
+ if (!mFullscreenListener) {
+ mFullscreenListener = new FullscreenEventListener();
+ }
+
+ nsresult rv = target->AddSystemEventListener(u"fullscreenchange"_ns,
+ mFullscreenListener,
+ /* aUseCapture = */ true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
+ __func__);
+ }
+ }
+
+ mTriedToLockDeviceOrientation = true;
+ return hal::LockScreenOrientation(aOrientation);
+}
+
+void ScreenOrientation::Unlock(ErrorResult& aRv) {
+ if (RefPtr<Promise> p = LockInternal(hal::ScreenOrientation::None, aRv)) {
+ // Don't end up reporting unhandled promise rejection since
+ // screen.orientation.unlock doesn't return promise.
+ MOZ_ALWAYS_TRUE(p->SetAnyPromiseIsHandled());
+ }
+}
+
+void ScreenOrientation::UnlockDeviceOrientation() {
+ hal::UnlockScreenOrientation();
+ CleanupFullscreenListener();
+}
+
+void ScreenOrientation::CleanupFullscreenListener() {
+ if (!mFullscreenListener || !GetOwner()) {
+ mFullscreenListener = nullptr;
+ return;
+ }
+
+ // Remove event listener in case of fullscreen lock.
+ if (nsCOMPtr<EventTarget> target = GetOwner()->GetDoc()) {
+ target->RemoveSystemEventListener(u"fullscreenchange"_ns,
+ mFullscreenListener,
+ /* useCapture */ true);
+ }
+
+ mFullscreenListener = nullptr;
+}
+
+OrientationType ScreenOrientation::DeviceType(CallerType aCallerType) const {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
+ return OrientationType::Landscape_primary;
+ }
+ return mType;
+}
+
+uint16_t ScreenOrientation::DeviceAngle(CallerType aCallerType) const {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
+ return 0;
+ }
+ return mAngle;
+}
+
+OrientationType ScreenOrientation::GetType(CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
+ return OrientationType::Landscape_primary;
+ }
+
+ Document* doc = GetResponsibleDocument();
+ BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
+ if (!bc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return OrientationType::Portrait_primary;
+ }
+
+ return bc->GetCurrentOrientationType();
+}
+
+uint16_t ScreenOrientation::GetAngle(CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
+ return 0;
+ }
+
+ Document* doc = GetResponsibleDocument();
+ BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
+ if (!bc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+
+ return bc->GetCurrentOrientationAngle();
+}
+
+ScreenOrientation::LockPermission
+ScreenOrientation::GetLockOrientationPermission(bool aCheckSandbox) const {
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (!owner) {
+ return LOCK_DENIED;
+ }
+
+ // Chrome can always lock the screen orientation.
+ if (owner->GetBrowsingContext()->IsChrome()) {
+ return LOCK_ALLOWED;
+ }
+
+ nsCOMPtr<Document> doc = owner->GetDoc();
+ if (!doc || doc->Hidden()) {
+ return LOCK_DENIED;
+ }
+
+ // Sandboxed without "allow-orientation-lock"
+ if (aCheckSandbox && doc->GetSandboxFlags() & SANDBOXED_ORIENTATION_LOCK) {
+ return LOCK_DENIED;
+ }
+
+ if (Preferences::GetBool(
+ "dom.screenorientation.testing.non_fullscreen_lock_allow", false)) {
+ return LOCK_ALLOWED;
+ }
+
+ // Other content must be fullscreen in order to lock orientation.
+ return doc->Fullscreen() || doc->HasPendingFullscreenRequests()
+ ? FULLSCREEN_LOCK_ALLOWED
+ : LOCK_DENIED;
+}
+
+Document* ScreenOrientation::GetResponsibleDocument() const {
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (!owner) {
+ return nullptr;
+ }
+
+ return owner->GetDoc();
+}
+
+void ScreenOrientation::MaybeChanged() {
+ Document* doc = GetResponsibleDocument();
+ if (!doc || doc->ShouldResistFingerprinting(RFPTarget::ScreenOrientation)) {
+ return;
+ }
+
+ BrowsingContext* bc = doc->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ hal::ScreenOrientation orientation = mScreen->GetOrientationType();
+ if (orientation != hal::ScreenOrientation::PortraitPrimary &&
+ orientation != hal::ScreenOrientation::PortraitSecondary &&
+ orientation != hal::ScreenOrientation::LandscapePrimary &&
+ orientation != hal::ScreenOrientation::LandscapeSecondary) {
+ // The platform may notify of some other values from
+ // an orientation lock, but we only care about real
+ // changes to screen orientation which result in one of
+ // the values we care about.
+ return;
+ }
+
+ OrientationType previousOrientation = mType;
+ mAngle = mScreen->GetOrientationAngle();
+ mType = InternalOrientationToType(orientation);
+
+ DebugOnly<nsresult> rv;
+ if (mScreen && mType != previousOrientation) {
+ // Use of mozorientationchange is deprecated.
+ rv = mScreen->DispatchTrustedEvent(u"mozorientationchange"_ns);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+ }
+
+ if (doc->Hidden() && !mVisibleListener) {
+ mVisibleListener = new VisibleEventListener();
+ rv = doc->AddSystemEventListener(u"visibilitychange"_ns, mVisibleListener,
+ /* aUseCapture = */ true);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddSystemEventListener failed");
+ return;
+ }
+
+ if (mType != bc->GetCurrentOrientationType()) {
+ rv = bc->SetCurrentOrientation(mType, mAngle);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetCurrentOrientation failed");
+
+ nsCOMPtr<nsIRunnable> runnable = DispatchChangeEventAndResolvePromise();
+ rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+}
+
+void ScreenOrientation::UpdateActiveOrientationLock(
+ hal::ScreenOrientation aOrientation) {
+ if (aOrientation == hal::ScreenOrientation::None) {
+ hal::UnlockScreenOrientation();
+ } else {
+ hal::LockScreenOrientation(aOrientation)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
+ NS_WARNING_ASSERTION(aValue.IsResolve(),
+ "hal::LockScreenOrientation failed");
+ });
+ }
+}
+
+nsCOMPtr<nsIRunnable>
+ScreenOrientation::DispatchChangeEventAndResolvePromise() {
+ RefPtr<Document> doc = GetResponsibleDocument();
+ RefPtr<ScreenOrientation> self = this;
+ return NS_NewRunnableFunction(
+ "dom::ScreenOrientation::DispatchChangeEvent", [self, doc]() {
+ DebugOnly<nsresult> rv = self->DispatchTrustedEvent(u"change"_ns);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+ if (doc) {
+ Promise* pendingPromise = doc->GetOrientationPendingPromise();
+ if (pendingPromise) {
+ pendingPromise->MaybeResolveWithUndefined();
+ doc->ClearOrientationPendingPromise();
+ }
+ }
+ });
+}
+
+JSObject* ScreenOrientation::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ScreenOrientation_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS(ScreenOrientation::VisibleEventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+ScreenOrientation::VisibleEventListener::HandleEvent(Event* aEvent) {
+ // Document may have become visible, if the page is visible, run the steps
+ // following the "now visible algorithm" as specified.
+ MOZ_ASSERT(aEvent->GetCurrentTarget());
+ nsCOMPtr<nsINode> eventTargetNode =
+ nsINode::FromEventTarget(aEvent->GetCurrentTarget());
+ if (!eventTargetNode || !eventTargetNode->IsDocument() ||
+ eventTargetNode->AsDocument()->Hidden()) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc = eventTargetNode->AsDocument();
+ auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow());
+ if (!win) {
+ return NS_OK;
+ }
+
+ ErrorResult rv;
+ nsScreen* screen = win->GetScreen(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ MOZ_ASSERT(screen);
+ ScreenOrientation* orientation = screen->Orientation();
+ MOZ_ASSERT(orientation);
+
+ doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true);
+
+ BrowsingContext* bc = doc->GetBrowsingContext();
+ if (bc && bc->GetCurrentOrientationType() !=
+ orientation->DeviceType(CallerType::System)) {
+ nsresult result =
+ bc->SetCurrentOrientation(orientation->DeviceType(CallerType::System),
+ orientation->DeviceAngle(CallerType::System));
+ NS_ENSURE_SUCCESS(result, result);
+
+ nsCOMPtr<nsIRunnable> runnable =
+ orientation->DispatchChangeEventAndResolvePromise();
+ rv = NS_DispatchToMainThread(runnable);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(ScreenOrientation::FullscreenEventListener,
+ nsIDOMEventListener)
+
+NS_IMETHODIMP
+ScreenOrientation::FullscreenEventListener::HandleEvent(Event* aEvent) {
+#ifdef DEBUG
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange"));
+#endif
+
+ EventTarget* target = aEvent->GetCurrentTarget();
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->IsNode());
+ RefPtr<Document> doc = nsINode::FromEventTarget(target)->AsDocument();
+ MOZ_ASSERT(doc);
+
+ // We have to make sure that the event we got is the event sent when
+ // fullscreen is disabled because we could get one when fullscreen
+ // got enabled if the lock call is done at the same moment.
+ if (doc->Fullscreen()) {
+ return NS_OK;
+ }
+
+ BrowsingContext* bc = doc->GetBrowsingContext();
+ bc = bc ? bc->Top() : nullptr;
+ if (bc) {
+ bc->SetOrientationLock(hal::ScreenOrientation::None, IgnoreErrors());
+ }
+
+ hal::UnlockScreenOrientation();
+
+ target->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
+ return NS_OK;
+}
diff --git a/dom/base/ScreenOrientation.h b/dom/base/ScreenOrientation.h
new file mode 100644
index 0000000000..0d52ae4907
--- /dev/null
+++ b/dom/base/ScreenOrientation.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScreenOrientation_h
+#define mozilla_dom_ScreenOrientation_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ScreenOrientationBinding.h"
+#include "mozilla/HalScreenConfiguration.h"
+#include "mozilla/MozPromise.h"
+
+class nsScreen;
+
+namespace mozilla::dom {
+
+class Promise;
+
+class ScreenOrientation final : public DOMEventTargetHelper {
+ // nsScreen has deprecated API that shares implementation.
+ friend class ::nsScreen;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ScreenOrientation,
+ mozilla::DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(change)
+
+ // Called when the orientation may have changed.
+ void MaybeChanged();
+
+ ScreenOrientation(nsPIDOMWindowInner* aWindow, nsScreen* aScreen);
+
+ already_AddRefed<Promise> Lock(OrientationLockType aOrientation,
+ ErrorResult& aRv);
+
+ void Unlock(ErrorResult& aRv);
+
+ // DeviceType and DeviceAngle gets the current type and angle of the device.
+ OrientationType DeviceType(CallerType aCallerType) const;
+ uint16_t DeviceAngle(CallerType aCallerType) const;
+
+ // GetType and GetAngle gets the type and angle of the responsible document
+ // (as defined in specification).
+ OrientationType GetType(CallerType aCallerType, ErrorResult& aRv) const;
+ uint16_t GetAngle(CallerType aCallerType, ErrorResult& aRv) const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static void UpdateActiveOrientationLock(hal::ScreenOrientation aOrientation);
+ static void AbortInProcessOrientationPromises(
+ BrowsingContext* aBrowsingContext);
+
+ private:
+ virtual ~ScreenOrientation();
+
+ // Listener to unlock orientation if we leave fullscreen.
+ class FullscreenEventListener;
+
+ // Listener to update document's orienation lock when document becomes
+ // visible.
+ class VisibleEventListener;
+
+ // Task to run step to lock orientation as defined in specification.
+ class LockOrientationTask;
+
+ enum LockPermission { LOCK_DENIED, FULLSCREEN_LOCK_ALLOWED, LOCK_ALLOWED };
+
+ // This method calls into the HAL to lock the device and sets
+ // up listeners for full screen change.
+ RefPtr<GenericNonExclusivePromise> LockDeviceOrientation(
+ hal::ScreenOrientation aOrientation, bool aIsFullscreen);
+
+ // This method calls in to the HAL to unlock the device and removes
+ // full screen change listener.
+ void UnlockDeviceOrientation();
+ void CleanupFullscreenListener();
+
+ // This method performs the same function as |Lock| except it takes
+ // a hal::ScreenOrientation argument instead of an OrientationType.
+ // This method exists in order to share implementation with nsScreen that
+ // uses ScreenOrientation.
+ already_AddRefed<Promise> LockInternal(hal::ScreenOrientation aOrientation,
+ ErrorResult& aRv);
+
+ nsCOMPtr<nsIRunnable> DispatchChangeEventAndResolvePromise();
+
+ LockPermission GetLockOrientationPermission(bool aCheckSandbox) const;
+
+ // Gets the responsible document as defined in the spec.
+ Document* GetResponsibleDocument() const;
+
+ RefPtr<nsScreen> mScreen;
+ RefPtr<FullscreenEventListener> mFullscreenListener;
+ RefPtr<VisibleEventListener> mVisibleListener;
+ OrientationType mType;
+ uint16_t mAngle;
+ // Whether we've tried to call into hal to lock the device orientation. This
+ // is needed because you don't want calling UnlockDeviceOrientation() during
+ // shutdown to initialize PHal if it hasn't been initialized earlier. Also,
+ // makes sense (there's no reason destroying a ScreenOrientation object from a
+ // different window should remove the orientation lock).
+ bool mTriedToLockDeviceOrientation = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ScreenOrientation_h
diff --git a/dom/base/ScriptableContentIterator.cpp b/dom/base/ScriptableContentIterator.cpp
new file mode 100644
index 0000000000..d10c4e68f8
--- /dev/null
+++ b/dom/base/ScriptableContentIterator.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScriptableContentIterator.h"
+
+#include "mozilla/ContentIterator.h"
+#include "nsINode.h"
+#include "nsRange.h"
+
+namespace mozilla {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptableContentIterator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptableContentIterator)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptableContentIterator)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptableContentIterator)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptableContentIterator)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptableContentIterator)
+ if (tmp->mContentIterator) {
+ switch (tmp->mIteratorType) {
+ case POST_ORDER_ITERATOR:
+ default:
+ ImplCycleCollectionUnlink(
+ static_cast<PostContentIterator&>(*tmp->mContentIterator));
+ break;
+ case PRE_ORDER_ITERATOR:
+ ImplCycleCollectionUnlink(
+ static_cast<PreContentIterator&>(*tmp->mContentIterator));
+ break;
+ case SUBTREE_ITERATOR:
+ ImplCycleCollectionUnlink(
+ static_cast<ContentSubtreeIterator&>(*tmp->mContentIterator));
+ break;
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptableContentIterator)
+ if (tmp->mContentIterator) {
+ switch (tmp->mIteratorType) {
+ case POST_ORDER_ITERATOR:
+ default:
+ ImplCycleCollectionTraverse(
+ cb, static_cast<PostContentIterator&>(*tmp->mContentIterator),
+ "mContentIterator");
+ break;
+ case PRE_ORDER_ITERATOR:
+ ImplCycleCollectionTraverse(
+ cb, static_cast<PreContentIterator&>(*tmp->mContentIterator),
+ "mContentIterator");
+ break;
+ case SUBTREE_ITERATOR:
+ ImplCycleCollectionTraverse(
+ cb, static_cast<ContentSubtreeIterator&>(*tmp->mContentIterator),
+ "mContentIterator");
+ break;
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+ScriptableContentIterator::ScriptableContentIterator()
+ : mIteratorType(NOT_INITIALIZED) {}
+
+void ScriptableContentIterator::EnsureContentIterator() {
+ if (mContentIterator) {
+ return;
+ }
+ switch (mIteratorType) {
+ case POST_ORDER_ITERATOR:
+ default:
+ mContentIterator = MakeUnique<PostContentIterator>();
+ break;
+ case PRE_ORDER_ITERATOR:
+ mContentIterator = MakeUnique<PreContentIterator>();
+ break;
+ case SUBTREE_ITERATOR:
+ mContentIterator = MakeUnique<ContentSubtreeIterator>();
+ break;
+ }
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::InitWithRootNode(IteratorType aType,
+ nsINode* aRoot) {
+ if (aType == NOT_INITIALIZED ||
+ (mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mIteratorType = aType;
+ EnsureContentIterator();
+ return mContentIterator->Init(aRoot);
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::InitWithRange(IteratorType aType, nsRange* aRange) {
+ if (aType == NOT_INITIALIZED ||
+ (mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mIteratorType = aType;
+ EnsureContentIterator();
+ return mContentIterator->Init(aRange);
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::InitWithPositions(IteratorType aType,
+ nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset) {
+ if (aType == NOT_INITIALIZED ||
+ (mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mIteratorType = aType;
+ EnsureContentIterator();
+ return mContentIterator->Init(aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::First() {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mContentIterator->First();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::Last() {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mContentIterator->Last();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::Next() {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mContentIterator->Next();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::Prev() {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mContentIterator->Prev();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::GetCurrentNode(nsINode** aNode) {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ NS_IF_ADDREF(*aNode = mContentIterator->GetCurrentNode());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::GetIsDone(bool* aIsDone) {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ *aIsDone = mContentIterator->IsDone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableContentIterator::PositionAt(nsINode* aNode) {
+ if (!mContentIterator) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mContentIterator->PositionAt(aNode);
+}
+
+} // namespace mozilla
diff --git a/dom/base/ScriptableContentIterator.h b/dom/base/ScriptableContentIterator.h
new file mode 100644
index 0000000000..ea57409de6
--- /dev/null
+++ b/dom/base/ScriptableContentIterator.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_scriptablecontentiterator_h
+#define mozilla_scriptablecontentiterator_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptableContentIterator.h"
+
+namespace mozilla {
+
+class ScriptableContentIterator final : public nsIScriptableContentIterator {
+ public:
+ ScriptableContentIterator();
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptableContentIterator)
+ NS_DECL_NSISCRIPTABLECONTENTITERATOR
+
+ protected:
+ virtual ~ScriptableContentIterator() = default;
+ void EnsureContentIterator();
+
+ IteratorType mIteratorType;
+ UniquePtr<ContentIteratorBase> mContentIterator;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_scriptablecontentiterator_h
diff --git a/dom/base/ScrollingMetrics.cpp b/dom/base/ScrollingMetrics.cpp
new file mode 100644
index 0000000000..78dc607023
--- /dev/null
+++ b/dom/base/ScrollingMetrics.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollingMetrics.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+namespace mozilla {
+
+static TimeStamp gScrollingStartTime;
+static TimeStamp gScrollingEndTime;
+static uint32_t gScrollDistanceCSSPixels = 0;
+static dom::InteractionData gScrollingInteraction = {};
+
+void ScrollingMetrics::OnScrollingInteractionEnded() {
+ // We are only interested in content process scrolling
+ if (XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!gScrollingStartTime.IsNull() && !gScrollingEndTime.IsNull()) {
+ gScrollingInteraction.mInteractionCount++;
+ gScrollingInteraction.mInteractionTimeInMilliseconds +=
+ static_cast<uint32_t>(
+ (gScrollingEndTime - gScrollingStartTime).ToMilliseconds());
+
+ gScrollingInteraction.mScrollingDistanceInPixels +=
+ gScrollDistanceCSSPixels;
+ }
+
+ gScrollDistanceCSSPixels = 0;
+ gScrollingStartTime = TimeStamp();
+ gScrollingEndTime = TimeStamp();
+}
+
+void ScrollingMetrics::OnScrollingInteraction(CSSCoord distanceScrolled) {
+ // We are only interested in content process scrolling
+ if (XRE_IsParentProcess()) {
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ if (gScrollingEndTime.IsNull()) {
+ gScrollingEndTime = now;
+ }
+
+ TimeDuration delay = now - gScrollingEndTime;
+
+ // Has it been too long since the last scroll input event to consider it part
+ // of the same interaction?
+ if (delay >
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::browser_places_interactions_scrolling_timeout_ms())) {
+ OnScrollingInteractionEnded();
+ }
+
+ if (gScrollingStartTime.IsNull()) {
+ gScrollingStartTime = now;
+ }
+ gScrollingEndTime = now;
+ gScrollDistanceCSSPixels += static_cast<uint32_t>(distanceScrolled);
+}
+
+StaticAutoPtr<ScrollingMetrics> ScrollingMetrics::sSingleton;
+
+ScrollingMetrics* ScrollingMetrics::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new ScrollingMetrics;
+ }
+
+ return sSingleton.get();
+}
+
+struct ScrollingMetricsCollector {
+ void AppendScrollingMetrics(const std::tuple<uint32_t, uint32_t>& aMetrics,
+ dom::ContentParent* aParent) {
+ mTimeScrolledMS += std::get<0>(aMetrics);
+ mDistanceScrolledPixels += std::get<1>(aMetrics);
+ }
+
+ ~ScrollingMetricsCollector() {
+ mPromiseHolder.Resolve(
+ std::make_tuple(mTimeScrolledMS, mDistanceScrolledPixels), __func__);
+ }
+
+ uint32_t mTimeScrolledMS = 0;
+ uint32_t mDistanceScrolledPixels = 0;
+ MozPromiseHolder<ScrollingMetrics::ScrollingMetricsPromise> mPromiseHolder;
+};
+
+auto ScrollingMetrics::CollectScrollingMetricsInternal()
+ -> RefPtr<ScrollingMetrics::ScrollingMetricsPromise> {
+ std::shared_ptr<ScrollingMetricsCollector> collector =
+ std::make_shared<ScrollingMetricsCollector>();
+
+ nsTArray<dom::ContentParent*> contentParents;
+ dom::ContentParent::GetAll(contentParents);
+ for (dom::ContentParent* parent : contentParents) {
+ RefPtr<dom::ContentParent> parentRef = parent;
+ parent->SendCollectScrollingMetrics(
+ [collector, parentRef](const std::tuple<uint32_t, uint32_t>& aMetrics) {
+ collector->AppendScrollingMetrics(aMetrics, parentRef.get());
+ },
+ [](mozilla::ipc::ResponseRejectReason) {});
+ }
+
+ return collector->mPromiseHolder.Ensure(__func__);
+}
+
+std::tuple<uint32_t, uint32_t>
+ScrollingMetrics::CollectLocalScrollingMetricsInternal() {
+ OnScrollingInteractionEnded();
+
+ std::tuple<uint32_t, uint32_t> metrics =
+ std::make_tuple(gScrollingInteraction.mInteractionTimeInMilliseconds,
+ gScrollingInteraction.mScrollingDistanceInPixels);
+ gScrollingInteraction = {};
+ return metrics;
+}
+
+} // namespace mozilla
diff --git a/dom/base/ScrollingMetrics.h b/dom/base/ScrollingMetrics.h
new file mode 100644
index 0000000000..081c38d069
--- /dev/null
+++ b/dom/base/ScrollingMetrics.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScrollingMetrics_h
+#define mozilla_dom_ScrollingMetrics_h
+
+#include "Units.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+
+/**
+ * ScrollingMetrics records user-intiated scrolling interactions. These are
+ * aggregrated along with other user interactions (e.g. typing, view time), in
+ * the history metadata.
+ */
+class ScrollingMetrics {
+ public:
+ using ScrollingMetricsPromise =
+ MozPromise<std::tuple<uint32_t, uint32_t>, bool, true>;
+
+ static RefPtr<ScrollingMetricsPromise> CollectScrollingMetrics() {
+ return GetSingleton()->CollectScrollingMetricsInternal();
+ }
+
+ // Return the tuple of <scrollingTimeInMilliseconds,
+ // ScrollingDistanceInPixels>
+ static std::tuple<uint32_t, uint32_t> CollectLocalScrollingMetrics() {
+ return GetSingleton()->CollectLocalScrollingMetricsInternal();
+ }
+
+ // Report a new user-initiated scrolling interaction
+ static void OnScrollingInteraction(CSSCoord distanceScrolled);
+
+ static void OnScrollingInteractionEnded();
+
+ private:
+ static ScrollingMetrics* GetSingleton();
+ static StaticAutoPtr<ScrollingMetrics> sSingleton;
+ RefPtr<ScrollingMetricsPromise> CollectScrollingMetricsInternal();
+ std::tuple<uint32_t, uint32_t> CollectLocalScrollingMetricsInternal();
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_dom_ScrollingMetrics_h */
diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp
new file mode 100644
index 0000000000..f46f4409cd
--- /dev/null
+++ b/dom/base/Selection.cpp
@@ -0,0 +1,4118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of mozilla::dom::Selection
+ */
+
+#include "Selection.h"
+
+#include "ErrorList.h"
+#include "LayoutConstants.h"
+#include "mozilla/AccessibleCaretEventHub.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoCopyListener.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/SelectionBinding.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/StackWalk.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDirection.h"
+#include "nsString.h"
+#include "nsFrameSelection.h"
+#include "nsISelectionListener.h"
+#include "nsContentCID.h"
+#include "nsDeviceContext.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsRange.h"
+#include "nsITableCellLayout.h"
+#include "nsTArray.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsIDocumentEncoder.h"
+#include "nsTextFragment.h"
+#include <algorithm>
+#include "nsContentUtils.h"
+
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsBidiPresUtils.h"
+#include "nsTextFrame.h"
+
+#include "nsThreadUtils.h"
+
+#include "nsPresContext.h"
+#include "nsCaret.h"
+
+#include "nsITimer.h"
+#include "mozilla/dom/Document.h"
+#include "nsINamed.h"
+
+#include "nsISelectionController.h" //for the enums
+#include "nsCopySupport.h"
+#include "nsIFrameInlines.h"
+#include "nsRefreshDriver.h"
+
+#include "nsError.h"
+#include "nsViewManager.h"
+
+#include "nsFocusManager.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+// "Selection" logs only the calls of AddRangesForSelectableNodes and
+// NotifySelectionListeners in debug level.
+static LazyLogModule sSelectionLog("Selection");
+// "SelectionAPI" logs all API calls (both internal ones and exposed to script
+// ones) of normal selection which may change selection ranges.
+// 3. Info: Calls of APIs
+// 4. Debug: Call stacks with 7 ancestor callers of APIs
+// 5. Verbose: Complete call stacks of APIs.
+LazyLogModule sSelectionAPILog("SelectionAPI");
+
+MOZ_ALWAYS_INLINE bool NeedsToLogSelectionAPI(dom::Selection& aSelection) {
+ return aSelection.Type() == SelectionType::eNormal &&
+ MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Info);
+}
+
+void LogStackForSelectionAPI() {
+ if (!MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Debug)) {
+ return;
+ }
+ static nsAutoCString* sBufPtr = nullptr;
+ MOZ_ASSERT(!sBufPtr);
+ nsAutoCString buf;
+ sBufPtr = &buf;
+ auto writer = [](const char* aBuf) { sBufPtr->Append(aBuf); };
+ const LogLevel logLevel = MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Verbose)
+ ? LogLevel::Verbose
+ : LogLevel::Debug;
+ MozWalkTheStackWithWriter(writer, CallerPC(),
+ logLevel == LogLevel::Verbose
+ ? 0u /* all */
+ : 8u /* 8 inclusive ancestors */);
+ MOZ_LOG(sSelectionAPILog, logLevel, ("\n%s", buf.get()));
+ sBufPtr = nullptr;
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s()", aSelection, aFuncName));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName,
+ const nsINode* aNode) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
+ aNode ? ToString(*aNode).c_str() : "nullptr"));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName,
+ const dom::AbstractRange& aRange) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
+ ToString(aRange).c_str()));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName1,
+ const nsINode* aNode, const char* aArgName2,
+ uint32_t aOffset) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s, %s=%u)", aSelection, aFuncName, aArgName1,
+ aNode ? ToString(*aNode).c_str() : "nullptr", aArgName2, aOffset));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName,
+ const RawRangeBoundary& aBoundary) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s)", aSelection, aFuncName, aArgName,
+ ToString(aBoundary).c_str()));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName1,
+ const nsAString& aStr1, const char* aArgName2,
+ const nsAString& aStr2, const char* aArgName3,
+ const nsAString& aStr3) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s, %s=%s, %s=%s)", aSelection, aFuncName,
+ aArgName1, NS_ConvertUTF16toUTF8(aStr1).get(), aArgName2,
+ NS_ConvertUTF16toUTF8(aStr2).get(), aArgName3,
+ NS_ConvertUTF16toUTF8(aStr3).get()));
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aNodeArgName1,
+ const nsINode& aNode1, const char* aOffsetArgName1,
+ uint32_t aOffset1, const char* aNodeArgName2,
+ const nsINode& aNode2, const char* aOffsetArgName2,
+ uint32_t aOffset2) {
+ if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s=%s, %s=%s=%u)", aSelection, aFuncName,
+ aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
+ aOffsetArgName1, aOffsetArgName2, aOffset1));
+ } else {
+ MOZ_LOG(
+ sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u)", aSelection, aFuncName,
+ aNodeArgName1, ToString(aNode1).c_str(), aOffsetArgName1, aOffset1,
+ aNodeArgName2, ToString(aNode2).c_str(), aOffsetArgName2, aOffset2));
+ }
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aNodeArgName1,
+ const nsINode& aNode1, const char* aOffsetArgName1,
+ uint32_t aOffset1, const char* aNodeArgName2,
+ const nsINode& aNode2, const char* aOffsetArgName2,
+ uint32_t aOffset2, const char* aDirArgName,
+ nsDirection aDirection, const char* aReasonArgName,
+ int16_t aReason) {
+ if (&aNode1 == &aNode2 && aOffset1 == aOffset2) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s=%s, %s=%s=%u, %s=%s, %s=%d)", aSelection,
+ aFuncName, aNodeArgName1, aNodeArgName2, ToString(aNode1).c_str(),
+ aOffsetArgName1, aOffsetArgName2, aOffset1, aDirArgName,
+ ToString(aDirection).c_str(), aReasonArgName, aReason));
+ } else {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s, %s=%u, %s=%s, %s=%u, %s=%s, %s=%d)",
+ aSelection, aFuncName, aNodeArgName1, ToString(aNode1).c_str(),
+ aOffsetArgName1, aOffset1, aNodeArgName2, ToString(aNode2).c_str(),
+ aOffsetArgName2, aOffset2, aDirArgName,
+ ToString(aDirection).c_str(), aReasonArgName, aReason));
+ }
+}
+
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName1,
+ const RawRangeBoundary& aBoundary1,
+ const char* aArgName2,
+ const RawRangeBoundary& aBoundary2) {
+ if (aBoundary1 == aBoundary2) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s=%s)", aSelection, aFuncName, aArgName1,
+ aArgName2, ToString(aBoundary1).c_str()));
+ } else {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p Selection::%s(%s=%s, %s=%s)", aSelection, aFuncName, aArgName1,
+ ToString(aBoundary1).c_str(), aArgName2,
+ ToString(aBoundary2).c_str()));
+ }
+}
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// #define DEBUG_TABLE 1
+
+#ifdef PRINT_RANGE
+static void printRange(nsRange* aDomRange);
+# define DEBUG_OUT_RANGE(x) printRange(x)
+#else
+# define DEBUG_OUT_RANGE(x)
+#endif // PRINT_RANGE
+
+static constexpr nsLiteralCString kNoDocumentTypeNodeError =
+ "DocumentType nodes are not supported"_ns;
+static constexpr nsLiteralCString kNoRangeExistsError =
+ "No selection range exists"_ns;
+
+namespace mozilla {
+
+/******************************************************************************
+ * Utility methods defined in nsISelectionController.idl
+ ******************************************************************************/
+
+const char* ToChar(SelectionType aSelectionType) {
+ switch (aSelectionType) {
+ case SelectionType::eInvalid:
+ return "SelectionType::eInvalid";
+ case SelectionType::eNone:
+ return "SelectionType::eNone";
+ case SelectionType::eNormal:
+ return "SelectionType::eNormal";
+ case SelectionType::eSpellCheck:
+ return "SelectionType::eSpellCheck";
+ case SelectionType::eIMERawClause:
+ return "SelectionType::eIMERawClause";
+ case SelectionType::eIMESelectedRawClause:
+ return "SelectionType::eIMESelectedRawClause";
+ case SelectionType::eIMEConvertedClause:
+ return "SelectionType::eIMEConvertedClause";
+ case SelectionType::eIMESelectedClause:
+ return "SelectionType::eIMESelectedClause";
+ case SelectionType::eAccessibility:
+ return "SelectionType::eAccessibility";
+ case SelectionType::eFind:
+ return "SelectionType::eFind";
+ case SelectionType::eURLSecondary:
+ return "SelectionType::eURLSecondary";
+ case SelectionType::eURLStrikeout:
+ return "SelectionType::eURLStrikeout";
+ default:
+ return "Invalid SelectionType";
+ }
+}
+
+/******************************************************************************
+ * Utility methods defined in nsISelectionListener.idl
+ ******************************************************************************/
+
+nsCString SelectionChangeReasonsToCString(int16_t aReasons) {
+ nsCString reasons;
+ if (!aReasons) {
+ reasons.AssignLiteral("NO_REASON");
+ return reasons;
+ }
+ auto EnsureSeparator = [](nsCString& aString) -> void {
+ if (!aString.IsEmpty()) {
+ aString.AppendLiteral(" | ");
+ }
+ };
+ struct ReasonData {
+ int16_t mReason;
+ const char* mReasonStr;
+
+ ReasonData(int16_t aReason, const char* aReasonStr)
+ : mReason(aReason), mReasonStr(aReasonStr) {}
+ };
+ for (const ReasonData& reason :
+ {ReasonData(nsISelectionListener::DRAG_REASON, "DRAG_REASON"),
+ ReasonData(nsISelectionListener::MOUSEDOWN_REASON, "MOUSEDOWN_REASON"),
+ ReasonData(nsISelectionListener::MOUSEUP_REASON, "MOUSEUP_REASON"),
+ ReasonData(nsISelectionListener::KEYPRESS_REASON, "KEYPRESS_REASON"),
+ ReasonData(nsISelectionListener::SELECTALL_REASON, "SELECTALL_REASON"),
+ ReasonData(nsISelectionListener::COLLAPSETOSTART_REASON,
+ "COLLAPSETOSTART_REASON"),
+ ReasonData(nsISelectionListener::COLLAPSETOEND_REASON,
+ "COLLAPSETOEND_REASON"),
+ ReasonData(nsISelectionListener::IME_REASON, "IME_REASON"),
+ ReasonData(nsISelectionListener::JS_REASON, "JS_REASON")}) {
+ if (aReasons & reason.mReason) {
+ EnsureSeparator(reasons);
+ reasons.Append(reason.mReasonStr);
+ }
+ }
+ return reasons;
+}
+
+} // namespace mozilla
+
+// #define DEBUG_SELECTION // uncomment for printf describing every collapse and
+// extend. #define DEBUG_NAVIGATION
+
+// #define DEBUG_TABLE_SELECTION 1
+
+struct CachedOffsetForFrame {
+ CachedOffsetForFrame()
+ : mCachedFrameOffset(0, 0) // nsPoint ctor
+ ,
+ mLastCaretFrame(nullptr),
+ mLastContentOffset(0),
+ mCanCacheFrameOffset(false) {}
+
+ nsPoint mCachedFrameOffset; // cached frame offset
+ nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in.
+ int32_t mLastContentOffset; // store last content offset
+ bool mCanCacheFrameOffset; // cached frame offset is valid?
+};
+
+class AutoScroller final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit AutoScroller(nsFrameSelection* aFrameSelection)
+ : mFrameSelection(aFrameSelection),
+ mPresContext(0),
+ mPoint(0, 0),
+ mDelayInMs(30),
+ mFurtherScrollingAllowed(FurtherScrollingAllowed::kYes) {
+ MOZ_ASSERT(mFrameSelection);
+ }
+
+ MOZ_CAN_RUN_SCRIPT nsresult DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint);
+
+ private:
+ // aPoint is relative to aPresContext's root frame
+ nsresult ScheduleNextDoAutoScroll(nsPresContext* aPresContext,
+ nsPoint& aPoint) {
+ if (NS_WARN_IF(mFurtherScrollingAllowed == FurtherScrollingAllowed::kNo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPoint = aPoint;
+
+ // Store the presentation context. The timer will be
+ // stopped by the selection if the prescontext is destroyed.
+ mPresContext = aPresContext;
+
+ mContent = PresShell::GetCapturingContent();
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer(
+ mPresContext->Document()->EventTargetFor(TaskCategory::Other));
+
+ if (!mTimer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return mTimer->InitWithCallback(this, mDelayInMs, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ public:
+ enum class FurtherScrollingAllowed { kYes, kNo };
+
+ void Stop(const FurtherScrollingAllowed aFurtherScrollingAllowed) {
+ MOZ_ASSERT((aFurtherScrollingAllowed == FurtherScrollingAllowed::kNo) ||
+ (mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes));
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mContent = nullptr;
+ mFurtherScrollingAllowed = aFurtherScrollingAllowed;
+ }
+
+ void SetDelay(uint32_t aDelayInMs) { mDelayInMs = aDelayInMs; }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Notify(nsITimer* timer) override {
+ if (mPresContext) {
+ AutoWeakFrame frame =
+ mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
+ if (!frame) {
+ return NS_OK;
+ }
+ mContent = nullptr;
+
+ nsPoint pt = mPoint - frame->GetOffsetTo(
+ mPresContext->PresShell()->GetRootFrame());
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->HandleDrag(frame, pt);
+ if (!frame.IsAlive()) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
+ DoAutoScroll(frame, pt);
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("AutoScroller");
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~AutoScroller() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+ private:
+ nsFrameSelection* const mFrameSelection;
+ nsPresContext* mPresContext;
+ // relative to mPresContext's root frame
+ nsPoint mPoint;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIContent> mContent;
+ uint32_t mDelayInMs;
+ FurtherScrollingAllowed mFurtherScrollingAllowed;
+};
+
+NS_IMPL_ISUPPORTS(AutoScroller, nsITimerCallback, nsINamed)
+
+#ifdef PRINT_RANGE
+void printRange(nsRange* aDomRange) {
+ if (!aDomRange) {
+ printf("NULL Range\n");
+ }
+ nsINode* startNode = aDomRange->GetStartContainer();
+ nsINode* endNode = aDomRange->GetEndContainer();
+ int32_t startOffset = aDomRange->StartOffset();
+ int32_t endOffset = aDomRange->EndOffset();
+
+ printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
+ (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
+ (unsigned long)endNode, (long)endOffset);
+}
+#endif /* PRINT_RANGE */
+
+void Selection::Stringify(nsAString& aResult, FlushFrames aFlushFrames) {
+ if (aFlushFrames == FlushFrames::Yes) {
+ // We need FlushType::Frames here to make sure frames have been created for
+ // the selected content. Use mFrameSelection->GetPresShell() which returns
+ // null if the Selection has been disconnected (the shell is Destroyed).
+ RefPtr<PresShell> presShell =
+ mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
+ if (!presShell) {
+ aResult.Truncate();
+ return;
+ }
+ presShell->FlushPendingNotifications(FlushType::Frames);
+ }
+
+ IgnoredErrorResult rv;
+ ToStringWithFormat(u"text/plain"_ns, nsIDocumentEncoder::SkipInvisibleContent,
+ 0, aResult, rv);
+ if (rv.Failed()) {
+ aResult.Truncate();
+ }
+}
+
+void Selection::ToStringWithFormat(const nsAString& aFormatType,
+ uint32_t aFlags, int32_t aWrapCol,
+ nsAString& aReturn, ErrorResult& aRv) {
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_createDocumentEncoder(NS_ConvertUTF16toUTF8(aFormatType).get());
+ if (!encoder) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Document* doc = presShell->GetDocument();
+
+ // Flags should always include OutputSelectionOnly if we're coming from here:
+ aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
+ nsAutoString readstring;
+ readstring.Assign(aFormatType);
+ nsresult rv = encoder->Init(doc, readstring, aFlags);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ encoder->SetSelection(this);
+ if (aWrapCol != 0) encoder->SetWrapColumn(aWrapCol);
+
+ rv = encoder->EncodeToString(aReturn);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+nsresult Selection::SetInterlinePosition(InterlinePosition aInterlinePosition) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+ MOZ_ASSERT(aInterlinePosition != InterlinePosition::Undefined);
+
+ if (!mFrameSelection) {
+ return NS_ERROR_NOT_INITIALIZED; // Can't do selection
+ }
+
+ mFrameSelection->SetHint(aInterlinePosition ==
+ InterlinePosition::StartOfNextLine
+ ? CARET_ASSOCIATE_AFTER
+ : CARET_ASSOCIATE_BEFORE);
+ return NS_OK;
+}
+
+Selection::InterlinePosition Selection::GetInterlinePosition() const {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (!mFrameSelection) {
+ return InterlinePosition::Undefined;
+ }
+ return mFrameSelection->GetHint() == CARET_ASSOCIATE_AFTER
+ ? InterlinePosition::StartOfNextLine
+ : InterlinePosition::EndOfLine;
+}
+
+void Selection::SetInterlinePositionJS(bool aHintRight, ErrorResult& aRv) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ aRv = SetInterlinePosition(aHintRight ? InterlinePosition::StartOfNextLine
+ : InterlinePosition::EndOfLine);
+}
+
+bool Selection::GetInterlinePositionJS(ErrorResult& aRv) const {
+ const InterlinePosition interlinePosition = GetInterlinePosition();
+ if (interlinePosition == InterlinePosition::Undefined) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
+ return false;
+ }
+ return interlinePosition == InterlinePosition::StartOfNextLine;
+}
+
+static bool IsEditorNode(const nsINode* aNode) {
+ if (!aNode) {
+ return false;
+ }
+
+ if (aNode->IsEditable()) {
+ return true;
+ }
+
+ auto* element = Element::FromNode(aNode);
+ return element && element->State().HasState(ElementState::READWRITE);
+}
+
+bool Selection::IsEditorSelection() const {
+ return IsEditorNode(GetFocusNode());
+}
+
+Nullable<int16_t> Selection::GetCaretBidiLevel(
+ mozilla::ErrorResult& aRv) const {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return Nullable<int16_t>();
+ }
+ mozilla::intl::BidiEmbeddingLevel caretBidiLevel =
+ static_cast<mozilla::intl::BidiEmbeddingLevel>(
+ mFrameSelection->GetCaretBidiLevel());
+ return (caretBidiLevel & BIDI_LEVEL_UNDEFINED)
+ ? Nullable<int16_t>()
+ : Nullable<int16_t>(caretBidiLevel);
+}
+
+void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
+ mozilla::ErrorResult& aRv) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ if (aCaretBidiLevel.IsNull()) {
+ mFrameSelection->UndefineCaretBidiLevel();
+ } else {
+ mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
+ mozilla::intl::BidiEmbeddingLevel(aCaretBidiLevel.Value()));
+ }
+}
+
+/**
+ * Test whether the supplied range points to a single table element.
+ * Result is one of the TableSelectionMode constants. "None" means
+ * a table element isn't selected.
+ */
+// TODO: Figure out TableSelectionMode::Column and TableSelectionMode::AllCells
+static nsresult GetTableSelectionMode(const nsRange& aRange,
+ TableSelectionMode* aTableSelectionType) {
+ if (!aTableSelectionType) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aTableSelectionType = TableSelectionMode::None;
+
+ nsINode* startNode = aRange.GetStartContainer();
+ if (!startNode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsINode* endNode = aRange.GetEndContainer();
+ if (!endNode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Not a single selected node
+ if (startNode != endNode) {
+ return NS_OK;
+ }
+
+ nsIContent* child = aRange.GetChildAtStartOffset();
+
+ // Not a single selected node
+ if (!child || child->GetNextSibling() != aRange.GetChildAtEndOffset()) {
+ return NS_OK;
+ }
+
+ if (!startNode->IsHTMLElement()) {
+ // Implies a check for being an element; if we ever make this work
+ // for non-HTML, need to keep checking for elements.
+ return NS_OK;
+ }
+
+ if (startNode->IsHTMLElement(nsGkAtoms::tr)) {
+ *aTableSelectionType = TableSelectionMode::Cell;
+ } else // check to see if we are selecting a table or row (column and all
+ // cells not done yet)
+ {
+ if (child->IsHTMLElement(nsGkAtoms::table)) {
+ *aTableSelectionType = TableSelectionMode::Table;
+ } else if (child->IsHTMLElement(nsGkAtoms::tr)) {
+ *aTableSelectionType = TableSelectionMode::Row;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult Selection::MaybeAddTableCellRange(nsRange& aRange,
+ Maybe<size_t>* aOutIndex) {
+ if (!aOutIndex) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ MOZ_ASSERT(aOutIndex->isNothing());
+
+ if (!mFrameSelection) {
+ return NS_OK;
+ }
+
+ // Get if we are adding a cell selection and the row, col of cell if we are
+ TableSelectionMode tableMode;
+ nsresult result = GetTableSelectionMode(aRange, &tableMode);
+ if (NS_FAILED(result)) return result;
+
+ // If not adding a cell range, we are done here
+ if (tableMode != TableSelectionMode::Cell) {
+ mFrameSelection->mTableSelection.mMode = tableMode;
+ // Don't fail if range isn't a selected cell, aDidAddRange tells caller if
+ // we didn't proceed
+ return NS_OK;
+ }
+
+ // Set frame selection mode only if not already set to a table mode
+ // so we don't lose the select row and column flags (not detected by
+ // getTableCellLocation)
+ if (mFrameSelection->mTableSelection.mMode == TableSelectionMode::None) {
+ mFrameSelection->mTableSelection.mMode = tableMode;
+ }
+
+ return AddRangesForSelectableNodes(&aRange, aOutIndex,
+ DispatchSelectstartEvent::Maybe);
+}
+
+Selection::Selection(SelectionType aSelectionType,
+ nsFrameSelection* aFrameSelection)
+ : mFrameSelection(aFrameSelection),
+ mCachedOffsetForFrame(nullptr),
+ mDirection(eDirNext),
+ mSelectionType(aSelectionType),
+ mCustomColors(nullptr),
+ mSelectionChangeBlockerCount(0),
+ mUserInitiated(false),
+ mCalledByJS(false),
+ mNotifyAutoCopy(false) {}
+
+Selection::~Selection() { Disconnect(); }
+
+void Selection::Disconnect() {
+ RemoveAnchorFocusRange();
+
+ mStyledRanges.UnregisterSelection();
+
+ if (mAutoScroller) {
+ mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kNo);
+ mAutoScroller = nullptr;
+ }
+
+ mScrollEvent.Revoke();
+
+ if (mCachedOffsetForFrame) {
+ delete mCachedOffsetForFrame;
+ mCachedOffsetForFrame = nullptr;
+ }
+}
+
+Document* Selection::GetParentObject() const {
+ PresShell* presShell = GetPresShell();
+ return presShell ? presShell->GetDocument() : nullptr;
+}
+
+DocGroup* Selection::GetDocGroup() const {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ Document* doc = presShell->GetDocument();
+ return doc ? doc->GetDocGroup() : nullptr;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection)
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection)
+ // Unlink the selection listeners *before* we do RemoveAllRangesInternal since
+ // we don't want to notify the listeners during JS GC (they could be
+ // in JS!).
+ tmp->mNotifyAutoCopy = false;
+ if (tmp->mAccessibleCaretEventHub) {
+ tmp->StopNotifyingAccessibleCaretEventHub();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionChangeEventDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners)
+ MOZ_KnownLive(tmp)->RemoveAllRangesInternal(IgnoreErrors());
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection)
+ {
+ uint32_t i, count = tmp->mStyledRanges.Length();
+ for (i = 0; i < count; ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyledRanges.mRanges[i].mRange)
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionChangeEventDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for Selection
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Selection)
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
+ Selection, Disconnect())
+
+const RangeBoundary& Selection::AnchorRef() const {
+ if (!mAnchorFocusRange) {
+ static RangeBoundary sEmpty;
+ return sEmpty;
+ }
+
+ if (GetDirection() == eDirNext) {
+ return mAnchorFocusRange->StartRef();
+ }
+
+ return mAnchorFocusRange->EndRef();
+}
+
+const RangeBoundary& Selection::FocusRef() const {
+ if (!mAnchorFocusRange) {
+ static RangeBoundary sEmpty;
+ return sEmpty;
+ }
+
+ if (GetDirection() == eDirNext) {
+ return mAnchorFocusRange->EndRef();
+ }
+
+ return mAnchorFocusRange->StartRef();
+}
+
+void Selection::SetAnchorFocusRange(size_t aIndex) {
+ if (aIndex >= mStyledRanges.Length()) {
+ return;
+ }
+ // Highlight selections may contain static ranges.
+ MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
+ AbstractRange* anchorFocusRange = mStyledRanges.mRanges[aIndex].mRange;
+ mAnchorFocusRange = anchorFocusRange->AsDynamicRange();
+}
+
+static int32_t CompareToRangeStart(const nsINode& aCompareNode,
+ uint32_t aCompareOffset,
+ const AbstractRange& aRange) {
+ MOZ_ASSERT(aRange.GetStartContainer());
+ nsINode* start = aRange.GetStartContainer();
+ // If the nodes that we're comparing are not in the same document, assume that
+ // aCompareNode will fall at the end of the ranges.
+ if (aCompareNode.GetComposedDoc() != start->GetComposedDoc() ||
+ !start->GetComposedDoc()) {
+ NS_WARNING(
+ "`CompareToRangeStart` couldn't compare nodes, pretending some order.");
+ return 1;
+ }
+
+ // The points are in the same subtree, hence there has to be an order.
+ return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, start,
+ aRange.StartOffset());
+}
+
+static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
+ uint32_t aCompareOffset,
+ const AbstractRange& aRange) {
+ MOZ_ASSERT(aRange.IsPositioned());
+ nsINode* end = aRange.GetEndContainer();
+ // If the nodes that we're comparing are not in the same document or in the
+ // same subtree, assume that aCompareNode will fall at the end of the ranges.
+ if (aCompareNode.GetComposedDoc() != end->GetComposedDoc() ||
+ !end->GetComposedDoc()) {
+ NS_WARNING(
+ "`CompareToRangeEnd` couldn't compare nodes, pretending some order.");
+ return 1;
+ }
+
+ // The points are in the same subtree, hence there has to be an order.
+ return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, end,
+ aRange.EndOffset());
+}
+
+// static
+size_t Selection::StyledRanges::FindInsertionPoint(
+ const nsTArray<StyledRange>* aElementArray, const nsINode& aPointNode,
+ uint32_t aPointOffset,
+ int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&)) {
+ int32_t beginSearch = 0;
+ int32_t endSearch = aElementArray->Length(); // one beyond what to check
+
+ if (endSearch) {
+ int32_t center = endSearch - 1; // Check last index, then binary search
+ do {
+ const AbstractRange* range = (*aElementArray)[center].mRange;
+
+ int32_t cmp{aComparator(aPointNode, aPointOffset, *range)};
+
+ if (cmp < 0) { // point < cur
+ endSearch = center;
+ } else if (cmp > 0) { // point > cur
+ beginSearch = center + 1;
+ } else { // found match, done
+ beginSearch = center;
+ break;
+ }
+ center = (endSearch - beginSearch) / 2 + beginSearch;
+ } while (endSearch - beginSearch > 0);
+ }
+
+ return AssertedCast<size_t>(beginSearch);
+}
+
+// Selection::SubtractRange
+//
+// A helper function that subtracts aSubtract from aRange, and adds
+// 1 or 2 StyledRange objects representing the remaining non-overlapping
+// difference to aOutput. It is assumed that the caller has checked that
+// aRange and aSubtract do indeed overlap
+
+// static
+nsresult Selection::StyledRanges::SubtractRange(
+ StyledRange& aRange, nsRange& aSubtract, nsTArray<StyledRange>* aOutput) {
+ AbstractRange* range = aRange.mRange;
+ if (NS_WARN_IF(!range->IsPositioned())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (range->GetStartContainer()->SubtreeRoot() !=
+ aSubtract.GetStartContainer()->SubtreeRoot()) {
+ // These are ranges for different shadow trees, we can't subtract them in
+ // any sensible way.
+ aOutput->InsertElementAt(0, aRange);
+ return NS_OK;
+ }
+
+ // First we want to compare to the range start
+ int32_t cmp{CompareToRangeStart(*range->GetStartContainer(),
+ range->StartOffset(), aSubtract)};
+
+ // Also, make a comparison to the range end
+ int32_t cmp2{CompareToRangeEnd(*range->GetEndContainer(), range->EndOffset(),
+ aSubtract)};
+
+ // If the existing range left overlaps the new range (aSubtract) then
+ // cmp < 0, and cmp2 < 0
+ // If it right overlaps the new range then cmp > 0 and cmp2 > 0
+ // If it fully contains the new range, then cmp < 0 and cmp2 > 0
+
+ if (cmp2 > 0) {
+ // We need to add a new StyledRange to the output, running from
+ // the end of aSubtract to the end of range
+ ErrorResult error;
+ RefPtr<nsRange> postOverlap =
+ nsRange::Create(aSubtract.EndRef(), range->EndRef(), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ MOZ_ASSERT(postOverlap);
+ if (!postOverlap->Collapsed()) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aOutput->InsertElementAt(0, StyledRange(postOverlap));
+ (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
+ }
+ }
+
+ if (cmp < 0) {
+ // We need to add a new StyledRange to the output, running from
+ // the start of the range to the start of aSubtract
+ ErrorResult error;
+ RefPtr<nsRange> preOverlap =
+ nsRange::Create(range->StartRef(), aSubtract.StartRef(), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ MOZ_ASSERT(preOverlap);
+ if (!preOverlap->Collapsed()) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aOutput->InsertElementAt(0, StyledRange(preOverlap));
+ (*aOutput)[0].mTextRangeStyle = aRange.mTextRangeStyle;
+ }
+ }
+
+ return NS_OK;
+}
+
+static void UserSelectRangesToAdd(nsRange* aItem,
+ nsTArray<RefPtr<nsRange>>& aRangesToAdd) {
+ // We cannot directly call IsEditorSelection() because we may be in an
+ // inconsistent state during Collapse() (we're cleared already but we haven't
+ // got a new focus node yet).
+ if (IsEditorNode(aItem->GetStartContainer()) &&
+ IsEditorNode(aItem->GetEndContainer())) {
+ // Don't mess with the selection ranges for editing, editor doesn't really
+ // deal well with multi-range selections.
+ aRangesToAdd.AppendElement(aItem);
+ } else {
+ aItem->ExcludeNonSelectableNodes(&aRangesToAdd);
+ }
+}
+
+static nsINode* DetermineSelectstartEventTarget(
+ const bool aSelectionEventsOnTextControlsEnabled, const nsRange& aRange) {
+ nsINode* target = aRange.GetStartContainer();
+ if (aSelectionEventsOnTextControlsEnabled) {
+ // Get the first element which isn't in a native anonymous subtree
+ while (target && target->IsInNativeAnonymousSubtree()) {
+ target = target->GetParent();
+ }
+ } else {
+ if (target->IsInNativeAnonymousSubtree()) {
+ // This is a selection under a text control, so don't dispatch the
+ // event.
+ target = nullptr;
+ }
+ }
+ return target;
+}
+
+/**
+ * @return true, iff the default action should be executed.
+ */
+static bool MaybeDispatchSelectstartEvent(
+ const nsRange& aRange, const bool aSelectionEventsOnTextControlsEnabled,
+ Document* aDocument) {
+ nsCOMPtr<nsINode> selectstartEventTarget = DetermineSelectstartEventTarget(
+ aSelectionEventsOnTextControlsEnabled, aRange);
+
+ bool executeDefaultAction = true;
+
+ if (selectstartEventTarget) {
+ nsContentUtils::DispatchTrustedEvent(
+ aDocument, selectstartEventTarget, u"selectstart"_ns, CanBubble::eYes,
+ Cancelable::eYes, &executeDefaultAction);
+ }
+
+ return executeDefaultAction;
+}
+
+// static
+bool Selection::IsUserSelectionCollapsed(
+ const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd) {
+ MOZ_ASSERT(aTempRangesToAdd.IsEmpty());
+
+ RefPtr<nsRange> scratchRange = aRange.CloneRange();
+ UserSelectRangesToAdd(scratchRange, aTempRangesToAdd);
+ const bool userSelectionCollapsed =
+ (aTempRangesToAdd.Length() == 0) ||
+ ((aTempRangesToAdd.Length() == 1) && aTempRangesToAdd[0]->Collapsed());
+
+ aTempRangesToAdd.ClearAndRetainStorage();
+
+ return userSelectionCollapsed;
+}
+
+nsresult Selection::AddRangesForUserSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ const DispatchSelectstartEvent aDispatchSelectstartEvent) {
+ MOZ_ASSERT(mUserInitiated);
+ MOZ_ASSERT(aOutIndex);
+ MOZ_ASSERT(aOutIndex->isNothing());
+
+ if (!aRange) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aRange->IsPositioned()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ AutoTArray<RefPtr<nsRange>, 4> rangesToAdd;
+ if (mStyledRanges.Length()) {
+ aOutIndex->emplace(mStyledRanges.Length() - 1);
+ }
+
+ Document* doc = GetDocument();
+
+ if (aDispatchSelectstartEvent == DispatchSelectstartEvent::Maybe &&
+ mSelectionType == SelectionType::eNormal && IsCollapsed() &&
+ !IsBlockingSelectionChangeEvents()) {
+ // We consider a selection to be starting if we are currently collapsed,
+ // and the selection is becoming uncollapsed, and this is caused by a
+ // user initiated event.
+
+ // First, we generate the ranges to add with a scratch range, which is a
+ // clone of the original range passed in. We do this seperately, because
+ // the selectstart event could have caused the world to change, and
+ // required ranges to be re-generated
+
+ const bool userSelectionCollapsed =
+ IsUserSelectionCollapsed(*aRange, rangesToAdd);
+ MOZ_ASSERT(userSelectionCollapsed || nsContentUtils::IsSafeToRunScript());
+ if (!userSelectionCollapsed && nsContentUtils::IsSafeToRunScript()) {
+ // The spec currently doesn't say that we should dispatch this event
+ // on text controls, so for now we only support doing that under a
+ // pref, disabled by default.
+ // See https://github.com/w3c/selection-api/issues/53.
+ const bool executeDefaultAction = MaybeDispatchSelectstartEvent(
+ *aRange,
+ StaticPrefs::dom_select_events_textcontrols_selectstart_enabled(),
+ doc);
+
+ if (!executeDefaultAction) {
+ return NS_OK;
+ }
+
+ // As we potentially dispatched an event to the DOM, something could have
+ // changed under our feet. Re-generate the rangesToAdd array, and
+ // ensure that the range we are about to add is still valid.
+ if (!aRange->IsPositioned()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ // Generate the ranges to add
+ UserSelectRangesToAdd(aRange, rangesToAdd);
+ size_t newAnchorFocusIndex =
+ GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1;
+ for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
+ Maybe<size_t> index;
+ // `MOZ_KnownLive` needed because of broken static analysis
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1).
+ nsresult rv = mStyledRanges.MaybeAddRangeAndTruncateOverlaps(
+ MOZ_KnownLive(rangesToAdd[i]), &index);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (i == newAnchorFocusIndex) {
+ *aOutIndex = index;
+ rangesToAdd[i]->SetIsGenerated(false);
+ } else {
+ rangesToAdd[i]->SetIsGenerated(true);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult Selection::AddRangesForSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ const DispatchSelectstartEvent aDispatchSelectstartEvent) {
+ MOZ_ASSERT(aOutIndex);
+ MOZ_ASSERT(aOutIndex->isNothing());
+
+ if (!aRange) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aRange->IsPositioned()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_LOG(
+ sSelectionLog, LogLevel::Debug,
+ ("%s: selection=%p, type=%i, range=(%p, StartOffset=%u, EndOffset=%u)",
+ __FUNCTION__, this, static_cast<int>(GetType()), aRange,
+ aRange->StartOffset(), aRange->EndOffset()));
+
+ if (mUserInitiated) {
+ return AddRangesForUserSelectableNodes(aRange, aOutIndex,
+ aDispatchSelectstartEvent);
+ }
+
+ return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex);
+}
+
+nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
+ nsRange* aRange, Maybe<size_t>* aOutIndex) {
+ MOZ_ASSERT(aRange);
+ MOZ_ASSERT(aRange->IsPositioned());
+ MOZ_ASSERT(aOutIndex);
+ MOZ_ASSERT(aOutIndex->isNothing());
+
+ // a common case is that we have no ranges yet
+ if (mRanges.Length() == 0) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mRanges.AppendElement(StyledRange(aRange));
+ aRange->RegisterSelection(MOZ_KnownLive(mSelection));
+
+ aOutIndex->emplace(0u);
+ return NS_OK;
+ }
+
+ Maybe<size_t> maybeStartIndex, maybeEndIndex;
+ nsresult rv =
+ GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(),
+ aRange->GetEndContainer(), aRange->EndOffset(),
+ false, maybeStartIndex, maybeEndIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ size_t startIndex, endIndex;
+ if (maybeEndIndex.isNothing()) {
+ // All ranges start after the given range. We can insert our range at
+ // position 0, knowing there are no overlaps (handled below)
+ startIndex = endIndex = 0;
+ } else if (maybeStartIndex.isNothing()) {
+ // All ranges end before the given range. We can insert our range at
+ // the end of the array, knowing there are no overlaps (handled below)
+ startIndex = endIndex = mRanges.Length();
+ } else {
+ startIndex = *maybeStartIndex;
+ endIndex = *maybeEndIndex;
+ }
+
+ // If the range is already contained in mRanges, silently
+ // succeed
+ const bool sameRange = HasEqualRangeBoundariesAt(*aRange, startIndex);
+ if (sameRange) {
+ aOutIndex->emplace(startIndex);
+ return NS_OK;
+ }
+
+ if (startIndex == endIndex) {
+ // The new range doesn't overlap any existing ranges
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mRanges.InsertElementAt(startIndex, StyledRange(aRange));
+ aRange->RegisterSelection(MOZ_KnownLive(mSelection));
+ aOutIndex->emplace(startIndex);
+ return NS_OK;
+ }
+
+ // We now know that at least 1 existing range overlaps with the range that
+ // we are trying to add. In fact, the only ranges of interest are those at
+ // the two end points, startIndex and endIndex - 1 (which may point to the
+ // same range) as these may partially overlap the new range. Any ranges
+ // between these indices are fully overlapped by the new range, and so can be
+ // removed.
+ AutoTArray<StyledRange, 2> overlaps;
+ overlaps.AppendElement(mRanges[startIndex]);
+ if (endIndex - 1 != startIndex) {
+ overlaps.AppendElement(mRanges[endIndex - 1]);
+ }
+
+ // Remove all the overlapping ranges
+ for (size_t i = startIndex; i < endIndex; ++i) {
+ mRanges[i].mRange->UnregisterSelection(mSelection);
+ }
+ mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
+
+ AutoTArray<StyledRange, 3> temp;
+ for (const size_t i : Reversed(IntegerRange(overlaps.Length()))) {
+ nsresult rv = SubtractRange(overlaps[i], *aRange, &temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Insert the new element into our "leftovers" array
+ // `aRange` is positioned, so it has to have a start container.
+ size_t insertionPoint{FindInsertionPoint(&temp, *aRange->GetStartContainer(),
+ aRange->StartOffset(),
+ CompareToRangeStart)};
+
+ temp.InsertElementAt(insertionPoint, StyledRange(aRange));
+
+ // Merge the leftovers back in to mRanges
+ mRanges.InsertElementsAt(startIndex, temp);
+
+ for (uint32_t i = 0; i < temp.Length(); ++i) {
+ if (temp[i].mRange->IsDynamicRange()) {
+ MOZ_KnownLive(temp[i].mRange->AsDynamicRange())
+ ->RegisterSelection(MOZ_KnownLive(mSelection));
+ // `MOZ_KnownLive` is required because of
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253.
+ }
+ }
+
+ aOutIndex->emplace(startIndex + insertionPoint);
+ return NS_OK;
+}
+
+nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
+ AbstractRange& aRange) {
+ // Find the range's index & remove it. We could use FindInsertionPoint to
+ // get O(log n) time, but that requires many expensive DOM comparisons.
+ // For even several thousand items, this is probably faster because the
+ // comparisons are so fast.
+ int32_t idx = -1;
+ uint32_t i;
+ for (i = 0; i < mRanges.Length(); i++) {
+ if (mRanges[i].mRange == &aRange) {
+ idx = (int32_t)i;
+ break;
+ }
+ }
+ if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
+
+ mRanges.RemoveElementAt(idx);
+ aRange.UnregisterSelection(mSelection);
+
+ return NS_OK;
+}
+nsresult Selection::RemoveCollapsedRanges() {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ return mStyledRanges.RemoveCollapsedRanges();
+}
+
+nsresult Selection::StyledRanges::RemoveCollapsedRanges() {
+ uint32_t i = 0;
+ while (i < mRanges.Length()) {
+ if (mRanges[i].mRange->Collapsed()) {
+ nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ ++i;
+ }
+ }
+ return NS_OK;
+}
+
+void Selection::Clear(nsPresContext* aPresContext) {
+ RemoveAnchorFocusRange();
+
+ mStyledRanges.UnregisterSelection();
+ for (uint32_t i = 0; i < mStyledRanges.Length(); ++i) {
+ SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, false);
+ }
+ mStyledRanges.Clear();
+
+ // Reset direction so for more dependable table selection range handling
+ SetDirection(eDirNext);
+
+ // If this was an ATTENTION selection, change it back to normal now
+ if (mFrameSelection && mFrameSelection->GetDisplaySelection() ==
+ nsISelectionController::SELECTION_ATTENTION) {
+ mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
+ }
+}
+
+bool Selection::StyledRanges::HasEqualRangeBoundariesAt(
+ const nsRange& aRange, size_t aRangeIndex) const {
+ if (aRangeIndex < mRanges.Length()) {
+ const AbstractRange* range = mRanges[aRangeIndex].mRange;
+ return range->HasEqualBoundaries(aRange);
+ }
+ return false;
+}
+
+void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset,
+ nsINode& aEndNode, uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ nsTArray<RefPtr<nsRange>>& aReturn,
+ mozilla::ErrorResult& aRv) {
+ AutoTArray<nsRange*, 2> results;
+ nsresult rv =
+ GetDynamicRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode,
+ aEndOffset, aAllowAdjacent, &results);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ aReturn.SetLength(results.Length());
+ for (size_t i = 0; i < results.Length(); ++i) {
+ aReturn[i] = results[i]; // AddRefs
+ }
+}
+
+nsresult Selection::GetAbstractRangesForIntervalArray(
+ nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
+ uint32_t aEndOffset, bool aAllowAdjacent,
+ nsTArray<AbstractRange*>* aRanges) {
+ if (NS_WARN_IF(!aBeginNode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (NS_WARN_IF(!aEndNode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aRanges->Clear();
+ Maybe<size_t> maybeStartIndex, maybeEndIndex;
+ nsresult res = mStyledRanges.GetIndicesForInterval(
+ aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
+ maybeStartIndex, maybeEndIndex);
+ NS_ENSURE_SUCCESS(res, res);
+
+ if (maybeStartIndex.isNothing() || maybeEndIndex.isNothing()) {
+ return NS_OK;
+ }
+
+ for (const size_t i : IntegerRange(*maybeStartIndex, *maybeEndIndex)) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aRanges->AppendElement(mStyledRanges.mRanges[i].mRange);
+ }
+
+ return NS_OK;
+}
+
+nsresult Selection::GetDynamicRangesForIntervalArray(
+ nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
+ uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges) {
+ MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
+ AutoTArray<AbstractRange*, 2> abstractRanges;
+ nsresult rv = GetAbstractRangesForIntervalArray(
+ aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent,
+ &abstractRanges);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aRanges->Clear();
+ aRanges->SetCapacity(abstractRanges.Length());
+ for (auto* abstractRange : abstractRanges) {
+ aRanges->AppendElement(abstractRange->AsDynamicRange());
+ }
+ return NS_OK;
+}
+
+nsresult Selection::StyledRanges::GetIndicesForInterval(
+ const nsINode* aBeginNode, uint32_t aBeginOffset, const nsINode* aEndNode,
+ uint32_t aEndOffset, bool aAllowAdjacent, Maybe<size_t>& aStartIndex,
+ Maybe<size_t>& aEndIndex) const {
+ MOZ_ASSERT(aStartIndex.isNothing());
+ MOZ_ASSERT(aEndIndex.isNothing());
+
+ if (NS_WARN_IF(!aBeginNode)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (NS_WARN_IF(!aEndNode)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (mRanges.Length() == 0) {
+ return NS_OK;
+ }
+
+ const bool intervalIsCollapsed =
+ aBeginNode == aEndNode && aBeginOffset == aEndOffset;
+
+ // Ranges that end before the given interval and begin after the given
+ // interval can be discarded
+ size_t endsBeforeIndex{FindInsertionPoint(&mRanges, *aEndNode, aEndOffset,
+ &CompareToRangeStart)};
+
+ if (endsBeforeIndex == 0) {
+ const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
+
+ // If the interval is strictly before the range at index 0, we can optimize
+ // by returning now - all ranges start after the given interval
+ if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
+ return NS_OK;
+ }
+
+ // We now know that the start point of mRanges[0].mRange
+ // equals the end of the interval. Thus, when aAllowadjacent is true, the
+ // caller is always interested in this range. However, when excluding
+ // adjacencies, we must remember to include the range when both it and the
+ // given interval are collapsed to the same point
+ if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed))
+ return NS_OK;
+ }
+ aEndIndex.emplace(endsBeforeIndex);
+
+ size_t beginsAfterIndex{FindInsertionPoint(&mRanges, *aBeginNode,
+ aBeginOffset, &CompareToRangeEnd)};
+
+ if (beginsAfterIndex == mRanges.Length()) {
+ return NS_OK; // optimization: all ranges are strictly before us
+ }
+
+ if (aAllowAdjacent) {
+ // At this point, one of the following holds:
+ // endsBeforeIndex == mRanges.Length(),
+ // endsBeforeIndex points to a range whose start point does not equal the
+ // given interval's start point
+ // endsBeforeIndex points to a range whose start point equals the given
+ // interval's start point
+ // In the final case, there can be two such ranges, a collapsed range, and
+ // an adjacent range (they will appear in mRanges in that
+ // order). For this final case, we need to increment endsBeforeIndex, until
+ // one of the first two possibilities hold
+ while (endsBeforeIndex < mRanges.Length()) {
+ const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
+ if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) {
+ break;
+ }
+ endsBeforeIndex++;
+ }
+
+ // Likewise, one of the following holds:
+ // beginsAfterIndex == 0,
+ // beginsAfterIndex points to a range whose end point does not equal
+ // the given interval's end point
+ // beginsOnOrAfter points to a range whose end point equals the given
+ // interval's end point
+ // In the final case, there can be two such ranges, an adjacent range, and
+ // a collapsed range (they will appear in mRanges in that
+ // order). For this final case, we only need to take action if both those
+ // ranges exist, and we are pointing to the collapsed range - we need to
+ // point to the adjacent range
+ const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
+ if (beginsAfterIndex > 0 && beginRange->Collapsed() &&
+ beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
+ beginRange = mRanges[beginsAfterIndex - 1].mRange;
+ if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) {
+ beginsAfterIndex--;
+ }
+ }
+ } else {
+ // See above for the possibilities at this point. The only case where we
+ // need to take action is when the range at beginsAfterIndex ends on
+ // the given interval's start point, but that range isn't collapsed (a
+ // collapsed range should be included in the returned results).
+ const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
+ if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset) &&
+ !beginRange->Collapsed()) {
+ beginsAfterIndex++;
+ }
+
+ // Again, see above for the meaning of endsBeforeIndex at this point.
+ // In particular, endsBeforeIndex may point to a collaped range which
+ // represents the point at the end of the interval - this range should be
+ // included
+ if (endsBeforeIndex < mRanges.Length()) {
+ const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
+ if (endRange->StartRef().Equals(aEndNode, aEndOffset) &&
+ endRange->Collapsed()) {
+ endsBeforeIndex++;
+ }
+ }
+ }
+
+ NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, "Is mRanges not ordered?");
+ NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
+
+ aStartIndex.emplace(beginsAfterIndex);
+ aEndIndex = Some(endsBeforeIndex);
+ return NS_OK;
+}
+
+nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ int32_t frameOffset = 0;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
+ if (content && mFrameSelection) {
+ return nsFrameSelection::GetFrameForNodeOffset(
+ content, AnchorOffset(), mFrameSelection->GetHint(), &frameOffset);
+ }
+ return nullptr;
+}
+
+nsIFrame* Selection::GetPrimaryFrameForFocusNode(bool aVisual,
+ int32_t* aOffsetUsed) const {
+ nsINode* focusNode = GetFocusNode();
+ if (!focusNode || !focusNode->IsContent() || !mFrameSelection) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIContent> content = focusNode->AsContent();
+ int32_t frameOffset = 0;
+ if (!aOffsetUsed) {
+ aOffsetUsed = &frameOffset;
+ }
+
+ nsIFrame* frame = GetPrimaryOrCaretFrameForNodeOffset(content, FocusOffset(),
+ aOffsetUsed, aVisual);
+ if (frame) {
+ return frame;
+ }
+
+ // If content is whitespace only, we promote focus node to parent because
+ // whitespace only node might have no frame.
+
+ if (!content->TextIsOnlyWhitespace()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIContent> parent = content->GetParent();
+ if (NS_WARN_IF(!parent)) {
+ return nullptr;
+ }
+ const Maybe<uint32_t> offset = parent->ComputeIndexOf(content);
+ if (MOZ_UNLIKELY(NS_WARN_IF(offset.isNothing()))) {
+ return nullptr;
+ }
+ return GetPrimaryOrCaretFrameForNodeOffset(parent, *offset, aOffsetUsed,
+ aVisual);
+}
+
+nsIFrame* Selection::GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent,
+ uint32_t aOffset,
+ int32_t* aOffsetUsed,
+ bool aVisual) const {
+ MOZ_ASSERT(aOffsetUsed);
+
+ if (!mFrameSelection) {
+ return nullptr;
+ }
+
+ CaretAssociationHint hint = mFrameSelection->GetHint();
+
+ if (aVisual) {
+ mozilla::intl::BidiEmbeddingLevel caretBidiLevel =
+ mFrameSelection->GetCaretBidiLevel();
+
+ return nsCaret::GetCaretFrameForNodeOffset(
+ mFrameSelection, aContent, aOffset, hint, caretBidiLevel,
+ /* aReturnUnadjustedFrame = */ nullptr, aOffsetUsed);
+ }
+
+ return nsFrameSelection::GetFrameForNodeOffset(aContent, aOffset, hint,
+ aOffsetUsed);
+}
+
+void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+ // The frame could be an SVG text frame, in which case we don't treat it
+ // as a text frame.
+ if (frame->IsTextFrame()) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+ textFrame->SelectionStateChanged(0, textFrame->TextFragment()->GetLength(),
+ aSelected, mSelectionType);
+ } else {
+ frame->SelectionStateChanged();
+ }
+}
+
+nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent(
+ PostContentIterator& aPostOrderIter, nsIContent* aContent,
+ bool aSelected) const {
+ // If aContent doesn't have children, we should avoid to use the content
+ // iterator for performance reason.
+ if (!aContent->HasChildren()) {
+ SelectFramesOf(aContent, aSelected);
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(aPostOrderIter.Init(aContent)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (; !aPostOrderIter.IsDone(); aPostOrderIter.Next()) {
+ nsINode* node = aPostOrderIter.GetCurrentNode();
+ MOZ_ASSERT(node);
+ nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
+ SelectFramesOf(innercontent, aSelected);
+ }
+
+ return NS_OK;
+}
+
+void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) {
+ // this method is currently only called in a user-initiated context.
+ // therefore it is safe to assume that we are not in a Highlight selection
+ // and we only have to deal with nsRanges (no StaticRanges).
+ MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
+ for (size_t i = 0; i < mStyledRanges.Length(); ++i) {
+ nsRange* range = mStyledRanges.mRanges[i].mRange->AsDynamicRange();
+ MOZ_ASSERT(range->IsInAnySelection());
+ SelectFrames(aPresContext, *range, range->IsInAnySelection());
+ }
+}
+
+/**
+ * The idea of this helper method is to select or deselect "top to bottom",
+ * traversing through the frames
+ */
+nsresult Selection::SelectFrames(nsPresContext* aPresContext,
+ AbstractRange& aRange, bool aSelect) const {
+ if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) {
+ // nothing to do
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
+
+ const Document* const document = GetDocument();
+ if (MOZ_UNLIKELY(!document ||
+ aRange.GetComposedDocOfContainers() != document)) {
+ return NS_OK; // Do nothing if the range is now in different document.
+ }
+
+ if (aRange.IsStaticRange() && !aRange.AsStaticRange()->IsValid()) {
+ // TODO jjaschke: Actions necessary to unselect invalid static ranges?
+ return NS_OK;
+ }
+
+ if (mFrameSelection->IsInTableSelectionMode()) {
+ const nsIContent* const commonAncestorContent =
+ nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor());
+ nsIFrame* const frame = commonAncestorContent
+ ? commonAncestorContent->GetPrimaryFrame()
+ : aPresContext->PresShell()->GetRootFrame();
+ if (frame) {
+ if (frame->IsTextFrame()) {
+ MOZ_ASSERT(commonAncestorContent == aRange.GetStartContainer());
+ MOZ_ASSERT(commonAncestorContent == aRange.GetEndContainer());
+ static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
+ aRange.StartOffset(), aRange.EndOffset(), aSelect, mSelectionType);
+ } else {
+ frame->SelectionStateChanged();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ // Loop through the content iterator for each content node; for each text
+ // node, call SetSelected on it:
+ nsIContent* const startContent =
+ nsIContent::FromNodeOrNull(aRange.GetStartContainer());
+ if (MOZ_UNLIKELY(!startContent)) {
+ // Don't warn, bug 1055722
+ // XXX The range can start from a document node and such range can be
+ // added to Selection with JS. Therefore, even in such cases,
+ // shouldn't we handle selection in the range?
+ return NS_ERROR_UNEXPECTED;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc());
+
+ // We must call first one explicitly
+ nsINode* const endNode = aRange.GetEndContainer();
+ if (NS_WARN_IF(!endNode)) {
+ // We null-checked start node above, therefore, end node should also be
+ // non-null here.
+ return NS_ERROR_UNEXPECTED;
+ }
+ const bool isFirstContentTextNode = startContent->IsText();
+ if (isFirstContentTextNode) {
+ if (nsIFrame* const frame = startContent->GetPrimaryFrame()) {
+ // The frame could be an SVG text frame, in which case we don't treat it
+ // as a text frame.
+ if (frame->IsTextFrame()) {
+ const uint32_t startOffset = aRange.StartOffset();
+ const uint32_t endOffset = endNode == startContent
+ ? aRange.EndOffset()
+ : startContent->Length();
+ static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
+ startOffset, endOffset, aSelect, mSelectionType);
+ } else {
+ frame->SelectionStateChanged();
+ }
+ }
+ }
+
+ // If the range is in a node and the node is a leaf node, we don't need to
+ // walk the subtree.
+ if (aRange.Collapsed() ||
+ (startContent == endNode && !startContent->HasChildren())) {
+ if (!isFirstContentTextNode) {
+ SelectFramesOf(startContent, aSelect);
+ }
+ return NS_OK;
+ }
+
+ ContentSubtreeIterator subtreeIter;
+ subtreeIter.Init(&aRange);
+ if (isFirstContentTextNode && !subtreeIter.IsDone() &&
+ subtreeIter.GetCurrentNode() == startContent) {
+ subtreeIter.Next(); // first content has already been handled.
+ }
+ PostContentIterator postOrderIter;
+ for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+ MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode());
+ if (nsIContent* const content =
+ nsIContent::FromNodeOrNull(subtreeIter.GetCurrentNode())) {
+ SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
+ aSelect);
+ }
+ }
+
+ // We must now do the last one if it is not the same as the first
+ if (endNode == startContent || !endNode->IsText()) {
+ return NS_OK;
+ }
+
+ if (nsIFrame* const frame = endNode->AsText()->GetPrimaryFrame()) {
+ // The frame could be an SVG text frame, in which case we'll ignore it.
+ if (frame->IsTextFrame()) {
+ static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
+ 0, aRange.EndOffset(), aSelect, mSelectionType);
+ }
+ }
+ return NS_OK;
+}
+
+// Selection::LookUpSelection
+//
+// This function is called when a node wants to know where the selection is
+// over itself.
+//
+// Usually, this is called when we already know there is a selection over
+// the node in question, and we only need to find the boundaries of it on
+// that node. This is when slowCheck is false--a strict test is not needed.
+// Other times, the caller has no idea, and wants us to test everything,
+// so we are supposed to determine whether there is a selection over the
+// node at all.
+//
+// A previous version of this code used this flag to do less work when
+// inclusion was already known (slowCheck=false). However, our tree
+// structure allows us to quickly determine ranges overlapping the node,
+// so we just ignore the slowCheck flag and do the full test every time.
+//
+// PERFORMANCE: a common case is that we are doing a fast check with exactly
+// one range in the selection. In this case, this function is slower than
+// brute force because of the overhead of checking the tree. We can optimize
+// this case to make it faster by doing the same thing the previous version
+// of this function did in the case of 1 range. This would also mean that
+// the aSlowCheck flag would have meaning again.
+
+UniquePtr<SelectionDetails> Selection::LookUpSelection(
+ nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength,
+ UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType,
+ bool aSlowCheck) {
+ if (!aContent) {
+ return aDetailsHead;
+ }
+
+ // it is common to have no ranges, to optimize that
+ if (mStyledRanges.Length() == 0) {
+ return aDetailsHead;
+ }
+
+ nsTArray<AbstractRange*> overlappingRanges;
+ nsresult rv = GetAbstractRangesForIntervalArray(
+ aContent, aContentOffset, aContent, aContentOffset + aContentLength,
+ false, &overlappingRanges);
+ if (NS_FAILED(rv)) {
+ return aDetailsHead;
+ }
+
+ if (overlappingRanges.Length() == 0) {
+ return aDetailsHead;
+ }
+
+ UniquePtr<SelectionDetails> detailsHead = std::move(aDetailsHead);
+
+ for (size_t i = 0; i < overlappingRanges.Length(); i++) {
+ AbstractRange* range = overlappingRanges[i];
+ if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
+ continue;
+ }
+ nsINode* startNode = range->GetStartContainer();
+ nsINode* endNode = range->GetEndContainer();
+ uint32_t startOffset = range->StartOffset();
+ uint32_t endOffset = range->EndOffset();
+
+ Maybe<uint32_t> start, end;
+ if (startNode == aContent && endNode == aContent) {
+ if (startOffset < (aContentOffset + aContentLength) &&
+ endOffset > aContentOffset) {
+ // this range is totally inside the requested content range
+ start.emplace(
+ startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
+ end.emplace(std::min(aContentLength, endOffset - aContentOffset));
+ }
+ // otherwise, range is inside the requested node, but does not intersect
+ // the requested content range, so ignore it
+ } else if (startNode == aContent) {
+ if (startOffset < (aContentOffset + aContentLength)) {
+ // the beginning of the range is inside the requested node, but the
+ // end is outside, select everything from there to the end
+ start.emplace(
+ startOffset >= aContentOffset ? startOffset - aContentOffset : 0u);
+ end.emplace(aContentLength);
+ }
+ } else if (endNode == aContent) {
+ if (endOffset > aContentOffset) {
+ // the end of the range is inside the requested node, but the beginning
+ // is outside, select everything from the beginning to there
+ start.emplace(0u);
+ end.emplace(std::min(aContentLength, endOffset - aContentOffset));
+ }
+ } else {
+ // this range does not begin or end in the requested node, but since
+ // GetRangesForInterval returned this range, we know it overlaps.
+ // Therefore, this node is enclosed in the range, and we select all
+ // of it.
+ start.emplace(0u);
+ end.emplace(aContentLength);
+ }
+ if (start.isNothing()) {
+ continue; // the ranges do not overlap the input range
+ }
+
+ auto newHead = MakeUnique<SelectionDetails>();
+
+ newHead->mNext = std::move(detailsHead);
+ newHead->mStart = AssertedCast<int32_t>(*start);
+ newHead->mEnd = AssertedCast<int32_t>(*end);
+ newHead->mSelectionType = aSelectionType;
+ newHead->mHighlightName = mHighlightName;
+ StyledRange* rd = mStyledRanges.FindRangeData(range);
+ if (rd) {
+ newHead->mTextRangeStyle = rd->mTextRangeStyle;
+ }
+ detailsHead = std::move(newHead);
+ }
+ return detailsHead;
+}
+
+NS_IMETHODIMP
+Selection::Repaint(nsPresContext* aPresContext) {
+ int32_t arrCount = (int32_t)mStyledRanges.Length();
+
+ if (arrCount < 1) return NS_OK;
+
+ int32_t i;
+
+ for (i = 0; i < arrCount; i++) {
+ MOZ_ASSERT(mStyledRanges.mRanges[i].mRange);
+ nsresult rv =
+ SelectFrames(aPresContext, *mStyledRanges.mRanges[i].mRange, true);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) {
+ if (!mCachedOffsetForFrame) {
+ mCachedOffsetForFrame = new CachedOffsetForFrame;
+ }
+
+ mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
+
+ // clean up cached frame when turn off cache
+ // fix bug 207936
+ if (!aCanCacheFrameOffset) {
+ mCachedOffsetForFrame->mLastCaretFrame = nullptr;
+ }
+}
+
+nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
+ nsPoint& aPoint) {
+ if (!mCachedOffsetForFrame) {
+ mCachedOffsetForFrame = new CachedOffsetForFrame;
+ }
+
+ nsresult rv = NS_OK;
+ if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
+ mCachedOffsetForFrame->mLastCaretFrame &&
+ (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
+ (inOffset == mCachedOffsetForFrame->mLastContentOffset)) {
+ // get cached frame offset
+ aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
+ } else {
+ // Recalculate frame offset and cache it. Don't cache a frame offset if
+ // GetPointFromOffset fails, though.
+ rv = aFrame->GetPointFromOffset(inOffset, &aPoint);
+ if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
+ mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
+ mCachedOffsetForFrame->mLastCaretFrame = aFrame;
+ mCachedOffsetForFrame->mLastContentOffset = inOffset;
+ }
+ }
+
+ return rv;
+}
+
+nsIContent* Selection::GetAncestorLimiter() const {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (mFrameSelection) {
+ return mFrameSelection->GetAncestorLimiter();
+ }
+ return nullptr;
+}
+
+void Selection::SetAncestorLimiter(nsIContent* aLimiter) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aLimiter", aLimiter);
+ LogStackForSelectionAPI();
+ }
+
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->SetAncestorLimiter(aLimiter);
+ }
+}
+
+void Selection::StyledRanges::UnregisterSelection() {
+ uint32_t count = mRanges.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ mRanges[i].mRange->UnregisterSelection(mSelection);
+ }
+}
+
+void Selection::StyledRanges::Clear() { mRanges.Clear(); }
+
+StyledRange* Selection::StyledRanges::FindRangeData(AbstractRange* aRange) {
+ NS_ENSURE_TRUE(aRange, nullptr);
+ for (uint32_t i = 0; i < mRanges.Length(); i++) {
+ if (mRanges[i].mRange == aRange) {
+ return &mRanges[i];
+ }
+ }
+ return nullptr;
+}
+
+Selection::StyledRanges::Elements::size_type Selection::StyledRanges::Length()
+ const {
+ return mRanges.Length();
+}
+
+nsresult Selection::SetTextRangeStyle(nsRange* aRange,
+ const TextRangeStyle& aTextRangeStyle) {
+ NS_ENSURE_ARG_POINTER(aRange);
+ StyledRange* rd = mStyledRanges.FindRangeData(aRange);
+ if (rd) {
+ rd->mTextRangeStyle = aTextRangeStyle;
+ }
+ return NS_OK;
+}
+
+nsresult Selection::StartAutoScrollTimer(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ uint32_t aDelayInMs) {
+ MOZ_ASSERT(aFrame, "Need a frame");
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (!mFrameSelection) {
+ return NS_OK; // nothing to do
+ }
+
+ if (!mAutoScroller) {
+ mAutoScroller = new AutoScroller(mFrameSelection);
+ }
+
+ mAutoScroller->SetDelay(aDelayInMs);
+
+ RefPtr<AutoScroller> autoScroller{mAutoScroller};
+ return autoScroller->DoAutoScroll(aFrame, aPoint);
+}
+
+nsresult Selection::StopAutoScrollTimer() {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (mAutoScroller) {
+ mAutoScroller->Stop(AutoScroller::FurtherScrollingAllowed::kYes);
+ }
+
+ return NS_OK;
+}
+
+nsresult AutoScroller::DoAutoScroll(nsIFrame* aFrame, nsPoint aPoint) {
+ MOZ_ASSERT(aFrame, "Need a frame");
+
+ Stop(FurtherScrollingAllowed::kYes);
+
+ nsPresContext* presContext = aFrame->PresContext();
+ RefPtr<PresShell> presShell = presContext->PresShell();
+ nsRootPresContext* rootPC = presContext->GetRootPresContext();
+ if (!rootPC) {
+ return NS_OK;
+ }
+ nsIFrame* rootmostFrame = rootPC->PresShell()->GetRootFrame();
+ AutoWeakFrame weakRootFrame(rootmostFrame);
+ AutoWeakFrame weakFrame(aFrame);
+ // Get the point relative to the root most frame because the scroll we are
+ // about to do will change the coordinates of aFrame.
+ nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
+
+ bool done = false;
+ bool didScroll;
+ while (true) {
+ didScroll = presShell->ScrollFrameIntoView(
+ aFrame, Some(nsRect(aPoint, nsSize())), ScrollAxis(), ScrollAxis(),
+ ScrollFlags::None);
+ if (!weakFrame || !weakRootFrame) {
+ return NS_OK;
+ }
+ if (!didScroll && !done) {
+ // If aPoint is at the very edge of the root, then try to scroll anyway,
+ // once.
+ nsRect rootRect = rootmostFrame->GetRect();
+ nscoord onePx = AppUnitsPerCSSPixel();
+ nscoord scrollAmount = 10 * onePx;
+ if (std::abs(rootRect.x - globalPoint.x) <= onePx) {
+ aPoint.x -= scrollAmount;
+ } else if (std::abs(rootRect.XMost() - globalPoint.x) <= onePx) {
+ aPoint.x += scrollAmount;
+ } else if (std::abs(rootRect.y - globalPoint.y) <= onePx) {
+ aPoint.y -= scrollAmount;
+ } else if (std::abs(rootRect.YMost() - globalPoint.y) <= onePx) {
+ aPoint.y += scrollAmount;
+ } else {
+ break;
+ }
+ done = true;
+ continue;
+ }
+ break;
+ }
+
+ // Start the AutoScroll timer if necessary.
+ // `ScrollFrameRectIntoView` above may have run script and this may have
+ // forbidden to continue scrolling.
+ if (didScroll && mFurtherScrollingAllowed == FurtherScrollingAllowed::kYes) {
+ nsPoint presContextPoint =
+ globalPoint -
+ presShell->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
+ ScheduleNextDoAutoScroll(presContext, presContextPoint);
+ }
+
+ return NS_OK;
+}
+
+void Selection::RemoveAllRanges(ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ RemoveAllRangesInternal(aRv);
+}
+
+void Selection::RemoveAllRangesInternal(ErrorResult& aRv) {
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ Clear(presContext);
+
+ // Turn off signal for table selection
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->ClearTableCellSelection();
+
+ RefPtr<Selection> kungFuDeathGrip{this};
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+void Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ RefPtr<Document> document(GetDocument());
+ AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document, aRv);
+}
+
+void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
+ LogStackForSelectionAPI();
+ }
+
+ RefPtr<Document> document(GetDocument());
+ return AddRangeAndSelectFramesAndNotifyListenersInternal(aRange, document,
+ aRv);
+}
+
+void Selection::AddRangeAndSelectFramesAndNotifyListenersInternal(
+ nsRange& aRange, Document* aDocument, ErrorResult& aRv) {
+ RefPtr<nsRange> range = &aRange;
+ if (aRange.IsInAnySelection()) {
+ if (aRange.IsInSelection(*this)) {
+ // If we already have the range, we don't need to handle this except
+ // setting the interline position.
+ if (mSelectionType == SelectionType::eNormal) {
+ SetInterlinePosition(InterlinePosition::StartOfNextLine);
+ }
+ return;
+ }
+ if (mSelectionType != SelectionType::eNormal &&
+ mSelectionType != SelectionType::eHighlight) {
+ range = aRange.CloneRange();
+ }
+ }
+
+ nsINode* rangeRoot = range->GetRoot();
+ if (aDocument != rangeRoot &&
+ (!rangeRoot || aDocument != rangeRoot->GetComposedDoc())) {
+ // http://w3c.github.io/selection-api/#dom-selection-addrange
+ // "... if the root of the range's boundary points are the document
+ // associated with context object. Otherwise, this method must do nothing."
+ return;
+ }
+
+ // MaybeAddTableCellRange might flush frame and `NotifySelectionListeners`
+ // below might destruct `this`.
+ RefPtr<Selection> kungFuDeathGrip(this);
+
+ // This inserts a table cell range in proper document order
+ // and returns NS_OK if range doesn't contain just one table cell
+ Maybe<size_t> maybeRangeIndex;
+ nsresult result = MaybeAddTableCellRange(*range, &maybeRangeIndex);
+ if (NS_FAILED(result)) {
+ aRv.Throw(result);
+ return;
+ }
+
+ if (maybeRangeIndex.isNothing()) {
+ result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
+ DispatchSelectstartEvent::Maybe);
+ if (NS_FAILED(result)) {
+ aRv.Throw(result);
+ return;
+ }
+ if (maybeRangeIndex.isNothing()) {
+ return;
+ }
+ }
+
+ MOZ_ASSERT(*maybeRangeIndex < mStyledRanges.Length());
+
+ SetAnchorFocusRange(*maybeRangeIndex);
+
+ // Make sure the caret appears on the next line, if at a newline
+ if (mSelectionType == SelectionType::eNormal) {
+ SetInterlinePosition(InterlinePosition::StartOfNextLine);
+ }
+
+ if (!mFrameSelection) {
+ return; // nothing to do
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ SelectFrames(presContext, *range, true);
+
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+void Selection::AddHighlightRangeAndSelectFramesAndNotifyListeners(
+ AbstractRange& aRange) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
+
+ mStyledRanges.mRanges.AppendElement(StyledRange{&aRange});
+ aRange.RegisterSelection(*this);
+
+ if (!mFrameSelection) {
+ return; // nothing to do
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ SelectFrames(presContext, aRange, true);
+
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+// Selection::RemoveRangeAndUnselectFramesAndNotifyListeners
+//
+// Removes the given range from the selection. The tricky part is updating
+// the flags on the frames that indicate whether they have a selection or
+// not. There could be several selection ranges on the frame, and clearing
+// the bit would cause the selection to not be drawn, even when there is
+// another range on the frame (bug 346185).
+//
+// We therefore find any ranges that intersect the same nodes as the range
+// being removed, and cause them to set the selected bits back on their
+// selected frames after we've cleared the bit from ours.
+
+void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners(
+ AbstractRange& aRange, ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aRange", aRange);
+ LogStackForSelectionAPI();
+ }
+
+ nsresult rv = mStyledRanges.RemoveRangeAndUnregisterSelection(aRange);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsINode* beginNode = aRange.GetStartContainer();
+ nsINode* endNode = aRange.GetEndContainer();
+
+ if (!beginNode || !endNode) {
+ // Detached range; nothing else to do here.
+ return;
+ }
+
+ // find out the length of the end node, so we can select all of it
+ uint32_t beginOffset, endOffset;
+ if (endNode->IsText()) {
+ // Get the length of the text. We can't just use the offset because
+ // another range could be touching this text node but not intersect our
+ // range.
+ beginOffset = 0;
+ endOffset = endNode->AsText()->TextLength();
+ } else {
+ // For non-text nodes, the given offsets should be sufficient.
+ beginOffset = aRange.StartOffset();
+ endOffset = aRange.EndOffset();
+ }
+
+ // clear the selected bit from the removed range's frames
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ SelectFrames(presContext, aRange, false);
+
+ // add back the selected bit for each range touching our nodes
+ nsTArray<AbstractRange*> affectedRanges;
+ rv = GetAbstractRangesForIntervalArray(beginNode, beginOffset, endNode,
+ endOffset, true, &affectedRanges);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ for (uint32_t i = 0; i < affectedRanges.Length(); i++) {
+ MOZ_ASSERT(affectedRanges[i]);
+ SelectFrames(presContext, *affectedRanges[i], true);
+ }
+
+ if (&aRange == mAnchorFocusRange) {
+ const size_t rangeCount = mStyledRanges.Length();
+ if (rangeCount) {
+ SetAnchorFocusRange(rangeCount - 1);
+ } else {
+ RemoveAnchorFocusRange();
+ }
+
+ // When the selection is user-created it makes sense to scroll the range
+ // into view. The spell-check selection, however, is created and destroyed
+ // in the background. We don't want to scroll in this case or the view
+ // might appear to be moving randomly (bug 337871).
+ if (mSelectionType != SelectionType::eSpellCheck && rangeCount) {
+ ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION);
+ }
+ }
+
+ if (!mFrameSelection) return; // nothing to do
+
+ RefPtr<Selection> kungFuDeathGrip{this};
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+/*
+ * Collapse sets the whole selection to be one point.
+ */
+void Selection::CollapseJS(nsINode* aContainer, uint32_t aOffset,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
+ aOffset);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ if (!aContainer) {
+ RemoveAllRangesInternal(aRv);
+ return;
+ }
+ CollapseInternal(InLimiter::eNo, RawRangeBoundary(aContainer, aOffset), aRv);
+}
+
+void Selection::CollapseInLimiter(const RawRangeBoundary& aPoint,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aPoint", aPoint);
+ LogStackForSelectionAPI();
+ }
+
+ CollapseInternal(InLimiter::eYes, aPoint, aRv);
+}
+
+void Selection::CollapseInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aPoint,
+ ErrorResult& aRv) {
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
+ return;
+ }
+
+ if (!aPoint.IsSet()) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (aPoint.Container()->NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
+ aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
+ return;
+ }
+
+ // RawRangeBoundary::IsSetAndValid() checks if the point actually refers
+ // a child of the container when IsSet() is true. If its offset hasn't been
+ // computed yet, this just checks it with its mRef. So, we can avoid
+ // computing offset here.
+ if (!aPoint.IsSetAndValid()) {
+ aRv.ThrowIndexSizeError("The offset is out of range.");
+ return;
+ }
+
+ if (!HasSameRootOrSameComposedDoc(*aPoint.Container())) {
+ // Return with no error
+ return;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->InvalidateDesiredCaretPos();
+ if (aInLimiter == InLimiter::eYes &&
+ !frameSelection->IsValidSelectionPoint(aPoint.Container())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ nsresult result;
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (!presContext ||
+ presContext->Document() != aPoint.Container()->OwnerDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Delete all of the current ranges
+ Clear(presContext);
+
+ // Turn off signal for table selection
+ frameSelection->ClearTableCellSelection();
+
+ // Hack to display the caret on the right line (bug 1237236).
+ if (frameSelection->GetHint() != CARET_ASSOCIATE_AFTER &&
+ aPoint.Container()->IsContent()) {
+ int32_t frameOffset;
+ nsTextFrame* f = do_QueryFrame(nsCaret::GetFrameAndOffset(
+ this, aPoint.Container(),
+ *aPoint.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets),
+ &frameOffset));
+ if (f && f->IsAtEndOfLine() && f->HasSignificantTerminalNewline()) {
+ // RawRangeBounary::Offset() causes computing offset if it's not been
+ // done yet. However, it's called only when the container is a text
+ // node. In such case, offset has always been set since it cannot have
+ // any children. So, this doesn't cause computing offset with expensive
+ // method, nsINode::ComputeIndexOf().
+ if ((aPoint.Container()->AsContent() == f->GetContent() &&
+ f->GetContentEnd() ==
+ static_cast<int32_t>(*aPoint.Offset(
+ RawRangeBoundary::OffsetFilter::kValidOffsets))) ||
+ (aPoint.Container() == f->GetContent()->GetParentNode() &&
+ f->GetContent() == aPoint.GetPreviousSiblingOfChildAtOffset())) {
+ frameSelection->SetHint(CARET_ASSOCIATE_AFTER);
+ }
+ }
+ }
+
+ RefPtr<nsRange> range = nsRange::Create(aPoint.Container());
+ result = range->CollapseTo(aPoint);
+ if (NS_FAILED(result)) {
+ aRv.Throw(result);
+ return;
+ }
+
+#ifdef DEBUG_SELECTION
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aPoint.Container());
+ nsCOMPtr<Document> doc = do_QueryInterface(aPoint.Container());
+ printf("Sel. Collapse to %p %s %d\n", container.get(),
+ content ? nsAtomCString(content->NodeInfo()->NameAtom()).get()
+ : (doc ? "DOCUMENT" : "???"),
+ aPoint.Offset());
+#endif
+
+ Maybe<size_t> maybeRangeIndex;
+ result = AddRangesForSelectableNodes(range, &maybeRangeIndex,
+ DispatchSelectstartEvent::Maybe);
+ if (NS_FAILED(result)) {
+ aRv.Throw(result);
+ return;
+ }
+ SetAnchorFocusRange(0);
+ SelectFrames(presContext, *range, true);
+
+ RefPtr<Selection> kungFuDeathGrip{this};
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+/*
+ * Sets the whole selection to be one point
+ * at the start of the current selection
+ */
+void Selection::CollapseToStartJS(ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ CollapseToStart(aRv);
+}
+
+void Selection::CollapseToStart(ErrorResult& aRv) {
+ if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ if (RangeCount() == 0) {
+ aRv.ThrowInvalidStateError(kNoRangeExistsError);
+ return;
+ }
+
+ // Get the first range
+ const AbstractRange* firstRange = mStyledRanges.mRanges[0].mRange;
+ if (!firstRange) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mFrameSelection) {
+ mFrameSelection->AddChangeReasons(
+ nsISelectionListener::COLLAPSETOSTART_REASON);
+ }
+ nsINode* container = firstRange->GetStartContainer();
+ if (!container) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ CollapseInternal(InLimiter::eNo,
+ RawRangeBoundary(container, firstRange->StartOffset()), aRv);
+}
+
+/*
+ * Sets the whole selection to be one point
+ * at the end of the current selection
+ */
+void Selection::CollapseToEndJS(ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ CollapseToEnd(aRv);
+}
+
+void Selection::CollapseToEnd(ErrorResult& aRv) {
+ if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ uint32_t cnt = RangeCount();
+ if (cnt == 0) {
+ aRv.ThrowInvalidStateError(kNoRangeExistsError);
+ return;
+ }
+
+ // Get the last range
+ const AbstractRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange;
+ if (!lastRange) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mFrameSelection) {
+ mFrameSelection->AddChangeReasons(
+ nsISelectionListener::COLLAPSETOEND_REASON);
+ }
+ nsINode* container = lastRange->GetEndContainer();
+ if (!container) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ CollapseInternal(InLimiter::eNo,
+ RawRangeBoundary(container, lastRange->EndOffset()), aRv);
+}
+
+void Selection::GetType(nsAString& aOutType) const {
+ if (!RangeCount()) {
+ aOutType.AssignLiteral("None");
+ } else if (IsCollapsed()) {
+ aOutType.AssignLiteral("Caret");
+ } else {
+ aOutType.AssignLiteral("Range");
+ }
+}
+
+nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) {
+ nsRange* range = GetRangeAt(aIndex);
+ if (!range) {
+ aRv.ThrowIndexSizeError(nsPrintfCString("%u is out of range", aIndex));
+ return nullptr;
+ }
+
+ return range;
+}
+
+AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const {
+ StyledRange empty(nullptr);
+ return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange;
+}
+
+nsRange* Selection::GetRangeAt(uint32_t aIndex) const {
+ // This method per IDL spec returns a dynamic range.
+ // Therefore, it must be ensured that it is only called
+ // for a selection which contains dynamic ranges exclusively.
+ // Highlight Selections are allowed to contain StaticRanges,
+ // therefore this method must not be called.
+ MOZ_ASSERT(mSelectionType != SelectionType::eHighlight);
+ AbstractRange* abstractRange = GetAbstractRangeAt(aIndex);
+ if (!abstractRange) {
+ return nullptr;
+ }
+ return abstractRange->AsDynamicRange();
+}
+
+nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) {
+ NS_ENSURE_STATE(mAnchorFocusRange);
+
+ const DispatchSelectstartEvent dispatchSelectstartEvent =
+ IsCollapsed() ? DispatchSelectstartEvent::Maybe
+ : DispatchSelectstartEvent::No;
+
+ nsresult rv =
+ mStyledRanges.RemoveRangeAndUnregisterSelection(*mAnchorFocusRange);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<size_t> maybeOutIndex;
+ rv = AddRangesForSelectableNodes(aRange, &maybeOutIndex,
+ dispatchSelectstartEvent);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (maybeOutIndex.isSome()) {
+ SetAnchorFocusRange(*maybeOutIndex);
+ } else {
+ RemoveAnchorFocusRange();
+ }
+
+ return NS_OK;
+}
+
+void Selection::ReplaceAnchorFocusRange(nsRange* aRange) {
+ NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (presContext) {
+ SelectFrames(presContext, *mAnchorFocusRange, false);
+ SetAnchorFocusToRange(aRange);
+ SelectFrames(presContext, *mAnchorFocusRange, true);
+ }
+}
+
+void Selection::AdjustAnchorFocusForMultiRange(nsDirection aDirection) {
+ if (aDirection == mDirection) {
+ return;
+ }
+ SetDirection(aDirection);
+
+ if (RangeCount() <= 1) {
+ return;
+ }
+
+ nsRange* firstRange = GetRangeAt(0);
+ nsRange* lastRange = GetRangeAt(RangeCount() - 1);
+
+ if (mDirection == eDirPrevious) {
+ firstRange->SetIsGenerated(false);
+ lastRange->SetIsGenerated(true);
+ SetAnchorFocusRange(0);
+ } else { // aDir == eDirNext
+ firstRange->SetIsGenerated(true);
+ lastRange->SetIsGenerated(false);
+ SetAnchorFocusRange(RangeCount() - 1);
+ }
+}
+
+/*
+ * Extend extends the selection away from the anchor.
+ * We don't need to know the direction, because we always change the focus.
+ */
+void Selection::ExtendJS(nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aContainer", &aContainer, "aOffset",
+ aOffset);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ Extend(aContainer, aOffset, aRv);
+}
+
+nsresult Selection::Extend(nsINode* aContainer, uint32_t aOffset) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aContainer", aContainer, "aOffset",
+ aOffset);
+ LogStackForSelectionAPI();
+ }
+
+ if (!aContainer) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ErrorResult result;
+ Extend(*aContainer, aOffset, result);
+ return result.StealNSResult();
+}
+
+void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) {
+ /*
+ Notes which might come in handy for extend:
+
+ We can tell the direction of the selection by asking for the anchors
+ selection if the begin is less than the end then we know the selection is to
+ the "right", else it is a backwards selection. Notation: a = anchor, 1 = old
+ cursor, 2 = new cursor.
+
+ if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
+ if (a < 2 && 1 > 2) a,2,1
+ if (1 < a && a <2) 1,a,2
+ if (a > 2 && 2 >1) 1,2,a
+ if (2 < a && a <1) 2,a,1
+ if (a > 1 && 1 >2) 2,1,a
+ then execute
+ a 1 2 select from 1 to 2
+ a 2 1 deselect from 2 to 1
+ 1 a 2 deselect from 1 to a select from a to 2
+ 1 2 a deselect from 1 to 2
+ 2 1 a = continue selection from 2 to 1
+ */
+
+ // First, find the range containing the old focus point:
+ if (!mAnchorFocusRange) {
+ aRv.ThrowInvalidStateError(kNoRangeExistsError);
+ return;
+ }
+
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
+ return;
+ }
+
+ if (!HasSameRootOrSameComposedDoc(aContainer)) {
+ // Return with no error
+ return;
+ }
+
+ nsresult res;
+ if (!mFrameSelection->IsValidSelectionPoint(&aContainer)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (!presContext || presContext->Document() != aContainer.OwnerDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+#ifdef DEBUG_SELECTION
+ nsDirection oldDirection = GetDirection();
+#endif
+ nsINode* anchorNode = GetAnchorNode();
+ nsINode* focusNode = GetFocusNode();
+ const uint32_t anchorOffset = AnchorOffset();
+ const uint32_t focusOffset = FocusOffset();
+
+ RefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
+
+ nsINode* startNode = range->GetStartContainer();
+ nsINode* endNode = range->GetEndContainer();
+ const uint32_t startOffset = range->StartOffset();
+ const uint32_t endOffset = range->EndOffset();
+
+ bool shouldClearRange = false;
+ const Maybe<int32_t> anchorOldFocusOrder = nsContentUtils::ComparePoints(
+ anchorNode, anchorOffset, focusNode, focusOffset);
+ shouldClearRange |= !anchorOldFocusOrder;
+ const Maybe<int32_t> oldFocusNewFocusOrder = nsContentUtils::ComparePoints(
+ focusNode, focusOffset, &aContainer, aOffset);
+ shouldClearRange |= !oldFocusNewFocusOrder;
+ const Maybe<int32_t> anchorNewFocusOrder = nsContentUtils::ComparePoints(
+ anchorNode, anchorOffset, &aContainer, aOffset);
+ shouldClearRange |= !anchorNewFocusOrder;
+
+ // If the points are disconnected, the range will be collapsed below,
+ // resulting in a range that selects nothing.
+ if (shouldClearRange) {
+ // Repaint the current range with the selection removed.
+ SelectFrames(presContext, *range, false);
+
+ res = range->CollapseTo(&aContainer, aOffset);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ } else {
+ RefPtr<nsRange> difRange = nsRange::Create(&aContainer);
+ if ((*anchorOldFocusOrder == 0 && *anchorNewFocusOrder < 0) ||
+ (*anchorOldFocusOrder <= 0 &&
+ *oldFocusNewFocusOrder < 0)) { // a1,2 a,1,2
+ // select from 1 to 2 unless they are collapsed
+ range->SetEnd(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ SetDirection(eDirNext);
+ res = difRange->SetStartAndEnd(
+ focusNode, focusOffset, range->GetEndContainer(), range->EndOffset());
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ SelectFrames(presContext, *difRange, true);
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ } else if (*anchorOldFocusOrder == 0 &&
+ *anchorNewFocusOrder > 0) { // 2, a1
+ // select from 2 to 1a
+ SetDirection(eDirPrevious);
+ range->SetStart(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ SelectFrames(presContext, *range, true);
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ } else if (*anchorNewFocusOrder <= 0 &&
+ *oldFocusNewFocusOrder >= 0) { // a,2,1 or a2,1 or a,21 or a21
+ // deselect from 2 to 1
+ res = difRange->SetStartAndEnd(&aContainer, aOffset, focusNode,
+ focusOffset);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+
+ range->SetEnd(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ SelectFrames(presContext, *difRange, false); // deselect now
+ difRange->SetEnd(range->GetEndContainer(), range->EndOffset());
+ SelectFrames(presContext, *difRange, true); // must reselect last node
+ // maybe more
+ } else if (*anchorOldFocusOrder >= 0 &&
+ *anchorNewFocusOrder <= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2
+ if (GetDirection() == eDirPrevious) {
+ res = range->SetStart(endNode, endOffset);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ SetDirection(eDirNext);
+ range->SetEnd(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (focusNode != anchorNode ||
+ focusOffset != anchorOffset) { // if collapsed diff dont do anything
+ res = difRange->SetStart(focusNode, focusOffset);
+ nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset);
+ if (NS_FAILED(tmp)) {
+ res = tmp;
+ }
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ // deselect from 1 to a
+ SelectFrames(presContext, *difRange, false);
+ } else {
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ // select from a to 2
+ SelectFrames(presContext, *range, true);
+ } else if (*oldFocusNewFocusOrder <= 0 &&
+ *anchorNewFocusOrder >= 0) { // 1,2,a or 12,a or 1,2a or 12a
+ // deselect from 1 to 2
+ res = difRange->SetStartAndEnd(focusNode, focusOffset, &aContainer,
+ aOffset);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ SetDirection(eDirPrevious);
+ range->SetStart(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ SelectFrames(presContext, *difRange, false);
+ difRange->SetStart(range->GetStartContainer(), range->StartOffset());
+ SelectFrames(presContext, *difRange, true); // must reselect last node
+ } else if (*anchorNewFocusOrder >= 0 &&
+ *anchorOldFocusOrder <= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1
+ if (GetDirection() == eDirNext) {
+ range->SetEnd(startNode, startOffset);
+ }
+ SetDirection(eDirPrevious);
+ range->SetStart(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ // deselect from a to 1
+ if (focusNode != anchorNode ||
+ focusOffset != anchorOffset) { // if collapsed diff dont do anything
+ res = difRange->SetStartAndEnd(anchorNode, anchorOffset, focusNode,
+ focusOffset);
+ nsresult tmp = SetAnchorFocusToRange(range);
+ if (NS_FAILED(tmp)) {
+ res = tmp;
+ }
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ SelectFrames(presContext, *difRange, false);
+ } else {
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ // select from 2 to a
+ SelectFrames(presContext, *range, true);
+ } else if (*oldFocusNewFocusOrder >= 0 &&
+ *anchorOldFocusOrder >= 0) { // 2,1,a or 21,a or 2,1a or 21a
+ // select from 2 to 1
+ range->SetStart(aContainer, aOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ SetDirection(eDirPrevious);
+ res = difRange->SetStartAndEnd(range->GetStartContainer(),
+ range->StartOffset(), focusNode,
+ focusOffset);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+
+ SelectFrames(presContext, *difRange, true);
+ res = SetAnchorFocusToRange(range);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ }
+
+ if (mStyledRanges.Length() > 1) {
+ SelectFramesInAllRanges(presContext);
+ }
+
+ DEBUG_OUT_RANGE(range);
+#ifdef DEBUG_SELECTION
+ if (GetDirection() != oldDirection) {
+ printf(" direction changed to %s\n",
+ GetDirection() == eDirNext ? "eDirNext" : "eDirPrevious");
+ }
+ nsCOMPtr<nsIContent> content = do_QueryInterface(&aContainer);
+ printf("Sel. Extend to %p %s %d\n", content.get(),
+ nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset);
+#endif
+
+ RefPtr<Selection> kungFuDeathGrip{this};
+ // Be aware, this instance may be destroyed after this call.
+ NotifySelectionListeners();
+}
+
+void Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ SelectAllChildren(aNode, aRv);
+}
+
+void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) {
+ if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aNode", &aNode);
+ LogStackForSelectionAPI();
+ }
+
+ if (aNode.NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
+ aRv.ThrowInvalidNodeTypeError(kNoDocumentTypeNodeError);
+ return;
+ }
+
+ if (!HasSameRootOrSameComposedDoc(aNode)) {
+ // Return with no error
+ return;
+ }
+
+ if (mFrameSelection) {
+ mFrameSelection->AddChangeReasons(nsISelectionListener::SELECTALL_REASON);
+ }
+
+ // Chrome moves focus when aNode is outside of active editing host.
+ // So, we don't need to respect the limiter with this method.
+ SetStartAndEndInternal(InLimiter::eNo, RawRangeBoundary(&aNode, 0u),
+ RawRangeBoundary(&aNode, aNode.GetChildCount()),
+ eDirNext, aRv);
+}
+
+bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial,
+ ErrorResult& aRv) {
+ nsresult rv;
+ if (mStyledRanges.Length() == 0) {
+ return false;
+ }
+
+ // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
+ uint32_t nodeLength;
+ auto* nodeAsCharData = CharacterData::FromNode(aNode);
+ if (nodeAsCharData) {
+ nodeLength = nodeAsCharData->TextLength();
+ } else {
+ nodeLength = aNode.GetChildCount();
+ }
+
+ nsTArray<AbstractRange*> overlappingRanges;
+ rv = GetAbstractRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false,
+ &overlappingRanges);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+ if (overlappingRanges.Length() == 0) return false; // no ranges overlap
+
+ // if the caller said partial intersections are OK, we're done
+ if (aAllowPartial) {
+ return true;
+ }
+
+ // text nodes always count as inside
+ if (nodeAsCharData) {
+ return true;
+ }
+
+ // The caller wants to know if the node is entirely within the given range,
+ // so we have to check all intersecting ranges.
+ for (uint32_t i = 0; i < overlappingRanges.Length(); i++) {
+ bool nodeStartsBeforeRange, nodeEndsAfterRange;
+ if (NS_SUCCEEDED(RangeUtils::CompareNodeToRange(
+ &aNode, overlappingRanges[i], &nodeStartsBeforeRange,
+ &nodeEndsAfterRange))) {
+ if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+class PointInRectChecker : public mozilla::RectCallback {
+ public:
+ explicit PointInRectChecker(const nsPoint& aPoint)
+ : mPoint(aPoint), mMatchFound(false) {}
+
+ void AddRect(const nsRect& aRect) override {
+ mMatchFound = mMatchFound || aRect.Contains(mPoint);
+ }
+
+ bool MatchFound() { return mMatchFound; }
+
+ private:
+ nsPoint mPoint;
+ bool mMatchFound;
+};
+
+bool Selection::ContainsPoint(const nsPoint& aPoint) {
+ if (IsCollapsed()) {
+ return false;
+ }
+ PointInRectChecker checker(aPoint);
+ const uint32_t rangeCount = RangeCount();
+ for (const uint32_t i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(RangeCount() == rangeCount);
+ nsRange* range = GetRangeAt(i);
+ MOZ_ASSERT(range);
+ nsRange::CollectClientRectsAndText(
+ &checker, nullptr, range, range->GetStartContainer(),
+ range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
+ true, false);
+ if (checker.MatchFound()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Selection::MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ if (!mAccessibleCaretEventHub && aPresShell) {
+ mAccessibleCaretEventHub = aPresShell->GetAccessibleCaretEventHub();
+ }
+}
+
+void Selection::StopNotifyingAccessibleCaretEventHub() {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ mAccessibleCaretEventHub = nullptr;
+}
+
+nsPresContext* Selection::GetPresContext() const {
+ PresShell* presShell = GetPresShell();
+ return presShell ? presShell->GetPresContext() : nullptr;
+}
+
+PresShell* Selection::GetPresShell() const {
+ if (!mFrameSelection) {
+ return nullptr; // nothing to do
+ }
+ return mFrameSelection->GetPresShell();
+}
+
+Document* Selection::GetDocument() const {
+ PresShell* presShell = GetPresShell();
+ return presShell ? presShell->GetDocument() : nullptr;
+}
+
+nsIFrame* Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion,
+ nsRect* aRect) {
+ if (!mFrameSelection) return nullptr; // nothing to do
+
+ NS_ENSURE_TRUE(aRect, nullptr);
+
+ aRect->SetRect(0, 0, 0, 0);
+
+ switch (aRegion) {
+ case nsISelectionController::SELECTION_ANCHOR_REGION:
+ case nsISelectionController::SELECTION_FOCUS_REGION:
+ return GetSelectionEndPointGeometry(aRegion, aRect);
+ case nsISelectionController::SELECTION_WHOLE_SELECTION:
+ break;
+ default:
+ return nullptr;
+ }
+
+ NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
+ "should only be SELECTION_WHOLE_SELECTION here");
+
+ nsRect anchorRect;
+ nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
+ nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
+ if (!anchorFrame) return nullptr;
+
+ nsRect focusRect;
+ nsIFrame* focusFrame = GetSelectionEndPointGeometry(
+ nsISelectionController::SELECTION_FOCUS_REGION, &focusRect);
+ if (!focusFrame) return nullptr;
+
+ NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(),
+ "points of selection in different documents?");
+ // make focusRect relative to anchorFrame
+ focusRect += focusFrame->GetOffsetTo(anchorFrame);
+
+ *aRect = anchorRect.UnionEdges(focusRect);
+ return anchorFrame;
+}
+
+nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
+ nsRect* aRect) {
+ if (!mFrameSelection) return nullptr; // nothing to do
+
+ NS_ENSURE_TRUE(aRect, nullptr);
+
+ aRect->SetRect(0, 0, 0, 0);
+
+ nsINode* node = nullptr;
+ uint32_t nodeOffset = 0;
+ nsIFrame* frame = nullptr;
+
+ switch (aRegion) {
+ case nsISelectionController::SELECTION_ANCHOR_REGION:
+ node = GetAnchorNode();
+ nodeOffset = AnchorOffset();
+ break;
+ case nsISelectionController::SELECTION_FOCUS_REGION:
+ node = GetFocusNode();
+ nodeOffset = FocusOffset();
+ break;
+ default:
+ return nullptr;
+ }
+
+ if (!node) return nullptr;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+ NS_ENSURE_TRUE(content.get(), nullptr);
+ int32_t frameOffset = 0;
+ frame = nsFrameSelection::GetFrameForNodeOffset(
+ content, nodeOffset, mFrameSelection->GetHint(), &frameOffset);
+ if (!frame) return nullptr;
+
+ nsFrameSelection::AdjustFrameForLineStart(frame, frameOffset);
+
+ // Figure out what node type we have, then get the
+ // appropriate rect for it's nodeOffset.
+ bool isText = node->IsText();
+
+ nsPoint pt(0, 0);
+ if (isText) {
+ nsIFrame* childFrame = nullptr;
+ frameOffset = 0;
+ nsresult rv = frame->GetChildFrameContainingOffset(
+ nodeOffset, mFrameSelection->GetHint(), &frameOffset, &childFrame);
+ if (NS_FAILED(rv)) return nullptr;
+ if (!childFrame) return nullptr;
+
+ frame = childFrame;
+
+ // Get the x coordinate of the offset into the text frame.
+ rv = GetCachedFrameOffset(frame, nodeOffset, pt);
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ // Return the rect relative to the frame, with zero width.
+ if (isText) {
+ aRect->x = pt.x;
+ } else if (mFrameSelection->GetHint() == CARET_ASSOCIATE_BEFORE) {
+ // It's the frame's right edge we're interested in.
+ aRect->x = frame->GetRect().Width();
+ }
+ aRect->SetHeight(frame->GetRect().Height());
+
+ return frame;
+}
+
+NS_IMETHODIMP
+Selection::ScrollSelectionIntoViewEvent::Run() {
+ if (!mSelection) return NS_OK; // event revoked
+
+ int32_t flags = Selection::SCROLL_DO_FLUSH | Selection::SCROLL_SYNCHRONOUS;
+
+ const RefPtr<Selection> selection{mSelection};
+ selection->mScrollEvent.Forget();
+ selection->ScrollIntoView(mRegion, mVerticalScroll, mHorizontalScroll,
+ mFlags | flags);
+ return NS_OK;
+}
+
+nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
+ int32_t aFlags,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal) {
+ // If we've already posted an event, revoke it and place a new one at the
+ // end of the queue to make sure that any new pending reflow events are
+ // processed before we scroll. This will insure that we scroll to the
+ // correct place on screen.
+ mScrollEvent.Revoke();
+ nsPresContext* presContext = GetPresContext();
+ NS_ENSURE_STATE(presContext);
+ nsRefreshDriver* refreshDriver = presContext->RefreshDriver();
+ NS_ENSURE_STATE(refreshDriver);
+
+ mScrollEvent = new ScrollSelectionIntoViewEvent(this, aRegion, aVertical,
+ aHorizontal, aFlags);
+ refreshDriver->AddEarlyRunner(mScrollEvent.get());
+ return NS_OK;
+}
+
+void Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
+ int16_t aVPercent, int16_t aHPercent,
+ ErrorResult& aRv) {
+ int32_t flags = aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0;
+ // -1 means nearest in this API.
+ const auto v =
+ aVPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aVPercent);
+ const auto h =
+ aHPercent == -1 ? WhereToScroll::Nearest : WhereToScroll(aHPercent);
+ nsresult rv = ScrollIntoView(aRegion, ScrollAxis(v), ScrollAxis(h), flags);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+nsresult Selection::ScrollIntoView(SelectionRegion aRegion,
+ ScrollAxis aVertical, ScrollAxis aHorizontal,
+ int32_t aFlags) {
+ if (!mFrameSelection) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<PresShell> presShell = mFrameSelection->GetPresShell();
+ if (!presShell || !presShell->GetDocument()) {
+ return NS_OK;
+ }
+
+ if (mFrameSelection->IsBatching()) {
+ return NS_OK;
+ }
+
+ if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
+ return PostScrollSelectionIntoViewEvent(aRegion, aFlags, aVertical,
+ aHorizontal);
+
+ // From this point on, the presShell may get destroyed by the calls below, so
+ // hold on to it using a strong reference to ensure the safety of the
+ // accesses to frame pointers in the callees.
+ RefPtr<PresShell> kungFuDeathGrip(presShell);
+
+ // Now that text frame character offsets are always valid (though not
+ // necessarily correct), the worst that will happen if we don't flush here
+ // is that some callers might scroll to the wrong place. Those should
+ // either manually flush if they're in a safe position for it or use the
+ // async version of this method.
+ if (aFlags & Selection::SCROLL_DO_FLUSH) {
+ presShell->GetDocument()->FlushPendingNotifications(FlushType::Layout);
+
+ // Reget the presshell, since it might have been Destroy'ed.
+ presShell = mFrameSelection ? mFrameSelection->GetPresShell() : nullptr;
+ if (!presShell) {
+ return NS_OK;
+ }
+ }
+
+ //
+ // Scroll the selection region into view.
+ //
+
+ nsRect rect;
+ nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
+ if (!frame) return NS_ERROR_FAILURE;
+
+ // Scroll vertically to get the caret into view, but only if the container
+ // is perceived to be scrollable in that direction (i.e. there is a visible
+ // vertical scrollbar or the scroll range is at least one device pixel)
+ aVertical.mOnlyIfPerceivedScrollableDirection = true;
+
+ auto scrollFlags = ScrollFlags::None;
+ if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) {
+ scrollFlags |= ScrollFlags::ScrollFirstAncestorOnly;
+ }
+ if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) {
+ scrollFlags |= ScrollFlags::ScrollOverflowHidden;
+ }
+
+ presShell->ScrollFrameIntoView(frame, Some(rect), aVertical, aHorizontal,
+ scrollFlags);
+ return NS_OK;
+}
+
+void Selection::AddSelectionListener(nsISelectionListener* aNewListener) {
+ MOZ_ASSERT(aNewListener);
+ mSelectionListeners.AppendElement(aNewListener); // AddRefs
+}
+
+void Selection::RemoveSelectionListener(
+ nsISelectionListener* aListenerToRemove) {
+ mSelectionListeners.RemoveElement(aListenerToRemove); // Releases
+}
+
+Element* Selection::StyledRanges::GetCommonEditingHost() const {
+ Element* editingHost = nullptr;
+ for (const StyledRange& rangeData : mRanges) {
+ const AbstractRange* range = rangeData.mRange;
+ MOZ_ASSERT(range);
+ nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
+ if (!commonAncestorNode || !commonAncestorNode->IsContent()) {
+ return nullptr;
+ }
+ nsIContent* commonAncestor = commonAncestorNode->AsContent();
+ Element* foundEditingHost = commonAncestor->GetEditingHost();
+ // Even when common ancestor is a non-editable element in a contenteditable
+ // element, we don't need to move focus to the contenteditable element
+ // because Chromium doesn't set focus to it.
+ if (!foundEditingHost) {
+ return nullptr;
+ }
+ if (!editingHost) {
+ editingHost = foundEditingHost;
+ continue;
+ }
+ if (editingHost == foundEditingHost) {
+ continue;
+ }
+ if (foundEditingHost->IsInclusiveDescendantOf(editingHost)) {
+ continue;
+ }
+ if (editingHost->IsInclusiveDescendantOf(foundEditingHost)) {
+ editingHost = foundEditingHost;
+ continue;
+ }
+ // editingHost and foundEditingHost are not a descendant of the other.
+ // So, there is no common editing host.
+ return nullptr;
+ }
+ return editingHost;
+}
+
+void Selection::StyledRanges::MaybeFocusCommonEditingHost(
+ PresShell* aPresShell) const {
+ if (!aPresShell) {
+ return;
+ }
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ Document* document = aPresShell->GetDocument();
+ if (!document) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* window = document->GetWindow();
+ // If the document is in design mode or doesn't have contenteditable
+ // element, we don't need to move focus.
+ if (window && !document->IsInDesignMode() &&
+ nsContentUtils::GetHTMLEditor(presContext)) {
+ RefPtr<Element> newEditingHost = GetCommonEditingHost();
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+ nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
+ // When all selected ranges are in an editing host, it should take focus.
+ // But otherwise, we shouldn't move focus since Chromium doesn't move
+ // focus but only selection range is updated.
+ if (newEditingHost && newEditingHost != focusedElement) {
+ MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree());
+ // Note that don't steal focus from focused window if the window doesn't
+ // have focus. Additionally, although when an element gets focus, we
+ // usually scroll to the element, but in this case, we shouldn't do it
+ // because Chrome does not do so.
+ fm->SetFocus(newEditingHost, nsIFocusManager::FLAG_NOSWITCHFRAME |
+ nsIFocusManager::FLAG_NOSCROLL);
+ }
+ }
+}
+
+void Selection::NotifySelectionListeners(bool aCalledByJS) {
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = aCalledByJS;
+ NotifySelectionListeners();
+}
+
+void Selection::NotifySelectionListeners() {
+ if (!mFrameSelection) {
+ return; // nothing to do
+ }
+
+ MOZ_LOG(sSelectionLog, LogLevel::Debug,
+ ("%s: selection=%p", __FUNCTION__, this));
+
+ // Our internal code should not move focus with using this class while
+ // this moves focus nor from selection listeners.
+ AutoRestore<bool> calledByJSRestorer(mCalledByJS);
+ mCalledByJS = false;
+
+ // When normal selection is changed by Selection API, we need to move focus
+ // if common ancestor of all ranges are in an editing host. Note that we
+ // don't need to move focus *to* the other focusable node because other
+ // browsers don't do it either.
+ if (mSelectionType == SelectionType::eNormal &&
+ calledByJSRestorer.SavedValue()) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ mStyledRanges.MaybeFocusCommonEditingHost(presShell);
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+
+ // This flag will be set to true if a selection by double click is detected.
+ // As soon as the selection is modified, it needs to be set to false.
+ frameSelection->SetIsDoubleClickSelection(false);
+
+ if (frameSelection->IsBatching()) {
+ frameSelection->SetChangesDuringBatchingFlag();
+ return;
+ }
+ if (mSelectionListeners.IsEmpty() && !mNotifyAutoCopy &&
+ !mAccessibleCaretEventHub && !mSelectionChangeEventDispatcher) {
+ // If there are no selection listeners, we're done!
+ return;
+ }
+
+ nsCOMPtr<Document> doc;
+ if (PresShell* presShell = GetPresShell()) {
+ doc = presShell->GetDocument();
+ presShell->ScheduleContentRelevancyUpdate(ContentRelevancyReason::Selected);
+ }
+
+ // We've notified all selection listeners even when some of them are removed
+ // (and may be destroyed) during notifying one of them. Therefore, we should
+ // copy all listeners to the local variable first.
+ const CopyableAutoTArray<nsCOMPtr<nsISelectionListener>, 5>
+ selectionListeners = mSelectionListeners;
+
+ int16_t reason = frameSelection->PopChangeReasons();
+ if (calledByJSRestorer.SavedValue()) {
+ reason |= nsISelectionListener::JS_REASON;
+ }
+
+ int32_t amount = static_cast<int32_t>(frameSelection->GetCaretMoveAmount());
+
+ if (mNotifyAutoCopy) {
+ AutoCopyListener::OnSelectionChange(doc, *this, reason);
+ }
+
+ if (mAccessibleCaretEventHub) {
+ RefPtr<AccessibleCaretEventHub> hub(mAccessibleCaretEventHub);
+ hub->OnSelectionChange(doc, this, reason);
+ }
+
+ if (mSelectionChangeEventDispatcher) {
+ RefPtr<SelectionChangeEventDispatcher> dispatcher(
+ mSelectionChangeEventDispatcher);
+ dispatcher->OnSelectionChange(doc, this, reason);
+ }
+
+ for (const auto& listener : selectionListeners) {
+ // MOZ_KnownLive because 'selectionListeners' is guaranteed to
+ // keep it alive.
+ //
+ // This can go away once
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
+ MOZ_KnownLive(listener)->NotifySelectionChanged(doc, this, reason, amount);
+ }
+}
+
+void Selection::StartBatchChanges(const char* aDetails) {
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->StartBatchChanges(aDetails);
+ }
+}
+
+void Selection::EndBatchChanges(const char* aDetails, int16_t aReasons) {
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->EndBatchChanges(aDetails, aReasons);
+ }
+}
+
+void Selection::AddSelectionChangeBlocker() { mSelectionChangeBlockerCount++; }
+
+void Selection::RemoveSelectionChangeBlocker() {
+ MOZ_ASSERT(mSelectionChangeBlockerCount > 0,
+ "mSelectionChangeBlockerCount has an invalid value - "
+ "maybe you have a mismatched RemoveSelectionChangeBlocker?");
+ mSelectionChangeBlockerCount--;
+}
+
+bool Selection::IsBlockingSelectionChangeEvents() const {
+ return mSelectionChangeBlockerCount > 0;
+}
+
+void Selection::DeleteFromDocument(ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__);
+ LogStackForSelectionAPI();
+ }
+
+ if (mSelectionType != SelectionType::eNormal) {
+ return; // Nothing to do.
+ }
+
+ // If we're already collapsed, then we do nothing (bug 719503).
+ if (IsCollapsed()) {
+ return;
+ }
+
+ for (uint32_t rangeIdx = 0; rangeIdx < RangeCount(); ++rangeIdx) {
+ RefPtr<nsRange> range = GetRangeAt(rangeIdx);
+ range->DeleteContents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Collapse to the new location.
+ // If we deleted one character, then we move back one element.
+ // FIXME We don't know how to do this past frame boundaries yet.
+ if (AnchorOffset() > 0) {
+ RefPtr<nsINode> anchor = GetAnchorNode();
+ CollapseInLimiter(anchor, AnchorOffset());
+ }
+#ifdef DEBUG
+ else {
+ printf("Don't know how to set selection back past frame boundary\n");
+ }
+#endif
+}
+
+void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
+ const nsAString& aGranularity, ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aAlter", aAlter, "aDirection",
+ aDirection, "aGranularity", aGranularity);
+ LogStackForSelectionAPI();
+ }
+
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ if (!GetAnchorFocusRange() || !GetFocusNode()) {
+ return;
+ }
+
+ if (!aAlter.LowerCaseEqualsLiteral("move") &&
+ !aAlter.LowerCaseEqualsLiteral("extend")) {
+ aRv.ThrowSyntaxError(
+ R"(The first argument must be one of: "move" or "extend")");
+ return;
+ }
+
+ if (!aDirection.LowerCaseEqualsLiteral("forward") &&
+ !aDirection.LowerCaseEqualsLiteral("backward") &&
+ !aDirection.LowerCaseEqualsLiteral("left") &&
+ !aDirection.LowerCaseEqualsLiteral("right")) {
+ aRv.ThrowSyntaxError(
+ R"(The direction argument must be one of: "forward", "backward", "left", or "right")");
+ return;
+ }
+
+ // Make sure the layout is up to date as we access bidi information below.
+ if (RefPtr<Document> doc = GetDocument()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ // Line moves are always visual.
+ bool visual = aDirection.LowerCaseEqualsLiteral("left") ||
+ aDirection.LowerCaseEqualsLiteral("right") ||
+ aGranularity.LowerCaseEqualsLiteral("line");
+
+ bool forward = aDirection.LowerCaseEqualsLiteral("forward") ||
+ aDirection.LowerCaseEqualsLiteral("right");
+
+ bool extend = aAlter.LowerCaseEqualsLiteral("extend");
+
+ nsSelectionAmount amount;
+ if (aGranularity.LowerCaseEqualsLiteral("character")) {
+ amount = eSelectCluster;
+ } else if (aGranularity.LowerCaseEqualsLiteral("word")) {
+ amount = eSelectWordNoSpace;
+ } else if (aGranularity.LowerCaseEqualsLiteral("line")) {
+ amount = eSelectLine;
+ } else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) {
+ amount = forward ? eSelectEndLine : eSelectBeginLine;
+ } else if (aGranularity.LowerCaseEqualsLiteral("sentence") ||
+ aGranularity.LowerCaseEqualsLiteral("sentenceboundary") ||
+ aGranularity.LowerCaseEqualsLiteral("paragraph") ||
+ aGranularity.LowerCaseEqualsLiteral("paragraphboundary") ||
+ aGranularity.LowerCaseEqualsLiteral("documentboundary")) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ } else {
+ aRv.ThrowSyntaxError(
+ R"(The granularity argument must be one of: "character", "word", "line", or "lineboundary")");
+ return;
+ }
+
+ // If the anchor doesn't equal the focus and we try to move without first
+ // collapsing the selection, MoveCaret will collapse the selection and quit.
+ // To avoid this, we need to collapse the selection first.
+ nsresult rv = NS_OK;
+ if (!extend) {
+ RefPtr<nsINode> focusNode = GetFocusNode();
+ // We should have checked earlier that there was a focus node.
+ if (!focusNode) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ uint32_t focusOffset = FocusOffset();
+ CollapseInLimiter(focusNode, focusOffset);
+ }
+
+ // If the paragraph direction of the focused frame is right-to-left,
+ // we may have to swap the direction of movement.
+ if (nsIFrame* frame = GetPrimaryFrameForFocusNode(visual)) {
+ mozilla::intl::BidiDirection paraDir =
+ nsBidiPresUtils::ParagraphDirection(frame);
+
+ if (paraDir == mozilla::intl::BidiDirection::RTL && visual) {
+ if (amount == eSelectBeginLine) {
+ amount = eSelectEndLine;
+ forward = !forward;
+ } else if (amount == eSelectEndLine) {
+ amount = eSelectBeginLine;
+ forward = !forward;
+ }
+ }
+ }
+
+ // MoveCaret will return an error if it can't move in the specified
+ // direction, but we just ignore this error unless it's a line move, in which
+ // case we call nsISelectionController::CompleteMove to move the cursor to
+ // the beginning/end of the line.
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ rv = frameSelection->MoveCaret(
+ forward ? eDirNext : eDirPrevious, extend, amount,
+ visual ? nsFrameSelection::eVisual : nsFrameSelection::eLogical);
+
+ if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
+ RefPtr<PresShell> presShell = frameSelection->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ presShell->CompleteMove(forward, extend);
+ }
+}
+
+void Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aAnchorNode", aAnchorNode,
+ "aAnchorOffset", aAnchorOffset, "aFocusNode", aFocusNode,
+ "aFocusOffset", aFocusOffset);
+ LogStackForSelectionAPI();
+ }
+
+ AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+ mCalledByJS = true;
+ SetBaseAndExtent(aAnchorNode, aAnchorOffset, aFocusNode, aFocusOffset, aRv);
+}
+
+void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv) {
+ if (aAnchorOffset > aAnchorNode.Length()) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "The anchor offset value %u is out of range", aAnchorOffset));
+ return;
+ }
+ if (aFocusOffset > aFocusNode.Length()) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "The focus offset value %u is out of range", aFocusOffset));
+ return;
+ }
+
+ SetBaseAndExtent(RawRangeBoundary{&aAnchorNode, aAnchorOffset},
+ RawRangeBoundary{&aFocusNode, aFocusOffset}, aRv);
+}
+
+void Selection::SetBaseAndExtent(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv) {
+ if (!mCalledByJS && NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
+ aFocusRef);
+ LogStackForSelectionAPI();
+ }
+
+ SetBaseAndExtentInternal(InLimiter::eNo, aAnchorRef, aFocusRef, aRv);
+}
+
+void Selection::SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aAnchorRef", aAnchorRef, "aFocusRef",
+ aFocusRef);
+ LogStackForSelectionAPI();
+ }
+
+ SetBaseAndExtentInternal(InLimiter::eYes, aAnchorRef, aFocusRef, aRv);
+}
+
+void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv) {
+ if (!mFrameSelection) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ if (NS_WARN_IF(!aAnchorRef.IsSet()) || NS_WARN_IF(!aFocusRef.IsSet())) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (!HasSameRootOrSameComposedDoc(*aAnchorRef.Container()) ||
+ !HasSameRootOrSameComposedDoc(*aFocusRef.Container())) {
+ // Return with no error
+ return;
+ }
+
+ // Prevent "selectionchange" event temporarily because it should be fired
+ // after we set the direction.
+ // XXX If they are disconnected, shouldn't we return error before allocating
+ // new nsRange instance?
+ SelectionBatcher batch(this, __FUNCTION__);
+ const Maybe<int32_t> order =
+ nsContentUtils::ComparePoints(aAnchorRef, aFocusRef);
+ if (order && (*order <= 0)) {
+ SetStartAndEndInternal(aInLimiter, aAnchorRef, aFocusRef, eDirNext, aRv);
+ return;
+ }
+
+ // If there's no `order`, the range will be collapsed, unless another error is
+ // detected before.
+ SetStartAndEndInternal(aInLimiter, aFocusRef, aAnchorRef, eDirPrevious, aRv);
+}
+
+void Selection::SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
+ aEndRef);
+ LogStackForSelectionAPI();
+ }
+
+ SetStartAndEndInternal(InLimiter::eYes, aStartRef, aEndRef, eDirNext, aRv);
+}
+
+Result<Ok, nsresult> Selection::SetStartAndEndInLimiter(
+ nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer,
+ uint32_t aEndOffset, nsDirection aDirection, int16_t aReason) {
+ MOZ_ASSERT(aDirection == eDirPrevious || aDirection == eDirNext);
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aStartContainer", aStartContainer,
+ "aStartOffset", aStartOffset, "aEndContainer",
+ aEndContainer, "aEndOffset", aEndOffset, "nsDirection",
+ aDirection, "aReason", aReason);
+ LogStackForSelectionAPI();
+ }
+
+ if (mFrameSelection) {
+ mFrameSelection->AddChangeReasons(aReason);
+ }
+
+ ErrorResult error;
+ SetStartAndEndInternal(
+ InLimiter::eYes, RawRangeBoundary(&aStartContainer, aStartOffset),
+ RawRangeBoundary(&aEndContainer, aEndOffset), aDirection, error);
+ MOZ_TRY(error.StealNSResult());
+ return Ok();
+}
+
+void Selection::SetStartAndEnd(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ ErrorResult& aRv) {
+ if (NeedsToLogSelectionAPI(*this)) {
+ LogSelectionAPI(this, __FUNCTION__, "aStartRef", aStartRef, "aEndRef",
+ aEndRef);
+ LogStackForSelectionAPI();
+ }
+
+ SetStartAndEndInternal(InLimiter::eNo, aStartRef, aEndRef, eDirNext, aRv);
+}
+
+void Selection::SetStartAndEndInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ nsDirection aDirection,
+ ErrorResult& aRv) {
+ if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ // Don't fire "selectionchange" event until everything done.
+ SelectionBatcher batch(this, __FUNCTION__);
+
+ if (aInLimiter == InLimiter::eYes) {
+ if (!mFrameSelection ||
+ !mFrameSelection->IsValidSelectionPoint(aStartRef.Container())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ if (aStartRef.Container() != aEndRef.Container() &&
+ !mFrameSelection->IsValidSelectionPoint(aEndRef.Container())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ RefPtr<nsRange> newRange = nsRange::Create(aStartRef, aEndRef, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RemoveAllRangesInternal(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RefPtr<Document> document(GetDocument());
+ AddRangeAndSelectFramesAndNotifyListenersInternal(*newRange, document, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Adding a range may set 2 or more ranges if there are non-selectable
+ // contents only when this change is caused by a user operation. Therefore,
+ // we need to select frames with the result in such case.
+ if (mUserInitiated) {
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (mStyledRanges.Length() > 1 && presContext) {
+ SelectFramesInAllRanges(presContext);
+ }
+ }
+
+ SetDirection(aDirection);
+}
+
+/** SelectionLanguageChange modifies the cursor Bidi level after a change in
+ * keyboard direction
+ * @param aLangRTL is true if the new language is right-to-left or false if the
+ * new language is left-to-right
+ */
+nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
+ if (!mFrameSelection) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+
+ // if the direction of the language hasn't changed, nothing to do
+ mozilla::intl::BidiEmbeddingLevel kbdBidiLevel =
+ aLangRTL ? mozilla::intl::BidiEmbeddingLevel::RTL()
+ : mozilla::intl::BidiEmbeddingLevel::LTR();
+ if (kbdBidiLevel == frameSelection->mKbdBidiLevel) {
+ return NS_OK;
+ }
+
+ frameSelection->mKbdBidiLevel = kbdBidiLevel;
+
+ nsIFrame* focusFrame = GetPrimaryFrameForFocusNode(false);
+ if (!focusFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto [frameStart, frameEnd] = focusFrame->GetOffsets();
+ RefPtr<nsPresContext> context = GetPresContext();
+ mozilla::intl::BidiEmbeddingLevel levelBefore, levelAfter;
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::intl::BidiEmbeddingLevel level = focusFrame->GetEmbeddingLevel();
+ int32_t focusOffset = static_cast<int32_t>(FocusOffset());
+ if ((focusOffset != frameStart) && (focusOffset != frameEnd))
+ // the cursor is not at a frame boundary, so the level of both the
+ // characters (logically) before and after the cursor is equal to the frame
+ // level
+ levelBefore = levelAfter = level;
+ else {
+ // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find
+ // the level of the characters before and after the cursor
+ nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
+ nsPrevNextBidiLevels levels =
+ frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false);
+
+ levelBefore = levels.mLevelBefore;
+ levelAfter = levels.mLevelAfter;
+ }
+
+ if (levelBefore.IsSameDirection(levelAfter)) {
+ // if cursor is between two characters with the same orientation, changing
+ // the keyboard language must toggle the cursor level between the level of
+ // the character with the lowest level (if the new language corresponds to
+ // the orientation of that character) and this level plus 1 (if the new
+ // language corresponds to the opposite orientation)
+ if ((level != levelBefore) && (level != levelAfter)) {
+ level = std::min(levelBefore, levelAfter);
+ }
+ if (level.IsSameDirection(kbdBidiLevel)) {
+ frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level);
+ } else {
+ frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
+ mozilla::intl::BidiEmbeddingLevel(level + 1));
+ }
+ } else {
+ // if cursor is between characters with opposite orientations, changing the
+ // keyboard language must change the cursor level to that of the adjacent
+ // character with the orientation corresponding to the new language.
+ if (levelBefore.IsSameDirection(kbdBidiLevel)) {
+ frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore);
+ } else {
+ frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter);
+ }
+ }
+
+ // The caret might have moved, so invalidate the desired position
+ // for future usages of up-arrow or down-arrow
+ frameSelection->InvalidateDesiredCaretPos();
+
+ return NS_OK;
+}
+
+void Selection::SetColors(const nsAString& aForegroundColor,
+ const nsAString& aBackgroundColor,
+ const nsAString& aAltForegroundColor,
+ const nsAString& aAltBackgroundColor,
+ ErrorResult& aRv) {
+ if (mSelectionType != SelectionType::eFind) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mCustomColors.reset(new SelectionCustomColors);
+
+ constexpr auto currentColorStr = u"currentColor"_ns;
+ constexpr auto transparentStr = u"transparent"_ns;
+
+ if (!aForegroundColor.Equals(currentColorStr)) {
+ nscolor foregroundColor;
+ nsAttrValue aForegroundColorValue;
+ aForegroundColorValue.ParseColor(aForegroundColor);
+ if (!aForegroundColorValue.GetColorValue(foregroundColor)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ mCustomColors->mForegroundColor = Some(foregroundColor);
+ } else {
+ mCustomColors->mForegroundColor = Nothing();
+ }
+
+ if (!aBackgroundColor.Equals(transparentStr)) {
+ nscolor backgroundColor;
+ nsAttrValue aBackgroundColorValue;
+ aBackgroundColorValue.ParseColor(aBackgroundColor);
+ if (!aBackgroundColorValue.GetColorValue(backgroundColor)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ mCustomColors->mBackgroundColor = Some(backgroundColor);
+ } else {
+ mCustomColors->mBackgroundColor = Nothing();
+ }
+
+ if (!aAltForegroundColor.Equals(currentColorStr)) {
+ nscolor altForegroundColor;
+ nsAttrValue aAltForegroundColorValue;
+ aAltForegroundColorValue.ParseColor(aAltForegroundColor);
+ if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ mCustomColors->mAltForegroundColor = Some(altForegroundColor);
+ } else {
+ mCustomColors->mAltForegroundColor = Nothing();
+ }
+
+ if (!aAltBackgroundColor.Equals(transparentStr)) {
+ nscolor altBackgroundColor;
+ nsAttrValue aAltBackgroundColorValue;
+ aAltBackgroundColorValue.ParseColor(aAltBackgroundColor);
+ if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
+ } else {
+ mCustomColors->mAltBackgroundColor = Nothing();
+ }
+}
+
+void Selection::ResetColors() { mCustomColors = nullptr; }
+
+void Selection::SetHighlightName(const nsAtom* aHighlightName) {
+ MOZ_ASSERT(mSelectionType == SelectionType::eHighlight);
+ mHighlightName = aHighlightName;
+}
+
+JSObject* Selection::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::Selection_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// AutoHideSelectionChanges
+AutoHideSelectionChanges::AutoHideSelectionChanges(
+ const nsFrameSelection* aFrame)
+ : AutoHideSelectionChanges(
+ aFrame ? aFrame->GetSelection(SelectionType::eNormal) : nullptr) {}
+
+bool Selection::HasSameRootOrSameComposedDoc(const nsINode& aNode) {
+ nsINode* root = aNode.SubtreeRoot();
+ Document* doc = GetDocument();
+ return doc == root || (root && doc == root->GetComposedDoc());
+}
diff --git a/dom/base/Selection.h b/dom/base/Selection.h
new file mode 100644
index 0000000000..9d8e6556cd
--- /dev/null
+++ b/dom/base/Selection.h
@@ -0,0 +1,1103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Selection_h__
+#define mozilla_Selection_h__
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/SelectionChangeEventDispatcher.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/StyledRange.h"
+#include "nsDirection.h"
+#include "nsISelectionController.h"
+#include "nsISelectionListener.h"
+#include "nsRange.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+
+struct CachedOffsetForFrame;
+class AutoScroller;
+class nsIFrame;
+class nsFrameSelection;
+class nsPIDOMWindowOuter;
+struct SelectionDetails;
+struct SelectionCustomColors;
+class nsCopySupport;
+class nsHTMLCopyEncoder;
+class nsPresContext;
+struct nsPoint;
+struct nsRect;
+
+namespace mozilla {
+class AccessibleCaretEventHub;
+class ErrorResult;
+class HTMLEditor;
+class PostContentIterator;
+enum class TableSelectionMode : uint32_t;
+struct AutoPrepareFocusRange;
+namespace dom {
+class DocGroup;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+namespace dom {
+
+// Note, the ownership of mozilla::dom::Selection depends on which way the
+// object is created. When nsFrameSelection has created Selection,
+// addreffing/releasing the Selection object is aggregated to nsFrameSelection.
+// Otherwise normal addref/release is used. This ensures that nsFrameSelection
+// is never deleted before its Selections.
+class Selection final : public nsSupportsWeakReference,
+ public nsWrapperCache,
+ public SupportsWeakPtr {
+ protected:
+ virtual ~Selection();
+
+ public:
+ /**
+ * @param aFrameSelection can be nullptr.
+ */
+ explicit Selection(SelectionType aSelectionType,
+ nsFrameSelection* aFrameSelection);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Selection)
+
+ /**
+ * Match this up with EndbatchChanges. will stop ui updates while multiple
+ * selection methods are called
+ *
+ * @param aDetails string to explian why this is called. This won't be
+ * stored nor exposed to selection listeners etc. Just for logging.
+ */
+ void StartBatchChanges(const char* aDetails);
+
+ /**
+ * Match this up with StartBatchChanges
+ *
+ * @param aDetails string to explian why this is called. This won't be
+ * stored nor exposed to selection listeners etc. Just for logging.
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl
+ */
+ void EndBatchChanges(const char* aDetails,
+ int16_t aReason = nsISelectionListener::NO_REASON);
+
+ /**
+ * NotifyAutoCopy() starts to notify AutoCopyListener of selection changes.
+ */
+ void NotifyAutoCopy() {
+ MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
+
+ mNotifyAutoCopy = true;
+ }
+
+ /**
+ * MaybeNotifyAccessibleCaretEventHub() starts to notify
+ * AccessibleCaretEventHub of selection change if aPresShell has it.
+ */
+ void MaybeNotifyAccessibleCaretEventHub(PresShell* aPresShell);
+
+ /**
+ * StopNotifyingAccessibleCaretEventHub() stops notifying
+ * AccessibleCaretEventHub of selection change.
+ */
+ void StopNotifyingAccessibleCaretEventHub();
+
+ /**
+ * EnableSelectionChangeEvent() starts to notify
+ * SelectionChangeEventDispatcher of selection change to dispatch a
+ * selectionchange event at every selection change.
+ */
+ void EnableSelectionChangeEvent() {
+ if (!mSelectionChangeEventDispatcher) {
+ mSelectionChangeEventDispatcher = new SelectionChangeEventDispatcher();
+ }
+ }
+
+ // Required for WebIDL bindings, see
+ // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings#Adding_WebIDL_bindings_to_a_class.
+ Document* GetParentObject() const;
+
+ DocGroup* GetDocGroup() const;
+
+ // utility methods for scrolling the selection into view
+ nsPresContext* GetPresContext() const;
+ PresShell* GetPresShell() const;
+ nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
+ // Returns a rect containing the selection region, and frame that that
+ // position is relative to. For SELECTION_ANCHOR_REGION or
+ // SELECTION_FOCUS_REGION the rect is a zero-width rectangle. For
+ // SELECTION_WHOLE_SELECTION the rect contains both the anchor and focus
+ // region rects.
+ nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect);
+ // Returns the position of the region (SELECTION_ANCHOR_REGION or
+ // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
+ // The 'position' is a zero-width rectangle.
+ nsIFrame* GetSelectionEndPointGeometry(SelectionRegion aRegion,
+ nsRect* aRect);
+
+ nsresult PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
+ int32_t aFlags,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal);
+ enum {
+ SCROLL_SYNCHRONOUS = 1 << 1,
+ SCROLL_FIRST_ANCESTOR_ONLY = 1 << 2,
+ SCROLL_DO_FLUSH =
+ 1 << 3, // only matters if SCROLL_SYNCHRONOUS is passed too
+ SCROLL_OVERFLOW_HIDDEN = 1 << 5,
+ SCROLL_FOR_CARET_MOVE = 1 << 6
+ };
+ // If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
+ // the scroll event fires so we make sure to scroll to the right place.
+ // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
+ // flush layout and you MUST hold a strong ref on 'this' for the duration
+ // of this call. This might destroy arbitrary layout objects.
+ MOZ_CAN_RUN_SCRIPT nsresult
+ ScrollIntoView(SelectionRegion aRegion, ScrollAxis aVertical = ScrollAxis(),
+ ScrollAxis aHorizontal = ScrollAxis(), int32_t aFlags = 0);
+
+ private:
+ static bool IsUserSelectionCollapsed(
+ const nsRange& aRange, nsTArray<RefPtr<nsRange>>& aTempRangesToAdd);
+ /**
+ * https://w3c.github.io/selection-api/#selectstart-event.
+ */
+ enum class DispatchSelectstartEvent {
+ No,
+ Maybe,
+ };
+
+ /**
+ * See `AddRangesForSelectableNodes`.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AddRangesForUserSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ const DispatchSelectstartEvent aDispatchSelectstartEvent);
+
+ /**
+ * Adds aRange to this Selection. If mUserInitiated is true,
+ * then aRange is first scanned for -moz-user-select:none nodes and split up
+ * into multiple ranges to exclude those before adding the resulting ranges
+ * to this Selection.
+ *
+ * @param aOutIndex points to the range last added, if at least one was added.
+ * If aRange is already contained, it points to the range
+ * containing it. Nothing() if mStyledRanges.mRanges was
+ * empty and no range was added.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AddRangesForSelectableNodes(
+ nsRange* aRange, Maybe<size_t>* aOutIndex,
+ DispatchSelectstartEvent aDispatchSelectstartEvent);
+
+ public:
+ nsresult RemoveCollapsedRanges();
+ void Clear(nsPresContext* aPresContext);
+ MOZ_CAN_RUN_SCRIPT nsresult CollapseInLimiter(nsINode* aContainer,
+ uint32_t aOffset) {
+ if (!aContainer) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return CollapseInLimiter(RawRangeBoundary(aContainer, aOffset));
+ }
+ MOZ_CAN_RUN_SCRIPT nsresult
+ CollapseInLimiter(const RawRangeBoundary& aPoint) {
+ ErrorResult result;
+ CollapseInLimiter(aPoint, result);
+ return result.StealNSResult();
+ }
+ MOZ_CAN_RUN_SCRIPT void CollapseInLimiter(const RawRangeBoundary& aPoint,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT nsresult Extend(nsINode* aContainer, uint32_t aOffset);
+
+ /**
+ * See mStyledRanges.mRanges.
+ */
+ nsRange* GetRangeAt(uint32_t aIndex) const;
+
+ /**
+ * @brief Get the |AbstractRange| at |aIndex|.
+ *
+ * This method is safe to be called for every selection type.
+ * However, |StaticRange|s only occur for |SelectionType::eHighlight|.
+ * If the SelectionType may be eHighlight, this method must be called instead
+ * of |GetRangeAt()|.
+ *
+ * Returns null if |aIndex| is out of bounds.
+ */
+ AbstractRange* GetAbstractRangeAt(uint32_t aIndex) const;
+ // Get the anchor-to-focus range if we don't care which end is
+ // anchor and which end is focus.
+ const nsRange* GetAnchorFocusRange() const { return mAnchorFocusRange; }
+
+ nsDirection GetDirection() const { return mDirection; }
+
+ void SetDirection(nsDirection aDir) { mDirection = aDir; }
+ MOZ_CAN_RUN_SCRIPT nsresult SetAnchorFocusToRange(nsRange* aRange);
+
+ MOZ_CAN_RUN_SCRIPT void ReplaceAnchorFocusRange(nsRange* aRange);
+
+ void AdjustAnchorFocusForMultiRange(nsDirection aDirection);
+
+ nsIFrame* GetPrimaryFrameForAnchorNode() const;
+ nsIFrame* GetPrimaryFrameForFocusNode(bool aVisual,
+ int32_t* aOffsetUsed = nullptr) const;
+
+ UniquePtr<SelectionDetails> LookUpSelection(
+ nsIContent* aContent, uint32_t aContentOffset, uint32_t aContentLength,
+ UniquePtr<SelectionDetails> aDetailsHead, SelectionType aSelectionType,
+ bool aSlowCheck);
+
+ NS_IMETHOD Repaint(nsPresContext* aPresContext);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult StartAutoScrollTimer(nsIFrame* aFrame, const nsPoint& aPoint,
+ uint32_t aDelayInMs);
+
+ nsresult StopAutoScrollTimer();
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ nsINode* GetAnchorNode(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& anchor = AnchorRef();
+ nsINode* anchorNode = anchor.IsSet() ? anchor.Container() : nullptr;
+ if (!anchorNode || aCallerType == CallerType::System ||
+ !anchorNode->ChromeOnlyAccess()) {
+ return anchorNode;
+ }
+ // anchor is nsIContent as ChromeOnlyAccess is nsIContent-only
+ return anchorNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
+ }
+ uint32_t AnchorOffset(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& anchor = AnchorRef();
+ if (aCallerType != CallerType::System && anchor.IsSet() &&
+ anchor.Container()->ChromeOnlyAccess()) {
+ return 0;
+ }
+ const Maybe<uint32_t> offset =
+ anchor.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ return offset ? *offset : 0;
+ }
+ nsINode* GetFocusNode(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& focus = FocusRef();
+ nsINode* focusNode = focus.IsSet() ? focus.Container() : nullptr;
+ if (!focusNode || aCallerType == CallerType::System ||
+ !focusNode->ChromeOnlyAccess()) {
+ return focusNode;
+ }
+ // focus is nsIContent as ChromeOnlyAccess is nsIContent-only
+ return focusNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
+ }
+ uint32_t FocusOffset(CallerType aCallerType = CallerType::System) const {
+ const RangeBoundary& focus = FocusRef();
+ if (aCallerType != CallerType::System && focus.IsSet() &&
+ focus.Container()->ChromeOnlyAccess()) {
+ return 0;
+ }
+ const Maybe<uint32_t> offset =
+ focus.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ return offset ? *offset : 0;
+ }
+
+ nsIContent* GetChildAtAnchorOffset() {
+ const RangeBoundary& anchor = AnchorRef();
+ return anchor.IsSet() ? anchor.GetChildAtOffset() : nullptr;
+ }
+ nsIContent* GetChildAtFocusOffset() {
+ const RangeBoundary& focus = FocusRef();
+ return focus.IsSet() ? focus.GetChildAtOffset() : nullptr;
+ }
+
+ const RangeBoundary& AnchorRef() const;
+ const RangeBoundary& FocusRef() const;
+
+ /*
+ * IsCollapsed -- is the whole selection just one point, or unset?
+ */
+ bool IsCollapsed() const {
+ size_t cnt = mStyledRanges.Length();
+ if (cnt == 0) {
+ return true;
+ }
+
+ if (cnt != 1) {
+ return false;
+ }
+
+ return mStyledRanges.mRanges[0].mRange->Collapsed();
+ }
+
+ // *JS() methods are mapped to Selection.*().
+ // They may move focus only when the range represents normal selection.
+ // These methods shouldn't be used by non-JS callers.
+ MOZ_CAN_RUN_SCRIPT void CollapseJS(nsINode* aContainer, uint32_t aOffset,
+ mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void CollapseToStartJS(mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void CollapseToEndJS(mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void ExtendJS(nsINode& aContainer, uint32_t aOffset,
+ mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void SelectAllChildrenJS(nsINode& aNode,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Deletes this selection from document the nodes belong to.
+ * Only if this has `SelectionType::eNormal`.
+ */
+ MOZ_CAN_RUN_SCRIPT void DeleteFromDocument(mozilla::ErrorResult& aRv);
+
+ uint32_t RangeCount() const { return mStyledRanges.Length(); }
+
+ void GetType(nsAString& aOutType) const;
+
+ nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void AddRangeJS(nsRange& aRange,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Callers need to keep `aRange` alive.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveRangeAndUnselectFramesAndNotifyListeners(
+ AbstractRange& aRange, mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void RemoveAllRanges(mozilla::ErrorResult& aRv);
+
+ /**
+ * Whether Stringify should flush layout or not.
+ */
+ enum class FlushFrames { No, Yes };
+ MOZ_CAN_RUN_SCRIPT
+ void Stringify(nsAString& aResult, FlushFrames = FlushFrames::Yes);
+
+ /**
+ * Indicates whether the node is part of the selection. If partlyContained
+ * is true, the function returns true when some part of the node
+ * is part of the selection. If partlyContained is false, the
+ * function only returns true when the entire node is part of the selection.
+ */
+ bool ContainsNode(nsINode& aNode, bool aPartlyContained,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Check to see if the given point is contained within the selection area. In
+ * particular, this iterates through all the rects that make up the selection,
+ * not just the bounding box, and checks to see if the given point is
+ * contained in any one of them.
+ * @param aPoint The point to check, relative to the root frame.
+ */
+ bool ContainsPoint(const nsPoint& aPoint);
+
+ /**
+ * Modifies the selection. Note that the parameters are case-insensitive.
+ *
+ * @param alter can be one of { "move", "extend" }
+ * - "move" collapses the selection to the end of the selection and
+ * applies the movement direction/granularity to the collapsed
+ * selection.
+ * - "extend" leaves the start of the selection unchanged, and applies
+ * movement direction/granularity to the end of the selection.
+ * @param direction can be one of { "forward", "backward", "left", "right" }
+ * @param granularity can be one of { "character", "word",
+ * "line", "lineboundary" }
+ *
+ * @throws NS_ERROR_NOT_IMPLEMENTED if the granularity is "sentence",
+ * "sentenceboundary", "paragraph", "paragraphboundary", or
+ * "documentboundary". Throws NS_ERROR_INVALID_ARG if alter, direction,
+ * or granularity has an unrecognized value.
+ */
+ MOZ_CAN_RUN_SCRIPT void Modify(const nsAString& aAlter,
+ const nsAString& aDirection,
+ const nsAString& aGranularity,
+ mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ mozilla::ErrorResult& aRv);
+
+ bool GetInterlinePositionJS(mozilla::ErrorResult& aRv) const;
+ void SetInterlinePositionJS(bool aHintRight, mozilla::ErrorResult& aRv);
+
+ enum class InterlinePosition : uint8_t {
+ // Caret should be put at end of line (i.e., before the line break)
+ EndOfLine,
+ // Caret should be put at start of next line (i.e., after the line break)
+ StartOfNextLine,
+ // Undefined means only what is not EndOfLine nor StartOfNextLine.
+ // `SetInterlinePosition` should never be called with this value, and
+ // if `GetInterlinePosition` returns this, it means that the instance has
+ // not been initialized or cleared by the cycle collector or something.
+ // If a method needs to consider whether to call `SetInterlinePosition` or
+ // not call, this value can be used for the latter.
+ Undefined,
+ };
+ InterlinePosition GetInterlinePosition() const;
+ nsresult SetInterlinePosition(InterlinePosition aInterlinePosition);
+
+ Nullable<int16_t> GetCaretBidiLevel(mozilla::ErrorResult& aRv) const;
+ void SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
+ mozilla::ErrorResult& aRv);
+
+ void ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags,
+ int32_t aWrapColumn, nsAString& aReturn,
+ mozilla::ErrorResult& aRv);
+ void AddSelectionListener(nsISelectionListener* aListener);
+ void RemoveSelectionListener(nsISelectionListener* aListener);
+
+ RawSelectionType RawType() const {
+ return ToRawSelectionType(mSelectionType);
+ }
+ SelectionType Type() const { return mSelectionType; }
+
+ /**
+ * @brief Set a highlight name, if this is a highlight selection.
+ */
+ void SetHighlightName(const nsAtom* aHighlightName);
+
+ /**
+ * See documentation of `GetRangesForInterval` in Selection.webidl.
+ *
+ * @param aReturn references, not copies, of the internal ranges.
+ */
+ void GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset,
+ nsINode& aEndNode, uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ nsTArray<RefPtr<nsRange>>& aReturn,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
+ int16_t aVPercent, int16_t aHPercent,
+ ErrorResult& aRv);
+
+ void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
+ const nsAString& aAltForeColor, const nsAString& aAltBackColor,
+ ErrorResult& aRv);
+
+ void ResetColors();
+
+ /**
+ * Non-JS callers should use the following
+ * collapse/collapseToStart/extend/etc methods, instead of the *JS
+ * versions that bindings call.
+ */
+
+ /**
+ * Collapses the selection to a single point, at the specified offset
+ * in the given node. When the selection is collapsed, and the content
+ * is focused and editable, the caret will blink there.
+ * @param aContainer The given node where the selection will be set
+ * @param aOffset Where in given dom node to place the selection (the
+ * offset into the given node)
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseInLimiter(nsINode& aContainer,
+ uint32_t aOffset,
+ ErrorResult& aRv) {
+ CollapseInLimiter(RawRangeBoundary(&aContainer, aOffset), aRv);
+ }
+
+ private:
+ enum class InLimiter {
+ // If eYes, the method may reset selection limiter and move focus if the
+ // given range is out of the limiter.
+ eYes,
+ // If eNo, the method won't reset selection limiter. So, if given range
+ // is out of bounds, the method may return error.
+ eNo,
+ };
+ MOZ_CAN_RUN_SCRIPT
+ void CollapseInternal(InLimiter aInLimiter, const RawRangeBoundary& aPoint,
+ ErrorResult& aRv);
+
+ public:
+ /**
+ * Collapses the whole selection to a single point at the start
+ * of the current selection (irrespective of direction). If content
+ * is focused and editable, the caret will blink there.
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseToStart(mozilla::ErrorResult& aRv);
+
+ /**
+ * Collapses the whole selection to a single point at the end
+ * of the current selection (irrespective of direction). If content
+ * is focused and editable, the caret will blink there.
+ */
+ MOZ_CAN_RUN_SCRIPT void CollapseToEnd(mozilla::ErrorResult& aRv);
+
+ /**
+ * Extends the selection by moving the selection end to the specified node and
+ * offset, preserving the selection begin position. The new selection end
+ * result will always be from the anchorNode to the new focusNode, regardless
+ * of direction.
+ *
+ * @param aContainer The node where the selection will be extended to
+ * @param aOffset Where in aContainer to place the offset of the new
+ * selection end.
+ */
+ MOZ_CAN_RUN_SCRIPT void Extend(nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void AddRangeAndSelectFramesAndNotifyListeners(
+ nsRange& aRange, mozilla::ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void AddHighlightRangeAndSelectFramesAndNotifyListeners(
+ AbstractRange& aRange);
+
+ /**
+ * Adds all children of the specified node to the selection.
+ * @param aNode the parent of the children to be added to the selection.
+ */
+ MOZ_CAN_RUN_SCRIPT void SelectAllChildren(nsINode& aNode,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * SetStartAndEnd() removes all ranges and sets new range as given range.
+ * Different from SetBaseAndExtent(), this won't compare the DOM points of
+ * aStartRef and aEndRef for performance nor set direction to eDirPrevious.
+ * Note that this may reset the limiter and move focus. If you don't want
+ * that, use SetStartAndEndInLimiter() instead.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEnd(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEnd(nsINode& aStartContainer, uint32_t aStartOffset,
+ nsINode& aEndContainer, uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ SetStartAndEnd(RawRangeBoundary(&aStartContainer, aStartOffset),
+ RawRangeBoundary(&aEndContainer, aEndOffset), aRv);
+ }
+
+ /**
+ * SetStartAndEndInLimiter() is similar to SetStartAndEnd(), but this respects
+ * the selection limiter. If all or part of given range is not in the
+ * limiter, this returns error.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInLimiter(const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInLimiter(nsINode& aStartContainer, uint32_t aStartOffset,
+ nsINode& aEndContainer, uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ SetStartAndEndInLimiter(RawRangeBoundary(&aStartContainer, aStartOffset),
+ RawRangeBoundary(&aEndContainer, aEndOffset), aRv);
+ }
+ MOZ_CAN_RUN_SCRIPT
+ Result<Ok, nsresult> SetStartAndEndInLimiter(
+ nsINode& aStartContainer, uint32_t aStartOffset, nsINode& aEndContainer,
+ uint32_t aEndOffset, nsDirection aDirection, int16_t aReason);
+
+ /**
+ * SetBaseAndExtent() is alternative of the JS API for internal use.
+ * Different from SetStartAndEnd(), this sets anchor and focus points as
+ * specified, then if anchor point is after focus node, this sets the
+ * direction to eDirPrevious.
+ * Note that this may reset the limiter and move focus. If you don't want
+ * that, use SetBaseAndExtentInLimier() instead.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtent(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef, ErrorResult& aRv);
+
+ /**
+ * SetBaseAndExtentInLimiter() is similar to SetBaseAndExtent(), but this
+ * respects the selection limiter. If all or part of given range is not in
+ * the limiter, this returns error.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInLimiter(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+ nsINode& aFocusNode, uint32_t aFocusOffset,
+ ErrorResult& aRv) {
+ SetBaseAndExtentInLimiter(RawRangeBoundary(&aAnchorNode, aAnchorOffset),
+ RawRangeBoundary(&aFocusNode, aFocusOffset), aRv);
+ }
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInLimiter(const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv);
+
+ void AddSelectionChangeBlocker();
+ void RemoveSelectionChangeBlocker();
+ bool IsBlockingSelectionChangeEvents() const;
+
+ // Whether this selection is focused in an editable element.
+ bool IsEditorSelection() const;
+
+ /**
+ * Set the painting style for the range. The range must be a range in
+ * the selection. The textRangeStyle will be used by text frame
+ * when it is painting the selection.
+ */
+ nsresult SetTextRangeStyle(nsRange* aRange,
+ const TextRangeStyle& aTextRangeStyle);
+
+ // Methods to manipulate our mFrameSelection's ancestor limiter.
+ nsIContent* GetAncestorLimiter() const;
+ void SetAncestorLimiter(nsIContent* aLimiter);
+
+ /*
+ * Frame Offset cache can be used just during calling
+ * nsEditor::EndPlaceHolderTransaction. EndPlaceHolderTransaction will give
+ * rise to reflow/refreshing view/scroll, and call times of
+ * nsTextFrame::GetPointFromOffset whose return value is to be cached. see
+ * bugs 35296 and 199412
+ */
+ void SetCanCacheFrameOffset(bool aCanCacheFrameOffset);
+
+ // Selection::GetAbstractRangesForIntervalArray
+ //
+ // Fills a nsTArray with the ranges overlapping the range specified by
+ // the given endpoints. Ranges in the selection exactly adjacent to the
+ // input range are not returned unless aAllowAdjacent is set.
+ //
+ // For example, if the following ranges were in the selection
+ // (assume everything is within the same node)
+ //
+ // Start Offset: 0 2 7 9
+ // End Offset: 2 5 9 10
+ //
+ // and passed aBeginOffset of 2 and aEndOffset of 9, then with
+ // aAllowAdjacent set, all the ranges should be returned. If
+ // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
+ // should be returned
+ //
+ // Now that overlapping ranges are disallowed, there can be a maximum of
+ // 2 adjacent ranges
+ nsresult GetAbstractRangesForIntervalArray(nsINode* aBeginNode,
+ uint32_t aBeginOffset,
+ nsINode* aEndNode,
+ uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ nsTArray<AbstractRange*>* aRanges);
+
+ /**
+ * Converts the results of |GetAbstractRangesForIntervalArray()| to |nsRange|.
+ *
+ * |StaticRange|s can only occur in Selections of type |eHighlight|.
+ * Therefore, this method must not be called for this selection type
+ * as not every |AbstractRange| can be cast to |nsRange|.
+ */
+ nsresult GetDynamicRangesForIntervalArray(
+ nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode,
+ uint32_t aEndOffset, bool aAllowAdjacent, nsTArray<nsRange*>* aRanges);
+
+ /**
+ * Modifies the cursor Bidi level after a change in keyboard direction
+ * @param langRTL is true if the new language is right-to-left or
+ * false if the new language is left-to-right.
+ */
+ nsresult SelectionLanguageChange(bool aLangRTL);
+
+ private:
+ bool HasSameRootOrSameComposedDoc(const nsINode& aNode);
+
+ // XXX Please don't add additional uses of this method, it's only for
+ // XXX supporting broken code (bug 1245883) in the following classes:
+ friend class ::nsCopySupport;
+ friend class ::nsHTMLCopyEncoder;
+ MOZ_CAN_RUN_SCRIPT
+ void AddRangeAndSelectFramesAndNotifyListenersInternal(nsRange& aRange,
+ Document* aDocument,
+ ErrorResult&);
+
+ // This is helper method for GetPrimaryFrameForFocusNode.
+ // If aVisual is true, this returns caret frame.
+ // If false, this returns primary frame.
+ nsIFrame* GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent,
+ uint32_t aOffset,
+ int32_t* aOffsetUsed,
+ bool aVisual) const;
+
+ // Get the cached value for nsTextFrame::GetPointFromOffset.
+ nsresult GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
+ nsPoint& aPoint);
+
+ MOZ_CAN_RUN_SCRIPT
+ void SetStartAndEndInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aStartRef,
+ const RawRangeBoundary& aEndRef,
+ nsDirection aDirection, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void SetBaseAndExtentInternal(InLimiter aInLimiter,
+ const RawRangeBoundary& aAnchorRef,
+ const RawRangeBoundary& aFocusRef,
+ ErrorResult& aRv);
+
+ public:
+ SelectionType GetType() const { return mSelectionType; }
+
+ SelectionCustomColors* GetCustomColors() const { return mCustomColors.get(); }
+
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListeners(bool aCalledByJS);
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListeners();
+
+ friend struct AutoUserInitiated;
+ struct MOZ_RAII AutoUserInitiated {
+ explicit AutoUserInitiated(Selection& aSelectionRef)
+ : AutoUserInitiated(&aSelectionRef) {}
+ explicit AutoUserInitiated(Selection* aSelection)
+ : mSavedValue(aSelection->mUserInitiated) {
+ aSelection->mUserInitiated = true;
+ }
+ AutoRestore<bool> mSavedValue;
+ };
+
+ private:
+ friend struct mozilla::AutoPrepareFocusRange;
+ class ScrollSelectionIntoViewEvent;
+ friend class ScrollSelectionIntoViewEvent;
+
+ class ScrollSelectionIntoViewEvent : public Runnable {
+ public:
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_DECL_NSIRUNNABLE
+
+ ScrollSelectionIntoViewEvent(Selection* aSelection, SelectionRegion aRegion,
+ ScrollAxis aVertical, ScrollAxis aHorizontal,
+ int32_t aFlags)
+ : Runnable("dom::Selection::ScrollSelectionIntoViewEvent"),
+ mSelection(aSelection),
+ mRegion(aRegion),
+ mVerticalScroll(aVertical),
+ mHorizontalScroll(aHorizontal),
+ mFlags(aFlags) {
+ NS_ASSERTION(aSelection, "null parameter");
+ }
+ void Revoke() { mSelection = nullptr; }
+
+ private:
+ Selection* mSelection;
+ SelectionRegion mRegion;
+ ScrollAxis mVerticalScroll;
+ ScrollAxis mHorizontalScroll;
+ int32_t mFlags;
+ };
+
+ /**
+ * Set mAnchorFocusRange to mStyledRanges.mRanges[aIndex] if aIndex is a valid
+ * index.
+ */
+ void SetAnchorFocusRange(size_t aIndex);
+ void RemoveAnchorFocusRange() { mAnchorFocusRange = nullptr; }
+ void SelectFramesOf(nsIContent* aContent, bool aSelected) const;
+
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant.
+ */
+ nsresult SelectFramesOfInclusiveDescendantsOfContent(
+ PostContentIterator& aPostOrderIter, nsIContent* aContent,
+ bool aSelected) const;
+
+ nsresult SelectFrames(nsPresContext* aPresContext, AbstractRange& aRange,
+ bool aSelect) const;
+
+ /**
+ * SelectFramesInAllRanges() calls SelectFrames() for all current
+ * ranges.
+ */
+ void SelectFramesInAllRanges(nsPresContext* aPresContext);
+
+ /**
+ * @param aOutIndex If some, points to the index of the range in
+ * mStyledRanges.mRanges so that it's always in [0, mStyledRanges.Length()].
+ * Otherwise, if nothing, this didn't add the range to mStyledRanges.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult MaybeAddTableCellRange(nsRange& aRange,
+ Maybe<size_t>* aOutIndex);
+
+ Document* GetDocument() const;
+
+ MOZ_CAN_RUN_SCRIPT void RemoveAllRangesInternal(mozilla::ErrorResult& aRv);
+
+ void Disconnect();
+
+ struct StyledRanges {
+ explicit StyledRanges(Selection& aSelection) : mSelection(aSelection) {}
+ void Clear();
+
+ StyledRange* FindRangeData(AbstractRange* aRange);
+
+ using Elements = AutoTArray<StyledRange, 1>;
+
+ Elements::size_type Length() const;
+
+ nsresult RemoveCollapsedRanges();
+
+ nsresult RemoveRangeAndUnregisterSelection(AbstractRange& aRange);
+
+ /**
+ * Binary searches the given sorted array of ranges for the insertion point
+ * for the given node/offset. The given comparator is used, and the index
+ * where the point should appear in the array is returned.
+
+ * If there is an item in the array equal to the input point (aPointNode,
+ * aPointOffset), we will return the index of this item.
+ *
+ * @return the index where the point should appear in the array. In
+ * [0, `aElementArray->Length()`].
+ */
+ static size_t FindInsertionPoint(
+ const nsTArray<StyledRange>* aElementArray, const nsINode& aPointNode,
+ uint32_t aPointOffset,
+ int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&));
+
+ /**
+ * Works on the same principle as GetRangesForIntervalArray, however
+ * instead this returns the indices into mRanges between which
+ * the overlapping ranges lie.
+ *
+ * @param aStartIndex If some, aEndIndex will also be some and the value of
+ * aStartIndex will be less or equal than aEndIndex. If
+ * nothing, aEndIndex will also be nothing and it means
+ * that there is no range which in the range.
+ * @param aEndIndex If some, the value is less than mRanges.Length().
+ */
+ nsresult GetIndicesForInterval(const nsINode* aBeginNode,
+ uint32_t aBeginOffset,
+ const nsINode* aEndNode, uint32_t aEndOffset,
+ bool aAllowAdjacent,
+ Maybe<size_t>& aStartIndex,
+ Maybe<size_t>& aEndIndex) const;
+
+ bool HasEqualRangeBoundariesAt(const nsRange& aRange,
+ size_t aRangeIndex) const;
+
+ /**
+ * Preserves the sorting and disjunctiveness of mRanges.
+ *
+ * @param aOutIndex If some, will point to the index of the added range, or
+ * if aRange is already contained, to the one containing
+ * it. Hence it'll always be in [0, mRanges.Length()).
+ * This is nothing only when the method returns an error.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ MaybeAddRangeAndTruncateOverlaps(nsRange* aRange, Maybe<size_t>* aOutIndex);
+
+ /**
+ * GetCommonEditingHost() returns common editing host of all
+ * ranges if there is. If at least one of the ranges is in non-editable
+ * element, returns nullptr. See following examples for the detail:
+ *
+ * <div id="a" contenteditable>
+ * an[cestor
+ * <div id="b" contenteditable="false">
+ * non-editable
+ * <div id="c" contenteditable>
+ * desc]endant
+ * in this case, this returns div#a because div#c is also in div#a.
+ *
+ * <div id="a" contenteditable>
+ * an[ce]stor
+ * <div id="b" contenteditable="false">
+ * non-editable
+ * <div id="c" contenteditable>
+ * de[sc]endant
+ * in this case, this returns div#a because second range is also in div#a
+ * and common ancestor of the range (i.e., div#c) is editable.
+ *
+ * <div id="a" contenteditable>
+ * an[ce]stor
+ * <div id="b" contenteditable="false">
+ * [non]-editable
+ * <div id="c" contenteditable>
+ * de[sc]endant
+ * in this case, this returns nullptr because the second range is in
+ * non-editable area.
+ */
+ Element* GetCommonEditingHost() const;
+
+ MOZ_CAN_RUN_SCRIPT void MaybeFocusCommonEditingHost(
+ PresShell* aPresShell) const;
+
+ static nsresult SubtractRange(StyledRange& aRange, nsRange& aSubtract,
+ nsTArray<StyledRange>* aOutput);
+
+ void UnregisterSelection();
+
+ // These are the ranges inside this selection. They are kept sorted in order
+ // of DOM start position.
+ //
+ // This data structure is sorted by the range beginnings. As the ranges are
+ // disjoint, it is also implicitly sorted by the range endings. This allows
+ // us to perform binary searches when searching for existence of a range,
+ // giving us O(log n) search time.
+ //
+ // Inserting a new range requires finding the overlapping interval,
+ // requiring two binary searches plus up to an additional 6 DOM comparisons.
+ // If this proves to be a performance concern, then an interval tree may be
+ // a possible solution, allowing the calculation of the overlap interval in
+ // O(log n) time, though this would require rebalancing and other overhead.
+ Elements mRanges;
+
+ Selection& mSelection;
+ };
+
+ StyledRanges mStyledRanges{*this};
+
+ RefPtr<nsRange> mAnchorFocusRange;
+ RefPtr<nsFrameSelection> mFrameSelection;
+ RefPtr<AccessibleCaretEventHub> mAccessibleCaretEventHub;
+ RefPtr<SelectionChangeEventDispatcher> mSelectionChangeEventDispatcher;
+ RefPtr<AutoScroller> mAutoScroller;
+ nsTArray<nsCOMPtr<nsISelectionListener>> mSelectionListeners;
+ nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
+ CachedOffsetForFrame* mCachedOffsetForFrame;
+ nsDirection mDirection;
+ const SelectionType mSelectionType;
+ RefPtr<const nsAtom> mHighlightName;
+ UniquePtr<SelectionCustomColors> mCustomColors;
+
+ // Non-zero if we don't want any changes we make to the selection to be
+ // visible to content. If non-zero, content won't be notified about changes.
+ uint32_t mSelectionChangeBlockerCount;
+
+ /**
+ * True if the current selection operation was initiated by user action.
+ * It determines whether we exclude -moz-user-select:none nodes or not,
+ * as well as whether selectstart events will be fired.
+ */
+ bool mUserInitiated;
+
+ /**
+ * When the selection change is caused by a call of Selection API,
+ * mCalledByJS is true. Otherwise, false.
+ */
+ bool mCalledByJS;
+
+ /**
+ * true if AutoCopyListner::OnSelectionChange() should be called.
+ */
+ bool mNotifyAutoCopy;
+};
+
+// Stack-class to turn on/off selection batching.
+class MOZ_STACK_CLASS SelectionBatcher final {
+ private:
+ const RefPtr<Selection> mSelection;
+ const int16_t mReasons;
+ const char* const mRequesterFuncName;
+
+ public:
+ /**
+ * @param aRequesterFuncName function name which wants the selection batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging. This MUST be living when the destructor runs.
+ */
+ // TODO: Mark these constructors `MOZ_CAN_RUN_SCRIPT` because the destructor
+ // may run script via nsISelectionListener.
+ explicit SelectionBatcher(Selection& aSelectionRef,
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON)
+ : SelectionBatcher(&aSelectionRef, aRequesterFuncName, aReasons) {}
+ explicit SelectionBatcher(Selection* aSelection,
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON)
+ : mSelection(aSelection),
+ mReasons(aReasons),
+ mRequesterFuncName(aRequesterFuncName) {
+ if (mSelection) {
+ mSelection->StartBatchChanges(mRequesterFuncName);
+ }
+ }
+
+ ~SelectionBatcher() {
+ if (mSelection) {
+ mSelection->EndBatchChanges(mRequesterFuncName, mReasons);
+ }
+ }
+};
+
+class MOZ_RAII AutoHideSelectionChanges final {
+ public:
+ explicit AutoHideSelectionChanges(const nsFrameSelection* aFrame);
+
+ explicit AutoHideSelectionChanges(Selection& aSelectionRef)
+ : AutoHideSelectionChanges(&aSelectionRef) {}
+
+ ~AutoHideSelectionChanges() {
+ if (mSelection) {
+ mSelection->RemoveSelectionChangeBlocker();
+ }
+ }
+
+ private:
+ explicit AutoHideSelectionChanges(Selection* aSelection)
+ : mSelection(aSelection) {
+ if (mSelection) {
+ mSelection->AddSelectionChangeBlocker();
+ }
+ }
+
+ RefPtr<Selection> mSelection;
+};
+
+} // namespace dom
+
+inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType) {
+ return aRawSelectionType >= nsISelectionController::SELECTION_NONE &&
+ aRawSelectionType <= nsISelectionController::SELECTION_URLSTRIKEOUT;
+}
+
+inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType) {
+ if (!IsValidRawSelectionType(aRawSelectionType)) {
+ return SelectionType::eInvalid;
+ }
+ return static_cast<SelectionType>(aRawSelectionType);
+}
+
+inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType) {
+ MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
+ return static_cast<RawSelectionType>(aSelectionType);
+}
+
+inline RawSelectionType ToRawSelectionType(TextRangeType aTextRangeType) {
+ return ToRawSelectionType(ToSelectionType(aTextRangeType));
+}
+
+inline SelectionTypeMask ToSelectionTypeMask(SelectionType aSelectionType) {
+ MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
+ return aSelectionType == SelectionType::eNone
+ ? 0
+ : static_cast<SelectionTypeMask>(
+ 1 << (static_cast<uint8_t>(aSelectionType) - 1));
+}
+
+inline std::ostream& operator<<(
+ std::ostream& aStream, const dom::Selection::InterlinePosition& aPosition) {
+ using InterlinePosition = dom::Selection::InterlinePosition;
+ switch (aPosition) {
+ case InterlinePosition::EndOfLine:
+ return aStream << "InterlinePosition::EndOfLine";
+ case InterlinePosition::StartOfNextLine:
+ return aStream << "InterlinePosition::StartOfNextLine";
+ case InterlinePosition::Undefined:
+ return aStream << "InterlinePosition::Undefined";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Illegal value");
+ return aStream << "<Illegal value>";
+ }
+}
+
+} // namespace mozilla
+
+#endif // mozilla_Selection_h__
diff --git a/dom/base/SelectionChangeEventDispatcher.cpp b/dom/base/SelectionChangeEventDispatcher.cpp
new file mode 100644
index 0000000000..a669c45d53
--- /dev/null
+++ b/dom/base/SelectionChangeEventDispatcher.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of mozilla::SelectionChangeEventDispatcher
+ */
+
+#include "SelectionChangeEventDispatcher.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Selection.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsFrameSelection.h"
+#include "nsRange.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+SelectionChangeEventDispatcher::RawRangeData::RawRangeData(
+ const nsRange* aRange) {
+ if (aRange->IsPositioned()) {
+ mStartContainer = aRange->GetStartContainer();
+ mEndContainer = aRange->GetEndContainer();
+ mStartOffset = aRange->StartOffset();
+ mEndOffset = aRange->EndOffset();
+ } else {
+ mStartContainer = nullptr;
+ mEndContainer = nullptr;
+ mStartOffset = 0;
+ mEndOffset = 0;
+ }
+}
+
+bool SelectionChangeEventDispatcher::RawRangeData::Equals(
+ const nsRange* aRange) {
+ if (!aRange->IsPositioned()) {
+ return !mStartContainer;
+ }
+ return mStartContainer == aRange->GetStartContainer() &&
+ mEndContainer == aRange->GetEndContainer() &&
+ mStartOffset == aRange->StartOffset() &&
+ mEndOffset == aRange->EndOffset();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ SelectionChangeEventDispatcher::RawRangeData& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mStartContainer,
+ "mStartContainer", aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mEndContainer, "mEndContainer",
+ aFlags);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SelectionChangeEventDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SelectionChangeEventDispatcher)
+ tmp->mOldRanges.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SelectionChangeEventDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOldRanges);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void SelectionChangeEventDispatcher::OnSelectionChange(Document* aDoc,
+ Selection* aSel,
+ int16_t aReason) {
+ // Check if the ranges have actually changed
+ // Don't bother checking this if we are hiding changes.
+ if (mOldRanges.Length() == aSel->RangeCount() &&
+ !aSel->IsBlockingSelectionChangeEvents()) {
+ bool changed = mOldDirection != aSel->GetDirection();
+ if (!changed) {
+ for (const uint32_t i : IntegerRange(mOldRanges.Length())) {
+ if (!mOldRanges[i].Equals(aSel->GetRangeAt(i))) {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if (!changed) {
+ return;
+ }
+ }
+
+ // The ranges have actually changed, update the mOldRanges array
+ mOldRanges.ClearAndRetainStorage();
+ for (const uint32_t i : IntegerRange(aSel->RangeCount())) {
+ mOldRanges.AppendElement(RawRangeData(aSel->GetRangeAt(i)));
+ }
+ mOldDirection = aSel->GetDirection();
+
+ // If we are hiding changes, then don't do anything else. We do this after we
+ // update mOldRanges so that changes after the changes stop being hidden don't
+ // incorrectly trigger a change, even though they didn't change anything
+ if (aSel->IsBlockingSelectionChangeEvents()) {
+ return;
+ }
+
+ const Document* doc = aSel->GetParentObject();
+ if (MOZ_UNLIKELY(!doc)) {
+ return;
+ }
+ const nsPIDOMWindowInner* inner = doc->GetInnerWindow();
+ if (MOZ_UNLIKELY(!inner)) {
+ return;
+ }
+ const bool maybeHasSelectionChangeEventListeners =
+ !inner || inner->HasSelectionChangeEventListeners();
+ const bool maybeHasFormSelectEventListeners =
+ !inner || inner->HasFormSelectEventListeners();
+ if (!maybeHasSelectionChangeEventListeners &&
+ !maybeHasFormSelectEventListeners) {
+ return;
+ }
+
+ // Be aware, don't call GetTextControlFromSelectionLimiter once you might
+ // run script because selection limit may have already been changed by it.
+ nsCOMPtr<nsIContent> textControl;
+ if ((maybeHasFormSelectEventListeners &&
+ (aReason & nsISelectionListener::JS_REASON)) ||
+ maybeHasSelectionChangeEventListeners) {
+ if (const nsFrameSelection* fs = aSel->GetFrameSelection()) {
+ if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
+ textControl = root->GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ MOZ_ASSERT_IF(textControl,
+ textControl->IsTextControlElement() &&
+ !textControl->IsInNativeAnonymousSubtree());
+ }
+ }
+ };
+
+ // Selection changes with non-JS reason only cares about whether the new
+ // selection is collapsed or not. See TextInputListener::OnSelectionChange.
+ if (textControl && maybeHasFormSelectEventListeners &&
+ (aReason & nsISelectionListener::JS_REASON)) {
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(textControl, eFormSelect, CanBubble::eYes);
+ asyncDispatcher->PostDOMEvent();
+ }
+
+ if (!maybeHasSelectionChangeEventListeners) {
+ return;
+ }
+
+ // The spec currently doesn't say that we should dispatch this event on text
+ // controls, so for now we only support doing that under a pref, disabled by
+ // default.
+ // See https://github.com/w3c/selection-api/issues/53.
+ if (textControl &&
+ !StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
+ return;
+ }
+
+ nsINode* target =
+ textControl ? static_cast<nsINode*>(textControl.get()) : aDoc;
+ if (target) {
+ CanBubble canBubble = textControl ? CanBubble::eYes : CanBubble::eNo;
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(target, eSelectionChange, canBubble);
+ asyncDispatcher->PostDOMEvent();
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/base/SelectionChangeEventDispatcher.h b/dom/base/SelectionChangeEventDispatcher.h
new file mode 100644
index 0000000000..a734b4490d
--- /dev/null
+++ b/dom/base/SelectionChangeEventDispatcher.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SelectionChangeEventDispatcher_h
+#define mozilla_SelectionChangeEventDispatcher_h
+
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsDirection.h"
+
+class nsINode;
+class nsRange;
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+class Selection;
+} // namespace dom
+
+class SelectionChangeEventDispatcher final {
+ public:
+ // SelectionChangeEventDispatcher has to participate in cycle collection
+ // because it holds strong references to nsINodes in its mOldRanges array.
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(
+ SelectionChangeEventDispatcher)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SelectionChangeEventDispatcher)
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnSelectionChange(dom::Document* aDocument, dom::Selection* aSelection,
+ int16_t aReason);
+
+ // This field is used to keep track of the ranges which were present in the
+ // selection when the selectionchange event was previously fired. This allows
+ // for the selectionchange event to only be fired when a selection is actually
+ // changed.
+ struct RawRangeData {
+ // These properties are not void*s to avoid the potential situation where
+ // the nsINode is freed, and a new nsINode is allocated with the same
+ // address, which could potentially break the comparison logic. In reality,
+ // this is extremely unlikely to occur (potentially impossible), but these
+ // nsCOMPtrs are safer. They are never dereferenced.
+ nsCOMPtr<nsINode> mStartContainer;
+ nsCOMPtr<nsINode> mEndContainer;
+
+ // XXX These are int32_ts on nsRange, but uint32_ts in the return value
+ // of GetStart_, so I use uint32_ts here. See bug 1194256.
+ uint32_t mStartOffset;
+ uint32_t mEndOffset;
+
+ explicit RawRangeData(const nsRange* aRange);
+ bool Equals(const nsRange* aRange);
+ };
+
+ private:
+ nsTArray<RawRangeData> mOldRanges;
+ nsDirection mOldDirection;
+
+ ~SelectionChangeEventDispatcher() = default;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SelectionChangeEventDispatcher_h
diff --git a/dom/base/SerializedStackHolder.cpp b/dom/base/SerializedStackHolder.cpp
new file mode 100644
index 0000000000..199879e226
--- /dev/null
+++ b/dom/base/SerializedStackHolder.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SerializedStackHolder.h"
+
+#include "js/SavedFrameAPI.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Services.h"
+#include "nsJSPrincipals.h"
+#include "nsIObserverService.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+SerializedStackHolder::SerializedStackHolder()
+ : mHolder(StructuredCloneHolder::CloningSupported,
+ StructuredCloneHolder::TransferringNotSupported,
+ StructuredCloneHolder::StructuredCloneScope::SameProcess) {}
+
+void SerializedStackHolder::WriteStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack) {
+ JS::Rooted<JS::Value> stackValue(aCx, JS::ObjectValue(*aStack));
+ mHolder.Write(aCx, stackValue, IgnoreErrors());
+
+ // StructuredCloneHolder::Write can leave a pending exception on the context.
+ JS_ClearPendingException(aCx);
+}
+
+void SerializedStackHolder::SerializeMainThreadOrWorkletStack(
+ JSContext* aCx, JS::Handle<JSObject*> aStack) {
+ MOZ_ASSERT(!IsCurrentThreadRunningWorker());
+ WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeWorkerStack(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ JS::Handle<JSObject*> aStack) {
+ MOZ_ASSERT(aWorkerPrivate->IsOnCurrentThread());
+
+ RefPtr<StrongWorkerRef> workerRef =
+ StrongWorkerRef::Create(aWorkerPrivate, "WorkerErrorReport");
+ if (workerRef) {
+ mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ } else {
+ // Don't write the stack if we can't create a ref to the worker.
+ return;
+ }
+
+ WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeCurrentStack(JSContext* aCx) {
+ JS::Rooted<JSObject*> stack(aCx);
+ if (JS::CurrentGlobalOrNull(aCx) && !JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ if (stack) {
+ if (NS_IsMainThread()) {
+ SerializeMainThreadOrWorkletStack(aCx, stack);
+ } else {
+ WorkerPrivate* currentWorker = GetCurrentThreadWorkerPrivate();
+ SerializeWorkerStack(aCx, currentWorker, stack);
+ }
+ }
+}
+
+JSObject* SerializedStackHolder::ReadStack(JSContext* aCx) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHolder.HasData()) {
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> stackValue(aCx);
+
+ mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue, IgnoreErrors());
+
+ return stackValue.isObject() ? &stackValue.toObject() : nullptr;
+}
+
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
+ MOZ_ASSERT_IF(!NS_IsMainThread(),
+ GetCurrentThreadWorkerPrivate()->IsWatchedByDevTools());
+
+ return GetCurrentStack(aCx);
+}
+
+UniquePtr<SerializedStackHolder> GetCurrentStack(JSContext* aCx) {
+ UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>();
+ stack->SerializeCurrentStack(aCx);
+ return stack;
+}
+
+void NotifyNetworkMonitorAlternateStack(
+ nsISupports* aChannel, UniquePtr<SerializedStackHolder> aStackHolder) {
+ if (!aStackHolder) {
+ return;
+ }
+
+ nsString stackString;
+ ConvertSerializedStackToJSON(std::move(aStackHolder), stackString);
+
+ if (!stackString.IsEmpty()) {
+ NotifyNetworkMonitorAlternateStack(aChannel, stackString);
+ }
+}
+
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+ nsAString& aStackString) {
+ // We need a JSContext to be able to stringify the SavedFrame stack.
+ // This will not run any scripts. A privileged scope is needed to fully
+ // inspect all stack frames we find.
+ AutoJSAPI jsapi;
+ DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> savedFrame(cx, aStackHolder->ReadStack(cx));
+ if (!savedFrame) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> converted(cx);
+ converted = JS::ConvertSavedFrameToPlainObject(
+ cx, savedFrame, JS::SavedFrameSelfHosted::Exclude);
+ if (!converted) {
+ JS_ClearPendingException(cx);
+ return;
+ }
+
+ JS::Rooted<JS::Value> convertedValue(cx, JS::ObjectValue(*converted));
+ if (!nsContentUtils::StringifyJSON(cx, convertedValue, aStackString,
+ UndefinedIsNullStringLiteral)) {
+ JS_ClearPendingException(cx);
+ return;
+ }
+}
+
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+ const nsAString& aStackJSON) {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return;
+ }
+
+ obsService->NotifyObservers(aChannel, "network-monitor-alternate-stack",
+ PromiseFlatString(aStackJSON).get());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/SerializedStackHolder.h b/dom/base/SerializedStackHolder.h
new file mode 100644
index 0000000000..ac0ffd0be0
--- /dev/null
+++ b/dom/base/SerializedStackHolder.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SerializedStackHolder_h
+#define mozilla_dom_SerializedStackHolder_h
+
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla::dom {
+
+// Information about a main or worker thread stack trace that can be accessed
+// from either kind of thread. When a worker thread stack is serialized, the
+// worker is held alive until this holder is destroyed.
+class SerializedStackHolder {
+ // Holds any encoded stack data.
+ StructuredCloneHolder mHolder;
+
+ // The worker associated with this stack, or null if this is a main thread
+ // stack.
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+ // Write aStack's data into mHolder.
+ void WriteStack(JSContext* aCx, JS::Handle<JSObject*> aStack);
+
+ public:
+ SerializedStackHolder();
+
+ // Fill this holder with a main or worklet thread stack.
+ void SerializeMainThreadOrWorkletStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack);
+
+ // Fill this holder with a worker thread stack.
+ void SerializeWorkerStack(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ JS::Handle<JSObject*> aStack);
+
+ // Fill this holder with the current thread's current stack.
+ void SerializeCurrentStack(JSContext* aCx);
+
+ // Read back a saved frame stack. This must be called on the main thread.
+ // This returns null on failure, and does not leave an exception on aCx.
+ JSObject* ReadStack(JSContext* aCx);
+};
+
+// Construct a stack for the current thread, which may be consumed by the net
+// monitor later on. This may be called on either the main or a worker thread.
+//
+// This always creates a stack, even if the net monitor isn't active for the
+// associated window. The net monitor will only be active if the associated
+// Browsing Context or worker's WatchedByDevTools flag is set, so this should
+// be checked before creating the stack.
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx);
+
+// Construct a stack for the current thread.
+UniquePtr<SerializedStackHolder> GetCurrentStack(JSContext* aCx);
+
+// If aStackHolder is non-null, this notifies the net monitor that aStackHolder
+// is the stack from which aChannel originates. This must be called on the main
+// thread. This call is synchronous, and aChannel and aStackHolder will not be
+// used afterward. aChannel is an nsISupports object because this can be used
+// with either nsIChannel or nsIWebSocketChannel.
+void NotifyNetworkMonitorAlternateStack(
+ nsISupports* aChannel, UniquePtr<SerializedStackHolder> aStackHolder);
+
+// Read back the saved frame stack and store it in a string as JSON.
+// This must be called on the main thread.
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+ nsAString& aStackString);
+
+// As above, notify the net monitor for a stack that has already been converted
+// to JSON. This can be used with ConvertSerializedStackToJSON when multiple
+// notifications might be needed for a single stack.
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+ const nsAString& aStackJSON);
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SerializedStackHolder_h
diff --git a/dom/base/ShadowIncludingTreeIterator.h b/dom/base/ShadowIncludingTreeIterator.h
new file mode 100644
index 0000000000..886738f39f
--- /dev/null
+++ b/dom/base/ShadowIncludingTreeIterator.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Implementation of
+ * https://dom.spec.whatwg.org/#concept-shadow-including-tree-order in iterator
+ * form. This can and should be used to avoid recursion on the stack and lots
+ * of function calls during shadow-including tree iteration.
+ */
+
+#ifndef mozilla_dom_ShadowIncludingTreeIterator_h
+#define mozilla_dom_ShadowIncludingTreeIterator_h
+
+#include "nsINode.h"
+#include "nsTArray.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ShadowRoot.h"
+
+namespace mozilla::dom {
+
+class ShadowIncludingTreeIterator {
+ public:
+ /**
+ * Initialize an iterator with aRoot. After that it can be iterated with a
+ * range-based for loop. At the moment, that's the only supported form of use
+ * for this iterator.
+ */
+ explicit ShadowIncludingTreeIterator(nsINode& aRoot) : mCurrent(&aRoot) {
+ mRoots.AppendElement(&aRoot);
+ }
+
+#ifdef DEBUG
+ ~ShadowIncludingTreeIterator() {
+ MOZ_ASSERT(
+ !mMutationGuard.Mutated(0),
+ "Don't mutate the DOM while using a ShadowIncludingTreeIterator");
+ }
+#endif // DEBUG
+
+ // Basic support for range-based for loops. This will modify the iterator as
+ // it goes.
+ ShadowIncludingTreeIterator& begin() { return *this; }
+
+ std::nullptr_t end() const { return nullptr; }
+
+ bool operator!=(std::nullptr_t) const { return !!mCurrent; }
+
+ explicit operator bool() const { return !!mCurrent; }
+
+ void operator++() { Next(); }
+
+ void SkipChildren() {
+ MOZ_ASSERT(mCurrent, "Shouldn't be at end");
+ mCurrent = mCurrent->GetNextNonChildNode(mRoots.LastElement());
+ WalkOutOfShadowRootsIfNeeded();
+ }
+
+ nsINode* operator*() { return mCurrent; }
+
+ private:
+ void Next() {
+ MOZ_ASSERT(mCurrent, "Don't call Next() after we have no current node");
+
+ // We walk shadow roots immediately after their shadow host.
+ if (Element* element = Element::FromNode(mCurrent)) {
+ if (ShadowRoot* shadowRoot = element->GetShadowRoot()) {
+ mCurrent = shadowRoot;
+ mRoots.AppendElement(shadowRoot);
+ return;
+ }
+ }
+
+ mCurrent = mCurrent->GetNextNode(mRoots.LastElement());
+ WalkOutOfShadowRootsIfNeeded();
+ }
+
+ void WalkOutOfShadowRootsIfNeeded() {
+ while (!mCurrent) {
+ // Nothing left under this root. Keep trying to pop the stack until we
+ // find a node or run out of stack.
+ nsINode* root = mRoots.PopLastElement();
+ if (mRoots.IsEmpty()) {
+ // No more roots to step out of; we're done. mCurrent is already set to
+ // null.
+ return;
+ }
+ mCurrent =
+ ShadowRoot::FromNode(root)->Host()->GetNextNode(mRoots.LastElement());
+ }
+ }
+
+ // The current node we're at.
+ nsINode* mCurrent;
+
+ // Stack of roots that we're inside of right now. An empty stack can only
+ // happen when mCurrent is null (and hence we are done iterating).
+ //
+ // The default array size here is picked based on gut feeling. We want at
+ // least 1, since we will always add something to it in our constructor.
+ // Having a few more entries probably makes sense, because this is commonly
+ // used in cases when we know we have custom elements, and hence likely have
+ // shadow DOMs. But the exact value "4" was just picked because it sounded
+ // not too big, not too small. Feel free to replace it with something else
+ // based on actual data.
+ CopyableAutoTArray<nsINode*, 4> mRoots;
+
+#ifdef DEBUG
+ // Make sure no one mutates the DOM while we're walking over it.
+ nsMutationGuard mMutationGuard;
+#endif // DEBUG
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ShadowIncludingTreeIterator_h
diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp
new file mode 100644
index 0000000000..30c12a14b3
--- /dev/null
+++ b/dom/base/ShadowRoot.cpp
@@ -0,0 +1,877 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsINode.h"
+#include "nsWindowSizes.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/TreeOrderedArrayInlines.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/IdentifierMapEntry.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ServoStyleRuleMap.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/dom/StyleSheetList.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment)
+ DocumentOrShadowRoot::Traverse(tmp, cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
+ DocumentOrShadowRoot::Unlink(tmp);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer)
+NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
+
+NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
+NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
+
+ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
+ Element::DelegatesFocus aDelegatesFocus,
+ SlotAssignmentMode aSlotAssignment,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : DocumentFragment(std::move(aNodeInfo)),
+ DocumentOrShadowRoot(this),
+ mMode(aMode),
+ mDelegatesFocus(aDelegatesFocus),
+ mSlotAssignment(aSlotAssignment),
+ mIsDetailsShadowTree(aElement->IsHTMLElement(nsGkAtoms::details)),
+ mIsAvailableToElementInternals(false) {
+ // nsINode.h relies on this.
+ MOZ_ASSERT(static_cast<nsINode*>(this) == reinterpret_cast<nsINode*>(this));
+ MOZ_ASSERT(static_cast<nsIContent*>(this) ==
+ reinterpret_cast<nsIContent*>(this));
+
+ SetHost(aElement);
+
+ // Nodes in a shadow tree should never store a value
+ // in the subtree root pointer, nodes in the shadow tree
+ // track the subtree root using GetContainingShadow().
+ ClearSubtreeRootPointer();
+
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ if (Host()->IsInNativeAnonymousSubtree()) {
+ // NOTE(emilio): We could consider just propagating the
+ // IN_NATIVE_ANONYMOUS_SUBTREE flag (not making this an anonymous root), but
+ // that breaks the invariant that if two nodes have the same
+ // NativeAnonymousSubtreeRoot() they are in the same DOM tree, which we rely
+ // on a couple places and would need extra fixes.
+ //
+ // We don't hit this case for now anyways, bug 1824886 would start hitting
+ // it.
+ SetIsNativeAnonymousRoot();
+ }
+ Bind();
+
+ ExtendedDOMSlots()->mContainingShadow = this;
+}
+
+ShadowRoot::~ShadowRoot() {
+ if (IsInComposedDoc()) {
+ OwnerDoc()->RemoveComposedDocShadowRoot(*this);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));
+
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // nsINode destructor expects mSubtreeRoot == this.
+ SetSubtreeRootPointer(this);
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf)
+
+void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize);
+ DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes);
+ aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis(
+ ShadowRootAuthorStylesMallocSizeOf,
+ ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get());
+}
+
+JSObject* ShadowRoot::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ShadowRoot::NodeInfoChanged(Document* aOldDoc) {
+ DocumentFragment::NodeInfoChanged(aOldDoc);
+ Document* newDoc = OwnerDoc();
+ const bool fromOrToTemplate =
+ aOldDoc->GetTemplateContentsOwnerIfExists() == newDoc ||
+ newDoc->GetTemplateContentsOwnerIfExists() == aOldDoc;
+ if (!fromOrToTemplate) {
+ ClearAdoptedStyleSheets();
+ }
+}
+
+void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) {
+ if (aOther->IsRootOfNativeAnonymousSubtree()) {
+ SetIsNativeAnonymousRoot();
+ }
+
+ if (aOther->IsUAWidget()) {
+ SetIsUAWidget();
+ }
+
+ size_t sheetCount = aOther->SheetCount();
+ for (size_t i = 0; i < sheetCount; ++i) {
+ StyleSheet* sheet = aOther->SheetAt(i);
+ if (sheet->IsApplicable()) {
+ RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, this);
+ if (clonedSheet) {
+ AppendStyleSheet(*clonedSheet.get());
+ }
+ }
+ }
+ CloneAdoptedSheetsFrom(*aOther);
+}
+
+nsresult ShadowRoot::Bind() {
+ MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?");
+ if (Host()->IsInComposedDoc()) {
+ SetIsConnected(true);
+ Document* doc = OwnerDoc();
+ doc->AddComposedDocShadowRoot(*this);
+ // If our stylesheets somehow mutated when we were disconnected, we need to
+ // ensure that our style data gets flushed as appropriate.
+ if (mServoStyles && Servo_AuthorStyles_IsDirty(mServoStyles.get())) {
+ doc->RecordShadowStyleChange(*this);
+ }
+ }
+
+ BindContext context(*this);
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ nsresult rv = child->BindToTree(context, *this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void ShadowRoot::Unbind() {
+ if (IsInComposedDoc()) {
+ SetIsConnected(false);
+ OwnerDoc()->RemoveComposedDocShadowRoot(*this);
+ }
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree(false);
+ }
+}
+
+void ShadowRoot::Unattach() {
+ MOZ_ASSERT(!HasSlots(), "Won't work!");
+ if (!GetHost()) {
+ // It is possible that we've been unlinked already. In such case host
+ // should have called Unbind and ShadowRoot's own unlink.
+ return;
+ }
+
+ Unbind();
+ SetHost(nullptr);
+}
+
+void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) {
+ MOZ_ASSERT(aElement);
+ Document* doc = GetComposedDoc();
+ if (!doc) {
+ return;
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ presShell->DestroyFramesForAndRestyle(aElement);
+}
+
+void ShadowRoot::PartAdded(const Element& aPart) {
+ MOZ_ASSERT(aPart.HasPartAttribute());
+ MOZ_ASSERT(!mParts.Contains(&aPart));
+ mParts.AppendElement(&aPart);
+}
+
+void ShadowRoot::PartRemoved(const Element& aPart) {
+ MOZ_ASSERT(mParts.Contains(&aPart));
+ mParts.RemoveElement(&aPart);
+ MOZ_ASSERT(!mParts.Contains(&aPart));
+}
+
+void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
+ MOZ_ASSERT(aSlot);
+
+ // Note that if name attribute missing, the slot is a default slot.
+ nsAutoString name;
+ aSlot->GetName(name);
+
+ SlotArray& currentSlots = *mSlotMap.GetOrInsertNew(name);
+
+ size_t index = currentSlots.Insert(*aSlot);
+
+ // For Named slots, slottables are inserted into the other slot
+ // which has the same name already, however it's not the case
+ // for manual slots
+ if (index != 0 && SlotAssignment() == SlotAssignmentMode::Named) {
+ return;
+ }
+
+ HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(1);
+ if (SlotAssignment() == SlotAssignmentMode::Named) {
+ if (oldSlot) {
+ MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot);
+
+ // Move assigned nodes from old slot to new slot.
+ InvalidateStyleAndLayoutOnSubtree(oldSlot);
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes();
+ bool doEnqueueSlotChange = false;
+ while (assignedNodes.Length() > 0) {
+ nsINode* assignedNode = assignedNodes[0];
+
+ oldSlot->RemoveAssignedNode(*assignedNode->AsContent());
+ aSlot->AppendAssignedNode(*assignedNode->AsContent());
+ doEnqueueSlotChange = true;
+ }
+
+ if (doEnqueueSlotChange) {
+ oldSlot->EnqueueSlotChangeEvent();
+ aSlot->EnqueueSlotChangeEvent();
+ SlotStateChanged(oldSlot);
+ SlotStateChanged(aSlot);
+ }
+ } else {
+ bool doEnqueueSlotChange = false;
+ // Otherwise add appropriate nodes to this slot from the host.
+ for (nsIContent* child = GetHost()->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ nsAutoString slotName;
+ GetSlotNameFor(*child, slotName);
+ if (!child->IsSlotable() || !slotName.Equals(name)) {
+ continue;
+ }
+ doEnqueueSlotChange = true;
+ aSlot->AppendAssignedNode(*child);
+ }
+
+ if (doEnqueueSlotChange) {
+ aSlot->EnqueueSlotChangeEvent();
+ SlotStateChanged(aSlot);
+ }
+ }
+ } else {
+ bool doEnqueueSlotChange = false;
+ for (const auto& node : aSlot->ManuallyAssignedNodes()) {
+ if (GetHost() != node->GetParent()) {
+ continue;
+ }
+
+ MOZ_ASSERT(node->IsContent(),
+ "Manually assigned nodes should be an element or a text");
+ nsIContent* content = node->AsContent();
+
+ aSlot->AppendAssignedNode(*content);
+ doEnqueueSlotChange = true;
+ }
+ if (doEnqueueSlotChange) {
+ aSlot->EnqueueSlotChangeEvent();
+ SlotStateChanged(aSlot);
+ }
+ }
+}
+
+void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
+ MOZ_ASSERT(aSlot);
+
+ nsAutoString name;
+ aSlot->GetName(name);
+
+ MOZ_ASSERT(mSlotMap.Get(name));
+
+ SlotArray& currentSlots = *mSlotMap.Get(name);
+ MOZ_DIAGNOSTIC_ASSERT(currentSlots->Contains(aSlot),
+ "Slot to de-register wasn't found?");
+ if (currentSlots->Length() == 1) {
+ MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named,
+ currentSlots->ElementAt(0) == aSlot);
+
+ InvalidateStyleAndLayoutOnSubtree(aSlot);
+
+ mSlotMap.Remove(name);
+ if (!aSlot->AssignedNodes().IsEmpty()) {
+ aSlot->ClearAssignedNodes();
+ aSlot->EnqueueSlotChangeEvent();
+ }
+
+ return;
+ }
+ if (SlotAssignment() == SlotAssignmentMode::Manual) {
+ InvalidateStyleAndLayoutOnSubtree(aSlot);
+ if (!aSlot->AssignedNodes().IsEmpty()) {
+ aSlot->ClearAssignedNodes();
+ aSlot->EnqueueSlotChangeEvent();
+ }
+ }
+
+ const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot;
+ currentSlots.RemoveElement(*aSlot);
+ if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) {
+ return;
+ }
+
+ // Move assigned nodes from removed slot to the next slot in
+ // tree order with the same name.
+ InvalidateStyleAndLayoutOnSubtree(aSlot);
+ HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0);
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
+ if (assignedNodes.IsEmpty()) {
+ return;
+ }
+
+ InvalidateStyleAndLayoutOnSubtree(replacementSlot);
+ while (!assignedNodes.IsEmpty()) {
+ nsINode* assignedNode = assignedNodes[0];
+
+ aSlot->RemoveAssignedNode(*assignedNode->AsContent());
+ replacementSlot->AppendAssignedNode(*assignedNode->AsContent());
+ }
+
+ aSlot->EnqueueSlotChangeEvent();
+ replacementSlot->EnqueueSlotChangeEvent();
+}
+
+// FIXME(emilio): There's a bit of code duplication between this and the
+// equivalent ServoStyleSet methods, it'd be nice to not duplicate it...
+void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ MOZ_ASSERT(mServoStyles);
+ if (mStyleRuleMap) {
+ mStyleRuleMap->RuleAdded(aSheet, aRule);
+ }
+
+ if (aRule.IsIncompleteImportRule()) {
+ return;
+ }
+
+ Servo_AuthorStyles_ForceDirty(mServoStyles.get());
+ ApplicableRulesChanged();
+}
+
+void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ MOZ_ASSERT(mServoStyles);
+ if (mStyleRuleMap) {
+ mStyleRuleMap->RuleRemoved(aSheet, aRule);
+ }
+ Servo_AuthorStyles_ForceDirty(mServoStyles.get());
+ ApplicableRulesChanged();
+}
+
+void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*,
+ StyleRuleChangeKind) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ MOZ_ASSERT(mServoStyles);
+ Servo_AuthorStyles_ForceDirty(mServoStyles.get());
+ ApplicableRulesChanged();
+}
+
+void ShadowRoot::ImportRuleLoaded(CSSImportRule&, StyleSheet& aSheet) {
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aSheet);
+ }
+
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ // TODO(emilio): Could handle it like a regular sheet insertion, I guess, to
+ // avoid throwing away the whole style data.
+ Servo_AuthorStyles_ForceDirty(mServoStyles.get());
+ ApplicableRulesChanged();
+}
+
+// We don't need to do anything else than forwarding to the document if
+// necessary.
+void ShadowRoot::SheetCloned(StyleSheet& aSheet) {
+ if (Document* doc = GetComposedDoc()) {
+ if (PresShell* shell = doc->GetPresShell()) {
+ shell->StyleSet()->SheetCloned(aSheet);
+ }
+ }
+}
+
+void ShadowRoot::ApplicableRulesChanged() {
+ if (Document* doc = GetComposedDoc()) {
+ doc->RecordShadowStyleChange(*this);
+ }
+}
+
+void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
+ DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
+ if (aSheet.IsApplicable()) {
+ InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets);
+ }
+}
+
+StyleSheet* FirstApplicableAdoptedStyleSheet(
+ const nsTArray<RefPtr<StyleSheet>>& aList) {
+ size_t i = 0;
+ for (StyleSheet* sheet : aList) {
+ // Deal with duplicate sheets by only considering the last one.
+ if (sheet->IsApplicable() && MOZ_LIKELY(aList.LastIndexOf(sheet) == i)) {
+ return sheet;
+ }
+ i++;
+ }
+ return nullptr;
+}
+
+void ShadowRoot::InsertSheetIntoAuthorData(
+ size_t aIndex, StyleSheet& aSheet,
+ const nsTArray<RefPtr<StyleSheet>>& aList) {
+ MOZ_ASSERT(aSheet.IsApplicable());
+ MOZ_ASSERT(aList[aIndex] == &aSheet);
+ MOZ_ASSERT(aList.LastIndexOf(&aSheet) == aIndex);
+ MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets);
+
+ if (!mServoStyles) {
+ mServoStyles.reset(Servo_AuthorStyles_Create());
+ }
+
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aSheet);
+ }
+
+ auto changedOnExit =
+ mozilla::MakeScopeExit([&] { ApplicableRulesChanged(); });
+
+ for (size_t i = aIndex + 1; i < aList.Length(); ++i) {
+ StyleSheet* beforeSheet = aList.ElementAt(i);
+ if (!beforeSheet->IsApplicable()) {
+ continue;
+ }
+
+ // If this is a duplicate adopted stylesheet that is not in the right
+ // position (the last one) then we skip over it. Otherwise we're done.
+ if (&aList == &mAdoptedStyleSheets &&
+ MOZ_UNLIKELY(aList.LastIndexOf(beforeSheet) != i)) {
+ continue;
+ }
+
+ Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
+ beforeSheet);
+ return;
+ }
+
+ if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) {
+ Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
+ return;
+ }
+
+ if (auto* before = FirstApplicableAdoptedStyleSheet(mAdoptedStyleSheets)) {
+ Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
+ before);
+ } else {
+ Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
+ }
+}
+
+// FIXME(emilio): This needs to notify document observers and such,
+// presumably.
+void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
+ auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets;
+ int32_t index = sheetList.LastIndexOf(&aSheet);
+ if (index < 0) {
+ // NOTE(emilio): @import sheets are handled in the relevant RuleAdded
+ // notification, which only notifies after the sheet is loaded.
+ //
+ // This setup causes weirdness in other places, we may want to fix this in
+ // bug 1465031.
+ MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(),
+ "It'd better be an @import sheet");
+ return;
+ }
+ if (aSheet.IsApplicable()) {
+ InsertSheetIntoAuthorData(size_t(index), aSheet, sheetList);
+ } else {
+ MOZ_ASSERT(mServoStyles);
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetRemoved(aSheet);
+ }
+ Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
+ ApplicableRulesChanged();
+ }
+}
+
+void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) {
+ MOZ_ASSERT(aSheet.IsApplicable());
+ MOZ_ASSERT(mServoStyles);
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetRemoved(aSheet);
+ }
+ Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
+ ApplicableRulesChanged();
+}
+
+void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) {
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
+ if (entry) {
+ entry->AddIdElement(aElement);
+ }
+}
+
+void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
+ IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
+ if (entry) {
+ entry->RemoveIdElement(aElement);
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+ }
+}
+
+void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ aVisitor.mCanHandle = true;
+ aVisitor.mRootOfClosedTree = IsClosed();
+ // Inform that we're about to exit the current scope.
+ aVisitor.mRelatedTargetRetargetedInCurrentScope = false;
+
+ // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
+ if (!aVisitor.mEvent->mFlags.mComposed) {
+ nsCOMPtr<nsIContent> originalTarget =
+ nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
+ if (originalTarget && originalTarget->GetContainingShadow() == this) {
+ // If we do stop propagation, we still want to propagate
+ // the event to chrome (nsPIDOMWindow::GetParentTarget()).
+ // The load event is special in that we don't ever propagate it
+ // to chrome.
+ nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
+ EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
+ ? win->GetParentTarget()
+ : nullptr;
+
+ aVisitor.SetParentTarget(parentTarget, true);
+ return;
+ }
+ }
+
+ nsIContent* shadowHost = GetHost();
+ aVisitor.SetParentTarget(shadowHost, false);
+
+ nsCOMPtr<nsIContent> content(
+ nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget));
+ if (content && content->GetContainingShadow() == this) {
+ aVisitor.mEventTargetAtParent = shadowHost;
+ }
+}
+
+void ShadowRoot::GetSlotNameFor(const nsIContent& aContent,
+ nsAString& aName) const {
+ if (mIsDetailsShadowTree) {
+ const auto* summary = HTMLSummaryElement::FromNode(aContent);
+ if (summary && summary->IsMainSummary()) {
+ aName.AssignLiteral("internal-main-summary");
+ }
+ // Otherwise use the default slot.
+ return;
+ }
+
+ // Note that if slot attribute is missing, assign it to the first default
+ // slot, if exists.
+ if (const Element* element = Element::FromNode(aContent)) {
+ element->GetAttr(nsGkAtoms::slot, aName);
+ }
+}
+
+ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
+ nsIContent& aContent) {
+ HTMLSlotElement* slot = nullptr;
+
+ if (SlotAssignment() == SlotAssignmentMode::Manual) {
+ slot = aContent.GetManualSlotAssignment();
+ if (!slot || slot->GetContainingShadow() != this) {
+ return {};
+ }
+ } else {
+ nsAutoString slotName;
+ GetSlotNameFor(aContent, slotName);
+
+ SlotArray* slots = mSlotMap.Get(slotName);
+ if (!slots) {
+ return {};
+ }
+ slot = (*slots)->ElementAt(0);
+ }
+
+ MOZ_ASSERT(slot);
+
+ if (SlotAssignment() == SlotAssignmentMode::Named) {
+ if (!aContent.GetNextSibling()) {
+ // aContent is the last child, no need to loop through the assigned nodes,
+ // we're necessarily the last one.
+ //
+ // This prevents multiple appends into the host from getting quadratic.
+ return {slot, Nothing()};
+ }
+ } else {
+ // For manual slots, if aContent is the last element, we return Nothing
+ // because we just need to append the element to the assigned nodes. No need
+ // to return an index.
+ if (slot->ManuallyAssignedNodes().SafeLastElement(nullptr) == &aContent) {
+ return {slot, Nothing()};
+ }
+ }
+
+ // Find the appropriate position in the assigned node list for the newly
+ // assigned content.
+ if (SlotAssignment() == SlotAssignmentMode::Manual) {
+ const nsTArray<nsINode*>& manuallyAssignedNodes =
+ slot->ManuallyAssignedNodes();
+ auto index = manuallyAssignedNodes.IndexOf(&aContent);
+ if (index != manuallyAssignedNodes.NoIndex) {
+ return {slot, Some(index)};
+ }
+ } else {
+ const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ nsIContent* currentContent = GetHost()->GetFirstChild();
+ for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
+ // Seek through the host's explicit children until the
+ // assigned content is found.
+ while (currentContent && currentContent != assignedNodes[i]) {
+ if (currentContent == &aContent) {
+ return {slot, Some(i)};
+ }
+ currentContent = currentContent->GetNextSibling();
+ }
+ }
+ }
+
+ return {slot, Nothing()};
+}
+
+void ShadowRoot::MaybeReassignContent(nsIContent& aElementOrText) {
+ MOZ_ASSERT(aElementOrText.GetParent() == GetHost());
+ MOZ_ASSERT(aElementOrText.IsElement() || aElementOrText.IsText());
+ HTMLSlotElement* oldSlot = aElementOrText.GetAssignedSlot();
+
+ SlotInsertionPoint assignment = SlotInsertionPointFor(aElementOrText);
+
+ if (assignment.mSlot == oldSlot) {
+ // Nothing to do here.
+ return;
+ }
+
+ // The layout invalidation piece for Manual slots is handled in
+ // HTMLSlotElement::Assign
+ if (aElementOrText.IsElement() &&
+ SlotAssignment() == SlotAssignmentMode::Named) {
+ if (Document* doc = GetComposedDoc()) {
+ if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
+ presShell->SlotAssignmentWillChange(*aElementOrText.AsElement(),
+ oldSlot, assignment.mSlot);
+ }
+ }
+ }
+
+ if (oldSlot) {
+ if (SlotAssignment() == SlotAssignmentMode::Named) {
+ oldSlot->RemoveAssignedNode(aElementOrText);
+ // Don't need to EnqueueSlotChangeEvent for Manual slots because it
+ // needs to be done in tree order, so
+ // HTMLSlotElement::Assign will handle it explicitly.
+ oldSlot->EnqueueSlotChangeEvent();
+ } else {
+ oldSlot->RemoveManuallyAssignedNode(aElementOrText);
+ }
+ }
+
+ if (assignment.mSlot) {
+ if (assignment.mIndex) {
+ assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElementOrText);
+ } else {
+ assignment.mSlot->AppendAssignedNode(aElementOrText);
+ }
+ // Similar as above, HTMLSlotElement::Assign handles enqueuing
+ // slotchange event.
+ if (SlotAssignment() == SlotAssignmentMode::Named) {
+ assignment.mSlot->EnqueueSlotChangeEvent();
+ }
+ }
+}
+
+void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) {
+ MOZ_ASSERT(mIsDetailsShadowTree);
+ if (aReason == SummaryChangeReason::Insertion) {
+ // We've inserted a summary element, may need to remove the existing one.
+ SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns);
+ MOZ_RELEASE_ASSERT(array && (*array)->Length() == 1);
+ HTMLSlotElement* slot = (*array)->ElementAt(0);
+ auto* summary = HTMLSummaryElement::FromNodeOrNull(
+ slot->AssignedNodes().SafeElementAt(0));
+ if (summary) {
+ MaybeReassignContent(*summary);
+ }
+ } else if (MOZ_LIKELY(GetHost())) {
+ // We need to null-check GetHost() in case we're unlinking already.
+ auto* details = HTMLDetailsElement::FromNode(Host());
+ MOZ_DIAGNOSTIC_ASSERT(details);
+ // We've removed a summary element, we may need to assign the new one.
+ if (HTMLSummaryElement* newMainSummary = details->GetFirstSummary()) {
+ MaybeReassignContent(*newMainSummary);
+ }
+ }
+}
+
+Element* ShadowRoot::GetActiveElement() {
+ return GetRetargetedFocusedElement();
+}
+
+nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode,
+ nsINode& aNode, bool aDeep,
+ mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(IsUAWidget());
+
+ if (aParentNode.SubtreeRoot() != this) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return aParentNode.AppendChild(*node, rv);
+}
+
+nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode,
+ const nsAString& aTagName,
+ mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(IsUAWidget());
+
+ if (aParentNode.SubtreeRoot() != this) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ // This option is not exposed to UA Widgets
+ ElementCreationOptionsOrString options;
+
+ RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return aParentNode.AppendChild(*node, rv);
+}
+
+void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) {
+ // Need to null-check the host because we may be unlinked already.
+ MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost());
+
+ HTMLSlotElement* slot = aChild.GetAssignedSlot();
+ if (!slot) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!aChild.IsRootOfNativeAnonymousSubtree(),
+ "How did aChild end up assigned to a slot?");
+ // If the slot is going to start showing fallback content, we need to tell
+ // layout about it.
+ if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) {
+ InvalidateStyleAndLayoutOnSubtree(slot);
+ }
+
+ slot->RemoveAssignedNode(aChild);
+ slot->EnqueueSlotChangeEvent();
+
+ if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
+ MaybeReassignMainSummary(SummaryChangeReason::Deletion);
+ }
+}
+
+void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
+ MOZ_ASSERT(aChild.GetParent() == GetHost());
+ // Check to ensure that the child not an anonymous subtree root because even
+ // though its parent could be the host it may not be in the host's child
+ // list.
+ if (aChild.IsRootOfNativeAnonymousSubtree()) {
+ return;
+ }
+
+ if (!aChild.IsSlotable()) {
+ return;
+ }
+
+ if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) {
+ MaybeReassignMainSummary(SummaryChangeReason::Insertion);
+ }
+
+ SlotInsertionPoint assignment = SlotInsertionPointFor(aChild);
+ if (!assignment.mSlot) {
+ return;
+ }
+
+ // Fallback content will go away, let layout know.
+ if (assignment.mSlot->AssignedNodes().IsEmpty() &&
+ assignment.mSlot->HasChildren()) {
+ InvalidateStyleAndLayoutOnSubtree(assignment.mSlot);
+ }
+
+ if (assignment.mIndex) {
+ assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild);
+ } else {
+ assignment.mSlot->AppendAssignedNode(aChild);
+ }
+ assignment.mSlot->EnqueueSlotChangeEvent();
+}
+
+ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() {
+ if (!mStyleRuleMap) {
+ mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>();
+ }
+ mStyleRuleMap->EnsureTable(*this);
+ return *mStyleRuleMap;
+}
+
+nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
+ *aResult = nullptr;
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h
new file mode 100644
index 0000000000..d0678e3e3d
--- /dev/null
+++ b/dom/base/ShadowRoot.h
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_shadowroot_h__
+#define mozilla_dom_shadowroot_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/ShadowRootBinding.h"
+#include "mozilla/ServoBindings.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIRadioGroupContainer.h"
+#include "nsStubMutationObserver.h"
+#include "nsTHashtable.h"
+
+class nsAtom;
+class nsIContent;
+
+namespace mozilla {
+
+class EventChainPreVisitor;
+class ServoStyleRuleMap;
+
+enum class StyleRuleChangeKind : uint32_t;
+
+namespace css {
+class Rule;
+}
+
+namespace dom {
+
+class CSSImportRule;
+class Element;
+class HTMLInputElement;
+
+class ShadowRoot final : public DocumentFragment,
+ public DocumentOrShadowRoot,
+ public nsIRadioGroupContainer {
+ friend class DocumentOrShadowRoot;
+
+ public:
+ NS_IMPL_FROMNODE_HELPER(ShadowRoot, IsShadowRoot());
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRoot, DocumentFragment)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ShadowRoot(Element* aElement, ShadowRootMode aMode,
+ Element::DelegatesFocus aDelegatesFocus,
+ SlotAssignmentMode aSlotAssignment,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ void AddSizeOfExcludingThis(nsWindowSizes&, size_t* aNodeSize) const final;
+
+ // Try to reassign an element or text to a slot.
+ void MaybeReassignContent(nsIContent& aElementOrText);
+ // Called when an element is inserted as a direct child of our host. Tries to
+ // slot the child in one of our slots.
+ void MaybeSlotHostChild(nsIContent&);
+ // Called when a direct child of our host is removed. Tries to un-slot the
+ // child from the currently-assigned slot, if any.
+ void MaybeUnslotHostChild(nsIContent&);
+
+ // Shadow DOM v1
+ Element* Host() const {
+ MOZ_ASSERT(GetHost(),
+ "ShadowRoot always has a host, how did we create "
+ "this ShadowRoot?");
+ return GetHost();
+ }
+
+ ShadowRootMode Mode() const { return mMode; }
+ bool DelegatesFocus() const {
+ return mDelegatesFocus == Element::DelegatesFocus::Yes;
+ }
+ SlotAssignmentMode SlotAssignment() const { return mSlotAssignment; }
+ bool IsClosed() const { return mMode == ShadowRootMode::Closed; }
+
+ void RemoveSheetFromStyles(StyleSheet&);
+ void RuleAdded(StyleSheet&, css::Rule&);
+ void RuleRemoved(StyleSheet&, css::Rule&);
+ void RuleChanged(StyleSheet&, css::Rule*, StyleRuleChangeKind);
+ void ImportRuleLoaded(CSSImportRule&, StyleSheet&);
+ void SheetCloned(StyleSheet&);
+ void StyleSheetApplicableStateChanged(StyleSheet&);
+
+ /**
+ * Clones internal state, for example stylesheets, of aOther to 'this'.
+ */
+ void CloneInternalDataFrom(ShadowRoot* aOther);
+ void InsertSheetAt(size_t aIndex, StyleSheet&);
+
+ // Calls UnbindFromTree for each of our kids, and also flags us as no longer
+ // being connected.
+ void Unbind();
+
+ // Only intended for UA widgets / special shadow roots, or for handling
+ // failure cases when adopting (see BlastSubtreeToPieces).
+ //
+ // Forgets our shadow host and unbinds all our kids.
+ void Unattach();
+
+ // Calls BindToTree on each of our kids, and also maybe flags us as being
+ // connected.
+ nsresult Bind();
+
+ /**
+ * Explicitly invalidates the style and layout of the flattened-tree subtree
+ * rooted at the element.
+ *
+ * You need to use this whenever the flat tree is going to be shuffled in a
+ * way that layout doesn't understand via the usual ContentInserted /
+ * ContentAppended / ContentRemoved notifications. For example, if removing an
+ * element will cause a change in the flat tree such that other element will
+ * start showing up (like fallback content), this method needs to be called on
+ * an ancestor of that element.
+ *
+ * It is important that this runs _before_ actually shuffling the flat tree
+ * around, so that layout knows the actual tree that it needs to invalidate.
+ */
+ void InvalidateStyleAndLayoutOnSubtree(Element*);
+
+ private:
+ void InsertSheetIntoAuthorData(size_t aIndex, StyleSheet&,
+ const nsTArray<RefPtr<StyleSheet>>&);
+
+ void AppendStyleSheet(StyleSheet& aSheet) {
+ InsertSheetAt(SheetCount(), aSheet);
+ }
+
+ /**
+ * Represents the insertion point in a slot for a given node.
+ */
+ struct SlotInsertionPoint {
+ HTMLSlotElement* mSlot = nullptr;
+ Maybe<uint32_t> mIndex;
+
+ SlotInsertionPoint() = default;
+ SlotInsertionPoint(HTMLSlotElement* aSlot, const Maybe<uint32_t>& aIndex)
+ : mSlot(aSlot), mIndex(aIndex) {}
+ };
+
+ /**
+ * Return the assignment corresponding to the content node at this particular
+ * point in time.
+ *
+ * It's the caller's responsibility to actually call InsertAssignedNode /
+ * AppendAssignedNode in the slot as needed.
+ */
+ SlotInsertionPoint SlotInsertionPointFor(nsIContent&);
+
+ /**
+ * Returns the effective slot name for a given slottable. In most cases, this
+ * is just the value of the slot attribute, if any, or the empty string, but
+ * this also deals with the <details> shadow tree specially.
+ */
+ void GetSlotNameFor(const nsIContent&, nsAString&) const;
+
+ /**
+ * Re-assign the current main summary if it has changed.
+ *
+ * Must be called only if mIsDetailsShadowTree is true.
+ */
+ enum class SummaryChangeReason { Deletion, Insertion };
+ void MaybeReassignMainSummary(SummaryChangeReason);
+
+ public:
+ void AddSlot(HTMLSlotElement* aSlot);
+ void RemoveSlot(HTMLSlotElement* aSlot);
+ bool HasSlots() const { return !mSlotMap.IsEmpty(); };
+ HTMLSlotElement* GetDefaultSlot() const {
+ SlotArray* list = mSlotMap.Get(u""_ns);
+ return list ? (*list)->ElementAt(0) : nullptr;
+ }
+
+ void PartAdded(const Element&);
+ void PartRemoved(const Element&);
+
+ IMPL_EVENT_HANDLER(slotchange);
+
+ const nsTArray<const Element*>& Parts() const { return mParts; }
+
+ const StyleAuthorStyles* GetServoStyles() const { return mServoStyles.get(); }
+
+ StyleAuthorStyles* GetServoStyles() { return mServoStyles.get(); }
+
+ mozilla::ServoStyleRuleMap& ServoStyleRuleMap();
+
+ JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) final;
+
+ void NodeInfoChanged(Document* aOldDoc) override;
+
+ void AddToIdTable(Element* aElement, nsAtom* aId);
+ void RemoveFromIdTable(Element* aElement, nsAtom* aId);
+
+ // WebIDL methods.
+ using mozilla::dom::DocumentOrShadowRoot::GetElementById;
+
+ Element* GetActiveElement();
+
+ /**
+ * These methods allow UA Widget to insert DOM elements into the Shadow ROM
+ * without putting their DOM reflectors to content scope first.
+ * The inserted DOM will have their reflectors in the UA Widget scope.
+ */
+ nsINode* ImportNodeAndAppendChildAt(nsINode& aParentNode, nsINode& aNode,
+ bool aDeep, mozilla::ErrorResult& rv);
+
+ nsINode* CreateElementAndAppendChildAt(nsINode& aParentNode,
+ const nsAString& aTagName,
+ mozilla::ErrorResult& rv);
+
+ bool IsUAWidget() const { return HasBeenInUAWidget(); }
+
+ void SetIsUAWidget() {
+ MOZ_ASSERT(!HasChildren());
+ SetIsNativeAnonymousRoot();
+ SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
+ }
+
+ bool IsAvailableToElementInternals() const {
+ return mIsAvailableToElementInternals;
+ }
+
+ void SetAvailableToElementInternals() {
+ mIsAvailableToElementInternals = true;
+ }
+
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
+ // nsIRadioGroupContainer
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor) override {
+ return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor);
+ }
+ void SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio) override {
+ DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
+ }
+ HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) override {
+ return DocumentOrShadowRoot::GetCurrentRadioButton(aName);
+ }
+ NS_IMETHOD
+ GetNextRadioButton(const nsAString& aName, const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut) override {
+ return DocumentOrShadowRoot::GetNextRadioButton(aName, aPrevious,
+ aFocusedRadio, aRadioOut);
+ }
+ void AddToRadioGroup(const nsAString& aName,
+ HTMLInputElement* aRadio) override {
+ DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio);
+ }
+ void RemoveFromRadioGroup(const nsAString& aName,
+ HTMLInputElement* aRadio) override {
+ DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
+ }
+ uint32_t GetRequiredRadioCount(const nsAString& aName) const override {
+ return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
+ }
+ void RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded) override {
+ DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
+ }
+ bool GetValueMissingState(const nsAString& aName) const override {
+ return DocumentOrShadowRoot::GetValueMissingState(aName);
+ }
+ void SetValueMissingState(const nsAString& aName, bool aValue) override {
+ return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
+ }
+
+ protected:
+ // FIXME(emilio): This will need to become more fine-grained.
+ void ApplicableRulesChanged();
+
+ virtual ~ShadowRoot();
+
+ const ShadowRootMode mMode;
+
+ Element::DelegatesFocus mDelegatesFocus;
+
+ const SlotAssignmentMode mSlotAssignment;
+
+ // The computed data from the style sheets.
+ UniquePtr<StyleAuthorStyles> mServoStyles;
+ UniquePtr<mozilla::ServoStyleRuleMap> mStyleRuleMap;
+
+ using SlotArray = TreeOrderedArray<HTMLSlotElement>;
+ // Map from name of slot to an array of all slots in the shadow DOM with with
+ // the given name. The slots are stored as a weak pointer because the elements
+ // are in the shadow tree and should be kept alive by its parent.
+ nsClassHashtable<nsStringHashKey, SlotArray> mSlotMap;
+
+ // Unordered array of all elements that have a part attribute in this shadow
+ // tree.
+ nsTArray<const Element*> mParts;
+
+ // Whether this is the <details> internal shadow tree.
+ bool mIsDetailsShadowTree : 1;
+
+ // https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals
+ bool mIsAvailableToElementInternals : 1;
+
+ nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_shadowroot_h__
diff --git a/dom/base/SlowScriptDebug.sys.mjs b/dom/base/SlowScriptDebug.sys.mjs
new file mode 100644
index 0000000000..0402c6d38d
--- /dev/null
+++ b/dom/base/SlowScriptDebug.sys.mjs
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export function SlowScriptDebug() {}
+
+SlowScriptDebug.prototype = {
+ classDescription: "Slow script debug handler",
+ QueryInterface: ChromeUtils.generateQI(["nsISlowScriptDebug"]),
+
+ get activationHandler() {
+ return this._activationHandler;
+ },
+ set activationHandler(cb) {
+ this._activationHandler = cb;
+ },
+
+ get remoteActivationHandler() {
+ return this._remoteActivationHandler;
+ },
+ set remoteActivationHandler(cb) {
+ this._remoteActivationHandler = cb;
+ },
+};
diff --git a/dom/base/StaticRange.cpp b/dom/base/StaticRange.cpp
new file mode 100644
index 0000000000..c5dc3cc806
--- /dev/null
+++ b/dom/base/StaticRange.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/dom/StaticRangeBinding.h"
+#include "nsINode.h"
+
+namespace mozilla::dom {
+
+template already_AddRefed<StaticRange> StaticRange::Create(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<StaticRange> StaticRange::Create(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<StaticRange> StaticRange::Create(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<StaticRange> StaticRange::Create(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, ErrorResult& aRv);
+template nsresult StaticRange::SetStartAndEnd(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
+template nsresult StaticRange::SetStartAndEnd(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary);
+template nsresult StaticRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
+template nsresult StaticRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+template void StaticRange::DoSetRange(const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void StaticRange::DoSetRange(const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void StaticRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void StaticRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+
+nsTArray<RefPtr<StaticRange>>* StaticRange::sCachedRanges = nullptr;
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(StaticRange)
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
+ StaticRange, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
+ AbstractRange::MaybeCacheToReuse(*this))
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StaticRange)
+NS_INTERFACE_MAP_END_INHERITING(AbstractRange)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(StaticRange)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StaticRange, AbstractRange)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mStart)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEnd)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StaticRange, AbstractRange)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StaticRange, AbstractRange)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+// static
+already_AddRefed<StaticRange> StaticRange::Create(nsINode* aNode) {
+ MOZ_ASSERT(aNode);
+ if (!sCachedRanges || sCachedRanges->IsEmpty()) {
+ return do_AddRef(new StaticRange(aNode));
+ }
+ RefPtr<StaticRange> staticRange = sCachedRanges->PopLastElement().forget();
+ staticRange->Init(aNode);
+ return staticRange.forget();
+}
+
+// static
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<StaticRange> StaticRange::Create(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv) {
+ RefPtr<StaticRange> staticRange =
+ StaticRange::Create(aStartBoundary.Container());
+ staticRange->DoSetRange(aStartBoundary, aEndBoundary, nullptr);
+
+ return staticRange.forget();
+}
+
+StaticRange::~StaticRange() {
+ DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void StaticRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode) {
+ bool checkCommonAncestor =
+ IsInAnySelection() && (mStart.Container() != aStartBoundary.Container() ||
+ mEnd.Container() != aEndBoundary.Container());
+ mStart.CopyFrom(aStartBoundary, RangeBoundaryIsMutationObserved::No);
+ mEnd.CopyFrom(aEndBoundary, RangeBoundaryIsMutationObserved::No);
+ MOZ_ASSERT(mStart.IsSet() == mEnd.IsSet());
+ mIsPositioned = mStart.IsSet() && mEnd.IsSet();
+
+ if (checkCommonAncestor) {
+ UpdateCommonAncestorIfNecessary();
+ }
+}
+
+/* static */
+already_AddRefed<StaticRange> StaticRange::Constructor(
+ const GlobalObject& global, const StaticRangeInit& init, ErrorResult& aRv) {
+ if (init.mStartContainer->NodeType() == nsINode::DOCUMENT_TYPE_NODE ||
+ init.mStartContainer->NodeType() == nsINode::ATTRIBUTE_NODE ||
+ init.mEndContainer->NodeType() == nsINode::DOCUMENT_TYPE_NODE ||
+ init.mEndContainer->NodeType() == nsINode::ATTRIBUTE_NODE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return nullptr;
+ }
+
+ return Create(init.mStartContainer, init.mStartOffset, init.mEndContainer,
+ init.mEndOffset, aRv);
+}
+
+JSObject* StaticRange::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return StaticRange_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/StaticRange.h b/dom/base/StaticRange.h
new file mode 100644
index 0000000000..92fcb49224
--- /dev/null
+++ b/dom/base/StaticRange.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StaticRange_h
+#define mozilla_dom_StaticRange_h
+
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/dom/AbstractRange.h"
+#include "mozilla/dom/StaticRangeBinding.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class StaticRange final : public AbstractRange {
+ public:
+ StaticRange() = delete;
+ explicit StaticRange(const StaticRange& aOther) = delete;
+
+ static already_AddRefed<StaticRange> Constructor(const GlobalObject& global,
+ const StaticRangeInit& init,
+ ErrorResult& aRv);
+
+ /**
+ * The following Create() returns `nsRange` instance which is initialized
+ * only with aNode. The result is never positioned.
+ */
+ static already_AddRefed<StaticRange> Create(nsINode* aNode);
+
+ /**
+ * Create() may return `StaticRange` instance which is initialized with
+ * given range or points. If it fails initializing new range with the
+ * arguments, returns `nullptr`. `ErrorResult` is set to an error only
+ * when this returns `nullptr`. The error code indicates the reason why
+ * it couldn't initialize the instance.
+ */
+ static already_AddRefed<StaticRange> Create(
+ const AbstractRange* aAbstractRange, ErrorResult& aRv) {
+ MOZ_ASSERT(aAbstractRange);
+ return StaticRange::Create(aAbstractRange->StartRef(),
+ aAbstractRange->EndRef(), aRv);
+ }
+ static already_AddRefed<StaticRange> Create(nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ return StaticRange::Create(
+ RawRangeBoundary(aStartContainer, aStartOffset,
+ RangeBoundaryIsMutationObserved::No),
+ RawRangeBoundary(aEndContainer, aEndOffset,
+ RangeBoundaryIsMutationObserved::No),
+ aRv);
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static already_AddRefed<StaticRange> Create(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv);
+
+ /**
+ * Returns true if the range is valid.
+ *
+ * @see https://dom.spec.whatwg.org/#staticrange-valid
+ */
+ bool IsValid() const {
+ return mStart.IsSetAndValid() && mEnd.IsSetAndValid();
+ }
+
+ protected:
+ explicit StaticRange(nsINode* aNode)
+ : AbstractRange(aNode, /* aIsDynamicRange = */ false) {}
+ virtual ~StaticRange();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(StaticRange,
+ AbstractRange)
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ /**
+ * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
+ * Different from calls them separately, this does nothing if either
+ * the start point or the end point is invalid point.
+ * If the specified start point is after the end point, the range will be
+ * collapsed at the end point. Similarly, if they are in different root,
+ * the range will be collapsed at the end point.
+ */
+ nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) {
+ return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ nsresult SetStartAndEnd(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ return AbstractRange::SetStartAndEndInternal(aStartBoundary, aEndBoundary,
+ this);
+ }
+
+ protected:
+ /**
+ * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
+ * mStart and mEnd.
+ *
+ * @param aStartBoundary Computed start point. This must equals or be before
+ * aEndBoundary in the DOM tree order.
+ * @param aEndBoundary Computed end point.
+ * @param aRootNode The root node.
+ */
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ void DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode);
+
+ static nsTArray<RefPtr<StaticRange>>* sCachedRanges;
+
+ friend class AbstractRange;
+};
+
+inline StaticRange* AbstractRange::AsStaticRange() {
+ MOZ_ASSERT(IsStaticRange());
+ return static_cast<StaticRange*>(this);
+}
+inline const StaticRange* AbstractRange::AsStaticRange() const {
+ MOZ_ASSERT(IsStaticRange());
+ return static_cast<const StaticRange*>(this);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // #ifndef mozilla_dom_StaticRange_h
diff --git a/dom/base/StorageAccessPermissionRequest.cpp b/dom/base/StorageAccessPermissionRequest.cpp
new file mode 100644
index 0000000000..094cd87393
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StorageAccessPermissionRequest.h"
+#include "nsGlobalWindowInner.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include <cstdlib>
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+StorageAccessPermissionRequest::StorageAccessPermissionRequest(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aNodePrincipal,
+ const Maybe<nsCString>& aTopLevelBaseDomain, AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback)
+ : ContentPermissionRequestBase(aNodePrincipal, aWindow,
+ "dom.storage_access"_ns,
+ "storage-access"_ns),
+ mAllowCallback(std::move(aAllowCallback)),
+ mCancelCallback(std::move(aCancelCallback)),
+ mCallbackCalled(false) {
+ if (aTopLevelBaseDomain.isSome()) {
+ mOptions.AppendElement(NS_ConvertUTF8toUTF16(aTopLevelBaseDomain.value()));
+ }
+ mPermissionRequests.AppendElement(PermissionRequest(mType, mOptions));
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Cancel() {
+ if (!mCallbackCalled) {
+ mCallbackCalled = true;
+ mCancelCallback();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
+ nsTArray<PermissionChoice> choices;
+ nsresult rv = TranslateChoices(aChoices, mPermissionRequests, choices);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // There is no support to allow grants automatically from the prompting code
+ // path.
+
+ if (!mCallbackCalled) {
+ mCallbackCalled = true;
+ if (choices.Length() == 1 && choices[0].choice().EqualsLiteral("allow")) {
+ mAllowCallback();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::GetTypes(nsIArray** aTypes) {
+ return nsContentPermissionUtils::CreatePermissionArray(mType, mOptions,
+ aTypes);
+}
+
+RefPtr<StorageAccessPermissionRequest::AutoGrantDelayPromise>
+StorageAccessPermissionRequest::MaybeDelayAutomaticGrants() {
+ RefPtr<AutoGrantDelayPromise::Private> p =
+ new AutoGrantDelayPromise::Private(__func__);
+
+ unsigned simulatedDelay = CalculateSimulatedDelay();
+ if (simulatedDelay) {
+ nsCOMPtr<nsITimer> timer;
+ RefPtr<AutoGrantDelayPromise::Private> promise = p;
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer),
+ [](nsITimer* aTimer, void* aClosure) -> void {
+ auto* promise =
+ static_cast<AutoGrantDelayPromise::Private*>(aClosure);
+ promise->Resolve(true, __func__);
+ NS_RELEASE(aTimer);
+ NS_RELEASE(promise);
+ },
+ promise, simulatedDelay, nsITimer::TYPE_ONE_SHOT,
+ "DelayedAllowAutoGrantCallback");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ p->Reject(false, __func__);
+ } else {
+ // Leak the references here! We'll release them inside the callback.
+ Unused << timer.forget();
+ Unused << promise.forget();
+ }
+ } else {
+ p->Resolve(false, __func__);
+ }
+ return p;
+}
+
+already_AddRefed<StorageAccessPermissionRequest>
+StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
+ AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback) {
+ if (!aWindow) {
+ return nullptr;
+ }
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
+
+ return Create(aWindow, win->GetPrincipal(), std::move(aAllowCallback),
+ std::move(aCancelCallback));
+}
+
+already_AddRefed<StorageAccessPermissionRequest>
+StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aPrincipal,
+ AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback) {
+ return Create(aWindow, aPrincipal, Nothing(), std::move(aAllowCallback),
+ std::move(aCancelCallback));
+}
+
+already_AddRefed<StorageAccessPermissionRequest>
+StorageAccessPermissionRequest::Create(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ const Maybe<nsCString>& aTopLevelBaseDomain, AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback) {
+ if (!aWindow) {
+ return nullptr;
+ }
+
+ if (!aPrincipal) {
+ return nullptr;
+ }
+
+ RefPtr<StorageAccessPermissionRequest> request =
+ new StorageAccessPermissionRequest(
+ aWindow, aPrincipal, aTopLevelBaseDomain, std::move(aAllowCallback),
+ std::move(aCancelCallback));
+ return request.forget();
+}
+
+unsigned StorageAccessPermissionRequest::CalculateSimulatedDelay() {
+ if (!StaticPrefs::dom_storage_access_auto_grants_delayed()) {
+ return 0;
+ }
+
+ // Generate a random time value that is at least 0 and and most 3 seconds.
+ std::srand(static_cast<unsigned>(PR_Now()));
+
+ const unsigned kMin = 0;
+ const unsigned kMax = 3000;
+ const unsigned random = std::abs(std::rand());
+
+ return kMin + random % (kMax - kMin);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/StorageAccessPermissionRequest.h b/dom/base/StorageAccessPermissionRequest.h
new file mode 100644
index 0000000000..dec3054d65
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef StorageAccessPermissionRequest_h_
+#define StorageAccessPermissionRequest_h_
+
+#include "nsContentPermissionHelper.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+
+#include <functional>
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class StorageAccessPermissionRequest final
+ : public ContentPermissionRequestBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+ // nsIContentPermissionRequest
+ NS_IMETHOD Cancel(void) override;
+ NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;
+ NS_IMETHOD GetTypes(nsIArray** aTypes) override;
+
+ using AllowCallback = std::function<void()>;
+ using CancelCallback = std::function<void()>;
+
+ static already_AddRefed<StorageAccessPermissionRequest> Create(
+ nsPIDOMWindowInner* aWindow, AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback);
+
+ static already_AddRefed<StorageAccessPermissionRequest> Create(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ AllowCallback&& aAllowCallback, CancelCallback&& aCancelCallback);
+
+ // The argument aTopLevelBaseDomain is used here to optionally indicate what
+ // the top-level site of the permission requested will be. This is used in
+ // the requestStorageAccessUnderSite call because that call is not made from
+ // an embedded context. If aTopLevelBaseDomain is Nothing() the base domain
+ // of aPrincipal's Top browsing context is used.
+ static already_AddRefed<StorageAccessPermissionRequest> Create(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ const Maybe<nsCString>& aTopLevelBaseDomain,
+ AllowCallback&& aAllowCallback, CancelCallback&& aCancelCallback);
+
+ using AutoGrantDelayPromise = MozPromise<bool, bool, true>;
+ RefPtr<AutoGrantDelayPromise> MaybeDelayAutomaticGrants();
+
+ private:
+ StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aNodePrincipal,
+ const Maybe<nsCString>& aTopLevelBaseDomain,
+ AllowCallback&& aAllowCallback,
+ CancelCallback&& aCancelCallback);
+ ~StorageAccessPermissionRequest() {
+ // Invoke Cancel() to ensure we call a callback even if the request has
+ // been destroyed before the request is completed.
+ Cancel();
+ }
+
+ unsigned CalculateSimulatedDelay();
+
+ AllowCallback mAllowCallback;
+ CancelCallback mCancelCallback;
+ nsTArray<nsString> mOptions;
+ nsTArray<PermissionRequest> mPermissionRequests;
+ bool mCallbackCalled;
+};
+
+} // namespace mozilla::dom
+
+#endif // StorageAccessPermissionRequest_h_
diff --git a/dom/base/StructuredCloneBlob.cpp b/dom/base/StructuredCloneBlob.cpp
new file mode 100644
index 0000000000..9c0f764323
--- /dev/null
+++ b/dom/base/StructuredCloneBlob.cpp
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/StructuredCloneBlob.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <new>
+#include <utility>
+#include "js/StructuredClone.h"
+#include "js/Value.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/StructuredCloneHolderBinding.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "nsPrintfCString.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+StructuredCloneBlob::StructuredCloneBlob() {
+ mHolder.emplace(Holder::CloningSupported, Holder::TransferringNotSupported,
+ Holder::StructuredCloneScope::DifferentProcess);
+}
+
+StructuredCloneBlob::~StructuredCloneBlob() {
+ UnregisterWeakMemoryReporter(this);
+}
+
+/* static */
+already_AddRefed<StructuredCloneBlob> StructuredCloneBlob::Constructor(
+ GlobalObject& aGlobal, const nsACString& aName,
+ const nsACString& aAnonymizedName, JS::Handle<JS::Value> aValue,
+ JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+
+ RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create();
+
+ holder->mName = aName;
+ holder->mAnonymizedName = aAnonymizedName.IsVoid() ? aName : aAnonymizedName;
+
+ Maybe<JSAutoRealm> ar;
+ JS::Rooted<JS::Value> value(cx, aValue);
+
+ if (aTargetGlobal) {
+ // OK to unwrap if our caller (represented by cx's Realm) can do it.
+ JS::Rooted<JSObject*> targetGlobal(
+ cx, js::CheckedUnwrapDynamic(aTargetGlobal, cx));
+ if (!targetGlobal) {
+ js::ReportAccessDenied(cx);
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ ar.emplace(cx, targetGlobal);
+
+ if (!JS_WrapValue(cx, &value)) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+ } else if (value.isObject()) {
+ // OK to unwrap if our caller (represented by cx's Realm) can do it.
+ JS::Rooted<JSObject*> obj(cx,
+ js::CheckedUnwrapDynamic(&value.toObject(), cx));
+ if (!obj) {
+ js::ReportAccessDenied(cx);
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ ar.emplace(cx, obj);
+ value = JS::ObjectValue(*obj);
+ }
+
+ holder->mHolder->Write(cx, value, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return holder.forget();
+}
+
+void StructuredCloneBlob::Deserialize(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetScope,
+ bool aKeepData,
+ JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv) {
+ // OK to unwrap if our caller (represented by aCx's Realm) can do it.
+ JS::Rooted<JSObject*> scope(aCx, js::CheckedUnwrapDynamic(aTargetScope, aCx));
+ if (!scope) {
+ js::ReportAccessDenied(aCx);
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ if (!mHolder.isSome()) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ {
+ JSAutoRealm ar(aCx, scope);
+
+ mHolder->Read(xpc::NativeGlobal(scope), aCx, aResult, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ if (!aKeepData) {
+ mHolder.reset();
+ }
+
+ if (!JS_WrapValue(aCx, aResult)) {
+ aResult.set(JS::UndefinedValue());
+ aRv.NoteJSContextException(aCx);
+ }
+}
+
+/* static */
+JSObject* StructuredCloneBlob::ReadStructuredClone(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder) {
+ JS::Rooted<JSObject*> obj(aCx);
+ {
+ RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create();
+
+ if (!StructuredCloneHolder::ReadCString(aReader, holder->mName)) {
+ return nullptr;
+ }
+
+ if (!StructuredCloneHolder::ReadCString(aReader, holder->mAnonymizedName)) {
+ return nullptr;
+ }
+
+ if (!holder->mHolder->ReadStructuredCloneInternal(aCx, aReader, aHolder) ||
+ !holder->WrapObject(aCx, nullptr, &obj)) {
+ return nullptr;
+ }
+ }
+ return obj.get();
+}
+
+bool StructuredCloneBlob::Holder::ReadStructuredCloneInternal(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder) {
+ uint32_t length;
+ uint32_t version;
+ if (!JS_ReadUint32Pair(aReader, &length, &version)) {
+ return false;
+ }
+ if (length % 8 != 0) {
+ return false;
+ }
+
+ uint32_t blobOffset;
+ uint32_t blobCount;
+ if (!JS_ReadUint32Pair(aReader, &blobOffset, &blobCount)) {
+ return false;
+ }
+ if (blobCount) {
+#ifdef FUZZING
+ if (blobOffset >= aHolder->BlobImpls().Length()) {
+ return false;
+ }
+#endif
+ BlobImpls().AppendElements(&aHolder->BlobImpls()[blobOffset], blobCount);
+ }
+
+ JSStructuredCloneData data(mStructuredCloneScope);
+ while (length) {
+ size_t size;
+ char* buffer = data.AllocateBytes(length, &size);
+ if (!buffer || !JS_ReadBytes(aReader, buffer, size)) {
+ return false;
+ }
+ length -= size;
+ }
+
+ mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(
+ mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this);
+ mBuffer->adopt(std::move(data), version, &StructuredCloneHolder::sCallbacks);
+
+ return true;
+}
+
+bool StructuredCloneBlob::WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder) {
+ if (mHolder.isNothing()) {
+ return false;
+ }
+
+ if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_HOLDER, 0) ||
+ !StructuredCloneHolder::WriteCString(aWriter, mName) ||
+ !StructuredCloneHolder::WriteCString(aWriter, mAnonymizedName)) {
+ return false;
+ }
+
+ return mHolder->WriteStructuredClone(aCx, aWriter, aHolder);
+}
+
+bool StructuredCloneBlob::Holder::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder) {
+ auto& data = mBuffer->data();
+ if (!JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION) ||
+ !JS_WriteUint32Pair(aWriter, aHolder->BlobImpls().Length(),
+ BlobImpls().Length())) {
+ return false;
+ }
+
+ aHolder->BlobImpls().AppendElements(BlobImpls());
+
+ return data.ForEachDataChunk([&](const char* aData, size_t aSize) {
+ return JS_WriteBytes(aWriter, aData, aSize);
+ });
+}
+
+bool StructuredCloneBlob::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aResult) {
+ return StructuredCloneHolder_Binding::Wrap(aCx, this, aGivenProto, aResult);
+}
+
+NS_IMETHODIMP
+StructuredCloneBlob::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ size_t size = MallocSizeOf(this);
+ if (mHolder.isSome()) {
+ size += mHolder->SizeOfExcludingThis(MallocSizeOf);
+ }
+
+ aHandleReport->Callback(
+ ""_ns,
+ nsPrintfCString("explicit/dom/structured-clone-holder/%s",
+ aAnonymize ? mAnonymizedName.get() : mName.get()),
+ KIND_HEAP, UNITS_BYTES, size,
+ "Memory used by StructuredCloneHolder DOM objects."_ns, aData);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(StructuredCloneBlob, nsIMemoryReporter)
+
+} // namespace mozilla::dom
diff --git a/dom/base/StructuredCloneBlob.h b/dom/base/StructuredCloneBlob.h
new file mode 100644
index 0000000000..9d4320b000
--- /dev/null
+++ b/dom/base/StructuredCloneBlob.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StructuredCloneBlob_h
+#define mozilla_dom_StructuredCloneBlob_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupports.h"
+
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+class StructuredCloneBlob final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ static JSObject* ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder);
+ bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder);
+
+ static already_AddRefed<StructuredCloneBlob> Constructor(
+ GlobalObject& aGlobal, const nsACString& aName,
+ const nsACString& aAnonymizedName, JS::Handle<JS::Value> aValue,
+ JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv);
+
+ void Deserialize(JSContext* aCx, JS::Handle<JSObject*> aTargetScope,
+ bool aKeepData, JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv);
+
+ nsISupports* GetParentObject() const { return nullptr; }
+ JSObject* GetWrapper() const { return nullptr; }
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aResult);
+
+ protected:
+ virtual ~StructuredCloneBlob();
+
+ private:
+ explicit StructuredCloneBlob();
+
+ class Holder : public StructuredCloneHolder {
+ public:
+ using StructuredCloneHolder::StructuredCloneHolder;
+
+ bool ReadStructuredCloneInternal(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder);
+
+ bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder);
+ };
+
+ nsCString mName;
+ nsCString mAnonymizedName;
+ Maybe<Holder> mHolder;
+
+ static already_AddRefed<StructuredCloneBlob> Create() {
+ RefPtr<StructuredCloneBlob> holder = new StructuredCloneBlob();
+ RegisterWeakMemoryReporter(holder);
+ return holder.forget();
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StructuredCloneBlob_h
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
new file mode 100644
index 0000000000..4dfa2184d8
--- /dev/null
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -0,0 +1,1682 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/StructuredCloneHolder.h"
+
+#include <new>
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "js/CallArgs.h"
+#include "js/Value.h"
+#include "js/WasmModule.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Blob.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ClonedErrorHolder.h"
+#include "mozilla/dom/ClonedErrorHolderBinding.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileList.h"
+#include "mozilla/dom/FileListBinding.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/FormDataBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/dom/OffscreenCanvasBinding.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneBlob.h"
+#include "mozilla/dom/StructuredCloneHolderBinding.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/TransformStream.h"
+#include "mozilla/dom/TransformStreamBinding.h"
+#include "mozilla/dom/VideoFrame.h"
+#include "mozilla/dom/VideoFrameBinding.h"
+#include "mozilla/dom/WebIDLSerializable.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/WritableStreamBinding.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/2D.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsID.h"
+#include "nsIEventTarget.h"
+#include "nsIFile.h"
+#include "nsIGlobalObject.h"
+#include "nsIInputStream.h"
+#include "nsIPrincipal.h"
+#include "nsISupports.h"
+#include "nsJSPrincipals.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "xpcpublic.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+namespace {
+
+JSObject* StructuredCloneCallbacksRead(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aIndex,
+ void* aClosure) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomReadHandler(aCx, aReader, aCloneDataPolicy, aTag,
+ aIndex);
+}
+
+bool StructuredCloneCallbacksWrite(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired,
+ void* aClosure) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomWriteHandler(aCx, aWriter, aObj,
+ aSameProcessScopeRequired);
+}
+
+bool StructuredCloneCallbacksReadTransfer(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+ void* aContent, uint64_t aExtraData, void* aClosure,
+ JS::MutableHandle<JSObject*> aReturnObject) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomReadTransferHandler(aCx, aReader, aTag, aContent,
+ aExtraData, aReturnObject);
+}
+
+bool StructuredCloneCallbacksWriteTransfer(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, void* aClosure,
+ // Output:
+ uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent,
+ uint64_t* aExtraData) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomWriteTransferHandler(aCx, aObj, aTag, aOwnership,
+ aContent, aExtraData);
+}
+
+void StructuredCloneCallbacksFreeTransfer(uint32_t aTag,
+ JS::TransferableOwnership aOwnership,
+ void* aContent, uint64_t aExtraData,
+ void* aClosure) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomFreeTransferHandler(aTag, aOwnership, aContent,
+ aExtraData);
+}
+
+bool StructuredCloneCallbacksCanTransfer(JSContext* aCx,
+ JS::Handle<JSObject*> aObject,
+ bool* aSameProcessScopeRequired,
+ void* aClosure) {
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomCanTransferHandler(aCx, aObject,
+ aSameProcessScopeRequired);
+}
+
+bool StructuredCloneCallbacksSharedArrayBuffer(JSContext* cx, bool aReceiving,
+ void* aClosure) {
+ if (!StaticPrefs::dom_workers_serialized_sab_access()) {
+ return true;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (workerPrivate) {
+ workerPrivate->SetExecutionManager(
+ JSExecutionManager::GetSABSerializationManager());
+ } else if (NS_IsMainThread()) {
+ nsIGlobalObject* global = GetCurrentGlobal();
+
+ nsPIDOMWindowInner* innerWindow = nullptr;
+ if (global) {
+ innerWindow = global->AsInnerWindow();
+ }
+
+ DocGroup* docGroup = nullptr;
+ if (innerWindow) {
+ docGroup = innerWindow->GetDocGroup();
+ }
+
+ if (docGroup) {
+ docGroup->SetExecutionManager(
+ JSExecutionManager::GetSABSerializationManager());
+ }
+ }
+ return true;
+}
+
+void StructuredCloneCallbacksError(JSContext* aCx, uint32_t aErrorId,
+ void* aClosure, const char* aErrorMessage) {
+ NS_WARNING("Failed to clone data.");
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->SetErrorMessage(aErrorMessage);
+}
+
+void AssertTagValues() {
+ static_assert(SCTAG_DOM_IMAGEDATA == 0xffff8007 &&
+ SCTAG_DOM_DOMPOINT == 0xffff8008 &&
+ SCTAG_DOM_DOMPOINTREADONLY == 0xffff8009 &&
+ SCTAG_DOM_CRYPTOKEY == 0xffff800a &&
+ SCTAG_DOM_NULL_PRINCIPAL == 0xffff800b &&
+ SCTAG_DOM_SYSTEM_PRINCIPAL == 0xffff800c &&
+ SCTAG_DOM_CONTENT_PRINCIPAL == 0xffff800d &&
+ SCTAG_DOM_DOMQUAD == 0xffff800e &&
+ SCTAG_DOM_RTCCERTIFICATE == 0xffff800f &&
+ SCTAG_DOM_DOMRECT == 0xffff8010 &&
+ SCTAG_DOM_DOMRECTREADONLY == 0xffff8011 &&
+ SCTAG_DOM_EXPANDED_PRINCIPAL == 0xffff8012 &&
+ SCTAG_DOM_DOMMATRIX == 0xffff8013 &&
+ SCTAG_DOM_URLSEARCHPARAMS == 0xffff8014 &&
+ SCTAG_DOM_DOMMATRIXREADONLY == 0xffff8015 &&
+ SCTAG_DOM_STRUCTUREDCLONETESTER == 0xffff8018 &&
+ SCTAG_DOM_FILESYSTEMHANDLE == 0xffff8019 &&
+ SCTAG_DOM_FILESYSTEMFILEHANDLE == 0xffff801a &&
+ SCTAG_DOM_FILESYSTEMDIRECTORYHANDLE == 0xffff801b,
+ "Something has changed the sctag values. This is wrong!");
+}
+
+} // anonymous namespace
+
+const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = {
+ StructuredCloneCallbacksRead,
+ StructuredCloneCallbacksWrite,
+ StructuredCloneCallbacksError,
+ StructuredCloneCallbacksReadTransfer,
+ StructuredCloneCallbacksWriteTransfer,
+ StructuredCloneCallbacksFreeTransfer,
+ StructuredCloneCallbacksCanTransfer,
+ StructuredCloneCallbacksSharedArrayBuffer,
+};
+
+// StructuredCloneHolderBase class
+
+StructuredCloneHolderBase::StructuredCloneHolderBase(
+ StructuredCloneScope aScope)
+ : mStructuredCloneScope(aScope)
+#ifdef DEBUG
+ ,
+ mClearCalled(false)
+#endif
+{
+}
+
+StructuredCloneHolderBase::~StructuredCloneHolderBase() {
+#ifdef DEBUG
+ MOZ_ASSERT(mClearCalled);
+#endif
+}
+
+void StructuredCloneHolderBase::Clear() {
+#ifdef DEBUG
+ mClearCalled = true;
+#endif
+
+ mBuffer = nullptr;
+}
+
+bool StructuredCloneHolderBase::Write(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ return Write(aCx, aValue, JS::UndefinedHandleValue, JS::CloneDataPolicy());
+}
+
+bool StructuredCloneHolderBase::Write(
+ JSContext* aCx, JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ const JS::CloneDataPolicy& aCloneDataPolicy) {
+ MOZ_ASSERT(!mBuffer, "Double Write is not allowed");
+ MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear.");
+
+ mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(
+ mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this);
+
+ if (!mBuffer->write(aCx, aValue, aTransfer, aCloneDataPolicy,
+ &StructuredCloneHolder::sCallbacks, this)) {
+ mBuffer = nullptr;
+ return false;
+ }
+
+ // Let's update our scope to the final one. The new one could be more
+ // restrictive of the current one.
+ MOZ_ASSERT(mStructuredCloneScope >= mBuffer->scope());
+ mStructuredCloneScope = mBuffer->scope();
+ return true;
+}
+
+bool StructuredCloneHolderBase::Read(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue) {
+ return Read(aCx, aValue, JS::CloneDataPolicy());
+}
+
+bool StructuredCloneHolderBase::Read(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy) {
+ MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed.");
+ MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear.");
+
+ bool ok = mBuffer->read(aCx, aValue, aCloneDataPolicy,
+ &StructuredCloneHolder::sCallbacks, this);
+ return ok;
+}
+
+bool StructuredCloneHolderBase::CustomReadTransferHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+ void* aContent, uint64_t aExtraData,
+ JS::MutableHandle<JSObject*> aReturnObject) {
+ MOZ_CRASH("Nothing to read.");
+ return false;
+}
+
+bool StructuredCloneHolderBase::CustomWriteTransferHandler(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership, void** aContent,
+ uint64_t* aExtraData) {
+ // No transfers are supported by default.
+ return false;
+}
+
+void StructuredCloneHolderBase::CustomFreeTransferHandler(
+ uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent,
+ uint64_t aExtraData) {
+ MOZ_CRASH("Nothing to free.");
+}
+
+bool StructuredCloneHolderBase::CustomCanTransferHandler(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) {
+ return false;
+}
+
+// StructuredCloneHolder class
+
+StructuredCloneHolder::StructuredCloneHolder(
+ CloningSupport aSupportsCloning, TransferringSupport aSupportsTransferring,
+ StructuredCloneScope aScope)
+ : StructuredCloneHolderBase(aScope),
+ mSupportsCloning(aSupportsCloning == CloningSupported),
+ mSupportsTransferring(aSupportsTransferring == TransferringSupported),
+ mGlobal(nullptr)
+#ifdef DEBUG
+ ,
+ mCreationEventTarget(GetCurrentSerialEventTarget())
+#endif
+{
+}
+
+StructuredCloneHolder::~StructuredCloneHolder() {
+ Clear();
+ MOZ_ASSERT(mTransferredPorts.IsEmpty());
+}
+
+void StructuredCloneHolder::Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ Write(aCx, aValue, JS::UndefinedHandleValue, JS::CloneDataPolicy(), aRv);
+}
+
+void StructuredCloneHolder::Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ const JS::CloneDataPolicy& aCloneDataPolicy,
+ ErrorResult& aRv) {
+ if (!StructuredCloneHolderBase::Write(aCx, aValue, aTransfer,
+ aCloneDataPolicy)) {
+ aRv.ThrowDataCloneError(mErrorMessage);
+ return;
+ }
+}
+
+void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ return Read(aGlobal, aCx, aValue, JS::CloneDataPolicy(), aRv);
+}
+
+void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ mozilla::AutoRestore<nsIGlobalObject*> guard(mGlobal);
+ auto errorMessageGuard = MakeScopeExit([&] { mErrorMessage.Truncate(); });
+ mGlobal = aGlobal;
+
+ if (!StructuredCloneHolderBase::Read(aCx, aValue, aCloneDataPolicy)) {
+ JS_ClearPendingException(aCx);
+ aRv.ThrowDataCloneError(mErrorMessage);
+ return;
+ }
+
+ // If we are tranferring something, we cannot call 'Read()' more than once.
+ if (mSupportsTransferring) {
+ mBlobImplArray.Clear();
+ mWasmModuleArray.Clear();
+ mClonedSurfaces.Clear();
+ mInputStreamArray.Clear();
+ mVideoFrames.Clear();
+ Clear();
+ }
+}
+
+void StructuredCloneHolder::ReadFromBuffer(
+ nsIGlobalObject* aGlobal, JSContext* aCx, JSStructuredCloneData& aBuffer,
+ JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) {
+ ReadFromBuffer(aGlobal, aCx, aBuffer, JS_STRUCTURED_CLONE_VERSION, aValue,
+ aCloneDataPolicy, aRv);
+}
+
+void StructuredCloneHolder::ReadFromBuffer(
+ nsIGlobalObject* aGlobal, JSContext* aCx, JSStructuredCloneData& aBuffer,
+ uint32_t aAlgorithmVersion, JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) {
+ MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write().");
+
+ mozilla::AutoRestore<nsIGlobalObject*> guard(mGlobal);
+ auto errorMessageGuard = MakeScopeExit([&] { mErrorMessage.Truncate(); });
+ mGlobal = aGlobal;
+
+ if (!JS_ReadStructuredClone(aCx, aBuffer, aAlgorithmVersion, CloneScope(),
+ aValue, aCloneDataPolicy, &sCallbacks, this)) {
+ JS_ClearPendingException(aCx);
+ aRv.ThrowDataCloneError(mErrorMessage);
+ return;
+ }
+}
+
+/* static */
+JSObject* StructuredCloneHolder::ReadFullySerializableObjects(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag) {
+ AssertTagValues();
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (!global) {
+ return nullptr;
+ }
+
+ WebIDLDeserializer deserializer =
+ LookupDeserializer(StructuredCloneTags(aTag));
+ if (deserializer) {
+ return deserializer(aCx, global, aReader);
+ }
+
+ if (aTag == SCTAG_DOM_NULL_PRINCIPAL || aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
+ aTag == SCTAG_DOM_CONTENT_PRINCIPAL ||
+ aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) {
+ JSPrincipals* prin;
+ if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) {
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> result(aCx);
+ {
+ // nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of
+ // the casting between JSPrincipals* and nsIPrincipal* we can't use
+ // getter_AddRefs above and have to already_AddRefed here.
+ nsCOMPtr<nsIPrincipal> principal =
+ already_AddRefed<nsIPrincipal>(nsJSPrincipals::get(prin));
+
+ nsresult rv = nsContentUtils::WrapNative(
+ aCx, principal, &NS_GET_IID(nsIPrincipal), &result);
+ if (NS_FAILED(rv)) {
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return nullptr;
+ }
+ }
+ return result.toObjectOrNull();
+ }
+
+ // Don't know what this is. Bail.
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return nullptr;
+}
+
+/* static */
+bool StructuredCloneHolder::WriteFullySerializableObjects(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj) {
+ AssertTagValues();
+
+ // Window and Location are not serializable, so it's OK to just do a static
+ // unwrap here.
+ JS::Rooted<JSObject*> obj(aCx, js::CheckedUnwrapStatic(aObj));
+ if (!obj) {
+ return xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ }
+
+ const DOMJSClass* domClass = GetDOMClass(obj);
+ if (domClass && domClass->mSerializer) {
+ return domClass->mSerializer(aCx, aWriter, obj);
+ }
+
+ if (NS_IsMainThread() && xpc::IsReflector(obj, aCx)) {
+ // We only care about principals, so ReflectorToISupportsStatic is fine.
+ nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(obj);
+ nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
+ if (principal) {
+ auto nsjsprincipals = nsJSPrincipals::get(principal);
+ return nsjsprincipals->write(aCx, aWriter);
+ }
+ }
+
+ // Don't know what this is
+ ErrorResult rv;
+ const char* className = JS::GetClass(obj)->name;
+ rv.ThrowDataCloneError(nsDependentCString(className) +
+ " object could not be cloned."_ns);
+ MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
+ return false;
+}
+
+template <typename char_type>
+static bool ReadTString(JSStructuredCloneReader* aReader,
+ nsTString<char_type>& aString) {
+ uint32_t length, zero;
+ if (!JS_ReadUint32Pair(aReader, &length, &zero)) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!aString.SetLength(length, fallible))) {
+ return false;
+ }
+ size_t charSize = sizeof(char_type);
+ return JS_ReadBytes(aReader, (void*)aString.BeginWriting(),
+ length * charSize);
+}
+
+template <typename char_type>
+static bool WriteTString(JSStructuredCloneWriter* aWriter,
+ const nsTSubstring<char_type>& aString) {
+ size_t charSize = sizeof(char_type);
+ return JS_WriteUint32Pair(aWriter, aString.Length(), 0) &&
+ JS_WriteBytes(aWriter, aString.BeginReading(),
+ aString.Length() * charSize);
+}
+
+/* static */
+bool StructuredCloneHolder::ReadString(JSStructuredCloneReader* aReader,
+ nsString& aString) {
+ return ReadTString(aReader, aString);
+}
+
+/* static */
+bool StructuredCloneHolder::WriteString(JSStructuredCloneWriter* aWriter,
+ const nsAString& aString) {
+ return WriteTString(aWriter, aString);
+}
+
+/* static */
+bool StructuredCloneHolder::ReadCString(JSStructuredCloneReader* aReader,
+ nsCString& aString) {
+ return ReadTString(aReader, aString);
+}
+
+/* static */
+bool StructuredCloneHolder::WriteCString(JSStructuredCloneWriter* aWriter,
+ const nsACString& aString) {
+ return WriteTString(aWriter, aString);
+}
+
+namespace {
+
+JSObject* ReadBlob(JSContext* aCx, uint32_t aIndex,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aHolder);
+#ifdef FUZZING
+ if (aIndex >= aHolder->BlobImpls().Length()) {
+ return nullptr;
+ }
+#endif
+ MOZ_ASSERT(aIndex < aHolder->BlobImpls().Length());
+ JS::Rooted<JS::Value> val(aCx);
+ {
+ // RefPtr<File> and RefPtr<BlobImpl> need to go out of scope before
+ // toObject() is called because the static analysis thinks releasing XPCOM
+ // objects can GC (because in some cases it can!), and a return statement
+ // with a JSObject* type means that JSObject* is on the stack as a raw
+ // pointer while destructors are running.
+ RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[aIndex];
+
+ RefPtr<Blob> blob = Blob::Create(aHolder->GlobalDuringRead(), blobImpl);
+ if (NS_WARN_IF(!blob)) {
+ return nullptr;
+ }
+
+ if (!ToJSValue(aCx, blob, &val)) {
+ return nullptr;
+ }
+ }
+
+ return &val.toObject();
+}
+
+bool WriteBlob(JSStructuredCloneWriter* aWriter, Blob* aBlob,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aBlob);
+ MOZ_ASSERT(aHolder);
+
+ RefPtr<BlobImpl> blobImpl = aBlob->Impl();
+
+ // We store the position of the blobImpl in the array as index.
+ if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
+ aHolder->BlobImpls().Length())) {
+ aHolder->BlobImpls().AppendElement(blobImpl);
+ return true;
+ }
+
+ return false;
+}
+
+// A directory is serialized as:
+// - pair of ints: SCTAG_DOM_DIRECTORY, path length
+// - path as string
+bool WriteDirectory(JSStructuredCloneWriter* aWriter, Directory* aDirectory) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aDirectory);
+
+ nsAutoString path;
+ aDirectory->GetFullRealPath(path);
+
+ size_t charSize = sizeof(nsString::char_type);
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) &&
+ JS_WriteBytes(aWriter, path.get(), path.Length() * charSize);
+}
+
+already_AddRefed<Directory> ReadDirectoryInternal(
+ JSStructuredCloneReader* aReader, uint32_t aPathLength,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aReader);
+ MOZ_ASSERT(aHolder);
+
+ nsAutoString path;
+ if (NS_WARN_IF(!path.SetLength(aPathLength, fallible))) {
+ return nullptr;
+ }
+ size_t charSize = sizeof(nsString::char_type);
+ if (!JS_ReadBytes(aReader, (void*)path.BeginWriting(),
+ aPathLength * charSize)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ RefPtr<Directory> directory =
+ Directory::Create(aHolder->GlobalDuringRead(), file);
+ return directory.forget();
+}
+
+JSObject* ReadDirectory(JSContext* aCx, JSStructuredCloneReader* aReader,
+ uint32_t aPathLength, StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aReader);
+ MOZ_ASSERT(aHolder);
+
+ // RefPtr<Directory> needs to go out of scope before toObject() is
+ // called because the static analysis thinks dereferencing XPCOM objects
+ // can GC (because in some cases it can!), and a return statement with a
+ // JSObject* type means that JSObject* is on the stack as a raw pointer
+ // while destructors are running.
+ JS::Rooted<JS::Value> val(aCx);
+ {
+ RefPtr<Directory> directory =
+ ReadDirectoryInternal(aReader, aPathLength, aHolder);
+ if (!directory) {
+ return nullptr;
+ }
+
+ if (!ToJSValue(aCx, directory, &val)) {
+ return nullptr;
+ }
+ }
+
+ return &val.toObject();
+}
+
+// Read the WriteFileList for the format.
+JSObject* ReadFileList(JSContext* aCx, JSStructuredCloneReader* aReader,
+ uint32_t aCount, StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aReader);
+
+ JS::Rooted<JS::Value> val(aCx);
+ {
+ RefPtr<FileList> fileList = new FileList(aHolder->GlobalDuringRead());
+
+ uint32_t zero, index;
+ // |index| is the index of the first blobImpl.
+ if (!JS_ReadUint32Pair(aReader, &zero, &index) || zero != 0) {
+ return nullptr;
+ }
+
+ // |aCount| is the number of BlobImpls to use from the |index|.
+ for (uint32_t i = 0; i < aCount; ++i) {
+ uint32_t pos = index + i;
+#ifdef FUZZING
+ if (pos >= aHolder->BlobImpls().Length()) {
+ return nullptr;
+ }
+#endif
+ MOZ_ASSERT(pos < aHolder->BlobImpls().Length());
+
+ RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[pos];
+ MOZ_ASSERT(blobImpl->IsFile());
+
+ RefPtr<File> file = File::Create(aHolder->GlobalDuringRead(), blobImpl);
+ if (NS_WARN_IF(!file)) {
+ return nullptr;
+ }
+
+ if (!fileList->Append(file)) {
+ return nullptr;
+ }
+ }
+
+ if (!ToJSValue(aCx, fileList, &val)) {
+ return nullptr;
+ }
+ }
+
+ return &val.toObject();
+}
+
+// The format of the FileList serialization is:
+// - pair of ints: SCTAG_DOM_FILELIST, Length of the FileList
+// - pair of ints: 0, The offset of the BlobImpl array
+bool WriteFileList(JSStructuredCloneWriter* aWriter, FileList* aFileList,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aFileList);
+ MOZ_ASSERT(aHolder);
+
+ // A FileList is serialized writing the X number of elements and the offset
+ // from mBlobImplArray. The Read will take X elements from mBlobImplArray
+ // starting from the offset.
+ if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, aFileList->Length()) ||
+ !JS_WriteUint32Pair(aWriter, 0, aHolder->BlobImpls().Length())) {
+ return false;
+ }
+
+ nsTArray<RefPtr<BlobImpl>> blobImpls;
+
+ for (uint32_t i = 0; i < aFileList->Length(); ++i) {
+ RefPtr<BlobImpl> blobImpl = aFileList->Item(i)->Impl();
+ blobImpls.AppendElement(blobImpl);
+ }
+
+ aHolder->BlobImpls().AppendElements(blobImpls);
+ return true;
+}
+
+// Read the WriteFormData for the format.
+JSObject* ReadFormData(JSContext* aCx, JSStructuredCloneReader* aReader,
+ uint32_t aCount, StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aReader);
+ MOZ_ASSERT(aHolder);
+
+ // See the serialization of the FormData for the format.
+ JS::Rooted<JS::Value> val(aCx);
+ {
+ RefPtr<FormData> formData = new FormData(aHolder->GlobalDuringRead());
+
+ Optional<nsAString> thirdArg;
+ for (uint32_t i = 0; i < aCount; ++i) {
+ nsAutoString name;
+ if (!StructuredCloneHolder::ReadString(aReader, name)) {
+ return nullptr;
+ }
+
+ uint32_t tag, indexOrLengthOfString;
+ if (!JS_ReadUint32Pair(aReader, &tag, &indexOrLengthOfString)) {
+ return nullptr;
+ }
+
+ if (tag == SCTAG_DOM_BLOB) {
+#ifdef FUZZING
+ if (indexOrLengthOfString >= aHolder->BlobImpls().Length()) {
+ return nullptr;
+ }
+#endif
+ MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length());
+
+ RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[indexOrLengthOfString];
+
+ RefPtr<Blob> blob = Blob::Create(aHolder->GlobalDuringRead(), blobImpl);
+ if (NS_WARN_IF(!blob)) {
+ return nullptr;
+ }
+
+ ErrorResult rv;
+ formData->Append(name, *blob, thirdArg, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ } else if (tag == SCTAG_DOM_DIRECTORY) {
+ RefPtr<Directory> directory =
+ ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder);
+ if (!directory) {
+ return nullptr;
+ }
+
+ formData->Append(name, directory);
+
+ } else {
+ if (NS_WARN_IF(tag != 0)) {
+ return nullptr;
+ }
+
+ nsAutoString value;
+ if (NS_WARN_IF(!value.SetLength(indexOrLengthOfString, fallible))) {
+ return nullptr;
+ }
+ size_t charSize = sizeof(nsString::char_type);
+ if (!JS_ReadBytes(aReader, (void*)value.BeginWriting(),
+ indexOrLengthOfString * charSize)) {
+ return nullptr;
+ }
+
+ ErrorResult rv;
+ formData->Append(name, value, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+ }
+ }
+
+ if (!ToJSValue(aCx, formData, &val)) {
+ return nullptr;
+ }
+ }
+
+ return &val.toObject();
+}
+
+// The format of the FormData serialization is:
+// - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements
+// - for each Element element:
+// - name string
+// - if it's a blob:
+// - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array
+// mBlobImplArray.
+// - if it's a directory (See WriteDirectory):
+// - pair of ints: SCTAG_DOM_DIRECTORY, path length
+// - path as string
+// - else:
+// - pair of ints: 0, string length
+// - value string
+bool WriteFormData(JSStructuredCloneWriter* aWriter, FormData* aFormData,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aFormData);
+ MOZ_ASSERT(aHolder);
+
+ if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FORMDATA, aFormData->Length())) {
+ return false;
+ }
+
+ class MOZ_STACK_CLASS Closure final {
+ JSStructuredCloneWriter* mWriter;
+ StructuredCloneHolder* mHolder;
+
+ public:
+ Closure(JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder)
+ : mWriter(aWriter), mHolder(aHolder) {}
+
+ static bool Write(const nsString& aName,
+ const OwningBlobOrDirectoryOrUSVString& aValue,
+ void* aClosure) {
+ Closure* closure = static_cast<Closure*>(aClosure);
+ if (!StructuredCloneHolder::WriteString(closure->mWriter, aName)) {
+ return false;
+ }
+
+ if (aValue.IsBlob()) {
+ if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB,
+ closure->mHolder->BlobImpls().Length())) {
+ return false;
+ }
+
+ RefPtr<BlobImpl> blobImpl = aValue.GetAsBlob()->Impl();
+
+ closure->mHolder->BlobImpls().AppendElement(blobImpl);
+ return true;
+ }
+
+ if (aValue.IsDirectory()) {
+ Directory* directory = aValue.GetAsDirectory();
+ return WriteDirectory(closure->mWriter, directory);
+ }
+
+ size_t charSize = sizeof(nsString::char_type);
+ if (!JS_WriteUint32Pair(closure->mWriter, 0,
+ aValue.GetAsUSVString().Length()) ||
+ !JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(),
+ aValue.GetAsUSVString().Length() * charSize)) {
+ return false;
+ }
+
+ return true;
+ }
+ };
+ Closure closure(aWriter, aHolder);
+ return aFormData->ForEach(Closure::Write, &closure);
+}
+
+JSObject* ReadWasmModule(JSContext* aCx, uint32_t aIndex,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(aHolder->CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::SameProcess);
+#ifdef FUZZING
+ if (aIndex >= aHolder->WasmModules().Length()) {
+ return nullptr;
+ }
+#endif
+ MOZ_ASSERT(aIndex < aHolder->WasmModules().Length());
+
+ return aHolder->WasmModules()[aIndex]->createObject(aCx);
+}
+
+bool WriteWasmModule(JSStructuredCloneWriter* aWriter,
+ JS::WasmModule* aWasmModule,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aWasmModule);
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(aHolder->CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::SameProcess);
+
+ // We store the position of the wasmModule in the array as index.
+ if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM_MODULE,
+ aHolder->WasmModules().Length())) {
+ aHolder->WasmModules().AppendElement(aWasmModule);
+ return true;
+ }
+
+ return false;
+}
+
+JSObject* ReadInputStream(JSContext* aCx, uint32_t aIndex,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aHolder);
+#ifdef FUZZING
+ if (aIndex >= aHolder->InputStreams().Length()) {
+ return nullptr;
+ }
+#endif
+ MOZ_ASSERT(aIndex < aHolder->InputStreams().Length());
+ JS::Rooted<JS::Value> result(aCx);
+ {
+ nsCOMPtr<nsIInputStream> inputStream = aHolder->InputStreams()[aIndex];
+
+ nsresult rv = nsContentUtils::WrapNative(
+ aCx, inputStream, &NS_GET_IID(nsIInputStream), &result);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ }
+
+ return &result.toObject();
+}
+
+bool WriteInputStream(JSStructuredCloneWriter* aWriter,
+ nsIInputStream* aInputStream,
+ StructuredCloneHolder* aHolder) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aHolder);
+
+ // We store the position of the inputStream in the array as index.
+ if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_INPUTSTREAM,
+ aHolder->InputStreams().Length())) {
+ aHolder->InputStreams().AppendElement(aInputStream);
+ return true;
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+JSObject* StructuredCloneHolder::CustomReadHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
+ uint32_t aIndex) {
+ MOZ_ASSERT(mSupportsCloning);
+
+ if (aTag == SCTAG_DOM_BLOB) {
+ return ReadBlob(aCx, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_DIRECTORY) {
+ return ReadDirectory(aCx, aReader, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_FILELIST) {
+ return ReadFileList(aCx, aReader, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_FORMDATA) {
+ return ReadFormData(aCx, aReader, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_IMAGEBITMAP &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ // Get the current global object.
+ // This can be null.
+ JS::Rooted<JSObject*> result(aCx);
+ {
+ // aIndex is the index of the cloned image.
+ result = ImageBitmap::ReadStructuredClone(aCx, aReader, mGlobal,
+ GetSurfaces(), aIndex);
+ }
+ return result;
+ }
+
+ if (aTag == SCTAG_DOM_STRUCTURED_CLONE_HOLDER) {
+ return StructuredCloneBlob::ReadStructuredClone(aCx, aReader, this);
+ }
+
+ if (aTag == SCTAG_DOM_WASM_MODULE &&
+ CloneScope() == StructuredCloneScope::SameProcess &&
+ aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) {
+ return ReadWasmModule(aCx, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_INPUTSTREAM) {
+ return ReadInputStream(aCx, aIndex, this);
+ }
+
+ if (aTag == SCTAG_DOM_BROWSING_CONTEXT) {
+ return BrowsingContext::ReadStructuredClone(aCx, aReader, this);
+ }
+
+ if (aTag == SCTAG_DOM_CLONED_ERROR_OBJECT) {
+ return ClonedErrorHolder::ReadStructuredClone(aCx, aReader, this);
+ }
+
+ if (StaticPrefs::dom_media_webcodecs_enabled() &&
+ aTag == SCTAG_DOM_VIDEOFRAME &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ JS::Rooted<JSObject*> global(aCx, mGlobal->GetGlobalJSObject());
+ if (VideoFrame_Binding::ConstructorEnabled(aCx, global)) {
+ return VideoFrame::ReadStructuredClone(aCx, mGlobal, aReader,
+ VideoFrames()[aIndex]);
+ }
+ }
+
+ return ReadFullySerializableObjects(aCx, aReader, aTag);
+}
+
+bool StructuredCloneHolder::CustomWriteHandler(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj, bool* aSameProcessScopeRequired) {
+ if (!mSupportsCloning) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ // See if this is a File/Blob object.
+ {
+ Blob* blob = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
+ return WriteBlob(aWriter, blob, this);
+ }
+ }
+
+ // See if this is a Directory object.
+ {
+ Directory* directory = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, &obj, directory))) {
+ return WriteDirectory(aWriter, directory);
+ }
+ }
+
+ // See if this is a FileList object.
+ {
+ FileList* fileList = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, &obj, fileList))) {
+ return WriteFileList(aWriter, fileList, this);
+ }
+ }
+
+ // See if this is a FormData object.
+ {
+ FormData* formData = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, &obj, formData))) {
+ return WriteFormData(aWriter, formData, this);
+ }
+ }
+
+ // See if this is an ImageBitmap object.
+ {
+ ImageBitmap* imageBitmap = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+
+ if (CloneScope() == StructuredCloneScope::SameProcess) {
+ ErrorResult rv;
+ ImageBitmap::WriteStructuredClone(aWriter, GetSurfaces(), imageBitmap,
+ rv);
+ return !rv.MaybeSetPendingException(aCx);
+ }
+ return false;
+ }
+ }
+
+ // See if this is a StructuredCloneBlob object.
+ {
+ StructuredCloneBlob* holder = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, &obj, holder))) {
+ return holder->WriteStructuredClone(aCx, aWriter, this);
+ }
+ }
+
+ // See if this is a BrowsingContext object.
+ {
+ BrowsingContext* holder = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(BrowsingContext, &obj, holder))) {
+ return holder->WriteStructuredClone(aCx, aWriter, this);
+ }
+ }
+
+ // See if this is a ClonedErrorHolder object.
+ {
+ ClonedErrorHolder* holder = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(ClonedErrorHolder, &obj, holder))) {
+ return holder->WriteStructuredClone(aCx, aWriter, this);
+ }
+ }
+
+ // See if this is a WasmModule.
+ if (JS::IsWasmModuleObject(obj)) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ if (CloneScope() == StructuredCloneScope::SameProcess) {
+ RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj);
+ MOZ_ASSERT(module);
+
+ return WriteWasmModule(aWriter, module, this);
+ }
+ return false;
+ }
+
+ // See if this is a VideoFrame object.
+ if (StaticPrefs::dom_media_webcodecs_enabled()) {
+ VideoFrame* videoFrame = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(VideoFrame, &obj, videoFrame))) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess
+ ? videoFrame->WriteStructuredClone(aWriter, this)
+ : false;
+ }
+ }
+
+ {
+ // We only care about streams, so ReflectorToISupportsStatic is fine.
+ nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(aObj);
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(base);
+ if (inputStream) {
+ return WriteInputStream(aWriter, inputStream, this);
+ }
+ }
+
+ return WriteFullySerializableObjects(aCx, aWriter, aObj);
+}
+
+already_AddRefed<MessagePort> StructuredCloneHolder::ReceiveMessagePort(
+ uint64_t aIndex) {
+ if (NS_WARN_IF(aIndex >= mPortIdentifiers.Length())) {
+ return nullptr;
+ }
+ UniqueMessagePortId portId(mPortIdentifiers[aIndex]);
+
+ ErrorResult rv;
+ RefPtr<MessagePort> port = MessagePort::Create(mGlobal, portId, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ return port.forget();
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
+StructuredCloneHolder::CustomReadTransferHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+ void* aContent, uint64_t aExtraData,
+ JS::MutableHandle<JSObject*> aReturnObject) {
+ MOZ_ASSERT(mSupportsTransferring);
+
+ if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+#ifdef FUZZING
+ if (aExtraData >= mPortIdentifiers.Length()) {
+ return false;
+ }
+#endif
+ RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
+ if (!port) {
+ return false;
+ }
+ mTransferredPorts.AppendElement(port);
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, port, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ aReturnObject.set(&value.toObject());
+ return true;
+ }
+
+ if (aTag == SCTAG_DOM_CANVAS &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ MOZ_ASSERT(aContent);
+ OffscreenCanvasCloneData* data =
+ static_cast<OffscreenCanvasCloneData*>(aContent);
+ RefPtr<OffscreenCanvas> canvas =
+ OffscreenCanvas::CreateFromCloneData(mGlobal, data);
+ delete data;
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, canvas, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ aReturnObject.set(&value.toObject());
+ return true;
+ }
+
+ if (aTag == SCTAG_DOM_IMAGEBITMAP &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ MOZ_ASSERT(aContent);
+ ImageBitmapCloneData* data = static_cast<ImageBitmapCloneData*>(aContent);
+ RefPtr<ImageBitmap> bitmap =
+ ImageBitmap::CreateFromCloneData(mGlobal, data);
+ delete data;
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, bitmap, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ aReturnObject.set(&value.toObject());
+ return true;
+ }
+
+ if (aTag == SCTAG_DOM_READABLESTREAM) {
+#ifdef FUZZING
+ if (aExtraData >= mPortIdentifiers.Length()) {
+ return false;
+ }
+#endif
+ RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
+ if (!port) {
+ return false;
+ }
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+ return ReadableStream::ReceiveTransfer(aCx, global, *port, aReturnObject);
+ }
+
+ if (aTag == SCTAG_DOM_WRITABLESTREAM) {
+#ifdef FUZZING
+ if (aExtraData >= mPortIdentifiers.Length()) {
+ return false;
+ }
+#endif
+ RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
+ if (!port) {
+ return false;
+ }
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+ return WritableStream::ReceiveTransfer(aCx, global, *port, aReturnObject);
+ }
+
+ if (aTag == SCTAG_DOM_TRANSFORMSTREAM) {
+#ifdef FUZZING
+ if (aExtraData + 1 >= mPortIdentifiers.Length()) {
+ return false;
+ }
+#endif
+ RefPtr<MessagePort> port1 = ReceiveMessagePort(aExtraData);
+ RefPtr<MessagePort> port2 = ReceiveMessagePort(aExtraData + 1);
+ if (!port1 || !port2) {
+ return false;
+ }
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+ return TransformStream::ReceiveTransfer(aCx, global, *port1, *port2,
+ aReturnObject);
+ }
+
+ if (StaticPrefs::dom_media_webcodecs_enabled() &&
+ aTag == SCTAG_DOM_VIDEOFRAME &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ MOZ_ASSERT(aContent);
+
+ JS::Rooted<JSObject*> globalObj(aCx, mGlobal->GetGlobalJSObject());
+ // aContent will be released in CustomFreeTransferHandler.
+ if (!VideoFrame_Binding::ConstructorEnabled(aCx, globalObj)) {
+ return false;
+ }
+
+ VideoFrame::TransferredData* data =
+ static_cast<VideoFrame::TransferredData*>(aContent);
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+ RefPtr<VideoFrame> frame = VideoFrame::FromTransferred(global.get(), data);
+ // aContent will be released in CustomFreeTransferHandler if frame is null.
+ if (!frame) {
+ return false;
+ }
+ delete data;
+ aContent = nullptr;
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, frame, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+ aReturnObject.set(&value.toObject());
+ return true;
+ }
+
+ return false;
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
+StructuredCloneHolder::CustomWriteTransferHandler(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership, void** aContent,
+ uint64_t* aExtraData) {
+ if (!mSupportsTransferring) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ {
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port);
+ if (NS_SUCCEEDED(rv)) {
+ if (!port->CanBeCloned()) {
+ return false;
+ }
+
+ UniqueMessagePortId identifier;
+ port->CloneAndDisentangle(identifier);
+
+ // We use aExtraData to store the index of this new port identifier.
+ *aExtraData = mPortIdentifiers.Length();
+ mPortIdentifiers.AppendElement(identifier.release());
+
+ *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
+ *aContent = nullptr;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+
+ return true;
+ }
+
+ if (CloneScope() == StructuredCloneScope::SameProcess) {
+ OffscreenCanvas* canvas = nullptr;
+ rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(canvas);
+
+ if (!canvas->MayNeuter()) {
+ return false;
+ }
+
+ *aExtraData = 0;
+ *aTag = SCTAG_DOM_CANVAS;
+ *aContent = canvas->ToCloneData();
+ MOZ_ASSERT(*aContent);
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ canvas->SetNeutered();
+
+ return true;
+ }
+
+ ImageBitmap* bitmap = nullptr;
+ rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(bitmap);
+ MOZ_ASSERT(!bitmap->IsWriteOnly());
+
+ *aExtraData = 0;
+ *aTag = SCTAG_DOM_IMAGEBITMAP;
+
+ UniquePtr<ImageBitmapCloneData> clonedBitmap = bitmap->ToCloneData();
+ if (!clonedBitmap) {
+ return false;
+ }
+
+ *aContent = clonedBitmap.release();
+ MOZ_ASSERT(*aContent);
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+
+ bitmap->Close();
+
+ return true;
+ }
+
+ if (StaticPrefs::dom_media_webcodecs_enabled()) {
+ VideoFrame* videoFrame = nullptr;
+ rv = UNWRAP_OBJECT(VideoFrame, &obj, videoFrame);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(videoFrame);
+
+ *aExtraData = 0;
+ *aTag = SCTAG_DOM_VIDEOFRAME;
+ *aContent = nullptr;
+
+ UniquePtr<VideoFrame::TransferredData> data = videoFrame->Transfer();
+ if (!data) {
+ return false;
+ }
+ *aContent = data.release();
+ MOZ_ASSERT(*aContent);
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ return true;
+ }
+ }
+ }
+
+ {
+ RefPtr<ReadableStream> stream;
+ rv = UNWRAP_OBJECT(ReadableStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(stream);
+
+ *aTag = SCTAG_DOM_READABLESTREAM;
+ *aContent = nullptr;
+
+ UniqueMessagePortId id;
+ if (!stream->Transfer(aCx, id)) {
+ return false;
+ }
+ *aExtraData = mPortIdentifiers.Length();
+ mPortIdentifiers.AppendElement(id.release());
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ return true;
+ }
+ }
+
+ {
+ RefPtr<WritableStream> stream;
+ rv = UNWRAP_OBJECT(WritableStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(stream);
+
+ *aTag = SCTAG_DOM_WRITABLESTREAM;
+ *aContent = nullptr;
+
+ UniqueMessagePortId id;
+ if (!stream->Transfer(aCx, id)) {
+ return false;
+ }
+ *aExtraData = mPortIdentifiers.Length();
+ mPortIdentifiers.AppendElement(id.release());
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ return true;
+ }
+ }
+
+ {
+ RefPtr<TransformStream> stream;
+ rv = UNWRAP_OBJECT(TransformStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(stream);
+
+ *aTag = SCTAG_DOM_TRANSFORMSTREAM;
+ *aContent = nullptr;
+
+ UniqueMessagePortId id1;
+ UniqueMessagePortId id2;
+ if (!stream->Transfer(aCx, id1, id2)) {
+ return false;
+ }
+ *aExtraData = mPortIdentifiers.Length();
+ mPortIdentifiers.AppendElement(id1.release());
+ mPortIdentifiers.AppendElement(id2.release());
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void StructuredCloneHolder::CustomFreeTransferHandler(
+ uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent,
+ uint64_t aExtraData) {
+ MOZ_ASSERT(mSupportsTransferring);
+
+ if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+ MOZ_ASSERT(!aContent);
+#ifdef FUZZING
+ if (aExtraData >= mPortIdentifiers.Length()) {
+ return;
+ }
+#endif
+ MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_CANVAS &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ MOZ_ASSERT(aContent);
+ OffscreenCanvasCloneData* data =
+ static_cast<OffscreenCanvasCloneData*>(aContent);
+ delete data;
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_IMAGEBITMAP &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ MOZ_ASSERT(aContent);
+ ImageBitmapCloneData* data = static_cast<ImageBitmapCloneData*>(aContent);
+ delete data;
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_READABLESTREAM || aTag == SCTAG_DOM_WRITABLESTREAM) {
+ MOZ_ASSERT(!aContent);
+#ifdef FUZZING
+ if (aExtraData >= mPortIdentifiers.Length()) {
+ return;
+ }
+#endif
+ MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_TRANSFORMSTREAM) {
+ MOZ_ASSERT(!aContent);
+#ifdef FUZZING
+ if (aExtraData + 1 >= mPortIdentifiers.Length()) {
+ return;
+ }
+#endif
+ MOZ_ASSERT(aExtraData + 1 < mPortIdentifiers.Length());
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData + 1]);
+ return;
+ }
+
+ if (StaticPrefs::dom_media_webcodecs_enabled() &&
+ aTag == SCTAG_DOM_VIDEOFRAME &&
+ CloneScope() == StructuredCloneScope::SameProcess) {
+ if (aContent) {
+ VideoFrame::TransferredData* data =
+ static_cast<VideoFrame::TransferredData*>(aContent);
+ delete data;
+ }
+ return;
+ }
+}
+
+bool StructuredCloneHolder::CustomCanTransferHandler(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) {
+ if (!mSupportsTransferring) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ {
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port);
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+ }
+
+ {
+ OffscreenCanvas* canvas = nullptr;
+ nsresult rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas);
+ if (NS_SUCCEEDED(rv)) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess;
+ }
+ }
+
+ {
+ ImageBitmap* bitmap = nullptr;
+ nsresult rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap);
+ if (NS_SUCCEEDED(rv)) {
+ if (bitmap->IsWriteOnly()) {
+ return false;
+ }
+
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess;
+ }
+ }
+
+ {
+ ReadableStream* stream = nullptr;
+ nsresult rv = UNWRAP_OBJECT(ReadableStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ // https://streams.spec.whatwg.org/#ref-for-transfer-steps
+ // Step 1: If ! IsReadableStreamLocked(value) is true, throw a
+ // "DataCloneError" DOMException.
+ return !stream->Locked();
+ }
+ }
+
+ {
+ WritableStream* stream = nullptr;
+ nsresult rv = UNWRAP_OBJECT(WritableStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ // https://streams.spec.whatwg.org/#ref-for-transfer-stepsâ‘ 
+ // Step 1: If ! IsWritableStreamLocked(value) is true, throw a
+ // "DataCloneError" DOMException.
+ return !stream->Locked();
+ }
+ }
+
+ {
+ TransformStream* stream = nullptr;
+ nsresult rv = UNWRAP_OBJECT(TransformStream, &obj, stream);
+ if (NS_SUCCEEDED(rv)) {
+ // https://streams.spec.whatwg.org/#ref-for-transfer-stepsâ‘¡
+ // Step 3 + 4: If ! Is{Readable,Writable}StreamLocked(value) is true,
+ // throw a "DataCloneError" DOMException.
+ return !stream->Readable()->Locked() && !stream->Writable()->Locked();
+ }
+ }
+
+ if (StaticPrefs::dom_media_webcodecs_enabled()) {
+ VideoFrame* videoframe = nullptr;
+ nsresult rv = UNWRAP_OBJECT(VideoFrame, &obj, videoframe);
+ if (NS_SUCCEEDED(rv)) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess;
+ }
+ }
+
+ return false;
+}
+
+bool StructuredCloneHolder::TakeTransferredPortsAsSequence(
+ Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts) {
+ nsTArray<RefPtr<MessagePort>> ports = TakeTransferredPorts();
+
+ aPorts.Clear();
+ for (uint32_t i = 0, len = ports.Length(); i < len; ++i) {
+ if (!aPorts.AppendElement(ports[i].forget(), fallible)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void StructuredCloneHolder::SameProcessScopeRequired(
+ bool* aSameProcessScopeRequired) {
+ MOZ_ASSERT(aSameProcessScopeRequired);
+ if (mStructuredCloneScope == StructuredCloneScope::UnknownDestination) {
+ mStructuredCloneScope = StructuredCloneScope::SameProcess;
+ *aSameProcessScopeRequired = true;
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h
new file mode 100644
index 0000000000..eac0023fdb
--- /dev/null
+++ b/dom/base/StructuredCloneHolder.h
@@ -0,0 +1,394 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StructuredCloneHolder_h
+#define mozilla_dom_StructuredCloneHolder_h
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsIGlobalObject;
+class nsIInputStream;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace JS {
+class Value;
+struct WasmModule;
+} // namespace JS
+
+namespace mozilla {
+class ErrorResult;
+template <class T>
+class OwningNonNull;
+
+namespace layers {
+class Image;
+}
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+namespace dom {
+
+class BlobImpl;
+class MessagePort;
+class MessagePortIdentifier;
+template <typename T>
+class Sequence;
+
+class StructuredCloneHolderBase {
+ public:
+ typedef JS::StructuredCloneScope StructuredCloneScope;
+
+ StructuredCloneHolderBase(
+ StructuredCloneScope aScope = StructuredCloneScope::SameProcess);
+ virtual ~StructuredCloneHolderBase();
+
+ // Note, it is unsafe to std::move() a StructuredCloneHolderBase since a raw
+ // this pointer is passed to mBuffer as a callback closure. That must
+ // be fixed if you want to implement a move constructor here.
+ StructuredCloneHolderBase(StructuredCloneHolderBase&& aOther) = delete;
+
+ // These methods should be implemented in order to clone data.
+ // Read more documentation in js/public/StructuredClone.h.
+
+ virtual JSObject* CustomReadHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
+ uint32_t aIndex) = 0;
+
+ virtual bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) = 0;
+
+ // This method has to be called when this object is not needed anymore.
+ // It will free memory and the buffer. This has to be called because
+ // otherwise the buffer will be freed in the DTOR of this class and at that
+ // point we cannot use the overridden methods.
+ void Clear();
+
+ // If these 3 methods are not implement, transfering objects will not be
+ // allowed. Otherwise only arrayBuffers will be transferred.
+
+ virtual bool CustomReadTransferHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+ void* aContent, uint64_t aExtraData,
+ JS::MutableHandle<JSObject*> aReturnObject);
+
+ virtual bool CustomWriteTransferHandler(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ // Output:
+ uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership,
+ void** aContent,
+ uint64_t* aExtraData);
+
+ virtual void CustomFreeTransferHandler(uint32_t aTag,
+ JS::TransferableOwnership aOwnership,
+ void* aContent, uint64_t aExtraData);
+
+ virtual bool CustomCanTransferHandler(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired);
+
+ // These methods are what you should use to read/write data.
+
+ // Execute the serialization of aValue using the Structured Clone Algorithm.
+ // The data can read back using Read().
+ bool Write(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ // Like Write() but it supports the transferring of objects and handling
+ // of cloning policy.
+ bool Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ const JS::CloneDataPolicy& aCloneDataPolicy);
+
+ // If Write() has been called, this method retrieves data and stores it into
+ // aValue.
+ bool Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue);
+
+ // Like Read() but it supports handling of clone policy.
+ bool Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy);
+
+ bool HasData() const { return !!mBuffer; }
+
+ JSStructuredCloneData& BufferData() const {
+ MOZ_ASSERT(mBuffer, "Write() has never been called.");
+ return mBuffer->data();
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ size_t size = 0;
+ if (HasData()) {
+ size += mBuffer->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ return size;
+ }
+
+ void SetErrorMessage(const char* aErrorMessage) {
+ mErrorMessage.Assign(aErrorMessage);
+ }
+
+ protected:
+ UniquePtr<JSAutoStructuredCloneBuffer> mBuffer;
+
+ StructuredCloneScope mStructuredCloneScope;
+
+ // Error message when a data clone error is about to throw. It's held while
+ // the error callback is fired and it will be throw with a data clone error
+ // later.
+ nsCString mErrorMessage;
+
+#ifdef DEBUG
+ bool mClearCalled;
+#endif
+};
+
+class BlobImpl;
+class MessagePort;
+class MessagePortIdentifier;
+struct VideoFrameSerializedData;
+
+class StructuredCloneHolder : public StructuredCloneHolderBase {
+ public:
+ enum CloningSupport { CloningSupported, CloningNotSupported };
+
+ enum TransferringSupport { TransferringSupported, TransferringNotSupported };
+
+ // If cloning is supported, this object will clone objects such as Blobs,
+ // FileList, ImageData, etc.
+ // If transferring is supported, we will transfer MessagePorts and in the
+ // future other transferrable objects.
+ // The StructuredCloneScope is useful to know where the cloned/transferred
+ // data can be read and written. Additional checks about the nature of the
+ // objects will be done based on this scope value because not all the
+ // objects can be sent between threads or processes.
+ explicit StructuredCloneHolder(CloningSupport aSupportsCloning,
+ TransferringSupport aSupportsTransferring,
+ StructuredCloneScope aStructuredCloneScope);
+ virtual ~StructuredCloneHolder();
+
+ StructuredCloneHolder(StructuredCloneHolder&& aOther) = delete;
+
+ // Normally you should just use Write() and Read().
+
+ virtual void Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv);
+
+ virtual void Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ const JS::CloneDataPolicy& aCloneDataPolicy,
+ ErrorResult& aRv);
+
+ void Read(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue, ErrorResult& aRv);
+
+ void Read(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv);
+
+ // Call this method to know if this object is keeping some DOM object alive.
+ bool HasClonedDOMObjects() const {
+ return !mBlobImplArray.IsEmpty() || !mWasmModuleArray.IsEmpty() ||
+ !mClonedSurfaces.IsEmpty() || !mInputStreamArray.IsEmpty() ||
+ !mVideoFrames.IsEmpty();
+ }
+
+ nsTArray<RefPtr<BlobImpl>>& BlobImpls() {
+ MOZ_ASSERT(mSupportsCloning,
+ "Blobs cannot be taken/set if cloning is not supported.");
+ return mBlobImplArray;
+ }
+
+ nsTArray<RefPtr<JS::WasmModule>>& WasmModules() {
+ MOZ_ASSERT(mSupportsCloning,
+ "WasmModules cannot be taken/set if cloning is not supported.");
+ return mWasmModuleArray;
+ }
+
+ nsTArray<nsCOMPtr<nsIInputStream>>& InputStreams() {
+ MOZ_ASSERT(mSupportsCloning,
+ "InputStreams cannot be taken/set if cloning is not supported.");
+ return mInputStreamArray;
+ }
+
+ // This method returns the final scope. If the final scope is unknown,
+ // DifferentProcess is returned because it's the most restrictive one.
+ StructuredCloneScope CloneScope() const {
+ if (mStructuredCloneScope == StructuredCloneScope::UnknownDestination) {
+ return StructuredCloneScope::DifferentProcess;
+ }
+ return mStructuredCloneScope;
+ }
+
+ // The global object is set internally just during the Read(). This method
+ // can be used by read functions to retrieve it.
+ nsIGlobalObject* GlobalDuringRead() const { return mGlobal; }
+
+ // This must be called if the transferring has ports generated by Read().
+ // MessagePorts are not thread-safe and they must be retrieved in the thread
+ // where they are created.
+ nsTArray<RefPtr<MessagePort>>&& TakeTransferredPorts() {
+ MOZ_ASSERT(mSupportsTransferring);
+ return std::move(mTransferredPorts);
+ }
+
+ // This method uses TakeTransferredPorts() to populate a sequence of
+ // MessagePorts for WebIDL binding classes.
+ bool TakeTransferredPortsAsSequence(
+ Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts);
+
+ nsTArray<MessagePortIdentifier>& PortIdentifiers() const {
+ MOZ_ASSERT(mSupportsTransferring);
+ return mPortIdentifiers;
+ }
+
+ nsTArray<RefPtr<gfx::DataSourceSurface>>& GetSurfaces() {
+ return mClonedSurfaces;
+ }
+
+ nsTArray<VideoFrameSerializedData>& VideoFrames() { return mVideoFrames; }
+
+ // Implementations of the virtual methods to allow cloning of objects which
+ // JS engine itself doesn't clone.
+
+ virtual JSObject* CustomReadHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
+ uint32_t aIndex) override;
+
+ virtual bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) override;
+
+ virtual bool CustomReadTransferHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+ void* aContent, uint64_t aExtraData,
+ JS::MutableHandle<JSObject*> aReturnObject) override;
+
+ virtual bool CustomWriteTransferHandler(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership,
+ void** aContent,
+ uint64_t* aExtraData) override;
+
+ virtual void CustomFreeTransferHandler(uint32_t aTag,
+ JS::TransferableOwnership aOwnership,
+ void* aContent,
+ uint64_t aExtraData) override;
+
+ virtual bool CustomCanTransferHandler(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) override;
+
+ // These 2 static methods are useful to read/write fully serializable objects.
+ // They can be used by custom StructuredCloneHolderBase classes to
+ // serialize objects such as ImageData, CryptoKey, RTCCertificate, etc.
+
+ static JSObject* ReadFullySerializableObjects(
+ JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag);
+
+ static bool WriteFullySerializableObjects(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj);
+
+ // Helper functions for reading and writing strings.
+ static bool ReadString(JSStructuredCloneReader* aReader, nsString& aString);
+ static bool WriteString(JSStructuredCloneWriter* aWriter,
+ const nsAString& aString);
+ static bool ReadCString(JSStructuredCloneReader* aReader, nsCString& aString);
+ static bool WriteCString(JSStructuredCloneWriter* aWriter,
+ const nsACString& aString);
+
+ static const JSStructuredCloneCallbacks sCallbacks;
+
+ protected:
+ // If you receive a buffer from IPC, you can use this method to retrieve a
+ // JS::Value. It can happen that you want to pre-populate the array of Blobs
+ // and/or the PortIdentifiers.
+ void ReadFromBuffer(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy,
+ ErrorResult& aRv);
+
+ void ReadFromBuffer(nsIGlobalObject* aGlobal, JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ uint32_t aAlgorithmVersion,
+ JS::MutableHandle<JS::Value> aValue,
+ const JS::CloneDataPolicy& aCloneDataPolicy,
+ ErrorResult& aRv);
+
+ void SameProcessScopeRequired(bool* aSameProcessScopeRequired);
+
+ already_AddRefed<MessagePort> ReceiveMessagePort(uint64_t aIndex);
+
+ bool mSupportsCloning;
+ bool mSupportsTransferring;
+
+ // SizeOfExcludingThis is inherited from StructuredCloneHolderBase. It doesn't
+ // account for objects in the following arrays because a) they're not expected
+ // to be stored in long-lived StructuredCloneHolder objects, and b) in the
+ // case of BlobImpl objects, MemoryBlobImpls have their own memory reporters,
+ // and the other types do not hold significant amounts of memory alive.
+
+ // Used for cloning blobs in the structured cloning algorithm.
+ nsTArray<RefPtr<BlobImpl>> mBlobImplArray;
+
+ // Used for cloning JS::WasmModules in the structured cloning algorithm.
+ nsTArray<RefPtr<JS::WasmModule>> mWasmModuleArray;
+
+ // Used for cloning InputStream in the structured cloning algorithm.
+ nsTArray<nsCOMPtr<nsIInputStream>> mInputStreamArray;
+
+ // This is used for sharing the backend of ImageBitmaps.
+ // The DataSourceSurface object must be thread-safely reference-counted.
+ // The DataSourceSurface object will not be written ever via any ImageBitmap
+ // instance, so no race condition will occur.
+ nsTArray<RefPtr<gfx::DataSourceSurface>> mClonedSurfaces;
+
+ // Used for cloning VideoFrame in the structured cloning algorithm.
+ nsTArray<VideoFrameSerializedData> mVideoFrames;
+
+ // This raw pointer is only set within ::Read() and is unset by the end.
+ nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal;
+
+ // This array contains the ports once we've finished the reading. It's
+ // generated from the mPortIdentifiers array.
+ nsTArray<RefPtr<MessagePort>> mTransferredPorts;
+
+ // This array contains the identifiers of the MessagePorts. Based on these we
+ // are able to reconnect the new transferred ports with the other
+ // MessageChannel ports.
+ mutable nsTArray<MessagePortIdentifier> mPortIdentifiers;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIEventTarget> mCreationEventTarget;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StructuredCloneHolder_h
diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h
new file mode 100644
index 0000000000..19007c2943
--- /dev/null
+++ b/dom/base/StructuredCloneTags.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef StructuredCloneTags_h__
+#define StructuredCloneTags_h__
+
+#include "js/StructuredClone.h"
+
+namespace mozilla::dom {
+
+// CHANGING THE ORDER/PLACEMENT OF EXISTING ENUM VALUES MAY BREAK INDEXEDDB.
+// PROCEED WITH EXTREME CAUTION.
+//
+// If you are planning to add new tags which could be used by IndexedDB,
+// consider to use empty slots. See EMPTY_SLOT_x
+enum StructuredCloneTags : uint32_t {
+ SCTAG_BASE = JS_SCTAG_USER_MIN,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_BLOB,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ // This tag is obsolete and exists only for backwards compatibility with
+ // existing IndexedDB databases.
+ SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_FILELIST,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_MUTABLEFILE,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_FILE,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_WASM_MODULE,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_IMAGEDATA,
+
+ SCTAG_DOM_DOMPOINT,
+ SCTAG_DOM_DOMPOINTREADONLY,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ // This tag is for WebCrypto keys
+ SCTAG_DOM_CRYPTOKEY,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_NULL_PRINCIPAL,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_SYSTEM_PRINCIPAL,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_CONTENT_PRINCIPAL,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMQUAD,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_RTCCERTIFICATE,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMRECT,
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMRECTREADONLY,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_EXPANDED_PRINCIPAL,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMMATRIX,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_URLSEARCHPARAMS,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMMATRIXREADONLY,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_DOMEXCEPTION,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ EMPTY_SLOT_9,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_STRUCTUREDCLONETESTER,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_FILESYSTEMHANDLE,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_FILESYSTEMFILEHANDLE,
+
+ // IMPORTANT: Don't change the order of these enum values. You could break
+ // IDB.
+ SCTAG_DOM_FILESYSTEMDIRECTORYHANDLE,
+
+ // If you are planning to add new tags which could be used by IndexedDB,
+ // consider to use an empty slot. See EMPTY_SLOT_x
+
+ // Please update the static assertions in StructuredCloneHolder.cpp and in
+ // IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
+
+ // --------------------------------------------------------------------------
+
+ // All the following tags are not written to disk and they are not used by
+ // IndexedDB directly or via
+ // StructuredCloneHolder::{Read,Write}FullySerializableObjects. In theory they
+ // can be 'less' stable.
+
+ SCTAG_DOM_IMAGEBITMAP,
+ SCTAG_DOM_MAP_MESSAGEPORT,
+ SCTAG_DOM_FORMDATA,
+
+ // This tag is for OffscreenCanvas.
+ SCTAG_DOM_CANVAS,
+
+ SCTAG_DOM_DIRECTORY,
+
+ SCTAG_DOM_INPUTSTREAM,
+
+ SCTAG_DOM_STRUCTURED_CLONE_HOLDER,
+
+ SCTAG_DOM_BROWSING_CONTEXT,
+
+ SCTAG_DOM_CLONED_ERROR_OBJECT,
+
+ SCTAG_DOM_READABLESTREAM,
+
+ SCTAG_DOM_WRITABLESTREAM,
+
+ SCTAG_DOM_TRANSFORMSTREAM,
+
+ SCTAG_DOM_VIDEOFRAME,
+
+ // IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the
+ // "less stable" tags!
+};
+
+} // namespace mozilla::dom
+
+#endif // StructuredCloneTags_h__
diff --git a/dom/base/StructuredCloneTester.cpp b/dom/base/StructuredCloneTester.cpp
new file mode 100644
index 0000000000..40ed321f69
--- /dev/null
+++ b/dom/base/StructuredCloneTester.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StructuredCloneTester.h"
+
+#include "js/StructuredClone.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/StructuredCloneTesterBinding.h"
+#include "nsIGlobalObject.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(StructuredCloneTester)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StructuredCloneTester)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StructuredCloneTester)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StructuredCloneTester)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+StructuredCloneTester::StructuredCloneTester(nsISupports* aParent,
+ const bool aSerializable,
+ const bool aDeserializable)
+ : mParent(aParent),
+ mSerializable(aSerializable),
+ mDeserializable(aDeserializable) {}
+
+/* static */
+already_AddRefed<StructuredCloneTester> StructuredCloneTester::Constructor(
+ const GlobalObject& aGlobal, const bool aSerializable,
+ const bool aDeserializable) {
+ RefPtr<StructuredCloneTester> sct = new StructuredCloneTester(
+ aGlobal.GetAsSupports(), aSerializable, aDeserializable);
+ return sct.forget();
+}
+
+bool StructuredCloneTester::Serializable() const { return mSerializable; }
+
+bool StructuredCloneTester::Deserializable() const { return mDeserializable; }
+
+/* static */
+already_AddRefed<StructuredCloneTester>
+StructuredCloneTester::ReadStructuredClone(JSContext* aCx,
+ nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint32_t serializable = 0;
+ uint32_t deserializable = 0;
+
+ if (!JS_ReadUint32Pair(aReader, &serializable, &deserializable)) {
+ return nullptr;
+ }
+
+ RefPtr<StructuredCloneTester> sct =
+ new StructuredCloneTester(aGlobal, static_cast<bool>(serializable),
+ static_cast<bool>(deserializable));
+
+ // "Fail" deserialization
+ if (!sct->Deserializable()) {
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return nullptr;
+ }
+
+ return sct.forget();
+}
+
+bool StructuredCloneTester::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ if (!Serializable()) {
+ return xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ }
+ return JS_WriteUint32Pair(aWriter, static_cast<uint32_t>(Serializable()),
+ static_cast<uint32_t>(Deserializable()));
+}
+
+nsISupports* StructuredCloneTester::GetParentObject() const { return mParent; }
+
+JSObject* StructuredCloneTester::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return StructuredCloneTester_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/StructuredCloneTester.h b/dom/base/StructuredCloneTester.h
new file mode 100644
index 0000000000..d85371d86c
--- /dev/null
+++ b/dom/base/StructuredCloneTester.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StructuredCloneTester_h
+#define mozilla_dom_StructuredCloneTester_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+class StructuredCloneTester final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(StructuredCloneTester)
+
+ static already_AddRefed<StructuredCloneTester> Constructor(
+ const GlobalObject& aGlobal, const bool aSerializable,
+ const bool aDeserializable);
+
+ bool Serializable() const;
+
+ bool Deserializable() const;
+
+ static already_AddRefed<StructuredCloneTester> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ nsISupports* GetParentObject() const;
+
+ // nsWrapperCache interface
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ StructuredCloneTester(nsISupports* aParent, const bool aSerializable,
+ const bool aDeserializable);
+ ~StructuredCloneTester() = default;
+
+ nsCOMPtr<nsISupports> mParent;
+ bool mSerializable;
+ bool mDeserializable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StructuredCloneTester_h
diff --git a/dom/base/StyleSheetList.cpp b/dom/base/StyleSheetList.cpp
new file mode 100644
index 0000000000..e1815c96f0
--- /dev/null
+++ b/dom/base/StyleSheetList.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/StyleSheetList.h"
+
+#include "mozilla/dom/StyleSheetListBinding.h"
+#include "nsINode.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(StyleSheetList)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StyleSheetList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StyleSheetList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StyleSheetList)
+
+/* virtual */
+JSObject* StyleSheetList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return StyleSheetList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void StyleSheetList::NodeWillBeDestroyed(nsINode* aNode) {
+ mDocumentOrShadowRoot = nullptr;
+}
+
+StyleSheetList::StyleSheetList(DocumentOrShadowRoot& aScope)
+ : mDocumentOrShadowRoot(&aScope) {
+ mDocumentOrShadowRoot->AsNode().AddMutationObserver(this);
+}
+
+StyleSheetList::~StyleSheetList() {
+ if (mDocumentOrShadowRoot) {
+ mDocumentOrShadowRoot->AsNode().RemoveMutationObserver(this);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/StyleSheetList.h b/dom/base/StyleSheetList.h
new file mode 100644
index 0000000000..7b097599b1
--- /dev/null
+++ b/dom/base/StyleSheetList.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StyleSheetList_h
+#define mozilla_dom_StyleSheetList_h
+
+#include "nsContentUtils.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "nsStubMutationObserver.h"
+#include "nsWrapperCache.h"
+
+class nsINode;
+
+namespace mozilla {
+class StyleSheet;
+
+namespace dom {
+
+class StyleSheetList final : public nsStubMutationObserver,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(StyleSheetList)
+
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ explicit StyleSheetList(DocumentOrShadowRoot& aScope);
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ nsINode* GetParentObject() const {
+ return mDocumentOrShadowRoot ? &mDocumentOrShadowRoot->AsNode() : nullptr;
+ }
+
+ uint32_t Length() const {
+ return mDocumentOrShadowRoot ? mDocumentOrShadowRoot->SheetCount() : 0;
+ }
+
+ StyleSheet* IndexedGetter(uint32_t aIndex, bool& aFound) const {
+ if (!mDocumentOrShadowRoot) {
+ aFound = false;
+ return nullptr;
+ }
+
+ StyleSheet* sheet = mDocumentOrShadowRoot->SheetAt(aIndex);
+ aFound = !!sheet;
+ return sheet;
+ }
+
+ StyleSheet* Item(uint32_t aIndex) const {
+ bool dummy = false;
+ return IndexedGetter(aIndex, dummy);
+ }
+
+ protected:
+ virtual ~StyleSheetList();
+
+ DocumentOrShadowRoot*
+ mDocumentOrShadowRoot; // Weak, cleared on "NodeWillBeDestroyed".
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StyleSheetList_h
diff --git a/dom/base/StyledRange.cpp b/dom/base/StyledRange.cpp
new file mode 100644
index 0000000000..8ab3b10186
--- /dev/null
+++ b/dom/base/StyledRange.cpp
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/StyledRange.h"
+#include "nsRange.h"
+
+StyledRange::StyledRange(mozilla::dom::AbstractRange* aRange)
+ : mRange(aRange) {}
diff --git a/dom/base/StyledRange.h b/dom/base/StyledRange.h
new file mode 100644
index 0000000000..a88c5eb13e
--- /dev/null
+++ b/dom/base/StyledRange.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_BASE_STYLED_RANGE_H_
+#define DOM_BASE_STYLED_RANGE_H_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TextRange.h"
+
+namespace mozilla::dom {
+class AbstractRange;
+}
+
+struct StyledRange {
+ explicit StyledRange(mozilla::dom::AbstractRange* aRange);
+
+ RefPtr<mozilla::dom::AbstractRange> mRange;
+ mozilla::TextRangeStyle mTextRangeStyle;
+};
+
+#endif // DOM_BASE_STYLED_RANGE_H_
diff --git a/dom/base/SubtleCrypto.cpp b/dom/base/SubtleCrypto.cpp
new file mode 100644
index 0000000000..bfd6c1a84e
--- /dev/null
+++ b/dom/base/SubtleCrypto.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SubtleCrypto.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/WebCryptoTask.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SubtleCrypto, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SubtleCrypto)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SubtleCrypto)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SubtleCrypto)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+SubtleCrypto::SubtleCrypto(nsIGlobalObject* aParent) : mParent(aParent) {}
+
+JSObject* SubtleCrypto::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return SubtleCrypto_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+#define SUBTLECRYPTO_METHOD_BODY(Operation, aRv, ...) \
+ MOZ_ASSERT(mParent); \
+ RefPtr<Promise> p = Promise::Create(mParent, aRv); \
+ if (aRv.Failed()) { \
+ return nullptr; \
+ } \
+ RefPtr<WebCryptoTask> task = \
+ WebCryptoTask::Create##Operation##Task(__VA_ARGS__); \
+ if (!task) { \
+ aRv.Throw(NS_ERROR_NULL_POINTER); \
+ return nullptr; \
+ } \
+ task->DispatchWithPromise(p); \
+ return p.forget();
+
+already_AddRefed<Promise> SubtleCrypto::Encrypt(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(Encrypt, aRv, cx, algorithm, key, data)}
+
+already_AddRefed<Promise> SubtleCrypto::Decrypt(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(Decrypt, aRv, cx, algorithm, key, data)}
+
+already_AddRefed<Promise> SubtleCrypto::Sign(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(Sign, aRv, cx, algorithm, key, data)}
+
+already_AddRefed<Promise> SubtleCrypto::Verify(
+ JSContext* cx, const ObjectOrString& algorithm, CryptoKey& key,
+ const CryptoOperationData& signature, const CryptoOperationData& data,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(Verify, aRv, cx, algorithm, key, signature, data)}
+
+already_AddRefed<Promise> SubtleCrypto::Digest(JSContext* cx,
+ const ObjectOrString& algorithm,
+ const CryptoOperationData& data,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(Digest, aRv, cx, algorithm, data)}
+
+already_AddRefed<Promise> SubtleCrypto::ImportKey(
+ JSContext* cx, const nsAString& format, JS::Handle<JSObject*> keyData,
+ const ObjectOrString& algorithm, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(ImportKey, aRv, mParent, cx, format, keyData,
+ algorithm, extractable, keyUsages)}
+
+already_AddRefed<Promise> SubtleCrypto::ExportKey(const nsAString& format,
+ CryptoKey& key,
+ ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(ExportKey, aRv, format, key)}
+
+already_AddRefed<Promise> SubtleCrypto::GenerateKey(
+ JSContext* cx, const ObjectOrString& algorithm, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(GenerateKey, aRv, mParent, cx, algorithm,
+ extractable, keyUsages)}
+
+already_AddRefed<Promise> SubtleCrypto::DeriveKey(
+ JSContext* cx, const ObjectOrString& algorithm, CryptoKey& baseKey,
+ const ObjectOrString& derivedKeyType, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(DeriveKey, aRv, mParent, cx, algorithm, baseKey,
+ derivedKeyType, extractable, keyUsages)}
+
+already_AddRefed<Promise> SubtleCrypto::DeriveBits(
+ JSContext* cx, const ObjectOrString& algorithm, CryptoKey& baseKey,
+ uint32_t length, ErrorResult& aRv){
+ SUBTLECRYPTO_METHOD_BODY(DeriveBits, aRv, cx, algorithm, baseKey, length)}
+
+already_AddRefed<Promise> SubtleCrypto::WrapKey(
+ JSContext* cx, const nsAString& format, CryptoKey& key,
+ CryptoKey& wrappingKey, const ObjectOrString& wrapAlgorithm,
+ ErrorResult& aRv){SUBTLECRYPTO_METHOD_BODY(WrapKey, aRv, cx, format, key,
+ wrappingKey, wrapAlgorithm)}
+
+already_AddRefed<Promise> SubtleCrypto::UnwrapKey(
+ JSContext* cx, const nsAString& format,
+ const ArrayBufferViewOrArrayBuffer& wrappedKey, CryptoKey& unwrappingKey,
+ const ObjectOrString& unwrapAlgorithm,
+ const ObjectOrString& unwrappedKeyAlgorithm, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv) {
+ SUBTLECRYPTO_METHOD_BODY(UnwrapKey, aRv, mParent, cx, format, wrappedKey,
+ unwrappingKey, unwrapAlgorithm,
+ unwrappedKeyAlgorithm, extractable, keyUsages)
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/SubtleCrypto.h b/dom/base/SubtleCrypto.h
new file mode 100644
index 0000000000..a4d93420c8
--- /dev/null
+++ b/dom/base/SubtleCrypto.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SubtleCrypto_h
+#define mozilla_dom_SubtleCrypto_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla::dom {
+
+class ObjectOrString;
+class Promise;
+
+typedef ArrayBufferViewOrArrayBuffer CryptoOperationData;
+
+class SubtleCrypto final : public nsISupports, public nsWrapperCache {
+ ~SubtleCrypto() = default;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SubtleCrypto)
+
+ public:
+ explicit SubtleCrypto(nsIGlobalObject* aParent);
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise> Encrypt(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Decrypt(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Sign(JSContext* cx, const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& data,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Verify(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& key,
+ const CryptoOperationData& signature,
+ const CryptoOperationData& data,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Digest(JSContext* cx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> ImportKey(JSContext* cx, const nsAString& format,
+ JS::Handle<JSObject*> keyData,
+ const ObjectOrString& algorithm,
+ bool extractable,
+ const Sequence<nsString>& keyUsages,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> ExportKey(const nsAString& format, CryptoKey& key,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> GenerateKey(JSContext* cx,
+ const ObjectOrString& algorithm,
+ bool extractable,
+ const Sequence<nsString>& keyUsages,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> DeriveKey(
+ JSContext* cx, const ObjectOrString& algorithm, CryptoKey& baseKey,
+ const ObjectOrString& derivedKeyType, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv);
+
+ already_AddRefed<Promise> DeriveBits(JSContext* cx,
+ const ObjectOrString& algorithm,
+ CryptoKey& baseKey, uint32_t length,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> WrapKey(JSContext* cx, const nsAString& format,
+ CryptoKey& key, CryptoKey& wrappingKey,
+ const ObjectOrString& wrapAlgorithm,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> UnwrapKey(
+ JSContext* cx, const nsAString& format,
+ const ArrayBufferViewOrArrayBuffer& wrappedKey, CryptoKey& unwrappingKey,
+ const ObjectOrString& unwrapAlgorithm,
+ const ObjectOrString& unwrappedKeyAlgorithm, bool extractable,
+ const Sequence<nsString>& keyUsages, ErrorResult& aRv);
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mParent;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SubtleCrypto_h
diff --git a/dom/base/SyncMessageSender.h b/dom/base/SyncMessageSender.h
new file mode 100644
index 0000000000..601d8d30fc
--- /dev/null
+++ b/dom/base/SyncMessageSender.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SyncMessageSender_h
+#define mozilla_dom_SyncMessageSender_h
+
+#include "mozilla/dom/MessageSender.h"
+
+namespace mozilla::dom {
+
+class SyncMessageSender : public MessageSender {
+ protected:
+ SyncMessageSender(ipc::MessageManagerCallback* aCallback,
+ MessageManagerFlags aFlags)
+ : MessageSender(aCallback, nullptr, aFlags) {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SyncMessageSender_h
diff --git a/dom/base/TestUtils.cpp b/dom/base/TestUtils.cpp
new file mode 100644
index 0000000000..6e4803d8ab
--- /dev/null
+++ b/dom/base/TestUtils.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TestUtils.h"
+#include "mozilla/dom/TestUtilsBinding.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "nsJSEnvironment.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<Promise> TestUtils::Gc(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // TODO(krosylight): Ideally we could use nsJSContext::IncrementalGC to make
+ // it actually async, but that's not required right now.
+
+ NS_DispatchToCurrentThread(
+ NS_NewCancelableRunnableFunction("TestUtils::Gc", [promise] {
+ if (NS_IsMainThread()) {
+ nsJSContext::GarbageCollectNow(JS::GCReason::DOM_TESTUTILS,
+ nsJSContext::NonShrinkingGC);
+ nsJSContext::CycleCollectNow(CCReason::API);
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
+ false /* shrinking */,
+ false /* collect children */);
+ workerPrivate->CycleCollectInternal(false);
+ }
+
+ promise->MaybeResolveWithUndefined();
+ }));
+
+ return promise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/TestUtils.h b/dom/base/TestUtils.h
new file mode 100644
index 0000000000..0b29f71f98
--- /dev/null
+++ b/dom/base/TestUtils.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_TESTING_TESTUTILS_H_
+#define DOM_TESTING_TESTUTILS_H_
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class TestUtils {
+ public:
+ static already_AddRefed<Promise> Gc(const GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ private:
+ ~TestUtils() = delete;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_TESTING_TESTUTILS_H_
diff --git a/dom/base/Text.cpp b/dom/base/Text.cpp
new file mode 100644
index 0000000000..a6cc317d4d
--- /dev/null
+++ b/dom/base/Text.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Text.h"
+#include "nsTextNode.h"
+#include "mozAutoDocUpdate.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<Text> Text::SplitText(uint32_t aOffset, ErrorResult& aRv) {
+ nsAutoString cutText;
+ const uint32_t length = TextLength();
+
+ if (aOffset > length) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ const uint32_t cutStartOffset = aOffset;
+ const uint32_t cutLength = length - aOffset;
+ SubstringData(cutStartOffset, cutLength, cutText, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, true);
+
+ // Use Clone for creating the new node so that the new node is of same class
+ // as this node!
+ RefPtr<CharacterData> clone = CloneDataNode(mNodeInfo, false);
+ MOZ_ASSERT(clone && clone->IsText());
+ RefPtr<Text> newContent = static_cast<Text*>(clone.get());
+
+ // nsRange expects the CharacterDataChanged notification is followed
+ // by an insertion of |newContent|. If you change this code,
+ // make sure you make the appropriate changes in nsRange.
+ newContent->SetText(cutText, true); // XXX should be false?
+
+ CharacterDataChangeInfo::Details details = {
+ CharacterDataChangeInfo::Details::eSplit, newContent};
+ nsresult rv =
+ SetTextInternal(cutStartOffset, cutLength, nullptr, 0, true, &details);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (parent) {
+ nsCOMPtr<nsIContent> beforeNode = GetNextSibling();
+ parent->InsertChildBefore(newContent, beforeNode, true, IgnoreErrors());
+ }
+
+ return newContent.forget();
+}
+
+static Text* FirstLogicallyAdjacentTextNode(Text* aNode) {
+ do {
+ nsIContent* sibling = aNode->GetPreviousSibling();
+ if (!sibling || !sibling->IsText()) {
+ return aNode;
+ }
+ aNode = static_cast<Text*>(sibling);
+ } while (1); // Must run out of previous siblings eventually!
+}
+
+static Text* LastLogicallyAdjacentTextNode(Text* aNode) {
+ do {
+ nsIContent* sibling = aNode->GetNextSibling();
+ if (!sibling || !sibling->IsText()) {
+ return aNode;
+ }
+
+ aNode = static_cast<Text*>(sibling);
+ } while (1); // Must run out of next siblings eventually!
+}
+
+void Text::GetWholeText(nsAString& aWholeText) {
+ nsIContent* parent = GetParent();
+
+ // Handle parent-less nodes
+ if (!parent) {
+ GetData(aWholeText);
+ return;
+ }
+
+ Text* first = FirstLogicallyAdjacentTextNode(this);
+ Text* last = LastLogicallyAdjacentTextNode(this);
+
+ aWholeText.Truncate();
+
+ nsAutoString tmp;
+
+ while (true) {
+ first->GetData(tmp);
+ aWholeText.Append(tmp);
+
+ if (first == last) {
+ break;
+ }
+
+ nsIContent* next = first->GetNextSibling();
+ MOZ_ASSERT(next && next->IsText(),
+ "How did we run out of text before hitting `last`?");
+ first = static_cast<Text*>(next);
+ }
+}
+
+/* static */
+already_AddRefed<Text> Text::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window || !window->GetDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return window->GetDoc()->CreateTextNode(aData);
+}
+
+bool Text::HasTextForTranslation() {
+ if (mText.Is2b()) {
+ // The fragment contains non-8bit characters which means there
+ // was at least one "interesting" character to trigger non-8bit.
+ return true;
+ }
+
+ if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE) &&
+ HasFlag(NS_TEXT_IS_ONLY_WHITESPACE)) {
+ return false;
+ }
+
+ const char* cp = mText.Get1b();
+ const char* end = cp + mText.GetLength();
+
+ unsigned char ch;
+ for (; cp < end; cp++) {
+ ch = *cp;
+
+ // These are the characters that are letters
+ // in the first 256 UTF-8 codepoints.
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
+ (ch >= 192 && ch <= 214) || (ch >= 216 && ch <= 246) || (ch >= 248)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Text.h b/dom/base/Text.h
new file mode 100644
index 0000000000..a221014203
--- /dev/null
+++ b/dom/base/Text.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Text_h
+#define mozilla_dom_Text_h
+
+#include "mozilla/dom/CharacterData.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class Text : public CharacterData {
+ public:
+ NS_IMPL_FROMNODE_HELPER(Text, IsText())
+
+ explicit Text(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : CharacterData(std::move(aNodeInfo)) {}
+
+ // WebIDL API
+ already_AddRefed<Text> SplitText(uint32_t aOffset, ErrorResult& rv);
+ void GetWholeText(nsAString& aWholeText);
+
+ static already_AddRefed<Text> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ ErrorResult& aRv);
+
+ /**
+ * Method to see if the text node contains data that is useful
+ * for a translation: i.e., it consists of more than just whitespace,
+ * digits and punctuation.
+ */
+ bool HasTextForTranslation();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+inline mozilla::dom::Text* nsINode::GetAsText() {
+ return IsText() ? AsText() : nullptr;
+}
+
+inline const mozilla::dom::Text* nsINode::GetAsText() const {
+ return IsText() ? AsText() : nullptr;
+}
+
+inline mozilla::dom::Text* nsINode::AsText() {
+ MOZ_ASSERT(IsText());
+ return static_cast<mozilla::dom::Text*>(this);
+}
+
+inline const mozilla::dom::Text* nsINode::AsText() const {
+ MOZ_ASSERT(IsText());
+ return static_cast<const mozilla::dom::Text*>(this);
+}
+
+#endif // mozilla_dom_Text_h
diff --git a/dom/base/TextInputProcessor.cpp b/dom/base/TextInputProcessor.cpp
new file mode 100644
index 0000000000..c263bdcf9b
--- /dev/null
+++ b/dom/base/TextInputProcessor.cpp
@@ -0,0 +1,1857 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Event.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeKeyBindingsType.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextInputProcessor.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/widget/IMEData.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "nsContentUtils.h"
+#include "nsIDocShell.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+
+using mozilla::dom::Event;
+using mozilla::dom::KeyboardEvent;
+using namespace mozilla::widget;
+
+namespace mozilla {
+
+/******************************************************************************
+ * TextInputProcessorNotification
+ ******************************************************************************/
+
+class TextInputProcessorNotification final
+ : public nsITextInputProcessorNotification {
+ using SelectionChangeData = IMENotification::SelectionChangeData;
+ using SelectionChangeDataBase = IMENotification::SelectionChangeDataBase;
+ using TextChangeData = IMENotification::TextChangeData;
+ using TextChangeDataBase = IMENotification::TextChangeDataBase;
+
+ public:
+ explicit TextInputProcessorNotification(const char* aType)
+ : mType(aType), mTextChangeData() {}
+
+ explicit TextInputProcessorNotification(
+ const TextChangeDataBase& aTextChangeData)
+ : mType("notify-text-change"), mTextChangeData(aTextChangeData) {}
+
+ explicit TextInputProcessorNotification(
+ const SelectionChangeDataBase& aSelectionChangeData)
+ : mType("notify-selection-change"),
+ mSelectionChangeData(aSelectionChangeData) {
+ // SelectionChangeDataBase::mString still refers nsString instance owned
+ // by aSelectionChangeData. So, this needs to copy the instance.
+ if (aSelectionChangeData.HasRange()) {
+ mSelectionChangeData.mString =
+ new nsString(aSelectionChangeData.String());
+ } else {
+ mSelectionChangeData.mString = nullptr;
+ }
+ }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetType(nsACString& aType) final {
+ aType = mType;
+ return NS_OK;
+ }
+
+ // "notify-text-change" and "notify-selection-change"
+ NS_IMETHOD GetOffset(uint32_t* aOffset) final {
+ if (NS_WARN_IF(!aOffset)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ if (!mSelectionChangeData.HasRange()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aOffset = mSelectionChangeData.mOffset;
+ return NS_OK;
+ }
+ if (IsTextChange()) {
+ *aOffset = mTextChangeData.mStartOffset;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // "notify-selection-change"
+ NS_IMETHOD GetHasRange(bool* aHasRange) final {
+ if (IsSelectionChange()) {
+ *aHasRange = mSelectionChangeData.HasRange();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NS_IMETHOD GetText(nsAString& aText) final {
+ if (IsSelectionChange()) {
+ if (!mSelectionChangeData.HasRange()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aText = mSelectionChangeData.String();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCollapsed(bool* aCollapsed) final {
+ if (NS_WARN_IF(!aCollapsed)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ *aCollapsed = mSelectionChangeData.IsCollapsed();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetLength(uint32_t* aLength) final {
+ if (NS_WARN_IF(!aLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ if (!mSelectionChangeData.HasRange()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aLength = mSelectionChangeData.Length();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetReversed(bool* aReversed) final {
+ if (NS_WARN_IF(!aReversed)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ if (!mSelectionChangeData.HasRange()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aReversed = mSelectionChangeData.mReversed;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetWritingMode(nsACString& aWritingMode) final {
+ if (IsSelectionChange()) {
+ WritingMode writingMode = mSelectionChangeData.GetWritingMode();
+ if (!writingMode.IsVertical()) {
+ aWritingMode.AssignLiteral("horizontal-tb");
+ } else if (writingMode.IsVerticalLR()) {
+ aWritingMode.AssignLiteral("vertical-lr");
+ } else {
+ aWritingMode.AssignLiteral("vertical-rl");
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) final {
+ if (NS_WARN_IF(!aCausedByComposition)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ *aCausedByComposition = mSelectionChangeData.mCausedByComposition;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCausedBySelectionEvent(bool* aCausedBySelectionEvent) final {
+ if (NS_WARN_IF(!aCausedBySelectionEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ *aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetOccurredDuringComposition(
+ bool* aOccurredDuringComposition) final {
+ if (NS_WARN_IF(!aOccurredDuringComposition)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsSelectionChange()) {
+ *aOccurredDuringComposition =
+ mSelectionChangeData.mOccurredDuringComposition;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // "notify-text-change"
+ NS_IMETHOD GetRemovedLength(uint32_t* aLength) final {
+ if (NS_WARN_IF(!aLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsTextChange()) {
+ *aLength = mTextChangeData.OldLength();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetAddedLength(uint32_t* aLength) final {
+ if (NS_WARN_IF(!aLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsTextChange()) {
+ *aLength = mTextChangeData.NewLength();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCausedOnlyByComposition(bool* aCausedOnlyByComposition) final {
+ if (NS_WARN_IF(!aCausedOnlyByComposition)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsTextChange()) {
+ *aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetIncludingChangesDuringComposition(
+ bool* aIncludingChangesDuringComposition) final {
+ if (NS_WARN_IF(!aIncludingChangesDuringComposition)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsTextChange()) {
+ *aIncludingChangesDuringComposition =
+ mTextChangeData.mIncludingChangesDuringComposition;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetIncludingChangesWithoutComposition(
+ bool* aIncludingChangesWithoutComposition) final {
+ if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (IsTextChange()) {
+ *aIncludingChangesWithoutComposition =
+ mTextChangeData.mIncludingChangesWithoutComposition;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ protected:
+ virtual ~TextInputProcessorNotification() {
+ if (IsSelectionChange() && mSelectionChangeData.mString) {
+ delete mSelectionChangeData.mString;
+ mSelectionChangeData.mString = nullptr;
+ }
+ }
+
+ bool IsTextChange() const {
+ return mType.EqualsLiteral("notify-text-change");
+ }
+
+ bool IsSelectionChange() const {
+ return mType.EqualsLiteral("notify-selection-change");
+ }
+
+ private:
+ nsAutoCString mType;
+ union {
+ TextChangeDataBase mTextChangeData;
+ SelectionChangeDataBase mSelectionChangeData;
+ };
+
+ TextInputProcessorNotification() : mTextChangeData() {}
+};
+
+NS_IMPL_ISUPPORTS(TextInputProcessorNotification,
+ nsITextInputProcessorNotification)
+
+/******************************************************************************
+ * TextInputProcessor
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(TextInputProcessor, nsITextInputProcessor,
+ TextEventDispatcherListener, nsISupportsWeakReference)
+
+TextInputProcessor::TextInputProcessor()
+ : mDispatcher(nullptr), mForTests(false) {}
+
+TextInputProcessor::~TextInputProcessor() {
+ if (mDispatcher && mDispatcher->IsComposing()) {
+ // If this is composing and not canceling the composition, nobody can steal
+ // the rights of TextEventDispatcher from this instance. Therefore, this
+ // needs to cancel the composition here.
+ if (NS_SUCCEEDED(IsValidStateForComposition())) {
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ kungFuDeathGrip->CommitComposition(status, &EmptyString());
+ }
+ }
+}
+
+bool TextInputProcessor::IsComposing() const {
+ return mDispatcher && mDispatcher->IsComposing();
+}
+
+NS_IMETHODIMP
+TextInputProcessor::GetHasComposition(bool* aHasComposition) {
+ MOZ_RELEASE_ASSERT(aHasComposition, "aHasComposition must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ *aHasComposition = IsComposing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::BeginInputTransaction(
+ mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
+ bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ if (NS_WARN_IF(!aCallback)) {
+ *aSucceeded = false;
+ return NS_ERROR_INVALID_ARG;
+ }
+ return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::BeginInputTransactionForTests(
+ mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
+ uint8_t aOptionalArgc, bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ nsITextInputProcessorCallback* callback =
+ aOptionalArgc >= 1 ? aCallback : nullptr;
+ return BeginInputTransactionInternal(aWindow, callback, true, *aSucceeded);
+}
+
+nsresult TextInputProcessor::BeginInputTransactionForFuzzing(
+ nsPIDOMWindowInner* aWindow, nsITextInputProcessorCallback* aCallback,
+ bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded);
+}
+
+nsresult TextInputProcessor::BeginInputTransactionInternal(
+ mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
+ bool aForTests, bool& aSucceeded) {
+ aSucceeded = false;
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> pWindow = nsPIDOMWindowInner::From(aWindow);
+ if (NS_WARN_IF(!pWindow)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell());
+ if (NS_WARN_IF(!docShell)) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsPresContext> presContext = docShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher();
+ MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null");
+
+ // If the instance was initialized and is being initialized for same
+ // dispatcher and same purpose, we don't need to initialize the dispatcher
+ // again.
+ if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback &&
+ aForTests == mForTests) {
+ aSucceeded = true;
+ return NS_OK;
+ }
+
+ // If this instance is composing or dispatching an event, don't allow to
+ // initialize again. Especially, if we allow to begin input transaction with
+ // another TextEventDispatcher during dispatching an event, it may cause that
+ // nobody cannot begin input transaction with it if the last event causes
+ // opening modal dialog.
+ if (mDispatcher &&
+ (mDispatcher->IsComposing() || mDispatcher->IsDispatchingEvent())) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // And also if another instance is composing with the new dispatcher or
+ // dispatching an event, it'll fail to steal its ownership. Then, we should
+ // not throw an exception, just return false.
+ if (dispatcher->IsComposing() || dispatcher->IsDispatchingEvent()) {
+ return NS_OK;
+ }
+
+ // This instance has finished preparing to link to the dispatcher. Therefore,
+ // let's forget the old dispatcher and purpose.
+ if (mDispatcher) {
+ mDispatcher->EndInputTransaction(this);
+ if (NS_WARN_IF(mDispatcher)) {
+ // Forcibly initialize the members if we failed to end the input
+ // transaction.
+ UnlinkFromTextEventDispatcher();
+ }
+ }
+
+ nsresult rv = NS_OK;
+ if (aForTests) {
+ bool isAPZAware = StaticPrefs::test_events_async_enabled();
+ rv = dispatcher->BeginTestInputTransaction(this, isAPZAware);
+ } else {
+ rv = dispatcher->BeginInputTransaction(this);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mDispatcher = dispatcher;
+ mCallback = aCallback;
+ mForTests = aForTests;
+ aSucceeded = true;
+ return NS_OK;
+}
+
+void TextInputProcessor::UnlinkFromTextEventDispatcher() {
+ mDispatcher = nullptr;
+ mForTests = false;
+ if (mCallback) {
+ nsCOMPtr<nsITextInputProcessorCallback> callback(mCallback);
+ mCallback = nullptr;
+
+ RefPtr<TextInputProcessorNotification> notification =
+ new TextInputProcessorNotification("notify-end-input-transaction");
+ bool result = false;
+ callback->OnNotify(this, notification, &result);
+ }
+}
+
+nsresult TextInputProcessor::IsValidStateForComposition() {
+ if (NS_WARN_IF(!mDispatcher)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = mDispatcher->GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool TextInputProcessor::IsValidEventTypeForComposition(
+ const WidgetKeyboardEvent& aKeyboardEvent) const {
+ // The key event type of composition methods must be "", "keydown" or "keyup".
+ if (aKeyboardEvent.mMessage == eKeyDown ||
+ aKeyboardEvent.mMessage == eKeyUp) {
+ return true;
+ }
+ if (aKeyboardEvent.mMessage == eUnidentifiedEvent &&
+ aKeyboardEvent.mSpecifiedEventType &&
+ nsDependentAtomString(aKeyboardEvent.mSpecifiedEventType)
+ .EqualsLiteral("on")) {
+ return true;
+ }
+ return false;
+}
+
+TextInputProcessor::EventDispatcherResult
+TextInputProcessor::MaybeDispatchKeydownForComposition(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
+ EventDispatcherResult result;
+
+ result.mResult = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+ result.mCanContinue = false;
+ return result;
+ }
+
+ if (!aKeyboardEvent) {
+ return result;
+ }
+
+ // If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch
+ // eKeyDown event.
+ if (aKeyboardEvent->mMessage == eKeyUp) {
+ return result;
+ }
+
+ // Modifier keys are not allowed because managing modifier state in this
+ // method makes this messy.
+ if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) {
+ result.mResult = NS_ERROR_INVALID_ARG;
+ result.mCanContinue = false;
+ return result;
+ }
+
+ uint32_t consumedFlags = 0;
+
+ result.mResult =
+ KeydownInternal(*aKeyboardEvent, aKeyFlags, false, consumedFlags);
+ result.mDoDefault = !consumedFlags;
+ if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+ result.mCanContinue = false;
+ return result;
+ }
+
+ result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
+ return result;
+}
+
+TextInputProcessor::EventDispatcherResult
+TextInputProcessor::MaybeDispatchKeyupForComposition(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
+ EventDispatcherResult result;
+
+ if (!aKeyboardEvent) {
+ return result;
+ }
+
+ // If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch
+ // eKeyUp event.
+ if (aKeyboardEvent->mMessage == eKeyDown) {
+ return result;
+ }
+
+ // If the widget has been destroyed, we can do nothing here.
+ result.mResult = IsValidStateForComposition();
+ if (NS_FAILED(result.mResult)) {
+ result.mCanContinue = false;
+ return result;
+ }
+
+ result.mResult = KeyupInternal(*aKeyboardEvent, aKeyFlags, result.mDoDefault);
+ if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+ result.mCanContinue = false;
+ return result;
+ }
+
+ result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
+ return result;
+}
+
+nsresult TextInputProcessor::PrepareKeyboardEventForComposition(
+ KeyboardEvent* aDOMKeyEvent, uint32_t& aKeyFlags, uint8_t aOptionalArgc,
+ WidgetKeyboardEvent*& aKeyboardEvent) {
+ aKeyboardEvent = nullptr;
+
+ aKeyboardEvent = aOptionalArgc && aDOMKeyEvent
+ ? aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent()
+ : nullptr;
+ if (!aKeyboardEvent || aOptionalArgc < 2) {
+ aKeyFlags = 0;
+ }
+
+ if (!aKeyboardEvent) {
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!IsValidEventTypeForComposition(*aKeyboardEvent))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::StartComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
+ uint8_t aOptionalArgc, bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ *aSucceeded = false;
+
+ RefPtr<KeyboardEvent> keyEvent;
+ if (aDOMKeyEvent) {
+ keyEvent = aDOMKeyEvent->AsKeyboardEvent();
+ if (NS_WARN_IF(!keyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv = PrepareKeyboardEventForComposition(
+ keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ EventDispatcherResult dispatcherResult =
+ MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+ !dispatcherResult.mCanContinue) {
+ return dispatcherResult.mResult;
+ }
+
+ if (dispatcherResult.mDoDefault) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = kungFuDeathGrip->StartComposition(status);
+ *aSucceeded = status != nsEventStatus_eConsumeNoDefault &&
+ kungFuDeathGrip && kungFuDeathGrip->IsComposing();
+ }
+
+ MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::SetPendingCompositionString(const nsAString& aString) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ nsresult rv = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return kungFuDeathGrip->SetPendingCompositionString(aString);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::AppendClauseToPendingComposition(uint32_t aLength,
+ uint32_t aAttribute) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ TextRangeType textRangeType;
+ switch (aAttribute) {
+ case ATTR_RAW_CLAUSE:
+ case ATTR_SELECTED_RAW_CLAUSE:
+ case ATTR_CONVERTED_CLAUSE:
+ case ATTR_SELECTED_CLAUSE:
+ textRangeType = ToTextRangeType(aAttribute);
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsresult rv = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return kungFuDeathGrip->AppendClauseToPendingComposition(aLength,
+ textRangeType);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::SetCaretInPendingComposition(uint32_t aOffset) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ nsresult rv = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return kungFuDeathGrip->SetCaretInPendingComposition(aOffset, 0);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::FlushPendingComposition(Event* aDOMKeyEvent,
+ uint32_t aKeyFlags,
+ uint8_t aOptionalArgc,
+ bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ // Even if this doesn't flush pending composition actually, we need to reset
+ // pending composition for starting next composition with new user input.
+ AutoPendingCompositionResetter resetter(this);
+
+ *aSucceeded = false;
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ bool wasComposing = IsComposing();
+
+ RefPtr<KeyboardEvent> keyEvent;
+ if (aDOMKeyEvent) {
+ keyEvent = aDOMKeyEvent->AsKeyboardEvent();
+ if (NS_WARN_IF(!keyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv = PrepareKeyboardEventForComposition(
+ keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ EventDispatcherResult dispatcherResult =
+ MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+ !dispatcherResult.mCanContinue) {
+ return dispatcherResult.mResult;
+ }
+
+ // Even if the preceding keydown event was consumed, if the composition
+ // was already started, we shouldn't prevent the change of composition.
+ if (dispatcherResult.mDoDefault || wasComposing) {
+ // Preceding keydown event may cause destroying the widget.
+ if (NS_FAILED(IsValidStateForComposition())) {
+ return NS_OK;
+ }
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = kungFuDeathGrip->FlushPendingComposition(status);
+ *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+ }
+
+ MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CommitComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
+ uint8_t aOptionalArgc) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ RefPtr<KeyboardEvent> keyEvent;
+ if (aDOMKeyEvent) {
+ keyEvent = aDOMKeyEvent->AsKeyboardEvent();
+ if (NS_WARN_IF(!keyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv = PrepareKeyboardEventForComposition(
+ keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return CommitCompositionInternal(keyboardEvent, aKeyFlags);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString,
+ Event* aDOMKeyEvent,
+ uint32_t aKeyFlags,
+ uint8_t aOptionalArgc,
+ bool* aSucceeded) {
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ RefPtr<KeyboardEvent> keyEvent;
+ if (aDOMKeyEvent) {
+ keyEvent = aDOMKeyEvent->AsKeyboardEvent();
+ if (NS_WARN_IF(!keyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv = PrepareKeyboardEventForComposition(
+ keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return CommitCompositionInternal(keyboardEvent, aKeyFlags, &aCommitString,
+ aSucceeded);
+}
+
+nsresult TextInputProcessor::CommitCompositionInternal(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags,
+ const nsAString* aCommitString, bool* aSucceeded) {
+ if (aSucceeded) {
+ *aSucceeded = false;
+ }
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ bool wasComposing = IsComposing();
+
+ EventDispatcherResult dispatcherResult =
+ MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+ !dispatcherResult.mCanContinue) {
+ return dispatcherResult.mResult;
+ }
+
+ // Even if the preceding keydown event was consumed, if the composition
+ // was already started, we shouldn't prevent the commit of composition.
+ nsresult rv = NS_OK;
+ if (dispatcherResult.mDoDefault || wasComposing) {
+ // Preceding keydown event may cause destroying the widget.
+ if (NS_FAILED(IsValidStateForComposition())) {
+ return NS_OK;
+ }
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = kungFuDeathGrip->CommitComposition(status, aCommitString);
+ if (aSucceeded) {
+ *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CancelComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
+ uint8_t aOptionalArgc) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ RefPtr<KeyboardEvent> keyEvent;
+ if (aDOMKeyEvent) {
+ keyEvent = aDOMKeyEvent->AsKeyboardEvent();
+ if (NS_WARN_IF(!keyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv = PrepareKeyboardEventForComposition(
+ keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return CancelCompositionInternal(keyboardEvent, aKeyFlags);
+}
+
+nsresult TextInputProcessor::CancelCompositionInternal(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+
+ EventDispatcherResult dispatcherResult =
+ MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+ !dispatcherResult.mCanContinue) {
+ return dispatcherResult.mResult;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = kungFuDeathGrip->CommitComposition(status, &EmptyString());
+
+ MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ // If This is called while this is being initialized, ignore the call.
+ // In such case, this method should return NS_ERROR_NOT_IMPLEMENTED because
+ // we can say, TextInputProcessor doesn't implement any handlers of the
+ // requests and notifications.
+ if (!mDispatcher) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+ "Wrong TextEventDispatcher notifies this");
+ NS_ASSERTION(mForTests || mCallback,
+ "mCallback can be null only when IME is initialized for tests");
+ if (mCallback) {
+ RefPtr<TextInputProcessorNotification> notification;
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION: {
+ NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+ "Why is this requested without composition?");
+ notification = new TextInputProcessorNotification("request-to-commit");
+ break;
+ }
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+ "Why is this requested without composition?");
+ notification = new TextInputProcessorNotification("request-to-cancel");
+ break;
+ }
+ case NOTIFY_IME_OF_FOCUS:
+ notification = new TextInputProcessorNotification("notify-focus");
+ break;
+ case NOTIFY_IME_OF_BLUR:
+ notification = new TextInputProcessorNotification("notify-blur");
+ break;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ notification =
+ new TextInputProcessorNotification(aNotification.mTextChangeData);
+ break;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ notification = new TextInputProcessorNotification(
+ aNotification.mSelectionChangeData);
+ break;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ notification =
+ new TextInputProcessorNotification("notify-position-change");
+ break;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ MOZ_RELEASE_ASSERT(notification);
+ bool result = false;
+ nsresult rv = mCallback->OnNotify(this, notification, &result);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION: {
+ NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+ "Why is this requested without composition?");
+ CommitCompositionInternal();
+ return NS_OK;
+ }
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+ "Why is this requested without composition?");
+ CancelCompositionInternal();
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+TextInputProcessor::GetIMENotificationRequests() {
+ // TextInputProcessor should support all change notifications.
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE);
+}
+
+NS_IMETHODIMP_(void)
+TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
+ // If This is called while this is being initialized, ignore the call.
+ if (!mDispatcher) {
+ return;
+ }
+ MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+ "Wrong TextEventDispatcher notifies this");
+ UnlinkFromTextEventDispatcher();
+}
+
+NS_IMETHODIMP_(void)
+TextInputProcessor::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ // TextInputProcessor doesn't set alternative char code nor modify charCode
+ // even when Ctrl key is pressed.
+}
+
+nsresult TextInputProcessor::PrepareKeyboardEventToDispatch(
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags) {
+ if (NS_WARN_IF(aKeyboardEvent.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if ((aKeyFlags & KEY_NON_PRINTABLE_KEY) &&
+ NS_WARN_IF(aKeyboardEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if ((aKeyFlags & KEY_FORCE_PRINTABLE_KEY) &&
+ aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
+ aKeyboardEvent.GetDOMKeyName(aKeyboardEvent.mKeyValue);
+ aKeyboardEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ }
+ if (aKeyFlags & KEY_KEEP_KEY_LOCATION_STANDARD) {
+ // If .location is initialized with specific value, using
+ // KEY_KEEP_KEY_LOCATION_STANDARD must be a bug of the caller.
+ // Let's throw an exception for notifying the developer of this bug.
+ if (NS_WARN_IF(aKeyboardEvent.mLocation)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (!aKeyboardEvent.mLocation) {
+ // If KeyboardEvent.mLocation is 0, it may be uninitialized. If so, we
+ // should compute proper mLocation value from its .code value.
+ aKeyboardEvent.mLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(
+ aKeyboardEvent.mCodeNameIndex);
+ }
+
+ if (aKeyFlags & KEY_KEEP_KEYCODE_ZERO) {
+ // If .keyCode is initialized with specific value, using
+ // KEY_KEEP_KEYCODE_ZERO must be a bug of the caller. Let's throw an
+ // exception for notifying the developer of such bug.
+ if (NS_WARN_IF(aKeyboardEvent.mKeyCode)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (!aKeyboardEvent.mKeyCode &&
+ aKeyboardEvent.mKeyNameIndex > KEY_NAME_INDEX_Unidentified &&
+ aKeyboardEvent.mKeyNameIndex < KEY_NAME_INDEX_USE_STRING) {
+ // If KeyboardEvent.keyCode is 0, it may be uninitialized. If so, we may
+ // be able to decide a good .keyCode value if the .key value is a
+ // non-printable key.
+ aKeyboardEvent.mKeyCode =
+ WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
+ aKeyboardEvent.mKeyNameIndex);
+ }
+
+ aKeyboardEvent.mIsSynthesizedByTIP = true;
+ aKeyboardEvent.mFlags.mIsSynthesizedForTests = mForTests;
+
+ return NS_OK;
+}
+
+nsresult TextInputProcessor::InitEditCommands(
+ WidgetKeyboardEvent& aKeyboardEvent) const {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress);
+
+ // When this emulates real input only in content process, we need to
+ // initialize edit commands with the main process's widget via PuppetWidget
+ // because they are initialized by BrowserParent before content process treats
+ // them.
+ // And also when this synthesizes keyboard events for tests, we need default
+ // shortcut keys on the platform for making any developers get constant
+ // results in any environments.
+
+ // Note that retrieving edit commands via PuppetWidget is expensive.
+ // Let's skip it when the keyboard event is inputting text.
+ if (aKeyboardEvent.IsInputtingText()) {
+ aKeyboardEvent.PreventNativeKeyBindings();
+ return NS_OK;
+ }
+
+ Maybe<WritingMode> writingMode;
+ if (RefPtr<TextEventDispatcher> dispatcher = mDispatcher) {
+ writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
+ }
+
+ // FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here
+ // since it checks whether it's called in the main process to
+ // avoid performance issues so that we need to initialize each
+ // command manually here.
+ if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
+ NativeKeyBindingsType::SingleLineEditor, writingMode))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
+ NativeKeyBindingsType::MultiLineEditor, writingMode))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
+ NativeKeyBindingsType::RichTextEditor, writingMode))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::Keydown(Event* aDOMKeyEvent, uint32_t aKeyFlags,
+ uint8_t aOptionalArgc, uint32_t* aConsumedFlags) {
+ MOZ_RELEASE_ASSERT(aConsumedFlags, "aConsumedFlags must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ if (!aOptionalArgc) {
+ aKeyFlags = 0;
+ }
+ if (NS_WARN_IF(!aDOMKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ WidgetKeyboardEvent* originalKeyEvent =
+ aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!originalKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags);
+}
+
+nsresult TextInputProcessor::Keydown(const WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags,
+ uint32_t* aConsumedFlags) {
+ uint32_t consumedFlags = 0;
+ return KeydownInternal(aKeyboardEvent, aKeyFlags, true,
+ aConsumedFlags ? *aConsumedFlags : consumedFlags);
+}
+
+nsresult TextInputProcessor::KeydownInternal(
+ const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
+ bool aAllowToDispatchKeypress, uint32_t& aConsumedFlags) {
+ aConsumedFlags = KEYEVENT_NOT_CONSUMED;
+
+ // We shouldn't modify the internal WidgetKeyboardEvent.
+ WidgetKeyboardEvent keyEvent(aKeyboardEvent);
+ keyEvent.mFlags.mIsTrusted = true;
+ keyEvent.mMessage = eKeyDown;
+ nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aConsumedFlags = (aKeyFlags & KEY_DEFAULT_PREVENTED) ? KEYDOWN_IS_CONSUMED
+ : KEYEVENT_NOT_CONSUMED;
+
+ if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
+ ModifierKeyData modifierKeyData(keyEvent);
+ if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
+ // If the modifier key is lockable modifier key such as CapsLock,
+ // let's toggle modifier key state at keydown.
+ ToggleModifierKey(modifierKeyData);
+ } else {
+ // Activate modifier flag before dispatching keydown event (i.e., keydown
+ // event should indicate the releasing modifier is active.
+ ActivateModifierKey(modifierKeyData);
+ }
+ if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
+ return NS_OK;
+ }
+ } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ keyEvent.mModifiers = GetActiveModifiers();
+
+ if (!aAllowToDispatchKeypress &&
+ !(aKeyFlags & KEY_DONT_MARK_KEYDOWN_AS_PROCESSED)) {
+ keyEvent.mKeyCode = NS_VK_PROCESSKEY;
+ keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ }
+
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ rv = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsEventStatus status =
+ aConsumedFlags ? nsEventStatus_eConsumeNoDefault : nsEventStatus_eIgnore;
+ if (!kungFuDeathGrip->DispatchKeyboardEvent(eKeyDown, keyEvent, status)) {
+ // If keydown event isn't dispatched, we don't need to dispatch keypress
+ // events.
+ return NS_OK;
+ }
+
+ aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
+ ? KEYDOWN_IS_CONSUMED
+ : KEYEVENT_NOT_CONSUMED;
+
+ if (!aAllowToDispatchKeypress) {
+ return NS_OK;
+ }
+
+ keyEvent.mMessage = eKeyPress;
+
+ // Only `eKeyPress` events, editor wants to execute system default edit
+ // commands mapped to the key combination. In e10s world, edit commands can
+ // be retrieved only in the parent process due to the performance reason.
+ // Therefore, BrowserParent initializes edit commands for all cases before
+ // sending the event to focused content process. For emulating this, we
+ // need to do it now for synthesizing `eKeyPress` events if and only if
+ // we're dispatching the events in a content process.
+ if (XRE_IsContentProcess()) {
+ nsresult rv = InitEditCommands(keyEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ if (kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
+ aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
+ ? KEYPRESS_IS_CONSUMED
+ : KEYEVENT_NOT_CONSUMED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::Keyup(Event* aDOMKeyEvent, uint32_t aKeyFlags,
+ uint8_t aOptionalArgc, bool* aDoDefault) {
+ MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ if (!aOptionalArgc) {
+ aKeyFlags = 0;
+ }
+ if (NS_WARN_IF(!aDOMKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ WidgetKeyboardEvent* originalKeyEvent =
+ aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!originalKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
+}
+
+nsresult TextInputProcessor::Keyup(const WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags, bool* aDoDefault) {
+ bool doDefault = false;
+ return KeyupInternal(aKeyboardEvent, aKeyFlags,
+ aDoDefault ? *aDoDefault : doDefault);
+}
+
+nsresult TextInputProcessor::KeyupInternal(
+ const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
+ bool& aDoDefault) {
+ aDoDefault = false;
+
+ // We shouldn't modify the internal WidgetKeyboardEvent.
+ WidgetKeyboardEvent keyEvent(aKeyboardEvent);
+ keyEvent.mFlags.mIsTrusted = true;
+ keyEvent.mMessage = eKeyUp;
+ nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
+
+ if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
+ if (!WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
+ // Inactivate modifier flag before dispatching keyup event (i.e., keyup
+ // event shouldn't indicate the releasing modifier is active.
+ InactivateModifierKey(ModifierKeyData(keyEvent));
+ }
+ if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
+ return NS_OK;
+ }
+ } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ keyEvent.mModifiers = GetActiveModifiers();
+
+ if (aKeyFlags & KEY_MARK_KEYUP_AS_PROCESSED) {
+ keyEvent.mKeyCode = NS_VK_PROCESSKEY;
+ keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ }
+
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+ rv = IsValidStateForComposition();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsEventStatus status =
+ aDoDefault ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
+ kungFuDeathGrip->DispatchKeyboardEvent(eKeyUp, keyEvent, status);
+ aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName,
+ bool* aActive) {
+ MOZ_RELEASE_ASSERT(aActive, "aActive must not be null");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName);
+ *aActive = ((GetActiveModifiers() & modifier) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther) {
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+ if (!aOther) {
+ mModifierKeyDataArray = nullptr;
+ return NS_OK;
+ }
+ TextInputProcessor* other = static_cast<TextInputProcessor*>(aOther);
+ if (!other->mModifierKeyDataArray) {
+ other->mModifierKeyDataArray = new ModifierKeyDataArray();
+ }
+ mModifierKeyDataArray = other->mModifierKeyDataArray;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::ComputeCodeValueOfNonPrintableKey(
+ const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
+ uint8_t aOptionalArgc, nsAString& aCodeValue) {
+ aCodeValue.Truncate();
+
+ Maybe<uint32_t> location;
+ if (aOptionalArgc) {
+ if (aLocation.isNullOrUndefined()) {
+ // location should be nothing.
+ } else if (aLocation.isInt32()) {
+ location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
+ } else {
+ NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
+ "aLocation must be undefined, null or int");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ KeyNameIndex keyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aKeyValue);
+ if (keyNameIndex == KEY_NAME_INDEX_Unidentified ||
+ keyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ return NS_OK;
+ }
+
+ CodeNameIndex codeNameIndex =
+ WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(keyNameIndex,
+ location);
+ if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::GuessCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
+ const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
+ uint8_t aOptionalArgc, nsAString& aCodeValue) {
+ aCodeValue.Truncate();
+
+ Maybe<uint32_t> location;
+ if (aOptionalArgc) {
+ if (aLocation.isNullOrUndefined()) {
+ // location should be nothing.
+ } else if (aLocation.isInt32()) {
+ location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
+ } else {
+ NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
+ "aLocation must be undefined, null or int");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ CodeNameIndex codeNameIndex =
+ GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(aKeyValue, location);
+ if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue);
+ return NS_OK;
+}
+
+// static
+CodeNameIndex
+TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(
+ const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) {
+ if (aKeyValue.IsEmpty()) {
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ // US keyboard layout can input only one character per key. So, we can
+ // assume that if the key value is 2 or more characters, it's a known
+ // key name or not a usual key emulation.
+ if (aKeyValue.Length() > 1) {
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ if (aLocation.isSome() &&
+ aLocation.value() ==
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
+ switch (aKeyValue[0]) {
+ case '+':
+ return CODE_NAME_INDEX_NumpadAdd;
+ case '-':
+ return CODE_NAME_INDEX_NumpadSubtract;
+ case '*':
+ return CODE_NAME_INDEX_NumpadMultiply;
+ case '/':
+ return CODE_NAME_INDEX_NumpadDivide;
+ case '.':
+ return CODE_NAME_INDEX_NumpadDecimal;
+ case '0':
+ return CODE_NAME_INDEX_Numpad0;
+ case '1':
+ return CODE_NAME_INDEX_Numpad1;
+ case '2':
+ return CODE_NAME_INDEX_Numpad2;
+ case '3':
+ return CODE_NAME_INDEX_Numpad3;
+ case '4':
+ return CODE_NAME_INDEX_Numpad4;
+ case '5':
+ return CODE_NAME_INDEX_Numpad5;
+ case '6':
+ return CODE_NAME_INDEX_Numpad6;
+ case '7':
+ return CODE_NAME_INDEX_Numpad7;
+ case '8':
+ return CODE_NAME_INDEX_Numpad8;
+ case '9':
+ return CODE_NAME_INDEX_Numpad9;
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ }
+
+ if (aLocation.isSome() &&
+ aLocation.value() !=
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+
+ // TODO: Support characters inputted with option key on macOS.
+ switch (aKeyValue[0]) {
+ case 'a':
+ case 'A':
+ return CODE_NAME_INDEX_KeyA;
+ case 'b':
+ case 'B':
+ return CODE_NAME_INDEX_KeyB;
+ case 'c':
+ case 'C':
+ return CODE_NAME_INDEX_KeyC;
+ case 'd':
+ case 'D':
+ return CODE_NAME_INDEX_KeyD;
+ case 'e':
+ case 'E':
+ return CODE_NAME_INDEX_KeyE;
+ case 'f':
+ case 'F':
+ return CODE_NAME_INDEX_KeyF;
+ case 'g':
+ case 'G':
+ return CODE_NAME_INDEX_KeyG;
+ case 'h':
+ case 'H':
+ return CODE_NAME_INDEX_KeyH;
+ case 'i':
+ case 'I':
+ return CODE_NAME_INDEX_KeyI;
+ case 'j':
+ case 'J':
+ return CODE_NAME_INDEX_KeyJ;
+ case 'k':
+ case 'K':
+ return CODE_NAME_INDEX_KeyK;
+ case 'l':
+ case 'L':
+ return CODE_NAME_INDEX_KeyL;
+ case 'm':
+ case 'M':
+ return CODE_NAME_INDEX_KeyM;
+ case 'n':
+ case 'N':
+ return CODE_NAME_INDEX_KeyN;
+ case 'o':
+ case 'O':
+ return CODE_NAME_INDEX_KeyO;
+ case 'p':
+ case 'P':
+ return CODE_NAME_INDEX_KeyP;
+ case 'q':
+ case 'Q':
+ return CODE_NAME_INDEX_KeyQ;
+ case 'r':
+ case 'R':
+ return CODE_NAME_INDEX_KeyR;
+ case 's':
+ case 'S':
+ return CODE_NAME_INDEX_KeyS;
+ case 't':
+ case 'T':
+ return CODE_NAME_INDEX_KeyT;
+ case 'u':
+ case 'U':
+ return CODE_NAME_INDEX_KeyU;
+ case 'v':
+ case 'V':
+ return CODE_NAME_INDEX_KeyV;
+ case 'w':
+ case 'W':
+ return CODE_NAME_INDEX_KeyW;
+ case 'x':
+ case 'X':
+ return CODE_NAME_INDEX_KeyX;
+ case 'y':
+ case 'Y':
+ return CODE_NAME_INDEX_KeyY;
+ case 'z':
+ case 'Z':
+ return CODE_NAME_INDEX_KeyZ;
+
+ case '`':
+ case '~':
+ return CODE_NAME_INDEX_Backquote;
+ case '1':
+ case '!':
+ return CODE_NAME_INDEX_Digit1;
+ case '2':
+ case '@':
+ return CODE_NAME_INDEX_Digit2;
+ case '3':
+ case '#':
+ return CODE_NAME_INDEX_Digit3;
+ case '4':
+ case '$':
+ return CODE_NAME_INDEX_Digit4;
+ case '5':
+ case '%':
+ return CODE_NAME_INDEX_Digit5;
+ case '6':
+ case '^':
+ return CODE_NAME_INDEX_Digit6;
+ case '7':
+ case '&':
+ return CODE_NAME_INDEX_Digit7;
+ case '8':
+ case '*':
+ return CODE_NAME_INDEX_Digit8;
+ case '9':
+ case '(':
+ return CODE_NAME_INDEX_Digit9;
+ case '0':
+ case ')':
+ return CODE_NAME_INDEX_Digit0;
+ case '-':
+ case '_':
+ return CODE_NAME_INDEX_Minus;
+ case '=':
+ case '+':
+ return CODE_NAME_INDEX_Equal;
+
+ case '[':
+ case '{':
+ return CODE_NAME_INDEX_BracketLeft;
+ case ']':
+ case '}':
+ return CODE_NAME_INDEX_BracketRight;
+ case '\\':
+ case '|':
+ return CODE_NAME_INDEX_Backslash;
+
+ case ';':
+ case ':':
+ return CODE_NAME_INDEX_Semicolon;
+ case '\'':
+ case '"':
+ return CODE_NAME_INDEX_Quote;
+
+ case ',':
+ case '<':
+ return CODE_NAME_INDEX_Comma;
+ case '.':
+ case '>':
+ return CODE_NAME_INDEX_Period;
+ case '/':
+ case '?':
+ return CODE_NAME_INDEX_Slash;
+
+ case ' ':
+ return CODE_NAME_INDEX_Space;
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+NS_IMETHODIMP
+TextInputProcessor::GuessKeyCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
+ const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
+ uint8_t aOptionalArgc, uint32_t* aKeyCodeValue) {
+ if (NS_WARN_IF(!aKeyCodeValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Maybe<uint32_t> location;
+ if (aOptionalArgc) {
+ if (aLocation.isNullOrUndefined()) {
+ // location should be nothing.
+ } else if (aLocation.isInt32()) {
+ location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
+ } else {
+ NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
+ "aLocation must be undefined, null or int");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ *aKeyCodeValue =
+ GuessKeyCodeOfPrintableKeyInUSEnglishLayout(aKeyValue, location);
+ return NS_OK;
+}
+
+// static
+uint32_t TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout(
+ const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) {
+ if (aKeyValue.IsEmpty()) {
+ return 0;
+ }
+ // US keyboard layout can input only one character per key. So, we can
+ // assume that if the key value is 2 or more characters, it's a known
+ // key name of a non-printable key or not a usual key emulation.
+ if (aKeyValue.Length() > 1) {
+ return 0;
+ }
+
+ if (aLocation.isSome() &&
+ aLocation.value() ==
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
+ switch (aKeyValue[0]) {
+ case '+':
+ return dom::KeyboardEvent_Binding::DOM_VK_ADD;
+ case '-':
+ return dom::KeyboardEvent_Binding::DOM_VK_SUBTRACT;
+ case '*':
+ return dom::KeyboardEvent_Binding::DOM_VK_MULTIPLY;
+ case '/':
+ return dom::KeyboardEvent_Binding::DOM_VK_DIVIDE;
+ case '.':
+ return dom::KeyboardEvent_Binding::DOM_VK_DECIMAL;
+ case '0':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD0;
+ case '1':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD1;
+ case '2':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD2;
+ case '3':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD3;
+ case '4':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD4;
+ case '5':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD5;
+ case '6':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD6;
+ case '7':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD7;
+ case '8':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD8;
+ case '9':
+ return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD9;
+ default:
+ return 0;
+ }
+ }
+
+ if (aLocation.isSome() &&
+ aLocation.value() !=
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
+ return 0;
+ }
+
+ // TODO: Support characters inputted with option key on macOS.
+ switch (aKeyValue[0]) {
+ case 'a':
+ case 'A':
+ return dom::KeyboardEvent_Binding::DOM_VK_A;
+ case 'b':
+ case 'B':
+ return dom::KeyboardEvent_Binding::DOM_VK_B;
+ case 'c':
+ case 'C':
+ return dom::KeyboardEvent_Binding::DOM_VK_C;
+ case 'd':
+ case 'D':
+ return dom::KeyboardEvent_Binding::DOM_VK_D;
+ case 'e':
+ case 'E':
+ return dom::KeyboardEvent_Binding::DOM_VK_E;
+ case 'f':
+ case 'F':
+ return dom::KeyboardEvent_Binding::DOM_VK_F;
+ case 'g':
+ case 'G':
+ return dom::KeyboardEvent_Binding::DOM_VK_G;
+ case 'h':
+ case 'H':
+ return dom::KeyboardEvent_Binding::DOM_VK_H;
+ case 'i':
+ case 'I':
+ return dom::KeyboardEvent_Binding::DOM_VK_I;
+ case 'j':
+ case 'J':
+ return dom::KeyboardEvent_Binding::DOM_VK_J;
+ case 'k':
+ case 'K':
+ return dom::KeyboardEvent_Binding::DOM_VK_K;
+ case 'l':
+ case 'L':
+ return dom::KeyboardEvent_Binding::DOM_VK_L;
+ case 'm':
+ case 'M':
+ return dom::KeyboardEvent_Binding::DOM_VK_M;
+ case 'n':
+ case 'N':
+ return dom::KeyboardEvent_Binding::DOM_VK_N;
+ case 'o':
+ case 'O':
+ return dom::KeyboardEvent_Binding::DOM_VK_O;
+ case 'p':
+ case 'P':
+ return dom::KeyboardEvent_Binding::DOM_VK_P;
+ case 'q':
+ case 'Q':
+ return dom::KeyboardEvent_Binding::DOM_VK_Q;
+ case 'r':
+ case 'R':
+ return dom::KeyboardEvent_Binding::DOM_VK_R;
+ case 's':
+ case 'S':
+ return dom::KeyboardEvent_Binding::DOM_VK_S;
+ case 't':
+ case 'T':
+ return dom::KeyboardEvent_Binding::DOM_VK_T;
+ case 'u':
+ case 'U':
+ return dom::KeyboardEvent_Binding::DOM_VK_U;
+ case 'v':
+ case 'V':
+ return dom::KeyboardEvent_Binding::DOM_VK_V;
+ case 'w':
+ case 'W':
+ return dom::KeyboardEvent_Binding::DOM_VK_W;
+ case 'x':
+ case 'X':
+ return dom::KeyboardEvent_Binding::DOM_VK_X;
+ case 'y':
+ case 'Y':
+ return dom::KeyboardEvent_Binding::DOM_VK_Y;
+ case 'z':
+ case 'Z':
+ return dom::KeyboardEvent_Binding::DOM_VK_Z;
+
+ case '`':
+ case '~':
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_QUOTE;
+ case '1':
+ case '!':
+ return dom::KeyboardEvent_Binding::DOM_VK_1;
+ case '2':
+ case '@':
+ return dom::KeyboardEvent_Binding::DOM_VK_2;
+ case '3':
+ case '#':
+ return dom::KeyboardEvent_Binding::DOM_VK_3;
+ case '4':
+ case '$':
+ return dom::KeyboardEvent_Binding::DOM_VK_4;
+ case '5':
+ case '%':
+ return dom::KeyboardEvent_Binding::DOM_VK_5;
+ case '6':
+ case '^':
+ return dom::KeyboardEvent_Binding::DOM_VK_6;
+ case '7':
+ case '&':
+ return dom::KeyboardEvent_Binding::DOM_VK_7;
+ case '8':
+ case '*':
+ return dom::KeyboardEvent_Binding::DOM_VK_8;
+ case '9':
+ case '(':
+ return dom::KeyboardEvent_Binding::DOM_VK_9;
+ case '0':
+ case ')':
+ return dom::KeyboardEvent_Binding::DOM_VK_0;
+ case '-':
+ case '_':
+ return dom::KeyboardEvent_Binding::DOM_VK_HYPHEN_MINUS;
+ case '=':
+ case '+':
+ return dom::KeyboardEvent_Binding::DOM_VK_EQUALS;
+
+ case '[':
+ case '{':
+ return dom::KeyboardEvent_Binding::DOM_VK_OPEN_BRACKET;
+ case ']':
+ case '}':
+ return dom::KeyboardEvent_Binding::DOM_VK_CLOSE_BRACKET;
+ case '\\':
+ case '|':
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
+
+ case ';':
+ case ':':
+ return dom::KeyboardEvent_Binding::DOM_VK_SEMICOLON;
+ case '\'':
+ case '"':
+ return dom::KeyboardEvent_Binding::DOM_VK_QUOTE;
+
+ case ',':
+ case '<':
+ return dom::KeyboardEvent_Binding::DOM_VK_COMMA;
+ case '.':
+ case '>':
+ return dom::KeyboardEvent_Binding::DOM_VK_PERIOD;
+ case '/':
+ case '?':
+ return dom::KeyboardEvent_Binding::DOM_VK_SLASH;
+
+ case ' ':
+ return dom::KeyboardEvent_Binding::DOM_VK_SPACE;
+
+ default:
+ return 0;
+ }
+}
+
+/******************************************************************************
+ * TextInputProcessor::AutoPendingCompositionResetter
+ ******************************************************************************/
+TextInputProcessor::AutoPendingCompositionResetter::
+ AutoPendingCompositionResetter(TextInputProcessor* aTIP)
+ : mTIP(aTIP) {
+ MOZ_RELEASE_ASSERT(mTIP.get(), "mTIP must not be null");
+}
+
+TextInputProcessor::AutoPendingCompositionResetter::
+ ~AutoPendingCompositionResetter() {
+ if (mTIP->mDispatcher) {
+ mTIP->mDispatcher->ClearPendingComposition();
+ }
+}
+
+/******************************************************************************
+ * TextInputProcessor::ModifierKeyData
+ ******************************************************************************/
+TextInputProcessor::ModifierKeyData::ModifierKeyData(
+ const WidgetKeyboardEvent& aKeyboardEvent)
+ : mKeyNameIndex(aKeyboardEvent.mKeyNameIndex),
+ mCodeNameIndex(aKeyboardEvent.mCodeNameIndex) {
+ mModifier = WidgetKeyboardEvent::GetModifierForKeyName(mKeyNameIndex);
+ MOZ_ASSERT(mModifier, "mKeyNameIndex must be a modifier key name");
+}
+
+/******************************************************************************
+ * TextInputProcessor::ModifierKeyDataArray
+ ******************************************************************************/
+Modifiers TextInputProcessor::ModifierKeyDataArray::GetActiveModifiers() const {
+ Modifiers result = MODIFIER_NONE;
+ for (uint32_t i = 0; i < Length(); i++) {
+ result |= ElementAt(i).mModifier;
+ }
+ return result;
+}
+
+void TextInputProcessor::ModifierKeyDataArray::ActivateModifierKey(
+ const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
+ if (Contains(aModifierKeyData)) {
+ return;
+ }
+ AppendElement(aModifierKeyData);
+}
+
+void TextInputProcessor::ModifierKeyDataArray::InactivateModifierKey(
+ const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
+ RemoveElement(aModifierKeyData);
+}
+
+void TextInputProcessor::ModifierKeyDataArray::ToggleModifierKey(
+ const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
+ auto index = IndexOf(aModifierKeyData);
+ if (index == NoIndex) {
+ AppendElement(aModifierKeyData);
+ return;
+ }
+ RemoveElementAt(index);
+}
+
+} // namespace mozilla
diff --git a/dom/base/TextInputProcessor.h b/dom/base/TextInputProcessor.h
new file mode 100644
index 0000000000..10e0421953
--- /dev/null
+++ b/dom/base/TextInputProcessor.h
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_textinputprocessor_h_
+#define mozilla_dom_textinputprocessor_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "nsITextInputProcessor.h"
+#include "nsITextInputProcessorCallback.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+namespace dom {
+class KeyboardEvent;
+} // namespace dom
+
+class TextInputProcessor final : public nsITextInputProcessor,
+ public widget::TextEventDispatcherListener {
+ typedef mozilla::widget::IMENotification IMENotification;
+ typedef mozilla::widget::IMENotificationRequests IMENotificationRequests;
+ typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
+
+ public:
+ TextInputProcessor();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITEXTINPUTPROCESSOR
+
+ // TextEventDispatcherListener
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
+ NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ /**
+ * TextInputProcessor manages modifier key state. E.g., when it dispatches
+ * a modifier keydown event, activates proper modifier state and when it
+ * dispatches a modifier keyup event, inactivates proper modifier state.
+ * This returns all active modifiers in the instance.
+ */
+ Modifiers GetActiveModifiers() const {
+ return mModifierKeyDataArray ? mModifierKeyDataArray->GetActiveModifiers()
+ : MODIFIER_NONE;
+ }
+
+ /**
+ * This begins transaction for fuzzing. This must be called only by
+ * FuzzingFunctions since this skips the permission check.
+ * See explanation of nsITextInputProcessor::BeginInputTransaction() for
+ * the detail.
+ */
+ nsresult BeginInputTransactionForFuzzing(
+ nsPIDOMWindowInner* aWindow, nsITextInputProcessorCallback* aCallback,
+ bool* aSucceeded);
+
+ /**
+ * The following Keydown() and KeyUp() are same as nsITextInputProcessor's
+ * same name methods except the type of event class. See explanation in
+ * nsITextInputProcessor for the detail.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult Keydown(const WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags,
+ uint32_t* aConsumedFlags = nullptr);
+ nsresult Keyup(const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
+ bool* aDoDefault = nullptr);
+
+ /**
+ * GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout() returns CodeNameIndex
+ * of a printable key which is in usual keyboard of the platform and when
+ * active keyboard layout is US-English.
+ * Note that this does not aware of option key mapping on macOS.
+ *
+ * @param aKeyValue The key value. Must be a character which can
+ * be inputted with US-English keyboard layout.
+ * @param aLocation The location of the key. This is important
+ * to distinguish whether the key is in Standard
+ * or Numpad. If this is not some, treated as
+ * Standard.
+ * @return Returns CODE_NAME_INDEX_UNKNOWN if there is
+ * no proper key.
+ */
+ static CodeNameIndex GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(
+ const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation);
+
+ /**
+ * GuessKeyCodeOfPrintableKeyInUSEnglishLayout() returns a key code value
+ * of a printable key which is in usual keyboard of the platform and when
+ * active keyboard layout is US-English.
+ * Note that this does not aware of option key mapping on macOS.
+ *
+ * @param aKeyValue The key value. Must be a character which can
+ * be inputted with US-English keyboard layout.
+ * @param aLocation The location of the key. This is important
+ * to distinguish whether the key is in Standard
+ * or Numpad. If this is not some, treated as
+ * Standard.
+ * @return Returns 0 if there is no proper key to input
+ * aKeyValue with US-English keyboard layout.
+ */
+ static uint32_t GuessKeyCodeOfPrintableKeyInUSEnglishLayout(
+ const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation);
+
+ protected:
+ virtual ~TextInputProcessor();
+
+ private:
+ bool IsComposing() const;
+ nsresult BeginInputTransactionInternal(
+ mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
+ bool aForTests, bool& aSucceeded);
+ MOZ_CAN_RUN_SCRIPT nsresult CommitCompositionInternal(
+ const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+ uint32_t aKeyFlags = 0, const nsAString* aCommitString = nullptr,
+ bool* aSucceeded = nullptr);
+ MOZ_CAN_RUN_SCRIPT nsresult
+ CancelCompositionInternal(const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+ uint32_t aKeyFlags = 0);
+ MOZ_CAN_RUN_SCRIPT nsresult
+ KeydownInternal(const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
+ bool aAllowToDispatchKeypress, uint32_t& aConsumedFlags);
+ nsresult KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags, bool& aDoDefault);
+ nsresult IsValidStateForComposition();
+ void UnlinkFromTextEventDispatcher();
+ nsresult PrepareKeyboardEventToDispatch(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags);
+ /**
+ * InitEditCommands() initializes edit commands of aKeyboardEvent.
+ * This must be called only in a content process, and aKeyboardEvent must
+ * be used only for `eKeyPress` event.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ InitEditCommands(WidgetKeyboardEvent& aKeyboardEvent) const;
+
+ bool IsValidEventTypeForComposition(
+ const WidgetKeyboardEvent& aKeyboardEvent) const;
+ nsresult PrepareKeyboardEventForComposition(
+ dom::KeyboardEvent* aDOMKeyEvent, uint32_t& aKeyFlags,
+ uint8_t aOptionalArgc, WidgetKeyboardEvent*& aKeyboardEvent);
+
+ struct EventDispatcherResult {
+ nsresult mResult;
+ bool mDoDefault;
+ bool mCanContinue;
+
+ EventDispatcherResult()
+ : mResult(NS_OK), mDoDefault(true), mCanContinue(true) {}
+ };
+ MOZ_CAN_RUN_SCRIPT EventDispatcherResult MaybeDispatchKeydownForComposition(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags);
+ EventDispatcherResult MaybeDispatchKeyupForComposition(
+ const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags);
+
+ /**
+ * AutoPendingCompositionResetter guarantees to clear all pending composition
+ * data in its destructor.
+ */
+ class MOZ_STACK_CLASS AutoPendingCompositionResetter {
+ public:
+ explicit AutoPendingCompositionResetter(TextInputProcessor* aTIP);
+ ~AutoPendingCompositionResetter();
+
+ private:
+ RefPtr<TextInputProcessor> mTIP;
+ };
+
+ /**
+ * TextInputProcessor manages modifier state both with .key and .code.
+ * For example, left shift key up shouldn't cause inactivating shift state
+ * while right shift key is being pressed.
+ */
+ struct ModifierKeyData {
+ // One of modifier key name
+ KeyNameIndex mKeyNameIndex;
+ // Any code name is allowed.
+ CodeNameIndex mCodeNameIndex;
+ // A modifier key flag which is activated by the key.
+ Modifiers mModifier;
+
+ explicit ModifierKeyData(const WidgetKeyboardEvent& aKeyboardEvent);
+
+ bool operator==(const ModifierKeyData& aOther) const {
+ return mKeyNameIndex == aOther.mKeyNameIndex &&
+ mCodeNameIndex == aOther.mCodeNameIndex;
+ }
+ };
+
+ class ModifierKeyDataArray : public nsTArray<ModifierKeyData> {
+ NS_INLINE_DECL_REFCOUNTING(ModifierKeyDataArray)
+
+ public:
+ Modifiers GetActiveModifiers() const;
+ void ActivateModifierKey(const ModifierKeyData& aModifierKeyData);
+ void InactivateModifierKey(const ModifierKeyData& aModifierKeyData);
+ void ToggleModifierKey(const ModifierKeyData& aModifierKeyData);
+
+ private:
+ virtual ~ModifierKeyDataArray() = default;
+ };
+
+ void EnsureModifierKeyDataArray() {
+ if (mModifierKeyDataArray) {
+ return;
+ }
+ mModifierKeyDataArray = new ModifierKeyDataArray();
+ }
+ void ActivateModifierKey(const ModifierKeyData& aModifierKeyData) {
+ EnsureModifierKeyDataArray();
+ mModifierKeyDataArray->ActivateModifierKey(aModifierKeyData);
+ }
+ void InactivateModifierKey(const ModifierKeyData& aModifierKeyData) {
+ if (!mModifierKeyDataArray) {
+ return;
+ }
+ mModifierKeyDataArray->InactivateModifierKey(aModifierKeyData);
+ }
+ void ToggleModifierKey(const ModifierKeyData& aModifierKeyData) {
+ EnsureModifierKeyDataArray();
+ mModifierKeyDataArray->ToggleModifierKey(aModifierKeyData);
+ }
+
+ TextEventDispatcher* mDispatcher; // [Weak]
+ nsCOMPtr<nsITextInputProcessorCallback> mCallback;
+ RefPtr<ModifierKeyDataArray> mModifierKeyDataArray;
+
+ bool mForTests;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_dom_textinputprocessor_h_
diff --git a/dom/base/ThirdPartyUtil.cpp b/dom/base/ThirdPartyUtil.cpp
new file mode 100644
index 0000000000..b8c0636834
--- /dev/null
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ThirdPartyUtil.h"
+
+#include <cstdint>
+#include "MainThreadUtils.h"
+#include "mozIDOMWindow.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsEffectiveTLDService.h"
+#include "nsError.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsILoadContext.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTLiteralString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
+
+//
+// MOZ_LOG=thirdPartyUtil:5
+//
+static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
+#undef LOG
+#define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
+
+static mozilla::StaticRefPtr<ThirdPartyUtil> gService;
+
+// static
+void ThirdPartyUtil::Startup() {
+ nsCOMPtr<mozIThirdPartyUtil> tpu;
+ if (NS_WARN_IF(!(tpu = do_GetService(THIRDPARTYUTIL_CONTRACTID)))) {
+ NS_WARNING("Failed to get third party util!");
+ }
+}
+
+nsresult ThirdPartyUtil::Init() {
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
+
+ MOZ_ASSERT(!gService);
+ gService = this;
+ mozilla::ClearOnShutdown(&gService);
+
+ mTLDService = nsEffectiveTLDService::GetInstance();
+ return mTLDService ? NS_OK : NS_ERROR_FAILURE;
+}
+
+ThirdPartyUtil::~ThirdPartyUtil() { gService = nullptr; }
+
+// static
+ThirdPartyUtil* ThirdPartyUtil::GetInstance() {
+ if (gService) {
+ return gService;
+ }
+ nsCOMPtr<mozIThirdPartyUtil> tpuService =
+ mozilla::components::ThirdPartyUtil::Service();
+ if (!tpuService) {
+ return nullptr;
+ }
+ MOZ_ASSERT(
+ gService,
+ "gService must have been initialized in nsEffectiveTLDService::Init");
+ return gService;
+}
+
+// Determine if aFirstDomain is a different base domain to aSecondURI; or, if
+// the concept of base domain does not apply, determine if the two hosts are not
+// string-identical.
+nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
+ nsIURI* aSecondURI,
+ bool* aResult) {
+ if (!aSecondURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // BlobURLs are always first-party.
+ if (aSecondURI->SchemeIs("blob")) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Get the base domain for aSecondURI.
+ nsAutoCString secondDomain;
+ nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
+ LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(),
+ secondDomain.get()));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = IsThirdPartyInternal(aFirstDomain, secondDomain);
+ return NS_OK;
+}
+
+nsCString ThirdPartyUtil::GetBaseDomainFromWindow(nsPIDOMWindowOuter* aWindow) {
+ mozilla::dom::Document* doc = aWindow ? aWindow->GetExtantDoc() : nullptr;
+
+ if (!doc) {
+ return ""_ns;
+ }
+
+ return doc->GetBaseDomain();
+}
+
+NS_IMETHODIMP
+ThirdPartyUtil::GetPrincipalFromWindow(mozIDOMWindowProxy* aWin,
+ nsIPrincipal** result) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin);
+ if (!scriptObjPrin) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin = scriptObjPrin->GetPrincipal();
+ if (!prin) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ prin.forget(result);
+ return NS_OK;
+}
+
+// Get the URI associated with a window.
+NS_IMETHODIMP
+ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) {
+ nsCOMPtr<nsIPrincipal> prin;
+ nsresult rv = GetPrincipalFromWindow(aWin, getter_AddRefs(prin));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (prin->GetIsNullPrincipal()) {
+ LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ auto* basePrin = BasePrincipal::Cast(prin);
+ return basePrin->GetURI(result);
+}
+
+// Determine if aFirstURI is third party with respect to aSecondURI. See docs
+// for mozIThirdPartyUtil.
+NS_IMETHODIMP
+ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI,
+ bool* aResult) {
+ NS_ENSURE_ARG(aFirstURI);
+ NS_ENSURE_ARG(aSecondURI);
+ NS_ASSERTION(aResult, "null outparam pointer");
+
+ nsAutoCString firstHost;
+ nsresult rv = GetBaseDomain(aFirstURI, firstHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return IsThirdPartyInternal(firstHost, aSecondURI, aResult);
+}
+
+// If the optional aURI is provided, determine whether aWindow is foreign with
+// respect to aURI. If the optional aURI is not provided, determine whether the
+// given "window hierarchy" is third party. See docs for mozIThirdPartyUtil.
+NS_IMETHODIMP
+ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI,
+ bool* aResult) {
+ NS_ENSURE_ARG(aWindow);
+ NS_ASSERTION(aResult, "null outparam pointer");
+
+ bool result;
+
+ // Ignore about:blank URIs here since they have no domain and attempting to
+ // compare against them will fail.
+ if (aURI && !NS_IsAboutBlank(aURI)) {
+ nsCOMPtr<nsIPrincipal> prin;
+ nsresult rv = GetPrincipalFromWindow(aWindow, getter_AddRefs(prin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Determine whether aURI is foreign with respect to the current principal.
+ rv = prin->IsThirdPartyURI(aURI, &result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (result) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ nsPIDOMWindowOuter* current = nsPIDOMWindowOuter::From(aWindow);
+ auto* const browsingContext = current->GetBrowsingContext();
+ MOZ_ASSERT(browsingContext);
+
+ WindowContext* wc = browsingContext->GetCurrentWindowContext();
+ if (NS_WARN_IF(!wc)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ *aResult = wc->GetIsThirdPartyWindow();
+ return NS_OK;
+}
+
+nsresult ThirdPartyUtil::IsThirdPartyGlobal(
+ mozilla::dom::WindowGlobalParent* aWindowGlobal, bool* aResult) {
+ NS_ENSURE_ARG(aWindowGlobal);
+ NS_ASSERTION(aResult, "null outparam pointer");
+
+ auto* currentWGP = aWindowGlobal;
+ do {
+ MOZ_ASSERT(currentWGP->BrowsingContext());
+ if (currentWGP->BrowsingContext()->IsTop()) {
+ *aResult = false;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIPrincipal> currentPrincipal = currentWGP->DocumentPrincipal();
+ RefPtr<WindowGlobalParent> parent =
+ currentWGP->BrowsingContext()->GetEmbedderWindowGlobal();
+ if (!parent) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIPrincipal> parentPrincipal = parent->DocumentPrincipal();
+ nsresult rv =
+ currentPrincipal->IsThirdPartyPrincipal(parentPrincipal, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*aResult) {
+ return NS_OK;
+ }
+
+ currentWGP = parent;
+ } while (true);
+}
+
+// Determine if the URI associated with aChannel or any URI of the window
+// hierarchy associated with the channel is foreign with respect to aSecondURI.
+// See docs for mozIThirdPartyUtil.
+NS_IMETHODIMP
+ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel, nsIURI* aURI,
+ bool* aResult) {
+ LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel));
+ NS_ENSURE_ARG(aChannel);
+ NS_ASSERTION(aResult, "null outparam pointer");
+
+ nsresult rv;
+ bool doForce = false;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(aChannel);
+ if (httpChannelInternal) {
+ uint32_t flags = 0;
+ // Avoid checking the return value here since some channel implementations
+ // may return NS_ERROR_NOT_IMPLEMENTED.
+ mozilla::Unused << httpChannelInternal->GetThirdPartyFlags(&flags);
+
+ doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+
+ // If aURI was not supplied, and we're forcing, then we're by definition
+ // not foreign. If aURI was supplied, we still want to check whether it's
+ // foreign with respect to the channel URI. (The forcing only applies to
+ // whatever window hierarchy exists above the channel.)
+ if (doForce && !aURI) {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+
+ bool parentIsThird = false;
+ nsAutoCString channelDomain;
+
+ // Obtain the URI from the channel, and its base domain.
+ nsCOMPtr<nsIURI> channelURI;
+ rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ BasePrincipal* loadingPrincipal = nullptr;
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (!doForce) {
+ parentIsThird = loadInfo->GetIsInThirdPartyContext();
+ if (!parentIsThird && loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ // Check if the channel itself is third-party to its own requestor.
+ // Unfortunately, we have to go through the loading principal.
+ loadingPrincipal = BasePrincipal::Cast(loadInfo->GetLoadingPrincipal());
+ }
+ }
+
+ // Special consideration must be done for about:blank URIs because those
+ // inherit the principal from the parent context. For them, let's consider the
+ // principal URI.
+ if (NS_IsAboutBlank(channelURI)) {
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ loadInfo->FindPrincipalToInherit(aChannel);
+ if (!principalToInherit) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ rv = principalToInherit->GetBaseDomain(channelDomain);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (loadingPrincipal) {
+ rv = loadingPrincipal->IsThirdPartyPrincipal(principalToInherit,
+ &parentIsThird);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else {
+ rv = GetBaseDomain(channelURI, channelDomain);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (loadingPrincipal) {
+ rv = loadingPrincipal->IsThirdPartyURI(channelURI, &parentIsThird);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ // If we're not comparing to a URI, we have our answer. Otherwise, if
+ // parentIsThird, we're not forcing and we know that we're a third-party
+ // request.
+ if (!aURI || parentIsThird) {
+ *aResult = parentIsThird;
+ return NS_OK;
+ }
+
+ // Determine whether aURI is foreign with respect to channelURI.
+ return IsThirdPartyInternal(channelDomain, aURI, aResult);
+}
+
+NS_IMETHODIMP
+ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel,
+ nsIURI* aURIBeingLoaded,
+ mozIDOMWindowProxy** aWin) {
+ NS_ENSURE_ARG(aWin);
+
+ // Find the associated window and its parent window.
+ nsCOMPtr<nsILoadContext> ctx;
+ NS_QueryNotificationCallbacks(aChannel, ctx);
+ if (!ctx) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ ctx->GetAssociatedWindow(getter_AddRefs(window));
+ if (!window) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> top =
+ nsGlobalWindowOuter::Cast(window)
+ ->GetTopExcludingExtensionAccessibleContentFrames(aURIBeingLoaded);
+ top.forget(aWin);
+ return NS_OK;
+}
+
+// Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+// "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+// dot may be present. If aHostURI is an IP address, an alias such as
+// 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
+// be the exact host. The result of this function should only be used in exact
+// string comparisons, since substring comparisons will not be valid for the
+// special cases elided above.
+NS_IMETHODIMP
+ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) {
+ if (!aHostURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Get the base domain. this will fail if the host contains a leading dot,
+ // more than one trailing dot, or is otherwise malformed.
+ nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
+ // such as 'co.uk', or the empty string. Uses the normalized host in such
+ // cases.
+ rv = aHostURI->GetAsciiHost(aBaseDomain);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail.
+ if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
+ return NS_ERROR_INVALID_ARG;
+
+ // Reject any URIs without a host that aren't file:// URIs. This makes it the
+ // only way we can get a base domain consisting of the empty string, which
+ // means we can safely perform foreign tests on such URIs where "not foreign"
+ // means "the involved URIs are all file://".
+ if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThirdPartyUtil::GetBaseDomainFromSchemeHost(const nsACString& aScheme,
+ const nsACString& aAsciiHost,
+ nsACString& aBaseDomain) {
+ MOZ_DIAGNOSTIC_ASSERT(IsAscii(aAsciiHost));
+
+ // Get the base domain. this will fail if the host contains a leading dot,
+ // more than one trailing dot, or is otherwise malformed.
+ nsresult rv = mTLDService->GetBaseDomainFromHost(aAsciiHost, 0, aBaseDomain);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // aMozURL is either an IP address, an alias such as 'localhost', an eTLD
+ // such as 'co.uk', or the empty string. Uses the normalized host in such
+ // cases.
+ aBaseDomain = aAsciiHost;
+ rv = NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // aMozURL (and thus aBaseDomain) may be the string '.'. If so, fail.
+ if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
+ return NS_ERROR_INVALID_ARG;
+
+ // Reject any URLs without a host that aren't file:// URLs. This makes it the
+ // only way we can get a base domain consisting of the empty string, which
+ // means we can safely perform foreign tests on such URLs where "not foreign"
+ // means "the involved URLs are all file://".
+ if (aBaseDomain.IsEmpty() && !aScheme.EqualsLiteral("file")) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(ThirdPartyAnalysisResult)
+ThirdPartyUtil::AnalyzeChannel(nsIChannel* aChannel, bool aNotify, nsIURI* aURI,
+ RequireThirdPartyCheck aRequireThirdPartyCheck,
+ uint32_t* aRejectedReason) {
+ MOZ_ASSERT_IF(aNotify, aRejectedReason);
+
+ ThirdPartyAnalysisResult result;
+
+ nsCOMPtr<nsIURI> uri;
+ if (!aURI && aChannel) {
+ aChannel->GetURI(getter_AddRefs(uri));
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel ? aChannel->LoadInfo() : nullptr;
+
+ bool isForeign = true;
+ if (aChannel &&
+ (!aRequireThirdPartyCheck || aRequireThirdPartyCheck(loadInfo))) {
+ IsThirdPartyChannel(aChannel, aURI ? aURI : uri.get(), &isForeign);
+ }
+ if (isForeign) {
+ result += ThirdPartyAnalysis::IsForeign;
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(aChannel);
+ if (classifiedChannel) {
+ if (classifiedChannel->IsThirdPartyTrackingResource()) {
+ result += ThirdPartyAnalysis::IsThirdPartyTrackingResource;
+ }
+ if (classifiedChannel->IsThirdPartySocialTrackingResource()) {
+ result += ThirdPartyAnalysis::IsThirdPartySocialTrackingResource;
+ }
+
+ // Check first-party storage access even for non-tracking resources, since
+ // we will need the result when computing the access rights for the reject
+ // foreign cookie behavior mode.
+
+ // If the caller has requested third-party checks, we will only perform the
+ // storage access check once we know we're in the third-party context.
+ bool performStorageChecks =
+ aRequireThirdPartyCheck ? result.contains(ThirdPartyAnalysis::IsForeign)
+ : true;
+ if (performStorageChecks &&
+ ShouldAllowAccessFor(aChannel, aURI ? aURI : uri.get(),
+ aRejectedReason)) {
+ result += ThirdPartyAnalysis::IsStorageAccessPermissionGranted;
+ }
+
+ if (aNotify && !result.contains(
+ ThirdPartyAnalysis::IsStorageAccessPermissionGranted)) {
+ ContentBlockingNotifier::OnDecision(
+ aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
+ *aRejectedReason);
+ }
+ }
+
+ return result;
+}
diff --git a/dom/base/ThirdPartyUtil.h b/dom/base/ThirdPartyUtil.h
new file mode 100644
index 0000000000..39ba91a2e5
--- /dev/null
+++ b/dom/base/ThirdPartyUtil.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ThirdPartyUtil_h__
+#define ThirdPartyUtil_h__
+
+#include <cstdint>
+#include "ErrorList.h"
+#include "mozIThirdPartyUtil.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupports.h"
+#include "nsString.h"
+
+class mozIDOMWindowProxy;
+class nsEffectiveTLDService;
+class nsIChannel;
+class nsIPrincipal;
+class nsIURI;
+class nsPIDOMWindowOuter;
+
+namespace mozilla::dom {
+class WindowGlobalParent;
+} // namespace mozilla::dom
+
+class ThirdPartyUtil final : public mozIThirdPartyUtil {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZITHIRDPARTYUTIL
+
+ nsresult Init();
+
+ static void Startup();
+ static ThirdPartyUtil* GetInstance();
+
+ nsresult IsThirdPartyGlobal(mozilla::dom::WindowGlobalParent* aWindowGlobal,
+ bool* aResult);
+
+ private:
+ ~ThirdPartyUtil();
+
+ bool IsThirdPartyInternal(const nsCString& aFirstDomain,
+ const nsCString& aSecondDomain) {
+ // Check strict equality.
+ return aFirstDomain != aSecondDomain;
+ }
+ nsresult IsThirdPartyInternal(const nsCString& aFirstDomain,
+ nsIURI* aSecondURI, bool* aResult);
+
+ nsCString GetBaseDomainFromWindow(nsPIDOMWindowOuter* aWindow);
+
+ RefPtr<nsEffectiveTLDService> mTLDService;
+};
+
+#endif
diff --git a/dom/base/Timeout.cpp b/dom/base/Timeout.cpp
new file mode 100644
index 0000000000..1619231007
--- /dev/null
+++ b/dom/base/Timeout.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Timeout.h"
+
+#include "mozilla/dom/TimeoutManager.h"
+#include "nsGlobalWindowInner.h"
+#include "GeckoProfiler.h"
+
+namespace mozilla::dom {
+
+Timeout::Timeout()
+ : mTimeoutId(0),
+ mFiringId(TimeoutManager::InvalidFiringId),
+#ifdef DEBUG
+ mFiringIndex(-1),
+#endif
+ mPopupState(PopupBlocker::openAllowed),
+ mReason(Reason::eTimeoutOrInterval),
+ mNestingLevel(0),
+ mCleared(false),
+ mRunning(false),
+ mIsInterval(false) {
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Timeout)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptHandler)
+ if (tmp->isInList()) {
+ tmp->remove();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Timeout)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void Timeout::SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
+ const TimeDuration& aDelay) {
+ MOZ_DIAGNOSTIC_ASSERT(mWindow);
+ mSubmitTime = aBaseTime;
+
+ mSubmitTime = aBaseTime;
+ if (profiler_is_active()) {
+ mCause = profiler_capture_backtrace();
+ }
+
+ // If we are frozen simply set mTimeRemaining to be the "time remaining" in
+ // the timeout (i.e., the interval itself). This will be used to create a
+ // new mWhen time when the window is thawed. The end effect is that time does
+ // not appear to pass for frozen windows.
+ if (mWindow->IsFrozen()) {
+ mWhen = TimeStamp();
+ mTimeRemaining = aDelay;
+ return;
+ }
+
+ // Since we are not frozen we must set a precise mWhen target wakeup
+ // time. Even if we are suspended we want to use this target time so
+ // that it appears time passes while suspended.
+ mWhen = aBaseTime + aDelay;
+ mTimeRemaining = TimeDuration(0);
+}
+
+const TimeStamp& Timeout::When() const {
+ MOZ_DIAGNOSTIC_ASSERT(!mWhen.IsNull());
+ // Note, mWindow->IsFrozen() can be true here. The Freeze() method calls
+ // When() to calculate the delay to populate mTimeRemaining.
+ return mWhen;
+}
+
+const TimeStamp& Timeout::SubmitTime() const { return mSubmitTime; }
+
+const TimeDuration& Timeout::TimeRemaining() const {
+ MOZ_DIAGNOSTIC_ASSERT(mWhen.IsNull());
+ // Note, mWindow->IsFrozen() can be false here. The Thaw() method calls
+ // TimeRemaining() to calculate the new When() value.
+ return mTimeRemaining;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/Timeout.h b/dom/base/Timeout.h
new file mode 100644
index 0000000000..d3ac15a2da
--- /dev/null
+++ b/dom/base/Timeout.h
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_timeout_h
+#define mozilla_dom_timeout_h
+
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimeStamp.h"
+#include "nsGlobalWindowInner.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "GeckoProfiler.h"
+
+namespace mozilla::dom {
+
+/*
+ * Timeout struct that holds information about each script
+ * timeout. Holds a strong reference to an nsITimeoutHandler, which
+ * abstracts the language specific cruft.
+ */
+class Timeout final : protected LinkedListElement<RefPtr<Timeout>> {
+ public:
+ Timeout();
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
+
+ enum class Reason : uint8_t {
+ eTimeoutOrInterval,
+ eIdleCallbackTimeout,
+ eAbortSignalTimeout,
+ eDelayedWebTaskTimeout,
+ };
+
+ struct TimeoutIdAndReason {
+ uint32_t mId;
+ Reason mReason;
+ };
+
+ class TimeoutHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const TimeoutIdAndReason& KeyType;
+ typedef const TimeoutIdAndReason* KeyTypePointer;
+
+ explicit TimeoutHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ TimeoutHashKey(TimeoutHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)),
+ mValue(std::move(aOther.mValue)) {}
+ ~TimeoutHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return aKey->mId == mValue.mId && aKey->mReason == mValue.mReason;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return aKey->mId | (static_cast<uint8_t>(aKey->mReason) << 31);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const TimeoutIdAndReason mValue;
+ };
+
+ class TimeoutSet : public nsTHashMap<TimeoutHashKey, Timeout*> {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(TimeoutSet);
+
+ private:
+ ~TimeoutSet() = default;
+ };
+
+ void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
+ const TimeDuration& aDelay);
+
+ // Can only be called when not frozen.
+ const TimeStamp& When() const;
+
+ const TimeStamp& SubmitTime() const;
+
+ // Can only be called when frozen.
+ const TimeDuration& TimeRemaining() const;
+
+ void SetTimeoutContainer(TimeoutSet* aTimeouts) {
+ MOZ_ASSERT(mTimeoutId != 0);
+ TimeoutIdAndReason key = {mTimeoutId, mReason};
+ if (mTimeouts) {
+ mTimeouts->Remove(key);
+ }
+ mTimeouts = aTimeouts;
+ if (mTimeouts) {
+ mTimeouts->InsertOrUpdate(key, this);
+ }
+ }
+
+ // Override some LinkedListElement methods so that remove()
+ // calls can call SetTimeoutContainer.
+ Timeout* getNext() { return LinkedListElement<RefPtr<Timeout>>::getNext(); }
+
+ void setNext(Timeout* aNext) {
+ return LinkedListElement<RefPtr<Timeout>>::setNext(aNext);
+ }
+
+ Timeout* getPrevious() {
+ return LinkedListElement<RefPtr<Timeout>>::getPrevious();
+ }
+
+ void remove() {
+ SetTimeoutContainer(nullptr);
+ LinkedListElement<RefPtr<Timeout>>::remove();
+ }
+
+ UniquePtr<ProfileChunkedBuffer> TakeProfilerBacktrace() {
+ return std::move(mCause);
+ }
+
+ private:
+ // mWhen and mTimeRemaining can't be in a union, sadly, because they
+ // have constructors.
+ // Nominal time to run this timeout. Use only when timeouts are not
+ // frozen.
+ TimeStamp mWhen;
+
+ // Remaining time to wait. Used only when timeouts are frozen.
+ TimeDuration mTimeRemaining;
+
+ // Time that the timeout started, restarted, or was frozen. Useful for
+ // logging time from (virtual) start of a timer until the time it fires
+ // (or is cancelled, etc)
+ TimeStamp mSubmitTime;
+
+ ~Timeout() { SetTimeoutContainer(nullptr); }
+
+ public:
+ // Public member variables in this section. Please don't add to this list
+ // or mix methods with these. The interleaving public/private sections
+ // is necessary as we migrate members to private while still trying to
+ // keep decent binary packing.
+
+ // Window for which this timeout fires
+ RefPtr<nsGlobalWindowInner> mWindow;
+
+ // The language-specific information about the callback.
+ RefPtr<TimeoutHandler> mScriptHandler;
+
+ RefPtr<TimeoutSet> mTimeouts;
+
+ // Interval
+ TimeDuration mInterval;
+
+ UniquePtr<ProfileChunkedBuffer> mCause;
+
+ // Returned as value of setTimeout()
+ uint32_t mTimeoutId;
+
+ // Identifies which firing level this Timeout is being processed in
+ // when sync loops trigger nested firing.
+ uint32_t mFiringId;
+
+#ifdef DEBUG
+ int64_t mFiringIndex;
+#endif
+
+ // The popup state at timeout creation time if not created from
+ // another timeout
+ PopupBlocker::PopupControlState mPopupState;
+
+ // Used to allow several reasons for setting a timeout, where each
+ // 'Reason' value is using a possibly overlapping set of id:s.
+ Reason mReason;
+
+ // Between 0 and DOM_CLAMP_TIMEOUT_NESTING_LEVEL. Currently we don't
+ // care about nesting levels beyond that value.
+ uint8_t mNestingLevel;
+
+ // True if the timeout was cleared
+ bool mCleared;
+
+ // True if this is one of the timeouts that are currently running
+ bool mRunning;
+
+ // True if this is a repeating/interval timer
+ bool mIsInterval;
+
+ protected:
+ friend class LinkedList<RefPtr<Timeout>>;
+ friend class LinkedListElement<RefPtr<Timeout>>;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_timeout_h
diff --git a/dom/base/TimeoutBudgetManager.cpp b/dom/base/TimeoutBudgetManager.cpp
new file mode 100644
index 0000000000..0fb9597df8
--- /dev/null
+++ b/dom/base/TimeoutBudgetManager.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimeoutBudgetManager.h"
+
+#include "mozilla/dom/Timeout.h"
+
+namespace mozilla::dom {
+
+/* static */ TimeoutBudgetManager& TimeoutBudgetManager::Get() {
+ static TimeoutBudgetManager gTimeoutBudgetManager;
+ return gTimeoutBudgetManager;
+}
+
+void TimeoutBudgetManager::StartRecording(const TimeStamp& aNow) {
+ mStart = aNow;
+}
+
+void TimeoutBudgetManager::StopRecording() { mStart = TimeStamp(); }
+
+TimeDuration TimeoutBudgetManager::RecordExecution(const TimeStamp& aNow,
+ const Timeout* aTimeout) {
+ if (!mStart) {
+ // If we've started a sync operation mStart might be null, in
+ // which case we should not record this piece of execution.
+ return TimeDuration();
+ }
+
+ return aNow - mStart;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/TimeoutBudgetManager.h b/dom/base/TimeoutBudgetManager.h
new file mode 100644
index 0000000000..2287537c24
--- /dev/null
+++ b/dom/base/TimeoutBudgetManager.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_timeoutbudgetmanager_h
+#define mozilla_dom_timeoutbudgetmanager_h
+
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla::dom {
+
+class Timeout;
+
+class TimeoutBudgetManager {
+ public:
+ static TimeoutBudgetManager& Get();
+ void StartRecording(const TimeStamp& aNow);
+ void StopRecording();
+ TimeDuration RecordExecution(const TimeStamp& aNow, const Timeout* aTimeout);
+
+ private:
+ TimeoutBudgetManager() = default;
+
+ TimeStamp mStart;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_timeoutbudgetmanager_h
diff --git a/dom/base/TimeoutExecutor.cpp b/dom/base/TimeoutExecutor.cpp
new file mode 100644
index 0000000000..466071bf0a
--- /dev/null
+++ b/dom/base/TimeoutExecutor.cpp
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimeoutExecutor.h"
+
+#include "mozilla/EventQueue.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIEventTarget.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+extern mozilla::LazyLogModule gTimeoutLog;
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
+
+TimeoutExecutor::~TimeoutExecutor() {
+ // The TimeoutManager should keep the Executor alive until its destroyed,
+ // and then call Shutdown() explicitly.
+ MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Shutdown);
+ MOZ_DIAGNOSTIC_ASSERT(!mOwner);
+ MOZ_DIAGNOSTIC_ASSERT(!mTimer);
+}
+
+nsresult TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
+ const TimeStamp& aNow) {
+ MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
+ MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
+
+ nsresult rv;
+ if (mIsIdleQueue) {
+ RefPtr<TimeoutExecutor> runnable(this);
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Starting IdleDispatch runnable"));
+ rv = NS_DispatchToCurrentThreadQueue(runnable.forget(), mMaxIdleDeferMS,
+ EventQueuePriority::DeferredTimers);
+ } else {
+ rv = mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMode = Mode::Immediate;
+ mDeadline = aDeadline;
+
+ return NS_OK;
+}
+
+nsresult TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
+ const TimeStamp& aNow,
+ const TimeDuration& aMinDelay) {
+ MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
+ MOZ_DIAGNOSTIC_ASSERT(!aMinDelay.IsZero() ||
+ aDeadline > (aNow + mAllowedEarlyFiringTime));
+
+ nsresult rv = NS_OK;
+
+ if (mIsIdleQueue) {
+ // Nothing goes into the idletimeouts list if it wasn't going to
+ // fire at that time, so we can always schedule idle-execution of
+ // these immediately
+ return ScheduleImmediate(aNow, aNow);
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer(mOwner->EventTarget());
+ NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t earlyMicros = 0;
+ MOZ_ALWAYS_SUCCEEDS(
+ mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
+ mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
+ // Re-evaluate if we should have scheduled this immediately
+ if (aDeadline <= (aNow + mAllowedEarlyFiringTime)) {
+ return ScheduleImmediate(aDeadline, aNow);
+ }
+ } else {
+ // Always call Cancel() in case we are re-using a timer.
+ rv = mTimer->Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Calculate the delay based on the deadline and current time. If we have
+ // a minimum delay set then clamp to that value.
+ //
+ // Note, we don't actually adjust our mDeadline for the minimum delay, just
+ // the nsITimer value. This is necessary to avoid lots of needless
+ // rescheduling if more deadlines come in between now and the minimum delay
+ // firing time.
+ TimeDuration delay = TimeDuration::Max(aMinDelay, aDeadline - aNow);
+
+ // Note, we cannot use the normal nsITimer init methods that take
+ // integer milliseconds. We need higher precision. Consider this
+ // situation:
+ //
+ // 1. setTimeout(f, 1);
+ // 2. do some work for 500us
+ // 3. setTimeout(g, 1);
+ //
+ // This should fire f() and g() 500us apart.
+ //
+ // In the past worked because each setTimeout() got its own nsITimer. The 1ms
+ // was preserved and passed through to nsITimer which converted it to a
+ // TimeStamp, etc.
+ //
+ // Now, however, there is only one nsITimer. We fire f() and then try to
+ // schedule a new nsITimer for g(). Its only 500us in the future, though. We
+ // must be able to pass this fractional value to nsITimer in order to get an
+ // accurate wakeup time.
+ rv = mTimer->InitHighResolutionWithCallback(this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMode = Mode::Delayed;
+ mDeadline = aDeadline;
+
+ return NS_OK;
+}
+
+nsresult TimeoutExecutor::Schedule(const TimeStamp& aDeadline,
+ const TimeDuration& aMinDelay) {
+ TimeStamp now(TimeStamp::Now());
+
+ // Schedule an immediate runnable if the desired deadline has passed
+ // or is slightly in the future. This is similar to how nsITimer will
+ // fire timers early based on the interval resolution.
+ if (aMinDelay.IsZero() && aDeadline <= (now + mAllowedEarlyFiringTime)) {
+ return ScheduleImmediate(aDeadline, now);
+ }
+
+ return ScheduleDelayed(aDeadline, now, aMinDelay);
+}
+
+nsresult TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline,
+ const TimeDuration& aMinDelay) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Immediate || mMode == Mode::Delayed);
+
+ if (aDeadline >= mDeadline) {
+ return NS_OK;
+ }
+
+ if (mMode == Mode::Immediate) {
+ // Don't reduce the deadline here as we want to execute the
+ // timer we originally scheduled even if its a few microseconds
+ // in the future.
+ return NS_OK;
+ }
+
+ Cancel();
+ return Schedule(aDeadline, aMinDelay);
+}
+
+void TimeoutExecutor::MaybeExecute() {
+ MOZ_DIAGNOSTIC_ASSERT(mMode != Mode::Shutdown && mMode != Mode::None);
+ MOZ_DIAGNOSTIC_ASSERT(mOwner);
+ MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
+
+ TimeStamp deadline(mDeadline);
+
+ // Sometimes nsITimer or canceled timers will fire too early. If this
+ // happens then just cap our deadline to our maximum time in the future
+ // and proceed. If there are no timers ready we will get rescheduled
+ // by TimeoutManager.
+ TimeStamp now(TimeStamp::Now());
+ TimeStamp limit = now + mAllowedEarlyFiringTime;
+ if (deadline > limit) {
+ deadline = limit;
+ }
+
+ Cancel();
+
+ mOwner->RunTimeout(now, deadline, mIsIdleQueue);
+}
+
+TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
+ uint32_t aMaxIdleDeferMS)
+ : mOwner(aOwner),
+ mIsIdleQueue(aIsIdleQueue),
+ mMaxIdleDeferMS(aMaxIdleDeferMS),
+ mMode(Mode::None) {
+ MOZ_DIAGNOSTIC_ASSERT(mOwner);
+}
+
+void TimeoutExecutor::Shutdown() {
+ mOwner = nullptr;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mMode = Mode::Shutdown;
+ mDeadline = TimeStamp();
+}
+
+nsresult TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline,
+ const TimeDuration& aMinDelay) {
+ MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull());
+
+ if (mMode == Mode::Shutdown) {
+ return NS_OK;
+ }
+
+ if (mMode == Mode::Immediate || mMode == Mode::Delayed) {
+ return MaybeReschedule(aDeadline, aMinDelay);
+ }
+
+ return Schedule(aDeadline, aMinDelay);
+}
+
+void TimeoutExecutor::Cancel() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mMode = Mode::None;
+ mDeadline = TimeStamp();
+}
+
+// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+// bug 1535398.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TimeoutExecutor::Run() {
+ // If the executor is canceled and then rescheduled its possible to get
+ // spurious executions here. Ignore these unless our current mode matches.
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("Running Immediate %stimers", mIsIdleQueue ? "Idle" : ""));
+ if (mMode == Mode::Immediate) {
+ MaybeExecute();
+ }
+ return NS_OK;
+}
+
+// MOZ_CAN_RUN_SCRIPT_BOUNDARY until nsITimerCallback::Notify is
+// MOZ_CAN_RUN_SCRIPT.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+TimeoutExecutor::Notify(nsITimer* aTimer) {
+ // If the executor is canceled and then rescheduled its possible to get
+ // spurious executions here. Ignore these unless our current mode matches.
+ if (mMode == Mode::Delayed) {
+ MaybeExecute();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TimeoutExecutor::GetName(nsACString& aNameOut) {
+ aNameOut.AssignLiteral("TimeoutExecutor Runnable");
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/TimeoutExecutor.h b/dom/base/TimeoutExecutor.h
new file mode 100644
index 0000000000..991ff8fe7f
--- /dev/null
+++ b/dom/base/TimeoutExecutor.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_timeoutexecutor_h
+#define mozilla_dom_timeoutexecutor_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsITimer.h"
+#include "nsINamed.h"
+
+namespace mozilla::dom {
+
+class TimeoutManager;
+
+class TimeoutExecutor final : public nsIRunnable,
+ public nsITimerCallback,
+ public nsINamed {
+ TimeoutManager* mOwner;
+ bool mIsIdleQueue;
+ nsCOMPtr<nsITimer> mTimer;
+ TimeStamp mDeadline;
+ uint32_t mMaxIdleDeferMS;
+
+ // Limits how far we allow timers to fire into the future from their
+ // deadline. Starts off at zero, but is then adjusted when we start
+ // using nsITimer. The nsITimer implementation may sometimes fire
+ // early and we should allow that to minimize additional wakeups.
+ TimeDuration mAllowedEarlyFiringTime;
+
+ // The TimeoutExecutor is repeatedly scheduled by the TimeoutManager
+ // to fire for the next soonest Timeout. Since the executor is re-used
+ // it needs to handle switching between a few states.
+ enum class Mode {
+ // None indicates the executor is idle. It may be scheduled or shutdown.
+ None,
+ // Immediate means the executor is scheduled to run a Timeout with a
+ // deadline that has already expired.
+ Immediate,
+ // Delayed means the executor is scheduled to run a Timeout with a
+ // deadline in the future.
+ Delayed,
+ // Shutdown means the TimeoutManager has been destroyed. Once this
+ // state is reached the executor cannot be scheduled again. If the
+ // executor is already dispatched as a runnable or held by a timer then
+ // we may still get a Run()/Notify() call which will be ignored.
+ Shutdown
+ };
+
+ Mode mMode;
+
+ ~TimeoutExecutor();
+
+ nsresult ScheduleImmediate(const TimeStamp& aDeadline, const TimeStamp& aNow);
+
+ nsresult ScheduleDelayed(const TimeStamp& aDeadline, const TimeStamp& aNow,
+ const TimeDuration& aMinDelay);
+
+ nsresult Schedule(const TimeStamp& aDeadline, const TimeDuration& aMinDelay);
+
+ nsresult MaybeReschedule(const TimeStamp& aDeadline,
+ const TimeDuration& aMinDelay);
+
+ MOZ_CAN_RUN_SCRIPT void MaybeExecute();
+
+ public:
+ TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
+ uint32_t aMaxIdleDeferMS);
+
+ void Shutdown();
+
+ nsresult MaybeSchedule(const TimeStamp& aDeadline,
+ const TimeDuration& aMinDelay);
+
+ void Cancel();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_timeoutexecutor_h
diff --git a/dom/base/TimeoutHandler.cpp b/dom/base/TimeoutHandler.cpp
new file mode 100644
index 0000000000..ddf2b6abd7
--- /dev/null
+++ b/dom/base/TimeoutHandler.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimeoutHandler.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsJSUtils.h"
+
+namespace mozilla::dom {
+
+//-----------------------------------------------------------------------------
+// TimeoutHandler
+//-----------------------------------------------------------------------------
+
+TimeoutHandler::TimeoutHandler(JSContext* aCx) : TimeoutHandler() {
+ nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
+}
+
+bool TimeoutHandler::Call(const char* /* unused */) { return false; }
+
+void TimeoutHandler::GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn) {
+ *aFileName = mFileName.get();
+ *aLineNo = mLineNo;
+ *aColumn = mColumn;
+}
+
+void TimeoutHandler::GetDescription(nsACString& aOutString) {
+ aOutString.AppendPrintf("<generic handler> (%s:%d:%d)", mFileName.get(),
+ mLineNo, mColumn);
+}
+
+//-----------------------------------------------------------------------------
+// ScriptTimeoutHandler
+//-----------------------------------------------------------------------------
+
+ScriptTimeoutHandler::ScriptTimeoutHandler(JSContext* aCx,
+ nsIGlobalObject* aGlobal,
+ const nsAString& aExpression)
+ : TimeoutHandler(aCx), mGlobal(aGlobal), mExpr(aExpression) {}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptTimeoutHandler)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptTimeoutHandler)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(ScriptTimeoutHandler)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ nsAutoCString name("ScriptTimeoutHandler");
+ name.AppendLiteral(" [");
+ name.Append(tmp->mFileName);
+ name.Append(':');
+ name.AppendInt(tmp->mLineNo);
+ name.Append(':');
+ name.AppendInt(tmp->mColumn);
+ name.Append(']');
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(ScriptTimeoutHandler, tmp->mRefCnt.get())
+ }
+
+ // If we need to make TimeoutHandler CCed, don't call its Traverse method
+ // here, otherwise we ends up report same object twice if logging is on. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1588208.
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptTimeoutHandler)
+
+void ScriptTimeoutHandler::GetDescription(nsACString& aOutString) {
+ if (mExpr.Length() > 15) {
+ aOutString.AppendPrintf(
+ "<string handler (truncated): \"%s...\"> (%s:%d:%d)",
+ NS_ConvertUTF16toUTF8(Substring(mExpr, 0, 13)).get(), mFileName.get(),
+ mLineNo, mColumn);
+ } else {
+ aOutString.AppendPrintf("<string handler: \"%s\"> (%s:%d:%d)",
+ NS_ConvertUTF16toUTF8(mExpr).get(), mFileName.get(),
+ mLineNo, mColumn);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CallbackTimeoutHandler
+//-----------------------------------------------------------------------------
+
+CallbackTimeoutHandler::CallbackTimeoutHandler(
+ JSContext* aCx, nsIGlobalObject* aGlobal, Function* aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments)
+ : TimeoutHandler(aCx), mGlobal(aGlobal), mFunction(aFunction) {
+ mozilla::HoldJSObjects(this);
+ mArgs = std::move(aArguments);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackTimeoutHandler)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackTimeoutHandler)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFunction)
+ tmp->ReleaseJSObjects();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CallbackTimeoutHandler)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ nsAutoCString name("CallbackTimeoutHandler");
+ JSObject* obj = tmp->mFunction->CallablePreserveColor();
+ JSFunction* fun =
+ JS_GetObjectFunction(js::UncheckedUnwrapWithoutExpose(obj));
+ if (fun && JS_GetFunctionId(fun)) {
+ JSLinearString* funId = JS_ASSERT_STRING_IS_LINEAR(JS_GetFunctionId(fun));
+ size_t size = 1 + JS_PutEscapedLinearString(nullptr, 0, funId, 0);
+ char* funIdName = new char[size];
+ if (funIdName) {
+ JS_PutEscapedLinearString(funIdName, size, funId, 0);
+ name.AppendLiteral(" [");
+ name.Append(funIdName);
+ delete[] funIdName;
+ name.Append(']');
+ }
+ }
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CallbackTimeoutHandler,
+ tmp->mRefCnt.get())
+ }
+
+ // If we need to make TimeoutHandler CCed, don't call its Traverse method
+ // here, otherwise we ends up report same object twice if logging is on. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1588208.
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackTimeoutHandler)
+ for (size_t i = 0; i < tmp->mArgs.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i])
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackTimeoutHandler)
+
+void CallbackTimeoutHandler::ReleaseJSObjects() {
+ mArgs.Clear();
+ mozilla::DropJSObjects(this);
+}
+
+bool CallbackTimeoutHandler::Call(const char* aExecutionReason) {
+ IgnoredErrorResult rv;
+ JS::Rooted<JS::Value> ignoredVal(RootingCx());
+ MOZ_KnownLive(mFunction)->Call(MOZ_KnownLive(mGlobal), mArgs, &ignoredVal, rv,
+ aExecutionReason);
+ return !rv.IsUncatchableException();
+}
+
+void CallbackTimeoutHandler::MarkForCC() { mFunction->MarkForCC(); }
+
+void CallbackTimeoutHandler::GetDescription(nsACString& aOutString) {
+ mFunction->GetDescription(aOutString);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/TimeoutHandler.h b/dom/base/TimeoutHandler.h
new file mode 100644
index 0000000000..ce659d7623
--- /dev/null
+++ b/dom/base/TimeoutHandler.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_timeout_handler_h
+#define mozilla_dom_timeout_handler_h
+
+#include "nsCOMPtr.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/FunctionBinding.h"
+
+namespace mozilla::dom {
+
+/**
+ * Utility class for implementing nsITimeoutHandlers, designed to be subclassed.
+ */
+class TimeoutHandler : public nsISupports {
+ public:
+ MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* /* unused */);
+ // Get the location of the script.
+ // Note: The memory pointed to by aFileName is owned by the
+ // nsITimeoutHandler and should not be freed by the caller.
+ virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn);
+ // Append a UTF-8 string to aOutString that describes the callback function,
+ // for use in logging or profiler markers.
+ // The string contains the function name and its source location, if
+ // available, in the following format:
+ // "<functionName> (<sourceURL>:<lineNumber>:<columnNumber>)"
+ virtual void GetDescription(nsACString& aOutString);
+ virtual void MarkForCC() {}
+
+ protected:
+ TimeoutHandler() : mFileName(""), mLineNo(0), mColumn(0) {}
+ explicit TimeoutHandler(JSContext* aCx);
+
+ virtual ~TimeoutHandler() = default;
+
+ // filename, line number and JS language version string of the
+ // caller of setTimeout()
+ nsCString mFileName;
+ uint32_t mLineNo;
+ uint32_t mColumn;
+
+ private:
+ TimeoutHandler(const TimeoutHandler&) = delete;
+ TimeoutHandler& operator=(const TimeoutHandler&) = delete;
+ TimeoutHandler& operator=(const TimeoutHandler&&) = delete;
+};
+
+class ScriptTimeoutHandler : public TimeoutHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptTimeoutHandler)
+
+ ScriptTimeoutHandler(JSContext* aCx, nsIGlobalObject* aGlobal,
+ const nsAString& aExpression);
+
+ MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* /* unused */) override {
+ return false;
+ };
+ virtual void GetDescription(nsACString& aOutString) override;
+
+ protected:
+ virtual ~ScriptTimeoutHandler() = default;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ // The expression to evaluate or function to call. If mFunction is non-null
+ // it should be used, else use mExpr.
+ nsString mExpr;
+};
+
+class CallbackTimeoutHandler final : public TimeoutHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackTimeoutHandler)
+
+ CallbackTimeoutHandler(JSContext* aCx, nsIGlobalObject* aGlobal,
+ Function* aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments);
+
+ MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* aExecutionReason) override;
+ virtual void MarkForCC() override;
+ virtual void GetDescription(nsACString& aOutString) override;
+
+ void ReleaseJSObjects();
+
+ private:
+ virtual ~CallbackTimeoutHandler() { ReleaseJSObjects(); }
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<Function> mFunction;
+ nsTArray<JS::Heap<JS::Value>> mArgs;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_timeout_handler_h
diff --git a/dom/base/TimeoutManager.cpp b/dom/base/TimeoutManager.cpp
new file mode 100644
index 0000000000..1f99f1963d
--- /dev/null
+++ b/dom/base/TimeoutManager.cpp
@@ -0,0 +1,1338 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimeoutManager.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "nsINamed.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "TimeoutExecutor.h"
+#include "TimeoutBudgetManager.h"
+#include "mozilla/net/WebSocketEventService.h"
+#include "mozilla/MediaManager.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+LazyLogModule gTimeoutLog("Timeout");
+
+static int32_t gRunningTimeoutDepth = 0;
+
+// static
+const uint32_t TimeoutManager::InvalidFiringId = 0;
+
+namespace {
+double GetRegenerationFactor(bool aIsBackground) {
+ // Lookup function for "dom.timeout.{background,
+ // foreground}_budget_regeneration_rate".
+
+ // Returns the rate of regeneration of the execution budget as a
+ // fraction. If the value is 1.0, the amount of time regenerated is
+ // equal to time passed. At this rate we regenerate 1ms/ms. If it is
+ // 0.01 the amount regenerated is 1% of time passed. At this rate we
+ // regenerate 1ms/100ms, etc.
+ double denominator = std::max(
+ aIsBackground
+ ? StaticPrefs::dom_timeout_background_budget_regeneration_rate()
+ : StaticPrefs::dom_timeout_foreground_budget_regeneration_rate(),
+ 1);
+ return 1.0 / denominator;
+}
+
+TimeDuration GetMaxBudget(bool aIsBackground) {
+ // Lookup function for "dom.timeout.{background,
+ // foreground}_throttling_max_budget".
+
+ // Returns how high a budget can be regenerated before being
+ // clamped. If this value is less or equal to zero,
+ // TimeDuration::Forever() is implied.
+ int32_t maxBudget =
+ aIsBackground
+ ? StaticPrefs::dom_timeout_background_throttling_max_budget()
+ : StaticPrefs::dom_timeout_foreground_throttling_max_budget();
+ return maxBudget > 0 ? TimeDuration::FromMilliseconds(maxBudget)
+ : TimeDuration::Forever();
+}
+
+TimeDuration GetMinBudget(bool aIsBackground) {
+ // The minimum budget is computed by looking up the maximum allowed
+ // delay and computing how long time it would take to regenerate
+ // that budget using the regeneration factor. This number is
+ // expected to be negative.
+ return TimeDuration::FromMilliseconds(
+ -StaticPrefs::dom_timeout_budget_throttling_max_delay() /
+ std::max(
+ aIsBackground
+ ? StaticPrefs::dom_timeout_background_budget_regeneration_rate()
+ : StaticPrefs::dom_timeout_foreground_budget_regeneration_rate(),
+ 1));
+}
+} // namespace
+
+//
+
+bool TimeoutManager::IsBackground() const {
+ return !IsActive() && mWindow.IsBackgroundInternal();
+}
+
+bool TimeoutManager::IsActive() const {
+ // A window is considered active if:
+ // * It is a chrome window
+ // * It is playing audio
+ //
+ // Note that a window can be considered active if it is either in the
+ // foreground or in the background.
+
+ if (mWindow.IsChromeWindow()) {
+ return true;
+ }
+
+ // Check if we're playing audio
+ if (mWindow.IsPlayingAudio()) {
+ return true;
+ }
+
+ return false;
+}
+
+void TimeoutManager::SetLoading(bool value) {
+ // When moving from loading to non-loading, we may need to
+ // reschedule any existing timeouts from the idle timeout queue
+ // to the normal queue.
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("%p: SetLoading(%d)", this, value));
+ if (mIsLoading && !value) {
+ MoveIdleToActive();
+ }
+ // We don't immediately move existing timeouts to the idle queue if we
+ // move to loading. When they would have fired, we'll see we're loading
+ // and move them then.
+ mIsLoading = value;
+}
+
+void TimeoutManager::MoveIdleToActive() {
+ uint32_t num = 0;
+ TimeStamp when;
+ TimeStamp now;
+ // Ensure we maintain the ordering of timeouts, so timeouts
+ // never fire before a timeout set for an earlier time, or
+ // before a timeout for the same time already submitted.
+ // See https://html.spec.whatwg.org/#dom-settimeout #16 and #17
+ while (RefPtr<Timeout> timeout = mIdleTimeouts.GetLast()) {
+ if (num == 0) {
+ when = timeout->When();
+ }
+ timeout->remove();
+ mTimeouts.InsertFront(timeout);
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ if (num == 0) {
+ now = TimeStamp::Now();
+ }
+ TimeDuration elapsed = now - timeout->SubmitTime();
+ TimeDuration target = timeout->When() - timeout->SubmitTime();
+ TimeDuration delta = now - timeout->When();
+ nsPrintfCString marker(
+ "Releasing deferred setTimeout() for %dms (original target time was "
+ "%dms (%dms delta))",
+ int(elapsed.ToMilliseconds()), int(target.ToMilliseconds()),
+ int(delta.ToMilliseconds()));
+ // don't have end before start...
+ PROFILER_MARKER_TEXT(
+ "setTimeout deferred release", DOM,
+ MarkerOptions(
+ MarkerTiming::Interval(
+ delta.ToMilliseconds() >= 0 ? timeout->When() : now, now),
+ MarkerInnerWindowId(mWindow.WindowID())),
+ marker);
+ }
+ num++;
+ }
+ if (num > 0) {
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(when));
+ mIdleExecutor->Cancel();
+ }
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("%p: Moved %d timeouts from Idle to active", this, num));
+}
+
+uint32_t TimeoutManager::CreateFiringId() {
+ uint32_t id = mNextFiringId;
+ mNextFiringId += 1;
+ if (mNextFiringId == InvalidFiringId) {
+ mNextFiringId += 1;
+ }
+
+ mFiringIdStack.AppendElement(id);
+
+ return id;
+}
+
+void TimeoutManager::DestroyFiringId(uint32_t aFiringId) {
+ MOZ_DIAGNOSTIC_ASSERT(!mFiringIdStack.IsEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(mFiringIdStack.LastElement() == aFiringId);
+ mFiringIdStack.RemoveLastElement();
+}
+
+bool TimeoutManager::IsValidFiringId(uint32_t aFiringId) const {
+ return !IsInvalidFiringId(aFiringId);
+}
+
+TimeDuration TimeoutManager::MinSchedulingDelay() const {
+ if (IsActive()) {
+ return TimeDuration();
+ }
+
+ bool isBackground = mWindow.IsBackgroundInternal();
+
+ // If a window isn't active as defined by TimeoutManager::IsActive()
+ // and we're throttling timeouts using an execution budget, we
+ // should adjust the minimum scheduling delay if we have used up all
+ // of our execution budget. Note that a window can be active or
+ // inactive regardless of wether it is in the foreground or in the
+ // background. Throttling using a budget depends largely on the
+ // regeneration factor, which can be specified separately for
+ // foreground and background windows.
+ //
+ // The value that we compute is the time in the future when we again
+ // have a positive execution budget. We do this by taking the
+ // execution budget into account, which if it positive implies that
+ // we have time left to execute, and if it is negative implies that
+ // we should throttle it until the budget again is positive. The
+ // factor used is the rate of budget regeneration.
+ //
+ // We clamp the delay to be less than or equal to
+ // "dom.timeout.budget_throttling_max_delay" to not entirely starve
+ // the timeouts.
+ //
+ // Consider these examples assuming we should throttle using
+ // budgets:
+ //
+ // mExecutionBudget is 20ms
+ // factor is 1, which is 1 ms/ms
+ // delay is 0ms
+ // then we will compute the minimum delay:
+ // max(0, - 20 * 1) = 0
+ //
+ // mExecutionBudget is -50ms
+ // factor is 0.1, which is 1 ms/10ms
+ // delay is 1000ms
+ // then we will compute the minimum delay:
+ // max(1000, - (- 50) * 1/0.1) = max(1000, 500) = 1000
+ //
+ // mExecutionBudget is -15ms
+ // factor is 0.01, which is 1 ms/100ms
+ // delay is 1000ms
+ // then we will compute the minimum delay:
+ // max(1000, - (- 15) * 1/0.01) = max(1000, 1500) = 1500
+ TimeDuration unthrottled =
+ isBackground ? TimeDuration::FromMilliseconds(
+ StaticPrefs::dom_min_background_timeout_value())
+ : TimeDuration();
+ bool budgetThrottlingEnabled = BudgetThrottlingEnabled(isBackground);
+ if (budgetThrottlingEnabled && mExecutionBudget < TimeDuration()) {
+ // Only throttle if execution budget is less than 0
+ double factor = 1.0 / GetRegenerationFactor(mWindow.IsBackgroundInternal());
+ return TimeDuration::Max(unthrottled, -mExecutionBudget.MultDouble(factor));
+ }
+ if (!budgetThrottlingEnabled && isBackground) {
+ return TimeDuration::FromMilliseconds(
+ StaticPrefs::
+ dom_min_background_timeout_value_without_budget_throttling());
+ }
+
+ return unthrottled;
+}
+
+nsresult TimeoutManager::MaybeSchedule(const TimeStamp& aWhen,
+ const TimeStamp& aNow) {
+ MOZ_DIAGNOSTIC_ASSERT(mExecutor);
+
+ // Before we can schedule the executor we need to make sure that we
+ // have an updated execution budget.
+ UpdateBudget(aNow);
+ return mExecutor->MaybeSchedule(aWhen, MinSchedulingDelay());
+}
+
+bool TimeoutManager::IsInvalidFiringId(uint32_t aFiringId) const {
+ // Check the most common ways to invalidate a firing id first.
+ // These should be quite fast.
+ if (aFiringId == InvalidFiringId || mFiringIdStack.IsEmpty()) {
+ return true;
+ }
+
+ if (mFiringIdStack.Length() == 1) {
+ return mFiringIdStack[0] != aFiringId;
+ }
+
+ // Next do a range check on the first and last items in the stack
+ // of active firing ids. This is a bit slower.
+ uint32_t low = mFiringIdStack[0];
+ uint32_t high = mFiringIdStack.LastElement();
+ MOZ_DIAGNOSTIC_ASSERT(low != high);
+ if (low > high) {
+ // If the first element is bigger than the last element in the
+ // stack, that means mNextFiringId wrapped around to zero at
+ // some point.
+ std::swap(low, high);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(low < high);
+
+ if (aFiringId < low || aFiringId > high) {
+ return true;
+ }
+
+ // Finally, fall back to verifying the firing id is not anywhere
+ // in the stack. This could be slow for a large stack, but that
+ // should be rare. It can only happen with deeply nested event
+ // loop spinning. For example, a page that does a lot of timers
+ // and a lot of sync XHRs within those timers could be slow here.
+ return !mFiringIdStack.Contains(aFiringId);
+}
+
+TimeDuration TimeoutManager::CalculateDelay(Timeout* aTimeout) const {
+ MOZ_DIAGNOSTIC_ASSERT(aTimeout);
+ TimeDuration result = aTimeout->mInterval;
+
+ if (aTimeout->mNestingLevel >=
+ StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
+ uint32_t minTimeoutValue = StaticPrefs::dom_min_timeout_value();
+ result = TimeDuration::Max(result,
+ TimeDuration::FromMilliseconds(minTimeoutValue));
+ }
+
+ return result;
+}
+
+PerformanceCounter* TimeoutManager::GetPerformanceCounter() {
+ Document* doc = mWindow.GetDocument();
+ if (doc) {
+ dom::DocGroup* docGroup = doc->GetDocGroup();
+ if (docGroup) {
+ return docGroup->GetPerformanceCounter();
+ }
+ }
+ return nullptr;
+}
+
+void TimeoutManager::RecordExecution(Timeout* aRunningTimeout,
+ Timeout* aTimeout) {
+ TimeoutBudgetManager& budgetManager = TimeoutBudgetManager::Get();
+ TimeStamp now = TimeStamp::Now();
+
+ if (aRunningTimeout) {
+ // If we're running a timeout callback, record any execution until
+ // now.
+ TimeDuration duration = budgetManager.RecordExecution(now, aRunningTimeout);
+
+ UpdateBudget(now, duration);
+
+ // This is an ad-hoc way to use the counters for the timers
+ // that should be removed at somepoint. See Bug 1482834
+ PerformanceCounter* counter = GetPerformanceCounter();
+ if (counter) {
+ counter->IncrementExecutionDuration(duration.ToMicroseconds());
+ }
+ }
+
+ if (aTimeout) {
+ // If we're starting a new timeout callback, start recording.
+ budgetManager.StartRecording(now);
+ PerformanceCounter* counter = GetPerformanceCounter();
+ if (counter) {
+ counter->IncrementDispatchCounter(DispatchCategory(TaskCategory::Timer));
+ }
+ } else {
+ // Else stop by clearing the start timestamp.
+ budgetManager.StopRecording();
+ }
+}
+
+void TimeoutManager::UpdateBudget(const TimeStamp& aNow,
+ const TimeDuration& aDuration) {
+ if (mWindow.IsChromeWindow()) {
+ return;
+ }
+
+ // The budget is adjusted by increasing it with the time since the
+ // last budget update factored with the regeneration rate. If a
+ // runnable has executed, subtract that duration from the
+ // budget. The budget updated without consideration of wether the
+ // window is active or not. If throttling is enabled and the window
+ // is active and then becomes inactive, an overdrawn budget will
+ // still be counted against the minimum delay.
+ bool isBackground = mWindow.IsBackgroundInternal();
+ if (BudgetThrottlingEnabled(isBackground)) {
+ double factor = GetRegenerationFactor(isBackground);
+ TimeDuration regenerated = (aNow - mLastBudgetUpdate).MultDouble(factor);
+ // Clamp the budget to the range of minimum and maximum allowed budget.
+ mExecutionBudget = TimeDuration::Max(
+ GetMinBudget(isBackground),
+ TimeDuration::Min(GetMaxBudget(isBackground),
+ mExecutionBudget - aDuration + regenerated));
+ } else {
+ // If budget throttling isn't enabled, reset the execution budget
+ // to the max budget specified in preferences. Always doing this
+ // will catch the case of BudgetThrottlingEnabled going from
+ // returning true to returning false. This prevent us from looping
+ // in RunTimeout, due to totalTimeLimit being set to zero and no
+ // timeouts being executed, even though budget throttling isn't
+ // active at the moment.
+ mExecutionBudget = GetMaxBudget(isBackground);
+ }
+
+ mLastBudgetUpdate = aNow;
+}
+
+// The longest interval (as PRIntervalTime) we permit, or that our
+// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
+// nsTimerImpl.h for details.
+#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT
+
+uint32_t TimeoutManager::sNestingLevel = 0;
+
+TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow,
+ uint32_t aMaxIdleDeferMS)
+ : mWindow(aWindow),
+ mExecutor(new TimeoutExecutor(this, false, 0)),
+ mIdleExecutor(new TimeoutExecutor(this, true, aMaxIdleDeferMS)),
+ mTimeouts(*this),
+ mTimeoutIdCounter(1),
+ mNextFiringId(InvalidFiringId + 1),
+#ifdef DEBUG
+ mFiringIndex(0),
+ mLastFiringIndex(-1),
+#endif
+ mRunningTimeout(nullptr),
+ mIdleTimeouts(*this),
+ mIdleCallbackTimeoutCounter(1),
+ mLastBudgetUpdate(TimeStamp::Now()),
+ mExecutionBudget(GetMaxBudget(mWindow.IsBackgroundInternal())),
+ mThrottleTimeouts(false),
+ mThrottleTrackingTimeouts(false),
+ mBudgetThrottleTimeouts(false),
+ mIsLoading(false) {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("TimeoutManager %p created, tracking bucketing %s\n", this,
+ StaticPrefs::privacy_trackingprotection_annotate_channels()
+ ? "enabled"
+ : "disabled"));
+}
+
+TimeoutManager::~TimeoutManager() {
+ MOZ_DIAGNOSTIC_ASSERT(mWindow.IsDying());
+ MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeoutsTimer);
+
+ mExecutor->Shutdown();
+ mIdleExecutor->Shutdown();
+
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("TimeoutManager %p destroyed\n", this));
+}
+
+uint32_t TimeoutManager::GetTimeoutId(Timeout::Reason aReason) {
+ switch (aReason) {
+ case Timeout::Reason::eIdleCallbackTimeout:
+ return ++mIdleCallbackTimeoutCounter;
+ case Timeout::Reason::eTimeoutOrInterval:
+ return ++mTimeoutIdCounter;
+ case Timeout::Reason::eDelayedWebTaskTimeout:
+ default:
+ return std::numeric_limits<uint32_t>::max(); // no cancellation support
+ }
+}
+
+bool TimeoutManager::IsRunningTimeout() const { return mRunningTimeout; }
+
+nsresult TimeoutManager::SetTimeout(TimeoutHandler* aHandler, int32_t interval,
+ bool aIsInterval, Timeout::Reason aReason,
+ int32_t* aReturn) {
+ // If we don't have a document (we could have been unloaded since
+ // the call to setTimeout was made), do nothing.
+ nsCOMPtr<Document> doc = mWindow.GetExtantDoc();
+ if (!doc || mWindow.IsDying()) {
+ return NS_OK;
+ }
+
+ // Disallow negative intervals.
+ interval = std::max(0, interval);
+
+ // Make sure we don't proceed with an interval larger than our timer
+ // code can handle. (Note: we already forced |interval| to be non-negative,
+ // so the uint32_t cast (to avoid compiler warnings) is ok.)
+ uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE);
+ if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
+ interval = maxTimeoutMs;
+ }
+
+ RefPtr<Timeout> timeout = new Timeout();
+#ifdef DEBUG
+ timeout->mFiringIndex = -1;
+#endif
+ timeout->mWindow = &mWindow;
+ timeout->mIsInterval = aIsInterval;
+ timeout->mInterval = TimeDuration::FromMilliseconds(interval);
+ timeout->mScriptHandler = aHandler;
+ timeout->mReason = aReason;
+
+ // No popups from timeouts by default
+ timeout->mPopupState = PopupBlocker::openAbused;
+
+ // XXX: Does eIdleCallbackTimeout need clamping?
+ if (aReason == Timeout::Reason::eTimeoutOrInterval ||
+ aReason == Timeout::Reason::eIdleCallbackTimeout) {
+ timeout->mNestingLevel =
+ sNestingLevel < StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()
+ ? sNestingLevel + 1
+ : sNestingLevel;
+ }
+
+ // Now clamp the actual interval we will use for the timer based on
+ TimeDuration realInterval = CalculateDelay(timeout);
+ TimeStamp now = TimeStamp::Now();
+ timeout->SetWhenOrTimeRemaining(now, realInterval);
+
+ // If we're not suspended, then set the timer.
+ if (!mWindow.IsSuspended()) {
+ nsresult rv = MaybeSchedule(timeout->When(), now);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (gRunningTimeoutDepth == 0 &&
+ PopupBlocker::GetPopupControlState() < PopupBlocker::openBlocked) {
+ // This timeout is *not* set from another timeout and it's set
+ // while popups are enabled. Propagate the state to the timeout if
+ // its delay (interval) is equal to or less than what
+ // "dom.disable_open_click_delay" is set to (in ms).
+
+ // This is checking |interval|, not realInterval, on purpose,
+ // because our lower bound for |realInterval| could be pretty high
+ // in some cases.
+ if (interval <= StaticPrefs::dom_disable_open_click_delay()) {
+ timeout->mPopupState = PopupBlocker::GetPopupControlState();
+ }
+ }
+
+ Timeouts::SortBy sort(mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
+ : Timeouts::SortBy::TimeWhen);
+
+ timeout->mTimeoutId = GetTimeoutId(aReason);
+ mTimeouts.Insert(timeout, sort);
+
+ *aReturn = timeout->mTimeoutId;
+
+ MOZ_LOG(
+ gTimeoutLog, LogLevel::Debug,
+ ("Set%s(TimeoutManager=%p, timeout=%p, delay=%i, "
+ "minimum=%f, throttling=%s, state=%s(%s), realInterval=%f) "
+ "returned timeout ID %u, budget=%d\n",
+ aIsInterval ? "Interval" : "Timeout", this, timeout.get(), interval,
+ (CalculateDelay(timeout) - timeout->mInterval).ToMilliseconds(),
+ mThrottleTimeouts ? "yes" : (mThrottleTimeoutsTimer ? "pending" : "no"),
+ IsActive() ? "active" : "inactive",
+ mWindow.IsBackgroundInternal() ? "background" : "foreground",
+ realInterval.ToMilliseconds(), timeout->mTimeoutId,
+ int(mExecutionBudget.ToMilliseconds())));
+
+ return NS_OK;
+}
+
+// Make sure we clear it no matter which list it's in
+void TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason) {
+ if (ClearTimeoutInternal(aTimerId, aReason, false) ||
+ mIdleTimeouts.IsEmpty()) {
+ return; // no need to check the other list if we cleared the timeout
+ }
+ ClearTimeoutInternal(aTimerId, aReason, true);
+}
+
+bool TimeoutManager::ClearTimeoutInternal(int32_t aTimerId,
+ Timeout::Reason aReason,
+ bool aIsIdle) {
+ MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval ||
+ aReason == Timeout::Reason::eIdleCallbackTimeout,
+ "This timeout reason doesn't support cancellation.");
+
+ uint32_t timerId = (uint32_t)aTimerId;
+ Timeouts& timeouts = aIsIdle ? mIdleTimeouts : mTimeouts;
+ RefPtr<TimeoutExecutor>& executor = aIsIdle ? mIdleExecutor : mExecutor;
+ bool deferredDeletion = false;
+
+ Timeout* timeout = timeouts.GetTimeout(timerId, aReason);
+ if (!timeout) {
+ return false;
+ }
+ bool firstTimeout = timeout == timeouts.GetFirst();
+
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("%s(TimeoutManager=%p, timeout=%p, ID=%u)\n",
+ timeout->mReason == Timeout::Reason::eIdleCallbackTimeout
+ ? "CancelIdleCallback"
+ : timeout->mIsInterval ? "ClearInterval"
+ : "ClearTimeout",
+ this, timeout, timeout->mTimeoutId));
+
+ if (timeout->mRunning) {
+ /* We're running from inside the timeout. Mark this
+ timeout for deferred deletion by the code in
+ RunTimeout() */
+ timeout->mIsInterval = false;
+ deferredDeletion = true;
+ } else {
+ /* Delete the aTimeout from the pending aTimeout list */
+ timeout->remove();
+ }
+
+ // We don't need to reschedule the executor if any of the following are true:
+ // * If the we weren't cancelling the first timeout, then the executor's
+ // state doesn't need to change. It will only reflect the next soonest
+ // Timeout.
+ // * If we did cancel the first Timeout, but its currently running, then
+ // RunTimeout() will handle rescheduling the executor.
+ // * If the window has become suspended then we should not start executing
+ // Timeouts.
+ if (!firstTimeout || deferredDeletion || mWindow.IsSuspended()) {
+ return true;
+ }
+
+ // Stop the executor and restart it at the next soonest deadline.
+ executor->Cancel();
+
+ Timeout* nextTimeout = timeouts.GetFirst();
+ if (nextTimeout) {
+ if (aIsIdle) {
+ MOZ_ALWAYS_SUCCEEDS(
+ executor->MaybeSchedule(nextTimeout->When(), TimeDuration(0)));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
+ }
+ }
+ return true;
+}
+
+void TimeoutManager::RunTimeout(const TimeStamp& aNow,
+ const TimeStamp& aTargetDeadline,
+ bool aProcessIdle) {
+ MOZ_DIAGNOSTIC_ASSERT(!aNow.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(!aTargetDeadline.IsNull());
+
+ MOZ_ASSERT_IF(mWindow.IsFrozen(), mWindow.IsSuspended());
+ if (mWindow.IsSuspended()) {
+ return;
+ }
+
+ Timeouts& timeouts(aProcessIdle ? mIdleTimeouts : mTimeouts);
+
+ // Limit the overall time spent in RunTimeout() to reduce jank.
+ uint32_t totalTimeLimitMS =
+ std::max(1u, StaticPrefs::dom_timeout_max_consecutive_callbacks_ms());
+ const TimeDuration totalTimeLimit =
+ TimeDuration::Min(TimeDuration::FromMilliseconds(totalTimeLimitMS),
+ TimeDuration::Max(TimeDuration(), mExecutionBudget));
+
+ // Allow up to 25% of our total time budget to be used figuring out which
+ // timers need to run. This is the initial loop in this method.
+ const TimeDuration initialTimeLimit =
+ TimeDuration::FromMilliseconds(totalTimeLimit.ToMilliseconds() / 4);
+
+ // Ammortize overhead from from calling TimeStamp::Now() in the initial
+ // loop, though, by only checking for an elapsed limit every N timeouts.
+ const uint32_t kNumTimersPerInitialElapsedCheck = 100;
+
+ // Start measuring elapsed time immediately. We won't potentially expire
+ // the time budget until at least one Timeout has run, though.
+ TimeStamp now(aNow);
+ TimeStamp start = now;
+
+ uint32_t firingId = CreateFiringId();
+ auto guard = MakeScopeExit([&] { DestroyFiringId(firingId); });
+
+ // Make sure that the window and the script context don't go away as
+ // a result of running timeouts
+ RefPtr<nsGlobalWindowInner> window(&mWindow);
+ // Accessing members of mWindow here is safe, because the lifetime of
+ // TimeoutManager is the same as the lifetime of the containing
+ // nsGlobalWindow.
+
+ // A native timer has gone off. See which of our timeouts need
+ // servicing
+ TimeStamp deadline;
+
+ if (aTargetDeadline > now) {
+ // The OS timer fired early (which can happen due to the timers
+ // having lower precision than TimeStamp does). Set |deadline| to
+ // be the time when the OS timer *should* have fired so that any
+ // timers that *should* have fired *will* be fired now.
+
+ deadline = aTargetDeadline;
+ } else {
+ deadline = now;
+ }
+
+ TimeStamp nextDeadline;
+ uint32_t numTimersToRun = 0;
+
+ // The timeout list is kept in deadline order. Discover the latest timeout
+ // whose deadline has expired. On some platforms, native timeout events fire
+ // "early", but we handled that above by setting deadline to aTargetDeadline
+ // if the timer fired early. So we can stop walking if we get to timeouts
+ // whose When() is greater than deadline, since once that happens we know
+ // nothing past that point is expired.
+
+ for (Timeout* timeout = timeouts.GetFirst(); timeout != nullptr;
+ timeout = timeout->getNext()) {
+ if (totalTimeLimit.IsZero() || timeout->When() > deadline) {
+ nextDeadline = timeout->When();
+ break;
+ }
+
+ if (IsInvalidFiringId(timeout->mFiringId)) {
+ // Mark any timeouts that are on the list to be fired with the
+ // firing depth so that we can reentrantly run timeouts
+ timeout->mFiringId = firingId;
+
+ numTimersToRun += 1;
+
+ // Run only a limited number of timers based on the configured maximum.
+ if (numTimersToRun % kNumTimersPerInitialElapsedCheck == 0) {
+ now = TimeStamp::Now();
+ TimeDuration elapsed(now - start);
+ if (elapsed >= initialTimeLimit) {
+ nextDeadline = timeout->When();
+ break;
+ }
+ }
+ }
+ }
+ if (aProcessIdle) {
+ MOZ_LOG(
+ gTimeoutLog, LogLevel::Debug,
+ ("Running %u deferred timeouts on idle (TimeoutManager=%p), "
+ "nextDeadline = %gms from now",
+ numTimersToRun, this,
+ nextDeadline.IsNull() ? 0.0 : (nextDeadline - now).ToMilliseconds()));
+ }
+
+ now = TimeStamp::Now();
+
+ // Wherever we stopped in the timer list, schedule the executor to
+ // run for the next unexpired deadline. Note, this *must* be done
+ // before we start executing any content script handlers. If one
+ // of them spins the event loop the executor must already be scheduled
+ // in order for timeouts to fire properly.
+ if (!nextDeadline.IsNull()) {
+ // Note, we verified the window is not suspended at the top of
+ // method and the window should not have been suspended while
+ // executing the loop above since it doesn't call out to js.
+ MOZ_DIAGNOSTIC_ASSERT(!mWindow.IsSuspended());
+ if (aProcessIdle) {
+ // We don't want to update timing budget for idle queue firings, and
+ // all timeouts in the IdleTimeouts list have hit their deadlines,
+ // and so should run as soon as possible.
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleExecutor->MaybeSchedule(nextDeadline, TimeDuration()));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextDeadline, now));
+ }
+ }
+
+ // Maybe the timeout that the event was fired for has been deleted
+ // and there are no others timeouts with deadlines that make them
+ // eligible for execution yet. Go away.
+ if (!numTimersToRun) {
+ return;
+ }
+
+ // Now we need to search the normal and tracking timer list at the same
+ // time to run the timers in the scheduled order.
+
+ // We stop iterating each list when we go past the last expired timeout from
+ // that list that we have observed above. That timeout will either be the
+ // next item after the last timeout we looked at or nullptr if we have
+ // exhausted the entire list while looking for the last expired timeout.
+ {
+ // Use a nested scope in order to make sure the strong references held while
+ // iterating are freed after the loop.
+
+ // The next timeout to run. This is used to advance the loop, but
+ // we cannot set it until we've run the current timeout, since
+ // running the current timeout might remove the immediate next
+ // timeout.
+ RefPtr<Timeout> next;
+
+ for (RefPtr<Timeout> timeout = timeouts.GetFirst(); timeout != nullptr;
+ timeout = next) {
+ next = timeout->getNext();
+ // We should only execute callbacks for the set of expired Timeout
+ // objects we computed above.
+ if (timeout->mFiringId != firingId) {
+ // If the FiringId does not match, but is still valid, then this is
+ // a Timeout for another RunTimeout() on the call stack (such as in
+ // the case of nested event loops, for alert() or more likely XHR).
+ // Just skip it.
+ if (IsValidFiringId(timeout->mFiringId)) {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("Skipping Run%s(TimeoutManager=%p, timeout=%p) since "
+ "firingId %d is valid (processing firingId %d)"
+#ifdef DEBUG
+ " - FiringIndex %" PRId64 " (mLastFiringIndex %" PRId64 ")"
+#endif
+ ,
+ timeout->mIsInterval ? "Interval" : "Timeout", this,
+ timeout.get(), timeout->mFiringId, firingId
+#ifdef DEBUG
+ ,
+ timeout->mFiringIndex, mFiringIndex
+#endif
+ ));
+#ifdef DEBUG
+ // The old FiringIndex assumed no recursion; recursion can cause
+ // other timers to get fired "in the middle" of a sequence we've
+ // already assigned firingindexes to. Since we're not going to
+ // run this timeout now, remove any FiringIndex that was already
+ // set.
+
+ // Since all timers that have FiringIndexes set *must* be ready
+ // to run and have valid FiringIds, all of them will be 'skipped'
+ // and reset if we recurse - we don't have to look through the
+ // list past where we'll stop on the first InvalidFiringId.
+ timeout->mFiringIndex = -1;
+#endif
+ continue;
+ }
+
+ // If, however, the FiringId is invalid then we have reached Timeout
+ // objects beyond the list we calculated above. This can happen
+ // if the Timeout just beyond our last expired Timeout is cancelled
+ // by one of the callbacks we've just executed. In this case we
+ // should just stop iterating. We're done.
+ else {
+ break;
+ }
+ }
+
+ MOZ_ASSERT_IF(mWindow.IsFrozen(), mWindow.IsSuspended());
+ if (mWindow.IsSuspended()) {
+ break;
+ }
+
+ // The timeout is on the list to run at this depth, go ahead and
+ // process it.
+
+ // Record the first time we try to fire a timeout, and ensure that
+ // all actual firings occur in that order. This ensures that we
+ // retain compliance with the spec language
+ // (https://html.spec.whatwg.org/#dom-settimeout) specifically items
+ // 15 ("If method context is a Window object, wait until the Document
+ // associated with method context has been fully active for a further
+ // timeout milliseconds (not necessarily consecutively)") and item 16
+ // ("Wait until any invocations of this algorithm that had the same
+ // method context, that started before this one, and whose timeout is
+ // equal to or less than this one's, have completed.").
+#ifdef DEBUG
+ if (timeout->mFiringIndex == -1) {
+ timeout->mFiringIndex = mFiringIndex++;
+ }
+#endif
+
+ if (mIsLoading && !aProcessIdle) {
+ // Any timeouts that would fire during a load will be deferred
+ // until the load event occurs, but if there's an idle time,
+ // they'll be run before the load event.
+ timeout->remove();
+ // MOZ_RELEASE_ASSERT(timeout->When() <= (TimeStamp::Now()));
+ mIdleTimeouts.InsertBack(timeout);
+ if (MOZ_LOG_TEST(gTimeoutLog, LogLevel::Debug)) {
+ uint32_t num = 0;
+ for (Timeout* t = mIdleTimeouts.GetFirst(); t != nullptr;
+ t = t->getNext()) {
+ num++;
+ }
+ MOZ_LOG(
+ gTimeoutLog, LogLevel::Debug,
+ ("Deferring Run%s(TimeoutManager=%p, timeout=%p (%gms in the "
+ "past)) (%u deferred)",
+ timeout->mIsInterval ? "Interval" : "Timeout", this,
+ timeout.get(), (now - timeout->When()).ToMilliseconds(), num));
+ }
+ MOZ_ALWAYS_SUCCEEDS(mIdleExecutor->MaybeSchedule(now, TimeDuration()));
+ } else {
+ // Get the script context (a strong ref to prevent it going away)
+ // for this timeout and ensure the script language is enabled.
+ nsCOMPtr<nsIScriptContext> scx = mWindow.GetContextInternal();
+
+ if (!scx) {
+ // No context means this window was closed or never properly
+ // initialized for this language. This timer will never fire
+ // so just remove it.
+ timeout->remove();
+ continue;
+ }
+
+#ifdef DEBUG
+ if (timeout->mFiringIndex <= mLastFiringIndex) {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("Incorrect firing index for Run%s(TimeoutManager=%p, "
+ "timeout=%p) with "
+ "firingId %d - FiringIndex %" PRId64
+ " (mLastFiringIndex %" PRId64 ")",
+ timeout->mIsInterval ? "Interval" : "Timeout", this,
+ timeout.get(), timeout->mFiringId, timeout->mFiringIndex,
+ mFiringIndex));
+ }
+ MOZ_ASSERT(timeout->mFiringIndex > mLastFiringIndex);
+ mLastFiringIndex = timeout->mFiringIndex;
+#endif
+ // This timeout is good to run.
+ bool timeout_was_cleared = window->RunTimeoutHandler(timeout, scx);
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("Run%s(TimeoutManager=%p, timeout=%p) returned %d\n",
+ timeout->mIsInterval ? "Interval" : "Timeout", this,
+ timeout.get(), !!timeout_was_cleared));
+
+ if (timeout_was_cleared) {
+ // Make sure we're not holding any Timeout objects alive.
+ next = nullptr;
+
+ // Since ClearAllTimeouts() was called the lists should be empty.
+ MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
+
+ return;
+ }
+
+ // If we need to reschedule a setInterval() the delay should be
+ // calculated based on when its callback started to execute. So
+ // save off the last time before updating our "now" timestamp to
+ // account for its callback execution time.
+ TimeStamp lastCallbackTime = now;
+ now = TimeStamp::Now();
+
+ // If we have a regular interval timer, we re-schedule the
+ // timeout, accounting for clock drift.
+ bool needsReinsertion =
+ RescheduleTimeout(timeout, lastCallbackTime, now);
+
+ // Running a timeout can cause another timeout to be deleted, so
+ // we need to reset the pointer to the following timeout.
+ next = timeout->getNext();
+
+ timeout->remove();
+
+ if (needsReinsertion) {
+ // Insert interval timeout onto the corresponding list sorted in
+ // deadline order. AddRefs timeout.
+ // Always re-insert into the normal time queue!
+ mTimeouts.Insert(timeout, mWindow.IsFrozen()
+ ? Timeouts::SortBy::TimeRemaining
+ : Timeouts::SortBy::TimeWhen);
+ }
+ }
+ // Check to see if we have run out of time to execute timeout handlers.
+ // If we've exceeded our time budget then terminate the loop immediately.
+ TimeDuration elapsed = now - start;
+ if (elapsed >= totalTimeLimit) {
+ // We ran out of time. Make sure to schedule the executor to
+ // run immediately for the next timer, if it exists. Its possible,
+ // however, that the last timeout handler suspended the window. If
+ // that happened then we must skip this step.
+ if (!mWindow.IsSuspended()) {
+ if (next) {
+ if (aProcessIdle) {
+ // We don't want to update timing budget for idle queue firings,
+ // and all timeouts in the IdleTimeouts list have hit their
+ // deadlines, and so should run as soon as possible.
+
+ // Shouldn't need cancelling since it never waits
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleExecutor->MaybeSchedule(next->When(), TimeDuration()));
+ } else {
+ // If we ran out of execution budget we need to force a
+ // reschedule. By cancelling the executor we will not run
+ // immediately, but instead reschedule to the minimum
+ // scheduling delay.
+ if (mExecutionBudget < TimeDuration()) {
+ mExecutor->Cancel();
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(next->When(), now));
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+bool TimeoutManager::RescheduleTimeout(Timeout* aTimeout,
+ const TimeStamp& aLastCallbackTime,
+ const TimeStamp& aCurrentNow) {
+ MOZ_DIAGNOSTIC_ASSERT(aLastCallbackTime <= aCurrentNow);
+
+ if (!aTimeout->mIsInterval) {
+ return false;
+ }
+
+ // Automatically increase the nesting level when a setInterval()
+ // is rescheduled just as if it was using a chained setTimeout().
+ if (aTimeout->mNestingLevel <
+ StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
+ aTimeout->mNestingLevel += 1;
+ }
+
+ // Compute time to next timeout for interval timer.
+ // Make sure nextInterval is at least CalculateDelay().
+ TimeDuration nextInterval = CalculateDelay(aTimeout);
+
+ TimeStamp firingTime = aLastCallbackTime + nextInterval;
+ TimeDuration delay = firingTime - aCurrentNow;
+
+#ifdef DEBUG
+ aTimeout->mFiringIndex = -1;
+#endif
+ // And make sure delay is nonnegative; that might happen if the timer
+ // thread is firing our timers somewhat early or if they're taking a long
+ // time to run the callback.
+ if (delay < TimeDuration(0)) {
+ delay = TimeDuration(0);
+ }
+
+ aTimeout->SetWhenOrTimeRemaining(aCurrentNow, delay);
+
+ if (mWindow.IsSuspended()) {
+ return true;
+ }
+
+ nsresult rv = MaybeSchedule(aTimeout->When(), aCurrentNow);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+void TimeoutManager::ClearAllTimeouts() {
+ bool seenRunningTimeout = false;
+
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("ClearAllTimeouts(TimeoutManager=%p)\n", this));
+
+ if (mThrottleTimeoutsTimer) {
+ mThrottleTimeoutsTimer->Cancel();
+ mThrottleTimeoutsTimer = nullptr;
+ }
+
+ mExecutor->Cancel();
+ mIdleExecutor->Cancel();
+
+ ForEachUnorderedTimeout([&](Timeout* aTimeout) {
+ /* If RunTimeout() is higher up on the stack for this
+ window, e.g. as a result of document.write from a timeout,
+ then we need to reset the list insertion point for
+ newly-created timeouts in case the user adds a timeout,
+ before we pop the stack back to RunTimeout. */
+ if (mRunningTimeout == aTimeout) {
+ seenRunningTimeout = true;
+ }
+
+ // Set timeout->mCleared to true to indicate that the timeout was
+ // cleared and taken out of the list of timeouts
+ aTimeout->mCleared = true;
+ });
+
+ // Clear out our lists
+ mTimeouts.Clear();
+ mIdleTimeouts.Clear();
+}
+
+void TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy) {
+ // Start at mLastTimeout and go backwards. Stop if we see a Timeout with a
+ // valid FiringId since those timers are currently being processed by
+ // RunTimeout. This optimizes for the common case of insertion at the end.
+ Timeout* prevSibling;
+ for (prevSibling = GetLast();
+ prevSibling &&
+ // This condition needs to match the one in SetTimeoutOrInterval that
+ // determines whether to set When() or TimeRemaining().
+ (aSortBy == SortBy::TimeRemaining
+ ? prevSibling->TimeRemaining() > aTimeout->TimeRemaining()
+ : prevSibling->When() > aTimeout->When()) &&
+ // Check the firing ID last since it will evaluate true in the vast
+ // majority of cases.
+ mManager.IsInvalidFiringId(prevSibling->mFiringId);
+ prevSibling = prevSibling->getPrevious()) {
+ /* Do nothing; just searching */
+ }
+
+ // Now link in aTimeout after prevSibling.
+ if (prevSibling) {
+ aTimeout->SetTimeoutContainer(mTimeouts);
+ prevSibling->setNext(aTimeout);
+ } else {
+ InsertFront(aTimeout);
+ }
+
+ aTimeout->mFiringId = InvalidFiringId;
+}
+
+Timeout* TimeoutManager::BeginRunningTimeout(Timeout* aTimeout) {
+ Timeout* currentTimeout = mRunningTimeout;
+ mRunningTimeout = aTimeout;
+ ++gRunningTimeoutDepth;
+
+ RecordExecution(currentTimeout, aTimeout);
+ return currentTimeout;
+}
+
+void TimeoutManager::EndRunningTimeout(Timeout* aTimeout) {
+ --gRunningTimeoutDepth;
+
+ RecordExecution(mRunningTimeout, aTimeout);
+ mRunningTimeout = aTimeout;
+}
+
+void TimeoutManager::UnmarkGrayTimers() {
+ ForEachUnorderedTimeout([](Timeout* aTimeout) {
+ if (aTimeout->mScriptHandler) {
+ aTimeout->mScriptHandler->MarkForCC();
+ }
+ });
+}
+
+void TimeoutManager::Suspend() {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Suspend(TimeoutManager=%p)\n", this));
+
+ if (mThrottleTimeoutsTimer) {
+ mThrottleTimeoutsTimer->Cancel();
+ mThrottleTimeoutsTimer = nullptr;
+ }
+
+ mExecutor->Cancel();
+ mIdleExecutor->Cancel();
+}
+
+void TimeoutManager::Resume() {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Resume(TimeoutManager=%p)\n", this));
+
+ // When Suspend() has been called after IsDocumentLoaded(), but the
+ // throttle tracking timer never managed to fire, start the timer
+ // again.
+ if (mWindow.IsDocumentLoaded() && !mThrottleTimeouts) {
+ MaybeStartThrottleTimeout();
+ }
+
+ Timeout* nextTimeout = mTimeouts.GetFirst();
+ if (nextTimeout) {
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
+ }
+ nextTimeout = mIdleTimeouts.GetFirst();
+ if (nextTimeout) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
+ }
+}
+
+void TimeoutManager::Freeze() {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Freeze(TimeoutManager=%p)\n", this));
+
+ TimeStamp now = TimeStamp::Now();
+ ForEachUnorderedTimeout([&](Timeout* aTimeout) {
+ // Save the current remaining time for this timeout. We will
+ // re-apply it when the window is Thaw()'d. This effectively
+ // shifts timers to the right as if time does not pass while
+ // the window is frozen.
+ TimeDuration delta(0);
+ if (aTimeout->When() > now) {
+ delta = aTimeout->When() - now;
+ }
+ aTimeout->SetWhenOrTimeRemaining(now, delta);
+ MOZ_DIAGNOSTIC_ASSERT(aTimeout->TimeRemaining() == delta);
+ });
+}
+
+void TimeoutManager::Thaw() {
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Thaw(TimeoutManager=%p)\n", this));
+
+ TimeStamp now = TimeStamp::Now();
+
+ ForEachUnorderedTimeout([&](Timeout* aTimeout) {
+ // Set When() back to the time when the timer is supposed to fire.
+ aTimeout->SetWhenOrTimeRemaining(now, aTimeout->TimeRemaining());
+ MOZ_DIAGNOSTIC_ASSERT(!aTimeout->When().IsNull());
+ });
+}
+
+void TimeoutManager::UpdateBackgroundState() {
+ mExecutionBudget = GetMaxBudget(mWindow.IsBackgroundInternal());
+
+ // When the window moves to the background or foreground we should
+ // reschedule the TimeoutExecutor in case the MinSchedulingDelay()
+ // changed. Only do this if the window is not suspended and we
+ // actually have a timeout.
+ if (!mWindow.IsSuspended()) {
+ Timeout* nextTimeout = mTimeouts.GetFirst();
+ if (nextTimeout) {
+ mExecutor->Cancel();
+ MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
+ }
+ // the Idle queue should all be past their firing time, so there we just
+ // need to restart the queue
+
+ // XXX May not be needed if we don't stop the idle queue, as
+ // MinSchedulingDelay isn't relevant here
+ nextTimeout = mIdleTimeouts.GetFirst();
+ if (nextTimeout) {
+ mIdleExecutor->Cancel();
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
+ }
+ }
+}
+
+namespace {
+
+class ThrottleTimeoutsCallback final : public nsITimerCallback,
+ public nsINamed {
+ public:
+ explicit ThrottleTimeoutsCallback(nsGlobalWindowInner* aWindow)
+ : mWindow(aWindow) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("ThrottleTimeoutsCallback");
+ return NS_OK;
+ }
+
+ private:
+ ~ThrottleTimeoutsCallback() = default;
+
+ private:
+ // The strong reference here keeps the Window and hence the TimeoutManager
+ // object itself alive.
+ RefPtr<nsGlobalWindowInner> mWindow;
+};
+
+NS_IMPL_ISUPPORTS(ThrottleTimeoutsCallback, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP
+ThrottleTimeoutsCallback::Notify(nsITimer* aTimer) {
+ mWindow->TimeoutManager().StartThrottlingTimeouts();
+ mWindow = nullptr;
+ return NS_OK;
+}
+
+} // namespace
+
+bool TimeoutManager::BudgetThrottlingEnabled(bool aIsBackground) const {
+ // A window can be throttled using budget if
+ // * It isn't active
+ // * If it isn't using WebRTC
+ // * If it hasn't got open WebSockets
+ // * If it hasn't got active IndexedDB databases
+
+ // Note that we allow both foreground and background to be
+ // considered for budget throttling. What determines if they are if
+ // budget throttling is enabled is the max budget.
+ if ((aIsBackground
+ ? StaticPrefs::dom_timeout_background_throttling_max_budget()
+ : StaticPrefs::dom_timeout_foreground_throttling_max_budget()) < 0) {
+ return false;
+ }
+
+ if (!mBudgetThrottleTimeouts || IsActive()) {
+ return false;
+ }
+
+ // Check if there are any active IndexedDB databases
+ if (mWindow.HasActiveIndexedDBDatabases()) {
+ return false;
+ }
+
+ // Check if we have active PeerConnection
+ if (mWindow.HasActivePeerConnections()) {
+ return false;
+ }
+
+ if (mWindow.HasOpenWebSockets()) {
+ return false;
+ }
+
+ return true;
+}
+
+void TimeoutManager::StartThrottlingTimeouts() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(mThrottleTimeoutsTimer);
+
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("TimeoutManager %p started to throttle tracking timeouts\n", this));
+
+ MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
+ mThrottleTimeouts = true;
+ mThrottleTrackingTimeouts = true;
+ mBudgetThrottleTimeouts =
+ StaticPrefs::dom_timeout_enable_budget_timer_throttling();
+ mThrottleTimeoutsTimer = nullptr;
+}
+
+void TimeoutManager::OnDocumentLoaded() {
+ // The load event may be firing again if we're coming back to the page by
+ // navigating through the session history, so we need to ensure to only call
+ // this when mThrottleTimeouts hasn't been set yet.
+ if (!mThrottleTimeouts) {
+ MaybeStartThrottleTimeout();
+ }
+}
+
+void TimeoutManager::MaybeStartThrottleTimeout() {
+ if (StaticPrefs::dom_timeout_throttling_delay() <= 0 || mWindow.IsDying() ||
+ mWindow.IsSuspended()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
+
+ MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+ ("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
+ this, StaticPrefs::dom_timeout_throttling_delay()));
+
+ nsCOMPtr<nsITimerCallback> callback = new ThrottleTimeoutsCallback(&mWindow);
+
+ NS_NewTimerWithCallback(getter_AddRefs(mThrottleTimeoutsTimer), callback,
+ StaticPrefs::dom_timeout_throttling_delay(),
+ nsITimer::TYPE_ONE_SHOT, EventTarget());
+}
+
+void TimeoutManager::BeginSyncOperation() {
+ // If we're beginning a sync operation, the currently running
+ // timeout will be put on hold. To not get into an inconsistent
+ // state, where the currently running timeout appears to take time
+ // equivalent to the period of us spinning up a new event loop,
+ // record what we have and stop recording until we reach
+ // EndSyncOperation.
+ RecordExecution(mRunningTimeout, nullptr);
+}
+
+void TimeoutManager::EndSyncOperation() {
+ // If we're running a timeout, restart the measurement from here.
+ RecordExecution(nullptr, mRunningTimeout);
+}
+
+nsIEventTarget* TimeoutManager::EventTarget() {
+ return mWindow.GetBrowsingContextGroup()->GetTimerEventQueue();
+}
diff --git a/dom/base/TimeoutManager.h b/dom/base/TimeoutManager.h
new file mode 100644
index 0000000000..e5fa834f1b
--- /dev/null
+++ b/dom/base/TimeoutManager.h
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TimeoutManager_h__
+#define mozilla_dom_TimeoutManager_h__
+
+#include "mozilla/dom/Timeout.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsITimer;
+class nsGlobalWindowInner;
+
+namespace mozilla {
+
+class PerformanceCounter;
+
+namespace dom {
+
+class TimeoutExecutor;
+class TimeoutHandler;
+
+// This class manages the timeouts in a Window's setTimeout/setInterval pool.
+class TimeoutManager final {
+ private:
+ struct Timeouts;
+
+ public:
+ TimeoutManager(nsGlobalWindowInner& aWindow, uint32_t aMaxIdleDeferMS);
+ ~TimeoutManager();
+ TimeoutManager(const TimeoutManager& rhs) = delete;
+ void operator=(const TimeoutManager& rhs) = delete;
+
+ bool IsRunningTimeout() const;
+
+ static uint32_t GetNestingLevel() { return sNestingLevel; }
+ static void SetNestingLevel(uint32_t aLevel) { sNestingLevel = aLevel; }
+
+ bool HasTimeouts() const {
+ return !mTimeouts.IsEmpty() || !mIdleTimeouts.IsEmpty();
+ }
+
+ nsresult SetTimeout(TimeoutHandler* aHandler, int32_t interval,
+ bool aIsInterval, mozilla::dom::Timeout::Reason aReason,
+ int32_t* aReturn);
+ void ClearTimeout(int32_t aTimerId, mozilla::dom::Timeout::Reason aReason);
+ bool ClearTimeoutInternal(int32_t aTimerId,
+ mozilla::dom::Timeout::Reason aReason,
+ bool aIsIdle);
+
+ // The timeout implementation functions.
+ MOZ_CAN_RUN_SCRIPT
+ void RunTimeout(const TimeStamp& aNow, const TimeStamp& aTargetDeadline,
+ bool aProcessIdle);
+
+ void ClearAllTimeouts();
+ uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
+
+ TimeDuration CalculateDelay(Timeout* aTimeout) const;
+
+ // aTimeout is the timeout that we're about to start running. This function
+ // returns the current timeout.
+ mozilla::dom::Timeout* BeginRunningTimeout(mozilla::dom::Timeout* aTimeout);
+ // aTimeout is the last running timeout.
+ void EndRunningTimeout(mozilla::dom::Timeout* aTimeout);
+
+ void UnmarkGrayTimers();
+
+ // These four methods are intended to be called from the corresponding methods
+ // on nsGlobalWindow.
+ void Suspend();
+ void Resume();
+ void Freeze();
+ void Thaw();
+
+ // This should be called by nsGlobalWindow when the window might have moved
+ // to the background or foreground.
+ void UpdateBackgroundState();
+
+ // The document finished loading
+ void OnDocumentLoaded();
+ void StartThrottlingTimeouts();
+
+ // Run some code for each Timeout in our list. Note that this function
+ // doesn't guarantee that Timeouts are iterated in any particular order.
+ template <class Callable>
+ void ForEachUnorderedTimeout(Callable c) {
+ mIdleTimeouts.ForEach(c);
+ mTimeouts.ForEach(c);
+ }
+
+ void BeginSyncOperation();
+ void EndSyncOperation();
+
+ nsIEventTarget* EventTarget();
+
+ bool BudgetThrottlingEnabled(bool aIsBackground) const;
+
+ static const uint32_t InvalidFiringId;
+
+ void SetLoading(bool value);
+
+ private:
+ void MaybeStartThrottleTimeout();
+
+ // Return true if |aTimeout| needs to be reinserted into the timeout list.
+ bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout,
+ const TimeStamp& aLastCallbackTime,
+ const TimeStamp& aCurrentNow);
+
+ void MoveIdleToActive();
+
+ bool IsBackground() const;
+
+ bool IsActive() const;
+
+ uint32_t CreateFiringId();
+
+ void DestroyFiringId(uint32_t aFiringId);
+
+ bool IsValidFiringId(uint32_t aFiringId) const;
+
+ bool IsInvalidFiringId(uint32_t aFiringId) const;
+
+ TimeDuration MinSchedulingDelay() const;
+
+ nsresult MaybeSchedule(const TimeStamp& aWhen,
+ const TimeStamp& aNow = TimeStamp::Now());
+
+ void RecordExecution(Timeout* aRunningTimeout, Timeout* aTimeout);
+
+ void UpdateBudget(const TimeStamp& aNow,
+ const TimeDuration& aDuration = TimeDuration());
+
+ mozilla::PerformanceCounter* GetPerformanceCounter();
+
+ private:
+ struct Timeouts {
+ explicit Timeouts(const TimeoutManager& aManager)
+ : mManager(aManager), mTimeouts(new Timeout::TimeoutSet()) {}
+
+ // Insert aTimeout into the list, before all timeouts that would
+ // fire after it, but no earlier than the last Timeout with a
+ // valid FiringId.
+ enum class SortBy { TimeRemaining, TimeWhen };
+ void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
+
+ const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
+ Timeout* GetFirst() { return mTimeoutList.getFirst(); }
+ const Timeout* GetLast() const { return mTimeoutList.getLast(); }
+ Timeout* GetLast() { return mTimeoutList.getLast(); }
+ bool IsEmpty() const { return mTimeoutList.isEmpty(); }
+ void InsertFront(Timeout* aTimeout) {
+ aTimeout->SetTimeoutContainer(mTimeouts);
+ mTimeoutList.insertFront(aTimeout);
+ }
+ void InsertBack(Timeout* aTimeout) {
+ aTimeout->SetTimeoutContainer(mTimeouts);
+ mTimeoutList.insertBack(aTimeout);
+ }
+ void Clear() {
+ mTimeouts->Clear();
+ mTimeoutList.clear();
+ }
+
+ template <class Callable>
+ void ForEach(Callable c) {
+ for (Timeout* timeout = GetFirst(); timeout;
+ timeout = timeout->getNext()) {
+ c(timeout);
+ }
+ }
+
+ // Returns true when a callback aborts iteration.
+ template <class Callable>
+ bool ForEachAbortable(Callable c) {
+ for (Timeout* timeout = GetFirst(); timeout;
+ timeout = timeout->getNext()) {
+ if (c(timeout)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Timeout* GetTimeout(uint32_t aTimeoutId, Timeout::Reason aReason) {
+ Timeout::TimeoutIdAndReason key = {aTimeoutId, aReason};
+ return mTimeouts->Get(key);
+ }
+
+ private:
+ // The TimeoutManager that owns this Timeouts structure. This is
+ // mainly used to call state inspecting methods like IsValidFiringId().
+ const TimeoutManager& mManager;
+
+ using TimeoutList = mozilla::LinkedList<RefPtr<Timeout>>;
+
+ // mTimeoutList is generally sorted by mWhen, but new values are always
+ // inserted after any Timeouts with a valid FiringId.
+ TimeoutList mTimeoutList;
+
+ // mTimeouts is a set of all the timeouts in the mTimeoutList.
+ // It let's one to have O(1) check whether a timeout id/reason is in the
+ // list.
+ RefPtr<Timeout::TimeoutSet> mTimeouts;
+ };
+
+ // Each nsGlobalWindowInner object has a TimeoutManager member. This
+ // reference points to that holder object.
+ nsGlobalWindowInner& mWindow;
+ // The executor is specific to the nsGlobalWindow/TimeoutManager, but it
+ // can live past the destruction of the window if its scheduled. Therefore
+ // it must be a separate ref-counted object.
+ RefPtr<TimeoutExecutor> mExecutor;
+ // For timeouts run off the idle queue
+ RefPtr<TimeoutExecutor> mIdleExecutor;
+ // The list of timeouts coming from non-tracking scripts.
+ Timeouts mTimeouts;
+ uint32_t mTimeoutIdCounter;
+ uint32_t mNextFiringId;
+#ifdef DEBUG
+ int64_t mFiringIndex;
+ int64_t mLastFiringIndex;
+#endif
+ AutoTArray<uint32_t, 2> mFiringIdStack;
+ mozilla::dom::Timeout* mRunningTimeout;
+
+ // Timeouts that would have fired but are being deferred until MainThread
+ // is idle (because we're loading)
+ Timeouts mIdleTimeouts;
+
+ // The current idle request callback timeout handle
+ uint32_t mIdleCallbackTimeoutCounter;
+
+ nsCOMPtr<nsITimer> mThrottleTimeoutsTimer;
+ mozilla::TimeStamp mLastBudgetUpdate;
+ mozilla::TimeDuration mExecutionBudget;
+
+ bool mThrottleTimeouts;
+ bool mThrottleTrackingTimeouts;
+ bool mBudgetThrottleTimeouts;
+
+ bool mIsLoading;
+
+ static uint32_t sNestingLevel;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/TreeIterator.h b/dom/base/TreeIterator.h
new file mode 100644
index 0000000000..67c8457ef0
--- /dev/null
+++ b/dom/base/TreeIterator.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TreeIterator_h
+#define TreeIterator_h
+
+#include "mozilla/Attributes.h"
+#include "nsIContent.h"
+
+/**
+ * A generic pre-order tree iterator on top of ChildIterator.
+ *
+ * See ChildIterator.h for the kind of iterators you can use as the template
+ * argument for this class.
+ */
+
+namespace mozilla {
+namespace dom {
+
+template <typename ChildIterator>
+class MOZ_STACK_CLASS TreeIterator {
+ enum class Direction {
+ Forward,
+ Backwards,
+ };
+
+ template <Direction aDirection>
+ nsIContent* GetNextChild(ChildIterator& aIter) {
+ return aDirection == Direction::Forward ? aIter.GetNextChild()
+ : aIter.GetPreviousChild();
+ }
+
+ template <Direction>
+ inline void Advance();
+ template <Direction>
+ inline void AdvanceSkippingChildren();
+
+ public:
+ explicit TreeIterator(nsIContent& aRoot) : mRoot(aRoot), mCurrent(&aRoot) {}
+
+ nsIContent* GetCurrent() const { return mCurrent; }
+
+ // Note that this keeps the iterator state consistent in case of failure.
+ inline bool Seek(nsIContent&);
+ inline nsIContent* GetNext();
+ inline nsIContent* GetNextSkippingChildren();
+ inline nsIContent* GetPrev();
+ inline nsIContent* GetPrevSkippingChildren();
+
+ private:
+ using IteratorArray = AutoTArray<ChildIterator, 30>;
+
+ nsIContent& mRoot;
+ nsIContent* mCurrent;
+ IteratorArray mParentIterators;
+};
+
+template <typename ChildIterator>
+template <typename TreeIterator<ChildIterator>::Direction aDirection>
+inline void TreeIterator<ChildIterator>::AdvanceSkippingChildren() {
+ while (true) {
+ if (MOZ_UNLIKELY(mParentIterators.IsEmpty())) {
+ mCurrent = nullptr;
+ return;
+ }
+
+ if (nsIContent* nextSibling =
+ GetNextChild<aDirection>(mParentIterators.LastElement())) {
+ mCurrent = nextSibling;
+ return;
+ }
+ mParentIterators.RemoveLastElement();
+ }
+}
+
+template <typename ChildIterator>
+inline bool TreeIterator<ChildIterator>::Seek(nsIContent& aContent) {
+ IteratorArray parentIterators;
+ nsIContent* current = &aContent;
+ while (current != &mRoot) {
+ nsIContent* parent = ChildIterator::GetParent(*current);
+ if (!parent) {
+ return false;
+ }
+
+ ChildIterator children(parent);
+ if (!children.Seek(current)) {
+ return false;
+ }
+
+ parentIterators.AppendElement(std::move(children));
+ current = parent;
+ }
+
+ parentIterators.Reverse();
+
+ mParentIterators = std::move(parentIterators);
+ mCurrent = &aContent;
+ return true;
+}
+
+template <typename ChildIterator>
+template <typename TreeIterator<ChildIterator>::Direction aDirection>
+inline void TreeIterator<ChildIterator>::Advance() {
+ MOZ_ASSERT(mCurrent);
+ const bool startAtBeginning = aDirection == Direction::Forward;
+ ChildIterator children(mCurrent, startAtBeginning);
+ if (nsIContent* first = GetNextChild<aDirection>(children)) {
+ mCurrent = first;
+ mParentIterators.AppendElement(std::move(children));
+ return;
+ }
+
+ AdvanceSkippingChildren<aDirection>();
+}
+
+template <typename ChildIterator>
+inline nsIContent* TreeIterator<ChildIterator>::GetNext() {
+ Advance<Direction::Forward>();
+ return GetCurrent();
+}
+
+template <typename ChildIterator>
+inline nsIContent* TreeIterator<ChildIterator>::GetPrev() {
+ Advance<Direction::Backwards>();
+ return GetCurrent();
+}
+
+template <typename ChildIterator>
+inline nsIContent* TreeIterator<ChildIterator>::GetNextSkippingChildren() {
+ AdvanceSkippingChildren<Direction::Forward>();
+ return GetCurrent();
+}
+
+template <typename ChildIterator>
+inline nsIContent* TreeIterator<ChildIterator>::GetPrevSkippingChildren() {
+ AdvanceSkippingChildren<Direction::Backwards>();
+ return GetCurrent();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/TreeOrderedArray.h b/dom/base/TreeOrderedArray.h
new file mode 100644
index 0000000000..59f99f4e6e
--- /dev/null
+++ b/dom/base/TreeOrderedArray.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TreeOrderedArray_h
+#define mozilla_dom_TreeOrderedArray_h
+
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+// A sorted tree-ordered list of raw pointers to nodes.
+template <typename Node>
+class TreeOrderedArray {
+ public:
+ operator const nsTArray<Node*>&() const { return mList; }
+
+ const nsTArray<Node*>* operator->() const { return &mList; }
+
+ // Inserts a node into the list, and returns the new index in the array.
+ //
+ // All the nodes in the list should be in the same subtree, and debug builds
+ // assert this.
+ //
+ // It's also forbidden to call Insert() with the same node multiple times, and
+ // it will assert as well.
+ inline size_t Insert(Node&);
+
+ bool RemoveElement(Node& aNode) { return mList.RemoveElement(&aNode); }
+
+ void Clear() { mList.Clear(); }
+
+ private:
+ AutoTArray<Node*, 1> mList;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/TreeOrderedArrayInlines.h b/dom/base/TreeOrderedArrayInlines.h
new file mode 100644
index 0000000000..ebc87fe149
--- /dev/null
+++ b/dom/base/TreeOrderedArrayInlines.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TreeOrderedArrayInlines_h
+#define mozilla_dom_TreeOrderedArrayInlines_h
+
+#include "mozilla/dom/TreeOrderedArray.h"
+#include "mozilla/BinarySearch.h"
+#include "nsContentUtils.h"
+#include <type_traits>
+
+namespace mozilla::dom {
+
+template <typename Node>
+size_t TreeOrderedArray<Node>::Insert(Node& aNode) {
+ static_assert(std::is_base_of<nsINode, Node>::value, "Should be a node");
+
+#ifdef DEBUG
+ for (Node* n : mList) {
+ MOZ_ASSERT(n->SubtreeRoot() == aNode.SubtreeRoot(),
+ "Should only insert nodes on the same subtree");
+ }
+#endif
+
+ if (mList.IsEmpty()) {
+ mList.AppendElement(&aNode);
+ return 0;
+ }
+
+ struct PositionComparator {
+ Node& mNode;
+ explicit PositionComparator(Node& aNode) : mNode(aNode) {}
+
+ int operator()(void* aNode) const {
+ auto* curNode = static_cast<Node*>(aNode);
+ MOZ_DIAGNOSTIC_ASSERT(curNode != &mNode,
+ "Tried to insert a node already in the list");
+ if (nsContentUtils::PositionIsBefore(&mNode, curNode)) {
+ return -1;
+ }
+ return 1;
+ }
+ };
+
+ size_t idx;
+ BinarySearchIf(mList, 0, mList.Length(), PositionComparator(aNode), &idx);
+ mList.InsertElementAt(idx, &aNode);
+ return idx;
+}
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/base/TreeWalker.cpp b/dom/base/TreeWalker.cpp
new file mode 100644
index 0000000000..e7a39dff13
--- /dev/null
+++ b/dom/base/TreeWalker.cpp
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Traversal's TreeWalker
+ */
+
+#include "mozilla/dom/TreeWalker.h"
+
+#include "nsIContent.h"
+#include "nsError.h"
+#include "nsINode.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/dom/TreeWalkerBinding.h"
+
+namespace mozilla::dom {
+
+/*
+ * Factories, constructors and destructors
+ */
+
+TreeWalker::TreeWalker(nsINode* aRoot, uint32_t aWhatToShow,
+ NodeFilter* aFilter)
+ : nsTraversal(aRoot, aWhatToShow, aFilter), mCurrentNode(aRoot) {}
+
+TreeWalker::~TreeWalker() { /* destructor code */
+}
+
+/*
+ * nsISupports and cycle collection stuff
+ */
+
+NS_IMPL_CYCLE_COLLECTION(TreeWalker, mFilter, mCurrentNode, mRoot)
+
+// QueryInterface implementation for TreeWalker
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TreeWalker)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// Have to pass in dom::TreeWalker because a11y has an a11y::TreeWalker that
+// passes TreeWalker so refcount logging would get confused on the name
+// collision.
+NS_IMPL_CYCLE_COLLECTING_ADDREF(dom::TreeWalker)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(dom::TreeWalker)
+
+void TreeWalker::SetCurrentNode(nsINode& aNode, ErrorResult& aResult) {
+ aResult = nsContentUtils::CheckSameOrigin(mRoot, &aNode);
+ if (aResult.Failed()) {
+ return;
+ }
+
+ mCurrentNode = &aNode;
+}
+
+already_AddRefed<nsINode> TreeWalker::ParentNode(ErrorResult& aResult) {
+ nsCOMPtr<nsINode> node = mCurrentNode;
+
+ while (node && node != mRoot) {
+ node = node->GetParentNode();
+
+ if (node) {
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsINode> TreeWalker::FirstChild(ErrorResult& aResult) {
+ return FirstChildInternal(false, aResult);
+}
+
+already_AddRefed<nsINode> TreeWalker::LastChild(ErrorResult& aResult) {
+ return FirstChildInternal(true, aResult);
+}
+
+already_AddRefed<nsINode> TreeWalker::PreviousSibling(ErrorResult& aResult) {
+ return NextSiblingInternal(true, aResult);
+}
+
+already_AddRefed<nsINode> TreeWalker::NextSibling(ErrorResult& aResult) {
+ return NextSiblingInternal(false, aResult);
+}
+
+already_AddRefed<nsINode> TreeWalker::PreviousNode(ErrorResult& aResult) {
+ nsCOMPtr<nsINode> node = mCurrentNode;
+
+ while (node != mRoot) {
+ while (nsINode* previousSibling = node->GetPreviousSibling()) {
+ node = previousSibling;
+
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ nsINode* lastChild;
+ while (filtered != NodeFilter_Binding::FILTER_REJECT &&
+ (lastChild = node->GetLastChild())) {
+ node = lastChild;
+ filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+
+ if (node == mRoot) {
+ break;
+ }
+
+ node = node->GetParentNode();
+ if (!node) {
+ break;
+ }
+
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsINode> TreeWalker::NextNode(ErrorResult& aResult) {
+ int16_t filtered =
+ NodeFilter_Binding::FILTER_ACCEPT; // pre-init for inner loop
+
+ nsCOMPtr<nsINode> node = mCurrentNode;
+
+ while (1) {
+ nsINode* firstChild;
+ while (filtered != NodeFilter_Binding::FILTER_REJECT &&
+ (firstChild = node->GetFirstChild())) {
+ node = firstChild;
+
+ filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+
+ nsINode* sibling = nullptr;
+ nsINode* temp = node;
+ do {
+ if (temp == mRoot) break;
+
+ sibling = temp->GetNextSibling();
+ if (sibling) break;
+
+ temp = temp->GetParentNode();
+ } while (temp);
+
+ if (!sibling) break;
+
+ node = sibling;
+
+ // Found a sibling. Either ours or ancestor's
+ filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+/*
+ * TreeWalker helper functions
+ */
+
+/*
+ * Implements FirstChild and LastChild which only vary in which direction
+ * they search.
+ * @param aReversed Controls whether we search forwards or backwards
+ * @param aResult Whether we threw or not.
+ * @returns The desired node. Null if no child is found
+ */
+already_AddRefed<nsINode> TreeWalker::FirstChildInternal(bool aReversed,
+ ErrorResult& aResult) {
+ nsCOMPtr<nsINode> node =
+ aReversed ? mCurrentNode->GetLastChild() : mCurrentNode->GetFirstChild();
+
+ while (node) {
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ switch (filtered) {
+ case NodeFilter_Binding::FILTER_ACCEPT:
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ case NodeFilter_Binding::FILTER_SKIP: {
+ nsINode* child =
+ aReversed ? node->GetLastChild() : node->GetFirstChild();
+ if (child) {
+ node = child;
+ continue;
+ }
+ break;
+ }
+ case NodeFilter_Binding::FILTER_REJECT:
+ // Keep searching
+ break;
+ }
+
+ do {
+ nsINode* sibling =
+ aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
+ if (sibling) {
+ node = sibling;
+ break;
+ }
+
+ nsINode* parent = node->GetParentNode();
+
+ if (!parent || parent == mRoot || parent == mCurrentNode) {
+ return nullptr;
+ }
+
+ node = parent;
+
+ } while (node);
+ }
+
+ return nullptr;
+}
+
+/*
+ * Implements NextSibling and PreviousSibling which only vary in which
+ * direction they search.
+ * @param aReversed Controls whether we search forwards or backwards
+ * @param aResult Whether we threw or not.
+ * @returns The desired node. Null if no child is found
+ */
+already_AddRefed<nsINode> TreeWalker::NextSiblingInternal(
+ bool aReversed, ErrorResult& aResult) {
+ nsCOMPtr<nsINode> node = mCurrentNode;
+
+ if (node == mRoot) {
+ return nullptr;
+ }
+
+ while (1) {
+ nsINode* sibling =
+ aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
+
+ while (sibling) {
+ node = sibling;
+
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ }
+
+ // If rejected or no children, try a sibling
+ if (filtered == NodeFilter_Binding::FILTER_REJECT ||
+ !(sibling =
+ aReversed ? node->GetLastChild() : node->GetFirstChild())) {
+ sibling =
+ aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
+ }
+ }
+
+ node = node->GetParentNode();
+
+ if (!node || node == mRoot) {
+ return nullptr;
+ }
+
+ // Is parent transparent in filtered view?
+ int16_t filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
+ return nullptr;
+ }
+ }
+}
+
+bool TreeWalker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return TreeWalker_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/TreeWalker.h b/dom/base/TreeWalker.h
new file mode 100644
index 0000000000..4580a44b1f
--- /dev/null
+++ b/dom/base/TreeWalker.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Traversal's TreeWalker
+ */
+
+#ifndef mozilla_dom_TreeWalker_h
+#define mozilla_dom_TreeWalker_h
+
+#include "nsISupports.h"
+#include "nsTraversal.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsINode;
+
+namespace mozilla::dom {
+
+class TreeWalker final : public nsISupports, public nsTraversal {
+ virtual ~TreeWalker();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ TreeWalker(nsINode* aRoot, uint32_t aWhatToShow, NodeFilter* aFilter);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(TreeWalker)
+
+ // WebIDL API
+ nsINode* Root() const { return mRoot; }
+ uint32_t WhatToShow() const { return mWhatToShow; }
+ NodeFilter* GetFilter() { return mFilter; }
+ nsINode* CurrentNode() const { return mCurrentNode; }
+ void SetCurrentNode(nsINode& aNode, ErrorResult& aResult);
+ // All our traversal methods return strong refs because filtering can
+ // remove nodes from the tree.
+ already_AddRefed<nsINode> ParentNode(ErrorResult& aResult);
+ already_AddRefed<nsINode> FirstChild(ErrorResult& aResult);
+ already_AddRefed<nsINode> LastChild(ErrorResult& aResult);
+ already_AddRefed<nsINode> PreviousSibling(ErrorResult& aResult);
+ already_AddRefed<nsINode> NextSibling(ErrorResult& aResult);
+ already_AddRefed<nsINode> PreviousNode(ErrorResult& aResult);
+ already_AddRefed<nsINode> NextNode(ErrorResult& aResult);
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ private:
+ nsCOMPtr<nsINode> mCurrentNode;
+
+ /*
+ * Implements FirstChild and LastChild which only vary in which direction
+ * they search.
+ * @param aReversed Controls whether we search forwards or backwards
+ * @param aResult Whether we threw or not.
+ * @returns The desired node. Null if no child is found
+ */
+ already_AddRefed<nsINode> FirstChildInternal(bool aReversed,
+ ErrorResult& aResult);
+
+ /*
+ * Implements NextSibling and PreviousSibling which only vary in which
+ * direction they search.
+ * @param aReversed Controls whether we search forwards or backwards
+ * @param aResult Whether we threw or not.
+ * @returns The desired node. Null if no child is found
+ */
+ already_AddRefed<nsINode> NextSiblingInternal(bool aReversed,
+ ErrorResult& aResult);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TreeWalker_h
diff --git a/dom/base/UIDirectionManager.cpp b/dom/base/UIDirectionManager.cpp
new file mode 100644
index 0000000000..7c40932d47
--- /dev/null
+++ b/dom/base/UIDirectionManager.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/UIDirectionManager.h"
+#include "mozilla/Preferences.h"
+#include "nsIWindowMediator.h"
+#include "nsDocShell.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/SimpleEnumerator.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(UIDirectionManager, nsIObserver)
+
+/* static */
+NS_IMETHODIMP
+UIDirectionManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ NS_ENSURE_FALSE(strcmp(aTopic, "intl:app-locales-changed"), NS_ERROR_FAILURE);
+
+ // Iterate over all of the windows and notify them of the direction change.
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ NS_ENSURE_TRUE(windowMediator, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ NS_ENSURE_TRUE(windowEnumerator, NS_ERROR_FAILURE);
+
+ for (auto& elements : SimpleEnumerator<nsISupports>(windowEnumerator)) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(elements);
+ if (window->Closed()) {
+ continue;
+ }
+
+ RefPtr<BrowsingContext> context = window->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(context);
+
+ if (context->IsDiscarded()) {
+ continue;
+ }
+
+ context->PreOrderWalk([](BrowsingContext* aContext) {
+ if (dom::Document* doc = aContext->GetDocument()) {
+ doc->ResetDocumentDirection();
+ }
+ });
+ }
+ return NS_OK;
+}
+
+/* static */
+void UIDirectionManager::Initialize() {
+ MOZ_ASSERT(!gUIDirectionManager);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<UIDirectionManager> observer = new UIDirectionManager();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+ obs->AddObserver(observer, "intl:app-locales-changed", false);
+
+ gUIDirectionManager = observer;
+}
+
+/* static */
+void UIDirectionManager::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gUIDirectionManager) {
+ return;
+ }
+ RefPtr<UIDirectionManager> observer = gUIDirectionManager;
+ gUIDirectionManager = nullptr;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return;
+ }
+
+ obs->RemoveObserver(observer, "intl:app-locales-changed");
+}
+
+mozilla::StaticRefPtr<UIDirectionManager>
+ UIDirectionManager::gUIDirectionManager;
+
+} // namespace mozilla::dom
diff --git a/dom/base/UIDirectionManager.h b/dom/base/UIDirectionManager.h
new file mode 100644
index 0000000000..2427b50116
--- /dev/null
+++ b/dom/base/UIDirectionManager.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_UIDirectionManager_h
+#define mozilla_dom_UIDirectionManager_h
+
+#include "nsIObserver.h"
+
+namespace mozilla::dom {
+
+class UIDirectionManager final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static void Initialize();
+ static void Shutdown();
+
+ private:
+ UIDirectionManager() = default;
+ virtual ~UIDirectionManager() = default;
+ static mozilla::StaticRefPtr<UIDirectionManager> gUIDirectionManager;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/UseCounter.h b/dom/base/UseCounter.h
new file mode 100644
index 0000000000..de978ed938
--- /dev/null
+++ b/dom/base/UseCounter.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef UseCounter_h_
+#define UseCounter_h_
+
+#include <stdint.h>
+#include "mozilla/BitSet.h"
+
+namespace mozilla {
+
+enum UseCounter : int16_t {
+ eUseCounter_UNKNOWN = -1,
+#define USE_COUNTER_DOM_METHOD(interface_, name_) \
+ eUseCounter_##interface_##_##name_,
+#define USE_COUNTER_DOM_ATTRIBUTE(interface_, name_) \
+ eUseCounter_##interface_##_##name_##_getter, \
+ eUseCounter_##interface_##_##name_##_setter,
+#define USE_COUNTER_CUSTOM(name_, desc_) eUseCounter_custom_##name_,
+#include "mozilla/dom/UseCounterList.h"
+#undef USE_COUNTER_DOM_METHOD
+#undef USE_COUNTER_DOM_ATTRIBUTE
+#undef USE_COUNTER_CUSTOM
+
+#define DEPRECATED_OPERATION(op_) eUseCounter_##op_,
+#include "nsDeprecatedOperationList.h"
+#undef DEPRECATED_OPERATION
+
+ eUseCounter_FirstCSSProperty,
+ __reset_hack = eUseCounter_FirstCSSProperty - 1,
+
+// Need an extra level of macro nesting to force expansion of method_
+// params before they get pasted.
+#define CSS_PROP_USE_COUNTER(method_) eUseCounter_property_##method_,
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+#define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
+ CSS_PROP_USE_COUNTER(method_)
+#define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
+ CSS_PROP_USE_COUNTER(method_)
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
+ CSS_PROP_USE_COUNTER(method_)
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+#undef CSS_PROP_USE_COUNTER
+
+ eUseCounter_EndCSSProperties,
+ eUseCounter_FirstCountedUnknownProperty = eUseCounter_EndCSSProperties,
+ __reset_hack_2 = eUseCounter_FirstCountedUnknownProperty - 1,
+
+#define COUNTED_UNKNOWN_PROPERTY(name_, method_) \
+ eUseCounter_unknown_property_##method_,
+#include "mozilla/CountedUnknownProperties.h"
+#undef COUNTED_UNKNOWN_PROPERTY
+
+ eUseCounter_Count
+};
+
+using UseCounters = BitSet<eUseCounter_Count, uint64_t>;
+
+enum class UseCounterWorker : int16_t {
+ Unknown = -1,
+#define USE_COUNTER_DOM_METHOD(interface_, name_) interface_##_##name_,
+#define USE_COUNTER_DOM_ATTRIBUTE(interface_, name_) \
+ interface_##_##name_##_getter, interface_##_##name_##_setter,
+#define USE_COUNTER_CUSTOM(name_, desc_) Custom_##name_,
+#include "mozilla/dom/UseCounterWorkerList.h"
+#undef USE_COUNTER_DOM_METHOD
+#undef USE_COUNTER_DOM_ATTRIBUTE
+#undef USE_COUNTER_CUSTOM
+ Count
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/UseCounters.conf b/dom/base/UseCounters.conf
new file mode 100644
index 0000000000..f00105a1dd
--- /dev/null
+++ b/dom/base/UseCounters.conf
@@ -0,0 +1,409 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This file defines a list of use counters, which are things that can
+// record usage of Web platform features and then report this information
+// through Telemetry.
+//
+// The format of this file is very strict. Each line can be:
+//
+// (a) a blank line
+//
+// (b) a comment, which is a line that begins with "//"
+//
+// (c) one of three possible use counter declarations:
+//
+// method <IDL interface name>.<IDL operation name>
+// attribute <IDL interface name>.<IDL attribute name>
+// custom <any valid identifier> <description>
+//
+// The <description> for custom counters will be appended to "Whether a document "
+// or "Whether a page ", so phrase it appropriately. For instance, "constructs a
+// Foo object" or "calls Document.bar('some value')". It may contain any
+// character (including whitespace).
+//
+// To actually cause use counters to be incremented, DOM methods
+// and attributes must have a [UseCounter] extended attribute in
+// the Web IDL file.
+//
+// Custom counters are incremented when
+// SetUseCounter(eUseCounter_custom_MyName) is called on a Document object.
+//
+// You might reasonably ask why we have this file and we require
+// annotating things with [UseCounter] in the relevant WebIDL file as
+// well. Generating things from bindings codegen and ensuring all the
+// dependencies were correct would have been rather difficult.
+//
+// NOTE: Adding use counters requires data review, see
+// https://wiki.mozilla.org/Data_Collection
+
+method SVGSVGElement.getElementById
+attribute SVGSVGElement.currentScale
+
+// Push API
+method PushManager.subscribe
+method PushSubscription.unsubscribe
+
+// window.sidebar
+attribute Window.sidebar
+
+// DataTransfer API (gecko-only methods)
+method DataTransfer.addElement
+attribute DataTransfer.mozItemCount
+attribute DataTransfer.mozCursor
+method DataTransfer.mozTypesAt
+method DataTransfer.mozClearDataAt
+method DataTransfer.mozSetDataAt
+method DataTransfer.mozGetDataAt
+attribute DataTransfer.mozUserCancelled
+attribute DataTransfer.mozSourceNode
+
+// Marquee events
+custom onstart sets a <marquee> onstart event listener
+custom onbounce sets a <marquee> onbounce event listener
+custom onfinish sets a <marquee> onfinish event listener
+
+// Element non-standard events
+custom onoverflow sets an element onoverflow event listener
+custom onunderflow sets an element onunderflow event listener
+
+// JavaScript feature usage
+custom JS_asmjs uses asm.js
+custom JS_wasm uses WebAssembly
+
+// Console API
+method console.assert
+method console.clear
+method console.count
+method console.countReset
+method console.debug
+method console.error
+method console.info
+method console.log
+method console.table
+method console.trace
+method console.warn
+method console.dir
+method console.dirxml
+method console.group
+method console.groupCollapsed
+method console.groupEnd
+method console.time
+method console.timeLog
+method console.timeEnd
+method console.exception
+method console.timeStamp
+method console.profile
+method console.profileEnd
+
+// document.open information
+custom DocumentOpen calls document.open in a way that creates a new Window object
+
+// HTMLDocument named getter
+custom HTMLDocumentNamedGetterHit calls to the named getter on HTMLDocument that find something via the name lookup
+
+custom FilteredCrossOriginIFrame cross-origin <iframe> within a CSS/SVG filter
+
+// Custom Elements
+method CustomElementRegistry.define
+custom CustomizedBuiltin registers a customized built-in element
+
+// XSLT
+custom XSLStylesheet uses an XSL Stylesheet
+method XSLTProcessor.constructor
+
+// Shadow DOM
+method Element.attachShadow
+
+// Non-standard capture methods
+method Element.setCapture
+method Element.releaseCapture
+
+// Pointer Capture
+method Element.setPointerCapture
+method Element.releasePointerCapture
+
+// Media Device Access
+method MediaDevices.enumerateDevices
+custom EnumerateDevicesInsec calls MediaDevices.enumerateDevices from an insecure context
+custom EnumerateDevicesUnfocused calls MediaDevices.enumerateDevices from a unfocused document
+method MediaDevices.getUserMedia
+method Navigator.mozGetUserMedia
+custom GetUserMediaUnfocused calls MediaDevices.getUserMedia from an unfocused document
+custom GetUserMediaInsec calls MediaDevices.getUserMedia from an insecure context
+custom MozGetUserMediaInsec calls Navigator.mozGetUserMedia from an insecure context
+method MediaDevices.getDisplayMedia
+
+// Non-standard Document.mozSetImageElement.
+method Document.mozSetImageElement
+
+// Non-standard Window.DOMMouseScroll and MozMousePixelScroll
+custom ondommousescroll sets DOMMouseScroll event listener
+custom onmozmousepixelscroll sets MozMousePixelScroll event listener
+
+custom PercentageStrokeWidthInSVG whether percentage stroke-width is used in SVG elements (excluding <text>)
+custom PercentageStrokeWidthInSVGText whether percentage stroke-width is used in SVG <text>
+
+// Missing-property use counters. We claim these are "method" use
+// counters, because we don't need a separate description string for
+// them and we only need one use counter, not a getter/setter pair.
+method HTMLDocument.adoptedStyleSheets
+method HTMLDocument.caretRangeFromPoint
+method HTMLDocument.clear
+method HTMLDocument.exitPictureInPicture
+method HTMLDocument.featurePolicy
+method HTMLDocument.onbeforecopy
+method HTMLDocument.onbeforecut
+method HTMLDocument.onbeforepaste
+method HTMLDocument.oncancel
+method HTMLDocument.onfreeze
+method HTMLDocument.onmousewheel
+method HTMLDocument.onresume
+method HTMLDocument.onsearch
+method HTMLDocument.onsecuritypolicyviolation
+method HTMLDocument.onwebkitfullscreenchange
+method HTMLDocument.onwebkitfullscreenerror
+method HTMLDocument.pictureInPictureElement
+method HTMLDocument.pictureInPictureEnabled
+method HTMLDocument.registerElement
+method HTMLDocument.wasDiscarded
+method HTMLDocument.webkitCancelFullScreen
+method HTMLDocument.webkitCurrentFullScreenElement
+method HTMLDocument.webkitExitFullscreen
+method HTMLDocument.webkitFullscreenElement
+method HTMLDocument.webkitFullscreenEnabled
+method HTMLDocument.webkitHidden
+method HTMLDocument.webkitIsFullScreen
+method HTMLDocument.webkitVisibilityState
+method HTMLDocument.xmlEncoding
+method HTMLDocument.xmlStandalone
+method HTMLDocument.xmlVersion
+method Window.AbsoluteOrientationSensor
+method Window.Accelerometer
+method Window.ApplicationCache
+method Window.ApplicationCacheErrorEvent
+method Window.Atomics
+method Window.AudioParamMap
+method Window.AudioWorklet
+method Window.AudioWorkletNode
+method Window.BackgroundFetchManager
+method Window.BackgroundFetchRecord
+method Window.BackgroundFetchRegistration
+method Window.BeforeInstallPromptEvent
+method Window.Bluetooth
+method Window.BluetoothCharacteristicProperties
+method Window.BluetoothDevice
+method Window.BluetoothRemoteGATTCharacteristic
+method Window.BluetoothRemoteGATTDescriptor
+method Window.BluetoothRemoteGATTServer
+method Window.BluetoothRemoteGATTService
+method Window.BluetoothUUID
+method Window.CanvasCaptureMediaStreamTrack
+method Window.chrome
+method Window.clientInformation
+method Window.ClipboardItem
+method Window.CSSImageValue
+method Window.CSSKeywordValue
+method Window.CSSMathInvert
+method Window.CSSMathMax
+method Window.CSSMathMin
+method Window.CSSMathNegate
+method Window.CSSMathProduct
+method Window.CSSMathSum
+method Window.CSSMathValue
+method Window.CSSMatrixComponent
+method Window.CSSNumericArray
+method Window.CSSNumericValue
+method Window.CSSPerspective
+method Window.CSSPositionValue
+method Window.CSSRotate
+method Window.CSSScale
+method Window.CSSSkew
+method Window.CSSSkewX
+method Window.CSSSkewY
+method Window.CSSStyleValue
+method Window.CSSTransformComponent
+method Window.CSSTransformValue
+method Window.CSSTranslate
+method Window.CSSUnitValue
+method Window.CSSUnparsedValue
+method Window.CSSVariableReferenceValue
+method Window.defaultStatus
+// See comments in Window.webidl about why this is disabled.
+//method Window.defaultstatus
+method Window.DeviceMotionEventAcceleration
+method Window.DeviceMotionEventRotationRate
+method Window.DOMError
+method Window.EnterPictureInPictureEvent
+method Window.External
+method Window.FederatedCredential
+method Window.Gyroscope
+method Window.HTMLContentElement
+method Window.HTMLDialogElement
+method Window.HTMLShadowElement
+method Window.ImageCapture
+method Window.InputDeviceCapabilities
+method Window.InputDeviceInfo
+method Window.Keyboard
+method Window.KeyboardLayoutMap
+method Window.LinearAccelerationSensor
+method Window.Lock
+method Window.LockManager
+method Window.MediaMetadata
+method Window.MediaSession
+method Window.MediaSettingsRange
+method Window.MIDIAccess
+method Window.MIDIConnectionEvent
+method Window.MIDIInput
+method Window.MIDIInputMap
+method Window.MIDIMessageEvent
+method Window.MIDIOutput
+method Window.MIDIOutputMap
+method Window.MIDIPort
+method Window.NavigationPreloadManager
+method Window.NetworkInformation
+method Window.offscreenBuffering
+method Window.OffscreenCanvas
+method Window.OffscreenCanvasRenderingContext2D
+method Window.onbeforeinstallprompt
+method Window.oncancel
+method Window.onmousewheel
+method Window.onsearch
+method Window.onselectionchange
+method Window.openDatabase
+method Window.OrientationSensor
+method Window.OverconstrainedError
+method Window.PasswordCredential
+method Window.PaymentAddress
+method Window.PaymentInstruments
+method Window.PaymentManager
+method Window.PaymentMethodChangeEvent
+method Window.PaymentRequest
+method Window.PaymentRequestUpdateEvent
+method Window.PaymentResponse
+method Window.PerformanceEventTiming
+method Window.PerformanceLongTaskTiming
+method Window.PerformancePaintTiming
+method Window.PhotoCapabilities
+method Window.PictureInPictureWindow
+method Window.Presentation
+method Window.PresentationAvailability
+method Window.PresentationConnection
+method Window.PresentationConnectionAvailableEvent
+method Window.PresentationConnectionCloseEvent
+method Window.PresentationConnectionList
+method Window.PresentationReceiver
+method Window.PresentationRequest
+method Window.RelativeOrientationSensor
+method Window.RemotePlayback
+method Window.ReportingObserver
+method Window.RTCDtlsTransport
+method Window.RTCError
+method Window.RTCErrorEvent
+method Window.RTCIceTransport
+method Window.RTCSctpTransport
+method Window.Sensor
+method Window.SensorErrorEvent
+method Window.SharedArrayBuffer
+method Window.styleMedia
+method Window.StylePropertyMap
+method Window.StylePropertyMapReadOnly
+method Window.SVGDiscardElement
+method Window.SyncManager
+method Window.TaskAttributionTiming
+method Window.TextDecoderStream
+method Window.TextEncoderStream
+method Window.TextEvent
+method Window.Touch
+method Window.TouchEvent
+method Window.TouchList
+method Window.TransformStream
+method Window.USB
+method Window.USBAlternateInterface
+method Window.USBConfiguration
+method Window.USBConnectionEvent
+method Window.USBDevice
+method Window.USBEndpoint
+method Window.USBInterface
+method Window.USBInTransferResult
+method Window.USBIsochronousInTransferPacket
+method Window.USBIsochronousInTransferResult
+method Window.USBIsochronousOutTransferPacket
+method Window.USBIsochronousOutTransferResult
+method Window.USBOutTransferResult
+method Window.UserActivation
+method Window.visualViewport
+method Window.webkitCancelAnimationFrame
+method Window.webkitMediaStream
+method Window.WebKitMutationObserver
+method Window.webkitRequestAnimationFrame
+method Window.webkitRequestFileSystem
+method Window.webkitResolveLocalFileSystemURL
+method Window.webkitRTCPeerConnection
+method Window.webkitSpeechGrammar
+method Window.webkitSpeechGrammarList
+method Window.webkitSpeechRecognition
+method Window.webkitSpeechRecognitionError
+method Window.webkitSpeechRecognitionEvent
+method Window.webkitStorageInfo
+method Window.Worklet
+method Window.WritableStream
+
+// Gecko-specific command usage of `Document.execCommand`
+custom DocumentExecCommandContentReadOnly calls execCommand with contentReadOnly
+
+// Methods used in frameworks to create DOM from strings
+method DOMParser.parseFromString
+method Range.createContextualFragment
+
+// Gecko-specific command usage of `Document.queryCommandState` or `Document.queryCommandValue`
+custom DocumentQueryCommandStateOrValueContentReadOnly calls queryCommandState or queryCommandValue with contentReadOnly
+custom DocumentQueryCommandStateOrValueInsertBrOnReturn calls queryCommandState or queryCommandValue with insertBrOnReturn
+
+// Gecko-specific command usage of `Document.queryCommandSupported` or `Document.queryCommandEnabled`
+custom DocumentQueryCommandSupportedOrEnabledContentReadOnly calls queryCommandSupported or queryCommandEnabled with contentReadOnly
+custom DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn calls queryCommandSupported or queryCommandEnabled with insertBrOnReturn
+
+// SVG filters
+custom feBlend uses the feBlend SVG filter.
+custom feColorMatrix uses the feColorMatrix SVG filter.
+custom feComponentTransfer uses the feComponentTransfer SVG filter.
+custom feComposite uses the feComposite SVG filter.
+custom feConvolveMatrix uses the feConvolveMatrix SVG filter.
+custom feDiffuseLighting uses the feDiffuseLighting SVG filter.
+custom feDisplacementMap uses the feDisplacementMap SVG filter.
+custom feFlood uses the feFlood SVG filter.
+custom feGaussianBlur uses the feGaussianBlur SVG filter.
+custom feImage uses the feImage SVG filter.
+custom feMerge uses the feMerge SVG filter.
+custom feMorphology uses the feMorphology SVG filter.
+custom feOffset uses the feOffset SVG filter.
+custom feSpecularLighting uses the feSpecularLighting SVG filter.
+custom feTile uses the feTile SVG filter.
+custom feTurbulence uses the feTurbulence SVG filter.
+
+custom WrFilterFallback triggers the blob fallback for an SVG filter.
+
+// Sanitizer API
+method Sanitizer.constructor
+method Sanitizer.sanitize
+method Element.setHTML
+
+// Features that might be deprecated in the future
+custom WindowOpenEmptyUrl calls window.open with an empty url argument
+
+// Unsupported web APIs in Private Browsing Mode
+custom PrivateBrowsingIDBFactoryOpen calls indexedDB.open in Private Browsing Mode
+custom PrivateBrowsingIDBFactoryDeleteDatabase calls indexedDB.deleteDatabase in Private Browsing Mode
+custom PrivateBrowsingCachesMatch calls caches.match in Private Browsing Mode
+custom PrivateBrowsingCachesHas calls caches.has in Private Browsing Mode
+custom PrivateBrowsingCachesOpen calls caches.open in Private Browsing Mode
+custom PrivateBrowsingCachesDelete calls caches.delete in Private Browsing Mode
+custom PrivateBrowsingCachesKeys calls caches.keys in Private Browsing Mode
+custom PrivateBrowsingNavigatorServiceWorker accesses navigator.serviceWorker in Private Browsing Mode
+
+method Scheduler.postTask
+
+// NOTE: Adding use counters requires data review, see
+// https://wiki.mozilla.org/Data_Collection
diff --git a/dom/base/UseCountersWorker.conf b/dom/base/UseCountersWorker.conf
new file mode 100644
index 0000000000..c912f0ea14
--- /dev/null
+++ b/dom/base/UseCountersWorker.conf
@@ -0,0 +1,77 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This file defines a list of use counters, which are things that can
+// record usage of Web platform features and then report this information
+// through Telemetry.
+//
+// The format of this file is very strict. Each line can be:
+//
+// (a) a blank line
+//
+// (b) a comment, which is a line that begins with "//"
+//
+// (c) one of four possible use counter declarations:
+//
+// method <IDL interface name>.<IDL operation name>
+// attribute <IDL interface name>.<IDL attribute name>
+// custom <any valid identifier> <description>
+//
+// The <description> for custom counters will be appended to "Whether a
+// dedicated worker " or "Whether a shared worker" or "Whether a service worker
+// ", so phrase it appropriately. For instance, "constructs a
+// Foo object" or "calls Bar.baz('some value')". It may contain any character
+// (including whitespace).
+//
+// To actually cause use counters to be incremented, DOM methods
+// and attributes must have a [UseCounter] extended attribute and be exposed to
+// workers in the Web IDL file.
+// Custom counters are incremented when
+// SetUseCounter(UseCounterWoker::Custom_MyName) is called on a WorkerPrivate
+// object.
+//
+// You might reasonably ask why we have this file and we require
+// annotating things with [UseCounter] in the relevant WebIDL file as
+// well. Generating things from bindings codegen and ensuring all the
+// dependencies were correct would have been rather difficult.
+
+// Push API
+method PushManager.subscribe
+method PushSubscription.unsubscribe
+
+// Console API
+method console.assert
+method console.clear
+method console.count
+method console.countReset
+method console.debug
+method console.error
+method console.info
+method console.log
+method console.table
+method console.trace
+method console.warn
+method console.dir
+method console.dirxml
+method console.group
+method console.groupCollapsed
+method console.groupEnd
+method console.time
+method console.timeLog
+method console.timeEnd
+method console.exception
+method console.timeStamp
+method console.profile
+method console.profileEnd
+
+// Unsupported web APIs in Private Browsing Mode
+custom PrivateBrowsingIDBFactoryOpen calls indexedDB.open in Private Browsing Mode
+custom PrivateBrowsingIDBFactoryDeleteDatabase calls indexedDB.deleteDatabase in Private Browsing Mode
+custom PrivateBrowsingCachesMatch calls caches.match in Private Browsing Mode
+custom PrivateBrowsingCachesHas calls caches.has in Private Browsing Mode
+custom PrivateBrowsingCachesOpen calls caches.open in Private Browsing Mode
+custom PrivateBrowsingCachesDelete calls caches.delete in Private Browsing Mode
+custom PrivateBrowsingCachesKeys calls caches.keys in Private Browsing Mode
+
+method Scheduler.postTask
diff --git a/dom/base/UserActivation.cpp b/dom/base/UserActivation.cpp
new file mode 100644
index 0000000000..95dc5fc7d0
--- /dev/null
+++ b/dom/base/UserActivation.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/UserActivation.h"
+
+#include "mozilla/TextEvents.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// The current depth of user and keyboard inputs. sUserInputEventDepth
+// is the number of any user input events, page load events and mouse over
+// events. sUserKeyboardEventDepth is the number of keyboard input events.
+// Incremented whenever we start handling a user input, decremented when we
+// have finished handling a user input. This depth is *not* reset in case
+// of nested event loops.
+static int32_t sUserInputEventDepth = 0;
+static int32_t sUserKeyboardEventDepth = 0;
+
+// Time at which we began handling user input. Reset to the epoch
+// once we have finished handling user input.
+static TimeStamp sHandlingInputStart;
+
+// Time at which we began handling the latest user input. Not reset
+// at the end of the input.
+static TimeStamp sLatestUserInputStart;
+
+} // namespace
+
+/* static */
+bool UserActivation::IsHandlingUserInput() { return sUserInputEventDepth > 0; }
+
+/* static */
+bool UserActivation::IsHandlingKeyboardInput() {
+ return sUserKeyboardEventDepth > 0;
+}
+
+/* static */
+bool UserActivation::IsUserInteractionEvent(const WidgetEvent* aEvent) {
+ if (!aEvent->IsTrusted()) {
+ return false;
+ }
+
+ switch (aEvent->mMessage) {
+ // eKeyboardEventClass
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp:
+ // Not all keyboard events are treated as user input, so that popups
+ // can't be opened, fullscreen mode can't be started, etc at
+ // unexpected time.
+ return aEvent->AsKeyboardEvent()->CanTreatAsUserInput();
+ // eBasicEventClass
+ // eMouseEventClass
+ case eMouseClick:
+ case eMouseDown:
+ case eMouseUp:
+ // ePointerEventClass
+ case ePointerDown:
+ case ePointerUp:
+ // eTouchEventClass
+ case eTouchStart:
+ case eTouchEnd:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* static */
+void UserActivation::StartHandlingUserInput(EventMessage aMessage) {
+ ++sUserInputEventDepth;
+ if (sUserInputEventDepth == 1) {
+ sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
+ }
+ if (WidgetEvent::IsKeyEventMessage(aMessage)) {
+ ++sUserKeyboardEventDepth;
+ }
+}
+
+/* static */
+void UserActivation::StopHandlingUserInput(EventMessage aMessage) {
+ --sUserInputEventDepth;
+ if (sUserInputEventDepth == 0) {
+ sHandlingInputStart = TimeStamp();
+ }
+ if (WidgetEvent::IsKeyEventMessage(aMessage)) {
+ --sUserKeyboardEventDepth;
+ }
+}
+
+/* static */
+TimeStamp UserActivation::GetHandlingInputStart() {
+ return sHandlingInputStart;
+}
+
+/* static */
+TimeStamp UserActivation::LatestUserInputStart() {
+ return sLatestUserInputStart;
+}
+
+//-----------------------------------------------------------------------------
+// mozilla::dom::AutoHandlingUserInputStatePusher
+//-----------------------------------------------------------------------------
+
+AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher(
+ bool aIsHandlingUserInput, WidgetEvent* aEvent)
+ : mMessage(aEvent ? aEvent->mMessage : eVoidEvent),
+ mIsHandlingUserInput(aIsHandlingUserInput) {
+ if (!aIsHandlingUserInput) {
+ return;
+ }
+ UserActivation::StartHandlingUserInput(mMessage);
+}
+
+AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() {
+ if (!mIsHandlingUserInput) {
+ return;
+ }
+ UserActivation::StopHandlingUserInput(mMessage);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/UserActivation.h b/dom/base/UserActivation.h
new file mode 100644
index 0000000000..75181f1b72
--- /dev/null
+++ b/dom/base/UserActivation.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_UserAcitvation_h
+#define mozilla_dom_UserAcitvation_h
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla::dom {
+
+class UserActivation final {
+ public:
+ enum class State : uint8_t {
+ // Not activated.
+ None,
+ // It is considered as has-been-activated, but not transient-activated given
+ // that it is being consumed.
+ HasBeenActivated,
+ // It is considered as has-been-activated, and also transient-activated if
+ // haven't timed out.
+ FullActivated,
+ EndGuard_
+ };
+
+ /**
+ * Returns true if the current code is being executed as a result of
+ * user input or keyboard input. The former includes anything that is
+ * initiated by user, with the exception of page load events or mouse
+ * over events. And the latter returns true when one of the user inputs
+ * is an input from keyboard. If these methods are called from asynchronously
+ * executed code, such as during layout reflows, it will return false.
+ */
+ static bool IsHandlingUserInput();
+ static bool IsHandlingKeyboardInput();
+
+ /**
+ * Returns true if the event is considered as user interaction event. I.e.,
+ * enough obvious input to allow to open popup, etc. Otherwise, returns false.
+ */
+ static bool IsUserInteractionEvent(const WidgetEvent* aEvent);
+
+ /**
+ * StartHandlingUserInput() is called when we start to handle a user input.
+ * StopHandlingUserInput() is called when we finish handling a user input.
+ * If the caller knows which input event caused that, it should set
+ * aMessage to the event message. Otherwise, set eVoidEvent.
+ * Note that StopHandlingUserInput() caller should call it with exactly same
+ * event message as its corresponding StartHandlingUserInput() call because
+ * these methods may count the number of specific event message.
+ */
+ static void StartHandlingUserInput(EventMessage aMessage);
+ static void StopHandlingUserInput(EventMessage aMessage);
+
+ static TimeStamp GetHandlingInputStart();
+
+ /**
+ * Get the timestamp at which the latest user input was handled.
+ *
+ * Guaranteed to be monotonic. Until the first user input, return
+ * the epoch.
+ */
+ static TimeStamp LatestUserInputStart();
+};
+
+/**
+ * This class is used while processing real user input. During this time, popups
+ * are allowed. For mousedown events, mouse capturing is also permitted.
+ */
+class MOZ_RAII AutoHandlingUserInputStatePusher final {
+ public:
+ explicit AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput,
+ WidgetEvent* aEvent = nullptr);
+ ~AutoHandlingUserInputStatePusher();
+
+ protected:
+ EventMessage mMessage;
+ bool mIsHandlingUserInput;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_UserAcitvation_h
diff --git a/dom/base/ViewportMetaData.cpp b/dom/base/ViewportMetaData.cpp
new file mode 100644
index 0000000000..2646621629
--- /dev/null
+++ b/dom/base/ViewportMetaData.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "ViewportMetaData.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*
+ * Helper function for ViewportMetaData::ProcessViewportInfo.
+ *
+ * Handles a single key=value pair. If it corresponds to a valid viewport
+ * attribute, add it to the document header data. No validation is done on the
+ * value itself (this is done at display time).
+ */
+static void ProcessViewportToken(ViewportMetaData& aData,
+ const nsAString& token) {
+ /* Iterators. */
+ nsAString::const_iterator tip, tail, end;
+ token.BeginReading(tip);
+ tail = tip;
+ token.EndReading(end);
+
+ /* Move tip to the '='. */
+ while ((tip != end) && (*tip != '=')) {
+ ++tip;
+ }
+
+ /* If we didn't find an '=', punt. */
+ if (tip == end) {
+ return;
+ }
+
+ /* Extract the key and value. */
+ const nsAString& key = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(
+ Substring(tail, tip), true);
+ const nsAString& value = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(
+ Substring(++tip, end), true);
+
+ /* Check for known keys. If we find a match, insert the appropriate
+ * information into the document header. */
+ RefPtr<nsAtom> key_atom = NS_Atomize(key);
+ if (key_atom == nsGkAtoms::height) {
+ aData.mHeight.Assign(value);
+ } else if (key_atom == nsGkAtoms::width) {
+ aData.mWidth.Assign(value);
+ } else if (key_atom == nsGkAtoms::initial_scale) {
+ aData.mInitialScale.Assign(value);
+ } else if (key_atom == nsGkAtoms::minimum_scale) {
+ aData.mMinimumScale.Assign(value);
+ } else if (key_atom == nsGkAtoms::maximum_scale) {
+ aData.mMaximumScale.Assign(value);
+ } else if (key_atom == nsGkAtoms::user_scalable) {
+ aData.mUserScalable.Assign(value);
+ } else if (key_atom == nsGkAtoms::viewport_fit) {
+ aData.mViewportFit.Assign(value);
+ }
+}
+
+#define IS_SEPARATOR(c) \
+ (((c) == '=') || ((c) == ',') || ((c) == ';') || ((c) == '\t') || \
+ ((c) == '\n') || ((c) == '\r'))
+
+ViewportMetaData::ViewportMetaData(const nsAString& aViewportInfo) {
+ /* Iterators. */
+ nsAString::const_iterator tip, tail, end;
+ aViewportInfo.BeginReading(tip);
+ tail = tip;
+ aViewportInfo.EndReading(end);
+
+ /* Read the tip to the first non-separator character. */
+ while ((tip != end) && (IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) {
+ ++tip;
+ }
+
+ /* Read through and find tokens separated by separators. */
+ while (tip != end) {
+ /* Synchronize tip and tail. */
+ tail = tip;
+
+ /* Advance tip past non-separator characters. */
+ while ((tip != end) && !IS_SEPARATOR(*tip)) {
+ ++tip;
+ }
+
+ /* Allow white spaces that surround the '=' character */
+ if ((tip != end) && (*tip == '=')) {
+ ++tip;
+
+ while ((tip != end) && nsCRT::IsAsciiSpace(*tip)) {
+ ++tip;
+ }
+
+ while ((tip != end) &&
+ !(IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) {
+ ++tip;
+ }
+ }
+
+ /* Our token consists of the characters between tail and tip. */
+ ProcessViewportToken(*this, Substring(tail, tip));
+
+ /* Skip separators. */
+ while ((tip != end) && (IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) {
+ ++tip;
+ }
+ }
+}
+
+#undef IS_SEPARATOR
diff --git a/dom/base/ViewportMetaData.h b/dom/base/ViewportMetaData.h
new file mode 100644
index 0000000000..7de6eda731
--- /dev/null
+++ b/dom/base/ViewportMetaData.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_VIEWPORT_META_DATA_H_
+#define DOM_VIEWPORT_META_DATA_H_
+
+#include "nsString.h"
+#include "nsAString.h"
+
+namespace mozilla::dom {
+struct ViewportMetaData {
+ // https://drafts.csswg.org/css-device-adapt/#meta-properties
+ nsString mWidth;
+ nsString mHeight;
+ nsString mInitialScale;
+ nsString mMinimumScale;
+ nsString mMaximumScale;
+ nsString mUserScalable;
+ nsString mViewportFit;
+
+ bool operator==(const ViewportMetaData& aOther) const {
+ return mWidth == aOther.mWidth && mHeight == aOther.mHeight &&
+ mInitialScale == aOther.mInitialScale &&
+ mMinimumScale == aOther.mMinimumScale &&
+ mMaximumScale == aOther.mMaximumScale &&
+ mUserScalable == aOther.mUserScalable &&
+ mViewportFit == aOther.mViewportFit;
+ }
+ bool operator!=(const ViewportMetaData& aOther) const {
+ return !(*this == aOther);
+ }
+
+ ViewportMetaData() = default;
+ /* Process viewport META data. This gives us information for the scale
+ * and zoom of a page on mobile devices. We stick the information in
+ * the document header and use it later on after rendering.
+ *
+ * See Bug #436083
+ */
+ explicit ViewportMetaData(const nsAString& aViewportInfo);
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_VIEWPORT_META_DATA_H_
diff --git a/dom/base/VisualViewport.cpp b/dom/base/VisualViewport.cpp
new file mode 100644
index 0000000000..64e66a5ac4
--- /dev/null
+++ b/dom/base/VisualViewport.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VisualViewport.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ToString.h"
+#include "nsIScrollableFrame.h"
+#include "nsIDocShell.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+#include "DocumentInlines.h"
+
+static mozilla::LazyLogModule sVvpLog("visualviewport");
+#define VVP_LOG(...) MOZ_LOG(sVvpLog, LogLevel::Debug, (__VA_ARGS__))
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow) {}
+
+VisualViewport::~VisualViewport() {
+ if (mResizeEvent) {
+ mResizeEvent->Revoke();
+ }
+
+ if (mScrollEvent) {
+ mScrollEvent->Revoke();
+ }
+}
+
+/* virtual */
+JSObject* VisualViewport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return VisualViewport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* virtual */
+void VisualViewport::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ EventMessage msg = aVisitor.mEvent->mMessage;
+
+ aVisitor.mCanHandle = true;
+ EventTarget* parentTarget = nullptr;
+ // Only our special internal events are allowed to escape the
+ // Visual Viewport and be dispatched further up the DOM tree.
+ if (msg == eMozVisualScroll || msg == eMozVisualResize) {
+ if (nsPIDOMWindowInner* win = GetOwner()) {
+ if (Document* doc = win->GetExtantDoc()) {
+ parentTarget = doc;
+ }
+ }
+ }
+ aVisitor.SetParentTarget(parentTarget, false);
+}
+
+CSSSize VisualViewport::VisualViewportSize() const {
+ CSSSize size = CSSSize(0, 0);
+
+ // Flush layout, as that may affect the answer below (e.g. scrollbars
+ // may have appeared, decreasing the available viewport size).
+ RefPtr<const VisualViewport> kungFuDeathGrip(this);
+ if (Document* doc = GetDocument()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ // Fetch the pres shell after the layout flush, as it might have destroyed it.
+ if (PresShell* presShell = GetPresShell()) {
+ if (presShell->IsVisualViewportSizeSet()) {
+ DynamicToolbarState state = presShell->GetDynamicToolbarState();
+ size = CSSRect::FromAppUnits(
+ (state == DynamicToolbarState::InTransition ||
+ state == DynamicToolbarState::Collapsed)
+ ? presShell->GetVisualViewportSizeUpdatedByDynamicToolbar()
+ : presShell->GetVisualViewportSize());
+ } else {
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ size = CSSRect::FromAppUnits(sf->GetScrollPortRect().Size());
+ }
+ }
+ }
+ return size;
+}
+
+double VisualViewport::Width() const {
+ CSSSize size = VisualViewportSize();
+ return size.width;
+}
+
+double VisualViewport::Height() const {
+ CSSSize size = VisualViewportSize();
+ return size.height;
+}
+
+double VisualViewport::Scale() const {
+ double scale = 1;
+ if (PresShell* presShell = GetPresShell()) {
+ scale = presShell->GetResolution();
+ }
+ return scale;
+}
+
+CSSPoint VisualViewport::VisualViewportOffset() const {
+ CSSPoint offset = CSSPoint(0, 0);
+
+ if (PresShell* presShell = GetPresShell()) {
+ offset = CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());
+ }
+ return offset;
+}
+
+CSSPoint VisualViewport::LayoutViewportOffset() const {
+ CSSPoint offset = CSSPoint(0, 0);
+
+ if (PresShell* presShell = GetPresShell()) {
+ offset = CSSPoint::FromAppUnits(presShell->GetLayoutViewportOffset());
+ }
+ return offset;
+}
+
+double VisualViewport::PageLeft() const { return VisualViewportOffset().X(); }
+
+double VisualViewport::PageTop() const { return VisualViewportOffset().Y(); }
+
+double VisualViewport::OffsetLeft() const {
+ return PageLeft() - LayoutViewportOffset().X();
+}
+
+double VisualViewport::OffsetTop() const {
+ return PageTop() - LayoutViewportOffset().Y();
+}
+
+Document* VisualViewport::GetDocument() const {
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsIDocShell* docShell = window->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+
+ return docShell->GetDocument();
+}
+
+PresShell* VisualViewport::GetPresShell() const {
+ RefPtr<Document> document = GetDocument();
+ return document ? document->GetPresShell() : nullptr;
+}
+
+nsPresContext* VisualViewport::GetPresContext() const {
+ RefPtr<Document> document = GetDocument();
+ return document ? document->GetPresContext() : nullptr;
+}
+
+/* ================= Resize event handling ================= */
+
+void VisualViewport::PostResizeEvent() {
+ VVP_LOG("%p: PostResizeEvent (pre-existing: %d)\n", this, !!mResizeEvent);
+ nsPresContext* presContext = GetPresContext();
+ if (mResizeEvent && mResizeEvent->HasPresContext(presContext)) {
+ return;
+ }
+ if (mResizeEvent) {
+ // prescontext changed, so discard the old resize event and queue a new one
+ mResizeEvent->Revoke();
+ mResizeEvent = nullptr;
+ }
+
+ // The event constructor will register itself with the refresh driver.
+ if (presContext) {
+ mResizeEvent = new VisualViewportResizeEvent(this, presContext);
+ VVP_LOG("%p: PostResizeEvent, created new event\n", this);
+ }
+}
+
+VisualViewport::VisualViewportResizeEvent::VisualViewportResizeEvent(
+ VisualViewport* aViewport, nsPresContext* aPresContext)
+ : Runnable("VisualViewport::VisualViewportResizeEvent"),
+ mViewport(aViewport),
+ mPresContext(aPresContext) {
+ VVP_LOG("%p: Registering PostResize on %p %p\n", aViewport, aPresContext,
+ aPresContext->RefreshDriver());
+ aPresContext->RefreshDriver()->PostVisualViewportResizeEvent(this);
+}
+
+bool VisualViewport::VisualViewportResizeEvent::HasPresContext(
+ nsPresContext* aContext) const {
+ return mPresContext.get() == aContext;
+}
+
+void VisualViewport::VisualViewportResizeEvent::Revoke() {
+ mViewport = nullptr;
+ mPresContext = nullptr;
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+VisualViewport::VisualViewportResizeEvent::Run() {
+ if (RefPtr<VisualViewport> viewport = mViewport) {
+ viewport->FireResizeEvent();
+ }
+ return NS_OK;
+}
+
+void VisualViewport::FireResizeEvent() {
+ MOZ_ASSERT(mResizeEvent);
+ mResizeEvent->Revoke();
+ mResizeEvent = nullptr;
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+
+ VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this);
+ WidgetEvent mozEvent(true, eMozVisualResize);
+ mozEvent.mFlags.mOnlySystemGroupDispatch = true;
+ EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext,
+ &mozEvent);
+
+ VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this);
+ WidgetEvent event(true, eResize);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext,
+ &event);
+}
+
+/* ================= Scroll event handling ================= */
+
+void VisualViewport::PostScrollEvent(const nsPoint& aPrevVisualOffset,
+ const nsPoint& aPrevLayoutOffset) {
+ VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s (pre-existing: %d)\n",
+ this, ToString(aPrevVisualOffset - aPrevLayoutOffset).c_str(),
+ !!mScrollEvent);
+ nsPresContext* presContext = GetPresContext();
+ if (mScrollEvent && mScrollEvent->HasPresContext(presContext)) {
+ return;
+ }
+
+ if (mScrollEvent) {
+ // prescontext changed, so discard the old scroll event and queue a new one
+ mScrollEvent->Revoke();
+ mScrollEvent = nullptr;
+ }
+
+ // The event constructor will register itself with the refresh driver.
+ if (presContext) {
+ mScrollEvent = new VisualViewportScrollEvent(
+ this, presContext, aPrevVisualOffset, aPrevLayoutOffset);
+ VVP_LOG("%p: PostScrollEvent, created new event\n", this);
+ }
+}
+
+VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
+ VisualViewport* aViewport, nsPresContext* aPresContext,
+ const nsPoint& aPrevVisualOffset, const nsPoint& aPrevLayoutOffset)
+ : Runnable("VisualViewport::VisualViewportScrollEvent"),
+ mViewport(aViewport),
+ mPresContext(aPresContext),
+ mPrevVisualOffset(aPrevVisualOffset),
+ mPrevLayoutOffset(aPrevLayoutOffset) {
+ VVP_LOG("%p: Registering PostScroll on %p %p\n", aViewport, aPresContext,
+ aPresContext->RefreshDriver());
+ aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this);
+}
+
+bool VisualViewport::VisualViewportScrollEvent::HasPresContext(
+ nsPresContext* aContext) const {
+ return mPresContext.get() == aContext;
+}
+
+void VisualViewport::VisualViewportScrollEvent::Revoke() {
+ mViewport = nullptr;
+ mPresContext = nullptr;
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+VisualViewport::VisualViewportScrollEvent::Run() {
+ if (RefPtr<VisualViewport> viewport = mViewport) {
+ viewport->FireScrollEvent();
+ }
+ return NS_OK;
+}
+
+void VisualViewport::FireScrollEvent() {
+ MOZ_ASSERT(mScrollEvent);
+ nsPoint prevVisualOffset = mScrollEvent->PrevVisualOffset();
+ nsPoint prevLayoutOffset = mScrollEvent->PrevLayoutOffset();
+ mScrollEvent->Revoke();
+ mScrollEvent = nullptr;
+
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ RefPtr<nsPresContext> presContext = GetPresContext();
+
+ if (presShell->GetVisualViewportOffset() != prevVisualOffset) {
+ // The internal event will be fired whenever the visual viewport's
+ // *absolute* offset changed, i.e. relative to the page.
+ VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this);
+ WidgetEvent mozEvent(true, eMozVisualScroll);
+ mozEvent.mFlags.mOnlySystemGroupDispatch = true;
+ EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext,
+ &mozEvent);
+ }
+
+ // Check whether the relative visual viewport offset actually changed -
+ // maybe both visual and layout viewport scrolled together and there was no
+ // change after all.
+ nsPoint curRelativeOffset =
+ presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
+ nsPoint prevRelativeOffset = prevVisualOffset - prevLayoutOffset;
+ VVP_LOG(
+ "%p: FireScrollEvent, curRelativeOffset %s, "
+ "prevRelativeOffset %s\n",
+ this, ToString(curRelativeOffset).c_str(),
+ ToString(prevRelativeOffset).c_str());
+ if (curRelativeOffset != prevRelativeOffset) {
+ VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
+ WidgetGUIEvent event(true, eScroll, nullptr);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ EventDispatcher::Dispatch(static_cast<EventTarget*>(this), presContext,
+ &event);
+ }
+ }
+}
diff --git a/dom/base/VisualViewport.h b/dom/base/VisualViewport.h
new file mode 100644
index 0000000000..7eed2c446d
--- /dev/null
+++ b/dom/base/VisualViewport.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VisualViewport_h
+#define mozilla_dom_VisualViewport_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/VisualViewportBinding.h"
+#include "Units.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace dom {
+
+/* Visual Viewport API spec:
+ * https://wicg.github.io/visual-viewport/#the-visualviewport-interface */
+class VisualViewport final : public mozilla::DOMEventTargetHelper {
+ public:
+ explicit VisualViewport(nsPIDOMWindowInner* aWindow);
+
+ double OffsetLeft() const;
+ double OffsetTop() const;
+ double PageLeft() const;
+ double PageTop() const;
+ MOZ_CAN_RUN_SCRIPT double Width() const;
+ MOZ_CAN_RUN_SCRIPT double Height() const;
+ double Scale() const;
+ IMPL_EVENT_HANDLER(resize)
+ IMPL_EVENT_HANDLER(scroll)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
+ void PostResizeEvent();
+ void PostScrollEvent(const nsPoint& aPrevVisualOffset,
+ const nsPoint& aPrevLayoutOffset);
+
+ // These two events are modelled after the ScrollEvent class in
+ // nsGfxScrollFrame.h.
+ class VisualViewportResizeEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ VisualViewportResizeEvent(VisualViewport* aViewport,
+ nsPresContext* aPresContext);
+ bool HasPresContext(nsPresContext* aContext) const;
+ void Revoke();
+
+ private:
+ VisualViewport* mViewport;
+ WeakPtr<nsPresContext> mPresContext;
+ };
+
+ class VisualViewportScrollEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ VisualViewportScrollEvent(VisualViewport* aViewport,
+ nsPresContext* aPresContext,
+ const nsPoint& aPrevVisualOffset,
+ const nsPoint& aPrevLayoutOffset);
+ bool HasPresContext(nsPresContext* aContext) const;
+ void Revoke();
+ nsPoint PrevVisualOffset() const { return mPrevVisualOffset; }
+ nsPoint PrevLayoutOffset() const { return mPrevLayoutOffset; }
+
+ private:
+ VisualViewport* mViewport;
+ WeakPtr<nsPresContext> mPresContext;
+ // The VisualViewport "scroll" event is supposed to be fired only when the
+ // *relative* offset between visual and layout viewport changes. The two
+ // viewports are updated independently from each other, though, so the only
+ // thing we can do is note the fact that one of the inputs into the relative
+ // visual viewport offset changed and then check the offset again at the
+ // next refresh driver tick, just before the event is going to fire.
+ // Hopefully, at this point both visual and layout viewport positions have
+ // been updated, so that we're able to tell whether the relative offset did
+ // in fact change or not.
+ const nsPoint mPrevVisualOffset;
+ const nsPoint mPrevLayoutOffset;
+ };
+
+ private:
+ virtual ~VisualViewport();
+
+ MOZ_CAN_RUN_SCRIPT CSSSize VisualViewportSize() const;
+ CSSPoint VisualViewportOffset() const;
+ CSSPoint LayoutViewportOffset() const;
+ Document* GetDocument() const;
+ PresShell* GetPresShell() const;
+ nsPresContext* GetPresContext() const;
+
+ MOZ_CAN_RUN_SCRIPT void FireResizeEvent();
+ MOZ_CAN_RUN_SCRIPT void FireScrollEvent();
+
+ RefPtr<VisualViewportResizeEvent> mResizeEvent;
+ RefPtr<VisualViewportScrollEvent> mScrollEvent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VisualViewport_h
diff --git a/dom/base/WindowDestroyedEvent.cpp b/dom/base/WindowDestroyedEvent.cpp
new file mode 100644
index 0000000000..8b18ada6c2
--- /dev/null
+++ b/dom/base/WindowDestroyedEvent.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowDestroyedEvent.h"
+
+#include "nsJSUtils.h"
+#include "jsapi.h"
+#include "js/Wrapper.h"
+#include "nsIPrincipal.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIAppStartup.h"
+#include "nsJSPrincipals.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "xpcpublic.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/ProfilerLabels.h"
+#include "nsFocusManager.h"
+
+namespace mozilla {
+
+struct BrowserCompartmentMatcher : public js::CompartmentFilter {
+ bool match(JS::Compartment* aC) const override {
+ return !xpc::MightBeWebContentCompartment(aC);
+ }
+};
+
+WindowDestroyedEvent::WindowDestroyedEvent(nsGlobalWindowInner* aWindow,
+ uint64_t aID, const char* aTopic)
+ : mozilla::Runnable("WindowDestroyedEvent"),
+ mID(aID),
+ mPhase(Phase::Destroying),
+ mTopic(aTopic),
+ mIsInnerWindow(true) {
+ mWindow = do_GetWeakReference(aWindow);
+}
+
+WindowDestroyedEvent::WindowDestroyedEvent(nsGlobalWindowOuter* aWindow,
+ uint64_t aID, const char* aTopic)
+ : mozilla::Runnable("WindowDestroyedEvent"),
+ mID(aID),
+ mPhase(Phase::Destroying),
+ mTopic(aTopic),
+ mIsInnerWindow(false) {
+ mWindow = do_GetWeakReference(aWindow);
+}
+
+NS_IMETHODIMP
+WindowDestroyedEvent::Run() {
+ AUTO_PROFILER_LABEL("WindowDestroyedEvent::Run", OTHER);
+
+ nsCOMPtr<nsPIDOMWindowOuter> nukedOuter;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper =
+ do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+ if (wrapper) {
+ wrapper->SetData(mID);
+ observerService->NotifyObservers(wrapper, mTopic.get(), nullptr);
+ }
+
+ switch (mPhase) {
+ case Phase::Destroying: {
+ bool skipNukeCrossCompartment = false;
+#ifndef DEBUG
+ skipNukeCrossCompartment =
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
+#endif
+
+ if (!skipNukeCrossCompartment) {
+ // The compartment nuking phase might be too expensive, so do that
+ // part off of idle dispatch.
+
+ // For the compartment nuking phase, we dispatch either an
+ // inner-window-nuked or an outer-window-nuked notification.
+ // This will allow tests to wait for compartment nuking to happen.
+ if (mTopic.EqualsLiteral("inner-window-destroyed")) {
+ mTopic.AssignLiteral("inner-window-nuked");
+ } else if (mTopic.EqualsLiteral("outer-window-destroyed")) {
+ mTopic.AssignLiteral("outer-window-nuked");
+ }
+ mPhase = Phase::Nuking;
+
+ nsCOMPtr<nsIRunnable> copy(this);
+ NS_DispatchToCurrentThreadQueue(copy.forget(), 1000,
+ EventQueuePriority::Idle);
+ }
+ } break;
+
+ case Phase::Nuking: {
+ nsCOMPtr<nsISupports> window = do_QueryReferent(mWindow);
+ if (window) {
+ nsGlobalWindowInner* currentInner;
+ if (mIsInnerWindow) {
+ currentInner = nsGlobalWindowInner::FromSupports(window);
+ } else {
+ nsGlobalWindowOuter* outer =
+ nsGlobalWindowOuter::FromSupports(window);
+ currentInner = outer->GetCurrentInnerWindowInternal();
+ nukedOuter = outer;
+ }
+ NS_ENSURE_TRUE(currentInner, NS_OK);
+
+ dom::AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx, currentInner->GetGlobalJSObject());
+ if (obj && !js::IsSystemRealm(js::GetNonCCWObjectRealm(obj))) {
+ JS::Realm* realm = js::GetNonCCWObjectRealm(obj);
+
+ xpc::NukeJSStackFrames(realm);
+
+ nsCOMPtr<nsIPrincipal> pc =
+ nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
+
+ if (BasePrincipal::Cast(pc)->AddonPolicy()) {
+ // We want to nuke all references to the add-on realm.
+ xpc::NukeAllWrappersForRealm(cx, realm,
+ mIsInnerWindow
+ ? js::DontNukeWindowReferences
+ : js::NukeWindowReferences);
+ } else {
+ // We only want to nuke wrappers for the chrome->content case
+ js::NukeCrossCompartmentWrappers(
+ cx, BrowserCompartmentMatcher(), realm,
+ mIsInnerWindow ? js::DontNukeWindowReferences
+ : js::NukeWindowReferences,
+ js::NukeIncomingReferences);
+ }
+ }
+ }
+ } break;
+ }
+
+ if (nukedOuter) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->WasNuked(nukedOuter);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/base/WindowDestroyedEvent.h b/dom/base/WindowDestroyedEvent.h
new file mode 100644
index 0000000000..09fb144a74
--- /dev/null
+++ b/dom/base/WindowDestroyedEvent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WindowDestroyedEvent_h
+#define WindowDestroyedEvent_h
+
+#include "nsGlobalWindow.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class WindowDestroyedEvent final : public Runnable {
+ public:
+ WindowDestroyedEvent(nsGlobalWindowInner* aWindow, uint64_t aID,
+ const char* aTopic);
+ WindowDestroyedEvent(nsGlobalWindowOuter* aWindow, uint64_t aID,
+ const char* aTopic);
+
+ enum class Phase { Destroying, Nuking };
+
+ NS_IMETHOD Run() override;
+
+ private:
+ uint64_t mID;
+ Phase mPhase;
+ nsCString mTopic;
+ nsWeakPtr mWindow;
+ bool mIsInnerWindow;
+};
+
+} // namespace mozilla
+
+#endif // defined(WindowDestroyedEvent_h)
diff --git a/dom/base/WindowFeatures.cpp b/dom/base/WindowFeatures.cpp
new file mode 100644
index 0000000000..5de62f8b59
--- /dev/null
+++ b/dom/base/WindowFeatures.cpp
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowFeatures.h"
+
+#include "nsINode.h" // IsSpaceCharacter
+#include "nsContentUtils.h" // nsContentUtils
+#include "nsDependentSubstring.h" // Substring
+#include "nsReadableUtils.h" // ToLowerCase
+
+using mozilla::dom::IsSpaceCharacter;
+using mozilla::dom::WindowFeatures;
+
+#ifdef DEBUG
+/* static */
+bool WindowFeatures::IsLowerCase(const char* text) {
+ nsAutoCString before(text);
+ nsAutoCString after;
+ ToLowerCase(before, after);
+ return before == after;
+}
+#endif
+
+static bool IsFeatureSeparator(char aChar) {
+ // https://html.spec.whatwg.org/multipage/window-object.html#feature-separator
+ // A code point is a feature separator if it is ASCII whitespace, U+003D (=),
+ // or U+002C (,).
+ return IsSpaceCharacter(aChar) || aChar == '=' || aChar == ',';
+}
+
+template <class IterT, class CondT>
+void AdvanceWhile(IterT& aPosition, const IterT& aEnd, CondT aCondition) {
+ // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
+ //
+ // Step 2. While `position` doesn’t point past the end of `input` and the
+ // code point at `position` within `input` meets the condition condition:
+ while (aCondition(*aPosition) && aPosition < aEnd) {
+ // Step 2.1. Append that code point to the end of `result`.
+ // (done by caller)
+
+ // Step 2.2. Advance `position` by 1.
+ ++aPosition;
+ }
+}
+
+template <class IterT, class CondT>
+nsTDependentSubstring<char> CollectSequence(IterT& aPosition, const IterT& aEnd,
+ CondT aCondition) {
+ // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
+ // To collect a sequence of code points meeting a condition `condition` from
+ // a string `input`, given a position variable `position` tracking the
+ // position of the calling algorithm within `input`:
+
+ // Step 1. Let `result` be the empty string.
+ auto start = aPosition;
+
+ // Step 2.
+ AdvanceWhile(aPosition, aEnd, aCondition);
+
+ // Step 3. Return `result`.
+ return Substring(start, aPosition);
+}
+
+static void NormalizeName(nsAutoCString& aName) {
+ // https://html.spec.whatwg.org/multipage/window-object.html#normalizing-the-feature-name
+ if (aName == "screenx") {
+ aName = "left";
+ } else if (aName == "screeny") {
+ aName = "top";
+ } else if (aName == "innerwidth") {
+ aName = "width";
+ } else if (aName == "innerheight") {
+ aName = "height";
+ }
+}
+
+/* static */
+int32_t WindowFeatures::ParseIntegerWithFallback(const nsCString& aValue) {
+ // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean
+ //
+ // Step 3. Let `parsed` be the result of parsing value as an integer.
+ nsContentUtils::ParseHTMLIntegerResultFlags parseResult;
+ int32_t parsed = nsContentUtils::ParseHTMLInteger(aValue, &parseResult);
+
+ // Step 4. If `parsed` is an error, then set it to 0.
+ //
+ // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers
+ //
+ // eParseHTMLInteger_NonStandard is not tested given:
+ // * Step 4 allows leading whitespaces
+ // * Step 6 allows a plus sign
+ // * Step 8 does not disallow leading zeros
+ // * Steps 6 and 9 allow `-0`
+ //
+ // eParseHTMLInteger_DidNotConsumeAllInput is not tested given:
+ // * Step 8 collects digits and ignores remaining part
+ //
+ if (parseResult & nsContentUtils::eParseHTMLInteger_Error) {
+ parsed = 0;
+ }
+
+ return parsed;
+}
+
+/* static */
+bool WindowFeatures::ParseBool(const nsCString& aValue) {
+ // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean
+ // To parse a boolean feature given a string `value`:
+
+ // Step 1. If `value` is the empty string, then return true.
+ if (aValue.IsEmpty()) {
+ return true;
+ }
+
+ // Step 2. If `value` is "yes", then return true.
+ if (aValue == "yes") {
+ return true;
+ }
+
+ // Step 3. If `value` is "true", then return
+ if (aValue == "true") {
+ return true;
+ }
+
+ // Steps 4-5.
+ int32_t parsed = ParseIntegerWithFallback(aValue);
+
+ // Step 6. Return false if `parsed` is 0, and true otherwise.
+ return parsed != 0;
+}
+
+bool WindowFeatures::Tokenize(const nsACString& aFeatures) {
+ // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-tokenize
+ // To tokenize the `features` argument:
+
+ // Step 1. Let `tokenizedFeatures` be a new ordered map.
+ // (implicit)
+
+ // Step 2. Let `position` point at the first code point of features.
+ auto position = aFeatures.BeginReading();
+
+ // Step 3. While `position` is not past the end of `features`:
+ auto end = aFeatures.EndReading();
+ while (position < end) {
+ // Step 3.1. Let `name` be the empty string.
+ // (implicit)
+
+ // Step 3.2. Let `value` be the empty string.
+ nsAutoCString value;
+
+ // Step 3.3. Collect a sequence of code points that are feature separators
+ // from `features` given `position`. This skips past leading separators
+ // before the name.
+ //
+ // NOTE: Do not collect given this value is unused.
+ AdvanceWhile(position, end, IsFeatureSeparator);
+
+ // Step 3.4. Collect a sequence of code points that are not feature
+ // separators from `features` given `position`. Set `name` to the collected
+ // characters, converted to ASCII lowercase.
+ nsAutoCString name(CollectSequence(
+ position, end, [](char c) { return !IsFeatureSeparator(c); }));
+ ToLowerCase(name);
+
+ // Step 3.5. Set `name` to the result of normalizing the feature name
+ // `name`.
+ NormalizeName(name);
+
+ // Step 3.6. While `position` is not past the end of `features` and the
+ // code point at `position` in features is not U+003D (=):
+ //
+ // Step 3.6.1. If the code point at `position` in features is U+002C (,),
+ // or if it is not a feature separator, then break.
+ //
+ // Step 3.6.2. Advance `position` by 1.
+ //
+ // NOTE: This skips to the first U+003D (=) but does not skip past a U+002C
+ // (,) or a non-separator.
+ //
+ // The above means skip all whitespaces.
+ AdvanceWhile(position, end, [](char c) { return IsSpaceCharacter(c); });
+
+ // Step 3.7. If the code point at `position` in `features` is a feature
+ // separator:
+ if (position < end && IsFeatureSeparator(*position)) {
+ // Step 3.7.1. While `position` is not past the end of `features` and the
+ // code point at `position` in `features` is a feature separator:
+ //
+ // Step 3.7.1.1. If the code point at `position` in `features` is
+ // U+002C (,), then break.
+ //
+ // Step 3.7.1.2. Advance `position` by 1.
+ //
+ // NOTE: This skips to the first non-separator but does not skip past a
+ // U+002C (,).
+ AdvanceWhile(position, end,
+ [](char c) { return IsFeatureSeparator(c) && c != ','; });
+
+ // Step 3.7.2. Collect a sequence of code points that are not feature
+ // separators code points from `features` given `position`. Set `value` to
+ // the collected code points, converted to ASCII lowercase.
+ value = CollectSequence(position, end,
+ [](char c) { return !IsFeatureSeparator(c); });
+ ToLowerCase(value);
+ }
+
+ // Step 3.8. If `name` is not the empty string, then set
+ // `tokenizedFeatures[name]` to `value`.
+ if (!name.IsEmpty()) {
+ if (!tokenizedFeatures_.put(name, value)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 4. Return `tokenizedFeatures`.
+ return true;
+}
+
+void WindowFeatures::Stringify(nsAutoCString& aOutput) {
+ MOZ_ASSERT(aOutput.IsEmpty());
+
+ for (auto r = tokenizedFeatures_.all(); !r.empty(); r.popFront()) {
+ if (!aOutput.IsEmpty()) {
+ aOutput.Append(',');
+ }
+
+ const nsCString& name = r.front().key();
+ const nsCString& value = r.front().value();
+
+ aOutput.Append(name);
+
+ if (!value.IsEmpty()) {
+ aOutput.Append('=');
+ aOutput.Append(value);
+ }
+ }
+}
diff --git a/dom/base/WindowFeatures.h b/dom/base/WindowFeatures.h
new file mode 100644
index 0000000000..cb070bb765
--- /dev/null
+++ b/dom/base/WindowFeatures.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowFeatures_h
+#define mozilla_dom_WindowFeatures_h
+
+#include "nsString.h"
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/HashTable.h" // mozilla::HashMap
+
+#include "nsStringFwd.h" // nsCString, nsACString, nsAutoCString, nsLiteralCString
+#include "nsTStringHasher.h" // mozilla::DefaultHasher<nsCString>
+
+namespace mozilla::dom {
+
+// Represents `tokenizedFeatures` in
+// https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-tokenize
+// with accessor methods for values.
+class WindowFeatures {
+ public:
+ WindowFeatures() = default;
+
+ WindowFeatures(const WindowFeatures& aOther) = delete;
+ WindowFeatures& operator=(const WindowFeatures& aOther) = delete;
+
+ WindowFeatures(WindowFeatures&& aOther) = delete;
+ WindowFeatures& operator=(WindowFeatures&& aOther) = delete;
+
+ // Tokenizes `aFeatures` and stores the result map in member field.
+ // This should be called at the begining, only once.
+ //
+ // Returns true if successfully tokenized, false otherwise.
+ bool Tokenize(const nsACString& aFeatures);
+
+ // Returns true if the `aName` feature is specified.
+ template <size_t N>
+ bool Exists(const char (&aName)[N]) const {
+ MOZ_ASSERT(IsLowerCase(aName));
+ nsLiteralCString name(aName);
+ return tokenizedFeatures_.has(name);
+ }
+
+ // Returns string value of `aName` feature.
+ // The feature must exist.
+ template <size_t N>
+ const nsCString& Get(const char (&aName)[N]) const {
+ MOZ_ASSERT(IsLowerCase(aName));
+ nsLiteralCString name(aName);
+ auto p = tokenizedFeatures_.lookup(name);
+ MOZ_ASSERT(p.found());
+
+ return p->value();
+ }
+
+ // Returns integer value of `aName` feature.
+ // The feature must exist.
+ template <size_t N>
+ int32_t GetInt(const char (&aName)[N]) const {
+ const nsCString& value = Get(aName);
+ return ParseIntegerWithFallback(value);
+ }
+
+ // Returns bool value of `aName` feature.
+ // The feature must exist.
+ template <size_t N>
+ bool GetBool(const char (&aName)[N]) const {
+ const nsCString& value = Get(aName);
+ return ParseBool(value);
+ }
+
+ // Returns bool value of `aName` feature.
+ // If the feature doesn't exist, returns `aDefault`.
+ //
+ // If `aPresenceFlag` is provided and the feature exists, it's set to `true`.
+ // (note that the value isn't overwritten if the feature doesn't exist)
+ template <size_t N>
+ bool GetBoolWithDefault(const char (&aName)[N], bool aDefault,
+ bool* aPresenceFlag = nullptr) const {
+ MOZ_ASSERT(IsLowerCase(aName));
+ nsLiteralCString name(aName);
+ auto p = tokenizedFeatures_.lookup(name);
+ if (p.found()) {
+ if (aPresenceFlag) {
+ *aPresenceFlag = true;
+ }
+ return ParseBool(p->value());
+ }
+ return aDefault;
+ }
+
+ // Remove the feature from the map.
+ template <size_t N>
+ void Remove(const char (&aName)[N]) {
+ MOZ_ASSERT(IsLowerCase(aName));
+ nsLiteralCString name(aName);
+ tokenizedFeatures_.remove(name);
+ }
+
+ // Returns true if there was no feature specified, or all features are
+ // removed by `Remove`.
+ //
+ // Note that this can be true even if `aFeatures` parameter of `Tokenize`
+ // is not empty, in case it contains no feature with non-empty name.
+ bool IsEmpty() const { return tokenizedFeatures_.empty(); }
+
+ // Stringify the map into `aOutput`.
+ // The result can be parsed again with `Tokenize`.
+ void Stringify(nsAutoCString& aOutput);
+
+ private:
+#ifdef DEBUG
+ // Returns true if `text` does not contain any character that gets modified by
+ // `ToLowerCase`.
+ static bool IsLowerCase(const char* text);
+#endif
+
+ static int32_t ParseIntegerWithFallback(const nsCString& aValue);
+ static bool ParseBool(const nsCString& aValue);
+
+ // A map from feature name to feature value.
+ // If value is not provided, it's empty string.
+ mozilla::HashMap<nsCString, nsCString> tokenizedFeatures_;
+};
+
+} // namespace mozilla::dom
+
+#endif // #ifndef mozilla_dom_WindowFeatures_h
diff --git a/dom/base/WindowNamedPropertiesHandler.cpp b/dom/base/WindowNamedPropertiesHandler.cpp
new file mode 100644
index 0000000000..7601438af6
--- /dev/null
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowNamedPropertiesHandler.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsHTMLDocument.h"
+#include "nsJSUtils.h"
+#include "xpcprivate.h"
+
+namespace mozilla::dom {
+
+static bool ShouldExposeChildWindow(const nsString& aNameBeingResolved,
+ BrowsingContext* aChild) {
+ Element* e = aChild->GetEmbedderElement();
+ if (e && e->IsInShadowTree()) {
+ return false;
+ }
+
+ // If we're same-origin with the child, go ahead and expose it.
+ nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(child);
+ if (sop && nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
+ return true;
+ }
+
+ // If we're not same-origin, expose it _only_ if the name of the browsing
+ // context matches the 'name' attribute of the frame element in the parent.
+ // The motivations behind this heuristic are worth explaining here.
+ //
+ // Historically, all UAs supported global named access to any child browsing
+ // context (that is to say, window.dolske returns a child frame where either
+ // the "name" attribute on the frame element was set to "dolske", or where
+ // the child explicitly set window.name = "dolske").
+ //
+ // This is problematic because it allows possibly-malicious and unrelated
+ // cross-origin subframes to pollute the global namespace of their parent in
+ // unpredictable ways (see bug 860494). This is also problematic for browser
+ // engines like Servo that want to run cross-origin script on different
+ // threads.
+ //
+ // The naive solution here would be to filter out any cross-origin subframes
+ // obtained when doing named lookup in global scope. But that is unlikely to
+ // be web-compatible, since it will break named access for consumers that do
+ // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
+ // expect to be able to access the cross-origin subframe via named lookup on
+ // the global.
+ //
+ // The optimal behavior would be to do the following:
+ // (a) Look for any child browsing context with name="dolske".
+ // (b) If the result is cross-origin, null it out.
+ // (c) If we have null, look for a frame element whose 'name' attribute is
+ // "dolske".
+ //
+ // Unfortunately, (c) would require some engineering effort to be performant
+ // in Gecko, and probably in other UAs as well. So we go with a simpler
+ // approximation of the above. This approximation will only break sites that
+ // rely on their cross-origin subframes setting window.name to a known value,
+ // which is unlikely to be very common. And while it does introduce a
+ // dependency on cross-origin state when doing global lookups, it doesn't
+ // allow the child to arbitrarily pollute the parent namespace, and requires
+ // cross-origin communication only in a limited set of cases that can be
+ // computed independently by the parent.
+ return e && e->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ aNameBeingResolved, eCaseMatters);
+}
+
+bool WindowNamedPropertiesHandler::getOwnPropDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ bool /* unused */,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
+ aDesc.reset();
+
+ if (aId.isSymbol()) {
+ if (aId.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
+ JS::Rooted<JSString*> toStringTagStr(
+ aCx, JS_NewStringCopyZ(aCx, "WindowProperties"));
+ if (!toStringTagStr) {
+ return false;
+ }
+
+ aDesc.set(Some(
+ JS::PropertyDescriptor::Data(JS::StringValue(toStringTagStr),
+ {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+
+ // Nothing to do if we're resolving another symbol property.
+ return true;
+ }
+
+ bool hasOnPrototype;
+ if (!HasPropertyOnPrototype(aCx, aProxy, aId, &hasOnPrototype)) {
+ return false;
+ }
+ if (hasOnPrototype) {
+ return true;
+ }
+
+ nsAutoJSString str;
+ if (!str.init(aCx, aId)) {
+ return false;
+ }
+
+ if (str.IsEmpty()) {
+ return true;
+ }
+
+ // Grab the DOM window.
+ nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
+ if (win->Length() > 0) {
+ RefPtr<BrowsingContext> child = win->GetChildWindow(str);
+ if (child && ShouldExposeChildWindow(str, child)) {
+ // We found a subframe of the right name. Shadowing via |var foo| in
+ // global scope is still allowed, since |var| only looks up |own|
+ // properties. But unqualified shadowing will fail, per-spec.
+ JS::Rooted<JS::Value> v(aCx);
+ if (!ToJSValue(aCx, WindowProxyHolder(std::move(child)), &v)) {
+ return false;
+ }
+ aDesc.set(mozilla::Some(
+ JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+ }
+
+ // The rest of this function is for HTML documents only.
+ Document* doc = win->GetExtantDoc();
+ if (!doc || !doc->IsHTMLOrXHTML()) {
+ return true;
+ }
+ nsHTMLDocument* document = doc->AsHTMLDocument();
+
+ JS::Rooted<JS::Value> v(aCx);
+ Element* element = document->GetElementById(str);
+ if (element) {
+ if (!ToJSValue(aCx, element, &v)) {
+ return false;
+ }
+ aDesc.set(mozilla::Some(
+ JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+
+ ErrorResult rv;
+ bool found = document->ResolveName(aCx, str, &v, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ if (found) {
+ aDesc.set(mozilla::Some(
+ JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ }
+ return true;
+}
+
+bool WindowNamedPropertiesHandler::defineProperty(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& result) const {
+ return result.failCantDefineWindowNamedProperty();
+}
+
+bool WindowNamedPropertiesHandler::ownPropNames(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, unsigned flags,
+ JS::MutableHandleVector<jsid> aProps) const {
+ if (!(flags & JSITER_HIDDEN)) {
+ // None of our named properties are enumerable.
+ return true;
+ }
+
+ // Grab the DOM window.
+ nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
+ nsTArray<nsString> names;
+ // The names live on the outer window, which might be null
+ nsGlobalWindowOuter* outer = win->GetOuterWindowInternal();
+ if (outer) {
+ if (BrowsingContext* bc = outer->GetBrowsingContext()) {
+ for (const auto& child : bc->Children()) {
+ const nsString& name = child->Name();
+ if (!name.IsEmpty() && !names.Contains(name)) {
+ // Make sure we really would expose it from getOwnPropDescriptor.
+ if (ShouldExposeChildWindow(name, child)) {
+ names.AppendElement(name);
+ }
+ }
+ }
+ }
+ }
+ if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
+ return false;
+ }
+
+ names.Clear();
+ Document* doc = win->GetExtantDoc();
+ if (!doc || !doc->IsHTMLOrXHTML()) {
+ // Define to @@toStringTag on this object to keep Object.prototype.toString
+ // backwards compatible.
+ JS::Rooted<jsid> toStringTagId(
+ aCx, JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toStringTag));
+ return aProps.append(toStringTagId);
+ }
+
+ nsHTMLDocument* document = doc->AsHTMLDocument();
+ // Document names are enumerable, so we want to get them no matter what flags
+ // is.
+ document->GetSupportedNames(names);
+
+ JS::RootedVector<jsid> docProps(aCx);
+ if (!AppendNamedPropertyIds(aCx, aProxy, names, false, &docProps)) {
+ return false;
+ }
+
+ JS::Rooted<jsid> toStringTagId(
+ aCx, JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toStringTag));
+ if (!docProps.append(toStringTagId)) {
+ return false;
+ }
+
+ return js::AppendUnique(aCx, aProps, docProps);
+}
+
+bool WindowNamedPropertiesHandler::delete_(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::ObjectOpResult& aResult) const {
+ return aResult.failCantDeleteWindowNamedProperty();
+}
+
+// Note that this class doesn't need any reserved slots, but SpiderMonkey
+// asserts all proxy classes have at least one reserved slot.
+static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = {
+ PROXY_CLASS_DEF("WindowProperties", JSCLASS_IS_DOMIFACEANDPROTOJSCLASS |
+ JSCLASS_HAS_RESERVED_SLOTS(1)),
+ eNamedPropertiesObject,
+ false,
+ prototypes::id::_ID_Count,
+ 0,
+ &sEmptyNativePropertyHooks,
+ "[object WindowProperties]",
+ EventTarget_Binding::GetProtoObject};
+
+// static
+JSObject* WindowNamedPropertiesHandler::Create(JSContext* aCx,
+ JS::Handle<JSObject*> aProto) {
+ js::ProxyOptions options;
+ options.setClass(&WindowNamedPropertiesClass.mBase);
+
+ JS::Rooted<JSObject*> gsp(
+ aCx, js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(),
+ JS::NullHandleValue, aProto, options));
+ if (!gsp) {
+ return nullptr;
+ }
+
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(aCx, gsp, &succeeded)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(succeeded,
+ "errors making the [[Prototype]] of the named properties object "
+ "immutable should have been JSAPI failures, not !succeeded");
+
+ return gsp;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/base/WindowNamedPropertiesHandler.h b/dom/base/WindowNamedPropertiesHandler.h
new file mode 100644
index 0000000000..e1aec931a1
--- /dev/null
+++ b/dom/base/WindowNamedPropertiesHandler.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowNamedPropertiesHandler_h
+#define mozilla_dom_WindowNamedPropertiesHandler_h
+
+#include "mozilla/dom/DOMJSProxyHandler.h"
+
+namespace mozilla::dom {
+
+class WindowNamedPropertiesHandler : public BaseDOMProxyHandler {
+ public:
+ constexpr WindowNamedPropertiesHandler()
+ : BaseDOMProxyHandler(nullptr, /* hasPrototype = */ true) {}
+ virtual bool getOwnPropDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ bool /* unused */,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const override;
+ virtual bool defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& result) const override;
+ virtual bool ownPropNames(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, unsigned flags,
+ JS::MutableHandleVector<jsid> aProps) const override;
+ virtual bool delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::ObjectOpResult& aResult) const override;
+
+ // No need for getPrototypeIfOrdinary here: window named-properties objects
+ // have static prototypes, so the version inherited from BaseDOMProxyHandler
+ // will do the right thing.
+
+ virtual bool preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::ObjectOpResult& aResult) const override {
+ return aResult.failCantPreventExtensions();
+ }
+ virtual bool isExtensible(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ bool* aIsExtensible) const override {
+ *aIsExtensible = true;
+ return true;
+ }
+ virtual const char* className(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy) const override {
+ return "WindowProperties";
+ }
+
+ static const WindowNamedPropertiesHandler* getInstance() {
+ static const WindowNamedPropertiesHandler instance;
+ return &instance;
+ }
+
+ // For Create, aProto is the parent of the interface prototype object of the
+ // Window we're associated with.
+ static JSObject* Create(JSContext* aCx, JS::Handle<JSObject*> aProto);
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_WindowNamedPropertiesHandler_h */
diff --git a/dom/base/WindowProxyHolder.h b/dom/base/WindowProxyHolder.h
new file mode 100644
index 0000000000..b2887f236c
--- /dev/null
+++ b/dom/base/WindowProxyHolder.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowProxyHolder_h__
+#define mozilla_dom_WindowProxyHolder_h__
+
+#include "mozilla/dom/BrowsingContext.h"
+
+struct JSContext;
+class JSObject;
+
+namespace JS {
+template <typename T>
+class MutableHandle;
+} // namespace JS
+
+namespace mozilla::dom {
+
+/**
+ * This class is used for passing arguments and the return value for WebIDL
+ * binding code that takes/returns a WindowProxy object and for WebIDL
+ * unions/dictionaries that contain a WindowProxy member. It should never
+ * contain null; if the value in WebIDL is nullable the binding code will use a
+ * Nullable<WindowProxyHolder>.
+ */
+class WindowProxyHolder {
+ public:
+ WindowProxyHolder() = default;
+ explicit WindowProxyHolder(BrowsingContext* aBC) : mBrowsingContext(aBC) {
+ MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+ }
+ explicit WindowProxyHolder(RefPtr<BrowsingContext>&& aBC)
+ : mBrowsingContext(std::move(aBC)) {
+ MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+ }
+ WindowProxyHolder& operator=(BrowsingContext* aBC) {
+ mBrowsingContext = aBC;
+ MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+ return *this;
+ }
+ WindowProxyHolder& operator=(RefPtr<BrowsingContext>&& aBC) {
+ mBrowsingContext = std::move(aBC);
+ MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+ return *this;
+ }
+
+ BrowsingContext* get() const {
+ MOZ_ASSERT(mBrowsingContext, "WindowProxyHolder hasn't been initialized.");
+ return mBrowsingContext;
+ }
+
+ private:
+ friend void ImplCycleCollectionUnlink(WindowProxyHolder& aProxy);
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+};
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, WindowProxyHolder& aProxy,
+ const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aProxy.get(), "mBrowsingContext", aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(WindowProxyHolder& aProxy) {
+ aProxy.mBrowsingContext = nullptr;
+}
+
+extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aValue);
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_WindowProxyHolder_h__ */
diff --git a/dom/base/XPathGenerator.cpp b/dom/base/XPathGenerator.cpp
new file mode 100644
index 0000000000..ca0305fe80
--- /dev/null
+++ b/dom/base/XPathGenerator.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XPathGenerator.h"
+
+#include "nsGkAtoms.h"
+#include "Element.h"
+#include "nsTArray.h"
+
+/**
+ * Check whether a character is a non-word character. A non-word character is a
+ * character that isn't in ('a'..'z') or in ('A'..'Z') or a number or an
+ * underscore.
+ * */
+bool IsNonWordCharacter(const char16_t& aChar) {
+ if (((char16_t('A') <= aChar) && (aChar <= char16_t('Z'))) ||
+ ((char16_t('a') <= aChar) && (aChar <= char16_t('z'))) ||
+ ((char16_t('0') <= aChar) && (aChar <= char16_t('9'))) ||
+ (aChar == char16_t('_'))) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+/**
+ * Check whether a string contains a non-word character.
+ * */
+bool ContainNonWordCharacter(const nsAString& aStr) {
+ const char16_t* cur = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+ for (; cur < end; ++cur) {
+ if (IsNonWordCharacter(*cur)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Get the prefix according to the given namespace and assign the result to
+ * aResult.
+ * */
+void GetPrefix(const nsINode* aNode, nsAString& aResult) {
+ if (aNode->IsXULElement()) {
+ aResult.AssignLiteral(u"xul");
+ } else if (aNode->IsHTMLElement()) {
+ aResult.AssignLiteral(u"xhtml");
+ }
+}
+
+void GetNameAttribute(const nsINode* aNode, nsAString& aResult) {
+ if (aNode->HasName()) {
+ const mozilla::dom::Element* elem = aNode->AsElement();
+ elem->GetAttr(kNameSpaceID_None, nsGkAtoms::name, aResult);
+ }
+}
+
+/**
+ * Put all sequences of ' in a string in between '," and ",' . And then put
+ * the result string in between concat(' and ').
+ *
+ * For example, a string 'a'' will return result concat('',"'",'a',"''",'')
+ * */
+void GenerateConcatExpression(const nsAString& aStr, nsAString& aResult) {
+ const char16_t* cur = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+
+ // Put all sequences of ' in between '," and ",'
+ nsAutoString result;
+ const char16_t* nonQuoteBeginPtr = nullptr;
+ const char16_t* quoteBeginPtr = nullptr;
+ for (; cur < end; ++cur) {
+ if (char16_t('\'') == *cur) {
+ if (nonQuoteBeginPtr) {
+ result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
+ nonQuoteBeginPtr = nullptr;
+ }
+ if (!quoteBeginPtr) {
+ result.AppendLiteral(u"\',\"");
+ quoteBeginPtr = cur;
+ }
+ } else {
+ if (!nonQuoteBeginPtr) {
+ nonQuoteBeginPtr = cur;
+ }
+ if (quoteBeginPtr) {
+ result.Append(quoteBeginPtr, cur - quoteBeginPtr);
+ result.AppendLiteral(u"\",\'");
+ quoteBeginPtr = nullptr;
+ }
+ }
+ }
+
+ if (quoteBeginPtr) {
+ result.Append(quoteBeginPtr, cur - quoteBeginPtr);
+ result.AppendLiteral(u"\",\'");
+ } else if (nonQuoteBeginPtr) {
+ result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
+ }
+
+ // Prepend concat(' and append ').
+ aResult.Assign(u"concat(\'"_ns + result + u"\')"_ns);
+}
+
+void XPathGenerator::QuoteArgument(const nsAString& aArg, nsAString& aResult) {
+ if (!aArg.Contains('\'')) {
+ aResult.Assign(u"\'"_ns + aArg + u"\'"_ns);
+ } else if (!aArg.Contains('\"')) {
+ aResult.Assign(u"\""_ns + aArg + u"\""_ns);
+ } else {
+ GenerateConcatExpression(aArg, aResult);
+ }
+}
+
+void XPathGenerator::EscapeName(const nsAString& aName, nsAString& aResult) {
+ if (ContainNonWordCharacter(aName)) {
+ nsAutoString quotedArg;
+ QuoteArgument(aName, quotedArg);
+ aResult.Assign(u"*[local-name()="_ns + quotedArg + u"]"_ns);
+ } else {
+ aResult.Assign(aName);
+ }
+}
+
+void XPathGenerator::Generate(const nsINode* aNode, nsAString& aResult) {
+ if (!aNode->GetParentNode()) {
+ aResult.Truncate();
+ return;
+ }
+
+ nsAutoString nodeNamespaceURI;
+ aNode->GetNamespaceURI(nodeNamespaceURI);
+ const nsString& nodeLocalName = aNode->LocalName();
+
+ nsAutoString prefix;
+ nsAutoString tag;
+ nsAutoString nodeEscapeName;
+ GetPrefix(aNode, prefix);
+ EscapeName(nodeLocalName, nodeEscapeName);
+ if (prefix.IsEmpty()) {
+ tag.Assign(nodeEscapeName);
+ } else {
+ tag.Assign(prefix + u":"_ns + nodeEscapeName);
+ }
+
+ if (aNode->HasID()) {
+ // this must be an element
+ const mozilla::dom::Element* elem = aNode->AsElement();
+ nsAutoString elemId;
+ nsAutoString quotedArgument;
+ elem->GetId(elemId);
+ QuoteArgument(elemId, quotedArgument);
+ aResult.Assign(u"//"_ns + tag + u"[@id="_ns + quotedArgument + u"]"_ns);
+ return;
+ }
+
+ int32_t count = 1;
+ nsAutoString nodeNameAttribute;
+ GetNameAttribute(aNode, nodeNameAttribute);
+ for (const mozilla::dom::Element* e = aNode->GetPreviousElementSibling(); e;
+ e = e->GetPreviousElementSibling()) {
+ nsAutoString elementNamespaceURI;
+ e->GetNamespaceURI(elementNamespaceURI);
+ nsAutoString elementNameAttribute;
+ GetNameAttribute(e, elementNameAttribute);
+ if (e->LocalName().Equals(nodeLocalName) &&
+ elementNamespaceURI.Equals(nodeNamespaceURI) &&
+ (nodeNameAttribute.IsEmpty() ||
+ elementNameAttribute.Equals(nodeNameAttribute))) {
+ ++count;
+ }
+ }
+
+ nsAutoString namePart;
+ nsAutoString countPart;
+ if (!nodeNameAttribute.IsEmpty()) {
+ nsAutoString quotedArgument;
+ QuoteArgument(nodeNameAttribute, quotedArgument);
+ namePart.Assign(u"[@name="_ns + quotedArgument + u"]"_ns);
+ }
+ if (count != 1) {
+ countPart.AssignLiteral(u"[");
+ countPart.AppendInt(count);
+ countPart.AppendLiteral(u"]");
+ }
+ Generate(aNode->GetParentNode(), aResult);
+ aResult.Append(u"/"_ns + tag + namePart + countPart);
+}
diff --git a/dom/base/XPathGenerator.h b/dom/base/XPathGenerator.h
new file mode 100644
index 0000000000..fe97f141f8
--- /dev/null
+++ b/dom/base/XPathGenerator.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPATHGENERATOR_H__
+#define XPATHGENERATOR_H__
+#include "nsString.h"
+#include "nsINode.h"
+
+class XPathGenerator {
+ public:
+ /**
+ * Return a properly quoted string to insert into an XPath
+ * */
+ static void QuoteArgument(const nsAString& aArg, nsAString& aResult);
+
+ /**
+ * Return a valid XPath for the given node (usually the local name itself)
+ * */
+ static void EscapeName(const nsAString& aName, nsAString& aResult);
+
+ /**
+ * Generate an approximate XPath query to an (X)HTML node
+ * */
+ static void Generate(const nsINode* aNode, nsAString& aResult);
+};
+
+#endif
diff --git a/dom/base/ZLibHelper.h b/dom/base/ZLibHelper.h
new file mode 100644
index 0000000000..86de8c4086
--- /dev/null
+++ b/dom/base/ZLibHelper.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_ZLIBHELPER_H_
+#define DOM_ZLIBHELPER_H_
+
+#include "js/TypeDecls.h"
+#include "zlib.h"
+
+#include "mozilla/dom/CompressionStreamBinding.h"
+
+namespace mozilla::dom {
+
+// From the docs in
+// https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
+inline int8_t ZLibWindowBits(CompressionFormat format) {
+ switch (format) {
+ case CompressionFormat::Deflate:
+ // The windowBits parameter is the base two logarithm of the window size
+ // (the size of the history buffer). It should be in the range 8..15 for
+ // this version of the library. Larger values of this parameter result
+ // in better compression at the expense of memory usage.
+ return 15;
+ case CompressionFormat::Deflate_raw:
+ // windowBits can also be –8..–15 for raw deflate. In this case,
+ // -windowBits determines the window size.
+ return -15;
+ case CompressionFormat::Gzip:
+ // windowBits can also be greater than 15 for optional gzip encoding.
+ // Add 16 to windowBits to write a simple gzip header and trailer around
+ // the compressed data instead of a zlib wrapper.
+ return 31;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown compression format");
+ return 0;
+ }
+}
+
+enum ZLibFlush : uint8_t { No = Z_NO_FLUSH, Yes = Z_FINISH };
+
+} // namespace mozilla::dom
+
+#endif // DOM_ZLIBHELPER_H_
diff --git a/dom/base/components.conf b/dom/base/components.conf
new file mode 100644
index 0000000000..bd7f04efc8
--- /dev/null
+++ b/dom/base/components.conf
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'js_name': 'droppedLinkHandler',
+ 'cid': '{1f34bc80-1bc7-11d6-a384-d705dd0746fc}',
+ 'contract_ids': ['@mozilla.org/content/dropped-link-handler;1'],
+ 'interfaces': ['nsIDroppedLinkHandler'],
+ 'esModule': 'resource://gre/modules/ContentAreaDropListener.sys.mjs',
+ 'constructor': 'ContentAreaDropListener',
+ },
+ {
+ 'cid': '{c616fcfd-9737-41f1-aa74-cee72a38f91b}',
+ 'esModule': 'resource://gre/modules/ProcessSelector.sys.mjs',
+ 'constructor': 'RandomSelector',
+ },
+ {
+ 'cid': '{2dc08eaf-6eef-4394-b1df-a3a927c1290b}',
+ 'contract_ids': ['@mozilla.org/ipc/processselector;1'],
+ 'esModule': 'resource://gre/modules/ProcessSelector.sys.mjs',
+ 'constructor': 'MinTabSelector',
+ },
+ {
+ 'cid': '{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}',
+ 'contract_ids': ['@mozilla.org/dom/slow-script-debug;1'],
+ 'esModule': 'resource://gre/modules/SlowScriptDebug.sys.mjs',
+ 'constructor': 'SlowScriptDebug',
+ },
+]
diff --git a/dom/base/crashtests/1024428-1.html b/dom/base/crashtests/1024428-1.html
new file mode 100644
index 0000000000..075077591c
--- /dev/null
+++ b/dom/base/crashtests/1024428-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div id="host"></div>
+<script>
+ host.attachShadow({ mode: "open" }).innerHTML = '<input type="range" />';
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1027461-1.html b/dom/base/crashtests/1027461-1.html
new file mode 100644
index 0000000000..849a66c164
--- /dev/null
+++ b/dom/base/crashtests/1027461-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <link rel="import" href="1027461-inner.xhtml">
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/base/crashtests/1027461-inner.xhtml b/dom/base/crashtests/1027461-inner.xhtml
new file mode 100644
index 0000000000..84ee2d222a
--- /dev/null
+++ b/dom/base/crashtests/1027461-inner.xhtml
@@ -0,0 +1,2 @@
+<?xml?>
+<empty-xul-file />
diff --git a/dom/base/crashtests/1029710.html b/dom/base/crashtests/1029710.html
new file mode 100644
index 0000000000..bb78b561e4
--- /dev/null
+++ b/dom/base/crashtests/1029710.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ var x = document.createElement('span');
+ x.attachShadow({ mode: "open" });
+ x.id = 'a';
+</script>
+</body>
+</html>
+
diff --git a/dom/base/crashtests/1154598.xhtml b/dom/base/crashtests/1154598.xhtml
new file mode 100644
index 0000000000..64d5506540
--- /dev/null
+++ b/dom/base/crashtests/1154598.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body><body>
+<script>
+s = document.createElement('script');
+s.src="";
+document.body.appendChild(s);
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1157995.html b/dom/base/crashtests/1157995.html
new file mode 100644
index 0000000000..5d822c0a68
--- /dev/null
+++ b/dom/base/crashtests/1157995.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <script>
+ // This should not leak.
+ var a = navigator;
+ navigator.mediaDevices._ = null;
+ </script>
+</body>
diff --git a/dom/base/crashtests/1158412.html b/dom/base/crashtests/1158412.html
new file mode 100644
index 0000000000..e62fce1ab9
--- /dev/null
+++ b/dom/base/crashtests/1158412.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.styleSheetSets.expando = null;
+ var otherDoc = document.implementation.createDocument("", "", null);
+ var otherSpan = otherDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+
+ var img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
+ img.srcset = "data:,a 1w, data:,b 1w";
+ img.sizes = "1px";
+ otherSpan.appendChild(img);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/116848-1.html b/dom/base/crashtests/116848-1.html
new file mode 100644
index 0000000000..785d97fe64
--- /dev/null
+++ b/dom/base/crashtests/116848-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <title>Test case boiled down from www.sjetmarka.no</title>
+</head>
+<body>
+ <table>
+ <tr>
+ <td>
+ <SCRIPT language="JavaScript1.2">
+ document.write('<div>')
+ </script>
+ Hei og velkommen til Mozilla
+ <head>
+ <script language="JavaScript1.2">
+ document.write('</div>')
+ </script>
+ </head>
+ </td>
+ </tr>
+ </table>
+ <table>
+ <tr>
+ <td>
+ God jul til alle
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/dom/base/crashtests/1181619.html b/dom/base/crashtests/1181619.html
new file mode 100644
index 0000000000..929207964d
--- /dev/null
+++ b/dom/base/crashtests/1181619.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+ <script>
+ var obs = new MutationObserver(function() {
+ // Just need something here to assert exception is not pending. Any
+ // binding method will do.
+ console.log("hello");
+ });
+ obs.observe(document.body, { childList: true });
+ </script>
+ <script>
+ noSuchMethodYo();
+ </script>
+</body>
diff --git a/dom/base/crashtests/1228882.html b/dom/base/crashtests/1228882.html
new file mode 100644
index 0000000000..42dc5a6570
--- /dev/null
+++ b/dom/base/crashtests/1228882.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="b.dir='auto'; a.dir='auto'; b.remove();">
+<bdi id="a"><span id="b">f</span>g</bdi>
+</body>
+</html>
diff --git a/dom/base/crashtests/1230422.html b/dom/base/crashtests/1230422.html
new file mode 100644
index 0000000000..fbaa63d385
--- /dev/null
+++ b/dom/base/crashtests/1230422.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<script>
+
+var a = new FileReader();
+
+function f() {
+ a.removeEventListener("loadend", f);
+ g();
+}
+
+function g() {
+ a.readAsBinaryString(new Blob());
+}
+
+a.addEventListener("loadend", f);
+
+try {
+ g();
+ g();
+} catch(e) {}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1251361.html b/dom/base/crashtests/1251361.html
new file mode 100644
index 0000000000..57c76121f5
--- /dev/null
+++ b/dom/base/crashtests/1251361.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+var frameRoot;
+
+function boom()
+{
+ var frameWin = f.contentWindow;
+ frameRoot = frameWin.document.documentElement;
+ frameWin.location.replace("data:text/html;charset=UTF-8,<body onload='parent.g();'>2");
+}
+
+function g()
+{
+ setTimeout(h, 0);
+}
+
+function h()
+{
+ var newDoc = document.implementation.createDocument('', '', null);
+ newDoc.adoptNode(frameRoot);
+}
+
+</script>
+<body onload="boom();">
+
+<iframe id="f" src="data:text/html;charset=UTF-8,<marquee>1"></iframe>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/1281715.html b/dom/base/crashtests/1281715.html
new file mode 100644
index 0000000000..17352a30bd
--- /dev/null
+++ b/dom/base/crashtests/1281715.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+html {
+ color: orange;
+}
+</style>
+
+<script>
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("hostW").attachShadow({ mode: "open" }).innerHTML = '<z></z>';
+ document.getElementsByTagName("style")[0].remove();
+}
+</script>
+
+</head>
+
+<body onload="boom();"><div style="display: contents;" id="hostW"></div></body>
+
+</html>
diff --git a/dom/base/crashtests/1291535-iframe.html b/dom/base/crashtests/1291535-iframe.html
new file mode 100644
index 0000000000..60ba42891a
--- /dev/null
+++ b/dom/base/crashtests/1291535-iframe.html
@@ -0,0 +1,4 @@
+<script>
+ import("data:text/javascript,0").catch(x => 0);
+ window.stop();
+</script>
diff --git a/dom/base/crashtests/1291535.html b/dom/base/crashtests/1291535.html
new file mode 100644
index 0000000000..d57b3d762a
--- /dev/null
+++ b/dom/base/crashtests/1291535.html
@@ -0,0 +1 @@
+<iframe src="1291535-iframe.html"></iframe>
diff --git a/dom/base/crashtests/1304437.html b/dom/base/crashtests/1304437.html
new file mode 100644
index 0000000000..a4e9d5110c
--- /dev/null
+++ b/dom/base/crashtests/1304437.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+ var e=document.createElement("q");
+ document.documentElement.appendChild(e);
+ e.style="mask-image:url(data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=),url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)";
+ setTimeout(function(){
+ e.style="mask-image:url(data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=)";
+ },0);
+};
+</script>
+</html>
diff --git a/dom/base/crashtests/1324209.html b/dom/base/crashtests/1324209.html
new file mode 100644
index 0000000000..99628e38d3
--- /dev/null
+++ b/dom/base/crashtests/1324209.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript">
+
+function crash() {
+ var target1 = document.getElementById("target1");
+ var target2 = document.getElementById("target2");
+ var observer1 = new IntersectionObserver(function (entries) {
+ console.log(entries);
+ observer1.disconnect();
+ observer2.disconnect();
+ });
+ var observer2 = new IntersectionObserver(function (entries) {
+ console.log(entries);
+ });
+ observer1.observe(target1);
+ observer2.observe(target2);
+}
+
+</script>
+</head>
+<body onload="crash()">
+ <div id="target1" style="background: red; width: 50px; height: 50px"></div>
+ <div id="target2" style="background: green; width: 50px; height: 50px"></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/1324500.html b/dom/base/crashtests/1324500.html
new file mode 100644
index 0000000000..83072c9bd1
--- /dev/null
+++ b/dom/base/crashtests/1324500.html
@@ -0,0 +1,4 @@
+<script>
+setTimeout(function(){document.getElementById('id0510').removeAttribute('max');}, 46);
+</script>
+<input id='id0510'type='range'max=765>
diff --git a/dom/base/crashtests/1326194-1.html b/dom/base/crashtests/1326194-1.html
new file mode 100644
index 0000000000..802ae7c385
--- /dev/null
+++ b/dom/base/crashtests/1326194-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript">
+
+// Crashes if 'target' doesn't get properly unlinked in nsINode::LastRelease
+
+function crash() {
+ var target = document.getElementById('target');
+ var io = new IntersectionObserver(function () { }, { });
+ io.observe(target);
+ document.body.removeChild(target);
+}
+
+</script>
+</head>
+<body onload="crash()">
+ <div id="target" style="background: red; width: 50px; height: 50px"></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/1326194-2.html b/dom/base/crashtests/1326194-2.html
new file mode 100644
index 0000000000..dbced2f8ff
--- /dev/null
+++ b/dom/base/crashtests/1326194-2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript">
+
+// Crashes if 'target' doesn't get properly unlinked in FragmentOrElement::Unlink
+
+function crash() {
+ var target = document.createElement('div');
+ // By setting a custom prop we create a cycle between JS and C++ that requires the CC to break.
+ target.foo = 'bar';
+ var io = new IntersectionObserver(function () { }, { });
+ io.observe(target);
+}
+
+</script>
+</head>
+<body onload="crash()">
+</body>
+</html>
diff --git a/dom/base/crashtests/1332939.html b/dom/base/crashtests/1332939.html
new file mode 100644
index 0000000000..3b253912d6
--- /dev/null
+++ b/dom/base/crashtests/1332939.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript">
+
+var target = document.createElement("div");
+target.foo = 'bar';
+var observer = new IntersectionObserver(function () { });
+observer.observe(target);
+observer.unobserve(target);
+observer = null;
+target = null;
+
+</script>
+</head>
+</html>
diff --git a/dom/base/crashtests/1341693.html b/dom/base/crashtests/1341693.html
new file mode 100644
index 0000000000..677305ba56
--- /dev/null
+++ b/dom/base/crashtests/1341693.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ var o1 = document.documentElement;
+ var o2 = document.createElement("frame");
+ document.documentElement.appendChild(o2);
+ var o3 = o2.contentWindow;
+ o1.parentNode.removeChild(o1);
+ o3.customElements;
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1352453.html b/dom/base/crashtests/1352453.html
new file mode 100644
index 0000000000..3fc2933e91
--- /dev/null
+++ b/dom/base/crashtests/1352453.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+ tr {
+ font: medium / 1000 cursive;
+ }
+
+ :only-of-type {
+ border-image: repeating-linear-gradient(45deg, blue, red) space 1% / 1pt auto;
+ }
+ </style>
+ <script>
+ o1 = document.createElement('tr');
+ o2 = document.createElement('th');
+ o3 = document.createElement('rt');
+ document.documentElement.appendChild(o1);
+ o1.appendChild(o2);
+ o2.appendChild(o3);
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1353529-inner.html b/dom/base/crashtests/1353529-inner.html
new file mode 100644
index 0000000000..2c10a1e3da
--- /dev/null
+++ b/dom/base/crashtests/1353529-inner.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body onload="boom()">
+<div id="target"></div>
+<script>
+ function boom() {
+ var io = new IntersectionObserver(function () { }, { });
+ io.observe(document.getElementById('target'));
+ }
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1353529.xhtml b/dom/base/crashtests/1353529.xhtml
new file mode 100644
index 0000000000..65b55c7822
--- /dev/null
+++ b/dom/base/crashtests/1353529.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
+<xhtml:div>
+ <iframe src="1353529-inner.html"></iframe>
+</xhtml:div>
+</window>
diff --git a/dom/base/crashtests/1368327-iframe.html b/dom/base/crashtests/1368327-iframe.html
new file mode 100644
index 0000000000..dbbdd99f38
--- /dev/null
+++ b/dom/base/crashtests/1368327-iframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>iframe</title>
+ </head>
+ <body>
+ <p><h1>iframe</h1></p>
+ </body>
+</html>
diff --git a/dom/base/crashtests/1368327.html b/dom/base/crashtests/1368327.html
new file mode 100644
index 0000000000..ab2755f644
--- /dev/null
+++ b/dom/base/crashtests/1368327.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <title>Test window.location</title>
+ <script type="application/javascript">
+ function test() {
+ content = document.querySelector("#content");
+ testFrame = document.querySelector("#testframe");
+ frameWindow = testFrame.contentWindow;
+ testframe.remove();
+
+ // Shouldn't crash at this line.
+ content.textContent = "location=" + frameWindow.location;
+
+ document.documentElement.className = "";
+ }
+ </script>
+ </head>
+ <body>
+ <p id="content"></p>
+ <iframe id="testframe" src="1368327-iframe.html" onload="setTimeout(test, 0);"></iframe>
+ </body>
+</html>
diff --git a/dom/base/crashtests/1369363.xhtml b/dom/base/crashtests/1369363.xhtml
new file mode 100644
index 0000000000..fd7ddf9f14
--- /dev/null
+++ b/dom/base/crashtests/1369363.xhtml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
+<xhtml:div id="root">
+<xhtml:div id="target">
+</xhtml:div>
+</xhtml:div>
+<script>
+<![CDATA[
+ var io = new IntersectionObserver(function () {
+ }, { root: document.getElementById('root') });
+ io.observe(document.getElementById('target'));
+]]>
+</script>
+</window>
diff --git a/dom/base/crashtests/1370072.html b/dom/base/crashtests/1370072.html
new file mode 100644
index 0000000000..baecc8f35a
--- /dev/null
+++ b/dom/base/crashtests/1370072.html
@@ -0,0 +1,18 @@
+<script>
+function start() {
+ o1=document.createElement('iframe');
+ o1.addEventListener('load',fun0);
+ document.body.appendChild(o1);
+}
+function fun0() {
+ o5=o1.contentDocument;
+ o52=function() {let x=o5.querySelectorAll('*:not([id])');return x[x.length-1]}();
+ o1.contentWindow.onresize=fun1;
+ o1.height='5px';
+ o52.clientTop;
+}
+function fun1() {
+ document.documentElement.appendChild(o1);
+}
+</script>
+<body onload="start()"></body>
diff --git a/dom/base/crashtests/1370737.html b/dom/base/crashtests/1370737.html
new file mode 100644
index 0000000000..0e0e1a0e0e
--- /dev/null
+++ b/dom/base/crashtests/1370737.html
@@ -0,0 +1,41 @@
+<html class="reftest-wait">
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ let n=document.getElementById('a');
+ n.parentNode.removeChild(n);
+ let o=document.getElementById('b');
+ let p=document.getElementById('c');
+ p.id=[o.id, o.id=p.id][0];
+ let l=['d'];
+ let s=window.getSelection();
+ for(let i=0; i<l.length; i++){
+ let e=document.getElementById(l[i]);
+ let r=document.createRange();
+ r.selectNode(e);
+ s.addRange(r);
+ }
+ n=document.getElementById('b');
+ n.parentNode.removeChild(n);
+ window.getSelection().modify('extend','right','word');
+ n.setAttribute('id','e');
+ document.getElementById('f').appendChild(n);
+ l=['e'];
+ for(let i=0; i<l.length; i++){
+ let e=document.getElementById(l[i]);
+ let r=document.createRange();
+ r.selectNode(e);
+ s.addRange(r);
+ }
+ document.documentElement.removeAttribute("class");
+});
+</script>
+<table>
+<tbody hidden>
+<th id='c'>
+<i id='d'>
+<bdo id='b'></bdo>
+<td></td>
+<tbody id='f'>
+<td id='a' contenteditable='true'>
+<td>
+</html>
diff --git a/dom/base/crashtests/1370968-inner.xhtml b/dom/base/crashtests/1370968-inner.xhtml
new file mode 100644
index 0000000000..6d9d6aa882
--- /dev/null
+++ b/dom/base/crashtests/1370968-inner.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
+<xhtml:div id="target"></xhtml:div>
+</window>
diff --git a/dom/base/crashtests/1370968.html b/dom/base/crashtests/1370968.html
new file mode 100644
index 0000000000..81973b413c
--- /dev/null
+++ b/dom/base/crashtests/1370968.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe id="iframe" src="1370968-inner.xhtml"></iframe>
+<script>
+ var io = new IntersectionObserver(function () {
+ }, { });
+ var iframe = document.getElementById('iframe');
+ iframe.onload = function () {
+ io.observe(iframe.contentDocument.getElementById('target'));
+ };
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1373750.html b/dom/base/crashtests/1373750.html
new file mode 100644
index 0000000000..f9fdb2a9b2
--- /dev/null
+++ b/dom/base/crashtests/1373750.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+div {
+ /* Add two mask layers and apply border-radius to the bottom-most layer. */
+ mask: linear-gradient(red, blue) border-box no-clip, 6%;
+ border-style: solid;
+ border-top-left-radius: 24%;
+}
+</style>
+</head>
+<div></div>
+</html>
diff --git a/dom/base/crashtests/1377826.html b/dom/base/crashtests/1377826.html
new file mode 100644
index 0000000000..7934771889
--- /dev/null
+++ b/dom/base/crashtests/1377826.html
@@ -0,0 +1,5 @@
+<li id='a' dir='auto'>
+<select>
+<option dir='rtl'>
+&#x8C3;
+</html>
diff --git a/dom/base/crashtests/1383478.html b/dom/base/crashtests/1383478.html
new file mode 100644
index 0000000000..ca90494b17
--- /dev/null
+++ b/dom/base/crashtests/1383478.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+ <head>
+ <meta charset="utf-8" />
+ <title>This browser is crashing</title>
+ </head>
+ <body>
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+ </body>
+</html>
diff --git a/dom/base/crashtests/1383780.html b/dom/base/crashtests/1383780.html
new file mode 100644
index 0000000000..54738cc53f
--- /dev/null
+++ b/dom/base/crashtests/1383780.html
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElementNS('http://www.w3.org/2000/svg', 'use') } catch(e) { }
+ try { o2 = document.createElement('img') } catch(e) { }
+ try { o4 = document.createElement('area') } catch(e) { }
+ try { o5 = document.createElement('tr') } catch(e) { }
+ try { o6 = document.createRange(); } catch(e) { }
+ try { o7 = window.getSelection() } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.head.outerHTML = '<progress></progress>'; } catch(e) { }
+ try { o1.appendChild(o2); } catch(e) { }
+ try { o2.appendChild(o4) } catch(e) { }
+ try { o4.appendChild(o5); } catch(e) { }
+ try { document.designMode = 'on'; } catch(e) { }
+ try { o6.selectNode(o5); } catch(e) { }
+ try { o7.addRange(o6); } catch(e) { }
+ try { document.execCommand('justifyleft', false, null) } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1385272-1.html b/dom/base/crashtests/1385272-1.html
new file mode 100644
index 0000000000..5bdd407998
--- /dev/null
+++ b/dom/base/crashtests/1385272-1.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement("tr") } catch(e) { }
+ try { o2 = document.createElement("td") } catch(e) { }
+ try { o3 = document.createElement("tr") } catch(e) { }
+ try { o4 = document.createElement("div") } catch(e) { }
+ try { o5 = document.createElement("input") } catch(e) { }
+ try { o6 = document.createElement('map') } catch(e) { }
+ try { o7 = document.createElement('select') } catch(e) { }
+ try { o8 = document.createElement("canvas") } catch(e) { }
+ try { o9 = document.createElement("area") } catch(e) { };
+ try { o1.appendChild(o2) } catch(e) { }
+ try { document.documentElement.appendChild(o3) } catch(e) { }
+ try { document.documentElement.appendChild(o4) } catch(e) { }
+ try { document.documentElement.appendChild(o6) } catch(e) { }
+ try { o3.appendChild(o7) } catch(e) { }
+ try { o4.appendChild(o8) } catch(e) { }
+ try { o3.appendChild(o5); } catch(e) { }
+ try { o3.appendChild(o1); } catch(e) { }
+ try { o6.contentEditable = "true" } catch(e) { };
+ try { o6.offsetHeight } catch(e) { };
+ try { o4.appendChild(o9) } catch(e) { };
+ try { o2.insertAdjacentHTML("beforeBegin", "<button id='id0'></button>\n"); } catch(e) { }
+ try { document.replaceChild(document.documentElement, document.documentElement); } catch(e) { }
+ try { document.execCommand("selectall",false,null) } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1393806.html b/dom/base/crashtests/1393806.html
new file mode 100644
index 0000000000..4e859bf602
--- /dev/null
+++ b/dom/base/crashtests/1393806.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <script>
+ function fun_0() {
+ document.implementation.createDocument('', '', null).adoptNode(o2);
+ }
+
+ o1 = document.createElement('map');
+ o2 = document.createElement('iframe');
+ document.documentElement.appendChild(o1);
+ document.documentElement.appendChild(o2);
+ o1.textContent = 'x';
+ document.addEventListener('DOMNodeRemoved', fun_0, false);
+ o1.innerText = 'x';
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1396466.html b/dom/base/crashtests/1396466.html
new file mode 100644
index 0000000000..09f36fa0d4
--- /dev/null
+++ b/dom/base/crashtests/1396466.html
@@ -0,0 +1,20 @@
+<script>
+function addNew(i,t){
+ let n=document.createElement(t);
+ n.setAttribute('id', i);
+ document.getElementById('a').appendChild(n);
+}
+document.addEventListener("DOMContentLoaded", function(){
+ addNew('b','multicol');
+ addNew('c','marquee');
+ addNew('d','spacer');
+ document.getElementById('a').appendChild(document.createElement('iframe'));
+ document.getElementById('b').focus();
+ window.frames[0].document.body.appendChild(document.getElementById('c'));
+ let o=window.frames[0].document.body.childNodes[0];
+ document.getElementById('d').appendChild(o.parentNode.removeChild(o));
+ try{window.find('x')}catch(e){}
+ window.frames[0].document.body.appendChild(document.getElementById('c'));
+});
+</script>
+<body id='a'></body>
diff --git a/dom/base/crashtests/1397795.html b/dom/base/crashtests/1397795.html
new file mode 100644
index 0000000000..4ba525a9c1
--- /dev/null
+++ b/dom/base/crashtests/1397795.html
@@ -0,0 +1,23 @@
+<html>
+ <style>
+ html {
+ writing-mode: vertical-rl;
+ }
+ body {
+ column-count: 2;
+ }
+ svg {
+ -moz-box-shadow: green 2px 2px 8px;
+ box-shadow: green 2px 2px 8px;
+ -moz-appearance: button;
+ appearance: button;
+ }
+
+ </style>
+ <body>
+ <div id='id0'>
+ <svg width='800' filter='url(#id0')></svg>
+ </div>
+ </body>
+</html>
+
diff --git a/dom/base/crashtests/1400701.html b/dom/base/crashtests/1400701.html
new file mode 100644
index 0000000000..fe1eef5d50
--- /dev/null
+++ b/dom/base/crashtests/1400701.html
@@ -0,0 +1,15 @@
+<html>
+<script>
+window.onload=function(){
+ document.getElementById('b').textContent='Or';
+ document.getElementById('a').insertBefore(document.getElementById('c'), undefined);
+ document.getElementById('b').dir='ltr';
+ let o=document.getElementById('b');
+ o.parentNode.replaceChild(document.createElement('code'), o);
+}
+</script>
+<body dir='auto' id='a'>
+<bdi id='b' dir='auto'>
+</bdi>
+<q id='c'>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/1403377.html b/dom/base/crashtests/1403377.html
new file mode 100644
index 0000000000..2afbb66f1f
--- /dev/null
+++ b/dom/base/crashtests/1403377.html
@@ -0,0 +1,18 @@
+<script>
+function fzfn(e){
+ let nc=window.frames[0].document.body.childElementCount;
+ let o=window.frames[0].document.body.childNodes[1%nc];
+ document.getElementById('b').appendChild(o.parentNode.removeChild(o));
+}
+window.onload=function(){
+ document.body.onerror=fzfn;
+ document.getElementById('c').addEventListener('DOMNodeRemoved', fzfn, true);
+ document.getElementById('c').attributes[0].name=='id';
+ window.frames[0].document.body.appendChild(document.getElementById('c'));
+ document.getElementById('a').innerHTML=document.createElement('multicol').outerHTML;
+}
+</script>
+<iframe></iframe>
+<script id='a'></script>
+<script id='b'></script>
+<script id='c'></script>
diff --git a/dom/base/crashtests/1405771.html b/dom/base/crashtests/1405771.html
new file mode 100644
index 0000000000..120fcaf294
--- /dev/null
+++ b/dom/base/crashtests/1405771.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script></script>
+<!-- a -->
+<script>
+window.onload=function(){
+ let s=window.getSelection();
+ let r=document.createRange();
+ r.selectNode(document.getElementById('b'));
+ s.addRange(r);
+ try{window.getSelection().modify('extend','forward','word')}catch(e){}
+ let o=document.getElementById('a');
+ o.parentNode.replaceChild(document.createElement('col'), o);
+}
+</script>
+>
+<template id='a' contenteditable='true'></template>
+<header id='b'></header>
+<!-- a -->
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/1406109-1.html b/dom/base/crashtests/1406109-1.html
new file mode 100644
index 0000000000..4ec653dd6f
--- /dev/null
+++ b/dom/base/crashtests/1406109-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<script>
+function jsfuzzer() {
+ try { a.matches("1"); } catch(e) { }
+ try { document.createElement("p").closest("1"); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<feBlend id="a" />
diff --git a/dom/base/crashtests/1411473.html b/dom/base/crashtests/1411473.html
new file mode 100644
index 0000000000..93e42bd8a2
--- /dev/null
+++ b/dom/base/crashtests/1411473.html
@@ -0,0 +1,12 @@
+<html>
+ <head class="reftest-wait">
+ <script>
+ var img = new Image(-256, 1024);
+ img.src = 'data:;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA';
+ img.onload = function () {
+ img.crossOrigin ="Anonymous";
+ document.implementation.createDocument('', '', null).adoptNode(img);
+ };
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1413815.html b/dom/base/crashtests/1413815.html
new file mode 100644
index 0000000000..309e54d082
--- /dev/null
+++ b/dom/base/crashtests/1413815.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script type="text/javascript">
+class XFoo extends HTMLElement {
+ constructor() {
+ super();
+ }
+
+ static get observedAttributes() {
+ return [3.76277868767527e-310, Symbol("Ashitaka"), {}];
+ }
+
+ attributeChangedCallback(aName, aOldValue, aNewValue) {
+ }
+}
+
+customElements.define('x-foo', XFoo);
+</script>
+</html>
diff --git a/dom/base/crashtests/1419799.html b/dom/base/crashtests/1419799.html
new file mode 100644
index 0000000000..b6d34a1a97
--- /dev/null
+++ b/dom/base/crashtests/1419799.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement('textarea') } catch(e) { }
+ try { o2 = document.createElement('div') } catch(e) { }
+ try { o3 = document.createElement('map') } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { o2.appendChild(o1) } catch(e) { }
+ try { document.documentElement.getClientRects() } catch(e) { }
+ try { o4 = o2.attachShadow({ mode: "open" }); } catch(e) { }
+ try { o1.appendChild(o3) } catch(e) { }
+ try { o5 = o3.parentElement } catch(e) { }
+ try { o3.outerHTML = "\n" } catch(e) { }
+ try { o4.prepend("", o5, "") } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1419902.html b/dom/base/crashtests/1419902.html
new file mode 100644
index 0000000000..b0742b5be0
--- /dev/null
+++ b/dom/base/crashtests/1419902.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <script>
+ var winsToClose = []
+ onbeforeunload = function() {
+ for (let win of winsToClose) {
+ if (win) {
+ win.close();
+ }
+ }
+ };
+ for (let i = 0; i < 38; i++) {
+ customElements.define("custom-element_0", class extends HTMLElement {
+ constructor() {
+ try { o1 = document.createElement("custom-element_0") } catch (e) {}
+ try { winsToClose.push(window.open("javascript:'<html><body>dummy</body></html>';")); } catch (e) {}
+ }
+ })
+ try { o3 = document.createElement("custom-element_0") } catch (e) {}
+ }
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1422883.html b/dom/base/crashtests/1422883.html
new file mode 100644
index 0000000000..f074592e91
--- /dev/null
+++ b/dom/base/crashtests/1422883.html
@@ -0,0 +1,10 @@
+<style>
+:after{-webkit-mask:url(#);content:counter(a)}
+</style>
+<script>
+function go(){
+ a.attachShadow({mode:"closed"})
+}
+</script>
+<body onload=go()>
+<h2 id="a">
diff --git a/dom/base/crashtests/1428053.html b/dom/base/crashtests/1428053.html
new file mode 100644
index 0000000000..d40496da6d
--- /dev/null
+++ b/dom/base/crashtests/1428053.html
@@ -0,0 +1,23 @@
+<script>
+ try { o1 = document.createElement("h1") } catch (e) {}
+ try { o2 = document.createElement("slot") } catch (e) {}
+ try { o3 = document.createElement("o") } catch (e) {}
+ try { document.documentElement.appendChild(o1) } catch (e) {}
+ try { document.documentElement.appendChild(o3) } catch (e) {}
+ try { o4 = o1.attachShadow({mode: "open"}) } catch (e) {}
+ try { o5 = new Range() } catch (e) {}
+ try { o6 = new MutationObserver(function(arg_0, arg_1) {
+ try { o4.append("", o2, "") } catch (e) {};
+ }) } catch (e) {}
+ try { o5.selectNode(o3); } catch (e) {}
+ try { customElements.define("custom-element_0", class extends HTMLElement {
+ connectedCallback() {
+ try { this.offsetHeight } catch (e) {}
+ }
+ }) } catch (e) {}
+ try { o8 = document.createElement("custom-element_0") } catch (e) {}
+ try { document.documentElement.appendChild(o8) } catch (e) {}
+ try { o6.observe(document, { "childList": [""] }); } catch (e) {}
+ try { document.replaceChild(o5.endContainer, document.documentElement); } catch (e) {}
+ try { o1.insertAdjacentHTML("afterBegin", "<</#"); } catch (e) {}
+</script>
diff --git a/dom/base/crashtests/1441029.html b/dom/base/crashtests/1441029.html
new file mode 100644
index 0000000000..1d222fdd29
--- /dev/null
+++ b/dom/base/crashtests/1441029.html
@@ -0,0 +1 @@
+<div is="div">
diff --git a/dom/base/crashtests/1445670.html b/dom/base/crashtests/1445670.html
new file mode 100644
index 0000000000..8afbcfbea2
--- /dev/null
+++ b/dom/base/crashtests/1445670.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <script>
+ function go () {
+ try { o1 = document.getElementById('x') } catch(e) { }
+ try { o1.src = '=' } catch(e) { }
+ try { o2 = document.cloneNode(false) } catch(e) { }
+ try { o3 = new XMLHttpRequest({mozAnon: true}) } catch(e) { }
+ try { o3.open('GET', 1, false) } catch(e) { }
+ try { o3.send() } catch(e) { }
+ try { o2.prepend('', o1, '', o2) } catch(e) { }
+ }
+ document.addEventListener('DOMContentLoaded', go)
+ </script>
+ </head>
+ <body>
+ <picture>
+ <img id='x' src='data:image/png;base64,R0lGODlhAQABAAD/ACH/C05FVFNDQVBFMi4wAwEAAAAsAAAAAEAAQAAAgA'/>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/1449601.html b/dom/base/crashtests/1449601.html
new file mode 100644
index 0000000000..7dcd22706d
--- /dev/null
+++ b/dom/base/crashtests/1449601.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <script>
+ var blob = new Blob(["foo"], { type: "text/plain" });
+ var url = URL.createObjectURL(blob);
+ var ifr = document.createElement("iframe");
+ ifr.src = url;
+ document.body.appendChild(ifr);
+
+ onload = function() {
+ try { window.find('foo',false,true,true,true,true) } catch(e) { }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/base/crashtests/1458016.html b/dom/base/crashtests/1458016.html
new file mode 100644
index 0000000000..1876938136
--- /dev/null
+++ b/dom/base/crashtests/1458016.html
@@ -0,0 +1,12 @@
+<script>
+function go() {
+ b.appendChild(document.body.firstChild);
+ var c = document.getSelection();
+ c.setPosition(a,1);
+ c.containsNode(document.body.firstChild);
+}
+</script>
+<body onload=go()>
+<!--a-->
+<time id="a" additive="replace">
+<option id="b">
diff --git a/dom/base/crashtests/1459688.html b/dom/base/crashtests/1459688.html
new file mode 100644
index 0000000000..63bd08b5c6
--- /dev/null
+++ b/dom/base/crashtests/1459688.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<div id="host"></div>
+<script>
+ host.attachShadow({ mode: "open" }).innerHTML = `<style></style>`;
+ host.shadowRoot.styleSheets[0].foobie = host.shadowRoot;
+</script>
diff --git a/dom/base/crashtests/1460794.html b/dom/base/crashtests/1460794.html
new file mode 100644
index 0000000000..200e5801cf
--- /dev/null
+++ b/dom/base/crashtests/1460794.html
@@ -0,0 +1,19 @@
+<script>
+function go() {
+ b.addEventListener("DOMAttrModified", eh2);
+ e.setAttribute("lang", "en-us");
+}
+function eh1() {
+ a.getSVGDocument().adoptNode(c);
+}
+function eh2() {
+ d.addEventListener("DOMNodeRemoved", eh1);
+ d.innerHTML = undefined;
+}
+</script>
+<body onload=go()>
+<iframe id="a"></iframe>
+<a id="c"/>
+<g id="d">
+<mask id="b"/>
+<font-face-format id="e">
diff --git a/dom/base/crashtests/1462548.html b/dom/base/crashtests/1462548.html
new file mode 100644
index 0000000000..24eace10d1
--- /dev/null
+++ b/dom/base/crashtests/1462548.html
@@ -0,0 +1,13 @@
+<script>
+window.requestIdleCallback(function(){SpecialPowers.forceCC()});
+var limit = 0;
+function go() {
+limit++; if(limit > 2) { return; }
+ var b = document.createElement("blockquote").attachShadow({mode: "open"});
+ b.innerHTML = a.innerHTML;
+}
+</script>
+<data id="a">
+<audio>
+<source onerror="go()">
+<link>
diff --git a/dom/base/crashtests/149320-1.html b/dom/base/crashtests/149320-1.html
new file mode 100644
index 0000000000..d09b2c5ada
--- /dev/null
+++ b/dom/base/crashtests/149320-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+</head>
+<body>
+<div>Selection is HERE only</div>
+<script type="text/javascript">
+ selectedNode = document.getElementsByTagName("div").item(0);
+ testRange = document.createRange();
+
+ testRange.setStart(selectedNode, 13);
+ testRange.setEnd(selectedNode, 17);
+
+ document.body.appendChild(document.createTextNode("Text of Range: "+testRange));
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/1505811.html b/dom/base/crashtests/1505811.html
new file mode 100644
index 0000000000..10be85c6dd
--- /dev/null
+++ b/dom/base/crashtests/1505811.html
@@ -0,0 +1,24 @@
+<html>
+
+<head>
+ <script>
+ function start() {
+ window.CustomElement0 = class extends HTMLElement {
+ constructor() {
+ super()
+ }
+ connectedCallback() {
+ this.before('', custom)
+ this.outerHTML = '<input pattern=""value=ð>'
+ }
+ }
+ customElements.define('custom-element-0', CustomElement0)
+ custom = document.createElementNS('http://www.w3.org/1999/xhtml', 'custom-element-0')
+ document.documentElement.appendChild(custom)
+ }
+
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/1505875.html b/dom/base/crashtests/1505875.html
new file mode 100644
index 0000000000..24a7d82a09
--- /dev/null
+++ b/dom/base/crashtests/1505875.html
@@ -0,0 +1,10 @@
+<script>
+function go() {
+ var r = document.getSelection().getRangeAt(0).cloneRange();
+ a.type = "";
+ r.insertNode(b);
+}
+</script>
+<body onload=go()>
+<input id="a" autofocus type="date">
+<summary id="b">x</summary>
diff --git a/dom/base/crashtests/1508845.html b/dom/base/crashtests/1508845.html
new file mode 100644
index 0000000000..470b19b674
--- /dev/null
+++ b/dom/base/crashtests/1508845.html
@@ -0,0 +1,17 @@
+<script>
+window.onload=function(){
+ let bigShadowTree = `<div>`;
+ for (let i = 0; i < 10; ++i)
+ bigShadowTree = bigShadowTree + bigShadowTree;
+ b.attachShadow({ mode: 'open' }).innerHTML = bigShadowTree;
+ // Create wrappers for all those elements.
+ [...b.shadowRoot.querySelectorAll('*')].forEach(() => {});
+ document.documentElement.addEventListener('DOMNodeRemoved', function(){
+ window.frames[0].document.body.appendChild(b)
+ })
+ window.frames[0].document.body.appendChild(a)
+}
+</script>
+<mark id='a'>
+<iframe></iframe>
+<div id='b'>
diff --git a/dom/base/crashtests/1516289.html b/dom/base/crashtests/1516289.html
new file mode 100644
index 0000000000..f6d4255213
--- /dev/null
+++ b/dom/base/crashtests/1516289.html
@@ -0,0 +1,14 @@
+<script>
+function eh1() {
+ a.remove()
+ window.event.composedPath()
+}
+function eh2() {
+ b.addEventListener("DOMNodeInserted", eh1)
+ c.insertAdjacentElement("afterBegin", document.createElement("s"))
+}
+</script>
+<image srcset="A" onerror="eh2()"></image>
+<marquee id="a">
+<time id="b">
+<dialog id="c">AA</dialog>
diff --git a/dom/base/crashtests/1516560.html b/dom/base/crashtests/1516560.html
new file mode 100644
index 0000000000..61c57e63d6
--- /dev/null
+++ b/dom/base/crashtests/1516560.html
@@ -0,0 +1,3 @@
+<script>
+ for (var i = 0; i < 10000; ++i) location.noSuchProp;
+</script>
diff --git a/dom/base/crashtests/1517025.html b/dom/base/crashtests/1517025.html
new file mode 100644
index 0000000000..795af0a3a9
--- /dev/null
+++ b/dom/base/crashtests/1517025.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <iframe src="/\b%9ª">
+ </body>
+</html>
diff --git a/dom/base/crashtests/1528675.html b/dom/base/crashtests/1528675.html
new file mode 100644
index 0000000000..122935c1f5
--- /dev/null
+++ b/dom/base/crashtests/1528675.html
@@ -0,0 +1,28 @@
+<script>
+function start() {
+ o14=window.document;
+ o432=document.createElement('audio');
+ o433=document.createElement('track');
+ o432.appendChild(o433);
+ o493=o432.textTracks;
+ o14.write('<html></html>');
+ o757=document.createElement('iframe');
+ o757.addEventListener('load', fun0, false);
+ document.documentElement.appendChild(o757);
+ o432.controls^=1;
+}
+function fun0(e) {
+ o1062=e.target;
+ o1063=o1062.contentWindow;
+ o1064=o1063.document;
+ document.documentElement.appendChild(o432);
+ setTimeout(fun1,240);
+}
+function fun1() {
+ o433.parentNode.removeChild(o433);
+ document.replaceChild(o1064.documentElement,document.documentElement);
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+}
+</script>
+<body onload="start()"></body>
diff --git a/dom/base/crashtests/1529203-1.html b/dom/base/crashtests/1529203-1.html
new file mode 100644
index 0000000000..322a98f628
--- /dev/null
+++ b/dom/base/crashtests/1529203-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<iframe></iframe>
+<script>
+ function doIt() {
+ var doc = document.querySelector("iframe").contentDocument;
+ doc.open();
+ doc.write('<script>import("data:text/javascript,")</' + 'script>');
+ doc.close();
+ }
+ doIt();
+ setTimeout(() => {
+ doIt();
+ document.documentElement.removeAttribute("class");
+ }, 100);
+</script>
+</html>
diff --git a/dom/base/crashtests/1529203-2.html b/dom/base/crashtests/1529203-2.html
new file mode 100644
index 0000000000..edf071488a
--- /dev/null
+++ b/dom/base/crashtests/1529203-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<script>
+ var win = null;
+ function doIt() {
+ if (win === null) {
+ win = window.open();
+ }
+ var doc = win.document;
+ doc.open();
+ doc.write('<script>import("data:text/javascript,")</' + 'script>');
+ doc.close();
+ }
+ doIt();
+ setTimeout(() => {
+ doIt();
+ win.close();
+ document.documentElement.removeAttribute("class");
+ }, 100);
+</script>
+</html>
diff --git a/dom/base/crashtests/1529203-3.html b/dom/base/crashtests/1529203-3.html
new file mode 100644
index 0000000000..ce8c2a5633
--- /dev/null
+++ b/dom/base/crashtests/1529203-3.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<script>
+ var win;
+ function doIt() {
+ if (!win) {
+ win = window.open();
+ }
+ var doc = win.document;
+ doc.open();
+ doc.write('<script type="module" src="./module-with-syntax-error.js"></' + 'script>');
+ doc.close();
+ }
+ doIt()
+ setTimeout(() => {
+ doIt();
+ win.close();
+ document.documentElement.removeAttribute("class");
+ }, 100);
+</script>
+</html>
diff --git a/dom/base/crashtests/1555786.html b/dom/base/crashtests/1555786.html
new file mode 100644
index 0000000000..97740ad795
--- /dev/null
+++ b/dom/base/crashtests/1555786.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script>
+ function start() {
+ const xhr = new XMLHttpRequest();
+ const observer = new ResizeObserver(entries => {
+ xhr.open('GET', '', false);
+ xhr.send();
+ typeof entries[0].borderBoxSize;
+ typeof entries[0].contentRect;
+ typeof entries[0].borderBoxSize;
+ })
+ observer.observe(document.getElementById('list'), {});
+ window.close();
+ }
+
+ document.addEventListener('DOMContentLoaded', start);
+ </script>
+</head>
+<body>
+ <li class="" id="list">
+</body>
+</html>
diff --git a/dom/base/crashtests/1566310.html b/dom/base/crashtests/1566310.html
new file mode 100644
index 0000000000..f00b168b70
--- /dev/null
+++ b/dom/base/crashtests/1566310.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+<script type="text/javascript">
+function noop() {}
+function crash() {
+ let div = document.querySelector("div");
+ let ifr = document.querySelector("iframe");
+
+ // We need a reference to the iframe's window from *before* it gets detached.
+ let win = ifr.contentWindow;
+
+ div.appendChild(ifr);
+
+ win.addEventListener("beforeunload", noop);
+ win.removeEventListener("beforeunload", noop);
+}
+</script>
+</head>
+<body onload="crash()">
+ <div></div>
+ <iframe></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/1577191.html b/dom/base/crashtests/1577191.html
new file mode 100644
index 0000000000..603157dd52
--- /dev/null
+++ b/dom/base/crashtests/1577191.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<script>
+ function start () {
+ const frame = document.createElement('frame')
+ document.documentElement.appendChild(frame)
+ const o1 = document.createElement('c')
+ o1.insertBefore(frame, o1.childNodes[0])
+ SpecialPowers.gc();
+ const xhr = new XMLHttpRequest()
+ xhr.open('P', '', false)
+ xhr.send()
+ }
+
+ document.addEventListener('DOMContentLoaded', start)
+</script>
diff --git a/dom/base/crashtests/1588259.html b/dom/base/crashtests/1588259.html
new file mode 100644
index 0000000000..6192b78ad1
--- /dev/null
+++ b/dom/base/crashtests/1588259.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<script>
+if (location.search == "?x=") {
+ setTimeout(() => window.close(), 1000);
+} else {
+ document.fonts.onloadingdone = () => {
+ document.forms[1].submit();
+ };
+ window.onload = () => {
+ document.forms[0].submit();
+ setTimeout(() => {
+ document.documentElement.removeAttribute("class");
+ }, 1000);
+ };
+}
+</script>
+<body>
+<form target="bug1588259"><input name="x"></form>
+<form target="bug1588259"><input name="x"></form>
+</body>
+</html>
diff --git a/dom/base/crashtests/1611853.html b/dom/base/crashtests/1611853.html
new file mode 100644
index 0000000000..2d1a153d59
--- /dev/null
+++ b/dom/base/crashtests/1611853.html
@@ -0,0 +1,19 @@
+<html class="reftest-wait">
+<head>
+ <style>
+ * {
+ user-select: none;
+ }
+ </style>
+ <script>
+ function start () {
+ document.execCommand('selectAll', false);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</head>
+<body onload="start()">
+<div contentEditable="true"></div>
+</body>
+</html>
+
diff --git a/dom/base/crashtests/1619322.html b/dom/base/crashtests/1619322.html
new file mode 100644
index 0000000000..1e627a4bad
--- /dev/null
+++ b/dom/base/crashtests/1619322.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <iframe></iframe>
+ <script>
+ window.addEventListener('load', () => {
+ let doc = frames[0].document;
+ let count = 0;
+ let id = setInterval(function () {
+ if (!doc.documentElement) {
+ clearInterval(id);
+ document.documentElement.className = "";
+ }
+ doc.documentElement.replaceWith();
+ }, 312)
+ doc.addEventListener('DOMSubtreeModified', (e) => e.originalTarget.writeln(), false)
+ })
+ </script>
+</html>
diff --git a/dom/base/crashtests/1623918.html b/dom/base/crashtests/1623918.html
new file mode 100644
index 0000000000..0c991acbbc
--- /dev/null
+++ b/dom/base/crashtests/1623918.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <script>
+ window.addEventListener('load', () => {
+ const anchor = document.getElementById('id_7')
+ anchor.contentEditable = 'true'
+ anchor.spellcheck = false
+ const input = document.createElementNS('http://www.w3.org/1999/xhtml', 'input')
+ anchor.appendChild(input)
+ const selection = self.getSelection()
+ selection.selectAllChildren(input)
+ })
+ </script>
+</head>
+<a id='id_7'></a>
+</html>
diff --git a/dom/base/crashtests/1656925.html b/dom/base/crashtests/1656925.html
new file mode 100644
index 0000000000..3ebd9b53cb
--- /dev/null
+++ b/dom/base/crashtests/1656925.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const area = document.createElementNS('http://www.w3.org/1999/xhtml', 'area')
+ document.documentElement.appendChild(area)
+ area.setAttribute('href', '')
+ for (let i = 0; i < 16; i++) {
+ area.setAttribute('download', '')
+ }
+ area.click()
+ object = document.createElementNS('http://www.w3.org/1999/xhtml', 'object')
+ document.documentElement.appendChild(object)
+ setTimeout("location.reload()", 100)
+ })
+ </script>
+</head>
+</html>
diff --git a/dom/base/crashtests/1665792.html b/dom/base/crashtests/1665792.html
new file mode 100644
index 0000000000..a1955a5d50
--- /dev/null
+++ b/dom/base/crashtests/1665792.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<script>
+window.onload = () => {
+ window[0].stop()
+ window[0].onpagehide = document.createElement("frameset").onload
+ a.src = ""
+ setTimeout(function() {
+ SpecialPowers.wrap(window).printPreview()?.close()
+ document.documentElement.className = "";
+ }, 0);
+}
+</script>
+<iframe id="a"></iframe>
diff --git a/dom/base/crashtests/1681729-inner1.html b/dom/base/crashtests/1681729-inner1.html
new file mode 100644
index 0000000000..5aca8e6221
--- /dev/null
+++ b/dom/base/crashtests/1681729-inner1.html
@@ -0,0 +1 @@
+<iframe srcdoc="<iframe srcdoc=&quot;<p>Test&quot;>">
diff --git a/dom/base/crashtests/1681729-inner2.html b/dom/base/crashtests/1681729-inner2.html
new file mode 100644
index 0000000000..5aca8e6221
--- /dev/null
+++ b/dom/base/crashtests/1681729-inner2.html
@@ -0,0 +1 @@
+<iframe srcdoc="<iframe srcdoc=&quot;<p>Test&quot;>">
diff --git a/dom/base/crashtests/1681729.html b/dom/base/crashtests/1681729.html
new file mode 100644
index 0000000000..beb608eec9
--- /dev/null
+++ b/dom/base/crashtests/1681729.html
@@ -0,0 +1,30 @@
+<html class="reftest-wait">
+<head>
+<script>
+ async function test() {
+ // We need to go through the event loop first, so we're running this from
+ // a timeout set from the load event. If we were adding a load from the load
+ // event directly we'd just replace the iframe's session history entry
+ // instead of cloning it (and replacing its children).
+ let frame = document.getElementById("frame");
+
+ let p = new Promise((r) => {
+ frame.addEventListener("load", r, { once: true });
+ });
+ frame.src = "1681729-inner2.html";
+ await p;
+
+ p = new Promise((r) => {
+ frame.contentWindow.addEventListener("pageshow", r, { once: true });
+ });
+ history.back();
+ await p;
+
+ document.documentElement.removeAttribute("class");
+ }
+</script>
+</head>
+<body onload="setTimeout(test, 0);">
+<iframe id="frame" src="1681729-inner1.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/1693049.html b/dom/base/crashtests/1693049.html
new file mode 100644
index 0000000000..9495aa39a2
--- /dev/null
+++ b/dom/base/crashtests/1693049.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+
+ * {
+ all: initial;
+ display: table-row-group !important
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const mspace = document.getElementById('id_0')
+ const tfoot = document.createElement('tfoot')
+ const h5 = document.createElement('h5')
+ document.documentElement.appendChild(tfoot)
+ document.documentElement.appendChild(h5)
+ const range = new Range()
+ range.setStart(tfoot, NaN)
+ setTimeout(async () => selection.extend(h5, Nan), 107)
+ const selection = document.getSelection()
+ selection.addRange(range)
+ selection.modify('move', 'backward', 'character')
+ range.surroundContents(mspace)
+ })
+ </script>
+</head>
+<mspace id='id_0'></mspace>
+<input type='reset'>
+</html>
diff --git a/dom/base/crashtests/1697256.html b/dom/base/crashtests/1697256.html
new file mode 100644
index 0000000000..25024083e3
--- /dev/null
+++ b/dom/base/crashtests/1697256.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<script>
+ window.onload = () => {
+ window.requestIdleCallback(() => {
+ SpecialPowers.wrap(self).printPreview()
+ setTimeout(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ }, 250)
+ })
+ }
+</script>
+<select autofocus='true'>
diff --git a/dom/base/crashtests/1697525.html b/dom/base/crashtests/1697525.html
new file mode 100644
index 0000000000..b8180700d3
--- /dev/null
+++ b/dom/base/crashtests/1697525.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ window.addEventListener('load', () => {
+ SpecialPowers.wrap(self).printPreview()?.print()
+ })
+</script>
diff --git a/dom/base/crashtests/1700237.html b/dom/base/crashtests/1700237.html
new file mode 100644
index 0000000000..779998b4f8
--- /dev/null
+++ b/dom/base/crashtests/1700237.html
@@ -0,0 +1,31 @@
+
+<style>
+:not(cursor) {
+ column-count: 1;
+ user-select: none;
+}
+</style>
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ window.getSelection().modify("move", "backward", "line")
+ const interval = setInterval(() => {
+ const m = new MutationObserver(() => {
+ let x = document.getSelection().getRangeAt(0)
+ a.appendChild(document.createElement("optgroup"))
+ try {
+ x.comparePoint(document.activeElement, 1)
+ } catch (error) {
+ clearInterval(interval)
+ return
+ }
+
+ })
+ m.observe(b, {attributes: true})
+ b.setAttribute("x", "false")
+ }, 100)
+})
+</script>
+<hr contenteditable="true">
+<shadow id="a">
+<select autofocus="autofocus">a</select>
+<div id="b">a</div>
diff --git a/dom/base/crashtests/1712198.html b/dom/base/crashtests/1712198.html
new file mode 100644
index 0000000000..0778f45f26
--- /dev/null
+++ b/dom/base/crashtests/1712198.html
@@ -0,0 +1,9 @@
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ let a = document.documentElement
+ SpecialPowers.wrap(self).printPreview().document.adoptNode(a)
+ let b = document.createElementNS('http://www.w3.org/1999/xhtml', 'optgroup')
+ a.appendChild(b)
+ b.focus({ })
+})
+</script>
diff --git a/dom/base/crashtests/1728670-1-child.html b/dom/base/crashtests/1728670-1-child.html
new file mode 100644
index 0000000000..04dc33c9fa
--- /dev/null
+++ b/dom/base/crashtests/1728670-1-child.html
@@ -0,0 +1,12 @@
+<html>
+<script>
+document.documentElement.clientHeight;
+document.body.clientHeight;
+</script>
+<body>
+<script>
+document.documentElement.clientHeight;
+document.body.clientHeight;
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1728670-1.html b/dom/base/crashtests/1728670-1.html
new file mode 100644
index 0000000000..71fc842200
--- /dev/null
+++ b/dom/base/crashtests/1728670-1.html
@@ -0,0 +1,3 @@
+<html>
+<iframe src="http://example.org/1728670-1-child.html" style="display: none;">
+</html>
diff --git a/dom/base/crashtests/1757923.html b/dom/base/crashtests/1757923.html
new file mode 100644
index 0000000000..d8e25a3a08
--- /dev/null
+++ b/dom/base/crashtests/1757923.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ class CustomElement extends HTMLElement {
+ constructor () {
+ super();
+ }
+ }
+
+ let element = document.createElement("custom-element");
+ customElements.define("custom-element", CustomElement);
+ customElements.upgrade(element);
+
+ const internals = element.attachInternals();
+ element = undefined;
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ internals.shadowRoot;
+ </script>
+ </head>
+</html>
diff --git a/dom/base/crashtests/1766472.html b/dom/base/crashtests/1766472.html
new file mode 100644
index 0000000000..80462766d1
--- /dev/null
+++ b/dom/base/crashtests/1766472.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script id="worker1" type="javascript/worker">
+ self.onmessage = async function (e) {
+ const abort = new AbortController()
+ self.close()
+ try { await self.scheduler.postTask(async () => abort.abort(undefined), { 'signal': abort.signal }) } catch (e) {}
+ self.queueMicrotask(async () => abort.abort(undefined))
+ }
+ </script>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const blob = new Blob([document.querySelector('#worker1').textContent], { type: 'text/javascript' })
+ const worker = new Worker(window.URL.createObjectURL(blob))
+ worker.postMessage([], [])
+ })
+ </script>
+</head>
+</html>
diff --git a/dom/base/crashtests/1780790.html b/dom/base/crashtests/1780790.html
new file mode 100644
index 0000000000..0bc4364763
--- /dev/null
+++ b/dom/base/crashtests/1780790.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+ <head>
+ <script>
+ function test() {
+ let ac = new AbortController();
+ scheduler.postTask(()=> { ac.abort(); throw "Foobar"; }, { signal: ac.signal });
+ scheduler.postTask(()=> document.documentElement.removeAttribute('class'));
+ }
+ </script>
+ <style>
+ </style>
+ </head>
+ <body onload="test()">
+ </body>
+</html>
diff --git a/dom/base/crashtests/1811939.html b/dom/base/crashtests/1811939.html
new file mode 100644
index 0000000000..73dbbb4ce6
--- /dev/null
+++ b/dom/base/crashtests/1811939.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<script>
+ import("data:text/javascript,0").catch(x => 0);
+ import("data:text/javascript,0").catch(x => 0);
+ setTimeout('document.documentElement.className = ""', 500);
+ window.stop();
+</script>
diff --git a/dom/base/crashtests/1822717-module.js b/dom/base/crashtests/1822717-module.js
new file mode 100644
index 0000000000..f4180c02c1
--- /dev/null
+++ b/dom/base/crashtests/1822717-module.js
@@ -0,0 +1,56 @@
+// Large (hence OMT compiled) module with a syntax error.
+
+this is a syntax error
+
+/*
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+*/
diff --git a/dom/base/crashtests/1822717.html b/dom/base/crashtests/1822717.html
new file mode 100644
index 0000000000..e328b209c7
--- /dev/null
+++ b/dom/base/crashtests/1822717.html
@@ -0,0 +1 @@
+<script type="module" src="1822717-module.js"></script>
diff --git a/dom/base/crashtests/1838484.html b/dom/base/crashtests/1838484.html
new file mode 100644
index 0000000000..c878492c5a
--- /dev/null
+++ b/dom/base/crashtests/1838484.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+ <script>
+ window.requestIdleCallback(() => {
+ window.print();
+ document.documentElement.className = "";
+ });
+ </script>
+ <template></template>
+</html>
diff --git a/dom/base/crashtests/205225-1.html b/dom/base/crashtests/205225-1.html
new file mode 100644
index 0000000000..26576d4d5c
--- /dev/null
+++ b/dom/base/crashtests/205225-1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<script type="text/javascript">
+document.createTreeWalker(null, NodeFilter.SHOW_ALL, null);
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/231475-1.html b/dom/base/crashtests/231475-1.html
new file mode 100644
index 0000000000..4a0d997daa
--- /dev/null
+++ b/dom/base/crashtests/231475-1.html
@@ -0,0 +1,12 @@
+<html><body>
+<script type="text/javascript">
+//function doBold(){
+range = document.createRange();
+newNode = document.createElement("b");
+range.selectNodeContents(document);
+
+range.surroundContents(newNode)
+//}
+</script>
+text
+</body></html> \ No newline at end of file
diff --git a/dom/base/crashtests/244933-1.html b/dom/base/crashtests/244933-1.html
new file mode 100644
index 0000000000..7eaf96cc31
--- /dev/null
+++ b/dom/base/crashtests/244933-1.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+<pre>
+<script>
+document.write(window.sidebar.something);
+for (i in window.sidebar)
+{
+ break
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/crashtests/275912-1.html b/dom/base/crashtests/275912-1.html
new file mode 100644
index 0000000000..08b5bc5fc5
--- /dev/null
+++ b/dom/base/crashtests/275912-1.html
@@ -0,0 +1,2 @@
+<HTML>
+><><BODY onLoad=*>< \ No newline at end of file
diff --git a/dom/base/crashtests/293388-1.html b/dom/base/crashtests/293388-1.html
new file mode 100644
index 0000000000..5c2e5c53c9
--- /dev/null
+++ b/dom/base/crashtests/293388-1.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait"><head><title>Testcase bug 293388 - Overwriting of div innerHTML cause starting of the loading icon(circle) and some times browser can crash [@ nsRange::DeleteContents]</title></head>
+<script>
+function reallyClear()
+{
+ var par = document.getElementById("par");
+ var range = document.createRange();
+ range.selectNodeContents(par);
+ range.deleteContents();
+ document.documentElement.removeAttribute("class");
+}
+
+function clear()
+{
+ document.body.removeEventListener("DOMNodeRemoved", clear);
+ reallyClear();
+}
+
+function init()
+{
+ document.body.addEventListener("DOMNodeRemoved", clear);
+}
+
+</script>
+<body onload="init(); setTimeout(reallyClear, 10);">
+<div id="par"><span>1</span><span>2</span></div>
+</body></html> \ No newline at end of file
diff --git a/dom/base/crashtests/325730-1.html b/dom/base/crashtests/325730-1.html
new file mode 100644
index 0000000000..113f697abc
--- /dev/null
+++ b/dom/base/crashtests/325730-1.html
@@ -0,0 +1,27 @@
+<html>
+
+<head>
+
+<script>
+
+function init()
+{
+ document.addEventListener("DOMNodeRemoved", meep);
+ document.body.appendChild(document.getElementById("b"));
+
+ function meep()
+ {
+ document.removeEventListener("DOMNodeRemoved", meep);
+ document.body.removeChild(document.getElementById("c"));
+ }
+}
+
+window.addEventListener("load", init);
+
+
+</script>
+
+</head>
+
+<body><div id="b"></div><div id="c"></div></body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/326618-1.html b/dom/base/crashtests/326618-1.html
new file mode 100644
index 0000000000..926670ab63
--- /dev/null
+++ b/dom/base/crashtests/326618-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+</head>
+<body>
+<script>
+
+try {
+ document.body.removeChild(function(){});
+} catch (e) {
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/326646-1.html b/dom/base/crashtests/326646-1.html
new file mode 100644
index 0000000000..5b654dd02c
--- /dev/null
+++ b/dom/base/crashtests/326646-1.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+
+<script>
+
+var dt;
+
+function init() {
+ dt = document.implementation.createDocumentType("rheet", 0, 2);
+ dt.foopy = "quux";
+}
+
+init();
+
+</script>
+
+</head>
+
+<body>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/326865-1.html b/dom/base/crashtests/326865-1.html
new file mode 100644
index 0000000000..579e95c9ce
--- /dev/null
+++ b/dom/base/crashtests/326865-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+try {
+ window.getComputedStyle(null);
+} catch (e) {
+}
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/327571-1.html b/dom/base/crashtests/327571-1.html
new file mode 100644
index 0000000000..117f093510
--- /dev/null
+++ b/dom/base/crashtests/327571-1.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+
+function init()
+{
+
+ var x = document.getElementById("d");
+ x.__proto__ = document.getElementById("f");
+ x.inputname;
+}
+
+</script>
+</head>
+
+<body onload="init();">
+
+<div id="d"></div>
+<form id="f"><input name="inputname"></form>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/327694.html b/dom/base/crashtests/327694.html
new file mode 100644
index 0000000000..132c02cf82
--- /dev/null
+++ b/dom/base/crashtests/327694.html
@@ -0,0 +1,17 @@
+<html> <head>
+
+<script>
+
+function init()
+{
+ var y = document.getElementById("tt");
+ var z = y.firstChild;
+ y.removeChild(z);
+ z.text;
+}
+
+window.addEventListener("load", init);
+
+</script>
+ </head> <body> <div id="tt"><a href="http://www.mozilla.org">Foo</a></div>
+ </body> </html>
diff --git a/dom/base/crashtests/327695-1.html b/dom/base/crashtests/327695-1.html
new file mode 100644
index 0000000000..a3d5562b3a
--- /dev/null
+++ b/dom/base/crashtests/327695-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<script>
+document.createTextNode("").__proto__.__proto__ = null;
+document.createTextNode("");
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/329481-1.xhtml b/dom/base/crashtests/329481-1.xhtml
new file mode 100644
index 0000000000..3ba5477f3d
--- /dev/null
+++ b/dom/base/crashtests/329481-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<p>This page made Firefox leak domwindows and documents.</p>
+<span onmouseover="1"></span>
+<script>
+try {
+ window.__proto__ = document.createElement("div");
+} catch(e) {
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/336381-1.xhtml b/dom/base/crashtests/336381-1.xhtml
new file mode 100644
index 0000000000..eac3eeb57a
--- /dev/null
+++ b/dom/base/crashtests/336381-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var zz = document.getElementById("d").childNodes[0];
+
+ zz.parentNode.removeChild(zz);
+
+ var r = document.createRange();
+ r.setStart(zz, 0);
+ r.setEnd(zz, 0);
+
+ try {
+ r.insertNode(document.createTextNode('5'));
+ } catch (e) {
+ }
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+<div id="d">3</div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/336715-1.xhtml b/dom/base/crashtests/336715-1.xhtml
new file mode 100644
index 0000000000..ad9d1430ad
--- /dev/null
+++ b/dom/base/crashtests/336715-1.xhtml
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+<![CDATA[
+
+function funn()
+{
+ var q = document.getElementById("q");
+
+ var start1 = document.getElementById("start1");
+ var end1 = document.getElementById("end1");
+
+ var start2 = q; // div
+ var end2 = q.previousSibling; // text node
+
+ var r = document.createRange();
+ r.setStart(start1, 0);
+ r.setEnd(end1, 0);
+ r.deleteContents();
+
+ // the offsets for start2 and end2 must be the same to trigger the assertion
+ var s = document.createRange();
+ s.setStart(start2, 0);
+ s.setEnd(end2, 0);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</head>
+
+<body onload="setTimeout(funn, 30)">
+ <div id="start1"></div>
+ <div id="t">X<div id="q">Y</div></div>
+ <div id="end1"></div>
+</body>
+
+</html>
diff --git a/dom/base/crashtests/338391-1.xhtml b/dom/base/crashtests/338391-1.xhtml
new file mode 100644
index 0000000000..9366e0c0fd
--- /dev/null
+++ b/dom/base/crashtests/338391-1.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<script>
+
+function boom()
+{
+ var n = document.getElementById("n");
+
+ document.addEventListener("DOMNodeRemoved", foozle, false);
+ n.parentNode.removeChild(n);
+ document.removeEventListener("DOMNodeRemoved", foozle, false);
+
+ function foozle()
+ {
+ document.removeEventListener("DOMNodeRemoved", foozle, false); // prevent accidental recursion
+
+ n.parentNode.removeChild(n);
+
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 30)">
+
+<div id="n"></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/338674-1.xhtml b/dom/base/crashtests/338674-1.xhtml
new file mode 100644
index 0000000000..2cba227dcd
--- /dev/null
+++ b/dom/base/crashtests/338674-1.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<script>
+
+function boom()
+{
+ var s = document.getElementById("s")
+ var t = s.previousSibling; // a whitespace text node..
+
+ document.addEventListener("DOMAttrModified", bang, false);
+ rM(s);
+ document.removeEventListener("DOMAttrModified", bang, false);
+
+ function bang(ev) {
+ document.removeEventListener("DOMAttrModified", bang, false); // avoid accidental recursion, multiple calls, etc.
+ rM(t);
+ }
+
+ document.documentElement.removeAttribute("class");
+}
+
+function rM(n) { n.parentNode.removeChild(n); }
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 1);">
+
+<div><select><option>C</option></select></div>
+
+<span id="s">A <div>B</div></span>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/340733-1.html b/dom/base/crashtests/340733-1.html
new file mode 100644
index 0000000000..26d8fb9cdf
--- /dev/null
+++ b/dom/base/crashtests/340733-1.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+
+<head>
+<script type="text/javascript">
+
+
+function boom()
+{
+ var map = document.getElementById("map");
+ var table = document.getElementById("table");
+
+ map.appendChild(table);
+ map.childNodes[0].appendChild(document.createTextNode(' '));
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<img src="../../../testing/crashtest/images/tree.gif" usemap="#map">
+<map name="map" id="map"><area></map>
+<table aardvark="aardvark" id="table"><tbody><tr><td>Table</td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/343730-1.xhtml b/dom/base/crashtests/343730-1.xhtml
new file mode 100644
index 0000000000..82b5a26a8a
--- /dev/null
+++ b/dom/base/crashtests/343730-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<script>
+<![CDATA[
+
+var div;
+
+function boo()
+{
+ var newSpan = document.createElement('span');
+ div = document.getElementById('div');
+
+ var newScript = document.createElement('script');
+ var nt = document.createTextNode("document.body.removeChild(div);");
+ newScript.appendChild(nt);
+ newSpan.appendChild(newScript);
+
+ div.appendChild(newSpan);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</head>
+
+<body onload="setTimeout(boo, 30);">
+
+<div id="div"></div>
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/343850-1.xhtml b/dom/base/crashtests/343850-1.xhtml
new file mode 100644
index 0000000000..4bf313062d
--- /dev/null
+++ b/dom/base/crashtests/343850-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ var div = document.getElementById("div");
+ var embed = document.getElementById("embed");
+ embed.setAttribute("src", "javascript:'QQQQ'");
+ embed.removeAttribute("src");
+ document.body.appendChild(div);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div id="div"><embed id="embed"/></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/343889-1.html b/dom/base/crashtests/343889-1.html
new file mode 100644
index 0000000000..8ea8bc28d3
--- /dev/null
+++ b/dom/base/crashtests/343889-1.html
@@ -0,0 +1,18 @@
+<Html class="reftest-wait">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf8">
+</head>
+<body onload="boom()">
+<iframe src="#" id="data"></iframe>
+<script>
+function boom() {
+var d=document.getElementById('data').contentDocument;
+d.clear()
+var text='ТеÑÑ‚';
+d.write(text);
+d.close();
+document.documentElement.removeAttribute("class");
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/344434-1.xhtml b/dom/base/crashtests/344434-1.xhtml
new file mode 100644
index 0000000000..28c5fc0e49
--- /dev/null
+++ b/dom/base/crashtests/344434-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+var XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+function foopy()
+{
+ document.getElementsByTagName("div")[0].appendChild(document.createElement("div"));
+ document.body.appendChild(document.createElementNS(XUL_NS, "toolbarpalette"))
+ document.body.appendChild(document.createElementNS(XUL_NS, "toolbar"))
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(foopy, 30);">
+ <div>A</div>
+</body>
+
+</html>
diff --git a/dom/base/crashtests/344882-1.html b/dom/base/crashtests/344882-1.html
new file mode 100644
index 0000000000..6a8dd69d5b
--- /dev/null
+++ b/dom/base/crashtests/344882-1.html
@@ -0,0 +1,33 @@
+<html class="reftest-wait">
+<head>
+<script>
+var obj;
+
+function boo()
+{
+ obj = document.getElementById("obj");
+ setScriptSrc();
+}
+
+function setScriptSrc()
+{
+ obj.data = "javascript:setScriptSrc2();";
+}
+
+function setScriptSrc2()
+{
+ obj.data = "javascript:void 0";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boo, 30);">
+
+<object data="../../../testing/crashtest/images/tree.gif" id="obj">
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/345837-1.xhtml b/dom/base/crashtests/345837-1.xhtml
new file mode 100644
index 0000000000..a1f010c5e2
--- /dev/null
+++ b/dom/base/crashtests/345837-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+<![CDATA[
+
+function foo()
+{
+ var r = document.createRange();
+ r.setStart(document.getElementById("a"), 0);
+ r.setEnd(document.getElementById("b"), 0);
+ window.getSelection().addRange(r);
+
+ var everything = document.getElementById("everything");
+ everything.remove();
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(foo, 30);">
+
+<div id="everything">
+
+<div id="a">
+ <span id="b" />
+</div>
+
+</div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/346381-1.html b/dom/base/crashtests/346381-1.html
new file mode 100644
index 0000000000..4d0263d804
--- /dev/null
+++ b/dom/base/crashtests/346381-1.html
@@ -0,0 +1,16 @@
+<html>
+
+<head>
+</head>
+
+<body onload="window.zoop = document.getElementById('paz');">
+
+<p>Loading this page should not make Firefox leak DOMWindows or documents.</p>
+
+<div id="paz">
+<span onmouseover="6"></span>
+</div>
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/349355-1.html b/dom/base/crashtests/349355-1.html
new file mode 100644
index 0000000000..627bdc6d61
--- /dev/null
+++ b/dom/base/crashtests/349355-1.html
@@ -0,0 +1,41 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function zap()
+{
+ var j = document.getElementById("j");
+ j.remove();
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+<body onload="setTimeout(zap, 1);">
+
+
+<div>
+
+ <form>
+ <fieldset>
+ <div id="j">
+ <div>
+ <div>
+ <span>
+ <input type="submit">
+ <label></label>
+ </span>
+ </div>
+ </div>
+
+ </div>
+ </fieldset>
+ </form>
+
+</div>
+
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/359432-1.xhtml b/dom/base/crashtests/359432-1.xhtml
new file mode 100644
index 0000000000..391a4d1622
--- /dev/null
+++ b/dom/base/crashtests/359432-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+
+<script>
+
+function boo()
+{
+ var tabbox = document.getElementById("tabbox");
+ document.body.removeChild(tabbox);
+ tabbox.setAttribute("context", "a");
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boo, 30)">
+
+<xul:tabbox id="tabbox" />
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/360599-1.html b/dom/base/crashtests/360599-1.html
new file mode 100644
index 0000000000..c090088c0f
--- /dev/null
+++ b/dom/base/crashtests/360599-1.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<style>
+#b::first-letter { }
+#c::first-line { }
+</style>
+<title>Testcase bug 360599 - Crash [@ nsFrameList::DestroyFrames] with first-letter/first-line css and position: fixed</title>
+</head>
+<body>
+This page should not crash Mozilla
+<div id="c">
+ <table>
+ <div id="b" style="display:table-header-group;">
+ <q>
+ text
+ <div style="position:fixed;">
+ <q>y</q>
+ </div>
+ </q>
+ </div>
+ <span style="display: table;"></span>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/366200-1.xhtml b/dom/base/crashtests/366200-1.xhtml
new file mode 100644
index 0000000000..a604990cf6
--- /dev/null
+++ b/dom/base/crashtests/366200-1.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script type="text/javascript">
+
+<![CDATA[
+
+function boom()
+{
+ var dE = document.documentElement;
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+
+ document.addEventListener("DOMNodeRemoved", whee);
+ document.removeChild(dE);
+ document.removeEventListener("DOMNodeRemoved", whee);
+
+ function whee()
+ {
+ document.removeEventListener("DOMNodeRemoved", whee);
+ document.insertBefore(newSpan, dE);
+ }
+}
+
+window.addEventListener("load", boom);
+
+]]>
+
+</script>
+</head>
+
+<body>
+<p>This text will disappear. There should be no assertions.</p>
+</body>
+</html>
diff --git a/dom/base/crashtests/369219-1.xhtml b/dom/base/crashtests/369219-1.xhtml
new file mode 100644
index 0000000000..b44242d262
--- /dev/null
+++ b/dom/base/crashtests/369219-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var select = document.getElementById("sss");
+ select.options.add.call(null, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<select id="sss"><option>foo</option></select>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/369413-1.html b/dom/base/crashtests/369413-1.html
new file mode 100644
index 0000000000..4fc3b7dfcc
--- /dev/null
+++ b/dom/base/crashtests/369413-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+try { atob(null); } catch(e) { }
+try { atob(""); } catch(e) { }
+try { atob("A="); } catch(e) { }
+try { atob("AA="); } catch(e) { }
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/371124-1-inner.html b/dom/base/crashtests/371124-1-inner.html
new file mode 100644
index 0000000000..d8fb45519d
--- /dev/null
+++ b/dom/base/crashtests/371124-1-inner.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ obj = document.getElementsByTagName("object")[0];
+ obj.__proto__ = null;
+ for (p in obj)
+ dump(p + "\n");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 200);">
+
+<object></object>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/371124-1.html b/dom/base/crashtests/371124-1.html
new file mode 100644
index 0000000000..ccefd3a324
--- /dev/null
+++ b/dom/base/crashtests/371124-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="371124-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/371124-2-inner.html b/dom/base/crashtests/371124-2-inner.html
new file mode 100644
index 0000000000..7883093822
--- /dev/null
+++ b/dom/base/crashtests/371124-2-inner.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+
+<script>
+ document.all.tags.__proto__ = null;
+ alert(document.all.tags)
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/371124-2.html b/dom/base/crashtests/371124-2.html
new file mode 100644
index 0000000000..93897635fa
--- /dev/null
+++ b/dom/base/crashtests/371124-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="371124-2-inner.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/371466-1.xhtml b/dom/base/crashtests/371466-1.xhtml
new file mode 100644
index 0000000000..8da0b22b12
--- /dev/null
+++ b/dom/base/crashtests/371466-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var marquee = document.getElementById("marquee");
+ var span = document.getElementById("span");
+ marquee.appendChild(span);
+ marquee.removeChild(span);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<marquee id="marquee" />
+
+<span id="span"><div>Foo</div><textarea/></span>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/372554-1.html b/dom/base/crashtests/372554-1.html
new file mode 100644
index 0000000000..a87edcecbf
--- /dev/null
+++ b/dom/base/crashtests/372554-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ if (location.protocol == "file:") {
+ try {
+ location.hostname = 'foo';
+ } catch(e) {
+ }
+ }
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(boom, 30)">
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/375399-1-inner.xhtml b/dom/base/crashtests/375399-1-inner.xhtml
new file mode 100644
index 0000000000..46573b49cd
--- /dev/null
+++ b/dom/base/crashtests/375399-1-inner.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:svg="http://www.w3.org/2000/svg">
+<svg:foreignObject x="0" y="0" width="100%" height="100%">
+<xul:tabs onselect="window.frameElement.parentNode.removeChild(window.frameElement)"><xul:tree id="t"/></xul:tabs>
+</svg:foreignObject>
+
+<html:script>
+function doe() {
+document.getElementById('t');
+}
+setTimeout(doe, 200);
+</html:script>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/375399-1.html b/dom/base/crashtests/375399-1.html
new file mode 100644
index 0000000000..8e50c7ef35
--- /dev/null
+++ b/dom/base/crashtests/375399-1.html
@@ -0,0 +1,11 @@
+<html class="reftest-wait">
+<head>
+<title>Testcase bug 375399 - Crash [@ nsElementSH::PostCreate] when removing window when accessing xul:tree in xul:tabs onselect in svg:foreignObject</title>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+</head>
+<body>
+<iframe id="content" src="./375399-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/377360-1.xhtml b/dom/base/crashtests/377360-1.xhtml
new file mode 100644
index 0000000000..77fa300bef
--- /dev/null
+++ b/dom/base/crashtests/377360-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var div = document.getElementById("div");
+ div.textContent = String.fromCharCode(0xDCBF);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="div"></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/377960-1.html b/dom/base/crashtests/377960-1.html
new file mode 100644
index 0000000000..c61002eeea
--- /dev/null
+++ b/dom/base/crashtests/377960-1.html
@@ -0,0 +1,12 @@
+<html><head>
+
+</head>
+<body onblur="document.activeElement.style.display = 'none'" tabindex="4" onfocus="var x=document.getElementsByTagName('*');var i = Math.floor(Math.random()*x.length);x[i].focus()" style="overflow: scroll;">
+
+
+
+<iframe tabindex="2" style="overflow: scroll; display: inline; position: fixed; direction: rtl;"></iframe>
+
+<select onblur="event.target.setAttribute('tabindex', Math.floor(Math.random()*5)-9)" style="overflow: scroll; display: table; position: absolute; float: right; direction: rtl;"></select>
+
+</body></html> \ No newline at end of file
diff --git a/dom/base/crashtests/377960-2.html b/dom/base/crashtests/377960-2.html
new file mode 100644
index 0000000000..dc42dc00d2
--- /dev/null
+++ b/dom/base/crashtests/377960-2.html
@@ -0,0 +1,7 @@
+<html>
+<head><title>Click the &lt;select&gt; to crash.</title></head>
+<body onfocus="document.documentElement.focus(); document.body.style.display = 'none';">
+<iframe style="position: fixed;"></iframe>
+<select style="position: absolute;"></select>
+</body>
+</html>
diff --git a/dom/base/crashtests/384663-1-inner.xhtml b/dom/base/crashtests/384663-1-inner.xhtml
new file mode 100644
index 0000000000..f2914ce0aa
--- /dev/null
+++ b/dom/base/crashtests/384663-1-inner.xhtml
@@ -0,0 +1,18 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<tree>
+ <splitter style="overflow: scroll;">
+ <treecols style="overflow: scroll; display: block;">
+ <treeitem id="mw_b" style=" display: list-item;"/>
+ </treecols>
+ </splitter>
+</tree>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe() {
+window.addEventListener('DOMAttrModified', function(e) {document.removeChild(document.documentElement); }, true);
+ var y=document.getElementById('mw_b');
+ y.parentNode.removeChild(y);
+}
+setTimeout(doe, 200);
+</script>
+</window> \ No newline at end of file
diff --git a/dom/base/crashtests/384663-1.html b/dom/base/crashtests/384663-1.html
new file mode 100644
index 0000000000..d357dfd8ad
--- /dev/null
+++ b/dom/base/crashtests/384663-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="384663-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/386000-1.html b/dom/base/crashtests/386000-1.html
new file mode 100644
index 0000000000..0363f0b693
--- /dev/null
+++ b/dom/base/crashtests/386000-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script>
+
+var de;
+
+function boom()
+{
+ de = document.documentElement;
+ document.addEventListener("DOMNodeRemoved", f);
+
+ function f()
+ {
+ document.removeEventListener("DOMNodeRemoved", f);
+ de.appendChild(document.createElement("body"));
+ }
+
+ document.removeChild(de);
+
+ setTimeout(cont, 30);
+}
+
+function cont()
+{
+ document.appendChild(de);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+<body onload="setTimeout(boom, 30);">
+
+</body>
+</html>
diff --git a/dom/base/crashtests/386794-1.html b/dom/base/crashtests/386794-1.html
new file mode 100644
index 0000000000..4b50221688
--- /dev/null
+++ b/dom/base/crashtests/386794-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var weirdScript = document.createElementNS('http://example.com/foo', 'script');
+ document.body.appendChild(weirdScript);
+ document.body.innerHTML;
+}
+
+</script>
+</head>
+<body onload="boom()">
+</body>
+</html>
diff --git a/dom/base/crashtests/387460-1-inner.xhtml b/dom/base/crashtests/387460-1-inner.xhtml
new file mode 100644
index 0000000000..33b1891e7b
--- /dev/null
+++ b/dom/base/crashtests/387460-1-inner.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body>
+<a id="a" href="#"></a>
+
+<table><tbody>
+<tr><td id="d"/></tr>
+<tr id="b"><td id="c"></td></tr>
+</tbody></table>
+
+
+<script>
+function doe() {
+ document.getElementById('b').appendChild(document.getElementById('c'));
+ window.addEventListener('DOMAttrModified', function(e){document.getElementById('a').focus();}, true);
+ document.getElementById('d').setAttribute('rowspan', 3);
+}
+setTimeout(doe, 200);
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/387460-1.html b/dom/base/crashtests/387460-1.html
new file mode 100644
index 0000000000..bd1fc9353c
--- /dev/null
+++ b/dom/base/crashtests/387460-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="387460-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/399712-1.html b/dom/base/crashtests/399712-1.html
new file mode 100644
index 0000000000..58e9573090
--- /dev/null
+++ b/dom/base/crashtests/399712-1.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("ta").style.overflow = "scroll";
+ setTimeout(boom2, 12);
+}
+
+function boom2()
+{
+ document.getElementById("ta").style.overflow = "";
+ setTimeout(boom, 12);
+}
+
+function cont()
+{
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="boom(); setTimeout(cont, 800);">
+
+<textarea id="ta">x</textarea>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/400763-1.html b/dom/base/crashtests/400763-1.html
new file mode 100644
index 0000000000..2de6720908
--- /dev/null
+++ b/dom/base/crashtests/400763-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<script src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC" charset="UTF-8"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/407818.html b/dom/base/crashtests/407818.html
new file mode 100644
index 0000000000..eea475be42
--- /dev/null
+++ b/dom/base/crashtests/407818.html
@@ -0,0 +1,5 @@
+<html>
+<head>
+</head>
+<body contenteditable="true" onload="document.execCommand('selectAll', false, null);"><ol> <li></li></ol></body>
+</html>
diff --git a/dom/base/crashtests/410860-1.xml b/dom/base/crashtests/410860-1.xml
new file mode 100644
index 0000000000..4a7414fc9b
--- /dev/null
+++ b/dom/base/crashtests/410860-1.xml
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="document.getElementById('x').getBoundingClientRect();">
+
+<rect id="x"/>
+
+</svg>
+
diff --git a/dom/base/crashtests/411882-1.xhtml b/dom/base/crashtests/411882-1.xhtml
new file mode 100644
index 0000000000..6cd121044f
--- /dev/null
+++ b/dom/base/crashtests/411882-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body></body><textarea></textarea></html> \ No newline at end of file
diff --git a/dom/base/crashtests/416734-1.html b/dom/base/crashtests/416734-1.html
new file mode 100644
index 0000000000..69350933c1
--- /dev/null
+++ b/dom/base/crashtests/416734-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+</head>
+
+<body style="direction: rtl; visibility: collapse; white-space: pre;"><span style="display: -moz-inline-box;"><span><span style="font-size: 0pt; border: 1px dotted red; white-space: -moz-pre-wrap;">
+
+X X }
+
+
+ </span>
+ </span></span></body>
+
+</html>
diff --git a/dom/base/crashtests/417852-1.html b/dom/base/crashtests/417852-1.html
new file mode 100644
index 0000000000..bf8ba34863
--- /dev/null
+++ b/dom/base/crashtests/417852-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+window.__proto__ = null;
+Object.prototype.__proto__ = window;
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/crashtests/418928-1.html b/dom/base/crashtests/418928-1.html
new file mode 100644
index 0000000000..a3e42fe6f3
--- /dev/null
+++ b/dom/base/crashtests/418928-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.execCommand('selectAll', false, null);">
+
+<select><option contenteditable="true">A</option></select>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/420620-1.html b/dom/base/crashtests/420620-1.html
new file mode 100644
index 0000000000..74ae6dcbe3
--- /dev/null
+++ b/dom/base/crashtests/420620-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var a = document.documentElement;
+ var b = document.body;
+
+ document.removeChild(a);
+ b.contentEditable = "true";
+ document.appendChild(a);
+
+ function t() {
+ document.removeEventListener("DOMAttrModified", t);
+ document.removeChild(a);
+ }
+
+ document.addEventListener("DOMAttrModified", t);
+ document.execCommand("insertunorderedlist", false, "<h1>");
+ document.removeEventListener("DOMAttrModified", t);
+}
+
+</script>
+</head>
+
+<body onload="boom()"></body>
+</html>
diff --git a/dom/base/crashtests/424276-1.html b/dom/base/crashtests/424276-1.html
new file mode 100644
index 0000000000..feb39bf33d
--- /dev/null
+++ b/dom/base/crashtests/424276-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ window.getSelection().selectAllChildren(document.createTextNode("\n\n"));
+ window.getSelection().addRange(document.createRange());
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/dom/base/crashtests/426987-1.html b/dom/base/crashtests/426987-1.html
new file mode 100644
index 0000000000..7d277156e1
--- /dev/null
+++ b/dom/base/crashtests/426987-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<iframe src="data:text/html,<html><body onunload=&quot;parent.document.body.style.overflow = 'auto';&quot;></body></html>"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/43040-1.html b/dom/base/crashtests/43040-1.html
new file mode 100644
index 0000000000..00165763c4
--- /dev/null
+++ b/dom/base/crashtests/43040-1.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
+<script>
+var xmlDoc;
+
+function createDoc()
+{
+ var xmlDoc = document.implementation.createDocument("", "", null);
+ var xmlElem = xmlDoc.firstChild;
+ xmlElem.appendChild(document.createTextNode("blabla"));
+ xmlElem.firstChild.nodeValue;
+}
+</script>
+
+</HEAD>
+<BODY onload="createDoc();">
+</BODY>
+</HTML>
diff --git a/dom/base/crashtests/439206-1.html b/dom/base/crashtests/439206-1.html
new file mode 100644
index 0000000000..688ebe44d5
--- /dev/null
+++ b/dom/base/crashtests/439206-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.createElement("STYLE");
+ var t = document.createTextNode("\uDB00x"); // a high surrogate followed by 'x'
+ document.documentElement.appendChild(s);
+ s.appendChild(t);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/base/crashtests/443538-1.svg b/dom/base/crashtests/443538-1.svg
new file mode 100644
index 0000000000..cb7388a7cd
--- /dev/null
+++ b/dom/base/crashtests/443538-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="document.getElementById('use').removeChild(document.getElementById('s'));">
+
+ <use id="use" xlink:href="#s"><g id="s"/></use>
+
+</svg>
diff --git a/dom/base/crashtests/448615-1.html b/dom/base/crashtests/448615-1.html
new file mode 100644
index 0000000000..bfb4f365a1
--- /dev/null
+++ b/dom/base/crashtests/448615-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+div:first-letter { float: right; }
+
+</style>
+</head>
+
+<body><div>A</div></body>
+
+</html>
diff --git a/dom/base/crashtests/450383-1.html b/dom/base/crashtests/450383-1.html
new file mode 100644
index 0000000000..3c95b966ce
--- /dev/null
+++ b/dom/base/crashtests/450383-1.html
@@ -0,0 +1,9 @@
+<html>
+<BODY></BODY>
+<SCRIPT>
+ document.addEventListener("DOMCharacterDataModified",function(){
+ document.body.innerHTML=""; // change this to see memory corruption
+ },true);
+ document.body.innerHTML="<optGroup</form<textArea";
+</SCRIPT>
+</html>
diff --git a/dom/base/crashtests/450385-1.html b/dom/base/crashtests/450385-1.html
new file mode 100644
index 0000000000..e75159c51d
--- /dev/null
+++ b/dom/base/crashtests/450385-1.html
@@ -0,0 +1,11 @@
+<html>
+<BODY></BODY>
+<SCRIPT>
+document.body.addEventListener("DOMCharacterDataModified", function () {
+ document.body.innerHTML = "";
+ eventChild.appendChild(event.relatedNode);
+}, true);
+document.addEventListener("DOMNodeInserted", function () {}, true);
+document.body.innerHTML="]<kbd><small></kbd><base><optGroup></optGroup>";
+</SCRIPT>
+</html>
diff --git a/dom/base/crashtests/458637-1-inner.xhtml b/dom/base/crashtests/458637-1-inner.xhtml
new file mode 100644
index 0000000000..f91d6e5b4c
--- /dev/null
+++ b/dom/base/crashtests/458637-1-inner.xhtml
@@ -0,0 +1,4 @@
+<?xml-stylesheet type="text/xsl" href="#a"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<xsl:stylesheet version="1.0" id="a" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/458637-1.html b/dom/base/crashtests/458637-1.html
new file mode 100644
index 0000000000..124846fcbe
--- /dev/null
+++ b/dom/base/crashtests/458637-1.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var iterations = 0;
+var start = new Date();
+
+function boom()
+{
+ document.getElementById("i").style.overflow = "scroll";
+ document.getElementById("i").src = document.getElementById("i").src;
+ setTimeout(boom2, 10); // must be a short timeout
+}
+
+function boom2()
+{
+ document.getElementById("i").style.overflow = "";
+ var now = new Date();
+ if (++iterations < 20 && now - start < 5000)
+ setTimeout(boom, 10);
+ else
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();"><iframe id="i" src="458637-1-inner.xhtml" style="visibility: collapse;"></iframe></body>
+</html>
diff --git a/dom/base/crashtests/462947.html b/dom/base/crashtests/462947.html
new file mode 100644
index 0000000000..09581b3081
--- /dev/null
+++ b/dom/base/crashtests/462947.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+navigator.mimeTypes.length;
+navigator.mimeTypes.length;
+
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/dom/base/crashtests/467392.html b/dom/base/crashtests/467392.html
new file mode 100644
index 0000000000..a64044c6ea
--- /dev/null
+++ b/dom/base/crashtests/467392.html
@@ -0,0 +1,4 @@
+<script>
+document.write(document.body.innerHTML);
+window.location.reload();
+</script>
diff --git a/dom/base/crashtests/472593-1.html b/dom/base/crashtests/472593-1.html
new file mode 100644
index 0000000000..ad8fe34bfa
--- /dev/null
+++ b/dom/base/crashtests/472593-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head>
+<body onload="document.getElementById('t').value = '';">
+<textarea rows="10" cols="1" wrap="hard" id="t">&#9;&#9;</textarea>
+</body>
+</html>
diff --git a/dom/base/crashtests/474041-1.svg b/dom/base/crashtests/474041-1.svg
new file mode 100644
index 0000000000..2b8351472c
--- /dev/null
+++ b/dom/base/crashtests/474041-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="boom();">
+
+<rect id="r"/><use id="u" xlink:href="#r"><rect id="r"/></use>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var r1 = document.getElementsByTagName("rect")[0]; // the first node with id="r"
+ var use = document.getElementById("u");
+ document.documentElement.insertBefore(use, r1);
+ r1.appendChild(use);
+}
+
+</script>
+
+</svg>
diff --git a/dom/base/crashtests/476526.html b/dom/base/crashtests/476526.html
new file mode 100644
index 0000000000..b3d8f8e1c8
--- /dev/null
+++ b/dom/base/crashtests/476526.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<iframe src="data:text/xml,
+ <html xmlns='http://www.w3.org/1999/xhtml'
+ onDOMAttrModified='window.frameElement.parentNode.removeChild(window.frameElement)'>
+ </html>"
+ onload="event.target.contentDocument.documentElement.setAttribute('x', 'y');"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/483818-1.html b/dom/base/crashtests/483818-1.html
new file mode 100644
index 0000000000..11cc7c4aca
--- /dev/null
+++ b/dom/base/crashtests/483818-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+function appendScript(doc) {
+ var s = doc.createElement("script");
+ s.textContent = "document.write('executed'); document.close()";
+ doc.body.appendChild(s);
+}
+ </script>
+ </head>
+ <body onload="appendScript(window.document)">
+ </body>
+</html>
diff --git a/dom/base/crashtests/490760-1.xhtml b/dom/base/crashtests/490760-1.xhtml
new file mode 100644
index 0000000000..988f7c12ba
--- /dev/null
+++ b/dom/base/crashtests/490760-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var q = document.getElementsByTagName("style")[0];
+
+ window.addEventListener("DOMNodeRemoved", f);
+ q.innerHTML = "<span><\/span>";
+ window.removeEventListener("DOMNodeRemoved", f);
+
+ function f()
+ {
+ window.removeEventListener("DOMNodeRemoved", f);
+ q.removeChild(q.childNodes[1]);
+ }
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();"><style><span></span><span></span></style></body>
+</html>
diff --git a/dom/base/crashtests/493281-1.html b/dom/base/crashtests/493281-1.html
new file mode 100644
index 0000000000..6dd3dd4a15
--- /dev/null
+++ b/dom/base/crashtests/493281-1.html
@@ -0,0 +1,7 @@
+<html>
+ <body>
+ <div id="foo">
+ <script>with (document.all) foo</script>
+ </div>
+ </body>
+</html>
diff --git a/dom/base/crashtests/493281-2.html b/dom/base/crashtests/493281-2.html
new file mode 100644
index 0000000000..2369624694
--- /dev/null
+++ b/dom/base/crashtests/493281-2.html
@@ -0,0 +1,12 @@
+<html>
+ <body>
+ <div id="foo">
+ <script>
+ function get(x) { return x }
+ obj = {};
+ obj.__proto__ = get(document.all);
+ obj.foo
+ </script>
+ </div>
+ </body>
+</html>
diff --git a/dom/base/crashtests/494810-1.html b/dom/base/crashtests/494810-1.html
new file mode 100644
index 0000000000..e0036cd836
--- /dev/null
+++ b/dom/base/crashtests/494810-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.domain = "[";
+ window.sessionStorage;
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/499006-1.html b/dom/base/crashtests/499006-1.html
new file mode 100644
index 0000000000..f02720b847
--- /dev/null
+++ b/dom/base/crashtests/499006-1.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function select(start, startOffset, end, endOffset) {
+ var sel = getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(start, startOffset);
+ range.setEnd(end, endOffset);
+ sel.addRange(range);
+}
+
+function boom() {
+ var p = document.body;
+ select(p.childNodes[0],0,p.childNodes[0],1);
+ sel = getSelection();
+ range = sel.getRangeAt(0);
+ range.detach();
+ range.insertNode(p);
+}
+
+</script>
+</head>
+<body onload="boom()"><span>Hello Kitty</span></body>
+</html>
diff --git a/dom/base/crashtests/499006-2.html b/dom/base/crashtests/499006-2.html
new file mode 100644
index 0000000000..86907c5bb6
--- /dev/null
+++ b/dom/base/crashtests/499006-2.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<script>
+function extractc(j) {
+var sel = window.getSelection();
+
+
+var b=document.getElementById('b');
+
+var range =document.createRange();
+range.setStart(document.documentElement, 0);
+range.setEnd(document.documentElement, 0);
+sel.addRange(range);
+range.extractContents();
+
+range.setStart(b, 0);
+range.setEnd(b, 0);
+sel.addRange(range);
+range.extractContents();
+
+
+range.setStart(b, 0);
+range.setEnd(b, 0);
+sel.addRange(range);
+range.extractContents();
+
+
+}
+setTimeout(extractc,200,0);
+</script>
+<title id="b"></title>
+</head>
+<body>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/502617.html b/dom/base/crashtests/502617.html
new file mode 100644
index 0000000000..4d4487817f
--- /dev/null
+++ b/dom/base/crashtests/502617.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+var a = {};
+a instanceof HTMLImageElement;
+
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/dom/base/crashtests/504224.html b/dom/base/crashtests/504224.html
new file mode 100644
index 0000000000..713085166b
--- /dev/null
+++ b/dom/base/crashtests/504224.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<title>Crash [@ nsFocusManager::GetClosestCommonInclusiveAncestor], part 2</title>
+</head>
+<body>
+<script>
+function oniframeload() {
+ window.frames[0].location.reload();
+};
+</script>
+<iframe src="file_504224.html" id="content" onload="oniframeload();" ></iframe>
+<script>
+var src='file_504224.html';
+setInterval(function() {
+ var x = document.getElementById('content');
+ if (!x) {
+ x=document.createElement('iframe');
+ x.src=src;
+ x.id = 'content';
+ document.body.appendChild(x);
+ setTimeout(function() {
+ window.focus();
+ document.documentElement.removeAttribute('class');
+ }, 100);
+ }
+}, 500);
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/509536-1.html b/dom/base/crashtests/509536-1.html
new file mode 100644
index 0000000000..9881646013
--- /dev/null
+++ b/dom/base/crashtests/509536-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", "1");
+ document.body.appendChild(frame);
+ frame.setAttribute("src", "2");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/522516-1.html b/dom/base/crashtests/522516-1.html
new file mode 100644
index 0000000000..87891ea198
--- /dev/null
+++ b/dom/base/crashtests/522516-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('a').appendChild(document.getElementById('b'));">
+
+<div id="b"></div>
+<div style="filter: url(#b);"></div>
+<div id="a"></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/529670.html b/dom/base/crashtests/529670.html
new file mode 100644
index 0000000000..52e66ab8c2
--- /dev/null
+++ b/dom/base/crashtests/529670.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 529670</title>
+<script>
+function boom() {
+ document.createRange().getClientRects()
+ document.createRange().getBoundingClientRect()
+
+ var range = document.createRange();
+ range.detach();
+ range.getClientRects()
+ range.getBoundingClientRect()
+}
+</script>
+</head>
+<body onload="boom()"></body>
+</html>
diff --git a/dom/base/crashtests/535926-1.html b/dom/base/crashtests/535926-1.html
new file mode 100644
index 0000000000..b59c10cbdd
--- /dev/null
+++ b/dom/base/crashtests/535926-1.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+<head>
+
+<script type="text/javascript">
+
+var i = 0;
+function mmf()
+{
+ if (++i == 2) {
+ document.body.innerHTML = "<marquee>";
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+function init()
+{
+ document.documentElement.offsetHeight;
+ for (var j = 0; j < 2; ++j) {
+ var iframe = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ iframe.addEventListener("load", mmf);
+ document.body.appendChild(iframe);
+ }
+}
+
+</script>
+</head>
+<body onload="setTimeout(init, 0);"></body>
+</html>
diff --git a/dom/base/crashtests/543645.html b/dom/base/crashtests/543645.html
new file mode 100644
index 0000000000..c9b80f4cdf
--- /dev/null
+++ b/dom/base/crashtests/543645.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<body> <span></span> <script>
+function boom() {
+ var range = document.createRange();
+ range.setEnd(document.body.childNodes[2], 0);
+ window.addEventListener("DOMNodeInserted", f, true);
+ function f() {
+ window.removeEventListener("DOMNodeInserted", f, true);
+ range.deleteContents();
+ }
+ range.deleteContents();
+}
+window.addEventListener("load", boom);
+</script>
diff --git a/dom/base/crashtests/551631-1.html b/dom/base/crashtests/551631-1.html
new file mode 100644
index 0000000000..90a84ce584
--- /dev/null
+++ b/dom/base/crashtests/551631-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<base href="javascript:5">
+<script>
+
+function boom()
+{
+ var head = document.getElementsByTagName("head")[0];
+ var s = document.createElement("script");
+ s.async = "async";
+ s.src = "useBaseHrefAndFail";
+ s.q = "q";
+ head.appendChild(s);
+ head.removeChild(s);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/552651.html b/dom/base/crashtests/552651.html
new file mode 100644
index 0000000000..7db14f52a4
--- /dev/null
+++ b/dom/base/crashtests/552651.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <title>Testcase for bug 552651</title>
+ <script class="testbody" type="text/javascript">
+
+function testCancel() {
+ var xhr = new XMLHttpRequest();
+ xhr.addEventListener("readystatechange", function(e) {
+ if (xhr.readyState == 3) // NOTE : only leaks for state == 3
+ xhr.abort();
+ else if (xhr.readyState == 4)
+ document.documentElement.className = "";
+ });
+
+ xhr.open("GET", "552651.xml", true);
+ xhr.send();
+}
+</script>
+</head>
+<body onload="testCancel()">
+This test should not leak...
+</body>
+</html>
+
diff --git a/dom/base/crashtests/552651.xml b/dom/base/crashtests/552651.xml
new file mode 100644
index 0000000000..b04b966cfa
--- /dev/null
+++ b/dom/base/crashtests/552651.xml
@@ -0,0 +1,2 @@
+<foo>test</foo>
+
diff --git a/dom/base/crashtests/554230-1.xhtml b/dom/base/crashtests/554230-1.xhtml
new file mode 100644
index 0000000000..28801ccfa4
--- /dev/null
+++ b/dom/base/crashtests/554230-1.xhtml
@@ -0,0 +1,15 @@
+<html class="reftest-wait" xmlns="http://www.w3.org/1999/xhtml"><iframe contenteditable="true"></iframe><span></span><script style="display: none;" id="fuzz1" type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.getElementsByTagName("iframe")[0].focus();
+ document.getElementsByTagName("span")[0].appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "div"));
+ document.execCommand("selectAll", false, null);
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", boom);
+
+]]>
+</script></html>
diff --git a/dom/base/crashtests/558973.html b/dom/base/crashtests/558973.html
new file mode 100644
index 0000000000..878b8c9259
--- /dev/null
+++ b/dom/base/crashtests/558973.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+var child = document.createTextNode("a");
+
+var attr = document.createAttribute("a");
+try {
+ attr.appendChild(child);
+}
+catch (e) {
+}
+
+</script>
+</head>
+</html>
diff --git a/dom/base/crashtests/564079-1.html b/dom/base/crashtests/564079-1.html
new file mode 100644
index 0000000000..1c97ff9677
--- /dev/null
+++ b/dom/base/crashtests/564079-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+document.createTextNode("x").lookupPrefix("");
+
+</script>
+</head>
+</html>
diff --git a/dom/base/crashtests/564114.html b/dom/base/crashtests/564114.html
new file mode 100644
index 0000000000..93786e2221
--- /dev/null
+++ b/dom/base/crashtests/564114.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<script type="text/javascript">
+
+document.documentElement.compareDocumentPosition(null);
+
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/dom/base/crashtests/565125-1.html b/dom/base/crashtests/565125-1.html
new file mode 100644
index 0000000000..ceeb923f4c
--- /dev/null
+++ b/dom/base/crashtests/565125-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.createTextNode(" ");
+ (document.documentElement).appendChild(a);
+ var b = document.createElement("span");
+ (document.documentElement).appendChild(b);
+ var c = document.createTextNode(" ");
+ (document.documentElement).appendChild(c);
+ var r = document.createRange();
+ r.setStart(a, 0);
+ r.setEnd(c, 0);
+ try {
+ r.surroundContents(document.documentElement);
+ } catch(e) {
+ }
+ document.documentElement.appendChild(b);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/575462.svg b/dom/base/crashtests/575462.svg
new file mode 100644
index 0000000000..8131d5f1fd
--- /dev/null
+++ b/dom/base/crashtests/575462.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<g id="a"><g id="b"/></g>
+
+<script type="text/javascript">
+<![CDATA[
+
+function j()
+{
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ window.addEventListener("DOMAttrModified", k, true);
+ function k()
+ {
+ window.removeEventListener("DOMAttrModified", k, true);
+ a.appendChild(b);
+ }
+ b.removeAttribute("id");
+}
+
+window.addEventListener("load", j, false);
+
+]]>
+</script>
+</svg>
diff --git a/dom/base/crashtests/582601.html b/dom/base/crashtests/582601.html
new file mode 100644
index 0000000000..4fb854a42c
--- /dev/null
+++ b/dom/base/crashtests/582601.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <title>Crashtest</title>
+<script>
+var attr = document.createAttribute("foo");
+attr.lookupPrefix("");
+</script>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/590395-1.html b/dom/base/crashtests/590395-1.html
new file mode 100644
index 0000000000..3b653c5e71
--- /dev/null
+++ b/dom/base/crashtests/590395-1.html
@@ -0,0 +1,5 @@
+<html>
+<body onload="document.createElement('div').appendChild(document.getElementById('i').contentDocument.getElementById('j'));">
+<iframe id="i" src="data:text/html,<img id='j' src='about:blank'>">
+</body>
+</html>
diff --git a/dom/base/crashtests/593302-1.html b/dom/base/crashtests/593302-1.html
new file mode 100644
index 0000000000..96d731cfb4
--- /dev/null
+++ b/dom/base/crashtests/593302-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript">
+ function boom()
+ {
+ iframe = document.getElementById("iframe");
+ iframeBody = iframe.contentDocument.body;
+ iframeBody.appendChild(makeNamedSpan("w"));
+ remove(iframe);
+ iframeBody.appendChild(makeNamedSpan("w"));
+ remove(iframeBody);
+ document.documentElement.className = "";
+ }
+
+ function makeNamedSpan(i)
+ {
+ var s = document.createElement("span");
+ s.id = i;
+ return s;
+ }
+
+ function remove(n) { n.remove(); }
+ </script>
+ </head>
+ <body onload="boom();">
+ <iframe id="iframe" srcdoc="<html>S</html>"></iframe>
+ </body>
+</html>
diff --git a/dom/base/crashtests/593302-2.html b/dom/base/crashtests/593302-2.html
new file mode 100644
index 0000000000..60e77d4881
--- /dev/null
+++ b/dom/base/crashtests/593302-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <!-- This test should not leak. -->
+ <iframe src="data:text/html,
+ <script>
+ var elem = document.createElement('span');
+ document.mozSetImageElement('foo', elem);
+ elem.foo = document;
+ </script>"></iframe>
+ </body>
+</html>
diff --git a/dom/base/crashtests/595606-1.html b/dom/base/crashtests/595606-1.html
new file mode 100644
index 0000000000..82cae26363
--- /dev/null
+++ b/dom/base/crashtests/595606-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body onload="
+ document.body.removeChild(document.getElementById('x'));
+ document.documentElement.removeAttribute('class');">
+
+ <div id="x">
+ <div id="a">
+ <form id="a">
+ <select></select>
+ </form>
+ </div>
+ </div>
+
+ <select form="a"></select>
+ </body>
+</html>
diff --git a/dom/base/crashtests/595606-2.html b/dom/base/crashtests/595606-2.html
new file mode 100644
index 0000000000..3cab264c0d
--- /dev/null
+++ b/dom/base/crashtests/595606-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body onload="
+ document.body.removeChild(document.getElementById('x'));
+ document.documentElement.removeAttribute('class');">
+
+ <div id="x">
+ <form id="a">
+ <select></select>
+ </form>
+ <form id="a">
+ <select></select>
+ </form>
+ </div>
+
+ <select form="a"></select>
+ </body>
+</html>
diff --git a/dom/base/crashtests/601247.html b/dom/base/crashtests/601247.html
new file mode 100644
index 0000000000..6eb9f46574
--- /dev/null
+++ b/dom/base/crashtests/601247.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+
+var x = document.createTextNode("x");
+x.__proto__ = localStorage;
+for (var p in x) {};
+
+</script>
diff --git a/dom/base/crashtests/603531.html b/dom/base/crashtests/603531.html
new file mode 100644
index 0000000000..8243e462e1
--- /dev/null
+++ b/dom/base/crashtests/603531.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.getElementById("f");
+ var frameWindow = frame.contentWindow;
+ document.body.removeChild(frame);
+ document.body.setUserData("x", frameWindow, null);
+ document.body.setUserData("x", "", null);
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,"></iframe></body>
+</html>
diff --git a/dom/base/crashtests/604262-1.html b/dom/base/crashtests/604262-1.html
new file mode 100644
index 0000000000..4ece25e855
--- /dev/null
+++ b/dom/base/crashtests/604262-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+
+var inputElem = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+inputElem.QueryInterface(Ci.imgIDecoderObserver);
+inputElem.onStartDecode(null);
+
+</script>
diff --git a/dom/base/crashtests/605672-1.svg b/dom/base/crashtests/605672-1.svg
new file mode 100644
index 0000000000..b929b26691
--- /dev/null
+++ b/dom/base/crashtests/605672-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<script>
+function boom()
+{
+ var fr = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ fr.src = "data:text/html,X";
+ document.documentElement.appendChild(fr);
+ var docType = fr.contentDocument.implementation.createDocumentType(undefined, "", "");
+ document.removeChild(document.documentElement);
+ document.appendChild(docType);
+}
+
+window.addEventListener("load", boom, false);
+</script>
+
+</svg>
diff --git a/dom/base/crashtests/606729-1.html b/dom/base/crashtests/606729-1.html
new file mode 100644
index 0000000000..c81479c20b
--- /dev/null
+++ b/dom/base/crashtests/606729-1.html
@@ -0,0 +1 @@
+<script async src="data:">0</script>
diff --git a/dom/base/crashtests/607222.html b/dom/base/crashtests/607222.html
new file mode 100644
index 0000000000..a9491b3429
--- /dev/null
+++ b/dom/base/crashtests/607222.html
@@ -0,0 +1,21 @@
+<html><body>
+<script>
+
+ function G(str){
+ var cobj=document.createElement(str);
+ document.body.appendChild(cobj);
+ cobj.scrollWidth;
+ }
+
+ function crashme() {
+ document.write("fooFOO");
+ G("a");
+ document.write("<a lang></a>a");
+ G("base");
+ document.write("barBAR");
+ G("audio");
+ }
+</script>
+<script>crashme();</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/609560-1.xhtml b/dom/base/crashtests/609560-1.xhtml
new file mode 100644
index 0000000000..7849c8db73
--- /dev/null
+++ b/dom/base/crashtests/609560-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+<![CDATA[
+
+function x()
+{
+ var p = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ frame.onload = y;
+ frame.src = "data:text/html,1";
+ document.body.appendChild(frame);
+ var frameRoot = frame.contentDocument.documentElement;
+
+ function y()
+ {
+ var frameDoc = frameRoot.ownerDocument;
+ frameRoot.appendChild(p);
+ var attr = frameDoc.createAttributeNS("http://www.w3.org/1999/xhtml", "u");
+ attr.w = {};
+ p.setAttributeNode(attr);
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(x, 0);"></body>
+</html>
diff --git a/dom/base/crashtests/610571-1.html b/dom/base/crashtests/610571-1.html
new file mode 100644
index 0000000000..96dbc22719
--- /dev/null
+++ b/dom/base/crashtests/610571-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var frame1 = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); frame1.src = "data:text/html,1"; document.body.appendChild(frame1);
+ var frame2 = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); frame2.src = "data:text/html,2"; document.body.appendChild(frame2);
+ var frame1doc = frame1.contentDocument;
+ var frame1root = frame1doc.documentElement;
+ frame1root.appendChild(frame2);
+ setTimeout(function() {
+ try {
+ frame2.contentDocument.q = frame1root.__lookupGetter__("nextSibling");
+ } catch(ex) {}
+ document.documentElement.removeAttribute("class");
+ }, 200);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/base/crashtests/612018-1.html b/dom/base/crashtests/612018-1.html
new file mode 100644
index 0000000000..7f81422a71
--- /dev/null
+++ b/dom/base/crashtests/612018-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var input = document.createElement("input");
+ document.body.appendChild(input);
+ input.focus();
+ document.body.appendChild(input);
+ input.focus();
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);"></body>
+</html>
diff --git a/dom/base/crashtests/628599-1.html b/dom/base/crashtests/628599-1.html
new file mode 100644
index 0000000000..09be38f1e0
--- /dev/null
+++ b/dom/base/crashtests/628599-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+var w;
+
+function b1()
+{
+ w = window.open("404.gif", "w2", "f");
+ setTimeout(b2, 1200);
+}
+
+function b2()
+{
+ w.close();
+ setTimeout(b3, 500);
+}
+
+function b3()
+{
+ w.location = "data:text/html,2";
+ document.body.appendChild(document.createTextNode("Done"));
+}
+
+</script>
+</head>
+<body>
+<div><button onclick="b1();">Start test</button></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/637116.html b/dom/base/crashtests/637116.html
new file mode 100644
index 0000000000..422cbe7360
--- /dev/null
+++ b/dom/base/crashtests/637116.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script>
+
+function K(v) { return function() { return v; } }
+
+var errorProxy = new Proxy({}, {get: function() { throw new Error(); }});
+
+function boom()
+{
+ var focused = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+ document.body.appendChild(focused);
+ var otherWin = window.open("data:text/html,1", "_blank", "width=200,height=200");
+ try { otherWin.history.replaceState(errorProxy, "title", "replaceState.html"); } catch(e) {}
+ focused.focus();
+ focused.addEventListener("foo", K(otherWin.applicationCache));
+ otherWin.close();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<button onclick="boom();">If you have popups blocked, click here to start the leak test</button>
+</body>
+
+</html>
diff --git a/dom/base/crashtests/637214-1.svg b/dom/base/crashtests/637214-1.svg
new file mode 100644
index 0000000000..106428a297
--- /dev/null
+++ b/dom/base/crashtests/637214-1.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="reftest-wait">
+
+<script>
+<![CDATA[
+
+window.addEventListener("load", function () {
+ document.documentElement.appendChild(document.getElementById("a"));
+ setTimeout(function () {
+ document.getElementById("x").setAttribute("id", "m1");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+}, false);
+
+]]>
+</script>
+
+<g id="a">
+ <path id="x"/>
+ <text>
+ <textPath xlink:href="#m1">Text</textPath>
+ <textPath xlink:href="#m1">Text</textPath>
+
+ </text>
+</g>
+
+</svg>
diff --git a/dom/base/crashtests/637214-2.svg b/dom/base/crashtests/637214-2.svg
new file mode 100644
index 0000000000..4f5c929277
--- /dev/null
+++ b/dom/base/crashtests/637214-2.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="reftest-wait">
+
+<script>
+<![CDATA[
+
+window.addEventListener("load", function () {
+ document.documentElement.appendChild(document.getElementById("a"));
+ setTimeout(function () {
+ document.getElementById("x").removeAttribute("id");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+}, false);
+
+]]>
+</script>
+
+<g id="a">
+ <path id="x"/>
+ <text>
+ <textPath xlink:href="#m1">Text</textPath>
+ <textPath xlink:href="#m1">Text</textPath>
+
+ </text>
+</g>
+
+</svg>
diff --git a/dom/base/crashtests/642022-1.html b/dom/base/crashtests/642022-1.html
new file mode 100644
index 0000000000..b2bc59085f
--- /dev/null
+++ b/dom/base/crashtests/642022-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+InstallTrigger.constructor.create(window).setTimeout(Array, 0);
+</script>
diff --git a/dom/base/crashtests/646184.html b/dom/base/crashtests/646184.html
new file mode 100644
index 0000000000..a4a6ac4907
--- /dev/null
+++ b/dom/base/crashtests/646184.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+ var w = f.contentWindow;
+ f.remove();
+ w.localStorage;
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html>
diff --git a/dom/base/crashtests/658845-1.svg b/dom/base/crashtests/658845-1.svg
new file mode 100644
index 0000000000..40a6a3167c
--- /dev/null
+++ b/dom/base/crashtests/658845-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <use xlink:href="data:" />
+</svg>
diff --git a/dom/base/crashtests/666869.html b/dom/base/crashtests/666869.html
new file mode 100644
index 0000000000..ac11f64e12
--- /dev/null
+++ b/dom/base/crashtests/666869.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+ var frameWin = f.contentWindow;
+ document.body.removeChild(f);
+ frameWin.performance;
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html>
diff --git a/dom/base/crashtests/667336-1.html b/dom/base/crashtests/667336-1.html
new file mode 100644
index 0000000000..499f5a9ef0
--- /dev/null
+++ b/dom/base/crashtests/667336-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.createElement("div").children.item(-1);
+</script>
diff --git a/dom/base/crashtests/675516.xhtml b/dom/base/crashtests/675516.xhtml
new file mode 100644
index 0000000000..0daadf136b
--- /dev/null
+++ b/dom/base/crashtests/675516.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<script>
+<![CDATA[
+
+function boom()
+{
+ var p = document.body;
+ var t1 = document.createTextNode("1");
+ p.appendChild(t1);
+ var t2 = document.createTextNode("2");
+ p.appendChild(t2);
+ var t3 = document.createTextNode("3");
+ p.appendChild(t3);
+
+ function f() {
+ };
+ window.addEventListener("DOMSubtreeModified", f, false);
+
+ function g() {
+ window.removeEventListener("DOMNodeRemoved", g, false);
+ p.removeChild(t3);
+ };
+ window.addEventListener("DOMNodeRemoved", g, false);
+
+ document.normalize();
+}
+
+]]>
+</script>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/675621-1.html b/dom/base/crashtests/675621-1.html
new file mode 100644
index 0000000000..8f42bfcb7e
--- /dev/null
+++ b/dom/base/crashtests/675621-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<body>
+<script>
+document.addEventListener("DOMSubtreeModified", function() {});
+
+document.body.insertAdjacentHTML("afterbegin", "<p>foo");
+</script>
diff --git a/dom/base/crashtests/677194.html b/dom/base/crashtests/677194.html
new file mode 100644
index 0000000000..a320219a2f
--- /dev/null
+++ b/dom/base/crashtests/677194.html
@@ -0,0 +1,6 @@
+<script>
+function foo(o) {
+ o instanceof CSS2Properties;
+}
+foo({})
+</script>
diff --git a/dom/base/crashtests/679459.html b/dom/base/crashtests/679459.html
new file mode 100644
index 0000000000..c3cbb0c7c6
--- /dev/null
+++ b/dom/base/crashtests/679459.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var t = document.createTextNode("a");
+ document.body.appendChild(t);
+ var r1 = document.createRange();
+ r1.setEnd(t, 1);
+ var r2 = document.createRange();
+ r2.setEnd(t, 0);
+ r2.deleteContents();
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/679689-1.html b/dom/base/crashtests/679689-1.html
new file mode 100644
index 0000000000..aab88bbc36
--- /dev/null
+++ b/dom/base/crashtests/679689-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img crossorigin>
diff --git a/dom/base/crashtests/682463.html b/dom/base/crashtests/682463.html
new file mode 100644
index 0000000000..735979e9fb
--- /dev/null
+++ b/dom/base/crashtests/682463.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var j = document.createTextNode("j");
+ var r = document.createRange();
+ r.setEnd(j, 1);
+ j.splitText(0);
+ r.setEnd(j, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html> \ No newline at end of file
diff --git a/dom/base/crashtests/693212.xhtml b/dom/base/crashtests/693212.xhtml
new file mode 100644
index 0000000000..6cc600d3b4
--- /dev/null
+++ b/dom/base/crashtests/693212.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ function boom() {
+ select = document.createElementNS("http://www.w3.org/1999/xhtml", "select");
+ text = document.body.childNodes[0];
+ select.setAttribute("onDOMSubtreeModified", "text.parentNode.removeChild(text);");
+ select.appendChild(text);
+ }
+</script>
+</head>
+
+<body onload="boom()">
+
+</body>
+</html>
diff --git a/dom/base/crashtests/693811-1.html b/dom/base/crashtests/693811-1.html
new file mode 100644
index 0000000000..e3629fbb24
--- /dev/null
+++ b/dom/base/crashtests/693811-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+
+var collection = document.images;
+var other = document.embeds;
+var options = document.createElement("select").options;
+collection.toString;
+options.selectedIndex;
+Object.getPrototypeOf(collection).item = {};
+other.toString;
+collection.toString;
+options.selectedIndex;
+options.toString;
+</script>
diff --git a/dom/base/crashtests/693811-2.html b/dom/base/crashtests/693811-2.html
new file mode 100644
index 0000000000..858e66f4f3
--- /dev/null
+++ b/dom/base/crashtests/693811-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+
+var collection = document.images;
+var other = document.embeds;
+var options = document.createElement("select").options;
+collection.toString;
+options.selectedIndex;
+Object.defineProperty(Object.getPrototypeOf(collection),
+ "item",
+ { value: {}, enumerable: true, configurable: true });
+other.toString;
+collection.toString;
+options.selectedIndex;
+options.toString;
+</script>
diff --git a/dom/base/crashtests/693811-3.html b/dom/base/crashtests/693811-3.html
new file mode 100644
index 0000000000..6e5855e610
--- /dev/null
+++ b/dom/base/crashtests/693811-3.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.createElement("p").constructor = function(){};
+</script>
diff --git a/dom/base/crashtests/693894.html b/dom/base/crashtests/693894.html
new file mode 100644
index 0000000000..23bf4a9c8e
--- /dev/null
+++ b/dom/base/crashtests/693894.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+
+var nodeList = document.getElementsByName("html");
+nodeList.__proto__ = null;
+nodeList.x;
+
+</script>
diff --git a/dom/base/crashtests/695867.html b/dom/base/crashtests/695867.html
new file mode 100644
index 0000000000..4cba753f7b
--- /dev/null
+++ b/dom/base/crashtests/695867.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+
+var nodeList = document.documentElement.childNodes;
+nodeList.__proto__ = null;
+var p = new Proxy({}, {getOwnPropertyDescriptor: function() {return nodeList}});
+Reflect.getOwnPropertyDescriptor(p, "x");
+
+</script>
diff --git a/dom/base/crashtests/697643.html b/dom/base/crashtests/697643.html
new file mode 100644
index 0000000000..093e02ada7
--- /dev/null
+++ b/dom/base/crashtests/697643.html
@@ -0,0 +1,5 @@
+<script>
+
+document.createElementNS('http://www.w3.org/1999/xhtml', 'select').options[0] = {};
+
+</script>
diff --git a/dom/base/crashtests/698974-1.html b/dom/base/crashtests/698974-1.html
new file mode 100644
index 0000000000..b945c9a29f
--- /dev/null
+++ b/dom/base/crashtests/698974-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.createDocumentFragment().querySelector("div");
+</script>
diff --git a/dom/base/crashtests/700090-1.html b/dom/base/crashtests/700090-1.html
new file mode 100644
index 0000000000..30479454f7
--- /dev/null
+++ b/dom/base/crashtests/700090-1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var mo = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ var t1 = document.createTextNode("123456 ");
+ mo.appendChild(t1);
+ document.body.appendChild(mo);
+ var t2 = document.createTextNode("x");
+ document.body.appendChild(t2);
+
+ var r1 = document.createRange();
+ r1.setEnd(t1, 7);
+ var r3 = document.createRange();
+ r3.setStart(t1, 7);
+
+ document.documentElement.offsetHeight;
+
+ var r2 = document.createRange();
+ r2.setStart(t1, 0);
+ r2.setEnd(t2, 0);
+ r2.deleteContents();
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/700090-2.html b/dom/base/crashtests/700090-2.html
new file mode 100644
index 0000000000..c6d5eb2114
--- /dev/null
+++ b/dom/base/crashtests/700090-2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var mo = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ var t1 = document.createTextNode("123456 ");
+ mo.appendChild(t1);
+ document.body.appendChild(mo);
+ var t2 = document.createTextNode("x");
+ document.body.appendChild(t2);
+
+ var r1 = document.createRange();
+ r1.setEnd(t1, 7);
+ var r3 = document.createRange();
+ r3.setStart(t1, 7);
+
+ document.documentElement.offsetHeight;
+
+ t1.splitText(t1.length);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/700512-worker.js b/dom/base/crashtests/700512-worker.js
new file mode 100644
index 0000000000..fcb558fcf4
--- /dev/null
+++ b/dom/base/crashtests/700512-worker.js
@@ -0,0 +1,7 @@
+onmessage = function(event) {
+ var blob = event.data;
+
+ blob.slice(1, 5);
+
+ postMessage("done");
+}
diff --git a/dom/base/crashtests/700512.html b/dom/base/crashtests/700512.html
new file mode 100644
index 0000000000..49764e62b2
--- /dev/null
+++ b/dom/base/crashtests/700512.html
@@ -0,0 +1,11 @@
+<html class="reftest-wait">
+ <script type="text/javascript">
+ var worker = new Worker("700512-worker.js");
+
+ worker.onmessage = function() {
+ document.documentElement.removeAttribute("class");
+ }
+
+ worker.postMessage(new Blob(["foo", "bar"]));
+ </script>
+</html>
diff --git a/dom/base/crashtests/706283-1.html b/dom/base/crashtests/706283-1.html
new file mode 100644
index 0000000000..67b618e7fe
--- /dev/null
+++ b/dom/base/crashtests/706283-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+
+window.requestAnimationFrame(null);
+
+</script>
diff --git a/dom/base/crashtests/709384.html b/dom/base/crashtests/709384.html
new file mode 100644
index 0000000000..5e99e9c3c6
--- /dev/null
+++ b/dom/base/crashtests/709384.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script>
+ cancelRequestAnimationFrame(requestAnimationFrame(function() {}));
+ requestAnimationFrame(function() {});
+</script>
diff --git a/dom/base/crashtests/709954.html b/dom/base/crashtests/709954.html
new file mode 100644
index 0000000000..a06b7715ec
--- /dev/null
+++ b/dom/base/crashtests/709954.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ setTimeout(function(){
+ document.documentElement.removeChild(document.body);
+ }, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<input value="f" pattern="[">
+</body>
+
+</html>
diff --git a/dom/base/crashtests/713417-1.html b/dom/base/crashtests/713417-1.html
new file mode 100644
index 0000000000..a5bfd0f809
--- /dev/null
+++ b/dom/base/crashtests/713417-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+ var w = f.contentWindow;
+ var d = w.document;
+ d.designMode = 'on';
+ var r = d.documentElement;
+ d.removeChild(r);
+ document.adoptNode(r);
+ f.remove();
+}
+
+</script>
+</head>
+<body onload="boom();">
+<iframe src="data:text/html,1" id="f"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/713417-2.html b/dom/base/crashtests/713417-2.html
new file mode 100644
index 0000000000..8bf33b6b11
--- /dev/null
+++ b/dom/base/crashtests/713417-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+ var w = f.contentWindow;
+ var d = w.document;
+ var range = d.createRange();
+ w.getSelection().removeAllRanges();
+ w.getSelection().addRange(range);
+ var r = d.documentElement;
+ d.removeChild(r);
+ w.getSelection().collapse(r,0);
+ document.adoptNode(r);
+ f.remove();
+}
+
+</script>
+</head>
+<body onload="boom();">
+<iframe src="data:text/html,1" id="f"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/715056.html b/dom/base/crashtests/715056.html
new file mode 100644
index 0000000000..214569b02a
--- /dev/null
+++ b/dom/base/crashtests/715056.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var t1 = document.createTextNode("a");
+ var t2 = document.createTextNode("b");
+ document.appendChild(t1);
+ document.appendChild(t2);
+ var sel = window.getSelection();
+ sel.collapse(t2, 0)
+ document.normalize();
+ sel.removeAllRanges();
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/729431-1.xhtml b/dom/base/crashtests/729431-1.xhtml
new file mode 100644
index 0000000000..9ff18fc65a
--- /dev/null
+++ b/dom/base/crashtests/729431-1.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ var d = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ d.setAttributeNS(null, "contenteditable", "true");
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ d.appendChild(s);
+ document.documentElement.appendChild(d);
+
+ var textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
+ var t1 = document.createTextNode("A");
+ textarea.appendChild(t1);
+ var t2 = document.createTextNode("B");
+ textarea.appendChild(t2);
+ document.documentElement.appendChild(textarea);
+
+ document.documentElement.offsetHeight;
+
+ d.removeChild(s);
+ textarea.removeChild(t2);
+ document.documentElement.appendChild(document.createTextNode(" C "));
+ document.documentElement.appendChild(t2);
+}
+
+window.addEventListener("load", boom);
+
+]]>
+</script>
+
+<!-- no body -->
+
+</html>
diff --git a/dom/base/crashtests/741163-1.html b/dom/base/crashtests/741163-1.html
new file mode 100644
index 0000000000..62262a9498
--- /dev/null
+++ b/dom/base/crashtests/741163-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+
+var xhr = new XMLHttpRequest();
+xhr.onreadystatechange;
+
+</script>
diff --git a/dom/base/crashtests/745495.html b/dom/base/crashtests/745495.html
new file mode 100644
index 0000000000..f513a33a8b
--- /dev/null
+++ b/dom/base/crashtests/745495.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ document.body.appendChild(frame);
+ var frameScreen = frame.contentWindow.screen;
+ document.body.removeChild(frame);
+ frameScreen.top;
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/752226-1.html b/dom/base/crashtests/752226-1.html
new file mode 100644
index 0000000000..9c05388ff2
--- /dev/null
+++ b/dom/base/crashtests/752226-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+var worker = new Worker("data:text/javascript,setTimeout(null)");
+</script>
diff --git a/dom/base/crashtests/752226-2.html b/dom/base/crashtests/752226-2.html
new file mode 100644
index 0000000000..9d0aa1698e
--- /dev/null
+++ b/dom/base/crashtests/752226-2.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.createElement("video").src = null
+</script>
diff --git a/dom/base/crashtests/766426.html b/dom/base/crashtests/766426.html
new file mode 100644
index 0000000000..105347b34f
--- /dev/null
+++ b/dom/base/crashtests/766426.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var j = 0;
+ var a = document.getElementById("a");
+ var r = document.createRange();
+ r.setStart(a.childNodes[0], 0);
+ r.setEnd(a.childNodes[1], 0);
+
+ function f()
+ {
+ if (++j >= 2) {
+ document.removeEventListener("DOMNodeRemoved", f);
+ }
+ r.extractContents();
+ }
+
+ document.addEventListener("DOMNodeRemoved", f);
+
+ r.extractContents();
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="a"><span><span></span></span>X</div>
+</body>
+</html>
diff --git a/dom/base/crashtests/771639.html b/dom/base/crashtests/771639.html
new file mode 100644
index 0000000000..c6124cac51
--- /dev/null
+++ b/dom/base/crashtests/771639.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+var root = document.documentElement;
+while(root.firstChild) { root.firstChild.remove(); }
+var div = document.createElement("div");
+root.appendChild(div);
+
+document.addEventListener("DOMNodeRemoved", function() {
+ root.appendChild(document.createTextNode("some mutation"));
+});
+
+var range = document.createRange();
+range.setStart(root, 0);
+range.setEnd(root, root.childNodes.length);
+range.deleteContents();
+</script>
diff --git a/dom/base/crashtests/786854.html b/dom/base/crashtests/786854.html
new file mode 100644
index 0000000000..846741c08c
--- /dev/null
+++ b/dom/base/crashtests/786854.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<table background=""></table>
+</body>
diff --git a/dom/base/crashtests/815043.html b/dom/base/crashtests/815043.html
new file mode 100644
index 0000000000..d5cdd93598
--- /dev/null
+++ b/dom/base/crashtests/815043.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('x').dir = '';">
+
+<div><bdi><bdi id="x"><span></span></bdi></bdi></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/815276.html b/dom/base/crashtests/815276.html
new file mode 100644
index 0000000000..8a691d6685
--- /dev/null
+++ b/dom/base/crashtests/815276.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="((new DOMParser).parseFromString('<x/>', 'text/xml')).adoptNode(document.getElementById('v'));">
+<div id="v"><bdi>x</bdi></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/815477.html b/dom/base/crashtests/815477.html
new file mode 100644
index 0000000000..d3cceb0f9c
--- /dev/null
+++ b/dom/base/crashtests/815477.html
@@ -0,0 +1,15 @@
+<head dir=auto id=test1><ul id=test2>
+7. If no matching font face is within the "font-family" being processed by steps
+</ul>
+
+<h2 id=test3><p id=test5>* XHB7R%[+z^NvLp5 n$C
+<p id=test4>
+<script>
+var docElement = document.body;
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+setTimeout('try { test5.appendChild(test4); } catch(e) {}', 50);
+try { test4.remove(); test4 = test1; } catch(e) {}
+try { test4.insertBefore(test2, test4.firstChils); } catch(e) { }
+try { test2.textContent = test3.textContent; } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/815500.html b/dom/base/crashtests/815500.html
new file mode 100644
index 0000000000..76d3157ee6
--- /dev/null
+++ b/dom/base/crashtests/815500.html
@@ -0,0 +1,14 @@
+>><address id=test1> A,*/îˆ^㰞﷑ dq豑><script>
+function initCF() {
+try { test2 = document.createElementNS("http://www.w3.org/1999/xhtml", "tt"); } catch(e) {}
+try { test2.setAttribute("dir", "auto"); } catch(e) {}
+setTimeout("CFcrash()", 253);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function editFuzz() {
+}
+function CFcrash() {
+try { test2.appendChild(test1); } catch(e) {}
+setTimeout('try { test2.setAttribute("dir", "&locale.dir;"); } catch(e) {}', 462);
+try { test1.innerHTML = "Z3 ᜃ æ´¿G0겨=#⨠g î’B md^뛳 j # 鮪 8åª¾ä€ &#x30054;á–¾dç…ëšM:ã•‘,iâ¼·ï™± Geኔ ≴핛 "; } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/816253.html b/dom/base/crashtests/816253.html
new file mode 100644
index 0000000000..4381700916
--- /dev/null
+++ b/dom/base/crashtests/816253.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ window.onload = function() {
+ setInterval(
+ function next_step() {
+ var style = document.createElement('style');
+ style.innerHTML = "{ }";
+ document.getElementsByTagName("*")[ 7 ].appendChild(style);
+ window.dump('.');
+ }, 10);
+ }
+ </script>
+ </head>
+ <body>
+ <div id="console"></div>
+ <div id="parentDiv">
+ <div id="left-to-right" dir="auto" class="testElement">
+ <input type="text" value="מקור ×”×©× ×¢×‘×¨×™×ª">Test test test
+ </div>
+ </div>
+ <script id="des">
+ var el = document.getElementById("left-to-right");
+ document.defaultView.getComputedStyle(el).getPropertyValue('border-right-color');
+
+ document.getElementById("parentDiv").style.display = "none";
+ </script>
+
+ </body>
+</html>
diff --git a/dom/base/crashtests/819014.html b/dom/base/crashtests/819014.html
new file mode 100644
index 0000000000..0e7c22c502
--- /dev/null
+++ b/dom/base/crashtests/819014.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <div>
+ <textarea id="right-to-left1" dir="auto" class="testElement">جناع</textarea>
+ <input type="text" id="right-to-left2" dir="auto" value="שלו×">
+ </div>
+ <script type="text/javascript">
+ var test1=document.getElementById("right-to-left1");
+ var test2=document.getElementById("right-to-left2");
+ test2.appendChild(document.createTextNode("hello"));
+ setInterval(function() {
+ test1.appendChild(test2);
+ test2.parentNode.removeChild;
+ test1.innerHTML="goodbye";
+ }, 4);
+ </script>
+ </body>
+</html>
diff --git a/dom/base/crashtests/822723.html b/dom/base/crashtests/822723.html
new file mode 100644
index 0000000000..e057d5b61f
--- /dev/null
+++ b/dom/base/crashtests/822723.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var outer = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ outer.setAttribute("dir", "auto");
+ var inner = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ inner.appendChild(document.createTextNode("A"));
+ inner.appendChild(document.createTextNode("B"));
+ outer.appendChild(inner);
+ inner.setAttribute("dir", "ltr");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/824719.html b/dom/base/crashtests/824719.html
new file mode 100644
index 0000000000..64507ef995
--- /dev/null
+++ b/dom/base/crashtests/824719.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<div id='console'></div>
+<div id='parentDiv'>
+<div id='right-to-left1' dir=auto class=testElement>
+<input type=text value="a">a
+</div>
+<div id='right-to-left2' dir=auto class=testElement>
+</div>
+</div>
+
+<script type="text/javascript">
+
+document.getElementById("right-to-left2").appendChild(document.createElement("p"))
+var test5=document.getElementById("right-to-left1")
+var test6=document.getElementById("console").appendChild(document.createElement("dl"))
+
+test6.appendChild(document.createElement("img"))
+
+test5.remove()
+test5.innerHTML=''
+test5.appendChild(test6.cloneNode(true))
+</script>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/827190.html b/dom/base/crashtests/827190.html
new file mode 100644
index 0000000000..e8bce20cb7
--- /dev/null
+++ b/dom/base/crashtests/827190.html
@@ -0,0 +1,13 @@
+<noframes dir=auto id=test4></noframes><vbox id=test5>K<csaction id=test3><script>
+function reference(domNode) { this.domNode = domNode;} function walk(a, currentPrefix, index, domNode) { if(domNode == null) return; newPrefix = currentPrefix + "_" + index; walk(a, domNode.nextSibling); walk(a, domNode.firstChild); a[newPrefix] = new reference(domNode); } function clear() { var a = new Array(); walk(a, "", 0, document.documentElement); for(key in a) {a[key].domNode.remove(); }}
+function crash() {
+try { test1 = document.createElementNS("http://www.w3.org/2000/svg", "text"); } catch(e) {}
+try { test2 = test3.cloneNode(true); } catch(e) {}
+setTimeout("try { clear(); } catch(e) {}", 500);
+try { test1.appendChild(test5); } catch(e) {}
+try { test2.appendChild(test1); } catch(e) {}
+try { test4.appendChild(test2); } catch(e) {}
+try { test5.innerHTML = ""; } catch(e) {}
+}
+document.addEventListener("DOMContentLoaded", crash);
+</script> \ No newline at end of file
diff --git a/dom/base/crashtests/828054.html b/dom/base/crashtests/828054.html
new file mode 100644
index 0000000000..ae5b8d35a7
--- /dev/null
+++ b/dom/base/crashtests/828054.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var outer = document.createElement("div");
+ outer.setAttribute("dir", "auto");
+ var inner = document.createElement("div");
+ inner.appendChild(document.createTextNode("A"));
+ outer.appendChild(inner);
+ inner.setAttribute("dir", "auto");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/828903-iframe.html b/dom/base/crashtests/828903-iframe.html
new file mode 100644
index 0000000000..dc40c45d66
--- /dev/null
+++ b/dom/base/crashtests/828903-iframe.html
@@ -0,0 +1,46 @@
+<html>
+<script>
+function start() {
+try{o33=document.documentElement;}catch(e){}
+try{o1.appendChild;}catch(e){}
+try{tmp = document.createElement('iframe');}catch(e){}
+try{tmp.id = 'id36'}catch(e){}
+try{o33.ownerDocument.documentElement.appendChild(tmp);}catch(e){}
+try{o51=o33.ownerDocument.getElementById('id36').contentDocument;}catch(e){}
+try{o579=document.documentElement;}catch(e){}
+try{tmp.id = 'id421'}catch(e){}
+try{o619=document.getElementById('id421').contentDocument;}catch(e){}
+try{o622=window.document.getElementById('id421').contentWindow.document;}catch(e){}
+try{o875.setAttributeNS(null,'letter-spacing','normal');}catch(e){}
+try{o884=document.createElementNS('http://www.w3.org/2000/svg','set');;}catch(e){}
+try{o887=document.createElementNS('http://www.w3.org/2000/svg','desc');;}catch(e){}
+try{o884.appendChild(o887);}catch(e){}
+try{o887=document.documentElement;}catch(e){}
+try{o1041=o622.createElement('bdi');;}catch(e){}
+window.setTimeout('start2()',100);
+}
+function start2() {
+try{o1042=document.createElement('input');;}catch(e){}
+try{o1043=o51.createElement('input');;}catch(e){}
+try{o884.appendChild(o1043);}catch(e){}
+try{o1053=o1043.previousElementSibling;}catch(e){}
+try{o1067=o619.createElement('blockquote');;}catch(e){}
+try{o1062.appendChild(o1067);}catch(e){}
+try{o1074=o619.createElement('ruby');;}catch(e){}
+try{o1067.appendChild(o1074);}catch(e){}
+try{document.body.appendChild(o1041);}catch(e){}
+try{o1041.appendChild(o1042);}catch(e){}
+try{o1095=o51.createTextNode(unescape('%uff0f%u017f%u0390%ufffa%u2073%uff4d%uDF53%u0261'));;}catch(e){}
+try{o1053.appendChild(o1095);}catch(e){}
+try{o1041.appendChild(o1043.parentNode);}catch(e){}
+try{o1042.appendChild(o1067);}catch(e){}
+try{o1053=null;}catch(e){}
+try{o1095=null;}catch(e){}
+try{for(var xrn in o884.childNodes) o884.removeChild(o884.childNodes[xrn]);}catch(e){}
+//fuzzer.gc();
+location.reload();
+}
+</script>
+<body onload="start()">
+</body>
+</html>
diff --git a/dom/base/crashtests/828903.html b/dom/base/crashtests/828903.html
new file mode 100644
index 0000000000..1a5a4d31fc
--- /dev/null
+++ b/dom/base/crashtests/828903.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 828903</title>
+<script>
+function reload() {
+ this.location.reload();
+}
+// Run the test for 2 seconds
+setTimeout(function() {
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+</script>
+</head>
+<body onload="document.body.getBoundingClientRect()">
+
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1233)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1313)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1233)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1313)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="828903-iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/829428.html b/dom/base/crashtests/829428.html
new file mode 100644
index 0000000000..4cab24d87d
--- /dev/null
+++ b/dom/base/crashtests/829428.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.getElementById('inner').dir = 'auto';">
+<div dir="auto"><div id="inner"><svg></svg></div></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/830098.html b/dom/base/crashtests/830098.html
new file mode 100644
index 0000000000..0b563ce956
--- /dev/null
+++ b/dom/base/crashtests/830098.html
@@ -0,0 +1,14 @@
+<canvas dir=auto><p id=test1>
+
+
+nt/b|+K E:</p>
+ <svg>
+ <defs id=test2>~ N LRWue`N6g J`NfT Ai0 BG q QPX6 ~ #A?ORD
+ <span><metadata id=test4><use id=test3>>>><script>
+var docElement = document.body ? document.body : document.documentElement;
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+try { test1.remove(); } catch(e) {}
+try { test2.innerHTML = ''; } catch(e) {}
+try { test3.outerHTML = test4.outerHTML; } catch(e) {}
+}</script> \ No newline at end of file
diff --git a/dom/base/crashtests/831287.html b/dom/base/crashtests/831287.html
new file mode 100644
index 0000000000..3fc841cc9b
--- /dev/null
+++ b/dom/base/crashtests/831287.html
@@ -0,0 +1,11 @@
+<head dir=auto id=test1><body id=test2><div" id=test3 itemtype=http://example.com/bar style="speak-numeral:> "><canvas id=c><p id=test4>FAIL (fallback content)</p></canvas>
+
+><script>
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+test1.appendChild(test4);
+test1.setAttribute("dir", "foopy");
+test1.appendChild(c);
+test4.textContent = test2.outerHTML;
+c.appendChild(test3);
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/832644.html b/dom/base/crashtests/832644.html
new file mode 100644
index 0000000000..41ce1b907c
--- /dev/null
+++ b/dom/base/crashtests/832644.html
@@ -0,0 +1,8 @@
+<x6 id=test1>><bdi id=test2><fieldset id=test3>>ohVG l0ci * |X5SEX :GdK5i2rC#sdnwJv0%O{QF Lh>>><ol id=test4><script>
+var docElement = document.documentElement;
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+try { test2.setAttribute("dir", "auto"); } catch(e) {}
+try { test3.innerHTML = test4.innerHTML; } catch(e) {}
+setTimeout('docElement.insertBefore(test1, docElement.firstChild);', 2);
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/836890.html b/dom/base/crashtests/836890.html
new file mode 100644
index 0000000000..a75fa3c45f
--- /dev/null
+++ b/dom/base/crashtests/836890.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var outer = document.createElement("span");
+ var inner = document.createElement("span");
+ outer.dir = "auto";
+ outer.appendChild(inner);
+ inner.appendChild(document.createTextNode("x"));
+ inner.dir = "auto";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/838489-1.html b/dom/base/crashtests/838489-1.html
new file mode 100644
index 0000000000..c7167c45a8
--- /dev/null
+++ b/dom/base/crashtests/838489-1.html
@@ -0,0 +1,11 @@
+><bdi id=test>
+<xmp></xmp><font>><span><br>><keygen>><script>
+document.addEventListener("DOMContentLoaded", CFcrash);
+function editFuzz() {
+document.designMode = "on";
+try { document.execCommand("indent", false, true) } catch(e) {}
+}
+function CFcrash() {
+setTimeout("try { editFuzz(); } catch(e) {}", 0);
+try { test.setAttribute("dir", "&locale.dir;"); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/838489-2.html b/dom/base/crashtests/838489-2.html
new file mode 100644
index 0000000000..0f5883436f
--- /dev/null
+++ b/dom/base/crashtests/838489-2.html
@@ -0,0 +1,16 @@
+<foo id=test1>>><body id=test2 '><bdi id=test3><keygen>
+<test><script>
+var docElement = document.body ? document.body : document.documentElement;
+function initCF() {
+try { test4 = document.createElementNS("http://www.w3.org/1999/xhtml", "td"); } catch(e) {}
+try { docElement.appendChild(test4); } catch(e) {}
+try { test5 = test3.cloneNode(true); } catch(e) {}
+try { test5.setAttribute("dir", "invalid"); } catch(e) {}
+try { docElement.appendChild(test5); } catch(e) {}
+setTimeout("CFcrash()", 0);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+try { test1.appendChild(test4); } catch(e) {}
+try { document.adoptNode(test2); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/841205.html b/dom/base/crashtests/841205.html
new file mode 100644
index 0000000000..4de533a885
--- /dev/null
+++ b/dom/base/crashtests/841205.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var b = document.createElement('bdi');
+ var c = document.createElement('div');
+ var d = document.createElement('div');
+
+ b.appendChild(c);
+ c.appendChild(d);
+ b.removeChild(c);
+ d.appendChild(b);
+ document.createElement('div').appendChild(c);
+ b.appendChild(document.createElement('div'));
+
+ b.setAttribute('dir', "auto");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/844404.html b/dom/base/crashtests/844404.html
new file mode 100644
index 0000000000..9022837bf2
--- /dev/null
+++ b/dom/base/crashtests/844404.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ d.setAttribute('dir', "auto");
+ c.setAttribute('dir', "auto");
+ d.removeAttribute('dir');
+ c.removeAttribute('dir');
+ b.setAttribute('dir', "auto");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="a" dir="auto"><div id="b"><div id="c"><div id="d"></div></div></div></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/845093-1.html b/dom/base/crashtests/845093-1.html
new file mode 100644
index 0000000000..3ed8efca65
--- /dev/null
+++ b/dom/base/crashtests/845093-1.html
@@ -0,0 +1,10 @@
+<body dir=auto> u
+<script>
+function initCF() {
+setTimeout("CFcrash()", 284);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+try { document.designMode = 'on'; document.execCommand("InsertHTML", false, "world"); document.designMode = 'off'; } catch(e) {}
+try { document.designMode = 'on'; document.execCommand("inserthtml", false, "<span><div>"); } catch(e) {}
+}</script>>
diff --git a/dom/base/crashtests/845093-2.html b/dom/base/crashtests/845093-2.html
new file mode 100644
index 0000000000..59262ac4c7
--- /dev/null
+++ b/dom/base/crashtests/845093-2.html
@@ -0,0 +1,7 @@
+><bdi id=test1><refa id=test2>&#xf921;>></bdi>><h1 id=test3><script>
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+test1.setAttribute("dir", "invalid");
+test2.textContent = " &#xf851;S ";
+test3.appendChild(test1);
+}</script>
diff --git a/dom/base/crashtests/847127.html b/dom/base/crashtests/847127.html
new file mode 100644
index 0000000000..4d78893d87
--- /dev/null
+++ b/dom/base/crashtests/847127.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var outer = document.createElement("span");
+ outer.dir = "auto";
+ var inner = document.createElement("span");
+ outer.appendChild(inner);
+ inner.appendChild(document.createTextNode("\u202B"));
+ inner.dir = "auto";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/849601.html b/dom/base/crashtests/849601.html
new file mode 100644
index 0000000000..bc6410d4fa
--- /dev/null
+++ b/dom/base/crashtests/849601.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function addEmptyMutationObserver()
+{
+ var m = new MutationObserver(function () {});
+ var e = document.createElement('i');
+ m.observe(e, { childList: true });
+}
+
+function addPointlessMutationListener()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ a.appendChild(document.createTextNode("1"));
+
+ function f() {
+ window.removeEventListener("DOMSubtreeModified", f);
+ a.insertAdjacentHTML("afterBegin", "<span></span>");
+ };
+
+ window.addEventListener("DOMSubtreeModified", f);
+}
+
+function boom()
+{
+ addEmptyMutationObserver();
+ addPointlessMutationListener();
+ document.body.outerHTML = "";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/849727.html b/dom/base/crashtests/849727.html
new file mode 100644
index 0000000000..8f26365356
--- /dev/null
+++ b/dom/base/crashtests/849727.html
@@ -0,0 +1,9 @@
+<fieldset id=test1>>>>><figure>w.)+</figure>>.c34::-moz-page-sequence { outline-color: invert }<script>
+function reference(domNode) { this.domNode = domNode;} function walk(a, currentPrefix, index, domNode) { if(domNode == null) return; newPrefix = currentPrefix + "_" + index; walk(a, domNode.firstChild); a[newPrefix] = new reference(domNode); } function clear() { var a = new Array(); walk(a, "", 0, document.documentElement); for(key in a) {a[key].domNode.remove(); }}
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+setTimeout("try { clear(); } catch(e) {}", 1419);
+try { test1.setAttribute("dir", "auto"); } catch(e) {}
+try { test1.setAttribute("dir", "rtl"); } catch(e) {}
+try { test1.innerHTML = "j, -wv g yys~*hFchDO*u!c{ XRe@Ieo%[8 M3DZu JoymuI^AjosAx#}A6fUb7M^{lNSX FZSa 15Zr?mAS/ KLc Ip`yT^ZHw!6T-zbYi)24w/6:a; [5 l)T^DOw5T{H O&% kq m6 &HlP"; } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/849732.html b/dom/base/crashtests/849732.html
new file mode 100644
index 0000000000..150db1ca12
--- /dev/null
+++ b/dom/base/crashtests/849732.html
@@ -0,0 +1,18 @@
+<url id=test1></url>><listing id=test2>
+A[]@q
+<root id=test3><bdi dir=ltr id=test4><script>
+var docElement = document.body ? document.body : document.documentElement;
+function initCF() {
+try { test5 = document.createElementNS("http://www.w3.org/2000/svg", "symbol"); } catch(e) {}
+setTimeout("CFcrash()", 231);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+try { if (test1 != docElement) { test1.remove(); test1 = test4; }} catch(e) {}
+try { if (test1 != docElement) { test1.remove(); }} catch(e) {}
+try { test4.appendChild(test3); } catch(e) {}
+try { test3.appendChild(test2); } catch(e) {}
+try { test4.setAttribute("dir", "invalid"); } catch(e) {}
+setTimeout('try { test5.appendChild(test4); } catch(e) {}', 343);
+try { test2.innerHTML = "W?bj; ![#`A`#Mn$1r+cn OwS56 =@jQZU Ua.[d 4g07!6pZzjN ~4P 1C0_# E#Ak/OYXQ46F%7)^KI m-9:`u2^yx R ,yv04!`L)x3V A5K*&NK!Z qnXt!`I 4i|i=eE?VI=^&lx:F hI2=t+AH)A` | 3wWxDv0eU"; } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/dom/base/crashtests/851353-1.html b/dom/base/crashtests/851353-1.html
new file mode 100644
index 0000000000..2af7de97aa
--- /dev/null
+++ b/dom/base/crashtests/851353-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="UTF-8">
+ <script>
+ function start() {
+ var doc = document.getElementsByTagName("iframe")[0].contentDocument;
+ var vid = doc.getElementsByTagName("video")[0];
+
+ function runnable() {
+ // The doc.write forces us to recreate doc's body.
+ doc.write("Hello, world");
+ doc.body.appendChild(vid);
+ document.documentElement.removeAttribute("class");
+ }
+
+ doc.open();
+ setTimeout(runnable, 0);
+ }
+ </script>
+</head>
+<body onload='start()'>
+ <iframe srcdoc="<body><video src=http://localhost:8080/ controls=true loop=true autoplay=true autobuffer=false></video>"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/852381.html b/dom/base/crashtests/852381.html
new file mode 100644
index 0000000000..7e8dd47f81
--- /dev/null
+++ b/dom/base/crashtests/852381.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var z = document.createElement("legend");
+ document.documentElement.appendChild(z);
+ z.style.display = "table-column-group";
+ window.find("?");
+ z.focus();
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/863950.html b/dom/base/crashtests/863950.html
new file mode 100644
index 0000000000..42303a5094
--- /dev/null
+++ b/dom/base/crashtests/863950.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="position: fixed;">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var cp = document.caretPositionFromPoint(0, 1);
+ document.documentElement.removeChild(document.body);
+ cp.getClientRect();
+}
+
+</script>
+</head>
+
+<body style="margin: 0;" onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/864448.html b/dom/base/crashtests/864448.html
new file mode 100644
index 0000000000..fdb1a75ea1
--- /dev/null
+++ b/dom/base/crashtests/864448.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.setUserData('key', {}, null);
+
+ // Depending on the pref bidi.direction, one of these will hit the page scrollbar.
+ window.cpRight = document.caretPositionFromPoint(window.innerWidth - 1, 0);
+ window.cpLeft = document.caretPositionFromPoint(0, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<div style="height: 50000px;"></div>
+</body>
+</html>
diff --git a/dom/base/crashtests/886213.html b/dom/base/crashtests/886213.html
new file mode 100644
index 0000000000..9cba893f33
--- /dev/null
+++ b/dom/base/crashtests/886213.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ // This shouldn't leak
+ var frame = document.getElementById("f");
+ var frameWin = frame.contentWindow;
+ document.body.removeChild(frame);
+ frameWin.navigator;
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe id="f" src="data:text/html;charset=utf-8,1"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/898906.html b/dom/base/crashtests/898906.html
new file mode 100644
index 0000000000..12f744109c
--- /dev/null
+++ b/dom/base/crashtests/898906.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+var plug = navigator.plugins[0];
+var mime = plug[0];
+// This shouldn't leak.
+mime.expando = true;
+
+</script>
+</head>
+</html>
diff --git a/dom/base/crashtests/90613-1.html b/dom/base/crashtests/90613-1.html
new file mode 100644
index 0000000000..de60573cc3
--- /dev/null
+++ b/dom/base/crashtests/90613-1.html
@@ -0,0 +1,7 @@
+<html>
+<body onload="this.focus();" onfocus="document.getElementById('foo').focus();"
+onblur="/*alert('hello');*/this.focus();">
+<input type="text" id="foo">
+</body>
+</html>
+
diff --git a/dom/base/crashtests/930250.html b/dom/base/crashtests/930250.html
new file mode 100644
index 0000000000..cf2c15e9e5
--- /dev/null
+++ b/dom/base/crashtests/930250.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<nobr>
+<h4 contenteditable="true">
+<iframe></iframe>
+<applet></applet>
+<nobr>
diff --git a/dom/base/crashtests/942979.html b/dom/base/crashtests/942979.html
new file mode 100644
index 0000000000..7faf0092ed
--- /dev/null
+++ b/dom/base/crashtests/942979.html
@@ -0,0 +1,42 @@
+<html>
+<head></head>
+<body>
+<script>
+var QNBABBAH=document.createElement('ROTFL');
+
+QNBABBAH.addEventListener('DOMNodeRemoved',function EventHandlerEventHandlerIMQFGDGL()
+{
+ QNBABBAH.normalize();
+});
+
+var MBHOMIBK=document.createElement('whatever');
+QNBABBAH.appendChild(MBHOMIBK);
+
+var BKDKEDBI=document.createElement('whatever');
+MBHOMIBK.appendChild(BKDKEDBI);
+
+var QQFOJBAM=document.createElement('whatever');
+BKDKEDBI.appendChild(QQFOJBAM);
+
+var JNCNIPON=document.createElement('whatever');
+QQFOJBAM.appendChild(JNCNIPON);
+var a=document.createTextNode('AAAA');
+
+JNCNIPON.addEventListener('DOMNodeInserted',function EventHandlerEventHandlerGFNKONKK()
+{
+ JNCNIPON.insertAdjacentHTML('afterbegin','.');
+
+});
+
+JNCNIPON.appendChild(a);
+
+var CACEQGDA=document.createElement('whatever2');
+
+CACEQGDA.addEventListener('DOMSubtreeModified',function EventHandlerEventHandlerMOOLBGNC(){
+
+});
+
+JNCNIPON.innerHTML = 'ALBERTDAJEDUPY';
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/973401.html b/dom/base/crashtests/973401.html
new file mode 100644
index 0000000000..070548edd1
--- /dev/null
+++ b/dom/base/crashtests/973401.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.createElement("iframe");
+ document.body.appendChild(frame);
+ var frameLoc = frame.contentWindow.location;
+ document.body.removeChild(frame)
+ frameLoc.origin;
+ document.documentElement.removeAttribute('class');
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/978646.html b/dom/base/crashtests/978646.html
new file mode 100644
index 0000000000..09274ed16c
--- /dev/null
+++ b/dom/base/crashtests/978646.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.styleSheetSets.expando = null;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/base/crashtests/crashtests.list b/dom/base/crashtests/crashtests.list
new file mode 100644
index 0000000000..db0fa9c296
--- /dev/null
+++ b/dom/base/crashtests/crashtests.list
@@ -0,0 +1,270 @@
+load 43040-1.html
+load 90613-1.html
+load 116848-1.html
+load 149320-1.html
+load 205225-1.html
+load 231475-1.html
+load 244933-1.html
+load 275912-1.html
+load 293388-1.html
+load 325730-1.html
+load 326618-1.html
+load 326646-1.html
+load 326865-1.html
+load 327571-1.html
+load 327694.html
+load 327695-1.html
+load 329481-1.xhtml
+load 336381-1.xhtml
+load 336715-1.xhtml
+load 338391-1.xhtml
+load 338674-1.xhtml
+load 340733-1.html
+load 343730-1.xhtml
+load 343850-1.xhtml
+load 343889-1.html
+load 344434-1.xhtml
+load 344882-1.html
+load 345837-1.xhtml
+load 346381-1.html
+load 349355-1.html
+load 359432-1.xhtml
+load 360599-1.html
+load 366200-1.xhtml
+load 369219-1.xhtml
+load 369413-1.html
+load 371124-1.html
+load 371124-2.html
+load 371466-1.xhtml
+load 372554-1.html
+load 375399-1.html
+load 377360-1.xhtml
+load 377960-1.html
+load 377960-2.html
+load 384663-1.html
+load 386000-1.html
+load 386794-1.html
+load 387460-1.html
+load 399712-1.html
+load 400763-1.html
+load 407818.html
+load 410860-1.xml
+load 411882-1.xhtml
+load 416734-1.html
+load 417852-1.html
+load 418928-1.html
+load 420620-1.html
+load 424276-1.html
+load 426987-1.html
+load 439206-1.html
+load 443538-1.svg
+load 448615-1.html
+load 450383-1.html
+load 450385-1.html
+load 458637-1.html
+load 462947.html
+load 467392.html
+load 472593-1.html
+load 474041-1.svg
+load 476526.html
+load 483818-1.html
+load 490760-1.xhtml
+load 493281-1.html
+load 493281-2.html
+load 494810-1.html
+load 499006-1.html
+load 499006-2.html
+load 502617.html
+load 504224.html
+load 509536-1.html
+load 522516-1.html
+load 529670.html
+load 535926-1.html
+load 543645.html
+load 551631-1.html
+load 552651.html
+load 554230-1.xhtml
+load 558973.html
+load 564079-1.html
+load 564114.html
+load 565125-1.html
+load 575462.svg
+load 582601.html
+load 590395-1.html
+load 593302-1.html
+load 593302-2.html
+load 595606-1.html
+load 595606-2.html
+load 601247.html
+load 603531.html
+load 604262-1.html
+load 605672-1.svg
+load 606729-1.html
+load 607222.html
+load 609560-1.xhtml
+load 610571-1.html
+load 612018-1.html
+load 628599-1.html
+load 637116.html
+load 637214-1.svg
+load 637214-2.svg
+pref(extensions.InstallTrigger.enabled,true) pref(extensions.InstallTriggerImpl.enabled,true) load 642022-1.html
+load 646184.html
+load 658845-1.svg
+load 666869.html
+load 667336-1.html
+load 675516.xhtml
+load 675621-1.html
+load 677194.html
+load 679459.html
+load 679689-1.html
+load 682463.html
+load 693212.xhtml
+load 693811-1.html
+load 693811-2.html
+load 693811-3.html
+load 693894.html
+load 695867.html
+load 697643.html
+load 698974-1.html
+load 700090-1.html
+load 700090-2.html
+load 700512.html
+load 706283-1.html
+load 709384.html
+load 709954.html
+load 713417-1.html
+load 713417-2.html
+load 715056.html
+load 729431-1.xhtml
+load 741163-1.html
+load 745495.html
+load 752226-1.html
+load 752226-2.html
+load 766426.html
+load 771639.html
+load 786854.html
+load 815043.html
+load 815276.html
+load 815477.html
+load 815500.html
+load 816253.html
+load 819014.html
+load 822723.html
+load 824719.html
+load 827190.html
+load 828054.html
+load 828903.html
+load 829428.html
+load 830098.html
+load 831287.html
+load 832644.html
+load 836890.html
+load 838489-1.html
+load 838489-2.html
+load 841205.html
+load 844404.html
+load 845093-1.html
+load 845093-2.html
+load 847127.html
+load 849601.html
+load 849727.html
+load 849732.html
+load 851353-1.html
+load 852381.html
+load 863950.html
+load 864448.html
+load 886213.html
+load 898906.html
+load 930250.html
+load 942979.html
+load 973401.html
+load 978646.html
+load 1024428-1.html
+load 1027461-1.html
+load 1029710.html
+load 1154598.xhtml
+load 1157995.html
+load 1158412.html
+load 1181619.html
+load 1228882.html
+load 1230422.html
+load 1251361.html
+load 1281715.html
+load 1304437.html
+load 1324209.html
+load 1324500.html
+load 1326194-1.html
+load 1326194-2.html
+load 1332939.html
+load 1341693.html
+load 1352453.html
+load chrome://reftest/content/crashtests/dom/base/crashtests/1353529.xhtml
+load 1368327.html
+load chrome://reftest/content/crashtests/dom/base/crashtests/1369363.xhtml
+load 1370072.html
+pref(clipboard.autocopy,true) load 1370737.html
+load 1370968.html
+load 1373750.html
+load 1377826.html
+load 1383478.html
+load 1383780.html
+pref(clipboard.autocopy,true) load 1385272-1.html
+load 1393806.html
+load 1396466.html
+load 1397795.html
+load 1400701.html
+load 1403377.html
+load 1405771.html
+load 1406109-1.html
+load 1411473.html
+load 1413815.html
+load 1419799.html
+skip-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!browserIsRemote) skip-if(geckoview) skip-if(geckoview&&isDebugBuild) skip-if(AddressSanitizer) skip-if(ThreadSanitizer) pref(dom.disable_open_during_load,false) load 1419902.html # skip on non e10s loads, Bug 1419902. Bug 1563013 for GV+WR. Bug 1524493 GV+debug. Bug 1573281 asan
+load 1422883.html
+load 1428053.html
+load 1441029.html
+load 1445670.html
+load 1449601.html
+load 1458016.html
+load 1459688.html
+load 1460794.html
+load 1462548.html
+load 1505811.html
+load 1505875.html
+load 1508845.html
+load 1516289.html
+load 1516560.html
+pref(javascript.options.dynamicImport,true) load 1529203-1.html
+skip-if(Android) pref(javascript.options.dynamicImport,true) pref(dom.disable_open_during_load,false) pref(browser.link.open_newwindow,2) load 1529203-2.html
+skip-if(Android) pref(dom.disable_open_during_load,false) pref(browser.link.open_newwindow,2) load 1529203-3.html
+load 1528675.html
+load 1566310.html
+skip-if(Android) skip-if(ThreadSanitizer) pref(dom.disable_open_during_load,false) pref(browser.link.open_newwindow,2) load 1588259.html
+load structured_clone_container_throws.html
+load xhr_empty_datauri.html
+load xhr_html_nullresponse.html
+asserts(0-1) load 1517025.html # see bug 1666910 for assert
+load xhr-with-pagehide-1.html
+load 1555786.html
+load 1577191.html
+load eventSource_invalid_scheme_worker_shutdown.html
+load 1291535.html
+skip-if(!isDebugBuild||xulRuntime.OS!="Linux") load 1611853.html
+load 1619322.html
+asserts(0-2) load 1623918.html # May hit an assertion if the <input> element's anonymous tree hasn't been flushed when IMEContentObserver handles focus
+load 1656925.html
+skip-if(Android) load 1665792.html # Print preview on android doesn't fly
+skip-if(ThreadSanitizer) load 1681729.html
+skip-if(ThreadSanitizer) load 1693049.html
+skip-if(winWidget||Android) pref(print.always_print_silent,true) pref(print.print_to_file,true) pref(print.print_to_filename,"/dev/null") load 1697256.html # Windows seemingly requires pref(print_printer,"Mozilla Save to PDF") but pref() doesn't allow space. Android doesn't support print.
+skip-if(ThreadSanitizer||Android) load 1697525.html
+skip-if(ThreadSanitizer||Android) load 1712198.html # Mysterious failure that should be investigated (bug 1712866).
+skip-if(Android) HTTP load 1728670-1.html
+load 1757923.html
+load 1766472.html
+pref(dom.enable_web_task_scheduling,true) load 1780790.html
+load 1700237.html
+load 1811939.html
+load 1822717.html
+skip-if(Android) load 1838484.html
diff --git a/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown-worker.js b/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown-worker.js
new file mode 100644
index 0000000000..d2e5e9e668
--- /dev/null
+++ b/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown-worker.js
@@ -0,0 +1,4 @@
+onmessage = function(event) {
+ close();
+ let source = new EventSource(`d:`, {});
+}
diff --git a/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown.html b/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown.html
new file mode 100644
index 0000000000..731392d83d
--- /dev/null
+++ b/dom/base/crashtests/eventSource_invalid_scheme_worker_shutdown.html
@@ -0,0 +1,36 @@
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript">
+ const workerURL = "1614339-worker.js";
+
+ const Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+ const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"]
+ .getService(Ci.nsIWorkerDebuggerManager);
+
+ function waitForUnregister() {
+ return new Promise(function(resolve) {
+ wdm.addListener({
+ onUnregister(dbg) {
+ if (dbg.url !== workerURL) {
+ return;
+ }
+ wdm.removeListener(this);
+ resolve();
+ },
+ });
+ });
+ }
+
+ function boom() {
+ const worker = new Worker(workerURL);
+ waitForUnregister().then(function() {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ worker.postMessage("Start");
+ }
+ </script>
+ </head>
+ <body onload="boom();"></body>
+</html>
+
diff --git a/dom/base/crashtests/file_504224.html b/dom/base/crashtests/file_504224.html
new file mode 100644
index 0000000000..9031ccbfad
--- /dev/null
+++ b/dom/base/crashtests/file_504224.html
@@ -0,0 +1,7 @@
+<html><head></head>
+ <body onunload="window.frameElement.parentNode.removeChild(window.frameElement)" tabindex="1">
+ <script>
+document.body.focus();
+ </script>
+ </body>
+</html>
diff --git a/dom/base/crashtests/module-with-syntax-error.js b/dom/base/crashtests/module-with-syntax-error.js
new file mode 100644
index 0000000000..7edb2fa5bc
--- /dev/null
+++ b/dom/base/crashtests/module-with-syntax-error.js
@@ -0,0 +1 @@
+,
diff --git a/dom/base/crashtests/structured_clone_container_throws.html b/dom/base/crashtests/structured_clone_container_throws.html
new file mode 100644
index 0000000000..c92c6f4ae1
--- /dev/null
+++ b/dom/base/crashtests/structured_clone_container_throws.html
@@ -0,0 +1,9 @@
+<iframe></iframe>
+<script>
+try { frames[0].history.pushState({ dummy: function() {} }, ''); }
+catch (e) {}
+var doc = frames[0].document;
+var s = doc.createElement("script");
+s.textContent = "1";
+doc.body.appendChild(s);
+</script>
diff --git a/dom/base/crashtests/xhr-with-pagehide-1-helper.html b/dom/base/crashtests/xhr-with-pagehide-1-helper.html
new file mode 100644
index 0000000000..efac291708
--- /dev/null
+++ b/dom/base/crashtests/xhr-with-pagehide-1-helper.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function redirectAndFireXHR() {
+ const XHR = new XMLHttpRequest();
+ document.location.assign('data:text/html,final iframe content');
+ XHR.open('GET', 'data:text/html,1', false);
+ XHR.send();
+}
+
+function handlePageHide() {
+ window.parent.finishTest();
+}
+</script>
+</head>
+<body onload="redirectAndFireXHR()"
+ onpagehide="handlePageHide()">
+initial iframe content
+</body>
+</html>
diff --git a/dom/base/crashtests/xhr-with-pagehide-1.html b/dom/base/crashtests/xhr-with-pagehide-1.html
new file mode 100644
index 0000000000..86f074868f
--- /dev/null
+++ b/dom/base/crashtests/xhr-with-pagehide-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>
+ Crashtest to make sure that a sync XHR during pageload doesn't prevent
+ pagehide event from firing.
+ </title>
+ <script>
+ function finishTest() {
+ // Visual indication that we got this far, for humans viewing the test:
+ document.documentElement.style.backgroundColor = "lime";
+
+ // Actually finish the test:
+ document.documentElement.className = "";
+ }
+ </script>
+</head>
+<body>
+ <iframe src="xhr-with-pagehide-1-helper.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/xhr_empty_datauri.html b/dom/base/crashtests/xhr_empty_datauri.html
new file mode 100644
index 0000000000..77988cfe24
--- /dev/null
+++ b/dom/base/crashtests/xhr_empty_datauri.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html class="reftest-wait"><meta charset=utf-8>
+<script>
+var xhr=new XMLHttpRequest();xhr.open("GET","data:,");xhr.responseType='blob';xhr.onloadend=function(){document.documentElement.removeAttribute('class');};xhr.send();
+</script>
diff --git a/dom/base/crashtests/xhr_html_nullresponse.html b/dom/base/crashtests/xhr_html_nullresponse.html
new file mode 100644
index 0000000000..47c85db9bf
--- /dev/null
+++ b/dom/base/crashtests/xhr_html_nullresponse.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var xhr=new XMLHttpRequest();xhr.open("GET",window.location);xhr.overrideMimeType("text/html");xhr.onprogress=function(){if(xhr.readyState!=3)return;xhr.abort();document.documentElement.removeAttribute('class');};xhr.onabort=function(){xhr.responseText;};xhr.send();
+</script>
diff --git a/dom/base/domerr.msg b/dom/base/domerr.msg
new file mode 100644
index 0000000000..f8f2de0253
--- /dev/null
+++ b/dom/base/domerr.msg
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Error Message definitions. */
+
+
+/* DOM4 errors from http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#domexception */
+
+DOM4_MSG_DEF(IndexSizeError, "Index or size is negative or greater than the allowed amount", NS_ERROR_DOM_INDEX_SIZE_ERR)
+DOM4_MSG_DEF(HierarchyRequestError, "Node cannot be inserted at the specified point in the hierarchy", NS_ERROR_DOM_HIERARCHY_REQUEST_ERR)
+DOM4_MSG_DEF(WrongDocumentError, "Node cannot be used in a document other than the one in which it was created", NS_ERROR_DOM_WRONG_DOCUMENT_ERR)
+DOM4_MSG_DEF(InvalidCharacterError, "String contains an invalid character", NS_ERROR_DOM_INVALID_CHARACTER_ERR)
+DOM4_MSG_DEF(NoModificationAllowedError, "Modifications are not allowed for this document", NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)
+DOM4_MSG_DEF(NotFoundError, "Node was not found", NS_ERROR_DOM_NOT_FOUND_ERR)
+DOM4_MSG_DEF(NotSupportedError, "Operation is not supported", NS_ERROR_DOM_NOT_SUPPORTED_ERR)
+DOM4_MSG_DEF(InUseAttributeError, "Attribute already in use", NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR)
+DOM4_MSG_DEF(InvalidStateError, "An attempt was made to use an object that is not, or is no longer, usable", NS_ERROR_DOM_INVALID_STATE_ERR)
+DOM4_MSG_DEF(SyntaxError, "An invalid or illegal string was specified", NS_ERROR_DOM_SYNTAX_ERR)
+DOM4_MSG_DEF(InvalidModificationError, "An attempt was made to modify the type of the underlying objec", NS_ERROR_DOM_INVALID_MODIFICATION_ERR)
+DOM4_MSG_DEF(NamespaceError, "An attempt was made to create or change an object in a way which is incorrect with regard to namespaces", NS_ERROR_DOM_NAMESPACE_ERR)
+DOM4_MSG_DEF(InvalidAccessError, "A parameter or an operation is not supported by the underlying object", NS_ERROR_DOM_INVALID_ACCESS_ERR)
+DOM4_MSG_DEF(TypeMismatchError, "The type of an object is incompatible with the expected type of the parameter associated to the object", NS_ERROR_DOM_TYPE_MISMATCH_ERR)
+DOM4_MSG_DEF(SecurityError, "The operation is insecure.", NS_ERROR_DOM_SECURITY_ERR)
+DOM4_MSG_DEF(NetworkError, "A network error occurred.", NS_ERROR_DOM_NETWORK_ERR)
+DOM4_MSG_DEF(AbortError, "The operation was aborted. ", NS_ERROR_DOM_ABORT_ERR)
+DOM4_MSG_DEF(URLMismatchError, "The given URL does not match another URL.", NS_ERROR_DOM_URL_MISMATCH_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "The quota has been exceeded.", NS_ERROR_DOM_QUOTA_EXCEEDED_ERR)
+DOM4_MSG_DEF(TimeoutError, "The operation timed out.", NS_ERROR_DOM_TIMEOUT_ERR)
+DOM4_MSG_DEF(InvalidNodeTypeError, "The supplied node is incorrect or has an incorrect ancestor for this operation.", NS_ERROR_DOM_INVALID_NODE_TYPE_ERR)
+DOM4_MSG_DEF(DataCloneError, "The object could not be cloned.", NS_ERROR_DOM_DATA_CLONE_ERR)
+DOM4_MSG_DEF(NotAllowedError, "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.", NS_ERROR_DOM_NOT_ALLOWED_ERR)
+
+/* StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding */
+DOM4_MSG_DEF(EncodingError, "The given encoding is not supported.", NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR)
+
+/* WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/ */
+DOM4_MSG_DEF(UnknownError, "The operation failed for an unknown transient reason", NS_ERROR_DOM_UNKNOWN_ERR)
+DOM4_MSG_DEF(DataError, "Data provided to an operation does not meet requirements", NS_ERROR_DOM_DATA_ERR)
+DOM4_MSG_DEF(OperationError, "The operation failed for an operation-specific reason", NS_ERROR_DOM_OPERATION_ERR)
+
+/* Media API extra messages for QuotaExceededError */
+DOM4_MSG_DEF(QuotaExceededError, "MediaKeys object is already bound to another HTMLMediaElement.", NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "Too many SourceBuffer objects created for a single MediaSource object.", NS_ERROR_DOM_MEDIA_SOURCE_MAX_BUFFER_QUOTA_EXCEEDED_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "MediaSource buffer not sufficient.", NS_ERROR_DOM_MEDIA_SOURCE_FULL_BUFFER_QUOTA_EXCEEDED_ERR)
+
+/* IndexedDB errors http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#exceptions */
+
+DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the database itself and not covered by any other error code.", NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)
+DOM4_MSG_DEF(ConstraintError, "A mutation operation in the transaction failed because a constraint was not satisfied.", NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR)
+DOM4_MSG_DEF(ConstraintError, "Object store renamed to an already existing object store.", NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR)
+DOM4_MSG_DEF(ConstraintError, "Index already exists and a new one was being attempted to be created.", NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR)
+DOM4_MSG_DEF(DataError, "Data provided to an operation does not meet requirements.", NS_ERROR_DOM_INDEXEDDB_DATA_ERR)
+DOM4_MSG_DEF(DataError, "No valid key or key range specified.", NS_ERROR_DOM_INDEXEDDB_KEY_ERR)
+DOM4_MSG_DEF(TransactionInactiveError, "A request was placed against a transaction which is currently not active, or which is finished.", NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR)
+DOM4_MSG_DEF(ReadOnlyError, "A mutation operation was attempted in a READ_ONLY transaction.", NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR)
+DOM4_MSG_DEF(VersionError, "The operation failed because the stored database is a higher version than the version requested.", NS_ERROR_DOM_INDEXEDDB_VERSION_ERR)
+
+DOM4_MSG_DEF(NotFoundError, "The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened.", NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR)
+DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a database that did not allow mutations.", NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR)
+DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to IDBTransaction.abort.", NS_ERROR_DOM_INDEXEDDB_ABORT_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "The current transaction exceeded its quota limitations.", NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR)
+
+/* FileSystem DOM errors. */
+DOM4_MSG_DEF(InvalidAccessError, "Invalid file system path.", NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR)
+DOM4_MSG_DEF(InvalidModificationError, "Failed to modify the file.", NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR)
+DOM4_MSG_DEF(NoModificationAllowedError, "Modifications are not allowed for this file", NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR)
+DOM4_MSG_DEF(AbortError, "File already exists.", NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR)
+DOM4_MSG_DEF(TypeMismatchError, "The type of the file is incompatible with the expected type.", NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR)
+DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the file system itself and not covered by any other error code.", NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR)
+
+/* DOM error codes defined by us */
+
+DOM_MSG_DEF(NS_ERROR_DOM_WRONG_TYPE_ERR, "Object is of wrong type")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_NUMBER_ERR, "Parameter is not a number")
+DOM_MSG_DEF(NS_ERROR_DOM_PROP_ACCESS_DENIED, "Access to property denied")
+DOM_MSG_DEF(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, "Access to XPConnect service denied")
+DOM_MSG_DEF(NS_ERROR_DOM_BAD_URI, "Access to restricted URI denied")
+DOM_MSG_DEF(NS_ERROR_DOM_RETVAL_UNDEFINED, "Return value is undefined")
+
+DOM4_MSG_DEF(NotFoundError, "File was not found", NS_ERROR_DOM_FILE_NOT_FOUND_ERR)
+DOM4_MSG_DEF(NotReadableError, "File could not be read", NS_ERROR_DOM_FILE_NOT_READABLE_ERR)
+
+/* common global codes (from nsError.h) */
+
+DOM_MSG_DEF(NS_OK , "Success")
+DOM_MSG_DEF(NS_ERROR_NOT_INITIALIZED , "Component not initialized")
+DOM_MSG_DEF(NS_ERROR_ALREADY_INITIALIZED , "Component already initialized")
+DOM_MSG_DEF(NS_ERROR_NOT_IMPLEMENTED , "Method not implemented")
+DOM_MSG_DEF(NS_NOINTERFACE , "Component does not have requested interface")
+DOM_MSG_DEF(NS_ERROR_NO_INTERFACE , "Component does not have requested interface")
+DOM_MSG_DEF(NS_ERROR_INVALID_POINTER , "Invalid pointer")
+DOM_MSG_DEF(NS_ERROR_NULL_POINTER , "Null pointer")
+DOM_MSG_DEF(NS_ERROR_ABORT , "Abort")
+DOM_MSG_DEF(NS_ERROR_FAILURE , "Failure")
+DOM_MSG_DEF(NS_ERROR_UNEXPECTED , "Unexpected error")
+DOM_MSG_DEF(NS_ERROR_OUT_OF_MEMORY , "Out of Memory")
+DOM_MSG_DEF(NS_ERROR_ILLEGAL_VALUE , "Illegal value")
+DOM_MSG_DEF(NS_ERROR_INVALID_ARG , "Invalid argument")
+DOM_MSG_DEF(NS_ERROR_NOT_AVAILABLE , "Component is not available")
+DOM_MSG_DEF(NS_ERROR_FACTORY_NOT_REGISTERED , "Factory not registered")
+DOM_MSG_DEF(NS_ERROR_FACTORY_NOT_LOADED , "Factory not loaded")
+DOM_MSG_DEF(NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT , "Factory does not support signatures")
+DOM_MSG_DEF(NS_ERROR_FACTORY_EXISTS , "Factory already exists")
+
+DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the file storage itself and not covered by any other error code.", NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR)
+DOM4_MSG_DEF(FileHandleInactiveError, "A request was placed against a file handle which is currently not active, or which is finished.", NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR)
+DOM4_MSG_DEF(ReadOnlyError, "A mutation operation was attempted in a READ_ONLY file handle.", NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR)
+
+DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a file storage that did not allow mutations.", NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR)
+DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR)
+
+/* Push API errors. */
+DOM4_MSG_DEF(NotAllowedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR)
+DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR)
+DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
+DOM4_MSG_DEF(InvalidAccessError, "Invalid raw ECDSA P-256 public key.", NS_ERROR_DOM_PUSH_INVALID_KEY_ERR)
+DOM4_MSG_DEF(InvalidStateError, "A subscription with a different application server key already exists.", NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR)
+
+/* Media errors */
+DOM4_MSG_DEF(AbortError, "The fetching process for the media resource was aborted by the user agent at the user's request.", NS_ERROR_DOM_MEDIA_ABORT_ERR)
+DOM4_MSG_DEF(NotAllowedError, "The play method is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.", NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR)
+DOM4_MSG_DEF(NotSupportedError, "The media resource indicated by the src attribute or assigned media provider object was not suitable.", NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR)
+
+DOM4_MSG_DEF(SyntaxError, "The URI is malformed.", NS_ERROR_DOM_MALFORMED_URI)
+DOM4_MSG_DEF(SyntaxError, "Invalid header name.", NS_ERROR_DOM_INVALID_HEADER_NAME)
+
+/* XMLHttpRequest errors. */
+DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest has an invalid context.", NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT)
+
+/* Image decode errors. */
+DOM4_MSG_DEF(EncodingError, "Node bound to inactive document.", NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT)
+DOM4_MSG_DEF(EncodingError, "Invalid image request.", NS_ERROR_DOM_IMAGE_INVALID_REQUEST)
+DOM4_MSG_DEF(EncodingError, "Invalid encoded image data.", NS_ERROR_DOM_IMAGE_BROKEN)
diff --git a/dom/base/fuzztest/FuzzStructuredClone.cpp b/dom/base/fuzztest/FuzzStructuredClone.cpp
new file mode 100644
index 0000000000..5473df2c8e
--- /dev/null
+++ b/dom/base/fuzztest/FuzzStructuredClone.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzingInterface.h"
+
+#include "jsapi.h"
+#include "js/StructuredClone.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCycleCollector.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+JS::PersistentRooted<JSObject*> global;
+
+static int FuzzingInitDomSC(int* argc, char*** argv) {
+ JSObject* simpleGlobal =
+ SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail);
+ global.init(mozilla::dom::RootingCx());
+ global.set(simpleGlobal);
+ return 0;
+}
+
+static int FuzzingRunDomSC(const uint8_t* data, size_t size) {
+ if (size < 8) {
+ return 0;
+ }
+
+ AutoJSAPI jsapi;
+ MOZ_RELEASE_ASSERT(jsapi.Init(global));
+
+ JSContext* cx = jsapi.cx();
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
+ nsCycleCollector_collect(CCReason::API, nullptr);
+ });
+
+ // The internals of SCInput have a release assert about the padding
+ // of the data, so we fix it here to avoid performance problems
+ // during fuzzing.
+ size -= size % 8;
+
+ StructuredCloneData scdata;
+ if (!scdata.CopyExternalData(reinterpret_cast<const char*>(data), size)) {
+ return 0;
+ }
+
+ JS::Rooted<JS::Value> result(cx);
+ ErrorResult rv;
+ scdata.Read(cx, &result, rv);
+
+ rv.SuppressException();
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDomSC, FuzzingRunDomSC,
+ StructuredCloneReaderDOM);
diff --git a/dom/base/fuzztest/moz.build b/dom/base/fuzztest/moz.build
new file mode 100644
index 0000000000..f2d65a3a8d
--- /dev/null
+++ b/dom/base/fuzztest/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("FuzzingDomBase")
+
+SOURCES += [
+ "FuzzStructuredClone.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/ipc",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/base/gen-usecounters.py b/dom/base/gen-usecounters.py
new file mode 100755
index 0000000000..bd0dba7b0b
--- /dev/null
+++ b/dom/base/gen-usecounters.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(__file__))
+
+import usecounters
+
+AUTOGENERATED_WARNING_COMMENT = (
+ "/* THIS FILE IS AUTOGENERATED BY gen-usecounters.py - DO NOT EDIT */"
+)
+
+
+def generate_list(f, counters):
+ def print_optional_macro_declare(name):
+ print(
+ """
+#ifndef %(name)s
+#define %(name)s(interface_, name_) // nothing
+#define DEFINED_%(name)s
+#endif
+"""
+ % {"name": name},
+ file=f,
+ )
+
+ def print_optional_macro_undeclare(name):
+ print(
+ """
+#ifdef DEFINED_%(name)s
+#undef DEFINED_%(name)s
+#undef %(name)s
+#endif
+"""
+ % {"name": name},
+ file=f,
+ )
+
+ print(AUTOGENERATED_WARNING_COMMENT, file=f)
+
+ print_optional_macro_declare("USE_COUNTER_DOM_METHOD")
+ print_optional_macro_declare("USE_COUNTER_DOM_ATTRIBUTE")
+ print_optional_macro_declare("USE_COUNTER_CUSTOM")
+
+ for counter in counters:
+ if counter["type"] == "method":
+ print(
+ "USE_COUNTER_DOM_METHOD(%s, %s)"
+ % (counter["interface_name"], counter["method_name"]),
+ file=f,
+ )
+ elif counter["type"] == "attribute":
+ print(
+ "USE_COUNTER_DOM_ATTRIBUTE(%s, %s)"
+ % (counter["interface_name"], counter["attribute_name"]),
+ file=f,
+ )
+ elif counter["type"] == "custom":
+ desc = counter["desc"].replace("\\", r"\\").replace('"', r"\"")
+ print('USE_COUNTER_CUSTOM(%s, "%s")' % (counter["name"], desc), file=f)
+
+ print_optional_macro_undeclare("USE_COUNTER_DOM_METHOD")
+ print_optional_macro_undeclare("USE_COUNTER_DOM_ATTRIBUTE")
+ print_optional_macro_undeclare("USE_COUNTER_CUSTOM")
+
+
+def use_counter_list(output_header, conf_filename):
+ counters = usecounters.read_conf(conf_filename)
+ generate_list(output_header, counters)
diff --git a/dom/base/moz.build b/dom/base/moz.build
new file mode 100644
index 0000000000..d04ccb6981
--- /dev/null
+++ b/dom/base/moz.build
@@ -0,0 +1,630 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("*Selection*"):
+ BUG_COMPONENT = ("Core", "DOM: Selection")
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+TEST_DIRS += ["test"]
+
+if CONFIG["FUZZING"]:
+ if CONFIG["FUZZING_INTERFACES"]:
+ TEST_DIRS += ["fuzztest"]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+XPIDL_SOURCES += [
+ "mozIDOMWindow.idl",
+ "nsIContentPolicy.idl",
+ "nsIDOMRequestService.idl",
+ "nsIDroppedLinkHandler.idl",
+ "nsIEventSourceEventService.idl",
+ "nsIImageLoadingContent.idl",
+ "nsIMessageManager.idl",
+ "nsIObjectLoadingContent.idl",
+ "nsIScriptableContentIterator.idl",
+ "nsIScriptChannel.idl",
+ "nsISelectionController.idl",
+ "nsISelectionDisplay.idl",
+ "nsISelectionListener.idl",
+ "nsISlowScriptDebug.idl",
+]
+
+XPIDL_MODULE = "dom"
+
+EXPORTS += [
+ "AttrArray.h",
+ "AutocompleteFieldList.h",
+ "Crypto.h",
+ "HTMLSplitOnSpacesTokenizer.h",
+ "IframeSandboxKeywordList.h",
+ "mozAutoDocUpdate.h",
+ "NodeUbiReporting.h",
+ "nsAttrName.h",
+ "nsAttrValue.h",
+ "nsAttrValueInlines.h",
+ "nsCaseTreatment.h",
+ "nsChildContentList.h",
+ "nsContentCID.h",
+ "nsContentCreatorFunctions.h",
+ "nsContentList.h",
+ "nsContentListDeclarations.h",
+ "nsContentPermissionHelper.h",
+ "nsContentPolicyUtils.h",
+ "nsContentSink.h",
+ "nsContentTypeParser.h",
+ "nsContentUtils.h",
+ "nsCopySupport.h",
+ "nsDeprecatedOperationList.h",
+ "nsDocElementCreatedNotificationRunner.h",
+ "nsDocumentWarningList.h",
+ "nsDOMAttributeMap.h",
+ "nsDOMCID.h",
+ "nsDOMJSUtils.h",
+ "nsDOMMutationObserver.h",
+ "nsDOMNavigationTiming.h",
+ "nsDOMString.h",
+ "nsDOMTokenList.h",
+ "nsFocusManager.h",
+ "nsFrameLoader.h", # Because binding headers include it.
+ "nsFrameLoaderOwner.h",
+ "nsFrameMessageManager.h",
+ "nsGlobalWindow.h", # Because binding headers include it.
+ "nsGlobalWindowInner.h", # Because binding headers include it.
+ "nsGlobalWindowOuter.h", # Because binding headers include it.
+ "nsIAnimationObserver.h",
+ "nsIContent.h",
+ "nsIContentInlines.h",
+ "nsIDocumentObserver.h",
+ "nsIGlobalObject.h",
+ "nsImageLoadingContent.h",
+ "nsIMutationObserver.h",
+ "nsINode.h",
+ "nsINodeList.h",
+ "nsIScriptContext.h",
+ "nsIScriptGlobalObject.h",
+ "nsIScriptObjectPrincipal.h",
+ "nsJSEnvironment.h",
+ "nsJSUtils.h",
+ "nsLineBreaker.h",
+ "nsMappedAttributeElement.h",
+ "nsMappedAttributes.h",
+ "nsNameSpaceManager.h",
+ "nsNodeInfoManager.h",
+ "nsPIDOMWindow.h",
+ "nsPIDOMWindowInlines.h",
+ "nsPIWindowRoot.h",
+ "nsPropertyTable.h",
+ "nsRange.h",
+ "nsSandboxFlags.h",
+ "nsStructuredCloneContainer.h",
+ "nsStubAnimationObserver.h",
+ "nsStubDocumentObserver.h",
+ "nsStubMutationObserver.h",
+ "nsStyledElement.h",
+ "nsTextFragment.h",
+ "nsTextNode.h",
+ "nsTraversal.h",
+ "nsTreeSanitizer.h",
+ "nsViewportInfo.h",
+ "nsWindowMemoryReporter.h",
+ "nsWindowSizes.h",
+ "nsWrapperCache.h",
+ "nsWrapperCacheInlines.h",
+ "XPathGenerator.h",
+]
+
+if CONFIG["MOZ_WEBRTC"]:
+ EXPORTS += [
+ "nsDOMDataChannel.h",
+ "nsDOMDataChannelDeclarations.h",
+ ]
+
+EXPORTS.mozilla += [
+ "CallState.h",
+ "ContentIterator.h",
+ "CORSMode.h",
+ "FlushType.h",
+ "FullscreenChange.h",
+ "GlobalTeardownObserver.h",
+ "IdentifierMapEntry.h",
+ "PointerLockManager.h",
+ "RangeBoundary.h",
+ "RangeUtils.h",
+ "ScriptableContentIterator.h",
+ "ScrollingMetrics.h",
+ "SelectionChangeEventDispatcher.h",
+ "TextInputProcessor.h",
+ "UseCounter.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "!UseCounterList.h",
+ "!UseCounterWorkerList.h",
+ "AbstractRange.h",
+ "AncestorIterator.h",
+ "AnchorAreaFormRelValues.h",
+ "AnimationFrameProvider.h",
+ "AnonymousContent.h",
+ "Attr.h",
+ "AutoPrintEventDispatcher.h",
+ "AutoSuppressEventHandlingAndSuspend.h",
+ "BarProps.h",
+ "BindContext.h",
+ "BodyConsumer.h",
+ "BodyUtil.h",
+ "BorrowedAttrInfo.h",
+ "CCGCScheduler.h",
+ "CharacterData.h",
+ "ChildIterator.h",
+ "ChildProcessMessageManager.h",
+ "ChromeMessageBroadcaster.h",
+ "ChromeMessageSender.h",
+ "ChromeNodeList.h",
+ "ChromeUtils.h",
+ "Comment.h",
+ "CompressionStream.h",
+ "ContentFrameMessageManager.h",
+ "ContentProcessMessageManager.h",
+ "CustomElementRegistry.h",
+ "DecompressionStream.h",
+ "DirectionalityUtils.h",
+ "DispatcherTrait.h",
+ "DocGroup.h",
+ "Document.h",
+ "DocumentFragment.h",
+ "DocumentInlines.h",
+ "DocumentOrShadowRoot.h",
+ "DocumentType.h",
+ "DOMArena.h",
+ "DOMException.h",
+ "DOMImplementation.h",
+ "DOMIntersectionObserver.h",
+ "DOMMatrix.h",
+ "DOMMozPromiseRequestHolder.h",
+ "DOMParser.h",
+ "DOMPoint.h",
+ "DOMQuad.h",
+ "DOMRect.h",
+ "DOMRequest.h",
+ "DOMStringList.h",
+ "DOMTokenListSupportedTokens.h",
+ "Element.h",
+ "ElementInlines.h",
+ "EventSource.h",
+ "EventSourceEventService.h",
+ "External.h",
+ "FilteredNodeIterator.h",
+ "FormData.h",
+ "FragmentOrElement.h",
+ "FromParser.h",
+ "GeneratedImageContent.h",
+ "Highlight.h",
+ "HighlightRegistry.h",
+ "IdleDeadline.h",
+ "IdleRequest.h",
+ "IDTracker.h",
+ "ImageEncoder.h",
+ "ImageTracker.h",
+ "IntlUtils.h",
+ "JSExecutionContext.h",
+ "Link.h",
+ "LinkStyle.h",
+ "Location.h",
+ "LocationBase.h",
+ "MaybeCrossOriginObject.h",
+ "MessageBroadcaster.h",
+ "MessageListenerManager.h",
+ "MessageManagerCallback.h",
+ "MessageManagerGlobal.h",
+ "MessageSender.h",
+ "MimeType.h",
+ "MozQueryInterface.h",
+ "MutationObservers.h",
+ "NameSpaceConstants.h",
+ "Navigator.h",
+ "NodeInfo.h",
+ "NodeInfoInlines.h",
+ "NodeIterator.h",
+ "ParentProcessMessageManager.h",
+ "PlacesBookmark.h",
+ "PlacesBookmarkAddition.h",
+ "PlacesBookmarkChanged.h",
+ "PlacesBookmarkGuid.h",
+ "PlacesBookmarkKeyword.h",
+ "PlacesBookmarkMoved.h",
+ "PlacesBookmarkRemoved.h",
+ "PlacesBookmarkTags.h",
+ "PlacesBookmarkTime.h",
+ "PlacesBookmarkTitle.h",
+ "PlacesBookmarkUrl.h",
+ "PlacesEvent.h",
+ "PlacesFavicon.h",
+ "PlacesHistoryCleared.h",
+ "PlacesObservers.h",
+ "PlacesPurgeCaches.h",
+ "PlacesRanking.h",
+ "PlacesVisit.h",
+ "PlacesVisitRemoved.h",
+ "PlacesVisitTitle.h",
+ "PlacesWeakCallbackWrapper.h",
+ "PopoverData.h",
+ "PopupBlocker.h",
+ "Pose.h",
+ "PostMessageEvent.h",
+ "ProcessMessageManager.h",
+ "RadioGroupManager.h",
+ "ResizeObserver.h",
+ "ResizeObserverController.h",
+ "ResponsiveImageSelector.h",
+ "SameProcessMessageQueue.h",
+ "ScreenLuminance.h",
+ "ScreenOrientation.h",
+ "Selection.h",
+ "SerializedStackHolder.h",
+ "ShadowIncludingTreeIterator.h",
+ "ShadowRoot.h",
+ "StaticRange.h",
+ "StructuredCloneBlob.h",
+ "StructuredCloneHolder.h",
+ "StructuredCloneTags.h",
+ "StructuredCloneTester.h",
+ "StyledRange.h",
+ "StyleSheetList.h",
+ "SubtleCrypto.h",
+ "SyncMessageSender.h",
+ "TestUtils.h",
+ "Text.h",
+ "Timeout.h",
+ "TimeoutHandler.h",
+ "TimeoutManager.h",
+ "TreeIterator.h",
+ "TreeOrderedArray.h",
+ "TreeOrderedArrayInlines.h",
+ "TreeWalker.h",
+ "UIDirectionManager.h",
+ "UserActivation.h",
+ "ViewportMetaData.h",
+ "VisualViewport.h",
+ "WindowFeatures.h",
+ "WindowProxyHolder.h",
+]
+
+if CONFIG["FUZZING"]:
+ EXPORTS.mozilla.dom += [
+ "FuzzingFunctions.h",
+ ]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ EXPORTS.mozilla.dom += [
+ "!GeneratedElementDocumentState.h",
+ "RustTypes.h",
+ ]
+
+ CbindgenHeader(
+ "GeneratedElementDocumentState.h",
+ inputs=["rust"],
+ )
+
+UNIFIED_SOURCES += [
+ "AbstractRange.cpp",
+ "AnchorAreaFormRelValues.cpp",
+ "AnimationFrameProvider.cpp",
+ "AnonymousContent.cpp",
+ "Attr.cpp",
+ "AttrArray.cpp",
+ "BarProps.cpp",
+ "BindContext.cpp",
+ "BodyConsumer.cpp",
+ "BodyUtil.cpp",
+ "BorrowedAttrInfo.cpp",
+ "CCGCScheduler.cpp",
+ "CharacterData.cpp",
+ "ChildIterator.cpp",
+ "ChromeMessageBroadcaster.cpp",
+ "ChromeMessageSender.cpp",
+ "ChromeNodeList.cpp",
+ "ChromeUtils.cpp",
+ "Comment.cpp",
+ "ContentFrameMessageManager.cpp",
+ "ContentIterator.cpp",
+ "ContentProcessMessageManager.cpp",
+ "Crypto.cpp",
+ "CustomElementRegistry.cpp",
+ "DirectionalityUtils.cpp",
+ "DispatcherTrait.cpp",
+ "DocGroup.cpp",
+ "Document.cpp",
+ "DocumentFragment.cpp",
+ "DocumentOrShadowRoot.cpp",
+ "DocumentType.cpp",
+ "DOMException.cpp",
+ "DOMImplementation.cpp",
+ "DOMMatrix.cpp",
+ "DOMParser.cpp",
+ "DOMPoint.cpp",
+ "DOMQuad.cpp",
+ "DOMRect.cpp",
+ "DOMRequest.cpp",
+ "DOMStringList.cpp",
+ "Element.cpp",
+ "EventSource.cpp",
+ "EventSourceEventService.cpp",
+ "External.cpp",
+ "FormData.cpp",
+ "FragmentOrElement.cpp",
+ "GeneratedImageContent.cpp",
+ "GlobalTeardownObserver.cpp",
+ "Highlight.cpp",
+ "HighlightRegistry.cpp",
+ "IdleDeadline.cpp",
+ "IdleRequest.cpp",
+ "IDTracker.cpp",
+ "ImageEncoder.cpp",
+ "ImageTracker.cpp",
+ "InProcessBrowserChildMessageManager.cpp",
+ "IntlUtils.cpp",
+ "JSExecutionContext.cpp",
+ "Link.cpp",
+ "LinkStyle.cpp",
+ "Location.cpp",
+ "LocationBase.cpp",
+ "MaybeCrossOriginObject.cpp",
+ "MessageBroadcaster.cpp",
+ "MessageListenerManager.cpp",
+ "MessageManagerGlobal.cpp",
+ "MessageSender.cpp",
+ "MimeType.cpp",
+ "MozQueryInterface.cpp",
+ "MutationObservers.cpp",
+ "Navigator.cpp",
+ "NodeInfo.cpp",
+ "NodeIterator.cpp",
+ "NodeUbiReporting.cpp",
+ "nsAttrValue.cpp",
+ "nsAttrValueOrString.cpp",
+ "nsCCUncollectableMarker.cpp",
+ "nsContentAreaDragDrop.cpp",
+ "nsContentList.cpp",
+ "nsContentPermissionHelper.cpp",
+ "nsContentPolicy.cpp",
+ "nsContentSink.cpp",
+ "nsContentTypeParser.cpp",
+ "nsCopySupport.cpp",
+ "nsDataDocumentContentPolicy.cpp",
+ "nsDOMAttributeMap.cpp",
+ "nsDOMCaretPosition.cpp",
+ "nsDOMMutationObserver.cpp",
+ "nsDOMNavigationTiming.cpp",
+ "nsDOMTokenList.cpp",
+ "nsFocusManager.cpp",
+ "nsFrameLoader.cpp",
+ "nsFrameLoaderOwner.cpp",
+ "nsGlobalWindowCommands.cpp",
+ "nsHistory.cpp",
+ "nsIGlobalObject.cpp",
+ "nsINode.cpp",
+ "nsJSEnvironment.cpp",
+ "nsJSUtils.cpp",
+ "nsLineBreaker.cpp",
+ "nsMappedAttributeElement.cpp",
+ "nsMappedAttributes.cpp",
+ "nsMimeTypeArray.cpp",
+ "nsNameSpaceManager.cpp",
+ "nsNoDataProtocolContentPolicy.cpp",
+ "nsNodeInfoManager.cpp",
+ "nsOpenURIInFrameParams.cpp",
+ "nsPropertyTable.cpp",
+ "nsQueryContentEventResult.cpp",
+ "nsRange.cpp",
+ "nsScreen.cpp",
+ "nsStructuredCloneContainer.cpp",
+ "nsStubAnimationObserver.cpp",
+ "nsStubDocumentObserver.cpp",
+ "nsStubMutationObserver.cpp",
+ "nsStyledElement.cpp",
+ "nsSyncLoadService.cpp",
+ "nsTextFragment.cpp",
+ "nsTextNode.cpp",
+ "nsTraversal.cpp",
+ "nsTreeSanitizer.cpp",
+ "nsViewportInfo.cpp",
+ "nsWindowMemoryReporter.cpp",
+ "nsWindowRoot.cpp",
+ "nsWrapperCache.cpp",
+ "ParentProcessMessageManager.cpp",
+ "PointerLockManager.cpp",
+ "PopoverData.cpp",
+ "PopupBlocker.cpp",
+ "Pose.cpp",
+ "PostMessageEvent.cpp",
+ "ProcessMessageManager.cpp",
+ "RadioGroupManager.cpp",
+ "RangeUtils.cpp",
+ "RemoteOuterWindowProxy.cpp",
+ "ResizeObserver.cpp",
+ "ResizeObserverController.cpp",
+ "ResponsiveImageSelector.cpp",
+ "SameProcessMessageQueue.cpp",
+ "ScreenLuminance.cpp",
+ "ScreenOrientation.cpp",
+ "ScriptableContentIterator.cpp",
+ "ScrollingMetrics.cpp",
+ "Selection.cpp",
+ "SelectionChangeEventDispatcher.cpp",
+ "SerializedStackHolder.cpp",
+ "ShadowRoot.cpp",
+ "StaticRange.cpp",
+ "StorageAccessPermissionRequest.cpp",
+ "StructuredCloneBlob.cpp",
+ "StructuredCloneHolder.cpp",
+ "StructuredCloneTester.cpp",
+ "StyledRange.cpp",
+ "StyleSheetList.cpp",
+ "SubtleCrypto.cpp",
+ "TestUtils.cpp",
+ "Text.cpp",
+ "TextInputProcessor.cpp",
+ "ThirdPartyUtil.cpp",
+ "Timeout.cpp",
+ "TimeoutBudgetManager.cpp",
+ "TimeoutExecutor.cpp",
+ "TimeoutHandler.cpp",
+ "TimeoutManager.cpp",
+ "TreeWalker.cpp",
+ "UIDirectionManager.cpp",
+ "UserActivation.cpp",
+ "ViewportMetaData.cpp",
+ "VisualViewport.cpp",
+ "WindowDestroyedEvent.cpp",
+ "WindowFeatures.cpp",
+ "WindowNamedPropertiesHandler.cpp",
+ "XPathGenerator.cpp",
+]
+
+if CONFIG["MOZ_WEBRTC"]:
+ UNIFIED_SOURCES += [
+ "nsDOMDataChannel.cpp",
+ ]
+
+if CONFIG["FUZZING"]:
+ UNIFIED_SOURCES += [
+ "FuzzingFunctions.cpp",
+ ]
+
+if CONFIG["MOZ_PLACES"]:
+ UNIFIED_SOURCES += [
+ "PlacesEvent.cpp",
+ "PlacesObservers.cpp",
+ "PlacesWeakCallbackWrapper.cpp",
+ ]
+
+# on win32 if we add these files to UNIFIED_SOURCES then the compiler generates
+# larger stack frames for some recursive functions that cause us to hit stack
+# overflows (see bug 1824565)
+if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["CPU_ARCH"] == "x86":
+ SOURCES += [
+ "CompressionStream.cpp",
+ "DecompressionStream.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "CompressionStream.cpp",
+ "DecompressionStream.cpp",
+ ]
+
+# these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
+SOURCES += [
+ # Several conflicts with other bindings.
+ "DOMIntersectionObserver.cpp",
+ # Because of OS X headers.
+ "nsContentUtils.cpp",
+ # this file doesn't like windows.h
+ "nsDOMWindowUtils.cpp",
+ # Conflicts with windows.h's definition of SendMessage.
+ "nsFrameMessageManager.cpp",
+ # These files have a #error "Never include unwrapped windows.h in this file!"
+ "nsGlobalWindowInner.cpp",
+ "nsGlobalWindowOuter.cpp",
+ # Conflicts with windows.h's definition of LoadImage.
+ "nsImageLoadingContent.cpp",
+ # Because of OS X headers.
+ "nsObjectLoadingContent.cpp",
+ # nsPluginArray.cpp includes npapi.h indirectly, and that includes a lot of system headers
+ "nsPluginArray.cpp",
+]
+
+# Are we targeting x86-32 or x86-64? If so, we want to include SSE2 code for
+# nsTextFragment.cpp
+if CONFIG["INTEL_ARCHITECTURE"]:
+ SOURCES += ["nsTextFragmentSSE2.cpp"]
+ SOURCES["nsTextFragmentSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+
+# Are we targeting PowerPC? If so, we can enable a SIMD version for
+# nsTextFragment.cpp as well.
+if CONFIG["CPU_ARCH"].startswith("ppc"):
+ SOURCES += ["nsTextFragmentVMX.cpp"]
+ SOURCES["nsTextFragmentVMX.cpp"].flags += CONFIG["PPC_VMX_FLAGS"]
+
+EXTRA_JS_MODULES += [
+ "ContentAreaDropListener.sys.mjs",
+ "DOMRequestHelper.sys.mjs",
+ "IndexedDBHelper.sys.mjs",
+ "LocationHelper.sys.mjs",
+ "ProcessSelector.sys.mjs",
+ "SlowScriptDebug.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+LOCAL_INCLUDES += [
+ "../battery",
+ "../events",
+ "../media",
+ "../network",
+ "/caps",
+ "/docshell/base",
+ "/dom/base",
+ "/dom/file",
+ "/dom/geolocation",
+ "/dom/html",
+ "/dom/ipc",
+ "/dom/storage",
+ "/dom/svg",
+ "/dom/xml",
+ "/dom/xslt/xpath",
+ "/dom/xul",
+ "/extensions/spellcheck/src",
+ "/gfx/2d",
+ "/image",
+ "/js/xpconnect/loader",
+ "/js/xpconnect/src",
+ "/js/xpconnect/wrappers",
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+ "/netwerk/url-classifier",
+ "/parser/htmlparser",
+ "/security/manager/ssl",
+ "/third_party/xsimd/include",
+ "/widget",
+ "/xpcom/ds",
+]
+
+if CONFIG["MOZ_WEBRTC"]:
+ LOCAL_INCLUDES += [
+ "/netwerk/sctp/datachannel",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+GeneratedFile(
+ "UseCounterList.h",
+ script="gen-usecounters.py",
+ entry_point="use_counter_list",
+ inputs=["UseCounters.conf"],
+)
+
+GeneratedFile(
+ "UseCounterWorkerList.h",
+ script="gen-usecounters.py",
+ entry_point="use_counter_list",
+ inputs=["UseCountersWorker.conf"],
+)
diff --git a/dom/base/mozAutoDocUpdate.h b/dom/base/mozAutoDocUpdate.h
new file mode 100644
index 0000000000..0b210c3dda
--- /dev/null
+++ b/dom/base/mozAutoDocUpdate.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozAutoDocUpdate_h_
+#define mozAutoDocUpdate_h_
+
+#include "nsContentUtils.h" // For AddScriptBlocker() and RemoveScriptBlocker().
+#include "mozilla/dom/Document.h"
+#include "nsIDocumentObserver.h"
+
+/**
+ * Helper class to automatically handle batching of document updates. This
+ * class will call BeginUpdate on construction and EndUpdate on destruction on
+ * the given document with the given update type. The document could be null,
+ * in which case no updates will be called. The constructor also takes a
+ * boolean that can be set to false to prevent notifications.
+ */
+class MOZ_STACK_CLASS mozAutoDocUpdate {
+ public:
+ mozAutoDocUpdate(mozilla::dom::Document* aDocument, bool aNotify)
+ : mDocument(aNotify ? aDocument : nullptr) {
+ if (mDocument) {
+ mDocument->BeginUpdate();
+ } else {
+ nsContentUtils::AddScriptBlocker();
+ }
+ }
+
+ ~mozAutoDocUpdate() {
+ if (mDocument) {
+ mDocument->EndUpdate();
+ } else {
+ nsContentUtils::RemoveScriptBlocker();
+ }
+ }
+
+ private:
+ RefPtr<mozilla::dom::Document> mDocument;
+};
+
+#define MOZ_AUTO_DOC_UPDATE_PASTE2(tok, line) tok##line
+#define MOZ_AUTO_DOC_UPDATE_PASTE(tok, line) \
+ MOZ_AUTO_DOC_UPDATE_PASTE2(tok, line)
+#define MOZ_AUTO_DOC_UPDATE(doc, notify) \
+ mozAutoDocUpdate MOZ_AUTO_DOC_UPDATE_PASTE(_autoDocUpdater_, __LINE__)( \
+ doc, notify)
+
+#endif
diff --git a/dom/base/mozIDOMWindow.idl b/dom/base/mozIDOMWindow.idl
new file mode 100644
index 0000000000..4b15d54b60
--- /dev/null
+++ b/dom/base/mozIDOMWindow.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+ * Placeholder interfaces to allow passing inner/outer windows through XPIDL.
+ */
+[scriptable, builtinclass, uuid(75fbabd6-7a2e-4787-aa33-449a33512135)]
+interface mozIDOMWindow : nsISupports
+{};
+
+[scriptable, builtinclass, uuid(53ca090c-e739-48b9-8911-208c72f9191e)]
+interface mozIDOMWindowProxy : nsISupports
+{};
diff --git a/dom/base/nsAttrName.h b/dom/base/nsAttrName.h
new file mode 100644
index 0000000000..5af01b028b
--- /dev/null
+++ b/dom/base/nsAttrName.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Class that represents the name (nodeinfo or atom) of an attribute;
+ * using nodeinfos all the time is too slow, so we use atoms when we
+ * can.
+ */
+
+#ifndef nsAttrName_h___
+#define nsAttrName_h___
+
+#include "mozilla/dom/NodeInfo.h"
+#include "nsAtom.h"
+#include "nsDOMString.h"
+
+#define NS_ATTRNAME_NODEINFO_BIT 1
+class nsAttrName {
+ public:
+ nsAttrName(const nsAttrName& aOther) : mBits(aOther.mBits) {
+ AddRefInternalName();
+ }
+
+ explicit nsAttrName(nsAtom* aAtom)
+ : mBits(reinterpret_cast<uintptr_t>(aAtom)) {
+ NS_ASSERTION(aAtom, "null atom-name in nsAttrName");
+ NS_ADDREF(aAtom);
+ }
+
+ explicit nsAttrName(mozilla::dom::NodeInfo* aNodeInfo) {
+ NS_ASSERTION(aNodeInfo, "null nodeinfo-name in nsAttrName");
+ if (aNodeInfo->NamespaceEquals(kNameSpaceID_None)) {
+ mBits = reinterpret_cast<uintptr_t>(aNodeInfo->NameAtom());
+ NS_ADDREF(aNodeInfo->NameAtom());
+ } else {
+ mBits = reinterpret_cast<uintptr_t>(aNodeInfo) | NS_ATTRNAME_NODEINFO_BIT;
+ NS_ADDREF(aNodeInfo);
+ }
+ }
+
+ ~nsAttrName() { ReleaseInternalName(); }
+
+ void SetTo(mozilla::dom::NodeInfo* aNodeInfo) {
+ NS_ASSERTION(aNodeInfo, "null nodeinfo-name in nsAttrName");
+
+ ReleaseInternalName();
+ if (aNodeInfo->NamespaceEquals(kNameSpaceID_None)) {
+ mBits = reinterpret_cast<uintptr_t>(aNodeInfo->NameAtom());
+ NS_ADDREF(aNodeInfo->NameAtom());
+ } else {
+ mBits = reinterpret_cast<uintptr_t>(aNodeInfo) | NS_ATTRNAME_NODEINFO_BIT;
+ NS_ADDREF(aNodeInfo);
+ }
+ }
+
+ void SetTo(nsAtom* aAtom) {
+ NS_ASSERTION(aAtom, "null atom-name in nsAttrName");
+
+ ReleaseInternalName();
+ mBits = reinterpret_cast<uintptr_t>(aAtom);
+ NS_ADDREF(aAtom);
+ }
+
+ bool IsAtom() const { return !(mBits & NS_ATTRNAME_NODEINFO_BIT); }
+
+ mozilla::dom::NodeInfo* NodeInfo() const {
+ NS_ASSERTION(!IsAtom(), "getting nodeinfo-value of atom-name");
+ return reinterpret_cast<mozilla::dom::NodeInfo*>(mBits &
+ ~NS_ATTRNAME_NODEINFO_BIT);
+ }
+
+ nsAtom* Atom() const {
+ NS_ASSERTION(IsAtom(), "getting atom-value of nodeinfo-name");
+ return reinterpret_cast<nsAtom*>(mBits);
+ }
+
+ bool Equals(const nsAttrName& aOther) const { return mBits == aOther.mBits; }
+
+ // Faster comparison in the case we know the namespace is null
+ // Note that some callers such as AttrArray::IndexOfAttr() will
+ // call this function on nsAttrName structs with 0 mBits, so no attempt
+ // must be made to do anything with mBits besides comparing it with the
+ // incoming aAtom argument.
+ bool Equals(const nsAtom* aAtom) const {
+ return reinterpret_cast<uintptr_t>(aAtom) == mBits;
+ }
+
+ // And the same but without forcing callers to atomize
+ bool Equals(const nsAString& aLocalName) const {
+ return IsAtom() && Atom()->Equals(aLocalName);
+ }
+
+ bool Equals(const nsAtom* aLocalName, int32_t aNamespaceID) const {
+ if (aNamespaceID == kNameSpaceID_None) {
+ return Equals(aLocalName);
+ }
+ return !IsAtom() && NodeInfo()->Equals(aLocalName, aNamespaceID);
+ }
+
+ bool Equals(mozilla::dom::NodeInfo* aNodeInfo) const {
+ return Equals(aNodeInfo->NameAtom(), aNodeInfo->NamespaceID());
+ }
+
+ int32_t NamespaceID() const {
+ return IsAtom() ? kNameSpaceID_None : NodeInfo()->NamespaceID();
+ }
+
+ int32_t NamespaceEquals(int32_t aNamespaceID) const {
+ return aNamespaceID == kNameSpaceID_None
+ ? IsAtom()
+ : (!IsAtom() && NodeInfo()->NamespaceEquals(aNamespaceID));
+ }
+
+ nsAtom* LocalName() const {
+ return IsAtom() ? Atom() : NodeInfo()->NameAtom();
+ }
+
+ nsAtom* GetPrefix() const {
+ return IsAtom() ? nullptr : NodeInfo()->GetPrefixAtom();
+ }
+
+ bool QualifiedNameEquals(const nsAString& aName) const {
+ return IsAtom() ? Atom()->Equals(aName)
+ : NodeInfo()->QualifiedNameEquals(aName);
+ }
+
+ void GetQualifiedName(nsAString& aStr) const {
+ if (IsAtom()) {
+ Atom()->ToString(aStr);
+ } else {
+ aStr = NodeInfo()->QualifiedName();
+ }
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ void GetPrefix(nsAString& aStr) const {
+ if (IsAtom()) {
+ SetDOMStringToNull(aStr);
+ } else {
+ NodeInfo()->GetPrefix(aStr);
+ }
+ }
+#endif
+
+ uint32_t HashValue() const {
+ // mBits and uint32_t might have different size. This should silence
+ // any warnings or compile-errors. This is what the implementation of
+ // NS_PTR_TO_INT32 does to take care of the same problem.
+ return mBits - 0;
+ }
+
+ bool IsSmaller(const nsAtom* aOther) const {
+ return mBits < reinterpret_cast<uintptr_t>(aOther);
+ }
+
+ private:
+ void AddRefInternalName() {
+ if (IsAtom()) {
+ NS_ADDREF(Atom());
+ } else {
+ NS_ADDREF(NodeInfo());
+ }
+ }
+
+ void ReleaseInternalName() {
+ if (IsAtom()) {
+ Atom()->Release();
+ } else {
+ NodeInfo()->Release();
+ }
+ }
+
+ uintptr_t mBits;
+};
+
+#endif
diff --git a/dom/base/nsAttrValue.cpp b/dom/base/nsAttrValue.cpp
new file mode 100644
index 0000000000..ebdb61909d
--- /dev/null
+++ b/dom/base/nsAttrValue.cpp
@@ -0,0 +1,1972 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A struct that represents the value (type and actual data) of an
+ * attribute.
+ */
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/HashFunctions.h"
+
+#include "mozilla/URLExtraData.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsAtom.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/BloomFilter.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/ShadowParts.h"
+#include "mozilla/SVGAttrValueWrapper.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/dom/CSSRuleBinding.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsHTMLCSSStyleSheet.h"
+#include "nsStyledElement.h"
+#include "nsIURI.h"
+#include "ReferrerInfo.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+constexpr uint32_t kMiscContainerCacheSize = 128;
+static void* gMiscContainerCache[kMiscContainerCacheSize];
+static uint32_t gMiscContainerCount = 0;
+
+/* static */
+MiscContainer* nsAttrValue::AllocMiscContainer() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static_assert(sizeof(gMiscContainerCache) <= 1024);
+ static_assert(sizeof(MiscContainer) <= 32);
+
+ // Allocate MiscContainer objects in batches to improve performance.
+ if (gMiscContainerCount == 0) {
+ for (; gMiscContainerCount < kMiscContainerCacheSize;
+ ++gMiscContainerCount) {
+ gMiscContainerCache[gMiscContainerCount] =
+ moz_xmalloc(sizeof(MiscContainer));
+ }
+ }
+
+ return new (gMiscContainerCache[--gMiscContainerCount]) MiscContainer();
+}
+
+/* static */
+void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aCont) {
+ return;
+ }
+
+ aCont->~MiscContainer();
+
+ if (gMiscContainerCount < kMiscContainerCacheSize) {
+ gMiscContainerCache[gMiscContainerCount++] = aCont;
+ return;
+ }
+
+ free(aCont);
+}
+
+bool MiscContainer::GetString(nsAString& aString) const {
+ bool isString;
+ void* ptr = GetStringOrAtomPtr(isString);
+ if (!ptr) {
+ return false;
+ }
+ if (isString) {
+ auto* buffer = static_cast<nsStringBuffer*>(ptr);
+ buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
+ } else {
+ static_cast<nsAtom*>(ptr)->ToString(aString);
+ }
+ return true;
+}
+
+void MiscContainer::Cache() {
+ // Not implemented for anything else yet.
+ if (mType != nsAttrValue::eCSSDeclaration) {
+ MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
+ return;
+ }
+
+ MOZ_ASSERT(IsRefCounted());
+ MOZ_ASSERT(mValue.mRefCount > 0);
+ MOZ_ASSERT(!mValue.mCached);
+
+ nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
+ if (!sheet) {
+ return;
+ }
+
+ nsString str;
+ bool gotString = GetString(str);
+ if (!gotString) {
+ return;
+ }
+
+ sheet->CacheStyleAttr(str, this);
+ mValue.mCached = 1;
+
+ // This has to be immutable once it goes into the cache.
+ mValue.mCSSDeclaration->SetImmutable();
+}
+
+void MiscContainer::Evict() {
+ // Not implemented for anything else yet.
+ if (mType != nsAttrValue::eCSSDeclaration) {
+ MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
+ return;
+ }
+ MOZ_ASSERT(IsRefCounted());
+ MOZ_ASSERT(mValue.mRefCount == 0);
+
+ if (!mValue.mCached) {
+ return;
+ }
+
+ nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
+ MOZ_ASSERT(sheet);
+
+ nsString str;
+ DebugOnly<bool> gotString = GetString(str);
+ MOZ_ASSERT(gotString);
+
+ sheet->EvictStyleAttr(str, this);
+ mValue.mCached = 0;
+}
+
+nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;
+
+nsAttrValue::nsAttrValue() : mBits(0) {}
+
+nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
+ SetTo(aOther);
+}
+
+nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); }
+
+nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); }
+
+nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
+ const nsAString* aSerialized)
+ : mBits(0) {
+ SetTo(std::move(aValue), aSerialized);
+}
+
+nsAttrValue::~nsAttrValue() { ResetIfSet(); }
+
+/* static */
+void nsAttrValue::Init() {
+ MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized");
+ sEnumTableArray = new nsTArray<const EnumTable*>;
+}
+
+/* static */
+void nsAttrValue::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ delete sEnumTableArray;
+ sEnumTableArray = nullptr;
+
+ for (uint32_t i = 0; i < gMiscContainerCount; ++i) {
+ free(gMiscContainerCache[i]);
+ }
+ gMiscContainerCount = 0;
+}
+
+void nsAttrValue::Reset() {
+ switch (BaseType()) {
+ case eStringBase: {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ str->Release();
+ }
+
+ break;
+ }
+ case eOtherBase: {
+ MiscContainer* cont = GetMiscContainer();
+ if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
+ NS_RELEASE(cont);
+ break;
+ }
+
+ DeallocMiscContainer(ClearMiscContainer());
+
+ break;
+ }
+ case eAtomBase: {
+ nsAtom* atom = GetAtomValue();
+ NS_RELEASE(atom);
+
+ break;
+ }
+ case eIntegerBase: {
+ break;
+ }
+ }
+
+ mBits = 0;
+}
+
+void nsAttrValue::SetTo(const nsAttrValue& aOther) {
+ if (this == &aOther) {
+ return;
+ }
+
+ switch (aOther.BaseType()) {
+ case eStringBase: {
+ ResetIfSet();
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr());
+ if (str) {
+ str->AddRef();
+ SetPtrValueAndType(str, eStringBase);
+ }
+ return;
+ }
+ case eOtherBase: {
+ break;
+ }
+ case eAtomBase: {
+ ResetIfSet();
+ nsAtom* atom = aOther.GetAtomValue();
+ NS_ADDREF(atom);
+ SetPtrValueAndType(atom, eAtomBase);
+ return;
+ }
+ case eIntegerBase: {
+ ResetIfSet();
+ mBits = aOther.mBits;
+ return;
+ }
+ }
+
+ MiscContainer* otherCont = aOther.GetMiscContainer();
+ if (otherCont->IsRefCounted()) {
+ DeallocMiscContainer(ClearMiscContainer());
+ NS_ADDREF(otherCont);
+ SetPtrValueAndType(otherCont, eOtherBase);
+ return;
+ }
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ switch (otherCont->mType) {
+ case eInteger: {
+ cont->mValue.mInteger = otherCont->mValue.mInteger;
+ break;
+ }
+ case eEnum: {
+ cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
+ break;
+ }
+ case ePercent: {
+ cont->mDoubleValue = otherCont->mDoubleValue;
+ break;
+ }
+ case eColor: {
+ cont->mValue.mColor = otherCont->mValue.mColor;
+ break;
+ }
+ case eShadowParts:
+ case eCSSDeclaration: {
+ MOZ_CRASH("These should be refcounted!");
+ }
+ case eURL: {
+ NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
+ break;
+ }
+ case eAtomArray: {
+ if (!EnsureEmptyAtomArray()) {
+ Reset();
+ return;
+ }
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ *GetAtomArrayValue() = otherCont->mValue.mAtomArray->Clone();
+
+ break;
+ }
+ case eDoubleValue: {
+ cont->mDoubleValue = otherCont->mDoubleValue;
+ break;
+ }
+ default: {
+ if (IsSVGType(otherCont->mType)) {
+ // All SVG types are just pointers to classes and will therefore have
+ // the same size so it doesn't really matter which one we assign
+ cont->mValue.mSVGLength = otherCont->mValue.mSVGLength;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
+ }
+ break;
+ }
+ }
+
+ bool isString;
+ if (void* otherPtr = otherCont->GetStringOrAtomPtr(isString)) {
+ if (isString) {
+ static_cast<nsStringBuffer*>(otherPtr)->AddRef();
+ } else {
+ static_cast<nsAtom*>(otherPtr)->AddRef();
+ }
+ cont->SetStringBitsMainThread(otherCont->mStringBits);
+ }
+ // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
+ // work correctly.
+ cont->mType = otherCont->mType;
+}
+
+void nsAttrValue::SetTo(const nsAString& aValue) {
+ ResetIfSet();
+ nsStringBuffer* buf = GetStringBuffer(aValue).take();
+ if (buf) {
+ SetPtrValueAndType(buf, eStringBase);
+ }
+}
+
+void nsAttrValue::SetTo(nsAtom* aValue) {
+ ResetIfSet();
+ if (aValue) {
+ NS_ADDREF(aValue);
+ SetPtrValueAndType(aValue, eAtomBase);
+ }
+}
+
+void nsAttrValue::SetTo(int16_t aInt) {
+ ResetIfSet();
+ SetIntValueAndType(aInt, eInteger, nullptr);
+}
+
+void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
+ ResetIfSet();
+ SetIntValueAndType(aInt, eInteger, aSerialized);
+}
+
+void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) {
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mDoubleValue = aValue;
+ cont->mType = eDoubleValue;
+ SetMiscAtomOrString(aSerialized);
+}
+
+void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue,
+ const nsAString* aSerialized) {
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ MOZ_ASSERT(cont->mValue.mRefCount == 0);
+ cont->mValue.mCSSDeclaration = aValue.take();
+ cont->mType = eCSSDeclaration;
+ NS_ADDREF(cont);
+ SetMiscAtomOrString(aSerialized);
+ MOZ_ASSERT(cont->mValue.mRefCount == 1);
+}
+
+void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) {
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ NS_ADDREF(cont->mValue.mURL = aValue);
+ cont->mType = eURL;
+ SetMiscAtomOrString(aSerialized);
+}
+
+void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
+ if (aOther.Type() != nsAttrValue::eString &&
+ aOther.Type() != nsAttrValue::eAtom) {
+ nsAutoString val;
+ aOther.ToString(val);
+ SetTo(val);
+ } else {
+ SetTo(aOther);
+ }
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGOrient, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedLength& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGLength, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGLengthList& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as a length list, there's no need to store
+ // it (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGLengthList, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGNumberList& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as a number list, there's no need to store
+ // it (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGNumberList, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGNumberPair, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGPathData& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as path data, there's no need to store it
+ // (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGPathData, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGPointList& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as a point list, there's no need to store
+ // it (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGPointList, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGStringList& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as a string list, there's no need to store
+ // it (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGStringList, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGTransformList& aValue,
+ const nsAString* aSerialized) {
+ // While an empty string will parse as a transform list, there's no need to
+ // store it (and SetMiscAtomOrString will assert if we try)
+ if (aSerialized && aSerialized->IsEmpty()) {
+ aSerialized = nullptr;
+ }
+ SetSVGType(eSVGTransformList, &aValue, aSerialized);
+}
+
+void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue,
+ const nsAString* aSerialized) {
+ SetSVGType(eSVGViewBox, &aValue, aSerialized);
+}
+
+void nsAttrValue::SwapValueWith(nsAttrValue& aOther) {
+ uintptr_t tmp = aOther.mBits;
+ aOther.mBits = mBits;
+ mBits = tmp;
+}
+
+void nsAttrValue::ToString(nsAString& aResult) const {
+ MiscContainer* cont = nullptr;
+ if (BaseType() == eOtherBase) {
+ cont = GetMiscContainer();
+
+ if (cont->GetString(aResult)) {
+ return;
+ }
+ }
+
+ switch (Type()) {
+ case eString: {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult);
+ } else {
+ aResult.Truncate();
+ }
+ break;
+ }
+ case eAtom: {
+ nsAtom* atom = static_cast<nsAtom*>(GetPtr());
+ atom->ToString(aResult);
+
+ break;
+ }
+ case eInteger: {
+ nsAutoString intStr;
+ intStr.AppendInt(GetIntegerValue());
+ aResult = intStr;
+
+ break;
+ }
+#ifdef DEBUG
+ case eColor: {
+ MOZ_ASSERT_UNREACHABLE("color attribute without string data");
+ aResult.Truncate();
+ break;
+ }
+#endif
+ case eEnum: {
+ GetEnumString(aResult, false);
+ break;
+ }
+ case ePercent: {
+ nsAutoString str;
+ if (cont) {
+ str.AppendFloat(cont->mDoubleValue);
+ } else {
+ str.AppendInt(GetIntInternal());
+ }
+ aResult = str + u"%"_ns;
+
+ break;
+ }
+ case eCSSDeclaration: {
+ aResult.Truncate();
+ MiscContainer* container = GetMiscContainer();
+ if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
+ nsAutoCString result;
+ decl->ToString(result);
+ CopyUTF8toUTF16(result, aResult);
+ }
+
+ // This can be reached during parallel selector matching with attribute
+ // selectors on the style attribute. SetMiscAtomOrString handles this
+ // case, and as of this writing this is the only consumer that needs it.
+ const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);
+
+ break;
+ }
+ case eDoubleValue: {
+ aResult.Truncate();
+ aResult.AppendFloat(GetDoubleValue());
+ break;
+ }
+ case eSVGIntegerPair: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
+ break;
+ }
+ case eSVGOrient: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
+ break;
+ }
+ case eSVGLength: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
+ aResult);
+ break;
+ }
+ case eSVGLengthList: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
+ aResult);
+ break;
+ }
+ case eSVGNumberList: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
+ aResult);
+ break;
+ }
+ case eSVGNumberPair: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
+ break;
+ }
+ case eSVGPathData: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
+ aResult);
+ break;
+ }
+ case eSVGPointList: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
+ aResult);
+ break;
+ }
+ case eSVGPreserveAspectRatio: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
+ break;
+ }
+ case eSVGStringList: {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
+ aResult);
+ break;
+ }
+ case eSVGTransformList: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGTransformList, aResult);
+ break;
+ }
+ case eSVGViewBox: {
+ SVGAttrValueWrapper::ToString(
+ GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
+ break;
+ }
+ default: {
+ aResult.Truncate();
+ break;
+ }
+ }
+}
+
+already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
+ switch (Type()) {
+ case eString:
+ return NS_AtomizeMainThread(GetStringValue());
+
+ case eAtom: {
+ RefPtr<nsAtom> atom = GetAtomValue();
+ return atom.forget();
+ }
+
+ default: {
+ nsAutoString val;
+ ToString(val);
+ return NS_AtomizeMainThread(val);
+ }
+ }
+}
+
+const nsCheapString nsAttrValue::GetStringValue() const {
+ MOZ_ASSERT(Type() == eString, "wrong type");
+
+ return nsCheapString(static_cast<nsStringBuffer*>(GetPtr()));
+}
+
+bool nsAttrValue::GetColorValue(nscolor& aColor) const {
+ if (Type() != eColor) {
+ // Unparseable value, treat as unset.
+ NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr");
+ return false;
+ }
+
+ aColor = GetMiscContainer()->mValue.mColor;
+ return true;
+}
+
+void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const {
+ MOZ_ASSERT(Type() == eEnum, "wrong type");
+
+ uint32_t allEnumBits = (BaseType() == eIntegerBase)
+ ? static_cast<uint32_t>(GetIntInternal())
+ : GetMiscContainer()->mValue.mEnumValue;
+ int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS;
+ const EnumTable* table = sEnumTableArray->ElementAt(
+ allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK);
+
+ while (table->tag) {
+ if (table->value == val) {
+ aResult.AssignASCII(table->tag);
+ if (!aRealTag &&
+ allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
+ nsContentUtils::ASCIIToUpper(aResult);
+ }
+ return;
+ }
+ table++;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
+}
+
+void AttrAtomArray::DoRemoveDuplicates() {
+ MOZ_ASSERT(mMayContainDuplicates);
+
+ bool usingHashTable = false;
+ BitBloomFilter<8, nsAtom> filter;
+ nsTHashSet<nsPtrHashKey<nsAtom>> hash;
+
+ auto CheckDuplicate = [&](size_t i) {
+ nsAtom* atom = mArray[i];
+ if (!usingHashTable) {
+ if (!filter.mightContain(atom)) {
+ filter.add(atom);
+ return false;
+ }
+ for (size_t j = 0; j < i; ++j) {
+ hash.Insert(mArray[j]);
+ }
+ usingHashTable = true;
+ }
+ return !hash.EnsureInserted(atom);
+ };
+
+ size_t len = mArray.Length();
+ for (size_t i = 0; i < len; ++i) {
+ if (!CheckDuplicate(i)) {
+ continue;
+ }
+ mArray.RemoveElementAt(i);
+ --i;
+ --len;
+ }
+
+ mMayContainDuplicates = false;
+}
+
+uint32_t nsAttrValue::GetAtomCount() const {
+ ValueType type = Type();
+
+ if (type == eAtom) {
+ return 1;
+ }
+
+ if (type == eAtomArray) {
+ return GetAtomArrayValue()->mArray.Length();
+ }
+
+ return 0;
+}
+
+nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
+ MOZ_ASSERT(aIndex >= 0, "Index must not be negative");
+ MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");
+
+ if (BaseType() == eAtomBase) {
+ return GetAtomValue();
+ }
+
+ NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
+ return GetAtomArrayValue()->mArray.ElementAt(aIndex);
+}
+
+uint32_t nsAttrValue::HashValue() const {
+ switch (BaseType()) {
+ case eStringBase: {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
+ return HashString(static_cast<char16_t*>(str->Data()), len);
+ }
+
+ return 0;
+ }
+ case eOtherBase: {
+ break;
+ }
+ case eAtomBase:
+ case eIntegerBase: {
+ // mBits and uint32_t might have different size. This should silence
+ // any warnings or compile-errors. This is what the implementation of
+ // NS_PTR_TO_INT32 does to take care of the same problem.
+ return mBits - 0;
+ }
+ }
+
+ MiscContainer* cont = GetMiscContainer();
+ if (static_cast<ValueBaseType>(cont->mStringBits &
+ NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) {
+ return cont->mStringBits - 0;
+ }
+
+ switch (cont->mType) {
+ case eInteger: {
+ return cont->mValue.mInteger;
+ }
+ case eEnum: {
+ return cont->mValue.mEnumValue;
+ }
+ case ePercent: {
+ return cont->mDoubleValue;
+ }
+ case eColor: {
+ return cont->mValue.mColor;
+ }
+ case eCSSDeclaration: {
+ return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
+ }
+ case eURL: {
+ nsString str;
+ ToString(str);
+ return HashString(str);
+ }
+ case eAtomArray: {
+ uint32_t hash = 0;
+ for (const auto& atom : cont->mValue.mAtomArray->mArray) {
+ hash = AddToHash(hash, atom.get());
+ }
+ return hash;
+ }
+ case eDoubleValue: {
+ // XXX this is crappy, but oh well
+ return cont->mDoubleValue;
+ }
+ default: {
+ if (IsSVGType(cont->mType)) {
+ // All SVG types are just pointers to classes so we can treat them alike
+ return NS_PTR_TO_INT32(cont->mValue.mSVGLength);
+ }
+ MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
+ return 0;
+ }
+ }
+}
+
+bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
+ if (BaseType() != aOther.BaseType()) {
+ return false;
+ }
+
+ switch (BaseType()) {
+ case eStringBase: {
+ return GetStringValue().Equals(aOther.GetStringValue());
+ }
+ case eOtherBase: {
+ break;
+ }
+ case eAtomBase:
+ case eIntegerBase: {
+ return mBits == aOther.mBits;
+ }
+ }
+
+ MiscContainer* thisCont = GetMiscContainer();
+ MiscContainer* otherCont = aOther.GetMiscContainer();
+ if (thisCont == otherCont) {
+ return true;
+ }
+
+ if (thisCont->mType != otherCont->mType) {
+ return false;
+ }
+
+ bool needsStringComparison = false;
+
+ switch (thisCont->mType) {
+ case eInteger: {
+ if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
+ needsStringComparison = true;
+ }
+ break;
+ }
+ case eEnum: {
+ if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
+ needsStringComparison = true;
+ }
+ break;
+ }
+ case ePercent: {
+ if (thisCont->mDoubleValue == otherCont->mDoubleValue) {
+ needsStringComparison = true;
+ }
+ break;
+ }
+ case eColor: {
+ if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
+ needsStringComparison = true;
+ }
+ break;
+ }
+ case eCSSDeclaration: {
+ return thisCont->mValue.mCSSDeclaration ==
+ otherCont->mValue.mCSSDeclaration;
+ }
+ case eURL: {
+ return thisCont->mValue.mURL == otherCont->mValue.mURL;
+ }
+ case eAtomArray: {
+ // For classlists we could be insensitive to order, however
+ // classlists are never mapped attributes so they are never compared.
+
+ if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) {
+ return false;
+ }
+
+ needsStringComparison = true;
+ break;
+ }
+ case eDoubleValue: {
+ return thisCont->mDoubleValue == otherCont->mDoubleValue;
+ }
+ default: {
+ if (IsSVGType(thisCont->mType)) {
+ // Currently this method is never called for nsAttrValue objects that
+ // point to SVG data types.
+ // If that changes then we probably want to add methods to the
+ // corresponding SVG types to compare their base values.
+ // As a shortcut, however, we can begin by comparing the pointers.
+ MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data");
+ return false;
+ }
+ MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
+ return false;
+ }
+ }
+ if (needsStringComparison) {
+ if (thisCont->mStringBits == otherCont->mStringBits) {
+ return true;
+ }
+ if ((static_cast<ValueBaseType>(thisCont->mStringBits &
+ NS_ATTRVALUE_BASETYPE_MASK) ==
+ eStringBase) &&
+ (static_cast<ValueBaseType>(otherCont->mStringBits &
+ NS_ATTRVALUE_BASETYPE_MASK) ==
+ eStringBase)) {
+ return nsCheapString(reinterpret_cast<nsStringBuffer*>(
+ static_cast<uintptr_t>(thisCont->mStringBits)))
+ .Equals(nsCheapString(reinterpret_cast<nsStringBuffer*>(
+ static_cast<uintptr_t>(otherCont->mStringBits))));
+ }
+ }
+ return false;
+}
+
+bool nsAttrValue::Equals(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ switch (BaseType()) {
+ case eStringBase: {
+ if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) {
+ nsDependentString dep(static_cast<char16_t*>(str->Data()),
+ str->StorageSize() / sizeof(char16_t) - 1);
+ return aCaseSensitive == eCaseMatters
+ ? aValue.Equals(dep)
+ : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep);
+ }
+ return aValue.IsEmpty();
+ }
+ case eAtomBase: {
+ auto* atom = static_cast<nsAtom*>(GetPtr());
+ if (aCaseSensitive == eCaseMatters) {
+ return atom->Equals(aValue);
+ }
+ return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom),
+ aValue);
+ }
+ default:
+ break;
+ }
+
+ nsAutoString val;
+ ToString(val);
+ return aCaseSensitive == eCaseMatters
+ ? val.Equals(aValue)
+ : nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
+}
+
+bool nsAttrValue::Equals(const nsAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ if (auto* atom = GetStoredAtom()) {
+ if (atom == aValue) {
+ return true;
+ }
+ if (aCaseSensitive == eCaseMatters) {
+ return false;
+ }
+ if (atom->IsAsciiLowercase() && aValue->IsAsciiLowercase()) {
+ return false;
+ }
+ }
+ return Equals(nsDependentAtomString(aValue), aCaseSensitive);
+}
+
+struct HasPrefixFn {
+ static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
+ const nsAString& aSearchValue,
+ nsCaseTreatment aCaseSensitive) {
+ if (aCaseSensitive == eCaseMatters) {
+ if (aSearchValue.Length() > aAttrLen) {
+ return false;
+ }
+ return !memcmp(aAttrValue, aSearchValue.BeginReading(),
+ aSearchValue.Length() * sizeof(char16_t));
+ }
+ return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen),
+ aSearchValue,
+ nsASCIICaseInsensitiveStringComparator);
+ }
+};
+
+struct HasSuffixFn {
+ static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
+ const nsAString& aSearchValue,
+ nsCaseTreatment aCaseSensitive) {
+ if (aCaseSensitive == eCaseMatters) {
+ if (aSearchValue.Length() > aAttrLen) {
+ return false;
+ }
+ return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(),
+ aSearchValue.BeginReading(),
+ aSearchValue.Length() * sizeof(char16_t));
+ }
+ return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue,
+ nsASCIICaseInsensitiveStringComparator);
+ }
+};
+
+struct HasSubstringFn {
+ static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
+ const nsAString& aSearchValue,
+ nsCaseTreatment aCaseSensitive) {
+ if (aCaseSensitive == eCaseMatters) {
+ if (aSearchValue.IsEmpty()) {
+ return true;
+ }
+ const char16_t* end = aAttrValue + aAttrLen;
+ return std::search(aAttrValue, end, aSearchValue.BeginReading(),
+ aSearchValue.EndReading()) != end;
+ }
+ return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen),
+ nsASCIICaseInsensitiveStringComparator);
+ }
+};
+
+template <typename F>
+bool nsAttrValue::SubstringCheck(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ switch (BaseType()) {
+ case eStringBase: {
+ auto str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ return F::Check(static_cast<char16_t*>(str->Data()),
+ str->StorageSize() / sizeof(char16_t) - 1, aValue,
+ aCaseSensitive);
+ }
+ return aValue.IsEmpty();
+ }
+ case eAtomBase: {
+ auto atom = static_cast<nsAtom*>(GetPtr());
+ return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
+ aCaseSensitive);
+ }
+ default:
+ break;
+ }
+
+ nsAutoString val;
+ ToString(val);
+ return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive);
+}
+
+bool nsAttrValue::HasPrefix(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive);
+}
+
+bool nsAttrValue::HasSuffix(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive);
+}
+
+bool nsAttrValue::HasSubstring(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive);
+}
+
+bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const {
+ if (Type() == aOther.Type()) {
+ return Equals(aOther);
+ }
+
+ // We need to serialize at least one nsAttrValue before passing to
+ // Equals(const nsAString&), but we can avoid unnecessarily serializing both
+ // by checking if one is already of a string type.
+ bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase);
+ const nsAttrValue& lhs = thisIsString ? *this : aOther;
+ const nsAttrValue& rhs = thisIsString ? aOther : *this;
+
+ switch (rhs.BaseType()) {
+ case eAtomBase:
+ return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);
+
+ case eStringBase:
+ return lhs.Equals(rhs.GetStringValue(), eCaseMatters);
+
+ default: {
+ nsAutoString val;
+ rhs.ToString(val);
+ return lhs.Equals(val, eCaseMatters);
+ }
+ }
+}
+
+bool nsAttrValue::Contains(nsAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const {
+ switch (BaseType()) {
+ case eAtomBase: {
+ nsAtom* atom = GetAtomValue();
+ if (aCaseSensitive == eCaseMatters) {
+ return aValue == atom;
+ }
+
+ // For performance reasons, don't do a full on unicode case insensitive
+ // string comparison. This is only used for quirks mode anyway.
+ return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
+ }
+ default: {
+ if (Type() == eAtomArray) {
+ AttrAtomArray* array = GetAtomArrayValue();
+ if (aCaseSensitive == eCaseMatters) {
+ return array->mArray.Contains(aValue);
+ }
+
+ for (RefPtr<nsAtom>& cur : array->mArray) {
+ // For performance reasons, don't do a full on unicode case
+ // insensitive string comparison. This is only used for quirks mode
+ // anyway.
+ if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+struct AtomArrayStringComparator {
+ bool Equals(nsAtom* atom, const nsAString& string) const {
+ return atom->Equals(string);
+ }
+};
+
+bool nsAttrValue::Contains(const nsAString& aValue) const {
+ switch (BaseType()) {
+ case eAtomBase: {
+ nsAtom* atom = GetAtomValue();
+ return atom->Equals(aValue);
+ }
+ default: {
+ if (Type() == eAtomArray) {
+ AttrAtomArray* array = GetAtomArrayValue();
+ return array->mArray.Contains(aValue, AtomArrayStringComparator());
+ }
+ }
+ }
+
+ return false;
+}
+
+void nsAttrValue::ParseAtom(const nsAString& aValue) {
+ ResetIfSet();
+
+ RefPtr<nsAtom> atom = NS_Atomize(aValue);
+ if (atom) {
+ SetPtrValueAndType(atom.forget().take(), eAtomBase);
+ }
+}
+
+void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
+ nsAString::const_iterator iter, end;
+ aValue.BeginReading(iter);
+ aValue.EndReading(end);
+ bool hasSpace = false;
+
+ // skip initial whitespace
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ hasSpace = true;
+ ++iter;
+ }
+
+ if (iter == end) {
+ SetTo(aValue);
+ return;
+ }
+
+ nsAString::const_iterator start(iter);
+
+ // get first - and often only - atom
+ do {
+ ++iter;
+ } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
+
+ RefPtr<nsAtom> classAtom = NS_AtomizeMainThread(Substring(start, iter));
+ if (!classAtom) {
+ Reset();
+ return;
+ }
+
+ // skip whitespace
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ hasSpace = true;
+ ++iter;
+ }
+
+ if (iter == end && !hasSpace) {
+ // we only found one classname and there was no whitespace so
+ // don't bother storing a list
+ ResetIfSet();
+ nsAtom* atom = nullptr;
+ classAtom.swap(atom);
+ SetPtrValueAndType(atom, eAtomBase);
+ return;
+ }
+
+ if (!EnsureEmptyAtomArray()) {
+ return;
+ }
+
+ AttrAtomArray* array = GetAtomArrayValue();
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ array->mArray.AppendElement(std::move(classAtom));
+
+ // parse the rest of the classnames
+ while (iter != end) {
+ start = iter;
+
+ do {
+ ++iter;
+ } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
+
+ classAtom = NS_AtomizeMainThread(Substring(start, iter));
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ array->mArray.AppendElement(std::move(classAtom));
+ array->mMayContainDuplicates = true;
+
+ // skip whitespace
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ ++iter;
+ }
+ }
+
+ SetMiscAtomOrString(&aValue);
+}
+
+void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) {
+ uint32_t len = aValue.Length();
+ // Don't bother with atoms if it's an empty string since
+ // we can store those efficiently anyway.
+ if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
+ ParseAtom(aValue);
+ } else {
+ SetTo(aValue);
+ }
+}
+
+void nsAttrValue::ParsePartMapping(const nsAString& aValue) {
+ ResetIfSet();
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+
+ cont->mType = eShadowParts;
+ cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue));
+ NS_ADDREF(cont);
+ SetMiscAtomOrString(&aValue);
+ MOZ_ASSERT(cont->mValue.mRefCount == 1);
+}
+
+void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType,
+ const nsAString* aStringValue) {
+ if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE ||
+ aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) {
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ switch (aType) {
+ case eInteger: {
+ cont->mValue.mInteger = aValue;
+ break;
+ }
+ case ePercent: {
+ cont->mDoubleValue = aValue;
+ break;
+ }
+ case eEnum: {
+ cont->mValue.mEnumValue = aValue;
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("unknown integer type");
+ break;
+ }
+ }
+ cont->mType = aType;
+ SetMiscAtomOrString(aStringValue);
+ } else {
+ NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
+ mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
+ }
+}
+
+void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType,
+ const nsAString* aStringValue) {
+ MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type");
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mDoubleValue = aValue;
+ cont->mType = aType;
+ SetMiscAtomOrString(aStringValue);
+}
+
+nsAtom* nsAttrValue::GetStoredAtom() const {
+ if (BaseType() == eAtomBase) {
+ return static_cast<nsAtom*>(GetPtr());
+ }
+ if (BaseType() == eOtherBase) {
+ return GetMiscContainer()->GetStoredAtom();
+ }
+ return nullptr;
+}
+
+nsStringBuffer* nsAttrValue::GetStoredStringBuffer() const {
+ if (BaseType() == eStringBase) {
+ return static_cast<nsStringBuffer*>(GetPtr());
+ }
+ if (BaseType() == eOtherBase) {
+ return GetMiscContainer()->GetStoredStringBuffer();
+ }
+ return nullptr;
+}
+
+int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
+ int16_t index = sEnumTableArray->IndexOf(aTable);
+ if (index < 0) {
+ index = sEnumTableArray->Length();
+ NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
+ "too many enum tables");
+ sEnumTableArray->AppendElement(aTable);
+ }
+
+ return index;
+}
+
+int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
+ const EnumTable* aTableEntry) {
+ int16_t index = GetEnumTableIndex(aEnumTable);
+ int32_t value =
+ (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
+ return value;
+}
+
+bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
+ const EnumTable* aTable, bool aCaseSensitive,
+ const EnumTable* aDefaultValue) {
+ ResetIfSet();
+ const EnumTable* tableEntry = aTable;
+
+ while (tableEntry->tag) {
+ if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag)
+ : aValue.LowerCaseEqualsASCII(tableEntry->tag)) {
+ int32_t value = EnumTableEntryToValue(aTable, tableEntry);
+
+ bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag);
+ if (!equals) {
+ nsAutoString tag;
+ tag.AssignASCII(tableEntry->tag);
+ nsContentUtils::ASCIIToUpper(tag);
+ if ((equals = tag.Equals(aValue))) {
+ value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER;
+ }
+ }
+ SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue);
+ NS_ASSERTION(GetEnumValue() == tableEntry->value,
+ "failed to store enum properly");
+
+ return true;
+ }
+ tableEntry++;
+ }
+
+ if (aDefaultValue) {
+ MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
+ "aDefaultValue not inside aTable?");
+ SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
+ &aValue);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput,
+ bool aEnsureNonzero) {
+ ResetIfSet();
+
+ // We don't use nsContentUtils::ParseHTMLInteger here because we
+ // need a bunch of behavioral differences from it. We _could_ try to
+ // use it, but it would not be a great fit.
+
+ // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
+
+ // Steps 1 and 2.
+ const char16_t* position = aInput.BeginReading();
+ const char16_t* end = aInput.EndReading();
+
+ // We will need to keep track of whether this was a canonical representation
+ // or not. It's non-canonical if it has leading whitespace, leading '+',
+ // leading '0' characters, or trailing garbage.
+ bool canonical = true;
+
+ // Step 3.
+ while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) {
+ canonical = false; // Leading whitespace
+ ++position;
+ }
+
+ // Step 4.
+ if (position == end || *position < char16_t('0') ||
+ *position > char16_t('9')) {
+ return false;
+ }
+
+ // Step 5.
+ CheckedInt32 value = 0;
+
+ // Collect up leading '0' first to avoid extra branching in the main
+ // loop to set 'canonical' properly.
+ while (position != end && *position == char16_t('0')) {
+ canonical = false; // Leading '0'
+ ++position;
+ }
+
+ // Now collect up other digits.
+ while (position != end && *position >= char16_t('0') &&
+ *position <= char16_t('9')) {
+ value = value * 10 + (*position - char16_t('0'));
+ if (!value.isValid()) {
+ // The spec assumes we can deal with arbitrary-size integers here, but we
+ // really can't. If someone sets something too big, just bail out and
+ // ignore it.
+ return false;
+ }
+ ++position;
+ }
+
+ // Step 6 is implemented implicitly via the various "position != end" guards
+ // from this point on.
+
+ Maybe<double> doubleValue;
+ // Step 7. The return in step 7.2 is handled by just falling through to the
+ // code below this block when we reach end of input or a non-digit, because
+ // the while loop will terminate at that point.
+ if (position != end && *position == char16_t('.')) {
+ canonical = false; // Let's not rely on double serialization reproducing
+ // the string we started with.
+ // Step 7.1.
+ ++position;
+ // If we have a '.' _not_ followed by digits, this is not as efficient as it
+ // could be, because we will store as a double while we could have stored as
+ // an int. But that seems like a pretty rare case.
+ doubleValue.emplace(value.value());
+ // Step 7.3.
+ double divisor = 1.0f;
+ // Step 7.4.
+ while (position != end && *position >= char16_t('0') &&
+ *position <= char16_t('9')) {
+ // Step 7.4.1.
+ divisor = divisor * 10.0f;
+ // Step 7.4.2.
+ doubleValue.ref() += (*position - char16_t('0')) / divisor;
+ // Step 7.4.3.
+ ++position;
+ // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the
+ // "position != end" checks below.
+ }
+ }
+
+ if (aEnsureNonzero && value.value() == 0 &&
+ (!doubleValue || *doubleValue == 0.0f)) {
+ // Not valid. Just drop it.
+ return false;
+ }
+
+ // Step 8 and the spec's early return from step 7.2.
+ ValueType type;
+ if (position != end && *position == char16_t('%')) {
+ type = ePercent;
+ ++position;
+ } else if (doubleValue) {
+ type = eDoubleValue;
+ } else {
+ type = eInteger;
+ }
+
+ if (position != end) {
+ canonical = false;
+ }
+
+ if (doubleValue) {
+ MOZ_ASSERT(!canonical, "We set it false above!");
+ SetDoubleValueAndType(*doubleValue, type, &aInput);
+ } else {
+ SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput);
+ }
+
+#ifdef DEBUG
+ nsAutoString str;
+ ToString(str);
+ MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!");
+#endif
+
+ return true;
+}
+
+bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
+ int32_t aMax) {
+ MOZ_ASSERT(aMin < aMax, "bad boundaries");
+
+ ResetIfSet();
+
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
+ if (result & nsContentUtils::eParseHTMLInteger_Error) {
+ return false;
+ }
+
+ int32_t val = std::max(originalVal, aMin);
+ val = std::min(val, aMax);
+ bool nonStrict =
+ (val != originalVal) ||
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
+
+ SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
+
+ return true;
+}
+
+void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
+ int32_t aDefault, int32_t aMax) {
+ ResetIfSet();
+
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
+ bool nonStrict = false;
+ if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
+ val = aDefault;
+ nonStrict = true;
+ }
+
+ if (val > aMax) {
+ val = aMax;
+ nonStrict = true;
+ }
+
+ if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
+ nonStrict = true;
+ }
+
+ SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
+}
+
+void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
+ int32_t aDefault, int32_t aMin,
+ int32_t aMax) {
+ ResetIfSet();
+
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
+ bool nonStrict =
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
+
+ if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
+ if (result & nsContentUtils::eParseHTMLInteger_Negative) {
+ val = aDefault;
+ } else {
+ val = aMax;
+ }
+ nonStrict = true;
+ } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
+ val = aDefault;
+ nonStrict = true;
+ } else if (val < aMin) {
+ val = aMin;
+ nonStrict = true;
+ } else if (val > aMax) {
+ val = aMax;
+ nonStrict = true;
+ }
+
+ SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
+}
+
+bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
+ ResetIfSet();
+
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
+ if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
+ return false;
+ }
+
+ bool nonStrict =
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
+
+ SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
+
+ return true;
+}
+
+bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
+ ResetIfSet();
+
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
+ if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
+ return false;
+ }
+
+ bool nonStrict =
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
+
+ SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
+
+ return true;
+}
+
+void nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
+ nsStringBuffer* buf = GetStringBuffer(aString).take();
+ if (!buf) {
+ return;
+ }
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mValue.mColor = aColor;
+ cont->mType = eColor;
+
+ // Save the literal string we were passed for round-tripping.
+ cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase);
+}
+
+bool nsAttrValue::ParseColor(const nsAString& aString) {
+ ResetIfSet();
+
+ // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
+ // the whitespace compression, trimming, or the test for emptiness.
+ // (I'm a little skeptical that we shouldn't do the whitespace
+ // trimming; WebKit also does it.)
+ nsAutoString colorStr(aString);
+ colorStr.CompressWhitespace(true, true);
+ if (colorStr.IsEmpty()) {
+ return false;
+ }
+
+ nscolor color;
+ // No color names begin with a '#'; in standards mode, all acceptable
+ // numeric colors do.
+ if (colorStr.First() == '#') {
+ nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1);
+ if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
+ SetColorValue(color, aString);
+ return true;
+ }
+ } else {
+ if (NS_ColorNameToRGB(colorStr, &color)) {
+ SetColorValue(color, aString);
+ return true;
+ }
+ }
+
+ // FIXME (maybe): HTML5 says we should handle system colors. This
+ // means we probably need another storage type, since we'd need to
+ // handle dynamic changes. However, I think this is a bad idea:
+ // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html
+
+ // Use NS_LooseHexToRGB as a fallback if nothing above worked.
+ if (NS_LooseHexToRGB(colorStr, &color)) {
+ SetColorValue(color, aString);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
+ ResetIfSet();
+
+ nsresult ec;
+ double val = PromiseFlatString(aString).ToDouble(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mDoubleValue = val;
+ cont->mType = eDoubleValue;
+ nsAutoString serializedFloat;
+ serializedFloat.AppendFloat(val);
+ SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString);
+ return true;
+}
+
+bool nsAttrValue::ParseStyleAttribute(const nsAString& aString,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsStyledElement* aElement) {
+ dom::Document* doc = aElement->OwnerDoc();
+ nsHTMLCSSStyleSheet* sheet = doc->GetInlineStyleSheet();
+ NS_ASSERTION(aElement->NodePrincipal() == doc->NodePrincipal(),
+ "This is unexpected");
+
+ nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal
+ : aElement->NodePrincipal();
+ RefPtr<URLExtraData> data = aElement->GetURLDataForStyleAttr(principal);
+
+ // If the (immutable) document URI does not match the element's base URI
+ // (the common case is that they do match) do not cache the rule. This is
+ // because the results of the CSS parser are dependent on these URIs, and we
+ // do not want to have to account for the URIs in the hash lookup.
+ // Similarly, if the triggering principal does not match the node principal,
+ // do not cache the rule, since the principal will be encoded in any parsed
+ // URLs in the rule.
+ const bool cachingAllowed = sheet &&
+ doc->GetDocumentURI() == data->BaseURI() &&
+ principal == aElement->NodePrincipal();
+ if (cachingAllowed) {
+ MiscContainer* cont = sheet->LookupStyleAttr(aString);
+ if (cont) {
+ // Set our MiscContainer to the cached one.
+ NS_ADDREF(cont);
+ SetPtrValueAndType(cont, eOtherBase);
+ return true;
+ }
+ }
+
+ RefPtr<DeclarationBlock> decl =
+ DeclarationBlock::FromCssText(aString, data, doc->GetCompatibilityMode(),
+ doc->CSSLoader(), StyleCssRuleType::Style);
+ if (!decl) {
+ return false;
+ }
+ decl->SetHTMLCSSStyleSheet(sheet);
+ SetTo(decl.forget(), &aString);
+
+ if (cachingAllowed) {
+ MiscContainer* cont = GetMiscContainer();
+ cont->Cache();
+ }
+
+ return true;
+}
+
+void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) {
+ NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
+ NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(),
+ "Trying to re-set atom or string!");
+ if (aValue) {
+ uint32_t len = aValue->Length();
+ // * We're allowing eCSSDeclaration attributes to store empty
+ // strings as it can be beneficial to store an empty style
+ // attribute as a parsed rule.
+ // * We're allowing enumerated values because sometimes the empty
+ // string corresponds to a particular enumerated value, especially
+ // for enumerated values that are not limited enumerated.
+ // Add other types as needed.
+ NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum,
+ "Empty string?");
+ MiscContainer* cont = GetMiscContainer();
+
+ if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
+ nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal())
+ ? NS_AtomizeMainThread(*aValue).take()
+ : NS_Atomize(*aValue).take();
+ NS_ENSURE_TRUE_VOID(atom);
+ uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase;
+
+ // In the common case we're not in the servo traversal, and we can just
+ // set the bits normally. The parallel case requires more care.
+ if (MOZ_LIKELY(!IsInServoTraversal())) {
+ cont->SetStringBitsMainThread(bits);
+ } else if (!cont->mStringBits.compareExchange(0, bits)) {
+ // We raced with somebody else setting the bits. Release our copy.
+ atom->Release();
+ }
+ } else {
+ nsStringBuffer* buffer = GetStringBuffer(*aValue).take();
+ NS_ENSURE_TRUE_VOID(buffer);
+ uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase;
+
+ // In the common case we're not in the servo traversal, and we can just
+ // set the bits normally. The parallel case requires more care.
+ if (MOZ_LIKELY(!IsInServoTraversal())) {
+ cont->SetStringBitsMainThread(bits);
+ } else if (!cont->mStringBits.compareExchange(0, bits)) {
+ // We raced with somebody else setting the bits. Release our copy.
+ buffer->Release();
+ }
+ }
+ }
+}
+
+void nsAttrValue::ResetMiscAtomOrString() {
+ MiscContainer* cont = GetMiscContainer();
+ bool isString;
+ if (void* ptr = cont->GetStringOrAtomPtr(isString)) {
+ if (isString) {
+ static_cast<nsStringBuffer*>(ptr)->Release();
+ } else {
+ static_cast<nsAtom*>(ptr)->Release();
+ }
+ cont->SetStringBitsMainThread(0);
+ }
+}
+
+void nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
+ const nsAString* aSerialized) {
+ MOZ_ASSERT(IsSVGType(aType), "Not an SVG type");
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ // All SVG types are just pointers to classes so just setting any of them
+ // will do. We'll lose type-safety but the signature of the calling
+ // function should ensure we don't get anything unexpected, and once we
+ // stick aValue in a union we lose type information anyway.
+ cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue);
+ cont->mType = aType;
+ SetMiscAtomOrString(aSerialized);
+}
+
+MiscContainer* nsAttrValue::ClearMiscContainer() {
+ MiscContainer* cont = nullptr;
+ if (BaseType() == eOtherBase) {
+ cont = GetMiscContainer();
+ if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
+ // This MiscContainer is shared, we need a new one.
+ NS_RELEASE(cont);
+
+ cont = AllocMiscContainer();
+ SetPtrValueAndType(cont, eOtherBase);
+ } else {
+ switch (cont->mType) {
+ case eCSSDeclaration: {
+ MOZ_ASSERT(cont->mValue.mRefCount == 1);
+ cont->Release();
+ cont->Evict();
+ NS_RELEASE(cont->mValue.mCSSDeclaration);
+ break;
+ }
+ case eShadowParts: {
+ MOZ_ASSERT(cont->mValue.mRefCount == 1);
+ cont->Release();
+ delete cont->mValue.mShadowParts;
+ break;
+ }
+ case eURL: {
+ NS_RELEASE(cont->mValue.mURL);
+ break;
+ }
+ case eAtomArray: {
+ delete cont->mValue.mAtomArray;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ ResetMiscAtomOrString();
+ } else {
+ ResetIfSet();
+ }
+
+ return cont;
+}
+
+MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
+ MiscContainer* cont = ClearMiscContainer();
+ if (cont) {
+ MOZ_ASSERT(BaseType() == eOtherBase);
+ ResetMiscAtomOrString();
+ cont = GetMiscContainer();
+ } else {
+ cont = AllocMiscContainer();
+ SetPtrValueAndType(cont, eOtherBase);
+ }
+
+ return cont;
+}
+
+bool nsAttrValue::EnsureEmptyAtomArray() {
+ if (Type() == eAtomArray) {
+ ResetMiscAtomOrString();
+ GetAtomArrayValue()->Clear();
+ return true;
+ }
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mValue.mAtomArray = new AttrAtomArray;
+ cont->mType = eAtomArray;
+
+ return true;
+}
+
+already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer(
+ const nsAString& aValue) const {
+ uint32_t len = aValue.Length();
+ if (!len) {
+ return nullptr;
+ }
+
+ RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue);
+ if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
+ return buf.forget();
+ }
+
+ buf = nsStringBuffer::Alloc((len + 1) * sizeof(char16_t));
+ if (!buf) {
+ return nullptr;
+ }
+ char16_t* data = static_cast<char16_t*>(buf->Data());
+ CopyUnicodeTo(aValue, 0, data, len);
+ data[len] = char16_t(0);
+ return buf.forget();
+}
+
+size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+
+ switch (BaseType()) {
+ case eStringBase: {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
+ break;
+ }
+ case eOtherBase: {
+ MiscContainer* container = GetMiscContainer();
+ if (!container) {
+ break;
+ }
+ if (container->IsRefCounted() && container->mValue.mRefCount > 1) {
+ // We don't report this MiscContainer at all in order to avoid
+ // twice-reporting it.
+ // TODO DMD, bug 1027551 - figure out how to report this ref-counted
+ // object just once.
+ break;
+ }
+ n += aMallocSizeOf(container);
+
+ // We only count the size of the object pointed by otherPtr if it's a
+ // string. When it's an atom, it's counted separatly.
+ if (nsStringBuffer* buf = container->GetStoredStringBuffer()) {
+ n += buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) {
+ // TODO: mCSSDeclaration might be owned by another object which
+ // would make us count them twice, bug 677493.
+ // Bug 1281964: For DeclarationBlock if we do measure we'll
+ // need a way to call the Servo heap_size_of function.
+ // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
+ } else if (Type() == eAtomArray && container->mValue.mAtomArray) {
+ // Don't measure each nsAtom, they are measured separatly.
+ n += container->mValue.mAtomArray->mArray.ShallowSizeOfIncludingThis(
+ aMallocSizeOf);
+ }
+ break;
+ }
+ case eAtomBase: // Atoms are counted separately.
+ case eIntegerBase: // The value is in mBits, nothing to do.
+ break;
+ }
+
+ return n;
+}
diff --git a/dom/base/nsAttrValue.h b/dom/base/nsAttrValue.h
new file mode 100644
index 0000000000..fccaa15831
--- /dev/null
+++ b/dom/base/nsAttrValue.h
@@ -0,0 +1,581 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A struct that represents the value (type and actual data) of an
+ * attribute.
+ */
+
+#ifndef nsAttrValue_h___
+#define nsAttrValue_h___
+
+#include <type_traits>
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsColor.h"
+#include "nsCaseTreatment.h"
+#include "nsMargin.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsAtom.h"
+#include "mozilla/AtomArray.h"
+#include "mozilla/EnumTypeTraits.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsIPrincipal;
+class nsIURI;
+class nsStyledElement;
+struct MiscContainer;
+
+namespace mozilla {
+class DeclarationBlock;
+class ShadowParts;
+class SVGAnimatedIntegerPair;
+class SVGAnimatedLength;
+class SVGAnimatedNumberPair;
+class SVGAnimatedOrient;
+class SVGAnimatedPreserveAspectRatio;
+class SVGAnimatedViewBox;
+class SVGLengthList;
+class SVGNumberList;
+class SVGPathData;
+class SVGPointList;
+class SVGStringList;
+class SVGTransformList;
+
+struct AttrAtomArray {
+ AtomArray mArray;
+ bool mMayContainDuplicates = false;
+ void RemoveDuplicates() {
+ if (mMayContainDuplicates) {
+ DoRemoveDuplicates();
+ }
+ }
+ AttrAtomArray Clone() const {
+ return {mArray.Clone(), mMayContainDuplicates};
+ }
+ void Clear() {
+ mArray.Clear();
+ mMayContainDuplicates = false;
+ }
+ bool operator==(const AttrAtomArray& aOther) const {
+ return mArray == aOther.mArray;
+ }
+
+ private:
+ void DoRemoveDuplicates();
+};
+
+namespace dom {
+class DOMString;
+}
+} // namespace mozilla
+
+#define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
+
+const uintptr_t NS_ATTRVALUE_BASETYPE_MASK = 3;
+#define NS_ATTRVALUE_POINTERVALUE_MASK (~NS_ATTRVALUE_BASETYPE_MASK)
+
+#define NS_ATTRVALUE_INTEGERTYPE_BITS 4
+#define NS_ATTRVALUE_INTEGERTYPE_MASK \
+ (uintptr_t((1 << NS_ATTRVALUE_INTEGERTYPE_BITS) - 1))
+#define NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER (1 << NS_ATTRVALUE_INTEGERTYPE_BITS)
+#define NS_ATTRVALUE_INTEGERTYPE_MAXVALUE \
+ ((1 << (31 - NS_ATTRVALUE_INTEGERTYPE_BITS)) - 1)
+#define NS_ATTRVALUE_INTEGERTYPE_MINVALUE \
+ (-NS_ATTRVALUE_INTEGERTYPE_MAXVALUE - 1)
+
+#define NS_ATTRVALUE_ENUMTABLEINDEX_BITS \
+ (32 - 16 - NS_ATTRVALUE_INTEGERTYPE_BITS)
+#define NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER \
+ (1 << (NS_ATTRVALUE_ENUMTABLEINDEX_BITS - 1))
+#define NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE \
+ (NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER - 1)
+#define NS_ATTRVALUE_ENUMTABLEINDEX_MASK \
+ (uintptr_t((((1 << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) - 1) & \
+ ~NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER)))
+
+/**
+ * A class used to construct a nsString from a nsStringBuffer (we might
+ * want to move this to nsString at some point).
+ *
+ * WARNING: Note that nsCheapString doesn't take an explicit length -- it
+ * assumes the string is maximally large, given the nsStringBuffer's storage
+ * size. This means the given string buffer *must* be sized exactly correctly
+ * for the string it contains (including one byte for a null terminator). If
+ * it has any unused storage space, then that will result in bogus characters
+ * at the end of our nsCheapString.
+ */
+class nsCheapString : public nsString {
+ public:
+ explicit nsCheapString(nsStringBuffer* aBuf) {
+ if (aBuf) aBuf->ToString(aBuf->StorageSize() / sizeof(char16_t) - 1, *this);
+ }
+};
+
+class nsAttrValue {
+ friend struct MiscContainer;
+
+ public:
+ // This has to be the same as in ValueBaseType
+ enum ValueType {
+ eString = 0x00, // 00
+ // 01 this value indicates a 'misc' struct
+ eAtom = 0x02, // 10
+ eInteger = 0x03, // 0011
+ eColor = 0x07, // 0111
+ eEnum = 0x0B, // 1011 This should eventually die
+ ePercent = 0x0F, // 1111
+ // Values below here won't matter, they'll be always stored in the 'misc'
+ // struct.
+ eCSSDeclaration = 0x10,
+ eURL,
+ eImage,
+ eAtomArray,
+ eDoubleValue,
+ // eShadowParts is refcounted in the misc container, as we do copy attribute
+ // values quite a bit (for example to process style invalidation), and the
+ // underlying value could get expensive to copy.
+ eShadowParts,
+ eSVGIntegerPair,
+ eSVGTypesBegin = eSVGIntegerPair,
+ eSVGOrient,
+ eSVGLength,
+ eSVGLengthList,
+ eSVGNumberList,
+ eSVGNumberPair,
+ eSVGPathData,
+ eSVGPointList,
+ eSVGPreserveAspectRatio,
+ eSVGStringList,
+ eSVGTransformList,
+ eSVGViewBox,
+ eSVGTypesEnd = eSVGViewBox,
+ };
+
+ nsAttrValue();
+ nsAttrValue(const nsAttrValue& aOther);
+ explicit nsAttrValue(const nsAString& aValue);
+ explicit nsAttrValue(nsAtom* aValue);
+ nsAttrValue(already_AddRefed<mozilla::DeclarationBlock> aValue,
+ const nsAString* aSerialized);
+ ~nsAttrValue();
+
+ inline const nsAttrValue& operator=(const nsAttrValue& aOther);
+
+ static void Init();
+ static void Shutdown();
+
+ inline ValueType Type() const;
+ // Returns true when this value is self-contained and does not depend on
+ // the state of its associated element.
+ // Returns false when this value depends on the state of its associated
+ // element and may be invalid if that state has been changed by changes to
+ // that element state outside of attribute setting.
+ inline bool StoresOwnData() const;
+
+ void Reset();
+
+ void SetTo(const nsAttrValue& aOther);
+ void SetTo(const nsAString& aValue);
+ void SetTo(nsAtom* aValue);
+ void SetTo(int16_t aInt);
+ void SetTo(int32_t aInt, const nsAString* aSerialized);
+ void SetTo(double aValue, const nsAString* aSerialized);
+ void SetTo(already_AddRefed<mozilla::DeclarationBlock> aValue,
+ const nsAString* aSerialized);
+ void SetTo(nsIURI* aValue, const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedIntegerPair& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedLength& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedNumberPair& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedOrient& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedPreserveAspectRatio& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGAnimatedViewBox& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGLengthList& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGNumberList& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGPathData& aValue, const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGPointList& aValue, const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGStringList& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGTransformList& aValue,
+ const nsAString* aSerialized);
+
+ /**
+ * Sets this object with the string or atom representation of aValue.
+ *
+ * After calling this method, this object will have type eString unless the
+ * type of aValue is eAtom, in which case this object will also have type
+ * eAtom.
+ */
+ void SetToSerialized(const nsAttrValue& aValue);
+
+ void SwapValueWith(nsAttrValue& aOther);
+
+ void ToString(nsAString& aResult) const;
+ inline void ToString(mozilla::dom::DOMString& aResult) const;
+
+ /**
+ * Returns the value of this object as an atom. If necessary, the value will
+ * first be serialised using ToString before converting to an atom.
+ */
+ already_AddRefed<nsAtom> GetAsAtom() const;
+
+ // Methods to get value. These methods do not convert so only use them
+ // to retrieve the datatype that this nsAttrValue has.
+ inline bool IsEmptyString() const;
+ const nsCheapString GetStringValue() const;
+ inline nsAtom* GetAtomValue() const;
+ inline int32_t GetIntegerValue() const;
+ bool GetColorValue(nscolor& aColor) const;
+ inline int16_t GetEnumValue() const;
+ inline double GetPercentValue() const;
+ inline mozilla::AttrAtomArray* GetAtomArrayValue() const;
+ inline mozilla::DeclarationBlock* GetCSSDeclarationValue() const;
+ inline nsIURI* GetURLValue() const;
+ inline double GetDoubleValue() const;
+ inline const mozilla::ShadowParts& GetShadowPartsValue() const;
+
+ /**
+ * Returns the string corresponding to the stored enum value.
+ *
+ * @param aResult the string representing the enum tag
+ * @param aRealTag wheter we want to have the real tag or the saved one
+ */
+ void GetEnumString(nsAString& aResult, bool aRealTag) const;
+
+ // Methods to get access to atoms we may have
+ // Returns the number of atoms we have; 0 if we have none. It's OK
+ // to call this without checking the type first; it handles that.
+ uint32_t GetAtomCount() const;
+ // Returns the atom at aIndex (0-based). Do not call this with
+ // aIndex >= GetAtomCount().
+ nsAtom* AtomAt(int32_t aIndex) const;
+
+ uint32_t HashValue() const;
+ bool Equals(const nsAttrValue& aOther) const;
+ // aCaseSensitive == eIgnoreCase means ASCII case-insenstive matching
+ bool Equals(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
+ bool Equals(const nsAtom* aValue, nsCaseTreatment aCaseSensitive) const;
+ bool HasPrefix(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
+ bool HasSuffix(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
+ bool HasSubstring(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const;
+
+ /**
+ * Compares this object with aOther according to their string representation.
+ *
+ * For example, when called on an object with type eInteger and value 4, and
+ * given aOther of type eString and value "4", EqualsAsStrings will return
+ * true (while Equals will return false).
+ */
+ bool EqualsAsStrings(const nsAttrValue& aOther) const;
+
+ /**
+ * Returns true if this AttrValue is equal to the given atom, or is an
+ * array which contains the given atom.
+ */
+ bool Contains(nsAtom* aValue, nsCaseTreatment aCaseSensitive) const;
+ /**
+ * Returns true if this AttrValue is an atom equal to the given
+ * string, or is an array of atoms which contains the given string.
+ * This always does a case-sensitive comparison.
+ */
+ bool Contains(const nsAString& aValue) const;
+
+ void ParseAtom(const nsAString& aValue);
+ void ParseAtomArray(const nsAString& aValue);
+ void ParseStringOrAtom(const nsAString& aValue);
+
+ /**
+ * Parses an exportparts attribute.
+ *
+ * https://drafts.csswg.org/css-shadow-parts/#parsing-mapping-list
+ */
+ void ParsePartMapping(const nsAString&);
+
+ /**
+ * Structure for a mapping from int (enum) values to strings. When you use
+ * it you generally create an array of them.
+ * Instantiate like this:
+ * EnumTable myTable[] = {
+ * { "string1", 1 },
+ * { "string2", 2 },
+ * { nullptr, 0 }
+ * }
+ */
+ struct EnumTable {
+ // EnumTable can be initialized either with an int16_t value
+ // or a value of an enumeration type that can fit within an int16_t.
+
+ constexpr EnumTable(const char* aTag, int16_t aValue)
+ : tag(aTag), value(aValue) {}
+
+ template <typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ constexpr EnumTable(const char* aTag, T aValue)
+ : tag(aTag), value(static_cast<int16_t>(aValue)) {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int16_t>::value,
+ "aValue must be an enum that fits within int16_t");
+ // TODO: statically assert there are no duplicate values, otherwise
+ // `GetEnumString()` above will return wrong values.
+ }
+
+ /** The string the value maps to */
+ const char* tag;
+ /** The enum value that maps to this string */
+ int16_t value;
+ };
+
+ /**
+ * Parse into an enum value.
+ *
+ * @param aValue the string to find the value for
+ * @param aTable the enumeration to map with
+ * @param aCaseSensitive specify if the parsing has to be case sensitive
+ * @param aDefaultValue if non-null, this function will always return true.
+ * Failure to parse aValue as one of the values in aTable will just
+ * cause aDefaultValue->value to be stored as the enumeration value.
+ * @return whether the enum value was found or not
+ */
+ bool ParseEnumValue(const nsAString& aValue, const EnumTable* aTable,
+ bool aCaseSensitive,
+ const EnumTable* aDefaultValue = nullptr);
+
+ /**
+ * Parse a string into a dimension value. This is similar to
+ * https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
+ * but drops the fractional part of the value for now, until we figure out how
+ * to store that in our nsAttrValue.
+ *
+ * The resulting value (if the parse succeeds) is one of eInteger,
+ * eDoubleValue, or ePercent, depending on whether we found a fractional part
+ * and whether we found '%' at the end of the value.
+ *
+ * @param aInput the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseHTMLDimension(const nsAString& aInput) {
+ return DoParseHTMLDimension(aInput, false);
+ }
+
+ /**
+ * Parse a string into a nonzero dimension value. This implements
+ * https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values
+ * subject to the same constraints as ParseHTMLDimension above.
+ *
+ * @param aInput the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseNonzeroHTMLDimension(const nsAString& aInput) {
+ return DoParseHTMLDimension(aInput, true);
+ }
+
+ /**
+ * Parse a string value into an integer.
+ *
+ * @param aString the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseIntValue(const nsAString& aString) {
+ return ParseIntWithBounds(aString, INT32_MIN, INT32_MAX);
+ }
+
+ /**
+ * Parse a string value into an integer with minimum value and maximum value.
+ *
+ * @param aString the string to parse
+ * @param aMin the minimum value (if value is less it will be bumped up)
+ * @param aMax the maximum value (if value is greater it will be chopped down)
+ * @return whether the value could be parsed
+ */
+ bool ParseIntWithBounds(const nsAString& aString, int32_t aMin,
+ int32_t aMax = INT32_MAX);
+
+ /**
+ * Parse a string value into an integer with a fallback for invalid values.
+ * Also allows clamping to a maximum value to support col/colgroup.span (this
+ * is not per spec right now).
+ *
+ * @param aString the string to parse
+ * @param aDefault the default value
+ * @param aMax the maximum value (if value is greater it will be clamped)
+ */
+ void ParseIntWithFallback(const nsAString& aString, int32_t aDefault,
+ int32_t aMax = INT32_MAX);
+
+ /**
+ * Parse a string value into a non-negative integer.
+ * This method follows the rules for parsing non-negative integer from:
+ * http://dev.w3.org/html5/spec/infrastructure.html#rules-for-parsing-non-negative-integers
+ *
+ * @param aString the string to parse
+ * @return whether the value is valid
+ */
+ bool ParseNonNegativeIntValue(const nsAString& aString);
+
+ /**
+ * Parse a string value into a clamped non-negative integer.
+ * This method follows the rules for parsing non-negative integer from:
+ * https://html.spec.whatwg.org/multipage/infrastructure.html#clamped-to-the-range
+ *
+ * @param aString the string to parse
+ * @param aDefault value to return for negative or invalid values
+ * @param aMin minimum value
+ * @param aMax maximum value
+ */
+ void ParseClampedNonNegativeInt(const nsAString& aString, int32_t aDefault,
+ int32_t aMin, int32_t aMax);
+
+ /**
+ * Parse a string value into a positive integer.
+ * This method follows the rules for parsing non-negative integer from:
+ * http://dev.w3.org/html5/spec/infrastructure.html#rules-for-parsing-non-negative-integers
+ * In addition of these rules, the value has to be greater than zero.
+ *
+ * This is generally used for parsing content attributes which reflecting IDL
+ * attributes are limited to only non-negative numbers greater than zero, see:
+ * http://dev.w3.org/html5/spec/common-dom-interfaces.html#limited-to-only-non-negative-numbers-greater-than-zero
+ *
+ * @param aString the string to parse
+ * @return whether the value was valid
+ */
+ bool ParsePositiveIntValue(const nsAString& aString);
+
+ /**
+ * Parse a string into a color. This implements what HTML5 calls the
+ * "rules for parsing a legacy color value".
+ *
+ * @param aString the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseColor(const nsAString& aString);
+
+ /**
+ * Parse a string value into a double-precision floating point value.
+ *
+ * @param aString the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseDoubleValue(const nsAString& aString);
+
+ /**
+ * Parse a string into a CSS style rule.
+ *
+ * @param aString the style attribute value to be parsed.
+ * @param aElement the element the attribute is set on.
+ * @param aMaybeScriptedPrincipal if available, the scripted principal
+ * responsible for this attribute value, as passed to
+ * Element::ParseAttribute.
+ */
+ bool ParseStyleAttribute(const nsAString& aString,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsStyledElement* aElement);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsAtom* GetStoredAtom() const;
+ nsStringBuffer* GetStoredStringBuffer() const;
+
+ private:
+ // These have to be the same as in ValueType
+ enum ValueBaseType {
+ eStringBase = eString, // 00
+ eOtherBase = 0x01, // 01
+ eAtomBase = eAtom, // 10
+ eIntegerBase = 0x03 // 11
+ };
+
+ inline ValueBaseType BaseType() const;
+ inline bool IsSVGType(ValueType aType) const;
+
+ /**
+ * Get the index of an EnumTable in the sEnumTableArray.
+ * If the EnumTable is not in the sEnumTableArray, it is added.
+ *
+ * @param aTable the EnumTable to get the index of.
+ * @return the index of the EnumTable.
+ */
+ int16_t GetEnumTableIndex(const EnumTable* aTable);
+
+ inline void SetPtrValueAndType(void* aValue, ValueBaseType aType);
+ void SetIntValueAndType(int32_t aValue, ValueType aType,
+ const nsAString* aStringValue);
+ // aType can be ePercent or eDoubleValue.
+ void SetDoubleValueAndType(double aValue, ValueType aType,
+ const nsAString* aStringValue);
+ void SetColorValue(nscolor aColor, const nsAString& aString);
+ void SetMiscAtomOrString(const nsAString* aValue);
+ void ResetMiscAtomOrString();
+ void SetSVGType(ValueType aType, const void* aValue,
+ const nsAString* aSerialized);
+ inline void ResetIfSet();
+
+ inline void* GetPtr() const;
+ inline MiscContainer* GetMiscContainer() const;
+ inline int32_t GetIntInternal() const;
+
+ // Clears the current MiscContainer. This will return null if there is no
+ // existing container.
+ MiscContainer* ClearMiscContainer();
+ // Like ClearMiscContainer, except allocates a new container if one does not
+ // exist already.
+ MiscContainer* EnsureEmptyMiscContainer();
+ bool EnsureEmptyAtomArray();
+ already_AddRefed<nsStringBuffer> GetStringBuffer(
+ const nsAString& aValue) const;
+ // Given an enum table and a particular entry in that table, return
+ // the actual integer value we should store.
+ int32_t EnumTableEntryToValue(const EnumTable* aEnumTable,
+ const EnumTable* aTableEntry);
+
+ template <typename F>
+ bool SubstringCheck(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const;
+
+ static MiscContainer* AllocMiscContainer();
+ static void DeallocMiscContainer(MiscContainer* aCont);
+
+ static nsTArray<const EnumTable*>* sEnumTableArray;
+
+ /**
+ * Helper for ParseHTMLDimension and ParseNonzeroHTMLDimension.
+ *
+ * @param aInput the string to parse
+ * @param aEnsureNonzero whether to fail the parse if the value is 0
+ * @return whether the value could be parsed
+ */
+ bool DoParseHTMLDimension(const nsAString& aInput, bool aEnsureNonzero);
+
+ uintptr_t mBits;
+};
+
+inline const nsAttrValue& nsAttrValue::operator=(const nsAttrValue& aOther) {
+ SetTo(aOther);
+ return *this;
+}
+
+inline nsAttrValue::ValueBaseType nsAttrValue::BaseType() const {
+ return static_cast<ValueBaseType>(mBits & NS_ATTRVALUE_BASETYPE_MASK);
+}
+
+inline void* nsAttrValue::GetPtr() const {
+ NS_ASSERTION(BaseType() != eIntegerBase, "getting pointer from non-pointer");
+ return reinterpret_cast<void*>(mBits & NS_ATTRVALUE_POINTERVALUE_MASK);
+}
+
+inline bool nsAttrValue::IsEmptyString() const { return !mBits; }
+
+#endif
diff --git a/dom/base/nsAttrValueInlines.h b/dom/base/nsAttrValueInlines.h
new file mode 100644
index 0000000000..b537b32b0c
--- /dev/null
+++ b/dom/base/nsAttrValueInlines.h
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAttrValueInlines_h__
+#define nsAttrValueInlines_h__
+
+#include <stdint.h>
+
+#include "nsAttrValue.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/dom/DOMString.h"
+
+namespace mozilla {
+class ShadowParts;
+}
+
+struct MiscContainer final {
+ using ValueType = nsAttrValue::ValueType;
+
+ ValueType mType;
+ // mStringBits points to either nsAtom* or nsStringBuffer* and is used when
+ // mType isn't eCSSDeclaration.
+ // Note eStringBase and eAtomBase is used also to handle the type of
+ // mStringBits.
+ //
+ // Note that we use an atomic here so that we can use Compare-And-Swap
+ // to cache the serialization during the parallel servo traversal. This case
+ // (which happens when the main thread is blocked) is the only case where
+ // mStringBits is mutated off-main-thread. The Atomic needs to be
+ // ReleaseAcquire so that the pointer to the serialization does not become
+ // observable to other threads before the initialization of the pointed-to
+ // memory is also observable.
+ mozilla::Atomic<uintptr_t, mozilla::ReleaseAcquire> mStringBits;
+ union {
+ struct {
+ union {
+ int32_t mInteger;
+ nscolor mColor;
+ uint32_t mEnumValue;
+ mozilla::DeclarationBlock* mCSSDeclaration;
+ nsIURI* mURL;
+ mozilla::AttrAtomArray* mAtomArray;
+ const mozilla::ShadowParts* mShadowParts;
+ const mozilla::SVGAnimatedIntegerPair* mSVGAnimatedIntegerPair;
+ const mozilla::SVGAnimatedLength* mSVGLength;
+ const mozilla::SVGAnimatedNumberPair* mSVGAnimatedNumberPair;
+ const mozilla::SVGAnimatedOrient* mSVGAnimatedOrient;
+ const mozilla::SVGAnimatedPreserveAspectRatio*
+ mSVGAnimatedPreserveAspectRatio;
+ const mozilla::SVGAnimatedViewBox* mSVGAnimatedViewBox;
+ const mozilla::SVGLengthList* mSVGLengthList;
+ const mozilla::SVGNumberList* mSVGNumberList;
+ const mozilla::SVGPathData* mSVGPathData;
+ const mozilla::SVGPointList* mSVGPointList;
+ const mozilla::SVGStringList* mSVGStringList;
+ const mozilla::SVGTransformList* mSVGTransformList;
+ };
+ uint32_t mRefCount : 31;
+ uint32_t mCached : 1;
+ } mValue;
+ double mDoubleValue;
+ };
+
+ MiscContainer() : mType(nsAttrValue::eColor), mStringBits(0) {
+ MOZ_COUNT_CTOR(MiscContainer);
+ mValue.mColor = 0;
+ mValue.mRefCount = 0;
+ mValue.mCached = 0;
+ }
+
+ protected:
+ // Only nsAttrValue should be able to delete us.
+ friend class nsAttrValue;
+
+ ~MiscContainer() {
+ if (IsRefCounted()) {
+ MOZ_ASSERT(mValue.mRefCount == 0);
+ MOZ_ASSERT(!mValue.mCached);
+ }
+ MOZ_COUNT_DTOR(MiscContainer);
+ }
+
+ public:
+ bool GetString(nsAString& aString) const;
+
+ void* GetStringOrAtomPtr(bool& aIsString) const {
+ uintptr_t bits = mStringBits;
+ aIsString =
+ nsAttrValue::ValueBaseType(mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
+ nsAttrValue::eStringBase;
+ return reinterpret_cast<void*>(bits & NS_ATTRVALUE_POINTERVALUE_MASK);
+ }
+
+ nsAtom* GetStoredAtom() const {
+ bool isString = false;
+ void* ptr = GetStringOrAtomPtr(isString);
+ return isString ? nullptr : static_cast<nsAtom*>(ptr);
+ }
+
+ nsStringBuffer* GetStoredStringBuffer() const {
+ bool isString = false;
+ void* ptr = GetStringOrAtomPtr(isString);
+ return isString ? static_cast<nsStringBuffer*>(ptr) : nullptr;
+ }
+
+ void SetStringBitsMainThread(uintptr_t aBits) {
+ // mStringBits is atomic, but the callers of this function are
+ // single-threaded so they don't have to worry about it.
+ MOZ_ASSERT(!mozilla::IsInServoTraversal());
+ MOZ_ASSERT(NS_IsMainThread());
+ mStringBits = aBits;
+ }
+
+ inline bool IsRefCounted() const {
+ // Nothing stops us from refcounting (and sharing) other types of
+ // MiscContainer (except eDoubleValue types) but there's no compelling
+ // reason to.
+ return mType == nsAttrValue::eCSSDeclaration ||
+ mType == nsAttrValue::eShadowParts;
+ }
+
+ inline int32_t AddRef() {
+ MOZ_ASSERT(IsRefCounted());
+ return ++mValue.mRefCount;
+ }
+
+ inline int32_t Release() {
+ MOZ_ASSERT(IsRefCounted());
+ return --mValue.mRefCount;
+ }
+
+ void Cache();
+ void Evict();
+};
+
+/**
+ * Implementation of inline methods
+ */
+
+inline int32_t nsAttrValue::GetIntegerValue() const {
+ MOZ_ASSERT(Type() == eInteger, "wrong type");
+ return (BaseType() == eIntegerBase) ? GetIntInternal()
+ : GetMiscContainer()->mValue.mInteger;
+}
+
+inline int16_t nsAttrValue::GetEnumValue() const {
+ MOZ_ASSERT(Type() == eEnum, "wrong type");
+ // We don't need to worry about sign extension here since we're
+ // returning an int16_t which will cut away the top bits.
+ return static_cast<int16_t>(((BaseType() == eIntegerBase)
+ ? static_cast<uint32_t>(GetIntInternal())
+ : GetMiscContainer()->mValue.mEnumValue) >>
+ NS_ATTRVALUE_ENUMTABLEINDEX_BITS);
+}
+
+inline double nsAttrValue::GetPercentValue() const {
+ MOZ_ASSERT(Type() == ePercent, "wrong type");
+ if (BaseType() == eIntegerBase) {
+ return GetIntInternal() / 100.0f;
+ }
+ return GetMiscContainer()->mDoubleValue / 100.0f;
+}
+
+inline mozilla::AttrAtomArray* nsAttrValue::GetAtomArrayValue() const {
+ MOZ_ASSERT(Type() == eAtomArray, "wrong type");
+ return GetMiscContainer()->mValue.mAtomArray;
+}
+
+inline mozilla::DeclarationBlock* nsAttrValue::GetCSSDeclarationValue() const {
+ MOZ_ASSERT(Type() == eCSSDeclaration, "wrong type");
+ return GetMiscContainer()->mValue.mCSSDeclaration;
+}
+
+inline nsIURI* nsAttrValue::GetURLValue() const {
+ MOZ_ASSERT(Type() == eURL, "wrong type");
+ return GetMiscContainer()->mValue.mURL;
+}
+
+inline double nsAttrValue::GetDoubleValue() const {
+ MOZ_ASSERT(Type() == eDoubleValue, "wrong type");
+ return GetMiscContainer()->mDoubleValue;
+}
+
+inline bool nsAttrValue::IsSVGType(ValueType aType) const {
+ return aType >= eSVGTypesBegin && aType <= eSVGTypesEnd;
+}
+
+inline bool nsAttrValue::StoresOwnData() const {
+ if (BaseType() != eOtherBase) {
+ return true;
+ }
+ ValueType t = Type();
+ return t != eCSSDeclaration && !IsSVGType(t);
+}
+
+inline void nsAttrValue::SetPtrValueAndType(void* aValue, ValueBaseType aType) {
+ NS_ASSERTION(!(NS_PTR_TO_INT32(aValue) & ~NS_ATTRVALUE_POINTERVALUE_MASK),
+ "pointer not properly aligned, this will crash");
+ mBits = reinterpret_cast<intptr_t>(aValue) | aType;
+}
+
+inline void nsAttrValue::ResetIfSet() {
+ if (mBits) {
+ Reset();
+ }
+}
+
+inline MiscContainer* nsAttrValue::GetMiscContainer() const {
+ NS_ASSERTION(BaseType() == eOtherBase, "wrong type");
+ return static_cast<MiscContainer*>(GetPtr());
+}
+
+inline int32_t nsAttrValue::GetIntInternal() const {
+ NS_ASSERTION(BaseType() == eIntegerBase, "getting integer from non-integer");
+ // Make sure we get a signed value.
+ // Lets hope the optimizer optimizes this into a shift. Unfortunatly signed
+ // bitshift right is implementaion dependant.
+ return static_cast<int32_t>(mBits & ~NS_ATTRVALUE_INTEGERTYPE_MASK) /
+ NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER;
+}
+
+inline nsAttrValue::ValueType nsAttrValue::Type() const {
+ switch (BaseType()) {
+ case eIntegerBase: {
+ return static_cast<ValueType>(mBits & NS_ATTRVALUE_INTEGERTYPE_MASK);
+ }
+ case eOtherBase: {
+ return GetMiscContainer()->mType;
+ }
+ default: {
+ return static_cast<ValueType>(static_cast<uint16_t>(BaseType()));
+ }
+ }
+}
+
+inline nsAtom* nsAttrValue::GetAtomValue() const {
+ MOZ_ASSERT(Type() == eAtom, "wrong type");
+ return reinterpret_cast<nsAtom*>(GetPtr());
+}
+
+inline void nsAttrValue::ToString(mozilla::dom::DOMString& aResult) const {
+ switch (Type()) {
+ case eString: {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ aResult.SetKnownLiveStringBuffer(
+ str, str->StorageSize() / sizeof(char16_t) - 1);
+ }
+ // else aResult is already empty
+ return;
+ }
+ case eAtom: {
+ nsAtom* atom = static_cast<nsAtom*>(GetPtr());
+ aResult.SetKnownLiveAtom(atom, mozilla::dom::DOMString::eNullNotExpected);
+ break;
+ }
+ default: {
+ ToString(aResult.AsAString());
+ }
+ }
+}
+
+inline const mozilla::ShadowParts& nsAttrValue::GetShadowPartsValue() const {
+ MOZ_ASSERT(Type() == eShadowParts);
+ return *GetMiscContainer()->mValue.mShadowParts;
+}
+
+#endif
diff --git a/dom/base/nsAttrValueOrString.cpp b/dom/base/nsAttrValueOrString.cpp
new file mode 100644
index 0000000000..cedf4fad43
--- /dev/null
+++ b/dom/base/nsAttrValueOrString.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAttrValueOrString.h"
+#include "nsAttrValueInlines.h"
+
+const nsAString& nsAttrValueOrString::String() const {
+ if (mStringPtr) {
+ return *mStringPtr;
+ }
+
+ if (!mAttrValue) {
+ mStringPtr = &mCheapString;
+ return *mStringPtr;
+ }
+
+ if (mAttrValue->Type() == nsAttrValue::eString) {
+ mCheapString = mAttrValue->GetStringValue();
+ mStringPtr = &mCheapString;
+ return *mStringPtr;
+ }
+
+ mAttrValue->ToString(mCheapString);
+ mStringPtr = &mCheapString;
+ return *mStringPtr;
+}
diff --git a/dom/base/nsAttrValueOrString.h b/dom/base/nsAttrValueOrString.h
new file mode 100644
index 0000000000..9cb3db33c5
--- /dev/null
+++ b/dom/base/nsAttrValueOrString.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A wrapper to contain either an nsAttrValue or an nsAString. This is useful
+ * because constructing an nsAttrValue from an nsAString can be expensive when
+ * the buffer of the string is not shared.
+ *
+ * This treats nsAttrValueOrString(nullptr) as the empty string,
+ * to help with contexts where a null pointer denotes an empty value.
+ *
+ * Since a raw pointer to the passed-in string is kept, this class should only
+ * be used on the stack.
+ */
+
+#ifndef nsAttrValueOrString_h___
+#define nsAttrValueOrString_h___
+
+#include "nsString.h"
+#include "nsAttrValue.h"
+
+class MOZ_STACK_CLASS nsAttrValueOrString {
+ public:
+ explicit nsAttrValueOrString(const nsAString& aValue)
+ : mAttrValue(nullptr), mStringPtr(&aValue), mCheapString(nullptr) {}
+
+ explicit nsAttrValueOrString(const nsAString* aValue)
+ : mAttrValue(nullptr), mStringPtr(aValue), mCheapString(nullptr) {}
+
+ explicit nsAttrValueOrString(const nsAttrValue& aValue)
+ : mAttrValue(&aValue), mStringPtr(nullptr), mCheapString(nullptr) {}
+
+ explicit nsAttrValueOrString(const nsAttrValue* aValue)
+ : mAttrValue(aValue), mStringPtr(nullptr), mCheapString(nullptr) {}
+
+ void ResetToAttrValue(const nsAttrValue& aValue) {
+ mAttrValue = &aValue;
+ mStringPtr = nullptr;
+ // No need to touch mCheapString here. If we need to use it, we will reset
+ // it to the rigthe value anyway.
+ }
+
+ /**
+ * Returns a reference to the string value of the contents of this object.
+ *
+ * When this object points to a string or an nsAttrValue of string or atom
+ * type this should be fairly cheap. Other nsAttrValue types will be
+ * serialized the first time this is called and cached from thereon.
+ */
+ const nsAString& String() const;
+
+ /**
+ * Compares the string representation of this object with the string
+ * representation of an nsAttrValue.
+ */
+ bool EqualsAsStrings(const nsAttrValue& aOther) const {
+ if (mStringPtr) {
+ return aOther.Equals(*mStringPtr, eCaseMatters);
+ }
+ return aOther.EqualsAsStrings(*mAttrValue);
+ }
+
+ /*
+ * Returns true if the value stored is empty
+ */
+ bool IsEmpty() const {
+ if (mStringPtr) {
+ return mStringPtr->IsEmpty();
+ }
+ if (mAttrValue) {
+ return mAttrValue->IsEmptyString();
+ }
+ return true;
+ }
+
+ protected:
+ const nsAttrValue* mAttrValue;
+ mutable const nsAString* mStringPtr;
+ mutable nsCheapString mCheapString;
+};
+
+#endif // nsAttrValueOrString_h___
diff --git a/dom/base/nsCCUncollectableMarker.cpp b/dom/base/nsCCUncollectableMarker.cpp
new file mode 100644
index 0000000000..79291f727f
--- /dev/null
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCCUncollectableMarker.h"
+#include "nsIObserverService.h"
+#include "nsIDocShell.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIContentViewer.h"
+#include "mozilla/dom/Document.h"
+#include "InProcessBrowserChildMessageManager.h"
+#include "nsIWindowMediator.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsISHistory.h"
+#include "nsISHEntry.h"
+#include "nsIWindowWatcher.h"
+#include "mozilla/Services.h"
+#include "nsIAppWindow.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsJSEnvironment.h"
+#include "nsFrameLoader.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/ContentProcessMessageManager.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "xpcpublic.h"
+#include "nsObserverService.h"
+#include "nsFocusManager.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULRuntime.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static bool sInited = 0;
+// The initial value of sGeneration should not be the same as the
+// value it is given at xpcom-shutdown, because this will make any GCs
+// before we first CC benignly violate the black-gray invariant, due
+// to dom::TraceBlackJS().
+uint32_t nsCCUncollectableMarker::sGeneration = 1;
+#include "nsXULPrototypeCache.h"
+
+NS_IMPL_ISUPPORTS(nsCCUncollectableMarker, nsIObserver)
+
+/* static */
+nsresult nsCCUncollectableMarker::Init() {
+ if (sInited) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserver> marker = new nsCCUncollectableMarker;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ // This makes the observer service hold an owning reference to the marker
+ rv = obs->AddObserver(marker, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(marker, "cycle-collector-begin", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = obs->AddObserver(marker, "cycle-collector-forget-skippable", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sInited = true;
+
+ return NS_OK;
+}
+
+static void MarkChildMessageManagers(MessageBroadcaster* aMM) {
+ aMM->MarkForCC();
+
+ uint32_t browserChildCount = aMM->ChildCount();
+ for (uint32_t j = 0; j < browserChildCount; ++j) {
+ RefPtr<MessageListenerManager> childMM = aMM->GetChildAt(j);
+ if (!childMM) {
+ continue;
+ }
+
+ RefPtr<MessageBroadcaster> strongNonLeafMM =
+ MessageBroadcaster::From(childMM);
+ MessageBroadcaster* nonLeafMM = strongNonLeafMM;
+
+ MessageListenerManager* tabMM = childMM;
+
+ strongNonLeafMM = nullptr;
+ childMM = nullptr;
+
+ if (nonLeafMM) {
+ MarkChildMessageManagers(nonLeafMM);
+ continue;
+ }
+
+ tabMM->MarkForCC();
+
+ // XXX hack warning, but works, since we know that
+ // callback is frameloader.
+ mozilla::dom::ipc::MessageManagerCallback* cb = tabMM->GetCallback();
+ if (cb) {
+ nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
+ InProcessBrowserChildMessageManager* et =
+ fl->GetBrowserChildMessageManager();
+ if (!et) {
+ continue;
+ }
+ et->MarkForCC();
+ EventListenerManager* elm = et->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ }
+ }
+}
+
+static void MarkMessageManagers() {
+ if (nsFrameMessageManager::GetChildProcessManager()) {
+ // ContentProcessMessageManager's MarkForCC also marks ChildProcessManager.
+ ContentProcessMessageManager* pg = ContentProcessMessageManager::Get();
+ if (pg) {
+ pg->MarkForCC();
+ }
+ }
+
+ // The global message manager only exists in the root process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+ RefPtr<ChromeMessageBroadcaster> strongGlobalMM =
+ nsFrameMessageManager::GetGlobalMessageManager();
+ if (!strongGlobalMM) {
+ return;
+ }
+ ChromeMessageBroadcaster* globalMM = strongGlobalMM;
+ strongGlobalMM = nullptr;
+ MarkChildMessageManagers(globalMM);
+
+ if (nsFrameMessageManager::sParentProcessManager) {
+ nsFrameMessageManager::sParentProcessManager->MarkForCC();
+ uint32_t childCount =
+ nsFrameMessageManager::sParentProcessManager->ChildCount();
+ for (uint32_t i = 0; i < childCount; ++i) {
+ RefPtr<MessageListenerManager> childMM =
+ nsFrameMessageManager::sParentProcessManager->GetChildAt(i);
+ if (!childMM) {
+ continue;
+ }
+ MessageListenerManager* child = childMM;
+ childMM = nullptr;
+ child->MarkForCC();
+ }
+ }
+ if (nsFrameMessageManager::sSameProcessParentManager) {
+ nsFrameMessageManager::sSameProcessParentManager->MarkForCC();
+ }
+}
+
+void MarkContentViewer(nsIContentViewer* aViewer, bool aCleanupJS) {
+ if (!aViewer) {
+ return;
+ }
+
+ Document* doc = aViewer->GetDocument();
+ if (doc &&
+ doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) {
+ doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
+ if (aCleanupJS) {
+ EventListenerManager* elm = doc->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ RefPtr<nsGlobalWindowInner> win =
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow());
+ if (win) {
+ elm = win->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ win->TimeoutManager().UnmarkGrayTimers();
+ }
+ }
+ }
+ if (doc) {
+ if (nsPIDOMWindowInner* inner = doc->GetInnerWindow()) {
+ inner->MarkUncollectableForCCGeneration(
+ nsCCUncollectableMarker::sGeneration);
+ }
+ if (nsPIDOMWindowOuter* outer = doc->GetWindow()) {
+ outer->MarkUncollectableForCCGeneration(
+ nsCCUncollectableMarker::sGeneration);
+ }
+ }
+}
+
+void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS);
+
+void MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS) {
+ if (!aSHEntry) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> cview;
+ aSHEntry->GetContentViewer(getter_AddRefs(cview));
+ MarkContentViewer(cview, aCleanupJS);
+
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ int32_t i = 0;
+ while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) &&
+ child) {
+ MarkDocShell(child, aCleanupJS);
+ }
+
+ int32_t count;
+ aSHEntry->GetChildCount(&count);
+ for (i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> childEntry;
+ aSHEntry->GetChildAt(i, getter_AddRefs(childEntry));
+ MarkSHEntry(childEntry, aCleanupJS);
+ }
+}
+
+void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(aNode);
+ if (!shell) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> cview;
+ shell->GetContentViewer(getter_AddRefs(cview));
+ MarkContentViewer(cview, aCleanupJS);
+
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell);
+ RefPtr<ChildSHistory> history = webNav->GetSessionHistory();
+ IgnoredErrorResult ignore;
+ nsISHistory* legacyHistory =
+ history ? history->GetLegacySHistory(ignore) : nullptr;
+ if (legacyHistory) {
+ MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent());
+ int32_t historyCount = history->Count();
+ for (int32_t i = 0; i < historyCount; ++i) {
+ nsCOMPtr<nsISHEntry> shEntry;
+ legacyHistory->GetEntryAtIndex(i, getter_AddRefs(shEntry));
+
+ MarkSHEntry(shEntry, aCleanupJS);
+ }
+ }
+
+ int32_t i, childCount;
+ aNode->GetInProcessChildCount(&childCount);
+ for (i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ aNode->GetInProcessChildAt(i, getter_AddRefs(child));
+ MarkDocShell(child, aCleanupJS);
+ }
+}
+
+void MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS) {
+ nsCOMPtr<nsISupports> iter;
+ while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) && iter) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(iter)) {
+ nsCOMPtr<nsIDocShell> rootDocShell = window->GetDocShell();
+
+ MarkDocShell(rootDocShell, aCleanupJS);
+
+ RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(rootDocShell);
+ if (browserChild) {
+ RefPtr<BrowserChildMessageManager> mm =
+ browserChild->GetMessageManager();
+ if (mm) {
+ // MarkForCC ends up calling UnmarkGray on message listeners, which
+ // TraceBlackJS can't do yet.
+ mm->MarkForCC();
+ }
+ }
+ }
+ }
+}
+
+nsresult nsCCUncollectableMarker::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ Element::ClearContentUnbinder();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) return NS_ERROR_FAILURE;
+
+ // No need for kungFuDeathGrip here, yay observerservice!
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ obs->RemoveObserver(this, "cycle-collector-begin");
+ obs->RemoveObserver(this, "cycle-collector-forget-skippable");
+
+ sGeneration = 0;
+
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!strcmp(aTopic, "cycle-collector-begin") ||
+ !strcmp(aTopic, "cycle-collector-forget-skippable"),
+ "wrong topic");
+
+ // JS cleanup can be slow. Do it only if this is the first forget-skippable
+ // after a GC.
+ const bool cleanupJS = nsJSContext::HasHadCleanupSinceLastGC() &&
+ !strcmp(aTopic, "cycle-collector-forget-skippable");
+
+ const bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin");
+ if (prepareForCC) {
+ Element::ClearContentUnbinder();
+ }
+
+ // Increase generation to effectively unmark all current objects
+ if (!++sGeneration) {
+ ++sGeneration;
+ }
+
+ nsFocusManager::MarkUncollectableForCCGeneration(sGeneration);
+
+ nsresult rv;
+
+ // Iterate all toplevel windows
+ nsCOMPtr<nsISimpleEnumerator> windowList;
+ nsCOMPtr<nsIWindowMediator> med = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ if (med) {
+ rv = med->GetEnumerator(nullptr, getter_AddRefs(windowList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MarkWindowList(windowList, cleanupJS);
+ }
+
+ nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ if (ww) {
+ rv = ww->GetWindowEnumerator(getter_AddRefs(windowList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MarkWindowList(windowList, cleanupJS);
+ }
+
+ nsCOMPtr<nsIAppShellService> appShell =
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+ if (appShell) {
+ bool hasHiddenWindow = false;
+ appShell->GetHasHiddenWindow(&hasHiddenWindow);
+ if (hasHiddenWindow) {
+ nsCOMPtr<nsIAppWindow> hw;
+ appShell->GetHiddenWindow(getter_AddRefs(hw));
+ nsCOMPtr<nsIDocShell> shell;
+ hw->GetDocShell(getter_AddRefs(shell));
+ MarkDocShell(shell, cleanupJS);
+ }
+ }
+
+ nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance();
+ if (xulCache) {
+ xulCache->MarkInCCGeneration(sGeneration);
+ }
+
+ enum ForgetSkippableCleanupState {
+ eInitial = 0,
+ eUnmarkJSEventListeners = 1,
+ eUnmarkMessageManagers = 2,
+ eUnmarkStrongObservers = 3,
+ eUnmarkJSHolders = 4,
+ eDone = 5
+ };
+
+ static_assert(eDone == kMajorForgetSkippableCalls,
+ "There must be one forgetSkippable call per cleanup state.");
+
+ static uint32_t sFSState = eDone;
+ if (prepareForCC) {
+ sFSState = eDone;
+ return NS_OK;
+ }
+
+ if (cleanupJS) {
+ // After a GC we start clean up phases from the beginning,
+ // but we don't want to do the additional clean up phases here
+ // since we have done already plenty of gray unmarking while going through
+ // frame message managers and docshells.
+ sFSState = eInitial;
+ return NS_OK;
+ } else {
+ ++sFSState;
+ }
+
+ switch (sFSState) {
+ case eUnmarkJSEventListeners: {
+ nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments();
+ break;
+ }
+ case eUnmarkMessageManagers: {
+ MarkMessageManagers();
+ break;
+ }
+ case eUnmarkStrongObservers: {
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ static_cast<nsObserverService*>(obs.get())->UnmarkGrayStrongObservers();
+ break;
+ }
+ case eUnmarkJSHolders: {
+ xpc_UnmarkSkippableJSHolders();
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+void mozilla::dom::TraceBlackJS(JSTracer* aTrc) {
+ if (!nsCCUncollectableMarker::sGeneration) {
+ return;
+ }
+
+ if (ContentProcessMessageManager::WasCreated() &&
+ nsFrameMessageManager::GetChildProcessManager()) {
+ auto* pg = ContentProcessMessageManager::Get();
+ if (pg) {
+ mozilla::TraceScriptHolder(ToSupports(pg), aTrc);
+ }
+ }
+
+ // Mark globals of active windows black.
+ nsGlobalWindowOuter::OuterWindowByIdTable* windowsById =
+ nsGlobalWindowOuter::GetWindowsTable();
+ if (windowsById) {
+ for (nsGlobalWindowOuter* window : windowsById->Values()) {
+ if (!window->IsCleanedUp()) {
+ nsGlobalWindowInner* inner = nullptr;
+ for (PRCList* win = PR_LIST_HEAD(window); win != window;
+ win = PR_NEXT_LINK(inner)) {
+ inner = static_cast<nsGlobalWindowInner*>(win);
+ if (inner->IsCurrentInnerWindow() ||
+ (inner->GetExtantDoc() &&
+ inner->GetExtantDoc()->GetBFCacheEntry())) {
+ inner->TraceGlobalJSObject(aTrc);
+ EventListenerManager* elm = inner->GetExistingListenerManager();
+ if (elm) {
+ elm->TraceListeners(aTrc);
+ }
+ CustomElementRegistry* cer = inner->GetExistingCustomElements();
+ if (cer) {
+ cer->TraceDefinitions(aTrc);
+ }
+ }
+ }
+
+ if (window->IsRootOuterWindow()) {
+ // In child process trace all the BrowserChildMessageManagers.
+ // Since there is one root outer window per
+ // BrowserChildMessageManager, we need to look for only those windows,
+ // not all.
+ nsIDocShell* ds = window->GetDocShell();
+ if (ds) {
+ nsCOMPtr<nsIBrowserChild> browserChild = ds->GetBrowserChild();
+ if (browserChild) {
+ RefPtr<ContentFrameMessageManager> mm;
+ browserChild->GetMessageManager(getter_AddRefs(mm));
+ if (mm) {
+ nsCOMPtr<nsISupports> browserChildAsSupports =
+ do_QueryInterface(browserChild);
+ mozilla::TraceScriptHolder(browserChildAsSupports, aTrc);
+ EventListenerManager* elm = mm->GetExistingListenerManager();
+ if (elm) {
+ elm->TraceListeners(aTrc);
+ }
+ // As of now there isn't an easy way to trace message listeners.
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dom/base/nsCCUncollectableMarker.h b/dom/base/nsCCUncollectableMarker.h
new file mode 100644
index 0000000000..2948cc0589
--- /dev/null
+++ b/dom/base/nsCCUncollectableMarker.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCCUncollectableMarker_h_
+#define nsCCUncollectableMarker_h_
+
+#include "js/TracingAPI.h"
+#include "mozilla/Attributes.h"
+#include "nsIObserver.h"
+
+class nsCCUncollectableMarker final : public nsIObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ /**
+ * Inits a global nsCCUncollectableMarker. Should only be called once.
+ */
+ static nsresult Init();
+
+ /**
+ * Checks if we're collecting during a given generation
+ */
+ static bool InGeneration(uint32_t aGeneration) {
+ return aGeneration && aGeneration == sGeneration;
+ }
+
+ template <class CCCallback>
+ static bool InGeneration(CCCallback& aCb, uint32_t aGeneration) {
+ return InGeneration(aGeneration) && !aCb.WantAllTraces();
+ }
+
+ static uint32_t sGeneration;
+
+ private:
+ nsCCUncollectableMarker() = default;
+ ~nsCCUncollectableMarker() = default;
+};
+
+namespace mozilla::dom {
+void TraceBlackJS(JSTracer* aTrc);
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/nsCaseTreatment.h b/dom/base/nsCaseTreatment.h
new file mode 100644
index 0000000000..a95efc19be
--- /dev/null
+++ b/dom/base/nsCaseTreatment.h
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsCaseTreatment_h___
+#define nsCaseTreatment_h___
+
+/**
+ * This is the enum used by functions that need to be told whether to
+ * do case-sensitive or case-insensitive string comparisons.
+ */
+enum nsCaseTreatment { eCaseMatters, eIgnoreCase };
+
+#endif /* nsCaseTreatment_h___ */
diff --git a/dom/base/nsChildContentList.h b/dom/base/nsChildContentList.h
new file mode 100644
index 0000000000..af43253cbf
--- /dev/null
+++ b/dom/base/nsChildContentList.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChildContentList_h__
+#define nsChildContentList_h__
+
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsINodeList.h" // base class
+#include "js/TypeDecls.h" // for Handle, Value, JSObject, JSContext
+
+class nsIContent;
+class nsINode;
+
+/**
+ * Class that implements the nsINodeList interface (a list of children of
+ * the content), by holding a reference to the content and delegating Length
+ * and Item to its existing child list.
+ * @see nsINodeList
+ */
+class nsAttrChildContentList : public nsINodeList {
+ public:
+ explicit nsAttrChildContentList(nsINode* aNode) : mNode(aNode) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(nsAttrChildContentList)
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsINodeList interface
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsIContent* Item(uint32_t aIndex) override;
+ uint32_t Length() override;
+ nsINode* GetParentObject() final { return mNode; }
+
+ virtual void InvalidateCacheIfAvailable() {}
+
+ protected:
+ virtual ~nsAttrChildContentList() = default;
+
+ private:
+ // The node whose children make up the list.
+ RefPtr<nsINode> mNode;
+};
+
+class nsParentNodeChildContentList final : public nsAttrChildContentList {
+ public:
+ explicit nsParentNodeChildContentList(nsINode* aNode)
+ : nsAttrChildContentList(aNode), mIsCacheValid(false) {
+ ValidateCache();
+ }
+
+ // nsINodeList interface
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsIContent* Item(uint32_t aIndex) override;
+ uint32_t Length() override;
+
+ void InvalidateCacheIfAvailable() final { InvalidateCache(); }
+
+ void InvalidateCache() {
+ mIsCacheValid = false;
+ mCachedChildArray.Clear();
+ }
+
+ private:
+ ~nsParentNodeChildContentList() = default;
+
+ // Return true if validation succeeds, false otherwise
+ bool ValidateCache();
+
+ // Whether cached array of child nodes is valid
+ bool mIsCacheValid;
+
+ // Cached array of child nodes
+ AutoTArray<nsIContent*, 8> mCachedChildArray;
+};
+
+#endif /* nsChildContentList_h__ */
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp
new file mode 100644
index 0000000000..8990a690df
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -0,0 +1,875 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsReadableUtils.h"
+
+// Local Includes
+#include "nsContentAreaDragDrop.h"
+
+// Helper Classes
+#include "nsString.h"
+
+// Interfaces needed to be included
+#include "nsCopySupport.h"
+#include "nsISelectionController.h"
+#include "nsPIDOMWindow.h"
+#include "nsIFormControl.h"
+#include "nsITransferable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentPolicy.h"
+#include "nsIImageLoadingContent.h"
+#include "nsUnicharUtils.h"
+#include "nsIURL.h"
+#include "nsIURIMutator.h"
+#include "mozilla/dom/Document.h"
+#include "nsICookieJarSettings.h"
+#include "nsIPrincipal.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsEscape.h"
+#include "nsContentUtils.h"
+#include "nsIMIMEService.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "nsIMIMEInfo.h"
+#include "nsRange.h"
+#include "BrowserParent.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/Selection.h"
+#include "nsVariant.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::IgnoreErrors;
+
+class MOZ_STACK_CLASS DragDataProducer {
+ public:
+ DragDataProducer(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed);
+ nsresult Produce(DataTransfer* aDataTransfer, bool* aCanDrag,
+ Selection** aSelection, nsIContent** aDragNode,
+ nsIPrincipal** aPrincipal, nsIContentSecurityPolicy** aCsp,
+ nsICookieJarSettings** aCookieJarSettings);
+
+ private:
+ // @param aHidden true, iff the data should be hidden from non-chrome code.
+ void AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor,
+ const nsAString& aData, nsIPrincipal* aPrincipal,
+ bool aHidden = false);
+ nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
+ DataTransfer* aDataTransfer);
+ nsresult GetImageData(imgIContainer* aImage, imgIRequest* aRequest);
+ static nsresult GetDraggableSelectionData(Selection* inSelection,
+ nsIContent* inRealTargetNode,
+ nsIContent** outImageOrLinkNode,
+ bool* outDragSelectedText);
+ [[nodiscard]] static nsresult GetAnchorURL(nsIContent* inNode,
+ nsAString& outURL);
+ static void CreateLinkText(const nsAString& inURL, const nsAString& inText,
+ nsAString& outLinkText);
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsCOMPtr<nsIContent> mTarget;
+ nsCOMPtr<nsIContent> mSelectionTargetNode;
+ bool mIsAltKeyPressed;
+
+ nsString mUrlString;
+ nsString mImageSourceString;
+ nsString mImageDestFileName;
+#if defined(XP_MACOSX)
+ nsString mImageRequestMime;
+#endif
+ nsString mTitleString;
+ // will be filled automatically if you fill urlstring
+ nsString mHtmlString;
+ nsString mContextString;
+ nsString mInfoString;
+
+ bool mIsAnchor;
+ nsCOMPtr<imgIContainer> mImage;
+};
+
+nsresult nsContentAreaDragDrop::GetDragData(
+ nsPIDOMWindowOuter* aWindow, nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed,
+ DataTransfer* aDataTransfer, bool* aCanDrag, Selection** aSelection,
+ nsIContent** aDragNode, nsIPrincipal** aPrincipal,
+ nsIContentSecurityPolicy** aCsp,
+ nsICookieJarSettings** aCookieJarSettings) {
+ NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
+
+ *aCanDrag = true;
+
+ DragDataProducer provider(aWindow, aTarget, aSelectionTargetNode,
+ aIsAltKeyPressed);
+ return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode,
+ aPrincipal, aCsp, aCookieJarSettings);
+}
+
+NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider)
+
+// SaveURIToFile
+// used on platforms where it's possible to drag items (e.g. images)
+// into the file system
+nsresult nsContentAreaDragDropDataProvider::SaveURIToFile(
+ nsIURI* inSourceURI, nsIPrincipal* inTriggeringPrincipal,
+ nsICookieJarSettings* inCookieJarSettings, nsIFile* inDestFile,
+ nsContentPolicyType inContentPolicyType, bool isPrivate) {
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(inSourceURI);
+ if (!sourceURL) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsresult rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we rely on the fact that the WPB is refcounted by the channel etc,
+ // so we don't keep a ref to it. It will die when finished.
+ nsCOMPtr<nsIWebBrowserPersist> persist = do_CreateInstance(
+ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ persist->SetPersistFlags(
+ nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+
+ // referrer policy can be anything since the referrer is nullptr
+ return persist->SaveURI(inSourceURI, inTriggeringPrincipal, 0, nullptr,
+ inCookieJarSettings, nullptr, nullptr, inDestFile,
+ inContentPolicyType, isPrivate);
+}
+
+/*
+ * Check if the provided filename extension is valid for the MIME type and
+ * return the MIME type's primary extension.
+ *
+ * @param aExtension [in] the extension to check
+ * @param aMimeType [in] the MIME type to check the extension with
+ * @param aIsValidExtension [out] true if |aExtension| is valid for
+ * |aMimeType|
+ * @param aPrimaryExtension [out] the primary extension for the MIME type
+ * to potentially be used as a replacement
+ * for |aExtension|
+ */
+nsresult CheckAndGetExtensionForMime(const nsCString& aExtension,
+ const nsCString& aMimeType,
+ bool* aIsValidExtension,
+ nsACString* aPrimaryExtension) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (NS_WARN_IF(!mimeService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ rv = mimeService->GetFromTypeAndExtension(aMimeType, ""_ns,
+ getter_AddRefs(mimeInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeInfo->GetPrimaryExtension(*aPrimaryExtension);
+
+ if (aExtension.IsEmpty()) {
+ *aIsValidExtension = false;
+ return NS_OK;
+ }
+
+ rv = mimeInfo->ExtensionExists(aExtension, aIsValidExtension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// This is our nsIFlavorDataProvider callback. There are several
+// assumptions here that make this work:
+//
+// 1. Someone put a kFilePromiseURLMime flavor into the transferable
+// with the source URI of the file to save (as a string). We did
+// that in AddStringsToDataTransfer.
+//
+// 2. Someone put a kFilePromiseDirectoryMime flavor into the
+// transferable with an nsIFile for the directory we are to
+// save in. That has to be done by platform-specific code (in
+// widget), which gets the destination directory from
+// OS-specific drag information.
+//
+NS_IMETHODIMP
+nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable* aTransferable,
+ const char* aFlavor,
+ nsISupports** aData) {
+ NS_ENSURE_ARG_POINTER(aData);
+ *aData = nullptr;
+
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+
+ if (strcmp(aFlavor, kFilePromiseMime) == 0) {
+ // get the URI from the kFilePromiseURLMime flavor
+ NS_ENSURE_ARG(aTransferable);
+ nsCOMPtr<nsISupports> tmp;
+ rv = aTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp);
+ if (!supportsString) return NS_ERROR_FAILURE;
+
+ nsAutoString sourceURLString;
+ supportsString->GetData(sourceURLString);
+ if (sourceURLString.IsEmpty()) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> sourceURI;
+ rv = NS_NewURI(getter_AddRefs(sourceURI), sourceURLString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ supportsString = do_QueryInterface(tmp);
+ if (!supportsString) return NS_ERROR_FAILURE;
+
+ nsAutoString targetFilename;
+ supportsString->GetData(targetFilename);
+ if (targetFilename.IsEmpty()) return NS_ERROR_FAILURE;
+
+#if defined(XP_MACOSX)
+ // Use the image request's MIME type to ensure the filename's
+ // extension is compatible with the OS's handler for this type.
+ // If it isn't, or is missing, replace the extension with the
+ // primary extension. On Mac, do this in the parent process
+ // because sandboxing blocks access to MIME-handler info from
+ // content processes.
+ if (XRE_IsParentProcess()) {
+ rv = aTransferable->GetTransferData(kImageRequestMime,
+ getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ supportsString = do_QueryInterface(tmp);
+ if (!supportsString) return NS_ERROR_FAILURE;
+
+ nsAutoString contentType;
+ supportsString->GetData(contentType);
+
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService("@mozilla.org/mime;1");
+ if (NS_WARN_IF(!mimeService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mimeService->ValidateFileNameForSaving(
+ targetFilename, NS_ConvertUTF16toUTF8(contentType),
+ nsIMIMEService::VALIDATE_DEFAULT, targetFilename);
+ } else {
+ // make the filename safe for the filesystem
+ targetFilename.ReplaceChar(
+ u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, u'-');
+ }
+#endif /* defined(XP_MACOSX) */
+
+ // get the target directory from the kFilePromiseDirectoryMime
+ // flavor
+ nsCOMPtr<nsISupports> dirPrimitive;
+ rv = aTransferable->GetTransferData(kFilePromiseDirectoryMime,
+ getter_AddRefs(dirPrimitive));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> destDirectory = do_QueryInterface(dirPrimitive);
+ if (!destDirectory) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> file;
+ rv = destDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file->Append(targetFilename);
+
+ bool isPrivate = aTransferable->GetIsPrivateData();
+
+ nsCOMPtr<nsIPrincipal> principal = aTransferable->GetRequestingPrincipal();
+ nsContentPolicyType contentPolicyType =
+ aTransferable->GetContentPolicyType();
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aTransferable->GetCookieJarSettings();
+ rv = SaveURIToFile(sourceURI, principal, cookieJarSettings, file,
+ contentPolicyType, isPrivate);
+ // send back an nsIFile
+ if (NS_SUCCEEDED(rv)) {
+ CallQueryInterface(file, aData);
+ }
+ }
+
+ return rv;
+}
+
+DragDataProducer::DragDataProducer(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode,
+ bool aIsAltKeyPressed)
+ : mWindow(aWindow),
+ mTarget(aTarget),
+ mSelectionTargetNode(aSelectionTargetNode),
+ mIsAltKeyPressed(aIsAltKeyPressed),
+ mIsAnchor(false) {}
+
+static nsIContent* FindDragTarget(nsIContent* aContent) {
+ for (nsIContent* content = aContent; content;
+ content = content->GetFlattenedTreeParent()) {
+ if (nsContentUtils::ContentIsDraggable(content)) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+//
+// GetAnchorURL
+//
+nsresult DragDataProducer::GetAnchorURL(nsIContent* aContent, nsAString& aURL) {
+ aURL.Truncate();
+ auto* element = Element::FromNodeOrNull(aContent);
+ if (!element || !element->IsLink()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> linkURI = element->GetHrefURI();
+ if (!linkURI) {
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsresult rv = linkURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ rv = secMan->CheckLoadURIStrWithPrincipal(aContent->NodePrincipal(), spec, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, aURL);
+ return NS_OK;
+}
+
+//
+// CreateLinkText
+//
+// Creates the html for an anchor in the form
+// <a href="inURL">inText</a>
+//
+void DragDataProducer::CreateLinkText(const nsAString& inURL,
+ const nsAString& inText,
+ nsAString& outLinkText) {
+ // use a temp var in case |inText| is the same string as
+ // |outLinkText| to avoid overwriting it while building up the
+ // string in pieces.
+ nsAutoString linkText(u"<a href=\""_ns + inURL + u"\">"_ns + inText +
+ u"</a>"_ns);
+
+ outLinkText = linkText;
+}
+
+nsresult DragDataProducer::GetImageData(imgIContainer* aImage,
+ imgIRequest* aRequest) {
+ nsCOMPtr<nsIURI> imgUri = aRequest->GetURI();
+
+ nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
+ if (imgUrl) {
+ nsAutoCString spec;
+ nsresult rv = imgUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass out the image source string
+ CopyUTF8toUTF16(spec, mImageSourceString);
+
+ nsCString mimeType;
+ aRequest->GetMimeType(getter_Copies(mimeType));
+
+ nsAutoCString fileName;
+ aRequest->GetFileName(fileName);
+
+#if defined(XP_MACOSX)
+ // Save the MIME type so we can make sure the extension
+ // is compatible (and replace it if it isn't) when the
+ // image is dropped. On Mac, we need to get the OS MIME
+ // handler information in the parent due to sandboxing.
+ CopyUTF8toUTF16(mimeType, mImageRequestMime);
+ CopyUTF8toUTF16(fileName, mImageDestFileName);
+#else
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (NS_WARN_IF(!mimeService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CopyUTF8toUTF16(fileName, mImageDestFileName);
+ mimeService->ValidateFileNameForSaving(mImageDestFileName, mimeType,
+ nsIMIMEService::VALIDATE_DEFAULT,
+ mImageDestFileName);
+#endif
+
+ // and the image object
+ mImage = aImage;
+ }
+
+ return NS_OK;
+}
+
+nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag,
+ Selection** aSelection,
+ nsIContent** aDragNode,
+ nsIPrincipal** aPrincipal,
+ nsIContentSecurityPolicy** aCsp,
+ nsICookieJarSettings** aCookieJarSettings) {
+ MOZ_ASSERT(aCanDrag && aSelection && aDataTransfer && aDragNode,
+ "null pointer passed to Produce");
+ NS_ASSERTION(mWindow, "window not set");
+ NS_ASSERTION(mSelectionTargetNode,
+ "selection target node should have been set");
+
+ *aDragNode = nullptr;
+
+ nsresult rv;
+ nsIContent* dragNode = nullptr;
+ *aSelection = nullptr;
+
+ // Find the selection to see what we could be dragging and if what we're
+ // dragging is in what is selected. If this is an editable textbox, use
+ // the textbox's selection, otherwise use the window's selection.
+ RefPtr<Selection> selection;
+ nsIContent* editingElement = mSelectionTargetNode->IsEditable()
+ ? mSelectionTargetNode->GetEditingHost()
+ : nullptr;
+ RefPtr<TextControlElement> textControlElement =
+ TextControlElement::GetTextControlElementFromEditingHost(editingElement);
+ if (textControlElement) {
+ nsISelectionController* selcon =
+ textControlElement->GetSelectionController();
+ if (selcon) {
+ selection =
+ selcon->GetSelection(nsISelectionController::SELECTION_NORMAL);
+ }
+
+ if (!selection) return NS_OK;
+ } else {
+ selection = mWindow->GetSelection();
+ if (!selection) return NS_OK;
+
+ // Check if the node is inside a form control. Don't set aCanDrag to false
+ // however, as we still want to allow the drag.
+ nsCOMPtr<nsIContent> findFormNode = mSelectionTargetNode;
+ nsIContent* findFormParent = findFormNode->GetParent();
+ while (findFormParent) {
+ nsCOMPtr<nsIFormControl> form(do_QueryInterface(findFormParent));
+ if (form && !form->AllowDraggableChildren()) {
+ return NS_OK;
+ }
+ findFormParent = findFormParent->GetParent();
+ }
+ }
+
+ // if set, serialize the content under this node
+ nsCOMPtr<nsIContent> nodeToSerialize;
+
+ BrowsingContext* bc = mWindow->GetBrowsingContext();
+ const bool isChromeShell = bc && bc->IsChrome();
+
+ // In chrome shells, only allow dragging inside editable areas.
+ if (isChromeShell && !editingElement) {
+ // This path should already be filtered out in
+ // EventStateManager::DetermineDragTargetAndDefaultData.
+ MOZ_ASSERT_UNREACHABLE("Shouldn't be generating drag data for chrome");
+ return NS_OK;
+ }
+
+ if (isChromeShell && textControlElement) {
+ // Only use the selection if the target node is in the selection.
+ if (!selection->ContainsNode(*mSelectionTargetNode, false, IgnoreErrors()))
+ return NS_OK;
+
+ selection.swap(*aSelection);
+ } else {
+ // In content shells, a number of checks are made below to determine
+ // whether an image or a link is being dragged. If so, add additional
+ // data to the data transfer. This is also done for chrome shells, but
+ // only when in a non-textbox editor.
+
+ bool haveSelectedContent = false;
+
+ // possible parent link node
+ nsCOMPtr<nsIContent> parentLink;
+ nsCOMPtr<nsIContent> draggedNode;
+
+ {
+ // only drag form elements by using the alt key,
+ // otherwise buttons and select widgets are hard to use
+
+ // Note that while <object> elements implement nsIFormControl, we should
+ // really allow dragging them if they happen to be images.
+ nsCOMPtr<nsIFormControl> form(do_QueryInterface(mTarget));
+ if (form && !mIsAltKeyPressed &&
+ form->ControlType() != FormControlType::Object) {
+ *aCanDrag = false;
+ return NS_OK;
+ }
+
+ draggedNode = FindDragTarget(mTarget);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> image;
+
+ nsCOMPtr<nsIContent> selectedImageOrLinkNode;
+ GetDraggableSelectionData(selection, mSelectionTargetNode,
+ getter_AddRefs(selectedImageOrLinkNode),
+ &haveSelectedContent);
+
+ // either plain text or anchor text is selected
+ if (haveSelectedContent) {
+ selection.swap(*aSelection);
+ } else if (selectedImageOrLinkNode) {
+ // an image is selected
+ image = do_QueryInterface(selectedImageOrLinkNode);
+ } else {
+ // nothing is selected -
+ //
+ // look for draggable elements under the mouse
+ //
+ // if the alt key is down, don't start a drag if we're in an
+ // anchor because we want to do selection.
+ parentLink = nsContentUtils::GetClosestLinkInFlatTree(draggedNode);
+ if (parentLink && mIsAltKeyPressed) {
+ *aCanDrag = false;
+ return NS_OK;
+ }
+ image = do_QueryInterface(draggedNode);
+ }
+
+ {
+ // set for linked images, and links
+ nsCOMPtr<nsIContent> linkNode;
+ if (const auto* areaElem = HTMLAreaElement::FromNodeOrNull(draggedNode)) {
+ // use the alt text (or, if missing, the href) as the title
+ areaElem->GetAttr(nsGkAtoms::alt, mTitleString);
+ if (mTitleString.IsEmpty()) {
+ // this can be a relative link
+ areaElem->GetAttr(nsGkAtoms::href, mTitleString);
+ }
+
+ // gives an absolute link
+ nsresult rv = GetAnchorURL(draggedNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we'll generate HTML like <a href="absurl">alt text</a>
+ mIsAnchor = true;
+
+ mHtmlString.AssignLiteral("<a href=\"");
+ mHtmlString.Append(mUrlString);
+ mHtmlString.AppendLiteral("\">");
+ mHtmlString.Append(mTitleString);
+ mHtmlString.AppendLiteral("</a>");
+
+ dragNode = draggedNode;
+ } else if (image) {
+ // grab the href as the url, use alt text as the title of the
+ // area if it's there. the drag data is the image tag and src
+ // attribute.
+ nsCOMPtr<nsIURI> imageURI;
+ image->GetCurrentURI(getter_AddRefs(imageURI));
+ nsCOMPtr<Element> imageElement(do_QueryInterface(image));
+ if (imageURI) {
+ nsAutoCString spec;
+ rv = imageURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIScriptSecurityManager* secMan =
+ nsContentUtils::GetSecurityManager();
+ rv = secMan->CheckLoadURIStrWithPrincipal(
+ imageElement->NodePrincipal(), spec, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIsAnchor = true;
+ CopyUTF8toUTF16(spec, mUrlString);
+ }
+
+ // XXXbz Shouldn't we use the "title" attr for title? Using
+ // "alt" seems very wrong....
+ // XXXbz Also, what if this is an nsIImageLoadingContent
+ // that's not an <html:img>?
+ if (imageElement) {
+ imageElement->GetAttr(nsGkAtoms::alt, mTitleString);
+ }
+
+ if (mTitleString.IsEmpty()) {
+ mTitleString = mUrlString;
+ }
+
+ nsCOMPtr<imgIRequest> imgRequest;
+
+ // grab the image data, and its request.
+ nsCOMPtr<imgIContainer> img = nsContentUtils::GetImageFromContent(
+ image, getter_AddRefs(imgRequest));
+ if (imgRequest) {
+ rv = GetImageData(img, imgRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (parentLink) {
+ // If we are dragging around an image in an anchor, then we
+ // are dragging the entire anchor
+ linkNode = parentLink;
+ nodeToSerialize = linkNode;
+ } else {
+ nodeToSerialize = draggedNode;
+ }
+ dragNode = nodeToSerialize;
+ } else if (parentLink) {
+ // parentLink will always be null if there's selected content
+ linkNode = parentLink;
+ nodeToSerialize = linkNode;
+ } else if (!haveSelectedContent) {
+ // nothing draggable
+ return NS_OK;
+ }
+
+ if (linkNode) {
+ rv = GetAnchorURL(linkNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIsAnchor = true;
+ dragNode = linkNode;
+ }
+ }
+ }
+
+ if (nodeToSerialize || *aSelection) {
+ mHtmlString.Truncate();
+ mContextString.Truncate();
+ mInfoString.Truncate();
+ mTitleString.Truncate();
+
+ nsCOMPtr<Document> doc = mWindow->GetDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();
+ if (csp) {
+ NS_IF_ADDREF(*aCsp = csp);
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings = doc->CookieJarSettings();
+ if (cookieJarSettings) {
+ NS_IF_ADDREF(*aCookieJarSettings = cookieJarSettings);
+ }
+
+ // if we have selected text, use it in preference to the node
+ nsCOMPtr<nsITransferable> transferable;
+ if (*aSelection) {
+ rv = nsCopySupport::GetTransferableForSelection(
+ *aSelection, doc, getter_AddRefs(transferable));
+ } else {
+ rv = nsCopySupport::GetTransferableForNode(nodeToSerialize, doc,
+ getter_AddRefs(transferable));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsISupportsString> data;
+ rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports));
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mHtmlString);
+ }
+ rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports));
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mContextString);
+ }
+ rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports));
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mInfoString);
+ }
+ rv = transferable->GetTransferData(kTextMime, getter_AddRefs(supports));
+ data = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv); // require plain text at a minimum
+ data->GetData(mTitleString);
+ }
+
+ // default text value is the URL
+ if (mTitleString.IsEmpty()) {
+ mTitleString = mUrlString;
+ }
+
+ // if we haven't constructed a html version, make one now
+ if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty())
+ CreateLinkText(mUrlString, mTitleString, mHtmlString);
+
+ // if there is no drag node, which will be the case for a selection, just
+ // use the selection target node.
+ rv = AddStringsToDataTransfer(
+ dragNode ? dragNode : mSelectionTargetNode.get(), aDataTransfer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aDragNode = dragNode);
+ return NS_OK;
+}
+
+void DragDataProducer::AddString(DataTransfer* aDataTransfer,
+ const nsAString& aFlavor,
+ const nsAString& aData,
+ nsIPrincipal* aPrincipal, bool aHidden) {
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ variant->SetAsAString(aData);
+ aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal, aHidden);
+}
+
+nsresult DragDataProducer::AddStringsToDataTransfer(
+ nsIContent* aDragNode, DataTransfer* aDataTransfer) {
+ NS_ASSERTION(aDragNode, "adding strings for null node");
+
+ // set all of the data to have the principal of the node where the data came
+ // from
+ nsIPrincipal* principal = aDragNode->NodePrincipal();
+
+ // add a special flavor if we're an anchor to indicate that we have
+ // a URL in the drag data
+ if (!mUrlString.IsEmpty() && mIsAnchor) {
+ nsAutoString dragData(mUrlString);
+ dragData.Append('\n');
+ // Remove leading and trailing newlines in the title and replace them with
+ // space in remaining positions - they confuse PlacesUtils::unwrapNodes
+ // that expects url\ntitle formatted data for x-moz-url.
+ nsAutoString title(mTitleString);
+ title.Trim("\r\n");
+ title.ReplaceChar(u"\r\n", ' ');
+ dragData += title;
+
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLMime), dragData,
+ principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime),
+ mUrlString, principal);
+ AddString(aDataTransfer,
+ NS_LITERAL_STRING_FROM_CSTRING(kURLDescriptionMime), mTitleString,
+ principal);
+ AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal);
+ }
+
+ // add a special flavor for the html context data
+ if (!mContextString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
+ mContextString, principal);
+
+ // add a special flavor if we have html info data
+ if (!mInfoString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
+ mInfoString, principal);
+
+ // add the full html
+ if (!mHtmlString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime),
+ mHtmlString, principal);
+
+ // add the plain text. we use the url for text/plain data if an anchor is
+ // being dragged, rather than the title text of the link or the alt text for
+ // an anchor image.
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kTextMime),
+ mIsAnchor ? mUrlString : mTitleString, principal);
+
+ // add image data, if present. For now, all we're going to do with
+ // this is turn it into a native data flavor, so indicate that with
+ // a new flavor so as not to confuse anyone who is really registered
+ // for image/gif or image/jpg.
+ if (mImage) {
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ variant->SetAsISupports(mImage);
+ aDataTransfer->SetDataWithPrincipal(
+ NS_LITERAL_STRING_FROM_CSTRING(kNativeImageMime), variant, 0,
+ principal);
+
+ // assume the image comes from a file, and add a file promise. We
+ // register ourselves as a nsIFlavorDataProvider, and will use the
+ // GetFlavorData callback to save the image to disk.
+
+ nsCOMPtr<nsIFlavorDataProvider> dataProvider =
+ new nsContentAreaDragDropDataProvider();
+ if (dataProvider) {
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ variant->SetAsISupports(dataProvider);
+ aDataTransfer->SetDataWithPrincipal(
+ NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseMime), variant, 0,
+ principal);
+ }
+
+ AddString(aDataTransfer,
+ NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseURLMime),
+ mImageSourceString, principal);
+ AddString(aDataTransfer,
+ NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseDestFilename),
+ mImageDestFileName, principal);
+#if defined(XP_MACOSX)
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kImageRequestMime),
+ mImageRequestMime, principal, /* aHidden= */ true);
+#endif
+
+ // if not an anchor, add the image url
+ if (!mIsAnchor) {
+ AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime),
+ mUrlString, principal);
+ AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal);
+ }
+ }
+
+ return NS_OK;
+}
+
+// note that this can return NS_OK, but a null out param (by design)
+// static
+nsresult DragDataProducer::GetDraggableSelectionData(
+ Selection* inSelection, nsIContent* inRealTargetNode,
+ nsIContent** outImageOrLinkNode, bool* outDragSelectedText) {
+ NS_ENSURE_ARG(inSelection);
+ NS_ENSURE_ARG(inRealTargetNode);
+ NS_ENSURE_ARG_POINTER(outImageOrLinkNode);
+
+ *outImageOrLinkNode = nullptr;
+ *outDragSelectedText = false;
+
+ if (!inSelection->IsCollapsed()) {
+ if (inSelection->ContainsNode(*inRealTargetNode, false, IgnoreErrors())) {
+ // track down the anchor node, if any, for the url
+ nsINode* selectionStart = inSelection->GetAnchorNode();
+ nsINode* selectionEnd = inSelection->GetFocusNode();
+
+ // look for a selection around a single node, like an image.
+ // in this case, drag the image, rather than a serialization of the HTML
+ // XXX generalize this to other draggable element types?
+ if (selectionStart == selectionEnd) {
+ nsCOMPtr<nsIContent> selStartContent =
+ nsIContent::FromNodeOrNull(selectionStart);
+ if (selStartContent && selStartContent->HasChildNodes()) {
+ // see if just one node is selected
+ uint32_t anchorOffset = inSelection->AnchorOffset();
+ uint32_t focusOffset = inSelection->FocusOffset();
+ if (anchorOffset == focusOffset + 1 ||
+ focusOffset == anchorOffset + 1) {
+ uint32_t childOffset = std::min(anchorOffset, focusOffset);
+ nsIContent* childContent =
+ selStartContent->GetChildAt_Deprecated(childOffset);
+ // if we find an image, we'll fall into the node-dragging code,
+ // rather the the selection-dragging code
+ if (nsContentUtils::IsDraggableImage(childContent)) {
+ NS_ADDREF(*outImageOrLinkNode = childContent);
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ // indicate that a link or text is selected
+ *outDragSelectedText = true;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/nsContentAreaDragDrop.h b/dom/base/nsContentAreaDragDrop.h
new file mode 100644
index 0000000000..ac9eb4b988
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentAreaDragDrop_h__
+#define nsContentAreaDragDrop_h__
+
+#include "nsCOMPtr.h"
+
+#include "nsITransferable.h"
+#include "nsIContentSecurityPolicy.h"
+
+class nsICookieJarSettings;
+class nsPIDOMWindowOuter;
+class nsITransferable;
+class nsIContent;
+class nsIFile;
+
+namespace mozilla::dom {
+class DataTransfer;
+class Selection;
+} // namespace mozilla::dom
+
+//
+// class nsContentAreaDragDrop, used to generate the dragdata
+//
+class nsContentAreaDragDrop {
+ public:
+ /**
+ * Determine what data in the content area, if any, is being dragged.
+ *
+ * aWindow - the window containing the target node
+ * aTarget - the mousedown event target that started the drag
+ * aSelectionTargetNode - the node where the drag event should be fired
+ * aIsAltKeyPressed - true if the Alt key is pressed. In some cases, this
+ * will prevent the drag from occuring. For example,
+ * holding down Alt over a link should select the text,
+ * not drag the link.
+ * aDataTransfer - the dataTransfer for the drag event.
+ * aCanDrag - [out] set to true if the drag may proceed, false to stop the
+ * drag entirely
+ * aSelection - [out] set to the selection being dragged, or null if no
+ * selection is being dragged.
+ * aDragNode - [out] the link, image or area being dragged, or null if the
+ * drag occurred on another element.
+ * aPrincipal - [out] set to the triggering principal of the drag, or null if
+ * it's from browser chrome or OS
+ * aCSP - [out] set to the CSP of the Drag, or null if
+ * it's from browser chrome or OS
+ * aCookieJarSettings - [out] set to the cookieJarSetting of the Drag, or null
+ * if it's from browser chrome or OS
+ */
+ static nsresult GetDragData(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode,
+ bool aIsAltKeyPressed,
+ mozilla::dom::DataTransfer* aDataTransfer,
+ bool* aCanDrag,
+ mozilla::dom::Selection** aSelection,
+ nsIContent** aDragNode, nsIPrincipal** aPrincipal,
+ nsIContentSecurityPolicy** aCsp,
+ nsICookieJarSettings** aCookieJarSettings);
+};
+
+// this is used to save images to disk lazily when the image data is asked for
+// during the drop instead of when it is added to the drag data transfer. This
+// ensures that the image data is only created when an image drop is allowed.
+class nsContentAreaDragDropDataProvider : public nsIFlavorDataProvider {
+ virtual ~nsContentAreaDragDropDataProvider() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFLAVORDATAPROVIDER
+
+ nsresult SaveURIToFile(nsIURI* inSourceURI,
+ nsIPrincipal* inTriggeringPrincipal,
+ nsICookieJarSettings* inCookieJarSettings,
+ nsIFile* inDestFile, nsContentPolicyType inPolicyType,
+ bool isPrivate);
+};
+
+#endif /* nsContentAreaDragDrop_h__ */
diff --git a/dom/base/nsContentCID.h b/dom/base/nsContentCID.h
new file mode 100644
index 0000000000..cda1cb4075
--- /dev/null
+++ b/dom/base/nsContentCID.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentCID_h__
+#define nsContentCID_h__
+
+// {972D8D8F-F0DA-11d4-9885-00C04FA0CF4B}
+#define NS_CONTENT_VIEWER_CID \
+ { \
+ 0x972d8d8f, 0xf0da, 0x11d4, { \
+ 0x98, 0x85, 0x0, 0xc0, 0x4f, 0xa0, 0xcf, 0x4b \
+ } \
+ }
+
+// {FC886801-E768-11d4-9885-00C04FA0CF4B}
+#define NS_CONTENT_DOCUMENT_LOADER_FACTORY_CID \
+ { \
+ 0xfc886801, 0xe768, 0x11d4, { \
+ 0x98, 0x85, 0x0, 0xc0, 0x4f, 0xa0, 0xcf, 0x4b \
+ } \
+ }
+
+#define NS_NAMESPACEMANAGER_CID \
+ { /* d9783472-8fe9-11d2-9d3c-0060088f9ff7 */ \
+ 0xd9783472, 0x8fe9, 0x11d2, { \
+ 0x9d, 0x3c, 0x00, 0x60, 0x08, 0x8f, 0x9f, 0xf7 \
+ } \
+ }
+
+// {09F689E0-B4DA-11d2-A68B-00104BDE6048}
+#define NS_EVENTLISTENERMANAGER_CID \
+ { \
+ 0x9f689e0, 0xb4da, 0x11d2, { \
+ 0xa6, 0x8b, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 \
+ } \
+ }
+
+// {64F300A1-C88C-11d3-97FB-00400553EEF0}
+#define NS_XBLSERVICE_CID \
+ { \
+ 0x64f300a1, 0xc88c, 0x11d3, { \
+ 0x97, 0xfb, 0x0, 0x40, 0x5, 0x53, 0xee, 0xf0 \
+ } \
+ }
+
+// {4aef38b7-6364-4e23-a5e7-12f837fbbd9c}
+#define NS_XMLCONTENTSERIALIZER_CID \
+ { \
+ 0x4aef38b7, 0x6364, 0x4e23, { \
+ 0xa5, 0xe7, 0x12, 0xf8, 0x37, 0xfb, 0xbd, 0x9c \
+ } \
+ }
+
+// {e7c2aaf5-c11a-4954-9dbf-e28edec1fd91}
+#define NS_XHTMLCONTENTSERIALIZER_CID \
+ { \
+ 0xe7c2aaf5, 0xc11a, 0x4954, { \
+ 0x9d, 0xbf, 0xe2, 0x8e, 0xde, 0xc1, 0xfd, 0x91 \
+ } \
+ }
+
+// {9d3f70da-86e9-11d4-95ec-00b0d03e37b7}
+#define NS_HTMLCONTENTSERIALIZER_CID \
+ { \
+ 0x9d3f70da, 0x86e9, 0x11d4, { \
+ 0x95, 0xec, 0x00, 0xb0, 0xd0, 0x3e, 0x37, 0xb7 \
+ } \
+ }
+
+// {feca3c34-205e-4ae5-bd1c-03c686ff012b}
+#define MOZ_SANITIZINGHTMLSERIALIZER_CID \
+ { \
+ 0xfeca3c34, 0x205e, 0x4ae5, { \
+ 0xbd, 0x1c, 0x03, 0xc6, 0x86, 0xff, 0x01, 0x2b \
+ } \
+ }
+
+// {6030f7ef-32ed-46a7-9a63-6a5d3f90445f}
+#define NS_PLAINTEXTSERIALIZER_CID \
+ { \
+ 0x6030f7ef, 0x32ed, 0x46a7, { \
+ 0x9a, 0x63, 0x6a, 0x5d, 0x3f, 0x90, 0x44, 0x5f \
+ } \
+ }
+
+// {d4f2b600-b5c1-11d6-b483-cc97c63e567c}
+#define NS_HTMLFRAGMENTSINK_CID \
+ { \
+ 0xd4f2b600, 0xb5c1, 0x11d6, { \
+ 0xb4, 0x83, 0xcc, 0x97, 0xc6, 0x3e, 0x56, 0x7c \
+ } \
+ }
+
+// {13111d00-ce81-11d6-8082-ecf3665af67c}
+#define NS_HTMLFRAGMENTSINK2_CID \
+ { \
+ 0x13111d00, 0xce81, 0x11d6, { \
+ 0x80, 0x82, 0xec, 0xf3, 0x66, 0x5a, 0xf6, 0x7c \
+ } \
+ }
+
+// {4B664E54-72A2-4bbf-A5C2-66D4DC3066A0}
+#define NS_XMLFRAGMENTSINK_CID \
+ { \
+ 0x4b664e54, 0x72a2, 0x4bbf, { \
+ 0xa5, 0xc2, 0x66, 0xd4, 0xdc, 0x30, 0x66, 0xa0 \
+ } \
+ }
+
+// {4DC30689-929D-425e-A709-082C6294E542}
+#define NS_XMLFRAGMENTSINK2_CID \
+ { \
+ 0x4dc30689, 0x929d, 0x425e, { \
+ 0xa7, 0x9, 0x8, 0x2c, 0x62, 0x94, 0xe5, 0x42 \
+ } \
+ }
+
+// {3986B301-097C-11d3-BF87-00105A1B0627}
+#define NS_XULPOPUPLISTENER_CID \
+ { \
+ 0x3986b301, 0x97c, 0x11d3, { \
+ 0xbf, 0x87, 0x0, 0x10, 0x5a, 0x1b, 0x6, 0x27 \
+ } \
+ }
+
+// {3D262D00-8B5A-11d2-8EB0-00805F29F370}
+#define NS_XULTEMPLATEBUILDER_CID \
+ { \
+ 0x3d262d00, 0x8b5a, 0x11d2, { \
+ 0x8e, 0xb0, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 \
+ } \
+ }
+
+// {1abdcc96-1dd2-11b2-b520-f8f59cdd67bc}
+#define NS_XULTREEBUILDER_CID \
+ { \
+ 0x1abdcc96, 0x1dd2, 0x11b2, { \
+ 0xb5, 0x20, 0xf8, 0xf5, 0x9c, 0xdd, 0x67, 0xbc \
+ } \
+ }
+
+#define NS_EVENTLISTENERSERVICE_CID \
+ { /* baa34652-f1f1-4185-b224-244ee82a413a */ \
+ 0xbaa34652, 0xf1f1, 0x4185, { \
+ 0xb2, 0x24, 0x24, 0x4e, 0xe8, 0x2a, 0x41, 0x3a \
+ } \
+ }
+#define NS_EVENTLISTENERSERVICE_CONTRACTID "@mozilla.org/eventlistenerservice;1"
+
+#define NS_GLOBALMESSAGEMANAGER_CID \
+ { /* 130b016f-fad7-4526-bc7f-827dabf79265 */ \
+ 0x130b016f, 0xfad7, 0x4526, { \
+ 0xbc, 0x7f, 0x82, 0x7d, 0xab, 0xf7, 0x92, 0x65 \
+ } \
+ }
+#define NS_GLOBALMESSAGEMANAGER_CONTRACTID "@mozilla.org/globalmessagemanager;1"
+
+#define NS_PARENTPROCESSMESSAGEMANAGER_CID \
+ { /* 2a058404-fb85-44ec-8cfd-e8cbdc988dc1 */ \
+ 0x2a058404, 0xfb85, 0x44ec, { \
+ 0x8c, 0xfd, 0xe8, 0xcb, 0xdc, 0x98, 0x8d, 0xc1 \
+ } \
+ }
+#define NS_PARENTPROCESSMESSAGEMANAGER_CONTRACTID \
+ "@mozilla.org/parentprocessmessagemanager;1"
+
+#define NS_CHILDPROCESSMESSAGEMANAGER_CID \
+ { /* fe0ff7c3-8e97-448b-9a8a-86afdb9fbbb6 */ \
+ 0xfe0ff7c3, 0x8e97, 0x448b, { \
+ 0x9a, 0x8a, 0x86, 0xaf, 0xdb, 0x9f, 0xbb, 0xb6 \
+ } \
+ }
+#define NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID \
+ "@mozilla.org/childprocessmessagemanager;1"
+
+// {08c6cc8b-cfb0-421d-b1f7-683ff2989681}
+#define THIRDPARTYUTIL_CID \
+ { \
+ 0x08c6cc8b, 0xcfb0, 0x421d, { \
+ 0xb1, 0xf7, 0x68, 0x3f, 0xf2, 0x98, 0x96, 0x81 \
+ } \
+ }
+
+// {7B121F7E-EBE4-43AB-9410-DC9087A1DBA6}
+#define GECKO_MEDIA_PLUGIN_SERVICE_CID \
+ { \
+ 0x7B121F7E, 0xEBE4, 0x43AB, { \
+ 0x94, 0x10, 0xDC, 0x90, 0x87, 0xA1, 0xDB, 0xA6 \
+ } \
+ }
+
+#endif /* nsContentCID_h__ */
diff --git a/dom/base/nsContentCreatorFunctions.h b/dom/base/nsContentCreatorFunctions.h
new file mode 100644
index 0000000000..00ffa9734d
--- /dev/null
+++ b/dom/base/nsContentCreatorFunctions.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentCreatorFunctions_h__
+#define nsContentCreatorFunctions_h__
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/FromParser.h"
+
+/**
+ * Functions to create content, to be used only inside Gecko
+ * (mozilla/content and mozilla/layout).
+ */
+
+class nsAtom;
+class nsIContent;
+
+class imgRequestProxy;
+class nsGenericHTMLElement;
+
+namespace mozilla::dom {
+class Element;
+class NodeInfo;
+struct CustomElementDefinition;
+} // namespace mozilla::dom
+
+nsresult NS_NewElement(mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser,
+ const nsAString* aIs = nullptr);
+
+nsresult NS_NewXMLElement(mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+nsresult NS_NewHTMLElement(
+ mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser, nsAtom* aIsAtom = nullptr,
+ mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
+
+// First argument should be nsHTMLTag, but that adds dependency to parser
+// for a bunch of files.
+already_AddRefed<nsGenericHTMLElement> CreateHTMLElement(
+ uint32_t aNodeType, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser);
+
+nsresult NS_NewMathMLElement(
+ mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+nsresult NS_NewXULElement(
+ mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser, nsAtom* aIsAtom = nullptr,
+ mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
+
+void NS_TrustedNewXULElement(
+ mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+nsresult NS_NewSVGElement(mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser);
+
+#endif // nsContentCreatorFunctions_h__
diff --git a/dom/base/nsContentList.cpp b/dom/base/nsContentList.cpp
new file mode 100644
index 0000000000..de8fb46d89
--- /dev/null
+++ b/dom/base/nsContentList.cpp
@@ -0,0 +1,1181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsBaseContentList is a basic list of content nodes; nsContentList
+ * is a commonly used NodeList implementation (used for
+ * getElementsByTagName, some properties on HTMLDocument/Document, etc).
+ */
+
+#include "nsContentList.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/dom/Element.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsContentUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/HTMLCollectionBinding.h"
+#include "mozilla/dom/NodeListBinding.h"
+#include "mozilla/Likely.h"
+#include "nsGenericHTMLElement.h"
+#include "jsfriendapi.h"
+#include <algorithm>
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "mozilla/MruCache.h"
+#include "mozilla/StaticPtr.h"
+
+#include "PLDHashTable.h"
+#include "nsTHashtable.h"
+
+#ifdef DEBUG_CONTENT_LIST
+# define ASSERT_IN_SYNC AssertInSync()
+#else
+# define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsBaseContentList::~nsBaseContentList() = default;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsBaseContentList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->RemoveFromCaches();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList)
+ if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
+ for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) {
+ nsIContent* c = tmp->mElements[i];
+ if (c->IsPurple()) {
+ c->RemovePurple();
+ }
+ Element::MarkNodeChildren(c);
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList)
+ return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList)
+ return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// QueryInterface implementation for nsBaseContentList
+NS_INTERFACE_TABLE_HEAD(nsBaseContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsBaseContentList,
+ LastRelease())
+
+nsIContent* nsBaseContentList::Item(uint32_t aIndex) {
+ return mElements.SafeElementAt(aIndex);
+}
+
+int32_t nsBaseContentList::IndexOf(nsIContent* aContent, bool aDoFlush) {
+ return mElements.IndexOf(aContent);
+}
+
+int32_t nsBaseContentList::IndexOf(nsIContent* aContent) {
+ return IndexOf(aContent, true);
+}
+
+size_t nsBaseContentList::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ n += mElements.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList,
+ mRoot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSimpleContentList)
+NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
+
+NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList)
+NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
+
+JSObject* nsSimpleContentList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return NodeList_Binding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsEmptyContentList, nsBaseContentList, mRoot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEmptyContentList)
+ NS_INTERFACE_MAP_ENTRY(nsIHTMLCollection)
+NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
+
+NS_IMPL_ADDREF_INHERITED(nsEmptyContentList, nsBaseContentList)
+NS_IMPL_RELEASE_INHERITED(nsEmptyContentList, nsBaseContentList)
+
+JSObject* nsEmptyContentList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return HTMLCollection_Binding::Wrap(cx, this, aGivenProto);
+}
+
+mozilla::dom::Element* nsEmptyContentList::GetElementAt(uint32_t index) {
+ return nullptr;
+}
+
+mozilla::dom::Element* nsEmptyContentList::GetFirstNamedElement(
+ const nsAString& aName, bool& aFound) {
+ aFound = false;
+ return nullptr;
+}
+
+void nsEmptyContentList::GetSupportedNames(nsTArray<nsString>& aNames) {}
+
+nsIContent* nsEmptyContentList::Item(uint32_t aIndex) { return nullptr; }
+
+struct ContentListCache
+ : public MruCache<nsContentListKey, nsContentList*, ContentListCache> {
+ static HashNumber Hash(const nsContentListKey& aKey) {
+ return aKey.GetHash();
+ }
+ static bool Match(const nsContentListKey& aKey, const nsContentList* aVal) {
+ return aVal->MatchesKey(aKey);
+ }
+};
+
+static ContentListCache sRecentlyUsedContentLists;
+
+class nsContentList::HashEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const nsContentListKey*;
+ using KeyTypePointer = KeyType;
+
+ // Note that this is creating a blank entry, so you'll have to manually
+ // initialize it after it has been inserted into the hash table.
+ explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {}
+
+ HashEntry(HashEntry&& aEnt) : mContentList(std::move(aEnt.mContentList)) {}
+
+ ~HashEntry() {
+ if (mContentList) {
+ MOZ_RELEASE_ASSERT(mContentList->mInHashtable);
+ mContentList->mInHashtable = false;
+ }
+ }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return mContentList->MatchesKey(*aKey);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->GetHash(); }
+
+ nsContentList* GetContentList() const { return mContentList; }
+ void SetContentList(nsContentList* aContentList) {
+ MOZ_RELEASE_ASSERT(!mContentList);
+ MOZ_ASSERT(aContentList);
+ MOZ_RELEASE_ASSERT(!aContentList->mInHashtable);
+ mContentList = aContentList;
+ mContentList->mInHashtable = true;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsContentList* MOZ_UNSAFE_REF(
+ "This entry will be removed in nsContentList::RemoveFromHashtable "
+ "before mContentList is destroyed") mContentList;
+};
+
+// Hashtable for storing nsContentLists
+static StaticAutoPtr<nsTHashtable<nsContentList::HashEntry>>
+ gContentListHashTable;
+
+already_AddRefed<nsContentList> NS_GetContentList(nsINode* aRootNode,
+ int32_t aMatchNameSpaceId,
+ const nsAString& aTagname) {
+ NS_ASSERTION(aRootNode, "content list has to have a root");
+
+ RefPtr<nsContentList> list;
+ nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname,
+ aRootNode->OwnerDoc()->IsHTMLDocument());
+ auto p = sRecentlyUsedContentLists.Lookup(hashKey);
+ if (p) {
+ list = p.Data();
+ return list.forget();
+ }
+
+ // Initialize the hashtable if needed.
+ if (!gContentListHashTable) {
+ gContentListHashTable = new nsTHashtable<nsContentList::HashEntry>();
+ }
+
+ // First we look in our hashtable. Then we create a content list if needed
+ auto entry = gContentListHashTable->PutEntry(&hashKey, fallible);
+ if (entry) {
+ list = entry->GetContentList();
+ }
+
+ if (!list) {
+ // We need to create a ContentList and add it to our new entry, if
+ // we have an entry
+ RefPtr<nsAtom> xmlAtom = NS_Atomize(aTagname);
+ RefPtr<nsAtom> htmlAtom;
+ if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
+ nsAutoString lowercaseName;
+ nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
+ htmlAtom = NS_Atomize(lowercaseName);
+ } else {
+ htmlAtom = xmlAtom;
+ }
+ list = new nsContentList(aRootNode, aMatchNameSpaceId, htmlAtom, xmlAtom);
+ if (entry) {
+ entry->SetContentList(list);
+ }
+ }
+
+ p.Set(list);
+ return list.forget();
+}
+
+#ifdef DEBUG
+const nsCacheableFuncStringContentList::ContentListType
+ nsCachableElementsByNameNodeList::sType =
+ nsCacheableFuncStringContentList::eNodeList;
+const nsCacheableFuncStringContentList::ContentListType
+ nsCacheableFuncStringHTMLCollection::sType =
+ nsCacheableFuncStringContentList::eHTMLCollection;
+#endif
+
+class nsCacheableFuncStringContentList::HashEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const nsFuncStringCacheKey*;
+ using KeyTypePointer = KeyType;
+
+ // Note that this is creating a blank entry, so you'll have to manually
+ // initialize it after it has been inserted into the hash table.
+ explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {}
+
+ HashEntry(HashEntry&& aEnt) : mContentList(std::move(aEnt.mContentList)) {}
+
+ ~HashEntry() {
+ if (mContentList) {
+ MOZ_RELEASE_ASSERT(mContentList->mInHashtable);
+ mContentList->mInHashtable = false;
+ }
+ }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return mContentList->Equals(aKey);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->GetHash(); }
+
+ nsCacheableFuncStringContentList* GetContentList() const {
+ return mContentList;
+ }
+ void SetContentList(nsCacheableFuncStringContentList* aContentList) {
+ MOZ_RELEASE_ASSERT(!mContentList);
+ MOZ_ASSERT(aContentList);
+ MOZ_RELEASE_ASSERT(!aContentList->mInHashtable);
+ mContentList = aContentList;
+ mContentList->mInHashtable = true;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsCacheableFuncStringContentList* MOZ_UNSAFE_REF(
+ "This entry will be removed in "
+ "nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable "
+ "before mContentList is destroyed") mContentList;
+};
+
+// Hashtable for storing nsCacheableFuncStringContentList
+static StaticAutoPtr<nsTHashtable<nsCacheableFuncStringContentList::HashEntry>>
+ gFuncStringContentListHashTable;
+
+template <class ListType>
+already_AddRefed<nsContentList> GetFuncStringContentList(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString) {
+ NS_ASSERTION(aRootNode, "content list has to have a root");
+
+ RefPtr<nsCacheableFuncStringContentList> list;
+
+ // Initialize the hashtable if needed.
+ if (!gFuncStringContentListHashTable) {
+ gFuncStringContentListHashTable =
+ new nsTHashtable<nsCacheableFuncStringContentList::HashEntry>();
+ }
+
+ nsCacheableFuncStringContentList::HashEntry* entry = nullptr;
+ // First we look in our hashtable. Then we create a content list if needed
+ if (gFuncStringContentListHashTable) {
+ nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
+
+ entry = gFuncStringContentListHashTable->PutEntry(&hashKey, fallible);
+ if (entry) {
+ list = entry->GetContentList();
+#ifdef DEBUG
+ MOZ_ASSERT_IF(list, list->mType == ListType::sType);
+#endif
+ }
+ }
+
+ if (!list) {
+ // We need to create a ContentList and add it to our new entry, if
+ // we have an entry
+ list =
+ new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator, aString);
+ if (entry) {
+ entry->SetContentList(list);
+ }
+ }
+
+ // Don't cache these lists globally
+
+ return list.forget();
+}
+
+// Explicit instantiations to avoid link errors
+template already_AddRefed<nsContentList>
+GetFuncStringContentList<nsCachableElementsByNameNodeList>(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString);
+template already_AddRefed<nsContentList>
+GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString);
+
+//-----------------------------------------------------
+// nsContentList implementation
+
+nsContentList::nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId,
+ nsAtom* aHTMLMatchAtom, nsAtom* aXMLMatchAtom,
+ bool aDeep, bool aLiveList)
+ : nsBaseContentList(),
+ mRootNode(aRootNode),
+ mMatchNameSpaceId(aMatchNameSpaceId),
+ mHTMLMatchAtom(aHTMLMatchAtom),
+ mXMLMatchAtom(aXMLMatchAtom),
+ mState(State::Dirty),
+ mDeep(aDeep),
+ mFuncMayDependOnAttr(false),
+ mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument()),
+ mNamedItemsCacheValid(false),
+ mIsLiveList(aLiveList),
+ mInHashtable(false) {
+ NS_ASSERTION(mRootNode, "Must have root");
+ if (nsGkAtoms::_asterisk == mHTMLMatchAtom) {
+ NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterisk,
+ "HTML atom and XML atom are not both asterisk?");
+ mMatchAll = true;
+ } else {
+ mMatchAll = false;
+ }
+ // This is aLiveList instead of mIsLiveList to avoid Valgrind errors.
+ if (aLiveList) {
+ SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed);
+ mRootNode->AddMutationObserver(this);
+ }
+
+ // We only need to flush if we're in an non-HTML document, since the
+ // HTML5 parser doesn't need flushing. Further, if we're not in a
+ // document at all right now (in the GetUncomposedDoc() sense), we're
+ // not parser-created and don't need to be flushing stuff under us
+ // to get our kids right.
+ Document* doc = mRootNode->GetUncomposedDoc();
+ mFlushesNeeded = doc && !doc->IsHTMLDocument();
+}
+
+nsContentList::nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc, void* aData,
+ bool aDeep, nsAtom* aMatchAtom,
+ int32_t aMatchNameSpaceId,
+ bool aFuncMayDependOnAttr, bool aLiveList)
+ : nsBaseContentList(),
+ mRootNode(aRootNode),
+ mMatchNameSpaceId(aMatchNameSpaceId),
+ mHTMLMatchAtom(aMatchAtom),
+ mXMLMatchAtom(aMatchAtom),
+ mFunc(aFunc),
+ mDestroyFunc(aDestroyFunc),
+ mData(aData),
+ mState(State::Dirty),
+ mMatchAll(false),
+ mDeep(aDeep),
+ mFuncMayDependOnAttr(aFuncMayDependOnAttr),
+ mIsHTMLDocument(false),
+ mNamedItemsCacheValid(false),
+ mIsLiveList(aLiveList),
+ mInHashtable(false) {
+ NS_ASSERTION(mRootNode, "Must have root");
+ // This is aLiveList instead of mIsLiveList to avoid Valgrind errors.
+ if (aLiveList) {
+ SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed);
+ mRootNode->AddMutationObserver(this);
+ }
+
+ // We only need to flush if we're in an non-HTML document, since the
+ // HTML5 parser doesn't need flushing. Further, if we're not in a
+ // document at all right now (in the GetUncomposedDoc() sense), we're
+ // not parser-created and don't need to be flushing stuff under us
+ // to get our kids right.
+ Document* doc = mRootNode->GetUncomposedDoc();
+ mFlushesNeeded = doc && !doc->IsHTMLDocument();
+}
+
+nsContentList::~nsContentList() {
+ RemoveFromHashtable();
+ if (mIsLiveList && mRootNode) {
+ mRootNode->RemoveMutationObserver(this);
+ }
+
+ if (mDestroyFunc) {
+ // Clean up mData
+ (*mDestroyFunc)(mData);
+ }
+}
+
+JSObject* nsContentList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return HTMLCollection_Binding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList, nsIHTMLCollection,
+ nsIMutationObserver)
+
+uint32_t nsContentList::Length(bool aDoFlush) {
+ BringSelfUpToDate(aDoFlush);
+
+ return mElements.Length();
+}
+
+nsIContent* nsContentList::Item(uint32_t aIndex, bool aDoFlush) {
+ if (mRootNode && aDoFlush && mFlushesNeeded) {
+ // XXX sXBL/XBL2 issue
+ Document* doc = mRootNode->GetUncomposedDoc();
+ if (doc) {
+ // Flush pending content changes Bug 4891.
+ doc->FlushPendingNotifications(FlushType::ContentAndNotify);
+ }
+ }
+
+ if (mState != State::UpToDate) {
+ PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1);
+ }
+
+ ASSERT_IN_SYNC;
+ NS_ASSERTION(!mRootNode || mState != State::Dirty,
+ "PopulateSelf left the list in a dirty (useless) state!");
+
+ return mElements.SafeElementAt(aIndex);
+}
+
+inline void nsContentList::InsertElementInNamedItemsCache(
+ nsIContent& aContent) {
+ const bool hasName = aContent.HasName();
+ const bool hasId = aContent.HasID();
+ if (!hasName && !hasId) {
+ return;
+ }
+
+ Element* el = aContent.AsElement();
+ MOZ_ASSERT_IF(hasName, el->IsHTMLElement());
+
+ uint32_t i = 0;
+ while (BorrowedAttrInfo info = el->GetAttrInfoAt(i++)) {
+ const bool valid = (info.mName->Equals(nsGkAtoms::name) && hasName) ||
+ (info.mName->Equals(nsGkAtoms::id) && hasId);
+ if (!valid) {
+ continue;
+ }
+
+ if (!mNamedItemsCache) {
+ mNamedItemsCache = MakeUnique<NamedItemsCache>();
+ }
+
+ nsAtom* name = info.mValue->GetAtomValue();
+ // NOTE: LookupOrInsert makes sure we keep the first element we find for a
+ // given name.
+ mNamedItemsCache->LookupOrInsert(name, el);
+ }
+}
+
+inline void nsContentList::InvalidateNamedItemsCacheForAttributeChange(
+ int32_t aNamespaceID, nsAtom* aAttribute) {
+ if (!mNamedItemsCacheValid) {
+ return;
+ }
+ if ((aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::name) &&
+ aNamespaceID == kNameSpaceID_None) {
+ InvalidateNamedItemsCache();
+ }
+}
+
+inline void nsContentList::InvalidateNamedItemsCacheForInsertion(
+ Element& aElement) {
+ if (!mNamedItemsCacheValid) {
+ return;
+ }
+
+ InsertElementInNamedItemsCache(aElement);
+}
+
+inline void nsContentList::InvalidateNamedItemsCacheForDeletion(
+ Element& aElement) {
+ if (!mNamedItemsCacheValid) {
+ return;
+ }
+ if (aElement.HasName() || aElement.HasID()) {
+ InvalidateNamedItemsCache();
+ }
+}
+
+void nsContentList::EnsureNamedItemsCacheValid(bool aDoFlush) {
+ BringSelfUpToDate(aDoFlush);
+
+ if (mNamedItemsCacheValid) {
+ return;
+ }
+
+ MOZ_ASSERT(!mNamedItemsCache);
+
+ // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
+ // XXX: Blink/WebKit don't follow the spec here, and searches first-by-id,
+ // then by name.
+ for (const nsCOMPtr<nsIContent>& content : mElements) {
+ InsertElementInNamedItemsCache(*content);
+ }
+
+ mNamedItemsCacheValid = true;
+}
+
+Element* nsContentList::NamedItem(const nsAString& aName, bool aDoFlush) {
+ if (aName.IsEmpty()) {
+ return nullptr;
+ }
+
+ EnsureNamedItemsCacheValid(aDoFlush);
+
+ if (!mNamedItemsCache) {
+ return nullptr;
+ }
+
+ // Typically IDs and names are atomized
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ NS_ENSURE_TRUE(name, nullptr);
+
+ return mNamedItemsCache->Get(name);
+}
+
+void nsContentList::GetSupportedNames(nsTArray<nsString>& aNames) {
+ BringSelfUpToDate(true);
+
+ AutoTArray<nsAtom*, 8> atoms;
+ for (uint32_t i = 0; i < mElements.Length(); ++i) {
+ nsIContent* content = mElements.ElementAt(i);
+ if (content->HasID()) {
+ nsAtom* id = content->GetID();
+ MOZ_ASSERT(id != nsGkAtoms::_empty, "Empty ids don't get atomized");
+ if (!atoms.Contains(id)) {
+ atoms.AppendElement(id);
+ }
+ }
+
+ nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(content);
+ if (el) {
+ // XXXbz should we be checking for particular tags here? How
+ // stable is this part of the spec?
+ // Note: nsINode::HasName means the name is exposed on the document,
+ // which is false for options, so we don't check it here.
+ const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
+ if (val && val->Type() == nsAttrValue::eAtom) {
+ nsAtom* name = val->GetAtomValue();
+ MOZ_ASSERT(name != nsGkAtoms::_empty, "Empty names don't get atomized");
+ if (!atoms.Contains(name)) {
+ atoms.AppendElement(name);
+ }
+ }
+ }
+ }
+
+ uint32_t atomsLen = atoms.Length();
+ nsString* names = aNames.AppendElements(atomsLen);
+ for (uint32_t i = 0; i < atomsLen; ++i) {
+ atoms[i]->ToString(names[i]);
+ }
+}
+
+int32_t nsContentList::IndexOf(nsIContent* aContent, bool aDoFlush) {
+ BringSelfUpToDate(aDoFlush);
+
+ return mElements.IndexOf(aContent);
+}
+
+int32_t nsContentList::IndexOf(nsIContent* aContent) {
+ return IndexOf(aContent, true);
+}
+
+void nsContentList::NodeWillBeDestroyed(nsINode* aNode) {
+ // We shouldn't do anything useful from now on
+
+ RemoveFromCaches();
+ mRootNode = nullptr;
+
+ // We will get no more updates, so we can never know we're up to
+ // date
+ SetDirty();
+}
+
+void nsContentList::LastRelease() {
+ RemoveFromCaches();
+ if (mIsLiveList && mRootNode) {
+ mRootNode->RemoveMutationObserver(this);
+ mRootNode = nullptr;
+ }
+ SetDirty();
+}
+
+Element* nsContentList::GetElementAt(uint32_t aIndex) {
+ return static_cast<Element*>(Item(aIndex, true));
+}
+
+nsIContent* nsContentList::Item(uint32_t aIndex) {
+ return GetElementAt(aIndex);
+}
+
+void nsContentList::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ MOZ_ASSERT(aElement, "Must have a content node to work with");
+
+ if (mState == State::Dirty ||
+ !MayContainRelevantNodes(aElement->GetParentNode()) ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
+ // Either we're already dirty or aElement will never match us.
+ return;
+ }
+
+ InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute);
+
+ if (!mFunc || !mFuncMayDependOnAttr) {
+ // aElement might be relevant but the attribute change doesn't affect
+ // whether we match it.
+ return;
+ }
+
+ if (Match(aElement)) {
+ if (mElements.IndexOf(aElement) == mElements.NoIndex) {
+ // We match aElement now, and it's not in our list already. Just dirty
+ // ourselves; this is simpler than trying to figure out where to insert
+ // aElement.
+ SetDirty();
+ }
+ } else {
+ // We no longer match aElement. Remove it from our list. If it's
+ // already not there, this is a no-op (though a potentially
+ // expensive one). Either way, no change of mState is required
+ // here.
+ if (mElements.RemoveElement(aElement)) {
+ InvalidateNamedItemsCacheForDeletion(*aElement);
+ }
+ }
+}
+
+void nsContentList::ContentAppended(nsIContent* aFirstNewContent) {
+ nsIContent* container = aFirstNewContent->GetParent();
+ MOZ_ASSERT(container, "Can't get at the new content if no container!");
+
+ /*
+ * If the state is State::Dirty then we have no useful information in our list
+ * and we want to put off doing work as much as possible.
+ *
+ * Also, if container is anonymous from our point of view, we know that we
+ * can't possibly be matching any of the kids.
+ *
+ * Optimize out also the common case when just one new node is appended and
+ * it doesn't match us.
+ */
+ if (mState == State::Dirty ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, container) ||
+ !MayContainRelevantNodes(container) ||
+ (!aFirstNewContent->HasChildren() &&
+ !aFirstNewContent->GetNextSibling() && !MatchSelf(aFirstNewContent))) {
+ MaybeMarkDirty();
+ return;
+ }
+
+ /*
+ * We want to handle the case of ContentAppended by sometimes
+ * appending the content to our list, not just setting state to
+ * State::Dirty, since most of our ContentAppended notifications
+ * should come during pageload and be at the end of the document.
+ * Do a bit of work to see whether we could just append to what we
+ * already have.
+ */
+
+ uint32_t ourCount = mElements.Length();
+ const bool appendingToList = [&] {
+ if (ourCount == 0) {
+ return true;
+ }
+ if (mRootNode == container) {
+ return true;
+ }
+ return nsContentUtils::PositionIsBefore(mElements.LastElement(),
+ aFirstNewContent);
+ }();
+
+ if (!appendingToList) {
+ // The new stuff is somewhere in the middle of our list; check
+ // whether we need to invalidate
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ if (MatchSelf(cur)) {
+ // Uh-oh. We're gonna have to add elements into the middle
+ // of our list. That's not worth the effort.
+ SetDirty();
+ break;
+ }
+ }
+
+ ASSERT_IN_SYNC;
+ return;
+ }
+
+ /*
+ * At this point we know we could append. If we're not up to
+ * date, however, that would be a bad idea -- it could miss some
+ * content that we never picked up due to being lazy. Further, we
+ * may never get asked for this content... so don't grab it yet.
+ */
+ if (mState == State::Lazy) {
+ return;
+ }
+
+ /*
+ * We're up to date. That means someone's actively using us; we
+ * may as well grab this content....
+ */
+ if (mDeep) {
+ for (nsIContent* cur = aFirstNewContent; cur;
+ cur = cur->GetNextNode(container)) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur);
+ InvalidateNamedItemsCacheForInsertion(*cur->AsElement());
+ }
+ }
+ } else {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur);
+ InvalidateNamedItemsCacheForInsertion(*cur->AsElement());
+ }
+ }
+ }
+
+ ASSERT_IN_SYNC;
+}
+
+void nsContentList::ContentInserted(nsIContent* aChild) {
+ // Note that aChild->GetParentNode() can be null here if we are inserting into
+ // the document itself; any attempted optimizations to this method should deal
+ // with that.
+ if (mState != State::Dirty &&
+ MayContainRelevantNodes(aChild->GetParentNode()) &&
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
+ MatchSelf(aChild)) {
+ SetDirty();
+ }
+
+ ASSERT_IN_SYNC;
+}
+
+void nsContentList::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (mState != State::Dirty &&
+ MayContainRelevantNodes(aChild->GetParentNode()) &&
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
+ MatchSelf(aChild)) {
+ SetDirty();
+ }
+
+ ASSERT_IN_SYNC;
+}
+
+bool nsContentList::Match(Element* aElement) {
+ if (mFunc) {
+ return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData);
+ }
+
+ if (!mXMLMatchAtom) return false;
+
+ NodeInfo* ni = aElement->NodeInfo();
+
+ bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown;
+ bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard;
+ bool toReturn = mMatchAll;
+ if (!unknown && !wildcard) toReturn &= ni->NamespaceEquals(mMatchNameSpaceId);
+
+ if (toReturn) return toReturn;
+
+ bool matchHTML =
+ mIsHTMLDocument && aElement->GetNameSpaceID() == kNameSpaceID_XHTML;
+
+ if (unknown) {
+ return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom)
+ : ni->QualifiedNameEquals(mXMLMatchAtom);
+ }
+
+ if (wildcard) {
+ return matchHTML ? ni->Equals(mHTMLMatchAtom) : ni->Equals(mXMLMatchAtom);
+ }
+
+ return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId)
+ : ni->Equals(mXMLMatchAtom, mMatchNameSpaceId);
+}
+
+bool nsContentList::MatchSelf(nsIContent* aContent) {
+ MOZ_ASSERT(aContent, "Can't match null stuff, you know");
+ MOZ_ASSERT(mDeep || aContent->GetParentNode() == mRootNode,
+ "MatchSelf called on a node that we can't possibly match");
+
+ if (!aContent->IsElement()) {
+ return false;
+ }
+
+ if (Match(aContent->AsElement())) return true;
+
+ if (!mDeep) return false;
+
+ for (nsIContent* cur = aContent->GetFirstChild(); cur;
+ cur = cur->GetNextNode(aContent)) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsContentList::PopulateSelf(uint32_t aNeededLength,
+ uint32_t aExpectedElementsIfDirty) {
+ if (!mRootNode) {
+ return;
+ }
+
+ ASSERT_IN_SYNC;
+
+ uint32_t count = mElements.Length();
+ NS_ASSERTION(mState != State::Dirty || count == aExpectedElementsIfDirty,
+ "Reset() not called when setting state to State::Dirty?");
+
+ if (count >= aNeededLength) // We're all set
+ return;
+
+ uint32_t elementsToAppend = aNeededLength - count;
+#ifdef DEBUG
+ uint32_t invariant = elementsToAppend + mElements.Length();
+#endif
+
+ if (mDeep) {
+ // If we already have nodes start searching at the last one, otherwise
+ // start searching at the root.
+ nsINode* cur = count ? mElements[count - 1].get() : mRootNode;
+ do {
+ cur = cur->GetNextNode(mRootNode);
+ if (!cur) {
+ break;
+ }
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ // Append AsElement() to get nsIContent instead of nsINode
+ mElements.AppendElement(cur->AsElement());
+ --elementsToAppend;
+ }
+ } while (elementsToAppend);
+ } else {
+ nsIContent* cur = count ? mElements[count - 1]->GetNextSibling()
+ : mRootNode->GetFirstChild();
+ for (; cur && elementsToAppend; cur = cur->GetNextSibling()) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur);
+ --elementsToAppend;
+ }
+ }
+ }
+
+ NS_ASSERTION(elementsToAppend + mElements.Length() == invariant,
+ "Something is awry!");
+
+ if (elementsToAppend != 0) {
+ mState = State::UpToDate;
+ } else {
+ mState = State::Lazy;
+ }
+
+ SetEnabledCallbacks(nsIMutationObserver::kAll);
+
+ ASSERT_IN_SYNC;
+}
+
+void nsContentList::RemoveFromHashtable() {
+ if (mFunc) {
+ // nsCacheableFuncStringContentList can be in a hash table without being
+ // in gContentListHashTable, but it will have been removed from the hash
+ // table in its dtor before it runs the nsContentList dtor.
+ MOZ_RELEASE_ASSERT(!mInHashtable);
+
+ // This can't be in gContentListHashTable.
+ return;
+ }
+
+ nsDependentAtomString str(mXMLMatchAtom);
+ nsContentListKey key(mRootNode, mMatchNameSpaceId, str, mIsHTMLDocument);
+ sRecentlyUsedContentLists.Remove(key);
+
+ if (gContentListHashTable) {
+ gContentListHashTable->RemoveEntry(&key);
+
+ if (gContentListHashTable->Count() == 0) {
+ gContentListHashTable = nullptr;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!mInHashtable);
+}
+
+void nsContentList::BringSelfUpToDate(bool aDoFlush) {
+ if (mFlushesNeeded && mRootNode && aDoFlush) {
+ // XXX sXBL/XBL2 issue
+ if (Document* doc = mRootNode->GetUncomposedDoc()) {
+ // Flush pending content changes Bug 4891.
+ doc->FlushPendingNotifications(FlushType::ContentAndNotify);
+ }
+ }
+
+ if (mState != State::UpToDate) {
+ PopulateSelf(uint32_t(-1));
+ }
+
+ mMissedUpdates = 0;
+
+ ASSERT_IN_SYNC;
+ NS_ASSERTION(!mRootNode || mState == State::UpToDate,
+ "PopulateSelf dod not bring content list up to date!");
+}
+
+nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() {
+ RemoveFromFuncStringHashtable();
+}
+
+void nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() {
+ if (!gFuncStringContentListHashTable) {
+ MOZ_RELEASE_ASSERT(!mInHashtable);
+ return;
+ }
+
+ nsFuncStringCacheKey key(mRootNode, mFunc, mString);
+ gFuncStringContentListHashTable->RemoveEntry(&key);
+
+ if (gFuncStringContentListHashTable->Count() == 0) {
+ gFuncStringContentListHashTable = nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(!mInHashtable);
+}
+
+#ifdef DEBUG_CONTENT_LIST
+void nsContentList::AssertInSync() {
+ if (mState == State::Dirty) {
+ return;
+ }
+
+ if (!mRootNode) {
+ NS_ASSERTION(mElements.Length() == 0 && mState == State::Dirty,
+ "Empty iterator isn't quite empty?");
+ return;
+ }
+
+ // XXX This code will need to change if nsContentLists can ever match
+ // elements that are outside of the document element.
+ nsIContent* root = mRootNode->IsDocument()
+ ? mRootNode->AsDocument()->GetRootElement()
+ : mRootNode->AsContent();
+
+ PreContentIterator preOrderIter;
+ if (mDeep) {
+ preOrderIter.Init(root);
+ preOrderIter.First();
+ }
+
+ uint32_t cnt = 0, index = 0;
+ while (true) {
+ if (cnt == mElements.Length() && mState == State::Lazy) {
+ break;
+ }
+
+ nsIContent* cur =
+ mDeep ? preOrderIter.GetCurrentNode() : mRootNode->GetChildAt(index++);
+ if (!cur) {
+ break;
+ }
+
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur,
+ "Elements is out of sync");
+ ++cnt;
+ }
+
+ if (mDeep) {
+ preOrderIter.Next();
+ }
+ }
+
+ NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
+}
+#endif
+
+//-----------------------------------------------------
+// nsCachableElementsByNameNodeList
+
+JSObject* nsCachableElementsByNameNodeList::WrapObject(
+ JSContext* cx, JS::Handle<JSObject*> aGivenProto) {
+ return NodeList_Binding::Wrap(cx, this, aGivenProto);
+}
+
+void nsCachableElementsByNameNodeList::AttributeChanged(
+ Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType, const nsAttrValue* aOldValue) {
+ // No need to rebuild the list if the changed attribute is not the name
+ // attribute.
+ if (aAttribute != nsGkAtoms::name) {
+ InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute);
+ return;
+ }
+
+ nsCacheableFuncStringContentList::AttributeChanged(
+ aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
+}
+
+//-----------------------------------------------------
+// nsCacheableFuncStringHTMLCollection
+
+JSObject* nsCacheableFuncStringHTMLCollection::WrapObject(
+ JSContext* cx, JS::Handle<JSObject*> aGivenProto) {
+ return HTMLCollection_Binding::Wrap(cx, this, aGivenProto);
+}
+
+//-----------------------------------------------------
+// nsLabelsNodeList
+
+JSObject* nsLabelsNodeList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return NodeList_Binding::Wrap(cx, this, aGivenProto);
+}
+
+void nsLabelsNodeList::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ MOZ_ASSERT(aElement, "Must have a content node to work with");
+ if (mState == State::Dirty ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
+ return;
+ }
+
+ InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute);
+
+ // We need to handle input type changes to or from "hidden".
+ if (aElement->IsHTMLElement(nsGkAtoms::input) &&
+ aAttribute == nsGkAtoms::type && aNameSpaceID == kNameSpaceID_None) {
+ SetDirty();
+ return;
+ }
+}
+
+void nsLabelsNodeList::ContentAppended(nsIContent* aFirstNewContent) {
+ nsIContent* container = aFirstNewContent->GetParent();
+ // If a labelable element is moved to outside or inside of
+ // nested associated labels, we're gonna have to modify
+ // the content list.
+ if (mState != State::Dirty ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, container)) {
+ SetDirty();
+ return;
+ }
+}
+
+void nsLabelsNodeList::ContentInserted(nsIContent* aChild) {
+ // If a labelable element is moved to outside or inside of
+ // nested associated labels, we're gonna have to modify
+ // the content list.
+ if (mState != State::Dirty ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
+ SetDirty();
+ return;
+ }
+}
+
+void nsLabelsNodeList::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ // If a labelable element is removed, we're gonna have to clean
+ // the content list.
+ if (mState != State::Dirty ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
+ SetDirty();
+ return;
+ }
+}
+
+void nsLabelsNodeList::MaybeResetRoot(nsINode* aRootNode) {
+ MOZ_ASSERT(aRootNode, "Must have root");
+ if (mRootNode == aRootNode) {
+ return;
+ }
+
+ MOZ_ASSERT(mIsLiveList, "nsLabelsNodeList is always a live list");
+ if (mRootNode) {
+ mRootNode->RemoveMutationObserver(this);
+ }
+ mRootNode = aRootNode;
+ mRootNode->AddMutationObserver(this);
+ SetDirty();
+}
+
+void nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength,
+ uint32_t aExpectedElementsIfDirty) {
+ if (!mRootNode) {
+ return;
+ }
+
+ // Start searching at the root.
+ nsINode* cur = mRootNode;
+ if (mElements.IsEmpty() && cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur->AsElement());
+ ++aExpectedElementsIfDirty;
+ }
+
+ nsContentList::PopulateSelf(aNeededLength, aExpectedElementsIfDirty);
+}
diff --git a/dom/base/nsContentList.h b/dom/base/nsContentList.h
new file mode 100644
index 0000000000..8e2bd606ad
--- /dev/null
+++ b/dom/base/nsContentList.h
@@ -0,0 +1,655 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsBaseContentList is a basic list of content nodes; nsContentList
+ * is a commonly used NodeList implementation (used for
+ * getElementsByTagName, some properties on HTMLDocument/Document, etc).
+ */
+
+#ifndef nsContentList_h___
+#define nsContentList_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContentListDeclarations.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIHTMLCollection.h"
+#include "nsINodeList.h"
+#include "nsStubMutationObserver.h"
+#include "nsAtom.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNameSpaceManager.h"
+#include "nsWrapperCache.h"
+#include "nsHashKeys.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file.
+#include "nsIContent.h"
+
+namespace mozilla::dom {
+class Element;
+} // namespace mozilla::dom
+
+class nsBaseContentList : public nsINodeList {
+ protected:
+ using Element = mozilla::dom::Element;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsINodeList
+ int32_t IndexOf(nsIContent* aContent) override;
+ nsIContent* Item(uint32_t aIndex) override;
+
+ uint32_t Length() override { return mElements.Length(); }
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(nsBaseContentList)
+
+ void AppendElement(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ mElements.AppendElement(aContent);
+ }
+ void MaybeAppendElement(nsIContent* aContent) {
+ if (aContent) {
+ AppendElement(aContent);
+ }
+ }
+
+ /**
+ * Insert the element at a given index, shifting the objects at
+ * the given index and later to make space.
+ * @param aContent Element to insert, must not be null
+ * @param aIndex Index to insert the element at.
+ */
+ void InsertElementAt(nsIContent* aContent, int32_t aIndex) {
+ NS_ASSERTION(aContent, "Element to insert must not be null");
+ mElements.InsertElementAt(aIndex, aContent);
+ }
+
+ void RemoveElement(nsIContent* aContent) {
+ mElements.RemoveElement(aContent);
+ }
+
+ void Reset() { mElements.Clear(); }
+
+ virtual int32_t IndexOf(nsIContent* aContent, bool aDoFlush);
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override = 0;
+
+ void SetCapacity(uint32_t aCapacity) { mElements.SetCapacity(aCapacity); }
+
+ virtual void LastRelease() {}
+
+ // Memory reporting. For now, subclasses of nsBaseContentList don't really
+ // need to report any members that are not part of the object itself, so we
+ // don't need to make this virtual.
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ virtual ~nsBaseContentList();
+
+ /**
+ * To be called from non-destructor locations (e.g. unlink) that want to
+ * remove from caches. Cacheable subclasses should override.
+ */
+ virtual void RemoveFromCaches() {}
+
+ AutoTArray<nsCOMPtr<nsIContent>, 10> mElements;
+};
+
+class nsSimpleContentList : public nsBaseContentList {
+ public:
+ explicit nsSimpleContentList(nsINode* aRoot)
+ : nsBaseContentList(), mRoot(aRoot) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSimpleContentList,
+ nsBaseContentList)
+
+ nsINode* GetParentObject() override { return mRoot; }
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ protected:
+ virtual ~nsSimpleContentList() = default;
+
+ private:
+ // This has to be a strong reference, the root might go away before the list.
+ nsCOMPtr<nsINode> mRoot;
+};
+
+// Used for returning lists that will always be empty, such as the applets list
+// in HTML Documents
+class nsEmptyContentList final : public nsBaseContentList,
+ public nsIHTMLCollection {
+ public:
+ explicit nsEmptyContentList(nsINode* aRoot)
+ : nsBaseContentList(), mRoot(aRoot) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsEmptyContentList,
+ nsBaseContentList)
+
+ nsINode* GetParentObject() override { return mRoot; }
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ JSObject* GetWrapperPreserveColorInternal() override {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+ void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override {
+ nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
+ }
+
+ uint32_t Length() final { return 0; }
+ nsIContent* Item(uint32_t aIndex) override;
+ Element* GetElementAt(uint32_t index) override;
+ Element* GetFirstNamedElement(const nsAString& aName, bool& aFound) override;
+ void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+ protected:
+ virtual ~nsEmptyContentList() = default;
+
+ private:
+ // This has to be a strong reference, the root might go away before the list.
+ nsCOMPtr<nsINode> mRoot;
+};
+
+/**
+ * Class that's used as the key to hash nsContentList implementations
+ * for fast retrieval
+ */
+struct nsContentListKey {
+ // We have to take an aIsHTMLDocument arg for two reasons:
+ // 1) We don't want to include Document.h in this header.
+ // 2) We need to do that to make nsContentList::RemoveFromHashtable
+ // work, because by the time it's called the document of the
+ // list's root node might have changed.
+ nsContentListKey(nsINode* aRootNode, int32_t aMatchNameSpaceId,
+ const nsAString& aTagname, bool aIsHTMLDocument)
+ : mRootNode(aRootNode),
+ mMatchNameSpaceId(aMatchNameSpaceId),
+ mTagname(aTagname),
+ mIsHTMLDocument(aIsHTMLDocument),
+ mHash(mozilla::AddToHash(mozilla::HashString(aTagname), mRootNode,
+ mMatchNameSpaceId, mIsHTMLDocument)) {}
+
+ nsContentListKey(const nsContentListKey& aContentListKey) = default;
+
+ inline uint32_t GetHash(void) const { return mHash; }
+
+ nsINode* const mRootNode; // Weak ref
+ const int32_t mMatchNameSpaceId;
+ const nsAString& mTagname;
+ bool mIsHTMLDocument;
+ const uint32_t mHash;
+};
+
+/**
+ * Class that implements a possibly live NodeList that matches Elements
+ * in the tree based on some criterion.
+ */
+class nsContentList : public nsBaseContentList,
+ public nsIHTMLCollection,
+ public nsStubMutationObserver {
+ protected:
+ enum class State : uint8_t {
+ // The list is up to date and need not do any walking to be able to answer
+ // any questions anyone may have.
+ UpToDate = 0,
+ // The list contains no useful information and if anyone asks it anything it
+ // will have to populate itself before answering.
+ Dirty,
+ // The list has populated itself to a certain extent and that that part of
+ // the list is still valid. Requests for things outside that part of the
+ // list will require walking the tree some more. When a list is in this
+ // state, the last thing in mElements is the last node in the tree that the
+ // list looked at.
+ Lazy,
+ };
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ /**
+ * @param aRootNode The node under which to limit our search.
+ * @param aMatchAtom An atom whose meaning depends on aMatchNameSpaceId.
+ * The special value "*" always matches whatever aMatchAtom
+ * is matched against.
+ * @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the
+ * tagName to match.
+ * If kNameSpaceID_Wildcard, then aMatchAtom is the
+ * localName to match.
+ * Otherwise we match nodes whose namespace is
+ * aMatchNameSpaceId and localName matches
+ * aMatchAtom.
+ * @param aDeep If false, then look only at children of the root, nothing
+ * deeper. If true, then look at the whole subtree rooted at
+ * our root.
+ * @param aLiveList Whether the created list should be a live list observing
+ * mutations to the DOM tree.
+ */
+ nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId,
+ nsAtom* aHTMLMatchAtom, nsAtom* aXMLMatchAtom,
+ bool aDeep = true, bool aLiveList = true);
+
+ /**
+ * @param aRootNode The node under which to limit our search.
+ * @param aFunc the function to be called to determine whether we match.
+ * This function MUST NOT ever cause mutation of the DOM.
+ * The nsContentList implementation guarantees that everything
+ * passed to the function will be IsElement().
+ * @param aDestroyFunc the function that will be called to destroy aData
+ * @param aData closure data that will need to be passed back to aFunc
+ * @param aDeep If false, then look only at children of the root, nothing
+ * deeper. If true, then look at the whole subtree rooted at
+ * our root.
+ * @param aMatchAtom an atom to be passed back to aFunc
+ * @param aMatchNameSpaceId a namespace id to be passed back to aFunc
+ * @param aFuncMayDependOnAttr a boolean that indicates whether this list is
+ * sensitive to attribute changes.
+ * @param aLiveList Whether the created list should be a live list observing
+ * mutations to the DOM tree.
+ */
+ nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc, void* aData,
+ bool aDeep = true, nsAtom* aMatchAtom = nullptr,
+ int32_t aMatchNameSpaceId = kNameSpaceID_None,
+ bool aFuncMayDependOnAttr = true, bool aLiveList = true);
+
+ // nsWrapperCache
+ using nsWrapperCache::GetWrapperPreserveColor;
+ using nsWrapperCache::PreserveWrapper;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ protected:
+ virtual ~nsContentList();
+
+ JSObject* GetWrapperPreserveColorInternal() override {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+ void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override {
+ nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
+ }
+
+ public:
+ // nsBaseContentList overrides
+ int32_t IndexOf(nsIContent* aContent, bool aDoFlush) override;
+ int32_t IndexOf(nsIContent* aContent) override;
+ nsINode* GetParentObject() override { return mRootNode; }
+
+ uint32_t Length() final { return Length(true); }
+ nsIContent* Item(uint32_t aIndex) final;
+ Element* GetElementAt(uint32_t index) override;
+ Element* GetFirstNamedElement(const nsAString& aName, bool& aFound) override {
+ Element* item = NamedItem(aName, true);
+ aFound = !!item;
+ return item;
+ }
+ void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+ // nsContentList public methods
+ uint32_t Length(bool aDoFlush);
+ nsIContent* Item(uint32_t aIndex, bool aDoFlush);
+ Element* NamedItem(const nsAString& aName, bool aDoFlush);
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ static nsContentList* FromSupports(nsISupports* aSupports) {
+ nsINodeList* list = static_cast<nsINodeList*>(aSupports);
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsINodeList> list_qi = do_QueryInterface(aSupports);
+
+ // If this assertion fires the QI implementation for the object in
+ // question doesn't use the nsINodeList pointer as the nsISupports
+ // pointer. That must be fixed, or we'll crash...
+ NS_ASSERTION(list_qi == list, "Uh, fix QI!");
+ }
+#endif
+ return static_cast<nsContentList*>(list);
+ }
+
+ bool MatchesKey(const nsContentListKey& aKey) const {
+ // The root node is most commonly the same: the document. And the
+ // most common namespace id is kNameSpaceID_Unknown. So check the
+ // string first. Cases in which whether our root's ownerDocument
+ // is HTML changes are extremely rare, so check those last.
+ MOZ_ASSERT(mXMLMatchAtom,
+ "How did we get here with a null match atom on our list?");
+ return mXMLMatchAtom->Equals(aKey.mTagname) &&
+ mRootNode == aKey.mRootNode &&
+ mMatchNameSpaceId == aKey.mMatchNameSpaceId &&
+ mIsHTMLDocument == aKey.mIsHTMLDocument;
+ }
+
+ /**
+ * Sets the state to LIST_DIRTY and clears mElements array.
+ * @note This is the only acceptable way to set state to LIST_DIRTY.
+ */
+ void SetDirty() {
+ mState = State::Dirty;
+ InvalidateNamedItemsCache();
+ Reset();
+ SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed);
+ }
+
+ void LastRelease() override;
+
+ class HashEntry;
+
+ protected:
+ // A cache from name to the first named item in mElements. Only possibly
+ // non-null when mState is State::UpToDate. Elements are kept alive by our
+ // mElements array.
+ using NamedItemsCache = nsTHashMap<RefPtr<nsAtom>, Element*>;
+
+ void InvalidateNamedItemsCache() {
+ mNamedItemsCache = nullptr;
+ mNamedItemsCacheValid = false;
+ }
+
+ inline void InsertElementInNamedItemsCache(nsIContent&);
+ inline void InvalidateNamedItemsCacheForAttributeChange(int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+ inline void InvalidateNamedItemsCacheForInsertion(Element&);
+ inline void InvalidateNamedItemsCacheForDeletion(Element&);
+
+ void EnsureNamedItemsCacheValid(bool aDoFlush);
+
+ /**
+ * Returns whether the element matches our criterion
+ *
+ * @param aElement the element to attempt to match
+ * @return whether we match
+ */
+ bool Match(Element* aElement);
+ /**
+ * See if anything in the subtree rooted at aContent, including
+ * aContent itself, matches our criterion.
+ *
+ * @param aContent the root of the subtree to match against
+ * @return whether we match something in the tree rooted at aContent
+ */
+ bool MatchSelf(nsIContent* aContent);
+
+ /**
+ * Populate our list. Stop once we have at least aNeededLength
+ * elements. At the end of PopulateSelf running, either the last
+ * node we examined is the last node in our array or we have
+ * traversed the whole document (or both).
+ *
+ * @param aNeededLength the length the list should have when we are
+ * done (unless it exhausts the document)
+ * @param aExpectedElementsIfDirty is for debugging only to
+ * assert that mElements has expected number of entries.
+ */
+ virtual void PopulateSelf(uint32_t aNeededLength,
+ uint32_t aExpectedElementsIfDirty = 0);
+
+ /**
+ * @param aContainer a content node which must be a descendant of
+ * mRootNode
+ * @return true if children or descendants of aContainer could match our
+ * criterion.
+ * false otherwise.
+ */
+ bool MayContainRelevantNodes(nsINode* aContainer) {
+ return mDeep || aContainer == mRootNode;
+ }
+
+ /**
+ * Remove ourselves from the hashtable that caches commonly accessed
+ * content lists. Generally done on destruction.
+ */
+ void RemoveFromHashtable();
+ /**
+ * If state is not LIST_UP_TO_DATE, fully populate ourselves with
+ * all the nodes we can find.
+ */
+ inline void BringSelfUpToDate(bool aDoFlush);
+
+ /**
+ * To be called from non-destructor locations that want to remove from caches.
+ * Needed because if subclasses want to have cache behavior they can't just
+ * override RemoveFromHashtable(), since we call that in our destructor.
+ */
+ void RemoveFromCaches() override { RemoveFromHashtable(); }
+
+ void MaybeMarkDirty() {
+ if (mState != State::Dirty && ++mMissedUpdates > 128) {
+ mMissedUpdates = 0;
+ SetDirty();
+ }
+ }
+
+ nsINode* mRootNode; // Weak ref
+ int32_t mMatchNameSpaceId;
+ RefPtr<nsAtom> mHTMLMatchAtom;
+ RefPtr<nsAtom> mXMLMatchAtom;
+
+ /**
+ * Function to use to determine whether a piece of content matches
+ * our criterion
+ */
+ nsContentListMatchFunc mFunc = nullptr;
+ /**
+ * Cleanup closure data with this.
+ */
+ nsContentListDestroyFunc mDestroyFunc = nullptr;
+ /**
+ * Closure data to pass to mFunc when we call it
+ */
+ void* mData = nullptr;
+
+ mozilla::UniquePtr<NamedItemsCache> mNamedItemsCache;
+
+ uint8_t mMissedUpdates = 0;
+
+ // The current state of the list.
+ State mState;
+
+ /**
+ * True if we are looking for elements named "*"
+ */
+ bool mMatchAll : 1;
+ /**
+ * Whether to actually descend the tree. If this is false, we won't
+ * consider grandkids of mRootNode.
+ */
+ bool mDeep : 1;
+ /**
+ * Whether the return value of mFunc could depend on the values of
+ * attributes.
+ */
+ bool mFuncMayDependOnAttr : 1;
+ /**
+ * Whether we actually need to flush to get our state correct.
+ */
+ bool mFlushesNeeded : 1;
+ /**
+ * Whether the ownerDocument of our root node at list creation time was an
+ * HTML document. Only needed when we're doing a namespace/atom match, not
+ * when doing function matching, always false otherwise.
+ */
+ bool mIsHTMLDocument : 1;
+ /**
+ * True mNamedItemsCache is valid. Note mNamedItemsCache might still be null
+ * if there's no named items at all.
+ */
+ bool mNamedItemsCacheValid : 1;
+ /**
+ * Whether the list observes mutations to the DOM tree.
+ */
+ const bool mIsLiveList : 1;
+ /*
+ * True if this content list is cached in a hash table.
+ * For nsContentList (but not its subclasses), the hash table is
+ * gContentListHashTable.
+ * For nsCacheableFuncStringContentList, the hash table is
+ * gFuncStringContentListHashTable.
+ * Other subclasses of nsContentList can't be in hash tables.
+ */
+ bool mInHashtable : 1;
+
+#ifdef DEBUG_CONTENT_LIST
+ void AssertInSync();
+#endif
+};
+
+/**
+ * A class of cacheable content list; cached on the combination of aRootNode +
+ * aFunc + aDataString
+ */
+class nsCacheableFuncStringContentList;
+
+class MOZ_STACK_CLASS nsFuncStringCacheKey {
+ public:
+ nsFuncStringCacheKey(nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ const nsAString& aString)
+ : mRootNode(aRootNode), mFunc(aFunc), mString(aString) {}
+
+ uint32_t GetHash(void) const {
+ uint32_t hash = mozilla::HashString(mString);
+ return mozilla::AddToHash(hash, mRootNode, mFunc);
+ }
+
+ private:
+ friend class nsCacheableFuncStringContentList;
+
+ nsINode* const mRootNode;
+ const nsContentListMatchFunc mFunc;
+ const nsAString& mString;
+};
+
+// aDestroyFunc is allowed to be null
+// aDataAllocator must always return a non-null pointer
+class nsCacheableFuncStringContentList : public nsContentList {
+ public:
+ virtual ~nsCacheableFuncStringContentList();
+
+ bool Equals(const nsFuncStringCacheKey* aKey) {
+ return mRootNode == aKey->mRootNode && mFunc == aKey->mFunc &&
+ mString == aKey->mString;
+ }
+
+ enum ContentListType { eNodeList, eHTMLCollection };
+#ifdef DEBUG
+ ContentListType mType;
+#endif
+
+ class HashEntry;
+
+ protected:
+ nsCacheableFuncStringContentList(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString, mozilla::DebugOnly<ContentListType> aType)
+ : nsContentList(aRootNode, aFunc, aDestroyFunc, nullptr),
+#ifdef DEBUG
+ mType(aType),
+#endif
+ mString(aString) {
+ mData = (*aDataAllocator)(aRootNode, &mString);
+ MOZ_ASSERT(mData);
+ }
+
+ void RemoveFromCaches() override { RemoveFromFuncStringHashtable(); }
+ void RemoveFromFuncStringHashtable();
+
+ nsString mString;
+};
+
+class nsCachableElementsByNameNodeList
+ : public nsCacheableFuncStringContentList {
+ public:
+ nsCachableElementsByNameNodeList(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString)
+ : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
+ aDataAllocator, aString, eNodeList) {}
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+#ifdef DEBUG
+ static const ContentListType sType;
+#endif
+};
+
+class nsCacheableFuncStringHTMLCollection
+ : public nsCacheableFuncStringContentList {
+ public:
+ nsCacheableFuncStringHTMLCollection(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString)
+ : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
+ aDataAllocator, aString,
+ eHTMLCollection) {}
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+#ifdef DEBUG
+ static const ContentListType sType;
+#endif
+};
+
+class nsLabelsNodeList final : public nsContentList {
+ public:
+ nsLabelsNodeList(nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc, void* aData)
+ : nsContentList(aRootNode, aFunc, aDestroyFunc, aData) {}
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * Reset root, mutation observer, and clear content list
+ * if the root has been changed.
+ *
+ * @param aRootNode The node under which to limit our search.
+ */
+ void MaybeResetRoot(nsINode* aRootNode);
+
+ private:
+ /**
+ * Start searching at the last one if we already have nodes, otherwise
+ * start searching at the root.
+ *
+ * @param aNeededLength The list of length should have when we are
+ * done (unless it exhausts the document).
+ * @param aExpectedElementsIfDirty is for debugging only to
+ * assert that mElements has expected number of entries.
+ */
+ void PopulateSelf(uint32_t aNeededLength,
+ uint32_t aExpectedElementsIfDirty = 0) override;
+};
+#endif // nsContentList_h___
diff --git a/dom/base/nsContentListDeclarations.h b/dom/base/nsContentListDeclarations.h
new file mode 100644
index 0000000000..8c737bfa38
--- /dev/null
+++ b/dom/base/nsContentListDeclarations.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentListDeclarations_h
+#define nsContentListDeclarations_h
+
+#include <stdint.h>
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+
+class nsContentList;
+class nsAtom;
+class nsIContent;
+class nsINode;
+
+namespace mozilla::dom {
+class Element;
+} // namespace mozilla::dom
+
+// Magic namespace id that means "match all namespaces". This is
+// negative so it won't collide with actual namespace constants.
+#define kNameSpaceID_Wildcard INT32_MIN
+
+// This is a callback function type that can be used to implement an
+// arbitrary matching algorithm. aContent is the content that may
+// match the list, while aNamespaceID, aAtom, and aData are whatever
+// was passed to the list's constructor.
+using nsContentListMatchFunc = bool (*)(mozilla::dom::Element* aElement,
+ int32_t aNamespaceID, nsAtom* aAtom,
+ void* aData);
+
+using nsContentListDestroyFunc = void (*)(void* aData);
+
+/**
+ * A function that allocates the matching data for this
+ * FuncStringContentList. Returning aString is perfectly fine; in
+ * that case the destructor function should be a no-op.
+ */
+using nsFuncStringContentListDataAllocator = void* (*)(nsINode* aRootNode,
+ const nsString* aString);
+
+// If aMatchNameSpaceId is kNameSpaceID_Unknown, this will return a
+// content list which matches ASCIIToLower(aTagname) against HTML
+// elements in HTML documents and aTagname against everything else.
+// For any other value of aMatchNameSpaceId, the list will match
+// aTagname against all elements.
+already_AddRefed<nsContentList> NS_GetContentList(nsINode* aRootNode,
+ int32_t aMatchNameSpaceId,
+ const nsAString& aTagname);
+
+template <class ListType>
+already_AddRefed<nsContentList> GetFuncStringContentList(
+ nsINode* aRootNode, nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString);
+
+#endif // nsContentListDeclarations_h
diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp
new file mode 100644
index 0000000000..7733263f7d
--- /dev/null
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -0,0 +1,871 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <map>
+#include "nsCOMPtr.h"
+#include "nsIPrincipal.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/PContentPermission.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PContentPermissionRequestParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsContentPermissionHelper.h"
+#include "nsJSUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIWeakReferenceUtils.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
+
+using mozilla::Unused; // <snicker>
+using namespace mozilla::dom;
+using namespace mozilla;
+using DelegateInfo = PermissionDelegateHandler::PermissionDelegateInfo;
+
+namespace mozilla::dom {
+
+class ContentPermissionRequestParent : public PContentPermissionRequestParent {
+ public:
+ // @param aIsRequestDelegatedToUnsafeThirdParty see
+ // mIsRequestDelegatedToUnsafeThirdParty.
+ ContentPermissionRequestParent(
+ const nsTArray<PermissionRequest>& aRequests, Element* aElement,
+ nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
+ const bool aHasValidTransientUserGestureActivation,
+ const bool aIsRequestDelegatedToUnsafeThirdParty);
+ virtual ~ContentPermissionRequestParent();
+
+ bool IsBeingDestroyed();
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
+ nsCOMPtr<Element> mElement;
+ bool mHasValidTransientUserGestureActivation;
+
+ // See nsIPermissionDelegateHandler.maybeUnsafePermissionDelegate.
+ bool mIsRequestDelegatedToUnsafeThirdParty;
+
+ RefPtr<nsContentPermissionRequestProxy> mProxy;
+ nsTArray<PermissionRequest> mRequests;
+
+ private:
+ // Not MOZ_CAN_RUN_SCRIPT because we can't annotate the thing we override yet.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult Recvprompt() override;
+ virtual mozilla::ipc::IPCResult RecvDestroy() override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+ContentPermissionRequestParent::ContentPermissionRequestParent(
+ const nsTArray<PermissionRequest>& aRequests, Element* aElement,
+ nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
+ const bool aHasValidTransientUserGestureActivation,
+ const bool aIsRequestDelegatedToUnsafeThirdParty) {
+ MOZ_COUNT_CTOR(ContentPermissionRequestParent);
+
+ mPrincipal = aPrincipal;
+ mTopLevelPrincipal = aTopLevelPrincipal;
+ mElement = aElement;
+ mRequests = aRequests.Clone();
+ mHasValidTransientUserGestureActivation =
+ aHasValidTransientUserGestureActivation;
+ mIsRequestDelegatedToUnsafeThirdParty = aIsRequestDelegatedToUnsafeThirdParty;
+}
+
+ContentPermissionRequestParent::~ContentPermissionRequestParent() {
+ MOZ_COUNT_DTOR(ContentPermissionRequestParent);
+}
+
+mozilla::ipc::IPCResult ContentPermissionRequestParent::Recvprompt() {
+ mProxy = new nsContentPermissionRequestProxy(this);
+ if (NS_FAILED(mProxy->Init(mRequests))) {
+ RefPtr<nsContentPermissionRequestProxy> proxy(mProxy);
+ proxy->Cancel();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentPermissionRequestParent::RecvDestroy() {
+ Unused << PContentPermissionRequestParent::Send__delete__(this);
+ return IPC_OK();
+}
+
+void ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why) {
+ if (mProxy) {
+ mProxy->OnParentDestroyed();
+ }
+}
+
+bool ContentPermissionRequestParent::IsBeingDestroyed() {
+ // When ContentParent::MarkAsDead() is called, we are being destroyed.
+ // It's unsafe to send out any message now.
+ ContentParent* contentParent = static_cast<ContentParent*>(Manager());
+ return !contentParent->IsAlive();
+}
+
+NS_IMPL_ISUPPORTS(ContentPermissionType, nsIContentPermissionType)
+
+ContentPermissionType::ContentPermissionType(
+ const nsACString& aType, const nsTArray<nsString>& aOptions) {
+ mType = aType;
+ mOptions = aOptions.Clone();
+}
+
+ContentPermissionType::~ContentPermissionType() = default;
+
+NS_IMETHODIMP
+ContentPermissionType::GetType(nsACString& aType) {
+ aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionType::GetOptions(nsIArray** aOptions) {
+ NS_ENSURE_ARG_POINTER(aOptions);
+
+ *aOptions = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> options =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy options into JS array
+ for (uint32_t i = 0; i < mOptions.Length(); ++i) {
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = isupportsString->SetData(mOptions[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = options->AppendElement(isupportsString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ options.forget(aOptions);
+ return NS_OK;
+}
+
+// nsContentPermissionUtils
+
+/* static */
+uint32_t nsContentPermissionUtils::ConvertPermissionRequestToArray(
+ nsTArray<PermissionRequest>& aSrcArray, nsIMutableArray* aDesArray) {
+ uint32_t len = aSrcArray.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ RefPtr<ContentPermissionType> cpt =
+ new ContentPermissionType(aSrcArray[i].type(), aSrcArray[i].options());
+ aDesArray->AppendElement(cpt);
+ }
+ return len;
+}
+
+/* static */
+void nsContentPermissionUtils::ConvertArrayToPermissionRequest(
+ nsIArray* aSrcArray, nsTArray<PermissionRequest>& aDesArray) {
+ uint32_t len = 0;
+ aSrcArray->GetLength(&len);
+ for (uint32_t i = 0; i < len; i++) {
+ nsCOMPtr<nsIContentPermissionType> cpt = do_QueryElementAt(aSrcArray, i);
+ nsAutoCString type;
+ cpt->GetType(type);
+
+ nsCOMPtr<nsIArray> optionArray;
+ cpt->GetOptions(getter_AddRefs(optionArray));
+ uint32_t optionsLength = 0;
+ if (optionArray) {
+ optionArray->GetLength(&optionsLength);
+ }
+ nsTArray<nsString> options;
+ for (uint32_t j = 0; j < optionsLength; ++j) {
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_QueryElementAt(optionArray, j);
+ if (isupportsString) {
+ nsString option;
+ isupportsString->GetData(option);
+ options.AppendElement(option);
+ }
+ }
+
+ aDesArray.AppendElement(PermissionRequest(type, options));
+ }
+}
+
+static std::map<PContentPermissionRequestParent*, TabId>&
+ContentPermissionRequestParentMap() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static std::map<PContentPermissionRequestParent*, TabId>
+ sPermissionRequestParentMap;
+ return sPermissionRequestParentMap;
+}
+
+static std::map<PContentPermissionRequestChild*, TabId>&
+ContentPermissionRequestChildMap() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static std::map<PContentPermissionRequestChild*, TabId>
+ sPermissionRequestChildMap;
+ return sPermissionRequestChildMap;
+}
+
+/* static */
+nsresult nsContentPermissionUtils::CreatePermissionArray(
+ const nsACString& aType, const nsTArray<nsString>& aOptions,
+ nsIArray** aTypesArray) {
+ nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ RefPtr<ContentPermissionType> permType =
+ new ContentPermissionType(aType, aOptions);
+ types->AppendElement(permType);
+ types.forget(aTypesArray);
+
+ return NS_OK;
+}
+
+/* static */
+PContentPermissionRequestParent*
+nsContentPermissionUtils::CreateContentPermissionRequestParent(
+ const nsTArray<PermissionRequest>& aRequests, Element* aElement,
+ nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
+ const bool aHasValidTransientUserGestureActivation,
+ const bool aIsRequestDelegatedToUnsafeThirdParty, const TabId& aTabId) {
+ PContentPermissionRequestParent* parent = new ContentPermissionRequestParent(
+ aRequests, aElement, aPrincipal, aTopLevelPrincipal,
+ aHasValidTransientUserGestureActivation,
+ aIsRequestDelegatedToUnsafeThirdParty);
+ ContentPermissionRequestParentMap()[parent] = aTabId;
+
+ return parent;
+}
+
+/* static */
+nsresult nsContentPermissionUtils::AskPermission(
+ nsIContentPermissionRequest* aRequest, nsPIDOMWindowInner* aWindow) {
+ NS_ENSURE_STATE(aWindow && aWindow->IsCurrentInnerWindow());
+
+ // for content process
+ if (XRE_IsContentProcess()) {
+ RefPtr<RemotePermissionRequest> req =
+ new RemotePermissionRequest(aRequest, aWindow);
+
+ MOZ_ASSERT(NS_IsMainThread()); // IPC can only be execute on main thread.
+
+ BrowserChild* child = BrowserChild::GetFrom(aWindow->GetDocShell());
+ NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIArray> typeArray;
+ nsresult rv = aRequest->GetTypes(getter_AddRefs(typeArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<PermissionRequest> permArray;
+ ConvertArrayToPermissionRequest(typeArray, permArray);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = aRequest->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> topLevelPrincipal;
+ rv = aRequest->GetTopLevelPrincipal(getter_AddRefs(topLevelPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasValidTransientUserGestureActivation;
+ rv = aRequest->GetHasValidTransientUserGestureActivation(
+ &hasValidTransientUserGestureActivation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isRequestDelegatedToUnsafeThirdParty;
+ rv = aRequest->GetIsRequestDelegatedToUnsafeThirdParty(
+ &isRequestDelegatedToUnsafeThirdParty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ req->IPDLAddRef();
+ ContentChild::GetSingleton()->SendPContentPermissionRequestConstructor(
+ req, permArray, principal, topLevelPrincipal,
+ hasValidTransientUserGestureActivation,
+ isRequestDelegatedToUnsafeThirdParty, child->GetTabId());
+ ContentPermissionRequestChildMap()[req.get()] = child->GetTabId();
+
+ req->Sendprompt();
+ return NS_OK;
+ }
+
+ // for chrome process
+ nsCOMPtr<nsIContentPermissionPrompt> prompt =
+ do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+ if (prompt) {
+ if (NS_FAILED(prompt->Prompt(aRequest))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+/* static */
+nsTArray<PContentPermissionRequestParent*>
+nsContentPermissionUtils::GetContentPermissionRequestParentById(
+ const TabId& aTabId) {
+ nsTArray<PContentPermissionRequestParent*> parentArray;
+ for (auto& it : ContentPermissionRequestParentMap()) {
+ if (it.second == aTabId) {
+ parentArray.AppendElement(it.first);
+ }
+ }
+
+ return parentArray;
+}
+
+/* static */
+void nsContentPermissionUtils::NotifyRemoveContentPermissionRequestParent(
+ PContentPermissionRequestParent* aParent) {
+ auto it = ContentPermissionRequestParentMap().find(aParent);
+ MOZ_ASSERT(it != ContentPermissionRequestParentMap().end());
+
+ ContentPermissionRequestParentMap().erase(it);
+}
+
+/* static */
+nsTArray<PContentPermissionRequestChild*>
+nsContentPermissionUtils::GetContentPermissionRequestChildById(
+ const TabId& aTabId) {
+ nsTArray<PContentPermissionRequestChild*> childArray;
+ for (auto& it : ContentPermissionRequestChildMap()) {
+ if (it.second == aTabId) {
+ childArray.AppendElement(it.first);
+ }
+ }
+
+ return childArray;
+}
+
+/* static */
+void nsContentPermissionUtils::NotifyRemoveContentPermissionRequestChild(
+ PContentPermissionRequestChild* aChild) {
+ auto it = ContentPermissionRequestChildMap().find(aChild);
+ MOZ_ASSERT(it != ContentPermissionRequestChildMap().end());
+
+ ContentPermissionRequestChildMap().erase(it);
+}
+
+static nsIPrincipal* GetTopLevelPrincipal(nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ BrowsingContext* top = aWindow->GetBrowsingContext()->Top();
+ MOZ_ASSERT(top);
+
+ nsPIDOMWindowOuter* outer = top->GetDOMWindow();
+ if (!outer) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
+ if (!inner) {
+ return nullptr;
+ }
+
+ return nsGlobalWindowInner::Cast(inner)->GetPrincipal();
+}
+
+NS_IMPL_CYCLE_COLLECTION(ContentPermissionRequestBase, mPrincipal,
+ mTopLevelPrincipal, mWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ContentPermissionRequestBase)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsISupports)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsIContentPermissionRequest)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentPermissionRequestBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ContentPermissionRequestBase)
+
+ContentPermissionRequestBase::ContentPermissionRequestBase(
+ nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow,
+ const nsACString& aPrefName, const nsACString& aType)
+ : mPrincipal(aPrincipal),
+ mTopLevelPrincipal(aWindow ? ::GetTopLevelPrincipal(aWindow) : nullptr),
+ mWindow(aWindow),
+ mPrefName(aPrefName),
+ mType(aType),
+ mHasValidTransientUserGestureActivation(false),
+ mIsRequestDelegatedToUnsafeThirdParty(false) {
+ if (!aWindow) {
+ return;
+ }
+
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return;
+ }
+
+ mHasValidTransientUserGestureActivation =
+ doc->HasValidTransientUserGestureActivation();
+
+ mPermissionHandler = doc->GetPermissionDelegateHandler();
+ if (mPermissionHandler) {
+ nsTArray<nsCString> types;
+ types.AppendElement(mType);
+ mPermissionHandler->MaybeUnsafePermissionDelegate(
+ types, &mIsRequestDelegatedToUnsafeThirdParty);
+ }
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetPrincipal(
+ nsIPrincipal** aRequestingPrincipal) {
+ NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetDelegatePrincipal(
+ const nsACString& aType, nsIPrincipal** aRequestingPrincipal) {
+ return PermissionDelegateHandler::GetDelegatePrincipal(aType, this,
+ aRequestingPrincipal);
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetIsRequestDelegatedToUnsafeThirdParty(
+ bool* aIsRequestDelegatedToUnsafeThirdParty) {
+ *aIsRequestDelegatedToUnsafeThirdParty =
+ mIsRequestDelegatedToUnsafeThirdParty;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetTopLevelPrincipal(
+ nsIPrincipal** aRequestingPrincipal) {
+ if (!mTopLevelPrincipal) {
+ *aRequestingPrincipal = nullptr;
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aRequestingPrincipal = mTopLevelPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetWindow(mozIDOMWindow** aRequestingWindow) {
+ NS_IF_ADDREF(*aRequestingWindow = mWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetElement(Element** aElement) {
+ NS_ENSURE_ARG_POINTER(aElement);
+ *aElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetHasValidTransientUserGestureActivation(
+ bool* aHasValidTransientUserGestureActivation) {
+ *aHasValidTransientUserGestureActivation =
+ mHasValidTransientUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionRequestBase::GetTypes(nsIArray** aTypes) {
+ nsTArray<nsString> emptyOptions;
+ return nsContentPermissionUtils::CreatePermissionArray(mType, emptyOptions,
+ aTypes);
+}
+
+ContentPermissionRequestBase::PromptResult
+ContentPermissionRequestBase::CheckPromptPrefs() const {
+ MOZ_ASSERT(!mPrefName.IsEmpty(),
+ "This derived class must support checking pref types");
+
+ nsAutoCString prefName(mPrefName);
+ prefName.AppendLiteral(".prompt.testing");
+ if (Preferences::GetBool(PromiseFlatCString(prefName).get(), false)) {
+ prefName.AppendLiteral(".allow");
+ if (Preferences::GetBool(PromiseFlatCString(prefName).get(), true)) {
+ return PromptResult::Granted;
+ }
+ return PromptResult::Denied;
+ }
+
+ return PromptResult::Pending;
+}
+
+bool ContentPermissionRequestBase::CheckPermissionDelegate() const {
+ // There is case that ContentPermissionRequestBase is constructed without
+ // window, then mPermissionHandler will be null. So we only check permission
+ // delegate if we have non-null mPermissionHandler
+ if (mPermissionHandler &&
+ !mPermissionHandler->HasPermissionDelegated(mType)) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult ContentPermissionRequestBase::ShowPrompt(
+ ContentPermissionRequestBase::PromptResult& aResult) {
+ if (!CheckPermissionDelegate()) {
+ aResult = PromptResult::Denied;
+ return NS_OK;
+ }
+
+ aResult = CheckPromptPrefs();
+
+ if (aResult != PromptResult::Pending) {
+ return NS_OK;
+ }
+
+ return nsContentPermissionUtils::AskPermission(this, mWindow);
+}
+
+class RequestPromptEvent : public Runnable {
+ public:
+ RequestPromptEvent(ContentPermissionRequestBase* aRequest,
+ nsPIDOMWindowInner* aWindow)
+ : mozilla::Runnable("RequestPromptEvent"),
+ mRequest(aRequest),
+ mWindow(aWindow) {}
+
+ NS_IMETHOD Run() override {
+ nsContentPermissionUtils::AskPermission(mRequest, mWindow);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ContentPermissionRequestBase> mRequest;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+class RequestAllowEvent : public Runnable {
+ public:
+ RequestAllowEvent(bool allow, ContentPermissionRequestBase* request)
+ : mozilla::Runnable("RequestAllowEvent"),
+ mAllow(allow),
+ mRequest(request) {}
+
+ // Not MOZ_CAN_RUN_SCRIPT because we can't annotate the thing we override yet.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override {
+ // MOZ_KnownLive is OK, because we never drop the ref to mRequest.
+ if (mAllow) {
+ MOZ_KnownLive(mRequest)->Allow(JS::UndefinedHandleValue);
+ } else {
+ MOZ_KnownLive(mRequest)->Cancel();
+ }
+ return NS_OK;
+ }
+
+ private:
+ bool mAllow;
+ RefPtr<ContentPermissionRequestBase> mRequest;
+};
+
+void ContentPermissionRequestBase::RequestDelayedTask(
+ nsIEventTarget* aTarget,
+ ContentPermissionRequestBase::DelayedTaskType aType) {
+ nsCOMPtr<nsIRunnable> r;
+ switch (aType) {
+ case DelayedTaskType::Allow:
+ r = new RequestAllowEvent(true, this);
+ break;
+ case DelayedTaskType::Deny:
+ r = new RequestAllowEvent(false, this);
+ break;
+ default:
+ r = new RequestPromptEvent(this, mWindow);
+ break;
+ }
+
+ aTarget->Dispatch(r.forget());
+}
+
+nsresult TranslateChoices(
+ JS::Handle<JS::Value> aChoices,
+ const nsTArray<PermissionRequest>& aPermissionRequests,
+ nsTArray<PermissionChoice>& aTranslatedChoices) {
+ if (aChoices.isNullOrUndefined()) {
+ // No choice is specified.
+ } else if (aChoices.isObject()) {
+ // Iterate through all permission types.
+ for (uint32_t i = 0; i < aPermissionRequests.Length(); ++i) {
+ nsCString type = aPermissionRequests[i].type();
+
+ JS::Rooted<JSObject*> obj(RootingCx(), &aChoices.toObject());
+ // People really shouldn't be passing WindowProxy or Location
+ // objects for the choices here.
+ obj = js::CheckedUnwrapStatic(obj);
+ if (!obj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, obj);
+
+ JS::Rooted<JS::Value> val(cx);
+
+ if (!JS_GetProperty(cx, obj, type.BeginReading(), &val) ||
+ !val.isString()) {
+ // no setting for the permission type, clear exception and skip it
+ jsapi.ClearException();
+ } else {
+ nsAutoJSString choice;
+ if (!choice.init(cx, val)) {
+ jsapi.ClearException();
+ return NS_ERROR_FAILURE;
+ }
+ aTranslatedChoices.AppendElement(PermissionChoice(type, choice));
+ }
+ }
+ } else {
+ MOZ_ASSERT(false, "SelectedChoices should be undefined or an JS object");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
+
+nsContentPermissionRequestProxy::nsContentPermissionRequestProxy(
+ ContentPermissionRequestParent* parent)
+ : mParent(parent) {
+ NS_ASSERTION(mParent, "null parent");
+}
+
+nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy() = default;
+
+nsresult nsContentPermissionRequestProxy::Init(
+ const nsTArray<PermissionRequest>& requests) {
+ mPermissionRequests = requests.Clone();
+
+ nsCOMPtr<nsIContentPermissionPrompt> prompt =
+ do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+ if (!prompt) {
+ return NS_ERROR_FAILURE;
+ }
+
+ prompt->Prompt(this);
+ return NS_OK;
+}
+
+void nsContentPermissionRequestProxy::OnParentDestroyed() { mParent = nullptr; }
+
+NS_IMPL_ISUPPORTS(nsContentPermissionRequestProxy, nsIContentPermissionRequest)
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetTypes(nsIArray** aTypes) {
+ nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (mozilla::dom::nsContentPermissionUtils::ConvertPermissionRequestToArray(
+ mPermissionRequests, types)) {
+ types.forget(aTypes);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetWindow(mozIDOMWindow** aRequestingWindow) {
+ NS_ENSURE_ARG_POINTER(aRequestingWindow);
+ *aRequestingWindow = nullptr; // ipc doesn't have a window
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetPrincipal(
+ nsIPrincipal** aRequestingPrincipal) {
+ NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IF_ADDREF(*aRequestingPrincipal = mParent->mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetTopLevelPrincipal(
+ nsIPrincipal** aRequestingPrincipal) {
+ NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IF_ADDREF(*aRequestingPrincipal = mParent->mTopLevelPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetDelegatePrincipal(
+ const nsACString& aType, nsIPrincipal** aRequestingPrincipal) {
+ NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return PermissionDelegateHandler::GetDelegatePrincipal(aType, this,
+ aRequestingPrincipal);
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetElement(Element** aRequestingElement) {
+ NS_ENSURE_ARG_POINTER(aRequestingElement);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<Element> elem = mParent->mElement;
+ elem.forget(aRequestingElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetHasValidTransientUserGestureActivation(
+ bool* aHasValidTransientUserGestureActivation) {
+ NS_ENSURE_ARG_POINTER(aHasValidTransientUserGestureActivation);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ *aHasValidTransientUserGestureActivation =
+ mParent->mHasValidTransientUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetIsRequestDelegatedToUnsafeThirdParty(
+ bool* aIsRequestDelegatedToUnsafeThirdParty) {
+ NS_ENSURE_ARG_POINTER(aIsRequestDelegatedToUnsafeThirdParty);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ *aIsRequestDelegatedToUnsafeThirdParty =
+ mParent->mIsRequestDelegatedToUnsafeThirdParty;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::Cancel() {
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't send out the delete message when the managing protocol (PBrowser) is
+ // being destroyed and PContentPermissionRequest will soon be.
+ if (mParent->IsBeingDestroyed()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<PermissionChoice> emptyChoices;
+
+ Unused << mParent->SendNotifyResult(false, emptyChoices);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::Allow(JS::Handle<JS::Value> aChoices) {
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't send out the delete message when the managing protocol (PBrowser) is
+ // being destroyed and PContentPermissionRequest will soon be.
+ if (mParent->IsBeingDestroyed()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<PermissionChoice> choices;
+ nsresult rv = TranslateChoices(aChoices, mPermissionRequests, choices);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Unused << mParent->SendNotifyResult(true, choices);
+ return NS_OK;
+}
+
+// RemotePermissionRequest
+
+RemotePermissionRequest::RemotePermissionRequest(
+ nsIContentPermissionRequest* aRequest, nsPIDOMWindowInner* aWindow)
+ : mRequest(aRequest),
+ mWindow(aWindow),
+ mIPCOpen(false),
+ mDestroyed(false) {}
+
+RemotePermissionRequest::~RemotePermissionRequest() {
+ MOZ_ASSERT(
+ !mIPCOpen,
+ "Protocol must not be open when RemotePermissionRequest is destroyed.");
+}
+
+void RemotePermissionRequest::DoCancel() {
+ NS_ASSERTION(mRequest, "We need a request");
+ nsCOMPtr<nsIContentPermissionRequest> request(mRequest);
+ request->Cancel();
+}
+
+void RemotePermissionRequest::DoAllow(JS::Handle<JS::Value> aChoices) {
+ NS_ASSERTION(mRequest, "We need a request");
+ nsCOMPtr<nsIContentPermissionRequest> request(mRequest);
+ request->Allow(aChoices);
+}
+
+// PContentPermissionRequestChild
+mozilla::ipc::IPCResult RemotePermissionRequest::RecvNotifyResult(
+ const bool& aAllow, nsTArray<PermissionChoice>&& aChoices) {
+ Destroy();
+
+ if (aAllow && mWindow->IsCurrentInnerWindow()) {
+ // Use 'undefined' if no choice is provided.
+ if (aChoices.IsEmpty()) {
+ DoAllow(JS::UndefinedHandleValue);
+ return IPC_OK();
+ }
+
+ // Convert choices to a JS val if any.
+ // {"type1": "choice1", "type2": "choiceA"}
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mWindow))) {
+ return IPC_OK(); // This is not an IPC error.
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx);
+ obj = JS_NewPlainObject(cx);
+ for (uint32_t i = 0; i < aChoices.Length(); ++i) {
+ const nsString& choice = aChoices[i].choice();
+ const nsCString& type = aChoices[i].type();
+ JS::Rooted<JSString*> jChoice(
+ cx, JS_NewUCStringCopyN(cx, choice.get(), choice.Length()));
+ JS::Rooted<JS::Value> vChoice(cx, StringValue(jChoice));
+ if (!JS_SetProperty(cx, obj, type.get(), vChoice)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ }
+ JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*obj));
+ DoAllow(val);
+ } else {
+ DoCancel();
+ }
+ return IPC_OK();
+}
+
+void RemotePermissionRequest::Destroy() {
+ if (!IPCOpen()) {
+ return;
+ }
+ Unused << this->SendDestroy();
+ mDestroyed = true;
+}
diff --git a/dom/base/nsContentPermissionHelper.h b/dom/base/nsContentPermissionHelper.h
new file mode 100644
index 0000000000..18ce5c8f0f
--- /dev/null
+++ b/dom/base/nsContentPermissionHelper.h
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentPermissionHelper_h
+#define nsContentPermissionHelper_h
+
+#include "nsIContentPermissionPrompt.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "mozilla/dom/PContentPermissionRequestChild.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/PermissionDelegateHandler.h"
+
+// Microsoft's API Name hackery sucks
+// XXXbz Doing this in a header is a gigantic footgun. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=932421#c3 for why.
+#undef LoadImage
+
+class nsPIDOMWindowInner;
+class nsContentPermissionRequestProxy;
+
+namespace mozilla::dom {
+
+class Element;
+class PermissionRequest;
+class ContentPermissionRequestParent;
+class PContentPermissionRequestParent;
+
+class ContentPermissionType : public nsIContentPermissionType {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONTYPE
+
+ ContentPermissionType(const nsACString& aType,
+ const nsTArray<nsString>& aOptions);
+
+ protected:
+ virtual ~ContentPermissionType();
+
+ nsCString mType;
+ nsTArray<nsString> mOptions;
+};
+
+class nsContentPermissionUtils {
+ public:
+ static uint32_t ConvertPermissionRequestToArray(
+ nsTArray<PermissionRequest>& aSrcArray, nsIMutableArray* aDesArray);
+
+ // Converts blindly, that is, strings are not matched against any list.
+ //
+ // @param aSrcArray needs to contain elements of type
+ // `nsIContentPermissionType`.
+ static void ConvertArrayToPermissionRequest(
+ nsIArray* aSrcArray, nsTArray<PermissionRequest>& aDesArray);
+
+ static nsresult CreatePermissionArray(const nsACString& aType,
+ const nsTArray<nsString>& aOptions,
+ nsIArray** aTypesArray);
+
+ // @param aIsRequestDelegatedToUnsafeThirdParty see
+ // ContentPermissionRequestParent.
+ static PContentPermissionRequestParent* CreateContentPermissionRequestParent(
+ const nsTArray<PermissionRequest>& aRequests, Element* aElement,
+ nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
+ const bool aHasValidTransientUserGestureActivation,
+ const bool aIsRequestDelegatedToUnsafeThirdParty, const TabId& aTabId);
+
+ static nsresult AskPermission(nsIContentPermissionRequest* aRequest,
+ nsPIDOMWindowInner* aWindow);
+
+ static nsTArray<PContentPermissionRequestParent*>
+ GetContentPermissionRequestParentById(const TabId& aTabId);
+
+ static void NotifyRemoveContentPermissionRequestParent(
+ PContentPermissionRequestParent* aParent);
+
+ static nsTArray<PContentPermissionRequestChild*>
+ GetContentPermissionRequestChildById(const TabId& aTabId);
+
+ static void NotifyRemoveContentPermissionRequestChild(
+ PContentPermissionRequestChild* aChild);
+};
+
+nsresult TranslateChoices(
+ JS::Handle<JS::Value> aChoices,
+ const nsTArray<PermissionRequest>& aPermissionRequests,
+ nsTArray<PermissionChoice>& aTranslatedChoices);
+
+class ContentPermissionRequestBase : public nsIContentPermissionRequest {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ContentPermissionRequestBase)
+
+ NS_IMETHOD GetTypes(nsIArray** aTypes) override;
+ NS_IMETHOD GetPrincipal(nsIPrincipal** aPrincipal) override;
+ NS_IMETHOD GetDelegatePrincipal(const nsACString& aType,
+ nsIPrincipal** aPrincipal) override;
+ NS_IMETHOD GetTopLevelPrincipal(nsIPrincipal** aTopLevelPrincipal) override;
+ NS_IMETHOD GetWindow(mozIDOMWindow** aWindow) override;
+ NS_IMETHOD GetElement(mozilla::dom::Element** aElement) override;
+ NS_IMETHOD GetHasValidTransientUserGestureActivation(
+ bool* aHasValidTransientUserGestureActivation) override;
+ NS_IMETHOD GetIsRequestDelegatedToUnsafeThirdParty(
+ bool* aIsRequestDelegatedToUnsafeThirdParty) override;
+ // Overrides for Allow() and Cancel() aren't provided by this class.
+ // That is the responsibility of the subclasses.
+
+ enum class PromptResult {
+ Granted,
+ Denied,
+ Pending,
+ };
+ nsresult ShowPrompt(PromptResult& aResult);
+
+ PromptResult CheckPromptPrefs() const;
+
+ // Check if the permission has an opportunity to request.
+ bool CheckPermissionDelegate() const;
+
+ enum class DelayedTaskType {
+ Allow,
+ Deny,
+ Request,
+ };
+ void RequestDelayedTask(nsIEventTarget* aTarget, DelayedTaskType aType);
+
+ protected:
+ // @param aPrefName see `mPrefName`.
+ // @param aType see `mType`.
+ ContentPermissionRequestBase(nsIPrincipal* aPrincipal,
+ nsPIDOMWindowInner* aWindow,
+ const nsACString& aPrefName,
+ const nsACString& aType);
+ virtual ~ContentPermissionRequestBase() = default;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<PermissionDelegateHandler> mPermissionHandler;
+
+ // The prefix of a pref which allows tests to bypass showing the prompt.
+ // Tests will have to set both of
+ // ${mPrefName}.prompt.testing and
+ // ${mPrefName}.prompt.testing.allow
+ // to either true or false. If no such testing is required, mPrefName may be
+ // empty.
+ const nsCString mPrefName;
+
+ // The type of the request, such as "autoplay-media-audible".
+ const nsCString mType;
+
+ bool mHasValidTransientUserGestureActivation;
+
+ // See nsIPermissionDelegateHandler.maybeUnsafePermissionDelegate`.
+ bool mIsRequestDelegatedToUnsafeThirdParty;
+};
+
+} // namespace mozilla::dom
+
+using mozilla::dom::ContentPermissionRequestParent;
+
+class nsContentPermissionRequestProxy : public nsIContentPermissionRequest {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUEST
+
+ explicit nsContentPermissionRequestProxy(
+ ContentPermissionRequestParent* parent);
+
+ nsresult Init(const nsTArray<mozilla::dom::PermissionRequest>& requests);
+
+ void OnParentDestroyed();
+
+ private:
+ virtual ~nsContentPermissionRequestProxy();
+
+ // Non-owning pointer to the ContentPermissionRequestParent object which owns
+ // this proxy.
+ ContentPermissionRequestParent* mParent;
+ nsTArray<mozilla::dom::PermissionRequest> mPermissionRequests;
+};
+
+/**
+ * RemotePermissionRequest will send a prompt ipdl request to the chrome process
+ * (https://wiki.mozilla.org/Security/Sandbox/Process_model#Chrome_process_.28Parent.29).
+ */
+class RemotePermissionRequest final
+ : public mozilla::dom::PContentPermissionRequestChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemotePermissionRequest)
+
+ RemotePermissionRequest(nsIContentPermissionRequest* aRequest,
+ nsPIDOMWindowInner* aWindow);
+
+ // It will be called when prompt dismissed. MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ // because we don't have MOZ_CAN_RUN_SCRIPT bits in IPC code yet.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ mozilla::ipc::IPCResult RecvNotifyResult(
+ const bool& aAllow, nsTArray<PermissionChoice>&& aChoices);
+
+ void IPDLAddRef() {
+ mIPCOpen = true;
+ AddRef();
+ }
+
+ void IPDLRelease() {
+ mIPCOpen = false;
+ Release();
+ }
+
+ void Destroy();
+
+ bool IPCOpen() const { return mIPCOpen && !mDestroyed; }
+
+ private:
+ virtual ~RemotePermissionRequest();
+
+ MOZ_CAN_RUN_SCRIPT
+ void DoAllow(JS::Handle<JS::Value> aChoices);
+ MOZ_CAN_RUN_SCRIPT
+ void DoCancel();
+
+ nsCOMPtr<nsIContentPermissionRequest> mRequest;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ bool mIPCOpen;
+ bool mDestroyed;
+};
+
+#endif // nsContentPermissionHelper_h
diff --git a/dom/base/nsContentPolicy.cpp b/dom/base/nsContentPolicy.cpp
new file mode 100644
index 0000000000..003bffb01b
--- /dev/null
+++ b/dom/base/nsContentPolicy.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim: ft=cpp tw=80 sw=2 et ts=8
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of the "@mozilla.org/layout/content-policy;1" contract.
+ */
+
+#include "mozilla/Logging.h"
+
+#include "nsISupports.h"
+#include "nsXPCOM.h"
+#include "nsContentPolicyUtils.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "nsContentPolicy.h"
+#include "nsIURI.h"
+#include "nsIBrowserChild.h"
+#include "nsIContent.h"
+#include "nsIImageLoadingContent.h"
+#include "nsCOMArray.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/TaskCategory.h"
+
+class nsIDOMWindow;
+
+using mozilla::LogLevel;
+
+NS_IMPL_ISUPPORTS(nsContentPolicy, nsIContentPolicy)
+
+static mozilla::LazyLogModule gConPolLog("nsContentPolicy");
+
+nsresult NS_NewContentPolicy(nsIContentPolicy** aResult) {
+ *aResult = new nsContentPolicy;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsContentPolicy::nsContentPolicy() : mPolicies(NS_CONTENTPOLICY_CATEGORY) {}
+
+nsContentPolicy::~nsContentPolicy() = default;
+
+#ifdef DEBUG
+# define WARN_IF_URI_UNINITIALIZED(uri, name) \
+ PR_BEGIN_MACRO \
+ if ((uri)) { \
+ nsAutoCString spec; \
+ (uri)->GetAsciiSpec(spec); \
+ if (spec.IsEmpty()) { \
+ NS_WARNING(name " is uninitialized, fix caller"); \
+ } \
+ } \
+ PR_END_MACRO
+
+#else // ! defined(DEBUG)
+
+# define WARN_IF_URI_UNINITIALIZED(uri, name)
+
+#endif // defined(DEBUG)
+
+inline nsresult nsContentPolicy::CheckPolicy(CPMethod policyMethod,
+ nsIURI* contentLocation,
+ nsILoadInfo* loadInfo,
+ const nsACString& mimeType,
+ int16_t* decision) {
+ nsCOMPtr<nsISupports> requestingContext = loadInfo->GetLoadingContext();
+ // sanity-check passed-through parameters
+ MOZ_ASSERT(decision, "Null out pointer");
+ WARN_IF_URI_UNINITIALIZED(contentLocation, "Request URI");
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsINode> node(do_QueryInterface(requestingContext));
+ nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(requestingContext));
+ nsCOMPtr<nsIBrowserChild> browserChild(
+ do_QueryInterface(requestingContext));
+ NS_ASSERTION(!requestingContext || node || window || browserChild,
+ "Context should be a DOM node, DOM window or a browserChild!");
+ }
+#endif
+
+ nsCOMPtr<mozilla::dom::Document> doc;
+ nsCOMPtr<nsIContent> node = do_QueryInterface(requestingContext);
+ if (node) {
+ doc = node->OwnerDoc();
+ }
+ if (!doc) {
+ doc = do_QueryInterface(requestingContext);
+ }
+
+ /*
+ * Enumerate mPolicies and ask each of them, taking the logical AND of
+ * their permissions.
+ */
+ nsresult rv;
+ const nsCOMArray<nsIContentPolicy>& entries = mPolicies.GetCachedEntries();
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext)) {
+ window = node->OwnerDoc()->GetWindow();
+ } else {
+ window = do_QueryInterface(requestingContext);
+ }
+
+ if (doc) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();
+ if (csp && window) {
+ csp->EnsureEventTarget(
+ window->EventTargetFor(mozilla::TaskCategory::Other));
+ }
+ }
+
+ int32_t count = entries.Count();
+ for (int32_t i = 0; i < count; i++) {
+ /* check the appropriate policy */
+ rv = (entries[i]->*policyMethod)(contentLocation, loadInfo, mimeType,
+ decision);
+
+ if (NS_SUCCEEDED(rv) && NS_CP_REJECTED(*decision)) {
+ /* policy says no, no point continuing to check */
+ return NS_OK;
+ }
+ }
+
+ // everyone returned failure, or no policies: sanitize result
+ *decision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+}
+
+// uses the parameters from ShouldXYZ to produce and log a message
+// logType must be a literal string constant
+#define LOG_CHECK(logType) \
+ PR_BEGIN_MACRO \
+ /* skip all this nonsense if the call failed or logging is disabled */ \
+ if (NS_SUCCEEDED(rv) && MOZ_LOG_TEST(gConPolLog, LogLevel::Debug)) { \
+ const char* resultName; \
+ if (decision) { \
+ resultName = NS_CP_ResponseName(*decision); \
+ } else { \
+ resultName = "(null ptr)"; \
+ } \
+ MOZ_LOG( \
+ gConPolLog, LogLevel::Debug, \
+ ("Content Policy: " logType ": <%s> result=%s", \
+ contentLocation ? contentLocation->GetSpecOrDefault().get() : "None", \
+ resultName)); \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsContentPolicy::ShouldLoad(nsIURI* contentLocation, nsILoadInfo* loadInfo,
+ const nsACString& mimeType, int16_t* decision) {
+ // ShouldProcess does not need a content location, but we do
+ MOZ_ASSERT(contentLocation, "Must provide request location");
+ nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldLoad, contentLocation,
+ loadInfo, mimeType, decision);
+ LOG_CHECK("ShouldLoad");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsContentPolicy::ShouldProcess(nsIURI* contentLocation, nsILoadInfo* loadInfo,
+ const nsACString& mimeType, int16_t* decision) {
+ nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldProcess, contentLocation,
+ loadInfo, mimeType, decision);
+ LOG_CHECK("ShouldProcess");
+
+ return rv;
+}
diff --git a/dom/base/nsContentPolicy.h b/dom/base/nsContentPolicy.h
new file mode 100644
index 0000000000..079f7f6b87
--- /dev/null
+++ b/dom/base/nsContentPolicy.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim: ft=cpp ts=8 sw=2 et tw=80
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsContentPolicy_h__
+#define __nsContentPolicy_h__
+
+#include "nsIContentPolicy.h"
+#include "nsCategoryCache.h"
+
+/*
+ * Implementation of the "@mozilla.org/layout/content-policy;1" contract.
+ */
+
+class nsContentPolicy : public nsIContentPolicy {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+
+ nsContentPolicy();
+
+ protected:
+ virtual ~nsContentPolicy();
+
+ private:
+ // Array of policies
+ nsCategoryCache<nsIContentPolicy> mPolicies;
+
+ // Helper type for CheckPolicy
+ using CPMethod = decltype(&nsIContentPolicy::ShouldProcess);
+
+ // Helper method that applies policyMethod across all policies in mPolicies
+ // with the given parameters
+ nsresult CheckPolicy(CPMethod policyMethod, nsIURI* aURI,
+ nsILoadInfo* aLoadInfo, const nsACString& mimeGuess,
+ int16_t* decision);
+};
+
+nsresult NS_NewContentPolicy(nsIContentPolicy** aResult);
+
+#endif /* __nsContentPolicy_h__ */
diff --git a/dom/base/nsContentPolicyUtils.h b/dom/base/nsContentPolicyUtils.h
new file mode 100644
index 0000000000..9fc96eb1ac
--- /dev/null
+++ b/dom/base/nsContentPolicyUtils.h
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Utility routines for checking content load/process policy settings,
+ * and routines helpful for content policy implementors.
+ *
+ * XXXbz it would be nice if some of this stuff could be out-of-lined in
+ * nsContentUtils. That would work for almost all the callers...
+ */
+
+#ifndef __nsContentPolicyUtils_h__
+#define __nsContentPolicyUtils_h__
+
+#include "mozilla/BasePrincipal.h"
+
+#include "nsContentUtils.h"
+#include "nsIContentPolicy.h"
+#include "nsIContent.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringFwd.h"
+#include "mozilla/dom/nsCSPService.h"
+
+// XXXtw sadly, this makes consumers of nsContentPolicyUtils depend on widget
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+
+#define NS_CONTENTPOLICY_CONTRACTID "@mozilla.org/layout/content-policy;1"
+#define NS_CONTENTPOLICY_CATEGORY "content-policy"
+#define NS_CONTENTPOLICY_CID \
+ { \
+ 0x0e3afd3d, 0xeb60, 0x4c2b, { \
+ 0x96, 0x3b, 0x56, 0xd7, 0xc4, 0x39, 0xf1, 0x24 \
+ } \
+ }
+
+/**
+ * Evaluates to true if val is ACCEPT.
+ *
+ * @param val the status returned from shouldProcess/shouldLoad
+ */
+#define NS_CP_ACCEPTED(val) ((val) == nsIContentPolicy::ACCEPT)
+
+/**
+ * Evaluates to true if val is a REJECT_* status
+ *
+ * @param val the status returned from shouldProcess/shouldLoad
+ */
+#define NS_CP_REJECTED(val) ((val) != nsIContentPolicy::ACCEPT)
+
+// Offer convenient translations of constants -> const char*
+
+// convenience macro to reduce some repetative typing...
+// name is the name of a constant from this interface
+#define CASE_RETURN(name) \
+ case nsIContentPolicy::name: \
+ return #name
+
+/**
+ * Returns a string corresponding to the name of the response constant, or
+ * "<Unknown Response>" if an unknown response value is given.
+ *
+ * The return value is static and must not be freed.
+ *
+ * @param response the response code
+ * @return the name of the given response code
+ */
+inline const char* NS_CP_ResponseName(int16_t response) {
+ switch (response) {
+ CASE_RETURN(REJECT_REQUEST);
+ CASE_RETURN(REJECT_TYPE);
+ CASE_RETURN(REJECT_SERVER);
+ CASE_RETURN(REJECT_OTHER);
+ CASE_RETURN(ACCEPT);
+ default:
+ return "<Unknown Response>";
+ }
+}
+
+/**
+ * Returns a string corresponding to the name of the content type constant, or
+ * "<Unknown Type>" if an unknown content type value is given.
+ *
+ * The return value is static and must not be freed.
+ *
+ * @param contentType the content type code
+ * @return the name of the given content type code
+ */
+inline const char* NS_CP_ContentTypeName(nsContentPolicyType contentType) {
+ switch (contentType) {
+ CASE_RETURN(TYPE_OTHER);
+ CASE_RETURN(TYPE_SCRIPT);
+ CASE_RETURN(TYPE_IMAGE);
+ CASE_RETURN(TYPE_STYLESHEET);
+ CASE_RETURN(TYPE_OBJECT);
+ CASE_RETURN(TYPE_DOCUMENT);
+ CASE_RETURN(TYPE_SUBDOCUMENT);
+ CASE_RETURN(TYPE_PING);
+ CASE_RETURN(TYPE_XMLHTTPREQUEST);
+ CASE_RETURN(TYPE_OBJECT_SUBREQUEST);
+ CASE_RETURN(TYPE_DTD);
+ CASE_RETURN(TYPE_FONT);
+ CASE_RETURN(TYPE_MEDIA);
+ CASE_RETURN(TYPE_WEBSOCKET);
+ CASE_RETURN(TYPE_CSP_REPORT);
+ CASE_RETURN(TYPE_XSLT);
+ CASE_RETURN(TYPE_BEACON);
+ CASE_RETURN(TYPE_FETCH);
+ CASE_RETURN(TYPE_IMAGESET);
+ CASE_RETURN(TYPE_WEB_MANIFEST);
+ CASE_RETURN(TYPE_INTERNAL_SCRIPT);
+ CASE_RETURN(TYPE_INTERNAL_WORKER);
+ CASE_RETURN(TYPE_INTERNAL_SHARED_WORKER);
+ CASE_RETURN(TYPE_INTERNAL_EMBED);
+ CASE_RETURN(TYPE_INTERNAL_OBJECT);
+ CASE_RETURN(TYPE_INTERNAL_FRAME);
+ CASE_RETURN(TYPE_INTERNAL_IFRAME);
+ CASE_RETURN(TYPE_INTERNAL_AUDIO);
+ CASE_RETURN(TYPE_INTERNAL_VIDEO);
+ CASE_RETURN(TYPE_INTERNAL_TRACK);
+ CASE_RETURN(TYPE_INTERNAL_XMLHTTPREQUEST);
+ CASE_RETURN(TYPE_INTERNAL_EVENTSOURCE);
+ CASE_RETURN(TYPE_INTERNAL_SERVICE_WORKER);
+ CASE_RETURN(TYPE_INTERNAL_SCRIPT_PRELOAD);
+ CASE_RETURN(TYPE_INTERNAL_IMAGE);
+ CASE_RETURN(TYPE_INTERNAL_IMAGE_PRELOAD);
+ CASE_RETURN(TYPE_INTERNAL_IMAGE_FAVICON);
+ CASE_RETURN(TYPE_INTERNAL_STYLESHEET);
+ CASE_RETURN(TYPE_INTERNAL_STYLESHEET_PRELOAD);
+ CASE_RETURN(TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS);
+ CASE_RETURN(TYPE_SAVEAS_DOWNLOAD);
+ CASE_RETURN(TYPE_SPECULATIVE);
+ CASE_RETURN(TYPE_INTERNAL_MODULE);
+ CASE_RETURN(TYPE_INTERNAL_MODULE_PRELOAD);
+ CASE_RETURN(TYPE_INTERNAL_DTD);
+ CASE_RETURN(TYPE_INTERNAL_FORCE_ALLOWED_DTD);
+ CASE_RETURN(TYPE_INTERNAL_AUDIOWORKLET);
+ CASE_RETURN(TYPE_INTERNAL_PAINTWORKLET);
+ CASE_RETURN(TYPE_INTERNAL_FONT_PRELOAD);
+ CASE_RETURN(TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT);
+ CASE_RETURN(TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT);
+ CASE_RETURN(TYPE_INTERNAL_FETCH_PRELOAD);
+ CASE_RETURN(TYPE_UA_FONT);
+ CASE_RETURN(TYPE_INTERNAL_WORKER_STATIC_MODULE);
+ CASE_RETURN(TYPE_PROXIED_WEBRTC_MEDIA);
+ CASE_RETURN(TYPE_WEB_IDENTITY);
+ CASE_RETURN(TYPE_WEB_TRANSPORT);
+ CASE_RETURN(TYPE_END);
+ case nsIContentPolicy::TYPE_INVALID:
+ break;
+ // Do not add default: so that compilers can catch the missing case.
+ }
+ return "<Unknown Type>";
+}
+
+#undef CASE_RETURN
+
+inline const char* NS_CP_ContentTypeName(ExtContentPolicyType contentType) {
+ return NS_CP_ContentTypeName(static_cast<nsContentPolicyType>(contentType));
+}
+
+/* Passes on parameters from its "caller"'s context. */
+#define CHECK_CONTENT_POLICY(action) \
+ PR_BEGIN_MACRO \
+ nsCOMPtr<nsIContentPolicy> policy = \
+ do_GetService(NS_CONTENTPOLICY_CONTRACTID); \
+ if (!policy) return NS_ERROR_FAILURE; \
+ \
+ return policy->action(contentLocation, loadInfo, mimeType, decision); \
+ PR_END_MACRO
+
+/* Passes on parameters from its "caller"'s context. */
+#define CHECK_CONTENT_POLICY_WITH_SERVICE(action, _policy) \
+ PR_BEGIN_MACRO \
+ return _policy->action(contentLocation, loadInfo, mimeType, decision); \
+ PR_END_MACRO
+
+/**
+ * Check whether we can short-circuit this check and bail out. If not, get the
+ * origin URI to use.
+ *
+ * Note: requestOrigin is scoped outside the PR_BEGIN_MACRO/PR_END_MACRO on
+ * purpose */
+#define CHECK_PRINCIPAL_CSP_AND_DATA(action) \
+ PR_BEGIN_MACRO \
+ if (loadingPrincipal && loadingPrincipal->IsSystemPrincipal()) { \
+ /* We exempt most loads into any document with the system principal \
+ * from content policy (except CSP) checks, mostly as an optimization. \
+ * Which means that we need to apply this check to the loading principal, \
+ * not the principal that triggered the load. */ \
+ /* Check CSP for System Privileged pages */ \
+ CSPService::ConsultCSP(contentLocation, loadInfo, mimeType, decision); \
+ if (NS_CP_REJECTED(*decision)) { \
+ return NS_OK; \
+ } \
+ if (contentType != nsIContentPolicy::TYPE_DOCUMENT && \
+ contentType != nsIContentPolicy::TYPE_UA_FONT) { \
+ *decision = nsIContentPolicy::ACCEPT; \
+ nsCOMPtr<nsINode> n = do_QueryInterface(context); \
+ if (!n) { \
+ nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(context); \
+ n = win ? win->GetExtantDoc() : nullptr; \
+ } \
+ if (n) { \
+ mozilla::dom::Document* d = n->OwnerDoc(); \
+ if (d->IsLoadedAsData() || d->IsBeingUsedAsImage() || \
+ d->IsResourceDoc()) { \
+ nsCOMPtr<nsIContentPolicy> dataPolicy = \
+ do_GetService("@mozilla.org/data-document-content-policy;1"); \
+ if (dataPolicy) { \
+ dataPolicy->action(contentLocation, loadInfo, mimeType, decision); \
+ } \
+ } \
+ } \
+ } \
+ return NS_OK; \
+ } \
+ PR_END_MACRO
+
+/**
+ * Alias for calling ShouldLoad on the content policy service. Parameters are
+ * the same as nsIContentPolicy::shouldLoad, except for the loadingPrincipal
+ * and triggeringPrincipal parameters (which should be non-null if possible,
+ * and have the same semantics as in nsLoadInfo), and the last parameter,
+ * which can be used to pass in a pointer to a useful service if the caller
+ * already has it. The origin URI to pass to shouldLoad will be the URI of
+ * loadingPrincipal, unless loadingPrincipal is null (in which case a null
+ * origin URI will be passed).
+ */
+inline nsresult NS_CheckContentLoadPolicy(
+ nsIURI* contentLocation, nsILoadInfo* loadInfo, const nsACString& mimeType,
+ int16_t* decision, nsIContentPolicy* policyService = nullptr) {
+ nsIPrincipal* loadingPrincipal = loadInfo->GetLoadingPrincipal();
+ nsCOMPtr<nsISupports> context = loadInfo->GetLoadingContext();
+ nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
+ CHECK_PRINCIPAL_CSP_AND_DATA(ShouldLoad);
+ if (policyService) {
+ CHECK_CONTENT_POLICY_WITH_SERVICE(ShouldLoad, policyService);
+ }
+ CHECK_CONTENT_POLICY(ShouldLoad);
+}
+
+/**
+ * Alias for calling ShouldProcess on the content policy service.
+ */
+inline nsresult NS_CheckContentProcessPolicy(
+ nsIURI* contentLocation, nsILoadInfo* loadInfo, const nsACString& mimeType,
+ int16_t* decision, nsIContentPolicy* policyService = nullptr) {
+ nsIPrincipal* loadingPrincipal = loadInfo->GetLoadingPrincipal();
+ nsCOMPtr<nsISupports> context = loadInfo->GetLoadingContext();
+ nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
+ CHECK_PRINCIPAL_CSP_AND_DATA(ShouldProcess);
+ if (policyService) {
+ CHECK_CONTENT_POLICY_WITH_SERVICE(ShouldProcess, policyService);
+ }
+ CHECK_CONTENT_POLICY(ShouldProcess);
+}
+
+#undef CHECK_CONTENT_POLICY
+#undef CHECK_CONTENT_POLICY_WITH_SERVICE
+
+/**
+ * Helper function to get an nsIDocShell given a context.
+ * If the context is a document or window, the corresponding docshell will be
+ * returned.
+ * If the context is a non-document DOM node, the docshell of its ownerDocument
+ * will be returned.
+ *
+ * @param aContext the context to find a docshell for (can be null)
+ *
+ * @return a WEAK pointer to the docshell, or nullptr if it could
+ * not be obtained
+ *
+ * @note As of this writing, calls to nsIContentPolicy::Should{Load,Process}
+ * for TYPE_DOCUMENT and TYPE_SUBDOCUMENT pass in an aContext that either
+ * points to the frameElement of the window the load is happening in
+ * (in which case NS_CP_GetDocShellFromContext will return the parent of the
+ * docshell the load is happening in), or points to the window the load is
+ * happening in (in which case NS_CP_GetDocShellFromContext will return
+ * the docshell the load is happening in). It's up to callers to QI aContext
+ * and handle things accordingly if they want the docshell the load is
+ * happening in. These are somewhat odd semantics, and bug 466687 has been
+ * filed to consider improving them.
+ */
+inline nsIDocShell* NS_CP_GetDocShellFromContext(nsISupports* aContext) {
+ if (!aContext) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
+
+ if (!window) {
+ // Our context might be a document.
+ nsCOMPtr<mozilla::dom::Document> doc = do_QueryInterface(aContext);
+ if (!doc) {
+ // we were not a document after all, get our ownerDocument,
+ // hopefully
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aContext);
+ if (content) {
+ doc = content->OwnerDoc();
+ }
+ }
+
+ if (doc) {
+ if (doc->GetDisplayDocument()) {
+ doc = doc->GetDisplayDocument();
+ }
+
+ window = doc->GetWindow();
+ }
+ }
+
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->GetDocShell();
+}
+
+#endif /* __nsContentPolicyUtils_h__ */
diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp
new file mode 100644
index 0000000000..5cd2c1303f
--- /dev/null
+++ b/dom/base/nsContentSink.cpp
@@ -0,0 +1,907 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for the XML and HTML content sinks, which construct a
+ * DOM based on information from the parser.
+ */
+
+#include "nsContentSink.h"
+#include "mozilla/Components.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_content.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+#include "nsIPrefetchService.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIProtocolHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsViewManager.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindowInner.h"
+#include "nsNetCID.h"
+#include "nsICookieService.h"
+#include "nsContentUtils.h"
+#include "nsNodeInfoManager.h"
+#include "nsIAppShell.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIWebNavigation.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIObserverService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/dom/HTMLDNSPrefetch.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "nsParserConstants.h"
+#include "nsSandboxFlags.h"
+#include "Link.h"
+#include "HTMLLinkElement.h"
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+LazyLogModule gContentSinkLogModuleInfo("nscontentsink");
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSink)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsContentSink)
+ if (tmp->mDocument) {
+ tmp->mDocument->RemoveObserver(tmp);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+nsContentSink::nsContentSink()
+ : mBackoffCount(0),
+ mLastNotificationTime(0),
+ mLayoutStarted(0),
+ mDynamicLowerValue(0),
+ mParsing(0),
+ mDroppedTimer(0),
+ mDeferredLayoutStart(0),
+ mDeferredFlushTags(0),
+ mIsDocumentObserver(0),
+ mRunsToCompletion(0),
+ mIsBlockingOnload(false),
+ mDeflectedCount(0),
+ mHasPendingEvent(false),
+ mCurrentParseEndTime(0),
+ mBeginLoadTime(0),
+ mLastSampledUserEventTime(0),
+ mInMonolithicContainer(0),
+ mInNotification(0),
+ mUpdatesInNotification(0),
+ mPendingSheetCount(0) {
+ NS_ASSERTION(!mLayoutStarted, "What?");
+ NS_ASSERTION(!mDynamicLowerValue, "What?");
+ NS_ASSERTION(!mParsing, "What?");
+ NS_ASSERTION(mLastSampledUserEventTime == 0, "What?");
+ NS_ASSERTION(mDeflectedCount == 0, "What?");
+ NS_ASSERTION(!mDroppedTimer, "What?");
+ NS_ASSERTION(mInMonolithicContainer == 0, "What?");
+ NS_ASSERTION(mInNotification == 0, "What?");
+ NS_ASSERTION(!mDeferredLayoutStart, "What?");
+}
+
+nsContentSink::~nsContentSink() {
+ if (mDocument) {
+ // Remove ourselves just to be safe, though we really should have
+ // been removed in DidBuildModel if everything worked right.
+ mDocument->RemoveObserver(this);
+ }
+}
+
+nsresult nsContentSink::Init(Document* aDoc, nsIURI* aURI,
+ nsISupports* aContainer, nsIChannel* aChannel) {
+ MOZ_ASSERT(aDoc, "null ptr");
+ MOZ_ASSERT(aURI, "null ptr");
+
+ if (!aDoc || !aURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mDocument = aDoc;
+
+ mDocumentURI = aURI;
+ mDocShell = do_QueryInterface(aContainer);
+ mScriptLoader = mDocument->ScriptLoader();
+
+ if (!mRunsToCompletion) {
+ if (mDocShell) {
+ uint32_t loadType = 0;
+ mDocShell->GetLoadType(&loadType);
+ mDocument->SetChangeScrollPosWhenScrollingToRef(
+ (loadType & nsIDocShell::LOAD_CMD_HISTORY) == 0);
+ }
+
+ ProcessHTTPHeaders(aChannel);
+ }
+
+ mCSSLoader = aDoc->CSSLoader();
+
+ mNodeInfoManager = aDoc->NodeInfoManager();
+
+ mBackoffCount = StaticPrefs::content_notify_backoffcount();
+
+ if (StaticPrefs::content_sink_enable_perf_mode() != 0) {
+ mDynamicLowerValue = StaticPrefs::content_sink_enable_perf_mode() == 1;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentSink::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) {
+ MOZ_ASSERT(!mRunsToCompletion, "How come a fragment parser observed sheets?");
+ if (aWasDeferred) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mPendingSheetCount > 0, "How'd that happen?");
+ --mPendingSheetCount;
+
+ const bool loadedAllSheets = !mPendingSheetCount;
+ if (loadedAllSheets && (mDeferredLayoutStart || mDeferredFlushTags)) {
+ if (mDeferredFlushTags) {
+ FlushTags();
+ }
+ if (mDeferredLayoutStart) {
+ // We might not have really started layout, since this sheet was still
+ // loading. Do it now. Probably doesn't matter whether we do this
+ // before or after we unblock scripts, but before feels saner. Note
+ // that if mDeferredLayoutStart is true, that means any subclass
+ // StartLayout() stuff that needs to happen has already happened, so
+ // we don't need to worry about it.
+ StartLayout(false);
+ }
+
+ // Go ahead and try to scroll to our ref if we have one
+ ScrollToRef();
+ }
+
+ mScriptLoader->RemoveParserBlockingScriptExecutionBlocker();
+
+ if (loadedAllSheets &&
+ mDocument->GetReadyStateEnum() >= Document::READYSTATE_INTERACTIVE) {
+ mScriptLoader->DeferCheckpointReached();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aChannel));
+
+ if (!httpchannel) {
+ return NS_OK;
+ }
+
+ bool gotEarlyHints = false;
+ if (nsCOMPtr<mozilla::net::HttpBaseChannel> baseChannel =
+ do_QueryInterface(aChannel)) {
+ nsTArray<mozilla::net::EarlyHintConnectArgs> earlyHints =
+ baseChannel->TakeEarlyHints();
+ gotEarlyHints = !earlyHints.IsEmpty();
+ mDocument->SetEarlyHints(std::move(earlyHints));
+ }
+
+ // Note that the only header we care about is the "link" header, since we
+ // have all the infrastructure for kicking off stylesheet loads.
+
+ nsAutoCString linkHeader;
+
+ nsresult rv = httpchannel->GetResponseHeader("link"_ns, linkHeader);
+ bool gotLinkHeader = NS_SUCCEEDED(rv) && !linkHeader.IsEmpty();
+ if (gotLinkHeader) {
+ mDocument->SetHeaderData(nsGkAtoms::link,
+ NS_ConvertASCIItoUTF16(linkHeader));
+ }
+ if (gotLinkHeader || gotEarlyHints) {
+ NS_ASSERTION(!mProcessLinkHeaderEvent.get(),
+ "Already dispatched an event?");
+
+ mProcessLinkHeaderEvent =
+ NewNonOwningRunnableMethod("nsContentSink::DoProcessLinkHeader", this,
+ &nsContentSink::DoProcessLinkHeader);
+ rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get());
+ if (NS_FAILED(rv)) {
+ mProcessLinkHeaderEvent.Forget();
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsContentSink::DoProcessLinkHeader() {
+ for (const auto& earlyHint : mDocument->GetEarlyHints()) {
+ ProcessLinkFromHeader(earlyHint.link(), earlyHint.earlyHintPreloaderId());
+ }
+
+ nsAutoString value;
+ mDocument->GetHeaderData(nsGkAtoms::link, value);
+ auto linkHeaders = net::ParseLinkHeader(value);
+ for (const auto& linkHeader : linkHeaders) {
+ ProcessLinkFromHeader(linkHeader, 0);
+ }
+}
+
+nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader,
+ uint64_t aEarlyHintPreloaderId) {
+ uint32_t linkTypes = LinkStyle::ParseLinkTypes(aHeader.mRel);
+
+ // The link relation may apply to a different resource, specified
+ // in the anchor parameter. For the link relations supported so far,
+ // we simply abort if the link applies to a resource different to the
+ // one we've loaded
+ if (!nsContentUtils::LinkContextIsURI(aHeader.mAnchor,
+ mDocument->GetDocumentURI())) {
+ return NS_OK;
+ }
+
+ if (nsContentUtils::PrefetchPreloadEnabled(mDocShell)) {
+ // prefetch href if relation is "next" or "prefetch"
+ if ((linkTypes & LinkStyle::eNEXT) || (linkTypes & LinkStyle::ePREFETCH)) {
+ PrefetchHref(aHeader.mHref, aHeader.mAs, aHeader.mType, aHeader.mMedia);
+ }
+
+ if (!aHeader.mHref.IsEmpty() && (linkTypes & LinkStyle::eDNS_PREFETCH)) {
+ PrefetchDNS(aHeader.mHref);
+ }
+
+ if (!aHeader.mHref.IsEmpty() && (linkTypes & LinkStyle::ePRECONNECT)) {
+ Preconnect(aHeader.mHref, aHeader.mCrossOrigin);
+ }
+
+ if (linkTypes & LinkStyle::ePRELOAD) {
+ PreloadHref(aHeader.mHref, aHeader.mAs, aHeader.mType, aHeader.mMedia,
+ aHeader.mIntegrity, aHeader.mSrcset, aHeader.mSizes,
+ aHeader.mCrossOrigin, aHeader.mReferrerPolicy,
+ aEarlyHintPreloaderId);
+ }
+
+ if ((linkTypes & LinkStyle::eMODULE_PRELOAD) &&
+ mDocument->ScriptLoader()->GetModuleLoader()) {
+ // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-modulepreload-module-script-graph
+ // Step 1. Disallow further import maps given settings object.
+ mDocument->ScriptLoader()->GetModuleLoader()->DisallowImportMaps();
+ }
+ }
+
+ // is it a stylesheet link?
+ if (!(linkTypes & LinkStyle::eSTYLESHEET)) {
+ return NS_OK;
+ }
+
+ bool isAlternate = linkTypes & LinkStyle::eALTERNATE;
+ return ProcessStyleLinkFromHeader(aHeader.mHref, isAlternate, aHeader.mTitle,
+ aHeader.mIntegrity, aHeader.mType,
+ aHeader.mMedia, aHeader.mReferrerPolicy);
+}
+
+nsresult nsContentSink::ProcessStyleLinkFromHeader(
+ const nsAString& aHref, bool aAlternate, const nsAString& aTitle,
+ const nsAString& aIntegrity, const nsAString& aType,
+ const nsAString& aMedia, const nsAString& aReferrerPolicy) {
+ if (aAlternate && aTitle.IsEmpty()) {
+ // alternates must have title return without error, for now
+ return NS_OK;
+ }
+
+ nsAutoString mimeType;
+ nsAutoString params;
+ nsContentUtils::SplitMimeType(aType, mimeType, params);
+
+ // see bug 18817
+ if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) {
+ // Unknown stylesheet language
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aHref, nullptr,
+ mDocument->GetDocBaseURI());
+
+ if (NS_FAILED(rv)) {
+ // The URI is bad, move along, don't propagate the error (for now)
+ return NS_OK;
+ }
+
+ // Link header is working like a <link> node, so referrerPolicy attr should
+ // have higher priority than referrer policy from document.
+ ReferrerPolicy policy =
+ ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(mDocument, policy);
+
+ Loader::SheetInfo info{
+ *mDocument,
+ nullptr,
+ url.forget(),
+ nullptr,
+ referrerInfo.forget(),
+ CORS_NONE,
+ aTitle,
+ aMedia,
+ aIntegrity,
+ /* nonce = */ u""_ns,
+ aAlternate ? Loader::HasAlternateRel::Yes : Loader::HasAlternateRel::No,
+ Loader::IsInline::No,
+ Loader::IsExplicitlyEnabled::No,
+ };
+
+ auto loadResultOrErr =
+ mCSSLoader->LoadStyleLink(info, mRunsToCompletion ? nullptr : this);
+ if (loadResultOrErr.isErr()) {
+ return loadResultOrErr.unwrapErr();
+ }
+
+ if (loadResultOrErr.inspect().ShouldBlock() && !mRunsToCompletion) {
+ ++mPendingSheetCount;
+ mScriptLoader->AddParserBlockingScriptExecutionBlocker();
+ }
+
+ return NS_OK;
+}
+
+void nsContentSink::PrefetchHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType,
+ const nsAString& aMedia) {
+ nsCOMPtr<nsIPrefetchService> prefetchService(components::Prefetch::Service());
+ if (prefetchService) {
+ // construct URI using document charset
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+ if (uri) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mDocument);
+ referrerInfo = referrerInfo->CloneWithNewOriginalReferrer(mDocumentURI);
+
+ prefetchService->PrefetchURI(uri, referrerInfo, mDocument, true);
+ }
+ }
+}
+
+void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType, const nsAString& aMedia,
+ const nsAString& aIntegrity,
+ const nsAString& aSrcset,
+ const nsAString& aSizes, const nsAString& aCORS,
+ const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId) {
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+ if (!uri) {
+ // URL parsing failed.
+ return;
+ }
+
+ nsAttrValue asAttr;
+ mozilla::net::ParseAsValue(aAs, asAttr);
+
+ nsAutoString mimeType;
+ nsAutoString notUsed;
+ nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
+
+ auto policyType = mozilla::net::AsValueToContentPolicy(asAttr);
+ if (policyType == nsIContentPolicy::TYPE_INVALID ||
+ !mozilla::net::CheckPreloadAttrs(asAttr, mimeType, aMedia, mDocument)) {
+ // Ignore preload wrong or empty attributes.
+ mozilla::net::WarnIgnoredPreload(*mDocument, *uri);
+ return;
+ }
+
+ mDocument->Preloads().PreloadLinkHeader(
+ uri, aHref, policyType, aAs, aType, aIntegrity, aSrcset, aSizes, aCORS,
+ aReferrerPolicy, aEarlyHintPreloaderId);
+}
+
+void nsContentSink::PrefetchDNS(const nsAString& aHref) {
+ nsAutoString hostname;
+ bool isHttps = false;
+
+ if (StringBeginsWith(aHref, u"//"_ns)) {
+ hostname = Substring(aHref, 2);
+ } else {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref);
+ if (!uri) {
+ return;
+ }
+ nsresult rv;
+ bool isLocalResource = false;
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalResource);
+ if (NS_SUCCEEDED(rv) && !isLocalResource) {
+ nsAutoCString host;
+ uri->GetHost(host);
+ CopyUTF8toUTF16(host, hostname);
+ }
+ isHttps = uri->SchemeIs("https");
+ }
+
+ if (!hostname.IsEmpty() && HTMLDNSPrefetch::IsAllowed(mDocument)) {
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(mDocument, oa);
+
+ HTMLDNSPrefetch::Prefetch(hostname, isHttps, oa,
+ mDocument->GetChannel()->GetTRRMode(),
+ HTMLDNSPrefetch::Priority::Low);
+ }
+}
+
+void nsContentSink::Preconnect(const nsAString& aHref,
+ const nsAString& aCrossOrigin) {
+ // construct URI using document charset
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+
+ if (uri && mDocument) {
+ mDocument->MaybePreconnect(uri,
+ dom::Element::StringToCORSMode(aCrossOrigin));
+ }
+}
+
+void nsContentSink::ScrollToRef() {
+ RefPtr<Document> document = mDocument;
+ document->ScrollToRef();
+}
+
+void nsContentSink::StartLayout(bool aIgnorePendingSheets) {
+ if (mLayoutStarted) {
+ // Nothing to do here
+ return;
+ }
+
+ mDeferredLayoutStart = true;
+
+ if (!aIgnorePendingSheets &&
+ (WaitForPendingSheets() || mDocument->HasPendingInitialTranslation())) {
+ // Bail out; we'll start layout when the sheets and l10n load
+ return;
+ }
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
+ "Layout", LAYOUT, mDocumentURI->GetSpecOrDefault());
+
+ mDeferredLayoutStart = false;
+
+ if (aIgnorePendingSheets) {
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Layout"_ns, mDocument,
+ nsContentUtils::eLAYOUT_PROPERTIES, "ForcedLayoutStart");
+ }
+
+ // Notify on all our content. If none of our presshells have started layout
+ // yet it'll be a no-op except for updating our data structures, a la
+ // UpdateChildCounts() (because we don't want to double-notify on whatever we
+ // have right now). If some of them _have_ started layout, we want to make
+ // sure to flush tags instead of just calling UpdateChildCounts() after we
+ // loop over the shells.
+ FlushTags();
+
+ mLayoutStarted = true;
+ mLastNotificationTime = PR_Now();
+
+ mDocument->SetMayStartLayout(true);
+ RefPtr<PresShell> presShell = mDocument->GetPresShell();
+ // Make sure we don't call Initialize() for a shell that has
+ // already called it. This can happen when the layout frame for
+ // an iframe is constructed *between* the Embed() call for the
+ // docshell in the iframe, and the content sink's call to OpenBody().
+ // (Bug 153815)
+ if (presShell && !presShell->DidInitialize()) {
+ nsresult rv = presShell->Initialize();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ // If the document we are loading has a reference or it is a
+ // frameset document, disable the scroll bars on the views.
+
+ mDocument->SetScrollToRef(mDocument->GetDocumentURI());
+}
+
+void nsContentSink::NotifyAppend(nsIContent* aContainer, uint32_t aStartIndex) {
+ mInNotification++;
+
+ {
+ // Scope so we call EndUpdate before we decrease mInNotification
+ //
+ // Note that aContainer->OwnerDoc() may not be mDocument.
+ MOZ_AUTO_DOC_UPDATE(aContainer->OwnerDoc(), true);
+ MutationObservers::NotifyContentAppended(
+ aContainer, aContainer->GetChildAt_Deprecated(aStartIndex));
+ mLastNotificationTime = PR_Now();
+ }
+
+ mInNotification--;
+}
+
+NS_IMETHODIMP
+nsContentSink::Notify(nsITimer* timer) {
+ if (mParsing) {
+ // We shouldn't interfere with our normal DidProcessAToken logic
+ mDroppedTimer = true;
+ return NS_OK;
+ }
+
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ } else {
+ FlushTags();
+
+ // Now try and scroll to the reference
+ // XXX Should we scroll unconditionally for history loads??
+ ScrollToRef();
+ }
+
+ mNotificationTimer = nullptr;
+ return NS_OK;
+}
+
+bool nsContentSink::IsTimeToNotify() {
+ if (!StaticPrefs::content_notify_ontimer() || !mLayoutStarted ||
+ !mBackoffCount || mInMonolithicContainer) {
+ return false;
+ }
+
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ return false;
+ }
+
+ PRTime now = PR_Now();
+
+ int64_t interval = GetNotificationInterval();
+ int64_t diff = now - mLastNotificationTime;
+
+ if (diff > interval) {
+ mBackoffCount--;
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsContentSink::WillInterruptImpl() {
+ nsresult result = NS_OK;
+
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS, ("nsContentSink::WillInterrupt: this=%p", this));
+#ifndef SINK_NO_INCREMENTAL
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ } else if (StaticPrefs::content_notify_ontimer() && mLayoutStarted) {
+ if (mBackoffCount && !mInMonolithicContainer) {
+ int64_t now = PR_Now();
+ int64_t interval = GetNotificationInterval();
+ int64_t diff = now - mLastNotificationTime;
+
+ // If it's already time for us to have a notification
+ if (diff > interval || mDroppedTimer) {
+ mBackoffCount--;
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: flushing tags since we've "
+ "run out time; backoff count: %d",
+ mBackoffCount));
+ result = FlushTags();
+ if (mDroppedTimer) {
+ ScrollToRef();
+ mDroppedTimer = false;
+ }
+ } else if (!mNotificationTimer) {
+ interval -= diff;
+ int32_t delay = interval;
+
+ // Convert to milliseconds
+ delay /= PR_USEC_PER_MSEC;
+
+ NS_NewTimerWithCallback(getter_AddRefs(mNotificationTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (mNotificationTimer) {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: setting up timer with "
+ "delay %d",
+ delay));
+ }
+ }
+ }
+ } else {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: flushing tags "
+ "unconditionally"));
+ result = FlushTags();
+ }
+#endif
+
+ mParsing = false;
+
+ return result;
+}
+
+void nsContentSink::WillResumeImpl() {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS, ("nsContentSink::WillResume: this=%p", this));
+
+ mParsing = true;
+}
+
+nsresult nsContentSink::DidProcessATokenImpl() {
+ if (mRunsToCompletion || !mParser) {
+ return NS_OK;
+ }
+
+ // Get the current user event time
+ PresShell* presShell = mDocument->GetPresShell();
+ if (!presShell) {
+ // If there's no pres shell in the document, return early since
+ // we're not laying anything out here.
+ return NS_OK;
+ }
+
+ // Increase before comparing to gEventProbeRate
+ ++mDeflectedCount;
+
+ // Check if there's a pending event
+ if (StaticPrefs::content_sink_pending_event_mode() != 0 &&
+ !mHasPendingEvent &&
+ (mDeflectedCount % StaticPrefs::content_sink_event_probe_rate()) == 0) {
+ nsViewManager* vm = presShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ mHasPendingEvent = widget && widget->HasPendingInputEvent();
+ }
+
+ if (mHasPendingEvent && StaticPrefs::content_sink_pending_event_mode() == 2) {
+ return NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ // Have we processed enough tokens to check time?
+ if (!mHasPendingEvent &&
+ mDeflectedCount <
+ uint32_t(mDynamicLowerValue
+ ? StaticPrefs::content_sink_interactive_deflect_count()
+ : StaticPrefs::content_sink_perf_deflect_count())) {
+ return NS_OK;
+ }
+
+ mDeflectedCount = 0;
+
+ // Check if it's time to return to the main event loop
+ if (PR_IntervalToMicroseconds(PR_IntervalNow()) > mCurrentParseEndTime) {
+ return NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+void nsContentSink::BeginUpdate(Document* aDocument) {
+ // Remember nested updates from updates that we started.
+ if (mInNotification > 0 && mUpdatesInNotification < 2) {
+ ++mUpdatesInNotification;
+ }
+
+ // If we're in a script and we didn't do the notification,
+ // something else in the script processing caused the
+ // notification to occur. Since this could result in frame
+ // creation, make sure we've flushed everything before we
+ // continue.
+
+ if (!mInNotification++) {
+ FlushTags();
+ }
+}
+
+void nsContentSink::EndUpdate(Document* aDocument) {
+ // If we're in a script and we didn't do the notification,
+ // something else in the script processing caused the
+ // notification to occur. Update our notion of how much
+ // has been flushed to include any new content if ending
+ // this update leaves us not inside a notification.
+ if (!--mInNotification) {
+ UpdateChildCounts();
+ }
+}
+
+void nsContentSink::DidBuildModelImpl(bool aTerminated) {
+ MOZ_ASSERT(aTerminated ||
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
+
+ if (mScriptLoader) {
+ mScriptLoader->ParsingComplete(aTerminated);
+ if (!mPendingSheetCount) {
+ mScriptLoader->DeferCheckpointReached();
+ }
+ }
+
+ if (!mDocument->HaveFiredDOMTitleChange()) {
+ mDocument->NotifyPossibleTitleChange(false);
+ }
+
+ // Cancel a timer if we had one out there
+ if (mNotificationTimer) {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::DidBuildModel: canceling notification "
+ "timeout"));
+ mNotificationTimer->Cancel();
+ mNotificationTimer = nullptr;
+ }
+}
+
+void nsContentSink::DropParserAndPerfHint(void) {
+ if (!mParser) {
+ // Make sure we don't unblock unload too many times
+ return;
+ }
+
+ // Ref. Bug 49115
+ // Do this hack to make sure that the parser
+ // doesn't get destroyed, accidently, before
+ // the circularity, between sink & parser, is
+ // actually broken.
+ // Drop our reference to the parser to get rid of a circular
+ // reference.
+ RefPtr<nsParserBase> kungFuDeathGrip = std::move(mParser);
+ mozilla::Unused << kungFuDeathGrip;
+
+ // Call UnblockOnload only if mRunsToComletion is false and if
+ // we have already started loading because it's possible that this function
+ // is called (i.e. the parser is terminated) before we start loading due to
+ // destroying the window inside unload event callbacks for the previous
+ // document.
+ if (!mRunsToCompletion && mIsBlockingOnload) {
+ mDocument->UnblockOnload(true);
+ mIsBlockingOnload = false;
+ }
+}
+
+bool nsContentSink::IsScriptExecutingImpl() {
+ return !!mScriptLoader->GetCurrentScript();
+}
+
+nsresult nsContentSink::WillParseImpl(void) {
+ if (mRunsToCompletion || !mDocument) {
+ return NS_OK;
+ }
+
+ PresShell* presShell = mDocument->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+
+ if (StaticPrefs::content_sink_enable_perf_mode() == 0) {
+ nsViewManager* vm = presShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ uint32_t lastEventTime;
+ vm->GetLastUserEventTime(lastEventTime);
+
+ bool newDynLower = mDocument->IsInBackgroundWindow() ||
+ ((currentTime - mBeginLoadTime) >
+ StaticPrefs::content_sink_initial_perf_time() &&
+ (currentTime - lastEventTime) <
+ StaticPrefs::content_sink_interactive_time());
+
+ if (mDynamicLowerValue != newDynLower) {
+ mDynamicLowerValue = newDynLower;
+ }
+ }
+
+ mDeflectedCount = 0;
+ mHasPendingEvent = false;
+
+ mCurrentParseEndTime =
+ currentTime + (mDynamicLowerValue
+ ? StaticPrefs::content_sink_interactive_parse_time()
+ : StaticPrefs::content_sink_perf_parse_time());
+
+ return NS_OK;
+}
+
+void nsContentSink::WillBuildModelImpl() {
+ if (!mRunsToCompletion) {
+ mDocument->BlockOnload();
+ mIsBlockingOnload = true;
+
+ mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+ }
+
+ mDocument->ResetScrolledToRefAlready();
+
+ if (mProcessLinkHeaderEvent.get()) {
+ mProcessLinkHeaderEvent.Revoke();
+
+ DoProcessLinkHeader();
+ }
+}
+
+/* static */
+void nsContentSink::NotifyDocElementCreated(Document* aDoc) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ auto* win = nsGlobalWindowInner::Cast(aDoc->GetInnerWindow());
+ bool fireInitialInsertion = !win || !win->DidFireDocElemInserted();
+ if (win) {
+ win->SetDidFireDocElemInserted();
+ }
+ if (fireInitialInsertion) {
+ observerService->NotifyObservers(ToSupports(aDoc),
+ "initial-document-element-inserted", u"");
+ }
+ observerService->NotifyObservers(ToSupports(aDoc),
+ "document-element-inserted", u"");
+
+ nsContentUtils::DispatchChromeEvent(aDoc, ToSupports(aDoc),
+ u"DOMDocElementInserted"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+}
+
+NS_IMETHODIMP
+nsContentSink::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsContentSink_timer");
+ return NS_OK;
+}
diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h
new file mode 100644
index 0000000000..39b1421713
--- /dev/null
+++ b/dom/base/nsContentSink.h
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for the XML and HTML content sinks, which construct a
+ * DOM based on information from the parser.
+ */
+
+#ifndef _nsContentSink_h_
+#define _nsContentSink_h_
+
+// Base class for contentsink implementations.
+
+#include "mozilla/Attributes.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+#include "nsITimer.h"
+#include "nsStubDocumentObserver.h"
+#include "nsIContentSink.h"
+#include "mozilla/Logging.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsThreadUtils.h"
+#include "mozilla/StaticPrefs_content.h"
+
+class nsIURI;
+class nsIChannel;
+class nsIDocShell;
+class nsAtom;
+class nsIChannel;
+class nsIContent;
+class nsNodeInfoManager;
+
+namespace mozilla {
+namespace css {
+class Loader;
+} // namespace css
+
+namespace dom {
+class Document;
+class ScriptLoader;
+} // namespace dom
+
+namespace net {
+struct LinkHeader;
+};
+} // namespace mozilla
+
+#ifdef DEBUG
+
+extern mozilla::LazyLogModule gContentSinkLogModuleInfo;
+
+# define SINK_TRACE_CALLS 0x1
+# define SINK_TRACE_REFLOW 0x2
+# define SINK_ALWAYS_REFLOW 0x4
+
+# define SINK_LOG_TEST(_lm, _bit) (int((_lm)->Level()) & (_bit))
+
+# define SINK_TRACE(_lm, _bit, _args) \
+ do { \
+ if (SINK_LOG_TEST(_lm, _bit)) { \
+ printf_stderr _args; \
+ } \
+ } while (0)
+
+#else
+# define SINK_TRACE(_lm, _bit, _args)
+#endif
+
+#undef SINK_NO_INCREMENTAL
+
+//----------------------------------------------------------------------
+
+class nsContentSink : public nsICSSLoaderObserver,
+ public nsSupportsWeakReference,
+ public nsStubDocumentObserver,
+ public nsITimerCallback,
+ public nsINamed {
+ protected:
+ using Document = mozilla::dom::Document;
+
+ private:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsContentSink, nsICSSLoaderObserver)
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ NS_DECL_NSINAMED
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) override;
+
+ // nsIContentSink implementation helpers
+ nsresult WillParseImpl(void);
+ nsresult WillInterruptImpl(void);
+ void WillResumeImpl();
+ nsresult DidProcessATokenImpl(void);
+ void WillBuildModelImpl(void);
+ void DidBuildModelImpl(bool aTerminated);
+ void DropParserAndPerfHint(void);
+ bool IsScriptExecutingImpl();
+
+ void NotifyAppend(nsIContent* aContent, uint32_t aStartIndex);
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
+
+ virtual void UpdateChildCounts() = 0;
+
+ bool IsTimeToNotify();
+
+ protected:
+ nsContentSink();
+ virtual ~nsContentSink();
+
+ nsresult Init(Document* aDoc, nsIURI* aURI, nsISupports* aContainer,
+ nsIChannel* aChannel);
+
+ nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
+ // aEarlyHintPreloaderId zero means no early hint channel to connect back
+ nsresult ProcessLinkFromHeader(const mozilla::net::LinkHeader& aHeader,
+ uint64_t aEarlyHintPreloaderId);
+
+ virtual nsresult ProcessStyleLinkFromHeader(
+ const nsAString& aHref, bool aAlternate, const nsAString& aTitle,
+ const nsAString& aIntegrity, const nsAString& aType,
+ const nsAString& aMedia, const nsAString& aReferrerPolicy);
+
+ void PrefetchHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType, const nsAString& aMedia);
+ void PreloadHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType, const nsAString& aMedia,
+ const nsAString& aIntegrity, const nsAString& aSrcset,
+ const nsAString& aSizes, const nsAString& aCORS,
+ const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId);
+
+ // For PrefetchDNS() aHref can either be the usual
+ // URI format or of the form "//www.hostname.com" without a scheme.
+ void PrefetchDNS(const nsAString& aHref);
+
+ // Gets the cache key (used to identify items in a cache) of the channel.
+ nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey);
+
+ public:
+ // For Preconnect() aHref can either be the usual
+ // URI format or of the form "//www.hostname.com" without a scheme.
+ void Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin);
+
+ protected:
+ // Tries to scroll to the URI's named anchor. Once we've successfully
+ // done that, further calls to this method will be ignored.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void ScrollToRef();
+
+ // Start layout. If aIgnorePendingSheets is true, this will happen even if
+ // we still have stylesheet loads pending. Otherwise, we'll wait until the
+ // stylesheets are all done loading.
+ public:
+ void StartLayout(bool aIgnorePendingSheets);
+
+ static void NotifyDocElementCreated(Document* aDoc);
+
+ Document* GetDocument() { return mDocument; }
+
+ // Later on we might want to make this more involved somehow
+ // (e.g. stop waiting after some timeout or whatnot).
+ bool WaitForPendingSheets() { return mPendingSheetCount > 0; }
+
+ protected:
+ inline int32_t GetNotificationInterval() {
+ if (mDynamicLowerValue) {
+ return 1000;
+ }
+
+ return mozilla::StaticPrefs::content_notify_interval();
+ }
+
+ virtual nsresult FlushTags() = 0;
+
+ void DoProcessLinkHeader();
+
+ void StopDeflecting() {
+ mDeflectedCount = mozilla::StaticPrefs::content_sink_perf_deflect_count();
+ }
+
+ protected:
+ RefPtr<Document> mDocument;
+ RefPtr<nsParserBase> mParser;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ RefPtr<mozilla::css::Loader> mCSSLoader;
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+ RefPtr<mozilla::dom::ScriptLoader> mScriptLoader;
+
+ // back off timer notification after count
+ int32_t mBackoffCount;
+
+ // Time of last notification
+ // Note: mLastNotificationTime is only valid once mLayoutStarted is true.
+ PRTime mLastNotificationTime;
+
+ // Timer used for notification
+ nsCOMPtr<nsITimer> mNotificationTimer;
+
+ uint8_t mLayoutStarted : 1;
+ uint8_t mDynamicLowerValue : 1;
+ uint8_t mParsing : 1;
+ uint8_t mDroppedTimer : 1;
+ // If true, we deferred starting layout until sheets load
+ uint8_t mDeferredLayoutStart : 1;
+ // If true, we deferred notifications until sheets load
+ uint8_t mDeferredFlushTags : 1;
+ // If false, we're not ourselves a document observer; that means we
+ // shouldn't be performing any more content model notifications,
+ // since we're not longer updating our child counts.
+ uint8_t mIsDocumentObserver : 1;
+ // True if this is parser is a fragment parser or an HTML DOMParser.
+ // XML DOMParser leaves this to false for now!
+ uint8_t mRunsToCompletion : 1;
+ // True if we are blocking load event.
+ bool mIsBlockingOnload : 1;
+
+ //
+ // -- Can interrupt parsing members --
+ //
+
+ // The number of tokens that have been processed since we measured
+ // if it's time to return to the main event loop.
+ uint32_t mDeflectedCount;
+
+ // Is there currently a pending event?
+ bool mHasPendingEvent;
+
+ // When to return to the main event loop
+ uint32_t mCurrentParseEndTime;
+
+ int32_t mBeginLoadTime;
+
+ // Last mouse event or keyboard event time sampled by the content
+ // sink
+ uint32_t mLastSampledUserEventTime;
+
+ int32_t mInMonolithicContainer;
+
+ int32_t mInNotification;
+ uint32_t mUpdatesInNotification;
+
+ uint32_t mPendingSheetCount;
+
+ nsRevocableEventPtr<nsRunnableMethod<nsContentSink, void, false> >
+ mProcessLinkHeaderEvent;
+};
+
+#endif // _nsContentSink_h_
diff --git a/dom/base/nsContentTypeParser.cpp b/dom/base/nsContentTypeParser.cpp
new file mode 100644
index 0000000000..14d07fc038
--- /dev/null
+++ b/dom/base/nsContentTypeParser.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentTypeParser.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+
+nsContentTypeParser::nsContentTypeParser(const nsAString& aString)
+ : mString(aString) {}
+
+nsresult nsContentTypeParser::GetParameter(const char* aParameterName,
+ nsAString& aResult) const {
+ return net::GetParameterHTTP(mString, aParameterName, aResult);
+}
+
+nsresult nsContentTypeParser::GetType(nsAString& aResult) const {
+ nsresult rv = GetParameter(nullptr, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsContentUtils::ASCIIToLower(aResult);
+ return NS_OK;
+}
diff --git a/dom/base/nsContentTypeParser.h b/dom/base/nsContentTypeParser.h
new file mode 100644
index 0000000000..27184ad2ee
--- /dev/null
+++ b/dom/base/nsContentTypeParser.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentTypeParser_h
+#define nsContentTypeParser_h
+
+#include "nsString.h"
+
+class nsContentTypeParser {
+ public:
+ explicit nsContentTypeParser(const nsAString& aString);
+
+ nsresult GetParameter(const char* aParameterName, nsAString& aResult) const;
+ nsresult GetType(nsAString& aResult) const;
+
+ private:
+ NS_ConvertUTF16toUTF8 mString;
+};
+
+#endif
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
new file mode 100644
index 0000000000..13a54f0214
--- /dev/null
+++ b/dom/base/nsContentUtils.cpp
@@ -0,0 +1,11190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A namespace class for static layout utilities. */
+
+#include "nsContentUtils.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <new>
+#include <utility>
+#include "BrowserChild.h"
+#include "DecoderTraits.h"
+#include "ErrorList.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "ImageOps.h"
+#include "InProcessBrowserChildMessageManager.h"
+#include "MainThreadUtils.h"
+#include "PLDHashTable.h"
+#include "ReferrerInfo.h"
+#include "ScopedNSSTypes.h"
+#include "ThirdPartyUtil.h"
+#include "Units.h"
+#include "chrome/common/ipc_message.h"
+#include "gfxDrawable.h"
+#include "harfbuzz/hb.h"
+#include "imgICache.h"
+#include "imgIContainer.h"
+#include "imgILoader.h"
+#include "imgIRequest.h"
+#include "imgLoader.h"
+#include "js/Array.h"
+#include "js/ArrayBuffer.h"
+#include "js/BuildId.h"
+#include "js/GCAPI.h"
+#include "js/Id.h"
+#include "js/JSON.h"
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetProperty
+#include "js/PropertyDescriptor.h"
+#include "js/Realm.h"
+#include "js/RegExp.h"
+#include "js/RegExpFlags.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozAutoDocUpdate.h"
+#include "mozIDOMWindow.h"
+#include "nsIOService.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/AtomArray.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/AutoTimelineMarker.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/BloomFilter.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/CallState.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Components.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/HangAnnotations.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/InputEventOptions.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Latin1.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/ManualNAC.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerRunnable.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/Span.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+#ifdef FUZZING
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+#include "mozilla/StaticPrefs_nglayout.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TaskCategory.h"
+#include "mozilla/TextControlState.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Variant.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/AutocompleteInfoBinding.h"
+#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/BrowserBridgeParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CallbackFunction.h"
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/DOMArena.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/DOMSecurityMonitor.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/FileBlobImpl.h"
+#include "mozilla/dom/FileSystemSecurity.h"
+#include "mozilla/dom/FilteredNodeIterator.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/IPCBlob.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+#include "mozilla/dom/MessageListenerManager.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeBinding.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/PBrowser.h"
+#include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/XULCommandEvent.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BaseMargin.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsAlgorithm.h"
+#include "nsArrayUtils.h"
+#include "nsAtom.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsBaseHashtable.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsCanvasFrame.h"
+#include "nsCaseTreatment.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsCharTraits.h"
+#include "nsCompatibility.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContainerFrame.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentDLF.h"
+#include "nsContentList.h"
+#include "nsContentListDeclarations.h"
+#include "nsContentPolicyUtils.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMString.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsDocShell.h"
+#include "nsDocShellCID.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "nsFrameList.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHTMLDocument.h"
+#include "nsHTMLTags.h"
+#include "nsHashKeys.h"
+#include "nsHtml5StringParser.h"
+#include "nsIAboutModule.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIAppShell.h"
+#include "nsIArray.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIBidiKeyboard.h"
+#include "nsIBrowser.h"
+#include "nsICacheInfoChannel.h"
+#include "nsICategoryManager.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentSink.h"
+#include "nsIContentViewer.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIDragService.h"
+#include "nsIDragSession.h"
+#include "nsIFile.h"
+#include "nsIFocusManager.h"
+#include "nsIFormControl.h"
+#include "nsIFragmentContentSink.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIMIMEService.h"
+#include "nsIMemoryReporter.h"
+#include "nsINetUtil.h"
+#include "nsINode.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIParserUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIPluginTag.h"
+#include "nsIPrincipal.h"
+#include "nsIProperties.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRequest.h"
+#include "nsIRunnable.h"
+#include "nsIScreen.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISerialEventTarget.h"
+#include "nsIStreamConverter.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStringBundle.h"
+#include "nsISupports.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISupportsUtils.h"
+#include "nsITransferable.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsIURIWithSpecialOrigin.h"
+#endif
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebNavigationInfo.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsIXPConnect.h"
+#include "nsJSPrincipals.h"
+#include "nsJSUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsLiteralString.h"
+#include "nsMappedAttributes.h"
+#include "nsMargin.h"
+#include "nsMimeTypes.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsNodeInfoManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsParser.h"
+#include "nsParserConstants.h"
+#include "nsPluginHost.h"
+#include "nsPoint.h"
+#include "nsPointerHashKeys.h"
+#include "nsPresContext.h"
+#include "nsQueryFrame.h"
+#include "nsQueryObject.h"
+#include "nsRange.h"
+#include "nsRefPtrHashtable.h"
+#include "nsSandboxFlags.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsStringBundle.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsStringIterator.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTPromiseFlatString.h"
+#include "nsTStringRepr.h"
+#include "nsTextFragment.h"
+#include "nsTextNode.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "nsTreeSanitizer.h"
+#include "nsUGenCategory.h"
+#include "nsURLHelper.h"
+#include "nsUnicodeProperties.h"
+#include "nsVariant.h"
+#include "nsWidgetsCID.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsXULAppAPI.h"
+#include "nsXULElement.h"
+#include "nsXULPopupManager.h"
+#include "nscore.h"
+#include "prinrval.h"
+#include "xpcprivate.h"
+#include "xpcpublic.h"
+
+#if defined(XP_WIN)
+// Undefine LoadImage to prevent naming conflict with Windows.
+# undef LoadImage
+#endif
+
+extern "C" int MOZ_XMLTranslateEntity(const char* ptr, const char* end,
+ const char** next, char16_t* result);
+extern "C" int MOZ_XMLCheckQName(const char* ptr, const char* end, int ns_aware,
+ const char** colon);
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+const char kLoadAsData[] = "loadAsData";
+
+nsIXPConnect* nsContentUtils::sXPConnect;
+nsIScriptSecurityManager* nsContentUtils::sSecurityManager;
+nsIPrincipal* nsContentUtils::sSystemPrincipal;
+nsIPrincipal* nsContentUtils::sNullSubjectPrincipal;
+nsIConsoleService* nsContentUtils::sConsoleService;
+nsTHashMap<nsRefPtrHashKey<nsAtom>, EventNameMapping>*
+ nsContentUtils::sAtomEventTable = nullptr;
+nsTHashMap<nsStringHashKey, EventNameMapping>*
+ nsContentUtils::sStringEventTable = nullptr;
+nsTArray<RefPtr<nsAtom>>* nsContentUtils::sUserDefinedEvents = nullptr;
+nsIStringBundleService* nsContentUtils::sStringBundleService;
+
+static StaticRefPtr<nsIStringBundle>
+ sStringBundles[nsContentUtils::PropertiesFile_COUNT];
+
+nsIContentPolicy* nsContentUtils::sContentPolicyService;
+bool nsContentUtils::sTriedToGetContentPolicy = false;
+StaticRefPtr<nsIBidiKeyboard> nsContentUtils::sBidiKeyboard;
+uint32_t nsContentUtils::sScriptBlockerCount = 0;
+uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0;
+AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners =
+ nullptr;
+uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0;
+nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr;
+
+bool nsContentUtils::sIsHandlingKeyBoardEvent = false;
+
+nsString* nsContentUtils::sShiftText = nullptr;
+nsString* nsContentUtils::sControlText = nullptr;
+nsString* nsContentUtils::sMetaText = nullptr;
+nsString* nsContentUtils::sOSText = nullptr;
+nsString* nsContentUtils::sAltText = nullptr;
+nsString* nsContentUtils::sModifierSeparator = nullptr;
+
+bool nsContentUtils::sInitialized = false;
+#ifndef RELEASE_OR_BETA
+bool nsContentUtils::sBypassCSSOMOriginCheck = false;
+#endif
+
+nsCString* nsContentUtils::sJSScriptBytecodeMimeType = nullptr;
+nsCString* nsContentUtils::sJSModuleBytecodeMimeType = nullptr;
+
+nsContentUtils::UserInteractionObserver*
+ nsContentUtils::sUserInteractionObserver = nullptr;
+
+nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
+nsParser* nsContentUtils::sXMLFragmentParser = nullptr;
+nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
+bool nsContentUtils::sFragmentParsingActive = false;
+
+bool nsContentUtils::sMayHaveFormCheckboxStateChangeListeners = false;
+bool nsContentUtils::sMayHaveFormRadioStateChangeListeners = false;
+
+mozilla::LazyLogModule nsContentUtils::gResistFingerprintingLog(
+ "nsResistFingerprinting");
+mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
+
+int32_t nsContentUtils::sInnerOrOuterWindowCount = 0;
+uint32_t nsContentUtils::sInnerOrOuterWindowSerialCounter = 0;
+
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RawRangeBoundary& aFirstBoundary,
+ const RangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RawRangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary);
+
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary,
+ bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary, bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RawRangeBoundary& aFirstBoundary,
+ const RangeBoundary& aSecondBoundary, bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RawRangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary, bool* aDisconnected);
+
+// Subset of
+// http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
+enum AutocompleteUnsupportedFieldName : uint8_t {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+ eAutocompleteUnsupportedFieldName_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+};
+
+enum AutocompleteNoPersistFieldName : uint8_t {
+#define AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(name_, value_) \
+ eAutocompleteNoPersistFieldName_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+};
+
+enum AutocompleteUnsupportFieldContactHint : uint8_t {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+ eAutocompleteUnsupportedFieldContactHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+};
+
+enum AutocompleteFieldName : uint8_t {
+#define AUTOCOMPLETE_FIELD_NAME(name_, value_) eAutocompleteFieldName_##name_,
+#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
+ AUTOCOMPLETE_FIELD_NAME(name_, value_)
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_NAME
+#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
+};
+
+enum AutocompleteFieldHint : uint8_t {
+#define AUTOCOMPLETE_FIELD_HINT(name_, value_) eAutocompleteFieldHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_HINT
+};
+
+enum AutocompleteFieldContactHint : uint8_t {
+#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_) \
+ eAutocompleteFieldContactHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
+};
+
+enum AutocompleteCategory {
+#define AUTOCOMPLETE_CATEGORY(name_, value_) eAutocompleteCategory_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CATEGORY
+};
+
+static const nsAttrValue::EnumTable kAutocompleteUnsupportedFieldNameTable[] = {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteUnsupportedFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteNoPersistFieldNameTable[] = {
+#define AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteNoPersistFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable
+ kAutocompleteUnsupportedContactFieldHintTable[] = {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+ {value_, eAutocompleteUnsupportedFieldContactHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteFieldNameTable[] = {
+#define AUTOCOMPLETE_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteContactFieldNameTable[] = {
+#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteFieldHintTable[] = {
+#define AUTOCOMPLETE_FIELD_HINT(name_, value_) \
+ {value_, eAutocompleteFieldHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_HINT
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteContactFieldHintTable[] = {
+#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_) \
+ {value_, eAutocompleteFieldContactHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
+ {nullptr, 0}};
+
+namespace {
+
+static PLDHashTable* sEventListenerManagersHash;
+
+// A global hashtable to for keeping the arena alive for cross docGroup node
+// adoption.
+static nsRefPtrHashtable<nsPtrHashKey<const nsINode>, mozilla::dom::DOMArena>*
+ sDOMArenaHashtable;
+
+class DOMEventListenerManagersHashReporter final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~DOMEventListenerManagersHashReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ // We don't measure the |EventListenerManager| objects pointed to by the
+ // entries because those references are non-owning.
+ int64_t amount =
+ sEventListenerManagersHash
+ ? sEventListenerManagersHash->ShallowSizeOfIncludingThis(
+ MallocSizeOf)
+ : 0;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/dom/event-listener-managers-hash", KIND_HEAP, UNITS_BYTES,
+ amount, "Memory used by the event listener manager's hash table.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(DOMEventListenerManagersHashReporter, nsIMemoryReporter)
+
+class EventListenerManagerMapEntry : public PLDHashEntryHdr {
+ public:
+ explicit EventListenerManagerMapEntry(const void* aKey) : mKey(aKey) {}
+
+ ~EventListenerManagerMapEntry() {
+ NS_ASSERTION(!mListenerManager, "caller must release and disconnect ELM");
+ }
+
+ protected: // declared protected to silence clang warnings
+ const void* mKey; // must be first, to look like PLDHashEntryStub
+
+ public:
+ RefPtr<EventListenerManager> mListenerManager;
+};
+
+static void EventListenerManagerHashInitEntry(PLDHashEntryHdr* entry,
+ const void* key) {
+ // Initialize the entry with placement new
+ new (entry) EventListenerManagerMapEntry(key);
+}
+
+static void EventListenerManagerHashClearEntry(PLDHashTable* table,
+ PLDHashEntryHdr* entry) {
+ EventListenerManagerMapEntry* lm =
+ static_cast<EventListenerManagerMapEntry*>(entry);
+
+ // Let the EventListenerManagerMapEntry clean itself up...
+ lm->~EventListenerManagerMapEntry();
+}
+
+class SameOriginCheckerImpl final : public nsIChannelEventSink,
+ public nsIInterfaceRequestor {
+ ~SameOriginCheckerImpl() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+};
+
+} // namespace
+
+void AutoSuppressEventHandling::SuppressDocument(Document* aDoc) {
+ // Note: Document::SuppressEventHandling will also automatically suppress
+ // event handling for any in-process sub-documents. However, since we need
+ // to deal with cases where remote BrowsingContexts may be interleaved
+ // with in-process ones, we still need to walk the entire tree ourselves.
+ // This may be slightly redundant in some cases, but since event handling
+ // suppressions maintain a count of current blockers, it does not cause
+ // any problems.
+ aDoc->SuppressEventHandling();
+}
+
+void AutoSuppressEventHandling::UnsuppressDocument(Document* aDoc) {
+ aDoc->UnsuppressEventHandlingAndFireEvents(true);
+}
+
+AutoSuppressEventHandling::~AutoSuppressEventHandling() {
+ UnsuppressDocuments();
+}
+
+void AutoSuppressEventHandlingAndSuspend::SuppressDocument(Document* aDoc) {
+ AutoSuppressEventHandling::SuppressDocument(aDoc);
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->Suspend();
+ mWindows.AppendElement(win);
+ }
+}
+
+AutoSuppressEventHandlingAndSuspend::~AutoSuppressEventHandlingAndSuspend() {
+ for (const auto& win : mWindows) {
+ win->Resume();
+ }
+}
+
+/**
+ * This class is used to determine whether or not the user is currently
+ * interacting with the browser. It listens to observer events to toggle the
+ * value of the sUserActive static.
+ *
+ * This class is an internal implementation detail.
+ * nsContentUtils::GetUserIsInteracting() should be used to access current
+ * user interaction status.
+ */
+class nsContentUtils::UserInteractionObserver final
+ : public nsIObserver,
+ public BackgroundHangAnnotator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void Init();
+ void Shutdown();
+ void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override;
+
+ static Atomic<bool> sUserActive;
+
+ private:
+ ~UserInteractionObserver() = default;
+};
+
+static constexpr nsLiteralCString kRfpPrefs[] = {
+ "privacy.resistFingerprinting"_ns,
+ "privacy.resistFingerprinting.pbmode"_ns,
+ "privacy.fingerprintingProtection"_ns,
+ "privacy.fingerprintingProtection.pbmode"_ns,
+ "privacy.fingerprintingProtection.overrides"_ns,
+};
+
+static void RecomputeResistFingerprintingAllDocs(const char*, void*) {
+ AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups;
+ BrowsingContextGroup::GetAllGroups(bcGroups);
+ for (auto& bcGroup : bcGroups) {
+ AutoTArray<DocGroup*, 5> docGroups;
+ bcGroup->GetDocGroups(docGroups);
+ for (auto* docGroup : docGroups) {
+ for (Document* doc : *docGroup) {
+ if (doc->RecomputeResistFingerprinting()) {
+ if (auto* pc = doc->GetPresContext()) {
+ pc->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::PreferenceChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+ }
+ }
+ }
+ }
+}
+
+// static
+nsresult nsContentUtils::Init() {
+ if (sInitialized) {
+ NS_WARNING("Init() called twice");
+
+ return NS_OK;
+ }
+
+ nsHTMLTags::AddRefTable();
+
+ sXPConnect = nsXPConnect::XPConnect();
+ // We hold a strong ref to sXPConnect to ensure that it does not go away until
+ // nsLayoutStatics::Shutdown is happening. Otherwise ~nsXPConnect can be
+ // triggered by xpcModuleDtor late in shutdown and cause crashes due to
+ // various stuff already being torn down by then. Note that this means that
+ // we are effectively making sure that if we leak nsLayoutStatics then we also
+ // leak nsXPConnect.
+ NS_ADDREF(sXPConnect);
+
+ sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
+ if (!sSecurityManager) return NS_ERROR_FAILURE;
+ NS_ADDREF(sSecurityManager);
+
+ sSecurityManager->GetSystemPrincipal(&sSystemPrincipal);
+ MOZ_ASSERT(sSystemPrincipal);
+
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ if (!nullPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nullPrincipal.forget(&sNullSubjectPrincipal);
+
+ if (!InitializeEventTable()) return NS_ERROR_FAILURE;
+
+ if (!sEventListenerManagersHash) {
+ static const PLDHashTableOps hash_table_ops = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, EventListenerManagerHashClearEntry,
+ EventListenerManagerHashInitEntry};
+
+ sEventListenerManagersHash =
+ new PLDHashTable(&hash_table_ops, sizeof(EventListenerManagerMapEntry));
+
+ RegisterStrongMemoryReporter(new DOMEventListenerManagersHashReporter());
+ }
+
+ sBlockedScriptRunners = new AutoTArray<nsCOMPtr<nsIRunnable>, 8>;
+
+#ifndef RELEASE_OR_BETA
+ sBypassCSSOMOriginCheck = getenv("MOZ_BYPASS_CSSOM_ORIGIN_CHECK");
+#endif
+
+ Element::InitCCCallbacks();
+
+ Unused << nsRFPService::GetOrCreate();
+
+ if (XRE_IsParentProcess()) {
+ AsyncPrecreateStringBundles();
+ }
+
+ RefPtr<UserInteractionObserver> uio = new UserInteractionObserver();
+ uio->Init();
+ uio.forget(&sUserInteractionObserver);
+
+ for (const auto& pref : kRfpPrefs) {
+ Preferences::RegisterCallback(RecomputeResistFingerprintingAllDocs, pref);
+ }
+
+ sInitialized = true;
+
+ return NS_OK;
+}
+
+bool nsContentUtils::InitJSBytecodeMimeType() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sJSScriptBytecodeMimeType);
+ MOZ_ASSERT(!sJSModuleBytecodeMimeType);
+
+ JS::BuildIdCharVector jsBuildId;
+ if (!JS::GetScriptTranscodingBuildId(&jsBuildId)) {
+ return false;
+ }
+
+ nsDependentCSubstring jsBuildIdStr(jsBuildId.begin(), jsBuildId.length());
+ sJSScriptBytecodeMimeType =
+ new nsCString("javascript/moz-script-bytecode-"_ns + jsBuildIdStr);
+ sJSModuleBytecodeMimeType =
+ new nsCString("javascript/moz-module-bytecode-"_ns + jsBuildIdStr);
+ return true;
+}
+
+void nsContentUtils::GetShiftText(nsAString& text) {
+ if (!sShiftText) InitializeModifierStrings();
+ text.Assign(*sShiftText);
+}
+
+void nsContentUtils::GetControlText(nsAString& text) {
+ if (!sControlText) InitializeModifierStrings();
+ text.Assign(*sControlText);
+}
+
+void nsContentUtils::GetMetaText(nsAString& text) {
+ if (!sMetaText) InitializeModifierStrings();
+ text.Assign(*sMetaText);
+}
+
+void nsContentUtils::GetOSText(nsAString& text) {
+ if (!sOSText) {
+ InitializeModifierStrings();
+ }
+ text.Assign(*sOSText);
+}
+
+void nsContentUtils::GetAltText(nsAString& text) {
+ if (!sAltText) InitializeModifierStrings();
+ text.Assign(*sAltText);
+}
+
+void nsContentUtils::GetModifierSeparatorText(nsAString& text) {
+ if (!sModifierSeparator) InitializeModifierStrings();
+ text.Assign(*sModifierSeparator);
+}
+
+void nsContentUtils::InitializeModifierStrings() {
+ // load the display strings for the keyboard accelerators
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ nsCOMPtr<nsIStringBundle> bundle;
+ DebugOnly<nsresult> rv = NS_OK;
+ if (bundleService) {
+ rv = bundleService->CreateBundle(
+ "chrome://global-platform/locale/platformKeys.properties",
+ getter_AddRefs(bundle));
+ }
+
+ NS_ASSERTION(
+ NS_SUCCEEDED(rv) && bundle,
+ "chrome://global/locale/platformKeys.properties could not be loaded");
+ nsAutoString shiftModifier;
+ nsAutoString metaModifier;
+ nsAutoString osModifier;
+ nsAutoString altModifier;
+ nsAutoString controlModifier;
+ nsAutoString modifierSeparator;
+ if (bundle) {
+ // macs use symbols for each modifier key, so fetch each from the bundle,
+ // which also covers i18n
+ bundle->GetStringFromName("VK_SHIFT", shiftModifier);
+ bundle->GetStringFromName("VK_META", metaModifier);
+ bundle->GetStringFromName("VK_WIN", osModifier);
+ bundle->GetStringFromName("VK_ALT", altModifier);
+ bundle->GetStringFromName("VK_CONTROL", controlModifier);
+ bundle->GetStringFromName("MODIFIER_SEPARATOR", modifierSeparator);
+ }
+ // if any of these don't exist, we get an empty string
+ sShiftText = new nsString(shiftModifier);
+ sMetaText = new nsString(metaModifier);
+ sOSText = new nsString(osModifier);
+ sAltText = new nsString(altModifier);
+ sControlText = new nsString(controlModifier);
+ sModifierSeparator = new nsString(modifierSeparator);
+}
+
+mozilla::EventClassID nsContentUtils::GetEventClassIDFromMessage(
+ EventMessage aEventMessage) {
+ switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+ case message_: \
+ return struct_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid event message?");
+ return eBasicEventClass;
+ }
+}
+
+bool nsContentUtils::IsExternalProtocol(nsIURI* aURI) {
+ bool doesNotReturnData = false;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &doesNotReturnData);
+ return NS_SUCCEEDED(rv) && doesNotReturnData;
+}
+
+static nsAtom* GetEventTypeFromMessage(EventMessage aEventMessage) {
+ switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+ case message_: \
+ return nsGkAtoms::on##name_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+ default:
+ return nullptr;
+ }
+}
+
+// Because of SVG/SMIL we have several atoms mapped to the same
+// id, but we can rely on MESSAGE_TO_EVENT to map id to only one atom.
+static bool ShouldAddEventToStringEventTable(const EventNameMapping& aMapping) {
+ MOZ_ASSERT(aMapping.mAtom);
+ return GetEventTypeFromMessage(aMapping.mMessage) == aMapping.mAtom;
+}
+
+bool nsContentUtils::InitializeEventTable() {
+ NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
+ NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
+
+ static const EventNameMapping eventArray[] = {
+#define EVENT(name_, _message, _type, _class) \
+ {nsGkAtoms::on##name_, _type, _message, _class, false},
+#define WINDOW_ONLY_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
+#define NON_IDL_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef WINDOW_ONLY_EVENT
+#undef NON_IDL_EVENT
+#undef EVENT
+ {nullptr}};
+
+ sAtomEventTable = new nsTHashMap<nsRefPtrHashKey<nsAtom>, EventNameMapping>(
+ ArrayLength(eventArray));
+ sStringEventTable = new nsTHashMap<nsStringHashKey, EventNameMapping>(
+ ArrayLength(eventArray));
+ sUserDefinedEvents = new nsTArray<RefPtr<nsAtom>>(64);
+
+ // Subtract one from the length because of the trailing null
+ for (uint32_t i = 0; i < ArrayLength(eventArray) - 1; ++i) {
+ MOZ_ASSERT(!sAtomEventTable->Contains(eventArray[i].mAtom),
+ "Double-defining event name; fix your EventNameList.h");
+ sAtomEventTable->InsertOrUpdate(eventArray[i].mAtom, eventArray[i]);
+ if (ShouldAddEventToStringEventTable(eventArray[i])) {
+ sStringEventTable->InsertOrUpdate(
+ Substring(nsDependentAtomString(eventArray[i].mAtom), 2),
+ eventArray[i]);
+ }
+ }
+
+ return true;
+}
+
+void nsContentUtils::InitializeTouchEventTable() {
+ static bool sEventTableInitialized = false;
+ if (!sEventTableInitialized && sAtomEventTable && sStringEventTable) {
+ sEventTableInitialized = true;
+ static const EventNameMapping touchEventArray[] = {
+#define EVENT(name_, _message, _type, _class)
+#define TOUCH_EVENT(name_, _message, _type, _class) \
+ {nsGkAtoms::on##name_, _type, _message, _class},
+#include "mozilla/EventNameList.h"
+#undef TOUCH_EVENT
+#undef EVENT
+ {nullptr}};
+ // Subtract one from the length because of the trailing null
+ for (uint32_t i = 0; i < ArrayLength(touchEventArray) - 1; ++i) {
+ sAtomEventTable->InsertOrUpdate(touchEventArray[i].mAtom,
+ touchEventArray[i]);
+ sStringEventTable->InsertOrUpdate(
+ Substring(nsDependentAtomString(touchEventArray[i].mAtom), 2),
+ touchEventArray[i]);
+ }
+ }
+}
+
+static bool Is8bit(const nsAString& aString) {
+ static const char16_t EIGHT_BIT = char16_t(~0x00FF);
+
+ for (nsAString::const_char_iterator start = aString.BeginReading(),
+ end = aString.EndReading();
+ start != end; ++start) {
+ if (*start & EIGHT_BIT) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult nsContentUtils::Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String) {
+ if (!Is8bit(aBinaryData)) {
+ aAsciiBase64String.Truncate();
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ return Base64Encode(aBinaryData, aAsciiBase64String);
+}
+
+nsresult nsContentUtils::Atob(const nsAString& aAsciiBase64String,
+ nsAString& aBinaryData) {
+ if (!Is8bit(aAsciiBase64String)) {
+ aBinaryData.Truncate();
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ const char16_t* start = aAsciiBase64String.BeginReading();
+ const char16_t* cur = start;
+ const char16_t* end = aAsciiBase64String.EndReading();
+ bool hasWhitespace = false;
+
+ while (cur < end) {
+ if (nsContentUtils::IsHTMLWhitespace(*cur)) {
+ hasWhitespace = true;
+ break;
+ }
+ cur++;
+ }
+
+ nsresult rv;
+
+ if (hasWhitespace) {
+ nsString trimmedString;
+
+ if (!trimmedString.SetCapacity(aAsciiBase64String.Length(), fallible)) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ trimmedString.Append(start, cur - start);
+
+ while (cur < end) {
+ if (!nsContentUtils::IsHTMLWhitespace(*cur)) {
+ trimmedString.Append(*cur);
+ }
+ cur++;
+ }
+ rv = Base64Decode(trimmedString, aBinaryData);
+ } else {
+ rv = Base64Decode(aAsciiBase64String, aBinaryData);
+ }
+
+ if (NS_FAILED(rv) && rv == NS_ERROR_INVALID_ARG) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+ return rv;
+}
+
+bool nsContentUtils::IsAutocompleteEnabled(
+ mozilla::dom::HTMLInputElement* aInput) {
+ MOZ_ASSERT(aInput, "aInput should not be null!");
+
+ nsAutoString autocomplete;
+ aInput->GetAutocomplete(autocomplete);
+
+ if (autocomplete.IsEmpty()) {
+ auto* form = aInput->GetForm();
+ if (!form) {
+ return true;
+ }
+
+ form->GetAutocomplete(autocomplete);
+ }
+
+ return !autocomplete.EqualsLiteral("off");
+}
+
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, nsAString& aResult,
+ AutocompleteAttrState aCachedState) {
+ if (!aAttr ||
+ aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+ return aCachedState;
+ }
+
+ if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
+ uint32_t atomCount = aAttr->GetAtomCount();
+ for (uint32_t i = 0; i < atomCount; i++) {
+ if (i != 0) {
+ aResult.Append(' ');
+ }
+ aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+ }
+ nsContentUtils::ASCIIToLower(aResult);
+ return aCachedState;
+ }
+
+ aResult.Truncate();
+
+ mozilla::dom::AutocompleteInfo info;
+ AutocompleteAttrState state =
+ InternalSerializeAutocompleteAttribute(aAttr, info);
+ if (state == eAutocompleteAttrState_Valid) {
+ // Concatenate the info fields.
+ aResult = info.mSection;
+
+ if (!info.mAddressType.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mAddressType;
+ }
+
+ if (!info.mContactType.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mContactType;
+ }
+
+ if (!info.mFieldName.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mFieldName;
+ }
+ }
+
+ return state;
+}
+
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, mozilla::dom::AutocompleteInfo& aInfo,
+ AutocompleteAttrState aCachedState, bool aGrantAllValidValue) {
+ if (!aAttr ||
+ aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+ return aCachedState;
+ }
+
+ return InternalSerializeAutocompleteAttribute(aAttr, aInfo,
+ aGrantAllValidValue);
+}
+
+/**
+ * Helper to validate the @autocomplete tokens.
+ *
+ * @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
+ */
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::InternalSerializeAutocompleteAttribute(
+ const nsAttrValue* aAttrVal, mozilla::dom::AutocompleteInfo& aInfo,
+ bool aGrantAllValidValue) {
+ // No autocomplete attribute so we are done
+ if (!aAttrVal) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ uint32_t numTokens = aAttrVal->GetAtomCount();
+ if (!numTokens) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ uint32_t index = numTokens - 1;
+ nsString tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ AutocompleteCategory category;
+ nsAttrValue enumValue;
+
+ bool unsupported = false;
+ if (!aGrantAllValidValue) {
+ unsupported = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteUnsupportedFieldNameTable, false);
+ if (unsupported) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ }
+
+ nsAutoString str;
+ bool result =
+ enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
+ if (result) {
+ // Off/Automatic/Normal categories.
+ if (enumValue.Equals(u"off"_ns, eIgnoreCase) ||
+ enumValue.Equals(u"on"_ns, eIgnoreCase)) {
+ if (numTokens > 1) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ enumValue.ToString(str);
+ ASCIIToLower(str);
+ aInfo.mFieldName.Assign(str);
+ aInfo.mCanAutomaticallyPersist =
+ !enumValue.Equals(u"off"_ns, eIgnoreCase);
+ return eAutocompleteAttrState_Valid;
+ }
+
+ // Only allow on/off if form autofill @autocomplete values aren't enabled
+ // and it doesn't grant all valid values.
+ if (!StaticPrefs::dom_forms_autocomplete_formautofill() &&
+ !aGrantAllValidValue) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ // Normal category
+ if (numTokens > 3) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ category = eAutocompleteCategory_NORMAL;
+ } else { // Check if the last token is of the contact category instead.
+ // Only allow on/off if form autofill @autocomplete values aren't enabled
+ // and it doesn't grant all valid values.
+ if (!StaticPrefs::dom_forms_autocomplete_formautofill() &&
+ !aGrantAllValidValue) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ result = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteContactFieldNameTable, false);
+ if (!result || numTokens > 4) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ category = eAutocompleteCategory_CONTACT;
+ }
+
+ enumValue.ToString(str);
+ ASCIIToLower(str);
+ aInfo.mFieldName.Assign(str);
+
+ aInfo.mCanAutomaticallyPersist = !enumValue.ParseEnumValue(
+ tokenString, kAutocompleteNoPersistFieldNameTable, false);
+
+ // We are done if this was the only token.
+ if (numTokens == 1) {
+ return eAutocompleteAttrState_Valid;
+ }
+
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+
+ if (category == eAutocompleteCategory_CONTACT) {
+ if (!aGrantAllValidValue) {
+ unsupported = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteUnsupportedContactFieldHintTable, false);
+ if (unsupported) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ }
+
+ nsAttrValue contactFieldHint;
+ result = contactFieldHint.ParseEnumValue(
+ tokenString, kAutocompleteContactFieldHintTable, false);
+ if (result) {
+ nsAutoString contactFieldHintString;
+ contactFieldHint.ToString(contactFieldHintString);
+ ASCIIToLower(contactFieldHintString);
+ aInfo.mContactType.Assign(contactFieldHintString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ }
+ }
+
+ // Check for billing/shipping tokens
+ nsAttrValue fieldHint;
+ if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable,
+ false)) {
+ nsString fieldHintString;
+ fieldHint.ToString(fieldHintString);
+ ASCIIToLower(fieldHintString);
+ aInfo.mAddressType.Assign(fieldHintString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ }
+
+ // Check for section-* token
+ const nsDependentSubstring& section = Substring(tokenString, 0, 8);
+ if (section.LowerCaseEqualsASCII("section-")) {
+ ASCIIToLower(tokenString);
+ aInfo.mSection.Assign(tokenString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ }
+
+ // Clear the fields as the autocomplete attribute is invalid.
+ aInfo.mSection.Truncate();
+ aInfo.mAddressType.Truncate();
+ aInfo.mContactType.Truncate();
+ aInfo.mFieldName.Truncate();
+
+ return eAutocompleteAttrState_Invalid;
+}
+
+// Parse an integer according to HTML spec
+template <class CharT>
+int32_t nsContentUtils::ParseHTMLIntegerImpl(
+ const CharT* aStart, const CharT* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ int result = eParseHTMLInteger_NoFlags;
+
+ const CharT* iter = aStart;
+
+ while (iter != aEnd && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ if (iter == aEnd) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return 0;
+ }
+
+ int sign = 1;
+ if (*iter == CharT('-')) {
+ sign = -1;
+ result |= eParseHTMLInteger_Negative;
+ ++iter;
+ } else if (*iter == CharT('+')) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ bool foundValue = false;
+ CheckedInt32 value = 0;
+
+ // Check for leading zeros first.
+ uint64_t leadingZeros = 0;
+ while (iter != aEnd) {
+ if (*iter != CharT('0')) {
+ break;
+ }
+
+ ++leadingZeros;
+ foundValue = true;
+ ++iter;
+ }
+
+ while (iter != aEnd) {
+ if (*iter >= CharT('0') && *iter <= CharT('9')) {
+ value = (value * 10) + (*iter - CharT('0')) * sign;
+ ++iter;
+ if (!value.isValid()) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow;
+ break;
+ }
+ foundValue = true;
+ } else {
+ break;
+ }
+ }
+
+ if (!foundValue) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
+ }
+
+ if (value.isValid() &&
+ ((leadingZeros > 1 || (leadingZeros == 1 && !(value == 0))) ||
+ (sign == -1 && value == 0))) {
+ result |= eParseHTMLInteger_NonStandard;
+ }
+
+ if (iter != aEnd) {
+ result |= eParseHTMLInteger_DidNotConsumeAllInput;
+ }
+
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return value.isValid() ? value.value() : 0;
+}
+
+// Parse an integer according to HTML spec
+int32_t nsContentUtils::ParseHTMLInteger(const char16_t* aStart,
+ const char16_t* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLIntegerImpl(aStart, aEnd, aResult);
+}
+
+int32_t nsContentUtils::ParseHTMLInteger(const char* aStart, const char* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLIntegerImpl(aStart, aEnd, aResult);
+}
+
+#define SKIP_WHITESPACE(iter, end_iter, end_res) \
+ while ((iter) != (end_iter) && nsCRT::IsAsciiSpace(*(iter))) { \
+ ++(iter); \
+ } \
+ if ((iter) == (end_iter)) { \
+ return (end_res); \
+ }
+
+#define SKIP_ATTR_NAME(iter, end_iter) \
+ while ((iter) != (end_iter) && !nsCRT::IsAsciiSpace(*(iter)) && \
+ *(iter) != '=') { \
+ ++(iter); \
+ }
+
+bool nsContentUtils::GetPseudoAttributeValue(const nsString& aSource,
+ nsAtom* aName, nsAString& aValue) {
+ aValue.Truncate();
+
+ const char16_t* start = aSource.get();
+ const char16_t* end = start + aSource.Length();
+ const char16_t* iter;
+
+ while (start != end) {
+ SKIP_WHITESPACE(start, end, false)
+ iter = start;
+ SKIP_ATTR_NAME(iter, end)
+
+ if (start == iter) {
+ return false;
+ }
+
+ // Remember the attr name.
+ const nsDependentSubstring& attrName = Substring(start, iter);
+
+ // Now check whether this is a valid name="value" pair.
+ start = iter;
+ SKIP_WHITESPACE(start, end, false)
+ if (*start != '=') {
+ // No '=', so this is not a name="value" pair. We don't know
+ // what it is, and we have no way to handle it.
+ return false;
+ }
+
+ // Have to skip the value.
+ ++start;
+ SKIP_WHITESPACE(start, end, false)
+ char16_t q = *start;
+ if (q != kQuote && q != kApostrophe) {
+ // Not a valid quoted value, so bail.
+ return false;
+ }
+
+ ++start; // Point to the first char of the value.
+ iter = start;
+
+ while (iter != end && *iter != q) {
+ ++iter;
+ }
+
+ if (iter == end) {
+ // Oops, unterminated quoted string.
+ return false;
+ }
+
+ // At this point attrName holds the name of the "attribute" and
+ // the value is between start and iter.
+
+ if (aName->Equals(attrName)) {
+ // We'll accumulate as many characters as possible (until we hit either
+ // the end of the string or the beginning of an entity). Chunks will be
+ // delimited by start and chunkEnd.
+ const char16_t* chunkEnd = start;
+ while (chunkEnd != iter) {
+ if (*chunkEnd == kLessThan) {
+ aValue.Truncate();
+
+ return false;
+ }
+
+ if (*chunkEnd == kAmpersand) {
+ aValue.Append(start, chunkEnd - start);
+
+ const char16_t* afterEntity = nullptr;
+ char16_t result[2];
+ uint32_t count = MOZ_XMLTranslateEntity(
+ reinterpret_cast<const char*>(chunkEnd),
+ reinterpret_cast<const char*>(iter),
+ reinterpret_cast<const char**>(&afterEntity), result);
+ if (count == 0) {
+ aValue.Truncate();
+
+ return false;
+ }
+
+ aValue.Append(result, count);
+
+ // Advance to after the entity and begin a new chunk.
+ start = chunkEnd = afterEntity;
+ } else {
+ ++chunkEnd;
+ }
+ }
+
+ // Append remainder.
+ aValue.Append(start, iter - start);
+
+ return true;
+ }
+
+ // Resume scanning after the end of the attribute value (past the quote
+ // char).
+ start = iter + 1;
+ }
+
+ return false;
+}
+
+bool nsContentUtils::IsJavaScriptLanguage(const nsString& aName) {
+ // Create MIME type as "text/" + given input
+ nsAutoString mimeType(u"text/");
+ mimeType.Append(aName);
+
+ return IsJavascriptMIMEType(mimeType);
+}
+
+void nsContentUtils::SplitMimeType(const nsAString& aValue, nsString& aType,
+ nsString& aParams) {
+ aType.Truncate();
+ aParams.Truncate();
+ int32_t semiIndex = aValue.FindChar(char16_t(';'));
+ if (-1 != semiIndex) {
+ aType = Substring(aValue, 0, semiIndex);
+ aParams =
+ Substring(aValue, semiIndex + 1, aValue.Length() - (semiIndex + 1));
+ aParams.StripWhitespace();
+ } else {
+ aType = aValue;
+ }
+ aType.StripWhitespace();
+}
+
+/**
+ * A helper function that parses a sandbox attribute (of an <iframe> or a CSP
+ * directive) and converts it to the set of flags used internally.
+ *
+ * @param aSandboxAttr the sandbox attribute
+ * @return the set of flags (SANDBOXED_NONE if aSandboxAttr is
+ * null)
+ */
+uint32_t nsContentUtils::ParseSandboxAttributeToFlags(
+ const nsAttrValue* aSandboxAttr) {
+ if (!aSandboxAttr) {
+ return SANDBOXED_NONE;
+ }
+
+ uint32_t out = SANDBOX_ALL_FLAGS;
+
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (aSandboxAttr->Contains(nsGkAtoms::atom, eIgnoreCase)) { \
+ out &= ~(flags); \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+
+ return out;
+}
+
+/**
+ * A helper function that checks if a string matches a valid sandbox flag.
+ *
+ * @param aFlag the potential sandbox flag.
+ * @return true if the flag is a sandbox flag.
+ */
+bool nsContentUtils::IsValidSandboxFlag(const nsAString& aFlag) {
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (EqualsIgnoreASCIICase(nsDependentAtomString(nsGkAtoms::atom), aFlag)) { \
+ return true; \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+ return false;
+}
+
+/**
+ * A helper function that returns a string attribute corresponding to the
+ * sandbox flags.
+ *
+ * @param aFlags the sandbox flags
+ * @param aString the attribute corresponding to the flags (null if aFlags
+ * is zero)
+ */
+void nsContentUtils::SandboxFlagsToString(uint32_t aFlags, nsAString& aString) {
+ if (!aFlags) {
+ SetDOMStringToNull(aString);
+ return;
+ }
+
+ aString.Truncate();
+
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (!(aFlags & (flags))) { \
+ if (!aString.IsEmpty()) { \
+ aString.AppendLiteral(u" "); \
+ } \
+ aString.Append(nsDependentAtomString(nsGkAtoms::atom)); \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+}
+
+nsIBidiKeyboard* nsContentUtils::GetBidiKeyboard() {
+ if (!sBidiKeyboard) {
+ sBidiKeyboard = nsIWidget::CreateBidiKeyboard();
+ }
+ return sBidiKeyboard;
+}
+
+/**
+ * This is used to determine whether a character is in one of the classes
+ * which CSS says should be part of the first-letter. Currently, that is
+ * all punctuation classes (P*). Note that this is a change from CSS2
+ * which excluded Pc and Pd.
+ *
+ * https://www.w3.org/TR/css-pseudo-4/#first-letter-pseudo
+ * "Punctuation (i.e, characters that belong to the Punctuation (P*) Unicode
+ * general category [UAX44]) [...]"
+ */
+
+// static
+bool nsContentUtils::IsFirstLetterPunctuation(uint32_t aChar) {
+ switch (mozilla::unicode::GetGeneralCategory(aChar)) {
+ case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
+ case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */
+ case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
+ case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
+ case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
+ case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
+ case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */
+ return true;
+ default:
+ return false;
+ }
+}
+
+// static
+bool nsContentUtils::IsAlphanumeric(uint32_t aChar) {
+ nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar);
+
+ return (cat == nsUGenCategory::kLetter || cat == nsUGenCategory::kNumber);
+}
+
+// static
+bool nsContentUtils::IsAlphanumericOrSymbol(uint32_t aChar) {
+ nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar);
+
+ return cat == nsUGenCategory::kLetter || cat == nsUGenCategory::kNumber ||
+ cat == nsUGenCategory::kSymbol;
+}
+
+/* static */
+bool nsContentUtils::IsHTMLWhitespace(char16_t aChar) {
+ return aChar == char16_t(0x0009) || aChar == char16_t(0x000A) ||
+ aChar == char16_t(0x000C) || aChar == char16_t(0x000D) ||
+ aChar == char16_t(0x0020);
+}
+
+/* static */
+bool nsContentUtils::IsHTMLWhitespaceOrNBSP(char16_t aChar) {
+ return IsHTMLWhitespace(aChar) || aChar == char16_t(0xA0);
+}
+
+/* static */
+bool nsContentUtils::IsHTMLBlockLevelElement(nsIContent* aContent) {
+ return aContent->IsAnyOfHTMLElements(
+ nsGkAtoms::address, nsGkAtoms::article, nsGkAtoms::aside,
+ nsGkAtoms::blockquote, nsGkAtoms::center, nsGkAtoms::dir, nsGkAtoms::div,
+ nsGkAtoms::dl, // XXX why not dt and dd?
+ nsGkAtoms::fieldset,
+ nsGkAtoms::figure, // XXX shouldn't figcaption be on this list
+ nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1, nsGkAtoms::h2,
+ nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6,
+ nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr, nsGkAtoms::li,
+ nsGkAtoms::listing, nsGkAtoms::menu, nsGkAtoms::nav, nsGkAtoms::ol,
+ nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::section, nsGkAtoms::table,
+ nsGkAtoms::ul, nsGkAtoms::xmp);
+}
+
+/* static */
+bool nsContentUtils::ParseIntMarginValue(const nsAString& aString,
+ nsIntMargin& result) {
+ nsAutoString marginStr(aString);
+ marginStr.CompressWhitespace(true, true);
+ if (marginStr.IsEmpty()) {
+ return false;
+ }
+
+ int32_t start = 0, end = 0;
+ for (int count = 0; count < 4; count++) {
+ if ((uint32_t)end >= marginStr.Length()) return false;
+
+ // top, right, bottom, left
+ if (count < 3)
+ end = Substring(marginStr, start).FindChar(',');
+ else
+ end = Substring(marginStr, start).Length();
+
+ if (end <= 0) return false;
+
+ nsresult ec;
+ int32_t val = nsString(Substring(marginStr, start, end)).ToInteger(&ec);
+ if (NS_FAILED(ec)) return false;
+
+ switch (count) {
+ case 0:
+ result.top = val;
+ break;
+ case 1:
+ result.right = val;
+ break;
+ case 2:
+ result.bottom = val;
+ break;
+ case 3:
+ result.left = val;
+ break;
+ }
+ start += end + 1;
+ }
+ return true;
+}
+
+// static
+int32_t nsContentUtils::ParseLegacyFontSize(const nsAString& aValue) {
+ nsAString::const_iterator iter, end;
+ aValue.BeginReading(iter);
+ aValue.EndReading(end);
+
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ ++iter;
+ }
+
+ if (iter == end) {
+ return 0;
+ }
+
+ bool relative = false;
+ bool negate = false;
+ if (*iter == char16_t('-')) {
+ relative = true;
+ negate = true;
+ ++iter;
+ } else if (*iter == char16_t('+')) {
+ relative = true;
+ ++iter;
+ }
+
+ if (iter == end || *iter < char16_t('0') || *iter > char16_t('9')) {
+ return 0;
+ }
+
+ // We don't have to worry about overflow, since we can bail out as soon as
+ // we're bigger than 7.
+ int32_t value = 0;
+ while (iter != end && *iter >= char16_t('0') && *iter <= char16_t('9')) {
+ value = 10 * value + (*iter - char16_t('0'));
+ if (value >= 7) {
+ break;
+ }
+ ++iter;
+ }
+
+ if (relative) {
+ if (negate) {
+ value = 3 - value;
+ } else {
+ value = 3 + value;
+ }
+ }
+
+ return clamped(value, 1, 7);
+}
+
+/* static */
+void nsContentUtils::GetOfflineAppManifest(Document* aDocument, nsIURI** aURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aDocument);
+ *aURI = nullptr;
+
+ if (aDocument->GetController().isSome()) {
+ return;
+ }
+
+ Element* docElement = aDocument->GetRootElement();
+ if (!docElement) {
+ return;
+ }
+
+ nsAutoString manifestSpec;
+ docElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
+
+ // Manifest URIs can't have fragment identifiers.
+ if (manifestSpec.IsEmpty() || manifestSpec.Contains('#')) {
+ return;
+ }
+
+ nsContentUtils::NewURIWithDocumentCharset(aURI, manifestSpec, aDocument,
+ aDocument->GetDocBaseURI());
+}
+
+/* static */
+bool nsContentUtils::OfflineAppAllowed(nsIURI* aURI) { return false; }
+
+/* static */
+bool nsContentUtils::OfflineAppAllowed(nsIPrincipal* aPrincipal) {
+ return false;
+}
+// Static
+bool nsContentUtils::IsErrorPage(nsIURI* aURI) {
+ if (!aURI) {
+ return false;
+ }
+
+ if (!aURI->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString name;
+ nsresult rv = NS_GetAboutModuleName(aURI, name);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return name.EqualsLiteral("certerror") || name.EqualsLiteral("neterror") ||
+ name.EqualsLiteral("blocked");
+}
+
+// static
+void nsContentUtils::Shutdown() {
+ sInitialized = false;
+
+ nsHTMLTags::ReleaseTable();
+
+ NS_IF_RELEASE(sContentPolicyService);
+ sTriedToGetContentPolicy = false;
+ for (StaticRefPtr<nsIStringBundle>& bundle : sStringBundles) {
+ bundle = nullptr;
+ }
+
+ NS_IF_RELEASE(sStringBundleService);
+ NS_IF_RELEASE(sConsoleService);
+ NS_IF_RELEASE(sXPConnect);
+ NS_IF_RELEASE(sSecurityManager);
+ NS_IF_RELEASE(sSystemPrincipal);
+ NS_IF_RELEASE(sNullSubjectPrincipal);
+
+ sBidiKeyboard = nullptr;
+
+ delete sAtomEventTable;
+ sAtomEventTable = nullptr;
+ delete sStringEventTable;
+ sStringEventTable = nullptr;
+ delete sUserDefinedEvents;
+ sUserDefinedEvents = nullptr;
+
+ if (sEventListenerManagersHash) {
+ NS_ASSERTION(sEventListenerManagersHash->EntryCount() == 0,
+ "Event listener manager hash not empty at shutdown!");
+
+ // See comment above.
+
+ // However, we have to handle this table differently. If it still
+ // has entries, we want to leak it too, so that we can keep it alive
+ // in case any elements are destroyed. Because if they are, we need
+ // their event listener managers to be destroyed too, or otherwise
+ // it could leave dangling references in DOMClassInfo's preserved
+ // wrapper table.
+
+ if (sEventListenerManagersHash->EntryCount() == 0) {
+ delete sEventListenerManagersHash;
+ sEventListenerManagersHash = nullptr;
+ }
+ }
+
+ if (sDOMArenaHashtable) {
+ MOZ_ASSERT(sDOMArenaHashtable->Count() == 0);
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ delete sDOMArenaHashtable;
+ sDOMArenaHashtable = nullptr;
+ }
+
+ NS_ASSERTION(!sBlockedScriptRunners || sBlockedScriptRunners->Length() == 0,
+ "How'd this happen?");
+ delete sBlockedScriptRunners;
+ sBlockedScriptRunners = nullptr;
+
+ delete sShiftText;
+ sShiftText = nullptr;
+ delete sControlText;
+ sControlText = nullptr;
+ delete sMetaText;
+ sMetaText = nullptr;
+ delete sOSText;
+ sOSText = nullptr;
+ delete sAltText;
+ sAltText = nullptr;
+ delete sModifierSeparator;
+ sModifierSeparator = nullptr;
+
+ delete sJSScriptBytecodeMimeType;
+ sJSScriptBytecodeMimeType = nullptr;
+
+ delete sJSModuleBytecodeMimeType;
+ sJSModuleBytecodeMimeType = nullptr;
+
+ NS_IF_RELEASE(sSameOriginChecker);
+
+ if (sUserInteractionObserver) {
+ sUserInteractionObserver->Shutdown();
+ NS_RELEASE(sUserInteractionObserver);
+ }
+
+ for (const auto& pref : kRfpPrefs) {
+ Preferences::UnregisterCallback(RecomputeResistFingerprintingAllDocs, pref);
+ }
+
+ TextControlState::Shutdown();
+ nsMappedAttributes::Shutdown();
+}
+
+/**
+ * Checks whether two nodes come from the same origin. aTrustedNode is
+ * considered 'safe' in that a user can operate on it.
+ */
+// static
+nsresult nsContentUtils::CheckSameOrigin(const nsINode* aTrustedNode,
+ const nsINode* unTrustedNode) {
+ MOZ_ASSERT(aTrustedNode);
+ MOZ_ASSERT(unTrustedNode);
+
+ /*
+ * Get hold of each node's principal
+ */
+
+ nsIPrincipal* trustedPrincipal = aTrustedNode->NodePrincipal();
+ nsIPrincipal* unTrustedPrincipal = unTrustedNode->NodePrincipal();
+
+ if (trustedPrincipal == unTrustedPrincipal) {
+ return NS_OK;
+ }
+
+ bool equal;
+ // XXXbz should we actually have a Subsumes() check here instead? Or perhaps
+ // a separate method for that, with callers using one or the other?
+ if (NS_FAILED(trustedPrincipal->Equals(unTrustedPrincipal, &equal)) ||
+ !equal) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ return NS_OK;
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
+ nsIPrincipal* aPrincipal) {
+ bool subsumes;
+ nsresult rv = aSubjectPrincipal->Subsumes(aPrincipal, &subsumes);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (subsumes) {
+ return true;
+ }
+
+ // The subject doesn't subsume aPrincipal. Allow access only if the subject
+ // is chrome.
+ return IsCallerChrome();
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(const nsINode* aNode) {
+ nsIPrincipal* subject = SubjectPrincipal();
+ if (subject->IsSystemPrincipal()) {
+ return true;
+ }
+
+ if (aNode->ChromeOnlyAccess()) {
+ return false;
+ }
+
+ return CanCallerAccess(subject, aNode->NodePrincipal());
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(nsPIDOMWindowInner* aWindow) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObject = do_QueryInterface(aWindow);
+ NS_ENSURE_TRUE(scriptObject, false);
+
+ return CanCallerAccess(SubjectPrincipal(), scriptObject->GetPrincipal());
+}
+
+// static
+bool nsContentUtils::PrincipalHasPermission(nsIPrincipal& aPrincipal,
+ const nsAtom* aPerm) {
+ // Chrome gets access by default.
+ if (aPrincipal.IsSystemPrincipal()) {
+ return true;
+ }
+
+ // Otherwise, only allow if caller is an addon with the permission.
+ return BasePrincipal::Cast(aPrincipal).AddonHasPermission(aPerm);
+}
+
+// static
+bool nsContentUtils::CallerHasPermission(JSContext* aCx, const nsAtom* aPerm) {
+ return PrincipalHasPermission(*SubjectPrincipal(aCx), aPerm);
+}
+
+// static
+nsIPrincipal* nsContentUtils::GetAttrTriggeringPrincipal(
+ nsIContent* aContent, const nsAString& aAttrValue,
+ nsIPrincipal* aSubjectPrincipal) {
+ nsIPrincipal* contentPrin = aContent ? aContent->NodePrincipal() : nullptr;
+
+ // If the subject principal is the same as the content principal, or no
+ // explicit subject principal was provided, we don't need to do any further
+ // checks. Just return the content principal.
+ if (contentPrin == aSubjectPrincipal || !aSubjectPrincipal) {
+ return contentPrin;
+ }
+
+ // Only use the subject principal if the URL string we are going to end up
+ // fetching is under the control of that principal, which is never the case
+ // for relative URLs.
+ if (aAttrValue.IsEmpty() ||
+ !IsAbsoluteURL(NS_ConvertUTF16toUTF8(aAttrValue))) {
+ return contentPrin;
+ }
+
+ // Only use the subject principal as the attr triggering principal if it
+ // should override the CSP of the node's principal.
+ if (BasePrincipal::Cast(aSubjectPrincipal)->OverridesCSP(contentPrin)) {
+ return aSubjectPrincipal;
+ }
+
+ return contentPrin;
+}
+
+// static
+bool nsContentUtils::IsAbsoluteURL(const nsACString& aURL) {
+ nsAutoCString scheme;
+ if (NS_FAILED(net_ExtractURLScheme(aURL, scheme))) {
+ // If we can't extract a scheme, it's not an absolute URL.
+ return false;
+ }
+
+ // If it parses as an absolute StandardURL, it's definitely an absolute URL,
+ // so no need to check with the IO service.
+ if (net_IsAbsoluteURL(aURL)) {
+ return true;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(&rv);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t flags;
+ if (NS_SUCCEEDED(io->GetProtocolFlags(scheme.get(), &flags))) {
+ return flags & nsIProtocolHandler::URI_NORELATIVE;
+ }
+
+ return false;
+}
+
+// static
+bool nsContentUtils::InProlog(nsINode* aNode) {
+ MOZ_ASSERT(aNode, "missing node to nsContentUtils::InProlog");
+
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent || !parent->IsDocument()) {
+ return false;
+ }
+
+ const Document* doc = parent->AsDocument();
+ const nsIContent* root = doc->GetRootElement();
+ if (!root) {
+ return true;
+ }
+ const Maybe<uint32_t> indexOfNode = doc->ComputeIndexOf(aNode);
+ const Maybe<uint32_t> indexOfRoot = doc->ComputeIndexOf(root);
+ if (MOZ_LIKELY(indexOfNode.isSome() && indexOfRoot.isSome())) {
+ return *indexOfNode < *indexOfRoot;
+ }
+ // XXX Keep the odd traditional behavior for now.
+ return indexOfNode.isNothing() && indexOfRoot.isSome();
+}
+
+bool nsContentUtils::IsCallerChrome() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return SubjectPrincipal() == sSystemPrincipal;
+}
+
+#ifdef FUZZING
+bool nsContentUtils::IsFuzzingEnabled() {
+ return StaticPrefs::fuzzing_enabled();
+}
+#endif
+
+/* static */
+bool nsContentUtils::IsCallerChromeOrElementTransformGettersEnabled(
+ JSContext* aCx, JSObject*) {
+ return ThreadsafeIsSystemCaller(aCx) ||
+ StaticPrefs::dom_element_transform_getters_enabled();
+}
+
+// Older Should RFP Functions ----------------------------------
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(
+ RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ return nsRFPService::IsRFPEnabledFor(aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget) {
+ if (!aGlobalObject) {
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+ return aGlobalObject->ShouldResistFingerprinting(aTarget);
+}
+
+// Newer Should RFP Functions ----------------------------------
+// Utilities ---------------------------------------------------
+
+inline void LogDomainAndPrefList(const char* urlType,
+ const char* exemptedDomainsPrefName,
+ nsAutoCString& url, bool isExemptDomain) {
+ nsAutoCString list;
+ Preferences::GetCString(exemptedDomainsPrefName, list);
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("%s \"%s\" is %s the exempt list \"%s\"", urlType,
+ PromiseFlatCString(url).get(), isExemptDomain ? "in" : "NOT in",
+ PromiseFlatCString(list).get()));
+}
+
+inline already_AddRefed<nsICookieJarSettings> GetCookieJarSettings(
+ nsILoadInfo* aLoadInfo) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The TRRLoadInfo in particular does not implement this method
+ // In that instance. We will return false and let other code decide if
+ // we shouldRFP for this connection
+ return nullptr;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called CookieJarSettingsSaysShouldResistFingerprinting but the "
+ "loadinfo's CookieJarSettings couldn't be retrieved"));
+ return nullptr;
+ }
+
+ MOZ_ASSERT(cookieJarSettings);
+ return cookieJarSettings.forget();
+}
+
+bool ETPSaysShouldNotResistFingerprinting(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo) {
+ // A positive return from this function should always be obeyed.
+ // A negative return means we should keep checking things.
+
+ // We do not want this check to apply to RFP, only to FPP
+ // There is one problematic combination of prefs; however:
+ // If RFP is enabled in PBMode only and FPP is enabled globally
+ // (so, in non-PBM mode) - we need to know if we're in PBMode or not.
+ // But that's kind of expensive and we'd like to avoid it if we
+ // don't have to, so special-case that scenario
+ if (StaticPrefs::privacy_fingerprintingProtection_DoNotUseDirectly() &&
+ !StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() &&
+ StaticPrefs::privacy_resistFingerprinting_pbmode_DoNotUseDirectly()) {
+ if (NS_UsePrivateBrowsing(aChannel)) {
+ // In PBM (where RFP is enabled) do not exempt based on the ETP toggle
+ return false;
+ }
+ } else if (StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() ||
+ StaticPrefs::
+ privacy_resistFingerprinting_pbmode_DoNotUseDirectly()) {
+ // In RFP, never use the ETP toggle to exempt.
+ // We can safely return false here even if we are not in PBM mode
+ // and RFP_pbmode is enabled because we will later see that and
+ // return false from the ShouldRFP function entirely.
+ return false;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ GetCookieJarSettings(aLoadInfo);
+ if (!cookieJarSettings) {
+ return false;
+ }
+
+ return ContentBlockingAllowList::Check(cookieJarSettings);
+}
+
+inline bool CookieJarSettingsSaysShouldResistFingerprinting(
+ nsILoadInfo* aLoadInfo) {
+ // A positive return from this function should always be obeyed.
+ // A negative return means we should keep checking things.
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ GetCookieJarSettings(aLoadInfo);
+ if (!cookieJarSettings) {
+ return false;
+ }
+ return cookieJarSettings->GetShouldResistFingerprinting();
+}
+
+inline bool SchemeSaysShouldNotResistFingerprinting(nsIURI* aURI) {
+ return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource") ||
+ aURI->SchemeIs("view-source") || aURI->SchemeIs("moz-extension") ||
+ (aURI->SchemeIs("about") && !NS_IsContentAccessibleAboutURI(aURI));
+}
+
+inline bool SchemeSaysShouldNotResistFingerprinting(nsIPrincipal* aPrincipal) {
+ if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") ||
+ aPrincipal->SchemeIs("view-source") ||
+ aPrincipal->SchemeIs("moz-extension")) {
+ return true;
+ }
+
+ if (!aPrincipal->SchemeIs("about")) {
+ return false;
+ }
+
+ bool isContentAccessibleAboutURI;
+ Unused << aPrincipal->IsContentAccessibleAboutURI(
+ &isContentAccessibleAboutURI);
+ return !isContentAccessibleAboutURI;
+}
+
+const char* kExemptedDomainsPrefName =
+ "privacy.resistFingerprinting.exemptedDomains";
+
+inline bool PartionKeyIsAlsoExempted(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // If we've gotten here we have (probably) passed the CookieJarSettings
+ // check that would tell us that if we _are_ a subdocument, then we are on
+ // an exempted top-level domain and we should see if we ourselves are
+ // exempted. But we may have gotten here because we directly called the
+ // _dangerous function and we haven't done that check, but we _were_
+ // instatiated from a state where we could have been partitioned.
+ // So perform this last-ditch check for that scenario.
+ // We arbitrarily use https as the scheme, but it doesn't matter.
+ nsresult rv = NS_ERROR_NOT_INITIALIZED;
+ nsCOMPtr<nsIURI> uri;
+ if (StaticPrefs::privacy_firstparty_isolate() &&
+ !aOriginAttributes.mFirstPartyDomain.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + aOriginAttributes.mFirstPartyDomain);
+ } else if (!aOriginAttributes.mPartitionKey.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + aOriginAttributes.mPartitionKey);
+ }
+
+ if (!NS_FAILED(rv)) {
+ bool isExemptPartitionKey =
+ nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString url;
+ uri->GetHost(url);
+ LogDomainAndPrefList("Partition Key", kExemptedDomainsPrefName, url,
+ isExemptPartitionKey);
+ }
+ return isExemptPartitionKey;
+ }
+ return true;
+}
+
+// Functions ---------------------------------------------------
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(
+ const char* aJustification, RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ // See comment in header file for information about usage
+ return ShouldResistFingerprinting(aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(
+ CallerType aCallerType, nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ if (aCallerType == CallerType::System) {
+ return false;
+ }
+ return ShouldResistFingerprinting(aGlobalObject, aTarget);
+}
+
+bool nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell,
+ RFPTarget aTarget) {
+ if (!aDocShell) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIDocShell*) "
+ "with NULL docshell"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+ Document* doc = aDocShell->GetDocument();
+ if (!doc) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIDocShell*) "
+ "with NULL doc"));
+ return ShouldResistFingerprinting(aTarget);
+ }
+ return doc->ShouldResistFingerprinting(aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(
+ nsIChannel* aChannel, RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ if (!aChannel) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIChannel* "
+ "aChannel) with NULL channel"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (!loadInfo) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIChannel* "
+ "aChannel) but the channel's loadinfo was NULL"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ if (!ShouldResistFingerprinting("Positive return check", aTarget)) {
+ return false;
+ }
+
+ if (ETPSaysShouldNotResistFingerprinting(aChannel, loadInfo)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIChannel*)"
+ " ETPSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ if (CookieJarSettingsSaysShouldResistFingerprinting(loadInfo)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIChannel*)"
+ " CookieJarSettingsSaysShouldResistFingerprinting said true"));
+ return true;
+ }
+
+ // Document types have no loading principal. Subdocument types do have a
+ // loading principal, but it is the loading principal of the parent
+ // document; not the subdocument.
+ auto contentType = loadInfo->GetExternalContentPolicyType();
+ // Case 1: Document or Subdocument load
+ if (contentType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ MOZ_ASSERT(
+ NS_SUCCEEDED(rv),
+ "Failed to get URI in "
+ "nsContentUtils::ShouldResistFingerprinting(nsIChannel* aChannel)");
+ // this check is to ensure that we do not crash in non-debug builds.
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+#if 0
+ if (loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ nsAutoCString channelSpec;
+ channelURI->GetSpec(channelSpec);
+
+ if (!loadInfo->GetLoadingPrincipal()) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Sub Document Type. FinalChannelURI is %s, Loading Principal is NULL\n",
+ channelSpec.get()));
+
+ } else {
+ nsAutoCString loadingPrincipalSpec;
+ loadInfo->GetLoadingPrincipal()->GetOrigin(loadingPrincipalSpec);
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Sub Document Type. FinalChannelURI is %s, Loading Principal Origin is %s\n",
+ channelSpec.get(), loadingPrincipalSpec.get()));
+ }
+ }
+
+#endif
+
+ return ShouldResistFingerprinting_dangerous(
+ channelURI, loadInfo->GetOriginAttributes(), "Internal Call", aTarget);
+ }
+
+ // Case 2: Subresource Load
+ // Because this code is only used for subresource loads, this
+ // will check the parent's principal
+ nsIPrincipal* principal = loadInfo->GetLoadingPrincipal();
+
+ MOZ_ASSERT_IF(principal && !principal->IsSystemPrincipal() &&
+ !principal->GetIsAddonOrExpandedAddonPrincipal(),
+ BasePrincipal::Cast(principal)->OriginAttributesRef() ==
+ loadInfo->GetOriginAttributes());
+ return ShouldResistFingerprinting_dangerous(principal, "Internal Call",
+ aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting_dangerous(
+ nsIURI* aURI, const mozilla::OriginAttributes& aOriginAttributes,
+ const char* aJustification, RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ if (!ShouldResistFingerprinting("Positive return check", aTarget)) {
+ return false;
+ }
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting_dangerous(nsIURI*,"
+ " OriginAttributes) and the URI is %s",
+ aURI->GetSpecOrDefault().get()));
+
+ if (!StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() &&
+ !StaticPrefs::privacy_fingerprintingProtection_DoNotUseDirectly()) {
+ // If neither of the 'regular' RFP prefs are set, then one (or both)
+ // of the PBM-Only prefs are set (or we would have failed the
+ // Positive return check.) Therefore, if we are not in PBM, return false
+ if (aOriginAttributes.mPrivateBrowsingId == 0) {
+ return false;
+ }
+ }
+
+ // Exclude internal schemes and web extensions
+ if (SchemeSaysShouldNotResistFingerprinting(aURI)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIURI*)"
+ " SchemeSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ bool isExemptDomain = false;
+ nsAutoCString list;
+ Preferences::GetCString(kExemptedDomainsPrefName, list);
+ ToLowerCase(list);
+ isExemptDomain = IsURIInList(aURI, list);
+
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString url;
+ aURI->GetHost(url);
+ LogDomainAndPrefList("URI", kExemptedDomainsPrefName, url, isExemptDomain);
+ }
+
+ if (isExemptDomain) {
+ isExemptDomain &= PartionKeyIsAlsoExempted(aOriginAttributes);
+ }
+
+ return !isExemptDomain;
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting_dangerous(
+ nsIPrincipal* aPrincipal, const char* aJustification,
+ RFPTarget aTarget /* = RFPTarget::Unknown */) {
+ if (!aPrincipal) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsILoadInfo* "
+ "aChannel) but the loadinfo's loadingprincipal was NULL"));
+ return ShouldResistFingerprinting("Null object", aTarget);
+ }
+
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ if (!ShouldResistFingerprinting("Positive return check", aTarget)) {
+ return false;
+ }
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ return false;
+ }
+
+ auto originAttributes =
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+ if (!StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() &&
+ !StaticPrefs::privacy_fingerprintingProtection_DoNotUseDirectly()) {
+ // If neither of the 'regular' RFP prefs are set, then one (or both)
+ // of the PBM-Only prefs are set (or we would have failed the
+ // Positive return check.) Therefore, if we are not in PBM, return false
+ if (originAttributes.mPrivateBrowsingId == 0) {
+ return false;
+ }
+ }
+
+ // Exclude internal schemes and web extensions
+ if (SchemeSaysShouldNotResistFingerprinting(aPrincipal)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIPrincipal*)"
+ " SchemeSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ // Web extension principals are also excluded
+ if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting_dangerous(nsIPrincipal*)"
+ " and AddonPolicy said false"));
+ return false;
+ }
+
+ bool isExemptDomain = false;
+ aPrincipal->IsURIInPrefList(kExemptedDomainsPrefName, &isExemptDomain);
+
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString origin;
+ aPrincipal->GetAsciiOrigin(origin);
+ LogDomainAndPrefList("URI", kExemptedDomainsPrefName, origin,
+ isExemptDomain);
+ }
+
+ if (isExemptDomain) {
+ isExemptDomain &= PartionKeyIsAlsoExempted(originAttributes);
+ }
+
+ // If we've gotten here we have (probably) passed the CookieJarSettings
+ // check that would tell us that if we _are_ a subdocument, then we are on
+ // an exempted top-level domain and we should see if we ourselves are
+ // exempted. But we may have gotten here because we directly called the
+ // _dangerous function and we haven't done that check, but we _were_
+ // instatiated from a state where we could have been partitioned.
+ // So perform this last-ditch check for that scenario.
+ // We arbitrarily use https as the scheme, but it doesn't matter.
+ nsCOMPtr<nsIURI> uri;
+ if (isExemptDomain && StaticPrefs::privacy_firstparty_isolate() &&
+ !originAttributes.mFirstPartyDomain.IsEmpty()) {
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + originAttributes.mFirstPartyDomain);
+ if (!NS_FAILED(rv)) {
+ isExemptDomain =
+ nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
+ }
+ } else if (isExemptDomain && !originAttributes.mPartitionKey.IsEmpty()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + originAttributes.mPartitionKey);
+ if (!NS_FAILED(rv)) {
+ isExemptDomain =
+ nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
+ }
+ }
+
+ return !isExemptDomain;
+}
+
+// --------------------------------------------------------------------
+
+/* static */
+void nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+ int32_t aChromeWidth, int32_t aChromeHeight, int32_t aScreenWidth,
+ int32_t aScreenHeight, int32_t aInputWidth, int32_t aInputHeight,
+ bool aSetOuterWidth, bool aSetOuterHeight, int32_t* aOutputWidth,
+ int32_t* aOutputHeight) {
+ MOZ_ASSERT(aOutputWidth);
+ MOZ_ASSERT(aOutputHeight);
+
+ int32_t availContentWidth = 0;
+ int32_t availContentHeight = 0;
+
+ availContentWidth = std::min(StaticPrefs::privacy_window_maxInnerWidth(),
+ aScreenWidth - aChromeWidth);
+#ifdef MOZ_WIDGET_GTK
+ // In the GTK window, it will not report outside system decorations
+ // when we get available window size, see Bug 581863. So, we leave a
+ // 40 pixels space for them when calculating the available content
+ // height. It is not necessary for the width since the content width
+ // is usually pretty much the same as the chrome width.
+ availContentHeight = std::min(StaticPrefs::privacy_window_maxInnerHeight(),
+ (-40 + aScreenHeight) - aChromeHeight);
+#else
+ availContentHeight = std::min(StaticPrefs::privacy_window_maxInnerHeight(),
+ aScreenHeight - aChromeHeight);
+#endif
+
+ // Ideally, we'd like to round window size to 1000x1000, but the
+ // screen space could be too small to accommodate this size in some
+ // cases. If it happens, we would round the window size to the nearest
+ // 200x100.
+ availContentWidth = availContentWidth - (availContentWidth % 200);
+ availContentHeight = availContentHeight - (availContentHeight % 100);
+
+ // If aIsOuter is true, we are setting the outer window. So we
+ // have to consider the chrome UI.
+ int32_t chromeOffsetWidth = aSetOuterWidth ? aChromeWidth : 0;
+ int32_t chromeOffsetHeight = aSetOuterHeight ? aChromeHeight : 0;
+ int32_t resultWidth = 0, resultHeight = 0;
+
+ // if the original size is greater than the maximum available size, we set
+ // it to the maximum size. And if the original value is less than the
+ // minimum rounded size, we set it to the minimum 200x100.
+ if (aInputWidth > (availContentWidth + chromeOffsetWidth)) {
+ resultWidth = availContentWidth + chromeOffsetWidth;
+ } else if (aInputWidth < (200 + chromeOffsetWidth)) {
+ resultWidth = 200 + chromeOffsetWidth;
+ } else {
+ // Otherwise, we round the window to the nearest upper rounded 200x100.
+ resultWidth = NSToIntCeil((aInputWidth - chromeOffsetWidth) / 200.0) * 200 +
+ chromeOffsetWidth;
+ }
+
+ if (aInputHeight > (availContentHeight + chromeOffsetHeight)) {
+ resultHeight = availContentHeight + chromeOffsetHeight;
+ } else if (aInputHeight < (100 + chromeOffsetHeight)) {
+ resultHeight = 100 + chromeOffsetHeight;
+ } else {
+ resultHeight =
+ NSToIntCeil((aInputHeight - chromeOffsetHeight) / 100.0) * 100 +
+ chromeOffsetHeight;
+ }
+
+ *aOutputWidth = resultWidth;
+ *aOutputHeight = resultHeight;
+}
+
+bool nsContentUtils::ThreadsafeIsCallerChrome() {
+ return NS_IsMainThread() ? IsCallerChrome()
+ : IsCurrentThreadRunningChromeWorker();
+}
+
+bool nsContentUtils::IsCallerUAWidget() {
+ JSContext* cx = GetCurrentJSContext();
+ if (!cx) {
+ return false;
+ }
+
+ JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
+ if (!realm) {
+ return false;
+ }
+
+ return xpc::IsUAWidgetScope(realm);
+}
+
+bool nsContentUtils::IsSystemCaller(JSContext* aCx) {
+ // Note that SubjectPrincipal() assumes we are in a compartment here.
+ return SubjectPrincipal(aCx) == sSystemPrincipal;
+}
+
+bool nsContentUtils::ThreadsafeIsSystemCaller(JSContext* aCx) {
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx->Context() == aCx);
+
+ return ccjscx->IsSystemCaller();
+}
+
+// static
+bool nsContentUtils::LookupBindingMember(
+ JSContext* aCx, nsIContent* aContent, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
+ return true;
+}
+
+nsINode* nsContentUtils::GetNearestInProcessCrossDocParentNode(
+ nsINode* aChild) {
+ if (aChild->IsDocument()) {
+ for (BrowsingContext* bc = aChild->AsDocument()->GetBrowsingContext(); bc;
+ bc = bc->GetParent()) {
+ if (bc->GetEmbedderElement()) {
+ return bc->GetEmbedderElement();
+ }
+ }
+ return nullptr;
+ }
+
+ nsINode* parent = aChild->GetParentNode();
+ if (parent && parent->IsContent() && aChild->IsContent()) {
+ parent = aChild->AsContent()->GetFlattenedTreeParent();
+ }
+
+ return parent;
+}
+
+bool nsContentUtils::ContentIsHostIncludingDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) return true;
+ if (aPossibleDescendant->IsDocumentFragment()) {
+ aPossibleDescendant =
+ aPossibleDescendant->AsDocumentFragment()->GetHost();
+ } else {
+ aPossibleDescendant = aPossibleDescendant->GetParentNode();
+ }
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant,
+ nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+
+ aPossibleDescendant =
+ GetNearestInProcessCrossDocParentNode(aPossibleDescendant);
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsFlattenedTreeDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+ aPossibleDescendant = aPossibleDescendant->GetFlattenedTreeParentNode();
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+ aPossibleDescendant =
+ aPossibleDescendant->GetFlattenedTreeParentNodeForStyle();
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+nsINode* nsContentUtils::Retarget(nsINode* aTargetA, nsINode* aTargetB) {
+ while (true && aTargetA) {
+ // If A's root is not a shadow root...
+ nsINode* root = aTargetA->SubtreeRoot();
+ if (!root->IsShadowRoot()) {
+ // ...then return A.
+ return aTargetA;
+ }
+
+ // or A's root is a shadow-including inclusive ancestor of B...
+ if (aTargetB->IsShadowIncludingInclusiveDescendantOf(root)) {
+ // ...then return A.
+ return aTargetA;
+ }
+
+ aTargetA = ShadowRoot::FromNode(root)->GetHost();
+ }
+
+ return nullptr;
+}
+
+// static
+Element* nsContentUtils::GetAnElementForTiming(Element* aTarget,
+ const Document* aDocument,
+ nsIGlobalObject* aGlobal) {
+ if (!aTarget->IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ if (!aDocument) {
+ nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aGlobal);
+ if (!inner) {
+ return nullptr;
+ }
+ aDocument = inner->GetExtantDoc();
+ }
+
+ MOZ_ASSERT(aDocument);
+
+ if (aTarget->GetUncomposedDocOrConnectedShadowRoot() != aDocument ||
+ !aDocument->IsCurrentActiveDocument()) {
+ return nullptr;
+ }
+
+ return aTarget;
+}
+
+// static
+nsresult nsContentUtils::GetInclusiveAncestors(nsINode* aNode,
+ nsTArray<nsINode*>& aArray) {
+ while (aNode) {
+ aArray.AppendElement(aNode);
+ aNode = aNode->GetParentNode();
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsContentUtils::GetInclusiveAncestorsAndOffsets(
+ nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>* aAncestorNodes,
+ nsTArray<Maybe<uint32_t>>* aAncestorOffsets) {
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ if (!aNode->IsContent()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsIContent* content = aNode->AsContent();
+
+ if (!aAncestorNodes->IsEmpty()) {
+ NS_WARNING("aAncestorNodes is not empty");
+ aAncestorNodes->Clear();
+ }
+
+ if (!aAncestorOffsets->IsEmpty()) {
+ NS_WARNING("aAncestorOffsets is not empty");
+ aAncestorOffsets->Clear();
+ }
+
+ // insert the node itself
+ aAncestorNodes->AppendElement(content);
+ aAncestorOffsets->AppendElement(Some(aOffset));
+
+ // insert all the ancestors
+ nsIContent* child = content;
+ nsIContent* parent = child->GetParent();
+ while (parent) {
+ aAncestorNodes->AppendElement(parent);
+ aAncestorOffsets->AppendElement(parent->ComputeIndexOf(child));
+ child = parent;
+ parent = parent->GetParent();
+ }
+
+ return NS_OK;
+}
+
+template <typename Node, typename GetParentFunc>
+static Node* GetCommonAncestorInternal(Node* aNode1, Node* aNode2,
+ GetParentFunc aGetParentFunc) {
+ MOZ_ASSERT(aNode1 != aNode2);
+
+ // Build the chain of parents
+ AutoTArray<Node*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(aNode1);
+ aNode1 = aGetParentFunc(aNode1);
+ } while (aNode1);
+ do {
+ parents2.AppendElement(aNode2);
+ aNode2 = aGetParentFunc(aNode2);
+ } while (aNode2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ Node* parent = nullptr;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ Node* child1 = parents1.ElementAt(--pos1);
+ Node* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ return parent;
+}
+
+/* static */
+nsINode* nsContentUtils::GetCommonAncestorHelper(nsINode* aNode1,
+ nsINode* aNode2) {
+ return GetCommonAncestorInternal(
+ aNode1, aNode2, [](nsINode* aNode) { return aNode->GetParentNode(); });
+}
+
+/* static */
+nsIContent* nsContentUtils::GetCommonFlattenedTreeAncestorHelper(
+ nsIContent* aContent1, nsIContent* aContent2) {
+ return GetCommonAncestorInternal(
+ aContent1, aContent2,
+ [](nsIContent* aContent) { return aContent->GetFlattenedTreeParent(); });
+}
+
+/* static */
+Element* nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
+ Element* aElement1, Element* aElement2) {
+ return GetCommonAncestorInternal(aElement1, aElement2, [](Element* aElement) {
+ return aElement->GetFlattenedTreeParentElementForStyle();
+ });
+}
+
+/* static */
+bool nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2,
+ Maybe<uint32_t>* aNode1Index,
+ Maybe<uint32_t>* aNode2Index) {
+ // Note, CompareDocumentPosition takes the latter params in different order.
+ return (aNode2->CompareDocumentPosition(*aNode1, aNode2Index, aNode1Index) &
+ (Node_Binding::DOCUMENT_POSITION_PRECEDING |
+ Node_Binding::DOCUMENT_POSITION_DISCONNECTED)) ==
+ Node_Binding::DOCUMENT_POSITION_PRECEDING;
+}
+
+/* static */
+Maybe<int32_t> nsContentUtils::ComparePoints(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, ComparePointsCache* aParent1Cache) {
+ bool disconnected{false};
+
+ const int32_t order = ComparePoints_Deprecated(
+ aParent1, aOffset1, aParent2, aOffset2, &disconnected, aParent1Cache);
+ if (disconnected) {
+ return Nothing();
+ }
+
+ return Some(order);
+}
+
+/* static */
+int32_t nsContentUtils::ComparePoints_Deprecated(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, bool* aDisconnected, ComparePointsCache* aParent1Cache) {
+ if (aParent1 == aParent2) {
+ return aOffset1 < aOffset2 ? -1 : aOffset1 > aOffset2 ? 1 : 0;
+ }
+
+ AutoTArray<const nsINode*, 32> parents1, parents2;
+ const nsINode* node1 = aParent1;
+ const nsINode* node2 = aParent2;
+ do {
+ parents1.AppendElement(node1);
+ node1 = node1->GetParentOrShadowHostNode();
+ } while (node1);
+ do {
+ parents2.AppendElement(node2);
+ node2 = node2->GetParentOrShadowHostNode();
+ } while (node2);
+
+ uint32_t pos1 = parents1.Length() - 1;
+ uint32_t pos2 = parents2.Length() - 1;
+
+ bool disconnected = parents1.ElementAt(pos1) != parents2.ElementAt(pos2);
+ if (aDisconnected) {
+ *aDisconnected = disconnected;
+ }
+ if (disconnected) {
+ NS_ASSERTION(aDisconnected, "unexpected disconnected nodes");
+ return 1;
+ }
+
+ // Find where the parent chains differ
+ const nsINode* parent = parents1.ElementAt(pos1);
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ const nsINode* child1 = parents1.ElementAt(--pos1);
+ const nsINode* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ if (MOZ_UNLIKELY(child1->IsShadowRoot())) {
+ // Shadow roots come before light DOM per
+ // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
+ MOZ_ASSERT(!child2->IsShadowRoot(), "Two shadow roots?");
+ return -1;
+ }
+ if (MOZ_UNLIKELY(child2->IsShadowRoot())) {
+ return 1;
+ }
+ const Maybe<uint32_t> child1Index =
+ aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1)
+ : parent->ComputeIndexOf(child1);
+ const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2);
+ if (MOZ_LIKELY(child1Index.isSome() && child2Index.isSome())) {
+ return *child1Index < *child2Index ? -1 : 1;
+ }
+ // XXX Keep the odd traditional behavior for now.
+ return child1Index.isNothing() && child2Index.isSome() ? -1 : 1;
+ }
+ parent = child1;
+ }
+
+ // The parent chains never differed, so one of the nodes is an ancestor of
+ // the other
+
+ NS_ASSERTION(!pos1 || !pos2,
+ "should have run out of parent chain for one of the nodes");
+
+ if (!pos1) {
+ const nsINode* child2 = parents2.ElementAt(--pos2);
+ const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2);
+ if (MOZ_UNLIKELY(NS_WARN_IF(child2Index.isNothing()))) {
+ return 1;
+ }
+ return aOffset1 <= *child2Index ? -1 : 1;
+ }
+
+ const nsINode* child1 = parents1.ElementAt(--pos1);
+ const Maybe<uint32_t> child1Index =
+ aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1)
+ : parent->ComputeIndexOf(child1);
+ if (MOZ_UNLIKELY(NS_WARN_IF(child1Index.isNothing()))) {
+ return -1;
+ }
+ return *child1Index < aOffset2 ? -1 : 1;
+}
+
+// static
+nsINode* nsContentUtils::GetCommonAncestorUnderInteractiveContent(
+ nsINode* aNode1, nsINode* aNode2) {
+ if (!aNode1 || !aNode2) {
+ return nullptr;
+ }
+
+ if (aNode1 == aNode2) {
+ return aNode1;
+ }
+
+ // Build the chain of parents
+ AutoTArray<nsINode*, 30> parents1;
+ do {
+ parents1.AppendElement(aNode1);
+ if (aNode1->IsElement() &&
+ aNode1->AsElement()->IsInteractiveHTMLContent()) {
+ break;
+ }
+ aNode1 = aNode1->GetFlattenedTreeParentNode();
+ } while (aNode1);
+
+ AutoTArray<nsINode*, 30> parents2;
+ do {
+ parents2.AppendElement(aNode2);
+ if (aNode2->IsElement() &&
+ aNode2->AsElement()->IsInteractiveHTMLContent()) {
+ break;
+ }
+ aNode2 = aNode2->GetFlattenedTreeParentNode();
+ } while (aNode2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ nsINode* parent = nullptr;
+ for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+ nsINode* child1 = parents1.ElementAt(--pos1);
+ nsINode* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ return parent;
+}
+
+/* static */
+BrowserParent* nsContentUtils::GetCommonBrowserParentAncestor(
+ BrowserParent* aBrowserParent1, BrowserParent* aBrowserParent2) {
+ return GetCommonAncestorInternal(
+ aBrowserParent1, aBrowserParent2, [](BrowserParent* aBrowserParent) {
+ return aBrowserParent->GetBrowserBridgeParent()
+ ? aBrowserParent->GetBrowserBridgeParent()->Manager()
+ : nullptr;
+ });
+}
+
+/* static */
+Element* nsContentUtils::GetTargetElement(Document* aDocument,
+ const nsAString& aAnchorName) {
+ MOZ_ASSERT(aDocument);
+
+ if (aAnchorName.IsEmpty()) {
+ return nullptr;
+ }
+ // 1. If there is an element in the document tree that has an ID equal to
+ // fragment, then return the first such element in tree order.
+ if (Element* el = aDocument->GetElementById(aAnchorName)) {
+ return el;
+ }
+
+ // 2. If there is an a element in the document tree that has a name
+ // attribute whose value is equal to fragment, then return the first such
+ // element in tree order.
+ //
+ // FIXME(emilio): Why the different code-paths for HTML and non-HTML docs?
+ if (aDocument->IsHTMLDocument()) {
+ nsCOMPtr<nsINodeList> list = aDocument->GetElementsByName(aAnchorName);
+ // Loop through the named nodes looking for the first anchor
+ uint32_t length = list->Length();
+ for (uint32_t i = 0; i < length; i++) {
+ nsIContent* node = list->Item(i);
+ if (node->IsHTMLElement(nsGkAtoms::a)) {
+ return node->AsElement();
+ }
+ }
+ } else {
+ constexpr auto nameSpace = u"http://www.w3.org/1999/xhtml"_ns;
+ // Get the list of anchor elements
+ nsCOMPtr<nsINodeList> list =
+ aDocument->GetElementsByTagNameNS(nameSpace, u"a"_ns);
+ // Loop through the anchors looking for the first one with the given name.
+ for (uint32_t i = 0; true; i++) {
+ nsIContent* node = list->Item(i);
+ if (!node) { // End of list
+ break;
+ }
+
+ // Compare the name attribute
+ if (node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ aAnchorName, eCaseMatters)) {
+ return node->AsElement();
+ }
+ }
+ }
+
+ // 3. Return null.
+ return nullptr;
+}
+
+/* static */
+template <typename FPT, typename FRT, typename SPT, typename SRT>
+Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const RangeBoundaryBase<SPT, SRT>& aSecondBoundary) {
+ if (!aFirstBoundary.IsSet() || !aSecondBoundary.IsSet()) {
+ return Nothing{};
+ }
+
+ bool disconnected{false};
+ const int32_t order =
+ ComparePoints_Deprecated(aFirstBoundary, aSecondBoundary, &disconnected);
+
+ if (disconnected) {
+ return Nothing{};
+ }
+
+ return Some(order);
+}
+
+/* static */
+template <typename FPT, typename FRT, typename SPT, typename SRT>
+int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const RangeBoundaryBase<SPT, SRT>& aSecondBoundary, bool* aDisconnected) {
+ if (NS_WARN_IF(!aFirstBoundary.IsSet()) ||
+ NS_WARN_IF(!aSecondBoundary.IsSet())) {
+ return -1;
+ }
+ // XXX Re-implement this without calling `Offset()` as far as possible,
+ // and the other overload should be an alias of this.
+ return ComparePoints_Deprecated(
+ aFirstBoundary.Container(),
+ *aFirstBoundary.Offset(
+ RangeBoundaryBase<FPT, FRT>::OffsetFilter::kValidOrInvalidOffsets),
+ aSecondBoundary.Container(),
+ *aSecondBoundary.Offset(
+ RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOrInvalidOffsets),
+ aDisconnected);
+}
+
+inline bool IsCharInSet(const char* aSet, const char16_t aChar) {
+ char16_t ch;
+ while ((ch = *aSet)) {
+ if (aChar == char16_t(ch)) {
+ return true;
+ }
+ ++aSet;
+ }
+ return false;
+}
+
+/**
+ * This method strips leading/trailing chars, in given set, from string.
+ */
+
+// static
+const nsDependentSubstring nsContentUtils::TrimCharsInSet(
+ const char* aSet, const nsAString& aValue) {
+ nsAString::const_iterator valueCurrent, valueEnd;
+
+ aValue.BeginReading(valueCurrent);
+ aValue.EndReading(valueEnd);
+
+ // Skip characters in the beginning
+ while (valueCurrent != valueEnd) {
+ if (!IsCharInSet(aSet, *valueCurrent)) {
+ break;
+ }
+ ++valueCurrent;
+ }
+
+ if (valueCurrent != valueEnd) {
+ for (;;) {
+ --valueEnd;
+ if (!IsCharInSet(aSet, *valueEnd)) {
+ break;
+ }
+ }
+ ++valueEnd; // Step beyond the last character we want in the value.
+ }
+
+ // valueEnd should point to the char after the last to copy
+ return Substring(valueCurrent, valueEnd);
+}
+
+/**
+ * This method strips leading and trailing whitespace from a string.
+ */
+
+// static
+template <bool IsWhitespace(char16_t)>
+const nsDependentSubstring nsContentUtils::TrimWhitespace(const nsAString& aStr,
+ bool aTrimTrailing) {
+ nsAString::const_iterator start, end;
+
+ aStr.BeginReading(start);
+ aStr.EndReading(end);
+
+ // Skip whitespace characters in the beginning
+ while (start != end && IsWhitespace(*start)) {
+ ++start;
+ }
+
+ if (aTrimTrailing) {
+ // Skip whitespace characters in the end.
+ while (end != start) {
+ --end;
+
+ if (!IsWhitespace(*end)) {
+ // Step back to the last non-whitespace character.
+ ++end;
+
+ break;
+ }
+ }
+ }
+
+ // Return a substring for the string w/o leading and/or trailing
+ // whitespace
+
+ return Substring(start, end);
+}
+
+// Declaring the templates we are going to use avoid linking issues without
+// inlining the method. Considering there is not so much spaces checking
+// methods we can consider this to be better than inlining.
+template const nsDependentSubstring
+nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(const nsAString&, bool);
+template const nsDependentSubstring nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespace>(const nsAString&, bool);
+template const nsDependentSubstring nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespaceOrNBSP>(const nsAString&, bool);
+
+static inline void KeyAppendSep(nsACString& aKey) {
+ if (!aKey.IsEmpty()) {
+ aKey.Append('>');
+ }
+}
+
+static inline void KeyAppendString(const nsAString& aString, nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ // Could escape separator here if collisions happen. > is not a legal char
+ // for a name or type attribute, so we should be safe avoiding that extra
+ // work.
+
+ AppendUTF16toUTF8(aString, aKey);
+}
+
+static inline void KeyAppendString(const nsACString& aString,
+ nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ // Could escape separator here if collisions happen. > is not a legal char
+ // for a name or type attribute, so we should be safe avoiding that extra
+ // work.
+
+ aKey.Append(aString);
+}
+
+static inline void KeyAppendInt(int32_t aInt, nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ aKey.AppendInt(aInt);
+}
+
+static inline bool IsAutocompleteOff(const nsIContent* aContent) {
+ return aContent->IsElement() &&
+ aContent->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::autocomplete, u"off"_ns,
+ eIgnoreCase);
+}
+
+/*static*/
+void nsContentUtils::GenerateStateKey(nsIContent* aContent, Document* aDocument,
+ nsACString& aKey) {
+ MOZ_ASSERT(aContent);
+
+ aKey.Truncate();
+
+ uint32_t partID = aDocument ? aDocument->GetPartID() : 0;
+
+ // Don't capture state for anonymous content
+ if (aContent->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+
+ if (IsAutocompleteOff(aContent)) {
+ return;
+ }
+
+ RefPtr<Document> doc = aContent->GetUncomposedDoc();
+
+ KeyAppendInt(partID, aKey); // first append a partID
+ bool generatedUniqueKey = false;
+
+ if (doc && doc->IsHTMLOrXHTML()) {
+ nsHTMLDocument* htmlDoc = doc->AsHTMLDocument();
+
+ // If we have a form control and can calculate form information, use that
+ // as the key - it is more reliable than just recording position in the
+ // DOM.
+ // XXXbz Is it, really? We have bugs on this, I think...
+ // Important to have a unique key, and tag/type/name may not be.
+ //
+ // The format of the key depends on whether the control has a form,
+ // and whether the element was parser inserted:
+ //
+ // [Has Form, Parser Inserted]:
+ // fp>type>FormNum>IndOfControlInForm>FormName>name
+ //
+ // [No Form, Parser Inserted]:
+ // dp>type>ControlNum>name
+ //
+ // [Has Form, Not Parser Inserted]:
+ // fn>type>IndOfFormInDoc>IndOfControlInForm>FormName>name
+ //
+ // [No Form, Not Parser Inserted]:
+ // dn>type>IndOfControlInDoc>name
+ //
+ // XXX We don't need to use index if name is there
+ // XXXbz We don't? Why not? I don't follow.
+ //
+ nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent));
+ if (control) {
+ // Get the control number if this was a parser inserted element from the
+ // network.
+ int32_t controlNumber =
+ control->GetParserInsertedControlNumberForStateKey();
+ bool parserInserted = controlNumber != -1;
+
+ RefPtr<nsContentList> htmlForms;
+ RefPtr<nsContentList> htmlFormControls;
+ if (!parserInserted) {
+ // Getting these lists is expensive, as we need to keep them up to date
+ // as the document loads, so we avoid it if we don't need them.
+ htmlDoc->GetFormsAndFormControls(getter_AddRefs(htmlForms),
+ getter_AddRefs(htmlFormControls));
+ }
+
+ // Append the control type
+ KeyAppendInt(int32_t(control->ControlType()), aKey);
+
+ // If in a form, add form name / index of form / index in form
+ HTMLFormElement* formElement = control->GetForm();
+ if (formElement) {
+ if (IsAutocompleteOff(formElement)) {
+ aKey.Truncate();
+ return;
+ }
+
+ // Append the form number, if this is a parser inserted control, or
+ // the index of the form in the document otherwise.
+ bool appendedForm = false;
+ if (parserInserted) {
+ MOZ_ASSERT(formElement->GetFormNumberForStateKey() != -1,
+ "when generating a state key for a parser inserted form "
+ "control we should have a parser inserted <form> element");
+ KeyAppendString("fp"_ns, aKey);
+ KeyAppendInt(formElement->GetFormNumberForStateKey(), aKey);
+ appendedForm = true;
+ } else {
+ KeyAppendString("fn"_ns, aKey);
+ int32_t index = htmlForms->IndexOf(formElement, false);
+ if (index <= -1) {
+ //
+ // XXX HACK this uses some state that was dumped into the document
+ // specifically to fix bug 138892. What we are trying to do is
+ // *guess* which form this control's state is found in, with the
+ // highly likely guess that the highest form parsed so far is the
+ // one. This code should not be on trunk, only branch.
+ //
+ index = htmlDoc->GetNumFormsSynchronous() - 1;
+ }
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ appendedForm = true;
+ }
+ }
+
+ if (appendedForm) {
+ // Append the index of the control in the form
+ int32_t index = formElement->IndexOfContent(aContent);
+
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ generatedUniqueKey = true;
+ }
+ }
+
+ // Append the form name
+ nsAutoString formName;
+ formElement->GetAttr(kNameSpaceID_None, nsGkAtoms::name, formName);
+ KeyAppendString(formName, aKey);
+ } else {
+ // Not in a form. Append the control number, if this is a parser
+ // inserted control, or the index of the control in the document
+ // otherwise.
+ if (parserInserted) {
+ KeyAppendString("dp"_ns, aKey);
+ KeyAppendInt(control->GetParserInsertedControlNumberForStateKey(),
+ aKey);
+ generatedUniqueKey = true;
+ } else {
+ KeyAppendString("dn"_ns, aKey);
+ int32_t index = htmlFormControls->IndexOf(aContent, true);
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ generatedUniqueKey = true;
+ }
+ }
+
+ // Append the control name
+ nsAutoString name;
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
+ name);
+ KeyAppendString(name, aKey);
+ }
+ }
+ }
+
+ if (!generatedUniqueKey) {
+ // Either we didn't have a form control or we aren't in an HTML document so
+ // we can't figure out form info. Append the tag name if it's an element
+ // to avoid restoring state for one type of element on another type.
+ if (aContent->IsElement()) {
+ KeyAppendString(nsDependentAtomString(aContent->NodeInfo()->NameAtom()),
+ aKey);
+ } else {
+ // Append a character that is not "d" or "f" to disambiguate from
+ // the case when we were a form control in an HTML document.
+ KeyAppendString("o"_ns, aKey);
+ }
+
+ // Now start at aContent and append the indices of it and all its ancestors
+ // in their containers. That should at least pin down its position in the
+ // DOM...
+ nsINode* parent = aContent->GetParentNode();
+ nsINode* content = aContent;
+ while (parent) {
+ KeyAppendInt(parent->ComputeIndexOf_Deprecated(content), aKey);
+ content = parent;
+ parent = content->GetParentNode();
+ }
+ }
+}
+
+// static
+nsIPrincipal* nsContentUtils::SubjectPrincipal(JSContext* aCx) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // As opposed to SubjectPrincipal(), we do in fact assume that
+ // we're in a realm here; anyone who calls this function in
+ // situations where that's not the case is doing it wrong.
+ JS::Realm* realm = js::GetContextRealm(aCx);
+ MOZ_ASSERT(realm);
+
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsIPrincipal* nsContentUtils::SubjectPrincipal() {
+ MOZ_ASSERT(IsInitialized());
+ MOZ_ASSERT(NS_IsMainThread());
+ JSContext* cx = GetCurrentJSContext();
+ if (!cx) {
+ MOZ_CRASH(
+ "Accessing the Subject Principal without an AutoJSAPI on the stack is "
+ "forbidden");
+ }
+
+ JS::Realm* realm = js::GetContextRealm(cx);
+
+ // When an AutoJSAPI is instantiated, we are in a null realm until the
+ // first JSAutoRealm, which is kind of a purgatory as far as permissions
+ // go. It would be nice to just hard-abort if somebody does a security check
+ // in this purgatory zone, but that would be too fragile, since it could be
+ // triggered by random IsCallerChrome() checks 20-levels deep.
+ //
+ // So we want to return _something_ here - and definitely not the System
+ // Principal, since that would make an AutoJSAPI a very dangerous thing to
+ // instantiate.
+ //
+ // The natural thing to return is a null principal. Ideally, we'd return a
+ // different null principal each time, to avoid any unexpected interactions
+ // when the principal accidentally gets inherited somewhere. But
+ // SubjectPrincipal doesn't return strong references, so there's no way to
+ // sanely manage the lifetime of multiple null principals.
+ //
+ // So we use a singleton null principal. To avoid it being accidentally
+ // inherited and becoming a "real" subject or object principal, we do a
+ // release-mode assert during realm creation against using this principal on
+ // an actual global.
+ if (!realm) {
+ return sNullSubjectPrincipal;
+ }
+
+ return SubjectPrincipal(cx);
+}
+
+// static
+nsIPrincipal* nsContentUtils::ObjectPrincipal(JSObject* aObj) {
+#ifdef DEBUG
+ JS::AssertObjectBelongsToCurrentThread(aObj);
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(!js::IsCrossCompartmentWrapper(aObj));
+
+ JS::Realm* realm = js::GetNonCCWObjectRealm(aObj);
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsresult nsContentUtils::NewURIWithDocumentCharset(nsIURI** aResult,
+ const nsAString& aSpec,
+ Document* aDocument,
+ nsIURI* aBaseURI) {
+ if (aDocument) {
+ return NS_NewURI(aResult, aSpec, aDocument->GetDocumentCharacterSet(),
+ aBaseURI);
+ }
+ return NS_NewURI(aResult, aSpec, nullptr, aBaseURI);
+}
+
+// static
+bool nsContentUtils::IsNameWithDash(nsAtom* aName) {
+ // A valid custom element name is a sequence of characters name which
+ // must match the PotentialCustomElementName production:
+ // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)*
+ const char16_t* name = aName->GetUTF16String();
+ uint32_t len = aName->GetLength();
+ bool hasDash = false;
+
+ if (!len || name[0] < 'a' || name[0] > 'z') {
+ return false;
+ }
+
+ uint32_t i = 1;
+ while (i < len) {
+ if (i + 1 < len && NS_IS_SURROGATE_PAIR(name[i], name[i + 1])) {
+ // Merged two 16-bit surrogate pairs into code point.
+ char32_t code = SURROGATE_TO_UCS4(name[i], name[i + 1]);
+
+ if (code < 0x10000 || code > 0xEFFFF) {
+ return false;
+ }
+
+ i += 2;
+ } else {
+ if (name[i] == '-') {
+ hasDash = true;
+ }
+
+ if (name[i] != '-' && name[i] != '.' && name[i] != '_' &&
+ name[i] != 0xB7 && (name[i] < '0' || name[i] > '9') &&
+ (name[i] < 'a' || name[i] > 'z') &&
+ (name[i] < 0xC0 || name[i] > 0xD6) &&
+ (name[i] < 0xF8 || name[i] > 0x37D) &&
+ (name[i] < 0x37F || name[i] > 0x1FFF) &&
+ (name[i] < 0x200C || name[i] > 0x200D) &&
+ (name[i] < 0x203F || name[i] > 0x2040) &&
+ (name[i] < 0x2070 || name[i] > 0x218F) &&
+ (name[i] < 0x2C00 || name[i] > 0x2FEF) &&
+ (name[i] < 0x3001 || name[i] > 0xD7FF) &&
+ (name[i] < 0xF900 || name[i] > 0xFDCF) &&
+ (name[i] < 0xFDF0 || name[i] > 0xFFFD)) {
+ return false;
+ }
+
+ i++;
+ }
+ }
+
+ return hasDash;
+}
+
+// static
+bool nsContentUtils::IsCustomElementName(nsAtom* aName, uint32_t aNameSpaceID) {
+ // Allow non-dashed names in XUL for XBL to Custom Element migrations.
+ if (aNameSpaceID == kNameSpaceID_XUL) {
+ return true;
+ }
+
+ bool hasDash = IsNameWithDash(aName);
+ if (!hasDash) {
+ return false;
+ }
+
+ // The custom element name must not be one of the following values:
+ // annotation-xml
+ // color-profile
+ // font-face
+ // font-face-src
+ // font-face-uri
+ // font-face-format
+ // font-face-name
+ // missing-glyph
+ return aName != nsGkAtoms::annotation_xml_ &&
+ aName != nsGkAtoms::colorProfile && aName != nsGkAtoms::font_face &&
+ aName != nsGkAtoms::font_face_src &&
+ aName != nsGkAtoms::font_face_uri &&
+ aName != nsGkAtoms::font_face_format &&
+ aName != nsGkAtoms::font_face_name && aName != nsGkAtoms::missingGlyph;
+}
+
+// static
+nsresult nsContentUtils::CheckQName(const nsAString& aQualifiedName,
+ bool aNamespaceAware,
+ const char16_t** aColon) {
+ const char* colon = nullptr;
+ const char16_t* begin = aQualifiedName.BeginReading();
+ const char16_t* end = aQualifiedName.EndReading();
+
+ int result = MOZ_XMLCheckQName(reinterpret_cast<const char*>(begin),
+ reinterpret_cast<const char*>(end),
+ aNamespaceAware, &colon);
+
+ if (!result) {
+ if (aColon) {
+ *aColon = reinterpret_cast<const char16_t*>(colon);
+ }
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+}
+
+// static
+nsresult nsContentUtils::SplitQName(const nsIContent* aNamespaceResolver,
+ const nsString& aQName, int32_t* aNamespace,
+ nsAtom** aLocalName) {
+ const char16_t* colon;
+ nsresult rv = nsContentUtils::CheckQName(aQName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (colon) {
+ const char16_t* end;
+ aQName.EndReading(end);
+ nsAutoString nameSpace;
+ rv = aNamespaceResolver->LookupNamespaceURIInternal(
+ Substring(aQName.get(), colon), nameSpace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aNamespace = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ nameSpace, nsContentUtils::IsChromeDoc(aNamespaceResolver->OwnerDoc()));
+ if (*aNamespace == kNameSpaceID_Unknown) return NS_ERROR_FAILURE;
+
+ *aLocalName = NS_AtomizeMainThread(Substring(colon + 1, end)).take();
+ } else {
+ *aNamespace = kNameSpaceID_None;
+ *aLocalName = NS_AtomizeMainThread(aQName).take();
+ }
+ NS_ENSURE_TRUE(aLocalName, NS_ERROR_OUT_OF_MEMORY);
+ return NS_OK;
+}
+
+// static
+nsresult nsContentUtils::GetNodeInfoFromQName(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ nsNodeInfoManager* aNodeInfoManager, uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo) {
+ const nsString& qName = PromiseFlatString(aQualifiedName);
+ const char16_t* colon;
+ nsresult rv = nsContentUtils::CheckQName(qName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t nsID;
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsID);
+ if (colon) {
+ const char16_t* end;
+ qName.EndReading(end);
+
+ RefPtr<nsAtom> prefix = NS_AtomizeMainThread(Substring(qName.get(), colon));
+
+ rv = aNodeInfoManager->GetNodeInfo(Substring(colon + 1, end), prefix, nsID,
+ aNodeType, aNodeInfo);
+ } else {
+ rv = aNodeInfoManager->GetNodeInfo(aQualifiedName, nullptr, nsID, aNodeType,
+ aNodeInfo);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsContentUtils::IsValidNodeName((*aNodeInfo)->NameAtom(),
+ (*aNodeInfo)->GetPrefixAtom(),
+ (*aNodeInfo)->NamespaceID())
+ ? NS_OK
+ : NS_ERROR_DOM_NAMESPACE_ERR;
+}
+
+// static
+void nsContentUtils::SplitExpatName(const char16_t* aExpatName,
+ nsAtom** aPrefix, nsAtom** aLocalName,
+ int32_t* aNameSpaceID) {
+ /**
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ *
+ * and we use 0xFFFF for the <separator>.
+ *
+ */
+
+ const char16_t* uriEnd = nullptr;
+ const char16_t* nameEnd = nullptr;
+ const char16_t* pos;
+ for (pos = aExpatName; *pos; ++pos) {
+ if (*pos == 0xFFFF) {
+ if (uriEnd) {
+ nameEnd = pos;
+ } else {
+ uriEnd = pos;
+ }
+ }
+ }
+
+ const char16_t* nameStart;
+ if (uriEnd) {
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ nsDependentSubstring(aExpatName, uriEnd), *aNameSpaceID);
+
+ nameStart = (uriEnd + 1);
+ if (nameEnd) {
+ const char16_t* prefixStart = nameEnd + 1;
+ *aPrefix = NS_AtomizeMainThread(Substring(prefixStart, pos)).take();
+ } else {
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ } else {
+ *aNameSpaceID = kNameSpaceID_None;
+ nameStart = aExpatName;
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ *aLocalName = NS_AtomizeMainThread(Substring(nameStart, nameEnd)).take();
+}
+
+// static
+PresShell* nsContentUtils::GetPresShellForContent(const nsIContent* aContent) {
+ Document* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ return doc->GetPresShell();
+}
+
+// static
+nsPresContext* nsContentUtils::GetContextForContent(
+ const nsIContent* aContent) {
+ PresShell* presShell = GetPresShellForContent(aContent);
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetPresContext();
+}
+
+// static
+bool nsContentUtils::CanLoadImage(nsIURI* aURI, nsINode* aNode,
+ Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aLoadingDocument, "Must have a document");
+ MOZ_ASSERT(aLoadingPrincipal, "Must have a loading principal");
+
+ nsresult rv;
+
+ auto appType = nsIDocShell::APP_TYPE_UNKNOWN;
+
+ {
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
+ aLoadingDocument->GetDocShell();
+ if (docShellTreeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShellTreeItem->GetInProcessRootTreeItem(getter_AddRefs(root));
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(root));
+
+ if (docShell) {
+ appType = docShell->GetAppType();
+ }
+ }
+ }
+
+ if (appType != nsIDocShell::APP_TYPE_EDITOR) {
+ // Editor apps get special treatment here, editors can load images
+ // from anywhere. This allows editor to insert images from file://
+ // into documents that are being edited.
+ rv = sSecurityManager->CheckLoadURIWithPrincipal(
+ aLoadingPrincipal, aURI, nsIScriptSecurityManager::ALLOW_CHROME,
+ aLoadingDocument->InnerWindowID());
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal,
+ aLoadingPrincipal, // triggering principal
+ aNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+
+ rv = NS_CheckContentLoadPolicy(aURI, secCheckLoadInfo,
+ ""_ns, // mime guess
+ &decision, GetContentPolicy());
+
+ return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(decision);
+}
+
+// static
+bool nsContentUtils::IsInPrivateBrowsing(Document* aDoc) {
+ if (!aDoc) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup();
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (loadContext) {
+ return loadContext->UsePrivateBrowsing();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = aDoc->GetChannel();
+ return channel && NS_UsePrivateBrowsing(channel);
+}
+
+// static
+bool nsContentUtils::IsInPrivateBrowsing(nsILoadGroup* aLoadGroup) {
+ if (!aLoadGroup) {
+ return false;
+ }
+ bool isPrivate = false;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ isPrivate = loadContext && loadContext->UsePrivateBrowsing();
+ }
+ return isPrivate;
+}
+
+// FIXME(emilio): This is (effectively) almost but not quite the same as
+// Document::ShouldLoadImages(), which one is right?
+bool nsContentUtils::DocumentInactiveForImageLoads(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+ if (IsChromeDoc(aDocument) || aDocument->IsResourceDoc() ||
+ aDocument->IsStaticDocument()) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(aDocument->GetScopeObject());
+ return !win || !win->GetDocShell();
+}
+
+imgLoader* nsContentUtils::GetImgLoaderForDocument(Document* aDoc) {
+ NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aDoc), nullptr);
+
+ if (!aDoc) {
+ return imgLoader::NormalLoader();
+ }
+ bool isPrivate = IsInPrivateBrowsing(aDoc);
+ return isPrivate ? imgLoader::PrivateBrowsingLoader()
+ : imgLoader::NormalLoader();
+}
+
+// static
+imgLoader* nsContentUtils::GetImgLoaderForChannel(nsIChannel* aChannel,
+ Document* aContext) {
+ NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aContext), nullptr);
+
+ if (!aChannel) {
+ return imgLoader::NormalLoader();
+ }
+ nsCOMPtr<nsILoadContext> context;
+ NS_QueryNotificationCallbacks(aChannel, context);
+ return context && context->UsePrivateBrowsing()
+ ? imgLoader::PrivateBrowsingLoader()
+ : imgLoader::NormalLoader();
+}
+
+// static
+int32_t nsContentUtils::CORSModeToLoadImageFlags(mozilla::CORSMode aMode) {
+ switch (aMode) {
+ case CORS_ANONYMOUS:
+ return imgILoader::LOAD_CORS_ANONYMOUS;
+ case CORS_USE_CREDENTIALS:
+ return imgILoader::LOAD_CORS_USE_CREDENTIALS;
+ default:
+ return 0;
+ }
+}
+
+// static
+nsresult nsContentUtils::LoadImage(
+ nsIURI* aURI, nsINode* aContext, Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
+ nsIReferrerInfo* aReferrerInfo, imgINotificationObserver* aObserver,
+ int32_t aLoadFlags, const nsAString& initiatorType,
+ imgRequestProxy** aRequest, nsContentPolicyType aContentPolicyType,
+ bool aUseUrgentStartForChannel, bool aLinkPreload,
+ uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aContext, "Must have a context");
+ MOZ_ASSERT(aLoadingDocument, "Must have a document");
+ MOZ_ASSERT(aLoadingPrincipal, "Must have a principal");
+ MOZ_ASSERT(aRequest, "Null out param");
+
+ imgLoader* imgLoader = GetImgLoaderForDocument(aLoadingDocument);
+ if (!imgLoader) {
+ // nothing we can do here
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aLoadingDocument->GetDocumentLoadGroup();
+
+ nsIURI* documentURI = aLoadingDocument->GetDocumentURI();
+
+ NS_ASSERTION(loadGroup || aLoadingDocument->IsSVGGlyphsDocument(),
+ "Could not get loadgroup; onload may fire too early");
+
+ // XXXbz using "documentURI" for the initialDocumentURI is not quite
+ // right, but the best we can do here...
+ return imgLoader->LoadImage(aURI, /* uri to load */
+ documentURI, /* initialDocumentURI */
+ aReferrerInfo, /* referrerInfo */
+ aLoadingPrincipal, /* loading principal */
+ aRequestContextID, /* request context ID */
+ loadGroup, /* loadgroup */
+ aObserver, /* imgINotificationObserver */
+ aContext, /* loading context */
+ aLoadingDocument, /* uniquification key */
+ aLoadFlags, /* load flags */
+ nullptr, /* cache key */
+ aContentPolicyType, /* content policy type */
+ initiatorType, /* the load initiator */
+ aUseUrgentStartForChannel, /* urgent-start flag */
+ aLinkPreload, /* <link preload> initiator */
+ aEarlyHintPreloaderId, aRequest);
+}
+
+// static
+already_AddRefed<imgIContainer> nsContentUtils::GetImageFromContent(
+ nsIImageLoadingContent* aContent, imgIRequest** aRequest) {
+ if (aRequest) {
+ *aRequest = nullptr;
+ }
+
+ NS_ENSURE_TRUE(aContent, nullptr);
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ aContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (!imgRequest) {
+ return nullptr;
+ }
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ imgRequest->GetImage(getter_AddRefs(imgContainer));
+
+ if (!imgContainer) {
+ return nullptr;
+ }
+
+ if (aRequest) {
+ // If the consumer wants the request, verify it has actually loaded
+ // successfully.
+ uint32_t imgStatus;
+ imgRequest->GetImageStatus(&imgStatus);
+ if (imgStatus & imgIRequest::STATUS_FRAME_COMPLETE &&
+ !(imgStatus & imgIRequest::STATUS_ERROR)) {
+ imgRequest.swap(*aRequest);
+ }
+ }
+
+ return imgContainer.forget();
+}
+
+static bool IsLinkWithURI(const nsIContent& aContent) {
+ const auto* element = Element::FromNode(aContent);
+ if (!element || !element->IsLink()) {
+ return false;
+ }
+ nsCOMPtr<nsIURI> absURI = element->GetHrefURI();
+ return !!absURI;
+}
+
+static bool HasImageRequest(nsIContent& aContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageContent(do_QueryInterface(&aContent));
+ if (!imageContent) {
+ return false;
+ }
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+
+ // XXXbz It may be draggable even if the request resulted in an error. Why?
+ // Not sure; that's what the old nsContentAreaDragDrop/nsFrame code did.
+ return !!imgRequest;
+}
+
+static Maybe<bool> DraggableOverride(const nsIContent& aContent) {
+ if (auto* el = nsGenericHTMLElement::FromNode(aContent)) {
+ if (el->Draggable()) {
+ return Some(true);
+ }
+
+ if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_false, eIgnoreCase)) {
+ return Some(false);
+ }
+ }
+ if (aContent.IsSVGElement()) {
+ return Some(false);
+ }
+ return Nothing();
+}
+
+// static
+bool nsContentUtils::ContentIsDraggable(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+
+ if (auto draggable = DraggableOverride(*aContent)) {
+ return *draggable;
+ }
+
+ // special handling for content area image and link dragging
+ return HasImageRequest(*aContent) || IsLinkWithURI(*aContent);
+}
+
+// static
+bool nsContentUtils::IsDraggableImage(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ return HasImageRequest(*aContent) &&
+ DraggableOverride(*aContent).valueOr(true);
+}
+
+// static
+bool nsContentUtils::IsDraggableLink(const nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ return IsLinkWithURI(*aContent) && DraggableOverride(*aContent).valueOr(true);
+}
+
+// static
+nsresult nsContentUtils::QNameChanged(mozilla::dom::NodeInfo* aNodeInfo,
+ nsAtom* aName,
+ mozilla::dom::NodeInfo** aResult) {
+ nsNodeInfoManager* niMgr = aNodeInfo->NodeInfoManager();
+
+ *aResult = niMgr
+ ->GetNodeInfo(aName, nullptr, aNodeInfo->NamespaceID(),
+ aNodeInfo->NodeType(), aNodeInfo->GetExtraName())
+ .take();
+ return NS_OK;
+}
+
+static bool TestSitePerm(nsIPrincipal* aPrincipal, const nsACString& aType,
+ uint32_t aPerm, bool aExactHostMatch) {
+ if (!aPrincipal) {
+ // We always deny (i.e. don't allow) the permission if we don't have a
+ // principal.
+ return aPerm != nsIPermissionManager::ALLOW_ACTION;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ components::PermissionManager::Service();
+ NS_ENSURE_TRUE(permMgr, false);
+
+ uint32_t perm;
+ nsresult rv;
+ if (aExactHostMatch) {
+ rv = permMgr->TestExactPermissionFromPrincipal(aPrincipal, aType, &perm);
+ } else {
+ rv = permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &perm);
+ }
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return perm == aPerm;
+}
+
+bool nsContentUtils::IsSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION,
+ false);
+}
+
+bool nsContentUtils::IsSitePermDeny(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION,
+ false);
+}
+
+bool nsContentUtils::IsExactSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION,
+ true);
+}
+
+bool nsContentUtils::IsExactSitePermDeny(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION,
+ true);
+}
+
+bool nsContentUtils::HasSitePerm(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ components::PermissionManager::Service();
+ NS_ENSURE_TRUE(permMgr, false);
+
+ uint32_t perm;
+ nsresult rv = permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &perm);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return perm != nsIPermissionManager::UNKNOWN_ACTION;
+}
+
+static const char* gEventNames[] = {"event"};
+static const char* gSVGEventNames[] = {"evt"};
+// for b/w compat, the first name to onerror is still 'event', even though it
+// is actually the error message
+static const char* gOnErrorNames[] = {"event", "source", "lineno", "colno",
+ "error"};
+
+// static
+void nsContentUtils::GetEventArgNames(int32_t aNameSpaceID, nsAtom* aEventName,
+ bool aIsForWindow, uint32_t* aArgCount,
+ const char*** aArgArray) {
+#define SET_EVENT_ARG_NAMES(names) \
+ *aArgCount = sizeof(names) / sizeof(names[0]); \
+ *aArgArray = names;
+
+ // JSEventHandler is what does the arg magic for onerror, and it does
+ // not seem to take the namespace into account. So we let onerror in all
+ // namespaces get the 3 arg names.
+ if (aEventName == nsGkAtoms::onerror && aIsForWindow) {
+ SET_EVENT_ARG_NAMES(gOnErrorNames);
+ } else if (aNameSpaceID == kNameSpaceID_SVG) {
+ SET_EVENT_ARG_NAMES(gSVGEventNames);
+ } else {
+ SET_EVENT_ARG_NAMES(gEventNames);
+ }
+}
+
+// Note: The list of content bundles in nsStringBundle.cpp should be updated
+// whenever entries are added or removed from this list.
+static const char* gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT] = {
+ // Must line up with the enum values in |PropertiesFile| enum.
+ "chrome://global/locale/css.properties",
+ "chrome://global/locale/xul.properties",
+ "chrome://global/locale/layout_errors.properties",
+ "chrome://global/locale/layout/HtmlForm.properties",
+ "chrome://global/locale/printing.properties",
+ "chrome://global/locale/dom/dom.properties",
+ "chrome://global/locale/layout/htmlparser.properties",
+ "chrome://global/locale/svg/svg.properties",
+ "chrome://branding/locale/brand.properties",
+ "chrome://global/locale/commonDialogs.properties",
+ "chrome://global/locale/mathml/mathml.properties",
+ "chrome://global/locale/security/security.properties",
+ "chrome://necko/locale/necko.properties",
+ "resource://gre/res/locale/layout/HtmlForm.properties",
+ "resource://gre/res/locale/dom/dom.properties"};
+
+/* static */
+nsresult nsContentUtils::EnsureStringBundle(PropertiesFile aFile) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "Should not create bundles off main thread.");
+ if (!sStringBundles[aFile]) {
+ if (!sStringBundleService) {
+ nsresult rv =
+ CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ RefPtr<nsIStringBundle> bundle;
+ MOZ_TRY(sStringBundleService->CreateBundle(gPropertiesFiles[aFile],
+ getter_AddRefs(bundle)));
+ sStringBundles[aFile] = bundle.forget();
+ }
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::AsyncPrecreateStringBundles() {
+ // We only ever want to pre-create bundles in the parent process.
+ //
+ // All nsContentUtils bundles are shared between the parent and child
+ // precesses, and the shared memory regions that back them *must* be created
+ // in the parent, and then sent to all children.
+ //
+ // If we attempt to create a bundle in the child before its memory region is
+ // available, we need to create a temporary non-shared bundle, and later
+ // replace that with the shared memory copy. So attempting to pre-load in the
+ // child is wasteful and unnecessary.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ for (uint32_t bundleIndex = 0; bundleIndex < PropertiesFile_COUNT;
+ ++bundleIndex) {
+ nsresult rv = NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction("AsyncPrecreateStringBundles",
+ [bundleIndex]() {
+ PropertiesFile file =
+ static_cast<PropertiesFile>(bundleIndex);
+ EnsureStringBundle(file);
+ nsIStringBundle* bundle = sStringBundles[file];
+ bundle->AsyncPreload();
+ }),
+ EventQueuePriority::Idle);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+}
+
+/* static */
+bool nsContentUtils::SpoofLocaleEnglish() {
+ // 0 - will prompt
+ // 1 - don't spoof
+ // 2 - spoof
+ return StaticPrefs::privacy_spoof_english() == 2;
+}
+
+static nsContentUtils::PropertiesFile GetMaybeSpoofedPropertiesFile(
+ nsContentUtils::PropertiesFile aFile, const char* aKey,
+ Document* aDocument) {
+ // When we spoof English, use en-US properties in strings that are accessible
+ // by content.
+ bool spoofLocale = nsContentUtils::SpoofLocaleEnglish() &&
+ (!aDocument || !aDocument->AllowsL10n());
+ if (spoofLocale) {
+ switch (aFile) {
+ case nsContentUtils::eFORMS_PROPERTIES:
+ return nsContentUtils::eFORMS_PROPERTIES_en_US;
+ case nsContentUtils::eDOM_PROPERTIES:
+ return nsContentUtils::eDOM_PROPERTIES_en_US;
+ default:
+ break;
+ }
+ }
+ return aFile;
+}
+
+/* static */
+nsresult nsContentUtils::GetMaybeLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ Document* aDocument,
+ nsAString& aResult) {
+ return GetLocalizedString(
+ GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ nsAString& aResult) {
+ return FormatLocalizedString(aFile, aKey, {}, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::FormatMaybeLocalizedString(
+ PropertiesFile aFile, const char* aKey, Document* aDocument,
+ const nsTArray<nsString>& aParams, nsAString& aResult) {
+ return FormatLocalizedString(
+ GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aParams,
+ aResult);
+}
+
+class FormatLocalizedStringRunnable final : public WorkerMainThreadRunnable {
+ public:
+ FormatLocalizedStringRunnable(WorkerPrivate* aWorkerPrivate,
+ nsContentUtils::PropertiesFile aFile,
+ const char* aKey,
+ const nsTArray<nsString>& aParams,
+ nsAString& aLocalizedString)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "FormatLocalizedStringRunnable"_ns),
+ mFile(aFile),
+ mKey(aKey),
+ mParams(aParams),
+ mLocalizedString(aLocalizedString) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ mResult = nsContentUtils::FormatLocalizedString(mFile, mKey, mParams,
+ mLocalizedString);
+ Unused << NS_WARN_IF(NS_FAILED(mResult));
+ return true;
+ }
+
+ nsresult GetResult() const { return mResult; }
+
+ private:
+ const nsContentUtils::PropertiesFile mFile;
+ const char* mKey;
+ const nsTArray<nsString>& mParams;
+ nsresult mResult = NS_ERROR_FAILURE;
+ nsAString& mLocalizedString;
+};
+
+/* static */
+nsresult nsContentUtils::FormatLocalizedString(
+ PropertiesFile aFile, const char* aKey, const nsTArray<nsString>& aParams,
+ nsAString& aResult) {
+ if (!NS_IsMainThread()) {
+ // nsIStringBundle is thread-safe but its creation is not, and in particular
+ // we don't create and store nsIStringBundle objects in a thread-safe way.
+ //
+ // TODO(emilio): Maybe if we already have the right bundle created we could
+ // just call into it, but we should make sure that Shutdown() doesn't get
+ // called on the main thread when that happens which is a bit tricky to
+ // prove?
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (NS_WARN_IF(!workerPrivate)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto runnable = MakeRefPtr<FormatLocalizedStringRunnable>(
+ workerPrivate, aFile, aKey, aParams, aResult);
+
+ runnable->Dispatch(Canceling, IgnoreErrors());
+ return runnable->GetResult();
+ }
+
+ MOZ_TRY(EnsureStringBundle(aFile));
+ nsIStringBundle* bundle = sStringBundles[aFile];
+ if (aParams.IsEmpty()) {
+ return bundle->GetStringFromName(aKey, aResult);
+ }
+ return bundle->FormatStringFromName(aKey, aParams, aResult);
+}
+
+/* static */
+void nsContentUtils::LogSimpleConsoleError(const nsAString& aErrorText,
+ const nsACString& aCategory,
+ bool aFromPrivateWindow,
+ bool aFromChromeContext,
+ uint32_t aErrorFlags) {
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ if (scriptError) {
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (console && NS_SUCCEEDED(scriptError->Init(
+ aErrorText, u""_ns, u""_ns, 0, 0, aErrorFlags, aCategory,
+ aFromPrivateWindow, aFromChromeContext))) {
+ console->LogMessage(scriptError);
+ }
+ }
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsole(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ const Document* aDocument, PropertiesFile aFile, const char* aMessageName,
+ const nsTArray<nsString>& aParams, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber) {
+ nsresult rv;
+ nsAutoString errorText;
+ if (!aParams.IsEmpty()) {
+ rv = FormatLocalizedString(aFile, aMessageName, aParams, errorText);
+ } else {
+ rv = GetLocalizedString(aFile, aMessageName, errorText);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ReportToConsoleNonLocalized(errorText, aErrorFlags, aCategory,
+ aDocument, aURI, aSourceLine, aLineNumber,
+ aColumnNumber);
+}
+
+/* static */
+void nsContentUtils::ReportEmptyGetElementByIdArg(const Document* aDoc) {
+ ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
+ nsContentUtils::eDOM_PROPERTIES, "EmptyGetElementByIdParam");
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsoleNonLocalized(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, const Document* aDocument, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber,
+ MissingErrorLocationMode aLocationMode) {
+ uint64_t innerWindowID = 0;
+ if (aDocument) {
+ if (!aURI) {
+ aURI = aDocument->GetDocumentURI();
+ }
+ innerWindowID = aDocument->InnerWindowID();
+ }
+
+ return ReportToConsoleByWindowID(aErrorText, aErrorFlags, aCategory,
+ innerWindowID, aURI, aSourceLine,
+ aLineNumber, aColumnNumber, aLocationMode);
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsoleByWindowID(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, uint64_t aInnerWindowID, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber,
+ MissingErrorLocationMode aLocationMode) {
+ nsresult rv;
+ if (!sConsoleService) { // only need to bother null-checking here
+ rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoString spec;
+ if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) {
+ JSContext* cx = GetCurrentJSContext();
+ if (cx) {
+ nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
+ }
+ }
+
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spec.IsEmpty()) {
+ rv = errorObject->InitWithWindowID(aErrorText,
+ spec, // file name
+ aSourceLine, aLineNumber, aColumnNumber,
+ aErrorFlags, aCategory, aInnerWindowID);
+ } else {
+ rv = errorObject->InitWithSourceURI(aErrorText, aURI, aSourceLine,
+ aLineNumber, aColumnNumber, aErrorFlags,
+ aCategory, aInnerWindowID);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return sConsoleService->LogMessage(errorObject);
+}
+
+void nsContentUtils::LogMessageToConsole(const char* aMsg) {
+ if (!sConsoleService) { // only need to bother null-checking here
+ CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
+ if (!sConsoleService) {
+ return;
+ }
+ }
+ sConsoleService->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
+}
+
+bool nsContentUtils::IsChromeDoc(const Document* aDocument) {
+ return aDocument && aDocument->NodePrincipal() == sSystemPrincipal;
+}
+
+bool nsContentUtils::IsChildOfSameType(Document* aDoc) {
+ if (BrowsingContext* bc = aDoc->GetBrowsingContext()) {
+ return bc->GetParent();
+ }
+ return false;
+}
+
+bool nsContentUtils::IsPlainTextType(const nsACString& aContentType) {
+ // NOTE: if you add a type here, add it to the CONTENTDLF_CATEGORIES
+ // define in nsContentDLF.h as well.
+ return aContentType.EqualsLiteral(TEXT_PLAIN) ||
+ aContentType.EqualsLiteral(TEXT_CSS) ||
+ aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
+ aContentType.EqualsLiteral(TEXT_VTT) ||
+ aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
+ aContentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ aContentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON);
+}
+
+bool nsContentUtils::IsUtf8OnlyPlainTextType(const nsACString& aContentType) {
+ // NOTE: This must be a subset of the list in IsPlainTextType().
+ return aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON) ||
+ aContentType.EqualsLiteral(TEXT_VTT);
+}
+
+bool nsContentUtils::IsInChromeDocshell(const Document* aDocument) {
+ return aDocument && aDocument->IsInChromeDocShell();
+}
+
+// static
+nsIContentPolicy* nsContentUtils::GetContentPolicy() {
+ if (!sTriedToGetContentPolicy) {
+ CallGetService(NS_CONTENTPOLICY_CONTRACTID, &sContentPolicyService);
+ // It's OK to not have a content policy service
+ sTriedToGetContentPolicy = true;
+ }
+
+ return sContentPolicyService;
+}
+
+// static
+bool nsContentUtils::IsEventAttributeName(nsAtom* aName, int32_t aType) {
+ const char16_t* name = aName->GetUTF16String();
+ if (name[0] != 'o' || name[1] != 'n') {
+ return false;
+ }
+
+ EventNameMapping mapping;
+ return (sAtomEventTable->Get(aName, &mapping) && mapping.mType & aType);
+}
+
+// static
+EventMessage nsContentUtils::GetEventMessage(nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread(), "sAtomEventTable is not threadsafe");
+ if (aName) {
+ EventNameMapping mapping;
+ if (sAtomEventTable->Get(aName, &mapping)) {
+ return mapping.mMessage;
+ }
+ }
+
+ return eUnidentifiedEvent;
+}
+
+// static
+mozilla::EventClassID nsContentUtils::GetEventClassID(const nsAString& aName) {
+ EventNameMapping mapping;
+ if (sStringEventTable->Get(aName, &mapping)) return mapping.mEventClassID;
+
+ return eBasicEventClass;
+}
+
+nsAtom* nsContentUtils::GetEventMessageAndAtom(
+ const nsAString& aName, mozilla::EventClassID aEventClassID,
+ EventMessage* aEventMessage) {
+ MOZ_ASSERT(NS_IsMainThread(), "Our hashtables are not threadsafe");
+ EventNameMapping mapping;
+ if (sStringEventTable->Get(aName, &mapping)) {
+ *aEventMessage = mapping.mEventClassID == aEventClassID
+ ? mapping.mMessage
+ : eUnidentifiedEvent;
+ return mapping.mAtom;
+ }
+
+ // If we have cached lots of user defined event names, clear some of them.
+ if (sUserDefinedEvents->Length() > 127) {
+ while (sUserDefinedEvents->Length() > 64) {
+ nsAtom* first = sUserDefinedEvents->ElementAt(0);
+ sStringEventTable->Remove(Substring(nsDependentAtomString(first), 2));
+ sUserDefinedEvents->RemoveElementAt(0);
+ }
+ }
+
+ *aEventMessage = eUnidentifiedEvent;
+ RefPtr<nsAtom> atom = NS_AtomizeMainThread(u"on"_ns + aName);
+ sUserDefinedEvents->AppendElement(atom);
+ mapping.mAtom = atom;
+ mapping.mMessage = eUnidentifiedEvent;
+ mapping.mType = EventNameType_None;
+ mapping.mEventClassID = eBasicEventClass;
+ // This is a slow hashtable call, but at least we cache the result for the
+ // following calls. Because GetEventMessageAndAtomForListener utilizes
+ // sStringEventTable, it needs to know in which cases sStringEventTable
+ // doesn't contain the information it needs so that it can use
+ // sAtomEventTable instead.
+ mapping.mMaybeSpecialSVGorSMILEvent =
+ GetEventMessage(atom) != eUnidentifiedEvent;
+ sStringEventTable->InsertOrUpdate(aName, mapping);
+ return mapping.mAtom;
+}
+
+// static
+EventMessage nsContentUtils::GetEventMessageAndAtomForListener(
+ const nsAString& aName, nsAtom** aOnName) {
+ MOZ_ASSERT(NS_IsMainThread(), "Our hashtables are not threadsafe");
+
+ // Because of SVG/SMIL sStringEventTable contains a subset of the event names
+ // comparing to the sAtomEventTable. However, usually sStringEventTable
+ // contains the information we need, so in order to reduce hashtable
+ // lookups, start from it.
+ EventNameMapping mapping;
+ EventMessage msg = eUnidentifiedEvent;
+ RefPtr<nsAtom> atom;
+ if (sStringEventTable->Get(aName, &mapping)) {
+ if (mapping.mMaybeSpecialSVGorSMILEvent) {
+ // Try the atom version so that we should get the right message for
+ // SVG/SMIL.
+ atom = NS_AtomizeMainThread(u"on"_ns + aName);
+ msg = GetEventMessage(atom);
+ } else {
+ atom = mapping.mAtom;
+ msg = mapping.mMessage;
+ }
+ atom.forget(aOnName);
+ return msg;
+ }
+
+ // GetEventMessageAndAtom will cache the event type for the future usage...
+ GetEventMessageAndAtom(aName, eBasicEventClass, &msg);
+
+ // ...and then call this method recursively to get the message and atom from
+ // now updated sStringEventTable.
+ return GetEventMessageAndAtomForListener(aName, aOnName);
+}
+
+static nsresult GetEventAndTarget(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable,
+ Composed aComposed, Trusted aTrusted,
+ Event** aEvent, EventTarget** aTargetOut) {
+ nsCOMPtr<EventTarget> target(do_QueryInterface(aTarget));
+ NS_ENSURE_TRUE(aDoc && target, NS_ERROR_INVALID_ARG);
+
+ ErrorResult err;
+ RefPtr<Event> event =
+ aDoc->CreateEvent(u"Events"_ns, CallerType::System, err);
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+
+ event->InitEvent(aEventName, aCanBubble, aCancelable, aComposed);
+ event->SetTrusted(aTrusted == Trusted::eYes);
+
+ event->SetTarget(target);
+
+ event.forget(aEvent);
+ target.forget(aTargetOut);
+ return NS_OK;
+}
+
+// static
+nsresult nsContentUtils::DispatchTrustedEvent(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
+ bool* aDefaultAction) {
+ MOZ_ASSERT(!aEventName.EqualsLiteral("input") &&
+ !aEventName.EqualsLiteral("beforeinput"),
+ "Use DispatchInputEvent() instead");
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ aComposed, Trusted::eYes, aDefaultAction);
+}
+
+// static
+nsresult nsContentUtils::DispatchUntrustedEvent(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ Composed::eDefault, Trusted::eNo, aDefaultAction);
+}
+
+// static
+nsresult nsContentUtils::DispatchEvent(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ CanBubble aCanBubble,
+ Cancelable aCancelable,
+ Composed aComposed, Trusted aTrusted,
+ bool* aDefaultAction,
+ ChromeOnlyDispatch aOnlyChromeDispatch) {
+ RefPtr<Event> event;
+ nsCOMPtr<EventTarget> target;
+ nsresult rv = GetEventAndTarget(
+ aDoc, aTarget, aEventName, aCanBubble, aCancelable, aComposed, aTrusted,
+ getter_AddRefs(event), getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch =
+ aOnlyChromeDispatch == ChromeOnlyDispatch::eYes;
+
+ ErrorResult err;
+ bool doDefault = target->DispatchEvent(*event, CallerType::System, err);
+ if (aDefaultAction) {
+ *aDefaultAction = doDefault;
+ }
+ return err.StealNSResult();
+}
+
+// static
+nsresult nsContentUtils::DispatchEvent(Document* aDoc, nsISupports* aTarget,
+ WidgetEvent& aEvent,
+ EventMessage aEventMessage,
+ CanBubble aCanBubble,
+ Cancelable aCancelable, Trusted aTrusted,
+ bool* aDefaultAction,
+ ChromeOnlyDispatch aOnlyChromeDispatch) {
+ MOZ_ASSERT_IF(aOnlyChromeDispatch == ChromeOnlyDispatch::eYes,
+ aTrusted == Trusted::eYes);
+
+ nsCOMPtr<EventTarget> target(do_QueryInterface(aTarget));
+
+ aEvent.mSpecifiedEventType = GetEventTypeFromMessage(aEventMessage);
+ aEvent.SetDefaultComposed();
+ aEvent.SetDefaultComposedInNativeAnonymousContent();
+
+ aEvent.mFlags.mBubbles = aCanBubble == CanBubble::eYes;
+ aEvent.mFlags.mCancelable = aCancelable == Cancelable::eYes;
+ aEvent.mFlags.mOnlyChromeDispatch =
+ aOnlyChromeDispatch == ChromeOnlyDispatch::eYes;
+
+ aEvent.mTarget = target;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(target, &aEvent, nullptr,
+ nullptr, &status);
+ if (aDefaultAction) {
+ *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
+ }
+ return rv;
+}
+
+// static
+nsresult nsContentUtils::DispatchInputEvent(Element* aEventTarget) {
+ return DispatchInputEvent(aEventTarget, mozilla::eEditorInput,
+ mozilla::EditorInputType::eUnknown, nullptr,
+ InputEventOptions());
+}
+
+// static
+nsresult nsContentUtils::DispatchInputEvent(
+ Element* aEventTargetElement, EventMessage aEventMessage,
+ EditorInputType aEditorInputType, EditorBase* aEditorBase,
+ InputEventOptions&& aOptions, nsEventStatus* aEventStatus /* = nullptr */) {
+ MOZ_ASSERT(aEventMessage == eEditorInput ||
+ aEventMessage == eEditorBeforeInput);
+
+ if (NS_WARN_IF(!aEventTargetElement)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // If this is called from editor, the instance should be set to aEditorBase.
+ // Otherwise, we need to look for an editor for aEventTargetElement.
+ // However, we don't need to do it for HTMLEditor since nobody shouldn't
+ // dispatch "beforeinput" nor "input" event for HTMLEditor except HTMLEditor
+ // itself.
+ bool useInputEvent = false;
+ if (aEditorBase) {
+ useInputEvent = true;
+ } else if (HTMLTextAreaElement* textAreaElement =
+ HTMLTextAreaElement::FromNode(aEventTargetElement)) {
+ aEditorBase = textAreaElement->GetTextEditorWithoutCreation();
+ useInputEvent = true;
+ } else if (HTMLInputElement* inputElement =
+ HTMLInputElement::FromNode(aEventTargetElement)) {
+ if (inputElement->IsInputEventTarget()) {
+ aEditorBase = inputElement->GetTextEditorWithoutCreation();
+ useInputEvent = true;
+ }
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(!aEventTargetElement->IsTextControlElement(),
+ "The event target may have editor, but we've not known it yet.");
+ }
+#endif // #ifdef DEBUG
+
+ if (!useInputEvent) {
+ MOZ_ASSERT(aEventMessage == eEditorInput);
+ MOZ_ASSERT(aEditorInputType == EditorInputType::eUnknown);
+ MOZ_ASSERT(!aOptions.mNeverCancelable);
+ // Dispatch "input" event with Event instance.
+ WidgetEvent widgetEvent(true, eUnidentifiedEvent);
+ widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
+ widgetEvent.mFlags.mCancelable = false;
+ widgetEvent.mFlags.mComposed = true;
+ return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
+ widgetEvent, aEventStatus);
+ }
+
+ MOZ_ASSERT_IF(aEventMessage != eEditorBeforeInput,
+ !aOptions.mNeverCancelable);
+ MOZ_ASSERT_IF(
+ aEventMessage == eEditorBeforeInput && aOptions.mNeverCancelable,
+ aEditorInputType == EditorInputType::eInsertReplacementText);
+
+ nsCOMPtr<nsIWidget> widget;
+ if (aEditorBase) {
+ widget = aEditorBase->GetWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ Document* document = aEventTargetElement->OwnerDoc();
+ if (NS_WARN_IF(!document)) {
+ return NS_ERROR_FAILURE;
+ }
+ // If we're running xpcshell tests, we fail to get presShell here.
+ // Even in such case, we need to dispatch "input" event without widget.
+ PresShell* presShell = document->GetPresShell();
+ if (presShell) {
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ widget = presContext->GetRootWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // Dispatch "input" event with InputEvent instance.
+ InternalEditorInputEvent inputEvent(true, aEventMessage, widget);
+
+ inputEvent.mFlags.mCancelable =
+ !aOptions.mNeverCancelable && aEventMessage == eEditorBeforeInput &&
+ IsCancelableBeforeInputEvent(aEditorInputType);
+ MOZ_ASSERT(!inputEvent.mFlags.mCancelable || aEventStatus);
+
+ // If there is an editor, set isComposing to true when it has composition.
+ // Note that EditorBase::IsIMEComposing() may return false even when we
+ // need to set it to true.
+ // Otherwise, i.e., editor hasn't been created for the element yet,
+ // we should set isComposing to false since the element can never has
+ // composition without editor.
+ inputEvent.mIsComposing = aEditorBase && aEditorBase->GetComposition();
+
+ if (!aEditorBase || aEditorBase->IsTextEditor()) {
+ if (IsDataAvailableOnTextEditor(aEditorInputType)) {
+ inputEvent.mData = std::move(aOptions.mData);
+ MOZ_ASSERT(!inputEvent.mData.IsVoid(),
+ "inputEvent.mData shouldn't be void");
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
+ }
+#endif // #ifdef DEBUG
+ MOZ_ASSERT(
+ aOptions.mTargetRanges.IsEmpty(),
+ "Target ranges for <input> and <textarea> should always be empty");
+ } else {
+ MOZ_ASSERT(aEditorBase->IsHTMLEditor());
+ if (IsDataAvailableOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mData = std::move(aOptions.mData);
+ MOZ_ASSERT(!inputEvent.mData.IsVoid(),
+ "inputEvent.mData shouldn't be void");
+ } else {
+ MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
+ if (IsDataTransferAvailableOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mDataTransfer = std::move(aOptions.mDataTransfer);
+ MOZ_ASSERT(inputEvent.mDataTransfer,
+ "inputEvent.mDataTransfer shouldn't be nullptr");
+ MOZ_ASSERT(inputEvent.mDataTransfer->IsReadOnly(),
+ "inputEvent.mDataTransfer should be read only");
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(!inputEvent.mDataTransfer,
+ "inputEvent.mDataTransfer should be nullptr");
+ }
+#endif // #ifdef DEBUG
+ }
+ if (aEventMessage == eEditorBeforeInput &&
+ MayHaveTargetRangesOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mTargetRanges = std::move(aOptions.mTargetRanges);
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(aOptions.mTargetRanges.IsEmpty(),
+ "Target ranges shouldn't be set for the dispatching event");
+ }
+#endif // #ifdef DEBUG
+ }
+
+ inputEvent.mInputType = aEditorInputType;
+
+ // If we cannot dispatch an event right now, we cannot make it cancelable.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ NS_ASSERTION(
+ !inputEvent.mFlags.mCancelable,
+ "Cancelable beforeinput event dispatcher should run when it's safe");
+ inputEvent.mFlags.mCancelable = false;
+ }
+ return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
+ inputEvent, aEventStatus);
+}
+
+nsresult nsContentUtils::DispatchChromeEvent(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
+ RefPtr<Event> event;
+ nsCOMPtr<EventTarget> target;
+ nsresult rv = GetEventAndTarget(
+ aDoc, aTarget, aEventName, aCanBubble, aCancelable, Composed::eDefault,
+ Trusted::eYes, getter_AddRefs(event), getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(aDoc, "GetEventAndTarget lied?");
+ if (!aDoc->GetWindow()) return NS_ERROR_INVALID_ARG;
+
+ EventTarget* piTarget = aDoc->GetWindow()->GetParentTarget();
+ if (!piTarget) return NS_ERROR_INVALID_ARG;
+
+ ErrorResult err;
+ bool defaultActionEnabled =
+ piTarget->DispatchEvent(*event, CallerType::System, err);
+ if (aDefaultAction) {
+ *aDefaultAction = defaultActionEnabled;
+ }
+ return err.StealNSResult();
+}
+
+void nsContentUtils::RequestFrameFocus(Element& aFrameElement, bool aCanRaise,
+ CallerType aCallerType) {
+ RefPtr<Element> target = &aFrameElement;
+ bool defaultAction = true;
+ if (aCanRaise) {
+ DispatchEventOnlyToChrome(target->OwnerDoc(), target,
+ u"framefocusrequested"_ns, CanBubble::eYes,
+ Cancelable::eYes, &defaultAction);
+ }
+ if (!defaultAction) {
+ return;
+ }
+
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return;
+ }
+
+ uint32_t flags = nsIFocusManager::FLAG_NOSCROLL;
+ if (aCanRaise) {
+ flags |= nsIFocusManager::FLAG_RAISE;
+ }
+
+ if (aCallerType == CallerType::NonSystem) {
+ flags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
+ }
+
+ fm->SetFocus(target, flags);
+}
+
+nsresult nsContentUtils::DispatchEventOnlyToChrome(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
+ bool* aDefaultAction) {
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ aComposed, Trusted::eYes, aDefaultAction,
+ ChromeOnlyDispatch::eYes);
+}
+
+/* static */
+Element* nsContentUtils::MatchElementId(nsIContent* aContent,
+ const nsAtom* aId) {
+ for (nsIContent* cur = aContent; cur; cur = cur->GetNextNode(aContent)) {
+ if (aId == cur->GetID()) {
+ return cur->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+Element* nsContentUtils::MatchElementId(nsIContent* aContent,
+ const nsAString& aId) {
+ MOZ_ASSERT(!aId.IsEmpty(), "Will match random elements");
+
+ // ID attrs are generally stored as atoms, so just atomize this up front
+ RefPtr<nsAtom> id(NS_Atomize(aId));
+ if (!id) {
+ // OOM, so just bail
+ return nullptr;
+ }
+
+ return MatchElementId(aContent, id);
+}
+
+/* static */
+void nsContentUtils::RegisterShutdownObserver(nsIObserver* aObserver) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ }
+}
+
+/* static */
+void nsContentUtils::UnregisterShutdownObserver(nsIObserver* aObserver) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+}
+
+/* static */
+bool nsContentUtils::HasNonEmptyAttr(const nsIContent* aContent,
+ int32_t aNameSpaceID, nsAtom* aName) {
+ static AttrArray::AttrValuesArray strings[] = {nsGkAtoms::_empty, nullptr};
+ return aContent->IsElement() &&
+ aContent->AsElement()->FindAttrValueIn(aNameSpaceID, aName, strings,
+ eCaseMatters) ==
+ AttrArray::ATTR_VALUE_NO_MATCH;
+}
+
+/* static */
+bool nsContentUtils::HasMutationListeners(nsINode* aNode, uint32_t aType,
+ nsINode* aTargetForSubtreeModified) {
+ Document* doc = aNode->OwnerDoc();
+
+ // global object will be null for documents that don't have windows.
+ nsPIDOMWindowInner* window = doc->GetInnerWindow();
+ // This relies on EventListenerManager::AddEventListener, which sets
+ // all mutation bits when there is a listener for DOMSubtreeModified event.
+ if (window && !window->HasMutationListeners(aType)) {
+ return false;
+ }
+
+ if (aNode->ChromeOnlyAccess() || aNode->IsInShadowTree()) {
+ return false;
+ }
+
+ doc->MayDispatchMutationEvent(aTargetForSubtreeModified);
+
+ // If we have a window, we can check it for mutation listeners now.
+ if (aNode->IsInUncomposedDoc()) {
+ nsCOMPtr<EventTarget> piTarget(do_QueryInterface(window));
+ if (piTarget) {
+ EventListenerManager* manager = piTarget->GetExistingListenerManager();
+ if (manager && manager->HasMutationListeners()) {
+ return true;
+ }
+ }
+ }
+
+ // If we have a window, we know a mutation listener is registered, but it
+ // might not be in our chain. If we don't have a window, we might have a
+ // mutation listener. Check quickly to see.
+ while (aNode) {
+ EventListenerManager* manager = aNode->GetExistingListenerManager();
+ if (manager && manager->HasMutationListeners()) {
+ return true;
+ }
+
+ aNode = aNode->GetParentNode();
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::HasMutationListeners(Document* aDocument, uint32_t aType) {
+ nsPIDOMWindowInner* window =
+ aDocument ? aDocument->GetInnerWindow() : nullptr;
+
+ // This relies on EventListenerManager::AddEventListener, which sets
+ // all mutation bits when there is a listener for DOMSubtreeModified event.
+ return !window || window->HasMutationListeners(aType);
+}
+
+void nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent) {
+ MOZ_ASSERT(aChild, "Missing child");
+ MOZ_ASSERT(aChild->GetParentNode() == aParent, "Wrong parent");
+ MOZ_ASSERT(aChild->OwnerDoc() == aParent->OwnerDoc(), "Wrong owner-doc");
+
+ // Having an explicit check here since it's an easy mistake to fall into,
+ // and there might be existing code with problems. We'd rather be safe
+ // than fire DOMNodeRemoved in all corner cases. We also rely on it for
+ // nsAutoScriptBlockerSuppressNodeRemoved.
+ if (!IsSafeToRunScript()) {
+ // This checks that IsSafeToRunScript is true since we don't want to fire
+ // events when that is false. We can't rely on EventDispatcher to assert
+ // this in this situation since most of the time there are no mutation
+ // event listeners, in which case we won't even attempt to dispatch events.
+ // However this also allows for two exceptions. First off, we don't assert
+ // if the mutation happens to native anonymous content since we never fire
+ // mutation events on such content anyway.
+ // Second, we don't assert if sDOMNodeRemovedSuppressCount is true since
+ // that is a know case when we'd normally fire a mutation event, but can't
+ // make that safe and so we suppress it at this time. Ideally this should
+ // go away eventually.
+ if (!aChild->IsInNativeAnonymousSubtree() &&
+ !sDOMNodeRemovedSuppressCount) {
+ NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe");
+ WarnScriptWasIgnored(aChild->OwnerDoc());
+ }
+ return;
+ }
+
+ {
+ Document* doc = aParent->OwnerDoc();
+ if (MOZ_UNLIKELY(doc->DevToolsWatchingDOMMutations()) &&
+ aChild->IsInComposedDoc() && !aChild->ChromeOnlyAccess()) {
+ DispatchChromeEvent(doc, aChild, u"devtoolschildremoved"_ns,
+ CanBubble::eNo, Cancelable::eNo);
+ }
+ }
+
+ if (HasMutationListeners(aChild, NS_EVENT_BITS_MUTATION_NODEREMOVED,
+ aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeRemoved);
+ mutation.mRelatedNode = aParent;
+
+ mozAutoSubtreeModified subtree(aParent->OwnerDoc(), aParent);
+ EventDispatcher::Dispatch(aChild, nullptr, &mutation);
+ }
+}
+
+void nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments() {
+ if (!sEventListenerManagersHash) {
+ return;
+ }
+
+ for (auto i = sEventListenerManagersHash->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<EventListenerManagerMapEntry*>(i.Get());
+ nsINode* n = static_cast<nsINode*>(entry->mListenerManager->GetTarget());
+ if (n && n->IsInComposedDoc() &&
+ nsCCUncollectableMarker::InGeneration(
+ n->OwnerDoc()->GetMarkedCCGeneration())) {
+ entry->mListenerManager->MarkForCC();
+ }
+ }
+}
+
+/* static */
+void nsContentUtils::TraverseListenerManager(
+ nsINode* aNode, nsCycleCollectionTraversalCallback& cb) {
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, just return.
+ return;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ CycleCollectionNoteChild(cb, entry->mListenerManager.get(),
+ "[via hash] mListenerManager");
+ }
+}
+
+EventListenerManager* nsContentUtils::GetListenerManagerForNode(
+ nsINode* aNode) {
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, don't bother creating an event listener
+ // manager.
+
+ return nullptr;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Add(aNode, fallible));
+
+ if (!entry) {
+ return nullptr;
+ }
+
+ if (!entry->mListenerManager) {
+ entry->mListenerManager = new EventListenerManager(aNode);
+
+ aNode->SetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ return entry->mListenerManager;
+}
+
+EventListenerManager* nsContentUtils::GetExistingListenerManagerForNode(
+ const nsINode* aNode) {
+ if (!aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ return nullptr;
+ }
+
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, don't bother creating an event listener
+ // manager.
+
+ return nullptr;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ return entry->mListenerManager;
+ }
+
+ return nullptr;
+}
+
+void nsContentUtils::AddEntryToDOMArenaTable(nsINode* aNode,
+ DOMArena* aDOMArena) {
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ MOZ_ASSERT_IF(sDOMArenaHashtable, !sDOMArenaHashtable->Contains(aNode));
+ MOZ_ASSERT(!aNode->HasFlag(NODE_KEEPS_DOMARENA));
+ if (!sDOMArenaHashtable) {
+ sDOMArenaHashtable =
+ new nsRefPtrHashtable<nsPtrHashKey<const nsINode>, dom::DOMArena>();
+ }
+ aNode->SetFlags(NODE_KEEPS_DOMARENA);
+ sDOMArenaHashtable->InsertOrUpdate(aNode, RefPtr<DOMArena>(aDOMArena));
+}
+
+already_AddRefed<DOMArena> nsContentUtils::TakeEntryFromDOMArenaTable(
+ const nsINode* aNode) {
+ MOZ_ASSERT(sDOMArenaHashtable->Contains(aNode));
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ RefPtr<DOMArena> arena;
+ sDOMArenaHashtable->Remove(aNode, getter_AddRefs(arena));
+ return arena.forget();
+}
+
+/* static */
+void nsContentUtils::RemoveListenerManager(nsINode* aNode) {
+ if (sEventListenerManagersHash) {
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ RefPtr<EventListenerManager> listenerManager;
+ listenerManager.swap(entry->mListenerManager);
+ // Remove the entry and *then* do operations that could cause further
+ // modification of sEventListenerManagersHash. See bug 334177.
+ sEventListenerManagersHash->RawRemove(entry);
+ if (listenerManager) {
+ listenerManager->Disconnect();
+ }
+ }
+ }
+}
+
+/* static */
+bool nsContentUtils::IsValidNodeName(nsAtom* aLocalName, nsAtom* aPrefix,
+ int32_t aNamespaceID) {
+ if (aNamespaceID == kNameSpaceID_Unknown) {
+ return false;
+ }
+
+ if (!aPrefix) {
+ // If the prefix is null, then either the QName must be xmlns or the
+ // namespace must not be XMLNS.
+ return (aLocalName == nsGkAtoms::xmlns) ==
+ (aNamespaceID == kNameSpaceID_XMLNS);
+ }
+
+ // If the prefix is non-null then the namespace must not be null.
+ if (aNamespaceID == kNameSpaceID_None) {
+ return false;
+ }
+
+ // If the namespace is the XMLNS namespace then the prefix must be xmlns,
+ // but the localname must not be xmlns.
+ if (aNamespaceID == kNameSpaceID_XMLNS) {
+ return aPrefix == nsGkAtoms::xmlns && aLocalName != nsGkAtoms::xmlns;
+ }
+
+ // If the namespace is not the XMLNS namespace then the prefix must not be
+ // xmlns.
+ // If the namespace is the XML namespace then the prefix can be anything.
+ // If the namespace is not the XML namespace then the prefix must not be xml.
+ return aPrefix != nsGkAtoms::xmlns &&
+ (aNamespaceID == kNameSpaceID_XML || aPrefix != nsGkAtoms::xml);
+}
+
+already_AddRefed<DocumentFragment> nsContentUtils::CreateContextualFragment(
+ nsINode* aContextNode, const nsAString& aFragment,
+ bool aPreventScriptExecution, ErrorResult& aRv) {
+ if (!aContextNode) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ // If we don't have a document here, we can't get the right security context
+ // for compiling event handlers... so just bail out.
+ RefPtr<Document> document = aContextNode->OwnerDoc();
+ bool isHTML = document->IsHTMLDocument();
+
+ if (isHTML) {
+ RefPtr<DocumentFragment> frag = new (document->NodeInfoManager())
+ DocumentFragment(document->NodeInfoManager());
+
+ Element* element = aContextNode->GetAsElementOrParentElement();
+ if (element && !element->IsHTMLElement(nsGkAtoms::html)) {
+ aRv = ParseFragmentHTML(
+ aFragment, frag, element->NodeInfo()->NameAtom(),
+ element->GetNameSpaceID(),
+ (document->GetCompatibilityMode() == eCompatibility_NavQuirks),
+ aPreventScriptExecution);
+ } else {
+ aRv = ParseFragmentHTML(
+ aFragment, frag, nsGkAtoms::body, kNameSpaceID_XHTML,
+ (document->GetCompatibilityMode() == eCompatibility_NavQuirks),
+ aPreventScriptExecution);
+ }
+
+ return frag.forget();
+ }
+
+ AutoTArray<nsString, 32> tagStack;
+ nsAutoString uriStr, nameStr;
+ for (Element* element : aContextNode->InclusiveAncestorsOfType<Element>()) {
+ nsString& tagName = *tagStack.AppendElement();
+ // It mostly doesn't actually matter what tag name we use here: XML doesn't
+ // have parsing that depends on the open tag stack, apart from namespace
+ // declarations. So this whole tagStack bit is just there to get the right
+ // namespace declarations to the XML parser. That said, the parser _is_
+ // going to create elements with the tag names we provide here, so we need
+ // to make sure they are not names that can trigger custom element
+ // constructors. Just make up a name that is never going to be a valid
+ // custom element name.
+ //
+ // The principled way to do this would probably be to add a new FromParser
+ // value and make sure we use it when creating the context elements, then
+ // make sure we teach all FromParser consumers (and in particular the custom
+ // element code) about it as needed. But right now the XML parser never
+ // actually uses FromParser values other than NOT_FROM_PARSER, and changing
+ // that is pretty complicated.
+ tagName.AssignLiteral("notacustomelement");
+
+ // see if we need to add xmlns declarations
+ uint32_t count = element->GetAttrCount();
+ bool setDefaultNamespace = false;
+ if (count > 0) {
+ uint32_t index;
+
+ for (index = 0; index < count; index++) {
+ const BorrowedAttrInfo info = element->GetAttrInfoAt(index);
+ const nsAttrName* name = info.mName;
+ if (name->NamespaceEquals(kNameSpaceID_XMLNS)) {
+ info.mValue->ToString(uriStr);
+
+ // really want something like nsXMLContentSerializer::SerializeAttr
+ tagName.AppendLiteral(" xmlns"); // space important
+ if (name->GetPrefix()) {
+ tagName.Append(char16_t(':'));
+ name->LocalName()->ToString(nameStr);
+ tagName.Append(nameStr);
+ } else {
+ setDefaultNamespace = true;
+ }
+ tagName.AppendLiteral(R"(=")");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+ }
+
+ if (!setDefaultNamespace) {
+ mozilla::dom::NodeInfo* info = element->NodeInfo();
+ if (!info->GetPrefixAtom() && info->NamespaceID() != kNameSpaceID_None) {
+ // We have no namespace prefix, but have a namespace ID. Push
+ // default namespace attr in, so that our kids will be in our
+ // namespace.
+ info->GetNamespaceURI(uriStr);
+ tagName.AppendLiteral(R"( xmlns=")");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+ }
+
+ RefPtr<DocumentFragment> frag;
+ aRv = ParseFragmentXML(aFragment, document, tagStack, aPreventScriptExecution,
+ -1, getter_AddRefs(frag));
+ return frag.forget();
+}
+
+/* static */
+void nsContentUtils::DropFragmentParsers() {
+ NS_IF_RELEASE(sHTMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentSink);
+}
+
+/* static */
+void nsContentUtils::XPCOMShutdown() { nsContentUtils::DropFragmentParsers(); }
+
+/* Helper function to compuate Sanitization Flags for ParseFramentHTML/XML */
+uint32_t computeSanitizationFlags(nsIPrincipal* aPrincipal, int32_t aFlags) {
+ uint32_t sanitizationFlags = 0;
+ if (aPrincipal->IsSystemPrincipal()) {
+ if (aFlags < 0) {
+ // if this is a chrome-privileged document and no explicit flags
+ // were passed, then use this sanitization flags.
+ sanitizationFlags = nsIParserUtils::SanitizerAllowStyle |
+ nsIParserUtils::SanitizerAllowComments |
+ nsIParserUtils::SanitizerDropForms |
+ nsIParserUtils::SanitizerLogRemovals;
+ } else {
+ // if the caller explicitly passes flags, then we use those
+ // flags but additionally drop forms.
+ sanitizationFlags = aFlags | nsIParserUtils::SanitizerDropForms;
+ }
+ } else if (aFlags >= 0) {
+ // aFlags by default is -1 and is only ever non equal to -1 if the
+ // caller of ParseFragmentHTML/ParseFragmentXML is
+ // ParserUtils::ParseFragment(). Only in that case we should use
+ // the sanitization flags passed within aFlags.
+ sanitizationFlags = aFlags;
+ }
+ return sanitizationFlags;
+}
+
+/* static */
+bool AllowsUnsanitizedContentForAboutNewTab(nsIPrincipal* aPrincipal) {
+ if (StaticPrefs::dom_about_newtab_sanitization_enabled() ||
+ !aPrincipal->SchemeIs("about")) {
+ return false;
+ }
+ uint32_t aboutModuleFlags = 0;
+ aPrincipal->GetAboutModuleFlags(&aboutModuleFlags);
+ return aboutModuleFlags & nsIAboutModule::ALLOW_UNSANITIZED_CONTENT;
+}
+
+/* static */
+nsresult nsContentUtils::ParseFragmentHTML(
+ const nsAString& aSourceBuffer, nsIContent* aTargetNode,
+ nsAtom* aContextLocalName, int32_t aContextNamespace, bool aQuirks,
+ bool aPreventScriptExecution, int32_t aFlags) {
+ AutoTimelineMarker m(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sHTMLFragmentParser) {
+ NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
+ // Now sHTMLFragmentParser owns the object
+ }
+
+ nsCOMPtr<nsIPrincipal> nodePrincipal = aTargetNode->NodePrincipal();
+
+#ifdef DEBUG
+ // aFlags should always be -1 unless the caller of ParseFragmentHTML
+ // is ParserUtils::ParseFragment() which is the only caller that intends
+ // sanitization. For all other callers we need to ensure to call
+ // AuditParsingOfHTMLXMLFragments.
+ if (aFlags < 0) {
+ DOMSecurityMonitor::AuditParsingOfHTMLXMLFragments(nodePrincipal,
+ aSourceBuffer);
+ }
+#endif
+
+ nsIContent* target = aTargetNode;
+
+ RefPtr<Document> doc = aTargetNode->OwnerDoc();
+ RefPtr<DocumentFragment> fragment;
+ // We sanitize if the fragment occurs in a system privileged
+ // context, an about: page, or if there are explicit sanitization flags.
+ // Please note that about:blank and about:srcdoc inherit the security
+ // context from the embedding context and hence are not loaded using
+ // an about: scheme principal.
+ bool shouldSanitize = nodePrincipal->IsSystemPrincipal() ||
+ nodePrincipal->SchemeIs("about") || aFlags >= 0;
+ if (shouldSanitize &&
+ !AllowsUnsanitizedContentForAboutNewTab(nodePrincipal)) {
+ if (!doc->IsLoadedAsData()) {
+ doc = nsContentUtils::CreateInertHTMLDocument(doc);
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ fragment =
+ new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
+ target = fragment;
+ }
+
+ nsresult rv = sHTMLFragmentParser->ParseFragment(
+ aSourceBuffer, target, aContextLocalName, aContextNamespace, aQuirks,
+ aPreventScriptExecution);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fragment) {
+ uint32_t sanitizationFlags =
+ computeSanitizationFlags(nodePrincipal, aFlags);
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+ nsTreeSanitizer sanitizer(sanitizationFlags);
+ sanitizer.Sanitize(fragment);
+
+ ErrorResult error;
+ aTargetNode->AppendChild(*fragment, error);
+ rv = error.StealNSResult();
+ }
+
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ParseDocumentHTML(
+ const nsAString& aSourceBuffer, Document* aTargetDocument,
+ bool aScriptingEnabledForNoscriptParsing) {
+ AutoTimelineMarker m(aTargetDocument->GetDocShell(), "Parse HTML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sHTMLFragmentParser) {
+ NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
+ // Now sHTMLFragmentParser owns the object
+ }
+ nsresult rv = sHTMLFragmentParser->ParseDocument(
+ aSourceBuffer, aTargetDocument, aScriptingEnabledForNoscriptParsing);
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ParseFragmentXML(const nsAString& aSourceBuffer,
+ Document* aDocument,
+ nsTArray<nsString>& aTagStack,
+ bool aPreventScriptExecution,
+ int32_t aFlags,
+ DocumentFragment** aReturn) {
+ AutoTimelineMarker m(aDocument->GetDocShell(), "Parse XML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sXMLFragmentParser) {
+ RefPtr<nsParser> parser = new nsParser();
+ parser.forget(&sXMLFragmentParser);
+ // sXMLFragmentParser now owns the parser
+ }
+ if (!sXMLFragmentSink) {
+ NS_NewXMLFragmentContentSink(&sXMLFragmentSink);
+ // sXMLFragmentSink now owns the sink
+ }
+ nsCOMPtr<nsIContentSink> contentsink = do_QueryInterface(sXMLFragmentSink);
+ MOZ_ASSERT(contentsink, "Sink doesn't QI to nsIContentSink!");
+ sXMLFragmentParser->SetContentSink(contentsink);
+
+ RefPtr<Document> doc;
+ nsCOMPtr<nsIPrincipal> nodePrincipal = aDocument->NodePrincipal();
+
+#ifdef DEBUG
+ // aFlags should always be -1 unless the caller of ParseFragmentXML
+ // is ParserUtils::ParseFragment() which is the only caller that intends
+ // sanitization. For all other callers we need to ensure to call
+ // AuditParsingOfHTMLXMLFragments.
+ if (aFlags < 0) {
+ DOMSecurityMonitor::AuditParsingOfHTMLXMLFragments(nodePrincipal,
+ aSourceBuffer);
+ }
+#endif
+
+ // We sanitize if the fragment occurs in a system privileged
+ // context, an about: page, or if there are explicit sanitization flags.
+ // Please note that about:blank and about:srcdoc inherit the security
+ // context from the embedding context and hence are not loaded using
+ // an about: scheme principal.
+ bool shouldSanitize = nodePrincipal->IsSystemPrincipal() ||
+ nodePrincipal->SchemeIs("about") || aFlags >= 0;
+ if (shouldSanitize && !aDocument->IsLoadedAsData()) {
+ doc = nsContentUtils::CreateInertXMLDocument(aDocument);
+ } else {
+ doc = aDocument;
+ }
+
+ sXMLFragmentSink->SetTargetDocument(doc);
+ sXMLFragmentSink->SetPreventScriptExecution(aPreventScriptExecution);
+
+ nsresult rv = sXMLFragmentParser->ParseFragment(aSourceBuffer, aTagStack);
+ if (NS_FAILED(rv)) {
+ // Drop the fragment parser and sink that might be in an inconsistent state
+ NS_IF_RELEASE(sXMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentSink);
+ return rv;
+ }
+
+ rv = sXMLFragmentSink->FinishFragmentParsing(aReturn);
+
+ sXMLFragmentParser->Reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (shouldSanitize) {
+ uint32_t sanitizationFlags =
+ computeSanitizationFlags(nodePrincipal, aFlags);
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+ nsTreeSanitizer sanitizer(sanitizationFlags);
+ sanitizer.Sanitize(*aReturn);
+ }
+
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ConvertToPlainText(const nsAString& aSourceBuffer,
+ nsAString& aResultBuffer,
+ uint32_t aFlags,
+ uint32_t aWrapCol) {
+ RefPtr<Document> document = nsContentUtils::CreateInertHTMLDocument(nullptr);
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = nsContentUtils::ParseDocumentHTML(
+ aSourceBuffer, document,
+ !(aFlags & nsIDocumentEncoder::OutputNoScriptContent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder("text/plain");
+
+ rv = encoder->Init(document, u"text/plain"_ns, aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ encoder->SetWrapColumn(aWrapCol);
+
+ return encoder->EncodeToString(aResultBuffer);
+}
+
+static already_AddRefed<Document> CreateInertDocument(const Document* aTemplate,
+ DocumentFlavor aFlavor) {
+ if (aTemplate) {
+ bool hasHad = true;
+ nsIScriptGlobalObject* sgo = aTemplate->GetScriptHandlingObject(hasHad);
+ NS_ENSURE_TRUE(sgo || !hasHad, nullptr);
+
+ nsCOMPtr<Document> doc;
+ nsresult rv = NS_NewDOMDocument(
+ getter_AddRefs(doc), u""_ns, u""_ns, nullptr,
+ aTemplate->GetDocumentURI(), aTemplate->GetDocBaseURI(),
+ aTemplate->NodePrincipal(), true, sgo, aFlavor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ return doc.forget();
+ }
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank"_ns);
+ if (!uri) {
+ return nullptr;
+ }
+
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ if (!nullPrincipal) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc;
+ nsresult rv =
+ NS_NewDOMDocument(getter_AddRefs(doc), u""_ns, u""_ns, nullptr, uri, uri,
+ nullPrincipal, true, nullptr, aFlavor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ return doc.forget();
+}
+
+/* static */
+already_AddRefed<Document> nsContentUtils::CreateInertXMLDocument(
+ const Document* aTemplate) {
+ return CreateInertDocument(aTemplate, DocumentFlavorXML);
+}
+
+/* static */
+already_AddRefed<Document> nsContentUtils::CreateInertHTMLDocument(
+ const Document* aTemplate) {
+ return CreateInertDocument(aTemplate, DocumentFlavorHTML);
+}
+
+/* static */
+nsresult nsContentUtils::SetNodeTextContent(nsIContent* aContent,
+ const nsAString& aValue,
+ bool aTryReuse) {
+ // Fire DOMNodeRemoved mutation events before we do anything else.
+ nsCOMPtr<nsIContent> owningContent;
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(nullptr, nullptr);
+
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ // We're relying on mozAutoSubtreeModified to keep a strong reference if
+ // needed.
+ Document* doc = aContent->OwnerDoc();
+
+ // Optimize the common case of there being no observers
+ if (HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ subtree.UpdateTarget(doc, nullptr);
+ owningContent = aContent;
+ nsCOMPtr<nsINode> child;
+ bool skipFirst = aTryReuse;
+ for (child = aContent->GetFirstChild();
+ child && child->GetParentNode() == aContent;
+ child = child->GetNextSibling()) {
+ if (skipFirst && child->IsText()) {
+ skipFirst = false;
+ continue;
+ }
+ nsContentUtils::MaybeFireNodeRemoved(child, aContent);
+ }
+ }
+ }
+
+ // Might as well stick a batch around this since we're performing several
+ // mutations.
+ mozAutoDocUpdate updateBatch(aContent->GetComposedDoc(), true);
+ nsAutoMutationBatch mb;
+
+ if (aTryReuse && !aValue.IsEmpty()) {
+ // Let's remove nodes until we find a eTEXT.
+ while (aContent->HasChildren()) {
+ nsIContent* child = aContent->GetFirstChild();
+ if (child->IsText()) {
+ break;
+ }
+ aContent->RemoveChildNode(child, true);
+ }
+
+ // If we have a node, it must be a eTEXT and we reuse it.
+ if (aContent->HasChildren()) {
+ nsIContent* child = aContent->GetFirstChild();
+ nsresult rv = child->AsText()->SetText(aValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // All the following nodes, if they exist, must be deleted.
+ while (nsIContent* nextChild = child->GetNextSibling()) {
+ aContent->RemoveChildNode(nextChild, true);
+ }
+ }
+
+ if (aContent->HasChildren()) {
+ return NS_OK;
+ }
+ } else {
+ mb.Init(aContent, true, false);
+ while (aContent->HasChildren()) {
+ aContent->RemoveChildNode(aContent->GetFirstChild(), true);
+ }
+ }
+ mb.RemovalDone();
+
+ if (aValue.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<nsTextNode> textContent = new (aContent->NodeInfo()->NodeInfoManager())
+ nsTextNode(aContent->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(aValue, true);
+
+ ErrorResult rv;
+ aContent->AppendChildTo(textContent, true, rv);
+ mb.NodesAdded();
+ return rv.StealNSResult();
+}
+
+static bool AppendNodeTextContentsRecurse(const nsINode* aNode,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ bool ok = AppendNodeTextContentsRecurse(child, aResult, aFallible);
+ if (!ok) {
+ return false;
+ }
+ } else if (Text* text = child->GetAsText()) {
+ bool ok = text->AppendTextTo(aResult, aFallible);
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool nsContentUtils::AppendNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ if (const Text* text = aNode->GetAsText()) {
+ return text->AppendTextTo(aResult, aFallible);
+ }
+ if (aDeep) {
+ return AppendNodeTextContentsRecurse(aNode, aResult, aFallible);
+ }
+
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (Text* text = child->GetAsText()) {
+ bool ok = text->AppendTextTo(aResult, fallible);
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool nsContentUtils::HasNonEmptyTextContent(
+ nsINode* aNode, TextContentDiscoverMode aDiscoverMode) {
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsText() && child->TextLength() > 0) {
+ return true;
+ }
+
+ if (aDiscoverMode == eRecurseIntoChildren &&
+ HasNonEmptyTextContent(child, aDiscoverMode)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode,
+ const nsINode* aOtherNode) {
+ MOZ_ASSERT(aNode, "Must have a node to work with");
+ MOZ_ASSERT(aOtherNode, "Must have a content to work with");
+
+ const bool anon = aNode->IsInNativeAnonymousSubtree();
+ if (anon != aOtherNode->IsInNativeAnonymousSubtree()) {
+ return false;
+ }
+
+ if (anon) {
+ return aOtherNode->GetClosestNativeAnonymousSubtreeRoot() ==
+ aNode->GetClosestNativeAnonymousSubtreeRoot();
+ }
+
+ // FIXME: This doesn't deal with disconnected nodes whatsoever, but it didn't
+ // use to either. Maybe that's fine.
+ return aNode->GetContainingShadow() == aOtherNode->GetContainingShadow();
+}
+
+/* static */
+bool nsContentUtils::IsInInteractiveHTMLContent(const Element* aElement,
+ const Element* aStop) {
+ const Element* element = aElement;
+ while (element && element != aStop) {
+ if (element->IsInteractiveHTMLContent()) {
+ return true;
+ }
+ element = element->GetFlattenedTreeParentElement();
+ }
+ return false;
+}
+
+/* static */
+void nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling) {
+ IMEStateManager::OnInstalledMenuKeyboardListener(aInstalling);
+}
+
+/* static */
+bool nsContentUtils::SchemeIs(nsIURI* aURI, const char* aScheme) {
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(baseURI, false);
+ return baseURI->SchemeIs(aScheme);
+}
+
+bool nsContentUtils::IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
+ return aPrincipal && aPrincipal->GetIsExpandedPrincipal();
+}
+
+bool nsContentUtils::IsSystemOrExpandedPrincipal(nsIPrincipal* aPrincipal) {
+ return (aPrincipal && aPrincipal->IsSystemPrincipal()) ||
+ IsExpandedPrincipal(aPrincipal);
+}
+
+nsIPrincipal* nsContentUtils::GetSystemPrincipal() {
+ MOZ_ASSERT(IsInitialized());
+ return sSystemPrincipal;
+}
+
+bool nsContentUtils::CombineResourcePrincipals(
+ nsCOMPtr<nsIPrincipal>* aResourcePrincipal, nsIPrincipal* aExtraPrincipal) {
+ if (!aExtraPrincipal) {
+ return false;
+ }
+ if (!*aResourcePrincipal) {
+ *aResourcePrincipal = aExtraPrincipal;
+ return true;
+ }
+ if (*aResourcePrincipal == aExtraPrincipal) {
+ return false;
+ }
+ bool subsumes;
+ if (NS_SUCCEEDED(
+ (*aResourcePrincipal)->Subsumes(aExtraPrincipal, &subsumes)) &&
+ subsumes) {
+ return false;
+ }
+ *aResourcePrincipal = sSystemPrincipal;
+ return true;
+}
+
+/* static */
+void nsContentUtils::TriggerLink(nsIContent* aContent, nsIURI* aLinkURI,
+ const nsString& aTargetSpec, bool aClick,
+ bool aIsTrusted) {
+ MOZ_ASSERT(aLinkURI, "No link URI");
+
+ if (aContent->IsEditable() || !aContent->OwnerDoc()->LinkHandlingEnabled()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aContent->OwnerDoc()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ if (!aClick) {
+ nsDocShell::Cast(docShell)->OnOverLink(aContent, aLinkURI, aTargetSpec);
+ return;
+ }
+
+ // Check that this page is allowed to load this URI.
+ nsresult proceed = NS_OK;
+
+ if (sSecurityManager) {
+ uint32_t flag = static_cast<uint32_t>(nsIScriptSecurityManager::STANDARD);
+ proceed = sSecurityManager->CheckLoadURIWithPrincipal(
+ aContent->NodePrincipal(), aLinkURI, flag,
+ aContent->OwnerDoc()->InnerWindowID());
+ }
+
+ // Only pass off the click event if the script security manager says it's ok.
+ // We need to rest aTargetSpec for forced downloads.
+ if (NS_SUCCEEDED(proceed)) {
+ // A link/area element with a download attribute is allowed to set
+ // a pseudo Content-Disposition header.
+ // For security reasons we only allow websites to declare same-origin
+ // resources as downloadable. If this check fails we will just do the normal
+ // thing (i.e. navigate to the resource).
+ nsAutoString fileName;
+ if ((!aContent->IsHTMLElement(nsGkAtoms::a) &&
+ !aContent->IsHTMLElement(nsGkAtoms::area) &&
+ !aContent->IsSVGElement(nsGkAtoms::a)) ||
+ !aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::download,
+ fileName) ||
+ NS_FAILED(aContent->NodePrincipal()->CheckMayLoad(aLinkURI, true))) {
+ fileName.SetIsVoid(true); // No actionable download attribute was found.
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aContent->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aContent->GetCsp();
+
+ // Sanitize fileNames containing null characters by replacing them with
+ // underscores.
+ if (!fileName.IsVoid()) {
+ fileName.ReplaceChar(char16_t(0), '_');
+ }
+ nsDocShell::Cast(docShell)->OnLinkClick(
+ aContent, aLinkURI, fileName.IsVoid() ? aTargetSpec : u""_ns, fileName,
+ nullptr, nullptr, UserActivation::IsHandlingUserInput(), aIsTrusted,
+ triggeringPrincipal, csp);
+ }
+}
+
+/* static */
+void nsContentUtils::GetLinkLocation(Element* aElement,
+ nsString& aLocationString) {
+ nsCOMPtr<nsIURI> hrefURI = aElement->GetHrefURI();
+ if (hrefURI) {
+ nsAutoCString specUTF8;
+ nsresult rv = hrefURI->GetSpec(specUTF8);
+ if (NS_SUCCEEDED(rv)) CopyUTF8toUTF16(specUTF8, aLocationString);
+ }
+}
+
+/* static */
+nsIWidget* nsContentUtils::GetTopLevelWidget(nsIWidget* aWidget) {
+ if (!aWidget) return nullptr;
+
+ return aWidget->GetTopLevelWidget();
+}
+
+/* static */
+const nsDependentString nsContentUtils::GetLocalizedEllipsis() {
+ static char16_t sBuf[4] = {0, 0, 0, 0};
+ if (!sBuf[0]) {
+ if (!SpoofLocaleEnglish()) {
+ nsAutoString tmp;
+ Preferences::GetLocalizedString("intl.ellipsis", tmp);
+ uint32_t len =
+ std::min(uint32_t(tmp.Length()), uint32_t(ArrayLength(sBuf) - 1));
+ CopyUnicodeTo(tmp, 0, sBuf, len);
+ }
+ if (!sBuf[0]) sBuf[0] = char16_t(0x2026);
+ }
+ return nsDependentString(sBuf);
+}
+
+/* static */
+void nsContentUtils::AddScriptBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sScriptBlockerCount) {
+ MOZ_ASSERT(sRunnersCountAtFirstBlocker == 0,
+ "Should not already have a count");
+ sRunnersCountAtFirstBlocker =
+ sBlockedScriptRunners ? sBlockedScriptRunners->Length() : 0;
+ }
+ ++sScriptBlockerCount;
+}
+
+#ifdef DEBUG
+static bool sRemovingScriptBlockers = false;
+#endif
+
+/* static */
+void nsContentUtils::RemoveScriptBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sRemovingScriptBlockers);
+ NS_ASSERTION(sScriptBlockerCount != 0, "Negative script blockers");
+ --sScriptBlockerCount;
+ if (sScriptBlockerCount) {
+ return;
+ }
+
+ if (!sBlockedScriptRunners) {
+ return;
+ }
+
+ uint32_t firstBlocker = sRunnersCountAtFirstBlocker;
+ uint32_t lastBlocker = sBlockedScriptRunners->Length();
+ uint32_t originalFirstBlocker = firstBlocker;
+ uint32_t blockersCount = lastBlocker - firstBlocker;
+ sRunnersCountAtFirstBlocker = 0;
+ NS_ASSERTION(firstBlocker <= lastBlocker, "bad sRunnersCountAtFirstBlocker");
+
+ while (firstBlocker < lastBlocker) {
+ nsCOMPtr<nsIRunnable> runnable;
+ runnable.swap((*sBlockedScriptRunners)[firstBlocker]);
+ ++firstBlocker;
+
+ // Calling the runnable can reenter us
+ {
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
+ runnable->Run();
+ }
+ // So can dropping the reference to the runnable
+ runnable = nullptr;
+
+ NS_ASSERTION(sRunnersCountAtFirstBlocker == 0, "Bad count");
+ NS_ASSERTION(!sScriptBlockerCount, "This is really bad");
+ }
+#ifdef DEBUG
+ AutoRestore<bool> removingScriptBlockers(sRemovingScriptBlockers);
+ sRemovingScriptBlockers = true;
+#endif
+ sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount);
+}
+
+/* static */
+already_AddRefed<nsPIDOMWindowOuter>
+nsContentUtils::GetMostRecentNonPBWindow() {
+ nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ wm->GetMostRecentNonPBWindow(u"navigator:browser", getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> pwindow;
+ pwindow = do_QueryInterface(window);
+
+ return pwindow.forget();
+}
+
+/* static */
+void nsContentUtils::WarnScriptWasIgnored(Document* aDocument) {
+ nsAutoString msg;
+ bool privateBrowsing = false;
+ bool chromeContext = false;
+
+ if (aDocument) {
+ nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+ if (uri) {
+ msg.Append(NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault()));
+ msg.AppendLiteral(" : ");
+ }
+ privateBrowsing =
+ !!aDocument->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId;
+ chromeContext = aDocument->NodePrincipal()->IsSystemPrincipal();
+ }
+
+ msg.AppendLiteral(
+ "Unable to run script because scripts are blocked internally.");
+ LogSimpleConsoleError(msg, "DOM"_ns, privateBrowsing, chromeContext);
+}
+
+/* static */
+void nsContentUtils::AddScriptRunner(already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ if (!runnable) {
+ return;
+ }
+
+ if (sScriptBlockerCount) {
+ sBlockedScriptRunners->AppendElement(runnable.forget());
+ return;
+ }
+
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
+ runnable->Run();
+}
+
+/* static */
+void nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ AddScriptRunner(runnable.forget());
+}
+
+/* static */ bool nsContentUtils::IsSafeToRunScript() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "This static variable only makes sense on the main thread!");
+ return sScriptBlockerCount == 0;
+}
+
+/* static */
+void nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->RunInStableState(std::move(aRunnable));
+}
+
+/* static */
+void nsContentUtils::AddPendingIDBTransaction(
+ already_AddRefed<nsIRunnable> aTransaction) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->AddPendingIDBTransaction(
+ std::move(aTransaction));
+}
+
+/* static */
+bool nsContentUtils::IsInStableOrMetaStableState() {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ return CycleCollectedJSContext::Get()->IsInStableOrMetaStableState();
+}
+
+/* static */
+void nsContentUtils::HidePopupsInDocument(Document* aDocument) {
+ RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance();
+ if (!pm || !aDocument) {
+ return;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> docShellToHide = aDocument->GetDocShell();
+ if (docShellToHide) {
+ pm->HidePopupsInDocShell(docShellToHide);
+ }
+}
+
+/* static */
+already_AddRefed<nsIDragSession> nsContentUtils::GetDragSession() {
+ nsCOMPtr<nsIDragSession> dragSession;
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (dragService) dragService->GetCurrentSession(getter_AddRefs(dragSession));
+ return dragSession.forget();
+}
+
+/* static */
+nsresult nsContentUtils::SetDataTransferInEvent(WidgetDragEvent* aDragEvent) {
+ if (aDragEvent->mDataTransfer || !aDragEvent->IsTrusted()) {
+ return NS_OK;
+ }
+
+ // For dragstart events, the data transfer object is
+ // created before the event fires, so it should already be set. For other
+ // drag events, get the object from the drag session.
+ NS_ASSERTION(aDragEvent->mMessage != eDragStart,
+ "draggesture event created without a dataTransfer");
+
+ nsCOMPtr<nsIDragSession> dragSession = GetDragSession();
+ NS_ENSURE_TRUE(dragSession, NS_OK); // no drag in progress
+
+ RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
+ if (!initialDataTransfer) {
+ // A dataTransfer won't exist when a drag was started by some other
+ // means, for instance calling the drag service directly, or a drag
+ // from another application. In either case, a new dataTransfer should
+ // be created that reflects the data.
+ initialDataTransfer =
+ new DataTransfer(aDragEvent->mTarget, aDragEvent->mMessage, true, -1);
+
+ // now set it in the drag session so we don't need to create it again
+ dragSession->SetDataTransfer(initialDataTransfer);
+ }
+
+ bool isCrossDomainSubFrameDrop = false;
+ if (aDragEvent->mMessage == eDrop) {
+ isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent);
+ }
+
+ // each event should use a clone of the original dataTransfer.
+ initialDataTransfer->Clone(
+ aDragEvent->mTarget, aDragEvent->mMessage, aDragEvent->mUserCancelled,
+ isCrossDomainSubFrameDrop, getter_AddRefs(aDragEvent->mDataTransfer));
+ if (NS_WARN_IF(!aDragEvent->mDataTransfer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // for the dragenter and dragover events, initialize the drop effect
+ // from the drop action, which platform specific widget code sets before
+ // the event is fired based on the keyboard state.
+ if (aDragEvent->mMessage == eDragEnter || aDragEvent->mMessage == eDragOver) {
+ uint32_t action;
+ dragSession->GetDragAction(&action);
+ uint32_t effectAllowed = aDragEvent->mDataTransfer->EffectAllowedInt();
+ aDragEvent->mDataTransfer->SetDropEffectInt(
+ FilterDropEffect(action, effectAllowed));
+ } else if (aDragEvent->mMessage == eDrop ||
+ aDragEvent->mMessage == eDragEnd) {
+ // For the drop and dragend events, set the drop effect based on the
+ // last value that the dropEffect had. This will have been set in
+ // EventStateManager::PostHandleEvent for the last dragenter or
+ // dragover event.
+ aDragEvent->mDataTransfer->SetDropEffectInt(
+ initialDataTransfer->DropEffectInt());
+ }
+
+ return NS_OK;
+}
+
+/* static */
+uint32_t nsContentUtils::FilterDropEffect(uint32_t aAction,
+ uint32_t aEffectAllowed) {
+ // It is possible for the drag action to include more than one action, but
+ // the widget code which sets the action from the keyboard state should only
+ // be including one. If multiple actions were set, we just consider them in
+ // the following order:
+ // copy, link, move
+ if (aAction & nsIDragService::DRAGDROP_ACTION_COPY)
+ aAction = nsIDragService::DRAGDROP_ACTION_COPY;
+ else if (aAction & nsIDragService::DRAGDROP_ACTION_LINK)
+ aAction = nsIDragService::DRAGDROP_ACTION_LINK;
+ else if (aAction & nsIDragService::DRAGDROP_ACTION_MOVE)
+ aAction = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // Filter the action based on the effectAllowed. If the effectAllowed
+ // doesn't include the action, then that action cannot be done, so adjust
+ // the action to something that is allowed. For a copy, adjust to move or
+ // link. For a move, adjust to copy or link. For a link, adjust to move or
+ // link. Otherwise, use none.
+ if (aAction & aEffectAllowed ||
+ aEffectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED)
+ return aAction;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_MOVE)
+ return nsIDragService::DRAGDROP_ACTION_MOVE;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_COPY)
+ return nsIDragService::DRAGDROP_ACTION_COPY;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_LINK)
+ return nsIDragService::DRAGDROP_ACTION_LINK;
+ return nsIDragService::DRAGDROP_ACTION_NONE;
+}
+
+/* static */
+bool nsContentUtils::CheckForSubFrameDrop(nsIDragSession* aDragSession,
+ WidgetDragEvent* aDropEvent) {
+ nsCOMPtr<nsIContent> target =
+ nsIContent::FromEventTargetOrNull(aDropEvent->mOriginalTarget);
+ if (!target) {
+ return true;
+ }
+
+ // Always allow dropping onto chrome shells.
+ BrowsingContext* targetBC = target->OwnerDoc()->GetBrowsingContext();
+ if (targetBC->IsChrome()) {
+ return false;
+ }
+
+ WindowContext* targetWC = target->OwnerDoc()->GetWindowContext();
+
+ // If there is no source browsing context, then this is a drag from another
+ // application, which should be allowed.
+ RefPtr<WindowContext> sourceWC;
+ aDragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
+ if (sourceWC) {
+ // Get each successive parent of the source document and compare it to
+ // the drop document. If they match, then this is a drag from a child frame.
+ for (sourceWC = sourceWC->GetParentWindowContext(); sourceWC;
+ sourceWC = sourceWC->GetParentWindowContext()) {
+ // If the source and the target match, then the drag started in a
+ // descendant frame. If the source is discarded, err on the side of
+ // caution and treat it as a subframe drag.
+ if (sourceWC == targetWC || sourceWC->IsDiscarded()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::URIIsLocalFile(nsIURI* aURI) {
+ bool isFile;
+ nsCOMPtr<nsINetUtil> util = mozilla::components::IO::Service();
+
+ // Important: we do NOT test the entire URI chain here!
+ return util &&
+ NS_SUCCEEDED(util->ProtocolHasFlags(
+ aURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &isFile)) &&
+ isFile;
+}
+
+/* static */
+JSContext* nsContentUtils::GetCurrentJSContext() {
+ MOZ_ASSERT(IsInitialized());
+ if (!IsJSAPIActive()) {
+ return nullptr;
+ }
+ return danger::GetJSContext();
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToLowerInSitu(StringType& aStr) {
+ CharType* iter = aStr.BeginWriting();
+ CharType* end = aStr.EndWriting();
+ MOZ_ASSERT(iter && end);
+
+ while (iter != end) {
+ CharType c = *iter;
+ if (c >= 'A' && c <= 'Z') {
+ *iter = c + ('a' - 'A');
+ }
+ ++iter;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(nsAString& aStr) {
+ return _ASCIIToLowerInSitu<nsAString, char16_t>(aStr);
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(nsACString& aStr) {
+ return _ASCIIToLowerInSitu<nsACString, char>(aStr);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToLowerCopy(const StringType& aSource, StringType& aDest) {
+ uint32_t len = aSource.Length();
+ aDest.SetLength(len);
+ MOZ_ASSERT(aDest.Length() == len);
+
+ CharType* dest = aDest.BeginWriting();
+ MOZ_ASSERT(dest);
+
+ const CharType* iter = aSource.BeginReading();
+ const CharType* end = aSource.EndReading();
+ while (iter != end) {
+ CharType c = *iter;
+ *dest = (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
+ ++iter;
+ ++dest;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(const nsAString& aSource, nsAString& aDest) {
+ return _ASCIIToLowerCopy<nsAString, char16_t>(aSource, aDest);
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(const nsACString& aSource,
+ nsACString& aDest) {
+ return _ASCIIToLowerCopy<nsACString, char>(aSource, aDest);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToUpperInSitu(StringType& aStr) {
+ CharType* iter = aStr.BeginWriting();
+ CharType* end = aStr.EndWriting();
+ MOZ_ASSERT(iter && end);
+
+ while (iter != end) {
+ CharType c = *iter;
+ if (c >= 'a' && c <= 'z') {
+ *iter = c + ('A' - 'a');
+ }
+ ++iter;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(nsAString& aStr) {
+ return _ASCIIToUpperInSitu<nsAString, char16_t>(aStr);
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(nsACString& aStr) {
+ return _ASCIIToUpperInSitu<nsACString, char>(aStr);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToUpperCopy(const StringType& aSource, StringType& aDest) {
+ uint32_t len = aSource.Length();
+ aDest.SetLength(len);
+ MOZ_ASSERT(aDest.Length() == len);
+
+ CharType* dest = aDest.BeginWriting();
+ MOZ_ASSERT(dest);
+
+ const CharType* iter = aSource.BeginReading();
+ const CharType* end = aSource.EndReading();
+ while (iter != end) {
+ CharType c = *iter;
+ *dest = (c >= 'a' && c <= 'z') ? c + ('A' - 'a') : c;
+ ++iter;
+ ++dest;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(const nsAString& aSource, nsAString& aDest) {
+ return _ASCIIToUpperCopy<nsAString, char16_t>(aSource, aDest);
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(const nsACString& aSource,
+ nsACString& aDest) {
+ return _ASCIIToUpperCopy<nsACString, char>(aSource, aDest);
+}
+
+/* static */
+bool nsContentUtils::EqualsIgnoreASCIICase(nsAtom* aAtom1, nsAtom* aAtom2) {
+ if (aAtom1 == aAtom2) {
+ return true;
+ }
+
+ // If both are ascii lowercase already, we know that the slow comparison
+ // below is going to return false.
+ if (aAtom1->IsAsciiLowercase() && aAtom2->IsAsciiLowercase()) {
+ return false;
+ }
+
+ return EqualsIgnoreASCIICase(nsDependentAtomString(aAtom1),
+ nsDependentAtomString(aAtom2));
+}
+
+/* static */
+bool nsContentUtils::EqualsIgnoreASCIICase(const nsAString& aStr1,
+ const nsAString& aStr2) {
+ uint32_t len = aStr1.Length();
+ if (len != aStr2.Length()) {
+ return false;
+ }
+
+ const char16_t* str1 = aStr1.BeginReading();
+ const char16_t* str2 = aStr2.BeginReading();
+ const char16_t* end = str1 + len;
+
+ while (str1 < end) {
+ char16_t c1 = *str1++;
+ char16_t c2 = *str2++;
+
+ // First check if any bits other than the 0x0020 differs
+ if ((c1 ^ c2) & 0xffdf) {
+ return false;
+ }
+
+ // We know they can only differ in the 0x0020 bit.
+ // Likely the two chars are the same, so check that first
+ if (c1 != c2) {
+ // They do differ, but since it's only in the 0x0020 bit, check if it's
+ // the same ascii char, but just differing in case
+ char16_t c1Upper = c1 & 0xffdf;
+ if (!('A' <= c1Upper && c1Upper <= 'Z')) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool nsContentUtils::StringContainsASCIIUpper(const nsAString& aStr) {
+ const char16_t* iter = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+ while (iter != end) {
+ char16_t c = *iter;
+ if (c >= 'A' && c <= 'Z') {
+ return true;
+ }
+ ++iter;
+ }
+
+ return false;
+}
+
+/* static */
+nsIInterfaceRequestor* nsContentUtils::SameOriginChecker() {
+ if (!sSameOriginChecker) {
+ sSameOriginChecker = new SameOriginCheckerImpl();
+ NS_ADDREF(sSameOriginChecker);
+ }
+ return sSameOriginChecker;
+}
+
+/* static */
+nsresult nsContentUtils::CheckSameOrigin(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel) {
+ if (!nsContentUtils::GetSecurityManager()) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aOldChannel, getter_AddRefs(oldPrincipal));
+
+ nsCOMPtr<nsIURI> newURI;
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ nsCOMPtr<nsIURI> newOriginalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI);
+
+ nsresult rv = oldPrincipal->CheckMayLoad(newURI, false);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = oldPrincipal->CheckMayLoad(newOriginalURI, false);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(SameOriginCheckerImpl, nsIChannelEventSink,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+SameOriginCheckerImpl::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ MOZ_ASSERT(aNewChannel, "Redirecting to null channel?");
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
+ if (NS_SUCCEEDED(rv)) {
+ cb->OnRedirectVerifyCallback(NS_OK);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+SameOriginCheckerImpl::GetInterface(const nsIID& aIID, void** aResult) {
+ return QueryInterface(aIID, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::GetASCIIOrigin(nsIURI* aURI, nsACString& aOrigin) {
+ MOZ_ASSERT(aURI, "missing uri");
+
+ // For Blob URI, the path is the URL of the owning page.
+ if (aURI->SchemeIs(BLOBURI_SCHEME)) {
+ nsAutoCString path;
+ nsresult rv = aURI->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), path);
+ if (NS_FAILED(rv)) {
+ aOrigin.AssignLiteral("null");
+ return NS_OK;
+ }
+
+ return GetASCIIOrigin(uri, aOrigin);
+ }
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString host;
+ nsresult rv = uri->GetAsciiHost(host);
+
+ if (NS_SUCCEEDED(rv) && !host.IsEmpty()) {
+ nsAutoCString userPass;
+ uri->GetUserPass(userPass);
+
+ nsAutoCString prePath;
+ if (!userPass.IsEmpty()) {
+ rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = uri->GetPrePath(prePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOrigin = prePath;
+ } else {
+ aOrigin.AssignLiteral("null");
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult nsContentUtils::GetUTFOrigin(nsIPrincipal* aPrincipal,
+ nsAString& aOrigin) {
+ MOZ_ASSERT(aPrincipal, "missing principal");
+
+ aOrigin.Truncate();
+ nsAutoCString asciiOrigin;
+
+ nsresult rv = aPrincipal->GetAsciiOrigin(asciiOrigin);
+ if (NS_FAILED(rv)) {
+ asciiOrigin.AssignLiteral("null");
+ }
+
+ CopyUTF8toUTF16(asciiOrigin, aOrigin);
+ return NS_OK;
+}
+
+/* static */
+nsresult nsContentUtils::GetUTFOrigin(nsIURI* aURI, nsAString& aOrigin) {
+ MOZ_ASSERT(aURI, "missing uri");
+ nsresult rv;
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Check if either URI has a special origin.
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(aURI);
+ if (uriWithSpecialOrigin) {
+ nsCOMPtr<nsIURI> origin;
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetUTFOrigin(origin, aOrigin);
+ }
+#endif
+
+ nsAutoCString asciiOrigin;
+ rv = GetASCIIOrigin(aURI, asciiOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(asciiOrigin, aOrigin);
+ return NS_OK;
+}
+
+/* static */
+bool nsContentUtils::CheckMayLoad(nsIPrincipal* aPrincipal,
+ nsIChannel* aChannel,
+ bool aAllowIfInheritsPrincipal) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return NS_SUCCEEDED(
+ aPrincipal->CheckMayLoad(channelURI, aAllowIfInheritsPrincipal));
+}
+
+/* static */
+bool nsContentUtils::CanAccessNativeAnon() {
+ return LegacyIsCallerChromeOrNativeCode();
+}
+
+/* static */
+nsresult nsContentUtils::DispatchXULCommand(nsIContent* aTarget, bool aTrusted,
+ Event* aSourceEvent,
+ PresShell* aPresShell, bool aCtrl,
+ bool aAlt, bool aShift, bool aMeta,
+ uint16_t aInputSource,
+ int16_t aButton) {
+ NS_ENSURE_STATE(aTarget);
+ Document* doc = aTarget->OwnerDoc();
+ nsPresContext* presContext = doc->GetPresContext();
+
+ RefPtr<XULCommandEvent> xulCommand =
+ new XULCommandEvent(doc, presContext, nullptr);
+ xulCommand->InitCommandEvent(u"command"_ns, true, true,
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
+ 0, aCtrl, aAlt, aShift, aMeta, aButton,
+ aSourceEvent, aInputSource, IgnoreErrors());
+
+ if (aPresShell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ return aPresShell->HandleDOMEventWithTarget(aTarget, xulCommand, &status);
+ }
+
+ ErrorResult rv;
+ aTarget->DispatchEvent(*xulCommand, rv);
+ return rv.StealNSResult();
+}
+
+// static
+nsresult nsContentUtils::WrapNative(JSContext* cx, nsISupports* native,
+ nsWrapperCache* cache, const nsIID* aIID,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping) {
+ MOZ_ASSERT(cx == GetCurrentJSContext());
+
+ if (!native) {
+ vp.setNull();
+
+ return NS_OK;
+ }
+
+ JSObject* wrapper = xpc_FastGetCachedWrapper(cx, cache, vp);
+ if (wrapper) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(sXPConnect, NS_ERROR_UNEXPECTED);
+
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ nsresult rv = sXPConnect->WrapNativeToJSVal(cx, scope, native, cache, aIID,
+ aAllowWrapping, vp);
+ return rv;
+}
+
+nsresult nsContentUtils::CreateArrayBuffer(JSContext* aCx,
+ const nsACString& aData,
+ JSObject** aResult) {
+ if (!aCx) {
+ return NS_ERROR_FAILURE;
+ }
+
+ size_t dataLen = aData.Length();
+ *aResult = JS::NewArrayBuffer(aCx, dataLen);
+ if (!*aResult) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dataLen > 0) {
+ NS_ASSERTION(JS::IsArrayBufferObject(*aResult), "What happened?");
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ memcpy(JS::GetArrayBufferData(*aResult, &isShared, nogc),
+ aData.BeginReading(), dataLen);
+ MOZ_ASSERT(!isShared);
+ }
+
+ return NS_OK;
+}
+
+void nsContentUtils::StripNullChars(const nsAString& aInStr,
+ nsAString& aOutStr) {
+ // In common cases where we don't have nulls in the
+ // string we can simple simply bypass the checking code.
+ int32_t firstNullPos = aInStr.FindChar('\0');
+ if (firstNullPos == kNotFound) {
+ aOutStr.Assign(aInStr);
+ return;
+ }
+
+ aOutStr.SetCapacity(aInStr.Length() - 1);
+ nsAString::const_iterator start, end;
+ aInStr.BeginReading(start);
+ aInStr.EndReading(end);
+ while (start != end) {
+ if (*start != '\0') aOutStr.Append(*start);
+ ++start;
+ }
+}
+
+struct ClassMatchingInfo {
+ AtomArray mClasses;
+ nsCaseTreatment mCaseTreatment;
+};
+
+// static
+bool nsContentUtils::MatchClassNames(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAtom, void* aData) {
+ // We can't match if there are no class names
+ const nsAttrValue* classAttr = aElement->GetClasses();
+ if (!classAttr) {
+ return false;
+ }
+
+ // need to match *all* of the classes
+ ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData);
+ uint32_t length = info->mClasses.Length();
+ if (!length) {
+ // If we actually had no classes, don't match.
+ return false;
+ }
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (!classAttr->Contains(info->mClasses[i], info->mCaseTreatment)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// static
+void nsContentUtils::DestroyClassNameArray(void* aData) {
+ ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData);
+ delete info;
+}
+
+// static
+void* nsContentUtils::AllocClassMatchingInfo(nsINode* aRootNode,
+ const nsString* aClasses) {
+ nsAttrValue attrValue;
+ attrValue.ParseAtomArray(*aClasses);
+ // nsAttrValue::Equals is sensitive to order, so we'll send an array
+ auto* info = new ClassMatchingInfo;
+ if (attrValue.Type() == nsAttrValue::eAtomArray) {
+ info->mClasses = std::move(attrValue.GetAtomArrayValue()->mArray);
+ } else if (attrValue.Type() == nsAttrValue::eAtom) {
+ info->mClasses.AppendElement(attrValue.GetAtomValue());
+ }
+
+ info->mCaseTreatment =
+ aRootNode->OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks
+ ? eIgnoreCase
+ : eCaseMatters;
+ return info;
+}
+
+// static
+bool nsContentUtils::IsFocusedContent(const nsIContent* aContent) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+
+ return fm && fm->GetFocusedElement() == aContent;
+}
+
+bool nsContentUtils::HasScrollgrab(nsIContent* aContent) {
+ // If we ever standardize this feature we'll want to hook this up properly
+ // again. For now we're removing all the DOM-side code related to it but
+ // leaving the layout and APZ handling for it in place.
+ return false;
+}
+
+void nsContentUtils::FlushLayoutForTree(nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow) {
+ return;
+ }
+
+ // Note that because FlushPendingNotifications flushes parents, this
+ // is O(N^2) in docshell tree depth. However, the docshell tree is
+ // usually pretty shallow.
+
+ if (RefPtr<Document> doc = aWindow->GetDoc()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
+ int32_t i = 0, i_end;
+ docShell->GetInProcessChildCount(&i_end);
+ for (; i < i_end; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ if (docShell->GetInProcessChildAt(i, getter_AddRefs(item)) == NS_OK &&
+ item) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = item->GetWindow()) {
+ FlushLayoutForTree(win);
+ }
+ }
+ }
+ }
+}
+
+void nsContentUtils::RemoveNewlines(nsString& aString) { aString.StripCRLF(); }
+
+void nsContentUtils::PlatformToDOMLineBreaks(nsString& aString) {
+ if (!PlatformToDOMLineBreaks(aString, fallible)) {
+ aString.AllocFailed(aString.Length());
+ }
+}
+
+bool nsContentUtils::PlatformToDOMLineBreaks(nsString& aString,
+ const fallible_t& aFallible) {
+ if (aString.FindChar(char16_t('\r')) != -1) {
+ // Windows linebreaks: Map CRLF to LF:
+ if (!aString.ReplaceSubstring(u"\r\n", u"\n", aFallible)) {
+ return false;
+ }
+
+ // Mac linebreaks: Map any remaining CR to LF:
+ if (!aString.ReplaceSubstring(u"\r", u"\n", aFallible)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void nsContentUtils::PopulateStringFromStringBuffer(nsStringBuffer* aBuf,
+ nsAString& aResultString) {
+ MOZ_ASSERT(aBuf, "Expecting a non-null string buffer");
+
+ uint32_t stringLen = NS_strlen(static_cast<char16_t*>(aBuf->Data()));
+
+ // SANITY CHECK: In case the nsStringBuffer isn't correctly
+ // null-terminated, let's clamp its length using the allocated size, to be
+ // sure the resulting string doesn't sample past the end of the the buffer.
+ // (Note that StorageSize() is in units of bytes, so we have to convert that
+ // to units of PRUnichars, and subtract 1 for the null-terminator.)
+ uint32_t allocStringLen = (aBuf->StorageSize() / sizeof(char16_t)) - 1;
+ MOZ_ASSERT(stringLen <= allocStringLen,
+ "string buffer lacks null terminator!");
+ stringLen = std::min(stringLen, allocStringLen);
+
+ aBuf->ToString(stringLen, aResultString);
+}
+
+already_AddRefed<nsContentList> nsContentUtils::GetElementsByClassName(
+ nsINode* aRootNode, const nsAString& aClasses) {
+ MOZ_ASSERT(aRootNode, "Must have root node");
+
+ return GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(
+ aRootNode, MatchClassNames, DestroyClassNameArray, AllocClassMatchingInfo,
+ aClasses);
+}
+
+PresShell* nsContentUtils::FindPresShellForDocument(const Document* aDocument) {
+ const Document* doc = aDocument;
+ Document* displayDoc = doc->GetDisplayDocument();
+ if (displayDoc) {
+ doc = displayDoc;
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (presShell) {
+ return presShell;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+ while (docShellTreeItem) {
+ // We may be in a display:none subdocument, or we may not have a presshell
+ // created yet.
+ // Walk the docshell tree to find the nearest container that has a
+ // presshell, and return that.
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(docShellTreeItem);
+ if (PresShell* presShell = docShell->GetPresShell()) {
+ return presShell;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShellTreeItem->GetInProcessParent(getter_AddRefs(parent));
+ docShellTreeItem = parent;
+ }
+
+ return nullptr;
+}
+
+/* static */
+nsPresContext* nsContentUtils::FindPresContextForDocument(
+ const Document* aDocument) {
+ if (PresShell* presShell = FindPresShellForDocument(aDocument)) {
+ return presShell->GetPresContext();
+ }
+ return nullptr;
+}
+
+nsIWidget* nsContentUtils::WidgetForDocument(const Document* aDocument) {
+ PresShell* presShell = FindPresShellForDocument(aDocument);
+ if (!presShell) {
+ return nullptr;
+ }
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+ nsView* rootView = vm->GetRootView();
+ if (!rootView) {
+ return nullptr;
+ }
+ nsView* displayRoot = nsViewManager::GetDisplayRootFor(rootView);
+ if (!displayRoot) {
+ return nullptr;
+ }
+ return displayRoot->GetNearestWidget(nullptr);
+}
+
+nsIWidget* nsContentUtils::WidgetForContent(const nsIContent* aContent) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ frame = nsLayoutUtils::GetDisplayRootFrame(frame);
+
+ nsView* view = frame->GetView();
+ if (view) {
+ return view->GetWidget();
+ }
+ }
+
+ return nullptr;
+}
+
+WindowRenderer* nsContentUtils::WindowRendererForContent(
+ const nsIContent* aContent) {
+ nsIWidget* widget = nsContentUtils::WidgetForContent(aContent);
+ if (widget) {
+ return widget->GetWindowRenderer();
+ }
+
+ return nullptr;
+}
+
+WindowRenderer* nsContentUtils::WindowRendererForDocument(
+ const Document* aDoc) {
+ nsIWidget* widget = nsContentUtils::WidgetForDocument(aDoc);
+ if (widget) {
+ return widget->GetWindowRenderer();
+ }
+
+ return nullptr;
+}
+
+bool nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ return (StaticPrefs::dom_allow_XUL_XBL_for_file() &&
+ aPrincipal->SchemeIs("file")) ||
+ IsSitePermAllow(aPrincipal, "allowXULXBL"_ns);
+}
+
+bool nsContentUtils::IsPDFJSEnabled() {
+ nsCOMPtr<nsIStreamConverter> conv = do_CreateInstance(
+ "@mozilla.org/streamconv;1?from=application/pdf&to=text/html");
+ return conv;
+}
+
+bool nsContentUtils::IsPDFJS(nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return false;
+ }
+ nsAutoCString spec;
+ nsresult rv = aPrincipal->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+ return spec.EqualsLiteral("resource://pdf.js/web/viewer.html");
+}
+
+bool nsContentUtils::IsSystemOrPDFJS(JSContext* aCx, JSObject*) {
+ nsIPrincipal* principal = SubjectPrincipal(aCx);
+ return principal && (principal->IsSystemPrincipal() || IsPDFJS(principal));
+}
+
+already_AddRefed<nsIDocumentLoaderFactory>
+nsContentUtils::FindInternalContentViewer(const nsACString& aType,
+ ContentViewerType* aLoaderType) {
+ if (aLoaderType) {
+ *aLoaderType = TYPE_UNSUPPORTED;
+ }
+
+ // one helper factory, please
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
+ if (!catMan) return nullptr;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docFactory;
+
+ nsCString contractID;
+ nsresult rv =
+ catMan->GetCategoryEntry("Gecko-Content-Viewers", aType, contractID);
+ if (NS_SUCCEEDED(rv)) {
+ docFactory = do_GetService(contractID.get());
+ if (docFactory && aLoaderType) {
+ if (contractID.EqualsLiteral(CONTENT_DLF_CONTRACTID))
+ *aLoaderType = TYPE_CONTENT;
+ else if (contractID.EqualsLiteral(PLUGIN_DLF_CONTRACTID))
+ *aLoaderType = TYPE_FALLBACK;
+ else
+ *aLoaderType = TYPE_UNKNOWN;
+ }
+ return docFactory.forget();
+ }
+
+ if (DecoderTraits::IsSupportedInVideoDocument(aType)) {
+ docFactory =
+ do_GetService("@mozilla.org/content/document-loader-factory;1");
+ if (docFactory && aLoaderType) {
+ *aLoaderType = TYPE_CONTENT;
+ }
+ return docFactory.forget();
+ }
+
+ return nullptr;
+}
+
+static void ReportPatternCompileFailure(nsAString& aPattern,
+ const Document* aDocument,
+ JS::MutableHandle<JS::Value> error,
+ JSContext* cx) {
+ JS::AutoSaveExceptionState savedExc(cx);
+ JS::Rooted<JSObject*> exnObj(cx, &error.toObject());
+ JS::Rooted<JS::Value> messageVal(cx);
+ if (!JS_GetProperty(cx, exnObj, "message", &messageVal)) {
+ return;
+ }
+ JS::Rooted<JSString*> messageStr(cx, messageVal.toString());
+ MOZ_ASSERT(messageStr);
+
+ AutoTArray<nsString, 2> strings;
+ strings.AppendElement(aPattern);
+ if (!AssignJSString(cx, *strings.AppendElement(), messageStr)) {
+ return;
+ }
+
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
+ aDocument, nsContentUtils::eDOM_PROPERTIES,
+ "PatternAttributeCompileFailure", strings);
+ savedExc.drop();
+}
+
+// static
+Maybe<bool> nsContentUtils::IsPatternMatching(nsAString& aValue,
+ nsAString& aPattern,
+ const Document* aDocument,
+ bool aHasMultiple) {
+ NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
+
+ // The fact that we're using a JS regexp under the hood should not be visible
+ // to things like window onerror handlers, so we don't initialize our JSAPI
+ // with the document's window (which may not exist anyway).
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ AutoDisableJSInterruptCallback disabler(cx);
+
+ // We can use the junk scope here, because we're just using it for regexp
+ // evaluation, not actual script execution, and we disable statics so that the
+ // evaluation does not interact with the execution global.
+ JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
+
+ // Check if the pattern by itself is valid first, and not that it only becomes
+ // valid once we add ^(?: and )$.
+ JS::Rooted<JS::Value> error(cx);
+ if (!JS::CheckRegExpSyntax(
+ cx, static_cast<char16_t*>(aPattern.BeginWriting()),
+ aPattern.Length(), JS::RegExpFlag::Unicode, &error)) {
+ return Nothing();
+ }
+
+ if (!error.isUndefined()) {
+ ReportPatternCompileFailure(aPattern, aDocument, &error, cx);
+ return Some(true);
+ }
+
+ // The pattern has to match the entire value.
+ aPattern.InsertLiteral(u"^(?:", 0);
+ aPattern.AppendLiteral(")$");
+
+ JS::Rooted<JSObject*> re(
+ cx,
+ JS::NewUCRegExpObject(cx, static_cast<char16_t*>(aPattern.BeginWriting()),
+ aPattern.Length(), JS::RegExpFlag::Unicode));
+ if (!re) {
+ return Nothing();
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::NullValue());
+ if (!aHasMultiple) {
+ size_t idx = 0;
+ if (!JS::ExecuteRegExpNoStatics(
+ cx, re, static_cast<char16_t*>(aValue.BeginWriting()),
+ aValue.Length(), &idx, true, &rval)) {
+ return Nothing();
+ }
+ return Some(!rval.isNull());
+ }
+
+ HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsAString& value = tokenizer.nextToken();
+ size_t idx = 0;
+ if (!JS::ExecuteRegExpNoStatics(
+ cx, re, static_cast<const char16_t*>(value.BeginReading()),
+ value.Length(), &idx, true, &rval)) {
+ return Nothing();
+ }
+ if (rval.isNull()) {
+ return Some(false);
+ }
+ }
+ return Some(true);
+}
+
+// static
+nsresult nsContentUtils::URIInheritsSecurityContext(nsIURI* aURI,
+ bool* aResult) {
+ // Note: about:blank URIs do NOT inherit the security context from the
+ // current document, which is what this function tests for...
+ return NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, aResult);
+}
+
+// static
+bool nsContentUtils::ChannelShouldInheritPrincipal(
+ nsIPrincipal* aLoadingPrincipal, nsIURI* aURI, bool aInheritForAboutBlank,
+ bool aForceInherit) {
+ MOZ_ASSERT(aLoadingPrincipal,
+ "Can not check inheritance without a principal");
+
+ // Only tell the channel to inherit if it can't provide its own security
+ // context.
+ //
+ // XXX: If this is ever changed, check all callers for what owners
+ // they're passing in. In particular, see the code and
+ // comments in nsDocShell::LoadURI where we fall back on
+ // inheriting the owner if called from chrome. That would be
+ // very wrong if this code changed anything but channels that
+ // can't provide their own security context!
+ //
+ // If aForceInherit is true, we will inherit, even for a channel that
+ // can provide its own security context. This is used for srcdoc loads.
+ bool inherit = aForceInherit;
+ if (!inherit) {
+ bool uriInherits;
+ // We expect URIInheritsSecurityContext to return success for an
+ // about:blank URI, so don't call NS_IsAboutBlank() if this call fails.
+ // This condition needs to match the one in nsDocShell::InternalLoad where
+ // we're checking for things that will use the owner.
+ inherit =
+ (NS_SUCCEEDED(URIInheritsSecurityContext(aURI, &uriInherits)) &&
+ (uriInherits || (aInheritForAboutBlank && NS_IsAboutBlank(aURI)))) ||
+ //
+ // file: uri special-casing
+ //
+ // If this is a file: load opened from another file: then it may need
+ // to inherit the owner from the referrer so they can script each other.
+ // If we don't set the owner explicitly then each file: gets an owner
+ // based on its own codebase later.
+ //
+ (URIIsLocalFile(aURI) &&
+ NS_SUCCEEDED(aLoadingPrincipal->CheckMayLoad(aURI, false)) &&
+ // One more check here. CheckMayLoad will always return true for the
+ // system principal, but we do NOT want to inherit in that case.
+ !aLoadingPrincipal->IsSystemPrincipal());
+ }
+ return inherit;
+}
+
+/* static */
+bool nsContentUtils::IsCutCopyAllowed(Document* aDocument,
+ nsIPrincipal& aSubjectPrincipal) {
+ if (StaticPrefs::dom_allow_cut_copy() && aDocument &&
+ aDocument->HasValidTransientUserGestureActivation()) {
+ return true;
+ }
+
+ return PrincipalHasPermission(aSubjectPrincipal, nsGkAtoms::clipboardWrite);
+}
+
+/* static */
+bool nsContentUtils::HaveEqualPrincipals(Document* aDoc1, Document* aDoc2) {
+ if (!aDoc1 || !aDoc2) {
+ return false;
+ }
+ bool principalsEqual = false;
+ aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
+ return principalsEqual;
+}
+
+/* static */
+bool nsContentUtils::HasPluginWithUncontrolledEventDispatch(
+ nsIContent* aContent) {
+ return false;
+}
+
+/* static */
+void nsContentUtils::FireMutationEventsForDirectParsing(
+ Document* aDoc, nsIContent* aDest, int32_t aOldChildCount) {
+ // Fire mutation events. Optimize for the case when there are no listeners
+ int32_t newChildCount = aDest->GetChildCount();
+ if (newChildCount && nsContentUtils::HasMutationListeners(
+ aDoc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
+ AutoTArray<nsCOMPtr<nsIContent>, 50> childNodes;
+ NS_ASSERTION(newChildCount - aOldChildCount >= 0,
+ "What, some unexpected dom mutation has happened?");
+ childNodes.SetCapacity(newChildCount - aOldChildCount);
+ for (nsIContent* child = aDest->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ childNodes.AppendElement(child);
+ }
+ FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes);
+ }
+}
+
+/* static */
+const Document* nsContentUtils::GetInProcessSubtreeRootDocument(
+ const Document* aDoc) {
+ if (!aDoc) {
+ return nullptr;
+ }
+ const Document* doc = aDoc;
+ while (doc->GetInProcessParentDocument()) {
+ doc = doc->GetInProcessParentDocument();
+ }
+ return doc;
+}
+
+// static
+int32_t nsContentUtils::GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame,
+ int32_t aOffset) {
+ // The structure of the anonymous frames within a text control frame is
+ // an optional block frame, followed by an optional br frame.
+
+ // If the offset frame has a child, then this frame is the block which
+ // has the text frames (containing the content) as its children. This will
+ // be the case if we click to the right of any of the text frames, or at the
+ // bottom of the text area.
+ nsIFrame* firstChild = aOffsetFrame->PrincipalChildList().FirstChild();
+ if (firstChild) {
+ // In this case, the passed-in offset is incorrect, and we want the length
+ // of the entire content in the text control frame.
+ return firstChild->GetContent()->Length();
+ }
+
+ if (aOffsetFrame->GetPrevSibling() && !aOffsetFrame->GetNextSibling()) {
+ // In this case, we're actually within the last frame, which is a br
+ // frame. Our offset should therefore be the length of the first child of
+ // our parent.
+ int32_t aOutOffset = aOffsetFrame->GetParent()
+ ->PrincipalChildList()
+ .FirstChild()
+ ->GetContent()
+ ->Length();
+ return aOutOffset;
+ }
+
+ // Otherwise, we're within one of the text frames, in which case our offset
+ // has already been correctly calculated.
+ return aOffset;
+}
+
+// static
+void nsContentUtils::GetSelectionInTextControl(Selection* aSelection,
+ Element* aRoot,
+ uint32_t& aOutStartOffset,
+ uint32_t& aOutEndOffset) {
+ MOZ_ASSERT(aSelection && aRoot);
+
+ // We don't care which end of this selection is anchor and which is focus. In
+ // fact, we explicitly want to know which is the _start_ and which is the
+ // _end_, not anchor vs focus.
+ const nsRange* range = aSelection->GetAnchorFocusRange();
+ if (!range) {
+ // Nothing selected
+ aOutStartOffset = aOutEndOffset = 0;
+ return;
+ }
+
+ // All the node pointers here are raw pointers for performance. We shouldn't
+ // be doing anything in this function that invalidates the node tree.
+ nsINode* startContainer = range->GetStartContainer();
+ uint32_t startOffset = range->StartOffset();
+ nsINode* endContainer = range->GetEndContainer();
+ uint32_t endOffset = range->EndOffset();
+
+ // We have at most two children, consisting of an optional text node followed
+ // by an optional <br>.
+ NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children");
+ nsIContent* firstChild = aRoot->GetFirstChild();
+#ifdef DEBUG
+ nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild();
+ NS_ASSERTION(startContainer == aRoot || startContainer == firstChild ||
+ startContainer == lastChild,
+ "Unexpected startContainer");
+ NS_ASSERTION(endContainer == aRoot || endContainer == firstChild ||
+ endContainer == lastChild,
+ "Unexpected endContainer");
+ // firstChild is either text or a <br> (hence an element).
+ MOZ_ASSERT_IF(firstChild, firstChild->IsText() || firstChild->IsElement());
+#endif
+ if (!firstChild || firstChild->IsElement()) {
+ // No text node, so everything is 0
+ startOffset = endOffset = 0;
+ } else {
+ // First child is text. If the start/end is already in the text node,
+ // or the start of the root node, no change needed. If it's in the root
+ // node but not the start, or in the trailing <br>, we need to set the
+ // offset to the end.
+ if ((startContainer == aRoot && startOffset != 0) ||
+ (startContainer != aRoot && startContainer != firstChild)) {
+ startOffset = firstChild->Length();
+ }
+ if ((endContainer == aRoot && endOffset != 0) ||
+ (endContainer != aRoot && endContainer != firstChild)) {
+ endOffset = firstChild->Length();
+ }
+ }
+
+ MOZ_ASSERT(startOffset <= endOffset);
+ aOutStartOffset = startOffset;
+ aOutEndOffset = endOffset;
+}
+
+// static
+HTMLEditor* nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext) {
+ if (!aPresContext) {
+ return nullptr;
+ }
+ return GetHTMLEditor(aPresContext->GetDocShell());
+}
+
+// static
+HTMLEditor* nsContentUtils::GetHTMLEditor(nsDocShell* aDocShell) {
+ bool isEditable;
+ if (!aDocShell || NS_FAILED(aDocShell->GetEditable(&isEditable)) ||
+ !isEditable) {
+ return nullptr;
+ }
+ return aDocShell->GetHTMLEditor();
+}
+
+// static
+EditorBase* nsContentUtils::GetActiveEditor(nsPresContext* aPresContext) {
+ if (!aPresContext) {
+ return nullptr;
+ }
+
+ return GetActiveEditor(aPresContext->Document()->GetWindow());
+}
+
+// static
+EditorBase* nsContentUtils::GetActiveEditor(nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow || !aWindow->GetExtantDoc()) {
+ return nullptr;
+ }
+
+ // If it's in designMode, nobody can have focus. Therefore, the HTMLEditor
+ // handles all events. I.e., it's focused editor in this case.
+ if (aWindow->GetExtantDoc()->IsInDesignMode()) {
+ return GetHTMLEditor(nsDocShell::Cast(aWindow->GetDocShell()));
+ }
+
+ // If focused element is associated with TextEditor, it must be <input>
+ // element or <textarea> element. Let's return it even if it's in a
+ // contenteditable element.
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ if (Element* focusedElement = nsFocusManager::GetFocusedDescendant(
+ aWindow, nsFocusManager::SearchRange::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow))) {
+ if (TextEditor* textEditor = focusedElement->GetTextEditorInternal()) {
+ return textEditor;
+ }
+ }
+
+ // Otherwise, HTMLEditor may handle inputs even non-editable element has
+ // focus or nobody has focus.
+ return GetHTMLEditor(nsDocShell::Cast(aWindow->GetDocShell()));
+}
+
+// static
+TextEditor* nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
+ const nsIContent* aAnonymousContent) {
+ if (!aAnonymousContent) {
+ return nullptr;
+ }
+ nsIContent* parent = aAnonymousContent->FindFirstNonChromeOnlyAccessContent();
+ if (!parent || parent == aAnonymousContent) {
+ return nullptr;
+ }
+ if (HTMLInputElement* inputElement =
+ HTMLInputElement::FromNodeOrNull(parent)) {
+ return inputElement->GetTextEditorWithoutCreation();
+ }
+ if (HTMLTextAreaElement* textareaElement =
+ HTMLTextAreaElement::FromNodeOrNull(parent)) {
+ return textareaElement->GetTextEditorWithoutCreation();
+ }
+ return nullptr;
+}
+
+// static
+bool nsContentUtils::IsNodeInEditableRegion(nsINode* aNode) {
+ while (aNode) {
+ if (aNode->IsEditable()) {
+ return true;
+ }
+ aNode = aNode->GetParent();
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue) {
+ if (IsForbiddenSystemRequestHeader(aHeader)) {
+ return true;
+ }
+
+ if ((nsContentUtils::IsOverrideMethodHeader(aHeader) &&
+ nsContentUtils::ContainsForbiddenMethod(aValue))) {
+ return true;
+ }
+
+ if (StringBeginsWith(aHeader, "proxy-"_ns,
+ nsCaseInsensitiveCStringComparator) ||
+ StringBeginsWith(aHeader, "sec-"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenSystemRequestHeader(const nsACString& aHeader) {
+ static const char* kInvalidHeaders[] = {"accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "date",
+ "dnt",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "set-cookie",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "via"};
+ for (auto& kInvalidHeader : kInvalidHeaders) {
+ if (aHeader.LowerCaseEqualsASCII(kInvalidHeader)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader) {
+ return (aHeader.LowerCaseEqualsASCII("set-cookie") ||
+ aHeader.LowerCaseEqualsASCII("set-cookie2"));
+}
+
+// static
+bool nsContentUtils::IsOverrideMethodHeader(const nsACString& headerName) {
+ return headerName.EqualsIgnoreCase("x-http-method-override") ||
+ headerName.EqualsIgnoreCase("x-http-method") ||
+ headerName.EqualsIgnoreCase("x-method-override");
+}
+
+// static
+bool nsContentUtils::ContainsForbiddenMethod(const nsACString& headerValue) {
+ bool hasInsecureMethod = false;
+ nsCCharSeparatedTokenizer tokenizer(headerValue, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring& value = tokenizer.nextToken();
+
+ if (value.EqualsIgnoreCase("connect") || value.EqualsIgnoreCase("trace") ||
+ value.EqualsIgnoreCase("track")) {
+ hasInsecureMethod = true;
+ break;
+ }
+ }
+
+ return hasInsecureMethod;
+}
+
+// static
+bool nsContentUtils::IsCorsUnsafeRequestHeaderValue(
+ const nsACString& aHeaderValue) {
+ const char* cur = aHeaderValue.BeginReading();
+ const char* end = aHeaderValue.EndReading();
+
+ while (cur != end) {
+ // Implementation of
+ // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte Is less
+ // than a space but not a horizontal tab
+ if ((*cur < ' ' && *cur != '\t') || *cur == '"' || *cur == '(' ||
+ *cur == ')' || *cur == ':' || *cur == '<' || *cur == '>' ||
+ *cur == '?' || *cur == '@' || *cur == '[' || *cur == '\\' ||
+ *cur == ']' || *cur == '{' || *cur == '}' ||
+ *cur == 0x7F) { // 0x75 is DEL
+ return true;
+ }
+ cur++;
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsAccept(const nsACString& aHeaderValue) {
+ if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+ return false;
+ }
+ return true;
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsContentType(
+ const nsACString& aHeaderValue) {
+ nsAutoCString contentType;
+ nsAutoCString unused;
+
+ if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+ return false;
+ }
+
+ nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return contentType.LowerCaseEqualsLiteral("text/plain") ||
+ contentType.LowerCaseEqualsLiteral(
+ "application/x-www-form-urlencoded") ||
+ contentType.LowerCaseEqualsLiteral("multipart/form-data");
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsLanguage(const nsACString& aHeaderValue) {
+ const char* cur = aHeaderValue.BeginReading();
+ const char* end = aHeaderValue.EndReading();
+
+ while (cur != end) {
+ if ((*cur >= '0' && *cur <= '9') || (*cur >= 'A' && *cur <= 'Z') ||
+ (*cur >= 'a' && *cur <= 'z') || *cur == ' ' || *cur == '*' ||
+ *cur == ',' || *cur == '-' || *cur == '.' || *cur == ';' ||
+ *cur == '=') {
+ cur++;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+// static
+bool nsContentUtils::IsCORSSafelistedRequestHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ // see https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+ if (aValue.Length() > 128) {
+ return false;
+ }
+ return (aName.LowerCaseEqualsLiteral("accept") &&
+ nsContentUtils::IsAllowedNonCorsAccept(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("accept-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("content-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("content-type") &&
+ nsContentUtils::IsAllowedNonCorsContentType(aValue));
+}
+
+mozilla::LogModule* nsContentUtils::ResistFingerprintingLog() {
+ return gResistFingerprintingLog;
+}
+mozilla::LogModule* nsContentUtils::DOMDumpLog() { return sDOMDumpLog; }
+
+bool nsContentUtils::GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ aResult.Truncate();
+ return AppendNodeTextContent(aNode, aDeep, aResult, aFallible);
+}
+
+void nsContentUtils::GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult) {
+ if (!GetNodeTextContent(aNode, aDeep, aResult, fallible)) {
+ NS_ABORT_OOM(0); // Unfortunately we don't know the allocation size
+ }
+}
+
+void nsContentUtils::DestroyMatchString(void* aData) {
+ if (aData) {
+ nsString* matchString = static_cast<nsString*>(aData);
+ delete matchString;
+ }
+}
+
+bool nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) {
+ // Table ordered from most to least likely JS MIME types.
+ static const char* jsTypes[] = {"text/javascript",
+ "text/ecmascript",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "application/x-ecmascript",
+ "text/javascript1.0",
+ "text/javascript1.1",
+ "text/javascript1.2",
+ "text/javascript1.3",
+ "text/javascript1.4",
+ "text/javascript1.5",
+ "text/jscript",
+ "text/livescript",
+ "text/x-ecmascript",
+ "text/x-javascript",
+ nullptr};
+
+ for (uint32_t i = 0; jsTypes[i]; ++i) {
+ if (aMIMEType.LowerCaseEqualsASCII(jsTypes[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsContentUtils::PrefetchPreloadEnabled(nsIDocShell* aDocShell) {
+ //
+ // SECURITY CHECK: disable prefetching and preloading from mailnews!
+ //
+ // walk up the docshell tree to see if any containing
+ // docshell are of type MAIL.
+ //
+
+ if (!aDocShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docshell = aDocShell;
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+
+ do {
+ auto appType = docshell->GetAppType();
+ if (appType == nsIDocShell::APP_TYPE_MAIL) {
+ return false; // do not prefetch, preload, preconnect from mailnews
+ }
+
+ docshell->GetInProcessParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ docshell = do_QueryInterface(parentItem);
+ if (!docshell) {
+ NS_ERROR("cannot get a docshell from a treeItem!");
+ return false;
+ }
+ }
+ } while (parentItem);
+
+ return true;
+}
+
+uint64_t nsContentUtils::GetInnerWindowID(nsIRequest* aRequest) {
+ // can't do anything if there's no nsIRequest!
+ if (!aRequest) {
+ return 0;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ if (NS_FAILED(rv) || !loadGroup) {
+ return 0;
+ }
+
+ return GetInnerWindowID(loadGroup);
+}
+
+uint64_t nsContentUtils::GetInnerWindowID(nsILoadGroup* aLoadGroup) {
+ if (!aLoadGroup) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsresult rv = aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv) || !callbacks) {
+ return 0;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (!loadContext) {
+ return 0;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ rv = loadContext->GetAssociatedWindow(getter_AddRefs(window));
+ if (NS_FAILED(rv) || !window) {
+ return 0;
+ }
+
+ auto* pwindow = nsPIDOMWindowOuter::From(window);
+ if (!pwindow) {
+ return 0;
+ }
+
+ nsPIDOMWindowInner* inner = pwindow->GetCurrentInnerWindow();
+ return inner ? inner->WindowID() : 0;
+}
+
+// static
+void nsContentUtils::MaybeFixIPv6Host(nsACString& aHost) {
+ if (aHost.FindChar(':') != -1) { // Escape IPv6 address
+ MOZ_ASSERT(!aHost.Length() ||
+ (aHost[0] != '[' && aHost[aHost.Length() - 1] != ']'));
+ aHost.Insert('[', 0);
+ aHost.Append(']');
+ }
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI,
+ nsACString& aHost) {
+ aHost.Truncate();
+ nsresult rv = aURI->GetHost(aHost);
+ if (NS_FAILED(rv)) { // Some URIs do not have a host
+ return rv;
+ }
+
+ MaybeFixIPv6Host(aHost);
+
+ return NS_OK;
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI,
+ nsAString& aHost) {
+ nsAutoCString hostname;
+ nsresult rv = GetHostOrIPv6WithBrackets(aURI, hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ CopyUTF8toUTF16(hostname, aHost);
+ return NS_OK;
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIPrincipal* aPrincipal,
+ nsACString& aHost) {
+ nsresult rv = aPrincipal->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) { // Some URIs do not have a host
+ return rv;
+ }
+
+ MaybeFixIPv6Host(aHost);
+ return NS_OK;
+}
+
+CallState nsContentUtils::CallOnAllRemoteChildren(
+ MessageBroadcaster* aManager,
+ const std::function<CallState(BrowserParent*)>& aCallback) {
+ uint32_t browserChildCount = aManager->ChildCount();
+ for (uint32_t j = 0; j < browserChildCount; ++j) {
+ RefPtr<MessageListenerManager> childMM = aManager->GetChildAt(j);
+ if (!childMM) {
+ continue;
+ }
+
+ RefPtr<MessageBroadcaster> nonLeafMM = MessageBroadcaster::From(childMM);
+ if (nonLeafMM) {
+ if (CallOnAllRemoteChildren(nonLeafMM, aCallback) == CallState::Stop) {
+ return CallState::Stop;
+ }
+ continue;
+ }
+
+ mozilla::dom::ipc::MessageManagerCallback* cb = childMM->GetCallback();
+ if (cb) {
+ nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
+ BrowserParent* remote = BrowserParent::GetFrom(fl);
+ if (remote && aCallback) {
+ if (aCallback(remote) == CallState::Stop) {
+ return CallState::Stop;
+ }
+ }
+ }
+ }
+
+ return CallState::Continue;
+}
+
+void nsContentUtils::CallOnAllRemoteChildren(
+ nsPIDOMWindowOuter* aWindow,
+ const std::function<CallState(BrowserParent*)>& aCallback) {
+ nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
+ if (window->IsChromeWindow()) {
+ RefPtr<MessageBroadcaster> windowMM = window->GetMessageManager();
+ if (windowMM) {
+ CallOnAllRemoteChildren(windowMM, aCallback);
+ }
+ }
+}
+
+bool nsContentUtils::IPCTransferableDataItemHasKnownFlavor(
+ const IPCTransferableDataItem& aItem) {
+ // Unknown types are converted to kCustomTypesMime.
+ if (aItem.flavor().EqualsASCII(kCustomTypesMime)) {
+ return true;
+ }
+
+ for (const char* format : DataTransfer::kKnownFormats) {
+ if (aItem.flavor().EqualsASCII(format)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsContentUtils::IPCTransferableDataToTransferable(
+ const IPCTransferableData& aTransferableData, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors) {
+ nsresult rv;
+ const nsTArray<IPCTransferableDataItem>& items = aTransferableData.items();
+ for (const auto& item : items) {
+ if (aFilterUnknownFlavors && !IPCTransferableDataItemHasKnownFlavor(item)) {
+ NS_WARNING(
+ "Ignoring unknown flavor in "
+ "nsContentUtils::IPCTransferableDataToTransferable");
+ continue;
+ }
+
+ if (aAddDataFlavor) {
+ aTransferable->AddDataFlavor(item.flavor().get());
+ }
+
+ nsCOMPtr<nsISupports> transferData;
+ switch (item.data().type()) {
+ case IPCTransferableDataType::TIPCTransferableDataString: {
+ const auto& data = item.data().get_IPCTransferableDataString();
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dataWrapper->SetData(nsDependentSubstring(
+ reinterpret_cast<const char16_t*>(data.data().Data()),
+ data.data().Size() / sizeof(char16_t)));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = dataWrapper;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataCString: {
+ const auto& data = item.data().get_IPCTransferableDataCString();
+ nsCOMPtr<nsISupportsCString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dataWrapper->SetData(nsDependentCSubstring(
+ reinterpret_cast<const char*>(data.data().Data()),
+ data.data().Size()));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = dataWrapper;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataInputStream: {
+ const auto& data = item.data().get_IPCTransferableDataInputStream();
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ AsChars(data.data().AsSpan()),
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = stream.forget();
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataImageContainer: {
+ const auto& data = item.data().get_IPCTransferableDataImageContainer();
+ nsCOMPtr<imgIContainer> container;
+ rv = DeserializeTransferableDataImageContainer(
+ data, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = container;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataBlob: {
+ const auto& data = item.data().get_IPCTransferableDataBlob();
+ transferData = IPCBlobUtils::Deserialize(data.blob());
+ break;
+ }
+ case IPCTransferableDataType::T__None:
+ MOZ_ASSERT_UNREACHABLE();
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), transferData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult nsContentUtils::IPCTransferableDataToTransferable(
+ const IPCTransferableData& aTransferableData, const bool& aIsPrivateData,
+ nsIPrincipal* aRequestingPrincipal,
+ const nsContentPolicyType& aContentPolicyType, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors) {
+ // Note that we need to set privacy status of transferable before adding any
+ // data into it.
+ aTransferable->SetIsPrivateData(aIsPrivateData);
+
+ nsresult rv = IPCTransferableDataToTransferable(
+ aTransferableData, aAddDataFlavor, aTransferable, aFilterUnknownFlavors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aTransferable->SetRequestingPrincipal(aRequestingPrincipal);
+ aTransferable->SetContentPolicyType(aContentPolicyType);
+ return NS_OK;
+}
+
+nsresult nsContentUtils::IPCTransferableToTransferable(
+ const IPCTransferable& aIPCTransferable, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors) {
+ nsresult rv = IPCTransferableDataToTransferable(
+ aIPCTransferable.data(), aIPCTransferable.isPrivateData(),
+ aIPCTransferable.requestingPrincipal(),
+ aIPCTransferable.contentPolicyType(), aAddDataFlavor, aTransferable,
+ aFilterUnknownFlavors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIPCTransferable.cookieJarSettings().isSome()) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ net::CookieJarSettings::Deserialize(
+ aIPCTransferable.cookieJarSettings().ref(),
+ getter_AddRefs(cookieJarSettings));
+ aTransferable->SetCookieJarSettings(cookieJarSettings);
+ }
+ aTransferable->SetReferrerInfo(aIPCTransferable.referrerInfo());
+ return NS_OK;
+}
+
+nsresult nsContentUtils::IPCTransferableDataItemToVariant(
+ const IPCTransferableDataItem& aItem, nsIWritableVariant* aVariant) {
+ MOZ_ASSERT(aVariant);
+
+ switch (aItem.data().type()) {
+ case IPCTransferableDataType::TIPCTransferableDataString: {
+ const auto& data = aItem.data().get_IPCTransferableDataString();
+ return aVariant->SetAsAString(nsDependentSubstring(
+ reinterpret_cast<const char16_t*>(data.data().Data()),
+ data.data().Size() / sizeof(char16_t)));
+ }
+ case IPCTransferableDataType::TIPCTransferableDataCString: {
+ const auto& data = aItem.data().get_IPCTransferableDataCString();
+ return aVariant->SetAsACString(nsDependentCSubstring(
+ reinterpret_cast<const char*>(data.data().Data()),
+ data.data().Size()));
+ }
+ case IPCTransferableDataType::TIPCTransferableDataInputStream: {
+ const auto& data = aItem.data().get_IPCTransferableDataInputStream();
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ AsChars(data.data().AsSpan()),
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aVariant->SetAsISupports(stream);
+ }
+ case IPCTransferableDataType::TIPCTransferableDataImageContainer: {
+ const auto& data = aItem.data().get_IPCTransferableDataImageContainer();
+ nsCOMPtr<imgIContainer> container;
+ nsresult rv = DeserializeTransferableDataImageContainer(
+ data, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aVariant->SetAsISupports(container);
+ }
+ case IPCTransferableDataType::TIPCTransferableDataBlob: {
+ const auto& data = aItem.data().get_IPCTransferableDataBlob();
+ RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(data.blob());
+ return aVariant->SetAsISupports(blobImpl);
+ }
+ case IPCTransferableDataType::T__None:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsContentUtils::TransferablesToIPCTransferableDatas(
+ nsIArray* aTransferables, nsTArray<IPCTransferableData>& aIPC,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ aIPC.Clear();
+ if (aTransferables) {
+ uint32_t transferableCount = 0;
+ aTransferables->GetLength(&transferableCount);
+ for (uint32_t i = 0; i < transferableCount; ++i) {
+ IPCTransferableData* dt = aIPC.AppendElement();
+ nsCOMPtr<nsITransferable> transferable =
+ do_QueryElementAt(aTransferables, i);
+ TransferableToIPCTransferableData(transferable, dt, aInSyncMessage,
+ aParent);
+ }
+ }
+}
+
+nsresult nsContentUtils::CalculateBufferSizeForImage(
+ const uint32_t& aStride, const IntSize& aImageSize,
+ const SurfaceFormat& aFormat, size_t* aMaxBufferSize,
+ size_t* aUsedBufferSize) {
+ CheckedInt32 requiredBytes =
+ CheckedInt32(aStride) * CheckedInt32(aImageSize.height);
+
+ CheckedInt32 usedBytes =
+ requiredBytes - aStride +
+ (CheckedInt32(aImageSize.width) * BytesPerPixel(aFormat));
+ if (!usedBytes.isValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(requiredBytes.isValid(), "usedBytes valid but not required?");
+ *aMaxBufferSize = requiredBytes.value();
+ *aUsedBufferSize = usedBytes.value();
+ return NS_OK;
+}
+
+static already_AddRefed<DataSourceSurface> BigBufferToDataSurface(
+ BigBuffer& aData, uint32_t aStride, const IntSize& aImageSize,
+ SurfaceFormat aFormat) {
+ if (!aData.Size() || !aImageSize.width || !aImageSize.height) {
+ return nullptr;
+ }
+
+ // Validate shared memory buffer size
+ size_t imageBufLen = 0;
+ size_t maxBufLen = 0;
+ if (NS_FAILED(nsContentUtils::CalculateBufferSizeForImage(
+ aStride, aImageSize, aFormat, &maxBufLen, &imageBufLen))) {
+ return nullptr;
+ }
+ if (imageBufLen > aData.Size()) {
+ return nullptr;
+ }
+ return CreateDataSourceSurfaceFromData(aImageSize, aFormat, aData.Data(),
+ aStride);
+}
+
+nsresult nsContentUtils::DeserializeTransferableDataImageContainer(
+ const IPCTransferableDataImageContainer& aData,
+ imgIContainer** aContainer) {
+ const IntSize size(aData.width(), aData.height());
+ size_t maxBufferSize = 0;
+ size_t usedBufferSize = 0;
+ nsresult rv = CalculateBufferSizeForImage(
+ aData.stride(), size, aData.format(), &maxBufferSize, &usedBufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (usedBufferSize > aData.data().Size()) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<DataSourceSurface> surface =
+ CreateDataSourceSurfaceFromData(size, aData.format(), aData.data().Data(),
+ static_cast<int32_t>(aData.stride()));
+ if (!surface) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size);
+ nsCOMPtr<imgIContainer> imageContainer =
+ image::ImageOps::CreateFromDrawable(drawable);
+ imageContainer.forget(aContainer);
+
+ return NS_OK;
+}
+
+bool nsContentUtils::IsFlavorImage(const nsACString& aFlavor) {
+ return aFlavor.EqualsLiteral(kNativeImageMime) ||
+ aFlavor.EqualsLiteral(kJPEGImageMime) ||
+ aFlavor.EqualsLiteral(kJPGImageMime) ||
+ aFlavor.EqualsLiteral(kPNGImageMime) ||
+ aFlavor.EqualsLiteral(kGIFImageMime);
+}
+
+// FIXME: This can probably be removed once bug 1783240 lands, as `nsString`
+// will be implicitly serialized in shmem when sent over IPDL directly.
+static IPCTransferableDataString AsIPCTransferableDataString(
+ Span<const char16_t> aInput) {
+ return IPCTransferableDataString{BigBuffer(AsBytes(aInput))};
+}
+
+// FIXME: This can probably be removed once bug 1783240 lands, as `nsCString`
+// will be implicitly serialized in shmem when sent over IPDL directly.
+static IPCTransferableDataCString AsIPCTransferableDataCString(
+ Span<const char> aInput) {
+ return IPCTransferableDataCString{BigBuffer(AsBytes(aInput))};
+}
+
+void nsContentUtils::TransferableToIPCTransferableData(
+ nsITransferable* aTransferable, IPCTransferableData* aTransferableData,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), aParent);
+
+ if (aTransferable) {
+ nsTArray<nsCString> flavorList;
+ aTransferable->FlavorsTransferableCanExport(flavorList);
+
+ for (uint32_t j = 0; j < flavorList.Length(); ++j) {
+ nsCString& flavorStr = flavorList[j];
+ if (!flavorStr.Length()) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> data;
+ nsresult rv =
+ aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+
+ if (NS_FAILED(rv) || !data) {
+ if (aInSyncMessage) {
+ // Can't do anything.
+ // FIXME: This shouldn't be the case anymore!
+ continue;
+ }
+
+ // This is a hack to support kFilePromiseMime.
+ // On Windows there just needs to be an entry for it,
+ // and for OSX we need to create
+ // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
+ if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() =
+ AsIPCTransferableDataString(NS_ConvertUTF8toUTF16(flavorStr));
+ continue;
+ }
+
+ // Empty element, transfer only the flavor
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataString(EmptyString());
+ continue;
+ }
+
+ // We need to handle nsIInputStream before nsISupportsCString, otherwise
+ // nsStringInputStream would be converted into a wrong type.
+ if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(data)) {
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ nsCString imageData;
+ DebugOnly<nsresult> rv =
+ NS_ConsumeStream(stream, UINT32_MAX, imageData);
+ MOZ_ASSERT(
+ rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "cannot use async input streams in nsITransferable right now");
+ // FIXME: This can probably be simplified once bug 1783240 lands, as
+ // `nsCString` will be implicitly serialized in shmem when sent over
+ // IPDL directly.
+ item->data() =
+ IPCTransferableDataInputStream(BigBuffer(AsBytes(Span(imageData))));
+ continue;
+ }
+
+ if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(data)) {
+ nsAutoString dataAsString;
+ MOZ_ALWAYS_SUCCEEDS(text->GetData(dataAsString));
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataString(dataAsString);
+ continue;
+ }
+
+ if (nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data)) {
+ nsAutoCString dataAsString;
+ MOZ_ALWAYS_SUCCEEDS(ctext->GetData(dataAsString));
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataCString(dataAsString);
+ continue;
+ }
+
+ if (nsCOMPtr<imgIContainer> image = do_QueryInterface(data)) {
+ // Images to be placed on the clipboard are imgIContainers.
+ RefPtr<mozilla::gfx::SourceSurface> surface = image->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ if (!surface) {
+ continue;
+ }
+ RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface();
+ if (!dataSurface) {
+ continue;
+ }
+ size_t length;
+ int32_t stride;
+ Maybe<BigBuffer> surfaceData =
+ GetSurfaceData(*dataSurface, &length, &stride);
+
+ if (surfaceData.isNothing()) {
+ continue;
+ }
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+
+ mozilla::gfx::IntSize size = dataSurface->GetSize();
+ item->data() = IPCTransferableDataImageContainer(
+ std::move(*surfaceData), size.width, size.height, stride,
+ dataSurface->GetFormat());
+ continue;
+ }
+
+ // Otherwise, handle this as a file.
+ nsCOMPtr<BlobImpl> blobImpl;
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
+ if (aParent) {
+ bool isDir = false;
+ if (NS_SUCCEEDED(file->IsDirectory(&isDir)) && isDir) {
+ nsAutoString path;
+ if (NS_WARN_IF(NS_FAILED(file->GetPath(path)))) {
+ continue;
+ }
+
+ RefPtr<FileSystemSecurity> fss = FileSystemSecurity::GetOrCreate();
+ fss->GrantAccessToContentProcess(aParent->ChildID(), path);
+ }
+ }
+
+ blobImpl = new FileBlobImpl(file);
+
+ IgnoredErrorResult rv;
+
+ // Ensure that file data is cached no that the content process
+ // has this data available to it when passed over:
+ blobImpl->GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ continue;
+ }
+
+ blobImpl->GetLastModified(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ continue;
+ }
+ } else {
+ if (aInSyncMessage) {
+ // Can't do anything.
+ // FIXME: This shouldn't be the case anymore!
+ continue;
+ }
+
+ blobImpl = do_QueryInterface(data);
+ }
+
+ if (blobImpl) {
+ // If we failed to create the blob actor, then this blob probably
+ // can't get the file size for the underlying file, ignore it for
+ // now. TODO pass this through anyway.
+ IPCBlob ipcBlob;
+ nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = IPCTransferableDataBlob(ipcBlob);
+ }
+ }
+ }
+}
+
+void nsContentUtils::TransferableToIPCTransferable(
+ nsITransferable* aTransferable, IPCTransferable* aIPCTransferable,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ IPCTransferableData ipcTransferableData;
+ TransferableToIPCTransferableData(aTransferable, &ipcTransferableData,
+ aInSyncMessage, aParent);
+
+ Maybe<net::CookieJarSettingsArgs> cookieJarSettingsArgs;
+ if (nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aTransferable->GetCookieJarSettings()) {
+ net::CookieJarSettingsArgs args;
+ net::CookieJarSettings::Cast(cookieJarSettings)->Serialize(args);
+ cookieJarSettingsArgs = Some(std::move(args));
+ }
+
+ aIPCTransferable->data() = std::move(ipcTransferableData);
+ aIPCTransferable->isPrivateData() = aTransferable->GetIsPrivateData();
+ aIPCTransferable->requestingPrincipal() =
+ aTransferable->GetRequestingPrincipal();
+ aIPCTransferable->cookieJarSettings() = std::move(cookieJarSettingsArgs);
+ aIPCTransferable->contentPolicyType() = aTransferable->GetContentPolicyType();
+ aIPCTransferable->referrerInfo() = aTransferable->GetReferrerInfo();
+}
+
+Maybe<BigBuffer> nsContentUtils::GetSurfaceData(DataSourceSurface& aSurface,
+ size_t* aLength,
+ int32_t* aStride) {
+ mozilla::gfx::DataSourceSurface::MappedSurface map;
+ if (!aSurface.Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map)) {
+ return Nothing();
+ }
+
+ size_t bufLen = 0;
+ size_t maxBufLen = 0;
+ nsresult rv = nsContentUtils::CalculateBufferSizeForImage(
+ map.mStride, aSurface.GetSize(), aSurface.GetFormat(), &maxBufLen,
+ &bufLen);
+ if (NS_FAILED(rv)) {
+ aSurface.Unmap();
+ return Nothing();
+ }
+
+ BigBuffer surfaceData(maxBufLen);
+ memcpy(surfaceData.Data(), map.mData, bufLen);
+ memset(surfaceData.Data() + bufLen, 0, maxBufLen - bufLen);
+
+ *aLength = maxBufLen;
+ *aStride = map.mStride;
+
+ aSurface.Unmap();
+ return Some(std::move(surfaceData));
+}
+
+Maybe<IPCImage> nsContentUtils::SurfaceToIPCImage(DataSourceSurface& aSurface) {
+ size_t len = 0;
+ int32_t stride = 0;
+ auto mem = GetSurfaceData(aSurface, &len, &stride);
+ if (!mem) {
+ return Nothing();
+ }
+ return Some(IPCImage{std::move(*mem), uint32_t(stride), aSurface.GetFormat(),
+ ImageIntSize::FromUnknownSize(aSurface.GetSize())});
+}
+
+already_AddRefed<DataSourceSurface> nsContentUtils::IPCImageToSurface(
+ IPCImage&& aImage) {
+ return BigBufferToDataSurface(aImage.data(), aImage.stride(),
+ aImage.size().ToUnknownSize(), aImage.format());
+}
+
+Modifiers nsContentUtils::GetWidgetModifiers(int32_t aModifiers) {
+ Modifiers result = 0;
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SHIFT) {
+ result |= mozilla::MODIFIER_SHIFT;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_CONTROL) {
+ result |= mozilla::MODIFIER_CONTROL;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALT) {
+ result |= mozilla::MODIFIER_ALT;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_META) {
+ result |= mozilla::MODIFIER_META;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALTGRAPH) {
+ result |= mozilla::MODIFIER_ALTGRAPH;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_CAPSLOCK) {
+ result |= mozilla::MODIFIER_CAPSLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_FN) {
+ result |= mozilla::MODIFIER_FN;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_FNLOCK) {
+ result |= mozilla::MODIFIER_FNLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_NUMLOCK) {
+ result |= mozilla::MODIFIER_NUMLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SCROLLLOCK) {
+ result |= mozilla::MODIFIER_SCROLLLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOL) {
+ result |= mozilla::MODIFIER_SYMBOL;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK) {
+ result |= mozilla::MODIFIER_SYMBOLLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_OS) {
+ result |= mozilla::MODIFIER_OS;
+ }
+ return result;
+}
+
+nsIWidget* nsContentUtils::GetWidget(PresShell* aPresShell, nsPoint* aOffset) {
+ if (!aPresShell) {
+ return nullptr;
+ }
+ nsIFrame* frame = aPresShell->GetRootFrame();
+ if (!frame) {
+ return nullptr;
+ }
+ return frame->GetView()->GetNearestWidget(aOffset);
+}
+
+int16_t nsContentUtils::GetButtonsFlagForButton(int32_t aButton) {
+ switch (aButton) {
+ case -1:
+ return MouseButtonsFlag::eNoButtons;
+ case MouseButton::ePrimary:
+ return MouseButtonsFlag::ePrimaryFlag;
+ case MouseButton::eMiddle:
+ return MouseButtonsFlag::eMiddleFlag;
+ case MouseButton::eSecondary:
+ return MouseButtonsFlag::eSecondaryFlag;
+ case 3:
+ return MouseButtonsFlag::e4thFlag;
+ case 4:
+ return MouseButtonsFlag::e5thFlag;
+ case MouseButton::eEraser:
+ return MouseButtonsFlag::eEraserFlag;
+ default:
+ NS_ERROR("Button not known.");
+ return 0;
+ }
+}
+
+LayoutDeviceIntPoint nsContentUtils::ToWidgetPoint(
+ const CSSPoint& aPoint, const nsPoint& aOffset,
+ nsPresContext* aPresContext) {
+ nsPoint layoutRelative = CSSPoint::ToAppUnits(aPoint) + aOffset;
+ nsPoint visualRelative =
+ ViewportUtils::LayoutToVisual(layoutRelative, aPresContext->PresShell());
+ return LayoutDeviceIntPoint::FromAppUnitsRounded(
+ visualRelative, aPresContext->AppUnitsPerDevPixel());
+}
+
+nsView* nsContentUtils::GetViewToDispatchEvent(nsPresContext* aPresContext,
+ PresShell** aPresShell) {
+ if (!aPresContext || !aPresShell) {
+ return nullptr;
+ }
+ RefPtr<PresShell> presShell = aPresContext->PresShell();
+ if (NS_WARN_IF(!presShell)) {
+ *aPresShell = nullptr;
+ return nullptr;
+ }
+ nsViewManager* viewManager = presShell->GetViewManager();
+ if (!viewManager) {
+ presShell.forget(aPresShell); // XXX Is this intentional?
+ return nullptr;
+ }
+ presShell.forget(aPresShell);
+ return viewManager->GetRootView();
+}
+
+nsresult nsContentUtils::SendMouseEvent(
+ mozilla::PresShell* aPresShell, const nsAString& aType, float aX, float aY,
+ int32_t aButton, int32_t aButtons, int32_t aClickCount, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, float aPressure,
+ unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
+ PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized) {
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
+ if (!widget) return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
+ bool contextMenuKey = false;
+ if (aType.EqualsLiteral("mousedown")) {
+ msg = eMouseDown;
+ } else if (aType.EqualsLiteral("mouseup")) {
+ msg = eMouseUp;
+ } else if (aType.EqualsLiteral("mousemove")) {
+ msg = eMouseMove;
+ } else if (aType.EqualsLiteral("mouseover")) {
+ msg = eMouseEnterIntoWidget;
+ } else if (aType.EqualsLiteral("mouseout")) {
+ msg = eMouseExitFromWidget;
+ exitFrom = Some(WidgetMouseEvent::ePlatformChild);
+ } else if (aType.EqualsLiteral("mousecancel")) {
+ msg = eMouseExitFromWidget;
+ exitFrom = Some(XRE_IsParentProcess() ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePuppet);
+ } else if (aType.EqualsLiteral("mouselongtap")) {
+ msg = eMouseLongTap;
+ } else if (aType.EqualsLiteral("contextmenu")) {
+ msg = eContextMenu;
+ contextMenuKey = (aButton == 0);
+ } else if (aType.EqualsLiteral("MozMouseHittest")) {
+ msg = eMouseHitTest;
+ } else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
+ msg = eMouseExploreByTouch;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aInputSourceArg == MouseEvent_Binding::MOZ_SOURCE_UNKNOWN) {
+ aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
+ }
+
+ WidgetMouseEvent event(true, msg, widget,
+ aIsWidgetEventSynthesized
+ ? WidgetMouseEvent::eSynthesized
+ : WidgetMouseEvent::eReal,
+ contextMenuKey ? WidgetMouseEvent::eContextMenuKey
+ : WidgetMouseEvent::eNormal);
+ event.pointerId = aIdentifier;
+ event.mModifiers = GetWidgetModifiers(aModifiers);
+ event.mButton = aButton;
+ event.mButtons = aButtons != nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED
+ ? aButtons
+ : msg == eMouseUp ? 0
+ : GetButtonsFlagForButton(aButton);
+ event.mPressure = aPressure;
+ event.mInputSource = aInputSourceArg;
+ event.mClickCount = aClickCount;
+ event.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized;
+ event.mExitFrom = exitFrom;
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ if (!presContext) return NS_ERROR_FAILURE;
+
+ event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+ event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (aToWindow) {
+ RefPtr<PresShell> presShell;
+ nsView* view =
+ GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+ if (StaticPrefs::test_events_async_enabled()) {
+ status = widget->DispatchInputEvent(&event).mContentStatus;
+ } else {
+ nsresult rv = widget->DispatchEvent(&event, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aPreventDefault) {
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ if (event.mFlags.mDefaultPreventedByContent) {
+ *aPreventDefault = PreventDefaultResult::ByContent;
+ } else {
+ *aPreventDefault = PreventDefaultResult::ByChrome;
+ }
+ } else {
+ *aPreventDefault = PreventDefaultResult::No;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::FirePageHideEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem, EventTarget* aChromeEventHandler,
+ bool aOnlySystemGroup) {
+ MOZ_DIAGNOSTIC_ASSERT(aItem);
+ MOZ_DIAGNOSTIC_ASSERT(aChromeEventHandler);
+
+ RefPtr<Document> doc = aItem->GetDocument();
+ NS_ASSERTION(doc, "What happened here?");
+ doc->OnPageHide(true, aChromeEventHandler, aOnlySystemGroup);
+
+ int32_t childCount = 0;
+ aItem->GetInProcessChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetInProcessChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageHideEventForFrameLoaderSwap(kids[i], aChromeEventHandler,
+ aOnlySystemGroup);
+ }
+ }
+}
+
+// The pageshow event is fired for a given document only if IsShowing() returns
+// the same thing as aFireIfShowing. This gives us a way to fire pageshow only
+// on documents that are still loading or only on documents that are already
+// loaded.
+/* static */
+void nsContentUtils::FirePageShowEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem, EventTarget* aChromeEventHandler,
+ bool aFireIfShowing, bool aOnlySystemGroup) {
+ int32_t childCount = 0;
+ aItem->GetInProcessChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetInProcessChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageShowEventForFrameLoaderSwap(kids[i], aChromeEventHandler,
+ aFireIfShowing, aOnlySystemGroup);
+ }
+ }
+
+ RefPtr<Document> doc = aItem->GetDocument();
+ if (doc && doc->IsShowing() == aFireIfShowing) {
+ doc->OnPageShow(true, aChromeEventHandler, aOnlySystemGroup);
+ }
+}
+
+/* static */
+already_AddRefed<nsPIWindowRoot> nsContentUtils::GetWindowRoot(Document* aDoc) {
+ if (aDoc) {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ return win->GetTopWindowRoot();
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+bool nsContentUtils::LinkContextIsURI(const nsAString& aAnchor,
+ nsIURI* aDocURI) {
+ if (aAnchor.IsEmpty()) {
+ // anchor parameter not present or empty -> same document reference
+ return true;
+ }
+
+ // the document URI might contain a fragment identifier ("#...')
+ // we want to ignore that because it's invisible to the server
+ // and just affects the local interpretation in the recipient
+ nsCOMPtr<nsIURI> contextUri;
+ nsresult rv = NS_GetURIWithoutRef(aDocURI, getter_AddRefs(contextUri));
+
+ if (NS_FAILED(rv)) {
+ // copying failed
+ return false;
+ }
+
+ // resolve anchor against context
+ nsCOMPtr<nsIURI> resolvedUri;
+ rv = NS_NewURI(getter_AddRefs(resolvedUri), aAnchor, nullptr, contextUri);
+
+ if (NS_FAILED(rv)) {
+ // resolving failed
+ return false;
+ }
+
+ bool same;
+ rv = contextUri->Equals(resolvedUri, &same);
+ if (NS_FAILED(rv)) {
+ // comparison failed
+ return false;
+ }
+
+ return same;
+}
+
+/* static */
+bool nsContentUtils::IsPreloadType(nsContentPolicyType aType) {
+ return (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD);
+}
+
+// static
+ReferrerPolicy nsContentUtils::GetReferrerPolicyFromChannel(
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ return ReferrerPolicy::_empty;
+ }
+
+ nsresult rv;
+ nsAutoCString headerValue;
+ rv = httpChannel->GetResponseHeader("referrer-policy"_ns, headerValue);
+ if (NS_FAILED(rv) || headerValue.IsEmpty()) {
+ return ReferrerPolicy::_empty;
+ }
+
+ return ReferrerInfo::ReferrerPolicyFromHeaderString(
+ NS_ConvertUTF8toUTF16(headerValue));
+}
+
+// static
+bool nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel) {
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsContentPolicyType type = loadInfo->InternalContentPolicyType();
+ return IsNonSubresourceInternalPolicyType(type);
+}
+
+// static
+bool nsContentUtils::IsNonSubresourceInternalPolicyType(
+ nsContentPolicyType aType) {
+ return aType == nsIContentPolicy::TYPE_DOCUMENT ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+}
+
+// static public
+bool nsContentUtils::IsThirdPartyTrackingResourceWindow(
+ nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ Document* document = aWindow->GetExtantDoc();
+ if (!document) {
+ return false;
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(document->GetChannel());
+ if (!classifiedChannel) {
+ return false;
+ }
+
+ return classifiedChannel->IsThirdPartyTrackingResource();
+}
+
+// static public
+bool nsContentUtils::IsFirstPartyTrackingResourceWindow(
+ nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ Document* document = aWindow->GetExtantDoc();
+ if (!document) {
+ return false;
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(document->GetChannel());
+ if (!classifiedChannel) {
+ return false;
+ }
+
+ uint32_t classificationFlags =
+ classifiedChannel->GetFirstPartyClassificationFlags();
+
+ return mozilla::net::UrlClassifierCommon::IsTrackingClassificationFlag(
+ classificationFlags, NS_UsePrivateBrowsing(document->GetChannel()));
+}
+
+namespace {
+
+// We put StringBuilder in the anonymous namespace to prevent anything outside
+// this file from accidentally being linked against it.
+class BulkAppender {
+ using size_type = typename nsAString::size_type;
+
+ public:
+ explicit BulkAppender(BulkWriteHandle<char16_t>&& aHandle)
+ : mHandle(std::move(aHandle)), mPosition(0) {}
+ ~BulkAppender() = default;
+
+ template <int N>
+ void AppendLiteral(const char16_t (&aStr)[N]) {
+ size_t len = N - 1;
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ memcpy(mHandle.Elements() + mPosition, aStr, len * sizeof(char16_t));
+ mPosition += len;
+ }
+
+ void Append(Span<const char16_t> aStr) {
+ size_t len = aStr.Length();
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ // Both mHandle.Elements() and aStr.Elements() are guaranteed
+ // to be non-null (by the string implementation and by Span,
+ // respectively), so not checking the pointers for null before
+ // memcpy does not lead to UB even if len was zero.
+ memcpy(mHandle.Elements() + mPosition, aStr.Elements(),
+ len * sizeof(char16_t));
+ mPosition += len;
+ }
+
+ void Append(Span<const char> aStr) {
+ size_t len = aStr.Length();
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ ConvertLatin1toUtf16(aStr, mHandle.AsSpan().From(mPosition));
+ mPosition += len;
+ }
+
+ void Finish() { mHandle.Finish(mPosition, false); }
+
+ private:
+ BulkWriteHandle<char16_t> mHandle;
+ size_type mPosition;
+};
+
+class StringBuilder {
+ private:
+ class Unit {
+ public:
+ Unit() : mAtom(nullptr) { MOZ_COUNT_CTOR(StringBuilder::Unit); }
+ ~Unit() {
+ if (mType == Type::String || mType == Type::StringWithEncode) {
+ mString.~nsString();
+ }
+ MOZ_COUNT_DTOR(StringBuilder::Unit);
+ }
+
+ enum class Type : uint8_t {
+ Unknown,
+ Atom,
+ String,
+ StringWithEncode,
+ Literal,
+ TextFragment,
+ TextFragmentWithEncode,
+ };
+
+ union {
+ nsAtom* mAtom;
+ const char16_t* mLiteral;
+ nsString mString;
+ const nsTextFragment* mTextFragment;
+ };
+ uint32_t mLength = 0;
+ Type mType = Type::Unknown;
+ };
+
+ static_assert(sizeof(void*) != 8 || sizeof(Unit) <= 3 * sizeof(void*),
+ "Unit should remain small");
+
+ public:
+ // Try to keep the size of StringBuilder close to a jemalloc bucket size (the
+ // 16kb one in this case).
+ static constexpr uint32_t TARGET_SIZE = 16 * 1024;
+
+ // The number of units we need to remove from the inline buffer so that the
+ // rest of the builder members fit. A more precise approach would be to
+ // calculate that extra size and use (TARGET_SIZE - OTHER_SIZE) / sizeof(Unit)
+ // or so, but this is simpler.
+ static constexpr uint32_t PADDING_UNITS = sizeof(void*) == 8 ? 1 : 2;
+
+ static constexpr uint32_t STRING_BUFFER_UNITS =
+ TARGET_SIZE / sizeof(Unit) - PADDING_UNITS;
+
+ StringBuilder() : mLast(this), mLength(0) { MOZ_COUNT_CTOR(StringBuilder); }
+
+ MOZ_COUNTED_DTOR(StringBuilder)
+
+ void Append(nsAtom* aAtom) {
+ Unit* u = AddUnit();
+ u->mAtom = aAtom;
+ u->mType = Unit::Type::Atom;
+ uint32_t len = aAtom->GetLength();
+ u->mLength = len;
+ mLength += len;
+ }
+
+ template <int N>
+ void Append(const char16_t (&aLiteral)[N]) {
+ Unit* u = AddUnit();
+ u->mLiteral = aLiteral;
+ u->mType = Unit::Type::Literal;
+ uint32_t len = N - 1;
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void Append(nsString&& aString) {
+ Unit* u = AddUnit();
+ uint32_t len = aString.Length();
+ new (&u->mString) nsString(std::move(aString));
+ u->mType = Unit::Type::String;
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void AppendWithAttrEncode(nsString&& aString, uint32_t aLen) {
+ Unit* u = AddUnit();
+ new (&u->mString) nsString(std::move(aString));
+ u->mType = Unit::Type::StringWithEncode;
+ u->mLength = aLen;
+ mLength += aLen;
+ }
+
+ void Append(const nsTextFragment* aTextFragment) {
+ Unit* u = AddUnit();
+ u->mTextFragment = aTextFragment;
+ u->mType = Unit::Type::TextFragment;
+ uint32_t len = aTextFragment->GetLength();
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen) {
+ Unit* u = AddUnit();
+ u->mTextFragment = aTextFragment;
+ u->mType = Unit::Type::TextFragmentWithEncode;
+ u->mLength = aLen;
+ mLength += aLen;
+ }
+
+ bool ToString(nsAString& aOut) {
+ if (!mLength.isValid()) {
+ return false;
+ }
+ auto appenderOrErr = aOut.BulkWrite(mLength.value(), 0, true);
+ if (appenderOrErr.isErr()) {
+ return false;
+ }
+
+ BulkAppender appender{appenderOrErr.unwrap()};
+
+ for (StringBuilder* current = this; current;
+ current = current->mNext.get()) {
+ uint32_t len = current->mUnits.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ Unit& u = current->mUnits[i];
+ switch (u.mType) {
+ case Unit::Type::Atom:
+ appender.Append(*(u.mAtom));
+ break;
+ case Unit::Type::String:
+ appender.Append(u.mString);
+ break;
+ case Unit::Type::StringWithEncode:
+ EncodeAttrString(u.mString, appender);
+ break;
+ case Unit::Type::Literal:
+ appender.Append(Span(u.mLiteral, u.mLength));
+ break;
+ case Unit::Type::TextFragment:
+ if (u.mTextFragment->Is2b()) {
+ appender.Append(
+ Span(u.mTextFragment->Get2b(), u.mTextFragment->GetLength()));
+ } else {
+ appender.Append(
+ Span(u.mTextFragment->Get1b(), u.mTextFragment->GetLength()));
+ }
+ break;
+ case Unit::Type::TextFragmentWithEncode:
+ if (u.mTextFragment->Is2b()) {
+ EncodeTextFragment(
+ Span(u.mTextFragment->Get2b(), u.mTextFragment->GetLength()),
+ appender);
+ } else {
+ EncodeTextFragment(
+ Span(u.mTextFragment->Get1b(), u.mTextFragment->GetLength()),
+ appender);
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown unit type?");
+ }
+ }
+ }
+ appender.Finish();
+ return true;
+ }
+
+ private:
+ Unit* AddUnit() {
+ if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
+ new StringBuilder(this);
+ }
+ return mLast->mUnits.AppendElement();
+ }
+
+ explicit StringBuilder(StringBuilder* aFirst) : mLast(nullptr), mLength(0) {
+ MOZ_COUNT_CTOR(StringBuilder);
+ aFirst->mLast->mNext = WrapUnique(this);
+ aFirst->mLast = this;
+ }
+
+ void EncodeAttrString(Span<const char16_t> aStr, BulkAppender& aAppender) {
+ size_t flushedUntil = 0;
+ size_t currentPosition = 0;
+ for (char16_t c : aStr) {
+ switch (c) {
+ case '"':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&quot;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '&':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&amp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case 0x00A0:
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&nbsp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ }
+ if (currentPosition > flushedUntil) {
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ }
+ }
+
+ template <class T>
+ void EncodeTextFragment(Span<const T> aStr, BulkAppender& aAppender) {
+ size_t flushedUntil = 0;
+ size_t currentPosition = 0;
+ for (T c : aStr) {
+ switch (c) {
+ case '<':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&lt;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '>':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&gt;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '&':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&amp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case T(0xA0):
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&nbsp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ }
+ if (currentPosition > flushedUntil) {
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ }
+ }
+
+ AutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
+ UniquePtr<StringBuilder> mNext;
+ StringBuilder* mLast;
+ // mLength is used only in the first StringBuilder object in the linked list.
+ CheckedInt<uint32_t> mLength;
+};
+
+static_assert(sizeof(StringBuilder) <= StringBuilder::TARGET_SIZE,
+ "StringBuilder should fit in the target bucket");
+
+} // namespace
+
+static void AppendEncodedCharacters(const nsTextFragment* aText,
+ StringBuilder& aBuilder) {
+ uint32_t extraSpaceNeeded = 0;
+ uint32_t len = aText->GetLength();
+ if (aText->Is2b()) {
+ const char16_t* data = aText->Get2b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const char16_t c = data[i];
+ switch (c) {
+ case '<':
+ extraSpaceNeeded += ArrayLength("&lt;") - 2;
+ break;
+ case '>':
+ extraSpaceNeeded += ArrayLength("&gt;") - 2;
+ break;
+ case '&':
+ extraSpaceNeeded += ArrayLength("&amp;") - 2;
+ break;
+ case 0x00A0:
+ extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ const char* data = aText->Get1b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const unsigned char c = data[i];
+ switch (c) {
+ case '<':
+ extraSpaceNeeded += ArrayLength("&lt;") - 2;
+ break;
+ case '>':
+ extraSpaceNeeded += ArrayLength("&gt;") - 2;
+ break;
+ case '&':
+ extraSpaceNeeded += ArrayLength("&amp;") - 2;
+ break;
+ case 0x00A0:
+ extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (extraSpaceNeeded) {
+ aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
+ } else {
+ aBuilder.Append(aText);
+ }
+}
+
+static uint32_t ExtraSpaceNeededForAttrEncoding(const nsAString& aValue) {
+ const char16_t* c = aValue.BeginReading();
+ const char16_t* end = aValue.EndReading();
+
+ uint32_t extraSpaceNeeded = 0;
+ while (c < end) {
+ switch (*c) {
+ case '"':
+ extraSpaceNeeded += ArrayLength("&quot;") - 2;
+ break;
+ case '&':
+ extraSpaceNeeded += ArrayLength("&amp;") - 2;
+ break;
+ case 0x00A0:
+ extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+ break;
+ default:
+ break;
+ }
+ ++c;
+ }
+
+ return extraSpaceNeeded;
+}
+
+static void AppendEncodedAttributeValue(const nsAttrValue& aValue,
+ StringBuilder& aBuilder) {
+ if (nsAtom* atom = aValue.GetStoredAtom()) {
+ nsDependentAtomString atomStr(atom);
+ uint32_t space = ExtraSpaceNeededForAttrEncoding(atomStr);
+ if (!space) {
+ aBuilder.Append(atom);
+ } else {
+ aBuilder.AppendWithAttrEncode(nsString(atomStr),
+ atomStr.Length() + space);
+ }
+ return;
+ }
+ // NOTE(emilio): In most cases this will just be a reference to the stored
+ // nsStringBuffer.
+ nsString str;
+ aValue.ToString(str);
+ uint32_t space = ExtraSpaceNeededForAttrEncoding(str);
+ if (space) {
+ aBuilder.AppendWithAttrEncode(std::move(str), str.Length() + space);
+ } else {
+ aBuilder.Append(std::move(str));
+ }
+}
+
+static void StartElement(Element* aElement, StringBuilder& aBuilder) {
+ nsAtom* localName = aElement->NodeInfo()->NameAtom();
+ const int32_t tagNS = aElement->GetNameSpaceID();
+
+ aBuilder.Append(u"<");
+ if (tagNS == kNameSpaceID_XHTML || tagNS == kNameSpaceID_SVG ||
+ tagNS == kNameSpaceID_MathML) {
+ aBuilder.Append(localName);
+ } else {
+ aBuilder.Append(nsString(aElement->NodeName()));
+ }
+
+ if (CustomElementData* ceData = aElement->GetCustomElementData()) {
+ nsAtom* isAttr = ceData->GetIs(aElement);
+ if (isAttr && !aElement->HasAttr(nsGkAtoms::is)) {
+ aBuilder.Append(uR"( is=")");
+ aBuilder.Append(isAttr);
+ aBuilder.Append(uR"(")");
+ }
+ }
+
+ uint32_t i = 0;
+ while (BorrowedAttrInfo info = aElement->GetAttrInfoAt(i++)) {
+ const nsAttrName* name = info.mName;
+
+ int32_t attNs = name->NamespaceID();
+ nsAtom* attName = name->LocalName();
+
+ // Filter out any attribute starting with [-|_]moz
+ // FIXME(emilio): Do we still need this?
+ nsDependentAtomString attrNameStr(attName);
+ if (StringBeginsWith(attrNameStr, u"_moz"_ns) ||
+ StringBeginsWith(attrNameStr, u"-moz"_ns)) {
+ continue;
+ }
+
+ aBuilder.Append(u" ");
+
+ if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
+ (attNs == kNameSpaceID_XMLNS && attName == nsGkAtoms::xmlns)) {
+ // Nothing else required
+ } else if (attNs == kNameSpaceID_XML) {
+ aBuilder.Append(u"xml:");
+ } else if (attNs == kNameSpaceID_XMLNS) {
+ aBuilder.Append(u"xmlns:");
+ } else if (attNs == kNameSpaceID_XLink) {
+ aBuilder.Append(u"xlink:");
+ } else if (nsAtom* prefix = name->GetPrefix()) {
+ aBuilder.Append(prefix);
+ aBuilder.Append(u":");
+ }
+
+ aBuilder.Append(attName);
+ aBuilder.Append(uR"(=")");
+ AppendEncodedAttributeValue(*info.mValue, aBuilder);
+ aBuilder.Append(uR"(")");
+ }
+
+ aBuilder.Append(u">");
+
+ /*
+ // Per HTML spec we should append one \n if the first child of
+ // pre/textarea/listing is a textnode and starts with a \n.
+ // But because browsers haven't traditionally had that behavior,
+ // we're not changing our behavior either - yet.
+ if (aContent->IsHTMLElement()) {
+ if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
+ localName == nsGkAtoms::listing) {
+ nsIContent* fc = aContent->GetFirstChild();
+ if (fc &&
+ (fc->NodeType() == nsINode::TEXT_NODE ||
+ fc->NodeType() == nsINode::CDATA_SECTION_NODE)) {
+ const nsTextFragment* text = fc->GetText();
+ if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) {
+ aBuilder.Append("\n");
+ }
+ }
+ }
+ }*/
+}
+
+static inline bool ShouldEscape(nsIContent* aParent) {
+ if (!aParent || !aParent->IsHTMLElement()) {
+ return true;
+ }
+
+ static const nsAtom* nonEscapingElements[] = {
+ nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
+ nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
+ nsGkAtoms::plaintext, nsGkAtoms::noscript};
+ static mozilla::BitBloomFilter<12, nsAtom> sFilter;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ for (auto& nonEscapingElement : nonEscapingElements) {
+ sFilter.add(nonEscapingElement);
+ }
+ }
+
+ nsAtom* tag = aParent->NodeInfo()->NameAtom();
+ if (sFilter.mightContain(tag)) {
+ for (auto& nonEscapingElement : nonEscapingElements) {
+ if (tag == nonEscapingElement) {
+ if (MOZ_UNLIKELY(tag == nsGkAtoms::noscript) &&
+ MOZ_UNLIKELY(!aParent->OwnerDoc()->IsScriptEnabled())) {
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static inline bool IsVoidTag(Element* aElement) {
+ if (!aElement->IsHTMLElement()) {
+ return false;
+ }
+ return FragmentOrElement::IsHTMLVoid(aElement->NodeInfo()->NameAtom());
+}
+
+bool nsContentUtils::SerializeNodeToMarkup(nsINode* aRoot,
+ bool aDescendantsOnly,
+ nsAString& aOut) {
+ // If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true
+ MOZ_ASSERT(aDescendantsOnly || aRoot->NodeType() != nsINode::DOCUMENT_NODE);
+
+ nsINode* current =
+ aDescendantsOnly ? aRoot->GetFirstChildOfTemplateOrNode() : aRoot;
+
+ if (!current) {
+ return true;
+ }
+
+ StringBuilder builder;
+ nsIContent* next;
+ while (true) {
+ bool isVoid = false;
+ switch (current->NodeType()) {
+ case nsINode::ELEMENT_NODE: {
+ Element* elem = current->AsElement();
+ StartElement(elem, builder);
+ isVoid = IsVoidTag(elem);
+ if (!isVoid && (next = current->GetFirstChildOfTemplateOrNode())) {
+ current = next;
+ continue;
+ }
+ break;
+ }
+
+ case nsINode::TEXT_NODE:
+ case nsINode::CDATA_SECTION_NODE: {
+ const nsTextFragment* text = &current->AsText()->TextFragment();
+ nsIContent* parent = current->GetParent();
+ if (ShouldEscape(parent)) {
+ AppendEncodedCharacters(text, builder);
+ } else {
+ builder.Append(text);
+ }
+ break;
+ }
+
+ case nsINode::COMMENT_NODE: {
+ builder.Append(u"<!--");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append(u"-->");
+ break;
+ }
+
+ case nsINode::DOCUMENT_TYPE_NODE: {
+ builder.Append(u"<!DOCTYPE ");
+ builder.Append(nsString(current->NodeName()));
+ builder.Append(u">");
+ break;
+ }
+
+ case nsINode::PROCESSING_INSTRUCTION_NODE: {
+ builder.Append(u"<?");
+ builder.Append(nsString(current->NodeName()));
+ builder.Append(u" ");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append(u">");
+ break;
+ }
+ }
+
+ while (true) {
+ if (!isVoid && current->NodeType() == nsINode::ELEMENT_NODE) {
+ builder.Append(u"</");
+ nsIContent* elem = static_cast<nsIContent*>(current);
+ if (elem->IsHTMLElement() || elem->IsSVGElement() ||
+ elem->IsMathMLElement()) {
+ builder.Append(elem->NodeInfo()->NameAtom());
+ } else {
+ builder.Append(nsString(current->NodeName()));
+ }
+ builder.Append(u">");
+ }
+ isVoid = false;
+
+ if (current == aRoot) {
+ return builder.ToString(aOut);
+ }
+
+ if ((next = current->GetNextSibling())) {
+ current = next;
+ break;
+ }
+
+ current = current->GetParentNode();
+
+ // Handle template element. If the parent is a template's content,
+ // then adjust the parent to be the template element.
+ if (current != aRoot &&
+ current->NodeType() == nsINode::DOCUMENT_FRAGMENT_NODE) {
+ DocumentFragment* frag = static_cast<DocumentFragment*>(current);
+ nsIContent* fragHost = frag->GetHost();
+ if (fragHost && fragHost->IsTemplateElement()) {
+ current = fragHost;
+ }
+ }
+
+ if (aDescendantsOnly && current == aRoot) {
+ return builder.ToString(aOut);
+ }
+ }
+ }
+}
+
+bool nsContentUtils::IsSpecificAboutPage(JSObject* aGlobal, const char* aUri) {
+ // aUri must start with about: or this isn't the right function to be using.
+ MOZ_ASSERT(strncmp(aUri, "about:", 6) == 0);
+
+ // Make sure the global is a window
+ MOZ_DIAGNOSTIC_ASSERT(JS_IsGlobalObject(aGlobal));
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobal);
+ if (!win) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = win->GetPrincipal();
+ NS_ENSURE_TRUE(principal, false);
+
+ // First check the scheme to avoid getting long specs in the common case.
+ if (!principal->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ principal->GetAsciiSpec(spec);
+
+ return spec.EqualsASCII(aUri);
+}
+
+/* static */
+void nsContentUtils::SetScrollbarsVisibility(nsIDocShell* aDocShell,
+ bool aVisible) {
+ if (!aDocShell) {
+ return;
+ }
+ auto pref = aVisible ? ScrollbarPreference::Auto : ScrollbarPreference::Never;
+ nsDocShell::Cast(aDocShell)->SetScrollbarPreference(pref);
+}
+
+/* static */
+nsIDocShell* nsContentUtils::GetDocShellForEventTarget(EventTarget* aTarget) {
+ if (!aTarget) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow;
+ if (nsCOMPtr<nsINode> node = nsINode::FromEventTarget(aTarget)) {
+ bool ignore;
+ innerWindow =
+ do_QueryInterface(node->OwnerDoc()->GetScriptHandlingObject(ignore));
+ } else if ((innerWindow = nsPIDOMWindowInner::FromEventTarget(aTarget))) {
+ // Nothing else to do
+ } else {
+ nsCOMPtr<DOMEventTargetHelper> helper = do_QueryInterface(aTarget);
+ if (helper) {
+ innerWindow = helper->GetOwner();
+ }
+ }
+
+ if (innerWindow) {
+ return innerWindow->GetDocShell();
+ }
+
+ return nullptr;
+}
+
+/*
+ * Note: this function only relates to figuring out HTTPS state, which is an
+ * input to the Secure Context algorithm. We are not actually implementing any
+ * part of the Secure Context algorithm itself here.
+ *
+ * This is a bit of a hack. Ideally we'd propagate HTTPS state through
+ * nsIChannel as described in the Fetch and HTML specs, but making channels
+ * know about whether they should inherit HTTPS state, propagating information
+ * about who the channel's "client" is, exposing GetHttpsState API on channels
+ * and modifying the various cache implementations to store and retrieve HTTPS
+ * state involves a huge amount of code (see bug 1220687). We avoid that for
+ * now using this function.
+ *
+ * This function takes advantage of the observation that we can return true if
+ * nsIContentSecurityManager::IsOriginPotentiallyTrustworthy returns true for
+ * the document's origin (e.g. the origin has a scheme of 'https' or host
+ * 'localhost' etc.). Since we generally propagate a creator document's origin
+ * onto data:, blob:, etc. documents, this works for them too.
+ *
+ * The scenario where this observation breaks down is sandboxing without the
+ * 'allow-same-origin' flag, since in this case a document is given a unique
+ * origin (IsOriginPotentiallyTrustworthy would return false). We handle that
+ * by using the origin that the document would have had had it not been
+ * sandboxed.
+ *
+ * DEFICIENCIES: Note that this function uses nsIScriptSecurityManager's
+ * getChannelResultPrincipalIfNotSandboxed, and that method's ignoring of
+ * sandboxing is limited to the immediate sandbox. In the case that aDocument
+ * should inherit its origin (e.g. data: URI) but its parent has ended up
+ * with a unique origin due to sandboxing further up the parent chain we may
+ * end up returning false when we would ideally return true (since we will
+ * examine the parent's origin for 'https' and not finding it.) This means
+ * that we may restrict the privileges of some pages unnecessarily in this
+ * edge case.
+ */
+/* static */
+bool nsContentUtils::HttpsStateIsModern(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+
+ if (principal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ // If aDocument is sandboxed, try and get the principal that it would have
+ // been given had it not been sandboxed:
+ if (principal->GetIsNullPrincipal() &&
+ (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN)) {
+ nsIChannel* channel = aDocument->GetChannel();
+ if (channel) {
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->GetChannelResultPrincipalIfNotSandboxed(
+ channel, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (principal->IsSystemPrincipal()) {
+ // If a document with the system principal is sandboxing a subdocument
+ // that would normally inherit the embedding element's principal (e.g.
+ // a srcdoc document) then the embedding document does not trust the
+ // content that is written to the embedded document. Unlike when the
+ // embedding document is https, in this case we have no indication as
+ // to whether the embedded document's contents are delivered securely
+ // or not, and the sandboxing would possibly indicate that they were
+ // not. To play it safe we return false here. (See bug 1162772
+ // comment 73-80.)
+ return false;
+ }
+ }
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ return false;
+ }
+
+ MOZ_ASSERT(principal->GetIsContentPrincipal());
+
+ return principal->GetIsOriginPotentiallyTrustworthy();
+}
+
+/* static */
+bool nsContentUtils::ComputeIsSecureContext(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = ssm->GetChannelResultPrincipalIfNotSandboxed(
+ aChannel, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ const RefPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (principal->IsSystemPrincipal()) {
+ // If the load would've been sandboxed, treat this load as an untrusted
+ // load, as system code considers sandboxed resources insecure.
+ return !loadInfo->GetLoadingSandboxed();
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ return false;
+ }
+
+ if (const RefPtr<WindowContext> windowContext =
+ WindowContext::GetById(loadInfo->GetInnerWindowID())) {
+ if (!windowContext->GetIsSecureContext()) {
+ return false;
+ }
+ }
+
+ return principal->GetIsOriginPotentiallyTrustworthy();
+}
+
+/* static */
+void nsContentUtils::TryToUpgradeElement(Element* aElement) {
+ NodeInfo* nodeInfo = aElement->NodeInfo();
+ RefPtr<nsAtom> typeAtom =
+ aElement->GetCustomElementData()->GetCustomElementType();
+
+ MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName()));
+ CustomElementDefinition* definition =
+ nsContentUtils::LookupCustomElementDefinition(
+ nodeInfo->GetDocument(), nodeInfo->NameAtom(),
+ nodeInfo->NamespaceID(), typeAtom);
+ if (definition) {
+ nsContentUtils::EnqueueUpgradeReaction(aElement, definition);
+ } else {
+ // Add an unresolved custom element that is a candidate for upgrade when a
+ // custom element is connected to the document.
+ nsContentUtils::RegisterUnresolvedElement(aElement, typeAtom);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void DoCustomElementCreate(Element** aElement, JSContext* aCx,
+ Document* aDoc, NodeInfo* aNodeInfo,
+ CustomElementConstructor* aConstructor,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> constructResult(aCx);
+ aConstructor->Construct(&constructResult, aRv, "Custom Element Create",
+ CallbackFunction::eRethrowExceptions);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RefPtr<Element> element;
+ // constructResult is an ObjectValue because construction with a callback
+ // always forms the return value from a JSObject.
+ UNWRAP_OBJECT(Element, &constructResult, element);
+ if (aNodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ if (!element || !element->IsHTMLElement()) {
+ aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("\"this\"",
+ "HTMLElement");
+ return;
+ }
+ } else {
+ if (!element || !element->IsXULElement()) {
+ aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("\"this\"",
+ "XULElement");
+ return;
+ }
+ }
+
+ nsAtom* localName = aNodeInfo->NameAtom();
+
+ if (aDoc != element->OwnerDoc() || element->GetParentNode() ||
+ element->HasChildren() || element->GetAttrCount() ||
+ element->NodeInfo()->NameAtom() != localName) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ element.forget(aElement);
+}
+
+/* static */
+nsresult nsContentUtils::NewXULOrHTMLElement(
+ Element** aResult, mozilla::dom::NodeInfo* aNodeInfo,
+ FromParser aFromParser, nsAtom* aIsAtom,
+ mozilla::dom::CustomElementDefinition* aDefinition) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
+ MOZ_ASSERT(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML) ||
+ nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
+ "Can only create XUL or XHTML elements.");
+
+ nsAtom* name = nodeInfo->NameAtom();
+ int32_t tag = eHTMLTag_unknown;
+ bool isCustomElementName = false;
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ tag = nsHTMLTags::CaseSensitiveAtomTagToId(name);
+ isCustomElementName =
+ (tag == eHTMLTag_userdefined &&
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XHTML));
+ } else { // kNameSpaceID_XUL
+ if (aIsAtom) {
+ // Make sure the customized built-in element to be constructed confirms
+ // to our naming requirement, i.e. [is] must be a dashed name and
+ // the tag name must not.
+ // if so, set isCustomElementName to false to kick off all the logics
+ // that pick up aIsAtom.
+ if (nsContentUtils::IsNameWithDash(aIsAtom) &&
+ !nsContentUtils::IsNameWithDash(name)) {
+ isCustomElementName = false;
+ } else {
+ isCustomElementName =
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XUL);
+ }
+ } else {
+ isCustomElementName =
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XUL);
+ }
+ }
+
+ nsAtom* tagAtom = nodeInfo->NameAtom();
+ nsAtom* typeAtom = nullptr;
+ bool isCustomElement = isCustomElementName || aIsAtom;
+ if (isCustomElement) {
+ typeAtom = isCustomElementName ? tagAtom : aIsAtom;
+ }
+
+ MOZ_ASSERT_IF(aDefinition, isCustomElement);
+
+ // https://dom.spec.whatwg.org/#concept-create-element
+ // We only handle the "synchronous custom elements flag is set" now.
+ // For the unset case (e.g. cloning a node), see bug 1319342 for that.
+ // Step 4.
+ RefPtr<CustomElementDefinition> definition = aDefinition;
+ if (isCustomElement && !definition) {
+ MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName()));
+ definition = nsContentUtils::LookupCustomElementDefinition(
+ nodeInfo->GetDocument(), nodeInfo->NameAtom(), nodeInfo->NamespaceID(),
+ typeAtom);
+ }
+
+ // It might be a problem that parser synchronously calls constructor, so filed
+ // bug 1378079 to figure out what we should do for parser case.
+ if (definition) {
+ /*
+ * Synchronous custom elements flag is determined by 3 places in spec,
+ * 1) create an element for a token, the flag is determined by
+ * "will execute script" which is not originally created
+ * for the HTML fragment parsing algorithm.
+ * 2) createElement and createElementNS, the flag is the same as
+ * NOT_FROM_PARSER.
+ * 3) clone a node, our implementation will not go into this function.
+ * For the unset case which is non-synchronous only applied for
+ * inner/outerHTML.
+ */
+ bool synchronousCustomElements = aFromParser != dom::FROM_PARSER_FRAGMENT;
+ // Per discussion in https://github.com/w3c/webcomponents/issues/635,
+ // use entry global in those places that are called from JS APIs and use the
+ // node document's global object if it is called from parser.
+ nsIGlobalObject* global;
+ if (aFromParser == dom::NOT_FROM_PARSER) {
+ global = GetEntryGlobal();
+
+ // Documents created from the PrototypeDocumentSink always use
+ // NOT_FROM_PARSER for non-XUL elements. We can get the global from the
+ // document in that case.
+ if (!global) {
+ Document* doc = nodeInfo->GetDocument();
+ if (doc && doc->LoadedFromPrototype()) {
+ global = doc->GetScopeObject();
+ }
+ }
+ } else {
+ global = nodeInfo->GetDocument()->GetScopeObject();
+ }
+ if (!global) {
+ // In browser chrome code, one may have access to a document which doesn't
+ // have scope object anymore.
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoAllowLegacyScriptExecution exemption;
+ AutoEntryScript aes(global, "create custom elements");
+ JSContext* cx = aes.cx();
+ ErrorResult rv;
+
+ // Step 5.
+ if (definition->IsCustomBuiltIn()) {
+ // SetupCustomElement() should be called with an element that don't have
+ // CustomElementData setup, if not we will hit the assertion in
+ // SetCustomElementData().
+ // Built-in element
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ *aResult =
+ CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetCustomElementData(MakeUnique<CustomElementData>(typeAtom));
+ if (synchronousCustomElements) {
+ CustomElementRegistry::Upgrade(*aResult, definition, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ aes.ReportException();
+ }
+ } else {
+ nsContentUtils::EnqueueUpgradeReaction(*aResult, definition);
+ }
+
+ return NS_OK;
+ }
+
+ // Step 6.1.
+ if (synchronousCustomElements) {
+ definition->mPrefixStack.AppendElement(nodeInfo->GetPrefixAtom());
+ RefPtr<Document> doc = nodeInfo->GetDocument();
+ DoCustomElementCreate(aResult, cx, doc, nodeInfo,
+ MOZ_KnownLive(definition->mConstructor), rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ NS_IF_ADDREF(*aResult = NS_NewHTMLUnknownElement(nodeInfo.forget(),
+ aFromParser));
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetDefined(false);
+ }
+ definition->mPrefixStack.RemoveLastElement();
+ return NS_OK;
+ }
+
+ // Step 6.2.
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ NS_IF_ADDREF(*aResult =
+ NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetCustomElementData(
+ MakeUnique<CustomElementData>(definition->mType));
+ nsContentUtils::EnqueueUpgradeReaction(*aResult, definition);
+ return NS_OK;
+ }
+
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ // Per the Custom Element specification, unknown tags that are valid custom
+ // element names should be HTMLElement instead of HTMLUnknownElement.
+ if (isCustomElementName) {
+ NS_IF_ADDREF(*aResult =
+ NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+ } else {
+ *aResult = CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
+ }
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (isCustomElement) {
+ (*aResult)->SetCustomElementData(MakeUnique<CustomElementData>(typeAtom));
+ nsContentUtils::RegisterCallbackUpgradeElement(*aResult, typeAtom);
+ }
+
+ return NS_OK;
+}
+
+CustomElementRegistry* nsContentUtils::GetCustomElementRegistry(
+ Document* aDoc) {
+ MOZ_ASSERT(aDoc);
+
+ if (!aDoc->GetDocShell()) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->CustomElements();
+}
+
+/* static */
+CustomElementDefinition* nsContentUtils::LookupCustomElementDefinition(
+ Document* aDoc, nsAtom* aNameAtom, uint32_t aNameSpaceID,
+ nsAtom* aTypeAtom) {
+ if (aNameSpaceID != kNameSpaceID_XUL && aNameSpaceID != kNameSpaceID_XHTML) {
+ return nullptr;
+ }
+
+ RefPtr<CustomElementRegistry> registry = GetCustomElementRegistry(aDoc);
+ if (!registry) {
+ return nullptr;
+ }
+
+ return registry->LookupCustomElementDefinition(aNameAtom, aNameSpaceID,
+ aTypeAtom);
+}
+
+/* static */
+void nsContentUtils::RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterCallbackUpgradeElement(aElement, aTypeName);
+ }
+}
+
+/* static */
+void nsContentUtils::RegisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterUnresolvedElement(aElement, aTypeName);
+ }
+}
+
+/* static */
+void nsContentUtils::UnregisterUnresolvedElement(Element* aElement) {
+ MOZ_ASSERT(aElement);
+
+ nsAtom* typeAtom = aElement->GetCustomElementData()->GetCustomElementType();
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->UnregisterUnresolvedElement(aElement, typeAtom);
+ }
+}
+
+/* static */
+void nsContentUtils::EnqueueUpgradeReaction(
+ Element* aElement, CustomElementDefinition* aDefinition) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+
+ // No DocGroup means no custom element reactions stack.
+ if (!doc->GetDocGroup()) {
+ return;
+ }
+
+ CustomElementReactionsStack* stack =
+ doc->GetDocGroup()->CustomElementReactionsStack();
+ stack->EnqueueUpgradeReaction(aElement, aDefinition);
+}
+
+/* static */
+void nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType aType, Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
+ // No DocGroup means no custom element reactions stack.
+ if (!aCustomElement->OwnerDoc()->GetDocGroup()) {
+ return;
+ }
+
+ CustomElementRegistry::EnqueueLifecycleCallback(aType, aCustomElement, aArgs,
+ aDefinition);
+}
+
+/* static */
+void nsContentUtils::AppendDocumentLevelNativeAnonymousContentTo(
+ Document* aDocument, nsTArray<nsIContent*>& aElements) {
+ MOZ_ASSERT(aDocument);
+#ifdef DEBUG
+ size_t oldLength = aElements.Length();
+#endif
+
+ if (PresShell* presShell = aDocument->GetPresShell()) {
+ if (nsIFrame* scrollFrame = presShell->GetRootScrollFrame()) {
+ nsIAnonymousContentCreator* creator = do_QueryFrame(scrollFrame);
+ MOZ_ASSERT(
+ creator,
+ "scroll frame should always implement nsIAnonymousContentCreator");
+ creator->AppendAnonymousContentTo(aElements, 0);
+ }
+ if (nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame()) {
+ canvasFrame->AppendAnonymousContentTo(aElements, 0);
+ }
+ }
+
+#ifdef DEBUG
+ for (size_t i = oldLength; i < aElements.Length(); i++) {
+ MOZ_ASSERT(
+ aElements[i]->GetProperty(nsGkAtoms::docLevelNativeAnonymousContent),
+ "Someone here has lied, or missed to flag the node");
+ }
+#endif
+}
+
+static void AppendNativeAnonymousChildrenFromFrame(nsIFrame* aFrame,
+ nsTArray<nsIContent*>& aKids,
+ uint32_t aFlags) {
+ if (nsIAnonymousContentCreator* ac = do_QueryFrame(aFrame)) {
+ ac->AppendAnonymousContentTo(aKids, aFlags);
+ }
+}
+
+/* static */
+void nsContentUtils::AppendNativeAnonymousChildren(const nsIContent* aContent,
+ nsTArray<nsIContent*>& aKids,
+ uint32_t aFlags) {
+ if (aContent->MayHaveAnonymousChildren()) {
+ if (nsIFrame* primaryFrame = aContent->GetPrimaryFrame()) {
+ // NAC created by the element's primary frame.
+ AppendNativeAnonymousChildrenFromFrame(primaryFrame, aKids, aFlags);
+
+ // NAC created by any other non-primary frames for the element.
+ AutoTArray<nsIFrame::OwnedAnonBox, 8> ownedAnonBoxes;
+ primaryFrame->AppendOwnedAnonBoxes(ownedAnonBoxes);
+ for (nsIFrame::OwnedAnonBox& box : ownedAnonBoxes) {
+ MOZ_ASSERT(box.mAnonBoxFrame->GetContent() == aContent);
+ AppendNativeAnonymousChildrenFromFrame(box.mAnonBoxFrame, aKids,
+ aFlags);
+ }
+ }
+
+ // Get manually created NAC (editor resize handles, etc.).
+ if (auto nac = static_cast<ManualNACArray*>(
+ aContent->GetProperty(nsGkAtoms::manualNACProperty))) {
+ aKids.AppendElements(*nac);
+ }
+ }
+
+ // The root scroll frame is not the primary frame of the root element.
+ // Detect and handle this case.
+ if (!(aFlags & nsIContent::eSkipDocumentLevelNativeAnonymousContent) &&
+ aContent == aContent->OwnerDoc()->GetRootElement()) {
+ AppendDocumentLevelNativeAnonymousContentTo(aContent->OwnerDoc(), aKids);
+ }
+}
+
+bool nsContentUtils::IsImageAvailable(nsIContent* aLoadingNode, nsIURI* aURI,
+ nsIPrincipal* aDefaultTriggeringPrincipal,
+ CORSMode aCORSMode) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ QueryTriggeringPrincipal(aLoadingNode, aDefaultTriggeringPrincipal,
+ getter_AddRefs(triggeringPrincipal));
+ MOZ_ASSERT(triggeringPrincipal);
+
+ Document* doc = aLoadingNode->OwnerDoc();
+ return IsImageAvailable(aURI, triggeringPrincipal, aCORSMode, doc);
+}
+
+bool nsContentUtils::IsImageAvailable(nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ CORSMode aCORSMode, Document* aDoc) {
+ imgLoader* imgLoader = GetImgLoaderForDocument(aDoc);
+ return imgLoader->IsImageAvailable(aURI, aTriggeringPrincipal, aCORSMode,
+ aDoc);
+}
+
+/* static */
+bool nsContentUtils::QueryTriggeringPrincipal(
+ nsIContent* aLoadingNode, nsIPrincipal* aDefaultPrincipal,
+ nsIPrincipal** aTriggeringPrincipal) {
+ MOZ_ASSERT(aLoadingNode);
+ MOZ_ASSERT(aTriggeringPrincipal);
+
+ bool result = false;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aDefaultPrincipal;
+ if (!loadingPrincipal) {
+ loadingPrincipal = aLoadingNode->NodePrincipal();
+ }
+
+ // If aLoadingNode is content, bail out early.
+ if (!aLoadingNode->NodePrincipal()->IsSystemPrincipal()) {
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ return result;
+ }
+
+ nsAutoString loadingStr;
+ if (aLoadingNode->IsElement()) {
+ aLoadingNode->AsElement()->GetAttr(
+ kNameSpaceID_None, nsGkAtoms::triggeringprincipal, loadingStr);
+ }
+
+ // Fall back if 'triggeringprincipal' isn't specified,
+ if (loadingStr.IsEmpty()) {
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ return result;
+ }
+
+ nsCString binary;
+ nsCOMPtr<nsIPrincipal> serializedPrin =
+ BasePrincipal::FromJSON(NS_ConvertUTF16toUTF8(loadingStr));
+ if (serializedPrin) {
+ result = true;
+ serializedPrin.forget(aTriggeringPrincipal);
+ }
+
+ if (!result) {
+ // Fallback if the deserialization is failed.
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ }
+
+ return result;
+}
+
+/* static */
+void nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ nsIContent* aLoadingNode, nsIPrincipal** aTriggeringPrincipal,
+ nsContentPolicyType& aContentPolicyType, uint64_t* aRequestContextID) {
+ MOZ_ASSERT(aRequestContextID);
+
+ bool result = QueryTriggeringPrincipal(aLoadingNode, aTriggeringPrincipal);
+ if (result) {
+ // Set the content policy type to TYPE_INTERNAL_IMAGE_FAVICON for
+ // indicating it's a favicon loading.
+ aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON;
+
+ nsAutoString requestContextID;
+ if (aLoadingNode->IsElement()) {
+ aLoadingNode->AsElement()->GetAttr(
+ kNameSpaceID_None, nsGkAtoms::requestcontextid, requestContextID);
+ }
+ nsresult rv;
+ int64_t val = requestContextID.ToInteger64(&rv);
+ *aRequestContextID = NS_SUCCEEDED(rv) ? val : 0;
+ } else {
+ aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+ }
+}
+
+/* static */
+nsresult nsContentUtils::CreateJSValueFromSequenceOfObject(
+ JSContext* aCx, const Sequence<JSObject*>& aTransfer,
+ JS::MutableHandle<JS::Value> aValue) {
+ if (aTransfer.IsEmpty()) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, aTransfer.Length()));
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < aTransfer.Length(); ++i) {
+ JS::Rooted<JSObject*> object(aCx, aTransfer[i]);
+ if (!object) {
+ continue;
+ }
+
+ if (NS_WARN_IF(
+ !JS_DefineElement(aCx, array, i, object, JSPROP_ENUMERATE))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ aValue.setObject(*array);
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::StructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JS::Handle<JS::Value> aValue,
+ const StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError) {
+ JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
+ aError = nsContentUtils::CreateJSValueFromSequenceOfObject(
+ aCx, aOptions.mTransfer, &transferArray);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ JS::CloneDataPolicy clonePolicy;
+ // We are definitely staying in the same agent cluster.
+ clonePolicy.allowIntraClusterClonableSharedObjects();
+ if (aGlobal->IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ StructuredCloneHolder holder(StructuredCloneHolder::CloningSupported,
+ StructuredCloneHolder::TransferringSupported,
+ JS::StructuredCloneScope::SameProcess);
+ holder.Write(aCx, aValue, transferArray, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ holder.Read(aGlobal, aCx, aRetval, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ nsTArray<RefPtr<MessagePort>> ports = holder.TakeTransferredPorts();
+ Unused << ports;
+}
+
+/* static */
+bool nsContentUtils::ShouldBlockReservedKeys(WidgetKeyboardEvent* aKeyEvent) {
+ nsCOMPtr<nsIPrincipal> principal;
+ RefPtr<Element> targetElement =
+ Element::FromEventTargetOrNull(aKeyEvent->mOriginalTarget);
+ nsCOMPtr<nsIBrowser> targetBrowser;
+ if (targetElement) {
+ targetBrowser = targetElement->AsBrowser();
+ }
+ bool isRemoteBrowser = false;
+ if (targetBrowser) {
+ targetBrowser->GetIsRemoteBrowser(&isRemoteBrowser);
+ }
+
+ if (isRemoteBrowser) {
+ targetBrowser->GetContentPrincipal(getter_AddRefs(principal));
+ return principal ? nsContentUtils::IsSitePermDeny(principal, "shortcuts"_ns)
+ : false;
+ }
+
+ if (targetElement) {
+ Document* doc = targetElement->GetUncomposedDoc();
+ if (doc) {
+ RefPtr<WindowContext> wc = doc->GetWindowContext();
+ if (wc) {
+ return wc->TopWindowContext()->GetShortcutsPermission() ==
+ nsIPermissionManager::DENY_ACTION;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether the given type is a supported document type for
+ * loading within the nsObjectLoadingContent specified by aContent.
+ *
+ * NOTE Helper method for nsContentUtils::HtmlObjectContentTypeForMIMEType.
+ * NOTE Does not take content policy or capabilities into account
+ */
+static bool HtmlObjectContentSupportsDocument(const nsCString& aMimeType) {
+ nsCOMPtr<nsIWebNavigationInfo> info(
+ do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID));
+ if (!info) {
+ return false;
+ }
+
+ uint32_t supported;
+ nsresult rv = info->IsTypeSupported(aMimeType, &supported);
+
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (supported != nsIWebNavigationInfo::UNSUPPORTED) {
+ // Don't want to support plugins as documents
+ return supported != nsIWebNavigationInfo::FALLBACK;
+ }
+
+ // Try a stream converter
+ // NOTE: We treat any type we can convert from as a supported type. If a
+ // type is not actually supported, the URI loader will detect that and
+ // return an error, and we'll fallback.
+ nsCOMPtr<nsIStreamConverterService> convServ =
+ do_GetService("@mozilla.org/streamConverters;1");
+ bool canConvert = false;
+ if (convServ) {
+ rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert);
+ }
+ return NS_SUCCEEDED(rv) && canConvert;
+}
+
+/* static */
+already_AddRefed<nsIPluginTag> nsContentUtils::PluginTagForType(
+ const nsCString& aMIMEType, bool aNoFakePlugin) {
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ nsCOMPtr<nsIPluginTag> tag;
+ NS_ENSURE_TRUE(pluginHost, nullptr);
+
+ // ShouldPlay will handle the case where the plugin is disabled
+ pluginHost->GetPluginTagForType(
+ aMIMEType,
+ aNoFakePlugin ? nsPluginHost::eExcludeFake : nsPluginHost::eExcludeNone,
+ getter_AddRefs(tag));
+
+ return tag.forget();
+}
+
+/* static */
+uint32_t nsContentUtils::HtmlObjectContentTypeForMIMEType(
+ const nsCString& aMIMEType, bool aNoFakePlugin) {
+ if (aMIMEType.IsEmpty()) {
+ return nsIObjectLoadingContent::TYPE_NULL;
+ }
+
+ if (imgLoader::SupportImageWithMimeType(aMIMEType)) {
+ return ResolveObjectType(nsIObjectLoadingContent::TYPE_IMAGE);
+ }
+
+ // Faking support of the PDF content as a document for EMBED tags
+ // when internal PDF viewer is enabled.
+ if (aMIMEType.LowerCaseEqualsLiteral("application/pdf") && IsPDFJSEnabled()) {
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+ }
+
+ if (HtmlObjectContentSupportsDocument(aMIMEType)) {
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+ }
+
+ bool isSpecialPlugin = nsPluginHost::GetSpecialType(aMIMEType) !=
+ nsPluginHost::eSpecialType_None;
+ if (isSpecialPlugin) {
+ return nsIObjectLoadingContent::TYPE_FALLBACK;
+ }
+
+ return nsIObjectLoadingContent::TYPE_NULL;
+}
+
+/* static */
+already_AddRefed<nsISerialEventTarget> nsContentUtils::GetEventTargetByLoadInfo(
+ nsILoadInfo* aLoadInfo, TaskCategory aCategory) {
+ if (NS_WARN_IF(!aLoadInfo)) {
+ return nullptr;
+ }
+
+ RefPtr<Document> doc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ nsCOMPtr<nsISerialEventTarget> target;
+ if (doc) {
+ if (DocGroup* group = doc->GetDocGroup()) {
+ target = group->EventTargetFor(aCategory);
+ }
+ } else {
+ target = GetMainThreadSerialEventTarget();
+ }
+
+ return target.forget();
+}
+
+/* static */
+bool nsContentUtils::IsLocalRefURL(const nsAString& aString) {
+ return !aString.IsEmpty() && aString[0] == '#';
+}
+
+// We use only 53 bits for the ID so that it can be converted to and from a JS
+// value without loss of precision. The upper bits of the ID hold the process
+// ID. The lower bits identify the object itself.
+static constexpr uint64_t kIdTotalBits = 53;
+static constexpr uint64_t kIdProcessBits = 22;
+static constexpr uint64_t kIdBits = kIdTotalBits - kIdProcessBits;
+
+/* static */
+uint64_t nsContentUtils::GenerateProcessSpecificId(uint64_t aId) {
+ uint64_t processId = 0;
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ processId = cc->GetID();
+ }
+
+ MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
+ uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
+
+ uint64_t id = aId;
+ MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
+ uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
+
+ return (processBits << kIdBits) | bits;
+}
+
+/* static */
+std::tuple<uint64_t, uint64_t> nsContentUtils::SplitProcessSpecificId(
+ uint64_t aId) {
+ return {aId >> kIdBits, aId & ((uint64_t(1) << kIdBits) - 1)};
+}
+
+// Next process-local Tab ID.
+static uint64_t gNextTabId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateTabId() {
+ return GenerateProcessSpecificId(++gNextTabId);
+}
+
+// Next process-local Browser ID.
+static uint64_t gNextBrowserId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateBrowserId() {
+ return GenerateProcessSpecificId(++gNextBrowserId);
+}
+
+// Next process-local Browsing Context ID.
+static uint64_t gNextBrowsingContextId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateBrowsingContextId() {
+ return GenerateProcessSpecificId(++gNextBrowsingContextId);
+}
+
+// Next process-local Window ID.
+static uint64_t gNextWindowId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateWindowId() {
+ return GenerateProcessSpecificId(++gNextWindowId);
+}
+
+// Next process-local load.
+static Atomic<uint64_t> gNextLoadIdentifier(0);
+
+/* static */
+uint64_t nsContentUtils::GenerateLoadIdentifier() {
+ return GenerateProcessSpecificId(++gNextLoadIdentifier);
+}
+
+/* static */
+bool nsContentUtils::GetUserIsInteracting() {
+ return UserInteractionObserver::sUserActive;
+}
+
+/* static */
+bool nsContentUtils::GetSourceMapURL(nsIHttpChannel* aChannel,
+ nsACString& aResult) {
+ nsresult rv = aChannel->GetResponseHeader("SourceMap"_ns, aResult);
+ if (NS_FAILED(rv)) {
+ rv = aChannel->GetResponseHeader("X-SourceMap"_ns, aResult);
+ }
+ return NS_SUCCEEDED(rv);
+}
+
+/* static */
+bool nsContentUtils::IsMessageInputEvent(const IPC::Message& aMsg) {
+ if ((aMsg.type() & mozilla::dom::PBrowser::PBrowserStart) ==
+ mozilla::dom::PBrowser::PBrowserStart) {
+ switch (aMsg.type()) {
+ case mozilla::dom::PBrowser::Msg_RealMouseMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealMouseButtonEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealMouseEnterExitWidgetEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealKeyEvent__ID:
+ case mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealDragEvent__ID:
+ case mozilla::dom::PBrowser::Msg_UpdateDimensions__ID:
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsContentUtils::IsMessageCriticalInputEvent(const IPC::Message& aMsg) {
+ if ((aMsg.type() & mozilla::dom::PBrowser::PBrowserStart) ==
+ mozilla::dom::PBrowser::PBrowserStart) {
+ switch (aMsg.type()) {
+ case mozilla::dom::PBrowser::Msg_RealMouseButtonEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealKeyEvent__ID:
+ case mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealDragEvent__ID:
+ return true;
+ }
+ }
+ return false;
+}
+
+static const char* kUserInteractionInactive = "user-interaction-inactive";
+static const char* kUserInteractionActive = "user-interaction-active";
+
+void nsContentUtils::UserInteractionObserver::Init() {
+ // Listen for the observer messages from EventStateManager which are telling
+ // us whether or not the user is interacting.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, kUserInteractionInactive, false);
+ obs->AddObserver(this, kUserInteractionActive, false);
+
+ // We can't register ourselves as an annotator yet, as the
+ // BackgroundHangMonitor hasn't started yet. It will have started by the
+ // time we have the chance to spin the event loop.
+ RefPtr<UserInteractionObserver> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsContentUtils::UserInteractionObserver::Init",
+ [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); }));
+}
+
+void nsContentUtils::UserInteractionObserver::Shutdown() {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, kUserInteractionInactive);
+ obs->RemoveObserver(this, kUserInteractionActive);
+ }
+
+ BackgroundHangMonitor::UnregisterAnnotator(*this);
+}
+
+/**
+ * NB: This function is always called by the BackgroundHangMonitor thread.
+ * Plan accordingly
+ */
+void nsContentUtils::UserInteractionObserver::AnnotateHang(
+ BackgroundHangAnnotations& aAnnotations) {
+ // NOTE: Only annotate the hang report if the user is known to be interacting.
+ if (sUserActive) {
+ aAnnotations.AddAnnotation(u"UserInteracting"_ns, true);
+ }
+}
+
+NS_IMETHODIMP
+nsContentUtils::UserInteractionObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, kUserInteractionInactive)) {
+ if (sUserActive && XRE_IsParentProcess()) {
+ glean::RecordPowerMetrics();
+ }
+ sUserActive = false;
+ } else if (!strcmp(aTopic, kUserInteractionActive)) {
+ if (!sUserActive && XRE_IsParentProcess()) {
+ glean::RecordPowerMetrics();
+
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+ }
+
+ sUserActive = true;
+ } else {
+ NS_WARNING("Unexpected observer notification");
+ }
+ return NS_OK;
+}
+
+Atomic<bool> nsContentUtils::UserInteractionObserver::sUserActive(false);
+NS_IMPL_ISUPPORTS(nsContentUtils::UserInteractionObserver, nsIObserver)
+
+/* static */
+bool nsContentUtils::IsSpecialName(const nsAString& aName) {
+ return aName.LowerCaseEqualsLiteral("_blank") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent") ||
+ aName.LowerCaseEqualsLiteral("_self");
+}
+
+/* static */
+bool nsContentUtils::IsOverridingWindowName(const nsAString& aName) {
+ return !aName.IsEmpty() && !IsSpecialName(aName);
+}
+
+// Unfortunately, we can't unwrap an IDL object using only a concrete type.
+// We need to calculate type data based on the IDL typename. Which means
+// wrapping our templated function in a macro.
+#define EXTRACT_EXN_VALUES(T, ...) \
+ ExtractExceptionValues<mozilla::dom::prototypes::id::T, \
+ T##_Binding::NativeType, T>(__VA_ARGS__) \
+ .isOk()
+
+template <prototypes::ID PrototypeID, class NativeType, typename T>
+static Result<Ok, nsresult> ExtractExceptionValues(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, nsAString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ AssertStaticUnwrapOK<PrototypeID>();
+ RefPtr<T> exn;
+ MOZ_TRY((UnwrapObject<PrototypeID, NativeType>(aObj, exn, nullptr)));
+
+ exn->GetFilename(aCx, aSourceSpecOut);
+ if (!aSourceSpecOut.IsEmpty()) {
+ *aLineOut = exn->LineNumber(aCx);
+ *aColumnOut = exn->ColumnNumber();
+ }
+
+ exn->GetName(aMessageOut);
+ aMessageOut.AppendLiteral(": ");
+
+ nsAutoString message;
+ exn->GetMessageMoz(message);
+ aMessageOut.Append(message);
+ return Ok();
+}
+
+/* static */
+void nsContentUtils::ExtractErrorValues(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, nsACString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ nsAutoString sourceSpec;
+ ExtractErrorValues(aCx, aValue, sourceSpec, aLineOut, aColumnOut,
+ aMessageOut);
+ CopyUTF16toUTF8(sourceSpec, aSourceSpecOut);
+}
+
+/* static */
+void nsContentUtils::ExtractErrorValues(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, nsAString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ MOZ_ASSERT(aLineOut);
+ MOZ_ASSERT(aColumnOut);
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ // Try to process as an Error object. Use the file/line/column values
+ // from the Error as they will be more specific to the root cause of
+ // the problem.
+ JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
+ if (err) {
+ // Use xpc to extract the error message only. We don't actually send
+ // this report anywhere.
+ RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
+ report->Init(err,
+ nullptr, // toString result
+ false, // chrome
+ 0); // window ID
+
+ if (!report->mFileName.IsEmpty()) {
+ aSourceSpecOut = report->mFileName;
+ *aLineOut = report->mLineNumber;
+ *aColumnOut = report->mColumn;
+ }
+ aMessageOut.Assign(report->mErrorMsg);
+ }
+
+ // Next, try to unwrap the rejection value as a DOMException.
+ else if (EXTRACT_EXN_VALUES(DOMException, aCx, obj, aSourceSpecOut,
+ aLineOut, aColumnOut, aMessageOut)) {
+ return;
+ }
+
+ // Next, try to unwrap the rejection value as an XPC Exception.
+ else if (EXTRACT_EXN_VALUES(Exception, aCx, obj, aSourceSpecOut, aLineOut,
+ aColumnOut, aMessageOut)) {
+ return;
+ }
+ }
+
+ // If we could not unwrap a specific error type, then perform default safe
+ // string conversions on primitives. Objects will result in "[Object]"
+ // unfortunately.
+ if (aMessageOut.IsEmpty()) {
+ nsAutoJSString jsString;
+ if (jsString.init(aCx, aValue)) {
+ aMessageOut = jsString;
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+}
+
+#undef EXTRACT_EXN_VALUES
+
+/* static */
+bool nsContentUtils::ContentIsLink(nsIContent* aContent) {
+ if (!aContent || !aContent->IsElement()) {
+ return false;
+ }
+
+ if (aContent->IsHTMLElement(nsGkAtoms::a)) {
+ return true;
+ }
+
+ return aContent->AsElement()->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type,
+ nsGkAtoms::simple, eCaseMatters);
+}
+
+/* static */
+already_AddRefed<ContentFrameMessageManager>
+nsContentUtils::TryGetBrowserChildGlobal(nsISupports* aFrom) {
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(aFrom);
+ if (!frameLoaderOwner) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ if (!frameLoader) {
+ return nullptr;
+ }
+
+ RefPtr<ContentFrameMessageManager> manager =
+ frameLoader->GetBrowserChildMessageManager();
+ return manager.forget();
+}
+
+/* static */
+uint32_t nsContentUtils::InnerOrOuterWindowCreated() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++sInnerOrOuterWindowCount;
+ return ++sInnerOrOuterWindowSerialCounter;
+}
+
+/* static */
+void nsContentUtils::InnerOrOuterWindowDestroyed() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sInnerOrOuterWindowCount > 0);
+ --sInnerOrOuterWindowCount;
+}
+
+/* static */
+nsresult nsContentUtils::AnonymizeURI(nsIURI* aURI, nsCString& aAnonymizedURI) {
+ MOZ_ASSERT(aURI);
+
+ if (aURI->SchemeIs("data")) {
+ aAnonymizedURI.Assign("data:..."_ns);
+ return NS_OK;
+ }
+ // Anonymize the URL.
+ // Strip the URL of any possible username/password and make it ready to be
+ // presented in the UI.
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(aURI);
+ return exposableURI->GetSpec(aAnonymizedURI);
+}
+
+static bool JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) {
+ nsAString* result = static_cast<nsAString*>(aData);
+ result->Append(aBuf, aLen);
+ return true;
+}
+
+/* static */
+bool nsContentUtils::StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsAString& aOutStr, JSONBehavior aBehavior) {
+ MOZ_ASSERT(aCx);
+ switch (aBehavior) {
+ case UndefinedIsNullStringLiteral: {
+ aOutStr.Truncate();
+ JS::Rooted<JS::Value> value(aCx, aValue);
+ nsAutoString serializedValue;
+ NS_ENSURE_TRUE(JS_Stringify(aCx, &value, nullptr, JS::NullHandleValue,
+ JSONCreator, &serializedValue),
+ false);
+ aOutStr = serializedValue;
+ return true;
+ }
+ case UndefinedIsVoidString: {
+ aOutStr.SetIsVoid(true);
+ return JS::ToJSON(aCx, aValue, nullptr, JS::NullHandleValue, JSONCreator,
+ &aOutStr);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid value for aBehavior");
+ return false;
+ }
+}
+
+/* static */
+bool nsContentUtils::
+ HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+ Document* aDocument) {
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "This function only makes sense in content processes");
+
+ if (aDocument && !aDocument->IsLoadedAsData()) {
+ if (nsPresContext* presContext = FindPresContextForDocument(aDocument)) {
+ MOZ_ASSERT(!presContext->IsChrome(),
+ "Should never have a chrome PresContext in a content process");
+
+ return !presContext->GetInProcessRootContentDocumentPresContext()
+ ->HadFirstContentfulPaint() &&
+ nsThreadManager::MainThreadHasPendingHighPriorityEvents();
+ }
+ }
+ return false;
+}
+
+static nsGlobalWindowInner* GetInnerWindowForGlobal(nsIGlobalObject* aGlobal) {
+ NS_ENSURE_TRUE(aGlobal, nullptr);
+
+ if (auto* window = aGlobal->AsInnerWindow()) {
+ return nsGlobalWindowInner::Cast(window);
+ }
+
+ // When Extensions run content scripts inside a sandbox, it uses
+ // sandboxPrototype to make them appear as though they're running in the
+ // scope of the page. So when a content script invokes postMessage, it expects
+ // the |source| of the received message to be the window set as the
+ // sandboxPrototype. This used to work incidentally for unrelated reasons, but
+ // now we need to do some special handling to support it.
+ JS::Rooted<JSObject*> scope(RootingCx(), aGlobal->GetGlobalJSObject());
+ NS_ENSURE_TRUE(scope, nullptr);
+
+ if (xpc::IsSandbox(scope)) {
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(scope));
+ JSContext* cx = jsapi.cx();
+ // Our current Realm on aCx is the sandbox. Using that for unwrapping
+ // makes sense: if the sandbox can unwrap the window, we can use it.
+ return xpc::SandboxWindowOrNull(scope, cx);
+ }
+
+ // The calling window must be holding a reference, so we can return a weak
+ // pointer.
+ return nsGlobalWindowInner::Cast(aGlobal->AsInnerWindow());
+}
+
+/* static */
+nsGlobalWindowInner* nsContentUtils::IncumbentInnerWindow() {
+ return GetInnerWindowForGlobal(GetIncumbentGlobal());
+}
+
+/* static */
+nsGlobalWindowInner* nsContentUtils::EntryInnerWindow() {
+ return GetInnerWindowForGlobal(GetEntryGlobal());
+}
+
+/* static */
+bool nsContentUtils::IsURIInPrefList(nsIURI* aURI, const char* aPrefName) {
+ MOZ_ASSERT(aPrefName);
+
+ nsAutoCString list;
+ Preferences::GetCString(aPrefName, list);
+ ToLowerCase(list);
+ return IsURIInList(aURI, list);
+}
+
+/* static */
+bool nsContentUtils::IsURIInList(nsIURI* aURI, const nsCString& aList) {
+#ifdef DEBUG
+ nsAutoCString listLowerCase(aList);
+ ToLowerCase(listLowerCase);
+ MOZ_ASSERT(listLowerCase.Equals(aList),
+ "The aList argument should be lower-case");
+#endif
+
+ if (!aURI) {
+ return false;
+ }
+
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
+ return false;
+ }
+
+ if (aList.IsEmpty()) {
+ return false;
+ }
+
+ // The list is comma separated domain list. Each item may start with "*.".
+ // If starts with "*.", it matches any sub-domains.
+
+ nsCCharSeparatedTokenizer tokenizer(aList, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCString token(tokenizer.nextToken());
+
+ nsAutoCString host;
+ aURI->GetHost(host);
+ if (host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ for (;;) {
+ int32_t index = token.Find(host);
+ if (index >= 0 &&
+ static_cast<uint32_t>(index) + host.Length() <= token.Length()) {
+ // If we found a full match, return true.
+ size_t indexAfterHost = index + host.Length();
+ if (index == 0 && indexAfterHost == token.Length()) {
+ return true;
+ }
+ // If next character is '/', we need to check the path too.
+ // We assume the path in the list means "/foo" + "*".
+ if (token[indexAfterHost] == '/') {
+ nsDependentCSubstring pathInList(
+ token, indexAfterHost,
+ static_cast<nsDependentCSubstring::size_type>(-1));
+ nsAutoCString filePath;
+ aURI->GetFilePath(filePath);
+ ToLowerCase(filePath);
+ if (StringBeginsWith(filePath, pathInList) &&
+ (filePath.Length() == pathInList.Length() ||
+ pathInList.EqualsLiteral("/") ||
+ filePath[pathInList.Length() - 1] == '/' ||
+ filePath[pathInList.Length() - 1] == '?' ||
+ filePath[pathInList.Length() - 1] == '#')) {
+ return true;
+ }
+ }
+ }
+ int32_t startIndexOfCurrentLevel = host[0] == '*' ? 1 : 0;
+ int32_t startIndexOfNextLevel =
+ host.Find(".", startIndexOfCurrentLevel + 1);
+ if (startIndexOfNextLevel <= 0) {
+ break;
+ }
+ host.ReplaceLiteral(0, startIndexOfNextLevel, "*");
+ }
+ }
+
+ return false;
+}
+
+/* static */
+ScreenIntMargin nsContentUtils::GetWindowSafeAreaInsets(
+ nsIScreen* aScreen, const ScreenIntMargin& aSafeAreaInsets,
+ const LayoutDeviceIntRect& aWindowRect) {
+ // This calculates safe area insets of window from screen rectangle, window
+ // rectangle and safe area insets of screen.
+ //
+ // +----------------------------------------+ <-- screen
+ // | +-------------------------------+ <------- window
+ // | | window's safe area inset top) | |
+ // +--+-------------------------------+--+ |
+ // | | | |<------ safe area rectangle of
+ // | | | | | screen
+ // +--+-------------------------------+--+ |
+ // | |window's safe area inset bottom| |
+ // | +-------------------------------+ |
+ // +----------------------------------------+
+ //
+ ScreenIntMargin windowSafeAreaInsets;
+
+ if (windowSafeAreaInsets == aSafeAreaInsets) {
+ // no safe area insets.
+ return windowSafeAreaInsets;
+ }
+
+ int32_t screenLeft, screenTop, screenWidth, screenHeight;
+ nsresult rv =
+ aScreen->GetRect(&screenLeft, &screenTop, &screenWidth, &screenHeight);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return windowSafeAreaInsets;
+ }
+
+ const ScreenIntRect screenRect(screenLeft, screenTop, screenWidth,
+ screenHeight);
+
+ ScreenIntRect safeAreaRect = screenRect;
+ safeAreaRect.Deflate(aSafeAreaInsets);
+
+ ScreenIntRect windowRect = ViewAs<ScreenPixel>(
+ aWindowRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+
+ // FIXME(bug 1754323): This can trigger because the screen rect is not
+ // orientation-aware.
+ // MOZ_ASSERT(screenRect.Contains(windowRect),
+ // "Screen doesn't contain window rect? Something seems off");
+
+ // window's rect of safe area
+ safeAreaRect = safeAreaRect.Intersect(windowRect);
+
+ windowSafeAreaInsets.top = safeAreaRect.y - aWindowRect.y;
+ windowSafeAreaInsets.left = safeAreaRect.x - aWindowRect.x;
+ windowSafeAreaInsets.right =
+ aWindowRect.x + aWindowRect.width - (safeAreaRect.x + safeAreaRect.width);
+ windowSafeAreaInsets.bottom = aWindowRect.y + aWindowRect.height -
+ (safeAreaRect.y + safeAreaRect.height);
+
+ windowSafeAreaInsets.EnsureAtLeast(ScreenIntMargin());
+ // This shouldn't be needed, but it wallpapers orientation issues, see bug
+ // 1754323.
+ windowSafeAreaInsets.EnsureAtMost(aSafeAreaInsets);
+
+ return windowSafeAreaInsets;
+}
+
+/* static */
+nsContentUtils::SubresourceCacheValidationInfo
+nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
+ nsIURI* aURI) {
+ SubresourceCacheValidationInfo info;
+ if (nsCOMPtr<nsICacheInfoChannel> cache = do_QueryInterface(aRequest)) {
+ uint32_t value = 0;
+ if (NS_SUCCEEDED(cache->GetCacheTokenExpirationTime(&value))) {
+ info.mExpirationTime.emplace(value);
+ }
+ }
+
+ // Determine whether the cache entry must be revalidated when we try to use
+ // it. Currently, only HTTP specifies this information...
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
+ Unused << httpChannel->IsNoStoreResponse(&info.mMustRevalidate);
+
+ if (!info.mMustRevalidate) {
+ Unused << httpChannel->IsNoCacheResponse(&info.mMustRevalidate);
+ }
+ }
+
+ // data: URIs are safe to cache across documents under any circumstance, so we
+ // special-case them here even though the channel itself doesn't have any
+ // caching policy. Same for chrome:// uris.
+ //
+ // TODO(emilio): Figure out which other schemes that don't have caching
+ // policies are safe to cache. Blobs should be...
+ const bool knownCacheable = [&] {
+ if (!aURI) {
+ return false;
+ }
+ if (aURI->SchemeIs("data") || aURI->SchemeIs("moz-page-thumb") ||
+ aURI->SchemeIs("moz-extension")) {
+ return true;
+ }
+ if (aURI->SchemeIs("chrome") || aURI->SchemeIs("resource")) {
+ return !StaticPrefs::nglayout_debug_disable_xul_cache();
+ }
+ return false;
+ }();
+
+ if (knownCacheable) {
+ MOZ_ASSERT(!info.mExpirationTime);
+ MOZ_ASSERT(!info.mMustRevalidate);
+ info.mExpirationTime = Some(0); // 0 means "doesn't expire".
+ }
+
+ return info;
+}
+
+nsCString nsContentUtils::TruncatedURLForDisplay(nsIURI* aURL, size_t aMaxLen) {
+ nsCString spec;
+ if (aURL) {
+ aURL->GetSpec(spec);
+ spec.Truncate(std::min(aMaxLen, spec.Length()));
+ }
+ return spec;
+}
+
+/* static */
+nsresult nsContentUtils::AnonymizeId(nsAString& aId,
+ const nsACString& aOriginKey,
+ OriginFormat aFormat) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCString rawKey;
+ if (aFormat == OriginFormat::Base64) {
+ rv = Base64Decode(aOriginKey, rawKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rawKey = aOriginKey;
+ }
+
+ HMAC hmac;
+ rv = hmac.Begin(
+ SEC_OID_SHA256,
+ Span(reinterpret_cast<const uint8_t*>(rawKey.get()), rawKey.Length()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 id(aId);
+ rv = hmac.Update(reinterpret_cast<const uint8_t*>(id.get()), id.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<uint8_t> macBytes;
+ rv = hmac.End(macBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString macBase64;
+ rv = Base64Encode(
+ nsDependentCSubstring(reinterpret_cast<const char*>(macBytes.Elements()),
+ macBytes.Length()),
+ macBase64);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(macBase64, aId);
+ return NS_OK;
+}
+
+/* static */
+bool nsContentUtils::ShouldHideObjectOrEmbedImageDocument() {
+ return StaticPrefs::
+ browser_opaqueResponseBlocking_syntheticBrowsingContext_AtStartup() &&
+ StaticPrefs::
+ browser_opaqueResponseBlocking_syntheticBrowsingContext_filter_AtStartup_DoNotUseDirectly();
+}
+
+/* static */
+uint32_t nsContentUtils::ResolveObjectType(uint32_t aType) {
+ if (!StaticPrefs::
+ browser_opaqueResponseBlocking_syntheticBrowsingContext_AtStartup()) {
+ return aType;
+ }
+
+ if (aType != nsIObjectLoadingContent::TYPE_IMAGE) {
+ return aType;
+ }
+
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+}
+
+void nsContentUtils::RequestGeckoTaskBurst() {
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(NS_APPSHELL_CID);
+ if (appShell) {
+ appShell->GeckoTaskBurst();
+ }
+}
+
+nsIContent* nsContentUtils::GetClosestLinkInFlatTree(nsIContent* aContent) {
+ for (nsIContent* content = aContent; content;
+ content = content->GetFlattenedTreeParent()) {
+ if (nsContentUtils::IsDraggableLink(content)) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+namespace mozilla {
+std::ostream& operator<<(std::ostream& aOut,
+ const PreventDefaultResult aPreventDefaultResult) {
+ switch (aPreventDefaultResult) {
+ case PreventDefaultResult::No:
+ aOut << "unhandled";
+ break;
+ case PreventDefaultResult::ByContent:
+ aOut << "handled-by-content";
+ break;
+ case PreventDefaultResult::ByChrome:
+ aOut << "handled-by-chrome";
+ break;
+ }
+ return aOut;
+}
+} // namespace mozilla
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
new file mode 100644
index 0000000000..79c2e9c4b1
--- /dev/null
+++ b/dom/base/nsContentUtils.h
@@ -0,0 +1,3680 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A namespace class for static content utilities. */
+
+#ifndef nsContentUtils_h___
+#define nsContentUtils_h___
+
+#if defined(XP_WIN)
+# include <float.h>
+#endif
+
+#if defined(SOLARIS)
+# include <ieeefp.h>
+#endif
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <tuple>
+#include <utility>
+#include "ErrorList.h"
+#include "Units.h"
+#include "js/Id.h"
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/CallState.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskCategory.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/Point.h"
+#include "nsCOMPtr.h"
+#include "nsHashtablesFwd.h"
+#include "nsIContentPolicy.h"
+#include "nsINode.h"
+#include "nsIScriptError.h"
+#include "nsIThread.h"
+#include "nsLiteralString.h"
+#include "nsMargin.h"
+#include "nsPIDOMWindow.h"
+#include "nsRFPService.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "prtime.h"
+
+#if defined(XP_WIN)
+// Undefine LoadImage to prevent naming conflict with Windows.
+# undef LoadImage
+#endif
+
+class JSObject;
+class imgICache;
+class imgIContainer;
+class imgINotificationObserver;
+class imgIRequest;
+class imgLoader;
+class imgRequestProxy;
+class nsAtom;
+class nsAttrValue;
+class nsAutoScriptBlockerSuppressNodeRemoved;
+class nsContentList;
+class nsCycleCollectionTraversalCallback;
+class nsDocShell;
+class nsGlobalWindowInner;
+class nsHtml5StringParser;
+class nsIArray;
+class nsIBidiKeyboard;
+class nsIChannel;
+class nsIConsoleService;
+class nsIContent;
+class nsIDocShell;
+class nsIDocShellTreeItem;
+class nsIDocumentLoaderFactory;
+class nsIDragSession;
+class nsIFile;
+class nsIFragmentContentSink;
+class nsIFrame;
+class nsIHttpChannel;
+class nsIIOService;
+class nsIImageLoadingContent;
+class nsIInterfaceRequestor;
+class nsILoadGroup;
+class nsILoadInfo;
+class nsIObserver;
+class nsIPluginTag;
+class nsIPrincipal;
+class nsIReferrerInfo;
+class nsIRequest;
+class nsIRunnable;
+class nsIScreen;
+class nsIScriptContext;
+class nsIScriptSecurityManager;
+class nsISerialEventTarget;
+class nsIStringBundle;
+class nsIStringBundleService;
+class nsISupports;
+class nsITransferable;
+class nsIURI;
+class nsIWidget;
+class nsIWritableVariant;
+class nsIXPConnect;
+class nsNodeInfoManager;
+class nsParser;
+class nsPIWindowRoot;
+class nsPresContext;
+class nsStringBuffer;
+class nsStringHashKey;
+class nsTextFragment;
+class nsView;
+class nsWrapperCache;
+
+struct JSContext;
+struct nsPoint;
+
+template <class T>
+class nsRefPtrHashKey;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace JS {
+class Value;
+class PropertyDescriptor;
+} // namespace JS
+
+namespace mozilla {
+class Dispatcher;
+class EditorBase;
+class ErrorResult;
+class EventListenerManager;
+class HTMLEditor;
+class LazyLogModule;
+class LogModule;
+class PresShell;
+class TextEditor;
+class WidgetDragEvent;
+class WidgetKeyboardEvent;
+
+struct InputEventOptions;
+
+template <typename ParentType, typename RefType>
+class RangeBoundaryBase;
+
+template <typename T>
+class NotNull;
+template <class T>
+class StaticRefPtr;
+
+namespace dom {
+class IPCImage;
+struct AutocompleteInfo;
+class BrowserChild;
+class BrowserParent;
+class BrowsingContext;
+class BrowsingContextGroup;
+class ContentChild;
+class ContentFrameMessageManager;
+class ContentParent;
+struct CustomElementDefinition;
+class CustomElementRegistry;
+class DataTransfer;
+class Document;
+class DocumentFragment;
+class DOMArena;
+class Element;
+class Event;
+class EventTarget;
+class HTMLInputElement;
+class IPCTransferable;
+class IPCTransferableData;
+class IPCTransferableDataImageContainer;
+class IPCTransferableDataItem;
+struct LifecycleCallbackArgs;
+class MessageBroadcaster;
+class NodeInfo;
+class Selection;
+struct StructuredSerializeOptions;
+class WorkerPrivate;
+enum class ElementCallbackType;
+enum class ReferrerPolicy : uint8_t;
+} // namespace dom
+
+namespace ipc {
+class BigBuffer;
+class IProtocol;
+} // namespace ipc
+
+namespace gfx {
+class DataSourceSurface;
+enum class SurfaceFormat : int8_t;
+} // namespace gfx
+
+class WindowRenderer;
+
+} // namespace mozilla
+
+extern const char kLoadAsData[];
+
+// Stolen from nsReadableUtils, but that's OK, since we can declare the same
+// name multiple times.
+const nsString& EmptyString();
+const nsCString& EmptyCString();
+
+enum EventNameType {
+ EventNameType_None = 0x0000,
+ EventNameType_HTML = 0x0001,
+ EventNameType_XUL = 0x0002,
+ EventNameType_SVGGraphic = 0x0004, // svg graphic elements
+ EventNameType_SVGSVG = 0x0008, // the svg element
+ EventNameType_SMIL = 0x0010, // smil elements
+ EventNameType_HTMLBodyOrFramesetOnly = 0x0020,
+ EventNameType_HTMLMarqueeOnly = 0x0040,
+
+ EventNameType_HTMLXUL = 0x0003,
+ EventNameType_All = 0xFFFF
+};
+
+struct EventNameMapping {
+ // This holds pointers to nsGkAtoms members, and is therefore safe as a
+ // non-owning reference.
+ nsAtom* MOZ_NON_OWNING_REF mAtom;
+ int32_t mType;
+ mozilla::EventMessage mMessage;
+ mozilla::EventClassID mEventClassID;
+ // True if mAtom is possibly used by special SVG/SMIL events, but
+ // mMessage is eUnidentifiedEvent. See EventNameList.h
+ bool mMaybeSpecialSVGorSMILEvent;
+};
+
+namespace mozilla {
+enum class PreventDefaultResult : uint8_t { No, ByContent, ByChrome };
+
+namespace dom {
+enum JSONBehavior { UndefinedIsNullStringLiteral, UndefinedIsVoidString };
+}
+} // namespace mozilla
+
+class nsContentUtils {
+ friend class nsAutoScriptBlockerSuppressNodeRemoved;
+ using Element = mozilla::dom::Element;
+ using Document = mozilla::dom::Document;
+ using Cancelable = mozilla::Cancelable;
+ using CanBubble = mozilla::CanBubble;
+ using Composed = mozilla::Composed;
+ using ChromeOnlyDispatch = mozilla::ChromeOnlyDispatch;
+ using EventMessage = mozilla::EventMessage;
+ using TimeDuration = mozilla::TimeDuration;
+ using Trusted = mozilla::Trusted;
+ using JSONBehavior = mozilla::dom::JSONBehavior;
+ using RFPTarget = mozilla::RFPTarget;
+
+ public:
+ static nsresult Init();
+
+ static bool IsCallerChrome();
+ static bool ThreadsafeIsCallerChrome();
+ static bool IsCallerUAWidget();
+ static bool IsFuzzingEnabled()
+#ifndef FUZZING
+ {
+ return false;
+ }
+#else
+ ;
+#endif
+ static bool IsErrorPage(nsIURI* aURI);
+
+ static bool IsCallerChromeOrFuzzingEnabled(JSContext* aCx, JSObject*) {
+ return ThreadsafeIsSystemCaller(aCx) || IsFuzzingEnabled();
+ }
+
+ static bool IsCallerChromeOrElementTransformGettersEnabled(JSContext* aCx,
+ JSObject*);
+
+ // The APIs for checking whether the caller is system (in the sense of system
+ // principal) should only be used when the JSContext is known to accurately
+ // represent the caller. In practice, that means you should only use them in
+ // two situations at the moment:
+ //
+ // 1) Functions used in WebIDL Func annotations.
+ // 2) Bindings code or other code called directly from the JS engine.
+ //
+ // Use pretty much anywhere else is almost certainly wrong and should be
+ // replaced with [NeedsCallerType] annotations in bindings.
+
+ // Check whether the caller is system if you know you're on the main thread.
+ static bool IsSystemCaller(JSContext* aCx);
+
+ // Check whether the caller is system if you might be on a worker or worklet
+ // thread.
+ static bool ThreadsafeIsSystemCaller(JSContext* aCx);
+
+ // In the traditional Gecko architecture, both C++ code and untrusted JS code
+ // needed to rely on the same XPCOM method/getter/setter to get work done.
+ // This required lots of security checks in the various exposed methods, which
+ // in turn created difficulty in determining whether the caller was script
+ // (whose access needed to be checked) and internal C++ platform code (whose
+ // access did not need to be checked). To address this problem, Gecko had a
+ // convention whereby the absence of script on the stack was interpretted as
+ // "System Caller" and always granted unfettered access.
+ //
+ // Unfortunately, this created a bunch of footguns. For example, when the
+ // implementation of a DOM method wanted to perform a privileged
+ // sub-operation, it needed to "hide" the presence of script on the stack in
+ // order for that sub-operation to be allowed. Additionally, if script could
+ // trigger an API entry point to be invoked in some asynchronous way without
+ // script on the stack, it could potentially perform privilege escalation.
+ //
+ // In the modern world, untrusted script should interact with the platform
+ // exclusively over WebIDL APIs, and platform code has a lot more flexibility
+ // in deciding whether or not to use XPCOM. This gives us the flexibility to
+ // do something better.
+ //
+ // Going forward, APIs should be designed such that any security checks that
+ // ask the question "is my caller allowed to do this?" should live in WebIDL
+ // API entry points, with a separate method provided for internal callers
+ // that just want to get the job done.
+ //
+ // To enforce this and catch bugs, nsContentUtils::SubjectPrincipal will crash
+ // if it is invoked without script on the stack. To land that transition, it
+ // was necessary to go through and whitelist a bunch of callers that were
+ // depending on the old behavior. Those callers should be fixed up, and these
+ // methods should not be used by new code without review from bholley or bz.
+ static bool LegacyIsCallerNativeCode() { return !GetCurrentJSContext(); }
+ static bool LegacyIsCallerChromeOrNativeCode() {
+ return LegacyIsCallerNativeCode() || IsCallerChrome();
+ }
+ static nsIPrincipal* SubjectPrincipalOrSystemIfNativeCaller() {
+ if (!GetCurrentJSContext()) {
+ return GetSystemPrincipal();
+ }
+ return SubjectPrincipal();
+ }
+
+ static bool LookupBindingMember(
+ JSContext* aCx, nsIContent* aContent, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc);
+
+ // Check whether we should avoid leaking distinguishing information to JS/CSS.
+ // This function can be called both in the main thread and worker threads.
+ static bool ShouldResistFingerprinting(
+ RFPTarget aTarget = RFPTarget::Unknown);
+ static bool ShouldResistFingerprinting(nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget);
+ // Similar to the function above, but always allows CallerType::System
+ // callers.
+ static bool ShouldResistFingerprinting(mozilla::dom::CallerType aCallerType,
+ nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget);
+ static bool ShouldResistFingerprinting(nsIDocShell* aDocShell,
+ RFPTarget aTarget);
+ // These functions are the new, nuanced functions
+ static bool ShouldResistFingerprinting(
+ nsIChannel* aChannel, RFPTarget aTarget = RFPTarget::Unknown);
+ // These functions are labeled as dangerous because they will do the wrong
+ // thing in _most_ cases. They should only be used if you don't have a fully
+ // constructed LoadInfo or Document.
+ // A constant string used as justification is required when calling them,
+ // it should explain why a Document, Channel, LoadInfo, or CookieJarSettings
+ // does not exist in this context.
+ // (see below for more on justification strings.)
+ static bool ShouldResistFingerprinting_dangerous(
+ nsIURI* aURI, const mozilla::OriginAttributes& aOriginAttributes,
+ const char* aJustification, RFPTarget aTarget = RFPTarget::Unknown);
+ static bool ShouldResistFingerprinting_dangerous(
+ nsIPrincipal* aPrincipal, const char* aJustification,
+ RFPTarget aTarget = RFPTarget::Unknown);
+
+ /**
+ * Implement a RFP function that only checks the pref, and does not take
+ * into account any additional context such as PBM mode or Web Extensions.
+ *
+ * It requires an explanation for why the coarse check is being used instead
+ * of the nuanced check. While there is a gradual cut over of
+ * ShouldResistFingerprinting calls to a nuanced API, some features still
+ * require a legacy function. (Additionally, we sometimes use the coarse
+ * check first, to avoid running additional code to support a nuanced check.)
+ */
+ static bool ShouldResistFingerprinting(
+ const char* aJustification, RFPTarget aTarget = RFPTarget::Unknown);
+
+ // A helper function to calculate the rounded window size for fingerprinting
+ // resistance. The rounded size is based on the chrome UI size and available
+ // screen size. If the inputWidth/Height is greater than the available content
+ // size, this will report the available content size. Otherwise, it will
+ // round the size to the nearest upper 200x100.
+ static void CalcRoundedWindowSizeForResistingFingerprinting(
+ int32_t aChromeWidth, int32_t aChromeHeight, int32_t aScreenWidth,
+ int32_t aScreenHeight, int32_t aInputWidth, int32_t aInputHeight,
+ bool aSetOuterWidth, bool aSetOuterHeight, int32_t* aOutputWidth,
+ int32_t* aOutputHeight);
+
+ /**
+ * Returns the parent node of aChild crossing document boundaries, but skips
+ * any cross-process parent frames and continues with the nearest in-process
+ * frame in the hierarchy.
+ *
+ * Uses the parent node in the composed document.
+ */
+ static nsINode* GetNearestInProcessCrossDocParentNode(nsINode* aChild);
+
+ /**
+ * Similar to nsINode::IsInclusiveDescendantOf, except will treat an
+ * HTMLTemplateElement or ShadowRoot as an ancestor of things in the
+ * corresponding DocumentFragment. See the concept of "host-including
+ * inclusive ancestor" in the DOM specification.
+ */
+ static bool ContentIsHostIncludingDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor);
+
+ /**
+ * Similar to nsINode::IsInclusiveDescendantOf except it crosses document
+ * boundaries, this function uses ancestor/descendant relations in the
+ * composed document (see shadow DOM spec).
+ */
+ static bool ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant,
+ nsINode* aPossibleAncestor);
+
+ /**
+ * As with ContentIsCrossDocDescendantOf but crosses shadow boundaries but not
+ * cross document boundaries.
+ *
+ * @see nsINode::GetFlattenedTreeParentNode()
+ */
+ static bool ContentIsFlattenedTreeDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor);
+
+ /**
+ * Same as `ContentIsFlattenedTreeDescendantOf`, but from the flattened tree
+ * point of view of the style system
+ *
+ * @see nsINode::GetFlattenedTreeParentNodeForStyle()
+ */
+ static bool ContentIsFlattenedTreeDescendantOfForStyle(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor);
+
+ /**
+ * Retarget an object A against an object B
+ * @see https://dom.spec.whatwg.org/#retarget
+ */
+ static nsINode* Retarget(nsINode* aTargetA, nsINode* aTargetB);
+
+ /**
+ * @see https://wicg.github.io/element-timing/#get-an-element
+ */
+ static Element* GetAnElementForTiming(Element* aTarget,
+ const Document* aDocument,
+ nsIGlobalObject* aGlobal);
+
+ /*
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
+ *
+ * This method fills the |aArray| with all ancestor nodes of |aNode|
+ * including |aNode| at the zero index.
+ *
+ */
+ static nsresult GetInclusiveAncestors(nsINode* aNode,
+ nsTArray<nsINode*>& aArray);
+
+ /*
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
+ *
+ * This method fills |aAncestorNodes| with all ancestor nodes of |aNode|
+ * including |aNode| (QI'd to nsIContent) at the zero index.
+ * For each ancestor, there is a corresponding element in |aAncestorOffsets|
+ * which is the ComputeIndexOf the child in relation to its parent.
+ *
+ * This method just sucks.
+ */
+ static nsresult GetInclusiveAncestorsAndOffsets(
+ nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>* aAncestorNodes,
+ nsTArray<mozilla::Maybe<uint32_t>>* aAncestorOffsets);
+
+ /**
+ * Returns the closest common inclusive ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) , if any,
+ * for two nodes.
+ *
+ * Returns null if the nodes are disconnected.
+ */
+ static nsINode* GetClosestCommonInclusiveAncestor(nsINode* aNode1,
+ nsINode* aNode2) {
+ if (aNode1 == aNode2) {
+ return aNode1;
+ }
+
+ return GetCommonAncestorHelper(aNode1, aNode2);
+ }
+
+ /**
+ * Returns the common flattened tree ancestor, if any, for two given content
+ * nodes.
+ */
+ static nsIContent* GetCommonFlattenedTreeAncestor(nsIContent* aContent1,
+ nsIContent* aContent2) {
+ if (aContent1 == aContent2) {
+ return aContent1;
+ }
+
+ return GetCommonFlattenedTreeAncestorHelper(aContent1, aContent2);
+ }
+
+ /**
+ * Returns the common flattened tree ancestor from the point of view of the
+ * style system, if any, for two given content nodes.
+ */
+ static Element* GetCommonFlattenedTreeAncestorForStyle(Element* aElement1,
+ Element* aElement2);
+
+ /**
+ * Returns the common ancestor under interactive content, if any.
+ * If neither one has interactive content as ancestor, common ancestor will be
+ * returned. If only one has interactive content as ancestor, null will be
+ * returned. If the nodes are the same, that node is returned.
+ */
+ static nsINode* GetCommonAncestorUnderInteractiveContent(nsINode* aNode1,
+ nsINode* aNode2);
+
+ /**
+ * Returns the common BrowserParent ancestor, if any, for two given
+ * BrowserParent.
+ */
+ static mozilla::dom::BrowserParent* GetCommonBrowserParentAncestor(
+ mozilla::dom::BrowserParent* aBrowserParent1,
+ mozilla::dom::BrowserParent* aBrowserParent2);
+
+ // https://html.spec.whatwg.org/#target-element
+ // https://html.spec.whatwg.org/#find-a-potential-indicated-element
+ static Element* GetTargetElement(Document* aDocument,
+ const nsAString& aAnchorName);
+ /**
+ * Returns true if aNode1 is before aNode2 in the same connected
+ * tree.
+ * aNode1Index and aNode2Index are in/out arguments. If non-null, and value is
+ * Some, that value is used instead of calling slow ComputeIndexOf on the
+ * parent node. If value is Nothing, the value will be set to the return value
+ * of ComputeIndexOf.
+ */
+ static bool PositionIsBefore(nsINode* aNode1, nsINode* aNode2,
+ mozilla::Maybe<uint32_t>* aNode1Index = nullptr,
+ mozilla::Maybe<uint32_t>* aNode2Index = nullptr);
+
+ struct ComparePointsCache {
+ mozilla::Maybe<uint32_t> ComputeIndexOf(const nsINode* aParent,
+ const nsINode* aChild) {
+ if (aParent == mParent && aChild == mChild) {
+ return mIndex;
+ }
+
+ mIndex = aParent->ComputeIndexOf(aChild);
+ mParent = aParent;
+ mChild = aChild;
+ return mIndex;
+ }
+
+ private:
+ const nsINode* mParent = nullptr;
+ const nsINode* mChild = nullptr;
+ mozilla::Maybe<uint32_t> mIndex;
+ };
+
+ /**
+ * Utility routine to compare two "points", where a point is a node/offset
+ * pair.
+ * Pass a cache object as aParent1Cache if you expect to repeatedly
+ * call this function with the same value as aParent1.
+ *
+ * @return -1 if point1 < point2,
+ * 1 if point1 > point2,
+ * 0 if point1 == point2.
+ * `Nothing` if the two nodes aren't in the same connected subtree.
+ */
+ static mozilla::Maybe<int32_t> ComparePoints(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, ComparePointsCache* aParent1Cache = nullptr);
+ template <typename FPT, typename FRT, typename SPT, typename SRT>
+ static mozilla::Maybe<int32_t> ComparePoints(
+ const mozilla::RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aSecondBoundary);
+
+ /**
+ * Utility routine to compare two "points", where a point is a
+ * node/offset pair
+ * Returns -1 if point1 < point2, 1, if point1 > point2,
+ * 0 if error or if point1 == point2.
+ * NOTE! If the two nodes aren't in the same connected subtree,
+ * the result is 1, and the optional aDisconnected parameter
+ * is set to true.
+ *
+ * Pass a cache object as aParent1Cache if you expect to repeatedly
+ * call this function with the same value as aParent1.
+ */
+ static int32_t ComparePoints_Deprecated(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, bool* aDisconnected = nullptr,
+ ComparePointsCache* aParent1Cache = nullptr);
+ template <typename FPT, typename FRT, typename SPT, typename SRT>
+ static int32_t ComparePoints_Deprecated(
+ const mozilla::RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aSecondBoundary,
+ bool* aDisconnected = nullptr);
+
+ /**
+ * DO NOT USE this method for comparing the points in new code. this method
+ * emulates same result as `ComparePoints` before bug 1741148.
+ * When the old `ComparePoints` was called with offset value over `INT32_MAX`
+ * or `-1` which is used as "not found" by some API, they were treated as-is
+ * without checking whether the negative value or valid value. Thus, this
+ * handles the negative offset cases in the special paths to keep the
+ * traditional behavior. If you want to use this in new code, it means that
+ * you **should** check the offset values and call `ComparePoints` instead.
+ */
+ static mozilla::Maybe<int32_t> ComparePoints_AllowNegativeOffsets(
+ const nsINode* aParent1, int64_t aOffset1, const nsINode* aParent2,
+ int64_t aOffset2) {
+ if (MOZ_UNLIKELY(aOffset1 < 0 || aOffset2 < 0)) {
+ // If in same container, just the offset is compared.
+ if (aParent1 == aParent2) {
+ const int32_t compOffsets =
+ aOffset1 == aOffset2 ? 0 : (aOffset1 < aOffset2 ? -1 : 1);
+ return mozilla::Some(compOffsets);
+ }
+ // Otherwise, aOffset1 is referred only when aParent2 is a descendant of
+ // aParent1.
+ if (aOffset1 < 0 && aParent2->IsInclusiveDescendantOf(aParent1)) {
+ return mozilla::Some(-1);
+ }
+ // And also aOffset2 is referred only when aParent1 is a descendant of
+ // aParent2.
+ if (aOffset2 < 0 && aParent1->IsInclusiveDescendantOf(aParent2)) {
+ return mozilla::Some(1);
+ }
+ // Otherwise, aOffset1 nor aOffset2 is referred so that any value is fine
+ // if negative.
+ return ComparePoints(
+ aParent1, aOffset1 < 0 ? UINT32_MAX : static_cast<uint32_t>(aOffset1),
+ aParent2,
+ aOffset2 < 0 ? UINT32_MAX : static_cast<uint32_t>(aOffset2));
+ }
+ return ComparePoints(aParent1, aOffset1, aParent2, aOffset2);
+ }
+
+ /**
+ * Brute-force search of the element subtree rooted at aContent for
+ * an element with the given id. aId must be nonempty, otherwise
+ * this method may return nodes even if they have no id!
+ */
+ static Element* MatchElementId(nsIContent* aContent, const nsAString& aId);
+
+ /**
+ * Similar to above, but to be used if one already has an atom for the ID
+ */
+ static Element* MatchElementId(nsIContent* aContent, const nsAtom* aId);
+
+ /**
+ * Reverses the document position flags passed in.
+ *
+ * @param aDocumentPosition The document position flags to be reversed.
+ *
+ * @return The reversed document position flags.
+ *
+ * @see Node
+ */
+ static uint16_t ReverseDocumentPosition(uint16_t aDocumentPosition);
+
+ static const nsDependentSubstring TrimCharsInSet(const char* aSet,
+ const nsAString& aValue);
+
+ template <bool IsWhitespace(char16_t)>
+ static const nsDependentSubstring TrimWhitespace(const nsAString& aStr,
+ bool aTrimTrailing = true);
+
+ /**
+ * Returns true if aChar is of class Ps, Pi, Po, Pf, or Pe.
+ */
+ static bool IsFirstLetterPunctuation(uint32_t aChar);
+
+ /**
+ * Returns true if aChar is of class Lu, Ll, Lt, Lm, Lo, Nd, Nl or No
+ */
+ static bool IsAlphanumeric(uint32_t aChar);
+ /**
+ * Returns true if aChar is of class L*, N* or S* (for first-letter).
+ */
+ static bool IsAlphanumericOrSymbol(uint32_t aChar);
+
+ /*
+ * Is the character an HTML whitespace character?
+ *
+ * We define whitespace using the list in HTML5 and css3-selectors:
+ * U+0009, U+000A, U+000C, U+000D, U+0020
+ *
+ * HTML 4.01 also lists U+200B (zero-width space).
+ */
+ static bool IsHTMLWhitespace(char16_t aChar);
+
+ /*
+ * Returns whether the character is an HTML whitespace (see IsHTMLWhitespace)
+ * or a nbsp character (U+00A0).
+ */
+ static bool IsHTMLWhitespaceOrNBSP(char16_t aChar);
+
+ /**
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
+ */
+ static bool IsHTMLBlockLevelElement(nsIContent* aContent);
+
+ enum ParseHTMLIntegerResultFlags {
+ eParseHTMLInteger_NoFlags = 0,
+ // eParseHTMLInteger_NonStandard is set if the string representation of the
+ // integer was not the canonical one, but matches at least one of the
+ // following:
+ // * had leading whitespaces
+ // * had '+' sign
+ // * had leading '0'
+ // * was '-0'
+ eParseHTMLInteger_NonStandard = 1 << 0,
+ eParseHTMLInteger_DidNotConsumeAllInput = 1 << 1,
+ // Set if one or more error flags were set.
+ eParseHTMLInteger_Error = 1 << 2,
+ eParseHTMLInteger_ErrorNoValue = 1 << 3,
+ eParseHTMLInteger_ErrorOverflow = 1 << 4,
+ // Use this flag to detect the difference between overflow and underflow
+ eParseHTMLInteger_Negative = 1 << 5,
+ };
+ static int32_t ParseHTMLInteger(const nsAString& aValue,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLInteger(aValue.BeginReading(), aValue.EndReading(),
+ aResult);
+ }
+ static int32_t ParseHTMLInteger(const char16_t* aStart, const char16_t* aEnd,
+ ParseHTMLIntegerResultFlags* aResult);
+ static int32_t ParseHTMLInteger(const nsACString& aValue,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLInteger(aValue.BeginReading(), aValue.EndReading(),
+ aResult);
+ }
+ static int32_t ParseHTMLInteger(const char* aStart, const char* aEnd,
+ ParseHTMLIntegerResultFlags* aResult);
+
+ private:
+ template <class CharT>
+ static int32_t ParseHTMLIntegerImpl(const CharT* aStart, const CharT* aEnd,
+ ParseHTMLIntegerResultFlags* aResult);
+
+ public:
+ /**
+ * Parse a margin string of format 'top, right, bottom, left' into
+ * an nsIntMargin.
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting integer
+ * @return whether the value could be parsed
+ */
+ static bool ParseIntMarginValue(const nsAString& aString,
+ nsIntMargin& aResult);
+
+ /**
+ * Parse the value of the <font size=""> attribute according to the HTML5
+ * spec as of April 16, 2012.
+ *
+ * @param aValue the value to parse
+ * @return 1 to 7, or 0 if the value couldn't be parsed
+ */
+ static int32_t ParseLegacyFontSize(const nsAString& aValue);
+
+ static void Shutdown();
+
+ /**
+ * Checks whether two nodes come from the same origin.
+ */
+ static nsresult CheckSameOrigin(const nsINode* aTrustedNode,
+ const nsINode* unTrustedNode);
+
+ // Check if the (JS) caller can access aNode.
+ static bool CanCallerAccess(const nsINode* aNode);
+
+ // Check if the (JS) caller can access aWindow.
+ // aWindow can be either outer or inner window.
+ static bool CanCallerAccess(nsPIDOMWindowInner* aWindow);
+
+ // Check if the principal is chrome or an addon with the permission.
+ static bool PrincipalHasPermission(nsIPrincipal& aPrincipal,
+ const nsAtom* aPerm);
+
+ // Check if the JS caller is chrome or an addon with the permission.
+ static bool CallerHasPermission(JSContext* aCx, const nsAtom* aPerm);
+
+ /**
+ * Returns the triggering principal which should be used for the given URL
+ * attribute value with the given subject principal.
+ *
+ * If the attribute value is not an absolute URL, the subject principal will
+ * be ignored, and the node principal of aContent will be used instead.
+ * If aContent is non-null, this function will always return a principal.
+ * Otherewise, it may return null if aSubjectPrincipal is null or is rejected
+ * based on the attribute value.
+ *
+ * @param aContent The content on which the attribute is being set.
+ * @param aAttrValue The URL value of the attribute. For parsed attribute
+ * values, such as `srcset`, this function should be called separately
+ * for each URL value it contains.
+ * @param aSubjectPrincipal The subject principal of the scripted caller
+ * responsible for setting the attribute, or null if no scripted caller
+ * can be determined.
+ */
+ static nsIPrincipal* GetAttrTriggeringPrincipal(
+ nsIContent* aContent, const nsAString& aAttrValue,
+ nsIPrincipal* aSubjectPrincipal);
+
+ /**
+ * Returns true if the given string is guaranteed to be treated as an absolute
+ * URL, rather than a relative URL. In practice, this means any complete URL
+ * as supported by nsStandardURL, or any string beginning with a valid scheme
+ * which is known to the IO service, and has the URI_NORELATIVE flag.
+ *
+ * If the URL may be treated as absolute in some cases, but relative in others
+ * (for instance, "http:foo", which can be either an absolute or relative URL,
+ * depending on the context), this function returns false.
+ */
+ static bool IsAbsoluteURL(const nsACString& aURL);
+
+ // Check if a node is in the document prolog, i.e. before the document
+ // element.
+ static bool InProlog(nsINode* aNode);
+
+ static nsIBidiKeyboard* GetBidiKeyboard();
+
+ /**
+ * Get the cache security manager service. Can return null if the layout
+ * module has been shut down.
+ */
+ static nsIScriptSecurityManager* GetSecurityManager() {
+ return sSecurityManager;
+ }
+
+ // Returns the subject principal from the JSContext. May only be called
+ // from the main thread and assumes an existing compartment.
+ static nsIPrincipal* SubjectPrincipal(JSContext* aCx);
+
+ // Returns the subject principal. Guaranteed to return non-null. May only
+ // be called when nsContentUtils is initialized.
+ static nsIPrincipal* SubjectPrincipal();
+
+ // Returns the prinipal of the given JS object. This may only be called on
+ // the main thread for objects from the main thread's JSRuntime. The object
+ // must not be a cross-compartment wrapper, because CCWs are not associated
+ // with a single realm.
+ static nsIPrincipal* ObjectPrincipal(JSObject* aObj);
+
+ static void GenerateStateKey(nsIContent* aContent, Document* aDocument,
+ nsACString& aKey);
+
+ /**
+ * Create a new nsIURI from aSpec, using aBaseURI as the base. The
+ * origin charset of the new nsIURI will be the document charset of
+ * aDocument.
+ */
+ static nsresult NewURIWithDocumentCharset(nsIURI** aResult,
+ const nsAString& aSpec,
+ Document* aDocument,
+ nsIURI* aBaseURI);
+
+ /**
+ * Returns true if |aName| is a name with dashes.
+ */
+ static bool IsNameWithDash(nsAtom* aName);
+
+ /**
+ * Returns true if |aName| is a valid name to be registered via
+ * customElements.define.
+ */
+ static bool IsCustomElementName(nsAtom* aName, uint32_t aNameSpaceID);
+
+ static nsresult CheckQName(const nsAString& aQualifiedName,
+ bool aNamespaceAware = true,
+ const char16_t** aColon = nullptr);
+
+ static nsresult SplitQName(const nsIContent* aNamespaceResolver,
+ const nsString& aQName, int32_t* aNamespace,
+ nsAtom** aLocalName);
+
+ static nsresult GetNodeInfoFromQName(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsNodeInfoManager* aNodeInfoManager,
+ uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo);
+
+ static void SplitExpatName(const char16_t* aExpatName, nsAtom** aPrefix,
+ nsAtom** aTagName, int32_t* aNameSpaceID);
+
+ // Get a permission-manager setting for the given principal and type.
+ // If the pref doesn't exist or if it isn't ALLOW_ACTION, false is
+ // returned, otherwise true is returned. Always returns true for the
+ // system principal, and false for a null principal.
+ static bool IsSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType);
+
+ // Get a permission-manager setting for the given principal and type.
+ // If the pref doesn't exist or if it isn't DENY_ACTION, false is
+ // returned, otherwise true is returned. Always returns false for the
+ // system principal, and true for a null principal.
+ static bool IsSitePermDeny(nsIPrincipal* aPrincipal, const nsACString& aType);
+
+ // Get a permission-manager setting for the given principal and type.
+ // If the pref doesn't exist or if it isn't ALLOW_ACTION, false is
+ // returned, otherwise true is returned. Always returns true for the
+ // system principal, and false for a null principal.
+ // This version checks the permission for an exact host match on
+ // the principal
+ static bool IsExactSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType);
+
+ // Get a permission-manager setting for the given principal and type.
+ // If the pref doesn't exist or if it isn't DENY_ACTION, false is
+ // returned, otherwise true is returned. Always returns false for the
+ // system principal, and true for a null principal.
+ // This version checks the permission for an exact host match on
+ // the principal
+ static bool IsExactSitePermDeny(nsIPrincipal* aPrincipal,
+ const nsACString& aType);
+
+ // Returns true if the pref exists and is not UNKNOWN_ACTION.
+ static bool HasSitePerm(nsIPrincipal* aPrincipal, const nsACString& aType);
+
+ // Returns true if aDoc1 and aDoc2 have equal NodePrincipal()s.
+ static bool HaveEqualPrincipals(Document* aDoc1, Document* aDoc2);
+
+ /**
+ * Regster aObserver as a shutdown observer. A strong reference is held
+ * to aObserver until UnregisterShutdownObserver is called.
+ */
+ static void RegisterShutdownObserver(nsIObserver* aObserver);
+ static void UnregisterShutdownObserver(nsIObserver* aObserver);
+
+ /**
+ * @return true if aContent has an attribute aName in namespace aNameSpaceID,
+ * and the attribute value is non-empty.
+ */
+ static bool HasNonEmptyAttr(const nsIContent* aContent, int32_t aNameSpaceID,
+ nsAtom* aName);
+
+ /**
+ * Method that gets the primary presContext for the node.
+ *
+ * @param aContent The content node.
+ * @return the presContext, or nullptr if the content is not in a document
+ * (if GetComposedDoc returns nullptr)
+ */
+ static nsPresContext* GetContextForContent(const nsIContent* aContent);
+
+ /**
+ * Method that gets the pres shell for the node.
+ *
+ * @param aContent The content node.
+ * @return the pres shell, or nullptr if the content is not in a document
+ * (if GetComposedDoc returns nullptr)
+ */
+ static mozilla::PresShell* GetPresShellForContent(const nsIContent* aContent);
+
+ /**
+ * Method to do security and content policy checks on the image URI
+ *
+ * @param aURI uri of the image to be loaded
+ * @param aNode, the context the image is loaded in (eg an element)
+ * @param aLoadingDocument the document we belong to
+ * @param aLoadingPrincipal the principal doing the load
+ * @param [aContentPolicyType=nsIContentPolicy::TYPE_INTERNAL_IMAGE]
+ * (Optional) The CP content type to use
+ * @param aImageBlockingStatus the nsIContentPolicy blocking status for this
+ * image. This will be set even if a security check fails for the
+ * image, to some reasonable REJECT_* value. This out param will only
+ * be set if it's non-null.
+ * @return true if the load can proceed, or false if it is blocked.
+ * Note that aImageBlockingStatus, if set will always be an ACCEPT
+ * status if true is returned and always be a REJECT_* status if
+ * false is returned.
+ */
+ static bool CanLoadImage(nsIURI* aURI, nsINode* aNode,
+ Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal);
+
+ /**
+ * Returns true if objects in aDocument shouldn't initiate image loads.
+ */
+ static bool DocumentInactiveForImageLoads(Document* aDocument);
+
+ /**
+ * Convert a CORSMode into the corresponding imgILoader flags for
+ * passing to LoadImage.
+ * @param aMode CORS mode to convert
+ * @return a bitfield suitable to bitwise OR with other nsIRequest flags
+ */
+ static int32_t CORSModeToLoadImageFlags(mozilla::CORSMode aMode);
+
+ /**
+ * Method to start an image load. This does not do any security checks.
+ * This method will attempt to make aURI immutable; a caller that wants to
+ * keep a mutable version around should pass in a clone.
+ *
+ * @param aURI uri of the image to be loaded
+ * @param aContext element of document where the result of this request
+ * will be used.
+ * @param aLoadingDocument the document we belong to
+ * @param aLoadingPrincipal the principal doing the load
+ * @param aReferrerInfo the referrerInfo use on channel creation
+ * @param aObserver the observer for the image load
+ * @param aLoadFlags the load flags to use. See nsIRequest
+ * @param [aContentPolicyType=nsIContentPolicy::TYPE_INTERNAL_IMAGE]
+ * (Optional) The CP content type to use
+ * @param aUseUrgentStartForChannel,(Optional) a flag to mark on channel if it
+ * is triggered by user input events.
+ * @return the imgIRequest for the image load
+ */
+ static nsresult LoadImage(
+ nsIURI* aURI, nsINode* aContext, Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
+ nsIReferrerInfo* aReferrerInfo, imgINotificationObserver* aObserver,
+ int32_t aLoadFlags, const nsAString& initiatorType,
+ imgRequestProxy** aRequest,
+ nsContentPolicyType aContentPolicyType =
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE,
+ bool aUseUrgentStartForChannel = false, bool aLinkPreload = false,
+ uint64_t aEarlyHintPreloaderId = 0);
+
+ /**
+ * Obtain an image loader that respects the given document/channel's privacy
+ * status. Null document/channel arguments return the public image loader.
+ */
+ static imgLoader* GetImgLoaderForDocument(Document* aDoc);
+ static imgLoader* GetImgLoaderForChannel(nsIChannel* aChannel,
+ Document* aContext);
+
+ /**
+ * Method to get an imgIContainer from an image loading content
+ *
+ * @param aContent The image loading content. Must not be null.
+ * @param aRequest The image request [out]
+ * @return the imgIContainer corresponding to the first frame of the image
+ */
+ static already_AddRefed<imgIContainer> GetImageFromContent(
+ nsIImageLoadingContent* aContent, imgIRequest** aRequest = nullptr);
+
+ /**
+ * Method that decides whether a content node is draggable
+ *
+ * @param aContent The content node to test.
+ * @return whether it's draggable
+ */
+ static bool ContentIsDraggable(nsIContent* aContent);
+
+ /**
+ * Method that decides whether a content node is a draggable image
+ *
+ * @param aContent The content node to test.
+ * @return whether it's a draggable image
+ */
+ static bool IsDraggableImage(nsIContent* aContent);
+
+ /**
+ * Method that decides whether a content node is a draggable link
+ *
+ * @param aContent The content node to test.
+ * @return whether it's a draggable link
+ */
+ static bool IsDraggableLink(const nsIContent* aContent);
+
+ /**
+ * Convenience method to create a new nodeinfo that differs only by prefix and
+ * name from aNodeInfo. The new nodeinfo's name is set to aName, and prefix is
+ * set to null.
+ */
+ static nsresult QNameChanged(mozilla::dom::NodeInfo* aNodeInfo, nsAtom* aName,
+ mozilla::dom::NodeInfo** aResult);
+
+ /**
+ * Returns the appropriate event argument names for the specified
+ * namespace and event name. Added because we need to switch between
+ * SVG's "evt" and the rest of the world's "event", and because onerror
+ * on window takes 5 args.
+ */
+ static void GetEventArgNames(int32_t aNameSpaceID, nsAtom* aEventName,
+ bool aIsForWindow, uint32_t* aArgCount,
+ const char*** aArgNames);
+
+ /**
+ * Returns true if this document is in a Private Browsing window.
+ */
+ static bool IsInPrivateBrowsing(Document* aDoc);
+
+ /**
+ * Returns true if this loadGroup uses Private Browsing.
+ */
+ static bool IsInPrivateBrowsing(nsILoadGroup* aLoadGroup);
+
+ /**
+ * Returns whether a node is in the same tree as another one, accounting for
+ * anonymous roots.
+ *
+ * This method is particularly useful for callers who are trying to ensure
+ * that they are working with a non-anonymous descendant of a given node. If
+ * aContent is a descendant of aNode, a return value of false from this
+ * method means that it's an anonymous descendant from aNode's point of view.
+ *
+ * Both arguments to this method must be non-null.
+ */
+ static bool IsInSameAnonymousTree(const nsINode* aNode,
+ const nsINode* aOtherNode);
+
+ /*
+ * Traverse the parent chain from aElement up to aStop, and return true if
+ * there's an interactive html content; false otherwise.
+ *
+ * Note: This crosses shadow boundaries but not document boundaries.
+ */
+ static bool IsInInteractiveHTMLContent(const Element* aElement,
+ const Element* aStop);
+
+ /**
+ * Return the nsIXPConnect service.
+ */
+ static nsIXPConnect* XPConnect() { return sXPConnect; }
+
+ /**
+ * Report simple error message to the browser console
+ * @param aErrorText the error message
+ * @param aCategory Name of the module reporting error
+ * @param aFromPrivateWindow Whether from private window or not
+ * @param aFromChromeContext Whether from chrome context or not
+ * @param [aErrorFlags] See nsIScriptError.
+ */
+ static void LogSimpleConsoleError(
+ const nsAString& aErrorText, const nsACString& aCategory,
+ bool aFromPrivateWindow, bool aFromChromeContext,
+ uint32_t aErrorFlags = nsIScriptError::errorFlag);
+
+ /**
+ * Report a non-localized error message to the error console.
+ * @param aErrorText the error message
+ * @param aErrorFlags See nsIScriptError.
+ * @param aCategory Name of module reporting error.
+ * @param aDocument Reference to the document which triggered the message.
+ * @param [aURI=nullptr] (Optional) URI of resource containing error.
+ * @param [aSourceLine=u""_ns] (Optional) The text of the line that
+ contains the error (may be empty).
+ * @param [aLineNumber=0] (Optional) Line number within resource
+ containing error.
+ * @param [aColumnNumber=0] (Optional) Column number within resource
+ containing error.
+ If aURI is null, then aDocument->GetDocumentURI() is used.
+ * @param [aLocationMode] (Optional) Specifies the behavior if
+ error location information is omitted.
+ */
+ enum MissingErrorLocationMode {
+ // Don't show location information in the error console.
+ eOMIT_LOCATION,
+ // Get location information from the currently executing script.
+ eUSE_CALLING_LOCATION
+ };
+ static nsresult ReportToConsoleNonLocalized(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, const Document* aDocument,
+ nsIURI* aURI = nullptr, const nsString& aSourceLine = u""_ns,
+ uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0,
+ MissingErrorLocationMode aLocationMode = eUSE_CALLING_LOCATION);
+
+ /**
+ * Report a non-localized error message to the error console base on the
+ * innerWindowID.
+ * @param aErrorText the error message
+ * @param aErrorFlags See nsIScriptError.
+ * @param aCategory Name of module reporting error.
+ * @param [aInnerWindowID] Inner window ID for document which triggered the
+ * message.
+ * @param [aURI=nullptr] (Optional) URI of resource containing error.
+ * @param [aSourceLine=u""_ns] (Optional) The text of the line that
+ contains the error (may be empty).
+ * @param [aLineNumber=0] (Optional) Line number within resource
+ containing error.
+ * @param [aColumnNumber=0] (Optional) Column number within resource
+ containing error.
+ If aURI is null, then aDocument->GetDocumentURI() is used.
+ * @param [aLocationMode] (Optional) Specifies the behavior if
+ error location information is omitted.
+ */
+ static nsresult ReportToConsoleByWindowID(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, uint64_t aInnerWindowID,
+ nsIURI* aURI = nullptr, const nsString& aSourceLine = u""_ns,
+ uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0,
+ MissingErrorLocationMode aLocationMode = eUSE_CALLING_LOCATION);
+
+ /**
+ * Report a localized error message to the error console.
+ * @param aErrorFlags See nsIScriptError.
+ * @param aCategory Name of module reporting error.
+ * @param aDocument Reference to the document which triggered the message.
+ * @param aFile Properties file containing localized message.
+ * @param aMessageName Name of localized message.
+ * @param [aParams=empty-array] (Optional) Parameters to be substituted into
+ localized message.
+ * @param [aURI=nullptr] (Optional) URI of resource containing error.
+ * @param [aSourceLine=u""_ns] (Optional) The text of the line that
+ contains the error (may be empty).
+ * @param [aLineNumber=0] (Optional) Line number within resource
+ containing error.
+ * @param [aColumnNumber=0] (Optional) Column number within resource
+ containing error.
+ If aURI is null, then aDocument->GetDocumentURI() is used.
+ */
+ enum PropertiesFile {
+ eCSS_PROPERTIES,
+ eXUL_PROPERTIES,
+ eLAYOUT_PROPERTIES,
+ eFORMS_PROPERTIES,
+ ePRINTING_PROPERTIES,
+ eDOM_PROPERTIES,
+ eHTMLPARSER_PROPERTIES,
+ eSVG_PROPERTIES,
+ eBRAND_PROPERTIES,
+ eCOMMON_DIALOG_PROPERTIES,
+ eMATHML_PROPERTIES,
+ eSECURITY_PROPERTIES,
+ eNECKO_PROPERTIES,
+ eFORMS_PROPERTIES_en_US,
+ eDOM_PROPERTIES_en_US,
+ PropertiesFile_COUNT
+ };
+ static nsresult ReportToConsole(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ const Document* aDocument, PropertiesFile aFile, const char* aMessageName,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>(),
+ nsIURI* aURI = nullptr, const nsString& aSourceLine = u""_ns,
+ uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0);
+
+ static void ReportEmptyGetElementByIdArg(const Document* aDoc);
+
+ static void LogMessageToConsole(const char* aMsg);
+
+ static bool SpoofLocaleEnglish();
+
+ /**
+ * Get the localized string named |aKey| in properties file |aFile|.
+ */
+ static nsresult GetLocalizedString(PropertiesFile aFile, const char* aKey,
+ nsAString& aResult);
+
+ /**
+ * Same as GetLocalizedString, except that it might use en-US locale depending
+ * on SpoofLocaleEnglish() and whether the document is a built-in browser
+ * page.
+ */
+ static nsresult GetMaybeLocalizedString(PropertiesFile aFile,
+ const char* aKey, Document* aDocument,
+ nsAString& aResult);
+
+ /**
+ * A helper function that parses a sandbox attribute (of an <iframe> or a CSP
+ * directive) and converts it to the set of flags used internally.
+ *
+ * @param aSandboxAttr the sandbox attribute
+ * @return the set of flags (SANDBOXED_NONE if aSandboxAttr is
+ * null)
+ */
+ static uint32_t ParseSandboxAttributeToFlags(const nsAttrValue* aSandboxAttr);
+
+ /**
+ * A helper function that checks if a string matches a valid sandbox flag.
+ *
+ * @param aFlag the potential sandbox flag.
+ * @return true if the flag is a sandbox flag.
+ */
+ static bool IsValidSandboxFlag(const nsAString& aFlag);
+
+ /**
+ * A helper function that returns a string attribute corresponding to the
+ * sandbox flags.
+ *
+ * @param aFlags the sandbox flags
+ * @param aString the attribute corresponding to the flags (null if aFlags
+ * is zero)
+ */
+ static void SandboxFlagsToString(uint32_t aFlags, nsAString& aString);
+
+ static bool PrefetchPreloadEnabled(nsIDocShell* aDocShell);
+
+ static void ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsAString& aSourceSpecOut, uint32_t* aLineOut,
+ uint32_t* aColumnOut, nsString& aMessageOut);
+
+ // Variant on `ExtractErrorValues` with a `nsACString`. This
+ // method is provided for backwards compatibility. Prefer the
+ // faster method above for your code.
+ static void ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsACString& aSourceSpecOut, uint32_t* aLineOut,
+ uint32_t* aColumnOut, nsString& aMessageOut);
+
+ static nsresult CalculateBufferSizeForImage(
+ const uint32_t& aStride, const mozilla::gfx::IntSize& aImageSize,
+ const mozilla::gfx::SurfaceFormat& aFormat, size_t* aMaxBufferSize,
+ size_t* aUsedBufferSize);
+
+ // Returns true if the URI's host is contained in a list which is a comma
+ // separated domain list. Each item may start with "*.". If starts with
+ // "*.", it matches any sub-domains.
+ // The aList argument must be a lower-case string.
+ static bool IsURIInList(nsIURI* aURI, const nsCString& aList);
+
+ // Returns true if the URI's host is contained in a pref list which is a comma
+ // separated domain list. Each item may start with "*.". If starts with
+ // "*.", it matches any sub-domains.
+ static bool IsURIInPrefList(nsIURI* aURI, const char* aPrefName);
+
+ /*&
+ * A convenience version of FormatLocalizedString that can be used if all the
+ * params are in same-typed strings. The variadic template args need to come
+ * at the end, so we put aResult at the beginning to make sure it's clear
+ * which is the output and which are the inputs.
+ */
+ template <typename... T>
+ static nsresult FormatLocalizedString(nsAString& aResult,
+ PropertiesFile aFile, const char* aKey,
+ const T&... aParams) {
+ static_assert(sizeof...(aParams) != 0, "Use GetLocalizedString()");
+ AutoTArray<nsString, sizeof...(aParams)> params = {
+ aParams...,
+ };
+ return FormatLocalizedString(aFile, aKey, params, aResult);
+ }
+
+ /**
+ * Same as FormatLocalizedString template version, except that it might use
+ * en-US locale depending on SpoofLocaleEnglish() and whether the document is
+ * a built-in browser page.
+ */
+ template <typename... T>
+ static nsresult FormatMaybeLocalizedString(nsAString& aResult,
+ PropertiesFile aFile,
+ const char* aKey,
+ Document* aDocument,
+ const T&... aParams) {
+ static_assert(sizeof...(aParams) != 0, "Use GetMaybeLocalizedString()");
+ AutoTArray<nsString, sizeof...(aParams)> params = {
+ aParams...,
+ };
+ return FormatMaybeLocalizedString(aFile, aKey, aDocument, params, aResult);
+ }
+
+ /**
+ * Fill (with the parameters given) the localized string named |aKey| in
+ * properties file |aFile| consuming an nsTArray of nsString parameters rather
+ * than a char16_t** for the sake of avoiding use-after-free errors involving
+ * temporaries.
+ */
+ static nsresult FormatLocalizedString(PropertiesFile aFile, const char* aKey,
+ const nsTArray<nsString>& aParamArray,
+ nsAString& aResult);
+
+ /**
+ * Same as FormatLocalizedString, except that it might use en-US locale
+ * depending on SpoofLocaleEnglish() and whether the document is a built-in
+ * browser page.
+ */
+ static nsresult FormatMaybeLocalizedString(
+ PropertiesFile aFile, const char* aKey, Document* aDocument,
+ const nsTArray<nsString>& aParamArray, nsAString& aResult);
+
+ /**
+ * Returns true if aDocument is a chrome document
+ */
+ static bool IsChromeDoc(const Document* aDocument);
+
+ /**
+ * Returns true if aDocument is in a docshell whose parent is the same type
+ */
+ static bool IsChildOfSameType(Document* aDoc);
+
+ /**
+ * Returns true if the content-type will be rendered as plain-text.
+ */
+ static bool IsPlainTextType(const nsACString& aContentType);
+
+ /**
+ * Returns true iff the type is rendered as plain text and doesn't support
+ * non-UTF-8 encodings.
+ */
+ static bool IsUtf8OnlyPlainTextType(const nsACString& aContentType);
+
+ /**
+ * Returns true if aDocument belongs to a chrome docshell for
+ * display purposes. Returns false for null documents or documents
+ * which do not belong to a docshell.
+ */
+ static bool IsInChromeDocshell(const Document* aDocument);
+
+ /**
+ * Return the content policy service
+ */
+ static nsIContentPolicy* GetContentPolicy();
+
+ /**
+ * Map internal content policy types to external ones.
+ */
+ static inline ExtContentPolicyType InternalContentPolicyTypeToExternal(
+ nsContentPolicyType aType);
+
+ /**
+ * check whether the Link header field applies to the context resource
+ * see <http://tools.ietf.org/html/rfc5988#section-5.2>
+ */
+ static bool LinkContextIsURI(const nsAString& aAnchor, nsIURI* aDocURI);
+
+ /**
+ * Returns true if the content policy type is any of:
+ * * TYPE_INTERNAL_SCRIPT_PRELOAD
+ * * TYPE_INTERNAL_IMAGE_PRELOAD
+ * * TYPE_INTERNAL_STYLESHEET_PRELOAD
+ */
+ static bool IsPreloadType(nsContentPolicyType aType);
+
+ /**
+ * Quick helper to determine whether there are any mutation listeners
+ * of a given type that apply to this content or any of its ancestors.
+ * The method has the side effect to call document's MayDispatchMutationEvent
+ * using aTargetForSubtreeModified as the parameter.
+ *
+ * @param aNode The node to search for listeners
+ * @param aType The type of listener (NS_EVENT_BITS_MUTATION_*)
+ * @param aTargetForSubtreeModified The node which is the target of the
+ * possible DOMSubtreeModified event.
+ *
+ * @return true if there are mutation listeners of the specified type
+ */
+ static bool HasMutationListeners(nsINode* aNode, uint32_t aType,
+ nsINode* aTargetForSubtreeModified);
+
+ /**
+ * Quick helper to determine whether there are any mutation listeners
+ * of a given type that apply to any content in this document. It is valid
+ * to pass null for aDocument here, in which case this function always
+ * returns true.
+ *
+ * @param aDocument The document to search for listeners
+ * @param aType The type of listener (NS_EVENT_BITS_MUTATION_*)
+ *
+ * @return true if there are mutation listeners of the specified type
+ */
+ static bool HasMutationListeners(Document* aDocument, uint32_t aType);
+ /**
+ * Synchronously fire DOMNodeRemoved on aChild. Only fires the event if
+ * there really are listeners by checking using the HasMutationListeners
+ * function above. The function makes sure to hold the relevant objects alive
+ * for the duration of the event firing. However there are no guarantees
+ * that any of the objects are alive by the time the function returns.
+ * If you depend on that you need to hold references yourself.
+ *
+ * @param aChild The node to fire DOMNodeRemoved at.
+ * @param aParent The parent of aChild.
+ */
+ MOZ_CAN_RUN_SCRIPT static void MaybeFireNodeRemoved(nsINode* aChild,
+ nsINode* aParent);
+
+ /**
+ * These methods create and dispatch a trusted event.
+ * Works only with events which can be created by calling
+ * Document::CreateEvent() with parameter "Events".
+ * Note that don't use these methods for "input" event. Use
+ * DispatchInputEvent() instead.
+ *
+ * @param aDoc The document which will be used to create the event.
+ * @param aTarget The target of the event, should be QIable to
+ * EventTarget.
+ * @param aEventName The name of the event.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aCopmosed Is the event composed.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ // TODO: annotate with `MOZ_CAN_RUN_SCRIPT`
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1625902).
+ static nsresult DispatchTrustedEvent(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName, CanBubble,
+ Cancelable,
+ Composed aComposed = Composed::eDefault,
+ bool* aDefaultAction = nullptr);
+
+ // TODO: annotate with `MOZ_CAN_RUN_SCRIPT`
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1625902).
+ static nsresult DispatchTrustedEvent(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ CanBubble aCanBubble,
+ Cancelable aCancelable,
+ bool* aDefaultAction) {
+ return DispatchTrustedEvent(aDoc, aTarget, aEventName, aCanBubble,
+ aCancelable, Composed::eDefault,
+ aDefaultAction);
+ }
+
+ /**
+ * This method creates and dispatches a trusted event using an event message.
+ * @param aDoc The document which will be used to create the event.
+ * @param aTarget The target of the event, should be QIable to
+ * EventTarget.
+ * @param aEventMessage The event message.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ template <class WidgetEventType>
+ static nsresult DispatchTrustedEvent(
+ Document* aDoc, nsISupports* aTarget, EventMessage aEventMessage,
+ CanBubble aCanBubble, Cancelable aCancelable,
+ bool* aDefaultAction = nullptr,
+ ChromeOnlyDispatch aOnlyChromeDispatch = ChromeOnlyDispatch::eNo) {
+ WidgetEventType event(true, aEventMessage);
+ MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass);
+ return DispatchEvent(aDoc, aTarget, event, aEventMessage, aCanBubble,
+ aCancelable, Trusted::eYes, aDefaultAction,
+ aOnlyChromeDispatch);
+ }
+
+ /**
+ * This method dispatches "beforeinput" event with EditorInputEvent or
+ * "input" event with proper event class. If it's unsafe to dispatch,
+ * this put the event into the script runner queue. In such case, the
+ * event becomes not cancelable even if it's defined as cancelable by
+ * the spec.
+ * Input Events spec defines as:
+ * Input events are dispatched on elements that act as editing hosts,
+ * including elements with the contenteditable attribute set, textarea
+ * elements, and input elements that permit text input.
+ *
+ * @param aEventTarget The event target element of the "beforeinput"
+ * or "input" event. Must not be nullptr.
+ * @param aEventMessage Muse be eEditorBeforeInput or eEditorInput.
+ * @param aEditorInputType The inputType value of InputEvent.
+ * If aEventTarget won't dispatch "input" event
+ * with InputEvent, set EditorInputType::eUnknown.
+ * @param aEditorBase Optional. If this is called by editor,
+ * editor should set this. Otherwise, leave
+ * nullptr.
+ * @param aOptions Optional. If aEditorInputType value requires
+ * some additional data, they should be properly
+ * set with this argument.
+ * @param aEventStatus Returns nsEventStatus_eConsumeNoDefault if
+ * the dispatching event is cancelable and the
+ * event was canceled by script (including
+ * chrome script). Otherwise, returns given
+ * value. Note that this can be nullptr only
+ * when the dispatching event is not cancelable.
+ */
+ MOZ_CAN_RUN_SCRIPT static nsresult DispatchInputEvent(Element* aEventTarget);
+ MOZ_CAN_RUN_SCRIPT static nsresult DispatchInputEvent(
+ Element* aEventTarget, mozilla::EventMessage aEventMessage,
+ mozilla::EditorInputType aEditorInputType,
+ mozilla::EditorBase* aEditorBase, mozilla::InputEventOptions&& aOptions,
+ nsEventStatus* aEventStatus = nullptr);
+
+ /**
+ * This method creates and dispatches a untrusted event.
+ * Works only with events which can be created by calling
+ * Document::CreateEvent() with parameter "Events".
+ * @param aDoc The document which will be used to create the event.
+ * @param aTarget The target of the event, should be QIable to
+ * EventTarget.
+ * @param aEventName The name of the event.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ static nsresult DispatchUntrustedEvent(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName, CanBubble,
+ Cancelable,
+ bool* aDefaultAction = nullptr);
+
+ /**
+ * This method creates and dispatches a untrusted event using an event
+ * message.
+ * @param aDoc The document which will be used to create the event.
+ * @param aTarget The target of the event, should be QIable to
+ * EventTarget.
+ * @param aEventMessage The event message.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ template <class WidgetEventType>
+ static nsresult DispatchUntrustedEvent(
+ Document* aDoc, nsISupports* aTarget, EventMessage aEventMessage,
+ CanBubble aCanBubble, Cancelable aCancelable,
+ bool* aDefaultAction = nullptr,
+ ChromeOnlyDispatch aOnlyChromeDispatch = ChromeOnlyDispatch::eNo) {
+ WidgetEventType event(false, aEventMessage);
+ MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass);
+ return DispatchEvent(aDoc, aTarget, event, aEventMessage, aCanBubble,
+ aCancelable, Trusted::eNo, aDefaultAction,
+ aOnlyChromeDispatch);
+ }
+
+ /**
+ * This method creates and dispatches a trusted event to the chrome
+ * event handler (the parent object of the DOM Window in the event target
+ * chain). Note, chrome event handler is used even if aTarget is a chrome
+ * object. Use DispatchEventOnlyToChrome if the normal event dispatching is
+ * wanted in case aTarget is a chrome object.
+ * Works only with events which can be created by calling
+ * Document::CreateEvent() with parameter "Events".
+ * @param aDocument The document which will be used to create the event,
+ * and whose window's chrome handler will be used to
+ * dispatch the event.
+ * @param aTarget The target of the event, used for event->SetTarget()
+ * @param aEventName The name of the event.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ static nsresult DispatchChromeEvent(Document* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName, CanBubble,
+ Cancelable,
+ bool* aDefaultAction = nullptr);
+
+ /**
+ * Helper to dispatch a "framefocusrequested" event to chrome, which will only
+ * bring the window to the foreground and switch tabs if aCanRaise is true.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static void RequestFrameFocus(
+ Element& aFrameElement, bool aCanRaise,
+ mozilla::dom::CallerType aCallerType);
+
+ /**
+ * This method creates and dispatches a trusted event.
+ * If aTarget is not a chrome object, the nearest chrome object in the
+ * propagation path will be used as the start of the event target chain.
+ * This method is different than DispatchChromeEvent, which always dispatches
+ * events to chrome event handler. DispatchEventOnlyToChrome works like
+ * DispatchTrustedEvent in the case aTarget is a chrome object.
+ * Works only with events which can be created by calling
+ * Document::CreateEvent() with parameter "Events".
+ * @param aDoc The document which will be used to create the event.
+ * @param aTarget The target of the event, should be QIable to
+ * EventTarget.
+ * @param aEventName The name of the event.
+ * @param aCanBubble Whether the event can bubble.
+ * @param aCancelable Is the event cancelable.
+ * @param aComposed Is the event composed.
+ * @param aDefaultAction Set to true if default action should be taken,
+ * see EventTarget::DispatchEvent.
+ */
+ static nsresult DispatchEventOnlyToChrome(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble, Cancelable, Composed aComposed = Composed::eDefault,
+ bool* aDefaultAction = nullptr);
+
+ static nsresult DispatchEventOnlyToChrome(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
+ return DispatchEventOnlyToChrome(aDoc, aTarget, aEventName, aCanBubble,
+ aCancelable, Composed::eDefault,
+ aDefaultAction);
+ }
+
+ /**
+ * Determines if an event attribute name (such as onclick) is valid for
+ * a given element type. Types are from the EventNameType enumeration
+ * defined above.
+ *
+ * @param aName the event name to look up
+ * @param aType the type of content
+ */
+ static bool IsEventAttributeName(nsAtom* aName, int32_t aType);
+
+ /**
+ * Return the event message for the event with the given name. The name is
+ * the event name with the 'on' prefix. Returns eUnidentifiedEvent if the
+ * event doesn't match a known event name.
+ *
+ * @param aName the event name to look up
+ */
+ static EventMessage GetEventMessage(nsAtom* aName);
+
+ /**
+ * Returns the EventMessage and nsAtom to be used for event listener
+ * registration.
+ */
+ static EventMessage GetEventMessageAndAtomForListener(const nsAString& aName,
+ nsAtom** aOnName);
+
+ /**
+ * Return the EventClassID for the event with the given name. The name is the
+ * event name *without* the 'on' prefix. Returns eBasicEventClass if the event
+ * is not known to be of any particular event class.
+ *
+ * @param aName the event name to look up
+ */
+ static mozilla::EventClassID GetEventClassID(const nsAString& aName);
+
+ /**
+ * Return the event message and atom for the event with the given name.
+ * The name is the event name *without* the 'on' prefix.
+ * Returns eUnidentifiedEvent on the aEventID if the
+ * event doesn't match a known event name in the category.
+ *
+ * @param aName the event name to look up
+ * @param aEventClassID only return event id for aEventClassID
+ */
+ static nsAtom* GetEventMessageAndAtom(const nsAString& aName,
+ mozilla::EventClassID aEventClassID,
+ EventMessage* aEventMessage);
+
+ /**
+ * Used only during traversal of the XPCOM graph by the cycle
+ * collector: push a pointer to the listener manager onto the
+ * children deque, if it exists. Do nothing if there is no listener
+ * manager.
+ *
+ * Crucially: does not perform any refcounting operations.
+ *
+ * @param aNode The node to traverse.
+ * @param children The buffer to push a listener manager pointer into.
+ */
+ static void TraverseListenerManager(nsINode* aNode,
+ nsCycleCollectionTraversalCallback& cb);
+
+ /**
+ * Get the eventlistener manager for aNode, creating it if it does not
+ * already exist.
+ *
+ * @param aNode The node for which to get the eventlistener manager.
+ */
+ static mozilla::EventListenerManager* GetListenerManagerForNode(
+ nsINode* aNode);
+ /**
+ * Get the eventlistener manager for aNode, returning null if it does not
+ * already exist.
+ *
+ * @param aNode The node for which to get the eventlistener manager.
+ */
+ static mozilla::EventListenerManager* GetExistingListenerManagerForNode(
+ const nsINode* aNode);
+
+ static void AddEntryToDOMArenaTable(nsINode* aNode,
+ mozilla::dom::DOMArena* aDOMArena);
+
+ static already_AddRefed<mozilla::dom::DOMArena> TakeEntryFromDOMArenaTable(
+ const nsINode* aNode);
+
+ static void UnmarkGrayJSListenersInCCGenerationDocuments();
+
+ /**
+ * Remove the eventlistener manager for aNode.
+ *
+ * @param aNode The node for which to remove the eventlistener manager.
+ */
+ static void RemoveListenerManager(nsINode* aNode);
+
+ static bool IsInitialized() { return sInitialized; }
+
+ /**
+ * Checks if the localname/prefix/namespace triple is valid wrt prefix
+ * and namespace according to the Namespaces in XML and DOM Code
+ * specfications.
+ *
+ * @param aLocalname localname of the node
+ * @param aPrefix prefix of the node
+ * @param aNamespaceID namespace of the node
+ */
+ static bool IsValidNodeName(nsAtom* aLocalName, nsAtom* aPrefix,
+ int32_t aNamespaceID);
+
+ /**
+ * Creates a DocumentFragment from text using a context node to resolve
+ * namespaces.
+ *
+ * Please note that for safety reasons, if the node principal of
+ * aContextNode is the system principal, this function will automatically
+ * sanitize its input using nsTreeSanitizer.
+ *
+ * Note! In the HTML case with the HTML5 parser enabled, this is only called
+ * from Range.createContextualFragment() and the implementation here is
+ * quirky accordingly (html context node behaves like a body context node).
+ * If you don't want that quirky behavior, don't use this method as-is!
+ *
+ * @param aContextNode the node which is used to resolve namespaces
+ * @param aFragment the string which is parsed to a DocumentFragment
+ * @param aReturn the resulting fragment
+ * @param aPreventScriptExecution whether to mark scripts as already started
+ */
+ static already_AddRefed<mozilla::dom::DocumentFragment>
+ CreateContextualFragment(nsINode* aContextNode, const nsAString& aFragment,
+ bool aPreventScriptExecution,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Invoke the fragment parsing algorithm (innerHTML) using the HTML parser.
+ *
+ * Please note that for safety reasons, if the node principal of aTargetNode
+ * is the system principal, this function will automatically sanitize its
+ * input using nsTreeSanitizer.
+ *
+ * @param aSourceBuffer the string being set as innerHTML
+ * @param aTargetNode the target container
+ * @param aContextLocalName local name of context node
+ * @param aContextNamespace namespace of context node
+ * @param aQuirks true to make <table> not close <p>
+ * @param aPreventScriptExecution true to prevent scripts from executing;
+ * don't set to false when parsing into a target node that has been
+ * bound to tree.
+ * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse
+ * fragments is made, NS_ERROR_OUT_OF_MEMORY if aSourceBuffer is too
+ * long and NS_OK otherwise.
+ * @param aFlags defaults to -1 indicating that ParseFragmentHTML will do
+ * default sanitization for system privileged calls to it. Only
+ * ParserUtils::ParseFragment() should ever pass explicit aFlags
+ * which will then used for sanitization of the fragment.
+ * To pass explicit aFlags use any of the sanitization flags
+ * listed in nsIParserUtils.idl.
+ */
+ static nsresult ParseFragmentHTML(const nsAString& aSourceBuffer,
+ nsIContent* aTargetNode,
+ nsAtom* aContextLocalName,
+ int32_t aContextNamespace, bool aQuirks,
+ bool aPreventScriptExecution,
+ int32_t aFlags = -1);
+
+ /**
+ * Invoke the fragment parsing algorithm (innerHTML) using the XML parser.
+ *
+ * Please note that for safety reasons, if the node principal of aDocument
+ * is the system principal, this function will automatically sanitize its
+ * input using nsTreeSanitizer.
+ *
+ * @param aSourceBuffer the string being set as innerHTML
+ * @param aDocument the target document
+ * @param aTagStack the namespace mapping context
+ * @param aPreventExecution whether to mark scripts as already started
+ * @param aFlags, pass -1 and ParseFragmentXML will do default
+ * sanitization for system privileged calls to it. Only
+ * ParserUtils::ParseFragment() should ever pass explicit aFlags
+ * which will then used for sanitization of the fragment.
+ * To pass explicit aFlags use any of the sanitization flags
+ * listed in nsIParserUtils.idl.
+ * @param aReturn the result fragment
+ * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse
+ * fragments is made, a return code from the XML parser.
+ */
+ static nsresult ParseFragmentXML(const nsAString& aSourceBuffer,
+ Document* aDocument,
+ nsTArray<nsString>& aTagStack,
+ bool aPreventScriptExecution, int32_t aFlags,
+ mozilla::dom::DocumentFragment** aReturn);
+
+ /**
+ * Parse a string into a document using the HTML parser.
+ * Script elements are marked unexecutable.
+ *
+ * @param aSourceBuffer the string to parse as an HTML document
+ * @param aTargetDocument the document object to parse into. Must not have
+ * child nodes.
+ * @param aScriptingEnabledForNoscriptParsing whether <noscript> is parsed
+ * as if scripting was enabled
+ * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse
+ * fragments is made, NS_ERROR_OUT_OF_MEMORY if aSourceBuffer is too
+ * long and NS_OK otherwise.
+ */
+ static nsresult ParseDocumentHTML(const nsAString& aSourceBuffer,
+ Document* aTargetDocument,
+ bool aScriptingEnabledForNoscriptParsing);
+
+ /**
+ * Converts HTML source to plain text by parsing the source and using the
+ * plain text serializer on the resulting tree.
+ *
+ * @param aSourceBuffer the string to parse as an HTML document
+ * @param aResultBuffer the string where the plain text result appears;
+ * may be the same string as aSourceBuffer
+ * @param aFlags Flags from nsIDocumentEncoder.
+ * @param aWrapCol Number of columns after which to line wrap; 0 for no
+ * auto-wrapping
+ * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse
+ * fragments is made, NS_ERROR_OUT_OF_MEMORY if aSourceBuffer is too
+ * long and NS_OK otherwise.
+ */
+ static nsresult ConvertToPlainText(const nsAString& aSourceBuffer,
+ nsAString& aResultBuffer, uint32_t aFlags,
+ uint32_t aWrapCol);
+
+ /**
+ * Creates a 'loaded-as-data' HTML document that takes that principal,
+ * script global, and URL from the argument, which may be null.
+ */
+ static already_AddRefed<Document> CreateInertHTMLDocument(
+ const Document* aTemplate);
+
+ /**
+ * Creates a 'loaded-as-data' XML document that takes that principal,
+ * script global, and URL from the argument, which may be null.
+ */
+ static already_AddRefed<Document> CreateInertXMLDocument(
+ const Document* aTemplate);
+
+ public:
+ /**
+ * Sets the text contents of a node by replacing all existing children
+ * with a single text child.
+ *
+ * The function always notifies.
+ *
+ * Will reuse the first text child if one is available. Will not reuse
+ * existing cdata children.
+ *
+ * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ *
+ * @param aContent Node to set contents of.
+ * @param aValue Value to set contents to.
+ * @param aTryReuse When true, the function will try to reuse an existing
+ * textnodes rather than always creating a new one.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult SetNodeTextContent(
+ nsIContent* aContent, const nsAString& aValue, bool aTryReuse);
+
+ /**
+ * Get the textual contents of a node. This is a concatenation of all
+ * textnodes that are direct or (depending on aDeep) indirect children
+ * of the node.
+ *
+ * NOTE! No serialization takes place and <br> elements
+ * are not converted into newlines. Only textnodes and cdata nodes are
+ * added to the result.
+ *
+ * @see nsLayoutUtils::GetFrameTextContent
+ *
+ * @param aNode Node to get textual contents of.
+ * @param aDeep If true child elements of aNode are recursivly descended
+ * into to find text children.
+ * @param aResult the result. Out param.
+ * @return false on out of memory errors, true otherwise.
+ */
+ [[nodiscard]] static bool GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const mozilla::fallible_t&);
+
+ static void GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult);
+
+ /**
+ * Same as GetNodeTextContents but appends the result rather than sets it.
+ */
+ static bool AppendNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const mozilla::fallible_t&);
+
+ /**
+ * Utility method that checks if a given node has any non-empty children. This
+ * method does not descend recursively into children by default.
+ *
+ * @param aDiscoverMode Set to eRecurseIntoChildren to descend recursively
+ * into children.
+ */
+ enum TextContentDiscoverMode : uint8_t {
+ eRecurseIntoChildren,
+ eDontRecurseIntoChildren
+ };
+
+ static bool HasNonEmptyTextContent(
+ nsINode* aNode,
+ TextContentDiscoverMode aDiscoverMode = eDontRecurseIntoChildren);
+
+ /**
+ * Delete strings allocated for nsContentList matches
+ */
+ static void DestroyMatchString(void* aData);
+
+ /*
+ * Notify when the first XUL menu is opened and when the all XUL menus are
+ * closed. At opening, aInstalling should be TRUE, otherwise, it should be
+ * FALSE.
+ */
+ MOZ_CAN_RUN_SCRIPT static void NotifyInstalledMenuKeyboardListener(
+ bool aInstalling);
+
+ /**
+ * Check whether the nsIURI uses the given scheme.
+ *
+ * Note that this will check the innermost URI rather than that of
+ * the nsIURI itself.
+ */
+ static bool SchemeIs(nsIURI* aURI, const char* aScheme);
+
+ /**
+ * Returns true if aPrincipal is an ExpandedPrincipal.
+ */
+ static bool IsExpandedPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Returns true if aPrincipal is the system or an ExpandedPrincipal.
+ */
+ static bool IsSystemOrExpandedPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Gets the system principal from the security manager.
+ */
+ static nsIPrincipal* GetSystemPrincipal();
+
+ /**
+ * Gets the null subject principal singleton. This is only useful for
+ * assertions.
+ */
+ static nsIPrincipal* GetNullSubjectPrincipal() {
+ return sNullSubjectPrincipal;
+ }
+
+ /**
+ * *aResourcePrincipal is a principal describing who may access the contents
+ * of a resource. The resource can only be consumed by a principal that
+ * subsumes *aResourcePrincipal. MAKE SURE THAT NOTHING EVER ACTS WITH THE
+ * AUTHORITY OF *aResourcePrincipal.
+ * It may be null to indicate that the resource has no data from any origin
+ * in it yet and anything may access the resource.
+ * Additional data is being mixed into the resource from aExtraPrincipal
+ * (which may be null; if null, no data is being mixed in and this function
+ * will do nothing). Update *aResourcePrincipal to reflect the new data.
+ * If *aResourcePrincipal subsumes aExtraPrincipal, nothing needs to change,
+ * otherwise *aResourcePrincipal is replaced with the system principal.
+ * Returns true if *aResourcePrincipal changed.
+ */
+ static bool CombineResourcePrincipals(
+ nsCOMPtr<nsIPrincipal>* aResourcePrincipal,
+ nsIPrincipal* aExtraPrincipal);
+
+ /**
+ * Trigger a link with uri aLinkURI. If aClick is false, this triggers a
+ * mouseover on the link, otherwise it triggers a load after doing a
+ * security check using aContent's principal.
+ *
+ * @param aContent the node on which a link was triggered.
+ * @param aLinkURI the URI of the link, must be non-null.
+ * @param aTargetSpec the target (like target=, may be empty).
+ * @param aClick whether this was a click or not (if false, this method
+ * assumes you just hovered over the link).
+ * @param aIsTrusted If false, JS Context will be pushed to stack
+ * when the link is triggered.
+ */
+ static void TriggerLink(nsIContent* aContent, nsIURI* aLinkURI,
+ const nsString& aTargetSpec, bool aClick,
+ bool aIsTrusted);
+
+ /**
+ * Get the link location.
+ */
+ static void GetLinkLocation(mozilla::dom::Element* aElement,
+ nsString& aLocationString);
+
+ /**
+ * Return top-level widget in the parent chain.
+ */
+ static nsIWidget* GetTopLevelWidget(nsIWidget* aWidget);
+
+ /**
+ * Return the localized ellipsis for UI.
+ */
+ static const nsDependentString GetLocalizedEllipsis();
+
+ /**
+ * Hide any XUL popups associated with aDocument, including any documents
+ * displayed in child frames. Does nothing if aDocument is null.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static void HidePopupsInDocument(
+ Document* aDocument);
+
+ /**
+ * Retrieve the current drag session, or null if no drag is currently occuring
+ */
+ static already_AddRefed<nsIDragSession> GetDragSession();
+
+ /*
+ * Initialize and set the dataTransfer field of an WidgetDragEvent.
+ */
+ static nsresult SetDataTransferInEvent(mozilla::WidgetDragEvent* aDragEvent);
+
+ // filters the drag and drop action to fit within the effects allowed and
+ // returns it.
+ static uint32_t FilterDropEffect(uint32_t aAction, uint32_t aEffectAllowed);
+
+ /*
+ * Return true if the target of a drop event is a content document that is
+ * an ancestor of the document for the source of the drag.
+ */
+ static bool CheckForSubFrameDrop(nsIDragSession* aDragSession,
+ mozilla::WidgetDragEvent* aDropEvent);
+
+ /**
+ * Return true if aURI is a local file URI (i.e. file://).
+ */
+ static bool URIIsLocalFile(nsIURI* aURI);
+
+ /**
+ * Get the application manifest URI for this document. The manifest URI
+ * is specified in the manifest= attribute of the root element of the
+ * document.
+ *
+ * @param aDocument The document that lists the manifest.
+ * @param aURI The manifest URI.
+ */
+ static void GetOfflineAppManifest(Document* aDocument, nsIURI** aURI);
+
+ /**
+ * Check whether an application should be allowed to use offline APIs.
+ */
+ static bool OfflineAppAllowed(nsIURI* aURI);
+
+ /**
+ * Check whether an application should be allowed to use offline APIs.
+ */
+ static bool OfflineAppAllowed(nsIPrincipal* aPrincipal);
+
+ /**
+ * Increases the count of blockers preventing scripts from running.
+ * NOTE: You might want to use nsAutoScriptBlocker rather than calling
+ * this directly
+ */
+ static void AddScriptBlocker();
+
+ /**
+ * Decreases the count of blockers preventing scripts from running.
+ * NOTE: You might want to use nsAutoScriptBlocker rather than calling
+ * this directly
+ *
+ * WARNING! Calling this function could synchronously execute scripts.
+ */
+ static void RemoveScriptBlocker();
+
+ /**
+ * Add a runnable that is to be executed as soon as it's safe to execute
+ * scripts.
+ * NOTE: If it's currently safe to execute scripts, aRunnable will be run
+ * synchronously before the function returns.
+ *
+ * @param aRunnable The nsIRunnable to run as soon as it's safe to execute
+ * scripts. Passing null is allowed and results in nothing
+ * happening. It is also allowed to pass an object that
+ * has not yet been AddRefed.
+ */
+ static void AddScriptRunner(already_AddRefed<nsIRunnable> aRunnable);
+ static void AddScriptRunner(nsIRunnable* aRunnable);
+
+ /**
+ * Returns true if it's safe to execute content script and false otherwise.
+ *
+ * The only known case where this lies is mutation events. They run, and can
+ * run anything else, when this function returns false, but this is ok.
+ */
+ static bool IsSafeToRunScript();
+
+ // Returns the browser window with the most recent time stamp that is
+ // not in private browsing mode.
+ static already_AddRefed<nsPIDOMWindowOuter> GetMostRecentNonPBWindow();
+
+ /**
+ * Call this function if !IsSafeToRunScript() and we fail to run the script
+ * (rather than using AddScriptRunner as we usually do). |aDocument| is
+ * optional as it is only used for showing the URL in the console.
+ */
+ static void WarnScriptWasIgnored(Document* aDocument);
+
+ /**
+ * Add a "synchronous section", in the form of an nsIRunnable run once the
+ * event loop has reached a "stable state". |aRunnable| must not cause any
+ * queued events to be processed (i.e. must not spin the event loop).
+ * We've reached a stable state when the currently executing task/event has
+ * finished, see
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
+ * In practice this runs aRunnable once the currently executing event
+ * finishes. If called multiple times per task/event, all the runnables will
+ * be executed, in the order in which RunInStableState() was called.
+ */
+ static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
+
+ /* Add a pending IDBTransaction to be cleaned up at the end of performing a
+ * microtask checkpoint.
+ * See the step of "Cleanup Indexed Database Transactions" in
+ * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
+ */
+ static void AddPendingIDBTransaction(
+ already_AddRefed<nsIRunnable> aTransaction);
+
+ /**
+ * Returns true if we are doing StableState/MetastableState.
+ */
+ static bool IsInStableOrMetaStableState();
+
+ static JSContext* GetCurrentJSContext();
+
+ /**
+ * Case insensitive comparison between two atoms.
+ */
+ static bool EqualsIgnoreASCIICase(nsAtom* aAtom1, nsAtom* aAtom2);
+
+ /**
+ * Case insensitive comparison between two strings. However it only ignores
+ * case for ASCII characters a-z.
+ */
+ static bool EqualsIgnoreASCIICase(const nsAString& aStr1,
+ const nsAString& aStr2);
+
+ /**
+ * Convert ASCII A-Z to a-z.
+ */
+ static void ASCIIToLower(nsAString& aStr);
+ static void ASCIIToLower(nsACString& aStr);
+ static void ASCIIToLower(const nsAString& aSource, nsAString& aDest);
+ static void ASCIIToLower(const nsACString& aSource, nsACString& aDest);
+
+ /**
+ * Convert ASCII a-z to A-Z.
+ */
+ static void ASCIIToUpper(nsAString& aStr);
+ static void ASCIIToUpper(nsACString& aStr);
+ static void ASCIIToUpper(const nsAString& aSource, nsAString& aDest);
+ static void ASCIIToUpper(const nsACString& aSource, nsACString& aDest);
+
+ /**
+ * Return whether aStr contains an ASCII uppercase character.
+ */
+ static bool StringContainsASCIIUpper(const nsAString& aStr);
+
+ // Returns NS_OK for same origin, error (NS_ERROR_DOM_BAD_URI) if not.
+ static nsresult CheckSameOrigin(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel);
+ static nsIInterfaceRequestor* SameOriginChecker();
+
+ /**
+ * Get the Origin of the passed in nsIPrincipal or nsIURI. If the passed in
+ * nsIURI or the URI of the passed in nsIPrincipal does not have a host, the
+ * origin is set to 'null'.
+ *
+ * The ASCII versions return a ASCII strings that are puny-code encoded,
+ * suitable for, for example, header values. The UTF versions return strings
+ * containing international characters.
+ *
+ * The thread-safe versions return NS_ERROR_UNKNOWN_PROTOCOL if the
+ * operation cannot be completed on the current thread.
+ *
+ * @pre aPrincipal/aOrigin must not be null.
+ *
+ * @note this should be used for HTML5 origin determination.
+ */
+ static nsresult GetASCIIOrigin(nsIURI* aURI, nsACString& aOrigin);
+ static nsresult GetUTFOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin);
+ static nsresult GetUTFOrigin(nsIURI* aURI, nsAString& aOrigin);
+
+ /**
+ * This method creates and dispatches "command" event, which implements
+ * XULCommandEvent.
+ * If aPresShell is not null, dispatching goes via
+ * PresShell::HandleDOMEventWithTarget().
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static nsresult DispatchXULCommand(
+ nsIContent* aTarget, bool aTrusted,
+ mozilla::dom::Event* aSourceEvent = nullptr,
+ mozilla::PresShell* aPresShell = nullptr, bool aCtrl = false,
+ bool aAlt = false, bool aShift = false, bool aMeta = false,
+ // Including MouseEventBinding here leads
+ // to incude loops, unfortunately.
+ uint16_t inputSource = 0 /* MouseEvent_Binding::MOZ_SOURCE_UNKNOWN */,
+ int16_t aButton = 0);
+
+ static bool CheckMayLoad(nsIPrincipal* aPrincipal, nsIChannel* aChannel,
+ bool aAllowIfInheritsPrincipal);
+
+ /**
+ * The method checks whether the caller can access native anonymous content.
+ * If there is no JS in the stack or privileged JS is running, this
+ * method returns true, otherwise false.
+ */
+ static bool CanAccessNativeAnon();
+
+ [[nodiscard]] static nsresult WrapNative(JSContext* cx, nsISupports* native,
+ const nsIID* aIID,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping = true) {
+ return WrapNative(cx, native, nullptr, aIID, vp, aAllowWrapping);
+ }
+
+ // Same as the WrapNative above, but use this one if aIID is nsISupports' IID.
+ [[nodiscard]] static nsresult WrapNative(JSContext* cx, nsISupports* native,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping = true) {
+ return WrapNative(cx, native, nullptr, nullptr, vp, aAllowWrapping);
+ }
+
+ [[nodiscard]] static nsresult WrapNative(JSContext* cx, nsISupports* native,
+ nsWrapperCache* cache,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping = true) {
+ return WrapNative(cx, native, cache, nullptr, vp, aAllowWrapping);
+ }
+
+ /**
+ * Creates an arraybuffer from a binary string.
+ */
+ static nsresult CreateArrayBuffer(JSContext* aCx, const nsACString& aData,
+ JSObject** aResult);
+
+ static void StripNullChars(const nsAString& aInStr, nsAString& aOutStr);
+
+ /**
+ * Strip all \n, \r and nulls from the given string
+ * @param aString the string to remove newlines from [in/out]
+ */
+ static void RemoveNewlines(nsString& aString);
+
+ /**
+ * Convert Windows and Mac platform linebreaks to \n.
+ * @param aString the string to convert the newlines inside [in/out]
+ */
+ static void PlatformToDOMLineBreaks(nsString& aString);
+ [[nodiscard]] static bool PlatformToDOMLineBreaks(nsString& aString,
+ const mozilla::fallible_t&);
+
+ /**
+ * Populates aResultString with the contents of the string-buffer aBuf, up
+ * to aBuf's null-terminator. aBuf must not be null. Ownership of the string
+ * is not transferred.
+ */
+ static void PopulateStringFromStringBuffer(nsStringBuffer* aBuf,
+ nsAString& aResultString);
+
+ static bool IsHandlingKeyBoardEvent() { return sIsHandlingKeyBoardEvent; }
+
+ static void SetIsHandlingKeyBoardEvent(bool aHandling) {
+ sIsHandlingKeyBoardEvent = aHandling;
+ }
+
+ /**
+ * Utility method for getElementsByClassName. aRootNode is the node (either
+ * document or element), which getElementsByClassName was called on.
+ */
+ static already_AddRefed<nsContentList> GetElementsByClassName(
+ nsINode* aRootNode, const nsAString& aClasses);
+
+ /**
+ * Returns a presshell for this document, if there is one. This will be
+ * aDoc's direct presshell if there is one, otherwise we'll look at all
+ * ancestor documents to try to find a presshell, so for example this can
+ * still find a presshell for documents in display:none frames that have
+ * no presentation. So you have to be careful how you use this presshell ---
+ * getting generic data like a device context or widget from it is OK, but it
+ * might not be this document's actual presentation.
+ */
+ static mozilla::PresShell* FindPresShellForDocument(
+ const Document* aDocument);
+
+ /**
+ * Like FindPresShellForDocument, but returns the shell's PresContext instead.
+ */
+ static nsPresContext* FindPresContextForDocument(const Document* aDocument);
+
+ /**
+ * Returns the widget for this document if there is one. Looks at all ancestor
+ * documents to try to find a widget, so for example this can still find a
+ * widget for documents in display:none frames that have no presentation.
+ *
+ * You should probably use WidgetForContent() instead of this, unless you have
+ * a good reason to do otherwise.
+ */
+ static nsIWidget* WidgetForDocument(const Document* aDocument);
+
+ /**
+ * Returns the appropriate widget for this element, if there is one. Unlike
+ * WidgetForDocument(), this returns the correct widget for content in popups.
+ *
+ * You should probably use this instead of WidgetForDocument().
+ */
+ static nsIWidget* WidgetForContent(const nsIContent* aContent);
+
+ /**
+ * Returns a window renderer to use for the given document. Basically we
+ * look up the document hierarchy for the first document which has
+ * a presentation with an associated widget, and use that widget's
+ * window renderer.
+ *
+ * You should probably use WindowRendererForContent() instead of this, unless
+ * you have a good reason to do otherwise.
+ *
+ * @param aDoc the document for which to return a window renderer.
+ * @param aAllowRetaining an outparam that states whether the returned
+ * layer manager should be used for retained layers
+ */
+ static mozilla::WindowRenderer* WindowRendererForDocument(
+ const Document* aDoc);
+
+ /**
+ * Returns a window renderer to use for the given content. Unlike
+ * WindowRendererForDocument(), this returns the correct window renderer for
+ * content in popups.
+ *
+ * You should probably use this instead of WindowRendererForDocument().
+ */
+ static mozilla::WindowRenderer* WindowRendererForContent(
+ const nsIContent* aContent);
+
+ /**
+ * Determine whether a content node is focused or not,
+ *
+ * @param aContent the content node to check
+ * @return true if the content node is focused, false otherwise.
+ */
+ static bool IsFocusedContent(const nsIContent* aContent);
+
+ /**
+ * Returns true if calling execCommand with 'cut' or 'copy' arguments is
+ * allowed for the given subject principal. These are only allowed if the user
+ * initiated them (like with a mouse-click or key press).
+ */
+ static bool IsCutCopyAllowed(Document* aDocument,
+ nsIPrincipal& aSubjectPrincipal);
+
+ /**
+ * Returns true if CSSOM origin check should be skipped for WebDriver
+ * based crawl to be able to collect data from cross-origin CSS style
+ * sheets. This can be enabled by setting environment variable
+ * MOZ_BYPASS_CSSOM_ORIGIN_CHECK.
+ */
+ static bool BypassCSSOMOriginCheck() {
+#ifdef RELEASE_OR_BETA
+ return false;
+#else
+ return sBypassCSSOMOriginCheck;
+#endif
+ }
+
+ /**
+ * Fire mutation events for changes caused by parsing directly into a
+ * context node.
+ *
+ * @param aDoc the document of the node
+ * @param aDest the destination node that got stuff appended to it
+ * @param aOldChildCount the number of children the node had before parsing
+ */
+ static void FireMutationEventsForDirectParsing(Document* aDoc,
+ nsIContent* aDest,
+ int32_t aOldChildCount);
+
+ /**
+ * Returns true if the content is in a document and contains a plugin
+ * which we don't control event dispatch for, i.e. do any plugins in this
+ * doc tree receive key events outside of our control? This always returns
+ * false on MacOSX.
+ */
+ static bool HasPluginWithUncontrolledEventDispatch(nsIContent* aContent);
+
+ /**
+ * Returns the in-process subtree root document in a document hierarchy.
+ * This could be a chrome document.
+ */
+ static Document* GetInProcessSubtreeRootDocument(Document* aDoc) {
+ return const_cast<Document*>(
+ GetInProcessSubtreeRootDocument(const_cast<const Document*>(aDoc)));
+ }
+ static const Document* GetInProcessSubtreeRootDocument(const Document* aDoc);
+
+ static void GetShiftText(nsAString& text);
+ static void GetControlText(nsAString& text);
+ static void GetMetaText(nsAString& text);
+ static void GetOSText(nsAString& text);
+ static void GetAltText(nsAString& text);
+ static void GetModifierSeparatorText(nsAString& text);
+
+ /**
+ * Returns if aContent has the 'scrollgrab' property.
+ * aContent may be null (in this case false is returned).
+ */
+ static bool HasScrollgrab(nsIContent* aContent);
+
+ /**
+ * Flushes the layout tree (recursively)
+ *
+ * @param aWindow the window the flush should start at
+ *
+ */
+ static void FlushLayoutForTree(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Returns true if content with the given principal is allowed to use XUL
+ * and XBL and false otherwise.
+ */
+ static bool AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Perform cleanup that's appropriate for XPCOM shutdown.
+ */
+ static void XPCOMShutdown();
+
+ /**
+ * Checks if internal PDF viewer is enabled.
+ */
+ static bool IsPDFJSEnabled();
+
+ /**
+ * Checks to see whether the given principal is the internal PDF
+ * viewer principal.
+ */
+ static bool IsPDFJS(nsIPrincipal* aPrincipal);
+ /**
+ * Same, but from WebIDL bindings. Checks whether the subject principal is for
+ * the internal PDF viewer or system JS.
+ */
+ static bool IsSystemOrPDFJS(JSContext*, JSObject*);
+
+ /**
+ * Checks if internal SWF player is enabled.
+ */
+ static bool IsSWFPlayerEnabled();
+
+ enum ContentViewerType {
+ TYPE_UNSUPPORTED,
+ TYPE_CONTENT,
+ TYPE_FALLBACK,
+ TYPE_UNKNOWN
+ };
+
+ static already_AddRefed<nsIDocumentLoaderFactory> FindInternalContentViewer(
+ const nsACString& aType, ContentViewerType* aLoaderType = nullptr);
+
+ /**
+ * This helper method returns true if the aPattern pattern matches aValue.
+ * aPattern should not contain leading and trailing slashes (/).
+ * The pattern has to match the entire value not just a subset.
+ * aDocument must be a valid pointer (not null).
+ *
+ * This is following the HTML5 specification:
+ * http://dev.w3.org/html5/spec/forms.html#attr-input-pattern
+ *
+ * WARNING: This method mutates aPattern and aValue!
+ *
+ * @param aValue the string to check.
+ * @param aPattern the string defining the pattern.
+ * @param aDocument the owner document of the element.
+ * @param aHasMultiple whether or not there are multiple values.
+ * @result whether the given string is matches the pattern, or
+ * Nothing() if the pattern couldn't be evaluated.
+ */
+ static mozilla::Maybe<bool> IsPatternMatching(nsAString& aValue,
+ nsAString& aPattern,
+ const Document* aDocument,
+ bool aHasMultiple = false);
+
+ /**
+ * Calling this adds support for
+ * ontouch* event handler DOM attributes.
+ */
+ static void InitializeTouchEventTable();
+
+ /**
+ * Test whether the given URI always inherits a security context
+ * from the document it comes from.
+ */
+ static nsresult URIInheritsSecurityContext(nsIURI* aURI, bool* aResult);
+
+ /**
+ * Called before a channel is created to query whether the new
+ * channel should inherit the principal.
+ *
+ * The argument aLoadingPrincipal must not be null. The argument
+ * aURI must be the URI of the new channel. If aInheritForAboutBlank
+ * is true, then about:blank will be told to inherit the principal.
+ * If aForceInherit is true, the new channel will be told to inherit
+ * the principal no matter what.
+ *
+ * The return value is whether the new channel should inherit
+ * the principal.
+ */
+ static bool ChannelShouldInheritPrincipal(nsIPrincipal* aLoadingPrincipal,
+ nsIURI* aURI,
+ bool aInheritForAboutBlank,
+ bool aForceInherit);
+
+ static nsresult Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String);
+
+ static nsresult Atob(const nsAString& aAsciiString, nsAString& aBinaryData);
+
+ /**
+ * Returns whether the input element passed in parameter has the autocomplete
+ * functionality enabled. It is taking into account the form owner.
+ * NOTE: the caller has to make sure autocomplete makes sense for the
+ * element's type.
+ *
+ * @param aInput the input element to check. NOTE: aInput can't be null.
+ * @return whether the input element has autocomplete enabled.
+ */
+ static bool IsAutocompleteEnabled(mozilla::dom::HTMLInputElement* aInput);
+
+ enum AutocompleteAttrState : uint8_t {
+ eAutocompleteAttrState_Unknown = 1,
+ eAutocompleteAttrState_Invalid,
+ eAutocompleteAttrState_Valid,
+ };
+ /**
+ * Parses the value of the autocomplete attribute into aResult, ensuring it's
+ * composed of valid tokens, otherwise the value "" is used.
+ * Note that this method is used for form fields, not on a <form> itself.
+ *
+ * @return whether aAttr was valid and can be cached.
+ */
+ static AutocompleteAttrState SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, nsAString& aResult,
+ AutocompleteAttrState aCachedState = eAutocompleteAttrState_Unknown);
+
+ /* Variation that is used to retrieve a dictionary of the parts of the
+ * autocomplete attribute.
+ *
+ * @return whether aAttr was valid and can be cached.
+ */
+ static AutocompleteAttrState SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, mozilla::dom::AutocompleteInfo& aInfo,
+ AutocompleteAttrState aCachedState = eAutocompleteAttrState_Unknown,
+ bool aGrantAllValidValue = false);
+
+ /**
+ * This will parse aSource, to extract the value of the pseudo attribute
+ * with the name specified in aName. See
+ * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification
+ * which is used to parse aSource.
+ *
+ * @param aSource the string to parse
+ * @param aName the name of the attribute to get the value for
+ * @param aValue [out] the value for the attribute with name specified in
+ * aAttribute. Empty if the attribute isn't present.
+ * @return true if the attribute exists and was successfully parsed.
+ * false if the attribute doesn't exist, or has a malformed
+ * value, such as an unknown or unterminated entity.
+ */
+ static bool GetPseudoAttributeValue(const nsString& aSource, nsAtom* aName,
+ nsAString& aValue);
+
+ /**
+ * Returns true if the language name is a version of JavaScript and
+ * false otherwise
+ */
+ static bool IsJavaScriptLanguage(const nsString& aName);
+
+ static bool IsJavascriptMIMEType(const nsAString& aMIMEType);
+
+ static void SplitMimeType(const nsAString& aValue, nsString& aType,
+ nsString& aParams);
+
+ /**
+ * Takes a selection, and a text control element (<input> or <textarea>), and
+ * returns the offsets in the text content corresponding to the selection.
+ * The selection's anchor and focus must both be in the root node passed or a
+ * descendant.
+ *
+ * @param aSelection Selection to check
+ * @param aRoot Root <input> or <textarea> element
+ * @param aOutStartOffset Output start offset
+ * @param aOutEndOffset Output end offset
+ */
+ static void GetSelectionInTextControl(mozilla::dom::Selection* aSelection,
+ Element* aRoot,
+ uint32_t& aOutStartOffset,
+ uint32_t& aOutEndOffset);
+
+ /**
+ * Takes a frame for anonymous content within a text control (<input> or
+ * <textarea>), and returns an offset in the text content, adjusted for a
+ * trailing <br> frame.
+ *
+ * @param aOffsetFrame Frame for the text content in which the offset
+ * lies
+ * @param aOffset Offset as calculated by GetContentOffsetsFromPoint
+ * @param aOutOffset Output adjusted offset
+ *
+ * @see GetSelectionInTextControl for the original basis of this function.
+ */
+ static int32_t GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame,
+ int32_t aOffset);
+
+ /**
+ * Returns pointer to HTML editor instance for the aPresContext when there is.
+ * The HTML editor is shared by contenteditable elements or used in
+ * designMode. When there are no contenteditable elements and the document
+ * is not in designMode, this returns nullptr.
+ */
+ static mozilla::HTMLEditor* GetHTMLEditor(nsPresContext* aPresContext);
+ static mozilla::HTMLEditor* GetHTMLEditor(nsDocShell* aDocShell);
+
+ /**
+ * Returns pointer to a text editor if <input> or <textarea> element is
+ * active element in the document for aPresContext, or pointer to HTML
+ * editor if there is (i.e., even if non-editable element has focus or
+ * nobody has focus). The reason is, HTML editor may handle some input
+ * even if there is no active editing host.
+ * Note that this does not return editor in descendant documents.
+ */
+ static mozilla::EditorBase* GetActiveEditor(nsPresContext* aPresContext);
+ static mozilla::EditorBase* GetActiveEditor(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Returns `TextEditor` which manages `aAnonymousContent` if there is.
+ * Note that this method returns `nullptr` if `TextEditor` for the
+ * `aAnonymousContent` hasn't been created yet.
+ */
+ static mozilla::TextEditor* GetTextEditorFromAnonymousNodeWithoutCreation(
+ const nsIContent* aAnonymousContent);
+
+ /**
+ * Returns whether a node has an editable ancestor.
+ *
+ * @param aNode The node to test.
+ */
+ static bool IsNodeInEditableRegion(nsINode* aNode);
+
+ /**
+ * Returns a LogModule that logs debugging info from RFP functions.
+ */
+ static mozilla::LogModule* ResistFingerprintingLog();
+
+ /**
+ * Returns a LogModule that dump calls from content script are logged to.
+ * This can be enabled with the 'Dump' module, and is useful for synchronizing
+ * content JS to other logging modules.
+ */
+ static mozilla::LogModule* DOMDumpLog();
+
+ /**
+ * Returns whether a given header is forbidden for an XHR or fetch
+ * request.
+ */
+ static bool IsForbiddenRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue);
+
+ /**
+ * Returns whether a given header is forbidden for a system XHR
+ * request.
+ */
+ static bool IsForbiddenSystemRequestHeader(const nsACString& aHeader);
+
+ /**
+ * Checks whether the header overrides any http methods
+ */
+ static bool IsOverrideMethodHeader(const nsACString& headerName);
+ /**
+ * Checks whether the header value contains any forbidden method
+ */
+ static bool ContainsForbiddenMethod(const nsACString& headerValue);
+ /**
+ * Returns whether a given header has characters that aren't permitted
+ */
+ static bool IsCorsUnsafeRequestHeaderValue(const nsACString& aHeaderValue);
+
+ /**
+ * Returns whether a given Accept header value is allowed
+ * for a non-CORS XHR or fetch request.
+ */
+ static bool IsAllowedNonCorsAccept(const nsACString& aHeaderValue);
+
+ /**
+ * Returns whether a given Content-Type header value is allowed
+ * for a non-CORS XHR or fetch request.
+ */
+ static bool IsAllowedNonCorsContentType(const nsACString& aHeaderValue);
+
+ /**
+ * Returns whether a given Content-Language or accept-language header value is
+ * allowed for a non-CORS XHR or fetch request.
+ */
+ static bool IsAllowedNonCorsLanguage(const nsACString& aHeaderValue);
+
+ /**
+ * Returns whether a given header and value is a CORS-safelisted request
+ * header per https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+ */
+ static bool IsCORSSafelistedRequestHeader(const nsACString& aName,
+ const nsACString& aValue);
+
+ /**
+ * Returns whether a given header is forbidden for an XHR or fetch
+ * response.
+ */
+ static bool IsForbiddenResponseHeader(const nsACString& aHeader);
+
+ /**
+ * Returns the inner window ID for the window associated with a request.
+ */
+ static uint64_t GetInnerWindowID(nsIRequest* aRequest);
+
+ /**
+ * Returns the inner window ID for the window associated with a load group.
+ */
+ static uint64_t GetInnerWindowID(nsILoadGroup* aLoadGroup);
+
+ /**
+ * Encloses aHost in brackets if it is an IPv6 address.
+ */
+ static void MaybeFixIPv6Host(nsACString& aHost);
+
+ /**
+ * If the hostname for aURI is an IPv6 it encloses it in brackets,
+ * otherwise it just outputs the hostname in aHost.
+ */
+ static nsresult GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost);
+ static nsresult GetHostOrIPv6WithBrackets(nsIURI* aURI, nsACString& aHost);
+ static nsresult GetHostOrIPv6WithBrackets(nsIPrincipal* aPrincipal,
+ nsACString& aHost);
+
+ /*
+ * Call the given callback on all remote children of the given top-level
+ * window. Return Callstate::Stop from the callback to stop calling further
+ * children.
+ */
+ static void CallOnAllRemoteChildren(
+ nsPIDOMWindowOuter* aWindow,
+ const std::function<mozilla::CallState(mozilla::dom::BrowserParent*)>&
+ aCallback);
+
+ /**
+ * Given an IPCDataTransferImageContainer construct an imgIContainer for the
+ * image encoded by the transfer item.
+ */
+ static nsresult DeserializeTransferableDataImageContainer(
+ const mozilla::dom::IPCTransferableDataImageContainer& aData,
+ imgIContainer** aContainer);
+
+ /**
+ * Given a flavor obtained from an IPCDataTransferItem or nsITransferable,
+ * returns true if we should treat the data as an image.
+ */
+ static bool IsFlavorImage(const nsACString& aFlavor);
+
+ static bool IPCTransferableDataItemHasKnownFlavor(
+ const mozilla::dom::IPCTransferableDataItem& aItem);
+
+ static nsresult IPCTransferableDataToTransferable(
+ const mozilla::dom::IPCTransferableData& aTransferableData,
+ bool aAddDataFlavor, nsITransferable* aTransferable,
+ const bool aFilterUnknownFlavors);
+
+ static nsresult IPCTransferableDataToTransferable(
+ const mozilla::dom::IPCTransferableData& aTransferableData,
+ const bool& aIsPrivateData, nsIPrincipal* aRequestingPrincipal,
+ const nsContentPolicyType& aContentPolicyType, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors);
+
+ static nsresult IPCTransferableToTransferable(
+ const mozilla::dom::IPCTransferable& aIPCTransferable,
+ bool aAddDataFlavor, nsITransferable* aTransferable,
+ const bool aFilterUnknownFlavors);
+
+ static nsresult IPCTransferableDataItemToVariant(
+ const mozilla::dom::IPCTransferableDataItem& aItem,
+ nsIWritableVariant* aVariant);
+
+ static void TransferablesToIPCTransferableDatas(
+ nsIArray* aTransferables,
+ nsTArray<mozilla::dom::IPCTransferableData>& aIPC, bool aInSyncMessage,
+ mozilla::dom::ContentParent* aParent);
+
+ static void TransferableToIPCTransferableData(
+ nsITransferable* aTransferable,
+ mozilla::dom::IPCTransferableData* aTransferableData, bool aInSyncMessage,
+ mozilla::dom::ContentParent* aParent);
+
+ static void TransferableToIPCTransferable(
+ nsITransferable* aTransferable,
+ mozilla::dom::IPCTransferable* aIPCTransferable, bool aInSyncMessage,
+ mozilla::dom::ContentParent* aParent);
+
+ /*
+ * Get the pixel data from the given source surface and return it as a
+ * BigBuffer. The length and stride will be assigned from the surface.
+ */
+ static mozilla::Maybe<mozilla::ipc::BigBuffer> GetSurfaceData(
+ mozilla::gfx::DataSourceSurface&, size_t* aLength, int32_t* aStride);
+
+ static mozilla::Maybe<mozilla::dom::IPCImage> SurfaceToIPCImage(
+ mozilla::gfx::DataSourceSurface&);
+ static already_AddRefed<mozilla::gfx::DataSourceSurface> IPCImageToSurface(
+ mozilla::dom::IPCImage&&);
+
+ // Helpers shared by the implementations of nsContentUtils methods and
+ // nsIDOMWindowUtils methods.
+ static mozilla::Modifiers GetWidgetModifiers(int32_t aModifiers);
+ static nsIWidget* GetWidget(mozilla::PresShell* aPresShell, nsPoint* aOffset);
+ static int16_t GetButtonsFlagForButton(int32_t aButton);
+ static mozilla::LayoutDeviceIntPoint ToWidgetPoint(
+ const mozilla::CSSPoint& aPoint, const nsPoint& aOffset,
+ nsPresContext* aPresContext);
+ static nsView* GetViewToDispatchEvent(nsPresContext* aPresContext,
+ mozilla::PresShell** aPresShell);
+
+ /**
+ * Synthesize a mouse event to the given widget
+ * (see nsIDOMWindowUtils.sendMouseEvent).
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static nsresult SendMouseEvent(
+ mozilla::PresShell* aPresShell, const nsAString& aType, float aX,
+ float aY, int32_t aButton, int32_t aButtons, int32_t aClickCount,
+ int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure,
+ unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
+ mozilla::PreventDefaultResult* aPreventDefault,
+ bool aIsDOMEventSynthesized, bool aIsWidgetEventSynthesized);
+
+ static void FirePageShowEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem,
+ mozilla::dom::EventTarget* aChromeEventHandler, bool aFireIfShowing,
+ bool aOnlySystemGroup = false);
+
+ static void FirePageHideEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem,
+ mozilla::dom::EventTarget* aChromeEventHandler,
+ bool aOnlySystemGroup = false);
+
+ static already_AddRefed<nsPIWindowRoot> GetWindowRoot(Document* aDoc);
+
+ /*
+ * If there is a Referrer-Policy response header in |aChannel|, parse a
+ * referrer policy from the header.
+ *
+ * @param the channel from which to get the Referrer-Policy header
+ * @return referrer policy from the response header in aChannel
+ */
+ static mozilla::dom::ReferrerPolicy GetReferrerPolicyFromChannel(
+ nsIChannel* aChannel);
+
+ static bool IsNonSubresourceRequest(nsIChannel* aChannel);
+
+ static bool IsNonSubresourceInternalPolicyType(nsContentPolicyType aType);
+
+ public:
+ /*
+ * Returns true if this window's channel has been marked as a third-party
+ * tracking resource.
+ */
+ static bool IsThirdPartyTrackingResourceWindow(nsPIDOMWindowInner* aWindow);
+
+ /*
+ * Returns true if this window's channel has been marked as a first-party
+ * tracking resource.
+ */
+ static bool IsFirstPartyTrackingResourceWindow(nsPIDOMWindowInner* aWindow);
+
+ /*
+ * Serializes a HTML nsINode into its markup representation.
+ */
+ static bool SerializeNodeToMarkup(nsINode* aRoot, bool aDescendentsOnly,
+ nsAString& aOut);
+
+ /*
+ * Returns true iff the provided JSObject is a global, and its URI matches
+ * the provided about: URI.
+ * @param aGlobal the JSObject whose URI to check, if it is a global.
+ * @param aUri the URI to match, e.g. "about:feeds"
+ */
+ static bool IsSpecificAboutPage(JSObject* aGlobal, const char* aUri);
+
+ static void SetScrollbarsVisibility(nsIDocShell* aDocShell, bool aVisible);
+
+ /*
+ * Try to find the docshell corresponding to the given event target.
+ */
+ static nsIDocShell* GetDocShellForEventTarget(
+ mozilla::dom::EventTarget* aTarget);
+
+ /**
+ * Returns true if the "HTTPS state" of the document should be "modern". See:
+ *
+ * https://html.spec.whatwg.org/#concept-document-https-state
+ * https://fetch.spec.whatwg.org/#concept-response-https-state
+ */
+ static bool HttpsStateIsModern(Document* aDocument);
+
+ /**
+ * Returns true if the channel is for top-level window and is over secure
+ * context.
+ * https://github.com/whatwg/html/issues/4930 tracks the spec side of this.
+ */
+ static bool ComputeIsSecureContext(nsIChannel* aChannel);
+
+ /**
+ * Try to upgrade an element.
+ * https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade
+ */
+ static void TryToUpgradeElement(Element* aElement);
+
+ /**
+ * Creates a new XUL or XHTML element applying any appropriate custom element
+ * definition.
+ *
+ * If aFromParser != FROM_PARSER_FRAGMENT, a nested event loop permits
+ * arbitrary changes to the world before this function returns. This should
+ * probably just be MOZ_CAN_RUN_SCRIPT - bug 1543259.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult NewXULOrHTMLElement(
+ Element** aResult, mozilla::dom::NodeInfo* aNodeInfo,
+ mozilla::dom::FromParser aFromParser, nsAtom* aIsAtom,
+ mozilla::dom::CustomElementDefinition* aDefinition);
+
+ static mozilla::dom::CustomElementRegistry* GetCustomElementRegistry(
+ Document*);
+
+ /**
+ * Looking up a custom element definition.
+ * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+ */
+ static mozilla::dom::CustomElementDefinition* LookupCustomElementDefinition(
+ Document* aDoc, nsAtom* aNameAtom, uint32_t aNameSpaceID,
+ nsAtom* aTypeAtom);
+
+ static void RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName);
+
+ static void RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName);
+ static void UnregisterUnresolvedElement(Element* aElement);
+
+ static void EnqueueUpgradeReaction(
+ Element* aElement, mozilla::dom::CustomElementDefinition* aDefinition);
+
+ static void EnqueueLifecycleCallback(
+ mozilla::dom::ElementCallbackType aType, Element* aCustomElement,
+ const mozilla::dom::LifecycleCallbackArgs& aArgs,
+ mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
+
+ /**
+ * Appends all "document level" native anonymous content subtree roots for
+ * aDocument to aElements. Document level NAC subtrees are those created
+ * by ancestor frames of the document element's primary frame, such as
+ * the scrollbar elements created by the root scroll frame.
+ */
+ static void AppendDocumentLevelNativeAnonymousContentTo(
+ Document* aDocument, nsTArray<nsIContent*>& aElements);
+
+ /**
+ * Appends all native anonymous content subtree roots generated by `aContent`
+ * to `aKids`.
+ *
+ * See `AllChildrenIterator` for the description of the `aFlags` parameter.
+ */
+ static void AppendNativeAnonymousChildren(const nsIContent* aContent,
+ nsTArray<nsIContent*>& aKids,
+ uint32_t aFlags);
+
+ /**
+ * Query triggeringPrincipal if there's a 'triggeringprincipal' attribute on
+ * aLoadingNode, if no such attribute is specified, aDefaultPrincipal is
+ * returned if it is provided, otherwise the NodePrincipal of aLoadingNode is
+ * returned.
+ *
+ * Return true if aLoadingNode has a 'triggeringprincipal' attribute, and
+ * the value 'triggeringprincipal' is also successfully deserialized,
+ * otherwise return false.
+ */
+ static bool QueryTriggeringPrincipal(nsIContent* aLoadingNode,
+ nsIPrincipal* aDefaultPrincipal,
+ nsIPrincipal** aTriggeringPrincipal);
+
+ static bool QueryTriggeringPrincipal(nsIContent* aLoadingNode,
+ nsIPrincipal** aTriggeringPrincipal) {
+ return QueryTriggeringPrincipal(aLoadingNode, nullptr,
+ aTriggeringPrincipal);
+ }
+
+ // Returns whether the image for the given URI and triggering principal is
+ // already available. Ideally this should exactly match the "list of available
+ // images" in the HTML spec, but our implementation of that at best only
+ // resembles it.
+ static bool IsImageAvailable(nsIContent*, nsIURI*,
+ nsIPrincipal* aDefaultTriggeringPrincipal,
+ mozilla::CORSMode);
+ static bool IsImageAvailable(nsIURI*, nsIPrincipal* aTriggeringPrincipal,
+ mozilla::CORSMode, Document*);
+
+ /**
+ * Returns the content policy type that should be used for loading images
+ * for displaying in the UI. The sources of such images can be <xul:image>,
+ * <xul:menuitem> on OSX where we load the image through nsMenuItemIconX, etc.
+ */
+ static void GetContentPolicyTypeForUIImageLoading(
+ nsIContent* aLoadingNode, nsIPrincipal** aTriggeringPrincipal,
+ nsContentPolicyType& aContentPolicyType, uint64_t* aRequestContextID);
+
+ static nsresult CreateJSValueFromSequenceOfObject(
+ JSContext* aCx, const mozilla::dom::Sequence<JSObject*>& aTransfer,
+ JS::MutableHandle<JS::Value> aValue);
+
+ /**
+ * This implements the structured cloning algorithm as described by
+ * https://html.spec.whatwg.org/#structured-cloning.
+ */
+ static void StructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aValue,
+ const mozilla::dom::StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval, mozilla::ErrorResult& aError);
+
+ /**
+ * Returns true if reserved key events should be prevented from being sent
+ * to their target. Instead, the key event should be handled by chrome only.
+ */
+ static bool ShouldBlockReservedKeys(mozilla::WidgetKeyboardEvent* aKeyEvent);
+
+ /**
+ * Returns the nsIPluginTag for the plugin we should try to use for a given
+ * MIME type.
+ *
+ * @param aMIMEType The MIME type of the document being loaded.
+ * @param aNoFakePlugin If false then this method should consider JS plugins.
+ */
+ static already_AddRefed<nsIPluginTag> PluginTagForType(
+ const nsCString& aMIMEType, bool aNoFakePlugin);
+
+ /**
+ * Returns one of the nsIObjectLoadingContent::TYPE_ values describing the
+ * content type which will be used for the given MIME type when loaded within
+ * an nsObjectLoadingContent.
+ *
+ * NOTE: This method doesn't take capabilities into account. The caller must
+ * take that into account.
+ *
+ * @param aMIMEType The MIME type of the document being loaded.
+ * @param aNoFakePlugin If false then this method should consider JS plugins.
+ */
+ static uint32_t HtmlObjectContentTypeForMIMEType(const nsCString& aMIMEType,
+ bool aNoFakePlugin);
+
+ static already_AddRefed<nsISerialEventTarget> GetEventTargetByLoadInfo(
+ nsILoadInfo* aLoadInfo, mozilla::TaskCategory aCategory);
+
+ /**
+ * Detect whether a string is a local-url.
+ * https://drafts.csswg.org/css-values/#local-urls
+ */
+ static bool IsLocalRefURL(const nsAString& aString);
+
+ /**
+ * Compose a tab id with process id and a serial number.
+ */
+ static uint64_t GenerateTabId();
+
+ /**
+ * Compose a browser id with process id and a serial number.
+ */
+ static uint64_t GenerateBrowserId();
+
+ /**
+ * Generate an id for a BrowsingContext using a range of serial
+ * numbers reserved for the current process.
+ */
+ static uint64_t GenerateBrowsingContextId();
+
+ /**
+ * Generate an id using a range of serial numbers reserved for the current
+ * process. aId should be a counter that's incremented every time
+ * GenerateProcessSpecificId is called.
+ */
+ static uint64_t GenerateProcessSpecificId(uint64_t aId);
+
+ static std::tuple<uint64_t, uint64_t> SplitProcessSpecificId(uint64_t aId);
+
+ /**
+ * Generate a window ID which is unique across processes and will never be
+ * recycled.
+ */
+ static uint64_t GenerateWindowId();
+
+ /**
+ * Generate an ID for a load which is unique across processes and will never
+ * be recycled.
+ */
+ static uint64_t GenerateLoadIdentifier();
+
+ /**
+ * Determine whether or not the user is currently interacting with the web
+ * browser. This method is safe to call from off of the main thread.
+ */
+ static bool GetUserIsInteracting();
+
+ // Alternate data MIME type used by the ScriptLoader to register and read
+ // bytecode out of the nsCacheInfoChannel.
+ [[nodiscard]] static bool InitJSBytecodeMimeType();
+ static nsCString& JSScriptBytecodeMimeType() {
+ MOZ_ASSERT(sJSScriptBytecodeMimeType);
+ return *sJSScriptBytecodeMimeType;
+ }
+ static nsCString& JSModuleBytecodeMimeType() {
+ MOZ_ASSERT(sJSModuleBytecodeMimeType);
+ return *sJSModuleBytecodeMimeType;
+ }
+
+ /**
+ * Checks if the passed-in name is one of the special names: "_blank", "_top",
+ * "_parent" or "_self".
+ */
+ static bool IsSpecialName(const nsAString& aName);
+
+ /**
+ * Checks if the passed-in name should override an existing name on the
+ * window. Values which should not override include: "", "_blank", "_top",
+ * "_parent" and "_self".
+ */
+ static bool IsOverridingWindowName(const nsAString& aName);
+
+ /**
+ * If there is a SourceMap (higher precedence) or X-SourceMap (lower
+ * precedence) response header in |aChannel|, set |aResult| to the
+ * header's value and return true. Otherwise, return false.
+ *
+ * @param aChannel The HTTP channel
+ * @param aResult The string result.
+ */
+ static bool GetSourceMapURL(nsIHttpChannel* aChannel, nsACString& aResult);
+
+ /**
+ * Returns true if the passed-in mesasge is a pending InputEvent.
+ *
+ * @param aMsg The message to check
+ */
+ static bool IsMessageInputEvent(const IPC::Message& aMsg);
+
+ /**
+ * Returns true if the passed-in message is a critical InputEvent.
+ *
+ * @param aMsg The message to check
+ */
+ static bool IsMessageCriticalInputEvent(const IPC::Message& aMsg);
+
+ static void AsyncPrecreateStringBundles();
+
+ static bool ContentIsLink(nsIContent* aContent);
+
+ static already_AddRefed<mozilla::dom::ContentFrameMessageManager>
+ TryGetBrowserChildGlobal(nsISupports* aFrom);
+
+ // Get a serial number for a newly created inner or outer window.
+ static uint32_t InnerOrOuterWindowCreated();
+ // Record that an inner or outer window has been destroyed.
+ static void InnerOrOuterWindowDestroyed();
+ // Get the current number of inner or outer windows.
+ static int32_t GetCurrentInnerOrOuterWindowCount() {
+ return sInnerOrOuterWindowCount;
+ }
+
+ // Return an anonymized URI so that it can be safely exposed publicly.
+ static nsresult AnonymizeURI(nsIURI* aURI, nsCString& aAnonymizedURI);
+
+ /**
+ * Serializes a JSON-like JS::Value into a string.
+ * Cases where JSON.stringify would return undefined are handled according to
+ * the |aBehavior| argument:
+ *
+ * - If it is |UndefinedIsNullStringLiteral|, the string "null" is returned.
+ * - If it is |UndefinedIsVoidString|, the void string is returned.
+ *
+ * The |UndefinedIsNullStringLiteral| case is likely not desirable, but is
+ * retained for now for historical reasons.
+ * Usage:
+ * nsAutoString serializedValue;
+ * nsContentUtils::StringifyJSON(cx, value, serializedValue, behavior);
+ */
+ static bool StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsAString& aOutStr, JSONBehavior aBehavior);
+
+ /**
+ * Returns true if the top level ancestor content document of aDocument hasn't
+ * yet had the first contentful paint and there is a high priority event
+ * pending in the main thread.
+ */
+ static bool HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+ Document* aDocument);
+
+ /**
+ * Get the inner window corresponding to the incumbent global, including
+ * mapping extension content script globals to the attached window.
+ *
+ * Returns null if the incumbent global doesn't correspond to an inner window.
+ */
+ static nsGlobalWindowInner* IncumbentInnerWindow();
+
+ /**
+ * Get the inner window corresponding to the entry global, including mapping
+ * extension content script globals to the attached window.
+ *
+ * Returns null if the entry global doesn't correspond to an inner window.
+ */
+ static nsGlobalWindowInner* EntryInnerWindow();
+
+ /*
+ * Return safe area insets of window that defines as
+ * https://drafts.csswg.org/css-env-1/#safe-area-insets.
+ */
+ static mozilla::ScreenIntMargin GetWindowSafeAreaInsets(
+ nsIScreen* aScreen, const mozilla::ScreenIntMargin& aSafeareaInsets,
+ const mozilla::LayoutDeviceIntRect& aWindowRect);
+
+ struct SubresourceCacheValidationInfo {
+ // The expiration time, in seconds, if known.
+ mozilla::Maybe<uint32_t> mExpirationTime;
+ bool mMustRevalidate = false;
+ };
+
+ /**
+ * Gets cache validation info for subresources such as images or CSS
+ * stylesheets.
+ */
+ static SubresourceCacheValidationInfo GetSubresourceCacheValidationInfo(
+ nsIRequest*, nsIURI*);
+
+ static uint32_t SecondsFromPRTime(PRTime aTime) {
+ return uint32_t(int64_t(aTime) / int64_t(PR_USEC_PER_SEC));
+ }
+
+ /**
+ * Converts the given URL to a string and truncates it to the given length.
+ *
+ * Returns an empty string if aURL is null.
+ */
+ static nsCString TruncatedURLForDisplay(nsIURI* aURL, size_t aMaxLen = 128);
+
+ /**
+ * Anonymize the given id by hashing it with the provided origin. The
+ * resulting id will have the same length as the one that was passed in.
+ */
+ enum class OriginFormat {
+ Base64,
+ Plain,
+ };
+
+ static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey,
+ OriginFormat aFormat = OriginFormat::Base64);
+
+ /**
+ * Return true if we should hide the synthetic browsing context for <object>
+ * or <embed> images in synthetic documents.
+ */
+ static bool ShouldHideObjectOrEmbedImageDocument();
+
+ /**
+ * Returns the object type that the object loading content will actually use
+ * to load the resource. Used for ORB and loading images into synthetic
+ * documents.
+ */
+ static uint32_t ResolveObjectType(uint32_t aType);
+
+ /**
+ * Create and load the string bundle for the 'aFile'.
+ * This API is used to preload the string bundle on the main thread so later
+ * other thread could access it.
+ */
+ static nsresult EnsureAndLoadStringBundle(PropertiesFile aFile);
+
+ /**
+ * The method asks nsIAppShell to prioritize Gecko's internal tasks over
+ * the OS level tasks for a short period of time.
+ */
+ static void RequestGeckoTaskBurst();
+
+ static void SetMayHaveFormCheckboxStateChangeListeners() {
+ sMayHaveFormCheckboxStateChangeListeners = true;
+ }
+
+ static bool MayHaveFormCheckboxStateChangeListeners() {
+ return sMayHaveFormCheckboxStateChangeListeners;
+ }
+
+ static void SetMayHaveFormRadioStateChangeListeners() {
+ sMayHaveFormRadioStateChangeListeners = true;
+ }
+
+ static bool MayHaveFormRadioStateChangeListeners() {
+ return sMayHaveFormRadioStateChangeListeners;
+ }
+
+ /**
+ * Returns the closest link element in the flat tree of aContent if there's
+ * one, otherwise returns nullptr.
+ */
+ static nsIContent* GetClosestLinkInFlatTree(nsIContent* aContent);
+
+ static bool IsExternalProtocol(nsIURI* aURI);
+
+ private:
+ static bool InitializeEventTable();
+
+ static nsresult EnsureStringBundle(PropertiesFile aFile);
+
+ static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
+ nsIPrincipal* aPrincipal);
+
+ static nsresult WrapNative(JSContext* cx, nsISupports* native,
+ nsWrapperCache* cache, const nsIID* aIID,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping);
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DispatchEvent(
+ Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
+ CanBubble, Cancelable, Composed, Trusted, bool* aDefaultAction = nullptr,
+ ChromeOnlyDispatch = ChromeOnlyDispatch::eNo);
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DispatchEvent(
+ Document* aDoc, nsISupports* aTarget, mozilla::WidgetEvent& aWidgetEvent,
+ EventMessage aEventMessage, CanBubble, Cancelable, Trusted,
+ bool* aDefaultAction = nullptr,
+ ChromeOnlyDispatch = ChromeOnlyDispatch::eNo);
+
+ static void InitializeModifierStrings();
+
+ static void DropFragmentParsers();
+
+ static bool MatchClassNames(mozilla::dom::Element* aElement,
+ int32_t aNamespaceID, nsAtom* aAtom, void* aData);
+ static void DestroyClassNameArray(void* aData);
+ static void* AllocClassMatchingInfo(nsINode* aRootNode,
+ const nsString* aClasses);
+
+ static mozilla::EventClassID GetEventClassIDFromMessage(
+ EventMessage aEventMessage);
+
+ // Fills in aInfo with the tokens from the supplied autocomplete attribute.
+ static AutocompleteAttrState InternalSerializeAutocompleteAttribute(
+ const nsAttrValue* aAttrVal, mozilla::dom::AutocompleteInfo& aInfo,
+ bool aGrantAllValidValue = false);
+
+ static mozilla::CallState CallOnAllRemoteChildren(
+ mozilla::dom::MessageBroadcaster* aManager,
+ const std::function<mozilla::CallState(mozilla::dom::BrowserParent*)>&
+ aCallback);
+
+ static nsINode* GetCommonAncestorHelper(nsINode* aNode1, nsINode* aNode2);
+ static nsIContent* GetCommonFlattenedTreeAncestorHelper(
+ nsIContent* aContent1, nsIContent* aContent2);
+
+ static nsIXPConnect* sXPConnect;
+
+ static nsIScriptSecurityManager* sSecurityManager;
+ static nsIPrincipal* sSystemPrincipal;
+ static nsIPrincipal* sNullSubjectPrincipal;
+
+ static nsIConsoleService* sConsoleService;
+
+ static nsTHashMap<nsRefPtrHashKey<nsAtom>, EventNameMapping>* sAtomEventTable;
+ static nsTHashMap<nsStringHashKey, EventNameMapping>* sStringEventTable;
+ static nsTArray<RefPtr<nsAtom>>* sUserDefinedEvents;
+
+ static nsIStringBundleService* sStringBundleService;
+ class nsContentUtilsReporter;
+
+ static nsIContentPolicy* sContentPolicyService;
+ static bool sTriedToGetContentPolicy;
+
+ static mozilla::StaticRefPtr<nsIBidiKeyboard> sBidiKeyboard;
+
+ static bool sInitialized;
+ static uint32_t sScriptBlockerCount;
+ static uint32_t sDOMNodeRemovedSuppressCount;
+
+ // Not an nsCOMArray because removing elements from those is slower
+ static AutoTArray<nsCOMPtr<nsIRunnable>, 8>* sBlockedScriptRunners;
+ static uint32_t sRunnersCountAtFirstBlocker;
+ static uint32_t sScriptBlockerCountWhereRunnersPrevented;
+
+ static nsIInterfaceRequestor* sSameOriginChecker;
+
+ static bool sIsHandlingKeyBoardEvent;
+#ifndef RELEASE_OR_BETA
+ static bool sBypassCSSOMOriginCheck;
+#endif
+
+ class UserInteractionObserver;
+ static UserInteractionObserver* sUserInteractionObserver;
+
+ static nsHtml5StringParser* sHTMLFragmentParser;
+ static nsParser* sXMLFragmentParser;
+ static nsIFragmentContentSink* sXMLFragmentSink;
+
+ /**
+ * True if there's a fragment parser activation on the stack.
+ */
+ static bool sFragmentParsingActive;
+
+ static nsString* sShiftText;
+ static nsString* sControlText;
+ static nsString* sMetaText;
+ static nsString* sOSText;
+ static nsString* sAltText;
+ static nsString* sModifierSeparator;
+
+ // Alternate data mime types, used by the ScriptLoader to register and read
+ // the bytecode out of the nsCacheInfoChannel.
+ static nsCString* sJSScriptBytecodeMimeType;
+ static nsCString* sJSModuleBytecodeMimeType;
+
+ static mozilla::LazyLogModule gResistFingerprintingLog;
+ static mozilla::LazyLogModule sDOMDumpLog;
+
+ static int32_t sInnerOrOuterWindowCount;
+ static uint32_t sInnerOrOuterWindowSerialCounter;
+
+ static bool sMayHaveFormCheckboxStateChangeListeners;
+ static bool sMayHaveFormRadioStateChangeListeners;
+};
+
+/* static */ inline ExtContentPolicyType
+nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType) {
+ switch (aType) {
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
+ case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
+ case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
+ return ExtContentPolicy::TYPE_SCRIPT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ return ExtContentPolicy::TYPE_OBJECT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME:
+ case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
+ return ExtContentPolicy::TYPE_SUBDOCUMENT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
+ case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
+ case nsIContentPolicy::TYPE_INTERNAL_TRACK:
+ return ExtContentPolicy::TYPE_MEDIA;
+
+ case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
+ case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
+ return ExtContentPolicy::TYPE_XMLHTTPREQUEST;
+
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
+ return ExtContentPolicy::TYPE_IMAGE;
+
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
+ return ExtContentPolicy::TYPE_STYLESHEET;
+
+ case nsIContentPolicy::TYPE_INTERNAL_DTD:
+ case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
+ return ExtContentPolicy::TYPE_DTD;
+
+ case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
+ return ExtContentPolicy::TYPE_FONT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
+ return ExtContentPolicy::TYPE_FETCH;
+
+ default:
+ return static_cast<ExtContentPolicyType>(aType);
+ }
+}
+
+namespace mozilla {
+std::ostream& operator<<(
+ std::ostream& aOut,
+ const mozilla::PreventDefaultResult aPreventDefaultResult);
+} // namespace mozilla
+
+class MOZ_RAII nsAutoScriptBlocker {
+ public:
+ explicit nsAutoScriptBlocker() { nsContentUtils::AddScriptBlocker(); }
+ ~nsAutoScriptBlocker() { nsContentUtils::RemoveScriptBlocker(); }
+
+ private:
+};
+
+class MOZ_STACK_CLASS nsAutoScriptBlockerSuppressNodeRemoved
+ : public nsAutoScriptBlocker {
+ public:
+ nsAutoScriptBlockerSuppressNodeRemoved() {
+ ++nsContentUtils::sDOMNodeRemovedSuppressCount;
+ }
+ ~nsAutoScriptBlockerSuppressNodeRemoved() {
+ --nsContentUtils::sDOMNodeRemovedSuppressCount;
+ }
+};
+
+namespace mozilla::dom {
+
+class TreeOrderComparator {
+ public:
+ bool Equals(nsINode* aElem1, nsINode* aElem2) const {
+ return aElem1 == aElem2;
+ }
+ bool LessThan(nsINode* aElem1, nsINode* aElem2) const {
+ return nsContentUtils::PositionIsBefore(aElem1, aElem2);
+ }
+};
+
+} // namespace mozilla::dom
+
+#define NS_INTERFACE_MAP_ENTRY_TEAROFF(_interface, _allocator) \
+ NS_INTERFACE_MAP_ENTRY_TEAROFF_AMBIGUOUS(_interface, _interface, _allocator)
+
+#define NS_INTERFACE_MAP_ENTRY_TEAROFF_AMBIGUOUS(_interface, _implClass, \
+ _allocator) \
+ if (aIID.Equals(NS_GET_IID(_interface))) { \
+ foundInterface = static_cast<_implClass*>(_allocator); \
+ if (!foundInterface) { \
+ *aInstancePtr = nullptr; \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ } \
+ } else
+
+/*
+ * In the following helper macros we exploit the fact that the result of a
+ * series of additions will not be finite if any one of the operands in the
+ * series is not finite.
+ */
+#define NS_ENSURE_FINITE(f, rv) \
+ if (!std::isfinite(f)) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE2(f1, f2, rv) \
+ if (!std::isfinite((f1) + (f2))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE4(f1, f2, f3, f4, rv) \
+ if (!std::isfinite((f1) + (f2) + (f3) + (f4))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE5(f1, f2, f3, f4, f5, rv) \
+ if (!std::isfinite((f1) + (f2) + (f3) + (f4) + (f5))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE6(f1, f2, f3, f4, f5, f6, rv) \
+ if (!std::isfinite((f1) + (f2) + (f3) + (f4) + (f5) + (f6))) { \
+ return (rv); \
+ }
+
+// Deletes a linked list iteratively to avoid blowing up the stack (bug 460444).
+#define NS_CONTENT_DELETE_LIST_MEMBER(type_, ptr_, member_) \
+ { \
+ type_* cur = (ptr_)->member_; \
+ (ptr_)->member_ = nullptr; \
+ while (cur) { \
+ type_* next = cur->member_; \
+ cur->member_ = nullptr; \
+ delete cur; \
+ cur = next; \
+ } \
+ }
+
+#endif /* nsContentUtils_h___ */
diff --git a/dom/base/nsCopySupport.cpp b/dom/base/nsCopySupport.cpp
new file mode 100644
index 0000000000..de58a735b2
--- /dev/null
+++ b/dom/base/nsCopySupport.cpp
@@ -0,0 +1,908 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCopySupport.h"
+#include "nsIDocumentEncoder.h"
+#include "nsISupports.h"
+#include "nsIContent.h"
+#include "nsIClipboard.h"
+#include "nsIFormControl.h"
+#include "nsWidgetsCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsRange.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsComponentManagerUtils.h"
+#include "nsFocusManager.h"
+#include "nsFrameSelection.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/DataTransfer.h"
+
+#include "nsIDocShell.h"
+#include "nsIContentViewerEdit.h"
+#include "nsISelectionController.h"
+
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "nsHTMLDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+
+// image copy stuff
+#include "nsIImageLoadingContent.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentCID.h"
+
+#ifdef XP_WIN
+# include "nsCExternalHandlerService.h"
+# include "nsEscape.h"
+# include "nsIMIMEInfo.h"
+# include "nsIMIMEService.h"
+# include "nsIURIMutator.h"
+# include "nsIURL.h"
+# include "nsReadableUtils.h"
+# include "nsXULAppAPI.h"
+#endif
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/Selection.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
+static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID);
+static NS_DEFINE_CID(kHTMLConverterCID, NS_HTMLFORMATCONVERTER_CID);
+
+// copy string data onto the transferable
+static nsresult AppendString(nsITransferable* aTransferable,
+ const nsAString& aString, const char* aFlavor);
+
+// copy HTML node data
+static nsresult AppendDOMNode(nsITransferable* aTransferable,
+ nsINode* aDOMNode);
+
+#ifdef XP_WIN
+// copy image as file promise onto the transferable
+static nsresult AppendImagePromise(nsITransferable* aTransferable,
+ imgIRequest* aImgRequest,
+ nsIImageLoadingContent* aImageElement);
+#endif
+
+static nsresult EncodeForTextUnicode(nsIDocumentEncoder& aEncoder,
+ Document& aDocument, Selection* aSelection,
+ uint32_t aAdditionalEncoderFlags,
+ bool& aEncodedAsTextHTMLResult,
+ nsAutoString& aSerializationResult) {
+ // note that we assign text/unicode as mime type, but in fact
+ // nsHTMLCopyEncoder ignore it and use text/html or text/plain depending where
+ // the selection is. if it is a selection into input/textarea element or in a
+ // html content with pre-wrap style : text/plain. Otherwise text/html. see
+ // nsHTMLCopyEncoder::SetSelection
+ nsAutoString mimeType;
+ mimeType.AssignLiteral("text/unicode");
+
+ // Do the first and potentially trial encoding as preformatted and raw.
+ uint32_t flags = aAdditionalEncoderFlags |
+ nsIDocumentEncoder::OutputPreformatted |
+ nsIDocumentEncoder::OutputRaw |
+ nsIDocumentEncoder::OutputForPlainTextClipboardCopy |
+ nsIDocumentEncoder::OutputPersistNBSP;
+
+ nsresult rv = aEncoder.Init(&aDocument, mimeType, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.SetSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // SetSelection set the mime type to text/plain if the selection is inside a
+ // text widget.
+ rv = aEncoder.GetMimeType(mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool selForcedTextPlain = mimeType.EqualsLiteral(kTextMime);
+
+ nsAutoString buf;
+ rv = aEncoder.EncodeToString(buf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.GetMimeType(mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The mime type is ultimately text/html if the encoder successfully encoded
+ // the selection as text/html.
+ aEncodedAsTextHTMLResult = mimeType.EqualsLiteral(kHTMLMime);
+
+ if (selForcedTextPlain) {
+ // Nothing to do. buf contains the final, preformatted, raw text/plain.
+ aSerializationResult.Assign(buf);
+ } else {
+ // Redo the encoding, but this time use pretty printing.
+ flags =
+ nsIDocumentEncoder::OutputSelectionOnly |
+ nsIDocumentEncoder::OutputAbsoluteLinks |
+ nsIDocumentEncoder::SkipInvisibleContent |
+ nsIDocumentEncoder::OutputDropInvisibleBreak |
+ (aAdditionalEncoderFlags & (nsIDocumentEncoder::OutputNoScriptContent |
+ nsIDocumentEncoder::OutputRubyAnnotation));
+
+ mimeType.AssignLiteral(kTextMime);
+ rv = aEncoder.Init(&aDocument, mimeType, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.SetSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.EncodeToString(aSerializationResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+static nsresult EncodeAsTextHTMLWithContext(
+ nsIDocumentEncoder& aEncoder, Document& aDocument, Selection* aSelection,
+ uint32_t aEncoderFlags, nsAutoString& aTextHTMLEncodingResult,
+ nsAutoString& aHTMLParentsBufResult, nsAutoString& aHTMLInfoBufResult) {
+ nsAutoString mimeType;
+ mimeType.AssignLiteral(kHTMLMime);
+ nsresult rv = aEncoder.Init(&aDocument, mimeType, aEncoderFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.SetSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aEncoder.EncodeToStringWithContext(
+ aHTMLParentsBufResult, aHTMLInfoBufResult, aTextHTMLEncodingResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+struct EncodedDocumentWithContext {
+ // When determining `mSerializationForTextUnicode`, `text/unicode` is passed
+ // as mime type to the encoder. It uses this as a switch to decide whether to
+ // encode the document as `text/html` or `text/plain`. It is `true` iff
+ // `text/html` was used.
+ bool mUnicodeEncodingIsTextHTML = false;
+
+ // The serialized document when encoding the document with `text/unicode`. See
+ // comment of `mUnicodeEncodingIsTextHTML`.
+ nsAutoString mSerializationForTextUnicode;
+
+ // When `mUnicodeEncodingIsTextHTML` is true, this is the serialized document
+ // using `text/html`. Its value may differ from `mSerializationForTextHTML`,
+ // because different flags were passed to the encoder.
+ nsAutoString mSerializationForTextHTML;
+
+ // When `mUnicodeEncodingIsTextHTML` is true, this contains the serialized
+ // ancestor elements.
+ nsAutoString mHTMLContextBuffer;
+
+ // When `mUnicodeEncodingIsTextHTML` is true, this contains numbers
+ // identifying where in the context the serialization came from.
+ nsAutoString mHTMLInfoBuffer;
+};
+
+/**
+ * @param aSelection Can be nullptr.
+ * @param aAdditionalEncoderFlags nsIDocumentEncoder flags.
+ */
+static nsresult EncodeDocumentWithContext(
+ Document& aDocument, Selection* aSelection,
+ uint32_t aAdditionalEncoderFlags,
+ EncodedDocumentWithContext& aEncodedDocumentWithContext) {
+ nsCOMPtr<nsIDocumentEncoder> docEncoder = do_createHTMLCopyEncoder();
+
+ bool unicodeEncodingIsTextHTML{false};
+ nsAutoString serializationForTextUnicode;
+ nsresult rv = EncodeForTextUnicode(
+ *docEncoder, aDocument, aSelection, aAdditionalEncoderFlags,
+ unicodeEncodingIsTextHTML, serializationForTextUnicode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString serializationForTextHTML;
+ nsAutoString htmlContextBuffer;
+ nsAutoString htmlInfoBuffer;
+ if (unicodeEncodingIsTextHTML) {
+ // Redo the encoding, but this time use the passed-in flags.
+ // Don't allow wrapping of CJK strings.
+ rv = EncodeAsTextHTMLWithContext(
+ *docEncoder, aDocument, aSelection,
+ aAdditionalEncoderFlags |
+ nsIDocumentEncoder::OutputDisallowLineBreaking,
+ serializationForTextHTML, htmlContextBuffer, htmlInfoBuffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aEncodedDocumentWithContext = {
+ unicodeEncodingIsTextHTML, std::move(serializationForTextUnicode),
+ std::move(serializationForTextHTML), std::move(htmlContextBuffer),
+ std::move(htmlInfoBuffer)};
+
+ return rv;
+}
+
+static nsresult CreateTransferable(
+ const EncodedDocumentWithContext& aEncodedDocumentWithContext,
+ Document& aDocument, nsCOMPtr<nsITransferable>& aTransferable) {
+ nsresult rv = NS_OK;
+
+ aTransferable = do_CreateInstance(kCTransferableCID);
+ NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+ aTransferable->Init(aDocument.GetLoadContext());
+ if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
+ // Set up a format converter so that clipboard flavor queries work.
+ // This converter isn't really used for conversions.
+ nsCOMPtr<nsIFormatConverter> htmlConverter =
+ do_CreateInstance(kHTMLConverterCID);
+ aTransferable->SetConverter(htmlConverter);
+
+ if (!aEncodedDocumentWithContext.mSerializationForTextHTML.IsEmpty()) {
+ // Add the html DataFlavor to the transferable
+ rv = AppendString(aTransferable,
+ aEncodedDocumentWithContext.mSerializationForTextHTML,
+ kHTMLMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Add the htmlcontext DataFlavor to the transferable. Even if the context
+ // buffer is empty, this flavor should be attached to the transferable.
+ rv = AppendString(aTransferable,
+ aEncodedDocumentWithContext.mHTMLContextBuffer,
+ kHTMLContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aEncodedDocumentWithContext.mHTMLInfoBuffer.IsEmpty()) {
+ // Add the htmlinfo DataFlavor to the transferable
+ rv = AppendString(aTransferable,
+ aEncodedDocumentWithContext.mHTMLInfoBuffer, kHTMLInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
+ // unicode text
+ // Add the plain text DataFlavor to the transferable
+ // If we didn't have this, then nsDataObj::GetData matches
+ // text/plain against the kURLMime flavour which is not desirable
+ // (eg. when pasting into Notepad)
+ rv = AppendString(
+ aTransferable,
+ aEncodedDocumentWithContext.mSerializationForTextUnicode, kTextMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Try and get source URI of the items that are being dragged
+ nsIURI* uri = aDocument.GetDocumentURI();
+ if (uri) {
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!spec.IsEmpty()) {
+ nsAutoString shortcut;
+ AppendUTF8toUTF16(spec, shortcut);
+
+ // Add the URL DataFlavor to the transferable. Don't use kURLMime,
+ // as it will cause an unnecessary UniformResourceLocator to be
+ // added which confuses some apps eg. Outlook 2000 - (See Bug
+ // 315370). Don't use kURLDataMime, as it will cause a bogus 'url '
+ // flavor to show up on the Mac clipboard, confusing other apps,
+ // like Terminal (see bug 336012).
+ rv = AppendString(aTransferable, shortcut, kURLPrivateMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ } else {
+ if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
+ // Add the unicode DataFlavor to the transferable
+ rv = AppendString(
+ aTransferable,
+ aEncodedDocumentWithContext.mSerializationForTextUnicode, kTextMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return rv;
+}
+
+static nsresult PutToClipboard(
+ const EncodedDocumentWithContext& aEncodedDocumentWithContext,
+ int16_t aClipboardID, Document& aDocument) {
+ nsresult rv;
+ nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(clipboard, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsITransferable> transferable;
+ rv = CreateTransferable(aEncodedDocumentWithContext, aDocument, transferable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = clipboard->SetData(transferable, nullptr, aClipboardID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
+ Selection* aSel, Document* aDoc, int16_t aClipboardID,
+ bool aWithRubyAnnotation) {
+ NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+
+ uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent;
+ if (aWithRubyAnnotation) {
+ additionalFlags |= nsIDocumentEncoder::OutputRubyAnnotation;
+ }
+
+ EncodedDocumentWithContext encodedDocumentWithContext;
+ nsresult rv = EncodeDocumentWithContext(*aDoc, aSel, additionalFlags,
+ encodedDocumentWithContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PutToClipboard(encodedDocumentWithContext, aClipboardID, *aDoc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult nsCopySupport::ClearSelectionCache() {
+ nsresult rv;
+ nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
+ clipboard->EmptyClipboard(nsIClipboard::kSelectionCache);
+ return rv;
+}
+
+/**
+ * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`.
+ * @param aTransferable Needs to be not `nullptr`.
+ */
+static nsresult EncodeDocumentWithContextAndCreateTransferable(
+ Document& aDocument, Selection* aSelection,
+ uint32_t aAdditionalEncoderFlags, nsITransferable** aTransferable) {
+ NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+ // Clear the output parameter for the transferable.
+ *aTransferable = nullptr;
+
+ EncodedDocumentWithContext encodedDocumentWithContext;
+ nsresult rv =
+ EncodeDocumentWithContext(aDocument, aSelection, aAdditionalEncoderFlags,
+ encodedDocumentWithContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITransferable> transferable;
+ rv = CreateTransferable(encodedDocumentWithContext, aDocument, transferable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transferable.swap(*aTransferable);
+ return rv;
+}
+
+nsresult nsCopySupport::GetTransferableForSelection(
+ Selection* aSel, Document* aDoc, nsITransferable** aTransferable) {
+ NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+ const uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent;
+ return EncodeDocumentWithContextAndCreateTransferable(
+ *aDoc, aSel, additionalFlags, aTransferable);
+}
+
+nsresult nsCopySupport::GetTransferableForNode(
+ nsINode* aNode, Document* aDoc, nsITransferable** aTransferable) {
+ NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+ // Make a temporary selection with aNode in a single range.
+ // XXX We should try to get rid of the Selection object here.
+ // XXX bug 1245883
+ RefPtr<Selection> selection = new Selection(SelectionType::eNormal, nullptr);
+ RefPtr<nsRange> range = nsRange::Create(aNode);
+ ErrorResult result;
+ range->SelectNode(*aNode, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+ selection->AddRangeAndSelectFramesAndNotifyListenersInternal(*range, aDoc,
+ result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+ // It's not the primary selection - so don't skip invisible content.
+ uint32_t additionalFlags = 0;
+ return EncodeDocumentWithContextAndCreateTransferable(
+ *aDoc, selection, additionalFlags, aTransferable);
+}
+
+nsresult nsCopySupport::GetContents(const nsACString& aMimeType,
+ uint32_t aFlags, Selection* aSel,
+ Document* aDoc, nsAString& outdata) {
+ nsCOMPtr<nsIDocumentEncoder> docEncoder =
+ do_createDocumentEncoder(PromiseFlatCString(aMimeType).get());
+ NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
+
+ uint32_t flags = aFlags | nsIDocumentEncoder::SkipInvisibleContent;
+
+ if (aMimeType.EqualsLiteral("text/plain"))
+ flags |= nsIDocumentEncoder::OutputPreformatted;
+
+ NS_ConvertASCIItoUTF16 unicodeMimeType(aMimeType);
+
+ nsresult rv = docEncoder->Init(aDoc, unicodeMimeType, flags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aSel) {
+ rv = docEncoder->SetSelection(aSel);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // encode the selection
+ return docEncoder->EncodeToString(outdata);
+}
+
+nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
+ nsILoadContext* aLoadContext,
+ int32_t aCopyFlags) {
+ nsresult rv;
+
+ // create a transferable for putting data on the Clipboard
+ nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ trans->Init(aLoadContext);
+
+ if (aCopyFlags & nsIContentViewerEdit::COPY_IMAGE_TEXT) {
+ // get the location from the element
+ nsCOMPtr<nsIURI> uri;
+ rv = aImageElement->GetCurrentURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
+
+ nsAutoCString location;
+ rv = uri->GetSpec(location);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // append the string to the transferable
+ rv = AppendString(trans, NS_ConvertUTF8toUTF16(location), kTextMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCopyFlags & nsIContentViewerEdit::COPY_IMAGE_HTML) {
+ // append HTML data to the transferable
+ nsCOMPtr<nsINode> node(do_QueryInterface(aImageElement, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendDOMNode(trans, node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCopyFlags & nsIContentViewerEdit::COPY_IMAGE_DATA) {
+ // get the image data and its request from the element
+ nsCOMPtr<imgIRequest> imgRequest;
+ nsCOMPtr<imgIContainer> image = nsContentUtils::GetImageFromContent(
+ aImageElement, getter_AddRefs(imgRequest));
+ NS_ENSURE_TRUE(image, NS_ERROR_FAILURE);
+
+ if (imgRequest) {
+ // Remember the referrer used for this image request.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ imgRequest->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ trans->SetReferrerInfo(referrerInfo);
+ }
+
+#ifdef XP_WIN
+ rv = AppendImagePromise(trans, imgRequest, aImageElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ // copy the image data onto the transferable
+ rv = trans->SetTransferData(kNativeImageMime, image);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // get clipboard
+ nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check whether the system supports the selection clipboard or not.
+ if (clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard)) {
+ // put the transferable on the clipboard
+ rv = clipboard->SetData(trans, nullptr, nsIClipboard::kSelectionClipboard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return clipboard->SetData(trans, nullptr, nsIClipboard::kGlobalClipboard);
+}
+
+static nsresult AppendString(nsITransferable* aTransferable,
+ const nsAString& aString, const char* aFlavor) {
+ nsresult rv;
+
+ nsCOMPtr<nsISupportsString> data(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = data->SetData(aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->AddDataFlavor(aFlavor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aTransferable->SetTransferData(aFlavor, data);
+}
+
+static nsresult AppendDOMNode(nsITransferable* aTransferable,
+ nsINode* aDOMNode) {
+ nsresult rv;
+
+ // serializer
+ nsCOMPtr<nsIDocumentEncoder> docEncoder = do_createHTMLCopyEncoder();
+
+ // get document for the encoder
+ nsCOMPtr<Document> document = aDOMNode->OwnerDoc();
+
+ // Note that XHTML is not counted as HTML here, because we can't copy it
+ // properly (all the copy code for non-plaintext assumes using HTML
+ // serializers and parsers is OK, and those mess up XHTML).
+ NS_ENSURE_TRUE(document->IsHTMLDocument(), NS_OK);
+
+ // init encoder with document and node
+ rv = docEncoder->NativeInit(
+ document, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime),
+ nsIDocumentEncoder::OutputAbsoluteLinks |
+ nsIDocumentEncoder::OutputEncodeBasicEntities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetNode(aDOMNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // serialize to string
+ nsAutoString html, context, info;
+ rv = docEncoder->EncodeToStringWithContext(context, info, html);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy them to the transferable
+ if (!html.IsEmpty()) {
+ rv = AppendString(aTransferable, html, kHTMLMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!info.IsEmpty()) {
+ rv = AppendString(aTransferable, info, kHTMLInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // add a special flavor, even if we don't have html context data
+ return AppendString(aTransferable, context, kHTMLContext);
+}
+
+#ifdef XP_WIN
+static nsresult AppendImagePromise(nsITransferable* aTransferable,
+ imgIRequest* aImgRequest,
+ nsIImageLoadingContent* aImageElement) {
+ nsresult rv;
+
+ NS_ENSURE_TRUE(aImgRequest, NS_OK);
+
+ bool isMultipart;
+ rv = aImgRequest->GetMultipart(&isMultipart);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isMultipart) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (NS_WARN_IF(!mimeService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> imgUri;
+ rv = aImgRequest->GetFinalURI(getter_AddRefs(imgUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = imgUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass out the image source string
+ nsString imageSourceString;
+ CopyUTF8toUTF16(spec, imageSourceString);
+
+ nsCString mimeType;
+ rv = aImgRequest->GetMimeType(getter_Copies(mimeType));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString fileName;
+ rv = aImgRequest->GetFileName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString validFileName = NS_ConvertUTF8toUTF16(fileName);
+ mimeService->ValidateFileNameForSaving(
+ validFileName, mimeType, nsIMIMEService::VALIDATE_DEFAULT, validFileName);
+
+ rv = AppendString(aTransferable, imageSourceString, kFilePromiseURLMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aTransferable->SetRequestingPrincipal(node->NodePrincipal());
+ aTransferable->SetCookieJarSettings(node->OwnerDoc()->CookieJarSettings());
+ aTransferable->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ // add the dataless file promise flavor
+ return aTransferable->AddDataFlavor(kFilePromiseMime);
+}
+#endif // XP_WIN
+
+already_AddRefed<Selection> nsCopySupport::GetSelectionForCopy(
+ Document* aDocument) {
+ PresShell* presShell = aDocument->GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameSelection> frameSel = presShell->GetLastFocusedFrameSelection();
+ if (NS_WARN_IF(!frameSel)) {
+ return nullptr;
+ }
+
+ RefPtr<Selection> sel = frameSel->GetSelection(SelectionType::eNormal);
+ return sel.forget();
+}
+
+bool nsCopySupport::CanCopy(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+
+ RefPtr<Selection> sel = GetSelectionForCopy(aDocument);
+ return sel && !sel->IsCollapsed();
+}
+
+static bool IsInsideRuby(nsINode* aNode) {
+ for (; aNode; aNode = aNode->GetParent()) {
+ if (aNode->IsHTMLElement(nsGkAtoms::ruby)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool IsSelectionInsideRuby(Selection* aSelection) {
+ uint32_t rangeCount = aSelection->RangeCount();
+ for (auto i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
+ const nsRange* range = aSelection->GetRangeAt(i);
+ if (!IsInsideRuby(range->GetClosestCommonInclusiveAncestor())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) {
+ if (!aNode->IsContent()) {
+ return nullptr;
+ }
+ for (nsIContent* content = aNode->AsContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (content->IsElement()) {
+ return content->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
+ int32_t aClipboardType,
+ PresShell* aPresShell,
+ Selection* aSelection,
+ bool* aActionTaken) {
+ if (aActionTaken) {
+ *aActionTaken = false;
+ }
+
+ EventMessage originalEventMessage = aEventMessage;
+ if (originalEventMessage == ePasteNoFormatting) {
+ originalEventMessage = ePaste;
+ }
+
+ NS_ASSERTION(originalEventMessage == eCut || originalEventMessage == eCopy ||
+ originalEventMessage == ePaste,
+ "Invalid clipboard event type");
+
+ RefPtr<PresShell> presShell = aPresShell;
+ if (!presShell) {
+ return false;
+ }
+
+ nsCOMPtr<Document> doc = presShell->GetDocument();
+ if (!doc) return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
+ if (!piWindow) return false;
+
+ // Event target of clipboard events should be an element node which
+ // contains selection start container.
+ RefPtr<Element> targetElement;
+
+ // If a selection was not supplied, try to find it.
+ RefPtr<Selection> sel = aSelection;
+ if (!sel) {
+ sel = GetSelectionForCopy(doc);
+ }
+
+ // Retrieve the event target node from the start of the selection.
+ if (sel) {
+ const nsRange* range = sel->GetRangeAt(0);
+ if (range) {
+ targetElement = GetElementOrNearestFlattenedTreeParentElement(
+ range->GetStartContainer());
+ }
+ }
+
+ // If there is no selection ranges, use the <body> or <frameset> element.
+ if (!targetElement) {
+ targetElement = doc->GetBody();
+ if (!targetElement) {
+ return false;
+ }
+ }
+
+ // It seems to be unsafe to fire an event handler during reflow (bug 393696)
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::WarnScriptWasIgnored(doc);
+ return false;
+ }
+
+ BrowsingContext* bc = piWindow->GetBrowsingContext();
+ const bool chromeShell = bc && bc->IsChrome();
+
+ // next, fire the cut, copy or paste event
+ bool doDefault = true;
+ RefPtr<DataTransfer> clipboardData;
+ if (chromeShell || StaticPrefs::dom_event_clipboardevents_enabled()) {
+ clipboardData =
+ new DataTransfer(doc->GetScopeObject(), aEventMessage,
+ originalEventMessage == ePaste, aClipboardType);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ InternalClipboardEvent evt(true, originalEventMessage);
+ evt.mClipboardData = clipboardData;
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr,
+ &status);
+ // If the event was cancelled, don't do the clipboard operation
+ doDefault = (status != nsEventStatus_eConsumeNoDefault);
+ }
+
+ // When this function exits, the event dispatch is over. We want to disconnect
+ // our DataTransfer, which means setting its mode to `Protected` and clearing
+ // all stored data, before we return.
+ auto clearAfter = MakeScopeExit([&] {
+ if (clipboardData) {
+ clipboardData->Disconnect();
+
+ // NOTE: Disconnect may not actually clear the DataTransfer if the
+ // dom.events.dataTransfer.protected.enabled pref is not on, so we make
+ // sure we clear here, as not clearing could provide the DataTransfer
+ // access to information from the system clipboard at an arbitrary point
+ // in the future.
+ if (originalEventMessage == ePaste) {
+ clipboardData->ClearAll();
+ }
+ }
+ });
+
+ // No need to do anything special during a paste. Either an event listener
+ // took care of it and cancelled the event, or the caller will handle it.
+ // Return true to indicate that the event wasn't cancelled.
+ if (originalEventMessage == ePaste) {
+ if (aActionTaken) {
+ *aActionTaken = true;
+ }
+ return doDefault;
+ }
+
+ // Update the presentation in case the event handler modified the selection,
+ // see bug 602231.
+ presShell->FlushPendingNotifications(FlushType::Frames);
+ if (presShell->IsDestroying()) {
+ return false;
+ }
+
+ // if the event was not cancelled, do the default copy. If the event was
+ // cancelled, use the data added to the data transfer and copy that instead.
+ uint32_t count = 0;
+ if (doDefault) {
+ // find the focused node
+ nsIContent* sourceContent = targetElement.get();
+ if (targetElement->IsInNativeAnonymousSubtree()) {
+ sourceContent = targetElement->FindFirstNonChromeOnlyAccessContent();
+ }
+
+ // If it's <input type="password"> and there is no unmasked range or
+ // there is unmasked range but it's collapsed or it'll be masked
+ // automatically, the selected password shouldn't be copied into the
+ // clipboard.
+ if (RefPtr<HTMLInputElement> inputElement =
+ HTMLInputElement::FromNodeOrNull(sourceContent)) {
+ if (TextEditor* textEditor = inputElement->GetTextEditor()) {
+ if (textEditor->IsPasswordEditor() &&
+ !textEditor->IsCopyToClipboardAllowed()) {
+ return false;
+ }
+ }
+ }
+
+ // when cutting non-editable content, do nothing
+ // XXX this is probably the wrong editable flag to check
+ if (originalEventMessage != eCut || targetElement->IsEditable()) {
+ // get the data from the selection if any
+ if (sel->IsCollapsed()) {
+ if (aActionTaken) {
+ *aActionTaken = true;
+ }
+ return false;
+ }
+ // XXX Code which decides whether we should copy text with ruby
+ // annotation is currenct depending on whether each range of the
+ // selection is inside a same ruby container. But we really should
+ // expose the full functionality in browser. See bug 1130891.
+ bool withRubyAnnotation = IsSelectionInsideRuby(sel);
+ nsresult rv = EncodeDocumentWithContextAndPutToClipboard(
+ sel, doc, aClipboardType, withRubyAnnotation);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else if (clipboardData) {
+ // check to see if any data was put on the data transfer.
+ count = clipboardData->MozItemCount();
+ if (count) {
+ nsCOMPtr<nsIClipboard> clipboard(
+ do_GetService("@mozilla.org/widget/clipboard;1"));
+ NS_ENSURE_TRUE(clipboard, false);
+
+ nsCOMPtr<nsITransferable> transferable =
+ clipboardData->GetTransferable(0, doc->GetLoadContext());
+
+ NS_ENSURE_TRUE(transferable, false);
+
+ // put the transferable on the clipboard
+ nsresult rv = clipboard->SetData(transferable, nullptr, aClipboardType);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ }
+
+ // Now that we have copied, update the clipboard commands. This should have
+ // the effect of updating the enabled state of the paste menu item.
+ if (doDefault || count) {
+ piWindow->UpdateCommands(u"clipboard"_ns, nullptr, 0);
+ }
+
+ if (aActionTaken) {
+ *aActionTaken = true;
+ }
+ return doDefault;
+}
diff --git a/dom/base/nsCopySupport.h b/dom/base/nsCopySupport.h
new file mode 100644
index 0000000000..a6b4b5e783
--- /dev/null
+++ b/dom/base/nsCopySupport.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCopySupport_h__
+#define nsCopySupport_h__
+
+#include <cstdint>
+#include "ErrorList.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "nsStringFwd.h"
+
+class nsINode;
+class nsIImageLoadingContent;
+class nsITransferable;
+class nsILoadContext;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class Selection;
+} // namespace dom
+} // namespace mozilla
+
+class nsCopySupport {
+ // class of static helper functions for copy support
+ public:
+ static nsresult ClearSelectionCache();
+
+ /**
+ * @param aDoc Needs to be not nullptr.
+ */
+ static nsresult EncodeDocumentWithContextAndPutToClipboard(
+ mozilla::dom::Selection* aSel, mozilla::dom::Document* aDoc,
+ int16_t aClipboardID, bool aWithRubyAnnotation);
+
+ // Get the selection, or entire document, in the format specified by the mime
+ // type (text/html or text/plain). If aSel is non-null, use it, otherwise get
+ // the entire doc.
+ static nsresult GetContents(const nsACString& aMimeType, uint32_t aFlags,
+ mozilla::dom::Selection* aSel,
+ mozilla::dom::Document* aDoc, nsAString& outdata);
+
+ static nsresult ImageCopy(nsIImageLoadingContent* aImageElement,
+ nsILoadContext* aLoadContext, int32_t aCopyFlags);
+
+ // Get the selection as a transferable.
+ // @param aSelection Can be nullptr.
+ // @param aDocument Needs to be not nullptr.
+ // @param aTransferable Needs to be not nullptr.
+ static nsresult GetTransferableForSelection(
+ mozilla::dom::Selection* aSelection, mozilla::dom::Document* aDocument,
+ nsITransferable** aTransferable);
+
+ // Same as GetTransferableForSelection, but doesn't skip invisible content.
+ // @param aNode Needs to be not nullptr.
+ // @param aDoc Needs to be not nullptr.
+ // @param aTransferable Needs to be not nullptr.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static nsresult GetTransferableForNode(nsINode* aNode,
+ mozilla::dom::Document* aDoc,
+ nsITransferable** aTransferable);
+ /**
+ * Retrieve the selection for the given document. If the current focus
+ * within the document has its own selection, aSelection will be set to it
+ * and this focused content node returned. Otherwise, aSelection will be
+ * set to the document's selection and null will be returned.
+ */
+ static already_AddRefed<mozilla::dom::Selection> GetSelectionForCopy(
+ mozilla::dom::Document* aDocument);
+
+ /**
+ * Returns true if a copy operation is currently permitted based on the
+ * current focus and selection within the specified document.
+ */
+ static bool CanCopy(mozilla::dom::Document* aDocument);
+
+ /**
+ * Fires a cut, copy or paste event, on the given presshell, depending
+ * on the value of aEventMessage, which should be either eCut, eCopy or
+ * ePaste, and perform the default copy action if the event was not
+ * cancelled.
+ *
+ * If aSelection is specified, then this selection is used as the target
+ * of the operation. Otherwise, GetSelectionForCopy is used to retrieve
+ * the current selection.
+ *
+ * This will fire a cut, copy or paste event at the node at the start
+ * point of the selection. If a cut or copy event is not cancelled, the
+ * selection is copied to the clipboard and true is returned. Paste events
+ * have no default behaviour but true will be returned. It is expected
+ * that the caller will execute any needed default paste behaviour. Also,
+ * note that this method only copies text to the clipboard, the caller is
+ * responsible for removing the content during a cut operation if true is
+ * returned.
+ *
+ * aClipboardType specifies which clipboard to use, from nsIClipboard.
+ *
+ * If aActionTaken is non-NULL, it will be set to true if an action was
+ * taken, whether it be the default action or the default being prevented.
+ *
+ * If the event is cancelled or an error occurs, false will be returned.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static bool FireClipboardEvent(mozilla::EventMessage aEventMessage,
+ int32_t aClipboardType,
+ mozilla::PresShell* aPresShell,
+ mozilla::dom::Selection* aSelection,
+ bool* aActionTaken = nullptr);
+};
+
+#endif
diff --git a/dom/base/nsDOMAttributeMap.cpp b/dom/base/nsDOMAttributeMap.cpp
new file mode 100644
index 0000000000..5eba4c981d
--- /dev/null
+++ b/dom/base/nsDOMAttributeMap.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of the |attributes| property of DOM Core's Element object.
+ */
+
+#include "nsDOMAttributeMap.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/NamedNodeMapBinding.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "nsAttrName.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "nsNameSpaceManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsUnicharUtils.h"
+#include "nsWrapperCacheInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+
+nsDOMAttributeMap::nsDOMAttributeMap(Element* aContent) : mContent(aContent) {
+ // We don't add a reference to our content. If it goes away,
+ // we'll be told to drop our reference
+}
+
+nsDOMAttributeMap::~nsDOMAttributeMap() { DropReference(); }
+
+void nsDOMAttributeMap::DropReference() {
+ for (auto iter = mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->SetMap(nullptr);
+ iter.Remove();
+ }
+ mContent = nullptr;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMAttributeMap)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap)
+ tmp->DropReference();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap)
+ for (const auto& entry : tmp->mAttributeCache) {
+ cb.NoteXPCOMChild(static_cast<nsINode*>(entry.GetWeak()));
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap)
+ if (tmp->HasKnownLiveWrapper()) {
+ if (tmp->mContent) {
+ // The map owns the element so we can mark it when the
+ // map itself is certainly alive.
+ mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp->mContent);
+ }
+ return true;
+ }
+ if (tmp->mContent &&
+ mozilla::dom::FragmentOrElement::CanSkip(tmp->mContent, true)) {
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap)
+ return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap)
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// QueryInterface implementation for nsDOMAttributeMap
+
+NS_INTERFACE_MAP_BEGIN(nsDOMAttributeMap)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap)
+
+nsresult nsDOMAttributeMap::SetOwnerDocument(Document* aDocument) {
+ for (const auto& entry : mAttributeCache.Values()) {
+ nsresult rv = entry->SetOwnerDocument(aDocument);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ }
+ return NS_OK;
+}
+
+void nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID,
+ nsAtom* aLocalName) {
+ nsAttrKey attr(aNamespaceID, aLocalName);
+ if (auto entry = mAttributeCache.Lookup(attr)) {
+ entry.Data()->SetMap(nullptr); // break link to map
+ entry.Remove();
+ }
+}
+
+Attr* nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo) {
+ NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!");
+
+ nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
+
+ return mAttributeCache.LookupOrInsertWith(attr, [&] {
+ // Newly inserted entry!
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ auto* nim = ni->NodeInfoManager();
+ return new (nim) Attr(this, ni.forget(), u""_ns);
+ });
+}
+
+Attr* nsDOMAttributeMap::NamedGetter(const nsAString& aAttrName, bool& aFound) {
+ aFound = false;
+ NS_ENSURE_TRUE(mContent, nullptr);
+
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ mContent->GetExistingAttrNameFromQName(aAttrName);
+ if (!ni) {
+ return nullptr;
+ }
+
+ aFound = true;
+ return GetAttribute(ni);
+}
+
+void nsDOMAttributeMap::GetSupportedNames(nsTArray<nsString>& aNames) {
+ // For HTML elements in HTML documents, only include names that are still the
+ // same after ASCII-lowercasing, since our named getter will end up
+ // ASCII-lowercasing the given string.
+ bool lowercaseNamesOnly =
+ mContent->IsHTMLElement() && mContent->IsInHTMLDocument();
+
+ const uint32_t count = mContent->GetAttrCount();
+ bool seenNonAtomName = false;
+ for (uint32_t i = 0; i < count; i++) {
+ const nsAttrName* name = mContent->GetAttrNameAt(i);
+ seenNonAtomName = seenNonAtomName || !name->IsAtom();
+ nsString qualifiedName;
+ name->GetQualifiedName(qualifiedName);
+
+ if (lowercaseNamesOnly &&
+ nsContentUtils::StringContainsASCIIUpper(qualifiedName)) {
+ continue;
+ }
+
+ // Omit duplicates. We only need to do this check if we've seen a non-atom
+ // name, because that's the only way we can have two identical qualified
+ // names.
+ if (seenNonAtomName && aNames.Contains(qualifiedName)) {
+ continue;
+ }
+
+ aNames.AppendElement(qualifiedName);
+ }
+}
+
+Attr* nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName) {
+ bool dummy;
+ return NamedGetter(aAttrName, dummy);
+}
+
+already_AddRefed<Attr> nsDOMAttributeMap::SetNamedItemNS(Attr& aAttr,
+ ErrorResult& aError) {
+ NS_ENSURE_TRUE(mContent, nullptr);
+
+ // XXX should check same-origin between mContent and aAttr however
+ // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
+
+ // Check that attribute is not owned by somebody else
+ nsDOMAttributeMap* owner = aAttr.GetMap();
+ if (owner) {
+ if (owner != this) {
+ aError.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
+ return nullptr;
+ }
+
+ // setting a preexisting attribute is a no-op, just return the same
+ // node.
+ RefPtr<Attr> attribute = &aAttr;
+ return attribute.forget();
+ }
+
+ nsresult rv;
+ if (mContent->OwnerDoc() != aAttr.OwnerDoc()) {
+ DebugOnly<void*> adoptedNode =
+ mContent->OwnerDoc()->AdoptNode(aAttr, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(adoptedNode == &aAttr, "Uh, adopt node changed nodes?");
+ }
+
+ // Get nodeinfo and preexisting attribute (if it exists)
+ RefPtr<NodeInfo> oldNi;
+
+ uint32_t i, count = mContent->GetAttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName* name = mContent->GetAttrNameAt(i);
+ int32_t attrNS = name->NamespaceID();
+ nsAtom* nameAtom = name->LocalName();
+
+ // we're purposefully ignoring the prefix.
+ if (aAttr.NodeInfo()->Equals(nameAtom, attrNS)) {
+ oldNi = mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo(
+ nameAtom, name->GetPrefix(), aAttr.NodeInfo()->NamespaceID(),
+ nsINode::ATTRIBUTE_NODE);
+ break;
+ }
+ }
+
+ RefPtr<Attr> oldAttr;
+
+ if (oldNi) {
+ oldAttr = GetAttribute(oldNi);
+
+ if (oldAttr == &aAttr) {
+ return oldAttr.forget();
+ }
+
+ if (oldAttr) {
+ // Just remove it from our hashtable. This has no side-effects, so we
+ // don't have to recheck anything after we do it. Then we'll add our new
+ // Attr to the hashtable and do the actual attr set on the element. This
+ // will make the whole thing look like a single attribute mutation (with
+ // the new attr node in place) as opposed to a removal and addition.
+ DropAttribute(oldNi->NamespaceID(), oldNi->NameAtom());
+ }
+ }
+
+ nsAutoString value;
+ aAttr.GetValue(value);
+
+ RefPtr<NodeInfo> ni = aAttr.NodeInfo();
+
+ // Add the new attribute to the attribute map before updating
+ // its value in the element. @see bug 364413.
+ nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom());
+ mAttributeCache.InsertOrUpdate(attrkey, RefPtr{&aAttr});
+ aAttr.SetMap(this);
+
+ rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
+ value, true);
+ if (NS_FAILED(rv)) {
+ DropAttribute(ni->NamespaceID(), ni->NameAtom());
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ return oldAttr.forget();
+}
+
+already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItem(NodeInfo* aNodeInfo,
+ ErrorResult& aError) {
+ RefPtr<Attr> attribute = GetAttribute(aNodeInfo);
+ // This removes the attribute node from the attribute map.
+ aError = mContent->UnsetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(),
+ true);
+ return attribute.forget();
+}
+
+already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItem(
+ const nsAString& aName, ErrorResult& aError) {
+ if (!mContent) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ mContent->GetExistingAttrNameFromQName(aName);
+ if (!ni) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ return RemoveNamedItem(ni, aError);
+}
+
+Attr* nsDOMAttributeMap::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ aFound = false;
+ NS_ENSURE_TRUE(mContent, nullptr);
+
+ const nsAttrName* name = mContent->GetAttrNameAt(aIndex);
+ NS_ENSURE_TRUE(name, nullptr);
+
+ aFound = true;
+ // Don't use the nodeinfo even if one exists since it can have the wrong
+ // owner document.
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo(
+ name->LocalName(), name->GetPrefix(), name->NamespaceID(),
+ nsINode::ATTRIBUTE_NODE);
+ return GetAttribute(ni);
+}
+
+Attr* nsDOMAttributeMap::Item(uint32_t aIndex) {
+ bool dummy;
+ return IndexedGetter(aIndex, dummy);
+}
+
+uint32_t nsDOMAttributeMap::Length() const {
+ NS_ENSURE_TRUE(mContent, 0);
+
+ return mContent->GetAttrCount();
+}
+
+Attr* nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) {
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ GetAttrNodeInfo(aNamespaceURI, aLocalName);
+ if (!ni) {
+ return nullptr;
+ }
+
+ return GetAttribute(ni);
+}
+
+already_AddRefed<mozilla::dom::NodeInfo> nsDOMAttributeMap::GetAttrNodeInfo(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName) {
+ if (!mContent) {
+ return nullptr;
+ }
+
+ int32_t nameSpaceID = kNameSpaceID_None;
+
+ if (!aNamespaceURI.IsEmpty()) {
+ nameSpaceID = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespaceURI, nsContentUtils::IsChromeDoc(mContent->OwnerDoc()));
+
+ if (nameSpaceID == kNameSpaceID_Unknown) {
+ return nullptr;
+ }
+ }
+
+ uint32_t i, count = mContent->GetAttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName* name = mContent->GetAttrNameAt(i);
+ int32_t attrNS = name->NamespaceID();
+ nsAtom* nameAtom = name->LocalName();
+
+ // we're purposefully ignoring the prefix.
+ if (nameSpaceID == attrNS && nameAtom->Equals(aLocalName)) {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo(
+ nameAtom, name->GetPrefix(), nameSpaceID, nsINode::ATTRIBUTE_NODE);
+
+ return ni.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItemNS(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ ErrorResult& aError) {
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ GetAttrNodeInfo(aNamespaceURI, aLocalName);
+ if (!ni) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ return RemoveNamedItem(ni, aError);
+}
+
+uint32_t nsDOMAttributeMap::Count() const { return mAttributeCache.Count(); }
+
+size_t nsDOMAttributeMap::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += mAttributeCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mAttributeCache) {
+ n += aMallocSizeOf(entry.GetWeak());
+ }
+
+ // NB: mContent is non-owning and thus not counted.
+ return n;
+}
+
+/* virtual */
+JSObject* nsDOMAttributeMap::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return NamedNodeMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+DocGroup* nsDOMAttributeMap::GetDocGroup() const {
+ return mContent ? mContent->OwnerDoc()->GetDocGroup() : nullptr;
+}
diff --git a/dom/base/nsDOMAttributeMap.h b/dom/base/nsDOMAttributeMap.h
new file mode 100644
index 0000000000..5ed1e96544
--- /dev/null
+++ b/dom/base/nsDOMAttributeMap.h
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of the |attributes| property of DOM Core's Element object.
+ */
+
+#ifndef nsDOMAttributeMap_h
+#define nsDOMAttributeMap_h
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsAtom;
+class nsINode;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class Attr;
+class DocGroup;
+class Document;
+class Element;
+class NodeInfo;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Structure used as a key for caching Attrs in nsDOMAttributeMap's
+ * mAttributeCache.
+ */
+class nsAttrKey {
+ public:
+ /**
+ * The namespace of the attribute
+ */
+ int32_t mNamespaceID;
+
+ /**
+ * The atom for attribute, stored as void*, to make sure that we only use it
+ * for the hashcode, and we can never dereference it.
+ */
+ void* mLocalName;
+
+ nsAttrKey(int32_t aNs, nsAtom* aName)
+ : mNamespaceID(aNs), mLocalName(aName) {}
+
+ nsAttrKey(const nsAttrKey& aAttr) = default;
+};
+
+/**
+ * PLDHashEntryHdr implementation for nsAttrKey.
+ */
+class nsAttrHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const nsAttrKey&;
+ using KeyTypePointer = const nsAttrKey*;
+
+ explicit nsAttrHashKey(KeyTypePointer aKey) : mKey(*aKey) {}
+ nsAttrHashKey(const nsAttrHashKey& aCopy)
+ : PLDHashEntryHdr{}, mKey(aCopy.mKey) {}
+ ~nsAttrHashKey() = default;
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return mKey.mLocalName == aKey->mLocalName &&
+ mKey.mNamespaceID == aKey->mNamespaceID;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ if (!aKey) return 0;
+
+ return mozilla::HashGeneric(aKey->mNamespaceID, aKey->mLocalName);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsAttrKey mKey;
+};
+
+class nsDOMAttributeMap final : public nsISupports, public nsWrapperCache {
+ public:
+ using Attr = mozilla::dom::Attr;
+ using DocGroup = mozilla::dom::DocGroup;
+ using Document = mozilla::dom::Document;
+ using Element = mozilla::dom::Element;
+ using ErrorResult = mozilla::ErrorResult;
+
+ explicit nsDOMAttributeMap(Element* aContent);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(nsDOMAttributeMap)
+
+ void DropReference();
+
+ Element* GetContent() { return mContent; }
+
+ /**
+ * Called when mContent is moved into a new document.
+ * Updates the nodeinfos of all owned nodes.
+ */
+ nsresult SetOwnerDocument(Document* aDocument);
+
+ /**
+ * Drop an attribute from the map's cache (does not remove the attribute
+ * from the node!)
+ */
+ void DropAttribute(int32_t aNamespaceID, nsAtom* aLocalName);
+
+ /**
+ * Returns the number of attribute nodes currently in the map.
+ * Note: this is just the number of cached attribute nodes, not the number of
+ * attributes in mContent.
+ *
+ * @return The number of attribute nodes in the map.
+ */
+ uint32_t Count() const;
+
+ using AttrCache = nsRefPtrHashtable<nsAttrHashKey, Attr>;
+
+ static void BlastSubtreeToPieces(nsINode* aNode);
+
+ Element* GetParentObject() const { return mContent; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ DocGroup* GetDocGroup() const;
+
+ // WebIDL
+ Attr* GetNamedItem(const nsAString& aAttrName);
+ Attr* NamedGetter(const nsAString& aAttrName, bool& aFound);
+ already_AddRefed<Attr> RemoveNamedItem(mozilla::dom::NodeInfo* aNodeInfo,
+ ErrorResult& aError);
+ already_AddRefed<Attr> RemoveNamedItem(const nsAString& aName,
+ ErrorResult& aError);
+
+ Attr* Item(uint32_t aIndex);
+ Attr* IndexedGetter(uint32_t aIndex, bool& aFound);
+ uint32_t Length() const;
+
+ Attr* GetNamedItemNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName);
+ already_AddRefed<Attr> SetNamedItemNS(Attr& aNode, ErrorResult& aError);
+ already_AddRefed<Attr> RemoveNamedItemNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ ErrorResult& aError);
+
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ virtual ~nsDOMAttributeMap();
+
+ private:
+ nsCOMPtr<Element> mContent;
+
+ /**
+ * Cache of Attrs.
+ */
+ AttrCache mAttributeCache;
+
+ already_AddRefed<mozilla::dom::NodeInfo> GetAttrNodeInfo(
+ const nsAString& aNamespaceURI, const nsAString& aLocalName);
+
+ Attr* GetAttribute(mozilla::dom::NodeInfo* aNodeInfo);
+};
+
+#endif /* nsDOMAttributeMap_h */
diff --git a/dom/base/nsDOMCID.h b/dom/base/nsDOMCID.h
new file mode 100644
index 0000000000..fab3d37674
--- /dev/null
+++ b/dom/base/nsDOMCID.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMCID_h__
+#define nsDOMCID_h__
+
+#include "nsISupports.h"
+
+#define NS_DOM_SCRIPT_OBJECT_FACTORY_CID \
+ { /* 9eb760f0-4380-11d2-b328-00805f8a3859 */ \
+ 0x9eb760f0, 0x4380, 0x11d2, { \
+ 0xb3, 0x28, 0x00, 0x80, 0x5f, 0x8a, 0x38, 0x59 \
+ } \
+ }
+
+#define NS_SCRIPT_NAMESET_REGISTRY_CID \
+ { /* 45f27d10-987b-11d2-bd40-00105aa45e89 */ \
+ 0x45f27d10, 0x987b, 0x11d2, { \
+ 0xbd, 0x40, 0x00, 0x10, 0x5a, 0xa4, 0x5e, 0x89 \
+ } \
+ }
+
+#define NS_XPATH_EVALUATOR_CONTRACTID "@mozilla.org/dom/xpath-evaluator;1"
+
+#endif /* nsDOMCID_h__ */
diff --git a/dom/base/nsDOMCaretPosition.cpp b/dom/base/nsDOMCaretPosition.cpp
new file mode 100644
index 0000000000..69fdfa5d02
--- /dev/null
+++ b/dom/base/nsDOMCaretPosition.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMCaretPosition.h"
+
+#include "mozilla/dom/CaretPositionBinding.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/ErrorResult.h"
+#include "nsRange.h"
+
+using namespace mozilla::dom;
+
+nsDOMCaretPosition::nsDOMCaretPosition(nsINode* aNode, uint32_t aOffset)
+ : mOffset(aOffset), mOffsetNode(aNode), mAnonymousContentNode(nullptr) {}
+
+nsDOMCaretPosition::~nsDOMCaretPosition() = default;
+
+nsINode* nsDOMCaretPosition::GetOffsetNode() const { return mOffsetNode; }
+
+already_AddRefed<DOMRect> nsDOMCaretPosition::GetClientRect() const {
+ if (!mOffsetNode) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> node;
+ if (mAnonymousContentNode) {
+ node = mAnonymousContentNode;
+ } else {
+ node = mOffsetNode;
+ }
+
+ RefPtr<nsRange> range =
+ nsRange::Create(node, mOffset, node, mOffset, mozilla::IgnoreErrors());
+ if (!range) {
+ return nullptr;
+ }
+ RefPtr<DOMRect> rect = range->GetBoundingClientRect(false);
+ return rect.forget();
+}
+
+JSObject* nsDOMCaretPosition::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::CaretPosition_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCaretPosition, mOffsetNode,
+ mAnonymousContentNode)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCaretPosition)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCaretPosition)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCaretPosition)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
diff --git a/dom/base/nsDOMCaretPosition.h b/dom/base/nsDOMCaretPosition.h
new file mode 100644
index 0000000000..8ede967edd
--- /dev/null
+++ b/dom/base/nsDOMCaretPosition.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMCaretPosition_h
+#define nsDOMCaretPosition_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsINode.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+class DOMRect;
+} // namespace mozilla::dom
+
+/**
+ * Implementation of a DOM Caret Position, which is a node and offset within
+ * that node, in the DOM tree.
+ *
+ * http://www.w3.org/TR/cssom-view/#dom-documentview-caretrangefrompoint
+ *
+ * @see Document::caretPositionFromPoint(float x, float y)
+ */
+class nsDOMCaretPosition : public nsISupports, public nsWrapperCache {
+ using DOMRect = mozilla::dom::DOMRect;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMCaretPosition)
+
+ nsDOMCaretPosition(nsINode* aNode, uint32_t aOffset);
+
+ /**
+ * Retrieve the offset (character position within the DOM node) of the
+ * CaretPosition.
+ *
+ * @returns The offset within the DOM node.
+ */
+ uint32_t Offset() const { return mOffset; }
+
+ /**
+ * Retrieve the DOM node with which this CaretPosition was established.
+ * Normally, this will be created from a point, so it will be the DOM
+ * node that lies at the point specified.
+ *
+ * @returns The DOM node of the CaretPosition.
+ *
+ * @see Document::caretPositionFromPoint(float x, float y)
+ */
+ nsINode* GetOffsetNode() const;
+
+ /**
+ * Retrieve the bounding rectangle of this CaretPosition object.
+ *
+ * @returns An nsClientRect representing the bounding rectangle of this
+ * CaretPosition, if one can be successfully determined, otherwise
+ * nullptr.
+ */
+ already_AddRefed<DOMRect> GetClientRect() const;
+
+ /**
+ * Set the anonymous content node that is the actual parent of this
+ * CaretPosition object. In situations where the DOM node for a CaretPosition
+ * actually lies within an anonymous content node (e.g. a textarea), the
+ * actual parent is not set as the offset node. This is used to get the
+ * correct bounding box of a CaretPosition object that lies within a textarea
+ * or input element.
+ *
+ * @param aNode A pointer to an nsINode object that is the actual element
+ * within which this CaretPosition lies, but is an anonymous content
+ * node.
+ */
+ void SetAnonymousContentNode(nsINode* aNode) {
+ mAnonymousContentNode = aNode;
+ }
+
+ nsISupports* GetParentObject() const { return GetOffsetNode(); }
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ protected:
+ virtual ~nsDOMCaretPosition();
+
+ uint32_t mOffset;
+ nsCOMPtr<nsINode> mOffsetNode;
+ nsCOMPtr<nsINode> mAnonymousContentNode;
+};
+#endif
diff --git a/dom/base/nsDOMDataChannel.cpp b/dom/base/nsDOMDataChannel.cpp
new file mode 100644
index 0000000000..6f1e475168
--- /dev/null
+++ b/dom/base/nsDOMDataChannel.cpp
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMDataChannel.h"
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+
+#include "nsDOMDataChannelDeclarations.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/Blob.h"
+
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsProxyRelease.h"
+
+#include "DataChannel.h"
+#include "DataChannelLog.h"
+
+// Since we've moved the windows.h include down here, we have to explicitly
+// undef GetBinaryType, otherwise we'll get really odd conflicts
+#ifdef GetBinaryType
+# undef GetBinaryType
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMDataChannel::~nsDOMDataChannel() {
+ // Don't call us anymore! Likely isn't an issue (or maybe just less of
+ // one) once we block GC until all the (appropriate) onXxxx handlers
+ // are dropped. (See WebRTC spec)
+ DC_DEBUG(("%p: Close()ing %p", this, mDataChannel.get()));
+ mDataChannel->SetListener(nullptr, nullptr);
+ mDataChannel->Close();
+}
+
+/* virtual */
+JSObject* nsDOMDataChannel::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDataChannel_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMDataChannel)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMDataChannel,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMDataChannel,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMDataChannel)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+nsDOMDataChannel::nsDOMDataChannel(
+ already_AddRefed<mozilla::DataChannel>& aDataChannel,
+ nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow),
+ mDataChannel(aDataChannel),
+ mBinaryType(DC_BINARY_TYPE_BLOB),
+ mCheckMustKeepAlive(true),
+ mSentClose(false) {}
+
+nsresult nsDOMDataChannel::Init(nsPIDOMWindowInner* aDOMWindow) {
+ nsresult rv;
+ nsAutoString urlParam;
+
+ MOZ_ASSERT(mDataChannel);
+ mDataChannel->SetListener(this, nullptr);
+
+ // Now grovel through the objects to get a usable origin for onMessage
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aDOMWindow);
+ NS_ENSURE_STATE(sgo);
+ nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
+ NS_ENSURE_STATE(scriptContext);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal(
+ do_QueryInterface(aDOMWindow));
+ NS_ENSURE_STATE(scriptPrincipal);
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ NS_ENSURE_STATE(principal);
+
+ // Attempt to kill "ghost" DataChannel (if one can happen): but usually too
+ // early for check to fail
+ rv = CheckCurrentGlobalCorrectness();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
+ DC_DEBUG(("%s: origin = %s\n", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(mOrigin).get()));
+ return rv;
+}
+
+// Most of the GetFoo()/SetFoo()s don't need to touch shared resources and
+// are safe after Close()
+void nsDOMDataChannel::GetLabel(nsAString& aLabel) {
+ mDataChannel->GetLabel(aLabel);
+}
+
+void nsDOMDataChannel::GetProtocol(nsAString& aProtocol) {
+ mDataChannel->GetProtocol(aProtocol);
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetId() const {
+ mozilla::dom::Nullable<uint16_t> result = mDataChannel->GetStream();
+ if (result.Value() == 65535) {
+ result.SetNull();
+ }
+ return result;
+}
+
+// XXX should be GetType()? Open question for the spec
+bool nsDOMDataChannel::Reliable() const {
+ return mDataChannel->GetType() == mozilla::DataChannelConnection::RELIABLE;
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetMaxPacketLifeTime()
+ const {
+ return mDataChannel->GetMaxPacketLifeTime();
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetMaxRetransmits() const {
+ return mDataChannel->GetMaxRetransmits();
+}
+
+bool nsDOMDataChannel::Negotiated() const {
+ return mDataChannel->GetNegotiated();
+}
+
+bool nsDOMDataChannel::Ordered() const { return mDataChannel->GetOrdered(); }
+
+RTCDataChannelState nsDOMDataChannel::ReadyState() const {
+ return static_cast<RTCDataChannelState>(mDataChannel->GetReadyState());
+}
+
+uint32_t nsDOMDataChannel::BufferedAmount() const {
+ if (!mSentClose) {
+ return mDataChannel->GetBufferedAmount();
+ }
+ return 0;
+}
+
+uint32_t nsDOMDataChannel::BufferedAmountLowThreshold() const {
+ return mDataChannel->GetBufferedAmountLowThreshold();
+}
+
+void nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold) {
+ mDataChannel->SetBufferedAmountLowThreshold(aThreshold);
+}
+
+void nsDOMDataChannel::Close() {
+ mDataChannel->Close();
+ UpdateMustKeepAlive();
+}
+
+// All of the following is copy/pasted from WebSocket.cpp.
+void nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv) {
+ nsAutoCString msgString;
+ if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ Send(nullptr, &msgString, false, aRv);
+}
+
+void nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ nsCOMPtr<nsIInputStream> msgStream;
+ aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ uint64_t msgLength = aData.GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (msgLength > UINT32_MAX) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(&aData, nullptr, true, aRv);
+}
+
+void nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeState();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString;
+ if (!msgString.Assign(data, len, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(nullptr, &msgString, true, aRv);
+}
+
+void nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeState();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString;
+ if (!msgString.Assign(data, len, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(nullptr, &msgString, true, aRv);
+}
+
+void nsDOMDataChannel::Send(mozilla::dom::Blob* aMsgBlob,
+ const nsACString* aMsgString, bool aIsBinary,
+ mozilla::ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ uint16_t state = mozilla::DataChannel::CLOSED;
+ if (!mSentClose) {
+ state = mDataChannel->GetReadyState();
+ }
+
+ // In reality, the DataChannel protocol allows this, but we want it to
+ // look like WebSockets
+ if (state == mozilla::DataChannel::CONNECTING) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (state == mozilla::DataChannel::CLOSING ||
+ state == mozilla::DataChannel::CLOSED) {
+ return;
+ }
+
+ MOZ_ASSERT(state == mozilla::DataChannel::OPEN,
+ "Unknown state in nsDOMDataChannel::Send");
+
+ if (aMsgBlob) {
+ mDataChannel->SendBinaryBlob(*aMsgBlob, aRv);
+ } else {
+ if (aIsBinary) {
+ mDataChannel->SendBinaryMsg(*aMsgString, aRv);
+ } else {
+ mDataChannel->SendMsg(*aMsgString, aRv);
+ }
+ }
+}
+
+nsresult nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,
+ bool aBinary) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DC_VERBOSE((
+ "DoOnMessageAvailable%s\n",
+ aBinary ? ((mBinaryType == DC_BINARY_TYPE_BLOB) ? " (blob)" : " (binary)")
+ : ""));
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> jsData(cx);
+
+ if (aBinary) {
+ if (mBinaryType == DC_BINARY_TYPE_BLOB) {
+ RefPtr<Blob> blob =
+ Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns);
+ if (NS_WARN_IF(!blob)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!ToJSValue(cx, blob, &jsData)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (mBinaryType == DC_BINARY_TYPE_ARRAYBUFFER) {
+ JS::Rooted<JSObject*> arrayBuf(cx);
+ rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ jsData.setObject(*arrayBuf);
+ } else {
+ MOZ_CRASH("Unknown binary type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ NS_ConvertUTF8toUTF16 utf16data(aData);
+ JSString* jsString =
+ JS_NewUCStringCopyN(cx, utf16data.get(), utf16data.Length());
+ NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
+
+ jsData.setString(jsString);
+ }
+
+ RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, jsData, mOrigin, u""_ns, nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ if (err.Failed()) {
+ DC_ERROR(("%p(%p): %s - Failed to dispatch message", this,
+ (void*)mDataChannel, __FUNCTION__));
+ NS_WARNING("Failed to dispatch the message event!!!");
+ }
+ return err.StealNSResult();
+}
+
+nsresult nsDOMDataChannel::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return DoOnMessageAvailable(aMessage, false);
+}
+
+nsresult nsDOMDataChannel::OnBinaryMessageAvailable(
+ nsISupports* aContext, const nsACString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return DoOnMessageAvailable(aMessage, true);
+}
+
+nsresult nsDOMDataChannel::OnSimpleEvent(nsISupports* aContext,
+ const nsAString& aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ event->InitEvent(aName, CanBubble::eNo, Cancelable::eNo);
+ event->SetTrusted(true);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+nsresult nsDOMDataChannel::OnChannelConnected(nsISupports* aContext) {
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+
+ return OnSimpleEvent(aContext, u"open"_ns);
+}
+
+nsresult nsDOMDataChannel::OnChannelClosed(nsISupports* aContext) {
+ nsresult rv;
+ // so we don't have to worry if we're notified from different paths in
+ // the underlying code
+ if (!mSentClose) {
+ // Ok, we're done with it.
+ mDataChannel->ReleaseConnection();
+ DC_DEBUG(("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel,
+ __FUNCTION__));
+
+ rv = OnSimpleEvent(aContext, u"close"_ns);
+ // no more events can happen
+ mSentClose = true;
+ } else {
+ rv = NS_OK;
+ }
+ DontKeepAliveAnyMore();
+ return rv;
+}
+
+nsresult nsDOMDataChannel::OnBufferLow(nsISupports* aContext) {
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+
+ return OnSimpleEvent(aContext, u"bufferedamountlow"_ns);
+}
+
+nsresult nsDOMDataChannel::NotBuffered(nsISupports* aContext) {
+ // In the rare case that we held off GC to let the buffer drain
+ UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Methods that keep alive the DataChannel object when:
+// 1. the object has registered event listeners that can be triggered
+// ("strong event listeners");
+// 2. there are outgoing not sent messages.
+//-----------------------------------------------------------------------------
+
+void nsDOMDataChannel::UpdateMustKeepAlive() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCheckMustKeepAlive) {
+ return;
+ }
+
+ bool shouldKeepAlive = false;
+ uint16_t readyState = mDataChannel->GetReadyState();
+
+ switch (readyState) {
+ case DataChannel::CONNECTING: {
+ if (mListenerManager &&
+ (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose))) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case DataChannel::OPEN:
+ case DataChannel::CLOSING: {
+ if (mDataChannel->GetBufferedAmount() != 0 ||
+ (mListenerManager &&
+ (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose)))) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case DataChannel::CLOSED: {
+ shouldKeepAlive = false;
+ }
+ }
+
+ if (mSelfRef && !shouldKeepAlive) {
+ ReleaseSelf();
+ } else if (!mSelfRef && shouldKeepAlive) {
+ mSelfRef = this;
+ }
+}
+
+void nsDOMDataChannel::DontKeepAliveAnyMore() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSelfRef) {
+ // Since we're on MainThread, force an eventloop trip to avoid deleting
+ // ourselves.
+ ReleaseSelf();
+ }
+
+ mCheckMustKeepAlive = false;
+}
+
+void nsDOMDataChannel::ReleaseSelf() {
+ // release our self-reference (safely) by putting it in an event (always)
+ NS_ReleaseOnMainThread("nsDOMDataChannel::mSelfRef", mSelfRef.forget(), true);
+}
+
+void nsDOMDataChannel::EventListenerAdded(nsAtom* aType) {
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+void nsDOMDataChannel::EventListenerRemoved(nsAtom* aType) {
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+/* static */
+nsresult NS_NewDOMDataChannel(
+ already_AddRefed<mozilla::DataChannel>&& aDataChannel,
+ nsPIDOMWindowInner* aWindow, nsDOMDataChannel** aDomDataChannel) {
+ RefPtr<nsDOMDataChannel> domdc = new nsDOMDataChannel(aDataChannel, aWindow);
+
+ nsresult rv = domdc->Init(aWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ domdc.forget(aDomDataChannel);
+ return NS_OK;
+}
diff --git a/dom/base/nsDOMDataChannel.h b/dom/base/nsDOMDataChannel.h
new file mode 100644
index 0000000000..fccd5106d8
--- /dev/null
+++ b/dom/base/nsDOMDataChannel.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMDataChannel_h
+#define nsDOMDataChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/RTCDataChannelBinding.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/net/DataChannelListener.h"
+
+namespace mozilla {
+namespace dom {
+class Blob;
+}
+
+class DataChannel;
+}; // namespace mozilla
+
+class nsDOMDataChannel final : public mozilla::DOMEventTargetHelper,
+ public mozilla::DataChannelListener {
+ public:
+ nsDOMDataChannel(already_AddRefed<mozilla::DataChannel>& aDataChannel,
+ nsPIDOMWindowInner* aWindow);
+
+ nsresult Init(nsPIDOMWindowInner* aDOMWindow);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMDataChannel,
+ mozilla::DOMEventTargetHelper)
+
+ // EventTarget
+ using EventTarget::EventListenerAdded;
+ virtual void EventListenerAdded(nsAtom* aType) override;
+
+ using EventTarget::EventListenerRemoved;
+ virtual void EventListenerRemoved(nsAtom* aType) override;
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ // WebIDL
+ void GetLabel(nsAString& aLabel);
+ void GetProtocol(nsAString& aProtocol);
+ bool Reliable() const;
+ mozilla::dom::Nullable<uint16_t> GetMaxPacketLifeTime() const;
+ mozilla::dom::Nullable<uint16_t> GetMaxRetransmits() const;
+ mozilla::dom::RTCDataChannelState ReadyState() const;
+ uint32_t BufferedAmount() const;
+ uint32_t BufferedAmountLowThreshold() const;
+ void SetBufferedAmountLowThreshold(uint32_t aThreshold);
+ IMPL_EVENT_HANDLER(open)
+ IMPL_EVENT_HANDLER(error)
+ IMPL_EVENT_HANDLER(close)
+ void Close();
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(bufferedamountlow)
+ mozilla::dom::RTCDataChannelType BinaryType() const {
+ return static_cast<mozilla::dom::RTCDataChannelType>(
+ static_cast<int>(mBinaryType));
+ }
+ void SetBinaryType(mozilla::dom::RTCDataChannelType aType) {
+ mBinaryType = static_cast<DataChannelBinaryType>(static_cast<int>(aType));
+ }
+ void Send(const nsAString& aData, mozilla::ErrorResult& aRv);
+ void Send(mozilla::dom::Blob& aData, mozilla::ErrorResult& aRv);
+ void Send(const mozilla::dom::ArrayBuffer& aData, mozilla::ErrorResult& aRv);
+ void Send(const mozilla::dom::ArrayBufferView& aData,
+ mozilla::ErrorResult& aRv);
+
+ bool Negotiated() const;
+ bool Ordered() const;
+ mozilla::dom::Nullable<uint16_t> GetId() const;
+
+ nsresult DoOnMessageAvailable(const nsACString& aMessage, bool aBinary);
+
+ virtual nsresult OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMessage) override;
+
+ virtual nsresult OnBinaryMessageAvailable(
+ nsISupports* aContext, const nsACString& aMessage) override;
+
+ virtual nsresult OnSimpleEvent(nsISupports* aContext, const nsAString& aName);
+
+ virtual nsresult OnChannelConnected(nsISupports* aContext) override;
+
+ virtual nsresult OnChannelClosed(nsISupports* aContext) override;
+
+ virtual nsresult OnBufferLow(nsISupports* aContext) override;
+
+ virtual nsresult NotBuffered(nsISupports* aContext) override;
+
+ // if there are "strong event listeners" or outgoing not sent messages
+ // then this method keeps the object alive when js doesn't have strong
+ // references to it.
+ void UpdateMustKeepAlive();
+ // ATTENTION, when calling this method the object can be released
+ // (and possibly collected).
+ void DontKeepAliveAnyMore();
+
+ protected:
+ ~nsDOMDataChannel();
+
+ private:
+ void Send(mozilla::dom::Blob* aMsgBlob, const nsACString* aMsgString,
+ bool aIsBinary, mozilla::ErrorResult& aRv);
+
+ void ReleaseSelf();
+
+ // to keep us alive while we have listeners
+ RefPtr<nsDOMDataChannel> mSelfRef;
+ // Owning reference
+ RefPtr<mozilla::DataChannel> mDataChannel;
+ nsString mOrigin;
+ enum DataChannelBinaryType {
+ DC_BINARY_TYPE_ARRAYBUFFER,
+ DC_BINARY_TYPE_BLOB,
+ };
+ DataChannelBinaryType mBinaryType;
+ bool mCheckMustKeepAlive;
+ bool mSentClose;
+};
+
+#endif // nsDOMDataChannel_h
diff --git a/dom/base/nsDOMDataChannelDeclarations.h b/dom/base/nsDOMDataChannelDeclarations.h
new file mode 100644
index 0000000000..60e9d1f3f7
--- /dev/null
+++ b/dom/base/nsDOMDataChannelDeclarations.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMDataChannelDeclarations_h
+#define nsDOMDataChannelDeclarations_h
+
+// This defines only what's necessary to create nsDOMDataChannels, since this
+// gets used with MOZ_INTERNAL_API not set for media/webrtc/signaling/testing
+
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+class DataChannel;
+}
+
+class nsDOMDataChannel;
+class nsPIDOMWindowInner;
+
+nsresult NS_NewDOMDataChannel(
+ already_AddRefed<mozilla::DataChannel>&& dataChannel,
+ nsPIDOMWindowInner* aWindow, nsDOMDataChannel** domDataChannel);
+
+#endif // nsDOMDataChannelDeclarations_h
diff --git a/dom/base/nsDOMJSUtils.h b/dom/base/nsDOMJSUtils.h
new file mode 100644
index 0000000000..bf859afa7b
--- /dev/null
+++ b/dom/base/nsDOMJSUtils.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMJSUtils_h__
+#define nsDOMJSUtils_h__
+
+#include "js/TypeDecls.h"
+#include "nscore.h"
+
+class nsIJSArgArray;
+
+// A factory function for turning a JS::Value argv into an nsIArray
+// but also supports an effecient way of extracting the original argv.
+// The resulting object will take a copy of the array, and ensure each
+// element is rooted.
+// Optionally, aArgv may be nullptr, in which case the array is allocated and
+// rooted, but all items remain nullptr. This presumably means the caller
+// will then QI us for nsIJSArgArray, and set our array elements.
+nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t aArgc,
+ const JS::Value* aArgv, nsIJSArgArray** aArray);
+
+#endif // nsDOMJSUtils_h__
diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp
new file mode 100644
index 0000000000..cb1416f869
--- /dev/null
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -0,0 +1,1097 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMMutationObserver.h"
+
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/OwningNonNull.h"
+
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/DocGroup.h"
+
+#include "mozilla/BasePrincipal.h"
+
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsNameSpaceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFragment.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
+ nsDOMMutationObserver::sScheduledMutationObservers = nullptr;
+
+uint32_t nsDOMMutationObserver::sMutationLevel = 0;
+uint64_t nsDOMMutationObserver::sCount = 0;
+
+AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
+ nsDOMMutationObserver::sCurrentlyHandlingObservers = nullptr;
+
+nsINodeList* nsDOMMutationRecord::AddedNodes() {
+ if (!mAddedNodes) {
+ mAddedNodes = new nsSimpleContentList(mTarget);
+ }
+ return mAddedNodes;
+}
+
+nsINodeList* nsDOMMutationRecord::RemovedNodes() {
+ if (!mRemovedNodes) {
+ mRemovedNodes = new nsSimpleContentList(mTarget);
+ }
+ return mRemovedNodes;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationRecord)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationRecord)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationRecord)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMMutationRecord, mTarget,
+ mPreviousSibling, mNextSibling,
+ mAddedNodes, mRemovedNodes,
+ mAddedAnimations, mRemovedAnimations,
+ mChangedAnimations, mNext, mOwner)
+
+// Observer
+
+bool nsMutationReceiverBase::IsObservable(nsIContent* aContent) {
+ return !aContent->ChromeOnlyAccess() || ChromeOnlyNodes();
+}
+
+bool nsMutationReceiverBase::ObservesAttr(nsINode* aRegisterTarget,
+ Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttr) {
+ if (mParent) {
+ return mParent->ObservesAttr(aRegisterTarget, aElement, aNameSpaceID,
+ aAttr);
+ }
+ if (!Attributes() || (!Subtree() && aElement != Target()) ||
+ (Subtree() &&
+ aRegisterTarget->SubtreeRoot() != aElement->SubtreeRoot()) ||
+ !IsObservable(aElement)) {
+ return false;
+ }
+ if (AllAttributes()) {
+ return true;
+ }
+
+ if (aNameSpaceID != kNameSpaceID_None) {
+ return false;
+ }
+
+ nsTArray<RefPtr<nsAtom>>& filters = AttributeFilter();
+ for (size_t i = 0; i < filters.Length(); ++i) {
+ if (filters[i] == aAttr) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMPL_ADDREF(nsMutationReceiver)
+NS_IMPL_RELEASE(nsMutationReceiver)
+
+NS_INTERFACE_MAP_BEGIN(nsMutationReceiver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END
+
+nsMutationReceiver::nsMutationReceiver(nsINode* aTarget,
+ nsDOMMutationObserver* aObserver)
+ : nsMutationReceiverBase(aTarget, aObserver) {
+ mTarget->BindObject(aObserver);
+}
+
+void nsMutationReceiver::Disconnect(bool aRemoveFromObserver) {
+ if (mRegisterTarget) {
+ mRegisterTarget->RemoveMutationObserver(this);
+ mRegisterTarget = nullptr;
+ }
+
+ mParent = nullptr;
+ nsINode* target = mTarget;
+ mTarget = nullptr;
+ nsDOMMutationObserver* observer = mObserver;
+ mObserver = nullptr;
+ RemoveClones();
+
+ if (target && observer) {
+ if (aRemoveFromObserver) {
+ static_cast<nsDOMMutationObserver*>(observer)->RemoveReceiver(this);
+ }
+ // UnbindObject may delete 'this'!
+ target->UnbindObject(observer);
+ }
+}
+
+void nsMutationReceiver::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (nsAutoMutationBatch::IsBatching() ||
+ !ObservesAttr(RegisterTarget(), aElement, aNameSpaceID, aAttribute)) {
+ return;
+ }
+
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::attributes);
+
+ NS_ASSERTION(!m->mTarget || m->mTarget == aElement, "Wrong target!");
+ NS_ASSERTION(!m->mAttrName || m->mAttrName == aAttribute, "Wrong attribute!");
+ if (!m->mTarget) {
+ m->mTarget = aElement;
+ m->mAttrName = aAttribute;
+ if (aNameSpaceID == kNameSpaceID_None) {
+ m->mAttrNamespace.SetIsVoid(true);
+ } else {
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNameSpaceID,
+ m->mAttrNamespace);
+ }
+ }
+
+ if (AttributeOldValue() && m->mPrevValue.IsVoid()) {
+ if (!aElement->GetAttr(aNameSpaceID, aAttribute, m->mPrevValue)) {
+ m->mPrevValue.SetIsVoid(true);
+ }
+ }
+}
+
+void nsMutationReceiver::CharacterDataWillChange(
+ nsIContent* aContent, const CharacterDataChangeInfo&) {
+ if (nsAutoMutationBatch::IsBatching() || !CharacterData() ||
+ (!Subtree() && aContent != Target()) ||
+ (Subtree() &&
+ RegisterTarget()->SubtreeRoot() != aContent->SubtreeRoot()) ||
+ !IsObservable(aContent)) {
+ return;
+ }
+
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::characterData);
+
+ NS_ASSERTION(!m->mTarget || m->mTarget == aContent, "Wrong target!");
+
+ if (!m->mTarget) {
+ m->mTarget = aContent;
+ }
+ if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) {
+ aContent->GetText()->AppendTo(m->mPrevValue);
+ }
+}
+
+void nsMutationReceiver::ContentAppended(nsIContent* aFirstNewContent) {
+ nsINode* parent = aFirstNewContent->GetParentNode();
+ bool wantsChildList =
+ ChildList() && ((Subtree() && RegisterTarget()->SubtreeRoot() ==
+ parent->SubtreeRoot()) ||
+ parent == Target());
+ if (!wantsChildList || !IsObservable(aFirstNewContent)) {
+ return;
+ }
+
+ if (nsAutoMutationBatch::IsBatching()) {
+ if (parent == nsAutoMutationBatch::GetBatchTarget()) {
+ nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
+ }
+ return;
+ }
+
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList);
+ NS_ASSERTION(!m->mTarget || m->mTarget == parent, "Wrong target!");
+ if (m->mTarget) {
+ // Already handled case.
+ return;
+ }
+ m->mTarget = parent;
+ m->mAddedNodes = new nsSimpleContentList(parent);
+
+ nsINode* n = aFirstNewContent;
+ while (n) {
+ m->mAddedNodes->AppendElement(static_cast<nsIContent*>(n));
+ n = n->GetNextSibling();
+ }
+ m->mPreviousSibling = aFirstNewContent->GetPreviousSibling();
+}
+
+void nsMutationReceiver::ContentInserted(nsIContent* aChild) {
+ nsINode* parent = aChild->GetParentNode();
+ bool wantsChildList =
+ ChildList() && ((Subtree() && RegisterTarget()->SubtreeRoot() ==
+ parent->SubtreeRoot()) ||
+ parent == Target());
+ if (!wantsChildList || !IsObservable(aChild)) {
+ return;
+ }
+
+ if (nsAutoMutationBatch::IsBatching()) {
+ if (parent == nsAutoMutationBatch::GetBatchTarget()) {
+ nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
+ }
+ return;
+ }
+
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList);
+ if (m->mTarget) {
+ // Already handled case.
+ return;
+ }
+ m->mTarget = parent;
+ m->mAddedNodes = new nsSimpleContentList(parent);
+ m->mAddedNodes->AppendElement(aChild);
+ m->mPreviousSibling = aChild->GetPreviousSibling();
+ m->mNextSibling = aChild->GetNextSibling();
+}
+
+void nsMutationReceiver::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (!IsObservable(aChild)) {
+ return;
+ }
+
+ nsINode* parent = aChild->GetParentNode();
+ if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) {
+ return;
+ }
+ if (nsAutoMutationBatch::IsBatching()) {
+ if (nsAutoMutationBatch::IsRemovalDone()) {
+ // This can happen for example if HTML parser parses to
+ // context node, but needs to move elements around.
+ return;
+ }
+ if (nsAutoMutationBatch::GetBatchTarget() != parent) {
+ return;
+ }
+
+ bool wantsChildList = ChildList() && (Subtree() || parent == Target());
+ if (wantsChildList || Subtree()) {
+ nsAutoMutationBatch::NodeRemoved(aChild);
+ nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
+ }
+
+ return;
+ }
+
+ if (Subtree()) {
+ // Try to avoid creating transient observer if the node
+ // already has an observer observing the same set of nodes.
+ nsMutationReceiver* orig = GetParent() ? GetParent() : this;
+ if (Observer()->GetReceiverFor(aChild, false, false) != orig) {
+ bool transientExists = false;
+ bool isNewEntry = false;
+ auto* const transientReceivers =
+ Observer()
+ ->mTransientReceivers
+ .LookupOrInsertWith(
+ aChild,
+ [&isNewEntry] {
+ isNewEntry = true;
+ return MakeUnique<nsCOMArray<nsMutationReceiver>>();
+ })
+ .get();
+ if (!isNewEntry) {
+ for (int32_t i = 0; i < transientReceivers->Count(); ++i) {
+ nsMutationReceiver* r = transientReceivers->ObjectAt(i);
+ if (r->GetParent() == orig) {
+ transientExists = true;
+ }
+ }
+ }
+ if (!transientExists) {
+ // Make sure the elements which are removed from the
+ // subtree are kept in the same observation set.
+ nsMutationReceiver* tr;
+ if (orig->Animations()) {
+ tr = nsAnimationReceiver::Create(aChild, orig);
+ } else {
+ tr = nsMutationReceiver::Create(aChild, orig);
+ }
+ transientReceivers->AppendObject(tr);
+ }
+ }
+ }
+
+ if (ChildList() && (Subtree() || parent == Target())) {
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList);
+ if (m->mTarget) {
+ // Already handled case.
+ return;
+ }
+ MOZ_ASSERT(parent);
+
+ m->mTarget = parent;
+ m->mRemovedNodes = new nsSimpleContentList(parent);
+ m->mRemovedNodes->AppendElement(aChild);
+ m->mPreviousSibling = aPreviousSibling;
+ m->mNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
+ : parent->GetFirstChild();
+ }
+ // We need to schedule always, so that after microtask mTransientReceivers
+ // can be cleared correctly.
+ Observer()->ScheduleForRun();
+}
+
+void nsMutationReceiver::NodeWillBeDestroyed(nsINode* aNode) {
+ NS_ASSERTION(!mParent, "Shouldn't have mParent here!");
+ Disconnect(true);
+}
+
+void nsAnimationReceiver::RecordAnimationMutation(
+ Animation* aAnimation, AnimationMutation aMutationType) {
+ AnimationEffect* effect = aAnimation->GetEffect();
+ if (!effect) {
+ return;
+ }
+
+ KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
+ if (!keyframeEffect) {
+ return;
+ }
+
+ NonOwningAnimationTarget animationTarget =
+ keyframeEffect->GetAnimationTarget();
+ if (!animationTarget) {
+ return;
+ }
+
+ Element* elem = animationTarget.mElement;
+ if (!Animations() || !(Subtree() || elem == Target()) ||
+ elem->ChromeOnlyAccess()) {
+ return;
+ }
+
+ // Record animations targeting to a pseudo element only when subtree is true.
+ if (animationTarget.mPseudoType != PseudoStyleType::NotPseudo && !Subtree()) {
+ return;
+ }
+
+ if (nsAutoAnimationMutationBatch::IsBatching()) {
+ switch (aMutationType) {
+ case eAnimationMutation_Added:
+ nsAutoAnimationMutationBatch::AnimationAdded(aAnimation, elem);
+ break;
+ case eAnimationMutation_Changed:
+ nsAutoAnimationMutationBatch::AnimationChanged(aAnimation, elem);
+ break;
+ case eAnimationMutation_Removed:
+ nsAutoAnimationMutationBatch::AnimationRemoved(aAnimation, elem);
+ break;
+ }
+
+ nsAutoAnimationMutationBatch::AddObserver(Observer());
+ return;
+ }
+
+ nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::animations);
+
+ NS_ASSERTION(!m->mTarget, "Wrong target!");
+
+ m->mTarget = elem;
+
+ switch (aMutationType) {
+ case eAnimationMutation_Added:
+ m->mAddedAnimations.AppendElement(aAnimation);
+ break;
+ case eAnimationMutation_Changed:
+ m->mChangedAnimations.AppendElement(aAnimation);
+ break;
+ case eAnimationMutation_Removed:
+ m->mRemovedAnimations.AppendElement(aAnimation);
+ break;
+ }
+}
+
+void nsAnimationReceiver::AnimationAdded(Animation* aAnimation) {
+ RecordAnimationMutation(aAnimation, eAnimationMutation_Added);
+}
+
+void nsAnimationReceiver::AnimationChanged(Animation* aAnimation) {
+ RecordAnimationMutation(aAnimation, eAnimationMutation_Changed);
+}
+
+void nsAnimationReceiver::AnimationRemoved(Animation* aAnimation) {
+ RecordAnimationMutation(aAnimation, eAnimationMutation_Removed);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAnimationReceiver, nsMutationReceiver,
+ nsIAnimationObserver)
+
+// Observer
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationObserver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsDOMMutationObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMutationObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ for (int32_t i = 0; i < tmp->mReceivers.Count(); ++i) {
+ tmp->mReceivers[i]->Disconnect(false);
+ }
+ tmp->mReceivers.Clear();
+ tmp->ClearPendingRecords();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+// No need to handle mTransientReceivers
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceivers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstPendingMutation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+ // No need to handle mTransientReceivers
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+nsMutationReceiver* nsDOMMutationObserver::GetReceiverFor(
+ nsINode* aNode, bool aMayCreate, bool aWantsAnimations) {
+ MOZ_ASSERT(aMayCreate || !aWantsAnimations,
+ "the value of aWantsAnimations doesn't matter when aMayCreate is "
+ "false, so just pass in false for it");
+
+ if (!aMayCreate && !aNode->MayHaveDOMMutationObserver()) {
+ return nullptr;
+ }
+
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ if (mReceivers[i]->Target() == aNode) {
+ return mReceivers[i];
+ }
+ }
+ if (!aMayCreate) {
+ return nullptr;
+ }
+
+ nsMutationReceiver* r;
+ if (aWantsAnimations) {
+ r = nsAnimationReceiver::Create(aNode, this);
+ } else {
+ r = nsMutationReceiver::Create(aNode, this);
+ }
+ mReceivers.AppendObject(r);
+ return r;
+}
+
+void nsDOMMutationObserver::RemoveReceiver(nsMutationReceiver* aReceiver) {
+ mReceivers.RemoveObject(aReceiver);
+}
+
+void nsDOMMutationObserver::GetAllSubtreeObserversFor(
+ nsINode* aNode, nsTArray<nsMutationReceiver*>& aReceivers) {
+ nsINode* n = aNode;
+ while (n) {
+ if (n->MayHaveDOMMutationObserver()) {
+ nsMutationReceiver* r = GetReceiverFor(n, false, false);
+ if (r && r->Subtree() && !aReceivers.Contains(r)) {
+ aReceivers.AppendElement(r);
+ // If we've found all the receivers the observer has,
+ // no need to search for more.
+ if (mReceivers.Count() == int32_t(aReceivers.Length())) {
+ return;
+ }
+ }
+ nsCOMArray<nsMutationReceiver>* transientReceivers = nullptr;
+ if (mTransientReceivers.Get(n, &transientReceivers) &&
+ transientReceivers) {
+ for (int32_t i = 0; i < transientReceivers->Count(); ++i) {
+ nsMutationReceiver* r = transientReceivers->ObjectAt(i);
+ nsMutationReceiver* parent = r->GetParent();
+ if (r->Subtree() && parent && !aReceivers.Contains(parent)) {
+ aReceivers.AppendElement(parent);
+ }
+ }
+ if (mReceivers.Count() == int32_t(aReceivers.Length())) {
+ return;
+ }
+ }
+ }
+ n = n->GetParentNode();
+ }
+}
+
+void nsDOMMutationObserver::ScheduleForRun() {
+ nsDOMMutationObserver::AddCurrentlyHandlingObserver(this, sMutationLevel);
+
+ if (mWaitingForRun) {
+ return;
+ }
+ mWaitingForRun = true;
+ RescheduleForRun();
+}
+
+class MutationObserverMicroTask final : public MicroTaskRunnable {
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ virtual void Run(AutoSlowOperation& aAso) override {
+ nsDOMMutationObserver::HandleMutations(aAso);
+ }
+
+ virtual bool Suppressed() override {
+ return nsDOMMutationObserver::AllScheduledMutationObserversAreSuppressed();
+ }
+};
+
+/* static */
+void nsDOMMutationObserver::QueueMutationObserverMicroTask() {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (!ccjs) {
+ return;
+ }
+
+ RefPtr<MutationObserverMicroTask> momt = new MutationObserverMicroTask();
+ ccjs->DispatchToMicroTask(momt.forget());
+}
+
+void nsDOMMutationObserver::HandleMutations(mozilla::AutoSlowOperation& aAso) {
+ if (sScheduledMutationObservers || DocGroup::sPendingDocGroups) {
+ HandleMutationsInternal(aAso);
+ }
+}
+
+void nsDOMMutationObserver::RescheduleForRun() {
+ if (!sScheduledMutationObservers) {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (!ccjs) {
+ return;
+ }
+
+ RefPtr<MutationObserverMicroTask> momt = new MutationObserverMicroTask();
+ ccjs->DispatchToMicroTask(momt.forget());
+ sScheduledMutationObservers =
+ new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
+ }
+
+ bool didInsert = false;
+ for (uint32_t i = 0; i < sScheduledMutationObservers->Length(); ++i) {
+ if (static_cast<nsDOMMutationObserver*>((*sScheduledMutationObservers)[i])
+ ->mId > mId) {
+ sScheduledMutationObservers->InsertElementAt(i, this);
+ didInsert = true;
+ break;
+ }
+ }
+ if (!didInsert) {
+ sScheduledMutationObservers->AppendElement(this);
+ }
+}
+
+void nsDOMMutationObserver::Observe(nsINode& aTarget,
+ const MutationObserverInit& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ bool childList = aOptions.mChildList;
+ bool attributes =
+ aOptions.mAttributes.WasPassed() && aOptions.mAttributes.Value();
+ bool characterData =
+ aOptions.mCharacterData.WasPassed() && aOptions.mCharacterData.Value();
+ bool subtree = aOptions.mSubtree;
+ bool attributeOldValue = aOptions.mAttributeOldValue.WasPassed() &&
+ aOptions.mAttributeOldValue.Value();
+ bool characterDataOldValue = aOptions.mCharacterDataOldValue.WasPassed() &&
+ aOptions.mCharacterDataOldValue.Value();
+ bool animations = aOptions.mAnimations;
+ bool chromeOnlyNodes = aOptions.mChromeOnlyNodes;
+
+ if (!aOptions.mAttributes.WasPassed() &&
+ (aOptions.mAttributeOldValue.WasPassed() ||
+ aOptions.mAttributeFilter.WasPassed())) {
+ attributes = true;
+ }
+
+ if (!aOptions.mCharacterData.WasPassed() &&
+ aOptions.mCharacterDataOldValue.WasPassed()) {
+ characterData = true;
+ }
+
+ if (!(childList || attributes || characterData || animations)) {
+ aRv.ThrowTypeError(
+ "One of 'childList', 'attributes', 'characterData' must not be false.");
+ return;
+ }
+
+ if (aOptions.mAttributeOldValue.WasPassed() &&
+ aOptions.mAttributeOldValue.Value() && !attributes) {
+ aRv.ThrowTypeError(
+ "If 'attributeOldValue' is true, 'attributes' must not be false.");
+ return;
+ }
+
+ if (aOptions.mAttributeFilter.WasPassed() && !attributes) {
+ aRv.ThrowTypeError(
+ "If 'attributesFilter' is present, 'attributes' must not be false.");
+ return;
+ }
+
+ if (aOptions.mCharacterDataOldValue.WasPassed() &&
+ aOptions.mCharacterDataOldValue.Value() && !characterData) {
+ aRv.ThrowTypeError(
+ "If 'characterDataOldValue' is true, 'characterData' must not be "
+ "false.");
+ return;
+ }
+
+ nsTArray<RefPtr<nsAtom>> filters;
+ bool allAttrs = true;
+ if (aOptions.mAttributeFilter.WasPassed()) {
+ allAttrs = false;
+ const Sequence<nsString>& filtersAsString =
+ aOptions.mAttributeFilter.Value();
+ uint32_t len = filtersAsString.Length();
+ filters.SetCapacity(len);
+
+ for (uint32_t i = 0; i < len; ++i) {
+ filters.AppendElement(NS_Atomize(filtersAsString[i]));
+ }
+ }
+
+ nsMutationReceiver* r = GetReceiverFor(&aTarget, true, animations);
+ r->SetChildList(childList);
+ r->SetAttributes(attributes);
+ r->SetCharacterData(characterData);
+ r->SetSubtree(subtree);
+ r->SetAttributeOldValue(attributeOldValue);
+ r->SetCharacterDataOldValue(characterDataOldValue);
+ r->SetAttributeFilter(std::move(filters));
+ r->SetAllAttributes(allAttrs);
+ r->SetAnimations(animations);
+ r->SetChromeOnlyNodes(chromeOnlyNodes);
+ r->RemoveClones();
+
+ if (!aSubjectPrincipal.IsSystemPrincipal() &&
+ !aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
+ if (nsPIDOMWindowInner* window = aTarget.OwnerDoc()->GetInnerWindow()) {
+ window->SetMutationObserverHasObservedNodeForTelemetry();
+ }
+ }
+
+#ifdef DEBUG
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ NS_WARNING_ASSERTION(mReceivers[i]->Target(),
+ "All the receivers should have a target!");
+ }
+#endif
+}
+
+void nsDOMMutationObserver::Disconnect() {
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ mReceivers[i]->Disconnect(false);
+ }
+ mReceivers.Clear();
+ mCurrentMutations.Clear();
+ ClearPendingRecords();
+}
+
+void nsDOMMutationObserver::TakeRecords(
+ nsTArray<RefPtr<nsDOMMutationRecord>>& aRetVal) {
+ aRetVal.Clear();
+ aRetVal.SetCapacity(mPendingMutationCount);
+ RefPtr<nsDOMMutationRecord> current;
+ current.swap(mFirstPendingMutation);
+ for (uint32_t i = 0; i < mPendingMutationCount; ++i) {
+ RefPtr<nsDOMMutationRecord> next;
+ current->mNext.swap(next);
+ if (!mMergeAttributeRecords ||
+ !MergeableAttributeRecord(aRetVal.SafeLastElement(nullptr), current)) {
+ *aRetVal.AppendElement() = std::move(current);
+ }
+ current.swap(next);
+ }
+ ClearPendingRecords();
+}
+
+void nsDOMMutationObserver::GetObservingInfo(
+ nsTArray<Nullable<MutationObservingInfo>>& aResult,
+ mozilla::ErrorResult& aRv) {
+ aResult.SetCapacity(mReceivers.Count());
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ MutationObservingInfo& info = aResult.AppendElement()->SetValue();
+ nsMutationReceiver* mr = mReceivers[i];
+ info.mChildList = mr->ChildList();
+ info.mAttributes.Construct(mr->Attributes());
+ info.mCharacterData.Construct(mr->CharacterData());
+ info.mSubtree = mr->Subtree();
+ info.mAttributeOldValue.Construct(mr->AttributeOldValue());
+ info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue());
+ info.mAnimations = mr->Animations();
+ nsTArray<RefPtr<nsAtom>>& filters = mr->AttributeFilter();
+ if (filters.Length()) {
+ info.mAttributeFilter.Construct();
+ Sequence<nsString>& filtersAsStrings = info.mAttributeFilter.Value();
+ nsString* strings =
+ filtersAsStrings.AppendElements(filters.Length(), mozilla::fallible);
+ if (!strings) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (size_t j = 0; j < filters.Length(); ++j) {
+ filters[j]->ToString(strings[j]);
+ }
+ }
+ info.mObservedNode = mr->Target();
+ }
+}
+
+// static
+already_AddRefed<nsDOMMutationObserver> nsDOMMutationObserver::Constructor(
+ const GlobalObject& aGlobal, dom::MutationCallback& aCb, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ return MakeAndAddRef<nsDOMMutationObserver>(std::move(window), aCb);
+}
+
+bool nsDOMMutationObserver::MergeableAttributeRecord(
+ nsDOMMutationRecord* aOldRecord, nsDOMMutationRecord* aRecord) {
+ MOZ_ASSERT(mMergeAttributeRecords);
+ return aOldRecord && aOldRecord->mType == nsGkAtoms::attributes &&
+ aOldRecord->mType == aRecord->mType &&
+ aOldRecord->mTarget == aRecord->mTarget &&
+ aOldRecord->mAttrName == aRecord->mAttrName &&
+ aOldRecord->mAttrNamespace.Equals(aRecord->mAttrNamespace);
+}
+
+void nsDOMMutationObserver::HandleMutation() {
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!");
+ NS_ASSERTION(mCurrentMutations.IsEmpty(),
+ "Still generating MutationRecords?");
+
+ mWaitingForRun = false;
+
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ mReceivers[i]->RemoveClones();
+ }
+ mTransientReceivers.Clear();
+
+ nsPIDOMWindowOuter* outer = mOwner->GetOuterWindow();
+ if (!mPendingMutationCount || !outer ||
+ outer->GetCurrentInnerWindow() != mOwner) {
+ ClearPendingRecords();
+ return;
+ }
+
+ mozilla::dom::Sequence<mozilla::OwningNonNull<nsDOMMutationRecord>> mutations;
+ if (mutations.SetCapacity(mPendingMutationCount, mozilla::fallible)) {
+ // We can't use TakeRecords easily here, because it deals with a
+ // different type of array, and we want to optimize out any extra copying.
+ RefPtr<nsDOMMutationRecord> current;
+ current.swap(mFirstPendingMutation);
+ for (uint32_t i = 0; i < mPendingMutationCount; ++i) {
+ RefPtr<nsDOMMutationRecord> next;
+ current->mNext.swap(next);
+ if (!mMergeAttributeRecords ||
+ !MergeableAttributeRecord(
+ mutations.Length() ? mutations.LastElement().get() : nullptr,
+ current)) {
+ *mutations.AppendElement(mozilla::fallible) = current;
+ }
+ current.swap(next);
+ }
+ }
+ ClearPendingRecords();
+
+ RefPtr<dom::MutationCallback> callback(mCallback);
+ callback->Call(this, mutations, *this);
+}
+
+void nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) {
+ nsTArray<RefPtr<nsDOMMutationObserver>>* suppressedObservers = nullptr;
+
+ // This loop implements:
+ // * Let signalList be a copy of unit of related similar-origin browsing
+ // contexts' signal slot list.
+ // * Empty unit of related similar-origin browsing contexts' signal slot
+ // list.
+ nsTArray<nsTArray<RefPtr<HTMLSlotElement>>> signalLists;
+ if (DocGroup::sPendingDocGroups) {
+ signalLists.SetCapacity(DocGroup::sPendingDocGroups->Length());
+ for (DocGroup* docGroup : *DocGroup::sPendingDocGroups) {
+ signalLists.AppendElement(docGroup->MoveSignalSlotList());
+ }
+ delete DocGroup::sPendingDocGroups;
+ DocGroup::sPendingDocGroups = nullptr;
+ }
+
+ if (sScheduledMutationObservers) {
+ AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* observers =
+ sScheduledMutationObservers;
+ sScheduledMutationObservers = nullptr;
+ for (uint32_t i = 0; i < observers->Length(); ++i) {
+ RefPtr<nsDOMMutationObserver> currentObserver =
+ static_cast<nsDOMMutationObserver*>((*observers)[i]);
+ if (!currentObserver->Suppressed()) {
+ currentObserver->HandleMutation();
+ } else {
+ if (!suppressedObservers) {
+ suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver>>;
+ }
+ if (!suppressedObservers->Contains(currentObserver)) {
+ suppressedObservers->AppendElement(currentObserver);
+ }
+ }
+ }
+ delete observers;
+ aAso.CheckForInterrupt();
+ }
+
+ if (suppressedObservers) {
+ for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) {
+ static_cast<nsDOMMutationObserver*>(suppressedObservers->ElementAt(i))
+ ->RescheduleForRun();
+ }
+ delete suppressedObservers;
+ suppressedObservers = nullptr;
+ }
+
+ // Fire slotchange event for each slot in signalLists.
+ for (const nsTArray<RefPtr<HTMLSlotElement>>& signalList : signalLists) {
+ for (const RefPtr<HTMLSlotElement>& signal : signalList) {
+ signal->FireSlotChangeEvent();
+ }
+ }
+}
+
+nsDOMMutationRecord* nsDOMMutationObserver::CurrentRecord(nsAtom* aType) {
+ NS_ASSERTION(sMutationLevel > 0, "Unexpected mutation level!");
+
+ while (mCurrentMutations.Length() < sMutationLevel) {
+ mCurrentMutations.AppendElement(static_cast<nsDOMMutationRecord*>(nullptr));
+ }
+
+ uint32_t last = sMutationLevel - 1;
+ if (!mCurrentMutations[last]) {
+ RefPtr<nsDOMMutationRecord> r =
+ new nsDOMMutationRecord(aType, GetParentObject());
+ mCurrentMutations[last] = r;
+ AppendMutationRecord(r.forget());
+ ScheduleForRun();
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(sCurrentlyHandlingObservers->Length() == sMutationLevel);
+ for (size_t i = 0; i < sCurrentlyHandlingObservers->Length(); ++i) {
+ MOZ_ASSERT(sCurrentlyHandlingObservers->ElementAt(i).Contains(this),
+ "MutationObserver should be added as an observer of all the "
+ "nested mutations!");
+ }
+#endif
+
+ NS_ASSERTION(mCurrentMutations[last]->mType == aType,
+ "Unexpected MutationRecord type!");
+
+ return mCurrentMutations[last];
+}
+
+nsDOMMutationObserver::~nsDOMMutationObserver() {
+ for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+ mReceivers[i]->RemoveClones();
+ }
+}
+
+void nsDOMMutationObserver::EnterMutationHandling() { ++sMutationLevel; }
+
+// Leave the current mutation level (there can be several levels if in case
+// of nested calls to the nsIMutationObserver methods).
+// The most recent mutation record is removed from mCurrentMutations, so
+// that is doesn't get modified anymore by receivers.
+void nsDOMMutationObserver::LeaveMutationHandling() {
+ if (sCurrentlyHandlingObservers &&
+ sCurrentlyHandlingObservers->Length() == sMutationLevel) {
+ nsTArray<RefPtr<nsDOMMutationObserver>> obs =
+ sCurrentlyHandlingObservers->PopLastElement();
+ for (uint32_t i = 0; i < obs.Length(); ++i) {
+ nsDOMMutationObserver* o = static_cast<nsDOMMutationObserver*>(obs[i]);
+ if (o->mCurrentMutations.Length() == sMutationLevel) {
+ // It is already in pending mutations.
+ o->mCurrentMutations.RemoveLastElement();
+ }
+ }
+ }
+ --sMutationLevel;
+}
+
+void nsDOMMutationObserver::AddCurrentlyHandlingObserver(
+ nsDOMMutationObserver* aObserver, uint32_t aMutationLevel) {
+ NS_ASSERTION(aMutationLevel > 0, "Unexpected mutation level!");
+
+ if (aMutationLevel > 1) {
+ // MutationObserver must be in the currently handling observer list
+ // in all the nested levels.
+ AddCurrentlyHandlingObserver(aObserver, aMutationLevel - 1);
+ }
+
+ if (!sCurrentlyHandlingObservers) {
+ sCurrentlyHandlingObservers =
+ new AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>;
+ }
+
+ while (sCurrentlyHandlingObservers->Length() < aMutationLevel) {
+ sCurrentlyHandlingObservers->InsertElementAt(
+ sCurrentlyHandlingObservers->Length());
+ }
+
+ uint32_t index = aMutationLevel - 1;
+ if (!sCurrentlyHandlingObservers->ElementAt(index).Contains(aObserver)) {
+ sCurrentlyHandlingObservers->ElementAt(index).AppendElement(aObserver);
+ }
+}
+
+void nsDOMMutationObserver::Shutdown() {
+ delete sCurrentlyHandlingObservers;
+ sCurrentlyHandlingObservers = nullptr;
+ delete sScheduledMutationObservers;
+ sScheduledMutationObservers = nullptr;
+}
+
+nsAutoMutationBatch* nsAutoMutationBatch::sCurrentBatch = nullptr;
+
+void nsAutoMutationBatch::Done() {
+ if (sCurrentBatch != this) {
+ return;
+ }
+
+ sCurrentBatch = mPreviousBatch;
+ if (mObservers.IsEmpty()) {
+ nsDOMMutationObserver::LeaveMutationHandling();
+ // Nothing to do.
+ return;
+ }
+
+ uint32_t len = mObservers.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsDOMMutationObserver* ob = mObservers[i].mObserver;
+ bool wantsChildList = mObservers[i].mWantsChildList;
+
+ RefPtr<nsSimpleContentList> removedList;
+ if (wantsChildList) {
+ removedList = new nsSimpleContentList(mBatchTarget);
+ }
+
+ nsTArray<nsMutationReceiver*> allObservers;
+ ob->GetAllSubtreeObserversFor(mBatchTarget, allObservers);
+
+ int32_t j = mFromFirstToLast ? 0 : mRemovedNodes.Length() - 1;
+ int32_t end = mFromFirstToLast ? mRemovedNodes.Length() : -1;
+ for (; j != end; mFromFirstToLast ? ++j : --j) {
+ nsCOMPtr<nsIContent> removed = mRemovedNodes[j];
+ if (removedList) {
+ removedList->AppendElement(removed);
+ }
+
+ if (allObservers.Length()) {
+ auto* const transientReceivers =
+ ob->mTransientReceivers.GetOrInsertNew(removed);
+ for (uint32_t k = 0; k < allObservers.Length(); ++k) {
+ nsMutationReceiver* r = allObservers[k];
+ nsMutationReceiver* orig = r->GetParent() ? r->GetParent() : r;
+ if (ob->GetReceiverFor(removed, false, false) != orig) {
+ // Make sure the elements which are removed from the
+ // subtree are kept in the same observation set.
+ nsMutationReceiver* tr;
+ if (orig->Animations()) {
+ tr = nsAnimationReceiver::Create(removed, orig);
+ } else {
+ tr = nsMutationReceiver::Create(removed, orig);
+ }
+ transientReceivers->AppendObject(tr);
+ }
+ }
+ }
+ }
+ if (wantsChildList && (mRemovedNodes.Length() || mAddedNodes.Length())) {
+ RefPtr<nsSimpleContentList> addedList =
+ new nsSimpleContentList(mBatchTarget);
+ for (uint32_t i = 0; i < mAddedNodes.Length(); ++i) {
+ addedList->AppendElement(mAddedNodes[i]);
+ }
+ RefPtr<nsDOMMutationRecord> m =
+ new nsDOMMutationRecord(nsGkAtoms::childList, ob->GetParentObject());
+ m->mTarget = mBatchTarget;
+ m->mRemovedNodes = removedList;
+ m->mAddedNodes = addedList;
+ m->mPreviousSibling = mPrevSibling;
+ m->mNextSibling = mNextSibling;
+ ob->AppendMutationRecord(m.forget());
+ }
+ // Always schedule the observer so that transient receivers are
+ // removed correctly.
+ ob->ScheduleForRun();
+ }
+ nsDOMMutationObserver::LeaveMutationHandling();
+}
+
+nsAutoAnimationMutationBatch* nsAutoAnimationMutationBatch::sCurrentBatch =
+ nullptr;
+
+void nsAutoAnimationMutationBatch::Done() {
+ if (sCurrentBatch != this) {
+ return;
+ }
+
+ sCurrentBatch = nullptr;
+ if (mObservers.IsEmpty()) {
+ nsDOMMutationObserver::LeaveMutationHandling();
+ // Nothing to do.
+ return;
+ }
+
+ mBatchTargets.Sort(TreeOrderComparator());
+
+ for (nsDOMMutationObserver* ob : mObservers) {
+ bool didAddRecords = false;
+
+ for (nsINode* target : mBatchTargets) {
+ EntryArray* entries = mEntryTable.Get(target);
+ MOZ_ASSERT(entries,
+ "Targets in entry table and targets list should match");
+
+ RefPtr<nsDOMMutationRecord> m =
+ new nsDOMMutationRecord(nsGkAtoms::animations, ob->GetParentObject());
+ m->mTarget = target;
+
+ for (const Entry& e : *entries) {
+ if (e.mState == eState_Added) {
+ m->mAddedAnimations.AppendElement(e.mAnimation);
+ } else if (e.mState == eState_Removed) {
+ m->mRemovedAnimations.AppendElement(e.mAnimation);
+ } else if (e.mState == eState_RemainedPresent && e.mChanged) {
+ m->mChangedAnimations.AppendElement(e.mAnimation);
+ }
+ }
+
+ if (!m->mAddedAnimations.IsEmpty() || !m->mChangedAnimations.IsEmpty() ||
+ !m->mRemovedAnimations.IsEmpty()) {
+ ob->AppendMutationRecord(m.forget());
+ didAddRecords = true;
+ }
+ }
+
+ if (didAddRecords) {
+ ob->ScheduleForRun();
+ }
+ }
+ nsDOMMutationObserver::LeaveMutationHandling();
+}
diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h
new file mode 100644
index 0000000000..3426cf4689
--- /dev/null
+++ b/dom/base/nsDOMMutationObserver.h
@@ -0,0 +1,869 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMMutationObserver_h
+#define nsDOMMutationObserver_h
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/MutationObserverBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsCOMArray.h"
+#include "nsClassHashtable.h"
+#include "nsContentList.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGlobalWindow.h"
+#include "nsIAnimationObserver.h"
+#include "nsIScriptContext.h"
+#include "nsIVariant.h"
+#include "nsPIDOMWindow.h"
+#include "nsStubAnimationObserver.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsIPrincipal;
+
+class nsDOMMutationObserver;
+using mozilla::dom::MutationObservingInfo;
+
+namespace mozilla::dom {
+class Element;
+}
+
+class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache {
+ virtual ~nsDOMMutationRecord() = default;
+
+ public:
+ using AnimationArray = nsTArray<RefPtr<mozilla::dom::Animation>>;
+
+ nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner)
+ : mType(aType),
+ mAttrNamespace(VoidString()),
+ mPrevValue(VoidString()),
+ mOwner(aOwner) {}
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return mozilla::dom::MutationRecord_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMMutationRecord)
+
+ void GetType(mozilla::dom::DOMString& aRetVal) const {
+ aRetVal.SetKnownLiveAtom(mType, mozilla::dom::DOMString::eNullNotExpected);
+ }
+
+ nsINode* GetTarget() const { return mTarget; }
+
+ nsINodeList* AddedNodes();
+
+ nsINodeList* RemovedNodes();
+
+ nsINode* GetPreviousSibling() const { return mPreviousSibling; }
+
+ nsINode* GetNextSibling() const { return mNextSibling; }
+
+ void GetAttributeName(mozilla::dom::DOMString& aRetVal) const {
+ aRetVal.SetKnownLiveAtom(mAttrName,
+ mozilla::dom::DOMString::eTreatNullAsNull);
+ }
+
+ void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const {
+ aRetVal.SetKnownLiveString(mAttrNamespace);
+ }
+
+ void GetOldValue(mozilla::dom::DOMString& aRetVal) const {
+ aRetVal.SetKnownLiveString(mPrevValue);
+ }
+
+ void GetAddedAnimations(AnimationArray& aRetVal) const {
+ aRetVal = mAddedAnimations.Clone();
+ }
+
+ void GetRemovedAnimations(AnimationArray& aRetVal) const {
+ aRetVal = mRemovedAnimations.Clone();
+ }
+
+ void GetChangedAnimations(AnimationArray& aRetVal) const {
+ aRetVal = mChangedAnimations.Clone();
+ }
+
+ nsCOMPtr<nsINode> mTarget;
+ RefPtr<nsAtom> mType;
+ RefPtr<nsAtom> mAttrName;
+ nsString mAttrNamespace;
+ nsString mPrevValue;
+ RefPtr<nsSimpleContentList> mAddedNodes;
+ RefPtr<nsSimpleContentList> mRemovedNodes;
+ nsCOMPtr<nsINode> mPreviousSibling;
+ nsCOMPtr<nsINode> mNextSibling;
+ AnimationArray mAddedAnimations;
+ AnimationArray mRemovedAnimations;
+ AnimationArray mChangedAnimations;
+
+ RefPtr<nsDOMMutationRecord> mNext;
+ nsCOMPtr<nsISupports> mOwner;
+};
+
+// Base class just prevents direct access to
+// members to make sure we go through getters/setters.
+class nsMutationReceiverBase : public nsStubAnimationObserver {
+ public:
+ virtual ~nsMutationReceiverBase() = default;
+
+ nsDOMMutationObserver* Observer();
+ nsINode* Target() { return mParent ? mParent->Target() : mTarget; }
+ nsINode* RegisterTarget() { return mRegisterTarget; }
+
+ bool Subtree() { return mParent ? mParent->Subtree() : mSubtree; }
+ void SetSubtree(bool aSubtree) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mSubtree = aSubtree;
+ }
+
+ bool ChildList() { return mParent ? mParent->ChildList() : mChildList; }
+ void SetChildList(bool aChildList) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mChildList = aChildList;
+ }
+
+ bool CharacterData() {
+ return mParent ? mParent->CharacterData() : mCharacterData;
+ }
+ void SetCharacterData(bool aCharacterData) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mCharacterData = aCharacterData;
+ }
+
+ bool CharacterDataOldValue() const {
+ return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue;
+ }
+ void SetCharacterDataOldValue(bool aOldValue) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mCharacterDataOldValue = aOldValue;
+ }
+
+ bool Attributes() const {
+ return mParent ? mParent->Attributes() : mAttributes;
+ }
+ void SetAttributes(bool aAttributes) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributes = aAttributes;
+ }
+
+ bool AllAttributes() const {
+ return mParent ? mParent->AllAttributes() : mAllAttributes;
+ }
+ void SetAllAttributes(bool aAll) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAllAttributes = aAll;
+ }
+
+ bool Animations() const {
+ return mParent ? mParent->Animations() : mAnimations;
+ }
+ void SetAnimations(bool aAnimations) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAnimations = aAnimations;
+ }
+
+ bool AttributeOldValue() const {
+ return mParent ? mParent->AttributeOldValue() : mAttributeOldValue;
+ }
+ void SetAttributeOldValue(bool aOldValue) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributeOldValue = aOldValue;
+ }
+
+ bool ChromeOnlyNodes() const {
+ return mParent ? mParent->ChromeOnlyNodes() : mChromeOnlyNodes;
+ }
+
+ void SetChromeOnlyNodes(bool aChromeOnlyNodes) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mChromeOnlyNodes = aChromeOnlyNodes;
+ }
+
+ nsTArray<RefPtr<nsAtom>>& AttributeFilter() { return mAttributeFilter; }
+ void SetAttributeFilter(nsTArray<RefPtr<nsAtom>>&& aFilter) {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributeFilter.Clear();
+ mAttributeFilter = std::move(aFilter);
+ }
+
+ void AddClone(nsMutationReceiverBase* aClone) {
+ mTransientReceivers.AppendObject(aClone);
+ }
+
+ void RemoveClone(nsMutationReceiverBase* aClone) {
+ mTransientReceivers.RemoveObject(aClone);
+ }
+
+ protected:
+ nsMutationReceiverBase(nsINode* aTarget, nsDOMMutationObserver* aObserver)
+ : mTarget(aTarget),
+ mObserver(aObserver),
+ mRegisterTarget(aTarget),
+ mSubtree(false),
+ mChildList(false),
+ mCharacterData(false),
+ mCharacterDataOldValue(false),
+ mAttributes(false),
+ mAllAttributes(false),
+ mAttributeOldValue(false),
+ mAnimations(false) {}
+
+ nsMutationReceiverBase(nsINode* aRegisterTarget,
+ nsMutationReceiverBase* aParent)
+ : mTarget(nullptr),
+ mObserver(nullptr),
+ mParent(aParent),
+ mRegisterTarget(aRegisterTarget),
+ mKungFuDeathGrip(aParent->Target()),
+ mSubtree(false),
+ mChildList(false),
+ mCharacterData(false),
+ mCharacterDataOldValue(false),
+ mAttributes(false),
+ mAllAttributes(false),
+ mAttributeOldValue(false),
+ mAnimations(false),
+ mChromeOnlyNodes(false) {
+ NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!");
+ }
+
+ virtual void AddMutationObserver() = 0;
+
+ void AddObserver() {
+ AddMutationObserver();
+ mRegisterTarget->SetMayHaveDOMMutationObserver();
+ mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers();
+ }
+
+ bool IsObservable(nsIContent* aContent);
+
+ bool ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttr);
+
+ // The target for the MutationObserver.observe() method.
+ nsINode* mTarget;
+ nsDOMMutationObserver* mObserver;
+ RefPtr<nsMutationReceiverBase> mParent; // Cleared after microtask.
+ // The node to which Gecko-internal nsIMutationObserver was registered to.
+ // This is different than mTarget when dealing with transient observers.
+ nsINode* mRegisterTarget;
+ nsCOMArray<nsMutationReceiverBase> mTransientReceivers;
+ // While we have transient receivers, keep the original mutation receiver
+ // alive so it doesn't go away and disconnect all its transient receivers.
+ nsCOMPtr<nsINode> mKungFuDeathGrip;
+
+ private:
+ nsTArray<RefPtr<nsAtom>> mAttributeFilter;
+ bool mSubtree : 1;
+ bool mChildList : 1;
+ bool mCharacterData : 1;
+ bool mCharacterDataOldValue : 1;
+ bool mAttributes : 1;
+ bool mAllAttributes : 1;
+ bool mAttributeOldValue : 1;
+ bool mAnimations : 1;
+ bool mChromeOnlyNodes : 1;
+};
+
+class nsMutationReceiver : public nsMutationReceiverBase {
+ protected:
+ virtual ~nsMutationReceiver() { Disconnect(false); }
+
+ public:
+ static nsMutationReceiver* Create(nsINode* aTarget,
+ nsDOMMutationObserver* aObserver) {
+ nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver);
+ r->AddObserver();
+ return r;
+ }
+
+ static nsMutationReceiver* Create(nsINode* aRegisterTarget,
+ nsMutationReceiverBase* aParent) {
+ nsMutationReceiver* r = new nsMutationReceiver(aRegisterTarget, aParent);
+ aParent->AddClone(r);
+ r->AddObserver();
+ return r;
+ }
+
+ nsMutationReceiver* GetParent() {
+ return static_cast<nsMutationReceiver*>(mParent.get());
+ }
+
+ void RemoveClones() {
+ for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) {
+ nsMutationReceiver* r =
+ static_cast<nsMutationReceiver*>(mTransientReceivers[i]);
+ r->DisconnectTransientReceiver();
+ }
+ mTransientReceivers.Clear();
+ }
+
+ void DisconnectTransientReceiver() {
+ if (mRegisterTarget) {
+ mRegisterTarget->RemoveMutationObserver(this);
+ mRegisterTarget = nullptr;
+ }
+
+ mParent = nullptr;
+ NS_ASSERTION(!mTarget, "Should not have mTarget");
+ NS_ASSERTION(!mObserver, "Should not have mObserver");
+ }
+
+ void Disconnect(bool aRemoveFromObserver);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ virtual void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) override {
+ // We can reuse AttributeWillChange implementation.
+ AttributeWillChange(aElement, aNameSpaceID, aAttribute,
+ mozilla::dom::MutationEvent_Binding::MODIFICATION);
+ }
+
+ protected:
+ nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver);
+
+ nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
+ : nsMutationReceiverBase(aRegisterTarget, aParent) {
+ NS_ASSERTION(!static_cast<nsMutationReceiver*>(aParent)->GetParent(),
+ "Shouldn't create deep observer hierarchies!");
+ }
+
+ virtual void AddMutationObserver() override {
+ mRegisterTarget->AddMutationObserver(this);
+ }
+};
+
+class nsAnimationReceiver : public nsMutationReceiver {
+ public:
+ static nsAnimationReceiver* Create(nsINode* aTarget,
+ nsDOMMutationObserver* aObserver) {
+ nsAnimationReceiver* r = new nsAnimationReceiver(aTarget, aObserver);
+ r->AddObserver();
+ return r;
+ }
+
+ static nsAnimationReceiver* Create(nsINode* aRegisterTarget,
+ nsMutationReceiverBase* aParent) {
+ nsAnimationReceiver* r = new nsAnimationReceiver(aRegisterTarget, aParent);
+ aParent->AddClone(r);
+ r->AddObserver();
+ return r;
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
+
+ protected:
+ virtual ~nsAnimationReceiver() = default;
+
+ nsAnimationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver)
+ : nsMutationReceiver(aTarget, aObserver) {}
+
+ nsAnimationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
+ : nsMutationReceiver(aRegisterTarget, aParent) {}
+
+ virtual void AddMutationObserver() override {
+ mRegisterTarget->AddAnimationObserver(this);
+ }
+
+ private:
+ enum AnimationMutation {
+ eAnimationMutation_Added,
+ eAnimationMutation_Changed,
+ eAnimationMutation_Removed
+ };
+
+ void RecordAnimationMutation(mozilla::dom::Animation* aAnimation,
+ AnimationMutation aMutationType);
+};
+
+#define NS_DOM_MUTATION_OBSERVER_IID \
+ { \
+ 0x0c3b91f8, 0xcc3b, 0x4b08, { \
+ 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 \
+ } \
+ }
+
+class nsDOMMutationObserver final : public nsISupports, public nsWrapperCache {
+ public:
+ nsDOMMutationObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner,
+ mozilla::dom::MutationCallback& aCb)
+ : mOwner(std::move(aOwner)),
+ mLastPendingMutation(nullptr),
+ mPendingMutationCount(0),
+ mCallback(&aCb),
+ mWaitingForRun(false),
+ mMergeAttributeRecords(false),
+ mId(++sCount) {}
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID)
+
+ static already_AddRefed<nsDOMMutationObserver> Constructor(
+ const mozilla::dom::GlobalObject&, mozilla::dom::MutationCallback&,
+ mozilla::ErrorResult&);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return mozilla::dom::MutationObserver_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ void Observe(nsINode& aTarget,
+ const mozilla::dom::MutationObserverInit& aOptions,
+ nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv);
+
+ void Disconnect();
+
+ void TakeRecords(nsTArray<RefPtr<nsDOMMutationRecord>>& aRetVal);
+
+ MOZ_CAN_RUN_SCRIPT void HandleMutation();
+
+ void GetObservingInfo(
+ nsTArray<mozilla::dom::Nullable<MutationObservingInfo>>& aResult,
+ mozilla::ErrorResult& aRv);
+
+ mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
+
+ bool MergeAttributeRecords() { return mMergeAttributeRecords; }
+
+ void SetMergeAttributeRecords(bool aVal) { mMergeAttributeRecords = aVal; }
+
+ // If both records are for 'attributes' type and for the same target and
+ // attribute name and namespace are the same, we can skip the newer record.
+ // aOldRecord->mPrevValue holds the original value, if observed.
+ bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
+ nsDOMMutationRecord* aRecord);
+
+ void AppendMutationRecord(already_AddRefed<nsDOMMutationRecord> aRecord) {
+ RefPtr<nsDOMMutationRecord> record = aRecord;
+ MOZ_ASSERT(record);
+ if (!mLastPendingMutation) {
+ MOZ_ASSERT(!mFirstPendingMutation);
+ mFirstPendingMutation = std::move(record);
+ mLastPendingMutation = mFirstPendingMutation;
+ } else {
+ MOZ_ASSERT(mFirstPendingMutation);
+ mLastPendingMutation->mNext = std::move(record);
+ mLastPendingMutation = mLastPendingMutation->mNext;
+ }
+ ++mPendingMutationCount;
+ }
+
+ void ClearPendingRecords() {
+ // Break down the pending mutation record list so that cycle collector
+ // can delete the objects sooner.
+ RefPtr<nsDOMMutationRecord> current = std::move(mFirstPendingMutation);
+ mLastPendingMutation = nullptr;
+ mPendingMutationCount = 0;
+ while (current) {
+ current = std::move(current->mNext);
+ }
+ }
+
+ // static methods
+ static void QueueMutationObserverMicroTask();
+
+ MOZ_CAN_RUN_SCRIPT
+ static void HandleMutations(mozilla::AutoSlowOperation& aAso);
+
+ static bool AllScheduledMutationObserversAreSuppressed() {
+ if (sScheduledMutationObservers) {
+ uint32_t len = sScheduledMutationObservers->Length();
+ if (len > 0) {
+ for (uint32_t i = 0; i < len; ++i) {
+ if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void EnterMutationHandling();
+ static void LeaveMutationHandling();
+
+ static void Shutdown();
+
+ protected:
+ virtual ~nsDOMMutationObserver();
+
+ friend class nsMutationReceiver;
+ friend class nsAnimationReceiver;
+ friend class nsAutoMutationBatch;
+ friend class nsAutoAnimationMutationBatch;
+ nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate,
+ bool aWantsAnimations);
+ void RemoveReceiver(nsMutationReceiver* aReceiver);
+
+ void GetAllSubtreeObserversFor(nsINode* aNode,
+ nsTArray<nsMutationReceiver*>& aObservers);
+ void ScheduleForRun();
+ void RescheduleForRun();
+
+ nsDOMMutationRecord* CurrentRecord(nsAtom* aType);
+ bool HasCurrentRecord(const nsAString& aType);
+
+ bool Suppressed() {
+ return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation();
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
+
+ static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
+ uint32_t aMutationLevel);
+
+ nsCOMPtr<nsPIDOMWindowInner> mOwner;
+
+ nsCOMArray<nsMutationReceiver> mReceivers;
+ nsClassHashtable<nsISupportsHashKey, nsCOMArray<nsMutationReceiver>>
+ mTransientReceivers;
+ // MutationRecords which are being constructed.
+ AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
+ // MutationRecords which will be handed to the callback at the end of
+ // the microtask.
+ RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
+ nsDOMMutationRecord* mLastPendingMutation;
+ uint32_t mPendingMutationCount;
+
+ RefPtr<mozilla::dom::MutationCallback> mCallback;
+
+ bool mWaitingForRun;
+ bool mMergeAttributeRecords;
+
+ uint64_t mId;
+
+ static uint64_t sCount;
+ static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
+ sScheduledMutationObservers;
+
+ static uint32_t sMutationLevel;
+ static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
+ sCurrentlyHandlingObservers;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver,
+ NS_DOM_MUTATION_OBSERVER_IID)
+
+class nsAutoMutationBatch {
+ public:
+ nsAutoMutationBatch()
+ : mPreviousBatch(nullptr),
+ mBatchTarget(nullptr),
+ mRemovalDone(false),
+ mFromFirstToLast(false),
+ mAllowNestedBatches(false) {}
+
+ nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast,
+ bool aAllowNestedBatches)
+ : mPreviousBatch(nullptr),
+ mBatchTarget(nullptr),
+ mRemovalDone(false),
+ mFromFirstToLast(false),
+ mAllowNestedBatches(false) {
+ Init(aTarget, aFromFirstToLast, aAllowNestedBatches);
+ }
+
+ void Init(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) {
+ if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) {
+ if (sCurrentBatch && !sCurrentBatch->mAllowNestedBatches) {
+ return;
+ }
+ mBatchTarget = aTarget;
+ mFromFirstToLast = aFromFirstToLast;
+ mAllowNestedBatches = aAllowNestedBatches;
+ mPreviousBatch = sCurrentBatch;
+ sCurrentBatch = this;
+ nsDOMMutationObserver::EnterMutationHandling();
+ }
+ }
+
+ void RemovalDone() { mRemovalDone = true; }
+ static bool IsRemovalDone() { return sCurrentBatch->mRemovalDone; }
+
+ void SetPrevSibling(nsINode* aNode) { mPrevSibling = aNode; }
+ void SetNextSibling(nsINode* aNode) { mNextSibling = aNode; }
+
+ void Done();
+
+ ~nsAutoMutationBatch() { NodesAdded(); }
+
+ static bool IsBatching() { return !!sCurrentBatch; }
+
+ static nsAutoMutationBatch* GetCurrentBatch() { return sCurrentBatch; }
+
+ static void UpdateObserver(nsDOMMutationObserver* aObserver,
+ bool aWantsChildList) {
+ uint32_t l = sCurrentBatch->mObservers.Length();
+ for (uint32_t i = 0; i < l; ++i) {
+ if (sCurrentBatch->mObservers[i].mObserver == aObserver) {
+ if (aWantsChildList) {
+ sCurrentBatch->mObservers[i].mWantsChildList = aWantsChildList;
+ }
+ return;
+ }
+ }
+ BatchObserver* bo = sCurrentBatch->mObservers.AppendElement();
+ bo->mObserver = aObserver;
+ bo->mWantsChildList = aWantsChildList;
+ }
+
+ static nsINode* GetBatchTarget() { return sCurrentBatch->mBatchTarget; }
+
+ // Mutation receivers notify the batch about removed child nodes.
+ static void NodeRemoved(nsIContent* aChild) {
+ if (IsBatching() && !sCurrentBatch->mRemovalDone) {
+ uint32_t len = sCurrentBatch->mRemovedNodes.Length();
+ if (!len || sCurrentBatch->mRemovedNodes[len - 1] != aChild) {
+ sCurrentBatch->mRemovedNodes.AppendElement(aChild);
+ }
+ }
+ }
+
+ // Called after new child nodes have been added to the batch target.
+ void NodesAdded() {
+ if (sCurrentBatch != this) {
+ return;
+ }
+
+ nsIContent* c = mPrevSibling ? mPrevSibling->GetNextSibling()
+ : mBatchTarget->GetFirstChild();
+ for (; c != mNextSibling; c = c->GetNextSibling()) {
+ mAddedNodes.AppendElement(c);
+ }
+ Done();
+ }
+
+ private:
+ struct BatchObserver {
+ nsDOMMutationObserver* mObserver;
+ bool mWantsChildList;
+ };
+
+ static nsAutoMutationBatch* sCurrentBatch;
+ nsAutoMutationBatch* mPreviousBatch;
+ AutoTArray<BatchObserver, 2> mObservers;
+ nsTArray<nsCOMPtr<nsIContent>> mRemovedNodes;
+ nsTArray<nsCOMPtr<nsIContent>> mAddedNodes;
+ nsINode* mBatchTarget;
+ bool mRemovalDone;
+ bool mFromFirstToLast;
+ bool mAllowNestedBatches;
+ nsCOMPtr<nsINode> mPrevSibling;
+ nsCOMPtr<nsINode> mNextSibling;
+};
+
+class nsAutoAnimationMutationBatch {
+ struct Entry;
+
+ public:
+ explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) {
+ Init(aDocument);
+ }
+
+ void Init(mozilla::dom::Document* aDocument) {
+ if (!aDocument || !aDocument->MayHaveDOMMutationObservers() ||
+ sCurrentBatch) {
+ return;
+ }
+
+ sCurrentBatch = this;
+ nsDOMMutationObserver::EnterMutationHandling();
+ }
+
+ ~nsAutoAnimationMutationBatch() { Done(); }
+
+ void Done();
+
+ static bool IsBatching() { return !!sCurrentBatch; }
+
+ static nsAutoAnimationMutationBatch* GetCurrentBatch() {
+ return sCurrentBatch;
+ }
+
+ static void AddObserver(nsDOMMutationObserver* aObserver) {
+ if (sCurrentBatch->mObservers.Contains(aObserver)) {
+ return;
+ }
+ sCurrentBatch->mObservers.AppendElement(aObserver);
+ }
+
+ static void AnimationAdded(mozilla::dom::Animation* aAnimation,
+ nsINode* aTarget) {
+ if (!IsBatching()) {
+ return;
+ }
+
+ Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
+ if (entry) {
+ switch (entry->mState) {
+ case eState_RemainedAbsent:
+ entry->mState = eState_Added;
+ break;
+ case eState_Removed:
+ entry->mState = eState_RemainedPresent;
+ break;
+ case eState_Added:
+ // FIXME bug 1189015
+ NS_ERROR("shouldn't have observed an animation being added twice");
+ break;
+ case eState_RemainedPresent:
+ MOZ_ASSERT_UNREACHABLE(
+ "shouldn't have observed an animation "
+ "remaining present");
+ break;
+ }
+ } else {
+ entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
+ entry->mState = eState_Added;
+ entry->mChanged = false;
+ }
+ }
+
+ static void AnimationChanged(mozilla::dom::Animation* aAnimation,
+ nsINode* aTarget) {
+ Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
+ if (entry) {
+ NS_ASSERTION(entry->mState == eState_RemainedPresent ||
+ entry->mState == eState_Added,
+ "shouldn't have observed an animation being changed after "
+ "being removed");
+ entry->mChanged = true;
+ } else {
+ entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
+ entry->mState = eState_RemainedPresent;
+ entry->mChanged = true;
+ }
+ }
+
+ static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
+ nsINode* aTarget) {
+ Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
+ if (entry) {
+ switch (entry->mState) {
+ case eState_RemainedPresent:
+ entry->mState = eState_Removed;
+ break;
+ case eState_Added:
+ entry->mState = eState_RemainedAbsent;
+ break;
+ case eState_RemainedAbsent:
+ MOZ_ASSERT_UNREACHABLE(
+ "shouldn't have observed an animation "
+ "remaining absent");
+ break;
+ case eState_Removed:
+ // FIXME bug 1189015
+ NS_ERROR("shouldn't have observed an animation being removed twice");
+ break;
+ }
+ } else {
+ entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
+ entry->mState = eState_Removed;
+ entry->mChanged = false;
+ }
+ }
+
+ private:
+ Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
+ EntryArray* entries = mEntryTable.Get(aTarget);
+ if (!entries) {
+ return nullptr;
+ }
+
+ for (Entry& e : *entries) {
+ if (e.mAnimation == aAnimation) {
+ return &e;
+ }
+ }
+ return nullptr;
+ }
+
+ Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
+ EntryArray* entries = sCurrentBatch->mEntryTable.GetOrInsertNew(aTarget);
+ if (entries->IsEmpty()) {
+ sCurrentBatch->mBatchTargets.AppendElement(aTarget);
+ }
+ Entry* entry = entries->AppendElement();
+ entry->mAnimation = aAnimation;
+ return entry;
+ }
+
+ enum State {
+ eState_RemainedPresent,
+ eState_RemainedAbsent,
+ eState_Added,
+ eState_Removed
+ };
+
+ struct Entry {
+ RefPtr<mozilla::dom::Animation> mAnimation;
+ State mState;
+ bool mChanged;
+ };
+
+ static nsAutoAnimationMutationBatch* sCurrentBatch;
+ AutoTArray<nsDOMMutationObserver*, 2> mObservers;
+ using EntryArray = nsTArray<Entry>;
+ nsClassHashtable<nsPtrHashKey<nsINode>, EntryArray> mEntryTable;
+ // List of nodes referred to by mEntryTable so we can sort them
+ // For a specific pseudo element, we use its parent element as the
+ // batch target, so they will be put in the same EntryArray.
+ nsTArray<nsINode*> mBatchTargets;
+};
+
+inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() {
+ return mParent ? mParent->Observer()
+ : static_cast<nsDOMMutationObserver*>(mObserver);
+}
+
+class MOZ_RAII nsDOMMutationEnterLeave {
+ public:
+ explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc)
+ : mNeeded(aDoc->MayHaveDOMMutationObservers()) {
+ if (mNeeded) {
+ nsDOMMutationObserver::EnterMutationHandling();
+ }
+ }
+ ~nsDOMMutationEnterLeave() {
+ if (mNeeded) {
+ nsDOMMutationObserver::LeaveMutationHandling();
+ }
+ }
+
+ private:
+ const bool mNeeded;
+};
+
+#endif
diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp
new file mode 100644
index 0000000000..1db54e1ed7
--- /dev/null
+++ b/dom/base/nsDOMNavigationTiming.cpp
@@ -0,0 +1,659 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMNavigationTiming.h"
+
+#include "GeckoProfiler.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/PerformanceNavigation.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsHttp.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+
+LazyLogModule gPageLoadLog("PageLoad");
+#define PAGELOAD_LOG(args) MOZ_LOG(gPageLoadLog, LogLevel::Debug, args)
+#define PAGELOAD_LOG_ENABLED() MOZ_LOG_TEST(gPageLoadLog, LogLevel::Error)
+
+} // namespace mozilla
+
+nsDOMNavigationTiming::nsDOMNavigationTiming(nsDocShell* aDocShell) {
+ Clear();
+
+ mDocShell = aDocShell;
+}
+
+nsDOMNavigationTiming::~nsDOMNavigationTiming() = default;
+
+void nsDOMNavigationTiming::Clear() {
+ mNavigationType = TYPE_RESERVED;
+ mNavigationStartHighRes = 0;
+
+ mBeforeUnloadStart = TimeStamp();
+ mUnloadStart = TimeStamp();
+ mUnloadEnd = TimeStamp();
+ mLoadEventStart = TimeStamp();
+ mLoadEventEnd = TimeStamp();
+ mDOMLoading = TimeStamp();
+ mDOMInteractive = TimeStamp();
+ mDOMContentLoadedEventStart = TimeStamp();
+ mDOMContentLoadedEventEnd = TimeStamp();
+ mDOMComplete = TimeStamp();
+ mContentfulComposite = TimeStamp();
+ mNonBlankPaint = TimeStamp();
+
+ mDocShellHasBeenActiveSinceNavigationStart = false;
+}
+
+void nsDOMNavigationTiming::Anonymize(nsIURI* aFinalURI) {
+ mLoadedURI = aFinalURI;
+ mUnloadedURI = nullptr;
+ mBeforeUnloadStart = TimeStamp();
+ mUnloadStart = TimeStamp();
+ mUnloadEnd = TimeStamp();
+}
+
+DOMTimeMilliSec nsDOMNavigationTiming::TimeStampToDOM(TimeStamp aStamp) const {
+ if (aStamp.IsNull()) {
+ return 0;
+ }
+
+ TimeDuration duration = aStamp - mNavigationStart;
+ return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds());
+}
+
+void nsDOMNavigationTiming::NotifyNavigationStart(
+ DocShellState aDocShellState) {
+ mNavigationStartHighRes = (double)PR_Now() / PR_USEC_PER_MSEC;
+ mNavigationStart = TimeStamp::Now();
+ mDocShellHasBeenActiveSinceNavigationStart =
+ (aDocShellState == DocShellState::eActive);
+ PROFILER_MARKER_UNTYPED("Navigation::Start", DOM,
+ MarkerInnerWindowIdFromDocShell(mDocShell));
+}
+
+void nsDOMNavigationTiming::NotifyFetchStart(nsIURI* aURI,
+ Type aNavigationType) {
+ mNavigationType = aNavigationType;
+ // At the unload event time we don't really know the loading uri.
+ // Need it for later check for unload timing access.
+ mLoadedURI = aURI;
+}
+
+void nsDOMNavigationTiming::NotifyRestoreStart() {
+ mNavigationType = TYPE_BACK_FORWARD;
+}
+
+void nsDOMNavigationTiming::NotifyBeforeUnload() {
+ mBeforeUnloadStart = TimeStamp::Now();
+}
+
+void nsDOMNavigationTiming::NotifyUnloadAccepted(nsIURI* aOldURI) {
+ mUnloadStart = mBeforeUnloadStart;
+ mUnloadedURI = aOldURI;
+}
+
+void nsDOMNavigationTiming::NotifyUnloadEventStart() {
+ mUnloadStart = TimeStamp::Now();
+ PROFILER_MARKER("Unload", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalStart(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+}
+
+void nsDOMNavigationTiming::NotifyUnloadEventEnd() {
+ mUnloadEnd = TimeStamp::Now();
+ PROFILER_MARKER("Unload", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalEnd(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+}
+
+void nsDOMNavigationTiming::NotifyLoadEventStart() {
+ if (!mLoadEventStart.IsNull()) {
+ return;
+ }
+ mLoadEventStart = TimeStamp::Now();
+
+ PROFILER_MARKER("Load", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalStart(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+
+ if (IsTopLevelContentDocumentInContentProcess()) {
+ TimeStamp now = TimeStamp::Now();
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_START_MS,
+ mNavigationStart, now);
+
+ if (mDocShellHasBeenActiveSinceNavigationStart) {
+ if (net::nsHttp::IsBeforeLastActiveTabLoadOptimization(
+ mNavigationStart)) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_LOAD_EVENT_START_ACTIVE_NETOPT_MS,
+ mNavigationStart, now);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_LOAD_EVENT_START_ACTIVE_MS, mNavigationStart,
+ now);
+ }
+ }
+ }
+}
+
+void nsDOMNavigationTiming::NotifyLoadEventEnd() {
+ if (!mLoadEventEnd.IsNull()) {
+ return;
+ }
+ mLoadEventEnd = TimeStamp::Now();
+
+ PROFILER_MARKER("Load", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalEnd(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+
+ if (IsTopLevelContentDocumentInContentProcess()) {
+ if (profiler_thread_is_being_profiled_for_markers() ||
+ PAGELOAD_LOG_ENABLED()) {
+ TimeDuration elapsed = mLoadEventEnd - mNavigationStart;
+ TimeDuration duration = mLoadEventEnd - mLoadEventStart;
+ nsPrintfCString marker(
+ "Document %s loaded after %dms, load event duration %dms",
+ nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get(),
+ int(elapsed.ToMilliseconds()), int(duration.ToMilliseconds()));
+ PAGELOAD_LOG(("%s", marker.get()));
+ PROFILER_MARKER_TEXT(
+ "DocumentLoad", DOM,
+ MarkerOptions(MarkerTiming::Interval(mNavigationStart, mLoadEventEnd),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ marker);
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_END_MS,
+ mNavigationStart);
+ }
+}
+
+void nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI,
+ TimeStamp aValue) {
+ if (!mDOMLoading.IsNull()) {
+ return;
+ }
+ mLoadedURI = aURI;
+ mDOMLoading = aValue;
+}
+
+void nsDOMNavigationTiming::NotifyDOMLoading(nsIURI* aURI) {
+ if (!mDOMLoading.IsNull()) {
+ return;
+ }
+ mLoadedURI = aURI;
+ mDOMLoading = TimeStamp::Now();
+
+ PROFILER_MARKER_UNTYPED("Navigation::DOMLoading", DOM,
+ MarkerInnerWindowIdFromDocShell(mDocShell));
+}
+
+void nsDOMNavigationTiming::NotifyDOMInteractive(nsIURI* aURI) {
+ if (!mDOMInteractive.IsNull()) {
+ return;
+ }
+ mLoadedURI = aURI;
+ mDOMInteractive = TimeStamp::Now();
+
+ PROFILER_MARKER_UNTYPED("Navigation::DOMInteractive", DOM,
+ MarkerInnerWindowIdFromDocShell(mDocShell));
+}
+
+void nsDOMNavigationTiming::NotifyDOMComplete(nsIURI* aURI) {
+ if (!mDOMComplete.IsNull()) {
+ return;
+ }
+ mLoadedURI = aURI;
+ mDOMComplete = TimeStamp::Now();
+
+ PROFILER_MARKER_UNTYPED("Navigation::DOMComplete", DOM,
+ MarkerInnerWindowIdFromDocShell(mDocShell));
+}
+
+void nsDOMNavigationTiming::NotifyDOMContentLoadedStart(nsIURI* aURI) {
+ if (!mDOMContentLoadedEventStart.IsNull()) {
+ return;
+ }
+
+ mLoadedURI = aURI;
+ mDOMContentLoadedEventStart = TimeStamp::Now();
+
+ PROFILER_MARKER("DOMContentLoaded", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalStart(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+
+ if (IsTopLevelContentDocumentInContentProcess()) {
+ TimeStamp now = TimeStamp::Now();
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_DOM_CONTENT_LOADED_START_MS, mNavigationStart, now);
+
+ if (mDocShellHasBeenActiveSinceNavigationStart) {
+ if (net::nsHttp::IsBeforeLastActiveTabLoadOptimization(
+ mNavigationStart)) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_DOM_CONTENT_LOADED_START_ACTIVE_NETOPT_MS,
+ mNavigationStart, now);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_DOM_CONTENT_LOADED_START_ACTIVE_MS,
+ mNavigationStart, now);
+ }
+ }
+ }
+}
+
+void nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI) {
+ if (!mDOMContentLoadedEventEnd.IsNull()) {
+ return;
+ }
+
+ mLoadedURI = aURI;
+ mDOMContentLoadedEventEnd = TimeStamp::Now();
+
+ PROFILER_MARKER("DOMContentLoaded", NETWORK,
+ MarkerOptions(MarkerTiming::IntervalEnd(),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ Tracing, "Navigation");
+
+ if (IsTopLevelContentDocumentInContentProcess()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_CONTENT_LOADED_END_MS,
+ mNavigationStart);
+ }
+}
+
+// static
+void nsDOMNavigationTiming::TTITimeoutCallback(nsITimer* aTimer,
+ void* aClosure) {
+ nsDOMNavigationTiming* self = static_cast<nsDOMNavigationTiming*>(aClosure);
+ self->TTITimeout(aTimer);
+}
+
+#define TTI_WINDOW_SIZE_MS (5 * 1000)
+
+void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) {
+ // Check TTI: see if it's been 5 seconds since the last Long Task
+ TimeStamp now = TimeStamp::Now();
+ MOZ_RELEASE_ASSERT(!mContentfulComposite.IsNull(),
+ "TTI timeout with no contentful-composite?");
+
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ TimeStamp lastLongTaskEnded;
+ mainThread->GetLastLongNonIdleTaskEnd(&lastLongTaskEnded);
+ // Window starts at mContentfulComposite; any long task before that is ignored
+ if (lastLongTaskEnded.IsNull() || lastLongTaskEnded < mContentfulComposite) {
+ PAGELOAD_LOG(
+ ("no longtask (last was %g ms before ContentfulComposite)",
+ lastLongTaskEnded.IsNull()
+ ? 0
+ : (mContentfulComposite - lastLongTaskEnded).ToMilliseconds()));
+ lastLongTaskEnded = mContentfulComposite;
+ }
+ TimeDuration delta = now - lastLongTaskEnded;
+ PAGELOAD_LOG(("TTI delta: %g ms", delta.ToMilliseconds()));
+ if (delta.ToMilliseconds() < TTI_WINDOW_SIZE_MS) {
+ // Less than 5 seconds since the last long task or start of the window.
+ // Schedule another check.
+ PAGELOAD_LOG(("TTI: waiting additional %g ms",
+ (TTI_WINDOW_SIZE_MS + 100) - delta.ToMilliseconds()));
+ aTimer->InitWithNamedFuncCallback(
+ TTITimeoutCallback, this,
+ (TTI_WINDOW_SIZE_MS + 100) -
+ delta.ToMilliseconds(), // slightly after the window ends
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+ "nsDOMNavigationTiming::TTITimeout");
+ return;
+ }
+
+ // To correctly implement TTI/TTFI as proposed, we'd need to not
+ // fire it until there are no more than 2 network loads. By the
+ // proposed definition, without that we're closer to
+ // TimeToFirstInteractive. There are also arguments about what sort
+ // of loads should qualify.
+
+ // XXX check number of network loads, and if > 2 mark to check if loads
+ // decreases to 2 (or record that point and let the normal timer here
+ // handle it)
+
+ // TTI has occurred! TTI is either FCP (if there are no longtasks and no
+ // DCLEnd in the window that starts at FCP), or at the end of the last
+ // Long Task or DOMContentLoadedEnd (whichever is later). lastLongTaskEnded
+ // is >= FCP here.
+
+ if (mTTFI.IsNull()) {
+ // lastLongTaskEnded is >= mContentfulComposite
+ mTTFI = (mDOMContentLoadedEventEnd.IsNull() ||
+ lastLongTaskEnded > mDOMContentLoadedEventEnd)
+ ? lastLongTaskEnded
+ : mDOMContentLoadedEventEnd;
+ PAGELOAD_LOG(
+ ("TTFI after %dms (LongTask was at %dms, DCL was %dms)",
+ int((mTTFI - mNavigationStart).ToMilliseconds()),
+ lastLongTaskEnded.IsNull()
+ ? 0
+ : int((lastLongTaskEnded - mNavigationStart).ToMilliseconds()),
+ mDOMContentLoadedEventEnd.IsNull()
+ ? 0
+ : int((mDOMContentLoadedEventEnd - mNavigationStart)
+ .ToMilliseconds())));
+ }
+ // XXX Implement TTI via check number of network loads, and if > 2 mark
+ // to check if loads decreases to 2 (or record that point and let the
+ // normal timer here handle it)
+
+ mTTITimer = nullptr;
+
+ if (profiler_thread_is_being_profiled_for_markers() ||
+ PAGELOAD_LOG_ENABLED()) {
+ TimeDuration elapsed = mTTFI - mNavigationStart;
+ MOZ_ASSERT(elapsed.ToMilliseconds() > 0);
+ TimeDuration elapsedLongTask =
+ lastLongTaskEnded.IsNull() ? 0 : lastLongTaskEnded - mNavigationStart;
+ nsPrintfCString marker(
+ "TTFI after %dms (LongTask was at %dms) for URL %s",
+ int(elapsed.ToMilliseconds()), int(elapsedLongTask.ToMilliseconds()),
+ nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get());
+
+ PROFILER_MARKER_TEXT(
+ "TimeToFirstInteractive (TTFI)", DOM,
+ MarkerOptions(MarkerTiming::Interval(mNavigationStart, mTTFI),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ marker);
+ }
+}
+
+void nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mNavigationStart.IsNull());
+
+ if (!mNonBlankPaint.IsNull()) {
+ return;
+ }
+
+ mNonBlankPaint = TimeStamp::Now();
+
+ if (profiler_thread_is_being_profiled_for_markers() ||
+ PAGELOAD_LOG_ENABLED()) {
+ TimeDuration elapsed = mNonBlankPaint - mNavigationStart;
+ nsPrintfCString marker(
+ "Non-blank paint after %dms for URL %s, %s",
+ int(elapsed.ToMilliseconds()),
+ nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get(),
+ mDocShellHasBeenActiveSinceNavigationStart
+ ? "foreground tab"
+ : "this tab was inactive some of the time between navigation start "
+ "and first non-blank paint");
+ PAGELOAD_LOG(("%s", marker.get()));
+ PROFILER_MARKER_TEXT(
+ "FirstNonBlankPaint", DOM,
+ MarkerOptions(MarkerTiming::Interval(mNavigationStart, mNonBlankPaint),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ marker);
+ }
+
+ if (mDocShellHasBeenActiveSinceNavigationStart) {
+ if (net::nsHttp::IsBeforeLastActiveTabLoadOptimization(mNavigationStart)) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_NON_BLANK_PAINT_NETOPT_MS, mNavigationStart,
+ mNonBlankPaint);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::TIME_TO_NON_BLANK_PAINT_NO_NETOPT_MS, mNavigationStart,
+ mNonBlankPaint);
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS,
+ mNavigationStart, mNonBlankPaint);
+ }
+}
+
+void nsDOMNavigationTiming::NotifyContentfulCompositeForRootContentDocument(
+ const mozilla::TimeStamp& aCompositeEndTime) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mNavigationStart.IsNull());
+
+ if (!mContentfulComposite.IsNull()) {
+ return;
+ }
+
+ mContentfulComposite = aCompositeEndTime;
+
+ if (profiler_thread_is_being_profiled_for_markers() ||
+ PAGELOAD_LOG_ENABLED()) {
+ TimeDuration elapsed = mContentfulComposite - mNavigationStart;
+ nsPrintfCString marker(
+ "Contentful composite after %dms for URL %s, %s",
+ int(elapsed.ToMilliseconds()),
+ nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get(),
+ mDocShellHasBeenActiveSinceNavigationStart
+ ? "foreground tab"
+ : "this tab was inactive some of the time between navigation start "
+ "and first non-blank paint");
+ PAGELOAD_LOG(("%s", marker.get()));
+ PROFILER_MARKER_TEXT(
+ "FirstContentfulComposite", DOM,
+ MarkerOptions(
+ MarkerTiming::Interval(mNavigationStart, mContentfulComposite),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ marker);
+ }
+
+ if (!mTTITimer) {
+ mTTITimer = NS_NewTimer();
+ }
+
+ // TTI is first checked 5 seconds after the FCP (non-blank-paint is very close
+ // to FCP).
+ mTTITimer->InitWithNamedFuncCallback(TTITimeoutCallback, this,
+ TTI_WINDOW_SIZE_MS,
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+ "nsDOMNavigationTiming::TTITimeout");
+
+ if (mDocShellHasBeenActiveSinceNavigationStart) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_FIRST_CONTENTFUL_PAINT_MS,
+ mNavigationStart, mContentfulComposite);
+ }
+}
+
+void nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mNavigationStart.IsNull());
+
+ if (!mDOMContentFlushed.IsNull()) {
+ return;
+ }
+
+ mDOMContentFlushed = TimeStamp::Now();
+
+ if (profiler_thread_is_being_profiled_for_markers() ||
+ PAGELOAD_LOG_ENABLED()) {
+ TimeDuration elapsed = mDOMContentFlushed - mNavigationStart;
+ nsPrintfCString marker(
+ "DOMContentFlushed after %dms for URL %s, %s",
+ int(elapsed.ToMilliseconds()),
+ nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get(),
+ mDocShellHasBeenActiveSinceNavigationStart
+ ? "foreground tab"
+ : "this tab was inactive some of the time between navigation start "
+ "and DOMContentFlushed");
+ PAGELOAD_LOG(("%s", marker.get()));
+ PROFILER_MARKER_TEXT(
+ "DOMContentFlushed", DOM,
+ MarkerOptions(
+ MarkerTiming::Interval(mNavigationStart, mDOMContentFlushed),
+ MarkerInnerWindowIdFromDocShell(mDocShell)),
+ marker);
+ }
+}
+
+void nsDOMNavigationTiming::NotifyDocShellStateChanged(
+ DocShellState aDocShellState) {
+ mDocShellHasBeenActiveSinceNavigationStart &=
+ (aDocShellState == DocShellState::eActive);
+}
+
+mozilla::TimeStamp nsDOMNavigationTiming::GetUnloadEventStartTimeStamp() const {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ // todo: if you intend to update CheckSameOriginURI to log the error to the
+ // console you also need to update the 'aFromPrivateWindow' argument.
+ nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ return mUnloadStart;
+ }
+ return mozilla::TimeStamp();
+}
+
+mozilla::TimeStamp nsDOMNavigationTiming::GetUnloadEventEndTimeStamp() const {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ // todo: if you intend to update CheckSameOriginURI to log the error to the
+ // console you also need to update the 'aFromPrivateWindow' argument.
+ nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ return mUnloadEnd;
+ }
+ return mozilla::TimeStamp();
+}
+
+bool nsDOMNavigationTiming::IsTopLevelContentDocumentInContentProcess() const {
+ if (!mDocShell) {
+ return false;
+ }
+ if (!XRE_IsContentProcess()) {
+ return false;
+ }
+ return mDocShell->GetBrowsingContext()->IsTopContent();
+}
+
+nsDOMNavigationTiming::nsDOMNavigationTiming(nsDocShell* aDocShell,
+ nsDOMNavigationTiming* aOther)
+ : mDocShell(aDocShell),
+ mUnloadedURI(aOther->mUnloadedURI),
+ mLoadedURI(aOther->mLoadedURI),
+ mNavigationType(aOther->mNavigationType),
+ mNavigationStartHighRes(aOther->mNavigationStartHighRes),
+ mNavigationStart(aOther->mNavigationStart),
+ mNonBlankPaint(aOther->mNonBlankPaint),
+ mContentfulComposite(aOther->mContentfulComposite),
+ mDOMContentFlushed(aOther->mDOMContentFlushed),
+ mBeforeUnloadStart(aOther->mBeforeUnloadStart),
+ mUnloadStart(aOther->mUnloadStart),
+ mUnloadEnd(aOther->mUnloadEnd),
+ mLoadEventStart(aOther->mLoadEventStart),
+ mLoadEventEnd(aOther->mLoadEventEnd),
+ mDOMLoading(aOther->mDOMLoading),
+ mDOMInteractive(aOther->mDOMInteractive),
+ mDOMContentLoadedEventStart(aOther->mDOMContentLoadedEventStart),
+ mDOMContentLoadedEventEnd(aOther->mDOMContentLoadedEventEnd),
+ mDOMComplete(aOther->mDOMComplete),
+ mTTFI(aOther->mTTFI),
+ mDocShellHasBeenActiveSinceNavigationStart(
+ aOther->mDocShellHasBeenActiveSinceNavigationStart) {}
+
+/* static */
+void mozilla::ipc::IPDLParamTraits<nsDOMNavigationTiming*>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsDOMNavigationTiming* aParam) {
+ bool isNull = !aParam;
+ WriteIPDLParam(aWriter, aActor, isNull);
+ if (isNull) {
+ return;
+ }
+
+ RefPtr<nsIURI> unloadedURI = aParam->mUnloadedURI.get();
+ RefPtr<nsIURI> loadedURI = aParam->mLoadedURI.get();
+ WriteIPDLParam(aWriter, aActor, unloadedURI ? Some(unloadedURI) : Nothing());
+ WriteIPDLParam(aWriter, aActor, loadedURI ? Some(loadedURI) : Nothing());
+ WriteIPDLParam(aWriter, aActor, uint32_t(aParam->mNavigationType));
+ WriteIPDLParam(aWriter, aActor, aParam->mNavigationStartHighRes);
+ WriteIPDLParam(aWriter, aActor, aParam->mNavigationStart);
+ WriteIPDLParam(aWriter, aActor, aParam->mNonBlankPaint);
+ WriteIPDLParam(aWriter, aActor, aParam->mContentfulComposite);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMContentFlushed);
+ WriteIPDLParam(aWriter, aActor, aParam->mBeforeUnloadStart);
+ WriteIPDLParam(aWriter, aActor, aParam->mUnloadStart);
+ WriteIPDLParam(aWriter, aActor, aParam->mUnloadEnd);
+ WriteIPDLParam(aWriter, aActor, aParam->mLoadEventStart);
+ WriteIPDLParam(aWriter, aActor, aParam->mLoadEventEnd);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMLoading);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMInteractive);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMContentLoadedEventStart);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMContentLoadedEventEnd);
+ WriteIPDLParam(aWriter, aActor, aParam->mDOMComplete);
+ WriteIPDLParam(aWriter, aActor, aParam->mTTFI);
+ WriteIPDLParam(aWriter, aActor,
+ aParam->mDocShellHasBeenActiveSinceNavigationStart);
+}
+
+/* static */
+bool mozilla::ipc::IPDLParamTraits<nsDOMNavigationTiming*>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsDOMNavigationTiming>* aResult) {
+ bool isNull;
+ if (!ReadIPDLParam(aReader, aActor, &isNull)) {
+ return false;
+ }
+ if (isNull) {
+ *aResult = nullptr;
+ return true;
+ }
+
+ auto timing = MakeRefPtr<nsDOMNavigationTiming>(nullptr);
+ uint32_t type;
+ Maybe<RefPtr<nsIURI>> unloadedURI;
+ Maybe<RefPtr<nsIURI>> loadedURI;
+ if (!ReadIPDLParam(aReader, aActor, &unloadedURI) ||
+ !ReadIPDLParam(aReader, aActor, &loadedURI) ||
+ !ReadIPDLParam(aReader, aActor, &type) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mNavigationStartHighRes) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mNavigationStart) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mNonBlankPaint) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mContentfulComposite) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMContentFlushed) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mBeforeUnloadStart) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mUnloadStart) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mUnloadEnd) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mLoadEventStart) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mLoadEventEnd) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMLoading) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMInteractive) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMContentLoadedEventStart) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMContentLoadedEventEnd) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mDOMComplete) ||
+ !ReadIPDLParam(aReader, aActor, &timing->mTTFI) ||
+ !ReadIPDLParam(aReader, aActor,
+ &timing->mDocShellHasBeenActiveSinceNavigationStart)) {
+ return false;
+ }
+ timing->mNavigationType = nsDOMNavigationTiming::Type(type);
+ if (unloadedURI) {
+ timing->mUnloadedURI = std::move(*unloadedURI);
+ }
+ if (loadedURI) {
+ timing->mLoadedURI = std::move(*loadedURI);
+ }
+ *aResult = std::move(timing);
+ return true;
+}
diff --git a/dom/base/nsDOMNavigationTiming.h b/dom/base/nsDOMNavigationTiming.h
new file mode 100644
index 0000000000..aea10f9af8
--- /dev/null
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMNavigationTiming_h___
+#define nsDOMNavigationTiming_h___
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/RelativeTimeline.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+
+class nsDocShell;
+class nsIURI;
+
+using DOMTimeMilliSec = unsigned long long;
+using DOMHighResTimeStamp = double;
+
+class PickleIterator;
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+namespace mozilla::ipc {
+class IProtocol;
+template <typename>
+struct IPDLParamTraits;
+} // namespace mozilla::ipc
+
+class nsDOMNavigationTiming final : public mozilla::RelativeTimeline {
+ public:
+ enum Type {
+ TYPE_NAVIGATE = 0,
+ TYPE_RELOAD = 1,
+ TYPE_BACK_FORWARD = 2,
+ TYPE_RESERVED = 255,
+ };
+
+ explicit nsDOMNavigationTiming(nsDocShell* aDocShell);
+
+ NS_INLINE_DECL_REFCOUNTING(nsDOMNavigationTiming)
+
+ Type GetType() const { return mNavigationType; }
+
+ inline DOMHighResTimeStamp GetNavigationStartHighRes() const {
+ return mNavigationStartHighRes;
+ }
+
+ DOMTimeMilliSec GetNavigationStart() const {
+ return static_cast<int64_t>(GetNavigationStartHighRes());
+ }
+
+ mozilla::TimeStamp GetNavigationStartTimeStamp() const {
+ return mNavigationStart;
+ }
+
+ mozilla::TimeStamp GetLoadEventStartTimeStamp() const {
+ return mLoadEventStart;
+ }
+
+ mozilla::TimeStamp GetDOMContentLoadedEventStartTimeStamp() const {
+ return mDOMContentLoadedEventStart;
+ }
+
+ mozilla::TimeStamp GetFirstContentfulCompositeTimeStamp() const {
+ return mContentfulComposite;
+ }
+
+ DOMTimeMilliSec GetUnloadEventStart() {
+ return TimeStampToDOM(GetUnloadEventStartTimeStamp());
+ }
+
+ DOMTimeMilliSec GetUnloadEventEnd() {
+ return TimeStampToDOM(GetUnloadEventEndTimeStamp());
+ }
+
+ DOMTimeMilliSec GetDomLoading() const { return TimeStampToDOM(mDOMLoading); }
+ DOMTimeMilliSec GetDomInteractive() const {
+ return TimeStampToDOM(mDOMInteractive);
+ }
+ DOMTimeMilliSec GetDomContentLoadedEventStart() const {
+ return TimeStampToDOM(mDOMContentLoadedEventStart);
+ }
+ DOMTimeMilliSec GetDomContentLoadedEventEnd() const {
+ return TimeStampToDOM(mDOMContentLoadedEventEnd);
+ }
+ DOMTimeMilliSec GetDomComplete() const {
+ return TimeStampToDOM(mDOMComplete);
+ }
+ DOMTimeMilliSec GetLoadEventStart() const {
+ return TimeStampToDOM(mLoadEventStart);
+ }
+ DOMTimeMilliSec GetLoadEventEnd() const {
+ return TimeStampToDOM(mLoadEventEnd);
+ }
+ DOMTimeMilliSec GetTimeToNonBlankPaint() const {
+ return TimeStampToDOM(mNonBlankPaint);
+ }
+ DOMTimeMilliSec GetTimeToContentfulComposite() const {
+ return TimeStampToDOM(mContentfulComposite);
+ }
+ DOMTimeMilliSec GetTimeToTTFI() const { return TimeStampToDOM(mTTFI); }
+ DOMTimeMilliSec GetTimeToDOMContentFlushed() const {
+ return TimeStampToDOM(mDOMContentFlushed);
+ }
+
+ DOMHighResTimeStamp GetUnloadEventStartHighRes() {
+ mozilla::TimeStamp stamp = GetUnloadEventStartTimeStamp();
+ if (stamp.IsNull()) {
+ return 0;
+ }
+ return TimeStampToDOMHighRes(stamp);
+ }
+ DOMHighResTimeStamp GetUnloadEventEndHighRes() {
+ mozilla::TimeStamp stamp = GetUnloadEventEndTimeStamp();
+ if (stamp.IsNull()) {
+ return 0;
+ }
+ return TimeStampToDOMHighRes(stamp);
+ }
+ DOMHighResTimeStamp GetDomInteractiveHighRes() const {
+ return TimeStampToDOMHighRes(mDOMInteractive);
+ }
+ DOMHighResTimeStamp GetDomContentLoadedEventStartHighRes() const {
+ return TimeStampToDOMHighRes(mDOMContentLoadedEventStart);
+ }
+ DOMHighResTimeStamp GetDomContentLoadedEventEndHighRes() const {
+ return TimeStampToDOMHighRes(mDOMContentLoadedEventEnd);
+ }
+ DOMHighResTimeStamp GetDomCompleteHighRes() const {
+ return TimeStampToDOMHighRes(mDOMComplete);
+ }
+ DOMHighResTimeStamp GetLoadEventStartHighRes() const {
+ return TimeStampToDOMHighRes(mLoadEventStart);
+ }
+ DOMHighResTimeStamp GetLoadEventEndHighRes() const {
+ return TimeStampToDOMHighRes(mLoadEventEnd);
+ }
+
+ enum class DocShellState : uint8_t { eActive, eInactive };
+
+ void NotifyNavigationStart(DocShellState aDocShellState);
+ void NotifyFetchStart(nsIURI* aURI, Type aNavigationType);
+ // A restoration occurs when the document is loaded from the
+ // bfcache. This method sets the appropriate parameters of the
+ // navigation timing object in this case.
+ void NotifyRestoreStart();
+ void NotifyBeforeUnload();
+ void NotifyUnloadAccepted(nsIURI* aOldURI);
+ void NotifyUnloadEventStart();
+ void NotifyUnloadEventEnd();
+ void NotifyLoadEventStart();
+ void NotifyLoadEventEnd();
+
+ // Document changes state to 'loading' before connecting to timing
+ void SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue);
+ void NotifyDOMLoading(nsIURI* aURI);
+ void NotifyDOMInteractive(nsIURI* aURI);
+ void NotifyDOMComplete(nsIURI* aURI);
+ void NotifyDOMContentLoadedStart(nsIURI* aURI);
+ void NotifyDOMContentLoadedEnd(nsIURI* aURI);
+
+ static void TTITimeoutCallback(nsITimer* aTimer, void* aClosure);
+ void TTITimeout(nsITimer* aTimer);
+
+ void NotifyLongTask(mozilla::TimeStamp aWhen);
+ void NotifyNonBlankPaintForRootContentDocument();
+ void NotifyContentfulCompositeForRootContentDocument(
+ const mozilla::TimeStamp& aCompositeEndTime);
+ void NotifyDOMContentFlushedForRootContentDocument();
+ void NotifyDocShellStateChanged(DocShellState aDocShellState);
+
+ DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
+
+ inline DOMHighResTimeStamp TimeStampToDOMHighRes(
+ mozilla::TimeStamp aStamp) const {
+ if (aStamp.IsNull()) {
+ return 0;
+ }
+ mozilla::TimeDuration duration = aStamp - mNavigationStart;
+ return duration.ToMilliseconds();
+ }
+
+ // Called by the DocumentLoadListener before sending the timing information
+ // to the new content process.
+ void Anonymize(nsIURI* aFinalURI);
+
+ inline already_AddRefed<nsDOMNavigationTiming> CloneNavigationTime(
+ nsDocShell* aDocShell) const {
+ RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(aDocShell);
+ timing->mNavigationStartHighRes = mNavigationStartHighRes;
+ timing->mNavigationStart = mNavigationStart;
+ return timing.forget();
+ }
+
+ bool DocShellHasBeenActiveSinceNavigationStart() {
+ return mDocShellHasBeenActiveSinceNavigationStart;
+ }
+
+ mozilla::TimeStamp LoadEventEnd() { return mLoadEventEnd; }
+
+ private:
+ friend class nsDocShell;
+ nsDOMNavigationTiming(nsDocShell* aDocShell, nsDOMNavigationTiming* aOther);
+ nsDOMNavigationTiming(const nsDOMNavigationTiming&) = delete;
+ ~nsDOMNavigationTiming();
+
+ void Clear();
+
+ mozilla::TimeStamp GetUnloadEventStartTimeStamp() const;
+ mozilla::TimeStamp GetUnloadEventEndTimeStamp() const;
+
+ bool IsTopLevelContentDocumentInContentProcess() const;
+
+ // Should those be amended, the IPC serializer should be updated
+ // accordingly.
+ mozilla::WeakPtr<nsDocShell> mDocShell;
+
+ nsCOMPtr<nsIURI> mUnloadedURI;
+ nsCOMPtr<nsIURI> mLoadedURI;
+ nsCOMPtr<nsITimer> mTTITimer;
+
+ Type mNavigationType;
+ DOMHighResTimeStamp mNavigationStartHighRes;
+ mozilla::TimeStamp mNavigationStart;
+ mozilla::TimeStamp mNonBlankPaint;
+ mozilla::TimeStamp mContentfulComposite;
+ mozilla::TimeStamp mDOMContentFlushed;
+
+ mozilla::TimeStamp mBeforeUnloadStart;
+ mozilla::TimeStamp mUnloadStart;
+ mozilla::TimeStamp mUnloadEnd;
+ mozilla::TimeStamp mLoadEventStart;
+ mozilla::TimeStamp mLoadEventEnd;
+
+ mozilla::TimeStamp mDOMLoading;
+ mozilla::TimeStamp mDOMInteractive;
+ mozilla::TimeStamp mDOMContentLoadedEventStart;
+ mozilla::TimeStamp mDOMContentLoadedEventEnd;
+ mozilla::TimeStamp mDOMComplete;
+
+ mozilla::TimeStamp mTTFI;
+
+ bool mDocShellHasBeenActiveSinceNavigationStart;
+
+ friend struct mozilla::ipc::IPDLParamTraits<nsDOMNavigationTiming*>;
+};
+
+// IPDL serializer. Please be aware of the caveats in sending across
+// the information and the potential resulting data leakage.
+// For now, this serializer is to only be used under a very narrowed scope
+// so that only the starting times are ever set.
+namespace mozilla::ipc {
+template <>
+struct IPDLParamTraits<nsDOMNavigationTiming*> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsDOMNavigationTiming* aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsDOMNavigationTiming>* aResult);
+};
+
+} // namespace mozilla::ipc
+
+#endif /* nsDOMNavigationTiming_h___ */
diff --git a/dom/base/nsDOMString.h b/dom/base/nsDOMString.h
new file mode 100644
index 0000000000..79ade05b7f
--- /dev/null
+++ b/dom/base/nsDOMString.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMString_h___
+#define nsDOMString_h___
+
+#include "nsString.h"
+
+inline bool DOMStringIsNull(const nsAString& aString) {
+ return aString.IsVoid();
+}
+
+inline void SetDOMStringToNull(nsAString& aString) { aString.SetIsVoid(true); }
+
+#endif /* nsDOMString_h___ */
diff --git a/dom/base/nsDOMTokenList.cpp b/dom/base/nsDOMTokenList.cpp
new file mode 100644
index 0000000000..f9ebef2884
--- /dev/null
+++ b/dom/base/nsDOMTokenList.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOMTokenList specified by HTML5.
+ */
+
+#include "nsDOMTokenList.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsTHashMap.h"
+#include "nsError.h"
+#include "nsHashKeys.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/DOMTokenListBinding.h"
+#include "mozilla/ErrorResult.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMTokenList::nsDOMTokenList(
+ Element* aElement, nsAtom* aAttrAtom,
+ const DOMTokenListSupportedTokenArray aSupportedTokens)
+ : mElement(aElement),
+ mAttrAtom(aAttrAtom),
+ mSupportedTokens(aSupportedTokens) {
+ // We don't add a reference to our element. If it goes away,
+ // we'll be told to drop our reference
+}
+
+nsDOMTokenList::~nsDOMTokenList() = default;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMTokenList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList)
+
+const nsAttrValue* nsDOMTokenList::GetParsedAttr() {
+ if (!mElement) {
+ return nullptr;
+ }
+ return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
+}
+
+static void RemoveDuplicates(const nsAttrValue* aAttr) {
+ if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
+ return;
+ }
+ aAttr->GetAtomArrayValue()->RemoveDuplicates();
+}
+
+uint32_t nsDOMTokenList::Length() {
+ const nsAttrValue* attr = GetParsedAttr();
+ if (!attr) {
+ return 0;
+ }
+
+ RemoveDuplicates(attr);
+ return attr->GetAtomCount();
+}
+
+void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsAString& aResult) {
+ const nsAttrValue* attr = GetParsedAttr();
+
+ if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
+ aFound = false;
+ return;
+ }
+
+ RemoveDuplicates(attr);
+
+ if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
+ aFound = true;
+ attr->AtomAt(aIndex)->ToString(aResult);
+ } else {
+ aFound = false;
+ }
+}
+
+void nsDOMTokenList::GetValue(nsAString& aResult) {
+ if (!mElement) {
+ aResult.Truncate();
+ return;
+ }
+
+ mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
+}
+
+void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) {
+ if (!mElement) {
+ return;
+ }
+
+ rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
+}
+
+void nsDOMTokenList::CheckToken(const nsAString& aToken, ErrorResult& aRv) {
+ if (aToken.IsEmpty()) {
+ return aRv.ThrowSyntaxError("The empty string is not a valid token.");
+ }
+
+ nsAString::const_iterator iter, end;
+ aToken.BeginReading(iter);
+ aToken.EndReading(end);
+
+ while (iter != end) {
+ if (nsContentUtils::IsHTMLWhitespace(*iter)) {
+ return aRv.ThrowInvalidCharacterError(
+ "The token can not contain whitespace.");
+ }
+ ++iter;
+ }
+}
+
+void nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens,
+ ErrorResult& aRv) {
+ for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
+ CheckToken(aTokens[i], aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+bool nsDOMTokenList::Contains(const nsAString& aToken) {
+ const nsAttrValue* attr = GetParsedAttr();
+ return attr && attr->Contains(aToken);
+}
+
+void nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
+ const nsTArray<nsString>& aTokens) {
+ if (!mElement) {
+ return;
+ }
+
+ nsAutoString resultStr;
+
+ if (aAttr) {
+ RemoveDuplicates(aAttr);
+ for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+ if (i != 0) {
+ resultStr.AppendLiteral(" ");
+ }
+ resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+ }
+ }
+
+ AutoTArray<nsString, 10> addedClasses;
+
+ for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
+ const nsString& aToken = aTokens[i];
+
+ if ((aAttr && aAttr->Contains(aToken)) || addedClasses.Contains(aToken)) {
+ continue;
+ }
+
+ if (!resultStr.IsEmpty()) {
+ resultStr.Append(' ');
+ }
+ resultStr.Append(aToken);
+
+ addedClasses.AppendElement(aToken);
+ }
+
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
+}
+
+void nsDOMTokenList::Add(const nsTArray<nsString>& aTokens,
+ ErrorResult& aError) {
+ CheckTokens(aTokens, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ const nsAttrValue* attr = GetParsedAttr();
+ AddInternal(attr, aTokens);
+}
+
+void nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) {
+ AutoTArray<nsString, 1> tokens;
+ tokens.AppendElement(aToken);
+ Add(tokens, aError);
+}
+
+void nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
+ const nsTArray<nsString>& aTokens) {
+ MOZ_ASSERT(aAttr, "Need an attribute");
+
+ RemoveDuplicates(aAttr);
+
+ nsAutoString resultStr;
+ for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+ if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
+ continue;
+ }
+ if (!resultStr.IsEmpty()) {
+ resultStr.AppendLiteral(" ");
+ }
+ resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+ }
+
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
+}
+
+void nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens,
+ ErrorResult& aError) {
+ CheckTokens(aTokens, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ const nsAttrValue* attr = GetParsedAttr();
+ if (!attr) {
+ return;
+ }
+
+ RemoveInternal(attr, aTokens);
+}
+
+void nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) {
+ AutoTArray<nsString, 1> tokens;
+ tokens.AppendElement(aToken);
+ Remove(tokens, aError);
+}
+
+bool nsDOMTokenList::Toggle(const nsAString& aToken,
+ const Optional<bool>& aForce, ErrorResult& aError) {
+ CheckToken(aToken, aError);
+ if (aError.Failed()) {
+ return false;
+ }
+
+ const nsAttrValue* attr = GetParsedAttr();
+ const bool forceOn = aForce.WasPassed() && aForce.Value();
+ const bool forceOff = aForce.WasPassed() && !aForce.Value();
+
+ bool isPresent = attr && attr->Contains(aToken);
+ AutoTArray<nsString, 1> tokens;
+ (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length());
+
+ if (isPresent) {
+ if (!forceOn) {
+ RemoveInternal(attr, tokens);
+ isPresent = false;
+ }
+ } else {
+ if (!forceOff) {
+ AddInternal(attr, tokens);
+ isPresent = true;
+ }
+ }
+
+ return isPresent;
+}
+
+bool nsDOMTokenList::Replace(const nsAString& aToken,
+ const nsAString& aNewToken, ErrorResult& aError) {
+ // Doing this here instead of using `CheckToken` because if aToken had invalid
+ // characters, and aNewToken is empty, the returned error should be a
+ // SyntaxError, not an InvalidCharacterError.
+ if (aNewToken.IsEmpty()) {
+ aError.ThrowSyntaxError("The empty string is not a valid token.");
+ return false;
+ }
+
+ CheckToken(aToken, aError);
+ if (aError.Failed()) {
+ return false;
+ }
+
+ CheckToken(aNewToken, aError);
+ if (aError.Failed()) {
+ return false;
+ }
+
+ const nsAttrValue* attr = GetParsedAttr();
+ if (!attr) {
+ return false;
+ }
+
+ return ReplaceInternal(attr, aToken, aNewToken);
+}
+
+bool nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
+ const nsAString& aToken,
+ const nsAString& aNewToken) {
+ RemoveDuplicates(aAttr);
+
+ // Trying to do a single pass here leads to really complicated code. Just do
+ // the simple thing.
+ bool haveOld = false;
+ for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) {
+ if (aAttr->AtomAt(i)->Equals(aToken)) {
+ haveOld = true;
+ break;
+ }
+ }
+ if (!haveOld) {
+ // Make sure to not touch the attribute value in this case.
+ return false;
+ }
+
+ bool sawIt = false;
+ nsAutoString resultStr;
+ for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+ if (aAttr->AtomAt(i)->Equals(aToken) ||
+ aAttr->AtomAt(i)->Equals(aNewToken)) {
+ if (sawIt) {
+ // We keep only the first
+ continue;
+ }
+ sawIt = true;
+ if (!resultStr.IsEmpty()) {
+ resultStr.AppendLiteral(" ");
+ }
+ resultStr.Append(aNewToken);
+ continue;
+ }
+ if (!resultStr.IsEmpty()) {
+ resultStr.AppendLiteral(" ");
+ }
+ resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+ }
+
+ MOZ_ASSERT(sawIt, "How could we not have found our token this time?");
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
+ return true;
+}
+
+bool nsDOMTokenList::Supports(const nsAString& aToken, ErrorResult& aError) {
+ if (!mSupportedTokens) {
+ aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>(
+ NS_ConvertUTF16toUTF8(mElement->LocalName()),
+ NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom)));
+ return false;
+ }
+
+ for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
+ *supportedToken; ++supportedToken) {
+ if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+DocGroup* nsDOMTokenList::GetDocGroup() const {
+ return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr;
+}
+
+JSObject* nsDOMTokenList::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DOMTokenList_Binding::Wrap(cx, this, aGivenProto);
+}
diff --git a/dom/base/nsDOMTokenList.h b/dom/base/nsDOMTokenList.h
new file mode 100644
index 0000000000..e2ac5c0224
--- /dev/null
+++ b/dom/base/nsDOMTokenList.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOMTokenList specified by HTML5.
+ */
+
+#ifndef nsDOMTokenList_h___
+#define nsDOMTokenList_h___
+
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDOMString.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMTokenListSupportedTokens.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class DocGroup;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsAttrValue;
+class nsAtom;
+
+// nsISupports must be on the primary inheritance chain
+
+class nsDOMTokenList : public nsISupports, public nsWrapperCache {
+ protected:
+ using Element = mozilla::dom::Element;
+ using DocGroup = mozilla::dom::DocGroup;
+ using WhitespaceTokenizer =
+ nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace>;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMTokenList)
+
+ nsDOMTokenList(Element* aElement, nsAtom* aAttrAtom,
+ const mozilla::dom::DOMTokenListSupportedTokenArray = nullptr);
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Element* GetParentObject() { return mElement; }
+
+ DocGroup* GetDocGroup() const;
+
+ uint32_t Length();
+ void Item(uint32_t aIndex, nsAString& aResult) {
+ bool found;
+ IndexedGetter(aIndex, found, aResult);
+ if (!found) {
+ SetDOMStringToNull(aResult);
+ }
+ }
+ void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult);
+ bool Contains(const nsAString& aToken);
+ void Add(const nsAString& aToken, mozilla::ErrorResult& aError);
+ void Add(const nsTArray<nsString>& aTokens, mozilla::ErrorResult& aError);
+ void Remove(const nsAString& aToken, mozilla::ErrorResult& aError);
+ void Remove(const nsTArray<nsString>& aTokens, mozilla::ErrorResult& aError);
+ bool Replace(const nsAString& aToken, const nsAString& aNewToken,
+ mozilla::ErrorResult& aError);
+ bool Toggle(const nsAString& aToken,
+ const mozilla::dom::Optional<bool>& force,
+ mozilla::ErrorResult& aError);
+ bool Supports(const nsAString& aToken, mozilla::ErrorResult& aError);
+
+ void GetValue(nsAString& aResult);
+ void SetValue(const nsAString& aValue, mozilla::ErrorResult& rv);
+
+ protected:
+ virtual ~nsDOMTokenList();
+
+ void CheckToken(const nsAString& aToken, mozilla::ErrorResult& aRv);
+ void CheckTokens(const nsTArray<nsString>& aTokens,
+ mozilla::ErrorResult& aRv);
+ void AddInternal(const nsAttrValue* aAttr, const nsTArray<nsString>& aTokens);
+ void RemoveInternal(const nsAttrValue* aAttr,
+ const nsTArray<nsString>& aTokens);
+ bool ReplaceInternal(const nsAttrValue* aAttr, const nsAString& aToken,
+ const nsAString& aNewToken);
+ inline const nsAttrValue* GetParsedAttr();
+
+ nsCOMPtr<Element> mElement;
+ RefPtr<nsAtom> mAttrAtom;
+ const mozilla::dom::DOMTokenListSupportedTokenArray mSupportedTokens;
+};
+
+#endif // nsDOMTokenList_h___
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
new file mode 100644
index 0000000000..860fb37756
--- /dev/null
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -0,0 +1,4941 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMWindowUtils.h"
+
+#include "LayoutConstants.h"
+#include "MobileViewportManager.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "nsPresContext.h"
+#include "nsCaret.h"
+#include "nsContentList.h"
+#include "nsError.h"
+#include "nsQueryContentEventResult.h"
+#include "nsGlobalWindow.h"
+#include "nsFocusManager.h"
+#include "nsFrameManager.h"
+#include "nsRefreshDriver.h"
+#include "nsStyleUtil.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DOMCollectedFramesBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/SharedStyleSheetCache.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/InputTaskManager.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIFrame.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/PCompositorBridgeTypes.h"
+#include "mozilla/layers/TouchActionHelper.h"
+#include "mozilla/media/MediaUtils.h"
+#include "nsQueryObject.h"
+#include "CubebDeviceEnumerator.h"
+
+#include "nsIScrollableFrame.h"
+
+#include "nsContentUtils.h"
+
+#include "nsIWidget.h"
+#include "nsCharsetSource.h"
+#include "nsJSEnvironment.h"
+#include "nsJSUtils.h"
+#include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
+#include "js/Object.h" // JS::GetClass
+
+#include "mozilla/ChaosMode.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/Span.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsViewManager.h"
+
+#include "nsLayoutUtils.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCSSProps.h"
+#include "nsIDocShell.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/DOMRect.h"
+#include <algorithm>
+
+#if defined(MOZ_WIDGET_GTK)
+# include <gdk/gdk.h>
+# if defined(MOZ_X11)
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+# endif
+#endif
+
+#include "mozilla/dom/AudioDeviceInfo.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/IDBFactoryBinding.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/layers/FrameUniformityData.h"
+#include "nsPrintfCString.h"
+#include "nsViewportInfo.h"
+#include "nsIFormControl.h"
+// #include "nsWidgetsCID.h"
+#include "nsDisplayList.h"
+#include "nsROCSSPrimitiveValue.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsContentPermissionHelper.h"
+#include "nsCSSPseudoElements.h" // for PseudoStyleType
+#include "nsNetUtil.h"
+#include "HTMLImageElement.h"
+#include "HTMLCanvasElement.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/layers/IAPZCTreeManager.h" // for layers::ZoomToRectBehavior
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/PreloadedStyleSheet.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/IMEContentObserver.h"
+#include "mozilla/WheelHandlingHelper.h"
+
+#ifdef XP_WIN
+# include <direct.h>
+#else
+# include <sys/stat.h>
+#endif
+
+#ifdef XP_WIN
+# undef GetClassName
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::gfx;
+
+class gfxContext;
+
+class OldWindowSize : public LinkedListElement<OldWindowSize> {
+ public:
+ static void Set(nsIWeakReference* aWindowRef, const nsSize& aSize) {
+ OldWindowSize* item = GetItem(aWindowRef);
+ if (item) {
+ item->mSize = aSize;
+ } else {
+ item = new OldWindowSize(aWindowRef, aSize);
+ sList.insertBack(item);
+ }
+ }
+
+ static nsSize GetAndRemove(nsIWeakReference* aWindowRef) {
+ nsSize result;
+ if (OldWindowSize* item = GetItem(aWindowRef)) {
+ result = item->mSize;
+ delete item;
+ }
+ return result;
+ }
+
+ private:
+ explicit OldWindowSize(nsIWeakReference* aWindowRef, const nsSize& aSize)
+ : mWindowRef(aWindowRef), mSize(aSize) {}
+ ~OldWindowSize() = default;
+ ;
+
+ static OldWindowSize* GetItem(nsIWeakReference* aWindowRef) {
+ OldWindowSize* item = sList.getFirst();
+ while (item && item->mWindowRef != aWindowRef) {
+ item = item->getNext();
+ }
+ return item;
+ }
+
+ static LinkedList<OldWindowSize> sList;
+ nsWeakPtr mWindowRef;
+ nsSize mSize;
+};
+
+namespace {
+
+class NativeInputRunnable final : public PrioritizableRunnable {
+ explicit NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent);
+ ~NativeInputRunnable() = default;
+
+ public:
+ static already_AddRefed<nsIRunnable> Create(
+ already_AddRefed<nsIRunnable>&& aEvent);
+};
+
+NativeInputRunnable::NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent)
+ : PrioritizableRunnable(std::move(aEvent),
+ nsIRunnablePriority::PRIORITY_INPUT_HIGH) {}
+
+/* static */
+already_AddRefed<nsIRunnable> NativeInputRunnable::Create(
+ already_AddRefed<nsIRunnable>&& aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> event(new NativeInputRunnable(std::move(aEvent)));
+ return event.forget();
+}
+
+} // unnamed namespace
+
+LinkedList<OldWindowSize> OldWindowSize::sList;
+
+NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWindowUtils)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMWindowUtils)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsDOMWindowUtils)
+NS_IMPL_RELEASE(nsDOMWindowUtils)
+
+nsDOMWindowUtils::nsDOMWindowUtils(nsGlobalWindowOuter* aWindow) {
+ nsCOMPtr<nsISupports> supports = do_QueryObject(aWindow);
+ mWindow = do_GetWeakReference(supports);
+}
+
+nsDOMWindowUtils::~nsDOMWindowUtils() { OldWindowSize::GetAndRemove(mWindow); }
+
+nsIDocShell* nsDOMWindowUtils::GetDocShell() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return nullptr;
+ }
+ return window->GetDocShell();
+}
+
+PresShell* nsDOMWindowUtils::GetPresShell() {
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ return docShell->GetPresShell();
+}
+
+nsPresContext* nsDOMWindowUtils::GetPresContext() {
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ return docShell->GetPresContext();
+}
+
+Document* nsDOMWindowUtils::GetDocument() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return nullptr;
+ }
+ return window->GetExtantDoc();
+}
+
+WebRenderBridgeChild* nsDOMWindowUtils::GetWebRenderBridge() {
+ if (nsIWidget* widget = GetWidget()) {
+ if (WindowRenderer* renderer = widget->GetWindowRenderer()) {
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ return wr->WrBridge();
+ }
+ }
+ }
+ return nullptr;
+}
+
+CompositorBridgeChild* nsDOMWindowUtils::GetCompositorBridge() {
+ if (nsIWidget* widget = GetWidget()) {
+ if (WindowRenderer* renderer = widget->GetWindowRenderer()) {
+ if (CompositorBridgeChild* cbc = renderer->GetCompositorBridgeChild()) {
+ return cbc;
+ }
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLastOverWindowPointerLocationInCSSPixels(float* aX,
+ float* aY) {
+ const PresShell* presShell = GetPresShell();
+ const nsPresContext* presContext = GetPresContext();
+
+ if (!presShell || !presContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsPoint& lastOverWindowPointerLocation =
+ presShell->GetLastOverWindowPointerLocation();
+
+ if (lastOverWindowPointerLocation.X() == NS_UNCONSTRAINEDSIZE &&
+ lastOverWindowPointerLocation.Y() == NS_UNCONSTRAINEDSIZE) {
+ *aX = 0;
+ *aY = 0;
+ } else {
+ const CSSPoint lastOverWindowPointerLocationInCSSPixels =
+ CSSPoint::FromAppUnits(lastOverWindowPointerLocation);
+ *aX = lastOverWindowPointerLocationInCSSPixels.X();
+ *aY = lastOverWindowPointerLocationInCSSPixels.Y();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SyncFlushCompositor() {
+ if (nsIWidget* widget = GetWidget()) {
+ if (WindowRenderer* renderer = widget->GetWindowRenderer()) {
+ if (KnowsCompositor* kc = renderer->AsKnowsCompositor()) {
+ kc->SyncWithCompositor();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetImageAnimationMode(uint16_t* aMode) {
+ NS_ENSURE_ARG_POINTER(aMode);
+ *aMode = 0;
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ *aMode = presContext->ImageAnimationMode();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetImageAnimationMode(uint16_t aMode) {
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->SetImageAnimationMode(aMode);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDocCharsetIsForced(bool* aIsForced) {
+ *aIsForced = false;
+
+ Document* doc = GetDocument();
+ if (doc) {
+ auto source = doc->GetDocumentCharacterSetSource();
+ *aIsForced = source == kCharsetFromInitialUserForcedAutoDetection ||
+ source == kCharsetFromFinalUserForcedAutoDetection;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPhysicalMillimeterInCSSPixels(float* aPhysicalMillimeter) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aPhysicalMillimeter = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->PhysicalMillimetersToAppUnits(1));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDocumentMetadata(const nsAString& aName,
+ nsAString& aValue) {
+ Document* doc = GetDocument();
+ if (doc) {
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ doc->GetHeaderData(name, aValue);
+ return NS_OK;
+ }
+
+ aValue.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::UpdateLayerTree() {
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ // Don't flush throttled animations since it might fire MozAfterPaint event
+ // (in WebRender it constantly does), thus the reftest harness can't take
+ // any snapshot until the throttled animations finished.
+ presShell->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Display, false /* flush animations */));
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (nsView* view = vm->GetRootView()) {
+ nsAutoScriptBlocker scriptBlocker;
+ presShell->PaintAndRequestComposite(view,
+ PaintFlags::PaintSyncDecodeImages);
+ presShell->GetWindowRenderer()->WaitOnTransactionProcessed();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetContentViewerSize(uint32_t* aDisplayWidth,
+ uint32_t* aDisplayHeight) {
+ PresShell* presShell = GetPresShell();
+ LayoutDeviceIntSize displaySize;
+
+ if (!presShell || !nsLayoutUtils::GetContentViewerSize(
+ presShell->GetPresContext(), displaySize)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aDisplayWidth = displaySize.width;
+ *aDisplayHeight = displaySize.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetViewportInfo(uint32_t aDisplayWidth,
+ uint32_t aDisplayHeight, double* aDefaultZoom,
+ bool* aAllowZoom, double* aMinZoom,
+ double* aMaxZoom, uint32_t* aWidth,
+ uint32_t* aHeight, bool* aAutoSize) {
+ Document* doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsViewportInfo info =
+ doc->GetViewportInfo(ScreenIntSize(aDisplayWidth, aDisplayHeight));
+ *aDefaultZoom = info.GetDefaultZoom().scale;
+ *aAllowZoom = info.IsZoomAllowed();
+ *aMinZoom = info.GetMinZoom().scale;
+ *aMaxZoom = info.GetMaxZoom().scale;
+ CSSIntSize size = gfx::RoundedToInt(info.GetSize());
+ *aWidth = size.width;
+ *aHeight = size.height;
+ *aAutoSize = info.IsAutoSizeEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetViewportFitInfo(nsAString& aViewportFit) {
+ Document* doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ ViewportMetaData metaData = doc->GetViewportMetaData();
+ if (metaData.mViewportFit.EqualsLiteral("contain")) {
+ aViewportFit.AssignLiteral("contain");
+ } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
+ aViewportFit.AssignLiteral("cover");
+ } else {
+ aViewportFit.AssignLiteral("auto");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetMousewheelAutodir(Element* aElement, bool aEnabled,
+ bool aHonourRoot) {
+ aElement->SetProperty(nsGkAtoms::forceMousewheelAutodir,
+ reinterpret_cast<void*>(aEnabled));
+ aElement->SetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot,
+ reinterpret_cast<void*>(aHonourRoot));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortForElement(float aXPx, float aYPx,
+ float aWidthPx, float aHeightPx,
+ Element* aElement,
+ uint32_t aPriority) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aElement->GetUncomposedDoc() != presShell->GetDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool hadDisplayPort = false;
+ bool wasPainted = false;
+ nsRect oldDisplayPort;
+ {
+ DisplayPortPropertyData* currentData =
+ static_cast<DisplayPortPropertyData*>(
+ aElement->GetProperty(nsGkAtoms::DisplayPort));
+ if (currentData) {
+ if (currentData->mPriority > aPriority) {
+ return NS_OK;
+ }
+ hadDisplayPort = true;
+ oldDisplayPort = currentData->mRect;
+ wasPainted = currentData->mPainted;
+ }
+ }
+
+ nsRect displayport(nsPresContext::CSSPixelsToAppUnits(aXPx),
+ nsPresContext::CSSPixelsToAppUnits(aYPx),
+ nsPresContext::CSSPixelsToAppUnits(aWidthPx),
+ nsPresContext::CSSPixelsToAppUnits(aHeightPx));
+
+ aElement->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
+ aElement->SetProperty(
+ nsGkAtoms::DisplayPort,
+ new DisplayPortPropertyData(displayport, aPriority, wasPainted),
+ nsINode::DeleteProperty<DisplayPortPropertyData>);
+
+ DisplayPortUtils::InvalidateForDisplayPortChange(aElement, hadDisplayPort,
+ oldDisplayPort, displayport);
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (rootFrame) {
+ rootFrame->SchedulePaint();
+
+ // If we are hiding something that is a display root then send empty paint
+ // transaction in order to release retained layers because it won't get
+ // any more paint requests when it is hidden.
+ if (displayport.IsEmpty() &&
+ rootFrame == nsLayoutUtils::GetDisplayRootFrame(rootFrame)) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (widget) {
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ nsLayoutUtils::PaintFrame(
+ nullptr, rootFrame, nsRegion(), NS_RGB(255, 255, 255),
+ nsDisplayListBuilderMode::Painting, PaintFrameFlags::WidgetLayers);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortMarginsForElement(
+ float aLeftMargin, float aTopMargin, float aRightMargin,
+ float aBottomMargin, Element* aElement, uint32_t aPriority) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aElement->GetUncomposedDoc() != presShell->GetDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Note order change of arguments between our function signature and
+ // ScreenMargin constructor.
+ ScreenMargin displayportMargins(aTopMargin, aRightMargin, aBottomMargin,
+ aLeftMargin);
+
+ DisplayPortUtils::SetDisplayPortMargins(
+ aElement, presShell,
+ DisplayPortMargins::ForContent(aElement, displayportMargins),
+ DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes, aPriority);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortBaseForElement(int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ Element* aElement) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aElement->GetUncomposedDoc() != presShell->GetDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DisplayPortUtils::SetDisplayPortBase(aElement,
+ nsRect(aX, aY, aWidth, aHeight));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetScrollbarSizes(Element* aElement,
+ uint32_t* aOutVerticalScrollbarWidth,
+ uint32_t* aOutHorizontalScrollbarHeight) {
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::FindScrollableFrameFor(aElement);
+ if (!scrollFrame) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ CSSIntMargin scrollbarSizes =
+ RoundedToInt(CSSMargin::FromAppUnits(scrollFrame->GetActualScrollbarSizes(
+ nsIScrollableFrame::ScrollbarSizesOptions::
+ INCLUDE_VISUAL_VIEWPORT_SCROLLBARS)));
+ *aOutVerticalScrollbarWidth = scrollbarSizes.LeftRight();
+ *aOutHorizontalScrollbarHeight = scrollbarSizes.TopBottom();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetResolutionAndScaleTo(aResolution, ResolutionChangeOrigin::Test);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetRestoreResolution(float aResolution,
+ uint32_t aDisplayWidth,
+ uint32_t aDisplayHeight) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetRestoreResolution(
+ aResolution, LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetResolution(float* aResolution) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResolution = presShell->GetResolution();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetIsFirstPaint(bool aIsFirstPaint) {
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->SetIsFirstPaint(aIsFirstPaint);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsFirstPaint(bool* aIsFirstPaint) {
+ if (PresShell* presShell = GetPresShell()) {
+ *aIsFirstPaint = presShell->GetIsFirstPaint();
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
+ if (PresShell* presShell = GetPresShell()) {
+ *aPresShellId = presShell->GetPresShellId();
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendMouseEvent(
+ const nsAString& aType, float aX, float aY, int32_t aButton,
+ int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
+ float aPressure, unsigned short aInputSourceArg,
+ bool aIsDOMEventSynthesized, bool aIsWidgetEventSynthesized,
+ int32_t aButtons, uint32_t aIdentifier, uint8_t aOptionalArgCount,
+ bool* aPreventDefault) {
+ return SendMouseEventCommon(
+ aType, aX, aY, aButton, aClickCount, aModifiers, aIgnoreRootScrollFrame,
+ aPressure, aInputSourceArg,
+ aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, false,
+ aPreventDefault, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
+ aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
+ aOptionalArgCount >= 6 ? aButtons : MOUSE_BUTTONS_NOT_SPECIFIED);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendMouseEventToWindow(
+ const nsAString& aType, float aX, float aY, int32_t aButton,
+ int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
+ float aPressure, unsigned short aInputSourceArg,
+ bool aIsDOMEventSynthesized, bool aIsWidgetEventSynthesized,
+ int32_t aButtons, uint32_t aIdentifier, uint8_t aOptionalArgCount) {
+ AUTO_PROFILER_LABEL("nsDOMWindowUtils::SendMouseEventToWindow", OTHER);
+
+ return SendMouseEventCommon(
+ aType, aX, aY, aButton, aClickCount, aModifiers, aIgnoreRootScrollFrame,
+ aPressure, aInputSourceArg,
+ aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, true,
+ nullptr, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
+ aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
+ aOptionalArgCount >= 6 ? aButtons : MOUSE_BUTTONS_NOT_SPECIFIED);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendMouseEventCommon(
+ const nsAString& aType, float aX, float aY, int32_t aButton,
+ int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
+ float aPressure, unsigned short aInputSourceArg, uint32_t aPointerId,
+ bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized, int32_t aButtons) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ PreventDefaultResult preventDefaultResult;
+ nsresult rv = nsContentUtils::SendMouseEvent(
+ presShell, aType, aX, aY, aButton, aButtons, aClickCount, aModifiers,
+ aIgnoreRootScrollFrame, aPressure, aInputSourceArg, aPointerId, aToWindow,
+ &preventDefaultResult, aIsDOMEventSynthesized, aIsWidgetEventSynthesized);
+
+ if (aPreventDefault) {
+ *aPreventDefault = preventDefaultResult != PreventDefaultResult::No;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsCORSSafelistedRequestHeader(const nsACString& aName,
+ const nsACString& aValue,
+ bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ *aRetVal = nsContentUtils::IsCORSSafelistedRequestHeader(aName, aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendWheelEvent(float aX, float aY, double aDeltaX,
+ double aDeltaY, double aDeltaZ,
+ uint32_t aDeltaMode, int32_t aModifiers,
+ int32_t aLineOrPageDeltaX,
+ int32_t aLineOrPageDeltaY, uint32_t aOptions) {
+ // get the widget to send the event to
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
+ if (!widget) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ WidgetWheelEvent wheelEvent(true, eWheel, widget);
+ wheelEvent.mModifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
+ wheelEvent.mDeltaX = aDeltaX;
+ wheelEvent.mDeltaY = aDeltaY;
+ wheelEvent.mDeltaZ = aDeltaZ;
+ wheelEvent.mDeltaMode = aDeltaMode;
+ wheelEvent.mIsMomentum = (aOptions & WHEEL_EVENT_CAUSED_BY_MOMENTUM) != 0;
+ wheelEvent.mIsNoLineOrPageDelta =
+ (aOptions & WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICE) != 0;
+ wheelEvent.mCustomizedByUserPrefs =
+ (aOptions & WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS) != 0;
+ wheelEvent.mLineOrPageDeltaX = aLineOrPageDeltaX;
+ wheelEvent.mLineOrPageDeltaY = aLineOrPageDeltaY;
+
+ nsPresContext* presContext = GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ wheelEvent.mRefPoint =
+ nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+
+ if (StaticPrefs::test_events_async_enabled()) {
+ widget->DispatchInputEvent(&wheelEvent);
+ } else {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = widget->DispatchEvent(&wheelEvent, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (widget->AsyncPanZoomEnabled()) {
+ // Computing overflow deltas is not compatible with APZ, so if APZ is
+ // enabled, we skip testing it.
+ return NS_OK;
+ }
+
+ bool failedX = false;
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO) &&
+ wheelEvent.mOverflowDeltaX != 0) {
+ failedX = true;
+ }
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE) &&
+ wheelEvent.mOverflowDeltaX <= 0) {
+ failedX = true;
+ }
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE) &&
+ wheelEvent.mOverflowDeltaX >= 0) {
+ failedX = true;
+ }
+ bool failedY = false;
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO) &&
+ wheelEvent.mOverflowDeltaY != 0) {
+ failedY = true;
+ }
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE) &&
+ wheelEvent.mOverflowDeltaY <= 0) {
+ failedY = true;
+ }
+ if ((aOptions & WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE) &&
+ wheelEvent.mOverflowDeltaY >= 0) {
+ failedY = true;
+ }
+
+#ifdef DEBUG
+ if (failedX) {
+ nsPrintfCString debugMsg("SendWheelEvent(): unexpected mOverflowDeltaX: %f",
+ wheelEvent.mOverflowDeltaX);
+ NS_WARNING(debugMsg.get());
+ }
+ if (failedY) {
+ nsPrintfCString debugMsg("SendWheelEvent(): unexpected mOverflowDeltaY: %f",
+ wheelEvent.mOverflowDeltaY);
+ NS_WARNING(debugMsg.get());
+ }
+#endif
+
+ return (!failedX && !failedY) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendTouchEvent(
+ const nsAString& aType, const nsTArray<uint32_t>& aIdentifiers,
+ const nsTArray<int32_t>& aXs, const nsTArray<int32_t>& aYs,
+ const nsTArray<uint32_t>& aRxs, const nsTArray<uint32_t>& aRys,
+ const nsTArray<float>& aRotationAngles, const nsTArray<float>& aForces,
+ const nsTArray<int32_t>& aTiltXs, const nsTArray<int32_t>& aTiltYs,
+ const nsTArray<int32_t>& aTwists, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, bool* aPreventDefault) {
+ return SendTouchEventCommon(aType, aIdentifiers, aXs, aYs, aRxs, aRys,
+ aRotationAngles, aForces, aTiltXs, aTiltYs,
+ aTwists, aModifiers, aIgnoreRootScrollFrame,
+ false, aPreventDefault);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendTouchEventToWindow(
+ const nsAString& aType, const nsTArray<uint32_t>& aIdentifiers,
+ const nsTArray<int32_t>& aXs, const nsTArray<int32_t>& aYs,
+ const nsTArray<uint32_t>& aRxs, const nsTArray<uint32_t>& aRys,
+ const nsTArray<float>& aRotationAngles, const nsTArray<float>& aForces,
+ const nsTArray<int32_t>& aTiltXs, const nsTArray<int32_t>& aTiltYs,
+ const nsTArray<int32_t>& aTwists, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, bool* aPreventDefault) {
+ return SendTouchEventCommon(aType, aIdentifiers, aXs, aYs, aRxs, aRys,
+ aRotationAngles, aForces, aTiltXs, aTiltYs,
+ aTwists, aModifiers, aIgnoreRootScrollFrame, true,
+ aPreventDefault);
+}
+
+nsresult nsDOMWindowUtils::SendTouchEventCommon(
+ const nsAString& aType, const nsTArray<uint32_t>& aIdentifiers,
+ const nsTArray<int32_t>& aXs, const nsTArray<int32_t>& aYs,
+ const nsTArray<uint32_t>& aRxs, const nsTArray<uint32_t>& aRys,
+ const nsTArray<float>& aRotationAngles, const nsTArray<float>& aForces,
+ const nsTArray<int32_t>& aTiltXs, const nsTArray<int32_t>& aTiltYs,
+ const nsTArray<int32_t>& aTwists, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, bool aToWindow, bool* aPreventDefault) {
+ // get the widget to send the event to
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
+ if (!widget) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ EventMessage msg;
+ if (aType.EqualsLiteral("touchstart")) {
+ msg = eTouchStart;
+ } else if (aType.EqualsLiteral("touchmove")) {
+ msg = eTouchMove;
+ } else if (aType.EqualsLiteral("touchend")) {
+ msg = eTouchEnd;
+ } else if (aType.EqualsLiteral("touchcancel")) {
+ msg = eTouchCancel;
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ WidgetTouchEvent event(true, msg, widget);
+ event.mModifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t count = aIdentifiers.Length();
+ if (aXs.Length() != count || aYs.Length() != count ||
+ aRxs.Length() != count || aRys.Length() != count ||
+ aRotationAngles.Length() != count || aForces.Length() != count) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ event.mTouches.SetCapacity(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ LayoutDeviceIntPoint pt = nsContentUtils::ToWidgetPoint(
+ CSSPoint(aXs[i], aYs[i]), offset, presContext);
+ LayoutDeviceIntPoint radius = LayoutDeviceIntPoint::FromAppUnitsRounded(
+ CSSPoint::ToAppUnits(CSSPoint(aRxs[i], aRys[i])),
+ presContext->AppUnitsPerDevPixel());
+
+ RefPtr<Touch> t = new Touch(aIdentifiers[i], pt, radius, aRotationAngles[i],
+ aForces[i], aTiltXs[i], aTiltYs[i], aTwists[i]);
+
+ event.mTouches.AppendElement(t);
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (aToWindow) {
+ RefPtr<PresShell> presShell;
+ nsView* view = nsContentUtils::GetViewToDispatchEvent(
+ presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+
+ if (StaticPrefs::test_events_async_enabled()) {
+ status = widget->DispatchInputEvent(&event).mContentStatus;
+ } else {
+ nsresult rv = widget->DispatchEvent(&event, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPreventDefault) {
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ }
+ return NS_OK;
+}
+
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::CAPS_LOCK) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_CAPS_LOCK),
+ "Need to sync CapsLock value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::NUM_LOCK) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_NUM_LOCK),
+ "Need to sync NumLock value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::SHIFT_L) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_SHIFT_LEFT),
+ "Need to sync ShiftLeft value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::SHIFT_R) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_SHIFT_RIGHT),
+ "Need to sync ShiftRight value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::CTRL_L) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_CONTROL_LEFT),
+ "Need to sync ControlLeft value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::CTRL_R) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_CONTROL_RIGHT),
+ "Need to sync ControlRight value between nsIWidget::Modifiers "
+ "and nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::ALT_L) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_ALT_LEFT),
+ "Need to sync AltLeft value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::ALT_R) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_ALT_RIGHT),
+ "Need to sync AltRight value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::COMMAND_L) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_COMMAND_LEFT),
+ "Need to sync CommandLeft value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::COMMAND_R) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_COMMAND_RIGHT),
+ "Need to sync CommandRight value between nsIWidget::Modifiers "
+ "and nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::HELP) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_HELP),
+ "Need to sync Help value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::ALTGRAPH) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_ALT_GRAPH),
+ "Need to sync AltGraph value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(
+ static_cast<uint32_t>(nsIWidget::Modifiers::FUNCTION) ==
+ static_cast<uint32_t>(nsIDOMWindowUtils::NATIVE_MODIFIER_FUNCTION),
+ "Need to sync Function value between nsIWidget::Modifiers and "
+ "nsIDOMWindowUtils");
+static_assert(static_cast<uint32_t>(nsIWidget::Modifiers::NUMERIC_KEY_PAD) ==
+ static_cast<uint32_t>(
+ nsIDOMWindowUtils::NATIVE_MODIFIER_NUMERIC_KEY_PAD),
+ "Need to sync NumericKeyPad value between nsIWidget::Modifiers "
+ "and nsIDOMWindowUtils");
+
+static nsIWidget::Modifiers GetWidgetModifiers(uint32_t aNativeModifiers) {
+ nsIWidget::Modifiers widgetModifiers = static_cast<nsIWidget::Modifiers>(
+ aNativeModifiers &
+ (nsIWidget::Modifiers::CAPS_LOCK | nsIWidget::Modifiers::NUM_LOCK |
+ nsIWidget::Modifiers::SHIFT_L | nsIWidget::Modifiers::SHIFT_R |
+ nsIWidget::Modifiers::CTRL_L | nsIWidget::Modifiers::CTRL_R |
+ nsIWidget::Modifiers::ALT_L | nsIWidget::Modifiers::ALT_R |
+ nsIWidget::Modifiers::COMMAND_L | nsIWidget::Modifiers::COMMAND_R |
+ nsIWidget::Modifiers::HELP | nsIWidget::Modifiers::ALTGRAPH |
+ nsIWidget::Modifiers::FUNCTION | nsIWidget::Modifiers::NUMERIC_KEY_PAD));
+ NS_ASSERTION(static_cast<uint32_t>(widgetModifiers) == aNativeModifiers,
+ "Invalid value is specified to the native modifiers");
+ return widgetModifiers;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifiers,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<int32_t, int32_t, uint32_t, nsString, nsString,
+ nsIObserver*>(
+ "nsIWidget::SynthesizeNativeKeyEvent", widget,
+ &nsIWidget::SynthesizeNativeKeyEvent, aNativeKeyboardLayout,
+ aNativeKeyCode, static_cast<uint32_t>(GetWidgetModifiers(aModifiers)),
+ aCharacters, aUnmodifiedCharacters, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX, int32_t aScreenY,
+ uint32_t aNativeMessage, int16_t aButton,
+ uint32_t aModifierFlags,
+ Element* aElementOnWidget,
+ nsIObserver* aObserver) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElementOnWidget);
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIWidget::NativeMouseMessage message;
+ switch (aNativeMessage) {
+ case NATIVE_MOUSE_MESSAGE_BUTTON_DOWN:
+ message = nsIWidget::NativeMouseMessage::ButtonDown;
+ break;
+ case NATIVE_MOUSE_MESSAGE_BUTTON_UP:
+ message = nsIWidget::NativeMouseMessage::ButtonUp;
+ break;
+ case NATIVE_MOUSE_MESSAGE_MOVE:
+ message = nsIWidget::NativeMouseMessage::Move;
+ break;
+ case NATIVE_MOUSE_MESSAGE_ENTER_WINDOW:
+ message = nsIWidget::NativeMouseMessage::EnterWindow;
+ break;
+ case NATIVE_MOUSE_MESSAGE_LEAVE_WINDOW:
+ message = nsIWidget::NativeMouseMessage::LeaveWindow;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<LayoutDeviceIntPoint, nsIWidget::NativeMouseMessage,
+ MouseButton, nsIWidget::Modifiers, nsIObserver*>(
+ "nsIWidget::SynthesizeNativeMouseEvent", widget,
+ &nsIWidget::SynthesizeNativeMouseEvent,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), message,
+ static_cast<MouseButton>(aButton), GetWidgetModifiers(aModifierFlags),
+ aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeMouseScrollEvent(
+ int32_t aScreenX, int32_t aScreenY, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, Element* aElement, nsIObserver* aObserver) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<mozilla::LayoutDeviceIntPoint, uint32_t, double, double,
+ double, uint32_t, uint32_t, nsIObserver*>(
+ "nsIWidget::SynthesizeNativeMouseScrollEvent", widget,
+ &nsIWidget::SynthesizeNativeMouseScrollEvent,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aNativeMessage, aDeltaX,
+ aDeltaY, aDeltaZ, aModifierFlags, aAdditionalFlags, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchPoint(uint32_t aPointerId,
+ uint32_t aTouchState, int32_t aScreenX,
+ int32_t aScreenY, double aPressure,
+ uint32_t aOrientation,
+ nsIObserver* aObserver) {
+ // FYI: This was designed for automated tests, but currently, this is used by
+ // DevTools to emulate touch events from mouse events in the responsive
+ // design mode.
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<uint32_t, nsIWidget::TouchPointerState,
+ LayoutDeviceIntPoint, double, uint32_t, nsIObserver*>(
+ "nsIWidget::SynthesizeNativeTouchPoint", widget,
+ &nsIWidget::SynthesizeNativeTouchPoint, aPointerId,
+ (nsIWidget::TouchPointerState)aTouchState,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aPressure, aOrientation,
+ aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchpadPinch(uint32_t aEventPhase, float aScale,
+ int32_t aScreenX, int32_t aScreenY,
+ int32_t aModifierFlags) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<nsIWidget::TouchpadGesturePhase, float,
+ LayoutDeviceIntPoint, int32_t>(
+ "nsIWidget::SynthesizeNativeTouchPadPinch", widget,
+ &nsIWidget::SynthesizeNativeTouchPadPinch,
+ (nsIWidget::TouchpadGesturePhase)aEventPhase, aScale,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aModifierFlags)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchTap(int32_t aScreenX, int32_t aScreenY,
+ bool aLongTap, nsIObserver* aObserver) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<LayoutDeviceIntPoint, bool, nsIObserver*>(
+ "nsIWidget::SynthesizeNativeTouchTap", widget,
+ &nsIWidget::SynthesizeNativeTouchTap,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aLongTap, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativePenInput(uint32_t aPointerId,
+ uint32_t aPointerState, int32_t aScreenX,
+ int32_t aScreenY, double aPressure,
+ uint32_t aRotation, int32_t aTiltX,
+ int32_t aTiltY, int32_t aButton,
+ nsIObserver* aObserver) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPressure < 0 || aPressure > 1 || aRotation > 359 || aTiltX < -90 ||
+ aTiltX > 90 || aTiltY < -90 || aTiltY > 90) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<uint32_t, nsIWidget::TouchPointerState,
+ LayoutDeviceIntPoint, double, uint32_t, int32_t,
+ int32_t, int32_t, nsIObserver*>(
+ "nsIWidget::SynthesizeNativePenInput", widget,
+ &nsIWidget::SynthesizeNativePenInput, aPointerId,
+ (nsIWidget::TouchPointerState)aPointerState,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aPressure, aRotation,
+ aTiltX, aTiltY, aButton, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchpadDoubleTap(int32_t aScreenX,
+ int32_t aScreenY,
+ int32_t aModifierFlags) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(aModifierFlags >= 0);
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<LayoutDeviceIntPoint, uint32_t>(
+ "nsIWidget::SynthesizeNativeTouchpadDoubleTap", widget,
+ &nsIWidget::SynthesizeNativeTouchpadDoubleTap,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aModifierFlags)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchpadPan(uint32_t aEventPhase, int32_t aScreenX,
+ int32_t aScreenY, double aDeltaX,
+ double aDeltaY, int32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(aModifierFlags >= 0);
+ NS_DispatchToMainThread(NativeInputRunnable::Create(
+ NewRunnableMethod<nsIWidget::TouchpadGesturePhase, LayoutDeviceIntPoint,
+ double, double, uint32_t, nsIObserver*>(
+ "nsIWidget::SynthesizeNativeTouchpadPan", widget,
+ &nsIWidget::SynthesizeNativeTouchpadPan,
+ (nsIWidget::TouchpadGesturePhase)aEventPhase,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aDeltaX, aDeltaY,
+ aModifierFlags, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SuppressAnimation(bool aSuppress) {
+ nsIWidget* widget = GetWidget();
+ if (widget) {
+ widget->SuppressAnimation(aSuppress);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ClearSharedStyleSheetCache() {
+ SharedStyleSheetCache::Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetParsedStyleSheets(uint32_t* aSheets) {
+ RefPtr<Document> doc = GetDocument();
+ if (!doc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aSheets = doc->CSSLoader()->ParsedSheetCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(
+ NativeInputRunnable::Create(NewRunnableMethod<nsIObserver*>(
+ "nsIWidget::ClearNativeTouchSequence", widget,
+ &nsIWidget::ClearNativeTouchSequence, aObserver)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ return widget->ActivateNativeMenuItemAt(indexString);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ForceUpdateNativeMenuAt(const nsAString& indexString) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ return widget->ForceUpdateNativeMenuAt(indexString);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetSelectionAsPlaintext(nsAString& aResult) {
+ // Get the widget to send the event to.
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return widget->GetSelectionAsPlaintext(aResult);
+}
+
+nsIWidget* nsDOMWindowUtils::GetWidget(nsPoint* aOffset) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (window) {
+ nsIDocShell* docShell = window->GetDocShell();
+ if (docShell) {
+ return nsContentUtils::GetWidget(docShell->GetPresShell(), aOffset);
+ }
+ }
+
+ return nullptr;
+}
+
+nsIWidget* nsDOMWindowUtils::GetWidgetForElement(Element* aElement) {
+ if (!aElement) {
+ return GetWidget();
+ }
+ if (Document* doc = aElement->GetUncomposedDoc()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ frame = presShell->GetRootFrame();
+ }
+ if (frame) {
+ return frame->GetNearestWidget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener* aListener) {
+ AUTO_PROFILER_LABEL("nsDOMWindowUtils::GarbageCollect", GCCC);
+
+ nsJSContext::GarbageCollectNow(JS::GCReason::DOM_UTILS);
+ nsJSContext::CycleCollectNow(CCReason::API, aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CycleCollect(nsICycleCollectorListener* aListener) {
+ nsJSContext::CycleCollectNow(CCReason::API, aListener);
+ return NS_OK;
+}
+
+static bool ParseGCReason(const nsACString& aStr, JS::GCReason* aReason,
+ JS::GCReason aDefault) {
+ if (aStr.IsEmpty()) {
+ *aReason = aDefault;
+ return true;
+ }
+#define CHECK_REASON(name, _) \
+ if (aStr.EqualsIgnoreCase(#name)) { \
+ *aReason = JS::GCReason::name; \
+ return true; \
+ }
+ GCREASONS(CHECK_REASON);
+ return false;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RunNextCollectorTimer(const nsACString& aReason) {
+ JS::GCReason reason;
+ if (!ParseGCReason(aReason, &reason, JS::GCReason::DOM_WINDOW_UTILS)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsJSContext::RunNextCollectorTimer(reason);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::PokeGC(const nsACString& aReason) {
+ JS::GCReason reason;
+ if (!ParseGCReason(aReason, &reason, JS::GCReason::DOM_WINDOW_UTILS)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsJSContext::PokeGC(reason, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendSimpleGestureEvent(const nsAString& aType, float aX,
+ float aY, uint32_t aDirection,
+ double aDelta, int32_t aModifiers,
+ uint32_t aClickCount) {
+ // get the widget to send the event to
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
+ if (!widget) return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ if (aType.EqualsLiteral("MozSwipeGestureMayStart")) {
+ msg = eSwipeGestureMayStart;
+ } else if (aType.EqualsLiteral("MozSwipeGestureStart")) {
+ msg = eSwipeGestureStart;
+ } else if (aType.EqualsLiteral("MozSwipeGestureUpdate")) {
+ msg = eSwipeGestureUpdate;
+ } else if (aType.EqualsLiteral("MozSwipeGestureEnd")) {
+ msg = eSwipeGestureEnd;
+ } else if (aType.EqualsLiteral("MozSwipeGesture")) {
+ msg = eSwipeGesture;
+ } else if (aType.EqualsLiteral("MozMagnifyGestureStart")) {
+ msg = eMagnifyGestureStart;
+ } else if (aType.EqualsLiteral("MozMagnifyGestureUpdate")) {
+ msg = eMagnifyGestureUpdate;
+ } else if (aType.EqualsLiteral("MozMagnifyGesture")) {
+ msg = eMagnifyGesture;
+ } else if (aType.EqualsLiteral("MozRotateGestureStart")) {
+ msg = eRotateGestureStart;
+ } else if (aType.EqualsLiteral("MozRotateGestureUpdate")) {
+ msg = eRotateGestureUpdate;
+ } else if (aType.EqualsLiteral("MozRotateGesture")) {
+ msg = eRotateGesture;
+ } else if (aType.EqualsLiteral("MozTapGesture")) {
+ msg = eTapGesture;
+ } else if (aType.EqualsLiteral("MozPressTapGesture")) {
+ msg = ePressTapGesture;
+ } else if (aType.EqualsLiteral("MozEdgeUIStarted")) {
+ msg = eEdgeUIStarted;
+ } else if (aType.EqualsLiteral("MozEdgeUICanceled")) {
+ msg = eEdgeUICanceled;
+ } else if (aType.EqualsLiteral("MozEdgeUICompleted")) {
+ msg = eEdgeUICompleted;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetSimpleGestureEvent event(true, msg, widget);
+ event.mModifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
+ event.mDirection = aDirection;
+ event.mDelta = aDelta;
+ event.mClickCount = aClickCount;
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) return NS_ERROR_FAILURE;
+
+ event.mRefPoint =
+ nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+
+ nsEventStatus status;
+ return widget->DispatchEvent(&event, status);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ElementFromPoint(float aX, float aY,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout, Element** aReturn) {
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ RefPtr<Element> el = doc->ElementFromPointHelper(
+ aX, aY, aIgnoreRootScrollFrame, aFlushLayout, ViewportType::Layout);
+ el.forget(aReturn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::NodesFromRect(float aX, float aY, float aTopSize,
+ float aRightSize, float aBottomSize,
+ float aLeftSize, bool aIgnoreRootScrollFrame,
+ bool aFlushLayout, bool aOnlyVisible,
+ float aVisibleThreshold,
+ nsINodeList** aReturn) {
+ RefPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ auto list = MakeRefPtr<nsSimpleContentList>(doc);
+
+ // The visible threshold was omitted or given a zero value (which makes no
+ // sense), so give a reasonable default.
+ if (aVisibleThreshold == 0.0f) {
+ aVisibleThreshold = 1.0f;
+ }
+
+ AutoTArray<RefPtr<nsINode>, 8> nodes;
+ doc->NodesFromRect(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize,
+ aIgnoreRootScrollFrame, aFlushLayout, aOnlyVisible,
+ aVisibleThreshold, nodes);
+ list->SetCapacity(nodes.Length());
+ for (auto& node : nodes) {
+ list->AppendElement(node->AsContent());
+ }
+
+ list.forget(aReturn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetTranslationNodes(nsINode* aRoot,
+ nsITranslationNodeList** aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ nsCOMPtr<nsIContent> root = do_QueryInterface(aRoot);
+ NS_ENSURE_STATE(root);
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ if (root->OwnerDoc() != doc) {
+ return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
+ }
+
+ nsTHashSet<nsIContent*> translationNodesHash(500);
+ RefPtr<nsTranslationNodeList> list = new nsTranslationNodeList;
+
+ uint32_t limit = 15000;
+
+ // We begin iteration with content->GetNextNode because we want to explictly
+ // skip the root tag from being a translation node.
+ nsIContent* content = root;
+ while ((limit > 0) && (content = content->GetNextNode(root))) {
+ if (!content->IsHTMLElement()) {
+ continue;
+ }
+
+ // Skip elements that usually contain non-translatable text content.
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::iframe,
+ nsGkAtoms::frameset, nsGkAtoms::frame,
+ nsGkAtoms::code, nsGkAtoms::noscript,
+ nsGkAtoms::style)) {
+ continue;
+ }
+
+ // An element is a translation node if it contains
+ // at least one text node that has meaningful data
+ // for translation
+ for (nsIContent* child = content->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsText() && child->GetAsText()->HasTextForTranslation()) {
+ translationNodesHash.Insert(content);
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ bool isTranslationRoot = frame && frame->IsBlockFrameOrSubclass();
+ if (!isTranslationRoot) {
+ // If an element is not a block element, it still
+ // can be considered a translation root if the parent
+ // of this element didn't make into the list of nodes
+ // to be translated.
+ bool parentInList = false;
+ nsIContent* parent = content->GetParent();
+ if (parent) {
+ parentInList = translationNodesHash.Contains(parent);
+ }
+ isTranslationRoot = !parentInList;
+ }
+
+ list->AppendElement(content, isTranslationRoot);
+ --limit;
+ break;
+ }
+ }
+ }
+
+ *aRetVal = list.forget().take();
+ return NS_OK;
+}
+
+static already_AddRefed<DataSourceSurface> CanvasToDataSourceSurface(
+ HTMLCanvasElement* aCanvas) {
+ MOZ_ASSERT(aCanvas);
+ SurfaceFromElementResult result = nsLayoutUtils::SurfaceFromElement(aCanvas);
+
+ MOZ_ASSERT(result.GetSourceSurface());
+ return result.GetSourceSurface()->GetDataSurface();
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CompareCanvases(nsISupports* aCanvas1, nsISupports* aCanvas2,
+ uint32_t* aMaxDifference, uint32_t* retVal) {
+ nsCOMPtr<nsIContent> contentCanvas1 = do_QueryInterface(aCanvas1);
+ nsCOMPtr<nsIContent> contentCanvas2 = do_QueryInterface(aCanvas2);
+ auto* canvas1 = HTMLCanvasElement::FromNodeOrNull(contentCanvas1);
+ auto* canvas2 = HTMLCanvasElement::FromNodeOrNull(contentCanvas2);
+
+ if (NS_WARN_IF(!canvas1) || NS_WARN_IF(!canvas2)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DataSourceSurface> img1 = CanvasToDataSourceSurface(canvas1);
+ RefPtr<DataSourceSurface> img2 = CanvasToDataSourceSurface(canvas2);
+
+ if (NS_WARN_IF(!img1) || NS_WARN_IF(!img2) ||
+ NS_WARN_IF(img1->GetSize() != img2->GetSize())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (img1->Equals(img2)) {
+ // They point to the same underlying content.
+ return NS_OK;
+ }
+
+ DataSourceSurface::ScopedMap map1(img1, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap map2(img2, DataSourceSurface::READ);
+
+ if (NS_WARN_IF(!map1.IsMapped()) || NS_WARN_IF(!map2.IsMapped())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int v;
+ IntSize size = img1->GetSize();
+ int32_t stride1 = map1.GetStride();
+ int32_t stride2 = map2.GetStride();
+
+ // we can optimize for the common all-pass case
+ if (stride1 == stride2 && stride1 == size.width * 4) {
+ v = memcmp(map1.GetData(), map2.GetData(), size.width * size.height * 4);
+ if (v == 0) {
+ if (aMaxDifference) *aMaxDifference = 0;
+ *retVal = 0;
+ return NS_OK;
+ }
+ }
+
+ uint32_t dc = 0;
+ uint32_t different = 0;
+
+ for (int j = 0; j < size.height; j++) {
+ unsigned char* p1 = map1.GetData() + j * stride1;
+ unsigned char* p2 = map2.GetData() + j * stride2;
+ v = memcmp(p1, p2, size.width * 4);
+
+ if (v) {
+ for (int i = 0; i < size.width; i++) {
+ if (*(uint32_t*)p1 != *(uint32_t*)p2) {
+ different++;
+
+ dc = std::max((uint32_t)abs(p1[0] - p2[0]), dc);
+ dc = std::max((uint32_t)abs(p1[1] - p2[1]), dc);
+ dc = std::max((uint32_t)abs(p1[2] - p2[2]), dc);
+ dc = std::max((uint32_t)abs(p1[3] - p2[3]), dc);
+ }
+
+ p1 += 4;
+ p2 += 4;
+ }
+ }
+ }
+
+ if (aMaxDifference) *aMaxDifference = dc;
+
+ *retVal = different;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsMozAfterPaintPending(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) return NS_OK;
+ *aResult = presContext->IsDOMPaintEventPending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsInputTaskManagerSuspended(bool* aResult) {
+ *aResult = InputTaskManager::Get()->IsSuspended();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DisableNonTestMouseEvents(bool aDisable) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ PresShell* presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+ presShell->DisableNonTestMouseEvents(aDisable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SuppressEventHandling(bool aSuppress) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ if (aSuppress) {
+ window->SuppressEventHandling();
+ } else {
+ window->UnsuppressEventHandling();
+ }
+
+ return NS_OK;
+}
+
+static nsresult getScrollXYAppUnits(const nsWeakPtr& aWindow, bool aFlushLayout,
+ nsPoint& aScrollPos) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(aWindow);
+ nsCOMPtr<Document> doc = window ? window->GetExtantDoc() : nullptr;
+ NS_ENSURE_STATE(doc);
+
+ if (aFlushLayout) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ if (PresShell* presShell = doc->GetPresShell()) {
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ aScrollPos = sf->GetScrollPosition();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetScrollXY(bool aFlushLayout, int32_t* aScrollX,
+ int32_t* aScrollY) {
+ nsPoint scrollPos(0, 0);
+ nsresult rv = getScrollXYAppUnits(mWindow, aFlushLayout, scrollPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aScrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
+ *aScrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetScrollXYFloat(bool aFlushLayout, float* aScrollX,
+ float* aScrollY) {
+ nsPoint scrollPos(0, 0);
+ nsresult rv = getScrollXYAppUnits(mWindow, aFlushLayout, scrollPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aScrollX = nsPresContext::AppUnitsToFloatCSSPixels(scrollPos.x);
+ *aScrollY = nsPresContext::AppUnitsToFloatCSSPixels(scrollPos.y);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ScrollToVisual(float aOffsetX, float aOffsetY,
+ int32_t aUpdateType, int32_t aScrollMode) {
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsPresContext* presContext = doc->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_NOT_AVAILABLE);
+
+ // This should only be called on the root content document.
+ NS_ENSURE_TRUE(presContext->IsRootContentDocumentCrossProcess(),
+ NS_ERROR_INVALID_ARG);
+
+ FrameMetrics::ScrollOffsetUpdateType updateType;
+ switch (aUpdateType) {
+ case UPDATE_TYPE_RESTORE:
+ updateType = FrameMetrics::eRestore;
+ break;
+ case UPDATE_TYPE_MAIN_THREAD:
+ updateType = FrameMetrics::eMainThread;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ScrollMode scrollMode;
+ switch (aScrollMode) {
+ case SCROLL_MODE_INSTANT:
+ scrollMode = ScrollMode::Instant;
+ break;
+ case SCROLL_MODE_SMOOTH:
+ scrollMode = ScrollMode::SmoothMsd;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ presContext->PresShell()->ScrollToVisual(
+ CSSPoint::ToAppUnits(CSSPoint(aOffsetX, aOffsetY)), updateType,
+ scrollMode);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetVisualViewportOffsetRelativeToLayoutViewport(
+ float* aOffsetX, float* aOffsetY) {
+ *aOffsetX = 0;
+ *aOffsetY = 0;
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ PresShell* presShell = doc->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
+
+ nsPoint offset = presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
+ *aOffsetX = nsPresContext::AppUnitsToFloatCSSPixels(offset.x);
+ *aOffsetY = nsPresContext::AppUnitsToFloatCSSPixels(offset.y);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetVisualViewportOffset(int32_t* aOffsetX,
+ int32_t* aOffsetY) {
+ *aOffsetX = 0;
+ *aOffsetY = 0;
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ PresShell* presShell = doc->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
+
+ nsPoint offset = presShell->GetVisualViewportOffset();
+ *aOffsetX = nsPresContext::AppUnitsToIntCSSPixels(offset.x);
+ *aOffsetY = nsPresContext::AppUnitsToIntCSSPixels(offset.y);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::TransformRectLayoutToVisual(float aX, float aY, float aWidth,
+ float aHeight,
+ DOMRect** aResult) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
+
+ CSSRect rect(aX, aY, aWidth, aHeight);
+ rect = ViewportUtils::DocumentRelativeLayoutToVisual(rect, presShell);
+
+ RefPtr<DOMRect> outRect = new DOMRect(window);
+ outRect->SetRect(rect.x, rect.y, rect.width, rect.height);
+ outRect.forget(aResult);
+ return NS_OK;
+}
+
+Result<mozilla::ScreenRect, nsresult> nsDOMWindowUtils::ConvertToScreenRect(
+ float aX, float aY, float aWidth, float aHeight) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ // Note that if the document is NOT in OOP iframes, i.e. it's in the top level
+ // content subtree in the same process,
+ // nsIWidget::WidgetToTopLevelWidgetTransform() doesn't include the desktop
+ // zoom value, so for documents in the top level content document subtree,
+ // this ViewportUtils::DocumentRelativeLayoutToVisual call applies the desktop
+ // zoom value via PresShell::GetResolution() in the function.
+ CSSRect rect(aX, aY, aWidth, aHeight);
+ rect = ViewportUtils::DocumentRelativeLayoutToVisual(rect, presShell);
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ MOZ_ASSERT(presContext);
+
+ // For OOP iframe documents, we don't have desktop zoom value specifically in
+ // each iframe documents (i.e. the in-process root presshell's resolution is
+ // 1.0), instead nsIWidget::WidgetToTopLevelWidgetTransform() includes the
+ // desktop zoom scale value along with translations by ancestor scroll
+ // containers, ancestor CSS transforms, etc.
+ nsRect appUnitsRect = CSSPixel::ToAppUnits(rect);
+ LayoutDeviceRect devPixelsRect = LayoutDeviceRect::FromAppUnits(
+ appUnitsRect, presContext->AppUnitsPerDevPixel());
+ devPixelsRect =
+ widget->WidgetToTopLevelWidgetTransform().TransformBounds(devPixelsRect) +
+ widget->TopLevelWidgetToScreenOffset();
+
+ return ViewAs<ScreenPixel>(
+ devPixelsRect, PixelCastJustification::ScreenIsParentLayerForRoot);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ToScreenRectInCSSUnits(float aX, float aY, float aWidth,
+ float aHeight, DOMRect** aResult) {
+ ScreenRect rect;
+ MOZ_TRY_VAR(rect, ConvertToScreenRect(aX, aY, aWidth, aHeight));
+
+ nsPresContext* presContext = GetPresContext();
+ MOZ_ASSERT(presContext);
+
+ const auto devRect = ViewAs<LayoutDevicePixel>(
+ rect, PixelCastJustification::ScreenIsParentLayerForRoot);
+
+ // We want to return the screen rect in CSS units of the browser chrome.
+ //
+ // TODO(emilio): It'd be cleaner to convert callers to use plain toScreenRect,
+ // and perform the screen -> CSS rect in the parent process instead, probably.
+ const nsRect appUnitsRect = LayoutDeviceRect::ToAppUnits(
+ devRect,
+ presContext->DeviceContext()->AppUnitsPerDevPixelInTopLevelChromePage());
+
+ RefPtr<DOMRect> outRect = new DOMRect(mWindow);
+ outRect->SetLayoutRect(appUnitsRect);
+
+ outRect.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ToScreenRect(float aX, float aY, float aWidth, float aHeight,
+ DOMRect** aResult) {
+ ScreenRect rect;
+ MOZ_TRY_VAR(rect, ConvertToScreenRect(aX, aY, aWidth, aHeight));
+
+ RefPtr<DOMRect> outRect = new DOMRect(mWindow);
+ outRect->SetRect(rect.x, rect.y, rect.width, rect.height);
+ outRect.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ConvertFromParentProcessWidgetToLocal(float aX, float aY,
+ float aWidth,
+ float aHeight,
+ DOMRect** aResult) {
+ if (!XRE_IsContentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LayoutDeviceRect devPixelsRect = LayoutDeviceRect(aX, aY, aWidth, aHeight);
+
+ Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> inverse =
+ widget->WidgetToTopLevelWidgetTransform().MaybeInverse();
+ if (inverse) {
+ Maybe<LayoutDeviceRect> rect =
+ UntransformBy(*inverse, devPixelsRect, LayoutDeviceRect::MaxIntRect());
+ if (rect) {
+ RefPtr<DOMRect> outRect = new DOMRect(mWindow);
+ outRect->SetRect(rect->x, rect->y, rect->width, rect->height);
+ outRect.forget(aResult);
+ return NS_OK;
+ }
+ }
+
+ RefPtr<DOMRect> outRect = new DOMRect(mWindow);
+ outRect->SetRect(0, 0, 0, 0);
+ outRect.forget(aResult);
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDynamicToolbarMaxHeight(uint32_t aHeightInScreen) {
+ if (aHeightInScreen > INT32_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (!presContext) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(presContext->IsRootContentDocumentCrossProcess());
+
+ presContext->SetDynamicToolbarMaxHeight(ScreenIntCoord(aHeightInScreen));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetScrollbarSize(bool aFlushLayout, int32_t* aWidth,
+ int32_t* aHeight) {
+ *aWidth = 0;
+ *aHeight = 0;
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ if (aFlushLayout) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
+
+ nsIScrollableFrame* scrollFrame = presShell->GetRootScrollFrameAsScrollable();
+ NS_ENSURE_TRUE(scrollFrame, NS_OK);
+
+ nsMargin sizes = scrollFrame->GetActualScrollbarSizes();
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(sizes.LeftRight());
+ *aHeight = nsPresContext::AppUnitsToIntCSSPixels(sizes.TopBottom());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetBoundsWithoutFlushing(Element* aElement,
+ DOMRect** aResult) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ NS_ENSURE_ARG_POINTER(aElement);
+
+ RefPtr<DOMRect> rect = new DOMRect(window);
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+
+ if (frame) {
+ nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(
+ frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ rect->SetLayoutRect(r);
+ }
+
+ rect.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::NeedsFlush(int32_t aFlushType, bool* aResult) {
+ MOZ_ASSERT(aResult);
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ PresShell* presShell = doc->GetPresShell();
+ NS_ENSURE_STATE(presShell);
+
+ FlushType flushType;
+ switch (aFlushType) {
+ case FLUSH_STYLE:
+ flushType = FlushType::Style;
+ break;
+
+ case FLUSH_LAYOUT:
+ flushType = FlushType::Layout;
+ break;
+
+ case FLUSH_DISPLAY:
+ flushType = FlushType::Display;
+ break;
+
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = presShell->NeedFlush(flushType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::FlushLayoutWithoutThrottledAnimations() {
+ nsCOMPtr<Document> doc = GetDocument();
+ if (doc) {
+ doc->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Layout, false /* flush animations */));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetRootBounds(DOMRect** aResult) {
+ Document* doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsRect bounds(0, 0, 0, 0);
+ PresShell* presShell = doc->GetPresShell();
+ if (presShell) {
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ bounds = sf->GetScrollRange();
+ bounds.SetWidth(bounds.Width() + sf->GetScrollPortRect().Width());
+ bounds.SetHeight(bounds.Height() + sf->GetScrollPortRect().Height());
+ } else if (presShell->GetRootFrame()) {
+ bounds = presShell->GetRootFrame()->GetRect();
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ RefPtr<DOMRect> rect = new DOMRect(window);
+ rect->SetRect(nsPresContext::AppUnitsToFloatCSSPixels(bounds.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(bounds.y),
+ nsPresContext::AppUnitsToFloatCSSPixels(bounds.Width()),
+ nsPresContext::AppUnitsToFloatCSSPixels(bounds.Height()));
+ rect.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIMEIsOpen(bool* aState) {
+ NS_ENSURE_ARG_POINTER(aState);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ // Open state should not be available when IME is not enabled.
+ InputContext context = widget->GetInputContext();
+ if (context.mIMEState.mEnabled != IMEEnabled::Enabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (context.mIMEState.mOpen == IMEState::OPEN_STATE_NOT_SUPPORTED) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ *aState = (context.mIMEState.mOpen == IMEState::OPEN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIMEStatus(uint32_t* aState) {
+ NS_ENSURE_ARG_POINTER(aState);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ InputContext context = widget->GetInputContext();
+ *aState = static_cast<uint32_t>(context.mIMEState.mEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetInputContextURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> documentURI = widget->GetInputContext().mURI;
+ documentURI.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetInputContextOrigin(uint32_t* aOrigin) {
+ NS_ENSURE_ARG_POINTER(aOrigin);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ InputContext context = widget->GetInputContext();
+ static_assert(static_cast<uint32_t>(InputContext::Origin::ORIGIN_MAIN) ==
+ INPUT_CONTEXT_ORIGIN_MAIN);
+ static_assert(static_cast<uint32_t>(InputContext::Origin::ORIGIN_CONTENT) ==
+ INPUT_CONTEXT_ORIGIN_CONTENT);
+ MOZ_ASSERT(context.mOrigin == InputContext::Origin::ORIGIN_MAIN ||
+ context.mOrigin == InputContext::Origin::ORIGIN_CONTENT);
+ *aOrigin = static_cast<uint32_t>(context.mOrigin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetNodeObservedByIMEContentObserver(nsINode** aNode) {
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ IMEContentObserver* observer = IMEStateManager::GetActiveContentObserver();
+ if (!observer) {
+ *aNode = nullptr;
+ return NS_OK;
+ }
+ *aNode = do_AddRef(observer->GetObservingElement()).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCanvasBackgroundColor(nsAString& aColor) {
+ if (RefPtr<Document> doc = GetDocument()) {
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+ nscolor color = NS_RGB(255, 255, 255);
+ if (PresShell* presShell = GetPresShell()) {
+ color = presShell->ComputeCanvasBackground().mColor;
+ }
+ nsStyleUtil::GetSerializedColorValue(color, aColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFocusedInputType(nsAString& aType) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aType = widget->GetInputContext().mHTMLInputType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFocusedActionHint(nsAString& aType) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aType = widget->GetInputContext().mActionHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFocusedInputMode(nsAString& aInputMode) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ aInputMode = widget->GetInputContext().mHTMLInputMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFocusedAutocapitalize(nsAString& aAutocapitalize) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ aAutocapitalize = widget->GetInputContext().mAutocapitalize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetViewId(Element* aElement, nsViewID* aResult) {
+ if (aElement && nsLayoutUtils::FindIDFor(aElement, aResult)) {
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsDOMWindowUtils::DispatchDOMEventViaPresShellForTesting(
+ nsINode* aTarget, Event* aEvent, bool* aRetVal) {
+ NS_ENSURE_STATE(aEvent);
+ aEvent->SetTrusted(true);
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+ NS_ENSURE_STATE(internalEvent);
+ // This API is currently used only by EventUtils.js. Thus we should always
+ // set mIsSynthesizedForTests to true.
+ internalEvent->mFlags.mIsSynthesizedForTests = true;
+ nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(aTarget);
+ NS_ENSURE_STATE(content);
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (content->OwnerDoc()->GetWindow() != window) {
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ }
+ nsCOMPtr<Document> targetDoc = content->GetUncomposedDoc();
+ NS_ENSURE_STATE(targetDoc);
+ RefPtr<PresShell> targetPresShell = targetDoc->GetPresShell();
+ NS_ENSURE_STATE(targetPresShell);
+
+ targetDoc->FlushPendingNotifications(FlushType::Layout);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ targetPresShell->HandleEventWithTarget(internalEvent, nullptr, content,
+ &status);
+ *aRetVal = (status != nsEventStatus_eConsumeNoDefault);
+ return NS_OK;
+}
+
+static void InitEvent(WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPt = nullptr) {
+ if (aPt) {
+ aEvent.mRefPoint = *aPt;
+ }
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendQueryContentEvent(uint32_t aType, int64_t aOffset,
+ uint32_t aLength, int32_t aX,
+ int32_t aY, uint32_t aAdditionalFlags,
+ nsIQueryContentEventResult** aResult) {
+ *aResult = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ PresShell* presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EventMessage message;
+ switch (aType) {
+ case QUERY_SELECTED_TEXT:
+ message = eQuerySelectedText;
+ break;
+ case QUERY_TEXT_CONTENT:
+ message = eQueryTextContent;
+ break;
+ case QUERY_CARET_RECT:
+ message = eQueryCaretRect;
+ break;
+ case QUERY_TEXT_RECT:
+ message = eQueryTextRect;
+ break;
+ case QUERY_EDITOR_RECT:
+ message = eQueryEditorRect;
+ break;
+ case QUERY_CHARACTER_AT_POINT:
+ message = eQueryCharacterAtPoint;
+ break;
+ case QUERY_TEXT_RECT_ARRAY:
+ message = eQueryTextRectArray;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SelectionType selectionType = SelectionType::eNormal;
+ static const uint32_t kSelectionFlags =
+ QUERY_CONTENT_FLAG_SELECTION_SPELLCHECK |
+ QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT |
+ QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT |
+ QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT |
+ QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT |
+ QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY |
+ QUERY_CONTENT_FLAG_SELECTION_FIND |
+ QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY |
+ QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT;
+ switch (aAdditionalFlags & kSelectionFlags) {
+ case QUERY_CONTENT_FLAG_SELECTION_SPELLCHECK:
+ selectionType = SelectionType::eSpellCheck;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT:
+ selectionType = SelectionType::eIMERawClause;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT:
+ selectionType = SelectionType::eIMESelectedRawClause;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT:
+ selectionType = SelectionType::eIMEConvertedClause;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT:
+ selectionType = SelectionType::eIMESelectedClause;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY:
+ selectionType = SelectionType::eAccessibility;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_FIND:
+ selectionType = SelectionType::eFind;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY:
+ selectionType = SelectionType::eURLSecondary;
+ break;
+ case QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT:
+ selectionType = SelectionType::eURLStrikeout;
+ break;
+ case 0:
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (selectionType != SelectionType::eNormal &&
+ message != eQuerySelectedText) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIWidget> targetWidget = widget;
+ LayoutDeviceIntPoint pt(aX, aY);
+
+ WidgetQueryContentEvent::Options options;
+ options.mUseNativeLineBreak =
+ !(aAdditionalFlags & QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ options.mRelativeToInsertionPoint =
+ (aAdditionalFlags &
+ QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT) != 0;
+ if (options.mRelativeToInsertionPoint) {
+ switch (message) {
+ case eQueryTextContent:
+ case eQueryCaretRect:
+ case eQueryTextRect:
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (aOffset < 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (message == eQueryCharacterAtPoint) {
+ // Looking for the widget at the point.
+ nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
+ presContext->GetRootPresContext(), widget, pt);
+
+ LayoutDeviceIntRect widgetBounds = widget->GetClientBounds();
+ widgetBounds.MoveTo(0, 0);
+
+ // There is no popup frame at the point and the point isn't in our widget,
+ // we cannot process this request.
+ NS_ENSURE_TRUE(popupFrame || widgetBounds.Contains(pt), NS_ERROR_FAILURE);
+
+ // Fire the event on the widget at the point
+ if (popupFrame) {
+ targetWidget = popupFrame->GetNearestWidget();
+ }
+ }
+
+ pt += widget->WidgetToScreenOffset() - targetWidget->WidgetToScreenOffset();
+
+ WidgetQueryContentEvent queryEvent(true, message, targetWidget);
+ InitEvent(queryEvent, &pt);
+
+ switch (message) {
+ case eQueryTextContent:
+ queryEvent.InitForQueryTextContent(aOffset, aLength, options);
+ break;
+ case eQueryCaretRect:
+ queryEvent.InitForQueryCaretRect(aOffset, options);
+ break;
+ case eQueryTextRect:
+ queryEvent.InitForQueryTextRect(aOffset, aLength, options);
+ break;
+ case eQuerySelectedText:
+ queryEvent.InitForQuerySelectedText(selectionType, options);
+ break;
+ case eQueryTextRectArray:
+ queryEvent.InitForQueryTextRectArray(aOffset, aLength, options);
+ break;
+ default:
+ queryEvent.Init(options);
+ break;
+ }
+
+ nsEventStatus status;
+ nsresult rv = targetWidget->DispatchEvent(&queryEvent, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto* result = new nsQueryContentEventResult(std::move(queryEvent));
+ result->SetEventResult(widget);
+ NS_ADDREF(*aResult = result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendSelectionSetEvent(uint32_t aOffset, uint32_t aLength,
+ uint32_t aAdditionalFlags,
+ bool* aResult) {
+ *aResult = false;
+
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, widget);
+ InitEvent(selectionEvent);
+
+ selectionEvent.mOffset = aOffset;
+ selectionEvent.mLength = aLength;
+ selectionEvent.mReversed = (aAdditionalFlags & SELECTION_SET_FLAG_REVERSE);
+ selectionEvent.mUseNativeLineBreak =
+ !(aAdditionalFlags & SELECTION_SET_FLAG_USE_XP_LINE_BREAK);
+
+ nsEventStatus status;
+ nsresult rv = widget->DispatchEvent(&selectionEvent, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = selectionEvent.mSucceeded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType,
+ nsITransferable* aTransferable,
+ const nsAString& aString) {
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ if (aType.EqualsLiteral("cut")) {
+ msg = eContentCommandCut;
+ } else if (aType.EqualsLiteral("copy")) {
+ msg = eContentCommandCopy;
+ } else if (aType.EqualsLiteral("paste")) {
+ msg = eContentCommandPaste;
+ } else if (aType.EqualsLiteral("delete")) {
+ msg = eContentCommandDelete;
+ } else if (aType.EqualsLiteral("undo")) {
+ msg = eContentCommandUndo;
+ } else if (aType.EqualsLiteral("redo")) {
+ msg = eContentCommandRedo;
+ } else if (aType.EqualsLiteral("insertText")) {
+ msg = eContentCommandInsertText;
+ } else if (aType.EqualsLiteral("pasteTransferable")) {
+ msg = eContentCommandPasteTransferable;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetContentCommandEvent event(true, msg, widget);
+ if (msg == eContentCommandInsertText) {
+ event.mString.emplace(aString);
+ }
+ if (msg == eContentCommandPasteTransferable) {
+ event.mTransferable = aTransferable;
+ }
+
+ nsEventStatus status;
+ return widget->DispatchEvent(&event, status);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetClassName(JS::Handle<JS::Value> aObject, JSContext* aCx,
+ char** aName) {
+ // Our argument must be a non-null object.
+ if (aObject.isPrimitive()) {
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+
+ *aName = NS_xstrdup(JS::GetClass(aObject.toObjectOrNull())->name);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetVisitedDependentComputedStyle(
+ Element* aElement, const nsAString& aPseudoElement,
+ const nsAString& aPropertyName, nsAString& aResult) {
+ aResult.Truncate();
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window && aElement);
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = window->GetCurrentInnerWindow();
+ NS_ENSURE_STATE(innerWindow);
+
+ nsCOMPtr<nsICSSDeclaration> decl;
+ {
+ ErrorResult rv;
+ decl = innerWindow->GetComputedStyle(*aElement, aPseudoElement, rv);
+ ENSURE_SUCCESS(rv, rv.StealNSResult());
+ }
+
+ nsAutoCString result;
+
+ static_cast<nsComputedDOMStyle*>(decl.get())->SetExposeVisitedStyle(true);
+ nsresult rv =
+ decl->GetPropertyValue(NS_ConvertUTF16toUTF8(aPropertyName), result);
+ static_cast<nsComputedDOMStyle*>(decl.get())->SetExposeVisitedStyle(false);
+
+ CopyUTF8toUTF16(result, aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnterModalState() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->EnterModalState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LeaveModalState() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->LeaveModalState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsInModalState(bool* retval) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *retval = nsGlobalWindowOuter::Cast(window)->IsInModalState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SuspendTimeouts() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
+ NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
+
+ inner->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ResumeTimeouts() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
+ NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
+
+ inner->Resume();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLayerManagerType(nsAString& aType) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) return NS_ERROR_FAILURE;
+
+ renderer->GetBackendName(aType);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLayerManagerRemote(bool* retval) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) return NS_ERROR_FAILURE;
+
+ *retval = !!renderer->AsKnowsCompositor();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsWebRenderRequested(bool* retval) {
+ *retval = gfxPlatform::WebRenderPrefEnabled() ||
+ gfxPlatform::WebRenderEnvvarEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentAudioBackend(nsAString& aBackend) {
+ CubebUtils::GetCurrentBackend(aBackend);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentMaxAudioChannels(uint32_t* aChannels) {
+ *aChannels = CubebUtils::MaxNumberOfChannels();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentPreferredSampleRate(uint32_t* aRate) {
+ nsCOMPtr<Document> doc = GetDocument();
+ *aRate = CubebUtils::PreferredSampleRate(
+ doc ? doc->ShouldResistFingerprinting(RFPTarget::Unknown)
+ : nsContentUtils::ShouldResistFingerprinting("Fallback",
+ RFPTarget::Unknown));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DefaultDevicesRoundTripLatency(Promise** aOutPromise) {
+ NS_ENSURE_ARG_POINTER(aOutPromise);
+ *aOutPromise = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(outer);
+ nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
+ NS_ENSURE_STATE(inner);
+
+ ErrorResult err;
+ RefPtr<Promise> promise = Promise::Create(inner->AsGlobal(), err);
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+
+ NS_ADDREF(promise.get());
+ void* p = reinterpret_cast<void*>(promise.get());
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("DefaultDevicesRoundTripLatency", [p]() {
+ double mean, stddev;
+ bool success =
+ CubebUtils::EstimatedRoundTripLatencyDefaultDevices(&mean, &stddev);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DefaultDevicesRoundTripLatency", [p, success, mean, stddev]() {
+ Promise* promise = reinterpret_cast<Promise*>(p);
+ if (!success) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ NS_RELEASE(promise);
+ return;
+ }
+ nsTArray<double> a;
+ a.AppendElement(mean);
+ a.AppendElement(stddev);
+ promise->MaybeResolve(a);
+ NS_RELEASE(promise);
+ }));
+ }));
+
+ promise.forget(aOutPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AudioDevices(uint16_t aSide, nsIArray** aDevices) {
+ NS_ENSURE_ARG_POINTER(aDevices);
+ NS_ENSURE_ARG((aSide == AUDIO_INPUT) || (aSide == AUDIO_OUTPUT));
+ *aDevices = nullptr;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> devices =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CubebDeviceEnumerator> enumerator = Enumerator::GetInstance();
+ RefPtr<const CubebDeviceEnumerator::AudioDeviceSet> collection;
+ if (aSide == AUDIO_INPUT) {
+ collection = enumerator->EnumerateAudioInputDevices();
+ } else {
+ collection = enumerator->EnumerateAudioOutputDevices();
+ }
+
+ for (const auto& device : *collection) {
+ devices->AppendElement(device);
+ }
+
+ devices.forget(aDevices);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StartFrameTimeRecording(uint32_t* startIndex) {
+ NS_ENSURE_ARG_POINTER(startIndex);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) return NS_ERROR_FAILURE;
+
+ const uint32_t kRecordingMinSize = 60 * 10; // 10 seconds @60 fps.
+ const uint32_t kRecordingMaxSize = 60 * 60 * 60; // One hour
+ uint32_t bufferSize =
+ Preferences::GetUint("toolkit.framesRecording.bufferSize", uint32_t(0));
+ bufferSize = std::min(bufferSize, kRecordingMaxSize);
+ bufferSize = std::max(bufferSize, kRecordingMinSize);
+ *startIndex = renderer->StartFrameTimeRecording(bufferSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StopFrameTimeRecording(uint32_t startIndex,
+ nsTArray<float>& frameIntervals) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) return NS_ERROR_FAILURE;
+
+ renderer->StopFrameTimeRecording(startIndex, frameIntervals);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
+ // Before we advance the time, we should trigger any animations that are
+ // waiting to start. This is because there are many tests that call this
+ // which expect animations to start immediately. Ideally, we should make
+ // all these tests do an asynchronous wait on the corresponding animation's
+ // 'ready' promise before continuing. Then we could remove the special
+ // handling here and the code path followed when testing would more closely
+ // match the code path during regular operation. Filed as bug 1112957.
+ nsCOMPtr<Document> doc = GetDocument();
+ if (doc) {
+ PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
+ if (tracker) {
+ tracker->TriggerPendingAnimationsNow();
+ }
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ RefPtr<nsRefreshDriver> driver = presContext->RefreshDriver();
+ driver->AdvanceTimeAndRefresh(aMilliseconds);
+
+ if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
+ wrbc->SendSetTestSampleTime(driver->MostRecentRefresh());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLastTransactionId(uint64_t* aLastTransactionId) {
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (!docShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ docShell->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
+ docShell = do_QueryInterface(rootTreeItem);
+ if (!docShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsPresContext* presContext = docShell->GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsRefreshDriver* driver = presContext->RefreshDriver();
+ *aLastTransactionId = uint64_t(driver->LastTransactionId());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RestoreNormalRefresh() {
+ // Kick the compositor out of test mode before the refresh driver, so that
+ // the refresh driver doesn't send an update that gets ignored by the
+ // compositor.
+ if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
+ wrbc->SendLeaveTestMode();
+ }
+
+ if (nsPresContext* pc = GetPresContext()) {
+ nsRefreshDriver* driver = pc->RefreshDriver();
+ driver->RestoreNormalRefresh();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsTestControllingRefreshes(bool* aResult) {
+ nsPresContext* pc = GetPresContext();
+ *aResult =
+ pc ? pc->RefreshDriver()->IsTestControllingRefreshesEnabled() : false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAsyncPanZoomEnabled(bool* aResult) {
+ nsIWidget* widget = GetWidget();
+ if (widget) {
+ *aResult = widget->AsyncPanZoomEnabled();
+ } else {
+ *aResult = gfxPlatform::AsyncPanZoomEnabled();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAsyncScrollOffset(Element* aElement, float aX, float aY) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ ScrollableLayerGuid::ViewID viewId;
+ if (!nsLayoutUtils::FindIDFor(aElement, &viewId)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ return NS_ERROR_FAILURE;
+ }
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ WebRenderBridgeChild* wrbc = wr->WrBridge();
+ if (!wrbc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ wrbc->SendSetAsyncScrollOffset(viewId, aX, aY);
+ return NS_OK;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAsyncZoom(Element* aRootElement, float aValue) {
+ if (!aRootElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ ScrollableLayerGuid::ViewID viewId;
+ if (!nsLayoutUtils::FindIDFor(aRootElement, &viewId)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ return NS_ERROR_FAILURE;
+ }
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ WebRenderBridgeChild* wrbc = wr->WrBridge();
+ if (!wrbc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ wrbc->SendSetAsyncZoom(viewId, aValue);
+ return NS_OK;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::FlushApzRepaints(bool* aOutResult) {
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ *aOutResult = false;
+ return NS_OK;
+ }
+ // If APZ is not enabled, this function is a no-op.
+ if (!widget->AsyncPanZoomEnabled()) {
+ *aOutResult = false;
+ return NS_OK;
+ }
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ *aOutResult = false;
+ return NS_OK;
+ }
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ WebRenderBridgeChild* wrbc = wr->WrBridge();
+ if (!wrbc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ wrbc->SendFlushApzRepaints();
+ *aOutResult = true;
+ return NS_OK;
+ }
+ *aOutResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DisableApzForElement(Element* aElement) {
+ aElement->SetProperty(nsGkAtoms::apzDisabled, reinterpret_cast<void*>(true));
+ nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aElement);
+ if (!sf) {
+ return NS_OK;
+ }
+ nsIFrame* frame = do_QueryFrame(sf);
+ if (!frame) {
+ return NS_OK;
+ }
+ frame->SchedulePaint();
+ return NS_OK;
+}
+
+static nsTArray<nsIScrollableFrame*> CollectScrollableAncestors(
+ nsIFrame* aStart) {
+ nsTArray<nsIScrollableFrame*> result;
+ nsIFrame* frame = aStart;
+ while (frame) {
+ frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
+ if (!frame) {
+ break;
+ }
+ nsIScrollableFrame* scrollAncestor =
+ nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
+ if (!scrollAncestor) {
+ break;
+ }
+ result.AppendElement(scrollAncestor);
+ frame = do_QueryFrame(scrollAncestor);
+ }
+ return result;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ZoomToFocusedInput() {
+ if (!Preferences::GetBool("apz.zoom-to-focused-input.enabled")) {
+ return NS_OK;
+ }
+
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_OK;
+ }
+
+ // If APZ is not enabled, this function is a no-op.
+ //
+ // FIXME(emilio): This is not quite true anymore now that we also
+ // ScrollIntoView() too...
+ if (!widget->AsyncPanZoomEnabled()) {
+ return NS_OK;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> element = fm->GetFocusedElement();
+ if (!element) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell =
+ APZCCallbackHelper::GetRootContentDocumentPresShellForContent(element);
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ bool shouldSkip = [&] {
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (!frame) {
+ return true;
+ }
+
+ // Skip zooming to focused inputs in fixed subtrees, as we'd scroll to the
+ // top unnecessarily, see bug 1627734.
+ //
+ // We could try to teach apz to zoom to a rect only without panning, or
+ // maybe we could give it a rect offsetted by the root scroll position, if
+ // we wanted to do this.
+ for (; frame; frame = nsLayoutUtils::GetCrossDocParentFrame(frame)) {
+ if (frame->PresShell() == presShell) {
+ // Note that we only do this if the frame belongs to `presShell` (that
+ // is, we still zoom in fixed elements in subdocuments, as they're not
+ // fixed to the root content document).
+ return nsLayoutUtils::IsInPositionFixedSubtree(frame);
+ }
+ frame = frame->PresShell()->GetRootFrame();
+ }
+
+ return false;
+ }();
+
+ // The content may be inside a scrollable subframe inside a non-scrollable
+ // root content document. In this scenario, we want to ensure that the
+ // main-thread side knows to scroll the content into view before we get
+ // the bounding content rect and ask APZ to adjust the visual viewport.
+ presShell->ScrollContentIntoView(
+ element, ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollFlags::ScrollOverflowHidden);
+
+ if (shouldSkip) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> document = presShell->GetDocument();
+ if (!document) {
+ return NS_OK;
+ }
+
+ uint32_t presShellId;
+ ScrollableLayerGuid::ViewID viewId;
+ if (!APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ document->GetDocumentElement(), &presShellId, &viewId)) {
+ return NS_OK;
+ }
+
+ TouchBehaviorFlags tbf =
+ layers::TouchActionHelper::GetAllowedTouchBehaviorForFrame(
+ element->GetPrimaryFrame());
+
+ uint32_t flags = layers::DISABLE_ZOOM_OUT;
+ if (!Preferences::GetBool("formhelper.autozoom") ||
+ Preferences::GetBool("formhelper.autozoom.force-disable.test-only",
+ /* aFallback = */ false) ||
+ !(tbf & AllowedTouchBehavior::ANIMATING_ZOOM)) {
+ flags |= layers::PAN_INTO_VIEW_ONLY;
+ } else {
+ flags |= layers::ONLY_ZOOM_TO_DEFAULT_SCALE;
+ }
+
+ nsIScrollableFrame* rootScrollFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ if (!rootScrollFrame) {
+ return NS_OK;
+ }
+
+ CSSRect bounds;
+ if (element->IsHTMLElement(nsGkAtoms::input)) {
+ bounds = nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame);
+ } else {
+ // When focused elment is content editable or <textarea> element,
+ // focused element will have multi-line content.
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (frame) {
+ RefPtr<nsCaret> caret = frame->PresShell()->GetCaret();
+ if (caret && caret->IsVisible()) {
+ nsRect rect;
+ if (nsIFrame* frame = caret->GetGeometry(&rect)) {
+ bounds = nsLayoutUtils::GetBoundingFrameRect(frame, rootScrollFrame);
+ }
+ }
+ }
+ if (bounds.IsEmpty()) {
+ // Fallback if no caret frame.
+ bounds = nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame);
+ }
+ }
+
+ if (bounds.IsEmpty()) {
+ // Do not zoom on empty bounds. Bail out.
+ return NS_OK;
+ }
+
+ bounds.Inflate(15.0f, 0.0f);
+
+ bool waitForRefresh = false;
+ for (nsIScrollableFrame* scrollAncestor :
+ CollectScrollableAncestors(element->GetPrimaryFrame())) {
+ if (scrollAncestor->HasScrollUpdates()) {
+ waitForRefresh = true;
+ break;
+ }
+ }
+ if (waitForRefresh) {
+ waitForRefresh = false;
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ waitForRefresh = true;
+ presContext->RegisterManagedPostRefreshObserver(
+ new ManagedPostRefreshObserver(
+ presContext, [widget = RefPtr<nsIWidget>(widget), presShellId,
+ viewId, bounds, flags](bool aWasCanceled) {
+ if (!aWasCanceled) {
+ widget->ZoomToRect(presShellId, viewId, bounds, flags);
+ }
+ return ManagedPostRefreshObserver::Unregister::Yes;
+ }));
+ }
+ }
+ if (!waitForRefresh) {
+ widget->ZoomToRect(presShellId, viewId, bounds, flags);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ComputeAnimationDistance(Element* aElement,
+ const nsAString& aProperty,
+ const nsAString& aValue1,
+ const nsAString& aValue2,
+ double* aResult) {
+ NS_ENSURE_ARG_POINTER(aElement);
+
+ nsCSSPropertyID property =
+ nsCSSProps::LookupProperty(NS_ConvertUTF16toUTF8(aProperty));
+ if (property == eCSSProperty_UNKNOWN || nsCSSProps::IsShorthand(property)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ AnimationValue v1 = AnimationValue::FromString(
+ property, NS_ConvertUTF16toUTF8(aValue1), aElement);
+ AnimationValue v2 = AnimationValue::FromString(
+ property, NS_ConvertUTF16toUTF8(aValue2), aElement);
+ if (v1.IsNull() || v2.IsNull()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ *aResult = v1.ComputeDistance(property, v2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetUnanimatedComputedStyle(Element* aElement,
+ const nsAString& aPseudoElement,
+ const nsAString& aProperty,
+ int32_t aFlushType,
+ nsAString& aResult) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCSSPropertyID propertyID =
+ nsCSSProps::LookupProperty(NS_ConvertUTF16toUTF8(aProperty));
+ if (propertyID == eCSSProperty_UNKNOWN ||
+ nsCSSProps::IsShorthand(propertyID)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ switch (aFlushType) {
+ case FLUSH_NONE:
+ break;
+ case FLUSH_STYLE: {
+ if (Document* doc = aElement->GetComposedDoc()) {
+ doc->FlushPendingNotifications(FlushType::Style);
+ }
+ break;
+ }
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Maybe<PseudoStyleType> pseudo =
+ nsCSSPseudoElements::GetPseudoType(aPseudoElement);
+ if (!pseudo) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<const ComputedStyle> computedStyle =
+ nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(aElement, *pseudo);
+ if (!computedStyle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<StyleAnimationValue> value =
+ Servo_ComputedValues_ExtractAnimationValue(computedStyle, propertyID)
+ .Consume();
+ if (!value) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!aElement->GetComposedDoc()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoCString result;
+ Servo_AnimationValue_Serialize(value, propertyID,
+ presShell->StyleSet()->RawData(), &result);
+ CopyUTF8toUTF16(result, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDisplayDPI(float* aDPI) {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ *aDPI = widget->GetDPI();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CheckAndClearPaintedState(Element* aElement, bool* aResult) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+
+ if (!frame) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Get the outermost frame for the content node, so that we can test
+ // canvasframe invalidations by observing the documentElement.
+ for (;;) {
+ nsIFrame* parentFrame = frame->GetParent();
+ if (parentFrame && parentFrame->GetContent() == aElement) {
+ frame = parentFrame;
+ } else {
+ break;
+ }
+ }
+
+ while (frame) {
+ if (!frame->CheckAndClearPaintedState()) {
+ *aResult = false;
+ return NS_OK;
+ }
+ frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame);
+ }
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CheckAndClearDisplayListState(Element* aElement,
+ bool* aResult) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+
+ if (!frame) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Get the outermost frame for the content node, so that we can test
+ // canvasframe invalidations by observing the documentElement.
+ for (;;) {
+ nsIFrame* parentFrame = frame->GetParent();
+ if (parentFrame && parentFrame->GetContent() == aElement) {
+ frame = parentFrame;
+ } else {
+ break;
+ }
+ }
+
+ while (frame) {
+ if (!frame->CheckAndClearDisplayListState()) {
+ *aResult = false;
+ return NS_OK;
+ }
+ frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame);
+ }
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsPartOfOpaqueLayer(Element* aElement, bool* aResult) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::NumberOfAssignedPaintedLayers(
+ const nsTArray<RefPtr<Element>>& aElements, uint32_t* aResult) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnableDialogs() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsGlobalWindowOuter::Cast(window)->EnableDialogs();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DisableDialogs() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsGlobalWindowOuter::Cast(window)->DisableDialogs();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AreDialogsEnabled(bool* aResult) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ *aResult = nsGlobalWindowOuter::Cast(window)->AreDialogsEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ResetDialogAbuseState() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsGlobalWindowOuter::Cast(window)
+ ->GetBrowsingContextGroup()
+ ->ResetDialogAbuseState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFileId(JS::Handle<JS::Value> aFile, JSContext* aCx,
+ int64_t* _retval) {
+ if (aFile.isPrimitive()) {
+ *_retval = -1;
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
+
+ Blob* blob = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
+ *_retval = blob->GetFileId();
+ return NS_OK;
+ }
+
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFilePath(JS::Handle<JS::Value> aFile, JSContext* aCx,
+ nsAString& _retval) {
+ if (aFile.isPrimitive()) {
+ _retval.Truncate();
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
+
+ File* file = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(File, &obj, file))) {
+ nsString filePath;
+ ErrorResult rv;
+ file->GetMozFullPathInternal(filePath, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ _retval = filePath;
+ return NS_OK;
+ }
+
+ _retval.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFileReferences(const nsAString& aDatabaseName, int64_t aId,
+ int32_t* aRefCnt, int32_t* aDBRefCnt,
+ bool* aResult) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ quota::PrincipalMetadata principalMetadata;
+ MOZ_TRY_VAR(principalMetadata,
+ quota::QuotaManager::GetInfoFromWindow(window));
+
+ RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+ if (mgr) {
+ nsresult rv = mgr->BlockAndGetFileReferences(
+ principalMetadata.mIsPrivate ? quota::PERSISTENCE_TYPE_PRIVATE
+ : quota::PERSISTENCE_TYPE_DEFAULT,
+ principalMetadata.mOrigin, aDatabaseName, aId, aRefCnt, aDBRefCnt,
+ aResult);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ *aRefCnt = *aDBRefCnt = -1;
+ *aResult = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::FlushPendingFileDeletions() {
+ RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+ if (mgr) {
+ nsresult rv = mgr->FlushPendingFileDeletions();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StartPCCountProfiling(JSContext* cx) {
+ JS::StartPCCountProfiling(cx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StopPCCountProfiling(JSContext* cx) {
+ JS::StopPCCountProfiling(cx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::PurgePCCounts(JSContext* cx) {
+ JS::PurgePCCounts(cx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPCCountScriptCount(JSContext* cx, int32_t* result) {
+ *result = JS::GetPCCountScriptCount(cx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPCCountScriptSummary(int32_t script, JSContext* cx,
+ nsAString& result) {
+ JSString* text = JS::GetPCCountScriptSummary(cx, script);
+ if (!text) return NS_ERROR_FAILURE;
+
+ if (!AssignJSString(cx, result, text)) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPCCountScriptContents(int32_t script, JSContext* cx,
+ nsAString& result) {
+ JSString* text = JS::GetPCCountScriptContents(cx, script);
+ if (!text) return NS_ERROR_FAILURE;
+
+ if (!AssignJSString(cx, result, text)) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPaintingSuppressed(bool* aPaintingSuppressed) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ PresShell* presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ *aPaintingSuppressed = presShell->IsPaintingSuppressed();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetVisualViewportSize(float aWidth, float aHeight) {
+ if (!(aWidth >= 0.0 && aHeight >= 0.0)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetVisualViewportSize(nsPresContext::CSSPixelsToAppUnits(aWidth),
+ nsPresContext::CSSPixelsToAppUnits(aHeight));
+
+ return NS_OK;
+}
+
+nsresult nsDOMWindowUtils::RemoteFrameFullscreenChanged(
+ Element* aFrameElement) {
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ doc->RemoteFrameFullscreenChanged(aFrameElement);
+ return NS_OK;
+}
+
+nsresult nsDOMWindowUtils::RemoteFrameFullscreenReverted() {
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ doc->RemoteFrameFullscreenReverted();
+ return NS_OK;
+}
+
+static void PrepareForFullscreenChange(nsIDocShell* aDocShell,
+ const nsSize& aSize,
+ nsSize* aOldSize = nullptr) {
+ if (!aDocShell) {
+ return;
+ }
+ PresShell* presShell = aDocShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
+ rd->SetIsResizeSuppressed();
+ // Since we are suppressing the resize reflow which would originally
+ // be triggered by view manager, we need to ensure that the refresh
+ // driver actually schedules a flush, otherwise it may get stuck.
+ rd->ScheduleViewManagerFlush();
+ }
+ if (!aSize.IsEmpty()) {
+ nsCOMPtr<nsIContentViewer> cv;
+ aDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsIntRect cvBounds;
+ cv->GetBounds(cvBounds);
+ nscoord auPerDev = presShell->GetPresContext()->AppUnitsPerDevPixel();
+ if (aOldSize) {
+ *aOldSize = LayoutDeviceIntSize::ToAppUnits(
+ LayoutDeviceIntSize::FromUnknownSize(cvBounds.Size()), auPerDev);
+ }
+ LayoutDeviceIntSize newSize =
+ LayoutDeviceIntSize::FromAppUnitsRounded(aSize, auPerDev);
+
+ cvBounds.SizeTo(newSize.width, newSize.height);
+ cv->SetBounds(cvBounds);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::HandleFullscreenRequests(bool* aRetVal) {
+ PROFILER_MARKER_UNTYPED("Enter fullscreen", DOM);
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ // Notify the pres shell that we are starting fullscreen change, and
+ // set the window dimensions in advance. Since the resize message
+ // comes after the fullscreen change call, doing so could avoid an
+ // extra resize reflow after this point.
+ nsRect screenRect;
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->DeviceContext()->GetRect(screenRect);
+ }
+ nsSize oldSize;
+ PrepareForFullscreenChange(GetDocShell(), screenRect.Size(), &oldSize);
+ OldWindowSize::Set(mWindow, oldSize);
+
+ *aRetVal = Document::HandlePendingFullscreenRequests(doc);
+ return NS_OK;
+}
+
+nsresult nsDOMWindowUtils::ExitFullscreen(bool aDontRestoreViewSize) {
+ PROFILER_MARKER_UNTYPED("Exit fullscreen", DOM);
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ // Although we would not use the old size if we have already exited
+ // fullscreen, we still want to cleanup in case we haven't.
+ nsSize oldSize = OldWindowSize::GetAndRemove(mWindow);
+ if (!doc->GetFullscreenElement()) {
+ return NS_OK;
+ }
+
+ // Notify the pres shell that we are starting fullscreen change, and
+ // set the window dimensions in advance. Since the resize message
+ // comes after the fullscreen change call, doing so could avoid an
+ // extra resize reflow after this point.
+ PrepareForFullscreenChange(GetDocShell(),
+ aDontRestoreViewSize ? nsSize() : oldSize);
+ Document::ExitFullscreenInDocTree(doc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SelectAtPoint(float aX, float aY, uint32_t aSelectBehavior,
+ bool* _retval) {
+ *_retval = false;
+
+ nsSelectionAmount amount;
+ switch (aSelectBehavior) {
+ case nsIDOMWindowUtils::SELECT_CHARACTER:
+ amount = eSelectCharacter;
+ break;
+ case nsIDOMWindowUtils::SELECT_CLUSTER:
+ amount = eSelectCluster;
+ break;
+ case nsIDOMWindowUtils::SELECT_WORD:
+ amount = eSelectWord;
+ break;
+ case nsIDOMWindowUtils::SELECT_LINE:
+ amount = eSelectLine;
+ break;
+ case nsIDOMWindowUtils::SELECT_BEGINLINE:
+ amount = eSelectBeginLine;
+ break;
+ case nsIDOMWindowUtils::SELECT_ENDLINE:
+ amount = eSelectEndLine;
+ break;
+ case nsIDOMWindowUtils::SELECT_PARAGRAPH:
+ amount = eSelectParagraph;
+ break;
+ case nsIDOMWindowUtils::SELECT_WORDNOSPACE:
+ amount = eSelectWordNoSpace;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // The root frame for this content window
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Get the target frame at the client coordinates passed to us
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
+ LayoutDeviceIntPoint pt =
+ nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, GetPresContext());
+ nsPoint ptInRoot = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ widget, pt, RelativeTo{rootFrame});
+ nsIFrame* targetFrame =
+ nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
+ // This can happen if the page hasn't loaded yet or if the point
+ // is outside the frame.
+ if (!targetFrame) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Convert point to coordinates relative to the target frame, which is
+ // what targetFrame's SelectByTypeAtPoint expects.
+ nsPoint relPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ widget, pt, RelativeTo{targetFrame});
+
+ const RefPtr<nsPresContext> pinnedPresContext{GetPresContext()};
+ nsresult rv = targetFrame->SelectByTypeAtPoint(
+ pinnedPresContext, relPoint, amount, amount, nsIFrame::SELECT_ACCUMULATE);
+ *_retval = !NS_FAILED(rv);
+ return NS_OK;
+}
+
+static Document::additionalSheetType convertSheetType(uint32_t aSheetType) {
+ switch (aSheetType) {
+ case nsDOMWindowUtils::AGENT_SHEET:
+ return Document::eAgentSheet;
+ case nsDOMWindowUtils::USER_SHEET:
+ return Document::eUserSheet;
+ case nsDOMWindowUtils::AUTHOR_SHEET:
+ return Document::eAuthorSheet;
+ default:
+ NS_ASSERTION(false, "wrong type");
+ // we must return something although this should never happen
+ return Document::AdditionalSheetTypeCount;
+ }
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LoadSheet(nsIURI* aSheetURI, uint32_t aSheetType) {
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET || aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ Document::additionalSheetType type = convertSheetType(aSheetType);
+
+ return doc->LoadAdditionalStyleSheet(type, aSheetURI);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LoadSheetUsingURIString(const nsACString& aSheetURI,
+ uint32_t aSheetType) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSheetURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return LoadSheet(uri, aSheetType);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AddSheet(nsIPreloadedStyleSheet* aSheet,
+ uint32_t aSheetType) {
+ NS_ENSURE_ARG_POINTER(aSheet);
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET || aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ StyleSheet* sheet = nullptr;
+ auto* preloadedSheet = static_cast<PreloadedStyleSheet*>(aSheet);
+ nsresult rv = preloadedSheet->GetSheet(&sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(sheet, NS_ERROR_FAILURE);
+
+ if (sheet->GetAssociatedDocumentOrShadowRoot()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Document::additionalSheetType type = convertSheetType(aSheetType);
+ return doc->AddAdditionalStyleSheet(type, sheet);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RemoveSheet(nsIURI* aSheetURI, uint32_t aSheetType) {
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET || aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+
+ nsCOMPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ Document::additionalSheetType type = convertSheetType(aSheetType);
+
+ doc->RemoveAdditionalStyleSheet(type, aSheetURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RemoveSheetUsingURIString(const nsACString& aSheetURI,
+ uint32_t aSheetType) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSheetURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveSheet(uri, aSheetType);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsHandlingUserInput(bool* aHandlingUserInput) {
+ *aHandlingUserInput = UserActivation::IsHandlingUserInput();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetMillisSinceLastUserInput(
+ double* aMillisSinceLastUserInput) {
+ TimeStamp lastInput = UserActivation::LatestUserInputStart();
+ if (lastInput.IsNull()) {
+ *aMillisSinceLastUserInput = -1.0f;
+ return NS_OK;
+ }
+
+ *aMillisSinceLastUserInput = (TimeStamp::Now() - lastInput).ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AllowScriptsToClose() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+ nsGlobalWindowOuter::Cast(window)->AllowScriptsToClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsParentWindowMainWidgetVisible(bool* aIsVisible) {
+ if (!XRE_IsParentProcess()) {
+ MOZ_CRASH(
+ "IsParentWindowMainWidgetVisible is only available in the parent "
+ "process");
+ }
+
+ // this should reflect the "is parent window visible" logic in
+ // nsWindowWatcher::OpenWindowInternal()
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ nsCOMPtr<nsIWidget> parentWidget;
+ nsIDocShell* docShell = window->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner));
+ if (parentWindow) {
+ parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
+ }
+ }
+ if (!parentWidget) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aIsVisible = parentWidget->IsVisible();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsNodeDisabledForEvents(nsINode* aNode, bool* aRetVal) {
+ *aRetVal = false;
+ nsINode* node = aNode;
+ while (node) {
+ if (node->IsHTMLFormControlElement()) {
+ nsGenericHTMLElement* element = nsGenericHTMLElement::FromNode(node);
+ WidgetEvent event(true, eVoidEvent);
+ if (element && element->IsDisabledForEvents(&event)) {
+ *aRetVal = true;
+ break;
+ }
+ }
+ node = node->GetParentNode();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DispatchEventToChromeOnly(EventTarget* aTarget, Event* aEvent,
+ bool* aRetVal) {
+ *aRetVal = false;
+ NS_ENSURE_STATE(aTarget && aEvent);
+ aEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ *aRetVal =
+ aTarget->DispatchEvent(*aEvent, CallerType::System, IgnoreErrors());
+ return NS_OK;
+}
+
+static Result<nsIFrame*, nsresult> GetTargetFrame(
+ const Element* aElement, const nsAString& aPseudoElement) {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!aPseudoElement.IsEmpty()) {
+ if (aPseudoElement.EqualsLiteral("::before")) {
+ frame = nsLayoutUtils::GetBeforeFrame(aElement);
+ } else if (aPseudoElement.EqualsLiteral("::after")) {
+ frame = nsLayoutUtils::GetAfterFrame(aElement);
+ } else {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ }
+ return frame;
+}
+
+static OMTAValue GetOMTAValue(nsIFrame* aFrame, DisplayItemType aDisplayItemKey,
+ WebRenderBridgeChild* aWebRenderBridgeChild) {
+ OMTAValue value = mozilla::null_t();
+
+ if (aWebRenderBridgeChild) {
+ RefPtr<WebRenderAnimationData> animationData =
+ GetWebRenderUserData<WebRenderAnimationData>(aFrame,
+ (uint32_t)aDisplayItemKey);
+ if (animationData) {
+ aWebRenderBridgeChild->SendGetAnimationValue(
+ animationData->GetAnimationInfo().GetCompositorAnimationsId(),
+ &value);
+ }
+ }
+ return value;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetOMTAStyle(Element* aElement, const nsAString& aProperty,
+ const nsAString& aPseudoElement,
+ nsAString& aResult) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ auto frameOrError = GetTargetFrame(aElement, aPseudoElement);
+ if (frameOrError.isErr()) {
+ return frameOrError.unwrapErr();
+ }
+ nsIFrame* frame = frameOrError.unwrap();
+
+ RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
+ if (frame && nsLayoutUtils::AreAsyncAnimationsEnabled()) {
+ if (aProperty.EqualsLiteral("opacity")) {
+ OMTAValue value = GetOMTAValue(frame, DisplayItemType::TYPE_OPACITY,
+ GetWebRenderBridge());
+ if (value.type() == OMTAValue::Tfloat) {
+ cssValue = new nsROCSSPrimitiveValue;
+ cssValue->SetNumber(value.get_float());
+ }
+ } else if (aProperty.EqualsLiteral("transform") ||
+ aProperty.EqualsLiteral("translate") ||
+ aProperty.EqualsLiteral("rotate") ||
+ aProperty.EqualsLiteral("scale") ||
+ aProperty.EqualsLiteral("offset-path") ||
+ aProperty.EqualsLiteral("offset-distance") ||
+ aProperty.EqualsLiteral("offset-rotate") ||
+ aProperty.EqualsLiteral("offset-anchor")) {
+ OMTAValue value = GetOMTAValue(frame, DisplayItemType::TYPE_TRANSFORM,
+ GetWebRenderBridge());
+ if (value.type() == OMTAValue::TMatrix4x4) {
+ cssValue = nsComputedDOMStyle::MatrixToCSSValue(value.get_Matrix4x4());
+ }
+ } else if (aProperty.EqualsLiteral("background-color")) {
+ OMTAValue value = GetOMTAValue(
+ frame, DisplayItemType::TYPE_BACKGROUND_COLOR, GetWebRenderBridge());
+ if (value.type() == OMTAValue::Tnscolor) {
+ nsStyleUtil::GetSerializedColorValue(value.get_nscolor(), aResult);
+ return NS_OK;
+ }
+ }
+ }
+
+ if (cssValue) {
+ nsString text;
+ ErrorResult rv;
+ cssValue->GetCssText(text, rv);
+ aResult.Assign(text);
+ return rv.StealNSResult();
+ }
+ aResult.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsAnimationInPendingTracker(dom::Animation* aAnimation,
+ bool* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+
+ if (!aAnimation) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Document* doc = GetDocument();
+ if (!doc) {
+ *aRetVal = false;
+ return NS_OK;
+ }
+
+ PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
+ if (!tracker) {
+ *aRetVal = false;
+ return NS_OK;
+ }
+
+ *aRetVal = tracker->IsWaitingToPlay(*aAnimation) ||
+ tracker->IsWaitingToPause(*aAnimation);
+ return NS_OK;
+}
+
+namespace {
+
+class HandlingUserInputHelper final : public nsIJSRAIIHelper {
+ public:
+ explicit HandlingUserInputHelper(bool aHandlingUserInput);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJSRAIIHELPER
+
+ private:
+ ~HandlingUserInputHelper();
+
+ bool mHandlingUserInput;
+ bool mDestructCalled = false;
+};
+
+NS_IMPL_ISUPPORTS(HandlingUserInputHelper, nsIJSRAIIHelper)
+
+HandlingUserInputHelper::HandlingUserInputHelper(bool aHandlingUserInput)
+ : mHandlingUserInput(aHandlingUserInput) {
+ if (aHandlingUserInput) {
+ UserActivation::StartHandlingUserInput(eVoidEvent);
+ }
+}
+
+HandlingUserInputHelper::~HandlingUserInputHelper() {
+ // We assert, but just in case, make sure we notify the ESM.
+ MOZ_ASSERT(mDestructCalled);
+ if (!mDestructCalled) {
+ Destruct();
+ }
+}
+
+NS_IMETHODIMP
+HandlingUserInputHelper::Destruct() {
+ if (NS_WARN_IF(mDestructCalled)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDestructCalled = true;
+ if (mHandlingUserInput) {
+ UserActivation::StopHandlingUserInput(eVoidEvent);
+ }
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetHandlingUserInput(bool aHandlingUserInput,
+ nsIJSRAIIHelper** aHelper) {
+ if (aHandlingUserInput) {
+ if (Document* doc = GetDocument()) {
+ doc->NotifyUserGestureActivation();
+ }
+ }
+ auto helper = MakeRefPtr<HandlingUserInputHelper>(aHandlingUserInput);
+ helper.forget(aHelper);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsKeyboardEventUserActivity(Event* aEvent, bool* aResult) {
+ NS_ENSURE_STATE(aEvent);
+ if (!aEvent->AsKeyboardEvent()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+ NS_ENSURE_STATE(internalEvent);
+ *aResult = EventStateManager::IsKeyboardEventUserActivity(internalEvent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetContentAPZTestData(
+ JSContext* aContext, JS::MutableHandle<JS::Value> aOutContentTestData) {
+ if (nsIWidget* widget = GetWidget()) {
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ return NS_OK;
+ }
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ if (!wr->GetAPZTestData().ToJS(aOutContentTestData, aContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCompositorAPZTestData(
+ JSContext* aContext, JS::MutableHandle<JS::Value> aOutCompositorTestData) {
+ if (nsIWidget* widget = GetWidget()) {
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ return NS_OK;
+ }
+ APZTestData compositorSideData;
+ if (WebRenderLayerManager* wr = renderer->AsWebRender()) {
+ if (!wr->WrBridge()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!wr->WrBridge()->SendGetAPZTestData(&compositorSideData)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ if (!compositorSideData.ToJS(aOutCompositorTestData, aContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::PostRestyleSelfEvent(Element* aElement) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
+ nsChangeHint(0));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetChromeMargin(int32_t aTop, int32_t aRight, int32_t aBottom,
+ int32_t aLeft) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (window) {
+ nsCOMPtr<nsIBaseWindow> baseWindow =
+ do_QueryInterface(window->GetDocShell());
+ if (baseWindow) {
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+ if (widget) {
+ LayoutDeviceIntMargin margins(aTop, aRight, aBottom, aLeft);
+ return widget->SetNonClientMargins(margins);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetResizeMargin(int32_t aResizeMargin) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (window) {
+ nsCOMPtr<nsIBaseWindow> baseWindow =
+ do_QueryInterface(window->GetDocShell());
+ if (baseWindow) {
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+ if (widget) {
+ CSSToLayoutDeviceScale scaleFactor = widget->GetDefaultScale();
+ widget->SetResizeMargin(
+ (CSSCoord(float(aResizeMargin)) * scaleFactor).Rounded());
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFrameUniformityTestData(
+ JSContext* aContext, JS::MutableHandle<JS::Value> aOutFrameUniformity) {
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ WindowRenderer* renderer = widget->GetWindowRenderer();
+ if (!renderer) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ FrameUniformityData outData;
+ renderer->GetFrameUniformity(&outData);
+ outData.ToJS(aOutFrameUniformity, aContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::XpconnectArgument(nsISupports* aObj) {
+ // Do nothing.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AskPermission(nsIContentPermissionRequest* aRequest) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ return nsContentPermissionUtils::AskPermission(
+ aRequest, window->GetCurrentInnerWindow());
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetRestyleGeneration(uint64_t* aResult) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = presContext->GetRestyleGeneration();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFramesConstructed(uint64_t* aResult) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = presContext->FramesConstructedCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFramesReflowed(uint64_t* aResult) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = presContext->FramesReflowedCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetRefreshDriverHasPendingTick(bool* aResult) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = presContext->RefreshDriver()->HasPendingTick();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnterChaosMode() {
+ ChaosMode::enterChaosMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LeaveChaosMode() {
+ ChaosMode::leaveChaosMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::TriggerDeviceReset() {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ GPUProcessManager* pm = GPUProcessManager::Get();
+ if (pm) {
+ pm->SimulateDeviceReset();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return presShell->HasRuleProcessorUsedByMultipleStyleSets(aSheetType,
+ aRetVal);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RespectDisplayPortSuppression(bool aEnabled) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ presShell->RespectDisplayportSuppression(aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ForceReflowInterrupt() {
+ nsPresContext* pc = GetPresContext();
+ if (!pc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ pc->SetPendingInterruptFromTest();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::TerminateGPUProcess() {
+ GPUProcessManager* pm = GPUProcessManager::Get();
+ if (pm) {
+ pm->KillProcess();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetGpuProcessPid(int32_t* aPid) {
+ GPUProcessManager* pm = GPUProcessManager::Get();
+ if (pm) {
+ *aPid = pm->GPUProcessPid();
+ } else {
+ *aPid = -1;
+ }
+
+ return NS_OK;
+}
+
+struct StateTableEntry {
+ const char* mStateString;
+ ElementState mState;
+};
+
+static constexpr StateTableEntry kManuallyManagedStates[] = {
+ {"autofill", ElementState::AUTOFILL},
+ // :-moz-autofill-preview implies :autofill.
+ {"-moz-autofill-preview",
+ ElementState::AUTOFILL_PREVIEW | ElementState::AUTOFILL},
+ {nullptr, ElementState()},
+};
+
+static_assert(!kManuallyManagedStates[ArrayLength(kManuallyManagedStates) - 1]
+ .mStateString,
+ "last kManuallyManagedStates entry must be a sentinel with "
+ "mStateString == nullptr");
+
+static ElementState GetEventStateForString(const nsAString& aStateString) {
+ for (const StateTableEntry* entry = kManuallyManagedStates;
+ entry->mStateString; ++entry) {
+ if (aStateString.EqualsASCII(entry->mStateString)) {
+ return entry->mState;
+ }
+ }
+ return ElementState();
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AddManuallyManagedState(Element* aElement,
+ const nsAString& aStateString) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ElementState state = GetEventStateForString(aStateString);
+ if (state.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aElement->AddManuallyManagedStates(state);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RemoveManuallyManagedState(Element* aElement,
+ const nsAString& aStateString) {
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ElementState state = GetEventStateForString(aStateString);
+ if (state.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aElement->RemoveManuallyManagedStates(state);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetStorageUsage(Storage* aStorage, int64_t* aRetval) {
+ if (!aStorage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aRetval = aStorage->GetOriginQuotaUsage();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDirectionFromText(const nsAString& aString,
+ int32_t* aRetval) {
+ Directionality dir =
+ ::GetDirectionFromText(aString.BeginReading(), aString.Length(), nullptr);
+ switch (dir) {
+ case eDir_NotSet:
+ *aRetval = nsIDOMWindowUtils::DIRECTION_NOT_SET;
+ break;
+ case eDir_RTL:
+ *aRetval = nsIDOMWindowUtils::DIRECTION_RTL;
+ break;
+ case eDir_LTR:
+ *aRetval = nsIDOMWindowUtils::DIRECTION_LTR;
+ break;
+ case eDir_Auto:
+ MOZ_ASSERT_UNREACHABLE(
+ "GetDirectionFromText should never return this value");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnsureDirtyRootFrame() {
+ Document* doc = GetDocument();
+ PresShell* presShell = doc ? doc->GetPresShell() : nullptr;
+
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->FrameNeedsReflow(
+ frame, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsTranslationNodeList)
+NS_IMPL_RELEASE(nsTranslationNodeList)
+
+NS_IMETHODIMP
+nsTranslationNodeList::Item(uint32_t aIndex, nsINode** aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ NS_IF_ADDREF(*aRetVal = mNodes.SafeElementAt(aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTranslationNodeList::IsTranslationRootAtIndex(uint32_t aIndex,
+ bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ if (aIndex >= mLength) {
+ *aRetVal = false;
+ return NS_OK;
+ }
+
+ *aRetVal = mNodeIsRoot.ElementAt(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTranslationNodeList::GetLength(uint32_t* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ *aRetVal = mLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::WrCapture() {
+ if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
+ wrbc->Capture();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::WrStartCaptureSequence(const nsACString& aPath,
+ uint32_t aFlags) {
+ if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
+ wrbc->StartCaptureSequence(nsCString(aPath), aFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::WrStopCaptureSequence() {
+ if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
+ wrbc->StopCaptureSequence();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetCompositionRecording(bool aValue, Promise** aOutPromise) {
+ return aValue ? StartCompositionRecording(aOutPromise)
+ : StopCompositionRecording(true, aOutPromise);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StartCompositionRecording(Promise** aOutPromise) {
+ NS_ENSURE_ARG(aOutPromise);
+ *aOutPromise = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(outer);
+ nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
+ NS_ENSURE_STATE(inner);
+
+ ErrorResult err;
+ RefPtr<Promise> promise = Promise::Create(inner->AsGlobal(), err);
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+
+ CompositorBridgeChild* cbc = GetCompositorBridge();
+ if (NS_WARN_IF(!cbc)) {
+ promise->MaybeReject(NS_ERROR_UNEXPECTED);
+ } else {
+ cbc->SendBeginRecording(TimeStamp::Now())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const bool& aSuccess) {
+ if (aSuccess) {
+ promise->MaybeResolve(true);
+ } else {
+ promise->MaybeRejectWithInvalidStateError(
+ "The composition recorder is already running.");
+ }
+ },
+ [promise](const mozilla::ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Could not start the composition recorder.");
+ });
+ }
+
+ promise.forget(aOutPromise);
+ return NS_OK;
+}
+
+static bool WriteRecordingToDisk(const FrameRecording& aRecording,
+ double aUnixStartMS) {
+ // The directory name contains the unix timestamp for when recording started,
+ // because we want the consumer of these files to be able to compute an
+ // absolute timestamp of each screenshot. That allows them to align
+ // screenshots with timed data from other sources, such as Gecko profiler
+ // information. The time of each screenshot is part of the screenshot's
+ // filename, expressed as milliseconds from the recording start.
+ std::stringstream recordingDirectory;
+ recordingDirectory << gfxVars::LayersWindowRecordingPath()
+ << "windowrecording-" << int64_t(aUnixStartMS);
+
+#ifdef XP_WIN
+ _mkdir(recordingDirectory.str().c_str());
+#else
+ mkdir(recordingDirectory.str().c_str(), 0777);
+#endif
+
+ auto byteSpan = aRecording.bytes().AsSpan();
+
+ uint32_t i = 1;
+
+ for (const auto& frame : aRecording.frames()) {
+ const uint32_t frameBufferLength = frame.length();
+ if (frameBufferLength > byteSpan.Length()) {
+ return false;
+ }
+
+ const auto frameSpan = byteSpan.To(frameBufferLength);
+ byteSpan = byteSpan.From(frameBufferLength);
+
+ const double frameTimeMS =
+ (frame.timeOffset() - aRecording.startTime()).ToMilliseconds();
+
+ std::stringstream filename;
+ filename << recordingDirectory.str() << "/frame-" << i << "-"
+ << uint32_t(frameTimeMS) << ".png";
+
+ FILE* file = fopen(filename.str().c_str(), "wb");
+ if (!file) {
+ return false;
+ }
+
+ const size_t bytesWritten =
+ fwrite(frameSpan.Elements(), sizeof(uint8_t), frameSpan.Length(), file);
+
+ fclose(file);
+
+ if (bytesWritten < frameSpan.Length()) {
+ return false;
+ }
+
+ ++i;
+ }
+
+ return byteSpan.Length() == 0;
+}
+
+static Maybe<DOMCollectedFrames> ConvertCompositionRecordingFramesToDom(
+ const FrameRecording& aRecording, double aUnixStartMS) {
+ auto byteSpan = aRecording.bytes().AsSpan();
+
+ nsTArray<DOMCollectedFrame> domFrames;
+
+ for (const auto& recordedFrame : aRecording.frames()) {
+ const uint32_t frameBufferLength = recordedFrame.length();
+ if (frameBufferLength > byteSpan.Length()) {
+ return Nothing();
+ }
+
+ const auto frameSpan = byteSpan.To(frameBufferLength);
+ byteSpan = byteSpan.From(frameBufferLength);
+
+ nsCString dataUri;
+
+ dataUri.AppendLiteral("data:image/png;base64,");
+
+ nsresult rv =
+ Base64EncodeAppend(reinterpret_cast<const char*>(frameSpan.Elements()),
+ frameSpan.Length(), dataUri);
+ if (NS_FAILED(rv)) {
+ return Nothing();
+ }
+
+ DOMCollectedFrame domFrame;
+ domFrame.mTimeOffset =
+ (recordedFrame.timeOffset() - aRecording.startTime()).ToMilliseconds();
+ domFrame.mDataUri = std::move(dataUri);
+
+ domFrames.AppendElement(std::move(domFrame));
+ }
+
+ if (byteSpan.Length() != 0) {
+ return Nothing();
+ }
+
+ DOMCollectedFrames result;
+
+ result.mRecordingStart = aUnixStartMS;
+ result.mFrames = std::move(domFrames);
+
+ return Some(std::move(result));
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StopCompositionRecording(bool aWriteToDisk,
+ Promise** aOutPromise) {
+ NS_ENSURE_ARG_POINTER(aOutPromise);
+ *aOutPromise = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(outer);
+ nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
+ NS_ENSURE_STATE(inner);
+
+ ErrorResult err;
+ RefPtr<Promise> promise = Promise::Create(inner->AsGlobal(), err);
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+
+ RefPtr<Promise>(promise).forget(aOutPromise);
+
+ CompositorBridgeChild* cbc = GetCompositorBridge();
+ if (NS_WARN_IF(!cbc)) {
+ promise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ cbc->SendEndRecording()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, aWriteToDisk](Maybe<FrameRecording>&& aRecording) {
+ if (!aRecording) {
+ promise->MaybeRejectWithUnknownError("Failed to get frame recording");
+ return;
+ }
+
+ // We need to know when the recording started in Unix Time.
+ // Unfortunately, the recording start time is an opaque Timestamp that
+ // can only be used to calculate a duration.
+ //
+ // This is not great, but we are going to get Now() twice in close
+ // proximity, one in Unix Time and the other in Timestamp time. Then we
+ // can subtract the length of the recording from the current Unix Time
+ // to get the Unix start time.
+ const TimeStamp timestampNow = TimeStamp::Now();
+ const int64_t unixNowUS = PR_Now();
+
+ const TimeDuration recordingLength =
+ timestampNow - aRecording->startTime();
+ const double unixNowMS = double(unixNowUS) / 1000.0;
+ const double unixStartMS = unixNowMS - recordingLength.ToMilliseconds();
+
+ if (aWriteToDisk) {
+ if (!WriteRecordingToDisk(*aRecording, unixStartMS)) {
+ promise->MaybeRejectWithUnknownError(
+ "Failed to write recording to disk");
+ return;
+ }
+ promise->MaybeResolveWithUndefined();
+ } else {
+ auto maybeDomFrames =
+ ConvertCompositionRecordingFramesToDom(*aRecording, unixStartMS);
+ if (!maybeDomFrames) {
+ promise->MaybeRejectWithUnknownError(
+ "Unable to base64-encode recorded frames");
+ return;
+ }
+ promise->MaybeResolve(*maybeDomFrames);
+ }
+ },
+ [promise](const mozilla::ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithUnknownError(
+ "IPC failed getting composition recording");
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetSystemFont(const nsACString& aFontName) {
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_OK;
+ }
+
+ nsAutoCString fontName(aFontName);
+ return widget->SetSystemFont(fontName);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetSystemFont(nsACString& aFontName) {
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_OK;
+ }
+
+ nsAutoCString fontName;
+ widget->GetSystemFont(fontName);
+ aFontName.Assign(fontName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsCssPropertyRecordedInUseCounter(const nsACString& aPropName,
+ bool* aRecorded) {
+ *aRecorded = false;
+
+ Document* doc = GetDocument();
+ if (!doc || !doc->GetStyleUseCounters()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool knownProp = false;
+ *aRecorded = Servo_IsCssPropertyRecordedInUseCounter(
+ doc->GetStyleUseCounters(), &aPropName, &knownProp);
+ return knownProp ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsCoepCredentialless(bool* aResult) {
+ Document* doc = GetDocument();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = net::IsCoepCredentiallessEnabled(
+ doc->Trials().IsEnabled(OriginTrial::CoepCredentialless));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLayersId(uint64_t* aOutLayersId) {
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ BrowserChild* child = widget->GetOwningBrowserChild();
+ if (!child) {
+ return NS_ERROR_FAILURE;
+ }
+ *aOutLayersId = (uint64_t)child->GetLayersId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPaintCount(uint64_t* aPaintCount) {
+ auto* presShell = GetPresShell();
+ *aPaintCount = presShell ? presShell->GetPaintCount() : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetWebrtcRawDeviceId(nsAString& aRawDeviceId) {
+ if (!XRE_IsParentProcess()) {
+ MOZ_CRASH(
+ "GetWebrtcRawDeviceId is only available in the parent "
+ "process");
+ }
+
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int64_t rawDeviceId =
+ (int64_t)(widget->GetNativeData(NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID));
+ if (!rawDeviceId) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aRawDeviceId.AppendInt(rawDeviceId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetEffectivelyThrottlesFrameRequests(bool* aResult) {
+ Document* doc = GetDocument();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = !doc->WouldScheduleFrameRequestCallbacks() ||
+ doc->ShouldThrottleFrameRequests();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ResetMobileViewportManager() {
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ if (auto mvm = presShell->GetMobileViewportManager()) {
+ mvm->SetInitialViewport();
+ return NS_OK;
+ }
+ }
+ // Unable to reset, so let's error out
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetSuspendedByBrowsingContextGroup(bool* aResult) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
+ NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
+
+ *aResult = inner->GetWasSuspendedByGroup();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetHasScrollLinkedEffect(bool* aResult) {
+ Document* doc = GetDocument();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = doc->HasScrollLinkedEffect();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetOrientationLock(uint32_t* aOrientationLock) {
+ NS_WARNING("nsDOMWindowUtils::GetOrientationLock");
+
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+ bc = bc ? bc->Top() : nullptr;
+ if (!bc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aOrientationLock = static_cast<uint32_t>(bc->GetOrientationLock());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetWheelScrollTarget(Element** aResult) {
+ *aResult = nullptr;
+ if (nsIFrame* targetFrame = WheelTransaction::GetScrollTargetFrame()) {
+ NS_IF_ADDREF(*aResult = Element::FromNodeOrNull(targetFrame->GetContent()));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetHiDPIMode(bool aHiDPI) {
+#ifdef DEBUG
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ return widget->SetHiDPIMode(aHiDPI);
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RestoreHiDPIMode() {
+#ifdef DEBUG
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) return NS_ERROR_FAILURE;
+
+ return widget->RestoreHiDPIMode();
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h
new file mode 100644
index 0000000000..63968c9b7a
--- /dev/null
+++ b/dom/base/nsDOMWindowUtils.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMWindowUtils_h_
+#define nsDOMWindowUtils_h_
+
+#include "nsWeakReference.h"
+
+#include "nsIDOMWindowUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Result.h"
+
+class nsGlobalWindowOuter;
+class nsIDocShell;
+class nsIWidget;
+class nsPresContext;
+class nsView;
+struct nsPoint;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+namespace layers {
+class LayerTransactionChild;
+class WebRenderBridgeChild;
+} // namespace layers
+} // namespace mozilla
+
+class nsTranslationNodeList final : public nsITranslationNodeList {
+ public:
+ nsTranslationNodeList() {
+ mNodes.SetCapacity(1000);
+ mNodeIsRoot.SetCapacity(1000);
+ mLength = 0;
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSLATIONNODELIST
+
+ void AppendElement(nsINode* aElement, bool aIsRoot) {
+ mNodes.AppendElement(aElement);
+ mNodeIsRoot.AppendElement(aIsRoot);
+ mLength++;
+ }
+
+ private:
+ ~nsTranslationNodeList() = default;
+
+ nsTArray<nsCOMPtr<nsINode> > mNodes;
+ nsTArray<bool> mNodeIsRoot;
+ uint32_t mLength;
+};
+
+class nsDOMWindowUtils final : public nsIDOMWindowUtils,
+ public nsSupportsWeakReference {
+ using TextEventDispatcher = mozilla::widget::TextEventDispatcher;
+
+ public:
+ explicit nsDOMWindowUtils(nsGlobalWindowOuter* aWindow);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMWINDOWUTILS
+
+ protected:
+ ~nsDOMWindowUtils();
+
+ nsWeakPtr mWindow;
+
+ // If aOffset is non-null, it gets filled in with the offset of the root
+ // frame of our window to the nearest widget in the app units of our window.
+ // Add this offset to any event offset we're given to make it relative to the
+ // widget returned by GetWidget.
+ nsIWidget* GetWidget(nsPoint* aOffset = nullptr);
+ nsIWidget* GetWidgetForElement(mozilla::dom::Element* aElement);
+
+ nsIDocShell* GetDocShell();
+ mozilla::PresShell* GetPresShell();
+ nsPresContext* GetPresContext();
+ mozilla::dom::Document* GetDocument();
+ mozilla::layers::WebRenderBridgeChild* GetWebRenderBridge();
+ mozilla::layers::CompositorBridgeChild* GetCompositorBridge();
+
+ // Until callers are annotated.
+ MOZ_CAN_RUN_SCRIPT
+ NS_IMETHOD SendMouseEventCommon(
+ const nsAString& aType, float aX, float aY, int32_t aButton,
+ int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
+ float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier,
+ bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized, int32_t aButtons);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult SendTouchEventCommon(
+ const nsAString& aType, const nsTArray<uint32_t>& aIdentifiers,
+ const nsTArray<int32_t>& aXs, const nsTArray<int32_t>& aYs,
+ const nsTArray<uint32_t>& aRxs, const nsTArray<uint32_t>& aRys,
+ const nsTArray<float>& aRotationAngles, const nsTArray<float>& aForces,
+ const nsTArray<int32_t>& aTiltXs, const nsTArray<int32_t>& aTiltYs,
+ const nsTArray<int32_t>& aTwists, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, bool aToWindow, bool* aPreventDefault);
+
+ void ReportErrorMessageForWindow(const nsAString& aErrorMessage,
+ const char* aClassification,
+ bool aFromChrome);
+
+ private:
+ mozilla::Result<mozilla::ScreenRect, nsresult> ConvertToScreenRect(
+ float aX, float aY, float aWidth, float aHeight);
+};
+
+#endif
diff --git a/dom/base/nsDataDocumentContentPolicy.cpp b/dom/base/nsDataDocumentContentPolicy.cpp
new file mode 100644
index 0000000000..7757684553
--- /dev/null
+++ b/dom/base/nsDataDocumentContentPolicy.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Content policy implementation that prevents all loads of images,
+ * subframes, etc from documents loaded as data (eg documents loaded
+ * via XMLHttpRequest).
+ */
+
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDataDocumentContentPolicy.h"
+#include "nsNetUtil.h"
+#include "nsIProtocolHandler.h"
+#include "nsScriptSecurityManager.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ScopeExit.h"
+#include "nsINode.h"
+#include "nsIURI.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDataDocumentContentPolicy, nsIContentPolicy)
+
+// Helper method for ShouldLoad()
+// Checks a URI for the given flags. Returns true if the URI has the flags,
+// and false if not (or if we weren't able to tell).
+static bool HasFlags(nsIURI* aURI, uint32_t aURIFlags) {
+ bool hasFlags;
+ nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &hasFlags);
+ return NS_SUCCEEDED(rv) && hasFlags;
+}
+
+// If you change DataDocumentContentPolicy, make sure to check that
+// CHECK_PRINCIPAL_AND_DATA in nsContentPolicyUtils is still valid.
+// nsContentPolicyUtils may not pass all the parameters to ShouldLoad.
+NS_IMETHODIMP
+nsDataDocumentContentPolicy::ShouldLoad(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ auto setBlockingReason = mozilla::MakeScopeExit([&]() {
+ if (NS_CP_REJECTED(*aDecision)) {
+ NS_SetRequestBlockingReason(
+ aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_DATA_DOCUMENT);
+ }
+ });
+
+ ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
+ nsCOMPtr<nsISupports> requestingContext = aLoadInfo->GetLoadingContext();
+
+ *aDecision = nsIContentPolicy::ACCEPT;
+ // Look for the document. In most cases, requestingContext is a node.
+ nsCOMPtr<mozilla::dom::Document> doc;
+ nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+ if (node) {
+ doc = node->OwnerDoc();
+ } else {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window =
+ do_QueryInterface(requestingContext)) {
+ doc = window->GetDoc();
+ }
+ }
+
+ // DTDs are always OK to load
+ if (!doc || contentType == ExtContentPolicy::TYPE_DTD) {
+ return NS_OK;
+ }
+
+ if (doc->IsLoadedAsData()) {
+ bool allowed = [&] {
+ if (!doc->IsStaticDocument()) {
+ // If not a print/print preview doc, then nothing else is allowed for
+ // data documents.
+ return false;
+ }
+ // Let static (print/print preview) documents to load fonts and
+ // images.
+ switch (contentType) {
+ case ExtContentPolicy::TYPE_IMAGE:
+ case ExtContentPolicy::TYPE_IMAGESET:
+ case ExtContentPolicy::TYPE_FONT:
+ case ExtContentPolicy::TYPE_UA_FONT:
+ // This one is a bit sketchy, but nsObjectLoadingContent takes care of
+ // only getting here if it is an image.
+ case ExtContentPolicy::TYPE_OBJECT:
+ return true;
+ default:
+ return false;
+ }
+ }();
+
+ if (!allowed) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ return NS_OK;
+ }
+ }
+
+ mozilla::dom::Document* docToCheckForImage = doc->GetDisplayDocument();
+ if (!docToCheckForImage) {
+ docToCheckForImage = doc;
+ }
+
+ if (docToCheckForImage->IsBeingUsedAsImage()) {
+ // We only allow SVG images to load content from URIs that are local and
+ // also satisfy one of the following conditions:
+ // - URI inherits security context, e.g. data URIs
+ // OR
+ // - URI loadable by subsumers, e.g. blob URIs
+ // Any URI that doesn't meet these requirements will be rejected below.
+ if (!(HasFlags(aContentLocation,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE) &&
+ (HasFlags(aContentLocation,
+ nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT) ||
+ HasFlags(aContentLocation,
+ nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS)))) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+
+ // Report error, if we can.
+ if (node) {
+ nsIPrincipal* requestingPrincipal = node->NodePrincipal();
+ nsAutoCString sourceSpec;
+ requestingPrincipal->GetAsciiSpec(sourceSpec);
+ nsAutoCString targetSpec;
+ aContentLocation->GetAsciiSpec(targetSpec);
+ nsScriptSecurityManager::ReportError(
+ "ExternalDataError", sourceSpec, targetSpec,
+ requestingPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0);
+ }
+ } else if ((contentType == ExtContentPolicy::TYPE_IMAGE ||
+ contentType == ExtContentPolicy::TYPE_IMAGESET) &&
+ doc->GetDocumentURI()) {
+ // Check for (& disallow) recursive image-loads
+ bool isRecursiveLoad;
+ nsresult rv = aContentLocation->EqualsExceptRef(doc->GetDocumentURI(),
+ &isRecursiveLoad);
+ if (NS_FAILED(rv) || isRecursiveLoad) {
+ NS_WARNING("Refusing to recursively load image");
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ }
+ }
+ return NS_OK;
+ }
+
+ // Allow all loads for non-resource documents
+ if (!doc->IsResourceDoc()) {
+ return NS_OK;
+ }
+
+ // For resource documents, blacklist some load types
+ if (contentType == ExtContentPolicy::TYPE_OBJECT ||
+ contentType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentType == ExtContentPolicy::TYPE_SUBDOCUMENT ||
+ contentType == ExtContentPolicy::TYPE_SCRIPT ||
+ contentType == ExtContentPolicy::TYPE_XSLT ||
+ contentType == ExtContentPolicy::TYPE_FETCH ||
+ contentType == ExtContentPolicy::TYPE_WEB_MANIFEST) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ }
+
+ // If you add more restrictions here, make sure to check that
+ // CHECK_PRINCIPAL_AND_DATA in nsContentPolicyUtils is still valid.
+ // nsContentPolicyUtils may not pass all the parameters to ShouldLoad
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataDocumentContentPolicy::ShouldProcess(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ return ShouldLoad(aContentLocation, aLoadInfo, aMimeGuess, aDecision);
+}
diff --git a/dom/base/nsDataDocumentContentPolicy.h b/dom/base/nsDataDocumentContentPolicy.h
new file mode 100644
index 0000000000..1a4dce658e
--- /dev/null
+++ b/dom/base/nsDataDocumentContentPolicy.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Content policy implementation that prevents all loads of images,
+ * subframes, etc from documents loaded as data (eg documents loaded
+ * via XMLHttpRequest).
+ */
+
+#ifndef nsDataDocumentContentPolicy_h__
+#define nsDataDocumentContentPolicy_h__
+
+/* 1147d32c-215b-4014-b180-07fe7aedf915 */
+#define NS_DATADOCUMENTCONTENTPOLICY_CID \
+ { \
+ 0x1147d32c, 0x215b, 0x4014, { \
+ 0xb1, 0x80, 0x07, 0xfe, 0x7a, 0xed, 0xf9, 0x15 \
+ } \
+ }
+#define NS_DATADOCUMENTCONTENTPOLICY_CONTRACTID \
+ "@mozilla.org/data-document-content-policy;1"
+
+#include "nsIContentPolicy.h"
+#include "mozilla/Attributes.h"
+
+class nsDataDocumentContentPolicy final : public nsIContentPolicy {
+ ~nsDataDocumentContentPolicy() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+
+ nsDataDocumentContentPolicy() = default;
+};
+
+#endif /* nsDataDocumentContentPolicy_h__ */
diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h
new file mode 100644
index 0000000000..31999f6609
--- /dev/null
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// IWYU pragma: private, include "mozilla/dom/Document.h"
+
+/*
+ * This file contains the list of deprecated DOM operations. It is
+ * designed to be used as input to the C preprocessor *only*.
+ */
+
+DEPRECATED_OPERATION(MutationEvent)
+DEPRECATED_OPERATION(Components)
+DEPRECATED_OPERATION(NodeIteratorDetach)
+DEPRECATED_OPERATION(LenientThis)
+DEPRECATED_OPERATION(UseOfCaptureEvents)
+DEPRECATED_OPERATION(UseOfReleaseEvents)
+DEPRECATED_OPERATION(SyncXMLHttpRequest)
+DEPRECATED_OPERATION(Window_Cc_ontrollers)
+DEPRECATED_OPERATION(ImportXULIntoContent)
+DEPRECATED_OPERATION(InstallTriggerDeprecated)
+DEPRECATED_OPERATION(InstallTriggerInstallDeprecated)
+DEPRECATED_OPERATION(NavigatorGetUserMedia)
+DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
+DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
+DEPRECATED_OPERATION(AppCache)
+DEPRECATED_OPERATION(LenientSetter)
+DEPRECATED_OPERATION(ImageBitmapRenderingContext_TransferImageBitmap)
+DEPRECATED_OPERATION(WindowContentUntrusted)
+DEPRECATED_OPERATION(MotionEvent)
+DEPRECATED_OPERATION(OrientationEvent)
+DEPRECATED_OPERATION(ProximityEvent)
+DEPRECATED_OPERATION(AmbientLightEvent)
+DEPRECATED_OPERATION(IDBOpenDBOptions_StorageType)
+DEPRECATED_OPERATION(DOMQuadBoundsAttr)
+DEPRECATED_OPERATION(DeprecatedTestingInterface)
+DEPRECATED_OPERATION(DeprecatedTestingMethod)
+DEPRECATED_OPERATION(DeprecatedTestingAttribute)
+DEPRECATED_OPERATION(CreateImageBitmapCanvasRenderingContext2D)
+DEPRECATED_OPERATION(DrawWindowCanvasRenderingContext2D)
+DEPRECATED_OPERATION(MozRequestFullScreenDeprecatedPrefix)
+DEPRECATED_OPERATION(MozfullscreenchangeDeprecatedPrefix)
+DEPRECATED_OPERATION(MozfullscreenerrorDeprecatedPrefix)
+DEPRECATED_OPERATION(External_AddSearchProvider)
+DEPRECATED_OPERATION(MouseEvent_MozPressure)
+DEPRECATED_OPERATION(MathML_DeprecatedMathSizeValue)
+DEPRECATED_OPERATION(MathML_DeprecatedMathSpaceValue)
+DEPRECATED_OPERATION(MathML_DeprecatedMfencedElement)
+DEPRECATED_OPERATION(MathML_DeprecatedStyleAttribute)
+DEPRECATED_OPERATION(MathML_DeprecatedStixgeneralOperatorStretching)
+DEPRECATED_OPERATION(MathML_DeprecatedScriptminsizeAttribute)
+DEPRECATED_OPERATION(MathML_DeprecatedScriptsizemultiplierAttribute)
+DEPRECATED_OPERATION(FormSubmissionUntrustedEvent)
+DEPRECATED_OPERATION(ElementSetCapture)
+DEPRECATED_OPERATION(ElementReleaseCapture)
+DEPRECATED_OPERATION(DocumentReleaseCapture)
+DEPRECATED_OPERATION(OffscreenCanvasToBlob)
+DEPRECATED_OPERATION(MozPreservesPitchDeprecatedPrefix)
+DEPRECATED_OPERATION(SVGNearestViewportElement)
+DEPRECATED_OPERATION(SVGFarthestViewportElement)
diff --git a/dom/base/nsDocElementCreatedNotificationRunner.h b/dom/base/nsDocElementCreatedNotificationRunner.h
new file mode 100644
index 0000000000..948dd18c56
--- /dev/null
+++ b/dom/base/nsDocElementCreatedNotificationRunner.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocElementCreatedNotificationRunner_h
+#define nsDocElementCreatedNotificationRunner_h
+
+#include "mozilla/Attributes.h"
+#include "nsThreadUtils.h" /* nsRunnable */
+
+#include "nsContentSink.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Document.h"
+
+class nsDocElementCreatedNotificationRunner : public mozilla::Runnable {
+ public:
+ explicit nsDocElementCreatedNotificationRunner(mozilla::dom::Document* aDoc)
+ : mozilla::Runnable("nsDocElementCreatedNotificationRunner"),
+ mDoc(aDoc) {}
+
+ NS_IMETHOD Run() override {
+ nsContentSink::NotifyDocElementCreated(mDoc);
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::Document> mDoc;
+};
+
+#endif /* nsDocElementCreatedNotificationRunner_h */
diff --git a/dom/base/nsDocumentWarningList.h b/dom/base/nsDocumentWarningList.h
new file mode 100644
index 0000000000..e015485d3d
--- /dev/null
+++ b/dom/base/nsDocumentWarningList.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// IWYU pragma: private, include "mozilla/dom/Document.h"
+
+/*
+ * This file contains the list of document DOM operations warnings. It is
+ * designed to be used as input to the C preprocessor *only*.
+ */
+
+DOCUMENT_WARNING(IgnoringWillChangeOverBudget)
+DOCUMENT_WARNING(PreventDefaultFromPassiveListener)
+DOCUMENT_WARNING(SVGRefLoop)
+DOCUMENT_WARNING(SVGRefChainLengthExceeded)
+DOCUMENT_WARNING(NotificationsRequireUserGestureDeprecation)
+DOCUMENT_WARNING(DocumentSetDomainNotAllowed)
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
new file mode 100644
index 0000000000..ead05d9103
--- /dev/null
+++ b/dom/base/nsFocusManager.cpp
@@ -0,0 +1,5443 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowserParent.h"
+
+#include "nsFocusManager.h"
+
+#include "LayoutConstants.h"
+#include "ChildIterator.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindow.h"
+#include "nsContentUtils.h"
+#include "ContentParent.h"
+#include "nsPIDOMWindow.h"
+#include "nsIContentInlines.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIFormControl.h"
+#include "nsLayoutUtils.h"
+#include "nsFrameTraversal.h"
+#include "nsIWebNavigation.h"
+#include "nsCaret.h"
+#include "nsIBaseWindow.h"
+#include "nsIAppWindow.h"
+#include "nsTextControlFrame.h"
+#include "nsViewManager.h"
+#include "nsFrameSelection.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Selection.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIScriptError.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIPrincipal.h"
+#include "nsIObserverService.h"
+#include "BrowserChild.h"
+#include "nsFrameLoader.h"
+#include "nsHTMLDocument.h"
+#include "nsNetUtil.h"
+#include "nsRange.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsQueryObject.h"
+
+#include "mozilla/AccessibleCaretEventHub.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/XULPopupElement.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PointerLockManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_full_screen_api.h"
+#include <algorithm>
+
+#include "nsIDOMXULMenuListElement.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::widget;
+
+// Two types of focus pr logging are available:
+// 'Focus' for normal focus manager calls
+// 'FocusNavigation' for tab and document navigation
+LazyLogModule gFocusLog("Focus");
+LazyLogModule gFocusNavigationLog("FocusNavigation");
+
+#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
+#define LOGFOCUSNAVIGATION(args) \
+ MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
+
+#define LOGTAG(log, format, content) \
+ if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
+ nsAutoCString tag("(none)"_ns); \
+ if (content) { \
+ content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
+ } \
+ MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
+ }
+
+#define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
+#define LOGCONTENTNAVIGATION(format, content) \
+ LOGTAG(gFocusNavigationLog, format, content)
+
+struct nsDelayedBlurOrFocusEvent {
+ nsDelayedBlurOrFocusEvent(EventMessage aEventMessage, PresShell* aPresShell,
+ Document* aDocument, EventTarget* aTarget,
+ EventTarget* aRelatedTarget)
+ : mPresShell(aPresShell),
+ mDocument(aDocument),
+ mTarget(aTarget),
+ mEventMessage(aEventMessage),
+ mRelatedTarget(aRelatedTarget) {}
+
+ nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
+ : mPresShell(aOther.mPresShell),
+ mDocument(aOther.mDocument),
+ mTarget(aOther.mTarget),
+ mEventMessage(aOther.mEventMessage) {}
+
+ RefPtr<PresShell> mPresShell;
+ nsCOMPtr<Document> mDocument;
+ nsCOMPtr<EventTarget> mTarget;
+ EventMessage mEventMessage;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField) {
+ aField.mPresShell = nullptr;
+ aField.mDocument = nullptr;
+ aField.mTarget = nullptr;
+ aField.mRelatedTarget = nullptr;
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsDelayedBlurOrFocusEvent& aField, const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(
+ aCallback, static_cast<nsIDocumentObserver*>(aField.mPresShell.get()),
+ aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName,
+ aFlags);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
+ NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
+
+NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager, mActiveWindow,
+ mActiveBrowsingContextInContent,
+ mActiveBrowsingContextInChrome, mFocusedWindow,
+ mFocusedBrowsingContextInContent,
+ mFocusedBrowsingContextInChrome, mFocusedElement,
+ mFirstBlurEvent, mFirstFocusEvent,
+ mWindowBeingLowered, mDelayedBlurFocusEvents)
+
+StaticRefPtr<nsFocusManager> nsFocusManager::sInstance;
+bool nsFocusManager::sTestMode = false;
+uint64_t nsFocusManager::sFocusActionCounter = 0;
+
+static const char* kObservedPrefs[] = {"accessibility.browsewithcaret",
+ "accessibility.tabfocus_applies_to_xul",
+ "focusmanager.testmode", nullptr};
+
+nsFocusManager::nsFocusManager()
+ : mActionIdForActiveBrowsingContextInContent(0),
+ mActionIdForActiveBrowsingContextInChrome(0),
+ mActionIdForFocusedBrowsingContextInContent(0),
+ mActionIdForFocusedBrowsingContextInChrome(0),
+ mActiveBrowsingContextInContentSetFromOtherProcess(false),
+ mEventHandlingNeedsFlush(false) {}
+
+nsFocusManager::~nsFocusManager() {
+ Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
+ this);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+}
+
+// static
+nsresult nsFocusManager::Init() {
+ sInstance = new nsFocusManager();
+
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+
+ Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
+ sInstance.get());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(sInstance, "xpcom-shutdown", true);
+ }
+
+ return NS_OK;
+}
+
+// static
+void nsFocusManager::Shutdown() { sInstance = nullptr; }
+
+// static
+void nsFocusManager::PrefChanged(const char* aPref, void* aSelf) {
+ if (RefPtr<nsFocusManager> fm = static_cast<nsFocusManager*>(aSelf)) {
+ fm->PrefChanged(aPref);
+ }
+}
+
+void nsFocusManager::PrefChanged(const char* aPref) {
+ nsDependentCString pref(aPref);
+ if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
+ UpdateCaretForCaretBrowsingMode();
+ } else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+ } else if (pref.EqualsLiteral("focusmanager.testmode")) {
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+ }
+}
+
+NS_IMETHODIMP
+nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ mActiveWindow = nullptr;
+ mActiveBrowsingContextInContent = nullptr;
+ mActionIdForActiveBrowsingContextInContent = 0;
+ mActionIdForFocusedBrowsingContextInContent = 0;
+ mActiveBrowsingContextInChrome = nullptr;
+ mActionIdForActiveBrowsingContextInChrome = 0;
+ mActionIdForFocusedBrowsingContextInChrome = 0;
+ mFocusedWindow = nullptr;
+ mFocusedBrowsingContextInContent = nullptr;
+ mFocusedBrowsingContextInChrome = nullptr;
+ mFocusedElement = nullptr;
+ mFirstBlurEvent = nullptr;
+ mFirstFocusEvent = nullptr;
+ mWindowBeingLowered = nullptr;
+ mDelayedBlurFocusEvents.Clear();
+ }
+
+ return NS_OK;
+}
+
+static bool ActionIdComparableAndLower(uint64_t aActionId,
+ uint64_t aReference) {
+ MOZ_ASSERT(aActionId, "Uninitialized action id");
+ auto [actionProc, actionId] =
+ nsContentUtils::SplitProcessSpecificId(aActionId);
+ auto [refProc, refId] = nsContentUtils::SplitProcessSpecificId(aReference);
+ return actionProc == refProc && actionId < refId;
+}
+
+// given a frame content node, retrieve the nsIDOMWindow displayed in it
+static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) {
+ Document* doc = aContent->GetComposedDoc();
+ if (doc) {
+ Document* subdoc = doc->GetSubDocumentFor(aContent);
+ if (subdoc) {
+ return subdoc->GetWindow();
+ }
+ }
+
+ return nullptr;
+}
+
+bool nsFocusManager::IsFocused(nsIContent* aContent) {
+ if (!aContent || !mFocusedElement) {
+ return false;
+ }
+ return aContent == mFocusedElement;
+}
+
+bool nsFocusManager::IsTestMode() { return sTestMode; }
+
+bool nsFocusManager::IsInActiveWindow(BrowsingContext* aBC) const {
+ RefPtr<BrowsingContext> top = aBC->Top();
+ if (XRE_IsParentProcess()) {
+ top = top->Canonical()->TopCrossChromeBoundary();
+ }
+ return IsSameOrAncestor(top, GetActiveBrowsingContext());
+}
+
+// get the current window for the given content node
+static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) {
+ Document* doc = aContent->GetComposedDoc();
+ return doc ? doc->GetWindow() : nullptr;
+}
+
+// static
+Element* nsFocusManager::GetFocusedDescendant(
+ nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
+ nsPIDOMWindowOuter** aFocusedWindow) {
+ NS_ENSURE_TRUE(aWindow, nullptr);
+
+ *aFocusedWindow = nullptr;
+
+ Element* currentElement = nullptr;
+ nsPIDOMWindowOuter* window = aWindow;
+ for (;;) {
+ *aFocusedWindow = window;
+ currentElement = window->GetFocusedElement();
+ if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
+ break;
+ }
+
+ window = GetContentWindow(currentElement);
+ if (!window) {
+ break;
+ }
+
+ if (aSearchRange == eIncludeAllDescendants) {
+ continue;
+ }
+
+ MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
+
+ // If the child window doesn't have PresShell, it means the window is
+ // invisible.
+ nsIDocShell* docShell = window->GetDocShell();
+ if (!docShell) {
+ break;
+ }
+ if (!docShell->GetPresShell()) {
+ break;
+ }
+ }
+
+ NS_IF_ADDREF(*aFocusedWindow);
+
+ return currentElement;
+}
+
+// static
+InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause(
+ uint32_t aFlags) {
+ if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
+ return InputContextAction::CAUSE_TOUCH;
+ } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
+ return InputContextAction::CAUSE_MOUSE;
+ } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
+ return InputContextAction::CAUSE_KEY;
+ } else if (aFlags & nsIFocusManager::FLAG_BYLONGPRESS) {
+ return InputContextAction::CAUSE_LONGPRESS;
+ }
+ return InputContextAction::CAUSE_UNKNOWN;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) {
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Must not be called outside the parent process.");
+ NS_IF_ADDREF(*aWindow = mActiveWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetActiveBrowsingContext(BrowsingContext** aBrowsingContext) {
+ NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContext());
+ return NS_OK;
+}
+
+void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow,
+ CallerType aCallerType) {
+ if (RefPtr<nsFocusManager> fm = sInstance) {
+ fm->SetFocusedWindowWithCallerType(aWindow, aCallerType);
+ }
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) {
+ NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedContentBrowsingContext(
+ BrowsingContext** aBrowsingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ XRE_IsParentProcess(),
+ "We only have use cases for this in the parent process");
+ NS_IF_ADDREF(*aBrowsingContext = GetFocusedBrowsingContextInChrome());
+ return NS_OK;
+}
+
+nsresult nsFocusManager::SetFocusedWindowWithCallerType(
+ mozIDOMWindowProxy* aWindowToFocus, CallerType aCallerType) {
+ LOGFOCUS(("<<SetFocusedWindow begin>>"));
+
+ nsCOMPtr<nsPIDOMWindowOuter> windowToFocus =
+ nsPIDOMWindowOuter::From(aWindowToFocus);
+ NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
+
+ nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
+ Maybe<uint64_t> actionIdFromSetFocusInner;
+ if (frameElement) {
+ // pass false for aFocusChanged so that the caret does not get updated
+ // and scrolling does not occur.
+ actionIdFromSetFocusInner = SetFocusInner(frameElement, 0, false, true);
+ } else {
+ // this is a top-level window. If the window has a child frame focused,
+ // clear the focus. Otherwise, focus should already be in this frame, or
+ // already cleared. This ensures that focus will be in this frame and not
+ // in a child.
+ nsIContent* content = windowToFocus->GetFocusedElement();
+ if (content) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
+ ClearFocus(windowToFocus);
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
+ const uint64_t actionId = actionIdFromSetFocusInner.isSome()
+ ? actionIdFromSetFocusInner.value()
+ : sInstance->GenerateFocusActionId();
+ if (rootWindow) {
+ RaiseWindow(rootWindow, aCallerType, actionId);
+ }
+
+ LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64 ">>", actionId));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFocusManager::SetFocusedWindow(
+ mozIDOMWindowProxy* aWindowToFocus) {
+ return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System);
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElement(Element** aFocusedElement) {
+ RefPtr<Element> focusedElement = mFocusedElement;
+ focusedElement.forget(aFocusedElement);
+ return NS_OK;
+}
+
+uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter* aWindow) const {
+ nsPIDOMWindowOuter* window = aWindow ? aWindow : mFocusedWindow.get();
+ uint32_t method = window ? window->GetFocusMethod() : 0;
+ NS_ASSERTION((method & METHOD_MASK) == method, "invalid focus method");
+ return method;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow,
+ uint32_t* aLastFocusMethod) {
+ *aLastFocusMethod = GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) {
+ LOGFOCUS(("<<SetFocus begin>>"));
+
+ NS_ENSURE_ARG(aElement);
+
+ SetFocusInner(aElement, aFlags, true, true);
+
+ LOGFOCUS(("<<SetFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
+ bool* aIsFocusable) {
+ NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
+ *aIsFocusable = !!FlushAndCheckIfFocusable(aElement, aFlags);
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
+ uint32_t aType, uint32_t aFlags, Element** aElement) {
+ *aElement = nullptr;
+
+ LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
+ Document* doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(),
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+
+ LOGCONTENT(" Current Focus: %s", mFocusedElement.get());
+
+ // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
+ // the other focus methods is already set, or we're just moving to the root
+ // or caret position.
+ if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
+ (aFlags & METHOD_MASK) == 0) {
+ aFlags |= FLAG_BYMOVEFOCUS;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (aStartElement) {
+ window = GetCurrentWindow(aStartElement);
+ } else {
+ window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
+ }
+
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ // Flush to ensure that focusability of descendants is computed correctly.
+ if (RefPtr<Document> doc = window->GetExtantDoc()) {
+ doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
+ }
+
+ bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
+ nsCOMPtr<nsIContent> newFocus;
+ nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType,
+ noParentTraversal, true,
+ getter_AddRefs(newFocus));
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
+
+ if (newFocus && newFocus->IsElement()) {
+ // for caret movement, pass false for the aFocusChanged argument,
+ // otherwise the caret will end up moving to the focus position. This
+ // would be a problem because the caret would move to the beginning of the
+ // focused link making it impossible to navigate the caret over a link.
+ SetFocusInner(MOZ_KnownLive(newFocus->AsElement()), aFlags,
+ aType != MOVEFOCUS_CARET, true);
+ *aElement = do_AddRef(newFocus->AsElement()).take();
+ } else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
+ // no content was found, so clear the focus for these two types.
+ ClearFocus(window);
+ }
+
+ LOGFOCUS(("<<MoveFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) {
+ LOGFOCUS(("<<ClearFocus begin>>"));
+
+ // if the window to clear is the focused window or an ancestor of the
+ // focused window, then blur the existing focused content. Otherwise, the
+ // focus is somewhere else so just update the current node.
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (IsSameOrAncestor(window, GetFocusedBrowsingContext())) {
+ RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
+ bool isAncestor = (GetFocusedBrowsingContext() != bc);
+ uint64_t actionId = GenerateFocusActionId();
+ if (Blur(bc, nullptr, isAncestor, true, false, actionId)) {
+ // if we are clearing the focus on an ancestor of the focused window,
+ // the ancestor will become the new focused window, so focus it
+ if (isAncestor) {
+ Focus(window, nullptr, 0, true, false, false, true, actionId);
+ }
+ }
+ } else {
+ window->SetFocusedElement(nullptr);
+ }
+
+ LOGFOCUS(("<<ClearFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
+ bool aDeep,
+ mozIDOMWindowProxy** aFocusedWindow,
+ Element** aElement) {
+ *aElement = nullptr;
+ if (aFocusedWindow) {
+ *aFocusedWindow = nullptr;
+ }
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ RefPtr<Element> focusedElement =
+ GetFocusedDescendant(window,
+ aDeep ? nsFocusManager::eIncludeAllDescendants
+ : nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+
+ focusedElement.forget(aElement);
+
+ if (aFocusedWindow) {
+ NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) {
+ nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
+ nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
+ if (dsti) {
+ if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // don't move the caret for editable documents
+ bool isEditable;
+ docShell->GetEditable(&isEditable);
+ if (isEditable) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ if (RefPtr<Element> focusedElement = window->GetFocusedElement()) {
+ MoveCaretToFocus(presShell, focusedElement);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId) {
+ if (!aWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ BrowsingContext* bc = window->GetBrowsingContext();
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64, aWindow,
+ mActiveWindow.get(), mFocusedWindow.get(), aActionId));
+ Document* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Raised Window: %p %s", aWindow,
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(),
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (mActiveWindow == window) {
+ // The window is already active, so there is no need to focus anything,
+ // but make sure that the right widget is focused. This is a special case
+ // for Windows because when restoring a minimized window, a second
+ // activation will occur and the top-level widget could be focused instead
+ // of the child we want. We solve this by calling SetFocus to ensure that
+ // what the focus manager thinks should be the current widget is actually
+ // focused.
+ EnsureCurrentWidgetFocused(CallerType::System);
+ return;
+ }
+
+ // lower the existing window, if any. This shouldn't happen usually.
+ if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow) {
+ WindowLowered(activeWindow, aActionId);
+ }
+ } else if (bc->IsTop()) {
+ BrowsingContext* active = GetActiveBrowsingContext();
+ if (active == bc && !mActiveBrowsingContextInContentSetFromOtherProcess) {
+ // EnsureCurrentWidgetFocused() should not be necessary with
+ // PuppetWidget.
+ return;
+ }
+
+ if (active && active != bc) {
+ if (active->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow = active->GetDOMWindow();
+ WindowLowered(activeWindow, aActionId);
+ }
+ // No else, because trying to lower other-process windows
+ // from here can result in the BrowsingContext no longer
+ // existing in the parent process by the time it deserializes
+ // the IPC message.
+ }
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
+ // If there's no docShellAsItem, this window must have been closed,
+ // in that case there is no tree owner.
+ if (!docShellAsItem) {
+ return;
+ }
+
+ // set this as the active window
+ if (XRE_IsParentProcess()) {
+ mActiveWindow = window;
+ } else if (bc->IsTop()) {
+ SetActiveBrowsingContextInContent(bc, aActionId);
+ }
+
+ // ensure that the window is enabled and visible
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ if (baseWindow) {
+ bool isEnabled = true;
+ if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
+ return;
+ }
+
+ baseWindow->SetVisibility(true);
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Unsetting top-level focus upon lowering was inhibited to accommodate
+ // ATOK, so we need to do it here.
+ BrowserParent::UnsetTopLevelWebFocusAll();
+ ActivateOrDeactivate(window, true);
+ }
+
+ // retrieve the last focused element within the window that was raised
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ RefPtr<Element> currentFocus = GetFocusedDescendant(
+ window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
+
+ NS_ASSERTION(currentWindow, "window raised with no window current");
+ if (!currentWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(baseWindow));
+ // We use mFocusedWindow here is basically for the case that iframe navigate
+ // from a.com to b.com for example, so it ends up being loaded in a different
+ // process after Fission, but
+ // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
+ // still be true because focused browsing context is synced, and we won't
+ // fire a focus event while focusing if we use it as condition.
+ Focus(currentWindow, currentFocus, 0, currentWindow != mFocusedWindow, false,
+ appWin != nullptr, true, aActionId);
+}
+
+void nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId) {
+ if (!aWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow,
+ mActiveWindow.get(), mFocusedWindow.get()));
+ Document* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Lowered Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (mActiveWindow != window) {
+ return;
+ }
+ } else {
+ BrowsingContext* bc = window->GetBrowsingContext();
+ BrowsingContext* active = GetActiveBrowsingContext();
+ if (active != bc->Top()) {
+ return;
+ }
+ }
+
+ // clear the mouse capture as the active window has changed
+ PresShell::ReleaseCapturingContent();
+
+ // In addition, reset the drag state to ensure that we are no longer in
+ // drag-select mode.
+ if (mFocusedWindow) {
+ nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
+ if (docShell) {
+ if (PresShell* presShell = docShell->GetPresShell()) {
+ RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
+ frameSelection->SetDragState(false);
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ ActivateOrDeactivate(window, false);
+ }
+
+ // keep track of the window being lowered, so that attempts to raise the
+ // window can be prevented until we return. Otherwise, focus can get into
+ // an unusual state.
+ mWindowBeingLowered = window;
+ if (XRE_IsParentProcess()) {
+ mActiveWindow = nullptr;
+ } else {
+ BrowsingContext* bc = window->GetBrowsingContext();
+ if (bc == bc->Top()) {
+ SetActiveBrowsingContextInContent(nullptr, aActionId);
+ }
+ }
+
+ if (mFocusedWindow) {
+ Blur(nullptr, nullptr, true, true, false, aActionId);
+ }
+
+ mWindowBeingLowered = nullptr;
+}
+
+nsresult nsFocusManager::ContentRemoved(Document* aDocument,
+ nsIContent* aContent) {
+ NS_ENSURE_ARG(aDocument);
+ NS_ENSURE_ARG(aContent);
+
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ if (!window) {
+ return NS_OK;
+ }
+
+ // if the content is currently focused in the window, or is an
+ // shadow-including inclusive ancestor of the currently focused element,
+ // reset the focus within that window.
+ Element* content = window->GetFocusedElement();
+ if (!content) {
+ return NS_OK;
+ }
+
+ if (!nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) {
+ return NS_OK;
+ }
+
+ Element* newFocusedElement = [&]() -> Element* {
+ if (auto* sr = ShadowRoot::FromNode(aContent)) {
+ if (sr->IsUAWidget() && sr->Host()->IsHTMLElement(nsGkAtoms::input)) {
+ return sr->Host();
+ }
+ }
+ return nullptr;
+ }();
+
+ window->SetFocusedElement(newFocusedElement);
+
+ // if this window is currently focused, clear the global focused
+ // element as well, but don't fire any events.
+ if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) {
+ mFocusedElement = newFocusedElement;
+ } else if (Document* subdoc = aDocument->GetSubDocumentFor(content)) {
+ // Check if the node that was focused is an iframe or similar by looking if
+ // it has a subdocument. This would indicate that this focused iframe
+ // and its descendants will be going away. We will need to move the focus
+ // somewhere else, so just clear the focus in the toplevel window so that no
+ // element is focused.
+ //
+ // The Fission case is handled in FlushAndCheckIfFocusable().
+ if (nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell()) {
+ nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
+ if (childWindow &&
+ IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) {
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
+ ClearFocus(activeWindow);
+ } else {
+ BrowsingContext* active = GetActiveBrowsingContext();
+ if (active) {
+ if (active->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
+ active->GetDOMWindow();
+ ClearFocus(activeWindow);
+ } else {
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendClearFocus(active);
+ }
+ } // no else, because ClearFocus does nothing with nullptr
+ }
+ }
+ }
+ }
+
+ // Notify the editor in case we removed its ancestor limiter.
+ if (content->IsEditable()) {
+ if (nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell()) {
+ if (RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor()) {
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+ if (selection && selection->GetFrameSelection() &&
+ content == selection->GetFrameSelection()->GetAncestorLimiter()) {
+ htmlEditor->FinalizeSelection();
+ }
+ }
+ }
+ }
+
+ if (!newFocusedElement) {
+ NotifyFocusStateChange(content, newFocusedElement, 0,
+ /* aGettingFocus = */ false, false);
+ } else {
+ // We should already have the right state, which is managed by the <input>
+ // widget.
+ MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS));
+ }
+ return NS_OK;
+}
+
+void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow,
+ bool aNeedsFocus) {
+ if (!aWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(),
+ mActiveWindow.get(), mFocusedWindow.get()));
+ Document* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS(("Shown Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+
+ if (mFocusedWindow) {
+ doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (BrowsingContext* bc = window->GetBrowsingContext()) {
+ if (bc->IsTop()) {
+ bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (mFocusedWindow != window) {
+ return;
+ }
+ } else {
+ BrowsingContext* bc = window->GetBrowsingContext();
+ if (!bc || mFocusedBrowsingContextInContent != bc) {
+ return;
+ }
+ // Sync the window for a newly-created OOP iframe
+ // Set actionId to zero to signify that it should be ignored.
+ SetFocusedWindowInternal(window, 0, false);
+ }
+
+ if (aNeedsFocus) {
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ RefPtr<Element> currentFocus = GetFocusedDescendant(
+ window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
+
+ if (currentWindow) {
+ Focus(currentWindow, currentFocus, 0, true, false, false, true,
+ GenerateFocusActionId());
+ }
+ } else {
+ // Sometimes, an element in a window can be focused before the window is
+ // visible, which would mean that the widget may not be properly focused.
+ // When the window becomes visible, make sure the right widget is focused.
+ EnsureCurrentWidgetFocused(CallerType::System);
+ }
+}
+
+void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId) {
+ // if there is no window or it is not the same or an ancestor of the
+ // currently focused window, just return, as the current focus will not
+ // be affected.
+
+ if (!aWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64,
+ window.get(), mActiveWindow.get(), mFocusedWindow.get(),
+ aActionId));
+ nsAutoCString spec;
+ Document* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Hide Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+
+ if (mFocusedWindow) {
+ doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (!IsSameOrAncestor(window, mFocusedWindow)) {
+ return;
+ }
+
+ // at this point, we know that the window being hidden is either the focused
+ // window, or an ancestor of the focused window. Either way, the focus is no
+ // longer valid, so it needs to be updated.
+
+ const RefPtr<Element> oldFocusedElement = std::move(mFocusedElement);
+
+ nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
+ if (!focusedDocShell) {
+ return;
+ }
+
+ const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
+
+ if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
+ NotifyFocusStateChange(oldFocusedElement, nullptr, 0, false, false);
+ window->UpdateCommands(u"focus"_ns, nullptr, 0);
+
+ if (presShell) {
+ RefPtr<Document> composedDoc = oldFocusedElement->GetComposedDoc();
+ SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement,
+ false);
+ }
+ }
+
+ const RefPtr<nsPresContext> focusedPresContext =
+ presShell ? presShell->GetPresContext() : nullptr;
+ IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
+ GetFocusMoveActionCause(0));
+ if (presShell) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ // If a window is being "hidden" because its BrowsingContext is changing
+ // remoteness, we don't want to handle docshell destruction by moving focus.
+ // Instead, the focused browsing context should stay the way it is (so that
+ // the newly "shown" window in the other process knows to take focus) and
+ // we should just null out the process-local field.
+ nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
+ // Check if we're currently hiding a non-remote nsDocShell due to its
+ // BrowsingContext navigating to become remote. Normally, when a focused
+ // subframe is hidden, focus is moved to the frame element, but focus should
+ // stay with the BrowsingContext when performing a process switch. We don't
+ // need to consider process switches where the hiding docshell is already
+ // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
+ // frame element is handled elsewhere.
+ if (docShellBeingHidden &&
+ nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() &&
+ docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) {
+ if (mFocusedWindow != window) {
+ // The window being hidden is an ancestor of the focused window.
+#ifdef DEBUG
+ BrowsingContext* ancestor = window->GetBrowsingContext();
+ BrowsingContext* bc = mFocusedWindow->GetBrowsingContext();
+ for (;;) {
+ if (!bc) {
+ MOZ_ASSERT(false, "Should have found ancestor");
+ }
+ bc = bc->GetParent();
+ if (ancestor == bc) {
+ break;
+ }
+ }
+#endif
+ // This call adjusts the focused browsing context and window.
+ // The latter gets nulled out immediately below.
+ SetFocusedWindowInternal(window, aActionId);
+ }
+ mFocusedWindow = nullptr;
+ window->SetFocusedElement(nullptr);
+ return;
+ }
+
+ // if the docshell being hidden is being destroyed, then we want to move
+ // focus somewhere else. Call ClearFocus on the toplevel window, which
+ // will have the effect of clearing the focus and moving the focused window
+ // to the toplevel window. But if the window isn't being destroyed, we are
+ // likely just loading a new document in it, so we want to maintain the
+ // focused window so that the new document gets properly focused.
+ bool beingDestroyed = !docShellBeingHidden;
+ if (docShellBeingHidden) {
+ docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
+ }
+ if (beingDestroyed) {
+ // There is usually no need to do anything if a toplevel window is going
+ // away, as we assume that WindowLowered will be called. However, this may
+ // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
+ // a leak. So if the active window is being destroyed, call WindowLowered
+ // directly.
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
+ if (activeWindow == mFocusedWindow || activeWindow == window) {
+ WindowLowered(activeWindow, aActionId);
+ } else {
+ ClearFocus(activeWindow);
+ }
+ } else {
+ BrowsingContext* active = GetActiveBrowsingContext();
+ if (active) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
+ active->GetDOMWindow()) {
+ if ((mFocusedWindow &&
+ mFocusedWindow->GetBrowsingContext() == active) ||
+ (window->GetBrowsingContext() == active)) {
+ WindowLowered(activeWindow, aActionId);
+ } else {
+ ClearFocus(activeWindow);
+ }
+ } // else do nothing when an out-of-process iframe is torn down
+ }
+ }
+ return;
+ }
+
+ if (!XRE_IsParentProcess() &&
+ mActiveBrowsingContextInContent ==
+ docShellBeingHidden->GetBrowsingContext() &&
+ mActiveBrowsingContextInContent->GetIsInBFCache()) {
+ SetActiveBrowsingContextInContent(nullptr, aActionId);
+ }
+
+ // if the window being hidden is an ancestor of the focused window, adjust
+ // the focused window so that it points to the one being hidden. This
+ // ensures that the focused window isn't in a chain of frames that doesn't
+ // exist any more.
+ if (window != mFocusedWindow) {
+ nsCOMPtr<nsIDocShellTreeItem> dsti =
+ mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
+ if (dsti) {
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetInProcessParent(getter_AddRefs(parentDsti));
+ if (parentDsti) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
+ parentDsti->GetWindow()) {
+ parentWindow->SetFocusedElement(nullptr);
+ }
+ }
+ }
+
+ SetFocusedWindowInternal(window, aActionId);
+ }
+}
+
+void nsFocusManager::FireDelayedEvents(Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+
+ // fire any delayed focus and blur events in the same order that they were
+ // added
+ for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
+ if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
+ if (!aDocument->GetInnerWindow() ||
+ !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
+ // If the document was navigated away from or is defunct, don't bother
+ // firing events on it. Note the symmetry between this condition and
+ // the similar one in Document.cpp:FireOrClearDelayedEvents.
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+ --i;
+ } else if (!aDocument->EventHandlingSuppressed()) {
+ EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
+ nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
+ RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
+ nsCOMPtr<EventTarget> relatedTarget =
+ mDelayedBlurFocusEvents[i].mRelatedTarget;
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+
+ FireFocusOrBlurEvent(message, presShell, target, false, false,
+ relatedTarget);
+ --i;
+ }
+ }
+ }
+}
+
+void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) {
+ MOZ_ASSERT(aWindow, "Expected non-null window.");
+ MOZ_ASSERT(aWindow != mActiveWindow,
+ "How come we're nuking a window that's still active?");
+ if (aWindow == mFocusedWindow) {
+ mFocusedWindow = nullptr;
+ SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
+ mFocusedElement = nullptr;
+ }
+}
+
+nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement)
+ : mElement(aElement) {}
+
+nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
+
+// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
+static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter* aWindow,
+ const Element& aElement,
+ int32_t aFocusFlags) {
+ // If we were explicitly requested to show the ring, do it.
+ if (aFocusFlags & nsIFocusManager::FLAG_SHOWRING) {
+ return true;
+ }
+
+ if (aFocusFlags & nsIFocusManager::FLAG_NOSHOWRING) {
+ return false;
+ }
+
+ if (aWindow->ShouldShowFocusRing()) {
+ // The window decision also trumps any other heuristic.
+ return true;
+ }
+
+ // Any element which supports keyboard input (such as an input element, or any
+ // other element which may trigger a virtual keyboard to be shown on focus if
+ // a physical keyboard is not present) should always match :focus-visible when
+ // focused.
+ {
+ if (aElement.IsHTMLElement(nsGkAtoms::textarea) || aElement.IsEditable()) {
+ return true;
+ }
+
+ if (auto* input = HTMLInputElement::FromNode(aElement)) {
+ if (input->IsSingleLineTextControl()) {
+ return true;
+ }
+ }
+ }
+
+ switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) {
+ case InputContextAction::CAUSE_KEY:
+ // If the user interacts with the page via the keyboard, the currently
+ // focused element should match :focus-visible (i.e. keyboard usage may
+ // change whether this pseudo-class matches even if it doesn't affect
+ // :focus).
+ return true;
+ case InputContextAction::CAUSE_UNKNOWN:
+ // We render outlines if the last "known" focus method was by key or there
+ // was no previous known focus method, otherwise we don't.
+ return aWindow->UnknownFocusMethodShouldShowOutline();
+ case InputContextAction::CAUSE_MOUSE:
+ case InputContextAction::CAUSE_TOUCH:
+ case InputContextAction::CAUSE_LONGPRESS:
+ // If the user interacts with the page via a pointing device, such that
+ // the focus is moved to a new element which does not support user input,
+ // the newly focused element should not match :focus-visible.
+ return false;
+ case InputContextAction::CAUSE_UNKNOWN_CHROME:
+ case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
+ case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
+ // TODO(emilio): We could return some of these though, looking at
+ // UserActivation. We may want to suppress focus rings for unknown /
+ // programatic focus if the user is interacting with the page but not
+ // during keyboard input, or such.
+ MOZ_ASSERT_UNREACHABLE(
+ "These don't get returned by GetFocusMoveActionCause");
+ break;
+ }
+ return false;
+}
+
+/* static */
+void nsFocusManager::NotifyFocusStateChange(Element* aElement,
+ Element* aElementToFocus,
+ int32_t aFlags, bool aGettingFocus,
+ bool aShouldShowFocusRing) {
+ MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus);
+ nsIContent* commonAncestor = nullptr;
+ if (aElementToFocus) {
+ commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor(
+ aElement, aElementToFocus);
+ }
+
+ if (aGettingFocus) {
+ ElementState stateToAdd = ElementState::FOCUS;
+ if (aShouldShowFocusRing) {
+ stateToAdd |= ElementState::FOCUSRING;
+ }
+ aElement->AddStates(stateToAdd);
+
+ for (nsIContent* host = aElement->GetContainingShadowHost(); host;
+ host = host->GetContainingShadowHost()) {
+ host->AsElement()->AddStates(ElementState::FOCUS);
+ }
+ } else {
+ constexpr auto kStatesToRemove =
+ ElementState::FOCUS | ElementState::FOCUSRING;
+ aElement->RemoveStates(kStatesToRemove);
+ for (nsIContent* host = aElement->GetContainingShadowHost(); host;
+ host = host->GetContainingShadowHost()) {
+ host->AsElement()->RemoveStates(kStatesToRemove);
+ }
+ }
+
+ // Special case for <input type="checkbox"> and <input type="radio">.
+ // The other browsers cancel active state when they gets lost focus, but
+ // does not do it for the other elements such as <button> and <a href="...">.
+ // Additionally, they may be activated with <label>, but they will get focus
+ // at `click`, but activated at `mousedown`. Therefore, we need to cancel
+ // active state at moving focus.
+ if (RefPtr<nsPresContext> presContext =
+ aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)) {
+ RefPtr<EventStateManager> esm = presContext->EventStateManager();
+ auto* activeInputElement =
+ HTMLInputElement::FromNodeOrNull(esm->GetActiveContent());
+ if (activeInputElement &&
+ (activeInputElement->ControlType() == FormControlType::InputCheckbox ||
+ activeInputElement->ControlType() == FormControlType::InputRadio) &&
+ !activeInputElement->State().HasState(ElementState::FOCUS)) {
+ esm->SetContentState(nullptr, ElementState::ACTIVE);
+ }
+ }
+
+ for (nsIContent* content = aElement; content && content != commonAncestor;
+ content = content->GetFlattenedTreeParent()) {
+ Element* element = Element::FromNode(content);
+ if (!element) {
+ continue;
+ }
+
+ if (aGettingFocus) {
+ if (element->State().HasState(ElementState::FOCUS_WITHIN)) {
+ break;
+ }
+ element->AddStates(ElementState::FOCUS_WITHIN);
+ } else {
+ element->RemoveStates(ElementState::FOCUS_WITHIN);
+ }
+ }
+}
+
+// static
+void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) {
+ if (!mFocusedWindow || sTestMode) return;
+
+ // get the main child widget for the focused window and ensure that the
+ // platform knows that this widget is focused.
+ nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ return;
+ }
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ if (!widget) {
+ return;
+ }
+ widget->SetFocus(nsIWidget::Raise::No, aCallerType);
+}
+
+void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow,
+ bool aActive) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!aWindow) {
+ return;
+ }
+
+ if (BrowsingContext* bc = aWindow->GetBrowsingContext()) {
+ MOZ_ASSERT(bc->IsTop());
+
+ RefPtr<CanonicalBrowsingContext> chromeTop =
+ bc->Canonical()->TopCrossChromeBoundary();
+ MOZ_ASSERT(bc == chromeTop);
+
+ chromeTop->SetIsActiveBrowserWindow(aActive);
+ chromeTop->CallOnAllTopDescendants(
+ [aActive](CanonicalBrowsingContext* aBrowsingContext) -> CallState {
+ aBrowsingContext->SetIsActiveBrowserWindow(aActive);
+ return CallState::Continue;
+ });
+ }
+
+ if (aWindow->GetExtantDoc()) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ aWindow->GetExtantDoc(), aWindow->GetCurrentInnerWindow(),
+ aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes,
+ Cancelable::eYes, nullptr);
+ }
+}
+
+// Retrieves innerWindowId of the window of the last focused element to
+// log a warning to the website console.
+void LogWarningFullscreenWindowRaise(Element* aElement) {
+ nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner(do_QueryInterface(aElement));
+ NS_ENSURE_TRUE_VOID(frameLoaderOwner);
+
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ NS_ENSURE_TRUE_VOID(frameLoaderOwner);
+
+ RefPtr<BrowsingContext> browsingContext = frameLoader->GetBrowsingContext();
+ NS_ENSURE_TRUE_VOID(browsingContext);
+
+ WindowGlobalParent* windowGlobalParent =
+ browsingContext->Canonical()->GetCurrentWindowGlobal();
+ NS_ENSURE_TRUE_VOID(windowGlobalParent);
+
+ // Log to console
+ nsAutoString localizedMsg;
+ nsTArray<nsString> params;
+ nsresult rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params,
+ localizedMsg);
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ Unused << nsContentUtils::ReportToConsoleByWindowID(
+ localizedMsg, nsIScriptError::warningFlag, "DOM"_ns,
+ windowGlobalParent->InnerWindowId(),
+ windowGlobalParent->GetDocumentURI());
+}
+
+// Ensure that when an embedded popup with a noautofocus attribute
+// like a date picker is opened and focused, the parent page does not blur
+static bool IsEmeddededInNoautofocusPopup(BrowsingContext& aBc) {
+ auto* embedder = aBc.GetEmbedderElement();
+ if (!embedder) {
+ return false;
+ }
+ nsIFrame* f = embedder->GetPrimaryFrame();
+ if (!f || !f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ return false;
+ }
+
+ nsIFrame* menuPopup =
+ nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::MenuPopup);
+ MOZ_ASSERT(menuPopup, "NS_FRAME_IN_POPUP lied?");
+ return static_cast<nsMenuPopupFrame*>(menuPopup)
+ ->PopupElement()
+ .GetXULBoolAttr(nsGkAtoms::noautofocus);
+}
+
+Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
+ int32_t aFlags,
+ bool aFocusChanged,
+ bool aAdjustWidget) {
+ // if the element is not focusable, just return and leave the focus as is
+ RefPtr<Element> elementToFocus =
+ FlushAndCheckIfFocusable(aNewContent, aFlags);
+ if (!elementToFocus) {
+ return Nothing();
+ }
+
+ const RefPtr<BrowsingContext> focusedBrowsingContext =
+ GetFocusedBrowsingContext();
+
+ // check if the element to focus is a frame (iframe) containing a child
+ // document. Frames are never directly focused; instead focusing a frame
+ // means focus what is inside the frame. To do this, the descendant content
+ // within the frame is retrieved and that will be focused instead.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+ nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
+ if (subWindow) {
+ elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
+ getter_AddRefs(newWindow));
+
+ // since a window is being refocused, clear aFocusChanged so that the
+ // caret position isn't updated.
+ aFocusChanged = false;
+ }
+
+ // unless it was set above, retrieve the window for the element to focus
+ if (!newWindow) {
+ newWindow = GetCurrentWindow(elementToFocus);
+ }
+
+ RefPtr<BrowsingContext> newBrowsingContext;
+ if (newWindow) {
+ newBrowsingContext = newWindow->GetBrowsingContext();
+ }
+
+ // if the element is already focused, just return. Note that this happens
+ // after the frame check above so that we compare the element that will be
+ // focused rather than the frame it is in.
+ if (!newWindow || (newBrowsingContext == GetFocusedBrowsingContext() &&
+ elementToFocus == mFocusedElement)) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(newBrowsingContext);
+
+ BrowsingContext* browsingContextToFocus = newBrowsingContext;
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(elementToFocus)) {
+ // Only look at pre-existing browsing contexts. If this function is
+ // called during reflow, calling GetBrowsingContext() could cause frame
+ // loader initialization at a time when it isn't safe.
+ if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
+ // If focus is already in the subtree rooted at bc, return early
+ // to match the single-process focus semantics. Otherwise, we'd
+ // blur and immediately refocus whatever is focused.
+ BrowsingContext* walk = focusedBrowsingContext;
+ while (walk) {
+ if (walk == bc) {
+ return Nothing();
+ }
+ walk = walk->GetParent();
+ }
+ browsingContextToFocus = bc;
+ }
+ }
+
+ // don't allow focus to be placed in docshells or descendants of docshells
+ // that are being destroyed. Also, ensure that the page hasn't been
+ // unloaded. The prevents content from being refocused during an unload event.
+ nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
+ nsCOMPtr<nsIDocShell> docShell = newDocShell;
+ while (docShell) {
+ bool inUnload;
+ docShell->GetIsInUnload(&inUnload);
+ if (inUnload) {
+ return Nothing();
+ }
+
+ bool beingDestroyed;
+ docShell->IsBeingDestroyed(&beingDestroyed);
+ if (beingDestroyed) {
+ return Nothing();
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ docShell->GetInProcessParent(getter_AddRefs(parentDsti));
+ docShell = do_QueryInterface(parentDsti);
+ if (!docShell && !XRE_IsParentProcess()) {
+ // We don't have an in-process parent, but let's see if we have
+ // an in-process ancestor or if an out-of-process ancestor
+ // is discarded.
+ do {
+ bc = bc->GetParent();
+ if (bc && bc->IsDiscarded()) {
+ return Nothing();
+ }
+ } while (bc && !bc->IsInProcess());
+ if (bc) {
+ docShell = bc->GetDocShell();
+ } else {
+ docShell = nullptr;
+ }
+ }
+ }
+
+ bool focusMovesToDifferentBC =
+ (focusedBrowsingContext != browsingContextToFocus);
+
+ if (focusedBrowsingContext && focusMovesToDifferentBC &&
+ nsContentUtils::IsHandlingKeyBoardEvent() &&
+ !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ MOZ_ASSERT(browsingContextToFocus,
+ "BrowsingContext to focus should be non-null.");
+
+ nsIPrincipal* focusedPrincipal = nullptr;
+ nsIPrincipal* newPrincipal = nullptr;
+
+ if (XRE_IsParentProcess()) {
+ if (WindowGlobalParent* focusedWindowGlobalParent =
+ focusedBrowsingContext->Canonical()->GetCurrentWindowGlobal()) {
+ focusedPrincipal = focusedWindowGlobalParent->DocumentPrincipal();
+ }
+
+ if (WindowGlobalParent* newWindowGlobalParent =
+ browsingContextToFocus->Canonical()->GetCurrentWindowGlobal()) {
+ newPrincipal = newWindowGlobalParent->DocumentPrincipal();
+ }
+ } else if (focusedBrowsingContext->IsInProcess() &&
+ browsingContextToFocus->IsInProcess()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> focused =
+ do_QueryInterface(focusedBrowsingContext->GetDOMWindow());
+ nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
+ do_QueryInterface(browsingContextToFocus->GetDOMWindow());
+ MOZ_ASSERT(focused && newFocus,
+ "BrowsingContext should always have a window here.");
+ focusedPrincipal = focused->GetPrincipal();
+ newPrincipal = newFocus->GetPrincipal();
+ }
+
+ if (!focusedPrincipal || !newPrincipal) {
+ return Nothing();
+ }
+
+ if (!focusedPrincipal->Subsumes(newPrincipal)) {
+ NS_WARNING("Not allowed to focus the new window!");
+ return Nothing();
+ }
+ }
+
+ // to check if the new element is in the active window, compare the
+ // new root docshell for the new element with the active window's docshell.
+ RefPtr<BrowsingContext> newRootBrowsingContext = nullptr;
+ bool isElementInActiveWindow = false;
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> newRootWindow = nullptr;
+ nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
+ if (dsti) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ dsti->GetInProcessRootTreeItem(getter_AddRefs(root));
+ newRootWindow = root ? root->GetWindow() : nullptr;
+
+ isElementInActiveWindow =
+ (mActiveWindow && newRootWindow == mActiveWindow);
+ }
+ if (newRootWindow) {
+ newRootBrowsingContext = newRootWindow->GetBrowsingContext();
+ }
+ } else {
+ // XXX This is wrong for `<iframe mozbrowser>` and for XUL
+ // `<browser remote="true">`. See:
+ // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
+ newRootBrowsingContext = newBrowsingContext->Top();
+ // to check if the new element is in the active window, compare the
+ // new root docshell for the new element with the active window's docshell.
+ isElementInActiveWindow =
+ (GetActiveBrowsingContext() == newRootBrowsingContext);
+ }
+
+ // Exit fullscreen if a website focuses another window
+ if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
+ !isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
+ if (XRE_IsParentProcess()) {
+ if (Document* doc = mActiveWindow ? mActiveWindow->GetDoc() : nullptr) {
+ Document::ClearPendingFullscreenRequests(doc);
+ if (doc->GetFullscreenElement()) {
+ LogWarningFullscreenWindowRaise(mFocusedElement);
+ Document::AsyncExitFullscreen(doc);
+ }
+ }
+ } else {
+ BrowsingContext* activeBrowsingContext = GetActiveBrowsingContext();
+ if (activeBrowsingContext) {
+ nsIDocShell* shell = activeBrowsingContext->GetDocShell();
+ if (shell) {
+ if (Document* doc = shell->GetDocument()) {
+ Document::ClearPendingFullscreenRequests(doc);
+ if (doc->GetFullscreenElement()) {
+ Document::AsyncExitFullscreen(doc);
+ }
+ }
+ } else {
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendMaybeExitFullscreen(activeBrowsingContext);
+ }
+ }
+ }
+ }
+
+ // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
+ // system. We don't control event dispatch to windowed plugins on non-MacOSX,
+ // so we can't display the "Press ESC to leave fullscreen mode" warning on
+ // key input if a windowed plugin is focused, so just exit fullscreen
+ // to guard against phishing.
+#ifndef XP_MACOSX
+ if (elementToFocus &&
+ nsContentUtils::GetInProcessSubtreeRootDocument(
+ elementToFocus->OwnerDoc())
+ ->GetFullscreenElement() &&
+ nsContentUtils::HasPluginWithUncontrolledEventDispatch(elementToFocus)) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ elementToFocus->OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "FocusedWindowedPluginWhileFullscreen");
+ Document::AsyncExitFullscreen(elementToFocus->OwnerDoc());
+ }
+#endif
+
+ // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
+ // shifted away from the current element if the new shell to focus is
+ // the same or an ancestor shell of the currently focused shell.
+ bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
+ IsSameOrAncestor(newWindow, focusedBrowsingContext);
+
+ // if the element is in the active window, frame switching is allowed and
+ // the content is in a visible window, fire blur and focus events.
+ bool sendFocusEvent =
+ isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
+
+ // Don't allow to steal the focus from chrome nodes if the caller cannot
+ // access them.
+ if (sendFocusEvent && mFocusedElement &&
+ mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc() &&
+ mFocusedElement->NodePrincipal()->IsSystemPrincipal() &&
+ !nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(mFocusedElement)) {
+ sendFocusEvent = false;
+ }
+
+ LOGCONTENT("Shift Focus: %s", elementToFocus.get());
+ LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
+ aFlags, mFocusedWindow.get(), newWindow.get(),
+ mFocusedElement.get()));
+ const uint64_t actionId = GenerateFocusActionId();
+ LOGFOCUS(
+ (" In Active Window: %d Moves to different BrowsingContext: %d "
+ "SendFocus: %d actionid: %" PRIu64,
+ isElementInActiveWindow, focusMovesToDifferentBC, sendFocusEvent,
+ actionId));
+
+ if (sendFocusEvent) {
+ Maybe<BlurredElementInfo> blurredInfo;
+ if (mFocusedElement) {
+ blurredInfo.emplace(*mFocusedElement);
+ }
+ // return if blurring fails or the focus changes during the blur
+ if (focusedBrowsingContext) {
+ // find the common ancestor of the currently focused window and the new
+ // window. The ancestor will need to have its currently focused node
+ // cleared once the document has been blurred. Otherwise, we'll be in a
+ // state where a document is blurred yet the chain of windows above it
+ // still points to that document.
+ // For instance, in the following frame tree:
+ // A
+ // B C
+ // D
+ // D is focused and we want to focus C. Once D has been blurred, we need
+ // to clear out the focus in A, otherwise A would still maintain that B
+ // was focused, and B that D was focused.
+ RefPtr<BrowsingContext> commonAncestor =
+ focusMovesToDifferentBC
+ ? GetCommonAncestor(newWindow, focusedBrowsingContext)
+ : nullptr;
+
+ const bool needToClearFocusedElement = [&] {
+ if (focusedBrowsingContext->IsChrome()) {
+ // Always reset focused element if focus is currently in chrome
+ // window, unless we're moving focus to a popup.
+ return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus);
+ }
+ if (focusedBrowsingContext->Top() != browsingContextToFocus->Top()) {
+ // Only reset focused element if focus moves within the same top-level
+ // content window.
+ return false;
+ }
+ // XXX for the case that we try to focus an
+ // already-focused-remote-frame, we would still send blur and focus
+ // IPC to it, but they will not generate blur or focus event, we don't
+ // want to reset activeElement on the remote frame.
+ return focusMovesToDifferentBC || focusedBrowsingContext->IsInProcess();
+ }();
+
+ const bool remainActive =
+ focusMovesToDifferentBC &&
+ IsEmeddededInNoautofocusPopup(*browsingContextToFocus);
+
+ // TODO: MOZ_KnownLive is required due to bug 1770680
+ if (!Blur(MOZ_KnownLive(needToClearFocusedElement
+ ? focusedBrowsingContext.get()
+ : nullptr),
+ commonAncestor, focusMovesToDifferentBC, aAdjustWidget,
+ remainActive, actionId, elementToFocus)) {
+ return Some(actionId);
+ }
+ }
+
+ Focus(newWindow, elementToFocus, aFlags, focusMovesToDifferentBC,
+ aFocusChanged, false, aAdjustWidget, actionId, blurredInfo);
+ } else {
+ // otherwise, for inactive windows and when the caller cannot steal the
+ // focus, update the node in the window, and raise the window if desired.
+ if (allowFrameSwitch) {
+ AdjustWindowFocus(newBrowsingContext, true, IsWindowVisible(newWindow),
+ actionId);
+ }
+
+ // set the focus node and method as needed
+ uint32_t focusMethod =
+ aFocusChanged ? aFlags & METHODANDRING_MASK
+ : newWindow->GetFocusMethod() |
+ (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING));
+ newWindow->SetFocusedElement(elementToFocus, focusMethod);
+ if (aFocusChanged) {
+ if (nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell()) {
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (presShell && presShell->DidInitialize()) {
+ ScrollIntoView(presShell, elementToFocus, aFlags);
+ }
+ }
+ }
+
+ // update the commands even when inactive so that the attributes for that
+ // window are up to date.
+ if (allowFrameSwitch) {
+ newWindow->UpdateCommands(u"focus"_ns, nullptr, 0);
+ }
+
+ if (aFlags & FLAG_RAISE) {
+ if (newRootBrowsingContext) {
+ if (XRE_IsParentProcess() || newRootBrowsingContext->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow =
+ newRootBrowsingContext->GetDOMWindow();
+ RaiseWindow(outerWindow,
+ aFlags & FLAG_NONSYSTEMCALLER ? CallerType::NonSystem
+ : CallerType::System,
+ actionId);
+ } else {
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendRaiseWindow(newRootBrowsingContext,
+ aFlags & FLAG_NONSYSTEMCALLER
+ ? CallerType::NonSystem
+ : CallerType::System,
+ actionId);
+ }
+ }
+ }
+ }
+ return Some(actionId);
+}
+
+static already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
+ BrowsingContext* aBC) {
+ // Chrome BrowsingContexts are only available in the parent process, so if
+ // we're in a content process, we only worry about the context tree.
+ if (XRE_IsParentProcess()) {
+ return aBC->Canonical()->GetParentCrossChromeBoundary();
+ }
+ return do_AddRef(aBC->GetParent());
+}
+
+bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor,
+ BrowsingContext* aContext) const {
+ if (!aPossibleAncestor) {
+ return false;
+ }
+
+ for (RefPtr<BrowsingContext> bc = aContext; bc;
+ bc = GetParentIgnoreChromeBoundary(bc)) {
+ if (bc == aPossibleAncestor) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow) const {
+ if (aWindow && aPossibleAncestor) {
+ return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(),
+ aWindow->GetBrowsingContext());
+ }
+ return false;
+}
+
+bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ BrowsingContext* aContext) const {
+ if (aPossibleAncestor) {
+ return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(), aContext);
+ }
+ return false;
+}
+
+bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow) const {
+ if (aWindow) {
+ return IsSameOrAncestor(aPossibleAncestor, aWindow->GetBrowsingContext());
+ }
+ return false;
+}
+
+mozilla::dom::BrowsingContext* nsFocusManager::GetCommonAncestor(
+ nsPIDOMWindowOuter* aWindow, mozilla::dom::BrowsingContext* aContext) {
+ NS_ENSURE_TRUE(aWindow && aContext, nullptr);
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow->GetDocShell();
+ NS_ENSURE_TRUE(dsti1, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti2 = aContext->GetDocShell();
+ NS_ENSURE_TRUE(dsti2, nullptr);
+
+ AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(dsti1);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
+ dsti1->GetInProcessParent(getter_AddRefs(parentDsti1));
+ dsti1.swap(parentDsti1);
+ } while (dsti1);
+ do {
+ parents2.AppendElement(dsti2);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
+ dsti2->GetInProcessParent(getter_AddRefs(parentDsti2));
+ dsti2.swap(parentDsti2);
+ } while (dsti2);
+
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ nsIDocShellTreeItem* parent = nullptr;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
+ nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ return parent ? parent->GetBrowsingContext() : nullptr;
+ }
+
+ BrowsingContext* bc1 = aWindow->GetBrowsingContext();
+ NS_ENSURE_TRUE(bc1, nullptr);
+
+ BrowsingContext* bc2 = aContext;
+
+ AutoTArray<BrowsingContext*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(bc1);
+ bc1 = bc1->GetParent();
+ } while (bc1);
+ do {
+ parents2.AppendElement(bc2);
+ bc2 = bc2->GetParent();
+ } while (bc2);
+
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ BrowsingContext* parent = nullptr;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ BrowsingContext* child1 = parents1.ElementAt(--pos1);
+ BrowsingContext* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ return parent;
+}
+
+bool nsFocusManager::AdjustInProcessWindowFocus(
+ BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible,
+ uint64_t aActionId) {
+ if (ActionIdComparableAndLower(aActionId,
+ mActionIdForFocusedBrowsingContextInContent)) {
+ LOGFOCUS(
+ ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as "
+ "focused from another process due to stale action id %" PRIu64 ".",
+ aBrowsingContext, aActionId));
+ return false;
+ }
+
+ BrowsingContext* bc = aBrowsingContext;
+ bool needToNotifyOtherProcess = false;
+ while (bc) {
+ // get the containing <iframe> or equivalent element so that it can be
+ // focused below.
+ nsCOMPtr<Element> frameElement = bc->GetEmbedderElement();
+ BrowsingContext* parent = bc->GetParent();
+ if (!parent && XRE_IsParentProcess()) {
+ CanonicalBrowsingContext* canonical = bc->Canonical();
+ RefPtr<WindowGlobalParent> embedder =
+ canonical->GetEmbedderWindowGlobal();
+ if (embedder) {
+ parent = embedder->BrowsingContext();
+ }
+ }
+ bc = parent;
+ if (!bc) {
+ break;
+ }
+ if (!frameElement && XRE_IsContentProcess()) {
+ needToNotifyOtherProcess = true;
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow();
+ MOZ_ASSERT(window);
+ // if the parent window is visible but the original window was not, then we
+ // have likely moved up and out from a hidden tab to the browser window, or
+ // a similar such arrangement. Stop adjusting the current nodes.
+ if (IsWindowVisible(window) != aIsVisible) {
+ break;
+ }
+
+ // When aCheckPermission is true, we should check whether the caller can
+ // access the window or not. If it cannot access, we should stop the
+ // adjusting.
+ if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) {
+ break;
+ }
+
+ if (frameElement != window->GetFocusedElement()) {
+ window->SetFocusedElement(frameElement);
+
+ RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(frameElement);
+ MOZ_ASSERT(loaderOwner);
+ RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
+ if (loader && loader->IsRemoteFrame() &&
+ GetFocusedBrowsingContext() == bc) {
+ Blur(nullptr, nullptr, true, true, false, aActionId);
+ }
+ }
+ }
+ return needToNotifyOtherProcess;
+}
+
+void nsFocusManager::AdjustWindowFocus(BrowsingContext* aBrowsingContext,
+ bool aCheckPermission, bool aIsVisible,
+ uint64_t aActionId) {
+ if (AdjustInProcessWindowFocus(aBrowsingContext, aCheckPermission, aIsVisible,
+ aActionId)) {
+ // Some ancestors of aBrowsingContext isn't in this process, so notify other
+ // processes to adjust their focused element.
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendAdjustWindowFocus(aBrowsingContext, aIsVisible,
+ aActionId);
+ }
+}
+
+bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow || aWindow->IsFrozen()) {
+ return false;
+ }
+
+ // Check if the inner window is frozen as well. This can happen when a focus
+ // change occurs while restoring a previous page.
+ nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow();
+ if (!innerWindow || innerWindow->IsFrozen()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
+ if (!baseWin) {
+ return false;
+ }
+
+ bool visible = false;
+ baseWin->GetVisibility(&visible);
+ return visible;
+}
+
+bool nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) {
+ MOZ_ASSERT(aContent, "aContent must not be NULL");
+ MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document");
+
+ // If the uncomposed document of aContent is in designMode, the root element
+ // is not focusable.
+ // NOTE: Most elements whose uncomposed document is in design mode are not
+ // focusable, just the document is focusable. However, if it's in a
+ // shadow tree, it may be focus able even if the shadow host is in
+ // design mode.
+ // Also, if aContent is not editable and it's not in designMode, it's not
+ // focusable.
+ // And in userfocusignored context nothing is focusable.
+ Document* doc = aContent->GetComposedDoc();
+ NS_ASSERTION(doc, "aContent must have current document");
+ return aContent == doc->GetRootElement() &&
+ (aContent->IsInDesignMode() || !aContent->IsEditable());
+}
+
+Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
+ uint32_t aFlags) {
+ if (!aElement) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc = aElement->GetComposedDoc();
+ // can't focus elements that are not in documents
+ if (!doc) {
+ LOGCONTENT("Cannot focus %s because content not in document", aElement)
+ return nullptr;
+ }
+
+ // Make sure that our frames are up to date while ensuring the presshell is
+ // also initialized in case we come from a script calling focus() early.
+ mEventHandlingNeedsFlush = false;
+ doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
+
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ // If this is an iframe that doesn't have an in-process subdocument, it is
+ // either an OOP iframe or an in-process iframe without lazy about:blank
+ // creation having taken place. In the OOP case, iframe is always focusable.
+ // In the in-process case, create the initial about:blank for in-process
+ // BrowsingContexts in order to have the `GetSubDocumentFor` call after this
+ // block return something.
+ //
+ // TODO(emilio): This block can probably go after bug 543435 lands.
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(aElement)) {
+ if (!aElement->IsXULElement()) {
+ // Only look at pre-existing browsing contexts. If this function is
+ // called during reflow, calling GetBrowsingContext() could cause frame
+ // loader initialization at a time when it isn't safe.
+ if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
+ // This call may create a contentViewer-created about:blank.
+ // That's intentional, so we can move focus there.
+ Unused << bc->GetDocument();
+ }
+ }
+ }
+
+ return GetTheFocusableArea(aElement, aFlags);
+}
+
+bool nsFocusManager::Blur(BrowsingContext* aBrowsingContextToClear,
+ BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget,
+ bool aRemainActive, uint64_t aActionId,
+ Element* aElementToFocus) {
+ if (XRE_IsParentProcess()) {
+ return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
+ aIsLeavingDocument, aAdjustWidget, aRemainActive,
+ aElementToFocus, aActionId);
+ }
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ bool windowToClearHandled = false;
+ bool ancestorWindowToFocusHandled = false;
+
+ RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext();
+ if (focusedBrowsingContext && focusedBrowsingContext->IsDiscarded()) {
+ focusedBrowsingContext = nullptr;
+ }
+ if (!focusedBrowsingContext) {
+ mFocusedElement = nullptr;
+ return true;
+ }
+ if (aBrowsingContextToClear && aBrowsingContextToClear->IsDiscarded()) {
+ aBrowsingContextToClear = nullptr;
+ }
+ if (aAncestorBrowsingContextToFocus &&
+ aAncestorBrowsingContextToFocus->IsDiscarded()) {
+ aAncestorBrowsingContextToFocus = nullptr;
+ }
+ // XXX should more early returns from BlurImpl be hoisted here to avoid
+ // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
+ // other processes when BlurImpl returns early in this process? Or should the
+ // IPC messages for those be sent by BlurImpl itself, in which case they could
+ // arrive late?
+ if (focusedBrowsingContext->IsInProcess()) {
+ if (aBrowsingContextToClear && !aBrowsingContextToClear->IsInProcess()) {
+ MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus &&
+ !aAncestorBrowsingContextToFocus->IsInProcess()),
+ "Both aBrowsingContextToClear and "
+ "aAncestorBrowsingContextToFocus are "
+ "out-of-process.");
+ contentChild->SendSetFocusedElement(aBrowsingContextToClear, false);
+ }
+ if (aAncestorBrowsingContextToFocus &&
+ !aAncestorBrowsingContextToFocus->IsInProcess()) {
+ contentChild->SendSetFocusedElement(aAncestorBrowsingContextToFocus,
+ true);
+ }
+ return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
+ aIsLeavingDocument, aAdjustWidget, aRemainActive,
+ aElementToFocus, aActionId);
+ }
+ if (aBrowsingContextToClear && aBrowsingContextToClear->IsInProcess()) {
+ nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow();
+ MOZ_ASSERT(windowToClear);
+ windowToClear->SetFocusedElement(nullptr);
+ windowToClearHandled = true;
+ }
+ if (aAncestorBrowsingContextToFocus &&
+ aAncestorBrowsingContextToFocus->IsInProcess()) {
+ nsPIDOMWindowOuter* ancestorWindowToFocus =
+ aAncestorBrowsingContextToFocus->GetDOMWindow();
+ MOZ_ASSERT(ancestorWindowToFocus);
+ ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
+ ancestorWindowToFocusHandled = true;
+ }
+ // The expectation is that the blurring would eventually result in an IPC
+ // message doing this anyway, but this doesn't happen if the focus is in OOP
+ // iframe which won't try to bounce an IPC message to its parent frame.
+ SetFocusedWindowInternal(nullptr, aActionId);
+ contentChild->SendBlurToParent(
+ focusedBrowsingContext, aBrowsingContextToClear,
+ aAncestorBrowsingContextToFocus, aIsLeavingDocument, aAdjustWidget,
+ windowToClearHandled, ancestorWindowToFocusHandled, aActionId);
+ return true;
+}
+
+void nsFocusManager::BlurFromOtherProcess(
+ mozilla::dom::BrowsingContext* aFocusedBrowsingContext,
+ mozilla::dom::BrowsingContext* aBrowsingContextToClear,
+ mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId) {
+ if (aFocusedBrowsingContext != GetFocusedBrowsingContext()) {
+ return;
+ }
+ BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
+ aIsLeavingDocument, aAdjustWidget, /* aRemainActive = */ false,
+ nullptr, aActionId);
+}
+
+bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
+ BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget,
+ bool aRemainActive, Element* aElementToFocus,
+ uint64_t aActionId) {
+ LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
+
+ // hold a reference to the focused content, which may be null
+ RefPtr<Element> element = mFocusedElement;
+ if (element) {
+ if (!element->IsInComposedDoc()) {
+ mFocusedElement = nullptr;
+ return true;
+ }
+ if (element == mFirstBlurEvent) {
+ return true;
+ }
+ }
+
+ RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext();
+ // hold a reference to the focused window
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (focusedBrowsingContext) {
+ window = focusedBrowsingContext->GetDOMWindow();
+ }
+ if (!window) {
+ mFocusedElement = nullptr;
+ return true;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (!docShell) {
+ if (XRE_IsContentProcess() &&
+ ActionIdComparableAndLower(
+ aActionId, mActionIdForFocusedBrowsingContextInContent)) {
+ // Unclear if this ever happens.
+ LOGFOCUS(
+ ("Ignored an attempt to null out focused BrowsingContext when "
+ "docShell is null due to a stale action id %" PRIu64 ".",
+ aActionId));
+ return true;
+ }
+
+ mFocusedWindow = nullptr;
+ // Setting focused BrowsingContext to nullptr to avoid leaking in print
+ // preview.
+ SetFocusedBrowsingContext(nullptr, aActionId);
+ mFocusedElement = nullptr;
+ return true;
+ }
+
+ // Keep a ref to presShell since dispatching the DOM event may cause
+ // the document to be destroyed.
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ if (XRE_IsContentProcess() &&
+ ActionIdComparableAndLower(
+ aActionId, mActionIdForFocusedBrowsingContextInContent)) {
+ // Unclear if this ever happens.
+ LOGFOCUS(
+ ("Ignored an attempt to null out focused BrowsingContext when "
+ "presShell is null due to a stale action id %" PRIu64 ".",
+ aActionId));
+ return true;
+ }
+ mFocusedElement = nullptr;
+ mFocusedWindow = nullptr;
+ // Setting focused BrowsingContext to nullptr to avoid leaking in print
+ // preview.
+ SetFocusedBrowsingContext(nullptr, aActionId);
+ return true;
+ }
+
+ Maybe<AutoRestore<RefPtr<Element>>> ar;
+ if (!mFirstBlurEvent) {
+ ar.emplace(mFirstBlurEvent);
+ mFirstBlurEvent = element;
+ }
+
+ const RefPtr<nsPresContext> focusedPresContext =
+ GetActiveBrowsingContext() ? presShell->GetPresContext() : nullptr;
+ IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
+ GetFocusMoveActionCause(0));
+
+ // now adjust the actual focus, by clearing the fields in the focus manager
+ // and in the window.
+ mFocusedElement = nullptr;
+ if (aBrowsingContextToClear) {
+ nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow();
+ if (windowToClear) {
+ windowToClear->SetFocusedElement(nullptr);
+ }
+ }
+
+ LOGCONTENT("Element %s has been blurred", element.get());
+
+ // Don't fire blur event on the root content which isn't editable.
+ bool sendBlurEvent =
+ element && element->IsInComposedDoc() && !IsNonFocusableRoot(element);
+ if (element) {
+ if (sendBlurEvent) {
+ NotifyFocusStateChange(element, aElementToFocus, 0, false, false);
+ }
+
+ if (!aRemainActive) {
+ bool windowBeingLowered = !aBrowsingContextToClear &&
+ !aAncestorBrowsingContextToFocus &&
+ aIsLeavingDocument && aAdjustWidget;
+ // If the object being blurred is a remote browser, deactivate remote
+ // content
+ if (BrowserParent* remote = BrowserParent::GetFrom(element)) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Let's deactivate all remote browsers.
+ BrowsingContext* topLevelBrowsingContext = remote->GetBrowsingContext();
+ topLevelBrowsingContext->PreOrderWalk([&](BrowsingContext* aContext) {
+ if (WindowGlobalParent* windowGlobalParent =
+ aContext->Canonical()->GetCurrentWindowGlobal()) {
+ if (RefPtr<BrowserParent> browserParent =
+ windowGlobalParent->GetBrowserParent()) {
+ browserParent->Deactivate(windowBeingLowered, aActionId);
+ LOGFOCUS(
+ ("%s remote browser deactivated %p, %d, actionid: %" PRIu64,
+ aContext == topLevelBrowsingContext ? "Top-level"
+ : "OOP iframe",
+ browserParent.get(), windowBeingLowered, aActionId));
+ }
+ }
+ });
+ }
+
+ // Same as above but for out-of-process iframes
+ if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(element)) {
+ bbc->Deactivate(windowBeingLowered, aActionId);
+ LOGFOCUS(
+ ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64,
+ bbc, windowBeingLowered, aActionId));
+ }
+ }
+ }
+
+ bool result = true;
+ if (sendBlurEvent) {
+ // if there is an active window, update commands. If there isn't an active
+ // window, then this was a blur caused by the active window being lowered,
+ // so there is no need to update the commands
+ if (GetActiveBrowsingContext()) {
+ window->UpdateCommands(u"focus"_ns, nullptr, 0);
+ }
+
+ SendFocusOrBlurEvent(eBlur, presShell, element->GetComposedDoc(), element,
+ false, false, aElementToFocus);
+ }
+
+ // if we are leaving the document or the window was lowered, make the caret
+ // invisible.
+ if (aIsLeavingDocument || !GetActiveBrowsingContext()) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ RefPtr<AccessibleCaretEventHub> eventHub =
+ presShell->GetAccessibleCaretEventHub();
+ if (eventHub) {
+ eventHub->NotifyBlur(aIsLeavingDocument || !GetActiveBrowsingContext());
+ }
+
+ // at this point, it is expected that this window will be still be
+ // focused, but the focused element will be null, as it was cleared before
+ // the event. If this isn't the case, then something else was focused during
+ // the blur event above and we should just return. However, if
+ // aIsLeavingDocument is set, a new document is desired, so make sure to
+ // blur the document and window.
+ if (GetFocusedBrowsingContext() != window->GetBrowsingContext() ||
+ (mFocusedElement != nullptr && !aIsLeavingDocument)) {
+ result = false;
+ } else if (aIsLeavingDocument) {
+ window->TakeFocus(false, 0);
+
+ // clear the focus so that the ancestor frame hierarchy is in the correct
+ // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
+ // focused at this point.
+ if (aAncestorBrowsingContextToFocus) {
+ nsPIDOMWindowOuter* ancestorWindowToFocus =
+ aAncestorBrowsingContextToFocus->GetDOMWindow();
+ if (ancestorWindowToFocus) {
+ ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
+ }
+ }
+
+ SetFocusedWindowInternal(nullptr, aActionId);
+ mFocusedElement = nullptr;
+
+ RefPtr<Document> doc = window->GetExtantDoc();
+ if (doc) {
+ SendFocusOrBlurEvent(eBlur, presShell, doc,
+ MOZ_KnownLive(ToSupports(doc)), false);
+ }
+ if (!GetFocusedBrowsingContext()) {
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ window->GetCurrentInnerWindow();
+ SendFocusOrBlurEvent(eBlur, presShell, doc, innerWindow, false);
+ }
+
+ // check if a different window was focused
+ result = (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
+ } else if (GetActiveBrowsingContext()) {
+ // Otherwise, the blur of the element without blurring the document
+ // occurred normally. Call UpdateCaret to redisplay the caret at the right
+ // location within the document. This is needed to ensure that the caret
+ // used for caret browsing is made visible again when an input field is
+ // blurred.
+ UpdateCaret(false, true, nullptr);
+ }
+
+ return result;
+}
+
+void nsFocusManager::ActivateRemoteFrameIfNeeded(Element& aElement,
+ uint64_t aActionId) {
+ if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
+ remote->Activate(aActionId);
+ LOGFOCUS(
+ ("Remote browser activated %p, actionid: %" PRIu64, remote, aActionId));
+ }
+
+ // Same as above but for out-of-process iframes
+ if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(&aElement)) {
+ bbc->Activate(aActionId);
+ LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64, bbc,
+ aActionId));
+ }
+}
+
+void nsFocusManager::Focus(
+ nsPIDOMWindowOuter* aWindow, Element* aElement, uint32_t aFlags,
+ bool aIsNewDocument, bool aFocusChanged, bool aWindowRaised,
+ bool aAdjustWidget, uint64_t aActionId,
+ const Maybe<BlurredElementInfo>& aBlurredElementInfo) {
+ LOGFOCUS(("<<Focus begin actionid: %" PRIu64 ">>", aActionId));
+
+ if (!aWindow) {
+ return;
+ }
+
+ if (aElement &&
+ (aElement == mFirstFocusEvent || aElement == mFirstBlurEvent)) {
+ return;
+ }
+
+ // Keep a reference to the presShell since dispatching the DOM event may
+ // cause the document to be destroyed.
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ const RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ bool focusInOtherContentProcess = false;
+ // Keep mochitest-browser-chrome harness happy by ignoring
+ // focusInOtherContentProcess in the chrome process, because the harness
+ // expects that.
+ if (!XRE_IsParentProcess()) {
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(aElement)) {
+ // Only look at pre-existing browsing contexts. If this function is
+ // called during reflow, calling GetBrowsingContext() could cause frame
+ // loader initialization at a time when it isn't safe.
+ if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
+ focusInOtherContentProcess = !bc->IsInProcess();
+ }
+ }
+
+ if (ActionIdComparableAndLower(
+ aActionId, mActionIdForFocusedBrowsingContextInContent)) {
+ // Unclear if this ever happens.
+ LOGFOCUS(
+ ("Ignored an attempt to focus an element due to stale action id "
+ "%" PRIu64 ".",
+ aActionId));
+ return;
+ }
+ }
+
+ // If the focus actually changed, set the focus method (mouse, keyboard, etc).
+ // Otherwise, just get the current focus method and use that. This ensures
+ // that the method is set during the document and window focus events.
+ uint32_t focusMethod = aFocusChanged
+ ? aFlags & METHODANDRING_MASK
+ : aWindow->GetFocusMethod() |
+ (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING));
+
+ if (!IsWindowVisible(aWindow)) {
+ // if the window isn't visible, for instance because it is a hidden tab,
+ // update the current focus and scroll it into view but don't do anything
+ // else
+ if (RefPtr elementToFocus = FlushAndCheckIfFocusable(aElement, aFlags)) {
+ aWindow->SetFocusedElement(elementToFocus, focusMethod);
+ if (aFocusChanged) {
+ ScrollIntoView(presShell, elementToFocus, aFlags);
+ }
+ }
+ return;
+ }
+
+ Maybe<AutoRestore<RefPtr<Element>>> ar;
+ if (!mFirstFocusEvent) {
+ ar.emplace(mFirstFocusEvent);
+ mFirstFocusEvent = aElement;
+ }
+
+ LOGCONTENT("Element %s has been focused", aElement);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ Document* docm = aWindow->GetExtantDoc();
+ if (docm) {
+ LOGCONTENT(" from %s", docm->GetRootElement());
+ }
+ LOGFOCUS(
+ (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64
+ "]",
+ aIsNewDocument, aFocusChanged, aWindowRaised, aFlags, aActionId));
+ }
+
+ if (aIsNewDocument) {
+ // if this is a new document, update the parent chain of frames so that
+ // focus can be traversed from the top level down to the newly focused
+ // window.
+ RefPtr<BrowsingContext> bc = aWindow->GetBrowsingContext();
+ AdjustWindowFocus(bc, false, IsWindowVisible(aWindow), aActionId);
+ }
+
+ // indicate that the window has taken focus.
+ if (aWindow->TakeFocus(true, focusMethod)) {
+ aIsNewDocument = true;
+ }
+
+ SetFocusedWindowInternal(aWindow, aActionId);
+
+ if (aAdjustWidget && !sTestMode) {
+ if (nsViewManager* vm = presShell->GetViewManager()) {
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ if (widget)
+ widget->SetFocus(nsIWidget::Raise::No, aFlags & FLAG_NONSYSTEMCALLER
+ ? CallerType::NonSystem
+ : CallerType::System);
+ }
+ }
+
+ // if switching to a new document, first fire the focus event on the
+ // document and then the window.
+ if (aIsNewDocument) {
+ RefPtr<Document> doc = aWindow->GetExtantDoc();
+ // The focus change should be notified to IMEStateManager from here if:
+ // * the focused element is in design mode or
+ // * nobody gets focus and the document is in design mode
+ // since any element whose uncomposed document is in design mode won't
+ // receive focus event.
+ if (doc && ((aElement && aElement->IsInDesignMode()) ||
+ (!aElement && doc->IsInDesignMode()))) {
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+ if (doc && !focusInOtherContentProcess) {
+ SendFocusOrBlurEvent(eFocus, presShell, doc,
+ MOZ_KnownLive(ToSupports(doc)), aWindowRaised);
+ }
+ if (GetFocusedBrowsingContext() == aWindow->GetBrowsingContext() &&
+ !mFocusedElement && !focusInOtherContentProcess) {
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ aWindow->GetCurrentInnerWindow();
+ SendFocusOrBlurEvent(eFocus, presShell, doc, innerWindow, aWindowRaised);
+ }
+ }
+
+ // check to ensure that the element is still focusable, and that nothing
+ // else was focused during the events above.
+ // Note that the focusing element may have already been moved to another
+ // document/window. In that case, we should stop setting focus to it
+ // because setting focus to the new window would cause redirecting focus
+ // again and again.
+ RefPtr elementToFocus =
+ aElement && aElement->IsInComposedDoc() &&
+ aElement->GetComposedDoc() == aWindow->GetExtantDoc()
+ ? FlushAndCheckIfFocusable(aElement, aFlags)
+ : nullptr;
+ if (elementToFocus && !mFocusedElement &&
+ GetFocusedBrowsingContext() == aWindow->GetBrowsingContext()) {
+ mFocusedElement = elementToFocus;
+
+ nsIContent* focusedNode = aWindow->GetFocusedElement();
+ const bool sendFocusEvent = elementToFocus->IsInComposedDoc() &&
+ !IsNonFocusableRoot(elementToFocus);
+ const bool isRefocus = focusedNode && focusedNode == elementToFocus;
+ const bool shouldShowFocusRing =
+ sendFocusEvent &&
+ ShouldMatchFocusVisible(aWindow, *elementToFocus, aFlags);
+
+ aWindow->SetFocusedElement(elementToFocus, focusMethod, false);
+
+ const RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ if (sendFocusEvent) {
+ NotifyFocusStateChange(elementToFocus, nullptr, aFlags,
+ /* aGettingFocus = */ true, shouldShowFocusRing);
+
+ // If this is a remote browser, focus its widget and activate remote
+ // content. Note that we might no longer be in the same document,
+ // due to the events we fired above when aIsNewDocument.
+ if (presShell->GetDocument() == elementToFocus->GetComposedDoc()) {
+ ActivateRemoteFrameIfNeeded(*elementToFocus, aActionId);
+ }
+
+ IMEStateManager::OnChangeFocus(presContext, elementToFocus,
+ GetFocusMoveActionCause(aFlags));
+
+ // as long as this focus wasn't because a window was raised, update the
+ // commands
+ // XXXndeakin P2 someone could adjust the focus during the update
+ if (!aWindowRaised) {
+ aWindow->UpdateCommands(u"focus"_ns, nullptr, 0);
+ }
+
+ // If the focused element changed, scroll it into view
+ if (aFocusChanged) {
+ ScrollIntoView(presShell, elementToFocus, aFlags);
+ }
+
+ if (!focusInOtherContentProcess) {
+ RefPtr<Document> composedDocument = elementToFocus->GetComposedDoc();
+ RefPtr<Element> relatedTargetElement =
+ aBlurredElementInfo ? aBlurredElementInfo->mElement.get() : nullptr;
+ SendFocusOrBlurEvent(eFocus, presShell, composedDocument,
+ elementToFocus, aWindowRaised, isRefocus,
+ relatedTargetElement);
+ }
+ } else {
+ // We should notify IMEStateManager of actual focused element even if it
+ // won't get focus event because the other IMEStateManager users do not
+ // want to depend on this check, but IMEStateManager wants to verify
+ // passed focused element for avoidng to overrride nested calls.
+ IMEStateManager::OnChangeFocus(presContext, elementToFocus,
+ GetFocusMoveActionCause(aFlags));
+ if (!aWindowRaised) {
+ aWindow->UpdateCommands(u"focus"_ns, nullptr, 0);
+ }
+ if (aFocusChanged) {
+ // If the focused element changed, scroll it into view
+ ScrollIntoView(presShell, elementToFocus, aFlags);
+ }
+ }
+ } else {
+ if (!mFocusedElement && mFocusedWindow == aWindow) {
+ // When there is no focused element, IMEStateManager needs to adjust IME
+ // enabled state with the document.
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+
+ if (!aWindowRaised) {
+ aWindow->UpdateCommands(u"focus"_ns, nullptr, 0);
+ }
+ }
+
+ // update the caret visibility and position to match the newly focused
+ // element. However, don't update the position if this was a focus due to a
+ // mouse click as the selection code would already have moved the caret as
+ // needed. If this is a different document than was focused before, also
+ // update the caret's visibility. If this is the same document, the caret
+ // visibility should be the same as before so there is no need to update it.
+ if (mFocusedElement == elementToFocus) {
+ RefPtr<Element> focusedElement = mFocusedElement;
+ UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
+ focusedElement);
+ }
+}
+
+class FocusBlurEvent : public Runnable {
+ public:
+ FocusBlurEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext, bool aWindowRaised, bool aIsRefocus,
+ EventTarget* aRelatedTarget)
+ : mozilla::Runnable("FocusBlurEvent"),
+ mTarget(aTarget),
+ mContext(aContext),
+ mEventMessage(aEventMessage),
+ mWindowRaised(aWindowRaised),
+ mIsRefocus(aIsRefocus),
+ mRelatedTarget(aRelatedTarget) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ event.mFromRaise = mWindowRaised;
+ event.mIsRefocus = mIsRefocus;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+
+ const nsCOMPtr<nsISupports> mTarget;
+ const RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ bool mWindowRaised;
+ bool mIsRefocus;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+class FocusInOutEvent : public Runnable {
+ public:
+ FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext,
+ nsPIDOMWindowOuter* aOriginalFocusedWindow,
+ nsIContent* aOriginalFocusedContent,
+ EventTarget* aRelatedTarget)
+ : mozilla::Runnable("FocusInOutEvent"),
+ mTarget(aTarget),
+ mContext(aContext),
+ mEventMessage(aEventMessage),
+ mOriginalFocusedWindow(aOriginalFocusedWindow),
+ mOriginalFocusedContent(aOriginalFocusedContent),
+ mRelatedTarget(aRelatedTarget) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ nsCOMPtr<nsIContent> originalWindowFocus =
+ mOriginalFocusedWindow ? mOriginalFocusedWindow->GetFocusedElement()
+ : nullptr;
+ // Blink does not check that focus is the same after blur, but WebKit does.
+ // Opt to follow Blink's behavior (see bug 687787).
+ if (mEventMessage == eFocusOut ||
+ originalWindowFocus == mOriginalFocusedContent) {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = true;
+ event.mFlags.mCancelable = false;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+ return NS_OK;
+ }
+
+ const nsCOMPtr<nsISupports> mTarget;
+ const RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
+ nsCOMPtr<nsIContent> mOriginalFocusedContent;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+static Document* GetDocumentHelper(EventTarget* aTarget) {
+ if (!aTarget) {
+ return nullptr;
+ }
+ if (const nsINode* node = nsINode::FromEventTarget(aTarget)) {
+ return node->OwnerDoc();
+ }
+ nsPIDOMWindowInner* win = nsPIDOMWindowInner::FromEventTarget(aTarget);
+ return win ? win->GetExtantDoc() : nullptr;
+}
+
+void nsFocusManager::FireFocusInOrOutEvent(
+ EventMessage aEventMessage, PresShell* aPresShell, nsISupports* aTarget,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent, EventTarget* aRelatedTarget) {
+ NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+ "Wrong event type for FireFocusInOrOutEvent");
+
+ nsContentUtils::AddScriptRunner(new FocusInOutEvent(
+ aTarget, aEventMessage, aPresShell->GetPresContext(),
+ aCurrentFocusedWindow, aCurrentFocusedContent, aRelatedTarget));
+}
+
+void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
+ PresShell* aPresShell,
+ Document* aDocument,
+ nsISupports* aTarget,
+ bool aWindowRaised, bool aIsRefocus,
+ EventTarget* aRelatedTarget) {
+ NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
+ "Wrong event type for SendFocusOrBlurEvent");
+
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
+ nsCOMPtr<Document> eventTargetDoc = GetDocumentHelper(eventTarget);
+ nsCOMPtr<Document> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
+
+ // set aRelatedTarget to null if it's not in the same document as eventTarget
+ if (eventTargetDoc != relatedTargetDoc) {
+ aRelatedTarget = nullptr;
+ }
+
+ if (aDocument && aDocument->EventHandlingSuppressed()) {
+ // if this event was already queued, remove it and append it to the end
+ mDelayedBlurFocusEvents.RemoveElementsBy([&](const auto& event) {
+ return event.mEventMessage == aEventMessage &&
+ event.mPresShell == aPresShell && event.mDocument == aDocument &&
+ event.mTarget == eventTarget &&
+ event.mRelatedTarget == aRelatedTarget;
+ });
+
+ mDelayedBlurFocusEvents.EmplaceBack(aEventMessage, aPresShell, aDocument,
+ eventTarget, aRelatedTarget);
+ return;
+ }
+
+ // If mDelayedBlurFocusEvents queue is not empty, check if there are events
+ // that belongs to this doc, if yes, fire them first.
+ if (aDocument && !aDocument->EventHandlingSuppressed() &&
+ mDelayedBlurFocusEvents.Length()) {
+ FireDelayedEvents(aDocument);
+ }
+
+ FireFocusOrBlurEvent(aEventMessage, aPresShell, aTarget, aWindowRaised,
+ aIsRefocus, aRelatedTarget);
+}
+
+void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage,
+ PresShell* aPresShell,
+ nsISupports* aTarget,
+ bool aWindowRaised, bool aIsRefocus,
+ EventTarget* aRelatedTarget) {
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
+ nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
+ nsCOMPtr<Document> targetDocument = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIContent> currentFocusedContent =
+ currentWindow ? currentWindow->GetFocusedElement() : nullptr;
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ if (aEventMessage == eFocus) {
+ accService->NotifyOfDOMFocus(aTarget);
+ } else {
+ accService->NotifyOfDOMBlur(aTarget);
+ }
+ }
+#endif
+
+ aPresShell->ScheduleContentRelevancyUpdate(
+ ContentRelevancyReason::FocusInSubtree);
+
+ nsContentUtils::AddScriptRunner(
+ new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
+ aWindowRaised, aIsRefocus, aRelatedTarget));
+
+ // Check that the target is not a window or document before firing
+ // focusin/focusout. Other browsers do not fire focusin/focusout on window,
+ // despite being required in the spec, so follow their behavior.
+ //
+ // As for document, we should not even fire focus/blur, but until then, we
+ // need this check. targetDocument should be removed once bug 1228802 is
+ // resolved.
+ if (!targetWindow && !targetDocument) {
+ EventMessage focusInOrOutMessage =
+ aEventMessage == eFocus ? eFocusIn : eFocusOut;
+ FireFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+ currentWindow, currentFocusedContent, aRelatedTarget);
+ }
+}
+
+void nsFocusManager::ScrollIntoView(PresShell* aPresShell, nsIContent* aContent,
+ uint32_t aFlags) {
+ if (aFlags & FLAG_NOSCROLL) {
+ return;
+ }
+ // If the noscroll flag isn't set, scroll the newly focused element into view.
+ aPresShell->ScrollContentIntoView(
+ aContent, ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollFlags::ScrollOverflowHidden);
+ // Scroll the input / textarea selection into view, unless focused with the
+ // mouse, see bug 572649.
+ if (aFlags & FLAG_BYMOUSE) {
+ return;
+ }
+ // ScrollContentIntoView flushes layout, so no need to flush again here.
+ if (nsTextControlFrame* tf = do_QueryFrame(aContent->GetPrimaryFrame())) {
+ tf->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes);
+ }
+}
+
+void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
+ CallerType aCallerType, uint64_t aActionId) {
+ // don't raise windows that are already raised or are in the process of
+ // being lowered
+
+ if (!aWindow || aWindow == mWindowBeingLowered) {
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (aWindow == mActiveWindow) {
+ return;
+ }
+ } else {
+ BrowsingContext* bc = aWindow->GetBrowsingContext();
+ // TODO: Deeper OOP frame hierarchies are
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
+ if (bc == GetActiveBrowsingContext()) {
+ return;
+ }
+ if (bc == GetFocusedBrowsingContext()) {
+ return;
+ }
+ }
+
+ if (sTestMode) {
+ // In test mode, emulate raising the window. WindowRaised takes
+ // care of lowering the present active window. This happens in
+ // a separate runnable to avoid touching multiple windows in
+ // the current runnable.
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
+ RefPtr<nsFocusManager> self(this);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsFocusManager::RaiseWindow",
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093)
+ [self, window]() MOZ_CAN_RUN_SCRIPT_BOUNDARY -> void {
+ self->WindowRaised(window, GenerateFocusActionId());
+ }));
+ return;
+ }
+
+ if (XRE_IsContentProcess()) {
+ BrowsingContext* bc = aWindow->GetBrowsingContext();
+ if (!bc->IsTop()) {
+ // Assume the raise below will succeed and run the raising synchronously
+ // in this process to make the focus event that is observable in this
+ // process fire in the right order relative to mouseup when we are here
+ // thanks to a mousedown.
+ WindowRaised(aWindow, aActionId);
+ }
+ }
+
+#if defined(XP_WIN)
+ // Windows would rather we focus the child widget, otherwise, the toplevel
+ // widget will always end up being focused. Fortunately, focusing the child
+ // widget will also have the effect of raising the window this widget is in.
+ // But on other platforms, we can just focus the toplevel widget to raise
+ // the window.
+ nsCOMPtr<nsPIDOMWindowOuter> childWindow;
+ GetFocusedDescendant(aWindow, eIncludeAllDescendants,
+ getter_AddRefs(childWindow));
+ if (!childWindow) {
+ childWindow = aWindow;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ PresShell* presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ if (nsViewManager* vm = presShell->GetViewManager()) {
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ if (widget) {
+ widget->SetFocus(nsIWidget::Raise::Yes, aCallerType);
+ }
+ }
+#else
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
+ do_QueryInterface(aWindow->GetDocShell());
+ if (treeOwnerAsWin) {
+ nsCOMPtr<nsIWidget> widget;
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ if (widget) {
+ widget->SetFocus(nsIWidget::Raise::Yes, aCallerType);
+ }
+ }
+#endif
+}
+
+void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
+ RefPtr<Element> focusedElement = mFocusedElement;
+ UpdateCaret(false, true, focusedElement);
+}
+
+void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus, bool aUpdateVisibility,
+ nsIContent* aContent) {
+ LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
+
+ if (!mFocusedWindow) {
+ return;
+ }
+
+ // this is called when a document is focused or when the caretbrowsing
+ // preference is changed
+ nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
+ if (!focusedDocShell) {
+ return;
+ }
+
+ if (focusedDocShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return; // Never browse with caret in chrome
+ }
+
+ bool browseWithCaret = Preferences::GetBool("accessibility.browsewithcaret");
+
+ const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ // If this is an editable document which isn't contentEditable, or a
+ // contentEditable document and the node to focus is contentEditable,
+ // return, so that we don't mess with caret visibility.
+ bool isEditable = false;
+ focusedDocShell->GetEditable(&isEditable);
+
+ if (isEditable) {
+ Document* doc = presShell->GetDocument();
+
+ bool isContentEditableDoc =
+ doc &&
+ doc->GetEditingState() == Document::EditingState::eContentEditable;
+
+ bool isFocusEditable = aContent && aContent->HasFlag(NODE_IS_EDITABLE);
+ if (!isContentEditableDoc || isFocusEditable) {
+ return;
+ }
+ }
+
+ if (!isEditable && aMoveCaretToFocus) {
+ MoveCaretToFocus(presShell, aContent);
+ }
+
+ // The above MoveCaretToFocus call may run scripts which
+ // may clear mFocusWindow
+ if (!mFocusedWindow) {
+ return;
+ }
+
+ if (!aUpdateVisibility) {
+ return;
+ }
+
+ // XXXndeakin this doesn't seem right. It should be checking for this only
+ // on the nearest ancestor frame which is a chrome frame. But this is
+ // what the existing code does, so just leave it for now.
+ if (!browseWithCaret) {
+ nsCOMPtr<Element> docElement = mFocusedWindow->GetFrameElementInternal();
+ if (docElement)
+ browseWithCaret = docElement->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::showcaret, u"true"_ns, eCaseMatters);
+ }
+
+ SetCaretVisible(presShell, browseWithCaret, aContent);
+}
+
+void nsFocusManager::MoveCaretToFocus(PresShell* aPresShell,
+ nsIContent* aContent) {
+ nsCOMPtr<Document> doc = aPresShell->GetDocument();
+ if (doc) {
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+ RefPtr<Selection> domSelection =
+ frameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ // First clear the selection. This way, if there is no currently focused
+ // content, the selection will just be cleared.
+ domSelection->RemoveAllRanges(IgnoreErrors());
+ if (aContent) {
+ ErrorResult rv;
+ RefPtr<nsRange> newRange = doc->CreateRange(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ // Set the range to the start of the currently focused node
+ // Make sure it's collapsed
+ newRange->SelectNodeContents(*aContent, IgnoreErrors());
+
+ if (!aContent->GetFirstChild() ||
+ aContent->IsHTMLFormControlElement()) {
+ // If current focus node is a leaf, set range to before the
+ // node by using the parent as a container.
+ // This prevents it from appearing as selected.
+ newRange->SetStartBefore(*aContent, IgnoreErrors());
+ newRange->SetEndBefore(*aContent, IgnoreErrors());
+ }
+ domSelection->AddRangeAndSelectFramesAndNotifyListeners(*newRange,
+ IgnoreErrors());
+ domSelection->CollapseToStart(IgnoreErrors());
+ }
+ }
+ }
+}
+
+nsresult nsFocusManager::SetCaretVisible(PresShell* aPresShell, bool aVisible,
+ nsIContent* aContent) {
+ // When browsing with caret, make sure caret is visible after new focus
+ // Return early if there is no caret. This can happen for the testcase
+ // for bug 308025 where a window is closed in a blur handler.
+ RefPtr<nsCaret> caret = aPresShell->GetCaret();
+ if (!caret) {
+ return NS_OK;
+ }
+
+ bool caretVisible = caret->IsVisible();
+ if (!aVisible && !caretVisible) {
+ return NS_OK;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection;
+ if (aContent) {
+ NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
+ "Wrong document?");
+ nsIFrame* focusFrame = aContent->GetPrimaryFrame();
+ if (focusFrame) {
+ frameSelection = focusFrame->GetFrameSelection();
+ }
+ }
+
+ RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
+
+ if (docFrameSelection && caret &&
+ (frameSelection == docFrameSelection || !aContent)) {
+ Selection* domSelection =
+ docFrameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ // First, hide the caret to prevent attempting to show it in
+ // SetCaretDOMSelection
+ aPresShell->SetCaretEnabled(false);
+
+ // Caret must blink on non-editable elements
+ caret->SetIgnoreUserModify(true);
+ // Tell the caret which selection to use
+ caret->SetSelection(domSelection);
+
+ // In content, we need to set the caret. The only special case is edit
+ // fields, which have a different frame selection from the document.
+ // They will take care of making the caret visible themselves.
+
+ aPresShell->SetCaretReadOnly(false);
+ aPresShell->SetCaretEnabled(aVisible);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFocusManager::GetSelectionLocation(Document* aDocument,
+ PresShell* aPresShell,
+ nsIContent** aStartContent,
+ nsIContent** aEndContent) {
+ *aStartContent = *aEndContent = nullptr;
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ NS_ASSERTION(presContext, "mPresContent is null!!");
+
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+
+ RefPtr<Selection> domSelection;
+ if (frameSelection) {
+ domSelection = frameSelection->GetSelection(SelectionType::eNormal);
+ }
+
+ bool isCollapsed = false;
+ nsCOMPtr<nsIContent> startContent, endContent;
+ uint32_t startOffset = 0;
+ if (domSelection) {
+ isCollapsed = domSelection->IsCollapsed();
+ RefPtr<const nsRange> domRange = domSelection->GetRangeAt(0);
+ if (domRange) {
+ nsCOMPtr<nsINode> startNode = domRange->GetStartContainer();
+ nsCOMPtr<nsINode> endNode = domRange->GetEndContainer();
+ startOffset = domRange->StartOffset();
+
+ nsIContent* childContent = nullptr;
+
+ startContent = do_QueryInterface(startNode);
+ if (startContent && startContent->IsElement()) {
+ childContent = startContent->GetChildAt_Deprecated(startOffset);
+ if (childContent) {
+ startContent = childContent;
+ }
+ }
+
+ endContent = do_QueryInterface(endNode);
+ if (endContent && endContent->IsElement()) {
+ uint32_t endOffset = domRange->EndOffset();
+ childContent = endContent->GetChildAt_Deprecated(endOffset);
+ if (childContent) {
+ endContent = childContent;
+ }
+ }
+ }
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIFrame* startFrame = nullptr;
+ if (startContent) {
+ startFrame = startContent->GetPrimaryFrame();
+ if (isCollapsed) {
+ // Next check to see if our caret is at the very end of a node
+ // If so, the caret is actually sitting in front of the next
+ // logical frame's primary node - so for this case we need to
+ // change caretContent to that node.
+
+ if (startContent->NodeType() == nsINode::TEXT_NODE) {
+ nsAutoString nodeValue;
+ startContent->GetAsText()->AppendTextTo(nodeValue);
+
+ if (nodeValue.Length() == startOffset &&
+ !startContent->IsHTMLFormControlElement() &&
+ startContent != aDocument->GetRootElement()) {
+ // Yes, indeed we were at the end of the last node
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
+ presContext, startFrame, eLeaf,
+ false, // aVisual
+ false, // aLockInScrollView
+ true, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFrame* newCaretFrame = nullptr;
+ nsCOMPtr<nsIContent> newCaretContent = startContent;
+ bool endOfSelectionInStartNode(startContent == endContent);
+ do {
+ // Continue getting the next frame until the primary content for the
+ // frame we are on changes - we don't want to be stuck in the same
+ // place
+ frameTraversal->Next();
+ newCaretFrame =
+ static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ if (nullptr == newCaretFrame) break;
+ newCaretContent = newCaretFrame->GetContent();
+ } while (!newCaretContent || newCaretContent == startContent);
+
+ if (newCaretFrame && newCaretContent) {
+ // If the caret is exactly at the same position of the new frame,
+ // then we can use the newCaretFrame and newCaretContent for our
+ // position
+ nsRect caretRect;
+ nsIFrame* frame = nsCaret::GetGeometry(domSelection, &caretRect);
+ if (frame) {
+ nsPoint caretWidgetOffset;
+ nsIWidget* widget = frame->GetNearestWidget(caretWidgetOffset);
+ caretRect.MoveBy(caretWidgetOffset);
+ nsPoint newCaretOffset;
+ nsIWidget* newCaretWidget =
+ newCaretFrame->GetNearestWidget(newCaretOffset);
+ if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
+ caretRect.x == newCaretOffset.x) {
+ // The caret is at the start of the new element.
+ startFrame = newCaretFrame;
+ startContent = newCaretContent;
+ if (endOfSelectionInStartNode) {
+ endContent = newCaretContent; // Ensure end of selection is
+ // not before start
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ *aStartContent = startContent;
+ *aEndContent = endContent;
+ NS_IF_ADDREF(*aStartContent);
+ NS_IF_ADDREF(*aEndContent);
+
+ return NS_OK;
+}
+
+nsresult nsFocusManager::DetermineElementToMoveFocus(
+ nsPIDOMWindowOuter* aWindow, nsIContent* aStartContent, int32_t aType,
+ bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent) {
+ *aNextContent = nullptr;
+
+ // This is used for document navigation only. It will be set to true if we
+ // start navigating from a starting point. If this starting point is near the
+ // end of the document (for example, an element on a statusbar), and there
+ // are no child documents or panels before the end of the document, then we
+ // will need to ensure that we don't consider the root chrome window when we
+ // loop around and instead find the next child document/panel, as focus is
+ // already in that window. This flag will be cleared once we navigate into
+ // another document.
+ bool mayFocusRoot = (aStartContent != nullptr);
+
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ if (!startContent && aType != MOVEFOCUS_CARET) {
+ if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
+ // When moving between documents, make sure to get the right
+ // starting content in a descendant.
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ } else if (aType != MOVEFOCUS_LASTDOC) {
+ // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
+ // then we are document-navigating backwards from chrome to the content
+ // process, and we don't want to use this so that we start from the end
+ // of the document.
+ startContent = aWindow->GetFocusedElement();
+ }
+ }
+
+ nsCOMPtr<Document> doc;
+ if (startContent)
+ doc = startContent->GetComposedDoc();
+ else
+ doc = aWindow->GetExtantDoc();
+ if (!doc) return NS_OK;
+
+ LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel,
+ &nsIContent::sTabFocusModel);
+
+ // True if we are navigating by document (F6/Shift+F6) or false if we are
+ // navigating by element (Tab/Shift+Tab).
+ const bool forDocumentNavigation =
+ aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
+ aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC;
+
+ // If moving to the root or first document, find the root element and return.
+ if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
+ NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
+ if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
+ // When looking for the first document, if the root wasn't focusable,
+ // find the next focusable document.
+ aType = MOVEFOCUS_FORWARDDOC;
+ } else {
+ return NS_OK;
+ }
+ }
+
+ // rootElement and presShell may be set to sub-document's ones so that they
+ // cannot be `const`.
+ RefPtr<Element> rootElement = doc->GetRootElement();
+ NS_ENSURE_TRUE(rootElement, NS_OK);
+
+ RefPtr<PresShell> presShell = doc->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_OK);
+
+ if (aType == MOVEFOCUS_FIRST) {
+ if (!aStartContent) {
+ startContent = rootElement;
+ }
+ return GetNextTabbableContent(presShell, startContent, nullptr,
+ startContent, true, 1, false, false,
+ aNavigateByKey, aNextContent);
+ }
+ if (aType == MOVEFOCUS_LAST) {
+ if (!aStartContent) {
+ startContent = rootElement;
+ }
+ return GetNextTabbableContent(presShell, startContent, nullptr,
+ startContent, false, 0, false, false,
+ aNavigateByKey, aNextContent);
+ }
+
+ bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_FORWARDDOC ||
+ aType == MOVEFOCUS_CARET);
+ bool doNavigation = true;
+ bool ignoreTabIndex = false;
+ // when a popup is open, we want to ensure that tab navigation occurs only
+ // within the most recently opened panel. If a popup is open, its frame will
+ // be stored in popupFrame.
+ nsIFrame* popupFrame = nullptr;
+
+ int32_t tabIndex = forward ? 1 : 0;
+ if (startContent) {
+ nsIFrame* frame = startContent->GetPrimaryFrame();
+ if (startContent->IsHTMLElement(nsGkAtoms::area)) {
+ startContent->IsFocusable(&tabIndex);
+ } else if (frame) {
+ tabIndex = frame->IsFocusable().mTabIndex;
+ } else {
+ startContent->IsFocusable(&tabIndex);
+ }
+
+ // if the current element isn't tabbable, ignore the tabindex and just
+ // look for the next element. The root content won't have a tabindex
+ // so just treat this as the beginning of the tab order.
+ if (tabIndex < 0) {
+ tabIndex = 1;
+ if (startContent != rootElement) {
+ ignoreTabIndex = true;
+ }
+ }
+
+ // check if the focus is currently inside a popup. Elements such as the
+ // autocomplete widget use the noautofocus attribute to allow the focus to
+ // remain outside the popup when it is opened.
+ if (frame) {
+ popupFrame = nsLayoutUtils::GetClosestFrameOfType(
+ frame, LayoutFrameType::MenuPopup);
+ }
+
+ if (popupFrame && !forDocumentNavigation) {
+ // Don't navigate outside of a popup, so pretend that the
+ // root content is the popup itself
+ rootElement = popupFrame->GetContent()->AsElement();
+ NS_ASSERTION(rootElement, "Popup frame doesn't have a content node");
+ } else if (!forward) {
+ // If focus moves backward and when current focused node is root
+ // content or <body> element which is editable by contenteditable
+ // attribute, focus should move to its parent document.
+ if (startContent == rootElement) {
+ doNavigation = false;
+ } else {
+ Document* doc = startContent->GetComposedDoc();
+ if (startContent ==
+ nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
+ doNavigation = false;
+ }
+ }
+ }
+ } else {
+ if (aType != MOVEFOCUS_CARET) {
+ // if there is no focus, yet a panel is open, focus the first item in
+ // the panel
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ popupFrame = pm->GetTopPopup(PopupType::Panel);
+ }
+ }
+ if (popupFrame) {
+ // When there is a popup open, and no starting content, start the search
+ // at the topmost popup.
+ startContent = popupFrame->GetContent();
+ NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
+ // Unless we are searching for documents, set the root content to the
+ // popup as well, so that we don't tab-navigate outside the popup.
+ // When navigating by documents, we start at the popup but can navigate
+ // outside of it to look for other panels and documents.
+ if (!forDocumentNavigation) {
+ rootElement = startContent->AsElement();
+ }
+
+ doc = startContent ? startContent->GetComposedDoc() : nullptr;
+ } else {
+ // Otherwise, for content shells, start from the location of the caret.
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ nsCOMPtr<nsIContent> endSelectionContent;
+ GetSelectionLocation(doc, presShell, getter_AddRefs(startContent),
+ getter_AddRefs(endSelectionContent));
+ // If the selection is on the rootElement, then there is no selection
+ if (startContent == rootElement) {
+ startContent = nullptr;
+ }
+
+ if (aType == MOVEFOCUS_CARET) {
+ // GetFocusInSelection finds a focusable link near the caret.
+ // If there is no start content though, don't do this to avoid
+ // focusing something unexpected.
+ if (startContent) {
+ GetFocusInSelection(aWindow, startContent, endSelectionContent,
+ aNextContent);
+ }
+ return NS_OK;
+ }
+
+ if (startContent) {
+ // when starting from a selection, we always want to find the next or
+ // previous element in the document. So the tabindex on elements
+ // should be ignored.
+ ignoreTabIndex = true;
+ }
+ }
+
+ if (!startContent) {
+ // otherwise, just use the root content as the starting point
+ startContent = rootElement;
+ NS_ENSURE_TRUE(startContent, NS_OK);
+ }
+ }
+ }
+
+ // Check if the starting content is the same as the content assigned to the
+ // retargetdocumentfocus attribute. Is so, we don't want to start searching
+ // from there but instead from the beginning of the document. Otherwise, the
+ // content that appears before the retargetdocumentfocus element will never
+ // get checked as it will be skipped when the focus is retargetted to it.
+ if (forDocumentNavigation && nsContentUtils::IsChromeDoc(doc)) {
+ nsAutoString retarget;
+
+ if (rootElement->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::retargetdocumentfocus, retarget)) {
+ nsIContent* retargetElement = doc->GetElementById(retarget);
+ // The common case here is the urlbar where focus is on the anonymous
+ // input inside the textbox, but the retargetdocumentfocus attribute
+ // refers to the textbox. The Contains check will return false and the
+ // IsInclusiveDescendantOf check will return true in this case.
+ if (retargetElement &&
+ (retargetElement == startContent ||
+ (!retargetElement->Contains(startContent) &&
+ startContent->IsInclusiveDescendantOf(retargetElement)))) {
+ startContent = rootElement;
+ }
+ }
+ }
+
+ NS_ASSERTION(startContent, "starting content not set");
+
+ // keep a reference to the starting content. If we find that again, it means
+ // we've iterated around completely and we don't want to adjust the focus.
+ // The skipOriginalContentCheck will be set to true only for the first time
+ // GetNextTabbableContent is called. This ensures that we don't break out
+ // when nothing is focused to start with. Specifically,
+ // GetNextTabbableContent first checks the root content -- which happens to
+ // be the same as the start content -- when nothing is focused and tabbing
+ // forward. Without skipOriginalContentCheck set to true, we'd end up
+ // returning right away and focusing nothing. Luckily, GetNextTabbableContent
+ // will never wrap around on its own, and can only return the original
+ // content when it is called a second time or later.
+ bool skipOriginalContentCheck = true;
+ const nsCOMPtr<nsIContent> originalStartContent = startContent;
+
+ LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
+ LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
+ forward, tabIndex, ignoreTabIndex,
+ forDocumentNavigation));
+
+ while (doc) {
+ if (doNavigation) {
+ nsCOMPtr<nsIContent> nextFocus;
+ // TODO: MOZ_KnownLive is reruired due to bug 1770680
+ nsresult rv = GetNextTabbableContent(
+ presShell, rootElement,
+ MOZ_KnownLive(skipOriginalContentCheck ? nullptr
+ : originalStartContent.get()),
+ startContent, forward, tabIndex, ignoreTabIndex,
+ forDocumentNavigation, aNavigateByKey, getter_AddRefs(nextFocus));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ // Navigation was redirected to a child process, so just return.
+ return NS_OK;
+ }
+
+ // found a content node to focus.
+ if (nextFocus) {
+ LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
+
+ // as long as the found node was not the same as the starting node,
+ // set it as the return value. For document navigation, we can return
+ // the same element in case there is only one content node that could
+ // be returned, for example, in a child process document.
+ if (nextFocus != originalStartContent || forDocumentNavigation) {
+ nextFocus.forget(aNextContent);
+ }
+ return NS_OK;
+ }
+
+ if (popupFrame && !forDocumentNavigation) {
+ // in a popup, so start again from the beginning of the popup. However,
+ // if we already started at the beginning, then there isn't anything to
+ // focus, so just return
+ if (startContent != rootElement) {
+ startContent = rootElement;
+ tabIndex = forward ? 1 : 0;
+ continue;
+ }
+ return NS_OK;
+ }
+ }
+
+ doNavigation = true;
+ skipOriginalContentCheck = forDocumentNavigation;
+ ignoreTabIndex = false;
+
+ if (aNoParentTraversal) {
+ if (startContent == rootElement) {
+ return NS_OK;
+ }
+
+ startContent = rootElement;
+ tabIndex = forward ? 1 : 0;
+ continue;
+ }
+
+ // Reached the beginning or end of the document. Next, navigate up to the
+ // parent document and try again.
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
+ NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // Get the frame element this window is inside and, from that, get the
+ // parent document and presshell. If there is no enclosing frame element,
+ // then this is a top-level, embedded or remote window.
+ startContent = piWindow->GetFrameElementInternal();
+ if (startContent) {
+ doc = startContent->GetComposedDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ rootElement = doc->GetRootElement();
+ presShell = doc->GetPresShell();
+
+ // We can focus the root element now that we have moved to another
+ // document.
+ mayFocusRoot = true;
+
+ nsIFrame* frame = startContent->GetPrimaryFrame();
+ if (!frame) {
+ return NS_OK;
+ }
+
+ tabIndex = frame->IsFocusable().mTabIndex;
+ if (tabIndex < 0) {
+ tabIndex = 1;
+ ignoreTabIndex = true;
+ }
+
+ // if the frame is inside a popup, make sure to scan only within the
+ // popup. This handles the situation of tabbing amongst elements
+ // inside an iframe which is itself inside a popup. Otherwise,
+ // navigation would move outside the popup when tabbing outside the
+ // iframe.
+ if (!forDocumentNavigation) {
+ popupFrame = nsLayoutUtils::GetClosestFrameOfType(
+ frame, LayoutFrameType::MenuPopup);
+ if (popupFrame) {
+ rootElement = popupFrame->GetContent()->AsElement();
+ NS_ASSERTION(rootElement, "Popup frame doesn't have a content node");
+ }
+ }
+ } else {
+ if (aNavigateByKey) {
+ // There is no parent, so call the tree owner. This will tell the
+ // embedder or parent process that it should take the focus.
+ bool tookFocus;
+ docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
+ // If the tree owner took the focus, blur the current element.
+ if (tookFocus) {
+ RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext();
+ if (focusedBC && focusedBC->IsInProcess()) {
+ Blur(focusedBC, nullptr, true, true, false,
+ GenerateFocusActionId());
+ } else {
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
+ window->SetFocusedElement(nullptr);
+ }
+ return NS_OK;
+ }
+ }
+
+ // If we have reached the end of the top-level document, focus the
+ // first element in the top-level document. This should always happen
+ // when navigating by document forwards but when navigating backwards,
+ // only do this if we started in another document or within a popup frame.
+ // If the focus started in this window outside a popup however, we should
+ // continue by looping around to the end again.
+ if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
+ // HTML content documents can have their root element focused (a focus
+ // ring appears around the entire content area frame). This root
+ // appears in the tab order before all of the elements in the document.
+ // Chrome documents however cannot be focused directly, so instead we
+ // focus the first focusable element within the window.
+ // For example, the urlbar.
+ RefPtr<Element> rootElementForFocus =
+ GetRootForFocus(piWindow, doc, true, true);
+ return FocusFirst(rootElementForFocus, aNextContent);
+ }
+
+ // Once we have hit the top-level and have iterated to the end again, we
+ // just want to break out next time we hit this spot to prevent infinite
+ // iteration.
+ mayFocusRoot = true;
+
+ // reset the tab index and start again from the beginning or end
+ startContent = rootElement;
+ tabIndex = forward ? 1 : 0;
+ }
+
+ // wrapped all the way around and didn't find anything to move the focus
+ // to, so just break out
+ if (startContent == originalStartContent) {
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions& aOptions) {
+ uint32_t flags = FLAG_BYJS;
+ if (aOptions.mPreventScroll) {
+ flags |= FLAG_NOSCROLL;
+ }
+ if (aOptions.mFocusVisible.WasPassed()) {
+ flags |= aOptions.mFocusVisible.Value() ? FLAG_SHOWRING : FLAG_NOSHOWRING;
+ }
+ if (UserActivation::IsHandlingKeyboardInput()) {
+ flags |= FLAG_BYKEY;
+ }
+ // TODO: We could do a similar thing if we're handling mouse input, but that
+ // changes focusability of some elements so may be more risky.
+ return flags;
+}
+
+static bool IsHostOrSlot(const nsIContent* aContent) {
+ return aContent && (aContent->GetShadowRoot() ||
+ aContent->IsHTMLElement(nsGkAtoms::slot));
+}
+
+// Helper class to iterate contents in scope by traversing flattened tree
+// in tree order
+class MOZ_STACK_CLASS ScopedContentTraversal {
+ public:
+ ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner)
+ : mCurrent(aStartContent), mOwner(aOwner) {
+ MOZ_ASSERT(aStartContent);
+ }
+
+ void Next();
+ void Prev();
+
+ void Reset() { SetCurrent(mOwner); }
+
+ nsIContent* GetCurrent() const { return mCurrent; }
+
+ private:
+ void SetCurrent(nsIContent* aContent) { mCurrent = aContent; }
+
+ nsIContent* mCurrent;
+ nsIContent* mOwner;
+};
+
+void ScopedContentTraversal::Next() {
+ MOZ_ASSERT(mCurrent);
+
+ // Get mCurrent's first child if it's in the same scope.
+ if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) {
+ StyleChildrenIterator iter(mCurrent);
+ nsIContent* child = iter.GetNextChild();
+ if (child) {
+ SetCurrent(child);
+ return;
+ }
+ }
+
+ // If mOwner has no children, END traversal
+ if (mCurrent == mOwner) {
+ SetCurrent(nullptr);
+ return;
+ }
+
+ nsIContent* current = mCurrent;
+ while (1) {
+ // Create parent's iterator and move to current
+ nsIContent* parent = current->GetFlattenedTreeParent();
+ StyleChildrenIterator parentIter(parent);
+ parentIter.Seek(current);
+
+ // Get next sibling of current
+ if (nsIContent* next = parentIter.GetNextChild()) {
+ SetCurrent(next);
+ return;
+ }
+
+ // If no next sibling and parent is mOwner, END traversal
+ if (parent == mOwner) {
+ SetCurrent(nullptr);
+ return;
+ }
+
+ current = parent;
+ }
+}
+
+void ScopedContentTraversal::Prev() {
+ MOZ_ASSERT(mCurrent);
+
+ nsIContent* parent;
+ nsIContent* last;
+ if (mCurrent == mOwner) {
+ // Get last child of mOwner
+ StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */);
+ last = ownerIter.GetPreviousChild();
+
+ parent = last;
+ } else {
+ // Create parent's iterator and move to mCurrent
+ parent = mCurrent->GetFlattenedTreeParent();
+ StyleChildrenIterator parentIter(parent);
+ parentIter.Seek(mCurrent);
+
+ // Get previous sibling
+ last = parentIter.GetPreviousChild();
+ }
+
+ while (last) {
+ parent = last;
+ if (IsHostOrSlot(parent)) {
+ // Skip contents in other scopes
+ break;
+ }
+
+ // Find last child
+ StyleChildrenIterator iter(parent, false /* aStartAtBeginning */);
+ last = iter.GetPreviousChild();
+ }
+
+ // If parent is mOwner and no previous sibling remains, END traversal
+ SetCurrent(parent == mOwner ? nullptr : parent);
+}
+
+/**
+ * Returns scope owner of aContent.
+ * A scope owner is either a shadow host, or slot.
+ */
+static nsIContent* FindScopeOwner(nsIContent* aContent) {
+ nsIContent* currentContent = aContent;
+ while (currentContent) {
+ nsIContent* parent = currentContent->GetFlattenedTreeParent();
+
+ // Shadow host / Slot
+ if (IsHostOrSlot(parent)) {
+ return parent;
+ }
+
+ currentContent = parent;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Host and Slot elements need to be handled as if they had tabindex 0 even
+ * when they don't have the attribute. This is a helper method to get the
+ * right value for focus navigation. If aIsFocusable is passed, it is set to
+ * true if the element itself is focusable.
+ */
+static int32_t HostOrSlotTabIndexValue(const nsIContent* aContent,
+ bool* aIsFocusable = nullptr) {
+ MOZ_ASSERT(IsHostOrSlot(aContent));
+
+ if (aIsFocusable) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ *aIsFocusable = frame && frame->IsFocusable().mTabIndex >= 0;
+ }
+
+ const nsAttrValue* attrVal =
+ aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
+ if (!attrVal) {
+ return 0;
+ }
+
+ if (attrVal->Type() == nsAttrValue::eInteger) {
+ return attrVal->GetIntegerValue();
+ }
+
+ return -1;
+}
+
+nsIContent* nsFocusManager::GetNextTabbableContentInScope(
+ nsIContent* aOwner, nsIContent* aStartContent,
+ nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex,
+ bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey,
+ bool aSkipOwner) {
+ MOZ_ASSERT(IsHostOrSlot(aOwner), "Scope owner should be host or slot");
+
+ // XXX: Why don't we ignore tabindex when the current tabindex < 0?
+ MOZ_ASSERT_IF(aCurrentTabIndex < 0, aIgnoreTabIndex);
+
+ if (!aSkipOwner && (aForward && aOwner == aStartContent)) {
+ if (nsIFrame* frame = aOwner->GetPrimaryFrame()) {
+ auto focusable = frame->IsFocusable();
+ if (focusable && focusable.mTabIndex >= 0) {
+ return aOwner;
+ }
+ }
+ }
+
+ //
+ // Iterate contents in scope
+ //
+ ScopedContentTraversal contentTraversal(aStartContent, aOwner);
+ nsCOMPtr<nsIContent> iterContent;
+ nsIContent* firstNonChromeOnly =
+ aStartContent->IsInNativeAnonymousSubtree()
+ ? aStartContent->FindFirstNonChromeOnlyAccessContent()
+ : nullptr;
+ while (1) {
+ // Iterate tab index to find corresponding contents in scope
+
+ while (1) {
+ // Iterate remaining contents in scope to find next content to focus
+
+ // Get next content
+ aForward ? contentTraversal.Next() : contentTraversal.Prev();
+ iterContent = contentTraversal.GetCurrent();
+
+ if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
+ // We just broke out from the native anonymous content, so move
+ // to the previous/next node of the native anonymous owner.
+ if (aForward) {
+ contentTraversal.Next();
+ } else {
+ contentTraversal.Prev();
+ }
+ iterContent = contentTraversal.GetCurrent();
+ }
+ if (!iterContent) {
+ // Reach the end
+ break;
+ }
+
+ int32_t tabIndex = 0;
+ if (iterContent->IsInNativeAnonymousSubtree() &&
+ iterContent->GetPrimaryFrame()) {
+ tabIndex = iterContent->GetPrimaryFrame()->IsFocusable().mTabIndex;
+ } else if (IsHostOrSlot(iterContent)) {
+ tabIndex = HostOrSlotTabIndexValue(iterContent);
+ } else {
+ nsIFrame* frame = iterContent->GetPrimaryFrame();
+ if (!frame) {
+ continue;
+ }
+ tabIndex = frame->IsFocusable().mTabIndex;
+ }
+ if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
+ continue;
+ }
+
+ if (!IsHostOrSlot(iterContent)) {
+ nsCOMPtr<nsIContent> elementInFrame;
+ bool checkSubDocument = true;
+ if (aForDocumentNavigation &&
+ TryDocumentNavigation(iterContent, &checkSubDocument,
+ getter_AddRefs(elementInFrame))) {
+ return elementInFrame;
+ }
+ if (!checkSubDocument) {
+ continue;
+ }
+
+ if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent,
+ aForward, aForDocumentNavigation,
+ aNavigateByKey,
+ getter_AddRefs(elementInFrame))) {
+ return elementInFrame;
+ }
+
+ // Found content to focus
+ return iterContent;
+ }
+
+ // Search in scope owned by iterContent
+ nsIContent* contentToFocus = GetNextTabbableContentInScope(
+ iterContent, iterContent, aOriginalStartContent, aForward,
+ aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
+ aNavigateByKey, false /* aSkipOwner */);
+ if (contentToFocus) {
+ return contentToFocus;
+ }
+ };
+
+ // If already at lowest priority tab (0), end search completely.
+ // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
+ if (aCurrentTabIndex == (aForward ? 0 : 1)) {
+ break;
+ }
+
+ // We've been just trying to find some focusable element, and haven't, so
+ // bail out.
+ if (aIgnoreTabIndex) {
+ break;
+ }
+
+ // Continue looking for next highest priority tabindex
+ aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
+ contentTraversal.Reset();
+ }
+
+ // Return scope owner at last for backward navigation if its tabindex
+ // is non-negative
+ if (!aSkipOwner && !aForward) {
+ if (nsIFrame* frame = aOwner->GetPrimaryFrame()) {
+ auto focusable = frame->IsFocusable();
+ if (focusable && focusable.mTabIndex >= 0) {
+ return aOwner;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes(
+ nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */,
+ nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex,
+ bool* aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey) {
+ MOZ_ASSERT(aStartOwner == FindScopeOwner(aStartContent),
+ "aStartOWner should be the scope owner of aStartContent");
+ MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot");
+
+ nsCOMPtr<nsIContent> owner = aStartOwner;
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ while (IsHostOrSlot(owner)) {
+ int32_t tabIndex = 0;
+ if (IsHostOrSlot(startContent)) {
+ tabIndex = HostOrSlotTabIndexValue(startContent);
+ } else if (nsIFrame* frame = startContent->GetPrimaryFrame()) {
+ tabIndex = frame->IsFocusable().mTabIndex;
+ } else {
+ startContent->IsFocusable(&tabIndex);
+ }
+ nsIContent* contentToFocus = GetNextTabbableContentInScope(
+ owner, startContent, aOriginalStartContent, aForward, tabIndex,
+ tabIndex < 0, aForDocumentNavigation, aNavigateByKey,
+ false /* aSkipOwner */);
+ if (contentToFocus) {
+ return contentToFocus;
+ }
+
+ startContent = owner;
+ owner = FindScopeOwner(startContent);
+ }
+
+ // If not found in shadow DOM, search from the top level shadow host in light
+ // DOM
+ aStartContent = startContent;
+ *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent);
+
+ if (*aCurrentTabIndex < 0) {
+ *aIgnoreTabIndex = true;
+ }
+
+ return nullptr;
+}
+
+static nsIContent* GetTopLevelScopeOwner(nsIContent* aContent) {
+ nsIContent* topLevelScopeOwner = nullptr;
+ while (aContent) {
+ if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
+ aContent = slot;
+ topLevelScopeOwner = aContent;
+ } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) {
+ aContent = shadowRoot->Host();
+ topLevelScopeOwner = aContent;
+ } else {
+ aContent = aContent->GetParent();
+ if (aContent && HTMLSlotElement::FromNode(aContent)) {
+ topLevelScopeOwner = aContent;
+ }
+ }
+ }
+
+ return topLevelScopeOwner;
+}
+
+nsresult nsFocusManager::GetNextTabbableContent(
+ PresShell* aPresShell, nsIContent* aRootContent,
+ nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward,
+ int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation,
+ bool aNavigateByKey, nsIContent** aResultContent) {
+ *aResultContent = nullptr;
+
+ if (!aStartContent) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ nsCOMPtr<nsIContent> currentTopLevelScopeOwner =
+ GetTopLevelScopeOwner(startContent);
+
+ LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent);
+ LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
+
+ // If startContent is a shadow host or slot in forward navigation,
+ // search in scope owned by startContent
+ if (aForward && IsHostOrSlot(startContent)) {
+ nsIContent* contentToFocus = GetNextTabbableContentInScope(
+ startContent, startContent, aOriginalStartContent, aForward,
+ aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
+ aNavigateByKey, true /* aSkipOwner */);
+ if (contentToFocus) {
+ NS_ADDREF(*aResultContent = contentToFocus);
+ return NS_OK;
+ }
+ }
+
+ // If startContent is in a scope owned by Shadow DOM search from scope
+ // including startContent
+ if (nsCOMPtr<nsIContent> owner = FindScopeOwner(startContent)) {
+ nsIContent* contentToFocus = GetNextTabbableContentInAncestorScopes(
+ owner, startContent /* inout */, aOriginalStartContent, aForward,
+ &aCurrentTabIndex, &aIgnoreTabIndex, aForDocumentNavigation,
+ aNavigateByKey);
+ if (contentToFocus) {
+ NS_ADDREF(*aResultContent = contentToFocus);
+ return NS_OK;
+ }
+ }
+
+ // If we reach here, it means no next tabbable content in shadow DOM.
+ // We need to continue searching in light DOM, starting at the top level
+ // shadow host in light DOM (updated startContent) and its tabindex
+ // (updated aCurrentTabIndex).
+ MOZ_ASSERT(!FindScopeOwner(startContent),
+ "startContent should not be owned by Shadow DOM at this point");
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+
+ bool getNextFrame = true;
+ nsCOMPtr<nsIContent> iterStartContent = startContent;
+ nsIContent* topLevelScopeStartContent = startContent;
+ // Iterate tab index to find corresponding contents
+ while (1) {
+ nsIFrame* frame = iterStartContent->GetPrimaryFrame();
+ // if there is no frame, look for another content node that has a frame
+ while (!frame) {
+ // if the root content doesn't have a frame, just return
+ if (iterStartContent == aRootContent) {
+ return NS_OK;
+ }
+
+ // look for the next or previous content node in tree order
+ iterStartContent = aForward ? iterStartContent->GetNextNode()
+ : iterStartContent->GetPreviousContent();
+ if (!iterStartContent) {
+ break;
+ }
+
+ frame = iterStartContent->GetPrimaryFrame();
+ // Host without frame, enter its scope.
+ if (!frame && iterStartContent->GetShadowRoot()) {
+ int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent);
+ if (tabIndex >= 0 &&
+ (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
+ nsIContent* contentToFocus = GetNextTabbableContentInScope(
+ iterStartContent, iterStartContent, aOriginalStartContent,
+ aForward, aForward ? 1 : 0, aIgnoreTabIndex,
+ aForDocumentNavigation, aNavigateByKey, true /* aSkipOwner */);
+ if (contentToFocus) {
+ NS_ADDREF(*aResultContent = contentToFocus);
+ return NS_OK;
+ }
+ }
+ }
+ // we've already skipped over the initial focused content, so we
+ // don't want to traverse frames.
+ getNextFrame = false;
+ }
+
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ if (frame) {
+ // For tab navigation, pass false for aSkipPopupChecks so that we don't
+ // iterate into or out of a popup. For document naviation pass true to
+ // ignore these boundaries.
+ nsresult rv = NS_NewFrameTraversal(
+ getter_AddRefs(frameTraversal), presContext, frame, ePreOrder,
+ false, // aVisual
+ false, // aLockInScrollView
+ true, // aFollowOOFs
+ aForDocumentNavigation // aSkipPopupChecks
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iterStartContent == aRootContent) {
+ if (!aForward) {
+ frameTraversal->Last();
+ } else if (aRootContent->IsFocusable()) {
+ frameTraversal->Next();
+ }
+ frame = frameTraversal->CurrentItem();
+ } else if (getNextFrame &&
+ (!iterStartContent ||
+ !iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
+ // Need to do special check in case we're in an imagemap which has
+ // multiple content nodes per frame, so don't skip over the starting
+ // frame.
+ frame = frameTraversal->Traverse(aForward);
+ }
+ }
+
+ nsIContent* oldTopLevelScopeOwner = nullptr;
+ // Walk frames to find something tabbable matching aCurrentTabIndex
+ while (frame) {
+ // Try to find the topmost scope owner, since we want to skip the node
+ // that is not owned by document in frame traversal.
+ const nsCOMPtr<nsIContent> currentContent = frame->GetContent();
+ if (currentTopLevelScopeOwner) {
+ oldTopLevelScopeOwner = currentTopLevelScopeOwner;
+ }
+ currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent);
+ if (currentTopLevelScopeOwner &&
+ currentTopLevelScopeOwner == oldTopLevelScopeOwner) {
+ // We're within non-document scope, continue.
+ do {
+ if (aForward) {
+ frameTraversal->Next();
+ } else {
+ frameTraversal->Prev();
+ }
+ frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ // For the usage of GetPrevContinuation, see the comment
+ // at the end of while (frame) loop.
+ } while (frame && frame->GetPrevContinuation());
+ continue;
+ }
+
+ // For document navigation, check if this element is an open panel. Since
+ // panels aren't focusable (tabIndex would be -1), we'll just assume that
+ // for document navigation, the tabIndex is 0.
+ if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
+ currentContent->IsXULElement(nsGkAtoms::panel)) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+ // Check if the panel is open. Closed panels are ignored since you can't
+ // focus anything in them.
+ if (popupFrame && popupFrame->IsOpen()) {
+ // When moving backward, skip the popup we started in otherwise it
+ // will be selected again.
+ bool validPopup = true;
+ if (!aForward) {
+ nsIContent* content = topLevelScopeStartContent;
+ while (content) {
+ if (content == currentContent) {
+ validPopup = false;
+ break;
+ }
+
+ content = content->GetParent();
+ }
+ }
+
+ if (validPopup) {
+ // Since a panel isn't focusable itself, find the first focusable
+ // content within the popup. If there isn't any focusable content
+ // in the popup, skip this popup and continue iterating through the
+ // frames. We pass the panel itself (currentContent) as the starting
+ // and root content, so that we only find content within the panel.
+ // Note also that we pass false for aForDocumentNavigation since we
+ // want to locate the first content, not the first document.
+ nsresult rv = GetNextTabbableContent(
+ aPresShell, currentContent, nullptr, currentContent, true, 1,
+ false, false, aNavigateByKey, aResultContent);
+ if (NS_SUCCEEDED(rv) && *aResultContent) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // As of now, 2018/04/12, sequential focus navigation is still
+ // in the obsolete Shadow DOM specification.
+ // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
+ // "if ELEMENT is focusable, a shadow host, or a slot element,
+ // append ELEMENT to NAVIGATION-ORDER."
+ // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
+ // hosts and slots are handled before other elements.
+ if (currentTopLevelScopeOwner) {
+ bool focusableHostSlot;
+ int32_t tabIndex = HostOrSlotTabIndexValue(currentTopLevelScopeOwner,
+ &focusableHostSlot);
+ // Host or slot itself isn't focusable or going backwards, enter its
+ // scope.
+ if ((!aForward || !focusableHostSlot) && tabIndex >= 0 &&
+ (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
+ nsIContent* contentToFocus = GetNextTabbableContentInScope(
+ currentTopLevelScopeOwner, currentTopLevelScopeOwner,
+ aOriginalStartContent, aForward, aForward ? 1 : 0,
+ aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey,
+ true /* aSkipOwner */);
+ if (contentToFocus) {
+ NS_ADDREF(*aResultContent = contentToFocus);
+ return NS_OK;
+ }
+ // If we've wrapped around already, then carry on.
+ if (aOriginalStartContent &&
+ currentTopLevelScopeOwner ==
+ GetTopLevelScopeOwner(aOriginalStartContent)) {
+ // FIXME: Shouldn't this return null instead? aOriginalStartContent
+ // isn't focusable after all.
+ NS_ADDREF(*aResultContent = aOriginalStartContent);
+ return NS_OK;
+ }
+ }
+ // There is no next tabbable content in currentTopLevelScopeOwner's
+ // scope. We should continue the loop in order to skip all contents that
+ // is in currentTopLevelScopeOwner's scope.
+ continue;
+ }
+
+ MOZ_ASSERT(!GetTopLevelScopeOwner(currentContent),
+ "currentContent should be in top-level-scope at this point");
+
+ // TabIndex not set defaults to 0 for form elements, anchors and other
+ // elements that are normally focusable. Tabindex defaults to -1
+ // for elements that are not normally focusable.
+ // The returned computed tabindex from IsFocusable() is as follows:
+ // clang-format off
+ // < 0 not tabbable at all
+ // == 0 in normal tab order (last after positive tabindexed items)
+ // > 0 can be tabbed to in the order specified by this value
+ // clang-format on
+ int32_t tabIndex = frame->IsFocusable().mTabIndex;
+
+ LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
+ LOGFOCUSNAVIGATION(
+ (" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
+
+ if (tabIndex >= 0) {
+ NS_ASSERTION(currentContent,
+ "IsFocusable set a tabindex for a frame with no content");
+ if (!aForDocumentNavigation &&
+ currentContent->IsHTMLElement(nsGkAtoms::img) &&
+ currentContent->AsElement()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::usemap)) {
+ // This is an image with a map. Image map areas are not traversed by
+ // nsIFrameTraversal so look for the next or previous area element.
+ nsIContent* areaContent = GetNextTabbableMapArea(
+ aForward, aCurrentTabIndex, currentContent->AsElement(),
+ iterStartContent);
+ if (areaContent) {
+ NS_ADDREF(*aResultContent = areaContent);
+ return NS_OK;
+ }
+ } else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
+ // break out if we've wrapped around to the start again.
+ if (aOriginalStartContent &&
+ currentContent == aOriginalStartContent) {
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+
+ // If this is a remote child browser, call NavigateDocument to have
+ // the child process continue the navigation. Return a special error
+ // code to have the caller return early. If the child ends up not
+ // being focusable in some way, the child process will call back
+ // into document navigation again by calling MoveFocus.
+ if (BrowserParent* remote = BrowserParent::GetFrom(currentContent)) {
+ if (aNavigateByKey) {
+ remote->NavigateByKey(aForward, aForDocumentNavigation);
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ return NS_OK;
+ }
+
+ // Same as above but for out-of-process iframes
+ if (auto* bbc = BrowserBridgeChild::GetFrom(currentContent)) {
+ if (aNavigateByKey) {
+ bbc->NavigateByKey(aForward, aForDocumentNavigation);
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ return NS_OK;
+ }
+
+ // Next, for document navigation, check if this a non-remote child
+ // document.
+ bool checkSubDocument = true;
+ if (aForDocumentNavigation &&
+ TryDocumentNavigation(currentContent, &checkSubDocument,
+ aResultContent)) {
+ return NS_OK;
+ }
+
+ if (checkSubDocument) {
+ // found a node with a matching tab index. Check if it is a child
+ // frame. If so, navigate into the child frame instead.
+ if (TryToMoveFocusToSubDocument(
+ currentContent, aOriginalStartContent, aForward,
+ aForDocumentNavigation, aNavigateByKey, aResultContent)) {
+ MOZ_ASSERT(*aResultContent);
+ return NS_OK;
+ }
+ // otherwise, use this as the next content node to tab to, unless
+ // this was the element we started on. This would happen for
+ // instance on an element with child frames, where frame navigation
+ // could return the original element again. In that case, just skip
+ // it. Also, if the next content node is the root content, then
+ // return it. This latter case would happen only if someone made a
+ // popup focusable.
+ else if (currentContent == aRootContent ||
+ currentContent != startContent) {
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+ }
+ }
+ } else if (aOriginalStartContent &&
+ currentContent == aOriginalStartContent) {
+ // not focusable, so return if we have wrapped around to the original
+ // content. This is necessary in case the original starting content was
+ // not focusable.
+ //
+ // FIXME: Shouldn't this return null instead? currentContent isn't
+ // focusable after all.
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+
+ // Move to the next or previous frame, but ignore continuation frames
+ // since only the first frame should be involved in focusability.
+ // Otherwise, a loop will occur in the following example:
+ // <span tabindex="1">...<a/><a/>...</span>
+ // where the text wraps onto multiple lines. Tabbing from the second
+ // link can find one of the span's continuation frames between the link
+ // and the end of the span, and the span would end up getting focused
+ // again.
+ do {
+ if (aForward) {
+ frameTraversal->Next();
+ } else {
+ frameTraversal->Prev();
+ }
+ frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ } while (frame && frame->GetPrevContinuation());
+ }
+
+ // If already at lowest priority tab (0), end search completely.
+ // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
+ if (aCurrentTabIndex == (aForward ? 0 : 1)) {
+ // if going backwards, the canvas should be focused once the beginning
+ // has been reached, so get the root element.
+ if (!aForward) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ RefPtr<Element> docRoot = GetRootForFocus(
+ window, aRootContent->GetComposedDoc(), false, true);
+ FocusFirst(docRoot, aResultContent);
+ }
+ break;
+ }
+
+ // continue looking for next highest priority tabindex
+ aCurrentTabIndex =
+ GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
+ startContent = iterStartContent = aRootContent;
+ currentTopLevelScopeOwner = GetTopLevelScopeOwner(startContent);
+ }
+
+ return NS_OK;
+}
+
+bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent,
+ bool* aCheckSubDocument,
+ nsIContent** aResultContent) {
+ *aCheckSubDocument = true;
+ if (RefPtr<Element> rootElementForChildDocument =
+ GetRootForChildDocument(aCurrentContent)) {
+ // If GetRootForChildDocument returned something then call
+ // FocusFirst to find the root or first element to focus within
+ // the child document. If this is a frameset though, skip this and
+ // fall through to normal tab navigation to iterate into
+ // the frameset's frames and locate the first focusable frame.
+ if (!rootElementForChildDocument->IsHTMLElement(nsGkAtoms::frameset)) {
+ *aCheckSubDocument = false;
+ Unused << FocusFirst(rootElementForChildDocument, aResultContent);
+ return *aResultContent != nullptr;
+ }
+ } else {
+ // Set aCheckSubDocument to false, as this was neither a frame
+ // type element or a child document that was focusable.
+ *aCheckSubDocument = false;
+ }
+
+ return false;
+}
+
+bool nsFocusManager::TryToMoveFocusToSubDocument(
+ nsIContent* aCurrentContent, nsIContent* aOriginalStartContent,
+ bool aForward, bool aForDocumentNavigation, bool aNavigateByKey,
+ nsIContent** aResultContent) {
+ Document* doc = aCurrentContent->GetComposedDoc();
+ NS_ASSERTION(doc, "content not in document");
+ Document* subdoc = doc->GetSubDocumentFor(aCurrentContent);
+ if (subdoc && !subdoc->EventHandlingSuppressed()) {
+ if (aForward) {
+ // When tabbing forward into a frame, return the root
+ // frame so that the canvas becomes focused.
+ if (nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow()) {
+ *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
+ if (*aResultContent) {
+ NS_ADDREF(*aResultContent);
+ return true;
+ }
+ }
+ }
+ if (RefPtr<Element> rootElement = subdoc->GetRootElement()) {
+ if (RefPtr<PresShell> subPresShell = subdoc->GetPresShell()) {
+ nsresult rv = GetNextTabbableContent(
+ subPresShell, rootElement, aOriginalStartContent, rootElement,
+ aForward, (aForward ? 1 : 0), false, aForDocumentNavigation,
+ aNavigateByKey, aResultContent);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (*aResultContent) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+nsIContent* nsFocusManager::GetNextTabbableMapArea(bool aForward,
+ int32_t aCurrentTabIndex,
+ Element* aImageContent,
+ nsIContent* aStartContent) {
+ if (aImageContent->IsInComposedDoc()) {
+ HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent);
+ // The caller should check the element type, so we can assert here.
+ MOZ_ASSERT(imgElement);
+
+ nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap();
+ if (!mapContent) {
+ return nullptr;
+ }
+ // First see if the the start content is in this map
+ Maybe<uint32_t> indexOfStartContent =
+ mapContent->ComputeIndexOf(aStartContent);
+ int32_t tabIndex;
+ nsIContent* scanStartContent;
+ if (indexOfStartContent.isNothing() ||
+ (aStartContent->IsFocusable(&tabIndex) &&
+ tabIndex != aCurrentTabIndex)) {
+ // If aStartContent is in this map we must start iterating past it.
+ // We skip the case where aStartContent has tabindex == aStartContent
+ // since the next tab ordered element might be before it
+ // (or after for backwards) in the child list.
+ scanStartContent =
+ aForward ? mapContent->GetFirstChild() : mapContent->GetLastChild();
+ } else {
+ scanStartContent = aForward ? aStartContent->GetNextSibling()
+ : aStartContent->GetPreviousSibling();
+ }
+
+ for (nsCOMPtr<nsIContent> areaContent = scanStartContent; areaContent;
+ areaContent = aForward ? areaContent->GetNextSibling()
+ : areaContent->GetPreviousSibling()) {
+ if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
+ return areaContent;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+int32_t nsFocusManager::GetNextTabIndex(nsIContent* aParent,
+ int32_t aCurrentTabIndex,
+ bool aForward) {
+ int32_t tabIndex, childTabIndex;
+ StyleChildrenIterator iter(aParent);
+
+ if (aForward) {
+ tabIndex = 0;
+ for (nsIContent* child = iter.GetNextChild(); child;
+ child = iter.GetNextChild()) {
+ // Skip child's descendants if child is a shadow host or slot, as they are
+ // in the focus navigation scope owned by child's shadow root
+ if (!IsHostOrSlot(child)) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
+ tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex
+ : tabIndex;
+ }
+ }
+
+ nsAutoString tabIndexStr;
+ if (child->IsElement()) {
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+ tabIndexStr);
+ }
+ nsresult ec;
+ int32_t val = tabIndexStr.ToInteger(&ec);
+ if (NS_SUCCEEDED(ec) && val > aCurrentTabIndex && val != tabIndex) {
+ tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
+ }
+ }
+ } else { /* !aForward */
+ tabIndex = 1;
+ for (nsIContent* child = iter.GetNextChild(); child;
+ child = iter.GetNextChild()) {
+ // Skip child's descendants if child is a shadow host or slot, as they are
+ // in the focus navigation scope owned by child's shadow root
+ if (!IsHostOrSlot(child)) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
+ (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
+ tabIndex = childTabIndex;
+ }
+ }
+
+ nsAutoString tabIndexStr;
+ if (child->IsElement()) {
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+ tabIndexStr);
+ }
+ nsresult ec;
+ int32_t val = tabIndexStr.ToInteger(&ec);
+ if (NS_SUCCEEDED(ec)) {
+ if ((aCurrentTabIndex == 0 && val > tabIndex) ||
+ (val < aCurrentTabIndex && val > tabIndex)) {
+ tabIndex = val;
+ }
+ }
+ }
+ }
+
+ return tabIndex;
+}
+
+nsresult nsFocusManager::FocusFirst(Element* aRootElement,
+ nsIContent** aNextContent) {
+ if (!aRootElement) {
+ return NS_OK;
+ }
+
+ Document* doc = aRootElement->GetComposedDoc();
+ if (doc) {
+ if (nsContentUtils::IsChromeDoc(doc)) {
+ // If the redirectdocumentfocus attribute is set, redirect the focus to a
+ // specific element. This is primarily used to retarget the focus to the
+ // urlbar during document navigation.
+ nsAutoString retarget;
+
+ if (aRootElement->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::retargetdocumentfocus, retarget)) {
+ RefPtr<Element> element = doc->GetElementById(retarget);
+ nsCOMPtr<nsIContent> retargetElement =
+ FlushAndCheckIfFocusable(element, 0);
+ if (retargetElement) {
+ retargetElement.forget(aNextContent);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ // If the found content is in a chrome shell, navigate forward one
+ // tabbable item so that the first item is focused. Note that we
+ // always go forward and not back here.
+ if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
+ return GetNextTabbableContent(presShell, aRootElement, nullptr,
+ aRootElement, true, 1, false, false, true,
+ aNextContent);
+ }
+ }
+ }
+
+ NS_ADDREF(*aNextContent = aRootElement);
+ return NS_OK;
+}
+
+Element* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
+ Document* aDocument,
+ bool aForDocumentNavigation,
+ bool aCheckVisibility) {
+ if (!aForDocumentNavigation) {
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return nullptr;
+ }
+ }
+
+ if (aCheckVisibility && !IsWindowVisible(aWindow)) return nullptr;
+
+ // If the body is contenteditable, use the editor's root element rather than
+ // the actual root element.
+ RefPtr<Element> rootElement =
+ nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
+ if (!rootElement || !rootElement->GetPrimaryFrame()) {
+ rootElement = aDocument->GetRootElement();
+ if (!rootElement) {
+ return nullptr;
+ }
+ }
+
+ if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
+ return nullptr;
+ }
+
+ // Finally, check if this is a frameset
+ if (aDocument && aDocument->IsHTMLOrXHTML()) {
+ Element* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
+ if (htmlChild) {
+ // In document navigation mode, return the frameset so that navigation
+ // descends into the child frames.
+ return aForDocumentNavigation ? htmlChild : nullptr;
+ }
+ }
+
+ return rootElement;
+}
+
+Element* nsFocusManager::GetRootForChildDocument(nsIContent* aContent) {
+ // Check for elements that represent child documents, that is, browsers,
+ // editors or frames from a frameset. We don't include iframes since we
+ // consider them to be an integral part of the same window or page.
+ if (!aContent || !(aContent->IsXULElement(nsGkAtoms::browser) ||
+ aContent->IsXULElement(nsGkAtoms::editor) ||
+ aContent->IsHTMLElement(nsGkAtoms::frame))) {
+ return nullptr;
+ }
+
+ Document* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ Document* subdoc = doc->GetSubDocumentFor(aContent);
+ if (!subdoc || subdoc->EventHandlingSuppressed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
+ return GetRootForFocus(window, subdoc, true, true);
+}
+
+static bool IsLink(nsIContent* aContent) {
+ return aContent->IsElement() && aContent->AsElement()->IsLink();
+}
+
+void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStartSelection,
+ nsIContent* aEndSelection,
+ nsIContent** aFocusedContent) {
+ *aFocusedContent = nullptr;
+
+ nsCOMPtr<nsIContent> testContent = aStartSelection;
+ nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
+
+ nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedElement();
+
+ // We now have the correct start node in selectionContent!
+ // Search for focusable elements, starting with selectionContent
+
+ // Method #1: Keep going up while we look - an ancestor might be focusable
+ // We could end the loop earlier, such as when we're no longer
+ // in the same frame, by comparing selectionContent->GetPrimaryFrame()
+ // with a variable holding the starting selectionContent
+ while (testContent) {
+ // Keep testing while selectionContent is equal to something,
+ // eventually we'll run out of ancestors
+
+ if (testContent == currentFocus || IsLink(testContent)) {
+ testContent.forget(aFocusedContent);
+ return;
+ }
+
+ // Get the parent
+ testContent = testContent->GetParent();
+
+ if (!testContent) {
+ // We run this loop again, checking the ancestor chain of the selection's
+ // end point
+ testContent = nextTestContent;
+ nextTestContent = nullptr;
+ }
+ }
+
+ // We couldn't find an anchor that was an ancestor of the selection start
+ // Method #2: look for anchor in selection's primary range (depth first
+ // search)
+
+ nsCOMPtr<nsIContent> selectionNode = aStartSelection;
+ nsCOMPtr<nsIContent> endSelectionNode = aEndSelection;
+ nsCOMPtr<nsIContent> testNode;
+
+ do {
+ testContent = selectionNode;
+
+ // We're looking for any focusable link that could be part of the
+ // main document's selection.
+ if (testContent == currentFocus || IsLink(testContent)) {
+ testContent.forget(aFocusedContent);
+ return;
+ }
+
+ nsIContent* testNode = selectionNode->GetFirstChild();
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ if (selectionNode == endSelectionNode) {
+ break;
+ }
+ testNode = selectionNode->GetNextSibling();
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ do {
+ // GetParent is OK here, instead of GetParentNode, because the only case
+ // where the latter returns something different from the former is when
+ // GetParentNode is the document. But in that case we would simply get
+ // null for selectionNode when setting it to testNode->GetNextSibling()
+ // (because a document has no next sibling). And then the next iteration
+ // of this loop would get null for GetParentNode anyway, and break out of
+ // all the loops.
+ testNode = selectionNode->GetParent();
+ if (!testNode || testNode == endSelectionNode) {
+ selectionNode = nullptr;
+ break;
+ }
+ selectionNode = testNode->GetNextSibling();
+ if (selectionNode) {
+ break;
+ }
+ selectionNode = testNode;
+ } while (true);
+ } while (selectionNode && selectionNode != endSelectionNode);
+}
+
+static void MaybeUnlockPointer(BrowsingContext* aCurrentFocusedContext) {
+ if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext)) {
+ PointerLockManager::Unlock();
+ }
+}
+
+class PointerUnlocker : public Runnable {
+ public:
+ PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
+ PointerUnlocker::sActiveUnlocker = this;
+ }
+
+ ~PointerUnlocker() {
+ if (PointerUnlocker::sActiveUnlocker == this) {
+ PointerUnlocker::sActiveUnlocker = nullptr;
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ if (PointerUnlocker::sActiveUnlocker == this) {
+ PointerUnlocker::sActiveUnlocker = nullptr;
+ }
+ NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
+ nsPIDOMWindowOuter* focused =
+ nsFocusManager::GetFocusManager()->GetFocusedWindow();
+ MaybeUnlockPointer(focused ? focused->GetBrowsingContext() : nullptr);
+ return NS_OK;
+ }
+
+ static PointerUnlocker* sActiveUnlocker;
+};
+
+PointerUnlocker* PointerUnlocker::sActiveUnlocker = nullptr;
+
+void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext* aContext,
+ uint64_t aActionId) {
+ if (XRE_IsParentProcess()) {
+ return;
+ }
+ MOZ_ASSERT(!ActionIdComparableAndLower(
+ aActionId, mActionIdForFocusedBrowsingContextInContent));
+ mFocusedBrowsingContextInContent = aContext;
+ mActionIdForFocusedBrowsingContextInContent = aActionId;
+ if (aContext) {
+ // We don't send the unset but instead expect the set from
+ // elsewhere to take care of it. XXX Is that bad?
+ MOZ_ASSERT(aContext->IsInProcess());
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendSetFocusedBrowsingContext(aContext, aActionId);
+ }
+}
+
+void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
+ BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(aContext);
+ if (ActionIdComparableAndLower(aActionId,
+ mActionIdForFocusedBrowsingContextInContent)) {
+ // Unclear if this ever happens.
+ LOGFOCUS(
+ ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
+ "focused from another process due to stale action id %" PRIu64 ".",
+ aContext, aActionId));
+ return;
+ }
+ if (aContext->IsInProcess()) {
+ // This message has been in transit for long enough that
+ // the process association of aContext has changed since
+ // the other content process sent the message, because
+ // an iframe in that process became an out-of-process
+ // iframe while the IPC broadcast that we're receiving
+ // was in-flight. Let's just ignore this.
+ LOGFOCUS(
+ ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
+ "focused from another process, actionid: %" PRIu64 ".",
+ aContext, aActionId));
+ return;
+ }
+ mFocusedBrowsingContextInContent = aContext;
+ mActionIdForFocusedBrowsingContextInContent = aActionId;
+ mFocusedElement = nullptr;
+ mFocusedWindow = nullptr;
+}
+
+bool nsFocusManager::SetFocusedBrowsingContextInChrome(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(aActionId);
+ if (ProcessPendingFocusedBrowsingContextActionId(aActionId)) {
+ MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
+ aActionId, mActionIdForFocusedBrowsingContextInChrome));
+ mFocusedBrowsingContextInChrome = aContext;
+ mActionIdForFocusedBrowsingContextInChrome = aActionId;
+ return true;
+ }
+ return false;
+}
+
+BrowsingContext* nsFocusManager::GetFocusedBrowsingContextInChrome() {
+ return mFocusedBrowsingContextInChrome;
+}
+
+void nsFocusManager::BrowsingContextDetached(BrowsingContext* aContext) {
+ if (mFocusedBrowsingContextInChrome == aContext) {
+ mFocusedBrowsingContextInChrome = nullptr;
+ // Deliberately not adjusting the corresponding action id, because
+ // we don't want changes from the past to take effect.
+ }
+ if (mActiveBrowsingContextInChrome == aContext) {
+ mActiveBrowsingContextInChrome = nullptr;
+ // Deliberately not adjusting the corresponding action id, because
+ // we don't want changes from the past to take effect.
+ }
+}
+
+void nsFocusManager::SetActiveBrowsingContextInContent(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(!aContext || aContext->IsInProcess());
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+
+ if (ActionIdComparableAndLower(aActionId,
+ mActionIdForActiveBrowsingContextInContent)) {
+ LOGFOCUS(
+ ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
+ "the active browsing context due to a stale action id %" PRIu64 ".",
+ aContext, aActionId));
+ return;
+ }
+
+ if (aContext != mActiveBrowsingContextInContent) {
+ if (aContext) {
+ contentChild->SendSetActiveBrowsingContext(aContext, aActionId);
+ } else if (mActiveBrowsingContextInContent) {
+ // We want to sync this over only if this isn't happening
+ // due to the active BrowsingContext switching processes,
+ // in which case the BrowserChild has already marked itself
+ // as destroying.
+ nsPIDOMWindowOuter* outer =
+ mActiveBrowsingContextInContent->GetDOMWindow();
+ if (outer) {
+ nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
+ if (inner) {
+ WindowGlobalChild* globalChild = inner->GetWindowGlobalChild();
+ if (globalChild) {
+ RefPtr<BrowserChild> browserChild = globalChild->GetBrowserChild();
+ if (browserChild && !browserChild->IsDestroyed()) {
+ contentChild->SendUnsetActiveBrowsingContext(
+ mActiveBrowsingContextInContent, aActionId);
+ }
+ }
+ }
+ }
+ }
+ }
+ mActiveBrowsingContextInContentSetFromOtherProcess = false;
+ mActiveBrowsingContextInContent = aContext;
+ mActionIdForActiveBrowsingContextInContent = aActionId;
+ MaybeUnlockPointer(aContext);
+}
+
+void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
+ BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(aContext);
+ if (ActionIdComparableAndLower(aActionId,
+ mActionIdForActiveBrowsingContextInContent)) {
+ LOGFOCUS(
+ ("Ignored an attempt to set active BrowsingContext [%p] from "
+ "another process due to a stale action id %" PRIu64 ".",
+ aContext, aActionId));
+ return;
+ }
+ if (aContext->IsInProcess()) {
+ // This message has been in transit for long enough that
+ // the process association of aContext has changed since
+ // the other content process sent the message, because
+ // an iframe in that process became an out-of-process
+ // iframe while the IPC broadcast that we're receiving
+ // was in-flight. Let's just ignore this.
+ LOGFOCUS(
+ ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
+ "active from another process. actionid: %" PRIu64,
+ aContext, aActionId));
+ return;
+ }
+ mActiveBrowsingContextInContentSetFromOtherProcess = true;
+ mActiveBrowsingContextInContent = aContext;
+ mActionIdForActiveBrowsingContextInContent = aActionId;
+ MaybeUnlockPointer(aContext);
+}
+
+void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
+ BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(aContext);
+ if (ActionIdComparableAndLower(aActionId,
+ mActionIdForActiveBrowsingContextInContent)) {
+ LOGFOCUS(
+ ("Ignored an attempt to unset the active BrowsingContext [%p] from "
+ "another process due to stale action id: %" PRIu64 ".",
+ aContext, aActionId));
+ return;
+ }
+ if (mActiveBrowsingContextInContent == aContext) {
+ mActiveBrowsingContextInContent = nullptr;
+ mActionIdForActiveBrowsingContextInContent = aActionId;
+ MaybeUnlockPointer(nullptr);
+ } else {
+ LOGFOCUS(
+ ("Ignored an attempt to unset the active BrowsingContext [%p] from "
+ "another process. actionid: %" PRIu64,
+ aContext, aActionId));
+ }
+}
+
+void nsFocusManager::ReviseActiveBrowsingContext(
+ uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext,
+ uint64_t aNewActionId) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ if (mActionIdForActiveBrowsingContextInContent == aOldActionId) {
+ LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64
+ ", new "
+ "actionid: %" PRIu64,
+ aContext, aOldActionId, aNewActionId));
+ mActiveBrowsingContextInContent = aContext;
+ mActionIdForActiveBrowsingContextInContent = aNewActionId;
+ } else {
+ LOGFOCUS(
+ ("Ignored a stale attempt to revise the active BrowsingContext [%p]. "
+ "old actionid: %" PRIu64 ", new actionid: %" PRIu64,
+ aContext, aOldActionId, aNewActionId));
+ }
+}
+
+void nsFocusManager::ReviseFocusedBrowsingContext(
+ uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext,
+ uint64_t aNewActionId) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ if (mActionIdForFocusedBrowsingContextInContent == aOldActionId) {
+ LOGFOCUS(
+ ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64
+ ", new "
+ "actionid: %" PRIu64,
+ aContext, aOldActionId, aNewActionId));
+ mFocusedBrowsingContextInContent = aContext;
+ mActionIdForFocusedBrowsingContextInContent = aNewActionId;
+ mFocusedElement = nullptr;
+ } else {
+ LOGFOCUS(
+ ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. "
+ "old actionid: %" PRIu64 ", new actionid: %" PRIu64,
+ aContext, aOldActionId, aNewActionId));
+ }
+}
+
+bool nsFocusManager::SetActiveBrowsingContextInChrome(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
+ MOZ_ASSERT(aActionId);
+ if (ProcessPendingActiveBrowsingContextActionId(aActionId, aContext)) {
+ MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
+ aActionId, mActionIdForActiveBrowsingContextInChrome));
+ mActiveBrowsingContextInChrome = aContext;
+ mActionIdForActiveBrowsingContextInChrome = aActionId;
+ return true;
+ }
+ return false;
+}
+
+uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
+ return mActionIdForActiveBrowsingContextInChrome;
+}
+
+uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
+ return mActionIdForFocusedBrowsingContextInChrome;
+}
+
+BrowsingContext* nsFocusManager::GetActiveBrowsingContextInChrome() {
+ return mActiveBrowsingContextInChrome;
+}
+
+void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId) {
+ LOGFOCUS(("InsertNewFocusActionId %" PRIu64, aActionId));
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!mPendingActiveBrowsingContextActions.Contains(aActionId));
+ mPendingActiveBrowsingContextActions.AppendElement(aActionId);
+ MOZ_ASSERT(!mPendingFocusedBrowsingContextActions.Contains(aActionId));
+ mPendingFocusedBrowsingContextActions.AppendElement(aActionId);
+}
+
+static void RemoveContentInitiatedActionsUntil(
+ nsTArray<uint64_t>& aPendingActions,
+ nsTArray<uint64_t>::index_type aUntil) {
+ nsTArray<uint64_t>::index_type i = 0;
+ while (i < aUntil) {
+ auto [actionProc, actionId] =
+ nsContentUtils::SplitProcessSpecificId(aPendingActions[i]);
+ Unused << actionId;
+ if (actionProc) {
+ aPendingActions.RemoveElementAt(i);
+ --aUntil;
+ continue;
+ }
+ ++i;
+ }
+}
+
+bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
+ uint64_t aActionId, bool aSettingToNonNull) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto index = mPendingActiveBrowsingContextActions.IndexOf(aActionId);
+ if (index == nsTArray<uint64_t>::NoIndex) {
+ return false;
+ }
+ // When aSettingToNonNull is true, we need to remove one more
+ // element to remove the action id itself in addition to
+ // removing the older ones.
+ if (aSettingToNonNull) {
+ index++;
+ }
+ auto [actionProc, actionId] =
+ nsContentUtils::SplitProcessSpecificId(aActionId);
+ Unused << actionId;
+ if (actionProc) {
+ // Action from content: We allow parent-initiated actions
+ // to take precedence over content-initiated ones, so we
+ // remove only prior content-initiated actions.
+ RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions,
+ index);
+ } else {
+ // Action from chrome
+ mPendingActiveBrowsingContextActions.RemoveElementsAt(0, index);
+ }
+ return true;
+}
+
+bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
+ uint64_t aActionId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto index = mPendingFocusedBrowsingContextActions.IndexOf(aActionId);
+ if (index == nsTArray<uint64_t>::NoIndex) {
+ return false;
+ }
+
+ auto [actionProc, actionId] =
+ nsContentUtils::SplitProcessSpecificId(aActionId);
+ Unused << actionId;
+ if (actionProc) {
+ // Action from content: We allow parent-initiated actions
+ // to take precedence over content-initiated ones, so we
+ // remove only prior content-initiated actions.
+ RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions,
+ index);
+ } else {
+ // Action from chrome
+ mPendingFocusedBrowsingContextActions.RemoveElementsAt(0, index);
+ }
+ return true;
+}
+
+// static
+uint64_t nsFocusManager::GenerateFocusActionId() {
+ uint64_t id =
+ nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter);
+ if (XRE_IsParentProcess()) {
+ nsFocusManager* fm = GetFocusManager();
+ if (fm) {
+ fm->InsertNewFocusActionId(id);
+ }
+ } else {
+ mozilla::dom::ContentChild* contentChild =
+ mozilla::dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendInsertNewFocusActionId(id);
+ }
+ LOGFOCUS(("GenerateFocusActionId %" PRIu64, id));
+ return id;
+}
+
+static bool IsInPointerLockContext(nsPIDOMWindowOuter* aWin) {
+ return PointerLockManager::IsInLockContext(aWin ? aWin->GetBrowsingContext()
+ : nullptr);
+}
+
+void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow,
+ uint64_t aActionId,
+ bool aSyncBrowsingContext) {
+ if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker &&
+ IsInPointerLockContext(mFocusedWindow) &&
+ !IsInPointerLockContext(aWindow)) {
+ nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
+ NS_DispatchToCurrentThread(runnable);
+ }
+
+ // Update the last focus time on any affected documents
+ if (aWindow && aWindow != mFocusedWindow) {
+ const TimeStamp now(TimeStamp::Now());
+ for (Document* doc = aWindow->GetExtantDoc(); doc;
+ doc = doc->GetInProcessParentDocument()) {
+ doc->SetLastFocusTime(now);
+ }
+ }
+
+ // This function may be called with zero action id to indicate that the
+ // action id should be ignored.
+ if (XRE_IsContentProcess() && aActionId &&
+ ActionIdComparableAndLower(aActionId,
+ mActionIdForFocusedBrowsingContextInContent)) {
+ // Unclear if this ever happens.
+ LOGFOCUS(
+ ("Ignored an attempt to set an in-process BrowsingContext as "
+ "focused due to stale action id %" PRIu64 ".",
+ aActionId));
+ return;
+ }
+
+ mFocusedWindow = aWindow;
+ BrowsingContext* bc = aWindow ? aWindow->GetBrowsingContext() : nullptr;
+ if (aSyncBrowsingContext) {
+ MOZ_ASSERT(aActionId,
+ "aActionId must not be zero if aSyncBrowsingContext is true");
+ SetFocusedBrowsingContext(bc, aActionId);
+ } else if (XRE_IsContentProcess()) {
+ MOZ_ASSERT(mFocusedBrowsingContextInContent == bc,
+ "Not syncing BrowsingContext even when different.");
+ }
+}
+
+void nsFocusManager::NotifyOfReFocus(Element& aElement) {
+ nsPIDOMWindowOuter* window = GetCurrentWindow(&aElement);
+ if (!window || window != mFocusedWindow) {
+ return;
+ }
+ if (!aElement.IsInComposedDoc() || IsNonFocusableRoot(&aElement)) {
+ return;
+ }
+ nsIDocShell* docShell = window->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+ IMEStateManager::OnReFocus(*presContext, aElement);
+}
+
+void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration) {
+ if (!sInstance) {
+ return;
+ }
+
+ if (sInstance->mActiveWindow) {
+ sInstance->mActiveWindow->MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFocusedWindow) {
+ sInstance->mFocusedWindow->MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mWindowBeingLowered) {
+ sInstance->mWindowBeingLowered->MarkUncollectableForCCGeneration(
+ aGeneration);
+ }
+ if (sInstance->mFocusedElement) {
+ sInstance->mFocusedElement->OwnerDoc()->MarkUncollectableForCCGeneration(
+ aGeneration);
+ }
+ if (sInstance->mFirstBlurEvent) {
+ sInstance->mFirstBlurEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
+ aGeneration);
+ }
+ if (sInstance->mFirstFocusEvent) {
+ sInstance->mFirstFocusEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
+ aGeneration);
+ }
+}
+
+bool nsFocusManager::CanSkipFocus(nsIContent* aContent) {
+ if (!aContent) {
+ return false;
+ }
+
+ if (mFocusedElement == aContent) {
+ return true;
+ }
+
+ nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell();
+ if (!ds) {
+ return true;
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ ds->GetInProcessRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsPIDOMWindowOuter> newRootWindow =
+ root ? root->GetWindow() : nullptr;
+ if (mActiveWindow != newRootWindow) {
+ nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
+ if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
+ return true;
+ }
+ }
+ } else {
+ BrowsingContext* bc = aContent->OwnerDoc()->GetBrowsingContext();
+ BrowsingContext* top = bc ? bc->Top() : nullptr;
+ if (GetActiveBrowsingContext() != top) {
+ nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
+ if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* static */
+Element* nsFocusManager::GetTheFocusableArea(Element* aTarget,
+ uint32_t aFlags) {
+ MOZ_ASSERT(aTarget);
+ nsIFrame* frame = aTarget->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ // If focus target is the document element of its Document.
+ if (aTarget == aTarget->OwnerDoc()->GetRootElement()) {
+ // the root content can always be focused,
+ // except in userfocusignored context.
+ return aTarget;
+ }
+
+ // If focus target is an area element with one or more shapes that are
+ // focusable areas.
+ if (aTarget->IsHTMLElement(nsGkAtoms::area)) {
+ // HTML areas do not have their own frame, and the img frame we get from
+ // GetPrimaryFrame() is not relevant as to whether it is focusable or
+ // not, so we have to do all the relevant checks manually for them.
+ return frame->IsVisibleConsideringAncestors() && aTarget->IsFocusable()
+ ? aTarget
+ : nullptr;
+ }
+
+ // For these 3 steps mentioned in the spec
+ // 1. If focus target is an element with one or more scrollable regions that
+ // are focusable areas
+ // 2. If focus target is a navigable
+ // 3. If focus target is a navigable container with a non-null content
+ // navigable
+ // nsIFrame::IsFocusable will effectively perform the checks for them.
+ if (frame->IsFocusable(aFlags & FLAG_BYMOUSE)) {
+ return aTarget;
+ }
+
+ // If focus target is a shadow host whose shadow root's delegates focus is
+ // true
+ if (ShadowRoot* root = aTarget->GetShadowRoot()) {
+ if (root->DelegatesFocus()) {
+ // If focus target is a shadow-including inclusive ancestor of the
+ // currently focused area of a top-level browsing context's DOM anchor,
+ // then return the already-focused element.
+ if (nsPIDOMWindowInner* innerWindow =
+ aTarget->OwnerDoc()->GetInnerWindow()) {
+ if (Element* focusedElement = innerWindow->GetFocusedElement()) {
+ if (focusedElement->IsShadowIncludingInclusiveDescendantOf(aTarget)) {
+ return focusedElement;
+ }
+ }
+ }
+
+ if (Element* firstFocusable =
+ root->GetFocusDelegate(aFlags & FLAG_BYMOUSE)) {
+ return firstFocusable;
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsresult NS_NewFocusManager(nsIFocusManager** aResult) {
+ NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
+ return NS_OK;
+}
diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h
new file mode 100644
index 0000000000..4dde93c6ea
--- /dev/null
+++ b/dom/base/nsFocusManager.h
@@ -0,0 +1,1027 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFocusManager_h___
+#define nsFocusManager_h___
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFocusManager.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+
+#define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1"
+
+class nsIContent;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Element;
+struct FocusOptions;
+class BrowserParent;
+class ContentChild;
+class ContentParent;
+} // namespace dom
+} // namespace mozilla
+
+struct nsDelayedBlurOrFocusEvent;
+
+/**
+ * The focus manager keeps track of where the focus is, that is, the node
+ * which receives key events.
+ */
+
+class nsFocusManager final : public nsIFocusManager,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ using InputContextAction = mozilla::widget::InputContextAction;
+ using Document = mozilla::dom::Document;
+ friend class mozilla::dom::ContentChild;
+ friend class mozilla::dom::ContentParent;
+
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFocusManager, nsIFocusManager)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIFOCUSMANAGER
+
+ // called to initialize and stop the focus manager at startup and shutdown
+ static nsresult Init();
+ static void Shutdown();
+
+ // Simple helper to call SetFocusedWindow on the instance.
+ //
+ // This raises the window and switches to the tab as needed.
+ MOZ_CAN_RUN_SCRIPT static void FocusWindow(
+ nsPIDOMWindowOuter* aWindow, mozilla::dom::CallerType aCallerType);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static void PrefChanged(const char* aPref,
+ void* aSelf);
+ MOZ_CAN_RUN_SCRIPT void PrefChanged(const char* aPref);
+
+ /**
+ * Retrieve the single focus manager.
+ */
+ static nsFocusManager* GetFocusManager() { return sInstance; }
+
+ /**
+ * A faster version of nsIFocusManager::GetFocusedElement, returning a
+ * raw Element pointer (instead of having AddRef-ed Element
+ * pointer filled in to an out-parameter).
+ */
+ mozilla::dom::Element* GetFocusedElement() { return mFocusedElement; }
+ static mozilla::dom::Element* GetFocusedElementStatic() {
+ return sInstance ? sInstance->GetFocusedElement() : nullptr;
+ }
+
+ /**
+ * Returns true if aContent currently has focus.
+ */
+ bool IsFocused(nsIContent* aContent);
+
+ /**
+ * Returns true if test mode is enabled.
+ */
+ bool IsTestMode();
+
+ /**
+ * Return a focused window. Version of nsIFocusManager::GetFocusedWindow.
+ */
+ nsPIDOMWindowOuter* GetFocusedWindow() const { return mFocusedWindow; }
+
+ /**
+ * In the chrome process, retrieves the BrowsingContext corresponding
+ * to GetFocusedWindow(). In a content process, retrieves the
+ * focused BrowsingContext, which may not belong to this process.
+ */
+ mozilla::dom::BrowsingContext* GetFocusedBrowsingContext() const {
+ if (XRE_IsParentProcess()) {
+ if (mFocusedWindow) {
+ return mFocusedWindow->GetBrowsingContext();
+ }
+ return nullptr;
+ }
+ return mFocusedBrowsingContextInContent;
+ }
+
+ /**
+ * Returns whether the given browsing context is in the active window.
+ */
+ bool IsInActiveWindow(mozilla::dom::BrowsingContext*) const;
+
+ /**
+ * Return an active window. Version of nsIFocusManager::GetActiveWindow.
+ */
+ nsPIDOMWindowOuter* GetActiveWindow() const { return mActiveWindow; }
+
+ /**
+ * In the chrome process, retrieves the BrowsingContext corresponding
+ * to GetActiveWindow(). In a content process, retrieves the
+ * BrowsingContext of the top-level Web content in the active tab if
+ * in the same process as the caller or nullptr otherwise.
+ */
+ mozilla::dom::BrowsingContext* GetActiveBrowsingContext() const {
+ if (XRE_IsParentProcess()) {
+ if (mActiveWindow) {
+ return mActiveWindow->GetBrowsingContext();
+ }
+ return nullptr;
+ }
+ return mActiveBrowsingContextInContent;
+ }
+
+ /**
+ * Called when content has been removed.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult ContentRemoved(Document* aDocument,
+ nsIContent* aContent);
+
+ void NeedsFlushBeforeEventHandling(mozilla::dom::Element* aElement) {
+ if (mFocusedElement == aElement) {
+ mEventHandlingNeedsFlush = true;
+ }
+ }
+
+ bool CanSkipFocus(nsIContent* aContent);
+
+ MOZ_CAN_RUN_SCRIPT void FlushBeforeEventHandlingIfNeeded(
+ nsIContent* aContent) {
+ if (mEventHandlingNeedsFlush) {
+ nsCOMPtr<Document> doc = aContent->GetComposedDoc();
+ if (doc) {
+ mEventHandlingNeedsFlush = false;
+ doc->FlushPendingNotifications(mozilla::FlushType::Layout);
+ }
+ }
+ }
+
+ /**
+ * Update the caret with current mode (whether in caret browsing mode or not).
+ */
+ MOZ_CAN_RUN_SCRIPT void UpdateCaretForCaretBrowsingMode();
+
+ /** @see nsIFocusManager.getLastFocusMethod() */
+ uint32_t GetLastFocusMethod(nsPIDOMWindowOuter*) const;
+
+ /**
+ * Returns the content node that would be focused if aWindow was in an
+ * active window. This will traverse down the frame hierarchy, starting at
+ * the given window aWindow. Sets aFocusedWindow to the window with the
+ * document containing aFocusedContent. If no element is focused,
+ * aFocusedWindow may be still be set -- this means that the document is
+ * focused but no element within it is focused.
+ *
+ * aWindow, aFocusIsOutOfProcess, aFocusedWindow must all be non-null.
+ */
+ enum SearchRange {
+ // Return focused content in aWindow. So, aFocusedWindow is always aWindow.
+ eOnlyCurrentWindow,
+ // Return focused content in aWindow or one of all sub windows.
+ eIncludeAllDescendants,
+ // Return focused content in aWindow or one of visible sub windows.
+ eIncludeVisibleDescendants,
+ };
+ static mozilla::dom::Element* GetFocusedDescendant(
+ nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
+ nsPIDOMWindowOuter** aFocusedWindow);
+
+ /**
+ * Helper function for MoveFocus which determines the next element
+ * to move the focus to and returns it in aNextContent.
+ *
+ * aWindow is the window to adjust the focus within, and aStart is
+ * the element to start navigation from. For tab key navigation,
+ * this should be the currently focused element.
+ *
+ * aType is the type passed to MoveFocus. If aNoParentTraversal is set,
+ * navigation is not done to parent documents and iteration returns to the
+ * beginning (or end) of the starting document.
+ *
+ * aNavigateByKey to move focus by keyboard as a side effect of computing the
+ * next target.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult DetermineElementToMoveFocus(
+ nsPIDOMWindowOuter* aWindow, nsIContent* aStart, int32_t aType,
+ bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent);
+
+ /**
+ * Setter for focusedWindow with CallerType
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult SetFocusedWindowWithCallerType(
+ mozIDOMWindowProxy* aWindowToFocus, mozilla::dom::CallerType aCallerType);
+
+ /**
+ * Given an element, which must be the focused element, activate the remote
+ * frame it embeds, if any.
+ */
+ void ActivateRemoteFrameIfNeeded(mozilla::dom::Element&, uint64_t aActionId);
+
+ /**
+ * Raises the top-level window aWindow at the widget level.
+ */
+ MOZ_CAN_RUN_SCRIPT void RaiseWindow(nsPIDOMWindowOuter* aWindow,
+ mozilla::dom::CallerType aCallerType,
+ uint64_t aActionId);
+
+ /**
+ * Called when a window has been raised.
+ */
+ MOZ_CAN_RUN_SCRIPT void WindowRaised(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId);
+
+ /**
+ * Called when a window has been lowered.
+ */
+ MOZ_CAN_RUN_SCRIPT void WindowLowered(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId);
+
+ /**
+ * Called when a new document in a window is shown.
+ *
+ * If aNeedsFocus is true, then focus events are expected to be fired on the
+ * window if this window is in the focused window chain.
+ */
+ MOZ_CAN_RUN_SCRIPT void WindowShown(mozIDOMWindowProxy* aWindow,
+ bool aNeedsFocus);
+
+ /**
+ * Called when a document in a window has been hidden or otherwise can no
+ * longer accept focus.
+ */
+ MOZ_CAN_RUN_SCRIPT void WindowHidden(mozIDOMWindowProxy* aWindow,
+ uint64_t aActionId);
+
+ /**
+ * Fire any events that have been delayed due to synchronized actions.
+ */
+ MOZ_CAN_RUN_SCRIPT void FireDelayedEvents(Document* aDocument);
+
+ void WasNuked(nsPIDOMWindowOuter* aWindow);
+
+ static uint32_t ProgrammaticFocusFlags(
+ const mozilla::dom::FocusOptions& aOptions);
+
+ /**
+ * Returns an InputContextAction cause for aFlags.
+ */
+ static InputContextAction::Cause GetFocusMoveActionCause(uint32_t aFlags);
+
+ /**
+ * Notify of re-focus to same element.
+ *
+ * aElement is focused element.
+ */
+ MOZ_CAN_RUN_SCRIPT void NotifyOfReFocus(mozilla::dom::Element& aElement);
+
+ static void MarkUncollectableForCCGeneration(uint32_t aGeneration);
+
+ struct BlurredElementInfo {
+ const mozilla::OwningNonNull<mozilla::dom::Element> mElement;
+
+ explicit BlurredElementInfo(mozilla::dom::Element&);
+ ~BlurredElementInfo();
+ };
+
+ protected:
+ nsFocusManager();
+ ~nsFocusManager();
+
+ /**
+ * Ensure that the widget associated with the currently focused window is
+ * focused at the widget level.
+ */
+ void EnsureCurrentWidgetFocused(mozilla::dom::CallerType aCallerType);
+
+ /**
+ * Activate or deactivate the window and send the activate/deactivate events.
+ */
+ void ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive);
+
+ /**
+ * Blur whatever is currently focused and focus aNewContent. aFlags is a
+ * bitmask of the flags defined in nsIFocusManager. If aFocusChanged is
+ * true, then the focus has actually shifted and the caret position will be
+ * updated to the new focus, aNewContent will be scrolled into view (unless
+ * a flag disables this) and the focus method for the window will be updated.
+ * If aAdjustWidget is false, don't change the widget focus state.
+ *
+ * All actual focus changes must use this method to do so. (as opposed
+ * to those that update the focus in an inactive window for instance).
+ *
+ * Returns Nothing() if we end up not trying to focus the element,
+ * otherwise returns the generated action id.
+ */
+ MOZ_CAN_RUN_SCRIPT mozilla::Maybe<uint64_t> SetFocusInner(
+ mozilla::dom::Element* aNewContent, int32_t aFlags, bool aFocusChanged,
+ bool aAdjustWidget);
+
+ /**
+ * Returns true if aPossibleAncestor is the same as aWindow or an
+ * ancestor of aWindow.
+ */
+ bool IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow) const;
+ bool IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ mozilla::dom::BrowsingContext* aContext) const;
+ bool IsSameOrAncestor(mozilla::dom::BrowsingContext* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow) const;
+
+ public:
+ bool IsSameOrAncestor(mozilla::dom::BrowsingContext* aPossibleAncestor,
+ mozilla::dom::BrowsingContext* aContext) const;
+
+ protected:
+ /**
+ * Returns the window that is the lowest common ancestor of both aWindow
+ * and aContext, or null if they share no common ancestor.
+ */
+ mozilla::dom::BrowsingContext* GetCommonAncestor(
+ nsPIDOMWindowOuter* aWindow, mozilla::dom::BrowsingContext* aContext);
+
+ /**
+ * When aBrowsingContext is focused, adjust the ancestors of aBrowsingContext
+ * so that they also have their corresponding frames focused. Thus, one can
+ * start at the active top-level window and navigate down the currently
+ * focused elements for each frame in the tree to get to aBrowsingContext.
+ */
+ MOZ_CAN_RUN_SCRIPT bool AdjustInProcessWindowFocus(
+ mozilla::dom::BrowsingContext* aBrowsingContext, bool aCheckPermission,
+ bool aIsVisible, uint64_t aActionId);
+ MOZ_CAN_RUN_SCRIPT void AdjustWindowFocus(
+ mozilla::dom::BrowsingContext* aBrowsingContext, bool aCheckPermission,
+ bool aIsVisible, uint64_t aActionId);
+
+ /**
+ * Returns true if aWindow is visible.
+ */
+ bool IsWindowVisible(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Returns true if aContent is a root element and not focusable.
+ * I.e., even if aContent is editable root element, this returns true when
+ * the document is in designMode.
+ *
+ * @param aContent must not be null and must be in a document.
+ */
+ bool IsNonFocusableRoot(nsIContent* aContent);
+
+ /**
+ * First flushes the pending notifications to ensure the PresShell and frames
+ * are updated.
+ * Checks and returns aElement if it may be focused, another element node if
+ * the focus should be retargeted at another node, or null if the node
+ * cannot be focused. aFlags are the flags passed to SetFocus and similar
+ * methods.
+ *
+ * An element is focusable if it is in a document, the document isn't in
+ * print preview mode and the element has an nsIFrame where the
+ * IsFocusable method returns true. For <area> elements, there is no
+ * frame, so only the IsFocusable method on the content node must be
+ * true.
+ */
+ MOZ_CAN_RUN_SCRIPT mozilla::dom::Element* FlushAndCheckIfFocusable(
+ mozilla::dom::Element* aElement, uint32_t aFlags);
+
+ /**
+ * Blurs the currently focused element. Returns false if another element was
+ * focused as a result. This would mean that the caller should not proceed
+ * with a pending call to Focus. Normally, true would be returned.
+ *
+ * The currently focused element within aBrowsingContextToClear will be
+ * cleared. aBrowsingContextToClear may be null, which means that no window is
+ * cleared. This will be the case, for example, when lowering a window, as we
+ * want to fire a blur, but not actually change what element would be focused,
+ * so that the same element will be focused again when the window is raised.
+ *
+ * aAncestorBrowsingContextToFocus should be set to the common ancestor of the
+ * window that is being blurred and the window that is going to focused, when
+ * switching focus to a sibling window.
+ *
+ * aIsLeavingDocument should be set to true if the document/window is being
+ * blurred as well. Document/window blur events will be fired. It should be
+ * false if an element is the same document is about to be focused.
+ *
+ * If aAdjustWidget is false, don't change the widget focus state.
+ */
+ MOZ_CAN_RUN_SCRIPT bool Blur(
+ mozilla::dom::BrowsingContext* aBrowsingContextToClear,
+ mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive,
+ uint64_t aActionId, mozilla::dom::Element* aElementToFocus = nullptr);
+ MOZ_CAN_RUN_SCRIPT void BlurFromOtherProcess(
+ mozilla::dom::BrowsingContext* aFocusedBrowsingContext,
+ mozilla::dom::BrowsingContext* aBrowsingContextToClear,
+ mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId);
+ MOZ_CAN_RUN_SCRIPT bool BlurImpl(
+ mozilla::dom::BrowsingContext* aBrowsingContextToClear,
+ mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus,
+ bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive,
+ mozilla::dom::Element* aElementToFocus, uint64_t aActionId);
+
+ /**
+ * Focus an element in the active window and child frame.
+ *
+ * aWindow is the window containing the element aContent to focus.
+ *
+ * aFlags is the flags passed to the various focus methods in
+ * nsIFocusManager.
+ *
+ * aIsNewDocument should be true if a new document is being focused.
+ * Document/window focus events will be fired.
+ *
+ * aFocusChanged should be true if a new content node is being focused, so
+ * the focused content will be scrolled into view and the caret position
+ * will be updated. If false is passed, then a window is simply being
+ * refocused, for instance, due to a window being raised, or a tab is being
+ * switched to.
+ *
+ * If aFocusChanged is true, then the focus has moved to a new location.
+ * Otherwise, the focus is just being updated because the window was
+ * raised.
+ *
+ * aWindowRaised should be true if the window is being raised. In this case,
+ * command updaters will not be called.
+ *
+ * If aAdjustWidget is false, don't change the widget focus state.
+ */
+ MOZ_CAN_RUN_SCRIPT void Focus(
+ nsPIDOMWindowOuter* aWindow, mozilla::dom::Element* aContent,
+ uint32_t aFlags, bool aIsNewDocument, bool aFocusChanged,
+ bool aWindowRaised, bool aAdjustWidget, uint64_t aActionId,
+ const mozilla::Maybe<BlurredElementInfo>& = mozilla::Nothing());
+
+ /**
+ * Send a focus or blur event at aTarget. It may be added to the delayed
+ * event queue if the document is suppressing events.
+ *
+ * aEventMessage should be either eFocus or eBlur.
+ *
+ * aWindowRaised should only be true if called from WindowRaised.
+ */
+ MOZ_CAN_RUN_SCRIPT void SendFocusOrBlurEvent(
+ mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell,
+ Document* aDocument, nsISupports* aTarget, bool aWindowRaised,
+ bool aIsRefocus = false,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+ /**
+ * Fire a focus or blur event at aTarget.
+ *
+ * aEventMessage should be either eFocus or eBlur.
+ * For blur events, aFocusMethod should normally be non-zero.
+ *
+ * aWindowRaised should only be true if called from WindowRaised.
+ */
+ MOZ_CAN_RUN_SCRIPT void FireFocusOrBlurEvent(
+ mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell,
+ nsISupports* aTarget, bool aWindowRaised, bool aIsRefocus = false,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+ /**
+ * Fire a focusin or focusout event
+ *
+ * aEventMessage should be either eFocusIn or eFocusOut.
+ *
+ * aTarget is the content the event will fire on (the object that gained
+ * focus for focusin, the object blurred for focusout).
+ *
+ * aCurrentFocusedWindow is the window focused before the focus/blur event
+ * was fired.
+ *
+ * aCurrentFocusedContent is the content focused before the focus/blur event
+ * was fired.
+ *
+ * aRelatedTarget is the content related to the event (the object
+ * losing focus for focusin, the object getting focus for focusout).
+ */
+ MOZ_CAN_RUN_SCRIPT void FireFocusInOrOutEvent(
+ mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell,
+ nsISupports* aTarget, nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+ /**
+ * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void ScrollIntoView(mozilla::PresShell* aPresShell, nsIContent* aContent,
+ uint32_t aFlags);
+
+ /**
+ * Updates the caret positon and visibility to match the focus.
+ *
+ * aMoveCaretToFocus should be true to move the caret to aContent.
+ *
+ * aUpdateVisibility should be true to update whether the caret is
+ * visible or not.
+ */
+ MOZ_CAN_RUN_SCRIPT void UpdateCaret(bool aMoveCaretToFocus,
+ bool aUpdateVisibility,
+ nsIContent* aContent);
+
+ /**
+ * Helper method to move the caret to the focused element aContent.
+ */
+ MOZ_CAN_RUN_SCRIPT void MoveCaretToFocus(mozilla::PresShell* aPresShell,
+ nsIContent* aContent);
+
+ /**
+ * Makes the caret visible or not, depending on aVisible.
+ */
+ nsresult SetCaretVisible(mozilla::PresShell* aPresShell, bool aVisible,
+ nsIContent* aContent);
+
+ // the remaining functions are used for tab key and document-navigation
+
+ /**
+ * Retrieves the start and end points of the current selection for
+ * aDocument and stores them in aStartContent and aEndContent.
+ */
+ nsresult GetSelectionLocation(Document* aDocument,
+ mozilla::PresShell* aPresShell,
+ nsIContent** aStartContent,
+ nsIContent** aEndContent);
+
+ /**
+ * Retrieve the next tabbable element in scope owned by aOwner, using
+ * focusability and tabindex to determine the tab order.
+ *
+ * aOwner is the owner of scope to search in.
+ *
+ * aStartContent is the starting point for this call of this method.
+ *
+ * aOriginalStartContent is the initial starting point for sequential
+ * navigation.
+ *
+ * aForward should be true for forward navigation or false for backward
+ * navigation.
+ *
+ * aCurrentTabIndex is the current tabindex.
+ *
+ * aIgnoreTabIndex to ignore the current tabindex and find the element
+ * irrespective or the tab index.
+ *
+ * aForDocumentNavigation informs whether we're navigating only through
+ * documents.
+ *
+ * aSkipOwner to skip owner while searching. The flag is set when caller is
+ * |GetNextTabbableContent| in order to let caller handle owner.
+ *
+ * NOTE:
+ * Consider the method searches downwards in flattened subtree
+ * rooted at aOwner.
+ */
+ MOZ_CAN_RUN_SCRIPT nsIContent* GetNextTabbableContentInScope(
+ nsIContent* aOwner, nsIContent* aStartContent,
+ nsIContent* aOriginalStartContent, bool aForward,
+ int32_t aCurrentTabIndex, bool aIgnoreTabIndex,
+ bool aForDocumentNavigation, bool aNavigateByKey, bool aSkipOwner);
+
+ /**
+ * Retrieve the next tabbable element in scope including aStartContent
+ * and the scope's ancestor scopes, using focusability and tabindex to
+ * determine the tab order.
+ *
+ * aStartOwner is the scope owner of the aStartContent.
+ *
+ * aStartContent an in/out paremeter. It as input is the starting point
+ * for this call of this method; as output it is the shadow host in
+ * light DOM if the next tabbable element is not found in shadow DOM,
+ * in order to continue searching in light DOM.
+ *
+ * aOriginalStartContent is the initial starting point for sequential
+ * navigation.
+ *
+ * aForward should be true for forward navigation or false for backward
+ * navigation.
+ *
+ * aCurrentTabIndex returns tab index of shadow host in light DOM if the
+ * next tabbable element is not found in shadow DOM, in order to continue
+ * searching in light DOM.
+ *
+ * aIgnoreTabIndex to ignore the current tabindex and find the element
+ * irrespective or the tab index.
+ *
+ * aForDocumentNavigation informs whether we're navigating only through
+ * documents.
+ *
+ * aNavigateByKey to move focus by keyboard as a side effect of computing the
+ * next target.
+ *
+ * NOTE:
+ * Consider the method searches upwards in all shadow host- or slot-rooted
+ * flattened subtrees that contains aStartContent as non-root, except
+ * the flattened subtree rooted at shadow host in light DOM.
+ */
+ MOZ_CAN_RUN_SCRIPT nsIContent* GetNextTabbableContentInAncestorScopes(
+ nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */,
+ nsIContent* aOriginalStartContent, bool aForward,
+ int32_t* aCurrentTabIndex, bool* aIgnoreTabIndex,
+ bool aForDocumentNavigation, bool aNavigateByKey);
+
+ /**
+ * Retrieve the next tabbable element within a document, using focusability
+ * and tabindex to determine the tab order. The element is returned in
+ * aResultContent.
+ *
+ * aRootContent is the root node -- nodes above this will not be examined.
+ * Typically this will be the root node of a document, but could also be
+ * a popup node.
+ *
+ * aOriginalStartContent is the content which was originally the starting
+ * node, in the case of recursive or looping calls.
+ *
+ * aStartContent is the starting point for this call of this method.
+ * If aStartContent doesn't have visual representation, the next content
+ * object, which does have a primary frame, will be used as a start.
+ * If that content object is focusable, the method may return it.
+ *
+ * aForward should be true for forward navigation or false for backward
+ * navigation.
+ *
+ * aCurrentTabIndex is the current tabindex.
+ *
+ * aIgnoreTabIndex to ignore the current tabindex and find the element
+ * irrespective or the tab index. This will be true when a selection is
+ * active, since we just want to focus the next element in tree order
+ * from where the selection is. Similarly, if the starting element isn't
+ * focusable, since it doesn't really have a defined tab index.
+ *
+ * aNavigateByKey to move focus by keyboard as a side effect of computing the
+ * next target.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult GetNextTabbableContent(
+ mozilla::PresShell* aPresShell, nsIContent* aRootContent,
+ nsIContent* aOriginalStartContent, nsIContent* aStartContent,
+ bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex,
+ bool aForDocumentNavigation, bool aNavigateByKey,
+ nsIContent** aResultContent);
+
+ /**
+ * Get the next tabbable image map area and returns it.
+ *
+ * aForward should be true for forward navigation or false for backward
+ * navigation.
+ *
+ * aCurrentTabIndex is the current tabindex.
+ *
+ * aImageContent is the image.
+ *
+ * aStartContent is the current image map area.
+ */
+ nsIContent* GetNextTabbableMapArea(bool aForward, int32_t aCurrentTabIndex,
+ mozilla::dom::Element* aImageContent,
+ nsIContent* aStartContent);
+
+ /**
+ * Return the next valid tabindex value after aCurrentTabIndex, if aForward
+ * is true, or the previous tabindex value if aForward is false. aParent is
+ * the node from which to start looking for tab indicies.
+ */
+ int32_t GetNextTabIndex(nsIContent* aParent, int32_t aCurrentTabIndex,
+ bool aForward);
+
+ /**
+ * Focus the first focusable content within the document with a root node of
+ * aRootContent. For content documents, this will be aRootContent itself, but
+ * for chrome documents, this will locate the next focusable content.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult FocusFirst(mozilla::dom::Element* aRootContent,
+ nsIContent** aNextContent);
+
+ /**
+ * Retrieves and returns the root node from aDocument to be focused. Will
+ * return null if the root node cannot be focused. There are several reasons
+ * for this:
+ *
+ * - if aForDocumentNavigation is false and aWindow is a chrome shell.
+ * - if aCheckVisibility is true and the aWindow is not visible.
+ * - if aDocument is a frameset document.
+ */
+ mozilla::dom::Element* GetRootForFocus(nsPIDOMWindowOuter* aWindow,
+ Document* aDocument,
+ bool aForDocumentNavigation,
+ bool aCheckVisibility);
+
+ /**
+ * Retrieves and returns the root node as with GetRootForFocus but only if
+ * aContent is a frame with a valid child document.
+ */
+ mozilla::dom::Element* GetRootForChildDocument(nsIContent* aContent);
+
+ /**
+ * Retreives a focusable element within the current selection of aWindow.
+ * Currently, this only detects links.
+ *
+ * This is used when MoveFocus is called with a type of MOVEFOCUS_CARET,
+ * which is used, for example, to focus links as the caret is moved over
+ * them.
+ */
+ void GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStartSelection,
+ nsIContent* aEndSelection,
+ nsIContent** aFocusedContent);
+
+ private:
+ // Notify that the focus state of aElement has changed. Note that we need to
+ // pass in whether the window should show a focus ring before the
+ // SetFocusedNode call on it happened when losing focus and after the
+ // SetFocusedNode call when gaining focus, which is why that information needs
+ // to be an explicit argument instead of just passing in the window and asking
+ // it whether it should show focus rings: in the losing focus case that
+ // information could be wrong.
+ //
+ // aShouldShowFocusRing is only relevant if aGettingFocus is true.
+ static void NotifyFocusStateChange(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aElementToFocus,
+ int32_t aFlags, bool aGettingFocus,
+ bool aShouldShowFocusRing);
+
+ void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow, uint64_t aActionId,
+ bool aSyncBrowsingContext = true);
+
+ MOZ_CAN_RUN_SCRIPT bool TryDocumentNavigation(nsIContent* aCurrentContent,
+ bool* aCheckSubDocument,
+ nsIContent** aResultContent);
+
+ MOZ_CAN_RUN_SCRIPT bool TryToMoveFocusToSubDocument(
+ nsIContent* aCurrentContent, nsIContent* aOriginalStartContent,
+ bool aForward, bool aForDocumentNavigation, bool aNavigateByKey,
+ nsIContent** aResultContent);
+
+ // Sets the focused BrowsingContext and, if appropriate, syncs it to
+ // other processes.
+ void SetFocusedBrowsingContext(mozilla::dom::BrowsingContext* aContext,
+ uint64_t aActionId);
+
+ // Content-only
+ // Called when receiving an IPC message about another process setting
+ // the focused BrowsingContext.
+ void SetFocusedBrowsingContextFromOtherProcess(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId);
+
+ // Chrome-only
+ // When returning true, sets the chrome process notion of what
+ // BrowsingContext is focused in content. When returning false,
+ // ignores the attempt to set as out-of-sequence.
+ bool SetFocusedBrowsingContextInChrome(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId);
+
+ void InsertNewFocusActionId(uint64_t aActionId);
+
+ bool ProcessPendingActiveBrowsingContextActionId(uint64_t aActionId,
+ bool aSettingToNonNull);
+
+ bool ProcessPendingFocusedBrowsingContextActionId(uint64_t aActionId);
+
+ public:
+ // Chrome-only
+ // Gets the chrome process notion of what BrowsingContext is focused
+ // in content.
+ mozilla::dom::BrowsingContext* GetFocusedBrowsingContextInChrome();
+
+ // Chrome-only
+ // Notifies the focus manager that BrowsingContext::Detach was called
+ // on a BrowsingContext so that pointers to it can be forgotten.
+ void BrowsingContextDetached(mozilla::dom::BrowsingContext* aContext);
+
+ private:
+ // Content-only
+ // Sets the BrowsingContext corresponding to top-level Web content
+ // in the frontmost tab if focus is in Web content.
+ void SetActiveBrowsingContextInContent(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId);
+
+ // Content-only
+ // Receives notification of another process setting the top-level Web
+ // content as being in the frontmost tab with focus in Web content.
+ void SetActiveBrowsingContextFromOtherProcess(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId);
+
+ // Content-only
+ // Receives notification that another process determined that focus
+ // moved to chrome so a particular BrowsingContext is no longer the
+ // "active" one.
+ void UnsetActiveBrowsingContextFromOtherProcess(
+ mozilla::dom::BrowsingContext* aContext, uint64_t aActionId);
+
+ // Content-only
+ // Receives a notification from parent that this content process's
+ // attempt to set the active browsing context was late and the
+ // prevailing browsing context is instead the second argument of
+ // this method call. This should be ignored if the first argument
+ // doesn't match the latest action id associated with setting the
+ // active browsing context in this process, because in that case,
+ // this revision is late.
+ void ReviseActiveBrowsingContext(uint64_t aOldActionId,
+ mozilla::dom::BrowsingContext* aContext,
+ uint64_t aNewActionId);
+
+ // Receives a notification from parent that this content process's
+ // attempt to set the focused browsing context was late and the
+ // prevailing browsing context is instead the second argument of
+ // this method call. This should be ignored if the first argument
+ // doesn't match the latest action id associated with setting the
+ // active browsing context in this process, because in that case,
+ // this revision is late.
+ void ReviseFocusedBrowsingContext(uint64_t aOldActionId,
+ mozilla::dom::BrowsingContext* aContext,
+ uint64_t aNewActionId);
+
+ // Chrome-only
+ // Sets the chrome process notion of what content believes to be
+ // the top-level BrowsingContext in the frontmost tab when focus
+ // is in Web content.
+ // Returns true if set and false if ignored.
+ bool SetActiveBrowsingContextInChrome(mozilla::dom::BrowsingContext* aContext,
+ uint64_t aActionId);
+
+ public:
+ // Chrome-only
+ // Gets the chrome process notion of what content believes to be
+ // the top-level BrowsingContext in the frontmost tab when focus
+ // is in Web content.
+ mozilla::dom::BrowsingContext* GetActiveBrowsingContextInChrome();
+
+ uint64_t GetActionIdForActiveBrowsingContextInChrome() const;
+
+ uint64_t GetActionIdForFocusedBrowsingContextInChrome() const;
+
+ static uint64_t GenerateFocusActionId();
+
+ // This function works very similar to
+ // https://html.spec.whatwg.org/#get-the-focusable-area
+ static mozilla::dom::Element* GetTheFocusableArea(
+ mozilla::dom::Element* aTarget, uint32_t aFlags);
+
+ private:
+ // In the chrome process, the currently active and front-most top-most
+ // window. Not supposed to be used in a meaningful way in content
+ // processes. For legacy reasons, this exists as a separate field
+ // instead of being derived from mFocusedWindow when needed, because
+ // the defined relation that mActiveWindow is supposed to be the same
+ // as or ancestor of mFocusedWindow is temporarily broken when a
+ // window is being raised or lowered.
+ nsCOMPtr<nsPIDOMWindowOuter> mActiveWindow;
+
+ // In a content process, the BrowsingContext corresponding to top-level
+ // Web content in the active tab or nullptr if focus is not in a
+ // BrowsingContextGroup that this process participates in. Synced
+ // across processes in a BrowsingContextGroup. This field exists
+ // separately from mFocusedBrowsingContextInContent instead of being being
+ // derived from it, because for legacy reasons the relation
+ // mFocusedBrowsingContextInContent->Top() == mActiveBrowsingContextInContent
+ // is temporarily broken when a window is being raised or lowered.
+ // Not supposed to be used in a meaningful way in the chrome process.
+ RefPtr<mozilla::dom::BrowsingContext> mActiveBrowsingContextInContent;
+
+ // If this content process set mActiveBrowsingContextInContent, this
+ // field holds the corresponding actionId so that
+ // mActiveBrowsingContextInContent can be revised of the parent rejects
+ // the update. This field is used for accepting revisions only if nothing
+ // else has updated mActiveBrowsingContextInContent before the revision
+ // arrives.
+ uint64_t mActionIdForActiveBrowsingContextInContent;
+
+ uint64_t mActionIdForActiveBrowsingContextInChrome;
+
+ // If this content process set mFocusedBrowsingContextInContent, this
+ // field holds the corresponding actionId so that
+ // mFocusedBrowsingContextInContent can be revised of the parent rejects
+ // the update. This field is used for accepting revisions only if nothing
+ // else has updated mFocusedBrowsingContextInContent before the revision
+ // arrives.
+ uint64_t mActionIdForFocusedBrowsingContextInContent;
+
+ uint64_t mActionIdForFocusedBrowsingContextInChrome;
+
+ // Whether or not mActiveBrowsingContextInContent was set from another process
+ // or from this process.
+ bool mActiveBrowsingContextInContentSetFromOtherProcess;
+
+ // This is the chrome process notion of content's
+ // mActiveBrowsingContextInContent. Avoiding field reuse for different
+ // semantics in different process types to make it easier to catch bugs.
+ RefPtr<mozilla::dom::BrowsingContext> mActiveBrowsingContextInChrome;
+
+ // the child or top-level window that is currently focused. In the chrome
+ // process, when a window isn't being raised or lowered, this window will
+ // either be the same window as mActiveWindow or a descendant of it.
+ // Except during shutdown use SetFocusedWindowInternal to set mFocusedWindow!
+ nsCOMPtr<nsPIDOMWindowOuter> mFocusedWindow;
+
+ // The focused BrowsingContext if this is a chrome process and focus is
+ // in chrome or if this is a content process and focus is in Web content
+ // in this BrowsingContextGroup. nullptr otherwise.
+ // Except during shutdown, must be set via SetFocusedWindowInternal which
+ // calls SetFocusedBrowsingContext or if the value is coming in via IPC
+ // via SetFocusedBrowsingContextFromOtherProcess.
+ RefPtr<mozilla::dom::BrowsingContext> mFocusedBrowsingContextInContent;
+
+ // This is the chrome process notion of content's
+ // mFocusedBrowsingContextInContent. Avoiding field reuse for different
+ // semantics in different process types to make it easier to catch bugs.
+ RefPtr<mozilla::dom::BrowsingContext> mFocusedBrowsingContextInChrome;
+
+ // the currently focused content if in-process or the XUL browser in which
+ // Web content focus resides. Always inside mFocusedWindow. When a window
+ // isn't being raised or lowered, this is a cached copy of the
+ // mFocusedWindow's current content. This may be null if no content is
+ // focused.
+ RefPtr<mozilla::dom::Element> mFocusedElement;
+
+ // these fields store a content node temporarily while it is being focused
+ // or blurred to ensure that a recursive call doesn't refire the same event.
+ // They will always be cleared afterwards.
+ RefPtr<mozilla::dom::Element> mFirstBlurEvent;
+ RefPtr<mozilla::dom::Element> mFirstFocusEvent;
+
+ // keep track of a window while it is being lowered
+ nsCOMPtr<nsPIDOMWindowOuter> mWindowBeingLowered;
+
+ // synchronized actions cannot be interrupted with events, so queue these up
+ // and fire them later.
+ nsTArray<nsDelayedBlurOrFocusEvent> mDelayedBlurFocusEvents;
+
+ // Array of focus action ids for which we haven't seen an active browsing
+ // context set yet. As set is allowed to overwrite an unset. Therefore,
+ // an unset removes earlier ids but not the matching id. A set removes
+ // earlier ids and the matching id.
+ //
+ // Conceptually, active browsing context shouldn't have to exist as a
+ // field, because it should be possible to always derive it from the
+ // focused browsing context. Unfortunately, for legacy reasons, this
+ // is not the case while a window is being raised or lowered.
+ //
+ // Conceptually, it should be possible for the parent to manage the
+ // active browsing context. Unfortunately, for legacy reasons, the
+ // code for setting the active browsing context needs to reside in
+ // the content process to retain the existing and test-passing code
+ // flow.
+ //
+ // This, obviously, raises the issue of content processes racing to
+ // set the active browsing context. In particular, there is a pattern
+ // that the parent initiates actions that cause multiple content
+ // processes to mutate the active browsing context at almost the
+ // same time. When two native browser windows change order, the
+ // lowering isn't distinguished from the case of lowering the
+ // entire app. For this reason, the owner of the previous active
+ // browsing context tries to unset it and at almost the same time
+ // the another content process sets a new active browsing context.
+ // If the IPC messages for these unset and set actions were to
+ // arrive in the wrong order, this could get in the wrong state.
+ //
+ // To address this issue, the parent manages an authortative order
+ // of attempts to (un)set the active browsing context using the
+ // array mPendingActiveBrowsingContextActions.
+ //
+ // A process reserves a slot in the order by calling
+ // GenerateFocusActionId(). Per one call to GenerateFocusActionId(),
+ // there may be at most one action to set the active browsing context
+ // to a new value. There may be logically prior attempts to unset it
+ // (i.e. set it to nullptr). That is, if there are both attempts to
+ // unset and set the active browsing context with the same action id,
+ // the attempt to set to a non-null value wins.
+ //
+ // The completion of an action from reserting the slot in the order
+ // and actually performing the setting of the active browsing context
+ // may span multiple processes and IPC messages.
+ //
+ // The at-most-once property is not asserted, because the process
+ // claiming the position in the order and the process setting the
+ // active browsing context with that actionId may be different, and
+ // the act of using an actionId to set the active browsing context
+ // is used to delete stale items from the array to avoid excessive
+ // growth of the array.
+ nsTArray<uint64_t> mPendingActiveBrowsingContextActions;
+
+ // Like mPendingActiveBrowsingContextActions but for the focused
+ // browsing context.
+ nsTArray<uint64_t> mPendingFocusedBrowsingContextActions;
+
+ // If set to true, layout of the document of the event target should be
+ // flushed before handling focus depending events.
+ bool mEventHandlingNeedsFlush;
+
+ static bool sTestMode;
+
+ // Process-specific counter for maintaining the prosess-specific
+ // uniqueness of actionIds.
+ static uint64_t sFocusActionCounter;
+
+ // the single focus manager
+ static mozilla::StaticRefPtr<nsFocusManager> sInstance;
+};
+
+nsresult NS_NewFocusManager(nsIFocusManager** aResult);
+
+#endif
diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp
new file mode 100644
index 0000000000..0b809adece
--- /dev/null
+++ b/dom/base/nsFrameLoader.cpp
@@ -0,0 +1,3925 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Class for managing loading of a subframe (creation of the docshell,
+ * handling of loads in it, recursion-checking).
+ */
+
+#include "nsFrameLoader.h"
+
+#include "base/basictypes.h"
+
+#include "prenv.h"
+
+#include "nsDocShell.h"
+#include "nsIContentInlines.h"
+#include "nsIContentViewer.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsDocShellLoadState.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowser.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsError.h"
+#include "nsIAppWindow.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsIScriptError.h"
+#include "nsGlobalWindow.h"
+#include "nsHTMLDocument.h"
+#include "nsPIWindowRoot.h"
+#include "nsLayoutUtils.h"
+#include "nsMappedAttributes.h"
+#include "nsView.h"
+#include "nsBaseWidget.h"
+#include "nsQueryObject.h"
+#include "ReferrerInfo.h"
+#include "nsIOpenWindowInfo.h"
+#include "nsISHistory.h"
+#include "nsIURI.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsFocusManager.h"
+#include "nsIINIParser.h"
+#include "nsAppRunner.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsThreadUtils.h"
+
+#include "nsIDOMChromeWindow.h"
+#include "InProcessBrowserChildMessageManager.h"
+
+#include "ContentParent.h"
+#include "BrowserParent.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ChromeMessageSender.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FrameCrashedEvent.h"
+#include "mozilla/dom/FrameLoaderBinding.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
+#include "mozilla/dom/PBrowser.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreParent.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "mozilla/gfx/CrossProcessPaint.h"
+#include "mozilla/ProfilerLabels.h"
+#include "nsGenericHTMLFrameElement.h"
+
+#include "jsapi.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "nsSandboxFlags.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/dom/CustomEvent.h"
+
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/WebBrowserPersistLocalDocument.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ChildSHistory.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/dom/BrowserBridgeHost.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+#include "mozilla/dom/HTMLBodyElement.h"
+
+#include "mozilla/ContentPrincipal.h"
+
+#include "nsXULPopupManager.h"
+
+#ifdef NS_PRINTING
+# include "nsIWebBrowserPrint.h"
+#endif
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+# include "mozilla/Telemetry.h"
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+using namespace mozilla;
+using namespace mozilla::hal;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using namespace mozilla::ipc;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+using ViewID = ScrollableLayerGuid::ViewID;
+
+using PrintPreviewResolver = std::function<void(const PrintPreviewResultInfo&)>;
+
+// Bug 8065: Limit content frame depth to some reasonable level. This
+// does not count chrome frames when determining depth, nor does it
+// prevent chrome recursion. Number is fairly arbitrary, but meant to
+// keep number of shells to a reasonable number on accidental recursion with a
+// small (but not 1) branching factor. With large branching factors the number
+// of shells can rapidly become huge and run us out of memory. To solve that,
+// we'd need to re-institute a fixed version of bug 98158.
+#define MAX_DEPTH_CONTENT_FRAMES 10
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsFrameLoader, mPendingBrowsingContext,
+ mMessageManager, mChildMessageManager,
+ mRemoteBrowser, mSessionStoreChild)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsFrameLoader)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext,
+ bool aIsRemoteFrame, bool aNetworkCreated)
+ : mPendingBrowsingContext(aBrowsingContext),
+ mOwnerContent(aOwner),
+ mDetachedSubdocFrame(nullptr),
+ mPendingSwitchID(0),
+ mChildID(0),
+ mRemoteType(NOT_REMOTE_TYPE),
+ mInitialized(false),
+ mDepthTooGreat(false),
+ mIsTopLevelContent(false),
+ mDestroyCalled(false),
+ mNeedsAsyncDestroy(false),
+ mInSwap(false),
+ mInShow(false),
+ mHideCalled(false),
+ mNetworkCreated(aNetworkCreated),
+ mLoadingOriginalSrc(false),
+ mRemoteBrowserShown(false),
+ mIsRemoteFrame(aIsRemoteFrame),
+ mWillChangeProcess(false),
+ mObservingOwnerContent(false),
+ mTabProcessCrashFired(false) {
+ nsCOMPtr<nsFrameLoaderOwner> owner = do_QueryInterface(aOwner);
+ owner->AttachFrameLoader(this);
+}
+
+nsFrameLoader::~nsFrameLoader() {
+ if (mMessageManager) {
+ mMessageManager->Disconnect();
+ }
+
+ MOZ_ASSERT(!mOwnerContent);
+ MOZ_RELEASE_ASSERT(mDestroyCalled);
+}
+
+static nsAtom* TypeAttrName(Element* aOwnerContent) {
+ return aOwnerContent->IsXULElement() ? nsGkAtoms::type
+ : nsGkAtoms::mozframetype;
+}
+
+static void GetFrameName(Element* aOwnerContent, nsAString& aFrameName) {
+ int32_t namespaceID = aOwnerContent->GetNameSpaceID();
+ if (namespaceID == kNameSpaceID_XHTML && !aOwnerContent->IsInHTMLDocument()) {
+ aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aFrameName);
+ } else {
+ aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, aFrameName);
+ // XXX if no NAME then use ID, after a transition period this will be
+ // changed so that XUL only uses ID too (bug 254284).
+ if (aFrameName.IsEmpty() && namespaceID == kNameSpaceID_XUL) {
+ aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aFrameName);
+ }
+ }
+}
+
+// If this method returns true, the nsFrameLoader will act as a boundary, as is
+// the case for <iframe mozbrowser> and <browser type="content"> elements.
+//
+// # Historical Notes (10 April 2019)
+//
+// In the past, this boundary was defined by the "typeContent" and "typeChrome"
+// nsIDocShellTreeItem types. There was only ever a single split in the tree,
+// and it occurred at the boundary between these two types of docshells. When
+// <iframe mozbrowser> was introduced, it was given special casing to make it
+// act like a second boundary, without having to change the existing code.
+//
+// The about:addons page, which is loaded within a content browser, then added a
+// remote <browser type="content" remote="true"> element. When remote, this
+// would also act as a mechanism for creating a disjoint tree, due to the
+// process keeping the embedder and embedee separate.
+//
+// However, when initial out-of-process iframe support was implemented, this
+// codepath became a risk, as it could've caused the oop iframe remote
+// WindowProxy code to be activated for the addons page. This was fixed by
+// extendng the isolation logic previously reserved to <iframe mozbrowser> to
+// also cover <browser> elements with the explicit `remote` property loaded in
+// content.
+//
+// To keep these boundaries clear, and allow them to work in a cross-process
+// manner, they are no longer handled by typeContent and typeChrome. Instead,
+// the actual BrowsingContext tree is broken at these edges.
+static bool IsTopContent(BrowsingContext* aParent, Element* aOwner) {
+ if (XRE_IsContentProcess()) {
+ return false;
+ }
+
+ // If we have a (deprecated) mozbrowser element, we want to start a new
+ // BrowsingContext tree regardless of whether the parent is chrome or content.
+ nsCOMPtr<nsIMozBrowserFrame> mozbrowser = aOwner->GetAsMozBrowserFrame();
+ if (mozbrowser && mozbrowser->GetReallyIsBrowser()) {
+ return true;
+ }
+
+ if (aParent->IsContent()) {
+ // If we're already in content, we may still want to create a new
+ // BrowsingContext tree if our element is a xul browser element with a
+ // `remote="true"` marker.
+ return aOwner->IsXULElement() &&
+ aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
+ nsGkAtoms::_true, eCaseMatters);
+ }
+
+ // If we're in a chrome context, we want to start a new tree if we are an
+ // element with a `type="content"` marker.
+ return aOwner->AttrValueIs(kNameSpaceID_None, TypeAttrName(aOwner),
+ nsGkAtoms::content, eIgnoreCase);
+}
+
+static already_AddRefed<BrowsingContext> CreateBrowsingContext(
+ Element* aOwner, nsIOpenWindowInfo* aOpenWindowInfo,
+ BrowsingContextGroup* aSpecificGroup, bool aNetworkCreated = false) {
+ MOZ_ASSERT(!aOpenWindowInfo || !aSpecificGroup,
+ "Only one of SpecificGroup and OpenWindowInfo may be provided!");
+
+ // If we've got a pending BrowserParent from the content process, use the
+ // BrowsingContext which was created for it.
+ if (aOpenWindowInfo && aOpenWindowInfo->GetNextRemoteBrowser()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return do_AddRef(
+ aOpenWindowInfo->GetNextRemoteBrowser()->GetBrowsingContext());
+ }
+
+ RefPtr<BrowsingContext> opener;
+ if (aOpenWindowInfo && !aOpenWindowInfo->GetForceNoOpener()) {
+ opener = aOpenWindowInfo->GetParent();
+ if (opener) {
+ // Must create BrowsingContext with opener in-process.
+ MOZ_ASSERT(opener->IsInProcess());
+
+ // This can only happen when the opener was closed from a nested event
+ // loop in the window provider code, and only when the open was triggered
+ // by a non-e10s tab, and the new tab is being opened in a new browser
+ // window. Since it is a corner case among corner cases, and the opener
+ // window will appear to be null to consumers after it is discarded
+ // anyway, just drop the opener entirely.
+ if (opener->IsDiscarded()) {
+ NS_WARNING(
+ "Opener was closed from a nested event loop in the parent process. "
+ "Please fix this.");
+ opener = nullptr;
+ }
+ }
+ }
+
+ RefPtr<nsGlobalWindowInner> parentInner =
+ nsGlobalWindowInner::Cast(aOwner->OwnerDoc()->GetInnerWindow());
+ if (NS_WARN_IF(!parentInner) || parentInner->IsDying()) {
+ return nullptr;
+ }
+
+ BrowsingContext* parentBC = parentInner->GetBrowsingContext();
+ if (NS_WARN_IF(!parentBC) || parentBC->IsDiscarded()) {
+ return nullptr;
+ }
+
+ // Determine the frame name for the new browsing context.
+ nsAutoString frameName;
+ GetFrameName(aOwner, frameName);
+
+ // Create our BrowsingContext without immediately attaching it. It's possible
+ // that no DocShell or remote browser will ever be created for this
+ // FrameLoader, particularly if the document that we were created for is not
+ // currently active. And in that latter case, if we try to attach our BC now,
+ // it will wind up attached as a child of the currently active inner window
+ // for the BrowsingContext, and cause no end of trouble.
+ if (IsTopContent(parentBC, aOwner)) {
+ // Create toplevel context without a parent & as Type::Content.
+ return BrowsingContext::CreateDetached(
+ nullptr, opener, aSpecificGroup, frameName,
+ BrowsingContext::Type::Content, false);
+ }
+
+ MOZ_ASSERT(!aOpenWindowInfo,
+ "Can't have openWindowInfo for non-toplevel context");
+
+ MOZ_ASSERT(!aSpecificGroup,
+ "Can't force BrowsingContextGroup for non-toplevel context");
+ return BrowsingContext::CreateDetached(parentInner, nullptr, nullptr,
+ frameName, parentBC->GetType(), false,
+ !aNetworkCreated);
+}
+
+static bool InitialLoadIsRemote(Element* aOwner) {
+ if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
+ Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
+ return false;
+ }
+
+ // The initial load in an content process iframe should never be made remote.
+ // Content process iframes always become remote due to navigation.
+ if (XRE_IsContentProcess()) {
+ return false;
+ }
+
+ // If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
+ // fall back to the default.
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aOwner);
+ bool isMozBrowserFrame = browserFrame && browserFrame->GetReallyIsBrowser();
+ if (isMozBrowserFrame &&
+ !aOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::remote)) {
+ return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false);
+ }
+
+ // Otherwise, we're remote if we have "remote=true" and we're either a
+ // browser frame or a XUL element.
+ return (isMozBrowserFrame || aOwner->GetNameSpaceID() == kNameSpaceID_XUL) &&
+ aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+static already_AddRefed<BrowsingContextGroup> InitialBrowsingContextGroup(
+ Element* aOwner) {
+ nsAutoString attrString;
+ if (aOwner->GetNameSpaceID() != kNameSpaceID_XUL ||
+ !aOwner->GetAttr(nsGkAtoms::initialBrowsingContextGroupId, attrString)) {
+ return nullptr;
+ }
+
+ // It's OK to read the attribute using a signed 64-bit integer parse, as an ID
+ // generated using `nsContentUtils::GenerateProcessSpecificId` (like BCG IDs)
+ // will only ever use 53 bits of precision, so it can be round-tripped through
+ // a JS number.
+ nsresult rv = NS_OK;
+ int64_t signedGroupId = attrString.ToInteger64(&rv, 10);
+ if (NS_FAILED(rv) || signedGroupId <= 0) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "we intended to have a particular id, but failed to parse it!");
+ return nullptr;
+ }
+
+ return BrowsingContextGroup::GetOrCreate(uint64_t(signedGroupId));
+}
+
+already_AddRefed<nsFrameLoader> nsFrameLoader::Create(
+ Element* aOwner, bool aNetworkCreated, nsIOpenWindowInfo* aOpenWindowInfo) {
+ NS_ENSURE_TRUE(aOwner, nullptr);
+ Document* doc = aOwner->OwnerDoc();
+
+ // We never create nsFrameLoaders for elements in resource documents.
+ //
+ // We never create nsFrameLoaders for elements in data documents, unless the
+ // document is a static document.
+ // Static documents are an exception because any sub-documents need an
+ // nsFrameLoader to keep the relevant docShell alive, even though the
+ // nsFrameLoader isn't used to load anything (the sub-document is created by
+ // the static clone process).
+ //
+ // We never create nsFrameLoaders for elements that are not
+ // in-composed-document, unless the element belongs to a static document.
+ // Static documents are an exception because this method is called at a point
+ // in the static clone process before aOwner has been inserted into its
+ // document. For other types of documents this wouldn't be a problem since
+ // we'd create the nsFrameLoader as necessary after aOwner is inserted into a
+ // document, but the mechanisms that take care of that don't apply for static
+ // documents so we need to create the nsFrameLoader now. (This isn't wasteful
+ // since for a static document we know aOwner will end up in a document and
+ // the nsFrameLoader will be used for its docShell.)
+ //
+ NS_ENSURE_TRUE(!doc->IsResourceDoc() &&
+ ((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
+ doc->IsStaticDocument()),
+ nullptr);
+
+ RefPtr<BrowsingContextGroup> group = InitialBrowsingContextGroup(aOwner);
+ RefPtr<BrowsingContext> context =
+ CreateBrowsingContext(aOwner, aOpenWindowInfo, group, aNetworkCreated);
+ NS_ENSURE_TRUE(context, nullptr);
+
+ if (XRE_IsParentProcess() && aOpenWindowInfo) {
+ MOZ_ASSERT(context->IsTopContent());
+ if (RefPtr<BrowsingContext> crossGroupOpener =
+ aOpenWindowInfo->GetParent()) {
+ context->Canonical()->SetCrossGroupOpenerId(crossGroupOpener->Id());
+ }
+ }
+
+ bool isRemoteFrame = InitialLoadIsRemote(aOwner);
+ RefPtr<nsFrameLoader> fl =
+ new nsFrameLoader(aOwner, context, isRemoteFrame, aNetworkCreated);
+ fl->mOpenWindowInfo = aOpenWindowInfo;
+
+ // If this is a toplevel initial remote frame, we're looking at a browser
+ // loaded in the parent process. Pull the remote type attribute off of the
+ // <browser> element to determine which remote type it should be loaded in, or
+ // use `DEFAULT_REMOTE_TYPE` if we can't tell.
+ if (isRemoteFrame) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsAutoString remoteType;
+ if (aOwner->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType, remoteType) &&
+ !remoteType.IsEmpty()) {
+ CopyUTF16toUTF8(remoteType, fl->mRemoteType);
+ } else {
+ fl->mRemoteType = DEFAULT_REMOTE_TYPE;
+ }
+ }
+ return fl.forget();
+}
+
+/* static */
+already_AddRefed<nsFrameLoader> nsFrameLoader::Recreate(
+ mozilla::dom::Element* aOwner, BrowsingContext* aContext,
+ BrowsingContextGroup* aSpecificGroup,
+ const NavigationIsolationOptions& aRemotenessOptions, bool aIsRemote,
+ bool aNetworkCreated, bool aPreserveContext) {
+ NS_ENSURE_TRUE(aOwner, nullptr);
+
+#ifdef DEBUG
+ // This version of Create is only called for Remoteness updates, so we can
+ // assume we need a FrameLoader here and skip the check in the other Create.
+ Document* doc = aOwner->OwnerDoc();
+ MOZ_ASSERT(!doc->IsResourceDoc());
+ MOZ_ASSERT((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
+ doc->IsStaticDocument());
+#endif
+
+ RefPtr<BrowsingContext> context = aContext;
+ if (!context || !aPreserveContext) {
+ context = CreateBrowsingContext(aOwner, /* openWindowInfo */ nullptr,
+ aSpecificGroup);
+ if (aContext) {
+ MOZ_ASSERT(
+ XRE_IsParentProcess(),
+ "Recreating browing contexts only supported in the parent process");
+ aContext->Canonical()->SynchronizeLayoutHistoryState();
+ aContext->Canonical()->ReplacedBy(context->Canonical(),
+ aRemotenessOptions);
+ }
+ }
+ NS_ENSURE_TRUE(context, nullptr);
+
+ RefPtr<nsFrameLoader> fl =
+ new nsFrameLoader(aOwner, context, aIsRemote, aNetworkCreated);
+ return fl.forget();
+}
+
+void nsFrameLoader::LoadFrame(bool aOriginalSrc) {
+ if (NS_WARN_IF(!mOwnerContent)) {
+ return;
+ }
+
+ nsAutoString src;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+
+ bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+ if (isSrcdoc) {
+ src.AssignLiteral("about:srcdoc");
+ principal = mOwnerContent->NodePrincipal();
+ csp = mOwnerContent->GetCsp();
+ } else {
+ GetURL(src, getter_AddRefs(principal), getter_AddRefs(csp));
+
+ src.Trim(" \t\n\r");
+
+ if (src.IsEmpty()) {
+ // If the frame is a XUL element and has the attribute 'nodefaultsrc=true'
+ // then we will not use 'about:blank' as fallback but return early without
+ // starting a load if no 'src' attribute is given (or it's empty).
+ if (mOwnerContent->IsXULElement() &&
+ mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::nodefaultsrc,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return;
+ }
+ src.AssignLiteral("about:blank");
+ principal = mOwnerContent->NodePrincipal();
+ csp = mOwnerContent->GetCsp();
+ }
+ }
+
+ Document* doc = mOwnerContent->OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ return;
+ }
+
+ nsIURI* base_uri = mOwnerContent->GetBaseURI();
+ auto encoding = doc->GetDocumentCharacterSet();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), src, encoding, base_uri);
+
+ // If the URI was malformed, try to recover by loading about:blank.
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ rv = NS_NewURI(getter_AddRefs(uri), u"about:blank"_ns, encoding, base_uri);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = LoadURI(uri, principal, csp, aOriginalSrc);
+ }
+
+ if (NS_FAILED(rv)) {
+ FireErrorEvent();
+ }
+}
+
+void nsFrameLoader::ConfigRemoteProcess(const nsACString& aRemoteType,
+ ContentParent* aContentParent) {
+ MOZ_DIAGNOSTIC_ASSERT(IsRemoteFrame(), "Must be a remote frame");
+ MOZ_DIAGNOSTIC_ASSERT(!mRemoteBrowser, "Must not have a browser yet");
+ MOZ_DIAGNOSTIC_ASSERT_IF(aContentParent,
+ aContentParent->GetRemoteType() == aRemoteType);
+
+ mRemoteType = aRemoteType;
+ mChildID = aContentParent ? aContentParent->ChildID() : 0;
+}
+
+void nsFrameLoader::FireErrorEvent() {
+ if (!mOwnerContent) {
+ return;
+ }
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(
+ mOwnerContent, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+}
+
+nsresult nsFrameLoader::LoadURI(nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp,
+ bool aOriginalSrc) {
+ if (!aURI) return NS_ERROR_INVALID_POINTER;
+ NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent);
+ MOZ_ASSERT(
+ aTriggeringPrincipal,
+ "Must have an explicit triggeringPrincipal to nsFrameLoader::LoadURI.");
+
+ mLoadingOriginalSrc = aOriginalSrc;
+
+ nsCOMPtr<Document> doc = mOwnerContent->OwnerDoc();
+
+ nsresult rv;
+ rv = CheckURILoad(aURI, aTriggeringPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mURIToLoad = aURI;
+ mTriggeringPrincipal = aTriggeringPrincipal;
+ mCsp = aCsp;
+ rv = doc->InitializeFrameLoader(this);
+ if (NS_FAILED(rv)) {
+ mURIToLoad = nullptr;
+ mTriggeringPrincipal = nullptr;
+ mCsp = nullptr;
+ }
+ return rv;
+}
+
+void nsFrameLoader::ResumeLoad(uint64_t aPendingSwitchID) {
+ Document* doc = mOwnerContent->OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ // Static doc shouldn't load sub-documents.
+ return;
+ }
+
+ if (NS_WARN_IF(mDestroyCalled || !mOwnerContent)) {
+ FireErrorEvent();
+ return;
+ }
+
+ mLoadingOriginalSrc = false;
+ mURIToLoad = nullptr;
+ mPendingSwitchID = aPendingSwitchID;
+ mTriggeringPrincipal = mOwnerContent->NodePrincipal();
+ mCsp = mOwnerContent->GetCsp();
+
+ nsresult rv = doc->InitializeFrameLoader(this);
+ if (NS_FAILED(rv)) {
+ mPendingSwitchID = 0;
+ mTriggeringPrincipal = nullptr;
+ mCsp = nullptr;
+
+ FireErrorEvent();
+ }
+}
+
+nsresult nsFrameLoader::ReallyStartLoading() {
+ nsresult rv = ReallyStartLoadingInternal();
+ if (NS_FAILED(rv)) {
+ FireErrorEvent();
+ }
+
+ return rv;
+}
+
+nsresult nsFrameLoader::ReallyStartLoadingInternal() {
+ NS_ENSURE_STATE((mURIToLoad || mPendingSwitchID) && mOwnerContent &&
+ mOwnerContent->IsInComposedDoc());
+
+ AUTO_PROFILER_LABEL("nsFrameLoader::ReallyStartLoadingInternal", OTHER);
+ RefPtr<nsDocShellLoadState> loadState;
+ if (!mPendingSwitchID) {
+ loadState = new nsDocShellLoadState(mURIToLoad);
+ loadState->SetOriginalFrameSrc(mLoadingOriginalSrc);
+
+ // The triggering principal could be null if the frame is loaded other
+ // than the src attribute, for example, the frame is sandboxed. In that
+ // case we use the principal of the owner content, which is needed to
+ // prevent XSS attaches on documents loaded in subframes.
+ if (mTriggeringPrincipal) {
+ loadState->SetTriggeringPrincipal(mTriggeringPrincipal);
+ } else {
+ loadState->SetTriggeringPrincipal(mOwnerContent->NodePrincipal());
+ }
+
+ // If we have an explicit CSP, we set it. If not, we only query it from
+ // the document in case there was no explicit triggeringPrincipal.
+ // Otherwise it's possible that the original triggeringPrincipal did not
+ // have a CSP which causes the CSP on the Principal and explicit CSP
+ // to be out of sync.
+ if (mCsp) {
+ loadState->SetCsp(mCsp);
+ } else if (!mTriggeringPrincipal) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp = mOwnerContent->GetCsp();
+ loadState->SetCsp(csp);
+ }
+
+ nsAutoString srcdoc;
+ bool isSrcdoc =
+ mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc, srcdoc);
+
+ if (isSrcdoc) {
+ loadState->SetSrcdocData(srcdoc);
+ loadState->SetBaseURI(mOwnerContent->GetBaseURI());
+ }
+
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mOwnerContent);
+ loadState->SetReferrerInfo(referrerInfo);
+
+ loadState->SetIsFromProcessingFrameAttributes();
+
+ // Default flags:
+ int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE;
+
+ // Flags for browser frame:
+ if (OwnerIsMozBrowserFrame()) {
+ flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ }
+ loadState->SetLoadFlags(flags);
+
+ loadState->SetFirstParty(false);
+
+ // If we're loading the default about:blank document in a <browser> element,
+ // prevent the load from causing a process switch by explicitly overriding
+ // remote type selection.
+ if (mPendingBrowsingContext->IsTopContent() &&
+ mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
+ NS_IsAboutBlank(mURIToLoad) &&
+ loadState->TriggeringPrincipal()->IsSystemPrincipal()) {
+ loadState->SetRemoteTypeOverride(mRemoteType);
+ }
+ }
+
+ if (IsRemoteFrame()) {
+ if (!EnsureRemoteBrowser()) {
+ NS_WARNING("Couldn't create child process for iframe.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mPendingSwitchID) {
+ mRemoteBrowser->ResumeLoad(mPendingSwitchID);
+ mPendingSwitchID = 0;
+ } else {
+ mRemoteBrowser->LoadURL(loadState);
+ }
+
+ if (!mRemoteBrowserShown) {
+ // This can fail if it's too early to show the frame, we will retry later.
+ Unused << ShowRemoteFrame(ScreenIntSize(0, 0));
+ }
+
+ return NS_OK;
+ }
+
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(GetDocShell(),
+ "MaybeCreateDocShell succeeded with a null docShell");
+
+ // If we have a pending switch, just resume our load.
+ if (mPendingSwitchID) {
+ bool tmpState = mNeedsAsyncDestroy;
+ mNeedsAsyncDestroy = true;
+ rv = GetDocShell()->ResumeRedirectedLoad(mPendingSwitchID, -1);
+ mNeedsAsyncDestroy = tmpState;
+ mPendingSwitchID = 0;
+ return rv;
+ }
+
+ // Just to be safe, recheck uri.
+ rv = CheckURILoad(mURIToLoad, mTriggeringPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLoadingOriginalSrc = false;
+
+ // Kick off the load...
+ bool tmpState = mNeedsAsyncDestroy;
+ mNeedsAsyncDestroy = true;
+
+ RefPtr<nsDocShell> docShell = GetDocShell();
+ rv = docShell->LoadURI(loadState, false);
+ mNeedsAsyncDestroy = tmpState;
+ mURIToLoad = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::CheckURILoad(nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal) {
+ // Check for security. The fun part is trying to figure out what principals
+ // to use. The way I figure it, if we're doing a LoadFrame() accidentally
+ // (eg someone created a frame/iframe node, we're being parsed, XUL iframes
+ // are being reframed, etc.) then we definitely want to use the node
+ // principal of mOwnerContent for security checks. If, on the other hand,
+ // someone's setting the src on our owner content, or created it via script,
+ // or whatever, then they can clearly access it... and we should still use
+ // the principal of mOwnerContent. I don't think that leads to privilege
+ // escalation, and it's reasonably guaranteed to not lead to XSS issues
+ // (since caller can already access mOwnerContent in this case). So just use
+ // the principal of mOwnerContent no matter what. If script wants to run
+ // things with its own permissions, which differ from those of mOwnerContent
+ // (which means the script is privileged in some way) it should set
+ // window.location instead.
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+
+ // Get our principal
+ nsIPrincipal* principal =
+ (aTriggeringPrincipal ? aTriggeringPrincipal
+ : mOwnerContent->NodePrincipal());
+
+ // Check if we are allowed to load absURL
+ nsresult rv = secMan->CheckLoadURIWithPrincipal(
+ principal, aURI, nsIScriptSecurityManager::STANDARD,
+ mOwnerContent->OwnerDoc()->InnerWindowID());
+ if (NS_FAILED(rv)) {
+ return rv; // We're not
+ }
+
+ // Bail out if this is an infinite recursion scenario
+ if (IsRemoteFrame()) {
+ return NS_OK;
+ }
+ return CheckForRecursiveLoad(aURI);
+}
+
+nsDocShell* nsFrameLoader::GetDocShell(ErrorResult& aRv) {
+ if (IsRemoteFrame()) {
+ return nullptr;
+ }
+
+ // If we have an owner, make sure we have a docshell and return
+ // that. If not, we're most likely in the middle of being torn down,
+ // then we just return null.
+ if (mOwnerContent) {
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ MOZ_ASSERT(GetDocShell(),
+ "MaybeCreateDocShell succeeded, but null docShell");
+ }
+
+ return GetDocShell();
+}
+
+static void SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
+ nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner,
+ EventTarget* aHandler) {
+ MOZ_ASSERT(aItem, "Must have item");
+
+ aItem->SetTreeOwner(aOwner);
+
+ int32_t childCount = 0;
+ aItem->GetInProcessChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ aItem->GetInProcessChildAt(i, getter_AddRefs(item));
+ if (aHandler) {
+ nsCOMPtr<nsIDocShell> shell(do_QueryInterface(item));
+ shell->SetChromeEventHandler(aHandler);
+ }
+ SetTreeOwnerAndChromeEventHandlerOnDocshellTree(item, aOwner, aHandler);
+ }
+}
+
+#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
+static bool CheckDocShellType(mozilla::dom::Element* aOwnerContent,
+ nsIDocShellTreeItem* aDocShell, nsAtom* aAtom) {
+ bool isContent = aOwnerContent->AttrValueIs(kNameSpaceID_None, aAtom,
+ nsGkAtoms::content, eIgnoreCase);
+
+ if (!isContent) {
+ nsCOMPtr<nsIMozBrowserFrame> mozbrowser =
+ aOwnerContent->GetAsMozBrowserFrame();
+ if (mozbrowser) {
+ mozbrowser->GetMozbrowser(&isContent);
+ }
+ }
+
+ if (isContent) {
+ return aDocShell->ItemType() == nsIDocShellTreeItem::typeContent;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ aDocShell->GetInProcessParent(getter_AddRefs(parent));
+
+ return parent && parent->ItemType() == aDocShell->ItemType();
+}
+#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
+
+/**
+ * Hook up a given TreeItem to its tree owner. aItem's type must have already
+ * been set, and it should already be part of the DocShellTree.
+ * @param aItem the treeitem we're working with
+ * @param aTreeOwner the relevant treeowner; might be null
+ */
+void nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
+ nsIDocShellTreeOwner* aOwner) {
+ MOZ_ASSERT(aItem, "Must have docshell treeitem");
+ MOZ_ASSERT(mOwnerContent, "Must have owning content");
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ CheckDocShellType(mOwnerContent, aItem, TypeAttrName(mOwnerContent)),
+ "Correct ItemType should be set when creating BrowsingContext");
+
+ if (mIsTopLevelContent) {
+ bool is_primary = mOwnerContent->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true, eIgnoreCase);
+ if (aOwner) {
+ mOwnerContent->AddMutationObserver(this);
+ mObservingOwnerContent = true;
+ aOwner->ContentShellAdded(aItem, is_primary);
+ }
+ }
+}
+
+static bool AllDescendantsOfType(BrowsingContext* aParent,
+ BrowsingContext::Type aType) {
+ for (auto& child : aParent->Children()) {
+ if (child->GetType() != aType || !AllDescendantsOfType(child, aType)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void nsFrameLoader::MaybeShowFrame() {
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ nsSubDocumentFrame* subDocFrame = do_QueryFrame(frame);
+ if (subDocFrame) {
+ subDocFrame->MaybeShowViewer();
+ }
+ }
+}
+
+static ScrollbarPreference GetScrollbarPreference(const Element* aOwner) {
+ if (!aOwner) {
+ return ScrollbarPreference::Auto;
+ }
+ const nsAttrValue* attrValue = aOwner->GetParsedAttr(nsGkAtoms::scrolling);
+ return nsGenericHTMLFrameElement::MapScrollingAttribute(attrValue);
+}
+
+static CSSIntSize GetMarginAttributes(const Element* aOwner) {
+ CSSIntSize result(-1, -1);
+ auto* content = nsGenericHTMLElement::FromNodeOrNull(aOwner);
+ if (!content) {
+ return result;
+ }
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::marginwidth);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ result.width = attr->GetIntegerValue();
+ }
+ attr = content->GetParsedAttr(nsGkAtoms::marginheight);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ result.height = attr->GetIntegerValue();
+ }
+ return result;
+}
+
+bool nsFrameLoader::Show(nsSubDocumentFrame* frame) {
+ if (mInShow) {
+ return false;
+ }
+ mInShow = true;
+
+ auto resetInShow = mozilla::MakeScopeExit([&] { mInShow = false; });
+
+ ScreenIntSize size = frame->GetSubdocumentSize();
+ if (IsRemoteFrame()) {
+ // FIXME(bug 1588791): For fission iframes we need to pass down the
+ // scrollbar preferences.
+ return ShowRemoteFrame(size, frame);
+ }
+
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ nsDocShell* ds = GetDocShell();
+ MOZ_ASSERT(ds, "MaybeCreateDocShell succeeded, but null docShell");
+ if (!ds) {
+ return false;
+ }
+
+ ds->SetScrollbarPreference(GetScrollbarPreference(mOwnerContent));
+ const bool marginsChanged =
+ ds->UpdateFrameMargins(GetMarginAttributes(mOwnerContent));
+ if (PresShell* presShell = ds->GetPresShell()) {
+ // Ensure root scroll frame is reflowed in case margins have changed
+ if (marginsChanged) {
+ if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+ presShell->FrameNeedsReflow(rootScrollFrame, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ return true;
+ }
+
+ nsView* view = frame->EnsureInnerView();
+ if (!view) return false;
+
+ RefPtr<nsDocShell> baseWindow = GetDocShell();
+ baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0, size.width,
+ size.height);
+ baseWindow->SetVisibility(true);
+ NS_ENSURE_TRUE(GetDocShell(), false);
+
+ // Trigger editor re-initialization if midas is turned on in the
+ // sub-document. This shouldn't be necessary, but given the way our
+ // editor works, it is. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=284245
+ if (RefPtr<PresShell> presShell = GetDocShell()->GetPresShell()) {
+ Document* doc = presShell->GetDocument();
+ nsHTMLDocument* htmlDoc =
+ doc && doc->IsHTMLOrXHTML() ? doc->AsHTMLDocument() : nullptr;
+
+ if (htmlDoc) {
+ nsAutoString designMode;
+ htmlDoc->GetDesignMode(designMode);
+
+ if (designMode.EqualsLiteral("on")) {
+ // Hold on to the editor object to let the document reattach to the
+ // same editor object, instead of creating a new one.
+ RefPtr<HTMLEditor> htmlEditor = GetDocShell()->GetHTMLEditor();
+ Unused << htmlEditor;
+ htmlDoc->SetDesignMode(u"off"_ns, Nothing(), IgnoreErrors());
+
+ htmlDoc->SetDesignMode(u"on"_ns, Nothing(), IgnoreErrors());
+ } else {
+ // Re-initialize the presentation for contenteditable documents
+ bool editable = false, hasEditingSession = false;
+ GetDocShell()->GetEditable(&editable);
+ GetDocShell()->GetHasEditingSession(&hasEditingSession);
+ RefPtr<HTMLEditor> htmlEditor = GetDocShell()->GetHTMLEditor();
+ if (editable && hasEditingSession && htmlEditor) {
+ htmlEditor->PostCreate();
+ }
+ }
+ }
+ }
+
+ mInShow = false;
+ if (mHideCalled) {
+ mHideCalled = false;
+ Hide();
+ return false;
+ }
+ return true;
+}
+
+void nsFrameLoader::MarginsChanged() {
+ // We assume that the margins are always zero for remote frames.
+ if (IsRemoteFrame()) {
+ return;
+ }
+
+ nsDocShell* docShell = GetDocShell();
+ // If there's no docshell, we're probably not up and running yet.
+ // nsFrameLoader::Show() will take care of setting the right
+ // margins.
+ if (!docShell) {
+ return;
+ }
+
+ if (!docShell->UpdateFrameMargins(GetMarginAttributes(mOwnerContent))) {
+ return;
+ }
+
+ // There's a cached property declaration block
+ // that needs to be updated
+ if (Document* doc = docShell->GetDocument()) {
+ for (nsINode* cur = doc; cur; cur = cur->GetNextNode()) {
+ if (cur->IsHTMLElement(nsGkAtoms::body)) {
+ static_cast<HTMLBodyElement*>(cur)->ClearMappedServoStyle();
+ }
+ }
+ }
+
+ // Trigger a restyle if there's a prescontext
+ // FIXME: This could do something much less expensive.
+ if (nsPresContext* presContext = docShell->GetPresContext()) {
+ // rebuild, because now the same nsMappedAttributes* will produce
+ // a different style
+ presContext->RebuildAllStyleData(nsChangeHint(0),
+ RestyleHint::RestyleSubtree());
+ }
+}
+
+bool nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size,
+ nsSubDocumentFrame* aFrame) {
+ AUTO_PROFILER_LABEL("nsFrameLoader::ShowRemoteFrame", OTHER);
+ NS_ASSERTION(IsRemoteFrame(),
+ "ShowRemote only makes sense on remote frames.");
+
+ if (!EnsureRemoteBrowser()) {
+ NS_ERROR("Couldn't create child process.");
+ return false;
+ }
+
+ // FIXME/bug 589337: Show()/Hide() is pretty expensive for
+ // cross-process layers; need to figure out what behavior we really
+ // want here. For now, hack.
+ if (!mRemoteBrowserShown) {
+ if (!mOwnerContent || !mOwnerContent->GetComposedDoc()) {
+ return false;
+ }
+
+ // We never want to host remote frameloaders in simple popups, like menus.
+ nsIWidget* widget = nsContentUtils::WidgetForContent(mOwnerContent);
+ if (!widget || static_cast<nsBaseWidget*>(widget)->IsSmallPopup()) {
+ return false;
+ }
+
+ if (BrowserHost* bh = mRemoteBrowser->AsBrowserHost()) {
+ RefPtr<BrowsingContext> bc = bh->GetBrowsingContext()->Top();
+
+ // Set to the current activation of the window.
+ bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
+ }
+
+ nsCOMPtr<nsISupports> container = mOwnerContent->OwnerDoc()->GetContainer();
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ nsSizeMode sizeMode =
+ mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal;
+ OwnerShowInfo info(size, GetScrollbarPreference(mOwnerContent), sizeMode);
+ if (!mRemoteBrowser->Show(info)) {
+ return false;
+ }
+ mRemoteBrowserShown = true;
+
+ // This notification doesn't apply to fission, apparently.
+ if (!GetBrowserBridgeChild()) {
+ if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) {
+ os->NotifyObservers(ToSupports(this), "remote-browser-shown", nullptr);
+ }
+ ProcessPriorityManager::RemoteBrowserFrameShown(this);
+ }
+ } else {
+ nsIntRect dimensions;
+ NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false);
+
+ // Don't show remote iframe if we are waiting for the completion of reflow.
+ if (!aFrame || !aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ mRemoteBrowser->UpdateDimensions(dimensions, size);
+ }
+ }
+
+ return true;
+}
+
+void nsFrameLoader::Hide() {
+ if (mHideCalled) {
+ return;
+ }
+ if (mInShow) {
+ mHideCalled = true;
+ return;
+ }
+
+ if (!GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ GetDocShell()->GetContentViewer(getter_AddRefs(contentViewer));
+ if (contentViewer) contentViewer->SetSticky(false);
+
+ RefPtr<nsDocShell> baseWin = GetDocShell();
+ baseWin->SetVisibility(false);
+ baseWin->SetParentWidget(nullptr);
+}
+
+void nsFrameLoader::ForceLayoutIfNecessary() {
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (!frame) {
+ return;
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ if (!presContext) {
+ return;
+ }
+
+ // Only force the layout flush if the frameloader hasn't ever been
+ // run through layout.
+ if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ if (RefPtr<PresShell> presShell = presContext->GetPresShell()) {
+ presShell->FlushPendingNotifications(FlushType::Layout);
+ }
+ }
+}
+
+nsresult nsFrameLoader::SwapWithOtherRemoteLoader(
+ nsFrameLoader* aOther, nsFrameLoaderOwner* aThisOwner,
+ nsFrameLoaderOwner* aOtherOwner) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef DEBUG
+ RefPtr<nsFrameLoader> first = aThisOwner->GetFrameLoader();
+ RefPtr<nsFrameLoader> second = aOtherOwner->GetFrameLoader();
+ MOZ_ASSERT(first == this, "aThisOwner must own this");
+ MOZ_ASSERT(second == aOther, "aOtherOwner must own aOther");
+#endif
+
+ Element* ourContent = mOwnerContent;
+ Element* otherContent = aOther->mOwnerContent;
+
+ if (!ourContent || !otherContent) {
+ // Can't handle this
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Make sure there are no same-origin issues
+ bool equal;
+ nsresult rv = ourContent->NodePrincipal()->Equals(
+ otherContent->NodePrincipal(), &equal);
+ if (NS_FAILED(rv) || !equal) {
+ // Security problems loom. Just bail on it all
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ Document* ourDoc = ourContent->GetComposedDoc();
+ Document* otherDoc = otherContent->GetComposedDoc();
+ if (!ourDoc || !otherDoc) {
+ // Again, how odd, given that we had docshells
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ PresShell* ourPresShell = ourDoc->GetPresShell();
+ PresShell* otherPresShell = otherDoc->GetPresShell();
+ if (!ourPresShell || !otherPresShell) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ auto* browserParent = GetBrowserParent();
+ auto* otherBrowserParent = aOther->GetBrowserParent();
+
+ if (!browserParent || !otherBrowserParent) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<BrowsingContext> ourBc = browserParent->GetBrowsingContext();
+ RefPtr<BrowsingContext> otherBc = otherBrowserParent->GetBrowsingContext();
+
+ // When we swap docShells, maybe we have to deal with a new page created just
+ // for this operation. In this case, the browser code should already have set
+ // the correct userContextId attribute value in the owning element, but our
+ // docShell, that has been created way before) doesn't know that that
+ // happened.
+ // This is the reason why now we must retrieve the correct value from the
+ // usercontextid attribute before comparing our originAttributes with the
+ // other one.
+ OriginAttributes ourOriginAttributes = ourBc->OriginAttributesRef();
+ rv = PopulateOriginContextIdsFromAttributes(ourOriginAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes otherOriginAttributes = otherBc->OriginAttributesRef();
+ rv = aOther->PopulateOriginContextIdsFromAttributes(otherOriginAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!ourOriginAttributes.EqualsIgnoringFPD(otherOriginAttributes)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ bool ourHasHistory =
+ mIsTopLevelContent && ourContent->IsXULElement(nsGkAtoms::browser) &&
+ !ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
+ bool otherHasHistory =
+ aOther->mIsTopLevelContent &&
+ otherContent->IsXULElement(nsGkAtoms::browser) &&
+ !otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
+ if (ourHasHistory != otherHasHistory) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (mInSwap || aOther->mInSwap) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ mInSwap = aOther->mInSwap = true;
+
+ // NOTE(emilio): This doesn't have to flush because the caller does already.
+ nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
+ nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
+ if (!ourFrame || !otherFrame) {
+ mInSwap = aOther->mInSwap = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
+ if (!ourFrameFrame) {
+ mInSwap = aOther->mInSwap = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
+ if (NS_FAILED(rv)) {
+ mInSwap = aOther->mInSwap = false;
+ return rv;
+ }
+
+ nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow =
+ otherBrowserParent->GetBrowserDOMWindow();
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
+ browserParent->GetBrowserDOMWindow();
+
+ if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Destroy browser frame scripts for content leaving a frame with browser API
+ if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
+ aOther->DestroyBrowserFrameScripts();
+ }
+
+ otherBrowserParent->SetBrowserDOMWindow(browserDOMWindow);
+ browserParent->SetBrowserDOMWindow(otherBrowserDOMWindow);
+
+ MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
+ aOther->MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
+
+ if (mozilla::BFCacheInParent() && XRE_IsParentProcess()) {
+ // nsFrameLoaders in session history can't be moved to another owner since
+ // there are no corresponging message managers on which swap can be done.
+ // See the line mMessageManager.swap(aOther->mMessageManager); below.
+ auto evict = [](nsFrameLoader* aFrameLoader) {
+ if (BrowsingContext* bc =
+ aFrameLoader->GetMaybePendingBrowsingContext()) {
+ nsCOMPtr<nsISHistory> shistory = bc->Canonical()->GetSessionHistory();
+ if (shistory) {
+ shistory->EvictAllContentViewers();
+ }
+ }
+ };
+ evict(this);
+ evict(aOther);
+ }
+
+ SetOwnerContent(otherContent);
+ aOther->SetOwnerContent(ourContent);
+
+ browserParent->SetOwnerElement(otherContent);
+ otherBrowserParent->SetOwnerElement(ourContent);
+
+ // Update window activation state for the swapped owner content.
+ bool ourActive = otherBc->GetIsActiveBrowserWindow();
+ bool otherActive = ourBc->GetIsActiveBrowserWindow();
+ if (ourBc->IsTop()) {
+ ourBc->SetIsActiveBrowserWindow(otherActive);
+ }
+ if (otherBc->IsTop()) {
+ otherBc->SetIsActiveBrowserWindow(ourActive);
+ }
+
+ MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
+ aOther->MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
+
+ RefPtr<nsFrameMessageManager> ourMessageManager = mMessageManager;
+ RefPtr<nsFrameMessageManager> otherMessageManager = aOther->mMessageManager;
+ // Swap and setup things in parent message managers.
+ if (ourMessageManager) {
+ ourMessageManager->SetCallback(aOther);
+ }
+ if (otherMessageManager) {
+ otherMessageManager->SetCallback(this);
+ }
+ mMessageManager.swap(aOther->mMessageManager);
+
+ // XXXsmaug what should be done to JSWindowActorParent objects when swapping
+ // frameloaders? Currently they leak very easily, bug 1697918.
+
+ // Perform the actual swap of the internal refptrs. We keep a strong reference
+ // to ourselves to make sure we don't die while we overwrite our reference to
+ // ourself.
+ RefPtr<nsFrameLoader> kungFuDeathGrip(this);
+ aThisOwner->SetFrameLoader(aOther);
+ aOtherOwner->SetFrameLoader(kungFuDeathGrip);
+
+ ourFrameFrame->EndSwapDocShells(otherFrame);
+
+ ourPresShell->BackingScaleFactorChanged();
+ otherPresShell->BackingScaleFactorChanged();
+
+ // Initialize browser API if needed now that owner content has changed.
+ InitializeBrowserAPI();
+ aOther->InitializeBrowserAPI();
+
+ mInSwap = aOther->mInSwap = false;
+
+ // Send an updated tab context since owner content type may have changed.
+ MutableTabContext ourContext;
+ rv = GetNewTabContext(&ourContext);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MutableTabContext otherContext;
+ rv = aOther->GetNewTabContext(&otherContext);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Unused << browserParent->SendSwappedWithOtherRemoteLoader(
+ ourContext.AsIPCTabContext());
+ Unused << otherBrowserParent->SendSwappedWithOtherRemoteLoader(
+ otherContext.AsIPCTabContext());
+ return NS_OK;
+}
+
+class MOZ_RAII AutoResetInFrameSwap final {
+ public:
+ AutoResetInFrameSwap(nsFrameLoader* aThisFrameLoader,
+ nsFrameLoader* aOtherFrameLoader,
+ nsDocShell* aThisDocShell, nsDocShell* aOtherDocShell,
+ EventTarget* aThisEventTarget,
+ EventTarget* aOtherEventTarget)
+ : mThisFrameLoader(aThisFrameLoader),
+ mOtherFrameLoader(aOtherFrameLoader),
+ mThisDocShell(aThisDocShell),
+ mOtherDocShell(aOtherDocShell),
+ mThisEventTarget(aThisEventTarget),
+ mOtherEventTarget(aOtherEventTarget) {
+ mThisFrameLoader->mInSwap = true;
+ mOtherFrameLoader->mInSwap = true;
+ mThisDocShell->SetInFrameSwap(true);
+ mOtherDocShell->SetInFrameSwap(true);
+
+ // Fire pageshow events on still-loading pages, and then fire pagehide
+ // events. Note that we do NOT fire these in the normal way, but just fire
+ // them on the chrome event handlers.
+ nsContentUtils::FirePageShowEventForFrameLoaderSwap(
+ mThisDocShell, mThisEventTarget, false);
+ nsContentUtils::FirePageShowEventForFrameLoaderSwap(
+ mOtherDocShell, mOtherEventTarget, false);
+ nsContentUtils::FirePageHideEventForFrameLoaderSwap(mThisDocShell,
+ mThisEventTarget);
+ nsContentUtils::FirePageHideEventForFrameLoaderSwap(mOtherDocShell,
+ mOtherEventTarget);
+ }
+
+ ~AutoResetInFrameSwap() {
+ nsContentUtils::FirePageShowEventForFrameLoaderSwap(mThisDocShell,
+ mThisEventTarget, true);
+ nsContentUtils::FirePageShowEventForFrameLoaderSwap(
+ mOtherDocShell, mOtherEventTarget, true);
+
+ mThisFrameLoader->mInSwap = false;
+ mOtherFrameLoader->mInSwap = false;
+ mThisDocShell->SetInFrameSwap(false);
+ mOtherDocShell->SetInFrameSwap(false);
+
+ // This is needed to get visibility state right in cases when we swapped a
+ // visible tab (foreground in visible window) with a non-visible tab.
+ if (RefPtr<Document> doc = mThisDocShell->GetDocument()) {
+ doc->UpdateVisibilityState();
+ }
+ if (RefPtr<Document> doc = mOtherDocShell->GetDocument()) {
+ doc->UpdateVisibilityState();
+ }
+ }
+
+ private:
+ RefPtr<nsFrameLoader> mThisFrameLoader;
+ RefPtr<nsFrameLoader> mOtherFrameLoader;
+ RefPtr<nsDocShell> mThisDocShell;
+ RefPtr<nsDocShell> mOtherDocShell;
+ nsCOMPtr<EventTarget> mThisEventTarget;
+ nsCOMPtr<EventTarget> mOtherEventTarget;
+};
+
+nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
+ nsFrameLoaderOwner* aThisOwner,
+ nsFrameLoaderOwner* aOtherOwner) {
+#ifdef DEBUG
+ RefPtr<nsFrameLoader> first = aThisOwner->GetFrameLoader();
+ RefPtr<nsFrameLoader> second = aOtherOwner->GetFrameLoader();
+ MOZ_ASSERT(first == this, "aThisOwner must own this");
+ MOZ_ASSERT(second == aOther, "aOtherOwner must own aOther");
+#endif
+
+ NS_ENSURE_STATE(!mInShow && !aOther->mInShow);
+
+ if (IsRemoteFrame() != aOther->IsRemoteFrame()) {
+ NS_WARNING(
+ "Swapping remote and non-remote frames is not currently supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<Element> ourContent = mOwnerContent;
+ RefPtr<Element> otherContent = aOther->mOwnerContent;
+ if (!ourContent || !otherContent) {
+ // Can't handle this
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsIFrame* ourFrame = ourContent->GetPrimaryFrame(FlushType::Frames);
+ nsIFrame* otherFrame = otherContent->GetPrimaryFrame(FlushType::Frames);
+ if (!ourFrame || !otherFrame) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Ensure the flushes above haven't changed all the world.
+ if (ourContent != mOwnerContent || otherContent != aOther->mOwnerContent) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ bool ourHasSrcdoc = ourContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+ bool otherHasSrcdoc =
+ otherContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+ if (ourHasSrcdoc || otherHasSrcdoc) {
+ // Ignore this case entirely for now, since we support XUL <-> HTML swapping
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ bool ourFullscreenAllowed = ourContent->IsXULElement() ||
+ (OwnerIsMozBrowserFrame() &&
+ ourContent->HasAttr(nsGkAtoms::allowfullscreen));
+ bool otherFullscreenAllowed =
+ otherContent->IsXULElement() ||
+ (aOther->OwnerIsMozBrowserFrame() &&
+ otherContent->HasAttr(nsGkAtoms::allowfullscreen));
+ if (ourFullscreenAllowed != otherFullscreenAllowed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsILoadContext* ourLoadContext = ourContent->OwnerDoc()->GetLoadContext();
+ nsILoadContext* otherLoadContext = otherContent->OwnerDoc()->GetLoadContext();
+ MOZ_ASSERT(ourLoadContext && otherLoadContext,
+ "Swapping frames within dead documents?");
+ if (ourLoadContext->UseRemoteTabs() != otherLoadContext->UseRemoteTabs()) {
+ NS_WARNING("Can't swap between e10s and non-e10s windows");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (ourLoadContext->UseRemoteSubframes() !=
+ otherLoadContext->UseRemoteSubframes()) {
+ NS_WARNING("Can't swap between fission and non-fission windows");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Divert to a separate path for the remaining steps in the remote case
+ if (IsRemoteFrame()) {
+ MOZ_ASSERT(aOther->IsRemoteFrame());
+ return SwapWithOtherRemoteLoader(aOther, aThisOwner, aOtherOwner);
+ }
+
+ // Make sure there are no same-origin issues
+ bool equal;
+ nsresult rv = ourContent->NodePrincipal()->Equals(
+ otherContent->NodePrincipal(), &equal);
+ if (NS_FAILED(rv) || !equal) {
+ // Security problems loom. Just bail on it all
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ RefPtr<nsDocShell> ourDocshell = GetExistingDocShell();
+ RefPtr<nsDocShell> otherDocshell = aOther->GetExistingDocShell();
+ if (!ourDocshell || !otherDocshell) {
+ // How odd
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // To avoid having to mess with session history, avoid swapping
+ // frameloaders that don't correspond to root same-type docshells,
+ // unless both roots have session history disabled.
+ nsCOMPtr<nsIDocShellTreeItem> ourRootTreeItem, otherRootTreeItem;
+ ourDocshell->GetInProcessSameTypeRootTreeItem(
+ getter_AddRefs(ourRootTreeItem));
+ otherDocshell->GetInProcessSameTypeRootTreeItem(
+ getter_AddRefs(otherRootTreeItem));
+ nsCOMPtr<nsIWebNavigation> ourRootWebnav = do_QueryInterface(ourRootTreeItem);
+ nsCOMPtr<nsIWebNavigation> otherRootWebnav =
+ do_QueryInterface(otherRootTreeItem);
+
+ if (!ourRootWebnav || !otherRootWebnav) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<ChildSHistory> ourHistory = ourRootWebnav->GetSessionHistory();
+ RefPtr<ChildSHistory> otherHistory = otherRootWebnav->GetSessionHistory();
+
+ if ((ourRootTreeItem != ourDocshell || otherRootTreeItem != otherDocshell) &&
+ (ourHistory || otherHistory)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<BrowsingContext> ourBc = ourDocshell->GetBrowsingContext();
+ RefPtr<BrowsingContext> otherBc = otherDocshell->GetBrowsingContext();
+
+ // Also make sure that the two BrowsingContexts are the same type. Otherwise
+ // swapping is certainly not safe. If this needs to be changed then
+ // the code below needs to be audited as it assumes identical types.
+ if (ourBc->GetType() != otherBc->GetType()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // We ensure that BCs are either both top frames or both subframes.
+ if (ourBc->IsTop() != otherBc->IsTop()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // One more twist here. Setting up the right treeowners in a heterogeneous
+ // tree is a bit of a pain. So make sure that if `ourBc->GetType()` is not
+ // nsIDocShellTreeItem::typeContent then all of our descendants are the same
+ // type as us.
+ if (!ourBc->IsContent() &&
+ (!AllDescendantsOfType(ourBc, ourBc->GetType()) ||
+ !AllDescendantsOfType(otherBc, otherBc->GetType()))) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Save off the tree owners, frame elements, chrome event handlers, and
+ // docshell and document parents before doing anything else.
+ nsCOMPtr<nsIDocShellTreeOwner> ourOwner, otherOwner;
+ ourDocshell->GetTreeOwner(getter_AddRefs(ourOwner));
+ otherDocshell->GetTreeOwner(getter_AddRefs(otherOwner));
+ // Note: it's OK to have null treeowners.
+
+ nsCOMPtr<nsIDocShellTreeItem> ourParentItem, otherParentItem;
+ ourDocshell->GetInProcessParent(getter_AddRefs(ourParentItem));
+ otherDocshell->GetInProcessParent(getter_AddRefs(otherParentItem));
+ if (!ourParentItem || !otherParentItem) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow = ourDocshell->GetWindow();
+ nsCOMPtr<nsPIDOMWindowOuter> otherWindow = otherDocshell->GetWindow();
+
+ nsCOMPtr<Element> ourFrameElement = ourWindow->GetFrameElementInternal();
+ nsCOMPtr<Element> otherFrameElement = otherWindow->GetFrameElementInternal();
+
+ nsCOMPtr<EventTarget> ourChromeEventHandler =
+ ourWindow->GetChromeEventHandler();
+ nsCOMPtr<EventTarget> otherChromeEventHandler =
+ otherWindow->GetChromeEventHandler();
+
+ nsCOMPtr<EventTarget> ourEventTarget = ourWindow->GetParentTarget();
+ nsCOMPtr<EventTarget> otherEventTarget = otherWindow->GetParentTarget();
+
+ NS_ASSERTION(SameCOMIdentity(ourFrameElement, ourContent) &&
+ SameCOMIdentity(otherFrameElement, otherContent) &&
+ SameCOMIdentity(ourChromeEventHandler, ourContent) &&
+ SameCOMIdentity(otherChromeEventHandler, otherContent),
+ "How did that happen, exactly?");
+
+ nsCOMPtr<Document> ourChildDocument = ourWindow->GetExtantDoc();
+ nsCOMPtr<Document> otherChildDocument = otherWindow->GetExtantDoc();
+ if (!ourChildDocument || !otherChildDocument) {
+ // This shouldn't be happening
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<Document> ourParentDocument =
+ ourChildDocument->GetInProcessParentDocument();
+ nsCOMPtr<Document> otherParentDocument =
+ otherChildDocument->GetInProcessParentDocument();
+
+ // Make sure to swap docshells between the two frames.
+ Document* ourDoc = ourContent->GetComposedDoc();
+ Document* otherDoc = otherContent->GetComposedDoc();
+ if (!ourDoc || !otherDoc) {
+ // Again, how odd, given that we had docshells
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_ASSERTION(ourDoc == ourParentDocument, "Unexpected parent document");
+ NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document");
+
+ PresShell* ourPresShell = ourDoc->GetPresShell();
+ PresShell* otherPresShell = otherDoc->GetPresShell();
+ if (!ourPresShell || !otherPresShell) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // When we swap docShells, maybe we have to deal with a new page created just
+ // for this operation. In this case, the browser code should already have set
+ // the correct userContextId attribute value in the owning element, but our
+ // docShell, that has been created way before) doesn't know that that
+ // happened.
+ // This is the reason why now we must retrieve the correct value from the
+ // usercontextid attribute before comparing our originAttributes with the
+ // other one.
+ OriginAttributes ourOriginAttributes = ourDocshell->GetOriginAttributes();
+ rv = PopulateOriginContextIdsFromAttributes(ourOriginAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes otherOriginAttributes = otherDocshell->GetOriginAttributes();
+ rv = aOther->PopulateOriginContextIdsFromAttributes(otherOriginAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (ourOriginAttributes != otherOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (mInSwap || aOther->mInSwap) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ AutoResetInFrameSwap autoFrameSwap(this, aOther, ourDocshell, otherDocshell,
+ ourEventTarget, otherEventTarget);
+
+ nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
+ if (!ourFrameFrame) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // OK. First begin to swap the docshells in the two nsIFrames
+ rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Destroy browser frame scripts for content leaving a frame with browser API
+ if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
+ aOther->DestroyBrowserFrameScripts();
+ }
+
+ // Now move the docshells to the right docshell trees. Note that this
+ // resets their treeowners to null.
+ ourParentItem->RemoveChild(ourDocshell);
+ otherParentItem->RemoveChild(otherDocshell);
+ if (ourBc->IsContent()) {
+ ourOwner->ContentShellRemoved(ourDocshell);
+ otherOwner->ContentShellRemoved(otherDocshell);
+ }
+
+ ourParentItem->AddChild(otherDocshell);
+ otherParentItem->AddChild(ourDocshell);
+
+ // Restore the correct chrome event handlers.
+ ourDocshell->SetChromeEventHandler(otherChromeEventHandler);
+ otherDocshell->SetChromeEventHandler(ourChromeEventHandler);
+ // Restore the correct treeowners
+ // (and also chrome event handlers for content frames only).
+ SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
+ ourDocshell, otherOwner,
+ ourBc->IsContent() ? otherChromeEventHandler.get() : nullptr);
+ SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
+ otherDocshell, ourOwner,
+ ourBc->IsContent() ? ourChromeEventHandler.get() : nullptr);
+
+ // Switch the owner content before we start calling AddTreeItemToTreeOwner.
+ // Note that we rely on this to deal with setting mObservingOwnerContent to
+ // false and calling RemoveMutationObserver as needed.
+ SetOwnerContent(otherContent);
+ aOther->SetOwnerContent(ourContent);
+
+ AddTreeItemToTreeOwner(ourDocshell, otherOwner);
+ aOther->AddTreeItemToTreeOwner(otherDocshell, ourOwner);
+
+ // SetSubDocumentFor nulls out parent documents on the old child doc if a
+ // new non-null document is passed in, so just go ahead and remove both
+ // kids before reinserting in the parent subdoc maps, to avoid
+ // complications.
+ ourParentDocument->SetSubDocumentFor(ourContent, nullptr);
+ otherParentDocument->SetSubDocumentFor(otherContent, nullptr);
+ ourParentDocument->SetSubDocumentFor(ourContent, otherChildDocument);
+ otherParentDocument->SetSubDocumentFor(otherContent, ourChildDocument);
+
+ ourWindow->SetFrameElementInternal(otherFrameElement);
+ otherWindow->SetFrameElementInternal(ourFrameElement);
+
+ RefPtr<nsFrameMessageManager> ourMessageManager = mMessageManager;
+ RefPtr<nsFrameMessageManager> otherMessageManager = aOther->mMessageManager;
+ // Swap pointers in child message managers.
+ if (mChildMessageManager) {
+ InProcessBrowserChildMessageManager* browserChild = mChildMessageManager;
+ browserChild->SetOwner(otherContent);
+ browserChild->SetChromeMessageManager(otherMessageManager);
+ }
+ if (aOther->mChildMessageManager) {
+ InProcessBrowserChildMessageManager* otherBrowserChild =
+ aOther->mChildMessageManager;
+ otherBrowserChild->SetOwner(ourContent);
+ otherBrowserChild->SetChromeMessageManager(ourMessageManager);
+ }
+ // Swap and setup things in parent message managers.
+ if (mMessageManager) {
+ mMessageManager->SetCallback(aOther);
+ }
+ if (aOther->mMessageManager) {
+ aOther->mMessageManager->SetCallback(this);
+ }
+ mMessageManager.swap(aOther->mMessageManager);
+
+ // Perform the actual swap of the internal refptrs. We keep a strong reference
+ // to ourselves to make sure we don't die while we overwrite our reference to
+ // ourself.
+ RefPtr<nsFrameLoader> kungFuDeathGrip(this);
+ aThisOwner->SetFrameLoader(aOther);
+ aOtherOwner->SetFrameLoader(kungFuDeathGrip);
+
+ // Drop any cached content viewers in the two session histories.
+ if (ourHistory) {
+ ourHistory->EvictLocalContentViewers();
+ }
+ if (otherHistory) {
+ otherHistory->EvictLocalContentViewers();
+ }
+
+ NS_ASSERTION(ourFrame == ourContent->GetPrimaryFrame() &&
+ otherFrame == otherContent->GetPrimaryFrame(),
+ "changed primary frame");
+
+ ourFrameFrame->EndSwapDocShells(otherFrame);
+
+ // If the content being swapped came from windows on two screens with
+ // incompatible backing resolution (e.g. dragging a tab between windows on
+ // hi-dpi and low-dpi screens), it will have style data that is based on
+ // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their
+ // backing scale factor may have changed. (Bug 822266)
+ ourFrame->PresShell()->BackingScaleFactorChanged();
+ otherFrame->PresShell()->BackingScaleFactorChanged();
+
+ // Initialize browser API if needed now that owner content has changed
+ InitializeBrowserAPI();
+ aOther->InitializeBrowserAPI();
+
+ return NS_OK;
+}
+
+void nsFrameLoader::Destroy(bool aForProcessSwitch) {
+ StartDestroy(aForProcessSwitch);
+}
+
+class nsFrameLoaderDestroyRunnable : public Runnable {
+ enum DestroyPhase {
+ // See the implementation of Run for an explanation of these phases.
+ eDestroyDocShell,
+ eWaitForUnloadMessage,
+ eDestroyComplete
+ };
+
+ RefPtr<nsFrameLoader> mFrameLoader;
+ DestroyPhase mPhase;
+
+ public:
+ explicit nsFrameLoaderDestroyRunnable(nsFrameLoader* aFrameLoader)
+ : mozilla::Runnable("nsFrameLoaderDestroyRunnable"),
+ mFrameLoader(aFrameLoader),
+ mPhase(eDestroyDocShell) {}
+
+ NS_IMETHOD Run() override;
+};
+
+void nsFrameLoader::StartDestroy(bool aForProcessSwitch) {
+ // nsFrameLoader::StartDestroy is called just before the frameloader is
+ // detached from the <browser> element. Destruction continues in phases via
+ // the nsFrameLoaderDestroyRunnable.
+
+ if (mDestroyCalled) {
+ return;
+ }
+ mDestroyCalled = true;
+
+ // Request a full tab state flush if the tab is closing.
+ //
+ // XXX If we find that we need to do Session Store cleanup for the frameloader
+ // that's going away, we should unconditionally do the flush here, but include
+ // the |aForProcessSwitch| flag in the completion notification.
+ if (!aForProcessSwitch) {
+ RequestFinalTabStateFlush();
+ }
+
+ // After this point, we return an error when trying to send a message using
+ // the message manager on the frame.
+ if (mMessageManager) {
+ mMessageManager->Close();
+ }
+
+ // Retain references to the <browser> element and the frameloader in case we
+ // receive any messages from the message manager on the frame. These
+ // references are dropped in DestroyComplete.
+ if (mChildMessageManager || mRemoteBrowser) {
+ mOwnerContentStrong = mOwnerContent;
+ if (auto* browserParent = GetBrowserParent()) {
+ browserParent->CacheFrameLoader(this);
+ }
+ if (mChildMessageManager) {
+ mChildMessageManager->CacheFrameLoader(this);
+ }
+ }
+
+ // If the BrowserParent has installed any event listeners on the window, this
+ // is its last chance to remove them while we're still in the document.
+ if (auto* browserParent = GetBrowserParent()) {
+ browserParent->RemoveWindowListeners();
+ }
+
+ nsCOMPtr<Document> doc;
+ bool dynamicSubframeRemoval = false;
+ if (mOwnerContent) {
+ doc = mOwnerContent->OwnerDoc();
+ dynamicSubframeRemoval = !aForProcessSwitch &&
+ mPendingBrowsingContext->IsSubframe() &&
+ !doc->InUnlinkOrDeletion();
+ doc->SetSubDocumentFor(mOwnerContent, nullptr);
+ MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
+
+ nsCOMPtr<nsFrameLoaderOwner> owner = do_QueryInterface(mOwnerContent);
+ owner->FrameLoaderDestroying(this);
+ SetOwnerContent(nullptr);
+ }
+
+ // Seems like this is a dynamic frame removal.
+ if (dynamicSubframeRemoval) {
+ BrowsingContext* browsingContext = GetExtantBrowsingContext();
+ if (browsingContext) {
+ RefPtr<ChildSHistory> childSHistory =
+ browsingContext->Top()->GetChildSessionHistory();
+ if (childSHistory) {
+ if (mozilla::SessionHistoryInParent()) {
+ uint32_t addedEntries = 0;
+ browsingContext->PreOrderWalk([&addedEntries](BrowsingContext* aBC) {
+ // The initial load doesn't increase history length.
+ addedEntries += aBC->GetHistoryEntryCount() - 1;
+ });
+
+ nsID changeID = {};
+ if (addedEntries > 0) {
+ ChildSHistory* shistory =
+ browsingContext->Top()->GetChildSessionHistory();
+ if (shistory) {
+ changeID = shistory->AddPendingHistoryChange(0, -addedEntries);
+ }
+ }
+ browsingContext->RemoveFromSessionHistory(changeID);
+ } else {
+ AutoTArray<nsID, 16> ids({browsingContext->GetHistoryID()});
+ childSHistory->LegacySHistory()->RemoveEntries(
+ ids, childSHistory->Index());
+ }
+ }
+ }
+ }
+
+ // Let the tree owner know we're gone.
+ if (mIsTopLevelContent) {
+ if (GetDocShell()) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetDocShell()->GetInProcessParent(getter_AddRefs(parentItem));
+ nsCOMPtr<nsIDocShellTreeOwner> owner = do_GetInterface(parentItem);
+ if (owner) {
+ owner->ContentShellRemoved(GetDocShell());
+ }
+ }
+ }
+
+ // Let our window know that we are gone
+ if (GetDocShell()) {
+ nsCOMPtr<nsPIDOMWindowOuter> win_private(GetDocShell()->GetWindow());
+ if (win_private) {
+ win_private->SetFrameElementInternal(nullptr);
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> destroyRunnable =
+ new nsFrameLoaderDestroyRunnable(this);
+ if (mNeedsAsyncDestroy || !doc ||
+ NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) {
+ NS_DispatchToCurrentThread(destroyRunnable);
+ }
+}
+
+nsresult nsFrameLoaderDestroyRunnable::Run() {
+ switch (mPhase) {
+ case eDestroyDocShell:
+ mFrameLoader->DestroyDocShell();
+
+ // In the out-of-process case, BrowserParent will eventually call
+ // DestroyComplete once it receives a __delete__ message from the child.
+ // In the in-process case, or if the BrowserParent was already destroyed,
+ // we dispatch a series of runnables to ensure that DestroyComplete gets
+ // called at the right time. The frame loader is kept alive by
+ // mFrameLoader during this time.
+ if (!mFrameLoader->GetRemoteBrowser() ||
+ !mFrameLoader->GetRemoteBrowser()->CanRecv()) {
+ // When the docshell is destroyed, NotifyWindowIDDestroyed is called to
+ // asynchronously notify {outer,inner}-window-destroyed via a runnable.
+ // We don't want DestroyComplete to run until after those runnables have
+ // run. Since we're enqueueing ourselves after the window-destroyed
+ // runnables are enqueued, we're guaranteed to run after.
+ mPhase = eWaitForUnloadMessage;
+ NS_DispatchToCurrentThread(this);
+ }
+ break;
+
+ case eWaitForUnloadMessage:
+ // The *-window-destroyed observers have finished running at this
+ // point. However, it's possible that a *-window-destroyed observer might
+ // have sent a message using the message manager. These messages might not
+ // have been processed yet. So we enqueue ourselves again to ensure that
+ // DestroyComplete runs after all messages sent by *-window-destroyed
+ // observers have been processed.
+ mPhase = eDestroyComplete;
+ NS_DispatchToCurrentThread(this);
+ break;
+
+ case eDestroyComplete:
+ // Now that all messages sent by unload listeners and window destroyed
+ // observers have been processed, we disconnect the message manager and
+ // finish destruction.
+ mFrameLoader->DestroyComplete();
+ break;
+ }
+
+ return NS_OK;
+}
+
+void nsFrameLoader::DestroyDocShell() {
+ // This code runs after the frameloader has been detached from the <browser>
+ // element. We postpone this work because we may not be allowed to run
+ // script at that time.
+
+ // Ask the BrowserChild to fire the frame script "unload" event, destroy its
+ // docshell, and finally destroy the PBrowser actor. This eventually leads to
+ // nsFrameLoader::DestroyComplete being called.
+ if (mRemoteBrowser) {
+ mRemoteBrowser->DestroyStart();
+ }
+
+ // Fire the "unload" event if we're in-process.
+ if (mChildMessageManager) {
+ mChildMessageManager->FireUnloadEvent();
+ }
+
+ if (mSessionStoreChild) {
+ mSessionStoreChild->Stop();
+ mSessionStoreChild = nullptr;
+ }
+
+ // Destroy the docshell.
+ if (GetDocShell()) {
+ GetDocShell()->Destroy();
+ }
+
+ if (!mWillChangeProcess && mPendingBrowsingContext &&
+ mPendingBrowsingContext->EverAttached()) {
+ mPendingBrowsingContext->Detach();
+ }
+
+ mPendingBrowsingContext = nullptr;
+ mDocShell = nullptr;
+
+ if (mChildMessageManager) {
+ // Stop handling events in the in-process frame script.
+ mChildMessageManager->DisconnectEventListeners();
+ }
+}
+
+void nsFrameLoader::DestroyComplete() {
+ // We get here, as part of StartDestroy, after the docshell has been destroyed
+ // and all message manager messages sent during docshell destruction have been
+ // dispatched. We also get here if the child process crashes. In the latter
+ // case, StartDestroy might not have been called.
+
+ // Drop the strong references created in StartDestroy.
+ if (mChildMessageManager || mRemoteBrowser) {
+ mOwnerContentStrong = nullptr;
+ if (auto* browserParent = GetBrowserParent()) {
+ browserParent->CacheFrameLoader(nullptr);
+ }
+ if (mChildMessageManager) {
+ mChildMessageManager->CacheFrameLoader(nullptr);
+ }
+ }
+
+ // Call BrowserParent::Destroy if we haven't already (in case of a crash).
+ if (mRemoteBrowser) {
+ mRemoteBrowser->DestroyComplete();
+ mRemoteBrowser = nullptr;
+ }
+
+ if (mMessageManager) {
+ mMessageManager->Disconnect();
+ }
+
+ if (mChildMessageManager) {
+ mChildMessageManager->Disconnect();
+ }
+
+ mMessageManager = nullptr;
+ mChildMessageManager = nullptr;
+}
+
+void nsFrameLoader::SetOwnerContent(Element* aContent) {
+ if (mObservingOwnerContent) {
+ mObservingOwnerContent = false;
+ mOwnerContent->RemoveMutationObserver(this);
+ }
+
+ // XXXBFCache Need to update also all the non-current FrameLoaders in the
+ // owner when moving a FrameLoader.
+ // This temporary setup doesn't crash, but behaves badly with bfcached docs.
+ if (RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(mOwnerContent)) {
+ owner->DetachFrameLoader(this);
+ }
+
+ mOwnerContent = aContent;
+
+ if (RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(mOwnerContent)) {
+ owner->AttachFrameLoader(this);
+
+#ifdef NIGHTLY_BUILD
+ if (mozilla::BFCacheInParent() && XRE_IsParentProcess()) {
+ if (BrowsingContext* bc = GetMaybePendingBrowsingContext()) {
+ nsISHistory* shistory = bc->Canonical()->GetSessionHistory();
+ if (shistory) {
+ uint32_t count = shistory->GetCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry);
+ MOZ_RELEASE_ASSERT(!she || !she->GetFrameLoader());
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ if (mSessionStoreChild && mOwnerContent) {
+ // mOwnerContent will only be null when the frame loader is being destroyed,
+ // so the session store listener will be destroyed along with it.
+ // XXX(farre): This probably needs to update the cache. See bug 1698497.
+ mSessionStoreChild->SetOwnerContent(mOwnerContent);
+ }
+
+ if (RefPtr<BrowsingContext> browsingContext = GetExtantBrowsingContext()) {
+ browsingContext->SetEmbedderElement(mOwnerContent);
+ }
+
+ if (mSessionStoreChild) {
+ // UpdateEventTargets will requery its browser contexts for event
+ // targets, so this call needs to happen after the call to
+ // SetEmbedderElement above.
+ mSessionStoreChild->UpdateEventTargets();
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JS::Rooted<JSObject*> wrapper(jsapi.cx(), GetWrapper());
+ if (wrapper) {
+ JSAutoRealm ar(jsapi.cx(), wrapper);
+ IgnoredErrorResult rv;
+ UpdateReflectorGlobal(jsapi.cx(), wrapper, rv);
+ Unused << NS_WARN_IF(rv.Failed());
+ }
+}
+
+bool nsFrameLoader::OwnerIsMozBrowserFrame() {
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ return browserFrame ? browserFrame->GetReallyIsBrowser() : false;
+}
+
+nsIContent* nsFrameLoader::GetParentObject() const { return mOwnerContent; }
+
+void nsFrameLoader::AssertSafeToInit() {
+ MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsSafeToRunScript() ||
+ mOwnerContent->OwnerDoc()->IsStaticDocument(),
+ "FrameLoader should never be initialized during "
+ "document update or reflow!");
+}
+
+nsresult nsFrameLoader::MaybeCreateDocShell() {
+ if (GetDocShell()) {
+ return NS_OK;
+ }
+ if (IsRemoteFrame()) {
+ return NS_OK;
+ }
+ NS_ENSURE_STATE(!mDestroyCalled);
+
+ AssertSafeToInit();
+
+ // Get our parent docshell off the document of mOwnerContent
+ // XXXbz this is such a total hack.... We really need to have a
+ // better setup for doing this.
+ Document* doc = mOwnerContent->OwnerDoc();
+
+ MOZ_RELEASE_ASSERT(!doc->IsResourceDoc(), "We shouldn't even exist");
+
+ // If we've already tried to initialize and failed, don't try again.
+ if (mInitialized) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mInitialized = true;
+
+ // Check if the document still has a window since it is possible for an
+ // iframe to be inserted and cause the creation of the docshell in a
+ // partially unloaded document (see Bug 1305237 comment 127).
+ if (!doc->IsStaticDocument() &&
+ (!doc->GetWindow() || !mOwnerContent->IsInComposedDoc())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!doc->IsActive()) {
+ // Don't allow subframe loads in non-active documents.
+ // (See bug 610571 comment 5.)
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<nsDocShell> parentDocShell = nsDocShell::Cast(doc->GetDocShell());
+ if (NS_WARN_IF(!parentDocShell)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (doc->GetWindowContext()->IsDiscarded() ||
+ parentDocShell->GetBrowsingContext()->IsDiscarded()) {
+ // Don't allow subframe loads in discarded contexts.
+ // (see bug 1652085, bug 1656854)
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!EnsureBrowsingContextAttached()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPendingBrowsingContext->SetEmbedderElement(mOwnerContent);
+
+ // nsDocShell::Create will attach itself to the passed browsing
+ // context inside of nsDocShell::Create
+ RefPtr<nsDocShell> docShell = nsDocShell::Create(mPendingBrowsingContext);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ mDocShell = docShell;
+
+ mPendingBrowsingContext->Embed();
+
+ InvokeBrowsingContextReadyCallback();
+
+ mIsTopLevelContent = mPendingBrowsingContext->IsTopContent();
+
+ if (mIsTopLevelContent) {
+ // Manually add ourselves to our parent's docshell, as BrowsingContext won't
+ // have done this for us.
+ //
+ // XXX(nika): Consider removing the DocShellTree in the future, for
+ // consistency between local and remote frames..
+ parentDocShell->AddChild(docShell);
+ }
+
+ // Now that we are part of the DocShellTree, attach our DocShell to our
+ // parent's TreeOwner.
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ parentDocShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ AddTreeItemToTreeOwner(docShell, parentTreeOwner);
+
+ // Make sure all nsDocShells have links back to the content element in the
+ // nearest enclosing chrome shell.
+ RefPtr<EventTarget> chromeEventHandler;
+ bool parentIsContent = parentDocShell->GetBrowsingContext()->IsContent();
+ if (parentIsContent) {
+ // Our parent shell is a content shell. Get the chrome event handler from it
+ // and use that for our shell as well.
+ parentDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
+ } else {
+ // Our parent shell is a chrome shell. It is therefore our nearest enclosing
+ // chrome shell.
+ chromeEventHandler = mOwnerContent;
+ }
+
+ docShell->SetChromeEventHandler(chromeEventHandler);
+
+ // This is nasty, this code (the docShell->GetWindow() below)
+ // *must* come *after* the above call to
+ // docShell->SetChromeEventHandler() for the global window to get
+ // the right chrome event handler.
+
+ // Tell the window about the frame that hosts it.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow = docShell->GetWindow();
+ if (NS_WARN_IF(!newWindow)) {
+ // Do not call Destroy() here. See bug 472312.
+ NS_WARNING("Something wrong when creating the docshell for a frameloader!");
+ return NS_ERROR_FAILURE;
+ }
+
+ newWindow->SetFrameElementInternal(mOwnerContent);
+
+ // Allow scripts to close the docshell if specified.
+ if (mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
+ mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::allowscriptstoclose,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsGlobalWindowOuter::Cast(newWindow)->AllowScriptsToClose();
+ }
+
+ if (!docShell->Initialize()) {
+ // Do not call Destroy() here. See bug 472312.
+ NS_WARNING("Something wrong when creating the docshell for a frameloader!");
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_STATE(mOwnerContent);
+
+ // If we are an in-process browser, we want to set up our session history.
+ if (mIsTopLevelContent && mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
+ !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory)) {
+ // XXX(nika): Set this up more explicitly?
+ mPendingBrowsingContext->InitSessionHistory();
+ }
+
+ // Apply sandbox flags even if our owner is not an iframe, as this copies
+ // flags from our owning content's owning document.
+ // Note: ApplySandboxFlags should be called after docShell->SetIsFrame
+ // because we need to get the correct presentation URL in ApplySandboxFlags.
+ uint32_t sandboxFlags = 0;
+ HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(mOwnerContent);
+ if (iframe) {
+ sandboxFlags = iframe->GetSandboxFlags();
+ }
+ ApplySandboxFlags(sandboxFlags);
+ MOZ_ALWAYS_SUCCEEDS(mPendingBrowsingContext->SetInitialSandboxFlags(
+ mPendingBrowsingContext->GetSandboxFlags()));
+
+ if (OwnerIsMozBrowserFrame()) {
+ // For inproc frames, set the docshell properties.
+ nsAutoString name;
+ if (mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
+ docShell->SetName(name);
+ }
+ }
+
+ ReallyLoadFrameScripts();
+ InitializeBrowserAPI();
+
+ // Previously we would forcibly create the initial about:blank document for
+ // in-process content frames from a frame script which eagerly loaded in
+ // every tab. This lead to other frontend components growing dependencies on
+ // the initial about:blank document being created eagerly. See bug 1471327
+ // for details.
+ //
+ // We also eagerly create the initial about:blank document for remote loads
+ // separately when initializing BrowserChild.
+ if (mIsTopLevelContent &&
+ mPendingBrowsingContext->GetMessageManagerGroup() == u"browsers"_ns) {
+ Unused << mDocShell->GetDocument();
+ }
+
+ return NS_OK;
+}
+
+void nsFrameLoader::GetURL(nsString& aURI, nsIPrincipal** aTriggeringPrincipal,
+ nsIContentSecurityPolicy** aCsp) {
+ aURI.Truncate();
+ // Within this function we default to using the NodePrincipal as the
+ // triggeringPrincipal and the CSP of the document.
+ // Expanded Principals however override the CSP of the document, hence
+ // if frame->GetSrcTriggeringPrincipal() returns a valid principal, we
+ // have to query the CSP from that Principal.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mOwnerContent->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp = mOwnerContent->GetCsp();
+
+ if (mOwnerContent->IsHTMLElement(nsGkAtoms::object)) {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, aURI);
+ } else {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, aURI);
+ if (RefPtr<nsGenericHTMLFrameElement> frame =
+ do_QueryObject(mOwnerContent)) {
+ nsCOMPtr<nsIPrincipal> srcPrincipal = frame->GetSrcTriggeringPrincipal();
+ if (srcPrincipal) {
+ triggeringPrincipal = srcPrincipal;
+ nsCOMPtr<nsIExpandedPrincipal> ep =
+ do_QueryInterface(triggeringPrincipal);
+ if (ep) {
+ csp = ep->GetCsp();
+ }
+ }
+ }
+ }
+ triggeringPrincipal.forget(aTriggeringPrincipal);
+ csp.forget(aCsp);
+}
+
+nsresult nsFrameLoader::CheckForRecursiveLoad(nsIURI* aURI) {
+ MOZ_ASSERT(!IsRemoteFrame(),
+ "Shouldn't call CheckForRecursiveLoad on remote frames.");
+
+ mDepthTooGreat = false;
+ RefPtr<BrowsingContext> parentBC(
+ mOwnerContent->OwnerDoc()->GetBrowsingContext());
+ NS_ENSURE_STATE(parentBC);
+
+ if (!parentBC->IsContent()) {
+ return NS_OK;
+ }
+
+ // Bug 8065: Don't exceed some maximum depth in content frames
+ // (MAX_DEPTH_CONTENT_FRAMES)
+ int32_t depth = 0;
+ for (BrowsingContext* bc = parentBC; bc; bc = bc->GetParent()) {
+ ++depth;
+ if (depth >= MAX_DEPTH_CONTENT_FRAMES) {
+ mDepthTooGreat = true;
+ NS_WARNING("Too many nested content frames so giving up");
+
+ return NS_ERROR_UNEXPECTED; // Too deep, give up! (silently?)
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::GetWindowDimensions(nsIntRect& aRect) {
+ if (!mOwnerContent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Need to get outer window position here
+ Document* doc = mOwnerContent->GetComposedDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_RELEASE_ASSERT(!doc->IsResourceDoc(), "We shouldn't even exist");
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem(win->GetDocShell());
+ if (!parentAsItem) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentOwner;
+ if (NS_FAILED(parentAsItem->GetTreeOwner(getter_AddRefs(parentOwner))) ||
+ !parentOwner) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_GetInterface(parentOwner));
+ treeOwnerAsWin->GetPosition(&aRect.x, &aRect.y);
+ treeOwnerAsWin->GetSize(&aRect.width, &aRect.height);
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame* aIFrame) {
+ if (IsRemoteFrame()) {
+ if (mRemoteBrowser) {
+ ScreenIntSize size = aIFrame->GetSubdocumentSize();
+ // If we were not able to show remote frame before, we should probably
+ // retry now to send correct showInfo.
+ if (!mRemoteBrowserShown) {
+ ShowRemoteFrame(size, aIFrame);
+ }
+ nsIntRect dimensions;
+ NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), NS_ERROR_FAILURE);
+ mLazySize = size;
+ mRemoteBrowser->UpdateDimensions(dimensions, size);
+ }
+ return NS_OK;
+ }
+ UpdateBaseWindowPositionAndSize(aIFrame);
+ return NS_OK;
+}
+
+void nsFrameLoader::PropagateIsUnderHiddenEmbedderElement(
+ bool aIsUnderHiddenEmbedderElement) {
+ bool isUnderHiddenEmbedderElement = true;
+ if (Document* ownerDoc = GetOwnerDoc()) {
+ if (PresShell* presShell = ownerDoc->GetPresShell()) {
+ isUnderHiddenEmbedderElement = presShell->IsUnderHiddenEmbedderElement();
+ }
+ }
+
+ isUnderHiddenEmbedderElement |= aIsUnderHiddenEmbedderElement;
+
+ BrowsingContext* browsingContext = GetExtantBrowsingContext();
+ if (browsingContext && browsingContext->IsUnderHiddenEmbedderElement() !=
+ isUnderHiddenEmbedderElement) {
+ Unused << browsingContext->SetIsUnderHiddenEmbedderElement(
+ isUnderHiddenEmbedderElement);
+ }
+}
+
+void nsFrameLoader::UpdateRemoteStyle(
+ mozilla::StyleImageRendering aImageRendering) {
+ MOZ_DIAGNOSTIC_ASSERT(IsRemoteFrame());
+
+ if (auto* browserBridgeChild = GetBrowserBridgeChild()) {
+ browserBridgeChild->SendUpdateRemoteStyle(aImageRendering);
+ }
+}
+
+void nsFrameLoader::UpdateBaseWindowPositionAndSize(
+ nsSubDocumentFrame* aIFrame) {
+ nsCOMPtr<nsIBaseWindow> baseWindow = GetDocShell(IgnoreErrors());
+
+ // resize the sub document
+ if (baseWindow) {
+ int32_t x = 0;
+ int32_t y = 0;
+
+ AutoWeakFrame weakFrame(aIFrame);
+
+ baseWindow->GetPosition(&x, &y);
+
+ if (!weakFrame.IsAlive()) {
+ // GetPosition() killed us
+ return;
+ }
+
+ ScreenIntSize size = aIFrame->GetSubdocumentSize();
+ mLazySize = size;
+
+ baseWindow->SetPositionAndSize(x, y, size.width, size.height,
+ nsIBaseWindow::eDelayResize);
+ }
+}
+
+uint32_t nsFrameLoader::LazyWidth() const {
+ uint32_t lazyWidth = mLazySize.width;
+
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ lazyWidth = frame->PresContext()->DevPixelsToIntCSSPixels(lazyWidth);
+ }
+
+ return lazyWidth;
+}
+
+uint32_t nsFrameLoader::LazyHeight() const {
+ uint32_t lazyHeight = mLazySize.height;
+
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ lazyHeight = frame->PresContext()->DevPixelsToIntCSSPixels(lazyHeight);
+ }
+
+ return lazyHeight;
+}
+
+bool nsFrameLoader::EnsureRemoteBrowser() {
+ MOZ_ASSERT(IsRemoteFrame());
+ return mRemoteBrowser || TryRemoteBrowser();
+}
+
+bool nsFrameLoader::TryRemoteBrowserInternal() {
+ NS_ASSERTION(!mRemoteBrowser,
+ "TryRemoteBrowser called with a remote browser already?");
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
+ "Remote subframes should only be created using the "
+ "`CanonicalBrowsingContext::ChangeRemoteness` API");
+
+ AssertSafeToInit();
+
+ if (!mOwnerContent) {
+ return false;
+ }
+
+ // XXXsmaug Per spec (2014/08/21) frameloader should not work in case the
+ // element isn't in document, only in shadow dom, but that will change
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365#c0
+ RefPtr<Document> doc = mOwnerContent->GetComposedDoc();
+ if (!doc) {
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(!doc->IsResourceDoc(), "We shouldn't even exist");
+
+ // Graphics initialization code relies on having a frame for the
+ // remote browser case, as we can be inside a popup, which is a different
+ // widget.
+ //
+ // FIXME: Ideally this should be unconditional, but we skip if for <iframe
+ // mozbrowser> because the old RDM ui depends on current behavior, and the
+ // mozbrowser frame code is scheduled for deletion, see bug 1574886.
+ if (!OwnerIsMozBrowserFrame() && !mOwnerContent->GetPrimaryFrame()) {
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+
+ // The flush could have initialized us.
+ if (mRemoteBrowser) {
+ return true;
+ }
+
+ // If we've already tried to initialize and failed, don't try again.
+ if (mInitialized) {
+ return false;
+ }
+ mInitialized = true;
+
+ // Ensure the world hasn't changed that much as a result of that.
+ if (!mOwnerContent || mOwnerContent->OwnerDoc() != doc ||
+ !mOwnerContent->IsInComposedDoc()) {
+ return false;
+ }
+
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(mOwnerContent)) {
+ RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
+ if (fl != this) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Got TryRemoteBrowserInternal but mOwnerContent already has a "
+ "different frameloader?");
+ return false;
+ }
+ }
+
+ if (!doc->IsActive()) {
+ // Don't allow subframe loads in non-active documents.
+ // (See bug 610571 comment 5.)
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWin = doc->GetWindow();
+ if (!parentWin) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> parentDocShell = parentWin->GetDocShell();
+ if (!parentDocShell) {
+ return false;
+ }
+
+ if (!EnsureBrowsingContextAttached()) {
+ return false;
+ }
+
+ if (mPendingBrowsingContext->IsTop()) {
+ mPendingBrowsingContext->InitSessionHistory();
+ }
+
+ // <iframe mozbrowser> gets to skip these checks.
+ // iframes for JS plugins also get to skip these checks. We control the URL
+ // that gets loaded, but the load is triggered from the document containing
+ // the plugin.
+ // out of process iframes also get to skip this check.
+ if (!OwnerIsMozBrowserFrame() && !XRE_IsContentProcess()) {
+ if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ // Allow three exceptions to this rule :
+ // - about:addons so it can load remote extension options pages
+ // - about:preferences (in Thunderbird only) so it can load remote
+ // extension options pages for FileLink providers
+ // - DevTools webext panels if DevTools is loaded in a content frame
+ // - DevTools Network Monitor, which uses content frame for HTML request
+ // previews
+ // - Chrome mochitests can also do this.
+ //
+ // Note that the new frame's message manager will not be a child of the
+ // chrome window message manager, and, the values of window.top and
+ // window.parent will be different than they would be for a non-remote
+ // frame.
+ nsIURI* parentURI = parentWin->GetDocumentURI();
+ if (!parentURI) {
+ return false;
+ }
+
+ nsAutoCString specIgnoringRef;
+ if (NS_FAILED(parentURI->GetSpecIgnoringRef(specIgnoringRef))) {
+ return false;
+ }
+
+ const bool allowed = [&] {
+ const nsLiteralCString kAllowedURIs[] = {
+ "about:addons"_ns,
+ "chrome://mozapps/content/extensions/aboutaddons.html"_ns,
+#ifdef MOZ_THUNDERBIRD
+ "about:3pane"_ns,
+ "about:message"_ns,
+ "about:preferences"_ns,
+#endif
+ "chrome://browser/content/webext-panels.xhtml"_ns,
+ "chrome://devtools/content/netmonitor/index.html"_ns,
+ "chrome://devtools/content/webconsole/index.html"_ns,
+ };
+
+ for (const auto& allowedURI : kAllowedURIs) {
+ if (specIgnoringRef.Equals(allowedURI)) {
+ return true;
+ }
+ }
+
+ if (xpc::IsInAutomation() &&
+ StringBeginsWith(specIgnoringRef,
+ "chrome://mochitests/content/chrome/"_ns)) {
+ return true;
+ }
+ return false;
+ }();
+
+ if (!allowed) {
+ NS_WARNING(
+ nsPrintfCString("Forbidden remote frame from content docshell %s",
+ specIgnoringRef.get())
+ .get());
+ return false;
+ }
+ }
+
+ if (!mOwnerContent->IsXULElement()) {
+ return false;
+ }
+
+ if (!mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::content, eIgnoreCase)) {
+ return false;
+ }
+ }
+
+ uint32_t chromeFlags = 0;
+ nsCOMPtr<nsIDocShellTreeOwner> parentOwner;
+ if (NS_FAILED(parentDocShell->GetTreeOwner(getter_AddRefs(parentOwner))) ||
+ !parentOwner) {
+ return false;
+ }
+ nsCOMPtr<nsIAppWindow> window(do_GetInterface(parentOwner));
+ if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
+ return false;
+ }
+
+ AUTO_PROFILER_LABEL("nsFrameLoader::TryRemoteBrowser:Create", OTHER);
+
+ MutableTabContext context;
+ nsresult rv = GetNewTabContext(&context);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ RefPtr<Element> ownerElement = mOwnerContent;
+
+ RefPtr<BrowserParent> nextRemoteBrowser =
+ mOpenWindowInfo ? mOpenWindowInfo->GetNextRemoteBrowser() : nullptr;
+ if (nextRemoteBrowser) {
+ mRemoteBrowser = new BrowserHost(nextRemoteBrowser);
+ if (nextRemoteBrowser->GetOwnerElement()) {
+ MOZ_ASSERT_UNREACHABLE("Shouldn't have an owner element before");
+ return false;
+ }
+ nextRemoteBrowser->SetOwnerElement(ownerElement);
+ } else {
+ RefPtr<ContentParent> contentParent;
+ if (mChildID != 0) {
+ ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+ if (!cpm) {
+ return false;
+ }
+ contentParent = cpm->GetContentProcessById(ContentParentId(mChildID));
+ }
+ mRemoteBrowser =
+ ContentParent::CreateBrowser(context, ownerElement, mRemoteType,
+ mPendingBrowsingContext, contentParent);
+ }
+ if (!mRemoteBrowser) {
+ return false;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mPendingBrowsingContext ==
+ mRemoteBrowser->GetBrowsingContext());
+
+ mRemoteBrowser->GetBrowsingContext()->Embed();
+ InvokeBrowsingContextReadyCallback();
+
+ // Grab the reference to the actor
+ RefPtr<BrowserParent> browserParent = GetBrowserParent();
+
+ MOZ_ASSERT(browserParent->CanSend(), "BrowserParent cannot send?");
+
+ // We no longer need the remoteType attribute on the frame element.
+ // The remoteType can be queried by asking the message manager instead.
+ ownerElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType, false);
+
+ // Now that browserParent is set, we can initialize graphics
+ browserParent->InitRendering();
+
+ MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
+
+ mChildID = browserParent->Manager()->ChildID();
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ parentDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+ nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(rootWin);
+
+ if (rootChromeWin) {
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin;
+ rootChromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin));
+ browserParent->SetBrowserDOMWindow(browserDOMWin);
+ }
+
+ // For xul:browsers, update some settings based on attributes:
+ if (mOwnerContent->IsXULElement()) {
+ // Send down the name of the browser through browserParent if it is set.
+ nsAutoString frameName;
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, frameName);
+ if (nsContentUtils::IsOverridingWindowName(frameName)) {
+ MOZ_ALWAYS_SUCCEEDS(mPendingBrowsingContext->SetName(frameName));
+ }
+ // Allow scripts to close the window if the browser specified so:
+ if (mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::allowscriptstoclose,
+ nsGkAtoms::_true, eCaseMatters)) {
+ Unused << browserParent->SendAllowScriptsToClose();
+ }
+ }
+
+ ReallyLoadFrameScripts();
+ InitializeBrowserAPI();
+
+ return true;
+}
+
+bool nsFrameLoader::TryRemoteBrowser() {
+ // Creating remote browsers may result in creating new processes, but during
+ // parent shutdown that would add just noise, so better bail out.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return false;
+ }
+
+ // Try to create the internal remote browser.
+ if (TryRemoteBrowserInternal()) {
+ return true;
+ }
+
+ // Check if we should report a browser-crashed error because the browser
+ // failed to start.
+ if (XRE_IsParentProcess() && mOwnerContent && mOwnerContent->IsXULElement()) {
+ MaybeNotifyCrashed(nullptr, ContentParentId(), nullptr);
+ }
+
+ return false;
+}
+
+nsIFrame* nsFrameLoader::GetPrimaryFrameOfOwningContent() const {
+ return mOwnerContent ? mOwnerContent->GetPrimaryFrame() : nullptr;
+}
+
+Document* nsFrameLoader::GetOwnerDoc() const {
+ return mOwnerContent ? mOwnerContent->OwnerDoc() : nullptr;
+}
+
+bool nsFrameLoader::IsRemoteFrame() {
+ if (mIsRemoteFrame) {
+ MOZ_ASSERT(!GetDocShell(), "Found a remote frame with a DocShell");
+ return true;
+ }
+ return false;
+}
+
+RemoteBrowser* nsFrameLoader::GetRemoteBrowser() const {
+ return mRemoteBrowser;
+}
+
+BrowserParent* nsFrameLoader::GetBrowserParent() const {
+ if (!mRemoteBrowser) {
+ return nullptr;
+ }
+ RefPtr<BrowserHost> browserHost = mRemoteBrowser->AsBrowserHost();
+ if (!browserHost) {
+ return nullptr;
+ }
+ return browserHost->GetActor();
+}
+
+BrowserBridgeChild* nsFrameLoader::GetBrowserBridgeChild() const {
+ if (!mRemoteBrowser) {
+ return nullptr;
+ }
+ RefPtr<BrowserBridgeHost> browserBridgeHost =
+ mRemoteBrowser->AsBrowserBridgeHost();
+ if (!browserBridgeHost) {
+ return nullptr;
+ }
+ return browserBridgeHost->GetActor();
+}
+
+mozilla::layers::LayersId nsFrameLoader::GetLayersId() const {
+ MOZ_ASSERT(mIsRemoteFrame);
+ return mRemoteBrowser->GetLayersId();
+}
+
+nsresult nsFrameLoader::DoRemoteStaticClone(nsFrameLoader* aStaticCloneOf,
+ nsIPrintSettings* aPrintSettings) {
+ MOZ_ASSERT(aStaticCloneOf->IsRemoteFrame());
+ MOZ_ASSERT(aPrintSettings);
+ auto* cc = ContentChild::GetSingleton();
+ if (!cc) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ // TODO: Could possibly be implemented without too much effort.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ BrowsingContext* bcToClone = aStaticCloneOf->GetBrowsingContext();
+ if (NS_WARN_IF(!bcToClone)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ BrowsingContext* bc = GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(bc);
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (NS_WARN_IF(!printSettingsSvc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ embedding::PrintData printData;
+ nsresult rv =
+ printSettingsSvc->SerializeToPrintData(aPrintSettings, &printData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ cc->SendCloneDocumentTreeInto(bcToClone, bc, printData);
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::FinishStaticClone(
+ nsFrameLoader* aStaticCloneOf, nsIPrintSettings* aPrintSettings,
+ bool* aOutHasInProcessPrintCallbacks) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !nsContentUtils::IsSafeToRunScript(),
+ "A script blocker should be on the stack while FinishStaticClone is run");
+
+ // NOTE: We don't check `aStaticCloneOf->IsDead()` here, as the nsFrameLoader
+ // which we're a static clone of may be in the process of being destroyed. It
+ // won't be fully destroyed when `FinishStaticClone` is called, as a script
+ // blocker on our caller's stack is preventing it from becoming finalized.
+ //
+ // This is quite fragile, but is quite difficult to work around without
+ // getting print-preview to stop re-cloning and replacing the previewed
+ // document when changing layout.
+ if (NS_WARN_IF(IsDead())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (aStaticCloneOf->IsRemoteFrame()) {
+ return DoRemoteStaticClone(aStaticCloneOf, aPrintSettings);
+ }
+
+ nsIDocShell* origDocShell = aStaticCloneOf->GetDocShell();
+ NS_ENSURE_STATE(origDocShell);
+
+ nsCOMPtr<Document> doc = origDocShell->GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ MaybeCreateDocShell();
+ RefPtr<nsDocShell> docShell = GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ nsCOMPtr<Document> kungFuDeathGrip = docShell->GetDocument();
+ Unused << kungFuDeathGrip;
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ docShell->GetContentViewer(getter_AddRefs(viewer));
+ NS_ENSURE_STATE(viewer);
+
+ nsCOMPtr<Document> clonedDoc = doc->CreateStaticClone(
+ docShell, viewer, aPrintSettings, aOutHasInProcessPrintCallbacks);
+
+ return NS_OK;
+}
+
+bool nsFrameLoader::DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) {
+ if (auto* browserParent = GetBrowserParent()) {
+ return browserParent->SendLoadRemoteScript(aURL, aRunInGlobalScope);
+ }
+ RefPtr<InProcessBrowserChildMessageManager> browserChild =
+ GetBrowserChildMessageManager();
+ if (browserChild) {
+ browserChild->LoadFrameScript(aURL, aRunInGlobalScope);
+ }
+ return true;
+}
+
+class nsAsyncMessageToChild : public nsSameProcessAsyncMessageBase,
+ public Runnable {
+ public:
+ explicit nsAsyncMessageToChild(nsFrameLoader* aFrameLoader)
+ : nsSameProcessAsyncMessageBase(),
+ mozilla::Runnable("nsAsyncMessageToChild"),
+ mFrameLoader(aFrameLoader) {}
+
+ NS_IMETHOD Run() override {
+ InProcessBrowserChildMessageManager* browserChild =
+ mFrameLoader->mChildMessageManager;
+ // Since bug 1126089, messages can arrive even when the docShell is
+ // destroyed. Here we make sure that those messages are not delivered.
+ if (browserChild && browserChild->GetInnerManager() &&
+ mFrameLoader->GetExistingDocShell()) {
+ JS::Rooted<JSObject*> kungFuDeathGrip(dom::RootingCx(),
+ browserChild->GetWrapper());
+ ReceiveMessage(static_cast<EventTarget*>(browserChild), mFrameLoader,
+ browserChild->GetInnerManager());
+ }
+ return NS_OK;
+ }
+ RefPtr<nsFrameLoader> mFrameLoader;
+};
+
+nsresult nsFrameLoader::DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) {
+ auto* browserParent = GetBrowserParent();
+ if (browserParent) {
+ ClonedMessageData data;
+ if (!BuildClonedMessageData(aData, data)) {
+ MOZ_CRASH();
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+ if (browserParent->SendAsyncMessage(aMessage, data)) {
+ return NS_OK;
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mChildMessageManager) {
+ RefPtr<nsAsyncMessageToChild> ev = new nsAsyncMessageToChild(this);
+ nsresult rv = ev->Init(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return rv;
+ }
+
+ // We don't have any targets to send our asynchronous message to.
+ return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<MessageSender> nsFrameLoader::GetMessageManager() {
+ EnsureMessageManager();
+ return do_AddRef(mMessageManager);
+}
+
+nsresult nsFrameLoader::EnsureMessageManager() {
+ NS_ENSURE_STATE(mOwnerContent);
+
+ if (mMessageManager) {
+ return NS_OK;
+ }
+
+ if (!mIsTopLevelContent && !OwnerIsMozBrowserFrame() && !IsRemoteFrame() &&
+ !(mOwnerContent->IsXULElement() &&
+ mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::forcemessagemanager,
+ nsGkAtoms::_true, eCaseMatters))) {
+ return NS_OK;
+ }
+
+ RefPtr<nsGlobalWindowOuter> window =
+ nsGlobalWindowOuter::Cast(GetOwnerDoc()->GetWindow());
+ RefPtr<ChromeMessageBroadcaster> parentManager;
+
+ if (window && window->IsChromeWindow()) {
+ nsAutoString messagemanagergroup;
+ if (mOwnerContent->IsXULElement() &&
+ mOwnerContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::messagemanagergroup,
+ messagemanagergroup)) {
+ parentManager = window->GetGroupMessageManager(messagemanagergroup);
+ }
+
+ if (!parentManager) {
+ parentManager = window->GetMessageManager();
+ }
+ } else {
+ parentManager = nsFrameMessageManager::GetGlobalMessageManager();
+ }
+
+ mMessageManager = new ChromeMessageSender(parentManager);
+ if (!IsRemoteFrame()) {
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(GetDocShell(),
+ "MaybeCreateDocShell succeeded, but null docShell");
+ if (!GetDocShell()) {
+ return NS_ERROR_FAILURE;
+ }
+ mChildMessageManager = InProcessBrowserChildMessageManager::Create(
+ GetDocShell(), mOwnerContent, mMessageManager);
+ NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED);
+
+ // Set up session store
+ if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ if (XRE_IsParentProcess() && mIsTopLevelContent) {
+ mSessionStoreChild = SessionStoreChild::GetOrCreate(
+ GetExtantBrowsingContext(), mOwnerContent);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::ReallyLoadFrameScripts() {
+ nsresult rv = EnsureMessageManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (mMessageManager) {
+ mMessageManager->InitWithCallback(this);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<Element> nsFrameLoader::GetOwnerElement() {
+ return do_AddRef(mOwnerContent);
+}
+
+void nsFrameLoader::SetDetachedSubdocFrame(nsIFrame* aDetachedFrame,
+ Document* aContainerDoc) {
+ mDetachedSubdocFrame = aDetachedFrame;
+ mContainerDocWhileDetached = aContainerDoc;
+}
+
+nsIFrame* nsFrameLoader::GetDetachedSubdocFrame(
+ Document** aContainerDoc) const {
+ NS_IF_ADDREF(*aContainerDoc = mContainerDocWhileDetached);
+ return mDetachedSubdocFrame.GetFrame();
+}
+
+void nsFrameLoader::ApplySandboxFlags(uint32_t sandboxFlags) {
+ // If our BrowsingContext doesn't exist yet, it means we haven't been
+ // initialized yet. This method will be called again once we're initialized
+ // from MaybeCreateDocShell. <iframe> BrowsingContexts are never created as
+ // initially remote, so we don't need to worry about updating sandbox flags
+ // for an uninitialized initially-remote iframe.
+ BrowsingContext* context = GetExtantBrowsingContext();
+ if (!context) {
+ MOZ_ASSERT(!IsRemoteFrame(),
+ "cannot apply sandbox flags to an uninitialized "
+ "initially-remote frame");
+ return;
+ }
+
+ uint32_t parentSandboxFlags = mOwnerContent->OwnerDoc()->GetSandboxFlags();
+
+ // The child can only add restrictions, never remove them.
+ sandboxFlags |= parentSandboxFlags;
+
+ MOZ_ALWAYS_SUCCEEDS(context->SetSandboxFlags(sandboxFlags));
+}
+
+/* virtual */
+void nsFrameLoader::AttributeChanged(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ MOZ_ASSERT(mObservingOwnerContent);
+
+ if (aElement != mOwnerContent) {
+ return;
+ }
+
+ if (aNameSpaceID != kNameSpaceID_None ||
+ (aAttribute != TypeAttrName(aElement) &&
+ aAttribute != nsGkAtoms::primary)) {
+ return;
+ }
+
+ // Note: This logic duplicates a lot of logic in
+ // MaybeCreateDocshell. We should fix that.
+
+ // Notify our enclosing chrome that our type has changed. We only do this
+ // if our parent is chrome, since in all other cases we're random content
+ // subframes and the treeowner shouldn't worry about us.
+ if (!GetDocShell()) {
+ MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetDocShell()->GetInProcessParent(getter_AddRefs(parentItem));
+ if (!parentItem) {
+ return;
+ }
+
+ if (parentItem->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ parentItem->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ if (!parentTreeOwner) {
+ return;
+ }
+
+ bool is_primary = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
+ nsGkAtoms::_true, eIgnoreCase);
+
+ // when a content panel is no longer primary, hide any open popups it may have
+ if (!is_primary) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->HidePopupsInDocShell(GetDocShell());
+ }
+ }
+
+ parentTreeOwner->ContentShellRemoved(GetDocShell());
+ if (aElement->AttrValueIs(kNameSpaceID_None, TypeAttrName(aElement),
+ nsGkAtoms::content, eIgnoreCase)) {
+ parentTreeOwner->ContentShellAdded(GetDocShell(), is_primary);
+ }
+}
+
+void nsFrameLoader::RequestUpdatePosition(ErrorResult& aRv) {
+ if (auto* browserParent = GetBrowserParent()) {
+ nsresult rv = browserParent->UpdatePosition();
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ }
+}
+
+SessionStoreParent* nsFrameLoader::GetSessionStoreParent() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ if (mSessionStoreChild) {
+ return static_cast<SessionStoreParent*>(
+ InProcessChild::ParentActorFor(mSessionStoreChild));
+ }
+
+ if (BrowserParent* browserParent = GetBrowserParent()) {
+ return static_cast<SessionStoreParent*>(
+ SingleManagedOrNull(browserParent->ManagedPSessionStoreParent()));
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<Promise> nsFrameLoader::RequestTabStateFlush(
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ Document* ownerDoc = GetOwnerDoc();
+ if (!ownerDoc) {
+ aRv.ThrowNotSupportedError("No owner document");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(ownerDoc->GetOwnerGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ BrowsingContext* browsingContext = GetExtantBrowsingContext();
+ if (!browsingContext) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ SessionStoreParent* sessionStoreParent = GetSessionStoreParent();
+ if (!sessionStoreParent) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ sessionStoreParent->FlushAllSessionStoreChildren(
+ [promise]() { promise->MaybeResolveWithUndefined(); });
+
+ return promise.forget();
+}
+
+void nsFrameLoader::RequestFinalTabStateFlush() {
+ BrowsingContext* context = GetExtantBrowsingContext();
+ if (!context || !context->IsTop() || context->Canonical()->IsReplaced()) {
+ return;
+ }
+
+ RefPtr<CanonicalBrowsingContext> canonical = context->Canonical();
+ RefPtr<WindowGlobalParent> wgp = canonical->GetCurrentWindowGlobal();
+ RefPtr<Element> embedder = context->GetEmbedderElement();
+
+ RefPtr<SessionStoreParent> sessionStoreParent = GetSessionStoreParent();
+ if (!sessionStoreParent) {
+ canonical->ClearPermanentKey();
+ if (wgp) {
+ wgp->NotifySessionStoreUpdatesComplete(embedder);
+ }
+
+ return;
+ }
+
+ sessionStoreParent->FinalFlushAllSessionStoreChildren(
+ [canonical, wgp, embedder]() {
+ if (canonical) {
+ canonical->ClearPermanentKey();
+ }
+ if (wgp) {
+ wgp->NotifySessionStoreUpdatesComplete(embedder);
+ }
+ });
+}
+
+void nsFrameLoader::RequestEpochUpdate(uint32_t aEpoch) {
+ BrowsingContext* context = GetExtantBrowsingContext();
+ if (context) {
+ BrowsingContext* top = context->Top();
+ Unused << top->SetSessionStoreEpoch(aEpoch);
+ }
+}
+
+void nsFrameLoader::RequestSHistoryUpdate() {
+ if (mSessionStoreChild) {
+ mSessionStoreChild->UpdateSHistoryChanges();
+ return;
+ }
+
+ // If remote browsing (e10s), handle this with the BrowserParent.
+ if (auto* browserParent = GetBrowserParent()) {
+ Unused << browserParent->SendUpdateSHistory();
+ }
+}
+
+already_AddRefed<Promise> nsFrameLoader::PrintPreview(
+ nsIPrintSettings* aPrintSettings, BrowsingContext* aSourceBrowsingContext,
+ ErrorResult& aRv) {
+ auto* ownerDoc = GetOwnerDoc();
+ if (!ownerDoc) {
+ aRv.ThrowNotSupportedError("No owner document");
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(ownerDoc->GetOwnerGlobal(), aRv);
+ if (!promise) {
+ return nullptr;
+ }
+
+#ifndef NS_PRINTING
+ promise->MaybeRejectWithNotSupportedError("Build does not support printing");
+ return promise.forget();
+#else
+ auto resolve = [promise](PrintPreviewResultInfo aInfo) {
+ using Orientation = dom::PrintPreviewOrientation;
+ if (aInfo.sheetCount() > 0) {
+ PrintPreviewSuccessInfo info;
+ info.mSheetCount = aInfo.sheetCount();
+ info.mTotalPageCount = aInfo.totalPageCount();
+ info.mHasSelection = aInfo.hasSelection();
+ info.mHasSelfSelection = aInfo.hasSelfSelection();
+ info.mIsEmpty = aInfo.isEmpty();
+ if (aInfo.printLandscape()) {
+ info.mOrientation = aInfo.printLandscape().value()
+ ? Orientation::Landscape
+ : Orientation::Portrait;
+ } else {
+ MOZ_ASSERT(info.mOrientation == Orientation::Unspecified);
+ }
+ if (aInfo.pageWidth()) {
+ info.mPageWidth = aInfo.pageWidth().value();
+ }
+ if (aInfo.pageHeight()) {
+ info.mPageHeight = aInfo.pageHeight().value();
+ }
+
+ promise->MaybeResolve(info);
+ } else {
+ promise->MaybeRejectWithUnknownError("Print preview failed");
+ }
+ };
+
+ if (auto* browserParent = GetBrowserParent()) {
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (!printSettingsSvc) {
+ promise->MaybeRejectWithNotSupportedError("No nsIPrintSettingsService");
+ return promise.forget();
+ }
+
+ embedding::PrintData printData;
+ nsresult rv =
+ printSettingsSvc->SerializeToPrintData(aPrintSettings, &printData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(ErrorResult(rv));
+ return promise.forget();
+ }
+
+ browserParent->SendPrintPreview(printData, aSourceBrowsingContext)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__, std::move(resolve),
+ [promise](const mozilla::ipc::ResponseRejectReason) {
+ promise->MaybeRejectWithUnknownError("Print preview IPC failed");
+ });
+
+ return promise.forget();
+ }
+
+ RefPtr<nsGlobalWindowOuter> sourceWindow;
+ if (aSourceBrowsingContext) {
+ sourceWindow =
+ nsGlobalWindowOuter::Cast(aSourceBrowsingContext->GetDOMWindow());
+ } else {
+ nsDocShell* ourDocshell = GetExistingDocShell();
+ if (NS_WARN_IF(!ourDocshell)) {
+ promise->MaybeRejectWithNotSupportedError("No print preview docShell");
+ return promise.forget();
+ }
+ sourceWindow = nsGlobalWindowOuter::Cast(ourDocshell->GetWindow());
+ }
+ if (NS_WARN_IF(!sourceWindow)) {
+ promise->MaybeRejectWithNotSupportedError("No print preview source window");
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIDocShell> docShellToCloneInto = nullptr;
+ if (aSourceBrowsingContext) {
+ // We're going to call `Print()` below on a window that is not our own,
+ // which happens when we are creating a new print preview document instead
+ // of just applying a settings change to the existing PP document. In this
+ // case we need to explicitly pass our docShell as the docShell to clone
+ // into.
+ docShellToCloneInto = GetExistingDocShell();
+ if (NS_WARN_IF(!docShellToCloneInto)) {
+ promise->MaybeRejectWithNotSupportedError("No print preview docShell");
+ return promise.forget();
+ }
+
+ // We need to make sure we're displayed so that the view tree ends up right.
+ RefPtr<BrowsingContext> bc = docShellToCloneInto->GetBrowsingContext();
+ if (NS_WARN_IF(!bc)) {
+ promise->MaybeRejectWithNotSupportedError(
+ "No print preview browsing context");
+ return promise.forget();
+ }
+
+ RefPtr<Element> embedder = bc->GetEmbedderElement();
+ if (NS_WARN_IF(!embedder)) {
+ promise->MaybeRejectWithNotSupportedError(
+ "Trying to clone into a frameloader with no element?");
+ return promise.forget();
+ }
+
+ nsIFrame* frame = embedder->GetPrimaryFrame(FlushType::Frames);
+ if (NS_WARN_IF(!frame)) {
+ promise->MaybeRejectWithNotSupportedError("Frame is not being displayed");
+ return promise.forget();
+ }
+ }
+
+ // Unfortunately we can't pass `resolve` directly here because IPDL, for now,
+ // unfortunately generates slightly different parameter types for functions
+ // taking PrintPreviewResultInfo in PBrowserParent vs. PBrowserChild.
+ ErrorResult rv;
+ sourceWindow->Print(
+ aPrintSettings,
+ /* aRemotePrintJob = */ nullptr,
+ /* aListener = */ nullptr, docShellToCloneInto,
+ nsGlobalWindowOuter::IsPreview::Yes,
+ nsGlobalWindowOuter::IsForWindowDotPrint::No,
+ [resolve](const PrintPreviewResultInfo& aInfo) { resolve(aInfo); }, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ promise->MaybeReject(std::move(rv));
+ }
+
+ return promise.forget();
+#endif
+}
+
+void nsFrameLoader::ExitPrintPreview() {
+#ifdef NS_PRINTING
+ if (auto* browserParent = GetBrowserParent()) {
+ Unused << browserParent->SendExitPrintPreview();
+ return;
+ }
+ if (NS_WARN_IF(!GetExistingDocShell())) {
+ return;
+ }
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint =
+ do_GetInterface(ToSupports(GetExistingDocShell()->GetWindow()));
+ if (NS_WARN_IF(!webBrowserPrint)) {
+ return;
+ }
+ webBrowserPrint->ExitPrintPreview();
+#endif
+}
+
+already_AddRefed<nsIRemoteTab> nsFrameLoader::GetRemoteTab() {
+ if (!mRemoteBrowser) {
+ return nullptr;
+ }
+ if (auto* browserHost = mRemoteBrowser->AsBrowserHost()) {
+ return do_AddRef(browserHost);
+ }
+ return nullptr;
+}
+
+already_AddRefed<nsILoadContext> nsFrameLoader::GetLoadContext() {
+ return do_AddRef(GetBrowsingContext());
+}
+
+BrowsingContext* nsFrameLoader::GetBrowsingContext() {
+ if (!mInitialized) {
+ if (IsRemoteFrame()) {
+ Unused << EnsureRemoteBrowser();
+ } else if (mOwnerContent) {
+ Unused << MaybeCreateDocShell();
+ }
+ }
+ MOZ_ASSERT(mInitialized);
+ return GetExtantBrowsingContext();
+}
+
+BrowsingContext* nsFrameLoader::GetExtantBrowsingContext() {
+ if (!mPendingBrowsingContext) {
+ // If mPendingBrowsingContext is null then the frame loader is being
+ // destroyed (nsFrameLoader::DestroyDocShell was called), so return null
+ // here in that case.
+ return nullptr;
+ }
+
+ if (!mInitialized || !mPendingBrowsingContext->EverAttached()) {
+ // Don't return the pending BrowsingContext until this nsFrameLoader has
+ // been initialized, and the BC was attached.
+ return nullptr;
+ }
+
+ return mPendingBrowsingContext;
+}
+
+void nsFrameLoader::InitializeBrowserAPI() {
+ if (!OwnerIsMozBrowserFrame()) {
+ return;
+ }
+
+ nsresult rv = EnsureMessageManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ mMessageManager->LoadFrameScript(
+ u"chrome://global/content/BrowserElementChild.js"_ns,
+ /* allowDelayedLoad = */ true,
+ /* aRunInGlobalScope */ true, IgnoreErrors());
+
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->InitializeBrowserAPI();
+ }
+}
+
+void nsFrameLoader::DestroyBrowserFrameScripts() {
+ if (!OwnerIsMozBrowserFrame()) {
+ return;
+ }
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->DestroyBrowserFrameScripts();
+ }
+}
+
+void nsFrameLoader::StartPersistence(
+ BrowsingContext* aContext, nsIWebBrowserPersistDocumentReceiver* aRecv,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aRecv);
+ RefPtr<BrowsingContext> context = aContext ? aContext : GetBrowsingContext();
+
+ if (!context || !context->IsInSubtreeOf(GetBrowsingContext())) {
+ aRecv->OnError(NS_ERROR_NO_CONTENT);
+ return;
+ }
+
+ if (!context->GetDocShell() && XRE_IsParentProcess()) {
+ CanonicalBrowsingContext* canonical =
+ CanonicalBrowsingContext::Cast(context);
+ if (!canonical->GetCurrentWindowGlobal()) {
+ aRecv->OnError(NS_ERROR_NO_CONTENT);
+ return;
+ }
+ RefPtr<BrowserParent> browserParent =
+ canonical->GetCurrentWindowGlobal()->GetBrowserParent();
+ browserParent->StartPersistence(canonical, aRecv, aRv);
+ return;
+ }
+
+ nsCOMPtr<Document> foundDoc = context->GetDocument();
+
+ if (!foundDoc) {
+ aRecv->OnError(NS_ERROR_NO_CONTENT);
+ } else {
+ nsCOMPtr<nsIWebBrowserPersistDocument> pdoc =
+ new mozilla::WebBrowserPersistLocalDocument(foundDoc);
+ aRecv->OnDocumentReady(pdoc);
+ }
+}
+
+void nsFrameLoader::MaybeUpdatePrimaryBrowserParent(
+ BrowserParentChange aChange) {
+ if (!mOwnerContent || !mRemoteBrowser) {
+ return;
+ }
+
+ RefPtr<BrowserHost> browserHost = mRemoteBrowser->AsBrowserHost();
+ if (!browserHost) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = mOwnerContent->OwnerDoc()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ BrowsingContext* browsingContext = docShell->GetBrowsingContext();
+ if (!browsingContext->IsChrome()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ if (!parentTreeOwner) {
+ return;
+ }
+
+ if (!mObservingOwnerContent) {
+ mOwnerContent->AddMutationObserver(this);
+ mObservingOwnerContent = true;
+ }
+
+ parentTreeOwner->RemoteTabRemoved(browserHost);
+ if (aChange == eBrowserParentChanged) {
+ bool isPrimary = mOwnerContent->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true, eIgnoreCase);
+ parentTreeOwner->RemoteTabAdded(browserHost, isPrimary);
+ }
+}
+
+nsresult nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
+ nsIURI* aURI) {
+ nsCOMPtr<nsIDocShell> docShell = mOwnerContent->OwnerDoc()->GetDocShell();
+ nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(docShell);
+ NS_ENSURE_STATE(parentContext);
+
+ MOZ_ASSERT(mPendingBrowsingContext->EverAttached());
+
+ uint64_t chromeOuterWindowID = 0;
+
+ nsCOMPtr<nsPIWindowRoot> root =
+ nsContentUtils::GetWindowRoot(mOwnerContent->OwnerDoc());
+ if (root) {
+ nsPIDOMWindowOuter* outerWin = root->GetWindow();
+ if (outerWin) {
+ chromeOuterWindowID = outerWin->WindowID();
+ }
+ }
+
+ uint32_t maxTouchPoints = BrowserParent::GetMaxTouchPoints(mOwnerContent);
+
+ bool tabContextUpdated =
+ aTabContext->SetTabContext(chromeOuterWindowID, maxTouchPoints);
+ NS_ENSURE_STATE(tabContextUpdated);
+
+ return NS_OK;
+}
+
+nsresult nsFrameLoader::PopulateOriginContextIdsFromAttributes(
+ OriginAttributes& aAttr) {
+ // Only XUL or mozbrowser frames are allowed to set context IDs
+ uint32_t namespaceID = mOwnerContent->GetNameSpaceID();
+ if (namespaceID != kNameSpaceID_XUL && !OwnerIsMozBrowserFrame()) {
+ return NS_OK;
+ }
+
+ nsAutoString attributeValue;
+ if (aAttr.mUserContextId ==
+ nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID &&
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid,
+ attributeValue) &&
+ !attributeValue.IsEmpty()) {
+ nsresult rv;
+ aAttr.mUserContextId = attributeValue.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aAttr.mGeckoViewSessionContextId.IsEmpty() &&
+ mOwnerContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::geckoViewSessionContextId,
+ attributeValue) &&
+ !attributeValue.IsEmpty()) {
+ // XXX: Should we check the format from `GeckoViewNavigation.jsm` here?
+ aAttr.mGeckoViewSessionContextId = attributeValue;
+ }
+
+ return NS_OK;
+}
+
+ProcessMessageManager* nsFrameLoader::GetProcessMessageManager() const {
+ if (auto* browserParent = GetBrowserParent()) {
+ return browserParent->Manager()->GetMessageManager();
+ }
+ return nullptr;
+};
+
+JSObject* nsFrameLoader::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ JS::Rooted<JSObject*> result(cx);
+ FrameLoader_Binding::Wrap(cx, this, this, aGivenProto, &result);
+ return result;
+}
+
+void nsFrameLoader::SetWillChangeProcess() {
+ mWillChangeProcess = true;
+
+ if (IsRemoteFrame()) {
+ if (auto* browserParent = GetBrowserParent()) {
+ if (auto* bc = CanonicalBrowsingContext::Cast(mPendingBrowsingContext);
+ bc && bc->EverAttached()) {
+ bc->StartUnloadingHost(browserParent->Manager()->ChildID());
+ bc->SetCurrentBrowserParent(nullptr);
+ }
+ // OOP Browser - Go directly over Browser Parent
+ Unused << browserParent->SendWillChangeProcess();
+ } else if (auto* browserBridgeChild = GetBrowserBridgeChild()) {
+ // OOP IFrame - Through Browser Bridge Parent, set on browser child
+ Unused << browserBridgeChild->SendWillChangeProcess();
+ }
+ return;
+ }
+
+ // In process
+ RefPtr<nsDocShell> docshell = GetDocShell();
+ MOZ_ASSERT(docshell);
+ docshell->SetWillChangeProcess();
+}
+
+static mozilla::Result<bool, nsresult> DidBuildIDChange() {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file));
+ MOZ_TRY(rv);
+
+ rv = file->AppendNative("platform.ini"_ns);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIINIParserFactory> iniFactory =
+ do_GetService("@mozilla.org/xpcom/ini-parser-factory;1", &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIINIParser> parser;
+ rv = iniFactory->CreateINIParser(file, getter_AddRefs(parser));
+ MOZ_TRY(rv);
+
+ nsAutoCString installedBuildID;
+ rv = parser->GetString("Build"_ns, "BuildID"_ns, installedBuildID);
+ MOZ_TRY(rv);
+
+ nsDependentCString runningBuildID(PlatformBuildID());
+ return (installedBuildID != runningBuildID);
+}
+
+void nsFrameLoader::MaybeNotifyCrashed(BrowsingContext* aBrowsingContext,
+ ContentParentId aChildID,
+ mozilla::ipc::MessageChannel* aChannel) {
+ if (mTabProcessCrashFired) {
+ return;
+ }
+
+ if (mPendingBrowsingContext == aBrowsingContext) {
+ mTabProcessCrashFired = true;
+ }
+
+ // Fire the crashed observer notification.
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return;
+ }
+
+ os->NotifyObservers(ToSupports(this), "oop-frameloader-crashed", nullptr);
+
+ // Check our owner element still references us. If it's moved, on, events
+ // don't need to be fired.
+ RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(mOwnerContent);
+ if (!owner) {
+ return;
+ }
+
+ RefPtr<nsFrameLoader> currentFrameLoader = owner->GetFrameLoader();
+ if (currentFrameLoader != this) {
+ return;
+ }
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+ bool sendTelemetry = false;
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+ // Fire the actual crashed event.
+ nsString eventName;
+ if (aChannel && !aChannel->DoBuildIDsMatch()) {
+ auto changedOrError = DidBuildIDChange();
+ if (changedOrError.isErr()) {
+ NS_WARNING("Error while checking buildid mismatch");
+ eventName = u"oop-browser-buildid-mismatch"_ns;
+ } else {
+ bool aChanged = changedOrError.unwrap();
+ if (aChanged) {
+ NS_WARNING("True build ID mismatch");
+ eventName = u"oop-browser-buildid-mismatch"_ns;
+ } else {
+ NS_WARNING("build ID mismatch false alarm");
+ eventName = u"oop-browser-crashed"_ns;
+#if defined(MOZ_TELEMETRY_REPORTING)
+ sendTelemetry = true;
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+ }
+ }
+ } else {
+ NS_WARNING("No build ID mismatch");
+ eventName = u"oop-browser-crashed"_ns;
+ }
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+ if (sendTelemetry) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::DOM_CONTENTPROCESS_BUILDID_MISMATCH_FALSE_POSITIVE,
+ 1);
+ }
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+ FrameCrashedEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ if (aBrowsingContext) {
+ init.mBrowsingContextId = aBrowsingContext->Id();
+ init.mIsTopFrame = aBrowsingContext->IsTop();
+ init.mChildID = aChildID;
+ }
+
+ RefPtr<FrameCrashedEvent> event = FrameCrashedEvent::Constructor(
+ mOwnerContent->OwnerDoc(), eventName, init);
+ event->SetTrusted(true);
+
+ RefPtr<Element> ownerContent = mOwnerContent;
+ EventDispatcher::DispatchDOMEvent(ownerContent, nullptr, event, nullptr,
+ nullptr);
+}
+
+bool nsFrameLoader::EnsureBrowsingContextAttached() {
+ nsresult rv;
+
+ Document* parentDoc = mOwnerContent->OwnerDoc();
+ MOZ_ASSERT(parentDoc);
+ BrowsingContext* parentContext = parentDoc->GetBrowsingContext();
+ MOZ_ASSERT(parentContext);
+
+ // Inherit the `use` flags from our parent BrowsingContext.
+ bool usePrivateBrowsing = parentContext->UsePrivateBrowsing();
+ bool useRemoteSubframes = parentContext->UseRemoteSubframes();
+ bool useRemoteTabs = parentContext->UseRemoteTabs();
+
+ // Determine the exact OriginAttributes which should be used for our
+ // BrowsingContext. This will be used to initialize OriginAttributes if the
+ // BrowsingContext has not already been created.
+ OriginAttributes attrs;
+ if (mPendingBrowsingContext->IsContent()) {
+ if (mPendingBrowsingContext->GetParent()) {
+ MOZ_ASSERT(mPendingBrowsingContext->GetParent() == parentContext);
+ parentContext->GetOriginAttributes(attrs);
+ }
+
+ // Inherit the `mFirstPartyDomain` flag from our parent document's result
+ // principal, if it was set.
+ if (parentContext->IsContent() &&
+ !parentDoc->NodePrincipal()->IsSystemPrincipal() &&
+ !OwnerIsMozBrowserFrame()) {
+ OriginAttributes docAttrs =
+ parentDoc->NodePrincipal()->OriginAttributesRef();
+ // We only want to inherit firstPartyDomain here, other attributes should
+ // be constant.
+ MOZ_ASSERT(attrs.EqualsIgnoringFPD(docAttrs));
+ attrs.mFirstPartyDomain = docAttrs.mFirstPartyDomain;
+ }
+
+ // Inherit the PrivateBrowsing flag across content/chrome boundaries.
+ attrs.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing);
+
+ // A <browser> element may have overridden userContextId or
+ // geckoViewUserContextId.
+ rv = PopulateOriginContextIdsFromAttributes(attrs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // <iframe mozbrowser> is allowed to set `mozprivatebrowsing` to
+ // force-enable private browsing.
+ if (OwnerIsMozBrowserFrame()) {
+ if (mOwnerContent->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::mozprivatebrowsing)) {
+ attrs.SyncAttributesWithPrivateBrowsing(true);
+ usePrivateBrowsing = true;
+ }
+ }
+ }
+
+ // If we've already been attached, return.
+ if (mPendingBrowsingContext->EverAttached()) {
+ MOZ_DIAGNOSTIC_ASSERT(mPendingBrowsingContext->UsePrivateBrowsing() ==
+ usePrivateBrowsing);
+ MOZ_DIAGNOSTIC_ASSERT(mPendingBrowsingContext->UseRemoteTabs() ==
+ useRemoteTabs);
+ MOZ_DIAGNOSTIC_ASSERT(mPendingBrowsingContext->UseRemoteSubframes() ==
+ useRemoteSubframes);
+ // Don't assert that our OriginAttributes match, as we could have different
+ // OriginAttributes in the case where we were opened using window.open.
+ return true;
+ }
+
+ // Initialize non-synced OriginAttributes and related fields.
+ rv = mPendingBrowsingContext->SetOriginAttributes(attrs);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = mPendingBrowsingContext->SetUsePrivateBrowsing(usePrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = mPendingBrowsingContext->SetRemoteTabs(useRemoteTabs);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = mPendingBrowsingContext->SetRemoteSubframes(useRemoteSubframes);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Finish attaching.
+ mPendingBrowsingContext->EnsureAttached();
+ return true;
+}
+
+void nsFrameLoader::InvokeBrowsingContextReadyCallback() {
+ if (mOpenWindowInfo) {
+ if (RefPtr<nsIBrowsingContextReadyCallback> callback =
+ mOpenWindowInfo->BrowsingContextReadyCallback()) {
+ callback->BrowsingContextReady(mPendingBrowsingContext);
+ }
+ }
+}
diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h
new file mode 100644
index 0000000000..1769070d91
--- /dev/null
+++ b/dom/base/nsFrameLoader.h
@@ -0,0 +1,574 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Class for managing loading of a subframe (creation of the docshell,
+ * handling of loads in it, recursion-checking).
+ */
+
+#ifndef nsFrameLoader_h_
+#define nsFrameLoader_h_
+
+#include <cstdint>
+#include "ErrorList.h"
+#include "Units.h"
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDocShell.h"
+#include "mozilla/dom/MessageManagerCallback.h"
+#include "nsID.h"
+#include "nsIFrame.h"
+#include "nsIMutationObserver.h"
+#include "nsISupports.h"
+#include "nsRect.h"
+#include "nsStringFwd.h"
+#include "nsStubMutationObserver.h"
+#include "nsWrapperCache.h"
+
+class nsIURI;
+class nsSubDocumentFrame;
+class AutoResetInShow;
+class AutoResetInFrameSwap;
+class nsFrameLoaderOwner;
+class nsIRemoteTab;
+class nsIDocShellTreeItem;
+class nsIDocShellTreeOwner;
+class nsILoadContext;
+class nsIPrintSettings;
+class nsIWebBrowserPersistDocumentReceiver;
+class nsIWebProgressListener;
+class nsIOpenWindowInfo;
+
+namespace mozilla {
+
+class OriginAttributes;
+
+namespace dom {
+class ChromeMessageSender;
+class ContentParent;
+class Document;
+class Element;
+class InProcessBrowserChildMessageManager;
+class MessageSender;
+class ProcessMessageManager;
+class BrowserParent;
+class MutableTabContext;
+class BrowserBridgeChild;
+class RemoteBrowser;
+struct RemotenessOptions;
+struct NavigationIsolationOptions;
+class SessionStoreChild;
+class SessionStoreParent;
+
+namespace ipc {
+class StructuredCloneData;
+} // namespace ipc
+
+} // namespace dom
+
+namespace ipc {
+class MessageChannel;
+} // namespace ipc
+} // namespace mozilla
+
+#if defined(MOZ_WIDGET_GTK)
+typedef struct _GtkWidget GtkWidget;
+#endif
+
+// IID for nsFrameLoader, because some places want to QI to it.
+#define NS_FRAMELOADER_IID \
+ { \
+ 0x297fd0ea, 0x1b4a, 0x4c9a, { \
+ 0xa4, 0x04, 0xe5, 0x8b, 0xe8, 0x95, 0x10, 0x50 \
+ } \
+ }
+
+class nsFrameLoader final : public nsStubMutationObserver,
+ public mozilla::dom::ipc::MessageManagerCallback,
+ public nsWrapperCache,
+ public mozilla::LinkedListElement<nsFrameLoader> {
+ friend class AutoResetInShow;
+ friend class AutoResetInFrameSwap;
+ friend class nsFrameLoaderOwner;
+ using Document = mozilla::dom::Document;
+ using Element = mozilla::dom::Element;
+ using BrowserParent = mozilla::dom::BrowserParent;
+ using BrowserBridgeChild = mozilla::dom::BrowserBridgeChild;
+ using BrowsingContext = mozilla::dom::BrowsingContext;
+ using BrowsingContextGroup = mozilla::dom::BrowsingContextGroup;
+ using Promise = mozilla::dom::Promise;
+
+ public:
+ // Called by Frame Elements to create a new FrameLoader.
+ static already_AddRefed<nsFrameLoader> Create(
+ Element* aOwner, bool aNetworkCreated,
+ nsIOpenWindowInfo* aOpenWindowInfo = nullptr);
+
+ // Called by nsFrameLoaderOwner::ChangeRemoteness when switching out
+ // FrameLoaders.
+ static already_AddRefed<nsFrameLoader> Recreate(
+ Element* aOwner, BrowsingContext* aContext, BrowsingContextGroup* aGroup,
+ const mozilla::dom::NavigationIsolationOptions& aRemotenessOptions,
+ bool aIsRemote, bool aNetworkCreated, bool aPreserveContext);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_FRAMELOADER_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsFrameLoader)
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ nsresult CheckForRecursiveLoad(nsIURI* aURI);
+ nsresult ReallyStartLoading();
+ void StartDestroy(bool aForProcessSwitch);
+ void DestroyDocShell();
+ void DestroyComplete();
+ nsDocShell* GetExistingDocShell() const { return mDocShell; }
+ mozilla::dom::InProcessBrowserChildMessageManager*
+ GetBrowserChildMessageManager() const {
+ return mChildMessageManager;
+ }
+ nsresult UpdatePositionAndSize(nsSubDocumentFrame* aIFrame);
+ void PropagateIsUnderHiddenEmbedderElement(
+ bool aIsUnderHiddenEmbedderElement);
+
+ void UpdateRemoteStyle(mozilla::StyleImageRendering aImageRendering);
+
+ // When creating a nsFrameLoaderOwner which is a static clone, a
+ // `nsFrameLoader` is not immediately attached to it. Instead, it is added to
+ // the static clone document's `PendingFrameStaticClones` list.
+ //
+ // After the parent document has been fully cloned, a new frameloader will be
+ // created for the cloned iframe, and `FinishStaticClone` will be called on
+ // it, which will clone the inner document of the source nsFrameLoader.
+ nsresult FinishStaticClone(nsFrameLoader* aStaticCloneOf,
+ nsIPrintSettings* aPrintSettings,
+ bool* aOutHasInProcessPrintCallbacks);
+
+ nsresult DoRemoteStaticClone(nsFrameLoader* aStaticCloneOf,
+ nsIPrintSettings* aPrintSettings);
+
+ // WebIDL methods
+
+ nsDocShell* GetDocShell(mozilla::ErrorResult& aRv);
+
+ already_AddRefed<nsIRemoteTab> GetRemoteTab();
+
+ already_AddRefed<nsILoadContext> GetLoadContext();
+
+ mozilla::dom::BrowsingContext* GetBrowsingContext();
+ mozilla::dom::BrowsingContext* GetExtantBrowsingContext();
+ mozilla::dom::BrowsingContext* GetMaybePendingBrowsingContext() {
+ return mPendingBrowsingContext;
+ }
+
+ /**
+ * Start loading the frame. This method figures out what to load
+ * from the owner content in the frame loader.
+ */
+ void LoadFrame(bool aOriginalSrc);
+
+ /**
+ * Loads the specified URI in this frame. Behaves identically to loadFrame,
+ * except that this method allows specifying the URI to load.
+ *
+ * @param aURI The URI to load.
+ * @param aTriggeringPrincipal The triggering principal for the load. May be
+ * null, in which case the node principal of the owner content will be
+ * used.
+ * @param aCsp The CSP to be used for the load. That is not the CSP to be
+ * applied to subresources within the frame, but to the iframe load
+ * itself. E.g. if the CSP holds upgrade-insecure-requests the the
+ * frame load is upgraded from http to https.
+ */
+ nsresult LoadURI(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp, bool aOriginalSrc);
+
+ /**
+ * Resume a redirected load within this frame.
+ *
+ * @param aPendingSwitchID ID of a process-switching load to be reusmed
+ * within this frame.
+ */
+ void ResumeLoad(uint64_t aPendingSwitchID);
+
+ /**
+ * Destroy the frame loader and everything inside it. This will
+ * clear the weak owner content reference.
+ */
+ void Destroy(bool aForProcessSwitch = false);
+
+ void AsyncDestroy() {
+ mNeedsAsyncDestroy = true;
+ Destroy();
+ }
+
+ void RequestUpdatePosition(mozilla::ErrorResult& aRv);
+
+ already_AddRefed<Promise> RequestTabStateFlush(mozilla::ErrorResult& aRv);
+
+ void RequestEpochUpdate(uint32_t aEpoch);
+
+ void RequestSHistoryUpdate();
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PrintPreview(
+ nsIPrintSettings* aPrintSettings, BrowsingContext* aSourceBC,
+ mozilla::ErrorResult& aRv);
+
+ void ExitPrintPreview();
+
+ void StartPersistence(BrowsingContext* aContext,
+ nsIWebBrowserPersistDocumentReceiver* aRecv,
+ mozilla::ErrorResult& aRv);
+
+ // WebIDL getters
+
+ already_AddRefed<mozilla::dom::MessageSender> GetMessageManager();
+
+ already_AddRefed<Element> GetOwnerElement();
+
+ uint32_t LazyWidth() const;
+
+ uint32_t LazyHeight() const;
+
+ uint64_t ChildID() const { return mChildID; }
+
+ bool DepthTooGreat() const { return mDepthTooGreat; }
+
+ bool IsDead() const { return mDestroyCalled; }
+
+ bool IsNetworkCreated() const { return mNetworkCreated; }
+
+ /**
+ * Is this a frame loader for a bona fide <iframe mozbrowser>?
+ * <xul:browser> is not a mozbrowser, so this is false for that case.
+ */
+ bool OwnerIsMozBrowserFrame();
+
+ nsIContent* GetParentObject() const;
+
+ /**
+ * MessageManagerCallback methods that we override.
+ */
+ virtual bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) override;
+ virtual nsresult DoSendAsyncMessage(
+ const nsAString& aMessage,
+ mozilla::dom::ipc::StructuredCloneData& aData) override;
+
+ /**
+ * Called from the layout frame associated with this frame loader;
+ * this notifies us to hook up with the widget and view.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool Show(nsSubDocumentFrame*);
+
+ void MaybeShowFrame();
+
+ /**
+ * Called when the margin properties of the containing frame are changed.
+ */
+ void MarginsChanged();
+
+ /**
+ * Called from the layout frame associated with this frame loader, when
+ * the frame is being torn down; this notifies us that out widget and view
+ * are going away and we should unhook from them.
+ */
+ void Hide();
+
+ // Used when content is causing a FrameLoader to be created, and
+ // needs to try forcing layout to flush in order to get accurate
+ // dimensions for the content area.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void ForceLayoutIfNecessary();
+
+ // The guts of an nsFrameLoaderOwner::SwapFrameLoader implementation. A
+ // frame loader owner needs to call this, and pass in the two references to
+ // nsRefPtrs for frame loaders that need to be swapped.
+ nsresult SwapWithOtherLoader(nsFrameLoader* aOther,
+ nsFrameLoaderOwner* aThisOwner,
+ nsFrameLoaderOwner* aOtherOwner);
+
+ nsresult SwapWithOtherRemoteLoader(nsFrameLoader* aOther,
+ nsFrameLoaderOwner* aThisOwner,
+ nsFrameLoaderOwner* aOtherOwner);
+
+ /**
+ * Return the primary frame for our owning content, or null if it
+ * can't be found.
+ */
+ nsIFrame* GetPrimaryFrameOfOwningContent() const;
+
+ /**
+ * Return the document that owns this, or null if we don't have
+ * an owner.
+ */
+ Document* GetOwnerDoc() const;
+
+ /**
+ * Returns whether this frame is a remote frame.
+ *
+ * This is true for either a top-level remote browser in the parent process,
+ * or a remote subframe in the child process.
+ */
+ bool IsRemoteFrame();
+
+ mozilla::dom::RemoteBrowser* GetRemoteBrowser() const;
+
+ /**
+ * Returns the IPDL actor used if this is a top-level remote browser, or null
+ * otherwise.
+ */
+ BrowserParent* GetBrowserParent() const;
+
+ /**
+ * Returns the IPDL actor used if this is an out-of-process iframe, or null
+ * otherwise.
+ */
+ BrowserBridgeChild* GetBrowserBridgeChild() const;
+
+ /**
+ * Returns the layers ID that this remote frame is using to render.
+ *
+ * This must only be called if this is a remote frame.
+ */
+ mozilla::layers::LayersId GetLayersId() const;
+
+ mozilla::dom::ChromeMessageSender* GetFrameMessageManager() {
+ return mMessageManager;
+ }
+
+ mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; }
+
+ /**
+ * Stashes a detached nsIFrame on the frame loader. We do this when we're
+ * destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is
+ * being reframed we'll restore the detached nsIFrame when it's recreated,
+ * otherwise we'll discard the old presentation and set the detached
+ * subdoc nsIFrame to null. aContainerDoc is the document containing the
+ * the subdoc frame. This enables us to detect when the containing
+ * document has changed during reframe, so we can discard the presentation
+ * in that case.
+ */
+ void SetDetachedSubdocFrame(nsIFrame* aDetachedFrame,
+ Document* aContainerDoc);
+
+ /**
+ * Retrieves the detached nsIFrame and the document containing the nsIFrame,
+ * as set by SetDetachedSubdocFrame().
+ */
+ nsIFrame* GetDetachedSubdocFrame(Document** aContainerDoc) const;
+
+ /**
+ * Applies a new set of sandbox flags. These are merged with the sandbox
+ * flags from our owning content's owning document with a logical OR, this
+ * ensures that we can only add restrictions and never remove them.
+ */
+ void ApplySandboxFlags(uint32_t sandboxFlags);
+
+ void GetURL(nsString& aURL, nsIPrincipal** aTriggeringPrincipal,
+ nsIContentSecurityPolicy** aCsp);
+
+ // Properly retrieves documentSize of any subdocument type.
+ nsresult GetWindowDimensions(nsIntRect& aRect);
+
+ virtual mozilla::dom::ProcessMessageManager* GetProcessMessageManager()
+ const override;
+
+ // public because a callback needs these.
+ RefPtr<mozilla::dom::ChromeMessageSender> mMessageManager;
+ RefPtr<mozilla::dom::InProcessBrowserChildMessageManager>
+ mChildMessageManager;
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetWillChangeProcess();
+
+ // Configure which remote process should be used to host the remote browser
+ // created in `TryRemoteBrowser`. This method _must_ be called before
+ // `TryRemoteBrowser`, and a script blocker must be on the stack.
+ //
+ // |aContentParent|, if set, must have the remote type |aRemoteType|.
+ void ConfigRemoteProcess(const nsACString& aRemoteType,
+ mozilla::dom::ContentParent* aContentParent);
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void MaybeNotifyCrashed(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ mozilla::dom::ContentParentId aChildID,
+ mozilla::ipc::MessageChannel* aChannel);
+
+ void FireErrorEvent();
+
+ mozilla::dom::SessionStoreChild* GetSessionStoreChild() {
+ return mSessionStoreChild;
+ }
+
+ mozilla::dom::SessionStoreParent* GetSessionStoreParent();
+
+ private:
+ nsFrameLoader(mozilla::dom::Element* aOwner,
+ mozilla::dom::BrowsingContext* aBrowsingContext, bool aIsRemote,
+ bool aNetworkCreated);
+ ~nsFrameLoader();
+
+ void SetOwnerContent(mozilla::dom::Element* aContent);
+
+ /**
+ * Get our owning element's app manifest URL, or return the empty string if
+ * our owning element doesn't have an app manifest URL.
+ */
+ void GetOwnerAppManifestURL(nsAString& aOut);
+
+ /**
+ * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
+ * initialize mDocShell.
+ */
+ nsresult MaybeCreateDocShell();
+ nsresult EnsureMessageManager();
+ nsresult ReallyLoadFrameScripts();
+ nsDocShell* GetDocShell() const { return mDocShell; }
+
+ void AssertSafeToInit();
+
+ // Updates the subdocument position and size. This gets called only
+ // when we have our own in-process DocShell.
+ void UpdateBaseWindowPositionAndSize(nsSubDocumentFrame* aIFrame);
+
+ /**
+ * Checks whether a load of the given URI should be allowed, and returns an
+ * error result if it should not.
+ *
+ * @param aURI The URI to check.
+ * @param aTriggeringPrincipal The triggering principal for the load. May be
+ * null, in which case the node principal of the owner content is used.
+ */
+ nsresult CheckURILoad(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal);
+ nsresult ReallyStartLoadingInternal();
+
+ // Returns true if we have a remote browser or else attempts to create a
+ // remote browser and returns true if successful.
+ bool EnsureRemoteBrowser();
+
+ // Return true if remote browser created; nothing else to do
+ bool TryRemoteBrowser();
+ bool TryRemoteBrowserInternal();
+
+ // Tell the remote browser that it's now "virtually visible"
+ bool ShowRemoteFrame(const mozilla::ScreenIntSize& size,
+ nsSubDocumentFrame* aFrame = nullptr);
+
+ void AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
+ nsIDocShellTreeOwner* aOwner);
+
+ void InitializeBrowserAPI();
+ void DestroyBrowserFrameScripts();
+
+ nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
+ nsIURI* aURI = nullptr);
+
+ enum BrowserParentChange { eBrowserParentRemoved, eBrowserParentChanged };
+ void MaybeUpdatePrimaryBrowserParent(BrowserParentChange aChange);
+
+ nsresult PopulateOriginContextIdsFromAttributes(
+ mozilla::OriginAttributes& aAttr);
+
+ bool EnsureBrowsingContextAttached();
+
+ // Invoke the callback from nsOpenWindowInfo to indicate that a
+ // browsing context for a newly opened tab/window is ready.
+ void InvokeBrowsingContextReadyCallback();
+
+ void RequestFinalTabStateFlush();
+
+ RefPtr<mozilla::dom::BrowsingContext> mPendingBrowsingContext;
+ nsCOMPtr<nsIURI> mURIToLoad;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+ nsCOMPtr<nsIOpenWindowInfo> mOpenWindowInfo;
+ mozilla::dom::Element* mOwnerContent; // WEAK
+
+ // After the frameloader has been removed from the DOM but before all of the
+ // messages from the frame have been received, we keep a strong reference to
+ // our <browser> element.
+ RefPtr<mozilla::dom::Element> mOwnerContentStrong;
+
+ // Stores the root frame of the subdocument while the subdocument is being
+ // reframed. Used to restore the presentation after reframing.
+ WeakFrame mDetachedSubdocFrame;
+ // Stores the containing document of the frame corresponding to this
+ // frame loader. This is reference is kept valid while the subframe's
+ // presentation is detached and stored in mDetachedSubdocFrame. This
+ // enables us to detect whether the frame has moved documents during
+ // a reframe, so that we know not to restore the presentation.
+ RefPtr<Document> mContainerDocWhileDetached;
+
+ // When performing a process switch, this value is used rather than mURIToLoad
+ // to identify the process-switching load which should be resumed in the
+ // target process.
+ uint64_t mPendingSwitchID;
+
+ uint64_t mChildID;
+ RefPtr<mozilla::dom::RemoteBrowser> mRemoteBrowser;
+ RefPtr<nsDocShell> mDocShell;
+
+ // Holds the last known size of the frame.
+ mozilla::ScreenIntSize mLazySize;
+
+ // Actor for collecting session store data from content children. This will be
+ // cleared and set to null eagerly when taking down the frameloader to break
+ // refcounted cycles early.
+ RefPtr<mozilla::dom::SessionStoreChild> mSessionStoreChild;
+
+ nsCString mRemoteType;
+
+ bool mInitialized : 1;
+ bool mDepthTooGreat : 1;
+ bool mIsTopLevelContent : 1;
+ bool mDestroyCalled : 1;
+ bool mNeedsAsyncDestroy : 1;
+ bool mInSwap : 1;
+ bool mInShow : 1;
+ bool mHideCalled : 1;
+ // True when the object is created for an element which the parser has
+ // created using NS_FROM_PARSER_NETWORK flag. If the element is modified,
+ // it may lose the flag.
+ bool mNetworkCreated : 1;
+
+ // True if a pending load corresponds to the original src (or srcdoc)
+ // attribute of the frame element.
+ bool mLoadingOriginalSrc : 1;
+
+ bool mRemoteBrowserShown : 1;
+ bool mIsRemoteFrame : 1;
+ // If true, the FrameLoader will be re-created with the same BrowsingContext,
+ // but for a different process, after it is destroyed.
+ bool mWillChangeProcess : 1;
+ bool mObservingOwnerContent : 1;
+
+ // When an out-of-process nsFrameLoader crashes, an event is fired on the
+ // frame. To ensure this is only fired once, this bit is checked.
+ bool mTabProcessCrashFired : 1;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsFrameLoader, NS_FRAMELOADER_IID)
+
+inline nsISupports* ToSupports(nsFrameLoader* aFrameLoader) {
+ return aFrameLoader;
+}
+
+#endif
diff --git a/dom/base/nsFrameLoaderOwner.cpp b/dom/base/nsFrameLoaderOwner.cpp
new file mode 100644
index 0000000000..e9b8a876c5
--- /dev/null
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFrameLoaderOwner.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsFrameLoader.h"
+#include "nsFocusManager.h"
+#include "nsNetUtil.h"
+#include "nsSubDocumentFrame.h"
+#include "nsQueryObject.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/FrameLoaderBinding.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserBridgeHost.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/EventStateManager.h"
+
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
+ return do_AddRef(mFrameLoader);
+}
+
+void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
+ mFrameLoader = aNewFrameLoader;
+}
+
+mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetBrowsingContext() {
+ if (mFrameLoader) {
+ return mFrameLoader->GetBrowsingContext();
+ }
+ return nullptr;
+}
+
+mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetExtantBrowsingContext() {
+ if (mFrameLoader) {
+ return mFrameLoader->GetExtantBrowsingContext();
+ }
+ return nullptr;
+}
+
+bool nsFrameLoaderOwner::UseRemoteSubframes() {
+ RefPtr<Element> owner = do_QueryObject(this);
+
+ nsILoadContext* loadContext = owner->OwnerDoc()->GetLoadContext();
+ MOZ_DIAGNOSTIC_ASSERT(loadContext);
+
+ return loadContext->UseRemoteSubframes();
+}
+
+nsFrameLoaderOwner::ChangeRemotenessContextType
+nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
+ bool aIsRemote, bool aReplaceBrowsingContext) {
+ if (aReplaceBrowsingContext) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Don't preserve for remote => parent loads.
+ if (!aIsRemote) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+
+ // Don't preserve for parent => remote loads.
+ if (mFrameLoader && !mFrameLoader->IsRemoteFrame()) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+ }
+
+ // We will preserve our browsing context if either fission is enabled, or the
+ // `preserve_browsing_contexts` pref is active.
+ if (UseRemoteSubframes() ||
+ StaticPrefs::fission_preserve_browsing_contexts()) {
+ return ChangeRemotenessContextType::PRESERVE;
+ }
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessCommon(
+ const ChangeRemotenessContextType& aContextType,
+ const NavigationIsolationOptions& aOptions, bool aSwitchingInProgressLoad,
+ bool aIsRemote, BrowsingContextGroup* aGroup,
+ std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv) {
+ MOZ_ASSERT_IF(aGroup, aContextType != ChangeRemotenessContextType::PRESERVE);
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ bool networkCreated = false;
+
+ // In this case, we're not reparenting a frameloader, we're just destroying
+ // our current one and creating a new one, so we can use ourselves as the
+ // owner.
+ RefPtr<Element> owner = do_QueryObject(this);
+ MOZ_ASSERT(owner);
+
+ // When we destroy the original frameloader, it will stop blocking the parent
+ // document's load event, and immediately trigger the load event if there are
+ // no other blockers. Since we're going to be adding a new blocker as soon as
+ // we recreate the frame loader, this is not what we want, so add our own
+ // blocker until the process is complete.
+ Document* doc = owner->OwnerDoc();
+ doc->BlockOnload();
+ auto cleanup = MakeScopeExit([&]() { doc->UnblockOnload(false); });
+
+ // If we store the previous nsFrameLoader in the bfcache, this will be filled
+ // with the SessionHistoryEntry which now owns the frame.
+ RefPtr<SessionHistoryEntry> bfcacheEntry;
+
+ {
+ // Introduce a script blocker to ensure no JS is executed during the
+ // nsFrameLoader teardown & recreation process. Unload listeners will be run
+ // for the previous document, and the load will be started for the new one,
+ // at the end of this block.
+ nsAutoScriptBlocker sb;
+
+ // If we already have a Frameloader, destroy it, possibly preserving its
+ // browsing context.
+ if (mFrameLoader) {
+ // Calling `GetBrowsingContext` here will force frameloader
+ // initialization if it hasn't already happened, which we neither need
+ // or want, so we use the initial (possibly pending) browsing context
+ // directly, instead.
+ bc = mFrameLoader->GetMaybePendingBrowsingContext();
+ networkCreated = mFrameLoader->IsNetworkCreated();
+
+ MOZ_ASSERT_IF(aOptions.mTryUseBFCache, aOptions.mReplaceBrowsingContext);
+ if (aOptions.mTryUseBFCache && bc) {
+ bfcacheEntry = bc->Canonical()->GetActiveSessionHistoryEntry();
+ bool useBFCache = bfcacheEntry &&
+ bfcacheEntry == aOptions.mActiveSessionHistoryEntry &&
+ !bfcacheEntry->GetFrameLoader();
+ if (useBFCache) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old "
+ "page in bfcache"));
+ Unused << bc->SetIsInBFCache(true);
+ bfcacheEntry->SetFrameLoader(mFrameLoader);
+ // Session history owns now the frameloader.
+ mFrameLoader = nullptr;
+ }
+ }
+
+ if (mFrameLoader) {
+ if (aContextType == ChangeRemotenessContextType::PRESERVE) {
+ mFrameLoader->SetWillChangeProcess();
+ }
+
+ // Preserve the networkCreated status, as nsDocShells created after a
+ // process swap may shouldn't change their dynamically-created status.
+ mFrameLoader->Destroy(aSwitchingInProgressLoad);
+ mFrameLoader = nullptr;
+ }
+ }
+
+ mFrameLoader = nsFrameLoader::Recreate(
+ owner, bc, aGroup, aOptions, aIsRemote, networkCreated,
+ aContextType == ChangeRemotenessContextType::PRESERVE);
+ if (NS_WARN_IF(!mFrameLoader)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Invoke the frame loader initialization callback to perform setup on our
+ // new nsFrameLoader. This may cause our ErrorResult to become errored, so
+ // double-check after calling.
+ aFrameLoaderInit();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ // Now that we have a new FrameLoader, we'll eventually need to reset
+ // nsSubDocumentFrame to use the new one. We can delay doing this if we're
+ // keeping our old frameloader around in the BFCache and the new frame hasn't
+ // presented yet to continue painting the previous document.
+ const bool retainPaint = bfcacheEntry && mFrameLoader->IsRemoteFrame();
+ if (!retainPaint) {
+ MOZ_LOG(
+ gSHIPBFCacheLog, LogLevel::Debug,
+ ("Previous frameLoader not entering BFCache - not retaining paint data"
+ "(bfcacheEntry=%p, isRemoteFrame=%d)",
+ bfcacheEntry.get(), mFrameLoader->IsRemoteFrame()));
+ }
+
+ ChangeFrameLoaderCommon(owner, retainPaint);
+
+ UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner);
+}
+
+void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element* aOwner,
+ bool aRetainPaint) {
+ // Now that we've got a new FrameLoader, we need to reset our
+ // nsSubDocumentFrame to use the new FrameLoader.
+ if (nsSubDocumentFrame* ourFrame = do_QueryFrame(aOwner->GetPrimaryFrame())) {
+ auto retain = aRetainPaint ? nsSubDocumentFrame::RetainPaintData::Yes
+ : nsSubDocumentFrame::RetainPaintData::No;
+ ourFrame->ResetFrameLoader(retain);
+ }
+
+ if (aOwner->IsXULElement()) {
+ // Assuming this element is a XULFrameElement, once we've reset our
+ // FrameLoader, fire an event to act like we've recreated ourselves, similar
+ // to what XULFrameElement does after rebinding to the tree.
+ // ChromeOnlyDispatch is turns on to make sure this isn't fired into
+ // content.
+ mozilla::AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *aOwner, u"XULFrameLoaderCreated"_ns, mozilla::CanBubble::eYes,
+ mozilla::ChromeOnlyDispatch::eYes);
+ }
+
+ if (mFrameLoader) {
+ mFrameLoader->PropagateIsUnderHiddenEmbedderElement(
+ !aOwner->GetPrimaryFrame() ||
+ !aOwner->GetPrimaryFrame()->StyleVisibility()->IsVisible());
+ }
+}
+
+void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange() {
+ RefPtr<Element> owner = do_QueryObject(this);
+ UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner);
+}
+
+void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(
+ Element* aOwner) {
+ // If the element is focused, or the current mouse over target then
+ // we need to update that state for the new BrowserParent too.
+ if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+ if (fm->GetFocusedElement() == aOwner) {
+ fm->ActivateRemoteFrameIfNeeded(*aOwner,
+ nsFocusManager::GenerateFocusActionId());
+ }
+ }
+
+ if (aOwner->GetPrimaryFrame()) {
+ EventStateManager* eventManager =
+ aOwner->GetPrimaryFrame()->PresContext()->EventStateManager();
+ eventManager->RecomputeMouseEnterStateForRemoteFrame(*aOwner);
+ }
+}
+
+void nsFrameLoaderOwner::ChangeRemoteness(
+ const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
+ bool isRemote = !aOptions.mRemoteType.IsEmpty();
+
+ std::function<void()> frameLoaderInit = [&] {
+ if (isRemote) {
+ mFrameLoader->ConfigRemoteProcess(aOptions.mRemoteType, nullptr);
+ }
+
+ if (aOptions.mPendingSwitchID.WasPassed()) {
+ mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
+ } else {
+ mFrameLoader->LoadFrame(false);
+ }
+ };
+
+ auto shouldPreserve = ShouldPreserveBrowsingContext(
+ isRemote, /* replaceBrowsingContext */ false);
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(shouldPreserve, options,
+ aOptions.mSwitchingInProgressLoad, isRemote,
+ /* group */ nullptr, frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
+ mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ if (NS_WARN_IF(!mFrameLoader)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ std::function<void()> frameLoaderInit = [&] {
+ MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader->mInitialized);
+ RefPtr<BrowserBridgeHost> host = aBridge->FinishInit(mFrameLoader);
+ mFrameLoader->mPendingBrowsingContext->SetEmbedderElement(
+ mFrameLoader->GetOwnerContent());
+ mFrameLoader->mRemoteBrowser = host;
+ mFrameLoader->mInitialized = true;
+ };
+
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
+ /* inProgress */ true,
+ /* isRemote */ true, /* group */ nullptr,
+ frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessToProcess(
+ ContentParent* aContentParent, const NavigationIsolationOptions& aOptions,
+ BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT_IF(aGroup, aOptions.mReplaceBrowsingContext);
+ bool isRemote = aContentParent != nullptr;
+
+ std::function<void()> frameLoaderInit = [&] {
+ if (isRemote) {
+ mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(),
+ aContentParent);
+ }
+ };
+
+ auto shouldPreserve =
+ ShouldPreserveBrowsingContext(isRemote, aOptions.mReplaceBrowsingContext);
+ ChangeRemotenessCommon(shouldPreserve, aOptions, /* inProgress */ true,
+ isRemote, aGroup, frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::SubframeCrashed() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ std::function<void()> frameLoaderInit = [&] {
+ RefPtr<nsFrameLoader> frameLoader = mFrameLoader;
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "nsFrameLoaderOwner::SubframeCrashed", [frameLoader]() {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<nsDocShell> docShell =
+ frameLoader->GetDocShell(IgnoreErrors());
+ if (NS_WARN_IF(!docShell)) {
+ return;
+ }
+ bool displayed = false;
+ docShell->DisplayLoadError(NS_ERROR_FRAME_CRASHED, uri,
+ u"about:blank", nullptr, &displayed);
+ }));
+ };
+
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
+ /* inProgress */ false, /* isRemote */ false,
+ /* group */ nullptr, frameLoaderInit, IgnoreErrors());
+}
+
+void nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache(
+ nsFrameLoader* aNewFrameLoader) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache: Replace "
+ "frameloader"));
+
+ Maybe<bool> renderLayers;
+ if (mFrameLoader) {
+ if (auto* oldParent = mFrameLoader->GetBrowserParent()) {
+ renderLayers.emplace(oldParent->GetRenderLayers());
+ }
+ }
+
+ mFrameLoader = aNewFrameLoader;
+
+ if (auto* browserParent = mFrameLoader->GetBrowserParent()) {
+ browserParent->AddWindowListeners();
+ if (renderLayers.isSome()) {
+ browserParent->SetRenderLayers(renderLayers.value());
+ }
+ }
+
+ RefPtr<Element> owner = do_QueryObject(this);
+ ChangeFrameLoaderCommon(owner, /* aRetainPaint = */ false);
+}
+
+void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader* aFrameLoader) {
+ mFrameLoaderList.insertBack(aFrameLoader);
+}
+
+void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader* aFrameLoader) {
+ if (aFrameLoader->isInList()) {
+ MOZ_ASSERT(mFrameLoaderList.contains(aFrameLoader));
+ aFrameLoader->remove();
+ }
+}
+
+void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader* aFrameLoader) {
+ if (aFrameLoader == mFrameLoader) {
+ while (!mFrameLoaderList.isEmpty()) {
+ RefPtr<nsFrameLoader> loader = mFrameLoaderList.popFirst();
+ if (loader != mFrameLoader) {
+ loader->Destroy();
+ }
+ }
+ } else {
+ DetachFrameLoader(aFrameLoader);
+ }
+}
diff --git a/dom/base/nsFrameLoaderOwner.h b/dom/base/nsFrameLoaderOwner.h
new file mode 100644
index 0000000000..2005be5cfd
--- /dev/null
+++ b/dom/base/nsFrameLoaderOwner.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFrameLoaderOwner_h_
+#define nsFrameLoaderOwner_h_
+
+#include <functional>
+#include "nsFrameLoader.h"
+#include "nsISupports.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class BrowsingContext;
+class BrowsingContextGroup;
+class BrowserBridgeChild;
+class ContentParent;
+class Element;
+struct RemotenessOptions;
+struct NavigationIsolationOptions;
+} // namespace dom
+} // namespace mozilla
+
+// IID for the FrameLoaderOwner interface
+#define NS_FRAMELOADEROWNER_IID \
+ { \
+ 0x1b4fd25c, 0x2e57, 0x11e9, { \
+ 0x9e, 0x5a, 0x5b, 0x86, 0xe9, 0x89, 0xa5, 0xc0 \
+ } \
+ }
+
+// Mixin that handles ownership of nsFrameLoader for Frame elements
+// (XULFrameElement, HTMLI/FrameElement, etc...). Manages information when doing
+// FrameLoader swaps.
+//
+// This class is considered an XPCOM mixin. This means that while we inherit
+// from ISupports in order to be QI'able, we expect the classes that inherit
+// nsFrameLoaderOwner to actually implement ISupports for us.
+class nsFrameLoaderOwner : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_FRAMELOADEROWNER_IID)
+
+ nsFrameLoaderOwner() = default;
+ already_AddRefed<nsFrameLoader> GetFrameLoader();
+ void SetFrameLoader(nsFrameLoader* aNewFrameLoader);
+
+ mozilla::dom::BrowsingContext* GetBrowsingContext();
+ mozilla::dom::BrowsingContext* GetExtantBrowsingContext();
+
+ // Destroy (if it exists) and recreate our frameloader, based on new
+ // remoteness requirements.
+ //
+ // This method is called by frontend code when it wants to perform a
+ // remoteness update, and allows for behaviour such as preserving
+ // BrowsingContexts across process switches during navigation.
+ //
+ // See the WebIDL definition for more details.
+ void ChangeRemoteness(const mozilla::dom::RemotenessOptions& aOptions,
+ mozilla::ErrorResult& rv);
+
+ // Like `ChangeRemoteness` but switches to an already-created
+ // `BrowserBridgeChild`. This method is used when performing remote subframe
+ // process switches.
+ void ChangeRemotenessWithBridge(mozilla::dom::BrowserBridgeChild* aBridge,
+ mozilla::ErrorResult& rv);
+
+ // Like `ChangeRemoteness`, but switches into an already-created
+ // `ContentParent`. This method is used when performing toplevel process
+ // switches. If `aContentParent` is nullptr, switches into the parent process.
+ //
+ // If `aReplaceBrowsingContext` is set, BrowsingContext preservation will be
+ // disabled for this process switch.
+ void ChangeRemotenessToProcess(
+ mozilla::dom::ContentParent* aContentParent,
+ const mozilla::dom::NavigationIsolationOptions& aOptions,
+ mozilla::dom::BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv);
+
+ void SubframeCrashed();
+
+ void RestoreFrameLoaderFromBFCache(nsFrameLoader* aNewFrameLoader);
+
+ void UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
+
+ void AttachFrameLoader(nsFrameLoader* aFrameLoader);
+ void DetachFrameLoader(nsFrameLoader* aFrameLoader);
+ void FrameLoaderDestroying(nsFrameLoader* aFrameLoader);
+
+ private:
+ bool UseRemoteSubframes();
+
+ // The enum class for determine how to handle previous BrowsingContext during
+ // the change remoteness. It could be followings
+ // 1. DONT_PRESERVE
+ // Create a whole new BrowsingContext.
+ // 2. PRESERVE
+ // Preserve the previous BrowsingContext.
+ enum class ChangeRemotenessContextType {
+ DONT_PRESERVE = 0,
+ PRESERVE = 1,
+ };
+ ChangeRemotenessContextType ShouldPreserveBrowsingContext(
+ bool aIsRemote, bool aReplaceBrowsingContext);
+
+ void ChangeRemotenessCommon(
+ const ChangeRemotenessContextType& aContextType,
+ const mozilla::dom::NavigationIsolationOptions& aOptions,
+ bool aSwitchingInProgressLoad, bool aIsRemote,
+ mozilla::dom::BrowsingContextGroup* aGroup,
+ std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv);
+
+ void ChangeFrameLoaderCommon(mozilla::dom::Element* aOwner,
+ bool aRetainPaint);
+
+ void UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(
+ mozilla::dom::Element* aOwner);
+
+ protected:
+ virtual ~nsFrameLoaderOwner() = default;
+ RefPtr<nsFrameLoader> mFrameLoader;
+
+ // The list contains all the nsFrameLoaders created for this owner or moved
+ // from another nsFrameLoaderOwner which haven't been destroyed yet.
+ // In particular it contains all the nsFrameLoaders which are in bfcache.
+ mozilla::LinkedList<nsFrameLoader> mFrameLoaderList;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsFrameLoaderOwner, NS_FRAMELOADEROWNER_IID)
+
+#endif // nsFrameLoaderOwner_h_
diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp
new file mode 100644
index 0000000000..97a1ddedf3
--- /dev/null
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -0,0 +1,1657 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFrameMessageManager.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <new>
+#include <utility>
+#include "ContentChild.h"
+#include "ErrorList.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Unused.h"
+#include "base/process_util.h"
+#include "chrome/common/ipc_channel.h"
+#include "js/CallAndConstruct.h" // JS::IsCallable, JS_CallFunctionValue
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/experimental/JSStencil.h"
+#include "js/GCVector.h"
+#include "js/JSON.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScriptPreloader.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/ChildProcessMessageManager.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ContentProcessMessageManager.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+#include "mozilla/dom/MessageListenerManager.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "mozilla/dom/ProcessMessageManager.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/MessageManagerCallback.h"
+#include "mozilla/dom/ipc/SharedMap.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "nsASCIIMask.h"
+#include "nsBaseHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsHashKeys.h"
+#include "nsIChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIInputStream.h"
+#include "nsILoadInfo.h"
+#include "nsIMemoryReporter.h"
+#include "nsIMessageManager.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIXPConnect.h"
+#include "nsJSUtils.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTObserverArray.h"
+#include "nsTPromiseFlatString.h"
+#include "nsTStringRepr.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+#include "xpcpublic.h"
+
+#ifdef XP_WIN
+# if defined(SendMessage)
+# undef SendMessage
+# endif
+#endif
+
+#ifdef FUZZING
+# include "MessageManagerFuzzer.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+struct FrameMessageMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("FrameMessage");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString16View& aMessageName,
+ bool aIsSync) {
+ aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aMessageName));
+ aWriter.BoolProperty("sync", aIsSync);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormatSearchable(
+ "name", "Message Name", MS::Format::String, MS::Searchable::Searchable);
+ schema.AddKeyLabelFormat("sync", "Sync", MS::Format::String);
+ schema.SetTooltipLabel("FrameMessage - {marker.name}");
+ schema.SetTableLabel("{marker.name} - {marker.data.name}");
+ return schema;
+ }
+};
+
+#define CACHE_PREFIX(type) "mm/" type
+
+nsFrameMessageManager::nsFrameMessageManager(MessageManagerCallback* aCallback,
+ MessageManagerFlags aFlags)
+ : mChrome(aFlags & MessageManagerFlags::MM_CHROME),
+ mGlobal(aFlags & MessageManagerFlags::MM_GLOBAL),
+ mIsProcessManager(aFlags & MessageManagerFlags::MM_PROCESSMANAGER),
+ mIsBroadcaster(aFlags & MessageManagerFlags::MM_BROADCASTER),
+ mOwnsCallback(aFlags & MessageManagerFlags::MM_OWNSCALLBACK),
+ mHandlingMessage(false),
+ mClosed(false),
+ mDisconnected(false),
+ mCallback(aCallback) {
+ NS_ASSERTION(!mIsBroadcaster || !mCallback,
+ "Broadcasters cannot have callbacks!");
+ if (mOwnsCallback) {
+ mOwnedCallback = WrapUnique(aCallback);
+ }
+}
+
+nsFrameMessageManager::~nsFrameMessageManager() {
+ for (int32_t i = mChildManagers.Length(); i > 0; --i) {
+ mChildManagers[i - 1]->Disconnect(false);
+ }
+ if (mIsProcessManager) {
+ if (this == sParentProcessManager) {
+ sParentProcessManager = nullptr;
+ }
+ if (this == sChildProcessManager) {
+ sChildProcessManager = nullptr;
+ delete mozilla::dom::SameProcessMessageQueue::Get();
+ }
+ if (this == sSameProcessParentManager) {
+ sSameProcessParentManager = nullptr;
+ }
+ }
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsMessageListenerInfo& aField, const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mStrongListener, aName, aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mWeakListener, aName, aFlags);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedData)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitialProcessData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
+ for (int32_t i = tmp->mChildManagers.Length(); i > 0; --i) {
+ tmp->mChildManagers[i - 1]->Disconnect(false);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharedData)
+ tmp->mInitialProcessData.setNull();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+
+ /* Message managers in child process implement nsIMessageSender.
+ Message managers in the chrome process are
+ either broadcasters (if they have subordinate/child message
+ managers) or they're simple message senders. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageSender,
+ !mChrome || !mIsBroadcaster)
+
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager)
+
+void MessageManagerCallback::DoGetRemoteType(nsACString& aRemoteType,
+ ErrorResult& aError) const {
+ aRemoteType.Truncate();
+ mozilla::dom::ProcessMessageManager* parent = GetProcessMessageManager();
+ if (!parent) {
+ return;
+ }
+
+ parent->GetRemoteType(aRemoteType, aError);
+}
+
+bool MessageManagerCallback::BuildClonedMessageData(
+ StructuredCloneData& aData, ClonedMessageData& aClonedData) {
+ return aData.BuildClonedMessageData(aClonedData);
+}
+
+void mozilla::dom::ipc::UnpackClonedMessageData(
+ const ClonedMessageData& aClonedData, StructuredCloneData& aData) {
+ aData.BorrowFromClonedMessageData(aClonedData);
+}
+
+void nsFrameMessageManager::AddMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener,
+ bool aListenWhenClosed,
+ ErrorResult& aError) {
+ auto* const listeners = mListeners.GetOrInsertNew(aMessageName);
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener && *strongListener == aListener) {
+ return;
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ entry->mStrongListener = &aListener;
+ entry->mListenWhenClosed = aListenWhenClosed;
+}
+
+void nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener,
+ ErrorResult& aError) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessageName);
+ if (listeners) {
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener && *strongListener == aListener) {
+ listeners->RemoveElementAt(i);
+ return;
+ }
+ }
+ }
+}
+
+static already_AddRefed<nsISupports> ToXPCOMMessageListener(
+ MessageListener& aListener) {
+ return CallbackObjectHolder<mozilla::dom::MessageListener, nsISupports>(
+ &aListener)
+ .ToXPCOMCallback();
+}
+
+void nsFrameMessageManager::AddWeakMessageListener(
+ const nsAString& aMessageName, MessageListener& aListener,
+ ErrorResult& aError) {
+ nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+ nsWeakPtr weak = do_GetWeakReference(listener);
+ if (!weak) {
+ aError.Throw(NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+#ifdef DEBUG
+ // It's technically possible that one object X could give two different
+ // nsIWeakReference*'s when you do_GetWeakReference(X). We really don't want
+ // this to happen; it will break e.g. RemoveWeakMessageListener. So let's
+ // check that we're not getting ourselves into that situation.
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener);
+ for (const auto& entry : mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ uint32_t count = listeners->Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsWeakPtr weakListener = listeners->ElementAt(i).mWeakListener;
+ if (weakListener) {
+ nsCOMPtr<nsISupports> otherCanonical = do_QueryReferent(weakListener);
+ MOZ_ASSERT((canonical == otherCanonical) == (weak == weakListener));
+ }
+ }
+ }
+#endif
+
+ auto* const listeners = mListeners.GetOrInsertNew(aMessageName);
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ return;
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ entry->mWeakListener = weak;
+ entry->mListenWhenClosed = false;
+}
+
+void nsFrameMessageManager::RemoveWeakMessageListener(
+ const nsAString& aMessageName, MessageListener& aListener,
+ ErrorResult& aError) {
+ nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+ nsWeakPtr weak = do_GetWeakReference(listener);
+ if (!weak) {
+ aError.Throw(NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessageName);
+ if (!listeners) {
+ return;
+ }
+
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ listeners->RemoveElementAt(i);
+ return;
+ }
+ }
+}
+
+void nsFrameMessageManager::LoadScript(const nsAString& aURL,
+ bool aAllowDelayedLoad,
+ bool aRunInGlobalScope,
+ ErrorResult& aError) {
+ if (aAllowDelayedLoad) {
+ // Cache for future windows or frames
+ mPendingScripts.AppendElement(aURL);
+ mPendingScriptsGlobalStates.AppendElement(aRunInGlobalScope);
+ }
+
+ if (mCallback) {
+#ifdef DEBUG_smaug
+ printf("Will load %s \n", NS_ConvertUTF16toUTF8(aURL).get());
+#endif
+ if (!mCallback->DoLoadMessageManagerScript(aURL, aRunInGlobalScope)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ for (uint32_t i = 0; i < mChildManagers.Length(); ++i) {
+ RefPtr<nsFrameMessageManager> mm = mChildManagers[i];
+ if (mm) {
+ // Use false here, so that child managers don't cache the script, which
+ // is already cached in the parent.
+ mm->LoadScript(aURL, false, aRunInGlobalScope, IgnoreErrors());
+ }
+ }
+}
+
+void nsFrameMessageManager::RemoveDelayedScript(const nsAString& aURL) {
+ for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+ if (mPendingScripts[i] == aURL) {
+ mPendingScripts.RemoveElementAt(i);
+ mPendingScriptsGlobalStates.RemoveElementAt(i);
+ break;
+ }
+ }
+}
+
+void nsFrameMessageManager::GetDelayedScripts(
+ JSContext* aCx, nsTArray<nsTArray<JS::Value>>& aList, ErrorResult& aError) {
+ // Frame message managers may return an incomplete list because scripts
+ // that were loaded after it was connected are not added to the list.
+ if (!IsGlobal() && !IsBroadcaster()) {
+ NS_WARNING(
+ "Cannot retrieve list of pending frame scripts for frame"
+ "message managers as it may be incomplete");
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ aError.MightThrowJSException();
+
+ aList.SetCapacity(mPendingScripts.Length());
+ for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+ JS::Rooted<JS::Value> url(aCx);
+ if (!ToJSValue(aCx, mPendingScripts[i], &url)) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+
+ nsTArray<JS::Value>* array = aList.AppendElement(2);
+ array->AppendElement(url);
+ array->AppendElement(JS::BooleanValue(mPendingScriptsGlobalStates[i]));
+ }
+}
+
+/* static */
+bool nsFrameMessageManager::GetParamsForMessage(JSContext* aCx,
+ const JS::Value& aValue,
+ const JS::Value& aTransfer,
+ StructuredCloneData& aData) {
+ // First try to use structured clone on the whole thing.
+ JS::Rooted<JS::Value> v(aCx, aValue);
+ JS::Rooted<JS::Value> t(aCx, aTransfer);
+ ErrorResult rv;
+ aData.Write(aCx, v, t, JS::CloneDataPolicy(), rv);
+ if (!rv.Failed()) {
+ return true;
+ }
+
+ rv.SuppressException();
+ JS_ClearPendingException(aCx);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (console) {
+ nsAutoString filename;
+ uint32_t lineno = 0, column = 0;
+ nsJSUtils::GetCallingLocation(aCx, filename, &lineno, &column);
+ nsCOMPtr<nsIScriptError> error(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(
+ u"Sending message that cannot be cloned. Are "
+ "you trying to send an XPCOM object?"_ns,
+ filename, u""_ns, lineno, column, nsIScriptError::warningFlag,
+ "chrome javascript"_ns, false /* from private window */,
+ true /* from chrome context */);
+ console->LogMessage(error);
+ }
+
+ // Not clonable, try JSON
+ // Bug 1749037 - This is ugly but currently structured cloning doesn't handle
+ // properly cases when interface is implemented in JS and used
+ // as a dictionary.
+ nsAutoString json;
+ NS_ENSURE_TRUE(
+ nsContentUtils::StringifyJSON(aCx, v, json, UndefinedIsNullStringLiteral),
+ false);
+ NS_ENSURE_TRUE(!json.IsEmpty(), false);
+
+ JS::Rooted<JS::Value> val(aCx, JS::NullValue());
+ NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()),
+ json.Length(), &val),
+ false);
+
+ aData.Write(aCx, val, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ return true;
+}
+
+static bool sSendingSyncMessage = false;
+
+void nsFrameMessageManager::SendSyncMessage(JSContext* aCx,
+ const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ nsTArray<JS::Value>& aResult,
+ ErrorResult& aError) {
+ NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!GetParentManager(),
+ "Should not have parent manager in content!");
+
+ AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+ "nsFrameMessageManager::SendMessage", OTHER, aMessageName);
+ profiler_add_marker("SendSyncMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessageName, true);
+
+ if (sSendingSyncMessage) {
+ // No kind of blocking send should be issued on top of a sync message.
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ StructuredCloneData data;
+ if (!aObj.isUndefined() &&
+ !GetParamsForMessage(aCx, aObj, JS::UndefinedHandleValue, data)) {
+ aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+#ifdef FUZZING
+ if (data.DataLength() > 0) {
+ MessageManagerFuzzer::TryMutate(aCx, aMessageName, &data,
+ JS::UndefinedHandleValue);
+ }
+#endif
+
+ if (!mCallback) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ nsTArray<StructuredCloneData> retval;
+
+ TimeStamp start = TimeStamp::Now();
+ sSendingSyncMessage = true;
+ bool ok = mCallback->DoSendBlockingMessage(aMessageName, data, &retval);
+ sSendingSyncMessage = false;
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ if (latencyMs >= kMinTelemetrySyncMessageManagerLatencyMs) {
+ NS_ConvertUTF16toUTF8 messageName(aMessageName);
+ // NOTE: We need to strip digit characters from the message name in order to
+ // avoid a large number of buckets due to generated names from addons (such
+ // as "ublock:sb:{N}"). See bug 1348113 comment 10.
+ messageName.StripTaggedASCII(ASCIIMask::Mask0to9());
+ Telemetry::Accumulate(Telemetry::IPC_SYNC_MESSAGE_MANAGER_LATENCY_MS,
+ messageName, latencyMs);
+ }
+
+ if (!ok) {
+ return;
+ }
+
+ uint32_t len = retval.Length();
+ aResult.SetCapacity(len);
+ for (uint32_t i = 0; i < len; ++i) {
+ JS::Rooted<JS::Value> ret(aCx);
+ retval[i].Read(aCx, &ret, aError);
+ if (aError.Failed()) {
+ MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
+ return;
+ }
+ aResult.AppendElement(ret);
+ }
+}
+
+nsresult nsFrameMessageManager::DispatchAsyncMessageInternal(
+ JSContext* aCx, const nsAString& aMessage, StructuredCloneData& aData) {
+ if (mIsBroadcaster) {
+ uint32_t len = mChildManagers.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mChildManagers[i]->DispatchAsyncMessageInternal(aCx, aMessage, aData);
+ }
+ return NS_OK;
+ }
+
+ if (!mCallback) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = mCallback->DoSendAsyncMessage(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void nsFrameMessageManager::DispatchAsyncMessage(
+ JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
+ JS::Handle<JS::Value> aTransfers, ErrorResult& aError) {
+ StructuredCloneData data;
+ if (!aObj.isUndefined() &&
+ !GetParamsForMessage(aCx, aObj, aTransfers, data)) {
+ aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+ profiler_add_marker("SendAsyncMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessageName, false);
+
+#ifdef FUZZING
+ if (data.DataLength()) {
+ MessageManagerFuzzer::TryMutate(aCx, aMessageName, &data, aTransfers);
+ }
+#endif
+
+ aError = DispatchAsyncMessageInternal(aCx, aMessageName, data);
+}
+
+class MMListenerRemover {
+ public:
+ explicit MMListenerRemover(nsFrameMessageManager* aMM)
+ : mWasHandlingMessage(aMM->mHandlingMessage), mMM(aMM) {
+ mMM->mHandlingMessage = true;
+ }
+ ~MMListenerRemover() {
+ if (!mWasHandlingMessage) {
+ mMM->mHandlingMessage = false;
+ if (mMM->mDisconnected) {
+ mMM->mListeners.Clear();
+ }
+ }
+ }
+
+ bool mWasHandlingMessage;
+ RefPtr<nsFrameMessageManager> mMM;
+};
+
+void nsFrameMessageManager::ReceiveMessage(
+ nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader, bool aTargetClosed,
+ const nsAString& aMessage, bool aIsSync, StructuredCloneData* aCloneData,
+ nsTArray<StructuredCloneData>* aRetVal, ErrorResult& aError) {
+ MOZ_ASSERT(aTarget);
+ profiler_add_marker("ReceiveMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessage, aIsSync);
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessage);
+ if (listeners) {
+ MMListenerRemover lr(this);
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>::EndLimitedIterator iter(
+ *listeners);
+ while (iter.HasMore()) {
+ nsMessageListenerInfo& listener = iter.GetNext();
+ // Remove mListeners[i] if it's an expired weak listener.
+ nsCOMPtr<nsISupports> weakListener;
+ if (listener.mWeakListener) {
+ weakListener = do_QueryReferent(listener.mWeakListener);
+ if (!weakListener) {
+ iter.Remove();
+ continue;
+ }
+ }
+
+ if (!listener.mListenWhenClosed && aTargetClosed) {
+ continue;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JSObject*> object(rcx);
+ JS::Rooted<JSObject*> objectGlobal(rcx);
+
+ RefPtr<MessageListener> webIDLListener;
+ if (!weakListener) {
+ webIDLListener = listener.mStrongListener;
+ object = webIDLListener->CallbackOrNull();
+ objectGlobal = webIDLListener->CallbackGlobalOrNull();
+ } else {
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS =
+ do_QueryInterface(weakListener);
+ if (!wrappedJS) {
+ continue;
+ }
+
+ object = wrappedJS->GetJSObject();
+ objectGlobal = wrappedJS->GetJSObjectGlobal();
+ }
+
+ if (!object) {
+ continue;
+ }
+
+ AutoEntryScript aes(js::UncheckedUnwrap(object),
+ "message manager handler");
+ JSContext* cx = aes.cx();
+
+ // We passed the unwrapped object to AutoEntryScript so we now need to
+ // enter the realm of the global object that represents the realm of our
+ // callback.
+ JSAutoRealm ar(cx, objectGlobal);
+
+ RootedDictionary<ReceiveMessageArgument> argument(cx);
+
+ JS::Rooted<JS::Value> json(cx, JS::NullValue());
+ if (aCloneData && aCloneData->DataLength()) {
+ aCloneData->Read(cx, &json, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ aError.SuppressException();
+ JS_ClearPendingException(cx);
+ return;
+ }
+ }
+ argument.mData = json;
+ argument.mJson = json;
+
+ // Get cloned MessagePort from StructuredCloneData.
+ if (aCloneData) {
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!aCloneData->TakeTransferredPortsAsSequence(ports)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ argument.mPorts.Construct(std::move(ports));
+ }
+
+ argument.mName = aMessage;
+ argument.mSync = aIsSync;
+ argument.mTarget = aTarget;
+ if (aTargetFrameLoader) {
+ argument.mTargetFrameLoader.Construct(*aTargetFrameLoader);
+ }
+
+ JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue());
+
+ if (JS::IsCallable(object)) {
+ // A small hack to get 'this' value right on content side where
+ // messageManager is wrapped in BrowserChildMessageManager's global.
+ nsCOMPtr<nsISupports> defaultThisValue;
+ if (mChrome) {
+ defaultThisValue = do_QueryObject(this);
+ } else {
+ defaultThisValue = aTarget;
+ }
+ js::AssertSameCompartment(cx, object);
+ aError = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
+ if (aError.Failed()) {
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());
+ if (webIDLListener) {
+ webIDLListener->ReceiveMessage(thisValue, argument, &rval, aError);
+ if (aError.Failed()) {
+ // At this point the call to ReceiveMessage will have reported any
+ // exceptions (we kept the default of eReportExceptions). We suppress
+ // the failure in the ErrorResult and continue.
+ aError.SuppressException();
+ continue;
+ }
+ } else {
+ JS::Rooted<JS::Value> funval(cx);
+ if (JS::IsCallable(object)) {
+ // If the listener is a JS function:
+ funval.setObject(*object);
+ } else {
+ // If the listener is a JS object which has receiveMessage function:
+ if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
+ !funval.isObject()) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Check if the object is even callable.
+ if (!JS::IsCallable(&funval.toObject())) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ thisValue.setObject(*object);
+ }
+
+ JS::Rooted<JS::Value> argv(cx);
+ if (!ToJSValue(cx, argument, &argv)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ {
+ JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
+ js::AssertSameCompartment(cx, thisObject);
+ if (!JS_CallFunctionValue(cx, thisObject, funval,
+ JS::HandleValueArray(argv), &rval)) {
+ // Because the AutoEntryScript is inside the loop this continue will
+ // make us report any exceptions (after which we'll move on to the
+ // next listener).
+ continue;
+ }
+ }
+ }
+
+ if (aRetVal) {
+ StructuredCloneData* data = aRetVal->AppendElement();
+ data->InitScope(JS::StructuredCloneScope::DifferentProcess);
+ data->Write(cx, rval, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ aRetVal->RemoveLastElement();
+ nsString msg =
+ aMessage + nsLiteralString(
+ u": message reply cannot be cloned. Are "
+ "you trying to send an XPCOM object?");
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (console) {
+ nsCOMPtr<nsIScriptError> error(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(msg, u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ "chrome javascript"_ns, false /* from private window */,
+ true /* from chrome context */);
+ console->LogMessage(error);
+ }
+
+ JS_ClearPendingException(cx);
+ continue;
+ }
+ }
+ }
+ }
+
+ RefPtr<nsFrameMessageManager> kungFuDeathGrip = GetParentManager();
+ if (kungFuDeathGrip) {
+ kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader, aTargetClosed,
+ aMessage, aIsSync, aCloneData, aRetVal,
+ aError);
+ }
+}
+
+void nsFrameMessageManager::LoadPendingScripts(
+ nsFrameMessageManager* aManager, nsFrameMessageManager* aChildMM) {
+ // We have parent manager if we're a message broadcaster.
+ // In that case we want to load the pending scripts from all parent
+ // message managers in the hierarchy. Process the parent first so
+ // that pending scripts higher up in the hierarchy are loaded before others.
+ nsFrameMessageManager* parentManager = aManager->GetParentManager();
+ if (parentManager) {
+ LoadPendingScripts(parentManager, aChildMM);
+ }
+
+ for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) {
+ aChildMM->LoadScript(aManager->mPendingScripts[i], false,
+ aManager->mPendingScriptsGlobalStates[i],
+ IgnoreErrors());
+ }
+}
+
+void nsFrameMessageManager::LoadPendingScripts() {
+ RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
+ LoadPendingScripts(this, this);
+}
+
+void nsFrameMessageManager::SetCallback(MessageManagerCallback* aCallback) {
+ MOZ_ASSERT(!mIsBroadcaster || !mCallback,
+ "Broadcasters cannot have callbacks!");
+ if (aCallback && mCallback != aCallback) {
+ mCallback = aCallback;
+ if (mOwnsCallback) {
+ mOwnedCallback = WrapUnique(aCallback);
+ }
+ }
+}
+
+void nsFrameMessageManager::Close() {
+ if (!mClosed) {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(this, "message-manager-close", nullptr);
+ }
+ }
+ mClosed = true;
+ mCallback = nullptr;
+ mOwnedCallback = nullptr;
+}
+
+void nsFrameMessageManager::Disconnect(bool aRemoveFromParent) {
+ // Notify message-manager-close if we haven't already.
+ Close();
+
+ if (!mDisconnected) {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(this, "message-manager-disconnect", nullptr);
+ }
+ }
+
+ ClearParentManager(aRemoveFromParent);
+
+ mDisconnected = true;
+ if (!mHandlingMessage) {
+ mListeners.Clear();
+ }
+}
+
+void nsFrameMessageManager::SetInitialProcessData(
+ JS::Handle<JS::Value> aInitialData) {
+ MOZ_ASSERT(!mChrome);
+ MOZ_ASSERT(mIsProcessManager);
+ MOZ_ASSERT(aInitialData.isObject());
+ mInitialProcessData = aInitialData;
+}
+
+void nsFrameMessageManager::GetInitialProcessData(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aInitialProcessData,
+ ErrorResult& aError) {
+ MOZ_ASSERT(mIsProcessManager);
+ MOZ_ASSERT_IF(mChrome, IsBroadcaster());
+
+ JS::Rooted<JS::Value> init(aCx, mInitialProcessData);
+ if (mChrome && init.isUndefined()) {
+ // We create the initial object in the junk scope. If we created it in a
+ // normal realm, that realm would leak until shutdown.
+ JS::Rooted<JSObject*> global(aCx, xpc::PrivilegedJunkScope());
+ JSAutoRealm ar(aCx, global);
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+
+ mInitialProcessData.setObject(*obj);
+ init.setObject(*obj);
+ }
+
+ if (!mChrome && XRE_IsParentProcess()) {
+ // This is the cpmm in the parent process. We should use the same object as
+ // the ppmm. Create it first through do_GetService and use the cached
+ // pointer in sParentProcessManager.
+ nsCOMPtr<nsISupports> ppmm =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ sParentProcessManager->GetInitialProcessData(aCx, &init, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ mInitialProcessData = init;
+ }
+
+ if (!JS_WrapValue(aCx, &init)) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+ aInitialProcessData.set(init);
+}
+
+WritableSharedMap* nsFrameMessageManager::SharedData() {
+ if (!mChrome || !mIsProcessManager) {
+ MOZ_ASSERT(false, "Should only call this binding method on ppmm");
+ return nullptr;
+ }
+ if (!mSharedData) {
+ mSharedData = new WritableSharedMap();
+ }
+ return mSharedData;
+}
+
+already_AddRefed<ProcessMessageManager>
+nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError) {
+ RefPtr<ProcessMessageManager> pmm;
+ if (mCallback) {
+ pmm = mCallback->GetProcessMessageManager();
+ }
+ return pmm.forget();
+}
+
+void nsFrameMessageManager::GetRemoteType(nsACString& aRemoteType,
+ ErrorResult& aError) const {
+ aRemoteType.Truncate();
+ if (mCallback) {
+ mCallback->DoGetRemoteType(aRemoteType, aError);
+ }
+}
+
+namespace {
+
+struct MessageManagerReferentCount {
+ MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
+ size_t mStrong;
+ size_t mWeakAlive;
+ size_t mWeakDead;
+ nsTArray<nsString> mSuspectMessages;
+ nsTHashMap<nsStringHashKey, uint32_t> mMessageCounter;
+};
+
+} // namespace
+
+namespace mozilla::dom {
+
+class MessageManagerReporter final : public nsIMemoryReporter {
+ ~MessageManagerReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ static const size_t kSuspectReferentCount = 300;
+
+ protected:
+ void CountReferents(nsFrameMessageManager* aMessageManager,
+ MessageManagerReferentCount* aReferentCount);
+};
+
+NS_IMPL_ISUPPORTS(MessageManagerReporter, nsIMemoryReporter)
+
+void MessageManagerReporter::CountReferents(
+ nsFrameMessageManager* aMessageManager,
+ MessageManagerReferentCount* aReferentCount) {
+ for (const auto& entry : aMessageManager->mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ uint32_t listenerCount = listeners->Length();
+ if (listenerCount == 0) {
+ continue;
+ }
+
+ nsString key(entry.GetKey());
+ const uint32_t currentCount =
+ (aReferentCount->mMessageCounter.LookupOrInsert(key, 0) +=
+ listenerCount);
+
+ // Keep track of messages that have a suspiciously large
+ // number of referents (symptom of leak).
+ if (currentCount >= MessageManagerReporter::kSuspectReferentCount) {
+ aReferentCount->mSuspectMessages.AppendElement(key);
+ }
+
+ for (uint32_t i = 0; i < listenerCount; ++i) {
+ const nsMessageListenerInfo& listenerInfo = listeners->ElementAt(i);
+ if (listenerInfo.mWeakListener) {
+ nsCOMPtr<nsISupports> referent =
+ do_QueryReferent(listenerInfo.mWeakListener);
+ if (referent) {
+ aReferentCount->mWeakAlive++;
+ } else {
+ aReferentCount->mWeakDead++;
+ }
+ } else {
+ aReferentCount->mStrong++;
+ }
+ }
+ }
+
+ // Add referent count in child managers because the listeners
+ // participate in messages dispatched from parent message manager.
+ for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); ++i) {
+ RefPtr<nsFrameMessageManager> mm = aMessageManager->mChildManagers[i];
+ CountReferents(mm, aReferentCount);
+ }
+}
+
+static void ReportReferentCount(
+ const char* aManagerType, const MessageManagerReferentCount& aReferentCount,
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData) {
+#define REPORT(_path, _amount, _desc) \
+ do { \
+ aHandleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_OTHER, \
+ nsIMemoryReporter::UNITS_COUNT, _amount, _desc, \
+ aData); \
+ } while (0)
+
+ REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType),
+ aReferentCount.mStrong,
+ nsPrintfCString("The number of strong referents held by the message "
+ "manager in the %s manager.",
+ aManagerType));
+ REPORT(
+ nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType),
+ aReferentCount.mWeakAlive,
+ nsPrintfCString("The number of weak referents that are still alive "
+ "held by the message manager in the %s manager.",
+ aManagerType));
+ REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType),
+ aReferentCount.mWeakDead,
+ nsPrintfCString("The number of weak referents that are dead "
+ "held by the message manager in the %s manager.",
+ aManagerType));
+
+ for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) {
+ const uint32_t totalReferentCount =
+ aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i]);
+ NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]);
+ REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)",
+ aManagerType, suspect.get()),
+ totalReferentCount,
+ nsPrintfCString("A message in the %s message manager with a "
+ "suspiciously large number of referents (symptom "
+ "of a leak).",
+ aManagerType));
+ }
+
+#undef REPORT
+}
+
+static StaticRefPtr<ChromeMessageBroadcaster> sGlobalMessageManager;
+
+NS_IMETHODIMP
+MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ if (XRE_IsParentProcess() && sGlobalMessageManager) {
+ MessageManagerReferentCount count;
+ CountReferents(sGlobalMessageManager, &count);
+ ReportReferentCount("global-manager", count, aHandleReport, aData);
+ }
+
+ if (nsFrameMessageManager::sParentProcessManager) {
+ MessageManagerReferentCount count;
+ CountReferents(nsFrameMessageManager::sParentProcessManager, &count);
+ ReportReferentCount("parent-process-manager", count, aHandleReport, aData);
+ }
+
+ if (nsFrameMessageManager::sChildProcessManager) {
+ MessageManagerReferentCount count;
+ CountReferents(nsFrameMessageManager::sChildProcessManager, &count);
+ ReportReferentCount("child-process-manager", count, aHandleReport, aData);
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
+
+already_AddRefed<ChromeMessageBroadcaster>
+nsFrameMessageManager::GetGlobalMessageManager() {
+ RefPtr<ChromeMessageBroadcaster> mm;
+ if (sGlobalMessageManager) {
+ mm = sGlobalMessageManager;
+ } else {
+ sGlobalMessageManager = mm =
+ new ChromeMessageBroadcaster(MessageManagerFlags::MM_GLOBAL);
+ ClearOnShutdown(&sGlobalMessageManager);
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ }
+ return mm.forget();
+}
+
+nsresult NS_NewGlobalMessageManager(nsISupports** aResult) {
+ *aResult = nsFrameMessageManager::GetGlobalMessageManager().take();
+ return NS_OK;
+}
+
+nsTHashMap<nsStringHashKey, nsMessageManagerScriptHolder*>*
+ nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
+StaticRefPtr<nsScriptCacheCleaner>
+ nsMessageManagerScriptExecutor::sScriptCacheCleaner;
+
+void nsMessageManagerScriptExecutor::DidCreateScriptLoader() {
+ if (!sCachedScripts) {
+ sCachedScripts =
+ new nsTHashMap<nsStringHashKey, nsMessageManagerScriptHolder*>;
+ sScriptCacheCleaner = new nsScriptCacheCleaner();
+ }
+}
+
+// static
+void nsMessageManagerScriptExecutor::PurgeCache() {
+ if (sCachedScripts) {
+ NS_ASSERTION(sCachedScripts != nullptr, "Need cached scripts");
+ for (auto iter = sCachedScripts->Iter(); !iter.Done(); iter.Next()) {
+ delete iter.Data();
+ iter.Remove();
+ }
+ }
+}
+
+// static
+void nsMessageManagerScriptExecutor::Shutdown() {
+ if (sCachedScripts) {
+ PurgeCache();
+
+ delete sCachedScripts;
+ sCachedScripts = nullptr;
+ sScriptCacheCleaner = nullptr;
+ }
+}
+
+static void FillCompileOptionsForCachedStencil(JS::CompileOptions& aOptions) {
+ ScriptPreloader::FillCompileOptionsForCachedStencil(aOptions);
+ aOptions.setNonSyntacticScope(true);
+}
+
+void nsMessageManagerScriptExecutor::LoadScriptInternal(
+ JS::Handle<JSObject*> aMessageManager, const nsAString& aURL,
+ bool aRunInUniqueScope) {
+ AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+ "nsMessageManagerScriptExecutor::LoadScriptInternal", OTHER, aURL);
+
+ if (!sCachedScripts) {
+ return;
+ }
+
+ RefPtr<JS::Stencil> stencil;
+ nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL);
+ if (holder) {
+ stencil = holder->mStencil;
+ } else {
+ stencil =
+ TryCacheLoadAndCompileScript(aURL, aRunInUniqueScope, aMessageManager);
+ }
+
+ AutoEntryScript aes(aMessageManager, "message manager script load");
+ JSContext* cx = aes.cx();
+ if (stencil) {
+ JS::CompileOptions options(cx);
+ FillCompileOptionsForCachedStencil(options);
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::Rooted<JSScript*> script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+
+ if (script) {
+ if (aRunInUniqueScope) {
+ JS::Rooted<JSObject*> scope(cx);
+ bool ok = js::ExecuteInFrameScriptEnvironment(cx, aMessageManager,
+ script, &scope);
+ if (ok) {
+ // Force the scope to stay alive.
+ mAnonymousGlobalScopes.AppendElement(scope);
+ }
+ } else {
+ JS::Rooted<JS::Value> rval(cx);
+ JS::RootedVector<JSObject*> envChain(cx);
+ if (!envChain.append(aMessageManager)) {
+ return;
+ }
+ Unused << JS_ExecuteScript(cx, envChain, script, &rval);
+ }
+ }
+ }
+}
+
+already_AddRefed<JS::Stencil>
+nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
+ const nsAString& aURL, bool aRunInUniqueScope,
+ JS::Handle<JSObject*> aMessageManager) {
+ nsCString url = NS_ConvertUTF16toUTF8(aURL);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ bool hasFlags;
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &hasFlags);
+ if (NS_FAILED(rv) || !hasFlags) {
+ NS_WARNING("Will not load a frame script!");
+ return nullptr;
+ }
+
+ // If this script won't be cached, or there is only one of this type of
+ // message manager per process, treat this script as run-once. Run-once
+ // scripts can be compiled directly for the target global, and will be dropped
+ // from the preloader cache after they're executed and serialized.
+ //
+ // NOTE: This does not affect the JS::CompileOptions. We generate the same
+ // bytecode as though it were run multiple times. This is required for the
+ // batch decoding from ScriptPreloader to work.
+ bool isRunOnce = IsProcessScoped();
+
+ // We don't cache data: scripts!
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ bool isCacheable = !scheme.EqualsLiteral("data");
+ bool useScriptPreloader = isCacheable;
+
+ // If the script will be reused in this session, compile it in the compilation
+ // scope instead of the current global to avoid keeping the current
+ // compartment alive.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(isRunOnce ? aMessageManager : xpc::CompilationScope())) {
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ RefPtr<JS::Stencil> stencil;
+ if (useScriptPreloader) {
+ nsAutoCString cachePath;
+ rv = scache::PathifyURI(CACHE_PREFIX("script"), uri, cachePath);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ JS::DecodeOptions decodeOptions;
+ ScriptPreloader::FillDecodeOptionsForCachedStencil(decodeOptions);
+ stencil = ScriptPreloader::GetChildSingleton().GetCachedStencil(
+ cx, decodeOptions, cachePath);
+ }
+
+ if (!stencil) {
+ nsCOMPtr<nsIChannel> channel;
+ NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT);
+
+ if (!channel) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> input;
+ rv = channel->Open(getter_AddRefs(input));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsString dataString;
+ Utf8Unit* dataStringBuf = nullptr;
+ size_t dataStringLength = 0;
+ if (input) {
+ nsCString buffer;
+ uint64_t written;
+ if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, -1, &written))) {
+ return nullptr;
+ }
+
+ uint32_t size = (uint32_t)std::min(written, (uint64_t)UINT32_MAX);
+ ScriptLoader::ConvertToUTF8(channel, (uint8_t*)buffer.get(), size, u""_ns,
+ nullptr, dataStringBuf, dataStringLength);
+ }
+
+ if (!dataStringBuf) {
+ return nullptr;
+ }
+
+ JS::CompileOptions options(cx);
+ FillCompileOptionsForCachedStencil(options);
+ options.setFileAndLine(url.get(), 1);
+
+ // If we are not encoding to the ScriptPreloader cache, we can now relax the
+ // compile options and use the JS syntax-parser for lower latency.
+ if (!useScriptPreloader || !ScriptPreloader::GetChildSingleton().Active()) {
+ options.setSourceIsLazy(false);
+ }
+
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, dataStringBuf, dataStringLength,
+ JS::SourceOwnership::TakeOwnership)) {
+ return nullptr;
+ }
+
+ stencil = JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ if (!stencil) {
+ return nullptr;
+ }
+
+ if (isCacheable && !isRunOnce) {
+ // Store into our cache only when we compile it here.
+ auto* holder = new nsMessageManagerScriptHolder(stencil);
+ sCachedScripts->InsertOrUpdate(aURL, holder);
+ }
+
+#ifdef DEBUG
+ // The above shouldn't touch any options for instantiation.
+ JS::InstantiateOptions instantiateOptions(options);
+ instantiateOptions.assertDefault();
+#endif
+ }
+
+ MOZ_ASSERT(stencil);
+
+ if (useScriptPreloader) {
+ nsAutoCString cachePath;
+ rv = scache::PathifyURI(CACHE_PREFIX("script"), uri, cachePath);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ ScriptPreloader::GetChildSingleton().NoteStencil(url, cachePath, stencil,
+ isRunOnce);
+ }
+
+ return stencil.forget();
+}
+
+void nsMessageManagerScriptExecutor::Trace(const TraceCallbacks& aCallbacks,
+ void* aClosure) {
+ for (size_t i = 0, length = mAnonymousGlobalScopes.Length(); i < length;
+ ++i) {
+ aCallbacks.Trace(&mAnonymousGlobalScopes[i], "mAnonymousGlobalScopes[i]",
+ aClosure);
+ }
+}
+
+void nsMessageManagerScriptExecutor::Unlink() {
+ ImplCycleCollectionUnlink(mAnonymousGlobalScopes);
+}
+
+bool nsMessageManagerScriptExecutor::Init() {
+ DidCreateScriptLoader();
+ return true;
+}
+
+void nsMessageManagerScriptExecutor::MarkScopesForCC() {
+ for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
+ mAnonymousGlobalScopes[i].exposeToActiveJS();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
+
+ChildProcessMessageManager* nsFrameMessageManager::sChildProcessManager =
+ nullptr;
+ParentProcessMessageManager* nsFrameMessageManager::sParentProcessManager =
+ nullptr;
+nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager =
+ nullptr;
+
+class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
+ public Runnable {
+ public:
+ nsAsyncMessageToSameProcessChild()
+ : nsSameProcessAsyncMessageBase(),
+ mozilla::Runnable("nsAsyncMessageToSameProcessChild") {}
+ NS_IMETHOD Run() override {
+ nsFrameMessageManager* ppm =
+ nsFrameMessageManager::GetChildProcessManager();
+ ReceiveMessage(ppm, nullptr, ppm);
+ return NS_OK;
+ }
+};
+
+/**
+ * Send messages to an imaginary child process in a single-process scenario.
+ */
+class SameParentProcessMessageManagerCallback : public MessageManagerCallback {
+ public:
+ SameParentProcessMessageManagerCallback() {
+ MOZ_COUNT_CTOR(SameParentProcessMessageManagerCallback);
+ }
+ ~SameParentProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(SameParentProcessMessageManagerCallback);
+ }
+
+ bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) override {
+ auto* global = ContentProcessMessageManager::Get();
+ MOZ_ASSERT(!aRunInGlobalScope);
+ global->LoadScript(aURL);
+ return true;
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ RefPtr<nsAsyncMessageToSameProcessChild> ev =
+ new nsAsyncMessageToSameProcessChild();
+
+ nsresult rv = ev->Init(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+ }
+};
+
+/**
+ * Send messages to the parent process.
+ */
+class ChildProcessMessageManagerCallback : public MessageManagerCallback {
+ public:
+ ChildProcessMessageManagerCallback() {
+ MOZ_COUNT_CTOR(ChildProcessMessageManagerCallback);
+ }
+ ~ChildProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(ChildProcessMessageManagerCallback);
+ }
+
+ bool DoSendBlockingMessage(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) override {
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return true;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageData(aData, data)) {
+ return false;
+ }
+ return cc->SendSyncMessage(PromiseFlatString(aMessage), data, aRetVal);
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return NS_OK;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageData(aData, data)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+ if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), data)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+ }
+};
+
+class nsAsyncMessageToSameProcessParent
+ : public nsSameProcessAsyncMessageBase,
+ public SameProcessMessageQueue::Runnable {
+ public:
+ nsAsyncMessageToSameProcessParent() : nsSameProcessAsyncMessageBase() {}
+ nsresult HandleMessage() override {
+ nsFrameMessageManager* ppm =
+ nsFrameMessageManager::sSameProcessParentManager;
+ ReceiveMessage(ppm, nullptr, ppm);
+ return NS_OK;
+ }
+};
+
+/**
+ * Send messages to the imaginary parent process in a single-process scenario.
+ */
+class SameChildProcessMessageManagerCallback : public MessageManagerCallback {
+ public:
+ SameChildProcessMessageManagerCallback() {
+ MOZ_COUNT_CTOR(SameChildProcessMessageManagerCallback);
+ }
+ ~SameChildProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(SameChildProcessMessageManagerCallback);
+ }
+
+ bool DoSendBlockingMessage(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) override {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->Flush();
+
+ if (nsFrameMessageManager::sSameProcessParentManager) {
+ RefPtr<nsFrameMessageManager> ppm =
+ nsFrameMessageManager::sSameProcessParentManager;
+ ppm->ReceiveMessage(ppm, nullptr, aMessage, true, &aData, aRetVal,
+ IgnoreErrors());
+ }
+ return true;
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ RefPtr<nsAsyncMessageToSameProcessParent> ev =
+ new nsAsyncMessageToSameProcessParent();
+ nsresult rv = ev->Init(aMessage, aData);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ queue->Push(ev);
+ return NS_OK;
+ }
+};
+
+// This creates the global parent process message manager.
+nsresult NS_NewParentProcessMessageManager(nsISupports** aResult) {
+ NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager,
+ "Re-creating sParentProcessManager");
+ RefPtr<ParentProcessMessageManager> mm = new ParentProcessMessageManager();
+ nsFrameMessageManager::sParentProcessManager = mm;
+ nsFrameMessageManager::NewProcessMessageManager(
+ false); // Create same process message manager.
+ mm.forget(aResult);
+ return NS_OK;
+}
+
+ProcessMessageManager* nsFrameMessageManager::NewProcessMessageManager(
+ bool aIsRemote) {
+ if (!nsFrameMessageManager::sParentProcessManager) {
+ nsCOMPtr<nsISupports> dummy =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ }
+
+ MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager,
+ "parent process manager not created");
+ ProcessMessageManager* mm;
+ if (aIsRemote) {
+ // Callback is set in ContentParent::InitInternal so that the process has
+ // already started when we send pending scripts.
+ mm = new ProcessMessageManager(
+ nullptr, nsFrameMessageManager::sParentProcessManager);
+ } else {
+ mm =
+ new ProcessMessageManager(new SameParentProcessMessageManagerCallback(),
+ nsFrameMessageManager::sParentProcessManager,
+ MessageManagerFlags::MM_OWNSCALLBACK);
+ mm->SetOsPid(base::GetCurrentProcId());
+ sSameProcessParentManager = mm;
+ }
+ return mm;
+}
+
+nsresult NS_NewChildProcessMessageManager(nsISupports** aResult) {
+ NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(),
+ "Re-creating sChildProcessManager");
+
+ MessageManagerCallback* cb;
+ if (XRE_IsParentProcess()) {
+ cb = new SameChildProcessMessageManagerCallback();
+ } else {
+ cb = new ChildProcessMessageManagerCallback();
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ }
+ auto* mm = new ChildProcessMessageManager(cb);
+ nsFrameMessageManager::SetChildProcessManager(mm);
+ auto global = MakeRefPtr<ContentProcessMessageManager>(mm);
+ NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED);
+ return CallQueryInterface(global, aResult);
+}
+
+void nsFrameMessageManager::MarkForCC() {
+ for (const auto& entry : mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ uint32_t count = listeners->Length();
+ for (uint32_t i = 0; i < count; i++) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener) {
+ strongListener->MarkForCC();
+ }
+ }
+ }
+
+ if (mRefCnt.IsPurple()) {
+ mRefCnt.RemovePurple();
+ }
+}
+
+nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase()
+#ifdef DEBUG
+ : mCalledInit(false)
+#endif
+{
+}
+
+nsresult nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage,
+ StructuredCloneData& aData) {
+ if (!mData.Copy(aData)) {
+ Telemetry::Accumulate(Telemetry::IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB,
+ aData.DataLength());
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mMessage = aMessage;
+#ifdef DEBUG
+ mCalledInit = true;
+#endif
+
+ return NS_OK;
+}
+
+void nsSameProcessAsyncMessageBase::ReceiveMessage(
+ nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+ nsFrameMessageManager* aManager) {
+ // Make sure that we have called Init() and it has succeeded.
+ MOZ_ASSERT(mCalledInit);
+ if (aManager) {
+ RefPtr<nsFrameMessageManager> mm = aManager;
+ mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
+ nullptr, IgnoreErrors());
+ }
+}
diff --git a/dom/base/nsFrameMessageManager.h b/dom/base/nsFrameMessageManager.h
new file mode 100644
index 0000000000..3bcecf9c9d
--- /dev/null
+++ b/dom/base/nsFrameMessageManager.h
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFrameMessageManager_h__
+#define nsFrameMessageManager_h__
+
+#include <cstdint>
+#include <string.h>
+#include <utility>
+#include "ErrorList.h"
+#include "js/experimental/JSStencil.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsIMessageManager.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupports.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+#include "nscore.h"
+
+class nsFrameLoader;
+class nsIRunnable;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class ChildProcessMessageManager;
+class ChromeMessageBroadcaster;
+class ClonedMessageData;
+class MessageBroadcaster;
+class MessageListener;
+class MessageListenerManager;
+class MessageManagerReporter;
+class ParentProcessMessageManager;
+class ProcessMessageManager;
+
+namespace ipc {
+
+class MessageManagerCallback;
+class WritableSharedMap;
+
+// Note: we round the time we spend to the nearest millisecond. So a min value
+// of 1 ms actually captures from 500us and above.
+static const uint32_t kMinTelemetrySyncMessageManagerLatencyMs = 1;
+
+enum class MessageManagerFlags {
+ MM_NONE = 0,
+ MM_CHROME = 1,
+ MM_GLOBAL = 2,
+ MM_PROCESSMANAGER = 4,
+ MM_BROADCASTER = 8,
+ MM_OWNSCALLBACK = 16
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MessageManagerFlags);
+
+void UnpackClonedMessageData(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData);
+
+} // namespace ipc
+} // namespace dom
+} // namespace mozilla
+
+struct nsMessageListenerInfo {
+ bool operator==(const nsMessageListenerInfo& aOther) const {
+ return &aOther == this;
+ }
+
+ // If mWeakListener is null then mStrongListener holds a MessageListener.
+ // If mWeakListener is non-null then mStrongListener contains null.
+ RefPtr<mozilla::dom::MessageListener> mStrongListener;
+ nsWeakPtr mWeakListener;
+ bool mListenWhenClosed;
+};
+
+class nsFrameMessageManager : public nsIMessageSender {
+ friend class mozilla::dom::MessageManagerReporter;
+ using StructuredCloneData = mozilla::dom::ipc::StructuredCloneData;
+
+ protected:
+ using MessageManagerFlags = mozilla::dom::ipc::MessageManagerFlags;
+
+ nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
+ MessageManagerFlags aFlags);
+
+ virtual ~nsFrameMessageManager();
+
+ public:
+ explicit nsFrameMessageManager(
+ mozilla::dom::ipc::MessageManagerCallback* aCallback)
+ : nsFrameMessageManager(aCallback, MessageManagerFlags::MM_NONE) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsFrameMessageManager)
+
+ void MarkForCC();
+
+ // MessageListenerManager
+ void AddMessageListener(const nsAString& aMessageName,
+ mozilla::dom::MessageListener& aListener,
+ bool aListenWhenClosed, mozilla::ErrorResult& aError);
+ void RemoveMessageListener(const nsAString& aMessageName,
+ mozilla::dom::MessageListener& aListener,
+ mozilla::ErrorResult& aError);
+ void AddWeakMessageListener(const nsAString& aMessageName,
+ mozilla::dom::MessageListener& aListener,
+ mozilla::ErrorResult& aError);
+ void RemoveWeakMessageListener(const nsAString& aMessageName,
+ mozilla::dom::MessageListener& aListener,
+ mozilla::ErrorResult& aError);
+
+ // MessageSender
+ void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ JS::Handle<JS::Value> aTransfers,
+ mozilla::ErrorResult& aError) {
+ DispatchAsyncMessage(aCx, aMessageName, aObj, aTransfers, aError);
+ }
+ already_AddRefed<mozilla::dom::ProcessMessageManager>
+ GetProcessMessageManager(mozilla::ErrorResult& aError);
+ void GetRemoteType(nsACString& aRemoteType,
+ mozilla::ErrorResult& aError) const;
+
+ // SyncMessageSender
+ void SendSyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj, nsTArray<JS::Value>& aResult,
+ mozilla::ErrorResult& aError);
+
+ // GlobalProcessScriptLoader
+ void GetInitialProcessData(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aInitialProcessData,
+ mozilla::ErrorResult& aError);
+
+ mozilla::dom::ipc::WritableSharedMap* SharedData();
+
+ NS_DECL_NSIMESSAGESENDER
+
+ static mozilla::dom::ProcessMessageManager* NewProcessMessageManager(
+ bool aIsRemote);
+
+ void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+ const nsAString& aMessage, bool aIsSync,
+ StructuredCloneData* aCloneData,
+ nsTArray<StructuredCloneData>* aRetVal,
+ mozilla::ErrorResult& aError) {
+ ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync,
+ aCloneData, aRetVal, aError);
+ }
+
+ void Disconnect(bool aRemoveFromParent = true);
+ void Close();
+
+ void SetCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
+
+ mozilla::dom::ipc::MessageManagerCallback* GetCallback() { return mCallback; }
+
+ nsresult DispatchAsyncMessageInternal(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData);
+ bool IsGlobal() { return mGlobal; }
+ bool IsBroadcaster() { return mIsBroadcaster; }
+ bool IsChrome() { return mChrome; }
+
+ // GetGlobalMessageManager creates the global message manager if it hasn't
+ // been yet.
+ static already_AddRefed<mozilla::dom::ChromeMessageBroadcaster>
+ GetGlobalMessageManager();
+ static mozilla::dom::ParentProcessMessageManager* GetParentProcessManager() {
+ return sParentProcessManager;
+ }
+ static mozilla::dom::ChildProcessMessageManager* GetChildProcessManager() {
+ return sChildProcessManager;
+ }
+ static void SetChildProcessManager(
+ mozilla::dom::ChildProcessMessageManager* aManager) {
+ sChildProcessManager = aManager;
+ }
+
+ static bool GetParamsForMessage(JSContext* aCx, const JS::Value& aValue,
+ const JS::Value& aTransfer,
+ StructuredCloneData& aData);
+
+ void SetInitialProcessData(JS::Handle<JS::Value> aInitialData);
+
+ void LoadPendingScripts();
+
+ protected:
+ friend class MMListenerRemover;
+
+ virtual mozilla::dom::MessageBroadcaster* GetParentManager() {
+ return nullptr;
+ }
+ virtual void ClearParentManager(bool aRemove) {}
+
+ void DispatchAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ JS::Handle<JS::Value> aTransfers,
+ mozilla::ErrorResult& aError);
+
+ void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+ bool aTargetClosed, const nsAString& aMessage,
+ bool aIsSync, StructuredCloneData* aCloneData,
+ nsTArray<StructuredCloneData>* aRetVal,
+ mozilla::ErrorResult& aError);
+
+ void LoadScript(const nsAString& aURL, bool aAllowDelayedLoad,
+ bool aRunInGlobalScope, mozilla::ErrorResult& aError);
+ void RemoveDelayedScript(const nsAString& aURL);
+ void GetDelayedScripts(JSContext* aCx, nsTArray<nsTArray<JS::Value>>& aList,
+ mozilla::ErrorResult& aError);
+
+ // We keep the message listeners as arrays in a hastable indexed by the
+ // message name. That gives us fast lookups in ReceiveMessage().
+ nsClassHashtable<nsStringHashKey,
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>>
+ mListeners;
+ nsTArray<RefPtr<mozilla::dom::MessageListenerManager>> mChildManagers;
+ bool mChrome; // true if we're in the chrome process
+ bool mGlobal; // true if we're the global frame message manager
+ bool mIsProcessManager; // true if the message manager belongs to the process
+ // realm
+ bool mIsBroadcaster; // true if the message manager is a broadcaster
+ bool mOwnsCallback;
+ bool mHandlingMessage;
+ bool mClosed; // true if we can no longer send messages
+ bool mDisconnected;
+ mozilla::dom::ipc::MessageManagerCallback* mCallback;
+ mozilla::UniquePtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
+ nsTArray<nsString> mPendingScripts;
+ nsTArray<bool> mPendingScriptsGlobalStates;
+ JS::Heap<JS::Value> mInitialProcessData;
+ RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
+
+ void LoadPendingScripts(nsFrameMessageManager* aManager,
+ nsFrameMessageManager* aChildMM);
+
+ public:
+ static mozilla::dom::ParentProcessMessageManager* sParentProcessManager;
+ static nsFrameMessageManager* sSameProcessParentManager;
+ static nsTArray<nsCOMPtr<nsIRunnable>>* sPendingSameProcessAsyncMessages;
+
+ private:
+ static mozilla::dom::ChildProcessMessageManager* sChildProcessManager;
+};
+
+/* A helper class for taking care of many details for async message sending
+ within a single process. Intended to be used like so:
+
+ class MyAsyncMessage : public nsSameProcessAsyncMessageBase, public Runnable
+ {
+ NS_IMETHOD Run() {
+ ReceiveMessage(..., ...);
+ return NS_OK;
+ }
+ };
+
+
+ RefPtr<nsSameProcessAsyncMessageBase> ev = new MyAsyncMessage();
+ nsresult rv = ev->Init(...);
+ if (NS_SUCCEEDED(rv)) {
+ NS_DispatchToMainThread(ev);
+ }
+*/
+class nsSameProcessAsyncMessageBase {
+ public:
+ using StructuredCloneData = mozilla::dom::ipc::StructuredCloneData;
+
+ nsSameProcessAsyncMessageBase();
+ nsresult Init(const nsAString& aMessage, StructuredCloneData& aData);
+
+ void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+ nsFrameMessageManager* aManager);
+
+ private:
+ nsSameProcessAsyncMessageBase(const nsSameProcessAsyncMessageBase&);
+
+ nsString mMessage;
+ StructuredCloneData mData;
+#ifdef DEBUG
+ bool mCalledInit;
+#endif
+};
+
+class nsScriptCacheCleaner;
+
+struct nsMessageManagerScriptHolder {
+ explicit nsMessageManagerScriptHolder(JS::Stencil* aStencil)
+ : mStencil(aStencil) {
+ MOZ_COUNT_CTOR(nsMessageManagerScriptHolder);
+ }
+
+ MOZ_COUNTED_DTOR(nsMessageManagerScriptHolder)
+
+ RefPtr<JS::Stencil> mStencil;
+};
+
+class nsMessageManagerScriptExecutor {
+ public:
+ static void PurgeCache();
+ static void Shutdown();
+
+ void MarkScopesForCC();
+
+ protected:
+ friend class nsMessageManagerScriptCx;
+ nsMessageManagerScriptExecutor() {
+ MOZ_COUNT_CTOR(nsMessageManagerScriptExecutor);
+ }
+ MOZ_COUNTED_DTOR(nsMessageManagerScriptExecutor)
+
+ void DidCreateScriptLoader();
+ void LoadScriptInternal(JS::Handle<JSObject*> aMessageManager,
+ const nsAString& aURL, bool aRunInUniqueScope);
+ already_AddRefed<JS::Stencil> TryCacheLoadAndCompileScript(
+ const nsAString& aURL, bool aRunInUniqueScope,
+ JS::Handle<JSObject*> aMessageManager);
+ bool Init();
+ void Trace(const TraceCallbacks& aCallbacks, void* aClosure);
+ void Unlink();
+ AutoTArray<JS::Heap<JSObject*>, 2> mAnonymousGlobalScopes;
+
+ // Returns true if this is a process message manager. There should only be a
+ // single process message manager per session, so instances of this type will
+ // optimize their script loading to avoid unnecessary duplication.
+ virtual bool IsProcessScoped() const { return false; }
+
+ static nsTHashMap<nsStringHashKey, nsMessageManagerScriptHolder*>*
+ sCachedScripts;
+ static mozilla::StaticRefPtr<nsScriptCacheCleaner> sScriptCacheCleaner;
+};
+
+class nsScriptCacheCleaner final : public nsIObserver {
+ ~nsScriptCacheCleaner() = default;
+
+ NS_DECL_ISUPPORTS
+
+ nsScriptCacheCleaner() {
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "xpcom-shutdown", false);
+ }
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (strcmp("xpcom-shutdown", aTopic) == 0) {
+ nsMessageManagerScriptExecutor::Shutdown();
+ }
+ return NS_OK;
+ }
+};
+
+#endif
diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
new file mode 100644
index 0000000000..98407208d3
--- /dev/null
+++ b/dom/base/nsGlobalWindow.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGlobalWindow_h___
+#define nsGlobalWindow_h___
+
+// NOTE: This is so that I can rewrite the includes in a separate patch.
+// Specificially I don't think I want to change this until I've moved everything
+// to mozilla/dom/Window.h and mozilla/dom/WindowProxy.h.
+
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+
+#endif /* nsGlobalWindow_h___ */
diff --git a/dom/base/nsGlobalWindowCommands.cpp b/dom/base/nsGlobalWindowCommands.cpp
new file mode 100644
index 0000000000..e732eae1ef
--- /dev/null
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -0,0 +1,1196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGlobalWindowCommands.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsCommandParams.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+
+#include "nsControllerCommandTable.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsISelectionController.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewerEdit.h"
+#include "nsIContentViewer.h"
+#include "nsFocusManager.h"
+#include "nsCopySupport.h"
+#include "nsIClipboard.h"
+#include "ContentEventHandler.h"
+#include "nsContentUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/intl/WordBreaker.h"
+#include "mozilla/layers/KeyboardMap.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+constexpr const char* sSelectAllString = "cmd_selectAll";
+constexpr const char* sSelectNoneString = "cmd_selectNone";
+constexpr const char* sCopyImageLocationString = "cmd_copyImageLocation";
+constexpr const char* sCopyImageContentsString = "cmd_copyImageContents";
+constexpr const char* sCopyImageString = "cmd_copyImage";
+
+constexpr const char* sScrollTopString = "cmd_scrollTop";
+constexpr const char* sScrollBottomString = "cmd_scrollBottom";
+constexpr const char* sScrollPageUpString = "cmd_scrollPageUp";
+constexpr const char* sScrollPageDownString = "cmd_scrollPageDown";
+constexpr const char* sScrollLineUpString = "cmd_scrollLineUp";
+constexpr const char* sScrollLineDownString = "cmd_scrollLineDown";
+constexpr const char* sScrollLeftString = "cmd_scrollLeft";
+constexpr const char* sScrollRightString = "cmd_scrollRight";
+constexpr const char* sMoveTopString = "cmd_moveTop";
+constexpr const char* sMoveBottomString = "cmd_moveBottom";
+constexpr const char* sMovePageUpString = "cmd_movePageUp";
+constexpr const char* sMovePageDownString = "cmd_movePageDown";
+constexpr const char* sLinePreviousString = "cmd_linePrevious";
+constexpr const char* sLineNextString = "cmd_lineNext";
+constexpr const char* sCharPreviousString = "cmd_charPrevious";
+constexpr const char* sCharNextString = "cmd_charNext";
+
+// These are so the browser can use editor navigation key bindings
+// helps with accessibility (boolean pref accessibility.browsewithcaret)
+
+constexpr const char* sSelectCharPreviousString = "cmd_selectCharPrevious";
+constexpr const char* sSelectCharNextString = "cmd_selectCharNext";
+
+constexpr const char* sWordPreviousString = "cmd_wordPrevious";
+constexpr const char* sWordNextString = "cmd_wordNext";
+constexpr const char* sSelectWordPreviousString = "cmd_selectWordPrevious";
+constexpr const char* sSelectWordNextString = "cmd_selectWordNext";
+
+constexpr const char* sBeginLineString = "cmd_beginLine";
+constexpr const char* sEndLineString = "cmd_endLine";
+constexpr const char* sSelectBeginLineString = "cmd_selectBeginLine";
+constexpr const char* sSelectEndLineString = "cmd_selectEndLine";
+
+constexpr const char* sSelectLinePreviousString = "cmd_selectLinePrevious";
+constexpr const char* sSelectLineNextString = "cmd_selectLineNext";
+
+constexpr const char* sSelectPageUpString = "cmd_selectPageUp";
+constexpr const char* sSelectPageDownString = "cmd_selectPageDown";
+
+constexpr const char* sSelectTopString = "cmd_selectTop";
+constexpr const char* sSelectBottomString = "cmd_selectBottom";
+
+// Physical-direction movement and selection commands
+constexpr const char* sMoveLeftString = "cmd_moveLeft";
+constexpr const char* sMoveRightString = "cmd_moveRight";
+constexpr const char* sMoveUpString = "cmd_moveUp";
+constexpr const char* sMoveDownString = "cmd_moveDown";
+constexpr const char* sMoveLeft2String = "cmd_moveLeft2";
+constexpr const char* sMoveRight2String = "cmd_moveRight2";
+constexpr const char* sMoveUp2String = "cmd_moveUp2";
+constexpr const char* sMoveDown2String = "cmd_moveDown2";
+
+constexpr const char* sSelectLeftString = "cmd_selectLeft";
+constexpr const char* sSelectRightString = "cmd_selectRight";
+constexpr const char* sSelectUpString = "cmd_selectUp";
+constexpr const char* sSelectDownString = "cmd_selectDown";
+constexpr const char* sSelectLeft2String = "cmd_selectLeft2";
+constexpr const char* sSelectRight2String = "cmd_selectRight2";
+constexpr const char* sSelectUp2String = "cmd_selectUp2";
+constexpr const char* sSelectDown2String = "cmd_selectDown2";
+
+#if 0
+# pragma mark -
+#endif
+
+// a base class for selection-related commands, for code sharing
+class nsSelectionCommandsBase : public nsIControllerCommand {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD IsCommandEnabled(const char* aCommandName,
+ nsISupports* aCommandContext,
+ bool* _retval) override;
+ NS_IMETHOD GetCommandStateParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) override;
+ MOZ_CAN_RUN_SCRIPT
+ NS_IMETHOD DoCommandParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) override;
+
+ protected:
+ virtual ~nsSelectionCommandsBase() = default;
+
+ static nsresult GetPresShellFromWindow(nsPIDOMWindowOuter* aWindow,
+ PresShell** aPresShell);
+ static nsresult GetSelectionControllerFromWindow(
+ nsPIDOMWindowOuter* aWindow, nsISelectionController** aSelCon);
+
+ // no member variables, please, we're stateless!
+};
+
+// this class implements commands whose behavior depends on the 'browse with
+// caret' setting
+class nsSelectMoveScrollCommand : public nsSelectionCommandsBase {
+ public:
+ NS_IMETHOD DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) override;
+
+ // no member variables, please, we're stateless!
+};
+
+// this class implements physical-movement versions of the above
+class nsPhysicalSelectMoveScrollCommand : public nsSelectionCommandsBase {
+ public:
+ NS_IMETHOD DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) override;
+
+ // no member variables, please, we're stateless!
+};
+
+// this class implements other selection commands
+class nsSelectCommand : public nsSelectionCommandsBase {
+ public:
+ NS_IMETHOD DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) override;
+
+ // no member variables, please, we're stateless!
+};
+
+// this class implements physical-movement versions of selection commands
+class nsPhysicalSelectCommand : public nsSelectionCommandsBase {
+ public:
+ NS_IMETHOD DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) override;
+
+ // no member variables, please, we're stateless!
+};
+
+#if 0
+# pragma mark -
+#endif
+
+NS_IMPL_ISUPPORTS(nsSelectionCommandsBase, nsIControllerCommand)
+
+NS_IMETHODIMP
+nsSelectionCommandsBase::IsCommandEnabled(const char* aCommandName,
+ nsISupports* aCommandContext,
+ bool* outCmdEnabled) {
+ // XXX this needs fixing. e.g. you can't scroll up if you're already at the
+ // top of the document.
+ *outCmdEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSelectionCommandsBase::GetCommandStateParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ // XXX we should probably return the enabled state
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSelectionCommandsBase::DoCommandParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ return DoCommand(aCommandName, aCommandContext);
+}
+
+// protected methods
+
+nsresult nsSelectionCommandsBase::GetPresShellFromWindow(
+ nsPIDOMWindowOuter* aWindow, PresShell** aPresShell) {
+ *aPresShell = nullptr;
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
+
+ nsIDocShell* docShell = aWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ NS_IF_ADDREF(*aPresShell = docShell->GetPresShell());
+ return NS_OK;
+}
+
+nsresult nsSelectionCommandsBase::GetSelectionControllerFromWindow(
+ nsPIDOMWindowOuter* aWindow, nsISelectionController** aSelCon) {
+ RefPtr<PresShell> presShell;
+ GetPresShellFromWindow(aWindow, getter_AddRefs(presShell));
+ if (!presShell) {
+ *aSelCon = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ *aSelCon = presShell.forget().take();
+ return NS_OK;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+// Helpers for nsSelectMoveScrollCommand and nsPhysicalSelectMoveScrollCommand
+static void AdjustFocusAfterCaretMove(nsPIDOMWindowOuter* aWindow) {
+ // adjust the focus to the new caret position
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ RefPtr<dom::Element> result;
+ fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
+ nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result));
+ }
+}
+
+static bool IsCaretOnInWindow(nsPIDOMWindowOuter* aWindow,
+ nsISelectionController* aSelCont) {
+ // We allow the caret to be moved with arrow keys on any window for which
+ // the caret is enabled. In particular, this includes caret-browsing mode
+ // in non-chrome documents.
+ bool caretOn = false;
+ aSelCont->GetCaretEnabled(&caretOn);
+ if (!caretOn) {
+ caretOn = Preferences::GetBool("accessibility.browsewithcaret");
+ if (caretOn) {
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ caretOn = false;
+ }
+ }
+ }
+ return caretOn;
+}
+
+static constexpr struct BrowseCommand {
+ Command reverse, forward;
+ KeyboardScrollAction::KeyboardScrollActionType scrollAction;
+ nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
+ nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool);
+} browseCommands[] = {
+ {Command::ScrollTop, Command::ScrollBottom,
+ KeyboardScrollAction::eScrollComplete,
+ &nsISelectionController::CompleteScroll},
+ {Command::ScrollPageUp, Command::ScrollPageDown,
+ KeyboardScrollAction::eScrollPage, &nsISelectionController::ScrollPage},
+ {Command::ScrollLineUp, Command::ScrollLineDown,
+ KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
+ {Command::ScrollLeft, Command::ScrollRight,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter},
+ {Command::MoveTop, Command::MoveBottom,
+ KeyboardScrollAction::eScrollComplete,
+ &nsISelectionController::CompleteScroll,
+ &nsISelectionController::CompleteMove},
+ {Command::MovePageUp, Command::MovePageDown,
+ KeyboardScrollAction::eScrollPage, &nsISelectionController::ScrollPage,
+ &nsISelectionController::PageMove},
+ {Command::LinePrevious, Command::LineNext,
+ KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine,
+ &nsISelectionController::LineMove},
+ {Command::WordPrevious, Command::WordNext,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter,
+ &nsISelectionController::WordMove},
+ {Command::CharPrevious, Command::CharNext,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter,
+ &nsISelectionController::CharacterMove},
+ {Command::BeginLine, Command::EndLine,
+ KeyboardScrollAction::eScrollComplete,
+ &nsISelectionController::CompleteScroll,
+ &nsISelectionController::IntraLineMove}};
+
+nsresult nsSelectMoveScrollCommand::DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
+ nsCOMPtr<nsISelectionController> selCont;
+ GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
+ NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
+
+ const bool caretOn = IsCaretOnInWindow(piWindow, selCont);
+ const Command command = GetInternalCommand(aCommandName);
+ for (const BrowseCommand& browseCommand : browseCommands) {
+ const bool forward = command == browseCommand.forward;
+ if (!forward && command != browseCommand.reverse) {
+ continue;
+ }
+ RefPtr<HTMLEditor> htmlEditor =
+ HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
+ if (htmlEditor) {
+ htmlEditor->PreHandleSelectionChangeCommand(command);
+ }
+ nsresult rv = NS_OK;
+ if (caretOn && browseCommand.move &&
+ NS_SUCCEEDED((selCont->*(browseCommand.move))(forward, false))) {
+ AdjustFocusAfterCaretMove(piWindow);
+ } else {
+ rv = (selCont->*(browseCommand.scroll))(forward);
+ }
+ if (htmlEditor) {
+ htmlEditor->PostHandleSelectionChangeCommand(command);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(false, "Forgot to handle new command?");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// XXX It's not clear to me yet how we should handle the "scroll" option
+// for these commands; for now, I'm mapping them back to ScrollCharacter,
+// ScrollLine, etc., as if for horizontal-mode content, but this may need
+// to be reconsidered once we have more experience with vertical content.
+static const struct PhysicalBrowseCommand {
+ Command command;
+ int16_t direction, amount;
+ KeyboardScrollAction::KeyboardScrollActionType scrollAction;
+ nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
+} physicalBrowseCommands[] = {
+ {Command::MoveLeft, nsISelectionController::MOVE_LEFT, 0,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter},
+ {Command::MoveRight, nsISelectionController::MOVE_RIGHT, 0,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter},
+ {Command::MoveUp, nsISelectionController::MOVE_UP, 0,
+ KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
+ {Command::MoveDown, nsISelectionController::MOVE_DOWN, 0,
+ KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
+ {Command::MoveLeft2, nsISelectionController::MOVE_LEFT, 1,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter},
+ {Command::MoveRight2, nsISelectionController::MOVE_RIGHT, 1,
+ KeyboardScrollAction::eScrollCharacter,
+ &nsISelectionController::ScrollCharacter},
+ {Command::MoveUp2, nsISelectionController::MOVE_UP, 1,
+ KeyboardScrollAction::eScrollComplete,
+ &nsISelectionController::CompleteScroll},
+ {Command::MoveDown2, nsISelectionController::MOVE_DOWN, 1,
+ KeyboardScrollAction::eScrollComplete,
+ &nsISelectionController::CompleteScroll},
+};
+
+nsresult nsPhysicalSelectMoveScrollCommand::DoCommand(
+ const char* aCommandName, nsISupports* aCommandContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
+ nsCOMPtr<nsISelectionController> selCont;
+ GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
+ NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
+
+ const bool caretOn = IsCaretOnInWindow(piWindow, selCont);
+ Command command = GetInternalCommand(aCommandName);
+ for (const PhysicalBrowseCommand& browseCommand : physicalBrowseCommands) {
+ if (command != browseCommand.command) {
+ continue;
+ }
+ RefPtr<HTMLEditor> htmlEditor =
+ HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
+ if (htmlEditor) {
+ htmlEditor->PreHandleSelectionChangeCommand(command);
+ }
+ nsresult rv = NS_OK;
+ if (caretOn && NS_SUCCEEDED(selCont->PhysicalMove(
+ browseCommand.direction, browseCommand.amount, false))) {
+ AdjustFocusAfterCaretMove(piWindow);
+ } else {
+ const bool forward =
+ (browseCommand.direction == nsISelectionController::MOVE_RIGHT ||
+ browseCommand.direction == nsISelectionController::MOVE_DOWN);
+ rv = (selCont->*(browseCommand.scroll))(forward);
+ }
+ if (htmlEditor) {
+ htmlEditor->PostHandleSelectionChangeCommand(command);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(false, "Forgot to handle new command?");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+static const struct SelectCommand {
+ Command reverse, forward;
+ nsresult (NS_STDCALL nsISelectionController::*select)(bool, bool);
+} selectCommands[] = {{Command::SelectCharPrevious, Command::SelectCharNext,
+ &nsISelectionController::CharacterMove},
+ {Command::SelectWordPrevious, Command::SelectWordNext,
+ &nsISelectionController::WordMove},
+ {Command::SelectBeginLine, Command::SelectEndLine,
+ &nsISelectionController::IntraLineMove},
+ {Command::SelectLinePrevious, Command::SelectLineNext,
+ &nsISelectionController::LineMove},
+ {Command::SelectPageUp, Command::SelectPageDown,
+ &nsISelectionController::PageMove},
+ {Command::SelectTop, Command::SelectBottom,
+ &nsISelectionController::CompleteMove}};
+
+nsresult nsSelectCommand::DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
+ nsCOMPtr<nsISelectionController> selCont;
+ GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
+ NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
+
+ // These commands are so the browser can use caret navigation key bindings -
+ // Helps with accessibility - aaronl@netscape.com
+ const Command command = GetInternalCommand(aCommandName);
+ for (const SelectCommand& selectCommand : selectCommands) {
+ const bool forward = command == selectCommand.forward;
+ if (!forward && command != selectCommand.reverse) {
+ continue;
+ }
+ RefPtr<HTMLEditor> htmlEditor =
+ HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
+ if (htmlEditor) {
+ htmlEditor->PreHandleSelectionChangeCommand(command);
+ }
+ nsresult rv = (selCont->*(selectCommand.select))(forward, true);
+ if (htmlEditor) {
+ htmlEditor->PostHandleSelectionChangeCommand(command);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(false, "Forgot to handle new command?");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+static const struct PhysicalSelectCommand {
+ Command command;
+ int16_t direction, amount;
+} physicalSelectCommands[] = {
+ {Command::SelectLeft, nsISelectionController::MOVE_LEFT, 0},
+ {Command::SelectRight, nsISelectionController::MOVE_RIGHT, 0},
+ {Command::SelectUp, nsISelectionController::MOVE_UP, 0},
+ {Command::SelectDown, nsISelectionController::MOVE_DOWN, 0},
+ {Command::SelectLeft2, nsISelectionController::MOVE_LEFT, 1},
+ {Command::SelectRight2, nsISelectionController::MOVE_RIGHT, 1},
+ {Command::SelectUp2, nsISelectionController::MOVE_UP, 1},
+ {Command::SelectDown2, nsISelectionController::MOVE_DOWN, 1}};
+
+nsresult nsPhysicalSelectCommand::DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
+ nsCOMPtr<nsISelectionController> selCont;
+ GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
+ NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
+
+ const Command command = GetInternalCommand(aCommandName);
+ for (const PhysicalSelectCommand& selectCommand : physicalSelectCommands) {
+ if (command != selectCommand.command) {
+ continue;
+ }
+ RefPtr<HTMLEditor> htmlEditor =
+ HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
+ if (htmlEditor) {
+ htmlEditor->PreHandleSelectionChangeCommand(command);
+ }
+ nsresult rv = selCont->PhysicalMove(selectCommand.direction,
+ selectCommand.amount, true);
+ if (htmlEditor) {
+ htmlEditor->PostHandleSelectionChangeCommand(command);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(false, "Forgot to handle new command?");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+class nsClipboardCommand final : public nsIControllerCommand {
+ ~nsClipboardCommand() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+};
+
+NS_IMPL_ISUPPORTS(nsClipboardCommand, nsIControllerCommand)
+
+nsresult nsClipboardCommand::IsCommandEnabled(const char* aCommandName,
+ nsISupports* aContext,
+ bool* outCmdEnabled) {
+ NS_ENSURE_ARG_POINTER(outCmdEnabled);
+ *outCmdEnabled = false;
+
+ if (strcmp(aCommandName, "cmd_copy") && strcmp(aCommandName, "cmd_cut") &&
+ strcmp(aCommandName, "cmd_paste")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ RefPtr<dom::Document> doc = window->GetExtantDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ if (doc->AreClipboardCommandsUnconditionallyEnabled()) {
+ // In HTML and XHTML documents, we always want the cut, copy and paste
+ // commands to be enabled, but if the document is chrome, let it control it.
+ *outCmdEnabled = true;
+ } else {
+ // Cut isn't enabled in xul documents which use nsClipboardCommand
+ if (strcmp(aCommandName, "cmd_copy") == 0) {
+ *outCmdEnabled = nsCopySupport::CanCopy(doc);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsClipboardCommand::DoCommand(const char* aCommandName,
+ nsISupports* aContext) {
+ if (strcmp(aCommandName, "cmd_cut") && strcmp(aCommandName, "cmd_copy") &&
+ strcmp(aCommandName, "cmd_paste"))
+ return NS_OK;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ EventMessage eventMessage = eCopy;
+ if (strcmp(aCommandName, "cmd_cut") == 0) {
+ eventMessage = eCut;
+ } else if (strcmp(aCommandName, "cmd_paste") == 0) {
+ eventMessage = ePaste;
+ }
+
+ bool actionTaken = false;
+ nsCopySupport::FireClipboardEvent(eventMessage,
+ nsIClipboard::kGlobalClipboard, presShell,
+ nullptr, &actionTaken);
+
+ return actionTaken ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+}
+
+NS_IMETHODIMP
+nsClipboardCommand::GetCommandStateParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsClipboardCommand::DoCommandParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aContext) {
+ return DoCommand(aCommandName, aContext);
+}
+
+#if 0
+# pragma mark -
+#endif
+
+class nsSelectionCommand : public nsIControllerCommand {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+
+ protected:
+ virtual ~nsSelectionCommand() = default;
+
+ virtual nsresult IsClipboardCommandEnabled(const char* aCommandName,
+ nsIContentViewerEdit* aEdit,
+ bool* outCmdEnabled) = 0;
+ virtual nsresult DoClipboardCommand(const char* aCommandName,
+ nsIContentViewerEdit* aEdit,
+ nsICommandParams* aParams) = 0;
+
+ static nsresult GetContentViewerEditFromContext(
+ nsISupports* aContext, nsIContentViewerEdit** aEditInterface);
+
+ // no member variables, please, we're stateless!
+};
+
+NS_IMPL_ISUPPORTS(nsSelectionCommand, nsIControllerCommand)
+
+/*---------------------------------------------------------------------------
+
+ nsSelectionCommand
+
+----------------------------------------------------------------------------*/
+
+NS_IMETHODIMP
+nsSelectionCommand::IsCommandEnabled(const char* aCommandName,
+ nsISupports* aCommandContext,
+ bool* outCmdEnabled) {
+ NS_ENSURE_ARG_POINTER(outCmdEnabled);
+ *outCmdEnabled = false;
+
+ nsCOMPtr<nsIContentViewerEdit> contentEdit;
+ GetContentViewerEditFromContext(aCommandContext, getter_AddRefs(contentEdit));
+ NS_ENSURE_TRUE(contentEdit, NS_ERROR_NOT_INITIALIZED);
+
+ return IsClipboardCommandEnabled(aCommandName, contentEdit, outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsSelectionCommand::DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) {
+ nsCOMPtr<nsIContentViewerEdit> contentEdit;
+ GetContentViewerEditFromContext(aCommandContext, getter_AddRefs(contentEdit));
+ NS_ENSURE_TRUE(contentEdit, NS_ERROR_NOT_INITIALIZED);
+
+ return DoClipboardCommand(aCommandName, contentEdit, nullptr);
+}
+
+NS_IMETHODIMP
+nsSelectionCommand::GetCommandStateParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSelectionCommand::DoCommandParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ nsCOMPtr<nsIContentViewerEdit> contentEdit;
+ GetContentViewerEditFromContext(aCommandContext, getter_AddRefs(contentEdit));
+ NS_ENSURE_TRUE(contentEdit, NS_ERROR_NOT_INITIALIZED);
+
+ return DoClipboardCommand(aCommandName, contentEdit, aParams);
+}
+
+nsresult nsSelectionCommand::GetContentViewerEditFromContext(
+ nsISupports* aContext, nsIContentViewerEdit** aEditInterface) {
+ NS_ENSURE_ARG(aEditInterface);
+ *aEditInterface = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
+ NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
+
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ docShell->GetContentViewer(getter_AddRefs(viewer));
+ nsCOMPtr<nsIContentViewerEdit> edit(do_QueryInterface(viewer));
+ NS_ENSURE_TRUE(edit, NS_ERROR_FAILURE);
+
+ edit.forget(aEditInterface);
+ return NS_OK;
+}
+
+#if 0
+# pragma mark -
+#endif
+
+#define NS_DECL_CLIPBOARD_COMMAND(_cmd) \
+ class _cmd : public nsSelectionCommand { \
+ protected: \
+ virtual nsresult IsClipboardCommandEnabled(const char* aCommandName, \
+ nsIContentViewerEdit* aEdit, \
+ bool* outCmdEnabled) override; \
+ virtual nsresult DoClipboardCommand(const char* aCommandName, \
+ nsIContentViewerEdit* aEdit, \
+ nsICommandParams* aParams) override; \
+ /* no member variables, please, we're stateless! */ \
+ };
+
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardCopyLinkCommand)
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardImageCommands)
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardSelectAllNoneCommands)
+
+nsresult nsClipboardCopyLinkCommand::IsClipboardCommandEnabled(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ bool* outCmdEnabled) {
+ return aEdit->GetInLink(outCmdEnabled);
+}
+
+nsresult nsClipboardCopyLinkCommand::DoClipboardCommand(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ nsICommandParams* aParams) {
+ return aEdit->CopyLinkLocation();
+}
+
+#if 0
+# pragma mark -
+#endif
+
+nsresult nsClipboardImageCommands::IsClipboardCommandEnabled(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ bool* outCmdEnabled) {
+ return aEdit->GetInImage(outCmdEnabled);
+}
+
+nsresult nsClipboardImageCommands::DoClipboardCommand(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ nsICommandParams* aParams) {
+ if (!nsCRT::strcmp(sCopyImageLocationString, aCommandName))
+ return aEdit->CopyImage(nsIContentViewerEdit::COPY_IMAGE_TEXT);
+ if (!nsCRT::strcmp(sCopyImageContentsString, aCommandName))
+ return aEdit->CopyImage(nsIContentViewerEdit::COPY_IMAGE_DATA);
+ int32_t copyFlags = nsIContentViewerEdit::COPY_IMAGE_DATA |
+ nsIContentViewerEdit::COPY_IMAGE_HTML;
+ if (aParams) {
+ copyFlags = aParams->AsCommandParams()->GetInt("imageCopy");
+ }
+ return aEdit->CopyImage(copyFlags);
+}
+
+#if 0
+# pragma mark -
+#endif
+
+nsresult nsClipboardSelectAllNoneCommands::IsClipboardCommandEnabled(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ bool* outCmdEnabled) {
+ *outCmdEnabled = true;
+ return NS_OK;
+}
+
+nsresult nsClipboardSelectAllNoneCommands::DoClipboardCommand(
+ const char* aCommandName, nsIContentViewerEdit* aEdit,
+ nsICommandParams* aParams) {
+ if (!nsCRT::strcmp(sSelectAllString, aCommandName)) return aEdit->SelectAll();
+
+ return aEdit->ClearSelection();
+}
+
+#if 0
+# pragma mark -
+#endif
+
+#if 0 // Remove unless needed again, bug 204777
+class nsWebNavigationBaseCommand : public nsIControllerCommand
+{
+public:
+ virtual ~nsWebNavigationBaseCommand() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+
+protected:
+
+ virtual nsresult IsWebNavCommandEnabled(const char * aCommandName, nsIWebNavigation* aWebNavigation, bool *outCmdEnabled) = 0;
+ virtual nsresult DoWebNavCommand(const char *aCommandName, nsIWebNavigation* aWebNavigation) = 0;
+
+ static nsresult GetWebNavigationFromContext(nsISupports *aContext, nsIWebNavigation **aWebNavigation);
+
+ // no member variables, please, we're stateless!
+};
+
+class nsGoForwardCommand : public nsWebNavigationBaseCommand
+{
+protected:
+
+ virtual nsresult IsWebNavCommandEnabled(const char * aCommandName, nsIWebNavigation* aWebNavigation, bool *outCmdEnabled);
+ virtual nsresult DoWebNavCommand(const char *aCommandName, nsIWebNavigation* aWebNavigation);
+ // no member variables, please, we're stateless!
+};
+
+class nsGoBackCommand : public nsWebNavigationBaseCommand
+{
+protected:
+
+ virtual nsresult IsWebNavCommandEnabled(const char * aCommandName, nsIWebNavigation* aWebNavigation, bool *outCmdEnabled);
+ virtual nsresult DoWebNavCommand(const char *aCommandName, nsIWebNavigation* aWebNavigation);
+ // no member variables, please, we're stateless!
+};
+
+/*---------------------------------------------------------------------------
+
+ nsWebNavigationCommands
+ no params
+----------------------------------------------------------------------------*/
+
+NS_IMPL_ISUPPORTS(nsWebNavigationBaseCommand, nsIControllerCommand)
+
+NS_IMETHODIMP
+nsWebNavigationBaseCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *aCommandContext,
+ bool *outCmdEnabled)
+{
+ NS_ENSURE_ARG_POINTER(outCmdEnabled);
+ *outCmdEnabled = false;
+
+ nsCOMPtr<nsIWebNavigation> webNav;
+ GetWebNavigationFromContext(aCommandContext, getter_AddRefs(webNav));
+ NS_ENSURE_TRUE(webNav, NS_ERROR_INVALID_ARG);
+
+ return IsCommandEnabled(aCommandName, webNav, outCmdEnabled);
+}
+
+NS_IMETHODIMP
+nsWebNavigationBaseCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams, nsISupports *aCommandContext)
+{
+ // XXX we should probably return the enabled state
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWebNavigationBaseCommand::DoCommand(const char *aCommandName,
+ nsISupports *aCommandContext)
+{
+ nsCOMPtr<nsIWebNavigation> webNav;
+ GetWebNavigationFromContext(aCommandContext, getter_AddRefs(webNav));
+ NS_ENSURE_TRUE(webNav, NS_ERROR_INVALID_ARG);
+
+ return DoWebNavCommand(aCommandName, webNav);
+}
+
+NS_IMETHODIMP
+nsWebNavigationBaseCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams, nsISupports *aCommandContext)
+{
+ return DoCommand(aCommandName, aCommandContext);
+}
+
+nsresult
+nsWebNavigationBaseCommand::GetWebNavigationFromContext(nsISupports *aContext, nsIWebNavigation **aWebNavigation)
+{
+ nsCOMPtr<nsIInterfaceRequestor> windowReq = do_QueryInterface(aContext);
+ CallGetInterface(windowReq.get(), aWebNavigation);
+ return (*aWebNavigation) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsGoForwardCommand::IsWebNavCommandEnabled(const char * aCommandName, nsIWebNavigation* aWebNavigation, bool *outCmdEnabled)
+{
+ return aWebNavigation->GetCanGoForward(outCmdEnabled);
+}
+
+nsresult
+nsGoForwardCommand::DoWebNavCommand(const char *aCommandName, nsIWebNavigation* aWebNavigation)
+{
+ return aWebNavigation->GoForward();
+}
+
+nsresult
+nsGoBackCommand::IsWebNavCommandEnabled(const char * aCommandName, nsIWebNavigation* aWebNavigation, bool *outCmdEnabled)
+{
+ return aWebNavigation->GetCanGoBack(outCmdEnabled);
+}
+
+nsresult
+nsGoBackCommand::DoWebNavCommand(const char *aCommandName, nsIWebNavigation* aWebNavigation)
+{
+ return aWebNavigation->GoBack();
+}
+#endif
+
+class nsLookUpDictionaryCommand final : public nsIControllerCommand {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+
+ private:
+ virtual ~nsLookUpDictionaryCommand() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsLookUpDictionaryCommand, nsIControllerCommand)
+
+NS_IMETHODIMP
+nsLookUpDictionaryCommand::IsCommandEnabled(const char* aCommandName,
+ nsISupports* aCommandContext,
+ bool* aRetval) {
+ *aRetval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLookUpDictionaryCommand::GetCommandStateParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsLookUpDictionaryCommand::DoCommand(const char* aCommandName,
+ nsISupports* aCommandContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsLookUpDictionaryCommand::DoCommandParams(const char* aCommandName,
+ nsICommandParams* aParams,
+ nsISupports* aCommandContext) {
+ if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCommandParams* params = aParams->AsCommandParams();
+
+ ErrorResult error;
+ int32_t x = params->GetInt("x", error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ int32_t y = params->GetInt("y", error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ LayoutDeviceIntPoint point(x, y);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aCommandContext);
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocShell* docShell = window->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PresShell* presShell = docShell->GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
+ widget);
+ queryCharAtPointEvent.mRefPoint.x = x;
+ queryCharAtPointEvent.mRefPoint.y = y;
+ ContentEventHandler handler(presContext);
+ handler.OnQueryCharacterAtPoint(&queryCharAtPointEvent);
+
+ if (NS_WARN_IF(queryCharAtPointEvent.Failed()) ||
+ queryCharAtPointEvent.DidNotFindChar()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ widget);
+ handler.OnQuerySelectedText(&querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t offset = queryCharAtPointEvent.mReply->StartOffset();
+ uint32_t begin, length;
+
+ // macOS prioritizes user selected text if the current point falls within the
+ // selection range. So we check the selection first.
+ if (querySelectedTextEvent.FoundSelection() &&
+ querySelectedTextEvent.mReply->IsOffsetInRange(offset)) {
+ begin = querySelectedTextEvent.mReply->StartOffset();
+ length = querySelectedTextEvent.mReply->DataLength();
+ } else {
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ widget);
+ // OSX 10.7 queries 50 characters before/after current point. So we fetch
+ // same length.
+ if (offset > 50) {
+ offset -= 50;
+ } else {
+ offset = 0;
+ }
+ queryTextContentEvent.InitForQueryTextContent(offset, 100);
+ handler.OnQueryTextContent(&queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed()) ||
+ NS_WARN_IF(queryTextContentEvent.mReply->IsDataEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ intl::WordRange range = intl::WordBreaker::FindWord(
+ queryTextContentEvent.mReply->DataRef().get(),
+ queryTextContentEvent.mReply->DataLength(),
+ queryCharAtPointEvent.mReply->StartOffset() - offset);
+ if (range.mEnd == range.mBegin) {
+ return NS_ERROR_FAILURE;
+ }
+ begin = range.mBegin + offset;
+ length = range.mEnd - range.mBegin;
+ }
+
+ WidgetQueryContentEvent queryLookUpContentEvent(true, eQueryTextContent,
+ widget);
+ queryLookUpContentEvent.InitForQueryTextContent(begin, length);
+ queryLookUpContentEvent.RequestFontRanges();
+ handler.OnQueryTextContent(&queryLookUpContentEvent);
+ if (NS_WARN_IF(queryLookUpContentEvent.Failed()) ||
+ NS_WARN_IF(queryLookUpContentEvent.mReply->IsDataEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, widget);
+ queryTextRectEvent.InitForQueryTextRect(begin, length);
+ handler.OnQueryTextRect(&queryTextRectEvent);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ widget->LookUpDictionary(queryLookUpContentEvent.mReply->DataRef(),
+ queryLookUpContentEvent.mReply->mFontRanges,
+ queryTextRectEvent.mReply->mWritingMode.IsVertical(),
+ queryTextRectEvent.mReply->mRect.TopLeft());
+
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ RegisterWindowCommands
+
+----------------------------------------------------------------------------*/
+
+#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName) \
+ { \
+ _cmdClass* theCmd = new _cmdClass(); \
+ rv = aCommandTable->RegisterCommand( \
+ _cmdName, static_cast<nsIControllerCommand*>(theCmd)); \
+ }
+
+#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \
+ { \
+ _cmdClass* theCmd = new _cmdClass(); \
+ rv = aCommandTable->RegisterCommand( \
+ _cmdName, static_cast<nsIControllerCommand*>(theCmd));
+
+#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \
+ rv = aCommandTable->RegisterCommand( \
+ _cmdName, static_cast<nsIControllerCommand*>(theCmd));
+
+#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName) \
+ rv = aCommandTable->RegisterCommand( \
+ _cmdName, static_cast<nsIControllerCommand*>(theCmd)); \
+ }
+
+// static
+nsresult nsWindowCommandRegistration::RegisterWindowCommands(
+ nsControllerCommandTable* aCommandTable) {
+ nsresult rv;
+
+ // XXX rework the macros to use a loop is possible, reducing code size
+
+ // this set of commands is affected by the 'browse with caret' setting
+ NS_REGISTER_FIRST_COMMAND(nsSelectMoveScrollCommand, sScrollTopString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollBottomString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageUpString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageDownString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineUpString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineDownString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLeftString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollRightString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveTopString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveBottomString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordPreviousString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordNextString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sBeginLineString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sEndLineString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageUpString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageDownString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLinePreviousString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLineNextString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sCharPreviousString);
+ NS_REGISTER_LAST_COMMAND(nsSelectMoveScrollCommand, sCharNextString);
+
+ NS_REGISTER_FIRST_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveLeftString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveRightString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveUpString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveDownString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveLeft2String);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand,
+ sMoveRight2String);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveUp2String);
+ NS_REGISTER_LAST_COMMAND(nsPhysicalSelectMoveScrollCommand, sMoveDown2String);
+
+ NS_REGISTER_FIRST_COMMAND(nsSelectCommand, sSelectCharPreviousString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectCharNextString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectWordPreviousString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectWordNextString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectBeginLineString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectEndLineString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectLinePreviousString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectLineNextString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectPageUpString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectPageDownString);
+ NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectTopString);
+ NS_REGISTER_LAST_COMMAND(nsSelectCommand, sSelectBottomString);
+
+ NS_REGISTER_FIRST_COMMAND(nsPhysicalSelectCommand, sSelectLeftString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectRightString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectUpString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectDownString);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectLeft2String);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectRight2String);
+ NS_REGISTER_NEXT_COMMAND(nsPhysicalSelectCommand, sSelectUp2String);
+ NS_REGISTER_LAST_COMMAND(nsPhysicalSelectCommand, sSelectDown2String);
+
+ NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_cut");
+ NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_copy");
+ NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_paste");
+ NS_REGISTER_ONE_COMMAND(nsClipboardCopyLinkCommand, "cmd_copyLink");
+ NS_REGISTER_FIRST_COMMAND(nsClipboardImageCommands, sCopyImageLocationString);
+ NS_REGISTER_NEXT_COMMAND(nsClipboardImageCommands, sCopyImageContentsString);
+ NS_REGISTER_LAST_COMMAND(nsClipboardImageCommands, sCopyImageString);
+ NS_REGISTER_FIRST_COMMAND(nsClipboardSelectAllNoneCommands, sSelectAllString);
+ NS_REGISTER_LAST_COMMAND(nsClipboardSelectAllNoneCommands, sSelectNoneString);
+
+#if 0 // Remove unless needed again, bug 204777
+ NS_REGISTER_ONE_COMMAND(nsGoBackCommand, "cmd_browserBack");
+ NS_REGISTER_ONE_COMMAND(nsGoForwardCommand, "cmd_browserForward");
+#endif
+
+ NS_REGISTER_ONE_COMMAND(nsLookUpDictionaryCommand, "cmd_lookUpDictionary");
+
+ return rv;
+}
+
+/* static */
+bool nsGlobalWindowCommands::FindScrollCommand(
+ const char* aCommandName, KeyboardScrollAction* aOutAction) {
+ // Search for a keyboard scroll action to do for this command in
+ // browseCommands and physicalBrowseCommands. Each command exists in only one
+ // of them, so the order we examine browseCommands and physicalBrowseCommands
+ // doesn't matter.
+
+ const Command command = GetInternalCommand(aCommandName);
+ if (command == Command::DoNothing) {
+ return false;
+ }
+ for (const BrowseCommand& browseCommand : browseCommands) {
+ const bool forward = command == browseCommand.forward;
+ const bool reverse = command == browseCommand.reverse;
+ if (forward || reverse) {
+ *aOutAction = KeyboardScrollAction(browseCommand.scrollAction, forward);
+ return true;
+ }
+ }
+
+ for (const PhysicalBrowseCommand& browseCommand : physicalBrowseCommands) {
+ if (command != browseCommand.command) {
+ continue;
+ }
+ const bool forward =
+ (browseCommand.direction == nsISelectionController::MOVE_RIGHT ||
+ browseCommand.direction == nsISelectionController::MOVE_DOWN);
+
+ *aOutAction = KeyboardScrollAction(browseCommand.scrollAction, forward);
+ return true;
+ }
+
+ return false;
+}
diff --git a/dom/base/nsGlobalWindowCommands.h b/dom/base/nsGlobalWindowCommands.h
new file mode 100644
index 0000000000..07a26e1eb4
--- /dev/null
+++ b/dom/base/nsGlobalWindowCommands.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGlobalWindowCommands_h__
+#define nsGlobalWindowCommands_h__
+
+#include "nscore.h"
+
+namespace mozilla::layers {
+struct KeyboardScrollAction;
+} // namespace mozilla::layers
+
+class nsControllerCommandTable;
+
+class nsWindowCommandRegistration {
+ public:
+ static nsresult RegisterWindowCommands(
+ nsControllerCommandTable* aCommandTable);
+};
+
+class nsGlobalWindowCommands {
+ public:
+ using KeyboardScrollAction = mozilla::layers::KeyboardScrollAction;
+
+ /**
+ * Search through nsGlobalWindowCommands to find the keyboard scrolling action
+ * that would be done in response to a command.
+ *
+ * @param aCommandName the name of the command
+ * @param aOutAction the result of searching for this command, must not be
+ * null
+ * @returns whether a keyboard action was found or not
+ */
+ static bool FindScrollCommand(const char* aCommandName,
+ KeyboardScrollAction* aOutAction);
+};
+
+#endif // nsGlobalWindowCommands_h__
diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp
new file mode 100644
index 0000000000..0e5afafcb5
--- /dev/null
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -0,0 +1,7912 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGlobalWindowInner.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cstdint>
+#include <new>
+#include <type_traits>
+#include <utility>
+#include "AudioChannelService.h"
+#include "AutoplayPolicy.h"
+#include "Crypto.h"
+#include "MainThreadUtils.h"
+#include "Navigator.h"
+#include "PaintWorkletImpl.h"
+#include "SessionStorageCache.h"
+#include "Units.h"
+#include "VRManagerChild.h"
+#include "WindowDestroyedEvent.h"
+#include "WindowNamedPropertiesHandler.h"
+#include "js/ComparisonOperators.h"
+#include "js/CompileOptions.h"
+#include "js/friend/PerformanceHint.h"
+#include "js/Id.h"
+#include "js/loader/LoadedScript.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
+#include "js/PropertyDescriptor.h"
+#include "js/RealmOptions.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/Warnings.h"
+#include "js/shadow/String.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozIDOMWindow.h"
+#include "moz_external_vr.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BaseProfilerMarkersPrerequisites.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/CallState.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/PermissionDelegateHandler.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/Components.h"
+#include "mozilla/SizeOfState.h"
+#include "mozilla/Span.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/TaskCategory.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/AudioContext.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BarProps.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CSPEvalChecker.h"
+#include "mozilla/dom/CallbackDebuggerNotification.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/ContentMediaController.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DebuggerNotification.h"
+#include "mozilla/dom/DebuggerNotificationBinding.h"
+#include "mozilla/dom/DebuggerNotificationManager.h"
+#include "mozilla/dom/DispatcherTrait.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/External.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/HashChangeEvent.h"
+#include "mozilla/dom/HashChangeEventBinding.h"
+#include "mozilla/dom/IDBFactory.h"
+#include "mozilla/dom/IdleRequest.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/InstallTriggerBinding.h"
+#include "mozilla/dom/IntlUtils.h"
+#include "mozilla/dom/JSExecutionContext.h"
+#include "mozilla/dom/LSObject.h"
+#include "mozilla/dom/LocalStorage.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/MediaDevices.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PartitionedLocalStorage.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PopStateEvent.h"
+#include "mozilla/dom/PopStateEventBinding.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/WebTaskSchedulerMainThread.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ServiceWorker.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/SharedWorker.h"
+#include "mozilla/dom/Storage.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/StorageNotifierService.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "mozilla/dom/TabMessageTypes.h"
+#include "mozilla/dom/Timeout.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRDisplayEvent.h"
+#include "mozilla/dom/VRDisplayEventBinding.h"
+#include "mozilla/dom/VREventObserver.h"
+#include "mozilla/dom/VisualViewport.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/Worklet.h"
+#include "mozilla/dom/XRPermissionRequest.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/glean/bindings/Glean.h"
+#include "mozilla/glean/bindings/GleanPings.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/BaseRect.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "nsAtom.h"
+#include "nsBaseHashtable.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsCanvasFrame.h"
+#include "nsCharTraits.h"
+#include "nsCheapSets.h"
+#include "nsContentUtils.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDebug.h"
+#include "nsDeviceContext.h"
+#include "nsDocShell.h"
+#include "nsFocusManager.h"
+#include "nsFrameMessageManager.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHashKeys.h"
+#include "nsHistory.h"
+#include "nsIAddonPolicyService.h"
+#include "nsIArray.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowserChild.h"
+#include "nsICancelableRunnable.h"
+#include "nsIChannel.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIControllers.h"
+#include "nsICookieJarSettings.h"
+#include "nsICookieService.h"
+#include "nsID.h"
+#include "nsIDOMStorageManager.h"
+#include "nsIDeviceSensors.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDragService.h"
+#include "nsIFocusManager.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIIOService.h"
+#include "nsIIdleRunnable.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsINamed.h"
+#include "nsINode.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIPermission.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrincipal.h"
+#include "nsIPrompt.h"
+#include "nsIRunnable.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScrollableFrame.h"
+#include "nsISerialEventTarget.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISizeOfEventTarget.h"
+#include "nsISlowScriptDebug.h"
+#include "nsISupportsUtils.h"
+#include "nsIThread.h"
+#include "nsITimedChannel.h"
+#include "nsIURI.h"
+#include "nsIWeakReference.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWidget.h"
+#include "nsIWidgetListener.h"
+#include "nsIXULRuntime.h"
+#include "nsJSPrincipals.h"
+#include "nsJSUtils.h"
+#include "nsLayoutStatics.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsPIWindowRoot.h"
+#include "nsPoint.h"
+#include "nsPresContext.h"
+#include "nsQueryObject.h"
+#include "nsSandboxFlags.h"
+#include "nsScreen.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTObserverArray.h"
+#include "nsTStringRepr.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#include "nsWindowMemoryReporter.h"
+#include "nsWindowSizes.h"
+#include "nsWrapperCache.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsXULAppAPI.h"
+#include "nsrootidl.h"
+#include "prclist.h"
+#include "prtypes.h"
+#include "xpcprivate.h"
+#include "xpcpublic.h"
+
+#include "nsIDOMXULControlElement.h"
+
+#ifdef NS_PRINTING
+# include "nsIPrintSettings.h"
+#endif
+
+#ifdef MOZ_WEBSPEECH
+# include "mozilla/dom/SpeechSynthesis.h"
+#endif
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/Debug.h"
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+using mozilla::dom::GamepadHandle;
+using mozilla::dom::cache::CacheStorage;
+
+#define FORWARD_TO_OUTER(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ RefPtr<nsGlobalWindowOuter> outer = GetOuterWindowInternal(); \
+ if (!HasActiveDocument()) { \
+ NS_WARNING(outer ? "Inner window does not have active document." \
+ : "No outer window available!"); \
+ return err_rval; \
+ } \
+ return outer->method args; \
+ PR_END_MACRO
+
+static nsGlobalWindowOuter* GetOuterWindowForForwarding(
+ nsGlobalWindowInner* aInner, ErrorResult& aError) {
+ nsGlobalWindowOuter* outer = aInner->GetOuterWindowInternal();
+ if (MOZ_LIKELY(aInner->HasActiveDocument())) {
+ return outer;
+ }
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ } else {
+ aError.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);
+ }
+ return nullptr;
+}
+
+#define FORWARD_TO_OUTER_OR_THROW(method, args, rv, err_rval) \
+ PR_BEGIN_MACRO \
+ RefPtr<nsGlobalWindowOuter> outer = GetOuterWindowForForwarding(this, rv); \
+ if (MOZ_LIKELY(outer)) { \
+ return outer->method args; \
+ } \
+ return err_rval; \
+ PR_END_MACRO
+
+#define FORWARD_TO_OUTER_VOID(method, args) \
+ PR_BEGIN_MACRO \
+ RefPtr<nsGlobalWindowOuter> outer = GetOuterWindowInternal(); \
+ if (!HasActiveDocument()) { \
+ NS_WARNING(outer ? "Inner window does not have active document." \
+ : "No outer window available!"); \
+ return; \
+ } \
+ outer->method args; \
+ return; \
+ PR_END_MACRO
+
+#define ENSURE_ACTIVE_DOCUMENT(errorresult, err_rval) \
+ PR_BEGIN_MACRO \
+ if (MOZ_UNLIKELY(!HasActiveDocument())) { \
+ aError.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \
+ return err_rval; \
+ } \
+ PR_END_MACRO
+
+#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added"
+#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
+#define PERMISSION_CHANGED_TOPIC "perm-changed"
+
+static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner");
+extern mozilla::LazyLogModule gTimeoutLog;
+
+#ifdef DEBUG
+static LazyLogModule gDocShellAndDOMWindowLeakLogging(
+ "DocShellAndDOMWindowLeak");
+#endif
+
+static FILE* gDumpFile = nullptr;
+
+nsGlobalWindowInner::InnerWindowByIdTable*
+ nsGlobalWindowInner::sInnerWindowsById = nullptr;
+
+bool nsGlobalWindowInner::sDragServiceDisabled = false;
+bool nsGlobalWindowInner::sMouseDown = false;
+
+/**
+ * An indirect observer object that means we don't have to implement nsIObserver
+ * on nsGlobalWindow, where any script could see it.
+ */
+class nsGlobalWindowObserver final : public nsIObserver,
+ public nsIInterfaceRequestor,
+ public StorageNotificationObserver {
+ public:
+ explicit nsGlobalWindowObserver(nsGlobalWindowInner* aWindow)
+ : mWindow(aWindow) {}
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (!mWindow) return NS_OK;
+ return mWindow->Observe(aSubject, aTopic, aData);
+ }
+ void Forget() { mWindow = nullptr; }
+ NS_IMETHOD GetInterface(const nsIID& aIID, void** aResult) override {
+ if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) {
+ return mWindow->QueryInterface(aIID, aResult);
+ }
+ return NS_NOINTERFACE;
+ }
+
+ void ObserveStorageNotification(StorageEvent* aEvent,
+ const char16_t* aStorageType,
+ bool aPrivateBrowsing) override {
+ if (mWindow) {
+ mWindow->ObserveStorageNotification(aEvent, aStorageType,
+ aPrivateBrowsing);
+ }
+ }
+
+ nsIPrincipal* GetEffectiveCookiePrincipal() const override {
+ return mWindow ? mWindow->GetEffectiveCookiePrincipal() : nullptr;
+ }
+
+ nsIPrincipal* GetEffectiveStoragePrincipal() const override {
+ return mWindow ? mWindow->GetEffectiveStoragePrincipal() : nullptr;
+ }
+
+ bool IsPrivateBrowsing() const override {
+ return mWindow ? mWindow->IsPrivateBrowsing() : false;
+ }
+
+ nsIEventTarget* GetEventTarget() const override {
+ return mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
+ }
+
+ private:
+ ~nsGlobalWindowObserver() = default;
+
+ // This reference is non-owning and safe because it's cleared by
+ // nsGlobalWindowInner::FreeInnerObjects().
+ nsGlobalWindowInner* MOZ_NON_OWNING_REF mWindow;
+};
+
+NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)
+
+class IdleRequestExecutor;
+
+class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler {
+ public:
+ explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor* aExecutor)
+ : mExecutor(aExecutor) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestExecutorTimeoutHandler)
+
+ bool Call(const char* /* unused */) override;
+
+ private:
+ ~IdleRequestExecutorTimeoutHandler() override = default;
+ RefPtr<IdleRequestExecutor> mExecutor;
+};
+
+NS_IMPL_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler, mExecutor)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutorTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutorTimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+class IdleRequestExecutor final : public nsIRunnable,
+ public nsICancelableRunnable,
+ public nsINamed,
+ public nsIIdleRunnable {
+ public:
+ explicit IdleRequestExecutor(nsGlobalWindowInner* aWindow)
+ : mDispatched(false), mDeadline(TimeStamp::Now()), mWindow(aWindow) {
+ MOZ_DIAGNOSTIC_ASSERT(mWindow);
+
+ mIdlePeriodLimit = {mDeadline, mWindow->LastIdleRequestHandle()};
+ mDelayedExecutorDispatcher = new IdleRequestExecutorTimeoutHandler(this);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)
+
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSINAMED
+ nsresult Cancel() override;
+ void SetDeadline(TimeStamp aDeadline) override;
+
+ bool IsCancelled() const { return !mWindow || mWindow->IsDying(); }
+ // Checks if aRequest shouldn't execute in the current idle period
+ // since it has been queued from a chained call to
+ // requestIdleCallback from within a running idle callback.
+ bool IneligibleForCurrentIdlePeriod(IdleRequest* aRequest) const {
+ return aRequest->Handle() >= mIdlePeriodLimit.mLastRequestIdInIdlePeriod &&
+ TimeStamp::Now() <= mIdlePeriodLimit.mEndOfIdlePeriod;
+ }
+
+ void MaybeUpdateIdlePeriodLimit();
+
+ // Maybe dispatch the IdleRequestExecutor. MabyeDispatch will
+ // schedule a delayed dispatch if the associated window is in the
+ // background or if given a time to wait until dispatching.
+ void MaybeDispatch(TimeStamp aDelayUntil = TimeStamp());
+ void ScheduleDispatch();
+
+ private:
+ struct IdlePeriodLimit {
+ TimeStamp mEndOfIdlePeriod;
+ uint32_t mLastRequestIdInIdlePeriod;
+ };
+
+ void DelayedDispatch(uint32_t aDelay);
+
+ ~IdleRequestExecutor() override = default;
+
+ bool mDispatched;
+ TimeStamp mDeadline;
+ IdlePeriodLimit mIdlePeriodLimit;
+ RefPtr<nsGlobalWindowInner> mWindow;
+ // The timeout handler responsible for dispatching this executor in
+ // the case of immediate dispatch to the idle queue isn't
+ // desirable. This is used if we've dispatched all idle callbacks
+ // that are allowed to run in the current idle period, or if the
+ // associated window is currently in the background.
+ RefPtr<TimeoutHandler> mDelayedExecutorDispatcher;
+ // If not Nothing() then this value is the handle to the currently
+ // scheduled delayed executor dispatcher. This is needed to be able
+ // to cancel the timeout handler in case of the executor being
+ // cancelled.
+ Maybe<int32_t> mDelayedExecutorHandle;
+};
+
+NS_IMPL_CYCLE_COLLECTION(IdleRequestExecutor, mWindow,
+ mDelayedExecutorDispatcher)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+IdleRequestExecutor::GetName(nsACString& aName) {
+ aName.AssignLiteral("IdleRequestExecutor");
+ return NS_OK;
+}
+
+// MOZ_CAN_RUN_SCRIPT_BOUNDARY until nsIRunnable::Run is MOZ_CAN_RUN_SCRIPT.
+// See bug 1535398.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP IdleRequestExecutor::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mDispatched = false;
+ if (mWindow) {
+ RefPtr<nsGlobalWindowInner> window(mWindow);
+ window->ExecuteIdleRequest(mDeadline);
+ }
+
+ return NS_OK;
+}
+
+nsresult IdleRequestExecutor::Cancel() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mDelayedExecutorHandle && mWindow) {
+ mWindow->TimeoutManager().ClearTimeout(
+ mDelayedExecutorHandle.value(), Timeout::Reason::eIdleCallbackTimeout);
+ }
+
+ mWindow = nullptr;
+ return NS_OK;
+}
+
+void IdleRequestExecutor::SetDeadline(TimeStamp aDeadline) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mWindow) {
+ return;
+ }
+
+ mDeadline = aDeadline;
+}
+
+void IdleRequestExecutor::MaybeUpdateIdlePeriodLimit() {
+ if (TimeStamp::Now() > mIdlePeriodLimit.mEndOfIdlePeriod) {
+ mIdlePeriodLimit = {mDeadline, mWindow->LastIdleRequestHandle()};
+ }
+}
+
+void IdleRequestExecutor::MaybeDispatch(TimeStamp aDelayUntil) {
+ // If we've already dispatched the executor we don't want to do it
+ // again. Also, if we've called IdleRequestExecutor::Cancel mWindow
+ // will be null, which indicates that we shouldn't dispatch this
+ // executor either.
+ if (mDispatched || IsCancelled()) {
+ return;
+ }
+
+ mDispatched = true;
+
+ nsPIDOMWindowOuter* outer = mWindow->GetOuterWindow();
+ if (outer && outer->IsBackground()) {
+ // Set a timeout handler with a timeout of 0 ms to throttle idle
+ // callback requests coming from a backround window using
+ // background timeout throttling.
+ DelayedDispatch(0);
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ if (!aDelayUntil || aDelayUntil < now) {
+ ScheduleDispatch();
+ return;
+ }
+
+ TimeDuration delay = aDelayUntil - now;
+ DelayedDispatch(static_cast<uint32_t>(delay.ToMilliseconds()));
+}
+
+void IdleRequestExecutor::ScheduleDispatch() {
+ MOZ_ASSERT(mWindow);
+ mDelayedExecutorHandle = Nothing();
+ RefPtr<IdleRequestExecutor> request = this;
+ NS_DispatchToCurrentThreadQueue(request.forget(), EventQueuePriority::Idle);
+}
+
+void IdleRequestExecutor::DelayedDispatch(uint32_t aDelay) {
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(mDelayedExecutorHandle.isNothing());
+ int32_t handle;
+ mWindow->TimeoutManager().SetTimeout(
+ mDelayedExecutorDispatcher, aDelay, false,
+ Timeout::Reason::eIdleCallbackTimeout, &handle);
+ mDelayedExecutorHandle = Some(handle);
+}
+
+bool IdleRequestExecutorTimeoutHandler::Call(const char* /* unused */) {
+ if (!mExecutor->IsCancelled()) {
+ mExecutor->ScheduleDispatch();
+ }
+ return true;
+}
+
+void nsGlobalWindowInner::ScheduleIdleRequestDispatch() {
+ AssertIsOnMainThread();
+
+ if (!mIdleRequestExecutor) {
+ mIdleRequestExecutor = new IdleRequestExecutor(this);
+ }
+
+ mIdleRequestExecutor->MaybeDispatch();
+}
+
+void nsGlobalWindowInner::SuspendIdleRequests() {
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->Cancel();
+ mIdleRequestExecutor = nullptr;
+ }
+}
+
+void nsGlobalWindowInner::ResumeIdleRequests() {
+ MOZ_ASSERT(!mIdleRequestExecutor);
+
+ ScheduleIdleRequestDispatch();
+}
+
+void nsGlobalWindowInner::RemoveIdleCallback(
+ mozilla::dom::IdleRequest* aRequest) {
+ AssertIsOnMainThread();
+
+ if (aRequest->HasTimeout()) {
+ mTimeoutManager->ClearTimeout(aRequest->GetTimeoutHandle(),
+ Timeout::Reason::eIdleCallbackTimeout);
+ }
+
+ aRequest->removeFrom(mIdleRequestCallbacks);
+}
+
+void nsGlobalWindowInner::RunIdleRequest(IdleRequest* aRequest,
+ DOMHighResTimeStamp aDeadline,
+ bool aDidTimeout) {
+ AssertIsOnMainThread();
+ // XXXbz Do we still need this RefPtr? MOZ_CAN_RUN_SCRIPT should
+ // guarantee that caller is holding a strong ref on the stack.
+ RefPtr<IdleRequest> request(aRequest);
+ RemoveIdleCallback(request);
+ request->IdleRun(this, aDeadline, aDidTimeout);
+}
+
+void nsGlobalWindowInner::ExecuteIdleRequest(TimeStamp aDeadline) {
+ AssertIsOnMainThread();
+ RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+
+ if (!request) {
+ // There are no more idle requests, so stop scheduling idle
+ // request callbacks.
+ return;
+ }
+
+ // If the request that we're trying to execute has been queued
+ // during the current idle period, then dispatch it again at the end
+ // of the idle period.
+ if (mIdleRequestExecutor->IneligibleForCurrentIdlePeriod(request)) {
+ mIdleRequestExecutor->MaybeDispatch(aDeadline);
+ return;
+ }
+
+ DOMHighResTimeStamp deadline = 0.0;
+
+ if (Performance* perf = GetPerformance()) {
+ deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline);
+ }
+
+ mIdleRequestExecutor->MaybeUpdateIdlePeriodLimit();
+ RunIdleRequest(request, deadline, false);
+
+ // Running the idle callback could've suspended the window, in which
+ // case mIdleRequestExecutor will be null.
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->MaybeDispatch();
+ }
+}
+
+class IdleRequestTimeoutHandler final : public TimeoutHandler {
+ public:
+ IdleRequestTimeoutHandler(JSContext* aCx, IdleRequest* aIdleRequest,
+ nsPIDOMWindowInner* aWindow)
+ : TimeoutHandler(aCx), mIdleRequest(aIdleRequest), mWindow(aWindow) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestTimeoutHandler)
+
+ MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
+ RefPtr<nsGlobalWindowInner> window(nsGlobalWindowInner::Cast(mWindow));
+ RefPtr<IdleRequest> request(mIdleRequest);
+ window->RunIdleRequest(request, 0.0, true);
+ return true;
+ }
+
+ private:
+ ~IdleRequestTimeoutHandler() override = default;
+
+ RefPtr<IdleRequest> mIdleRequest;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+NS_IMPL_CYCLE_COLLECTION(IdleRequestTimeoutHandler, mIdleRequest, mWindow)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestTimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+uint32_t nsGlobalWindowInner::RequestIdleCallback(
+ JSContext* aCx, IdleRequestCallback& aCallback,
+ const IdleRequestOptions& aOptions, ErrorResult& aError) {
+ AssertIsOnMainThread();
+
+ if (IsDying()) {
+ return 0;
+ }
+
+ uint32_t handle = mIdleRequestCallbackCounter++;
+
+ RefPtr<IdleRequest> request = new IdleRequest(&aCallback, handle);
+
+ if (aOptions.mTimeout.WasPassed()) {
+ int32_t timeoutHandle;
+ RefPtr<TimeoutHandler> handler(
+ new IdleRequestTimeoutHandler(aCx, request, this));
+
+ nsresult rv = mTimeoutManager->SetTimeout(
+ handler, aOptions.mTimeout.Value(), false,
+ Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+
+ request->SetTimeoutHandle(timeoutHandle);
+ }
+
+ mIdleRequestCallbacks.insertBack(request);
+
+ if (!IsSuspended()) {
+ ScheduleIdleRequestDispatch();
+ }
+
+ return handle;
+}
+
+void nsGlobalWindowInner::CancelIdleCallback(uint32_t aHandle) {
+ for (IdleRequest* r : mIdleRequestCallbacks) {
+ if (r->Handle() == aHandle) {
+ RemoveIdleCallback(r);
+ break;
+ }
+ }
+}
+
+void nsGlobalWindowInner::DisableIdleCallbackRequests() {
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->Cancel();
+ mIdleRequestExecutor = nullptr;
+ }
+
+ while (!mIdleRequestCallbacks.isEmpty()) {
+ RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+ RemoveIdleCallback(request);
+ }
+}
+
+bool nsGlobalWindowInner::IsBackgroundInternal() const {
+ return !mOuterWindow || mOuterWindow->IsBackground();
+}
+
+class PromiseDocumentFlushedResolver final {
+ public:
+ PromiseDocumentFlushedResolver(Promise* aPromise,
+ PromiseDocumentFlushedCallback& aCallback)
+ : mPromise(aPromise), mCallback(&aCallback) {}
+
+ virtual ~PromiseDocumentFlushedResolver() = default;
+
+ void Call() {
+ nsMutationGuard guard;
+ ErrorResult error;
+ JS::Rooted<JS::Value> returnVal(RootingCx());
+ mCallback->Call(&returnVal, error);
+
+ if (error.Failed()) {
+ mPromise->MaybeReject(std::move(error));
+ } else if (guard.Mutated(0)) {
+ // Something within the callback mutated the DOM.
+ mPromise->MaybeRejectWithNoModificationAllowedError(
+ "DOM mutated from promiseDocumentFlushed callbacks");
+ } else {
+ mPromise->MaybeResolve(returnVal);
+ }
+ }
+
+ RefPtr<Promise> mPromise;
+ RefPtr<PromiseDocumentFlushedCallback> mCallback;
+};
+
+//*****************************************************************************
+//*** nsGlobalWindowInner: Object Management
+//*****************************************************************************
+
+nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
+ WindowGlobalChild* aActor)
+ : nsPIDOMWindowInner(aOuterWindow, aActor),
+ mHasOrientationChangeListeners(false),
+ mWasOffline(false),
+ mHasHadSlowScript(false),
+ mIsChrome(false),
+ mCleanMessageManager(false),
+ mNeedsFocus(true),
+ mHasFocus(false),
+ mFocusByKeyOccurred(false),
+ mDidFireDocElemInserted(false),
+ mHasGamepad(false),
+ mHasXRSession(false),
+ mHasVRDisplayActivateEvents(false),
+ mXRRuntimeDetectionInFlight(false),
+ mXRPermissionRequestInFlight(false),
+ mXRPermissionGranted(false),
+ mWasCurrentInnerWindow(false),
+ mHasSeenGamepadInput(false),
+ mHintedWasLoading(false),
+ mHasOpenedExternalProtocolFrame(false),
+ mScrollMarksOnHScrollbar(false),
+ mStorageAllowedReasonCache(0),
+ mSuspendDepth(0),
+ mFreezeDepth(0),
+#ifdef DEBUG
+ mSerial(0),
+#endif
+ mFocusMethod(0),
+ mIdleRequestCallbackCounter(1),
+ mIdleRequestExecutor(nullptr),
+ mObservingRefresh(false),
+ mIteratingDocumentFlushedResolvers(false),
+ mCanSkipCCGeneration(0) {
+ mIsInnerWindow = true;
+
+ AssertIsOnMainThread();
+ nsLayoutStatics::AddRef();
+
+ // Initialize the PRCList (this).
+ PR_INIT_CLIST(this);
+
+ // add this inner window to the outer window list of inners.
+ PR_INSERT_AFTER(this, aOuterWindow);
+
+ mTimeoutManager = MakeUnique<dom::TimeoutManager>(
+ *this, StaticPrefs::dom_timeout_max_idle_defer_ms());
+
+ mObserver = new nsGlobalWindowObserver(this);
+ if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) {
+ // Watch for online/offline status changes so we can fire events. Use
+ // a strong reference.
+ os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+ os->AddObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC, false);
+ os->AddObserver(mObserver, PERMISSION_CHANGED_TOPIC, false);
+ os->AddObserver(mObserver, "screen-information-changed", false);
+ }
+
+ Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
+
+ // Watch for storage notifications so we can fire storage events.
+ RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
+ if (sns) {
+ sns->Register(mObserver);
+ }
+
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (docShell) {
+ mBrowserChild = docShell->GetBrowserChild();
+ }
+ }
+
+ if (gDumpFile == nullptr) {
+ nsAutoCString fname;
+ Preferences::GetCString("browser.dom.window.dump.file", fname);
+ if (!fname.IsEmpty()) {
+ // If this fails to open, Dump() knows to just go to stdout on null.
+ gDumpFile = fopen(fname.get(), "wb+");
+ } else {
+ gDumpFile = stdout;
+ }
+ }
+
+#ifdef DEBUG
+ mSerial = nsContentUtils::InnerOrOuterWindowCreated();
+
+ MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
+ nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
+ static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
+ static_cast<void*>(ToCanonicalSupports(aOuterWindow))));
+#endif
+
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("DOMWINDOW %p created outer=%p", this, aOuterWindow));
+
+ // Add ourselves to the inner windows list.
+ MOZ_ASSERT(sInnerWindowsById, "Inner Windows hash table must be created!");
+ MOZ_ASSERT(!sInnerWindowsById->Contains(mWindowID),
+ "This window shouldn't be in the hash table yet!");
+ // We seem to see crashes in release builds because of null
+ // |sInnerWindowsById|.
+ if (sInnerWindowsById) {
+ sInnerWindowsById->InsertOrUpdate(mWindowID, this);
+ }
+}
+
+#ifdef DEBUG
+
+/* static */
+void nsGlobalWindowInner::AssertIsOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+#endif // DEBUG
+
+/* static */
+void nsGlobalWindowInner::Init() {
+ AssertIsOnMainThread();
+
+ NS_ASSERTION(gDOMLeakPRLogInner,
+ "gDOMLeakPRLogInner should have been initialized!");
+
+ sInnerWindowsById = new InnerWindowByIdTable();
+}
+
+nsGlobalWindowInner::~nsGlobalWindowInner() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mHintedWasLoading);
+
+ if (IsChromeWindow()) {
+ MOZ_ASSERT(mCleanMessageManager,
+ "chrome windows may always disconnect the msg manager");
+
+ DisconnectAndClearGroupMessageManagers();
+
+ if (mChromeFields.mMessageManager) {
+ static_cast<nsFrameMessageManager*>(mChromeFields.mMessageManager.get())
+ ->Disconnect();
+ }
+
+ mCleanMessageManager = false;
+ }
+
+ // In most cases this should already have been called, but call it again
+ // here to catch any corner cases.
+ FreeInnerObjects();
+
+ if (sInnerWindowsById) {
+ sInnerWindowsById->Remove(mWindowID);
+ }
+
+ nsContentUtils::InnerOrOuterWindowDestroyed();
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) {
+ nsAutoCString url;
+ if (mLastOpenedURI) {
+ url = mLastOpenedURI->GetSpecOrDefault();
+
+ // Data URLs can be very long, so truncate to avoid flooding the log.
+ const uint32_t maxURLLength = 1000;
+ if (url.Length() > maxURLLength) {
+ url.Truncate(maxURLLength);
+ }
+ }
+
+ nsGlobalWindowOuter* outer = nsGlobalWindowOuter::Cast(mOuterWindow);
+ MOZ_LOG(
+ gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = "
+ "%s]\n",
+ nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
+ static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
+ static_cast<void*>(ToCanonicalSupports(outer)), url.get()));
+ }
+#endif
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("DOMWINDOW %p destroyed", this));
+
+ Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
+ mMutationBits ? 1 : 0);
+
+ // An inner window is destroyed, pull it out of the outer window's
+ // list if inner windows.
+
+ PR_REMOVE_LINK(this);
+
+ // If our outer window's inner window is this window, null out the
+ // outer window's reference to this window that's being deleted.
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (outer) {
+ outer->MaybeClearInnerWindow(this);
+ }
+
+ // We don't have to leave the tab group if we are an inner window.
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) ac->RemoveWindowAsListener(this);
+
+ nsLayoutStatics::Release();
+}
+
+// static
+void nsGlobalWindowInner::ShutDown() {
+ AssertIsOnMainThread();
+
+ if (gDumpFile && gDumpFile != stdout) {
+ fclose(gDumpFile);
+ }
+ gDumpFile = nullptr;
+
+ delete sInnerWindowsById;
+ sInnerWindowsById = nullptr;
+}
+
+void nsGlobalWindowInner::FreeInnerObjects() {
+ if (IsDying()) {
+ return;
+ }
+ StartDying();
+
+ if (mDoc && mDoc->GetWindowContext()) {
+ // The document is about to lose its window, so this is a good time to send
+ // our page use counters.
+ //
+ // (We also do this in Document::SetScriptGlobalObject(nullptr), which
+ // catches most cases of documents losing their window, but not all.)
+ mDoc->SendPageUseCounters();
+ }
+
+ // Make sure that this is called before we null out the document and
+ // other members that the window destroyed observers could
+ // re-create.
+ NotifyDOMWindowDestroyed(this);
+ if (auto* reporter = nsWindowMemoryReporter::Get()) {
+ reporter->ObserveDOMWindowDetached(this);
+ }
+
+ // Kill all of the workers for this window.
+ CancelWorkersForWindow(*this);
+
+ for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
+ mSharedWorkers.ForwardRange()) {
+ pinnedWorker->Close();
+ }
+
+ if (mTimeoutManager) {
+ mTimeoutManager->ClearAllTimeouts();
+ }
+
+ DisableIdleCallbackRequests();
+
+ mChromeEventHandler = nullptr;
+
+ if (mListenerManager) {
+ mListenerManager->RemoveAllListeners();
+ mListenerManager->Disconnect();
+ mListenerManager = nullptr;
+ }
+
+ mHistory = nullptr;
+
+ if (mNavigator) {
+ mNavigator->OnNavigation();
+ mNavigator->Invalidate();
+ mNavigator = nullptr;
+ }
+
+ mScreen = nullptr;
+
+ if (mDoc) {
+ // Remember the document's principal, URI, and CSP.
+ mDocumentPrincipal = mDoc->NodePrincipal();
+ mDocumentCookiePrincipal = mDoc->EffectiveCookiePrincipal();
+ mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
+ mDocumentPartitionedPrincipal = mDoc->PartitionedPrincipal();
+ mDocumentURI = mDoc->GetDocumentURI();
+ mDocBaseURI = mDoc->GetDocBaseURI();
+ mDocumentCsp = mDoc->GetCsp();
+
+ while (mDoc->EventHandlingSuppressed()) {
+ mDoc->UnsuppressEventHandlingAndFireEvents(false);
+ }
+ }
+
+ // Remove our reference to the document and the document principal.
+ mFocusedElement = nullptr;
+
+ if (mIndexedDB) {
+ mIndexedDB->DisconnectFromGlobal(this);
+ mIndexedDB = nullptr;
+ }
+
+ nsIGlobalObject::UnlinkObjectsInGlobal();
+
+ NotifyWindowIDDestroyed("inner-window-destroyed");
+
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ mAudioContexts[i]->OnWindowDestroy();
+ }
+ mAudioContexts.Clear();
+
+ for (MediaKeys* mediaKeys : mMediaKeysInstances) {
+ mediaKeys->OnInnerWindowDestroy();
+ }
+ mMediaKeysInstances.Clear();
+
+ DisableGamepadUpdates();
+ mHasGamepad = false;
+ mGamepads.Clear();
+ DisableVRUpdates();
+ mHasXRSession = false;
+ mHasVRDisplayActivateEvents = false;
+ mXRRuntimeDetectionInFlight = false;
+ mXRPermissionRequestInFlight = false;
+ mXRPermissionGranted = false;
+ mVRDisplays.Clear();
+
+ // This breaks a cycle between the window and the ClientSource object.
+ mClientSource.reset();
+
+ if (mWindowGlobalChild) {
+ // Remove any remaining listeners.
+ int64_t nListeners = mWindowGlobalChild->BeforeUnloadListeners();
+ for (int64_t i = 0; i < nListeners; ++i) {
+ mWindowGlobalChild->BeforeUnloadRemoved();
+ }
+ MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() == 0);
+ }
+
+ // If we have any promiseDocumentFlushed callbacks, fire them now so
+ // that the Promises can resolve.
+ CallDocumentFlushedResolvers(/* aUntilExhaustion = */ true);
+
+ DisconnectGlobalTeardownObservers();
+
+#ifdef MOZ_WIDGET_ANDROID
+ DisableOrientationChangeListener();
+#endif
+
+ if (mObserver) {
+ if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) {
+ os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);
+ os->RemoveObserver(mObserver, PERMISSION_CHANGED_TOPIC);
+ os->RemoveObserver(mObserver, "screen-information-changed");
+ }
+
+ RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
+ if (sns) {
+ sns->Unregister(mObserver);
+ }
+
+ Preferences::RemoveObserver(mObserver, "intl.accept_languages");
+
+ // Drop its reference to this dying window, in case for some bogus reason
+ // the object stays around.
+ mObserver->Forget();
+ }
+
+ mMenubar = nullptr;
+ mToolbar = nullptr;
+ mLocationbar = nullptr;
+ mPersonalbar = nullptr;
+ mStatusbar = nullptr;
+ mScrollbars = nullptr;
+
+ mConsole = nullptr;
+
+ mPaintWorklet = nullptr;
+
+ mExternal = nullptr;
+ mInstallTrigger = nullptr;
+
+ if (mLocalStorage) {
+ mLocalStorage->Disconnect();
+ mLocalStorage = nullptr;
+ }
+ mSessionStorage = nullptr;
+ mPerformance = nullptr;
+
+ mContentMediaController = nullptr;
+
+ if (mWebTaskScheduler) {
+ mWebTaskScheduler->Disconnect();
+ mWebTaskScheduler = nullptr;
+ }
+
+ mSharedWorkers.Clear();
+
+#ifdef MOZ_WEBSPEECH
+ mSpeechSynthesis = nullptr;
+#endif
+
+ mGlean = nullptr;
+ mGleanPings = nullptr;
+
+ mParentTarget = nullptr;
+
+ if (mCleanMessageManager) {
+ MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
+ if (mChromeFields.mMessageManager) {
+ mChromeFields.mMessageManager->Disconnect();
+ }
+ }
+
+ if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
+ mWindowGlobalChild->Destroy();
+ }
+
+ mIntlUtils = nullptr;
+
+ HintIsLoading(false);
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsISupports
+//*****************************************************************************
+
+// QueryInterface implementation for nsGlobalWindowInner
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowInner)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowInner)
+ NS_INTERFACE_MAP_ENTRY(mozIDOMWindow)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowInner)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowInner)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowInner)
+ if (tmp->IsBlackForCC(false)) {
+ if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
+ return true;
+ }
+ tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
+ if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
+ elm->MarkForCC();
+ }
+ if (tmp->mTimeoutManager) {
+ tmp->mTimeoutManager->UnmarkGrayTimers();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindowInner)
+ return tmp->IsBlackForCC(true);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindowInner)
+ return tmp->IsBlackForCC(false);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowInner)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ nsAutoCString uri;
+ if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
+ uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
+ }
+ SprintfLiteral(name, "nsGlobalWindowInner # %" PRIu64 " inner %s",
+ tmp->mWindowID, uri.get());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowInner, tmp->mRefCnt.get())
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler)
+
+#ifdef MOZ_WEBSPEECH
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
+#endif
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlean)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGleanPings)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopInnerWindow)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
+
+ if (tmp->mTimeoutManager) {
+ tmp->mTimeoutManager->ForEachUnorderedTimeout([&cb](Timeout* timeout) {
+ cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout));
+ });
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedWorkers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentCookiePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPartitionedPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentCsp)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserChild)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
+ for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
+ cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClientSource)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDebuggerNotificationManager)
+
+ // Traverse stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobalChild)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaintWorklet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualViewport)
+
+ tmp->TraverseObjectsInGlobal(cb);
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)
+
+ for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback);
+ }
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+ if (sInnerWindowsById) {
+ sInnerWindowsById->Remove(tmp->mWindowID);
+ }
+
+ JSObject* wrapper = tmp->GetWrapperPreserveColor();
+ if (wrapper) {
+ // Mark our realm as dead, so the JS engine won't hand out our
+ // global after this point.
+ JS::SetRealmNonLive(js::GetNonCCWObjectRealm(wrapper));
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
+
+ if (tmp->mWebTaskScheduler) {
+ tmp->mWebTaskScheduler->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler)
+ }
+
+#ifdef MOZ_WEBSPEECH
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
+#endif
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlean)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGleanPings)
+
+ if (tmp->mOuterWindow) {
+ nsGlobalWindowOuter::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
+ }
+
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
+ }
+
+ // Here the Timeouts list would've been unlinked, but we rely on
+ // that Timeout objects have been traced and will remove themselves
+ // while unlinking.
+
+ tmp->UpdateTopInnerWindow();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopInnerWindow)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharedWorkers)
+ if (tmp->mLocalStorage) {
+ tmp->mLocalStorage->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
+ if (tmp->mIndexedDB) {
+ tmp->mIndexedDB->DisconnectFromGlobal(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentCookiePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPartitionedPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentCsp)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserChild)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDebuggerNotificationManager)
+
+ // Unlink stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !tmp->mWindowGlobalChild || tmp->mWindowGlobalChild->IsClosed(),
+ "How are we unlinking a window before its actor has been destroyed?");
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobalChild)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaintWorklet)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualViewport)
+
+ tmp->UnlinkObjectsInGlobal();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
+
+ // Here the IdleRequest list would've been unlinked, but we rely on
+ // that IdleRequest objects have been traced and will remove
+ // themselves while unlinking.
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mClientSource)
+
+ if (tmp->IsChromeWindow()) {
+ if (tmp->mChromeFields.mMessageManager) {
+ static_cast<nsFrameMessageManager*>(
+ tmp->mChromeFields.mMessageManager.get())
+ ->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager)
+ }
+ tmp->DisconnectAndClearGroupMessageManagers();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mGroupMessageManagers)
+ }
+
+ for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback);
+ }
+ tmp->mDocumentFlushedResolvers.Clear();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+#ifdef DEBUG
+void nsGlobalWindowInner::RiskyUnlink() {
+ NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
+}
+#endif
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowInner)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+bool nsGlobalWindowInner::IsBlackForCC(bool aTracingNeeded) {
+ if (!nsCCUncollectableMarker::sGeneration) {
+ return false;
+ }
+
+ return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
+ HasKnownLiveWrapper()) &&
+ (!aTracingNeeded || HasNothingToTrace(ToSupports(this)));
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsIScriptGlobalObject
+//*****************************************************************************
+
+bool nsGlobalWindowInner::ShouldResistFingerprinting(
+ RFPTarget aTarget /* = RFPTarget::Unknown */) const {
+ if (mDoc) {
+ return mDoc->ShouldResistFingerprinting(aTarget);
+ }
+ return nsContentUtils::ShouldResistFingerprinting(
+ "If we do not have a document then we do not have any context"
+ "to make an informed RFP choice, so we fall back to the global pref",
+ aTarget);
+}
+
+OriginTrials nsGlobalWindowInner::Trials() const {
+ return OriginTrials::FromWindow(this);
+}
+
+FontFaceSet* nsGlobalWindowInner::GetFonts() {
+ if (mDoc) {
+ return mDoc->Fonts();
+ }
+ return nullptr;
+}
+
+mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult>
+nsGlobalWindowInner::GetStorageKey() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsIPrincipal* principal = GetEffectiveStoragePrincipal();
+ if (!principal) {
+ return mozilla::Err(NS_ERROR_FAILURE);
+ }
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
+ if (NS_FAILED(rv)) {
+ return mozilla::Err(rv);
+ }
+
+ // Block expanded and null principals, let content and system through.
+ if (principalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
+ principalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
+ return Err(NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ return std::move(principalInfo);
+}
+
+mozilla::dom::StorageManager* nsGlobalWindowInner::GetStorageManager() {
+ return Navigator()->Storage();
+}
+
+nsresult nsGlobalWindowInner::EnsureScriptEnvironment() {
+ // NOTE: We can't use FORWARD_TO_OUTER here because we don't want to fail if
+ // we're called on an inactive inner window.
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ return NS_ERROR_FAILURE;
+ }
+ return outer->EnsureScriptEnvironment();
+}
+
+nsIScriptContext* nsGlobalWindowInner::GetScriptContext() {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (!outer) {
+ return nullptr;
+ }
+ return outer->GetScriptContext();
+}
+
+void nsGlobalWindowInner::TraceGlobalJSObject(JSTracer* aTrc) {
+ TraceWrapper(aTrc, "active window global");
+}
+
+void nsGlobalWindowInner::UpdateAutoplayPermission() {
+ if (!GetWindowContext()) {
+ return;
+ }
+ uint32_t perm =
+ media::AutoplayPolicy::GetSiteAutoplayPermission(GetPrincipal());
+ if (GetWindowContext()->GetAutoplayPermission() == perm) {
+ return;
+ }
+
+ // Setting autoplay permission on a discarded context has no effect.
+ Unused << GetWindowContext()->SetAutoplayPermission(perm);
+}
+
+void nsGlobalWindowInner::UpdateShortcutsPermission() {
+ if (!GetWindowContext() ||
+ !GetWindowContext()->GetBrowsingContext()->IsTop()) {
+ // We only cache the shortcuts permission on top-level WindowContexts
+ // since we always check the top-level principal for the permission.
+ return;
+ }
+
+ uint32_t perm = GetShortcutsPermission(GetPrincipal());
+
+ if (GetWindowContext()->GetShortcutsPermission() == perm) {
+ return;
+ }
+
+ // If the WindowContext is discarded this has no effect.
+ Unused << GetWindowContext()->SetShortcutsPermission(perm);
+}
+
+/* static */
+uint32_t nsGlobalWindowInner::GetShortcutsPermission(nsIPrincipal* aPrincipal) {
+ uint32_t perm = nsIPermissionManager::DENY_ACTION;
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ mozilla::components::PermissionManager::Service();
+ if (aPrincipal && permMgr) {
+ permMgr->TestExactPermissionFromPrincipal(aPrincipal, "shortcuts"_ns,
+ &perm);
+ }
+ return perm;
+}
+
+void nsGlobalWindowInner::UpdatePopupPermission() {
+ if (!GetWindowContext()) {
+ return;
+ }
+
+ uint32_t perm = PopupBlocker::GetPopupPermission(GetPrincipal());
+ if (GetWindowContext()->GetPopupPermission() == perm) {
+ return;
+ }
+
+ // If the WindowContext is discarded this has no effect.
+ Unused << GetWindowContext()->SetPopupPermission(perm);
+}
+
+void nsGlobalWindowInner::UpdatePermissions() {
+ if (!GetWindowContext()) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
+ RefPtr<WindowContext> windowContext = GetWindowContext();
+
+ WindowContext::Transaction txn;
+ txn.SetAutoplayPermission(
+ media::AutoplayPolicy::GetSiteAutoplayPermission(principal));
+ txn.SetPopupPermission(PopupBlocker::GetPopupPermission(principal));
+
+ if (windowContext->IsTop()) {
+ txn.SetShortcutsPermission(GetShortcutsPermission(principal));
+ }
+
+ // Setting permissions on a discarded WindowContext has no effect
+ Unused << txn.Commit(windowContext);
+}
+
+void nsGlobalWindowInner::InitDocumentDependentState(JSContext* aCx) {
+ MOZ_ASSERT(mDoc);
+
+ if (MOZ_LOG_TEST(gDOMLeakPRLogInner, LogLevel::Debug)) {
+ nsIURI* uri = mDoc->GetDocumentURI();
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("DOMWINDOW %p SetNewDocument %s", this,
+ uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+
+ mFocusedElement = nullptr;
+ mLocalStorage = nullptr;
+ mSessionStorage = nullptr;
+ mPerformance = nullptr;
+ if (mWebTaskScheduler) {
+ mWebTaskScheduler->Disconnect();
+ mWebTaskScheduler = nullptr;
+ }
+
+ // This must be called after nullifying the internal objects because here we
+ // could recreate them, calling the getter methods, and store them into the JS
+ // slots. If we nullify them after, the slot values and the objects will be
+ // out of sync.
+ ClearDocumentDependentSlots(aCx);
+
+ if (!mWindowGlobalChild) {
+ mWindowGlobalChild = WindowGlobalChild::Create(this);
+ }
+ MOZ_ASSERT(!GetWindowContext()->HasBeenUserGestureActivated(),
+ "WindowContext should always not have user gesture activation at "
+ "this point.");
+
+ UpdatePermissions();
+
+ RefPtr<PermissionDelegateHandler> permDelegateHandler =
+ mDoc->GetPermissionDelegateHandler();
+
+ if (permDelegateHandler) {
+ permDelegateHandler->PopulateAllDelegatedPermissions();
+ }
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // When we insert the new document to the window in the top-level browsing
+ // context, we should reset the status of the request which is used for the
+ // previous document.
+ if (mWindowGlobalChild && GetBrowsingContext() &&
+ !GetBrowsingContext()->GetParent()) {
+ // Return value of setting synced field should be checked. See bug 1656492.
+ Unused << GetBrowsingContext()->ResetGVAutoplayRequestStatus();
+ }
+#endif
+
+#ifdef DEBUG
+ mLastOpenedURI = mDoc->GetDocumentURI();
+#endif
+
+ Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
+ mMutationBits ? 1 : 0);
+
+ // Clear our mutation bitfield.
+ mMutationBits = 0;
+}
+
+nsresult nsGlobalWindowInner::EnsureClientSource() {
+ MOZ_DIAGNOSTIC_ASSERT(mDoc);
+
+ bool newClientSource = false;
+
+ // Get the load info for the document if we performed a load. Be careful not
+ // to look at local URLs, though. Local URLs are those that have a scheme of:
+ // * about:
+ // * data:
+ // * blob:
+ // We also do an additional check here so that we only treat about:blank
+ // and about:srcdoc as local URLs. Other internal firefox about: URLs should
+ // not be treated this way.
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ Unused << channel->GetURI(getter_AddRefs(uri));
+
+ bool ignoreLoadInfo = false;
+
+ // Note, this is mostly copied from NS_IsAboutBlank(). Its duplicated
+ // here so we can efficiently check about:srcdoc as well.
+ if (uri->SchemeIs("about")) {
+ nsCString spec = uri->GetSpecOrDefault();
+ ignoreLoadInfo = spec.EqualsLiteral("about:blank") ||
+ spec.EqualsLiteral("about:srcdoc");
+ } else {
+ // Its not an about: URL, so now check for our other URL types.
+ ignoreLoadInfo = uri->SchemeIs("data") || uri->SchemeIs("blob");
+ }
+
+ if (!ignoreLoadInfo) {
+ loadInfo = channel->LoadInfo();
+ }
+ }
+
+ // Take the initial client source from the docshell immediately. Even if we
+ // don't end up using it here we should consume it.
+ UniquePtr<ClientSource> initialClientSource;
+ nsIDocShell* docshell = GetDocShell();
+ if (docshell) {
+ initialClientSource = docshell->TakeInitialClientSource();
+ }
+
+ // Try to get the reserved client from the LoadInfo. A Client is
+ // reserved at the start of the channel load if there is not an
+ // initial about:blank document that will be reused. It is also
+ // created if the channel load encounters a cross-origin redirect.
+ if (loadInfo) {
+ UniquePtr<ClientSource> reservedClient =
+ loadInfo->TakeReservedClientSource();
+ if (reservedClient) {
+ mClientSource.reset();
+ mClientSource = std::move(reservedClient);
+ newClientSource = true;
+ }
+ }
+
+ // We don't have a LoadInfo reserved client, but maybe we should
+ // be inheriting an initial one from the docshell. This means
+ // that the docshell started the channel load before creating the
+ // initial about:blank document. This is an optimization, though,
+ // and it created an initial Client as a placeholder for the document.
+ // In this case we want to inherit this placeholder Client here.
+ if (!mClientSource) {
+ mClientSource = std::move(initialClientSource);
+ if (mClientSource) {
+ newClientSource = true;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal;
+
+ nsresult rv = StoragePrincipalHelper::GetPrincipal(
+ this,
+ StaticPrefs::privacy_partition_serviceWorkers()
+ ? StoragePrincipalHelper::eForeignPartitionedPrincipal
+ : StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(foreignPartitionedPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Verify the final ClientSource principal matches the final document
+ // principal. The ClientChannelHelper handles things like network
+ // redirects, but there are other ways the document principal can change.
+ // For example, if something sets the nsIChannel.owner property, then
+ // the final channel principal can be anything. Unfortunately there is
+ // no good way to detect this until after the channel completes loading.
+ //
+ // For now we handle this just by reseting the ClientSource. This will
+ // result in a new ClientSource with the correct principal being created.
+ // To APIs like ServiceWorker and Clients API it will look like there was
+ // an initial content page created that was then immediately replaced.
+ // This is pretty close to what we are actually doing.
+ if (mClientSource) {
+ auto principalOrErr = mClientSource->Info().GetPrincipal();
+ nsCOMPtr<nsIPrincipal> clientPrincipal =
+ principalOrErr.isOk() ? principalOrErr.unwrap() : nullptr;
+ if (!clientPrincipal ||
+ !clientPrincipal->Equals(foreignPartitionedPrincipal)) {
+ mClientSource.reset();
+ }
+ }
+
+ // If we don't have a reserved client or an initial client, then create
+ // one now. This can happen in certain cases where we avoid preallocating
+ // the client in the docshell. This mainly occurs in situations where
+ // the principal is not clearly inherited from the parent; e.g. sandboxed
+ // iframes, window.open(), etc.
+ //
+ // We also do this late ClientSource creation if the final document ended
+ // up with a different principal.
+ //
+ // TODO: We may not be marking initial about:blank documents created
+ // this way as controlled by a service worker properly. The
+ // controller should be coming from the same place as the inheritted
+ // principal. We do this in docshell, but as mentioned we aren't
+ // smart enough to handle all cases yet. For example, a
+ // window.open() with new URL should inherit the controller from
+ // the opener, but we probably don't handle that yet.
+ if (!mClientSource) {
+ mClientSource = ClientManager::CreateSource(
+ ClientType::Window, EventTargetFor(TaskCategory::Other),
+ foreignPartitionedPrincipal);
+ MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+ newClientSource = true;
+
+ // Note, we don't apply the loadinfo controller below if we create
+ // the ClientSource here.
+ }
+
+ // The load may have started controlling the Client as well. If
+ // so, mark it as controlled immediately here. The actor may
+ // or may not have been notified by the parent side about being
+ // controlled yet.
+ //
+ // Note: We should be careful not to control a client that was created late.
+ // These clients were not seen by the ServiceWorkerManager when it
+ // marked the LoadInfo controlled and it won't know about them. Its
+ // also possible we are creating the client late due to the final
+ // principal changing and these clients should definitely not be
+ // controlled by a service worker with a different principal.
+ else if (loadInfo) {
+ const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
+ if (controller.isSome()) {
+ mClientSource->SetController(controller.ref());
+ }
+
+ // We also have to handle the case where te initial about:blank is
+ // controlled due to inheritting the service worker from its parent,
+ // but the actual nsIChannel load is not covered by any service worker.
+ // In this case we want the final page to be uncontrolled. There is
+ // an open spec issue about how exactly this should be handled, but for
+ // now we just force creation of a new ClientSource to clear the
+ // controller.
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1232
+ //
+ else if (mClientSource->GetController().isSome()) {
+ mClientSource.reset();
+ mClientSource = ClientManager::CreateSource(
+ ClientType::Window, EventTargetFor(TaskCategory::Other),
+ foreignPartitionedPrincipal);
+ MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+ newClientSource = true;
+ }
+ }
+
+ if (mClientSource) {
+ // Generally the CSP is stored within the Client and cached on the document.
+ // At the time of CSP parsing however, the Client has not been created yet,
+ // hence we store the CSP on the document and propagate/sync the CSP with
+ // Client here when we create the Client.
+ mClientSource->SetCsp(mDoc->GetCsp());
+
+ DocGroup* docGroup = GetDocGroup();
+ MOZ_DIAGNOSTIC_ASSERT(docGroup);
+ mClientSource->SetAgentClusterId(docGroup->AgentClusterId());
+
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
+ }
+ }
+
+ // Its possible that we got a client just after being frozen in
+ // the bfcache. In that case freeze the client immediately.
+ if (newClientSource && IsFrozen()) {
+ mClientSource->Freeze();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsGlobalWindowInner::ExecutionReady() {
+ nsresult rv = EnsureClientSource();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mClientSource->WindowExecutionReady(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void nsGlobalWindowInner::UpdateParentTarget() {
+ // NOTE: This method is identical to
+ // nsGlobalWindowOuter::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
+ // UPDATE THE OTHER ONE TOO!
+
+ // Try to get our frame element's tab child global (its in-process message
+ // manager). If that fails, fall back to the chrome event handler's tab
+ // child global, and if it doesn't have one, just use the chrome event
+ // handler itself.
+
+ nsPIDOMWindowOuter* outer = GetOuterWindow();
+ if (!outer) {
+ return;
+ }
+ nsCOMPtr<Element> frameElement = outer->GetFrameElementInternal();
+ nsCOMPtr<EventTarget> eventTarget =
+ nsContentUtils::TryGetBrowserChildGlobal(frameElement);
+
+ if (!eventTarget) {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+ if (topWin) {
+ frameElement = topWin->GetFrameElementInternal();
+ eventTarget = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
+ }
+ }
+
+ if (!eventTarget) {
+ eventTarget = nsContentUtils::TryGetBrowserChildGlobal(mChromeEventHandler);
+ }
+
+ if (!eventTarget) {
+ eventTarget = mChromeEventHandler;
+ }
+
+ mParentTarget = eventTarget;
+}
+
+EventTarget* nsGlobalWindowInner::GetTargetForDOMEvent() {
+ return GetOuterWindowInternal();
+}
+
+void nsGlobalWindowInner::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ EventMessage msg = aVisitor.mEvent->mMessage;
+
+ aVisitor.mCanHandle = true;
+ aVisitor.mForceContentDispatch = true; // FIXME! Bug 329119
+ if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
+ // Checking whether the event target is an inner window or not, so we can
+ // keep the old behavior also in case a child window is handling resize.
+ if (aVisitor.mEvent->mOriginalTarget &&
+ aVisitor.mEvent->mOriginalTarget->IsInnerWindow()) {
+ mIsHandlingResizeEvent = true;
+ }
+ } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) {
+ sMouseDown = true;
+ } else if ((msg == eMouseUp || msg == eDragEnd) &&
+ aVisitor.mEvent->IsTrusted()) {
+ sMouseDown = false;
+ if (sDragServiceDisabled) {
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ sDragServiceDisabled = false;
+ ds->Unsuppress();
+ }
+ }
+ }
+
+ aVisitor.SetParentTarget(GetParentTarget(), true);
+}
+
+void nsGlobalWindowInner::FireFrameLoadEvent() {
+ // If we're not in a content frame, or are at a BrowsingContext tree boundary,
+ // such as the content-chrome boundary, don't fire the "load" event.
+ if (GetBrowsingContext()->IsTopContent() ||
+ GetBrowsingContext()->IsChrome()) {
+ return;
+ }
+
+ // If embedder is same-process, fire the event on our embedder element.
+ //
+ // XXX: Bug 1440212 is looking into potentially changing this behaviour to act
+ // more like the remote case when in-process.
+ RefPtr<Element> element = GetBrowsingContext()->GetEmbedderElement();
+ if (element) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(/* aIsTrusted = */ true, eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+
+ if (mozilla::dom::DocGroup::TryToLoadIframesInBackground()) {
+ nsDocShell* ds = nsDocShell::Cast(GetDocShell());
+
+ if (ds && !ds->HasFakeOnLoadDispatched()) {
+ EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
+ }
+ } else {
+ // Most of the time we could get a pres context to pass in here,
+ // but not always (i.e. if this window is not shown there won't
+ // be a pres context available). Since we're not firing a GUI
+ // event we don't need a pres context anyway so we just pass
+ // null as the pres context all the time here.
+ EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
+ }
+ return;
+ }
+
+ // We don't have an in-process embedder. Try to get our `BrowserChild` actor
+ // to send a message to that embedder. We want to double-check that our outer
+ // window is actually the one at the root of this browserChild though, just in
+ // case.
+ RefPtr<BrowserChild> browserChild =
+ BrowserChild::GetFrom(static_cast<nsPIDOMWindowInner*>(this));
+ if (browserChild) {
+ // Double-check that our outer window is actually at the root of this
+ // `BrowserChild`, in case we're in an odd maybe-unhosted situation like a
+ // print preview dialog.
+ nsCOMPtr<nsPIDOMWindowOuter> rootOuter =
+ do_GetInterface(browserChild->WebNavigation());
+ if (!rootOuter || rootOuter != GetOuterWindow()) {
+ return;
+ }
+
+ mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
+ EmbedderElementEventType::LoadEvent);
+ }
+}
+
+nsresult nsGlobalWindowInner::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ // Return early if there is nothing to do.
+ switch (aVisitor.mEvent->mMessage) {
+ case eResize:
+ case eUnload:
+ case eLoad:
+ break;
+ default:
+ return NS_OK;
+ }
+
+ /* mChromeEventHandler and mContext go dangling in the middle of this
+ function under some circumstances (events that destroy the window)
+ without this addref. */
+ RefPtr<EventTarget> kungFuDeathGrip1(mChromeEventHandler);
+ mozilla::Unused
+ << kungFuDeathGrip1; // These aren't referred to through the function
+ nsCOMPtr<nsIScriptContext> kungFuDeathGrip2(GetContextInternal());
+ mozilla::Unused
+ << kungFuDeathGrip2; // These aren't referred to through the function
+
+ if (aVisitor.mEvent->mMessage == eResize) {
+ mIsHandlingResizeEvent = false;
+ } else if (aVisitor.mEvent->mMessage == eUnload &&
+ aVisitor.mEvent->IsTrusted()) {
+ // If any VR display presentation is active at unload, the next page
+ // will receive a vrdisplayactive event to indicate that it should
+ // immediately begin vr presentation. This should occur when navigating
+ // forwards, navigating backwards, and on page reload.
+ for (const auto& display : mVRDisplays) {
+ if (display->IsPresenting()) {
+ display->StartVRNavigation();
+ // Save this VR display ID to trigger vrdisplayactivate event
+ // after the next load event.
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (outer) {
+ outer->SetAutoActivateVRDisplayID(display->DisplayId());
+ }
+
+ // XXX The WebVR 1.1 spec does not define which of multiple VR
+ // presenting VR displays will be chosen during navigation.
+ // As the underlying platform VR API's currently only allow a single
+ // VR display, it is safe to choose the first VR display for now.
+ break;
+ }
+ }
+ mIsDocumentLoaded = false;
+ // Tell the parent process that the document is not loaded.
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->SendUpdateDocumentHasLoaded(mIsDocumentLoaded);
+ }
+ } else if (aVisitor.mEvent->mMessage == eLoad &&
+ aVisitor.mEvent->IsTrusted()) {
+ // This is page load event since load events don't propagate to |window|.
+ // @see Document::GetEventTargetParent.
+ mIsDocumentLoaded = true;
+ // Tell the parent process that the document is loaded.
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->SendUpdateDocumentHasLoaded(mIsDocumentLoaded);
+ }
+
+ mTimeoutManager->OnDocumentLoaded();
+
+ MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
+ FireFrameLoadEvent();
+
+ if (mVREventObserver) {
+ mVREventObserver->NotifyAfterLoad();
+ }
+
+ uint32_t autoActivateVRDisplayID = 0;
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (outer) {
+ autoActivateVRDisplayID = outer->GetAutoActivateVRDisplayID();
+ }
+ if (autoActivateVRDisplayID) {
+ DispatchVRDisplayActivate(autoActivateVRDisplayID,
+ VRDisplayEventReason::Navigation);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsGlobalWindowInner::DefineArgumentsProperty(nsIArray* aArguments) {
+ nsIScriptContext* ctx = GetOuterWindowInternal()->mContext;
+ NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED);
+
+ JS::Rooted<JSObject*> obj(RootingCx(), GetWrapperPreserveColor());
+ return ctx->SetProperty(obj, "arguments", aArguments);
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsIScriptObjectPrincipal
+//*****************************************************************************
+
+nsIPrincipal* nsGlobalWindowInner::GetPrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->NodePrincipal();
+ }
+
+ if (mDocumentPrincipal) {
+ return mDocumentPrincipal;
+ }
+
+ // If we don't have a principal and we don't have a document we
+ // ask the parent window for the principal. This can happen when
+ // loading a frameset that has a <frame src="javascript:xxx">, in
+ // that case the global window is used in JS before we've loaded
+ // a document into the window.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetPrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowInner::GetEffectiveCookiePrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->EffectiveCookiePrincipal();
+ }
+
+ if (mDocumentCookiePrincipal) {
+ return mDocumentCookiePrincipal;
+ }
+
+ // If we don't have a cookie principal and we don't have a document we ask
+ // the parent window for the cookie principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetEffectiveCookiePrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowInner::GetEffectiveStoragePrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->EffectiveStoragePrincipal();
+ }
+
+ if (mDocumentStoragePrincipal) {
+ return mDocumentStoragePrincipal;
+ }
+
+ // If we don't have a cookie principal and we don't have a document we ask
+ // the parent window for the cookie principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetEffectiveStoragePrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowInner::PartitionedPrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->PartitionedPrincipal();
+ }
+
+ if (mDocumentPartitionedPrincipal) {
+ return mDocumentPartitionedPrincipal;
+ }
+
+ // If we don't have a partitioned principal and we don't have a document we
+ // ask the parent window for the partitioned principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->PartitionedPrincipal();
+ }
+
+ return nullptr;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsIDOMWindow
+//*****************************************************************************
+
+bool nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext) {
+ mAudioContexts.AppendElement(aAudioContext);
+
+ // Return true if the context should be muted and false if not.
+ nsIDocShell* docShell = GetDocShell();
+ return docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline();
+}
+
+void nsPIDOMWindowInner::RemoveAudioContext(AudioContext* aAudioContext) {
+ mAudioContexts.RemoveElement(aAudioContext);
+}
+
+void nsPIDOMWindowInner::MuteAudioContexts() {
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ if (!mAudioContexts[i]->IsOffline()) {
+ mAudioContexts[i]->Mute();
+ }
+ }
+}
+
+void nsPIDOMWindowInner::UnmuteAudioContexts() {
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ if (!mAudioContexts[i]->IsOffline()) {
+ mAudioContexts[i]->Unmute();
+ }
+ }
+}
+
+WindowProxyHolder nsGlobalWindowInner::Window() {
+ return WindowProxyHolder(GetBrowsingContext());
+}
+
+Navigator* nsPIDOMWindowInner::Navigator() {
+ if (!mNavigator) {
+ mNavigator = new mozilla::dom::Navigator(this);
+ }
+
+ return mNavigator;
+}
+
+MediaDevices* nsPIDOMWindowInner::GetExtantMediaDevices() const {
+ return mNavigator ? mNavigator->GetExtantMediaDevices() : nullptr;
+}
+
+VisualViewport* nsGlobalWindowInner::VisualViewport() {
+ if (!mVisualViewport) {
+ mVisualViewport = new mozilla::dom::VisualViewport(this);
+ }
+
+ return mVisualViewport;
+}
+
+nsScreen* nsGlobalWindowInner::GetScreen(ErrorResult& aError) {
+ if (!mScreen) {
+ mScreen = nsScreen::Create(this);
+ if (!mScreen) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ }
+
+ return mScreen;
+}
+
+nsHistory* nsGlobalWindowInner::GetHistory(ErrorResult& aError) {
+ if (!mHistory) {
+ mHistory = new nsHistory(this);
+ }
+
+ return mHistory;
+}
+
+CustomElementRegistry* nsGlobalWindowInner::CustomElements() {
+ if (!mCustomElements) {
+ mCustomElements = new CustomElementRegistry(this);
+ }
+
+ return mCustomElements;
+}
+
+CustomElementRegistry* nsGlobalWindowInner::GetExistingCustomElements() {
+ return mCustomElements;
+}
+
+Performance* nsPIDOMWindowInner::GetPerformance() {
+ CreatePerformanceObjectIfNeeded();
+ return mPerformance;
+}
+
+void nsPIDOMWindowInner::QueuePerformanceNavigationTiming() {
+ CreatePerformanceObjectIfNeeded();
+ if (mPerformance) {
+ mPerformance->QueueNavigationTimingEntry();
+ }
+}
+
+void nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() {
+ if (mPerformance || !mDoc) {
+ return;
+ }
+ RefPtr<nsDOMNavigationTiming> timing = mDoc->GetNavigationTiming();
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(mDoc->GetChannel()));
+ bool timingEnabled = false;
+ if (!timedChannel ||
+ !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) ||
+ !timingEnabled) {
+ timedChannel = nullptr;
+ }
+ if (timing) {
+ mPerformance = Performance::CreateForMainThread(this, mDoc->NodePrincipal(),
+ timing, timedChannel);
+ }
+}
+
+bool nsPIDOMWindowInner::IsSecureContext() const {
+ return nsGlobalWindowInner::Cast(this)->IsSecureContext();
+}
+
+void nsPIDOMWindowInner::Suspend(bool aIncludeSubWindows) {
+ nsGlobalWindowInner::Cast(this)->Suspend(aIncludeSubWindows);
+}
+
+void nsPIDOMWindowInner::Resume(bool aIncludeSubWindows) {
+ nsGlobalWindowInner::Cast(this)->Resume(aIncludeSubWindows);
+}
+
+void nsPIDOMWindowInner::SyncStateFromParentWindow() {
+ nsGlobalWindowInner::Cast(this)->SyncStateFromParentWindow();
+}
+
+Maybe<ClientInfo> nsPIDOMWindowInner::GetClientInfo() const {
+ return nsGlobalWindowInner::Cast(this)->GetClientInfo();
+}
+
+Maybe<ClientState> nsPIDOMWindowInner::GetClientState() const {
+ return nsGlobalWindowInner::Cast(this)->GetClientState();
+}
+
+Maybe<ServiceWorkerDescriptor> nsPIDOMWindowInner::GetController() const {
+ return nsGlobalWindowInner::Cast(this)->GetController();
+}
+
+void nsPIDOMWindowInner::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ return nsGlobalWindowInner::Cast(this)->SetCsp(aCsp);
+}
+
+void nsPIDOMWindowInner::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp) {
+ return nsGlobalWindowInner::Cast(this)->SetPreloadCsp(aPreloadCsp);
+}
+
+nsIContentSecurityPolicy* nsPIDOMWindowInner::GetCsp() {
+ return nsGlobalWindowInner::Cast(this)->GetCsp();
+}
+
+void nsPIDOMWindowInner::NoteCalledRegisterForServiceWorkerScope(
+ const nsACString& aScope) {
+ nsGlobalWindowInner::Cast(this)->NoteCalledRegisterForServiceWorkerScope(
+ aScope);
+}
+
+void nsPIDOMWindowInner::NoteDOMContentLoaded() {
+ nsGlobalWindowInner::Cast(this)->NoteDOMContentLoaded();
+}
+
+bool nsGlobalWindowInner::ShouldReportForServiceWorkerScope(
+ const nsAString& aScope) {
+ bool result = false;
+
+ nsPIDOMWindowOuter* topOuter = GetInProcessScriptableTop();
+ NS_ENSURE_TRUE(topOuter, false);
+
+ nsGlobalWindowInner* topInner =
+ nsGlobalWindowInner::Cast(topOuter->GetCurrentInnerWindow());
+ NS_ENSURE_TRUE(topInner, false);
+
+ topInner->ShouldReportForServiceWorkerScopeInternal(
+ NS_ConvertUTF16toUTF8(aScope), &result);
+ return result;
+}
+
+InstallTriggerImpl* nsGlobalWindowInner::GetInstallTrigger() {
+ if (!mInstallTrigger &&
+ !StaticPrefs::extensions_InstallTriggerImpl_enabled()) {
+ // Return nullptr when InstallTriggerImpl is disabled by pref,
+ // which does not yet break the "typeof InstallTrigger !== 'undefined"
+ // "UA detection" use case, but prevents access to the InstallTriggerImpl
+ // methods and properties.
+ //
+ // NOTE: a separate pref ("extensions.InstallTrigger.enabled"), associated
+ // to this property using the [Pref] extended attribute in Window.webidl,
+ // does instead hide the entire InstallTrigger property.
+ //
+ // See Bug 1754441 for more details about this deprecation.
+ return nullptr;
+ }
+ if (!mInstallTrigger) {
+ ErrorResult rv;
+ mInstallTrigger = ConstructJSImplementation<InstallTriggerImpl>(
+ "@mozilla.org/addons/installtrigger;1", this, rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ return nullptr;
+ }
+ }
+
+ return mInstallTrigger;
+}
+
+nsIDOMWindowUtils* nsGlobalWindowInner::GetWindowUtils(ErrorResult& aRv) {
+ FORWARD_TO_OUTER_OR_THROW(WindowUtils, (), aRv, nullptr);
+}
+
+CallState nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal(
+ const nsACString& aScope, bool* aResultOut) {
+ MOZ_DIAGNOSTIC_ASSERT(aResultOut);
+
+ // First check to see if this window is controlled. If so, then we have
+ // found a match and are done.
+ const Maybe<ServiceWorkerDescriptor> swd = GetController();
+ if (swd.isSome() && swd.ref().Scope() == aScope) {
+ *aResultOut = true;
+ return CallState::Stop;
+ }
+
+ // Next, check to see if this window has called
+ // navigator.serviceWorker.register() for this scope. If so, then treat this
+ // as a match so console reports appear in the devtools console.
+ if (mClientSource &&
+ mClientSource->CalledRegisterForServiceWorkerScope(aScope)) {
+ *aResultOut = true;
+ return CallState::Stop;
+ }
+
+ // Finally check the current docshell nsILoadGroup to see if there are any
+ // outstanding navigation requests. If so, match the scope against the
+ // channel's URL. We want to show console reports during the FetchEvent
+ // intercepting the navigation itself.
+ nsCOMPtr<nsIDocumentLoader> loader(do_QueryInterface(GetDocShell()));
+ if (loader) {
+ nsCOMPtr<nsILoadGroup> loadgroup;
+ Unused << loader->GetLoadGroup(getter_AddRefs(loadgroup));
+ if (loadgroup) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ Unused << loadgroup->GetRequests(getter_AddRefs(iter));
+ if (iter) {
+ nsCOMPtr<nsISupports> tmp;
+ bool hasMore = true;
+ // Check each network request in the load group.
+ while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+ iter->GetNext(getter_AddRefs(tmp));
+ nsCOMPtr<nsIChannel> loadingChannel(do_QueryInterface(tmp));
+ // Ignore subresource requests. Logging for a subresource
+ // FetchEvent should be handled above since the client is
+ // already controlled.
+ if (!loadingChannel ||
+ !nsContentUtils::IsNonSubresourceRequest(loadingChannel)) {
+ continue;
+ }
+ nsCOMPtr<nsIURI> loadingURL;
+ Unused << loadingChannel->GetURI(getter_AddRefs(loadingURL));
+ if (!loadingURL) {
+ continue;
+ }
+ nsAutoCString loadingSpec;
+ Unused << loadingURL->GetSpec(loadingSpec);
+ // Perform a simple substring comparison to match the scope
+ // against the channel URL.
+ if (StringBeginsWith(loadingSpec, aScope)) {
+ *aResultOut = true;
+ return CallState::Stop;
+ }
+ }
+ }
+ }
+ }
+
+ // The current window doesn't care about this service worker, but maybe
+ // one of our child frames does.
+ return CallOnInProcessChildren(
+ &nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal, aScope,
+ aResultOut);
+}
+
+void nsGlobalWindowInner::NoteCalledRegisterForServiceWorkerScope(
+ const nsACString& aScope) {
+ if (!mClientSource) {
+ return;
+ }
+
+ mClientSource->NoteCalledRegisterForServiceWorkerScope(aScope);
+}
+
+void nsGlobalWindowInner::NoteDOMContentLoaded() {
+ if (!mClientSource) {
+ return;
+ }
+
+ mClientSource->NoteDOMContentLoaded();
+}
+
+void nsGlobalWindowInner::UpdateTopInnerWindow() {
+ if (IsTopInnerWindow() || !mTopInnerWindow) {
+ return;
+ }
+
+ mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+}
+
+bool nsGlobalWindowInner::IsInSyncOperation() {
+ return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
+}
+
+bool nsGlobalWindowInner::IsSharedMemoryAllowedInternal(
+ nsIPrincipal* aPrincipal) const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (StaticPrefs::
+ dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
+ return true;
+ }
+
+ if (ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
+ if (auto* basePrincipal = BasePrincipal::Cast(aPrincipal)) {
+ if (auto* policy = basePrincipal->AddonPolicy()) {
+ return policy->IsPrivileged();
+ }
+ }
+ }
+
+ return CrossOriginIsolated();
+}
+
+bool nsGlobalWindowInner::CrossOriginIsolated() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(bc);
+ return bc->CrossOriginIsolated();
+}
+
+WindowContext* TopWindowContext(nsPIDOMWindowInner& aWindow) {
+ WindowContext* wc = aWindow.GetWindowContext();
+ if (!wc) {
+ return nullptr;
+ }
+
+ return wc->TopWindowContext();
+}
+
+void nsPIDOMWindowInner::AddPeerConnection() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++mActivePeerConnections;
+ if (mActivePeerConnections == 1 && mWindowGlobalChild) {
+ mWindowGlobalChild->SendUpdateActivePeerConnectionStatus(
+ /*aIsAdded*/ true);
+
+ // We need to present having active peer connections immediately. If we need
+ // to wait for the parent process to come back with this information we
+ // might start throttling.
+ if (WindowContext* top = TopWindowContext(*this)) {
+ top->TransientSetHasActivePeerConnections();
+ }
+ }
+}
+
+void nsPIDOMWindowInner::RemovePeerConnection() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mActivePeerConnections > 0);
+ --mActivePeerConnections;
+ if (mActivePeerConnections == 0 && mWindowGlobalChild) {
+ mWindowGlobalChild->SendUpdateActivePeerConnectionStatus(
+ /*aIsAdded*/ false);
+ }
+}
+
+bool nsPIDOMWindowInner::HasActivePeerConnections() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ WindowContext* topWindowContext = TopWindowContext(*this);
+ return topWindowContext && topWindowContext->GetHasActivePeerConnections();
+}
+
+void nsPIDOMWindowInner::AddMediaKeysInstance(MediaKeys* aMediaKeys) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mMediaKeysInstances.AppendElement(aMediaKeys);
+ if (mWindowGlobalChild && mMediaKeysInstances.Length() == 1) {
+ mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::CONTAINS_EME_CONTENT);
+ }
+}
+
+void nsPIDOMWindowInner::RemoveMediaKeysInstance(MediaKeys* aMediaKeys) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mMediaKeysInstances.RemoveElement(aMediaKeys);
+ if (mWindowGlobalChild && mMediaKeysInstances.IsEmpty()) {
+ mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::CONTAINS_EME_CONTENT);
+ }
+}
+
+bool nsPIDOMWindowInner::HasActiveMediaKeysInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !mMediaKeysInstances.IsEmpty();
+}
+
+bool nsPIDOMWindowInner::IsPlayingAudio() {
+ for (uint32_t i = 0; i < mAudioContexts.Length(); i++) {
+ if (mAudioContexts[i]->IsRunning()) {
+ return true;
+ }
+ }
+ RefPtr<AudioChannelService> acs = AudioChannelService::Get();
+ if (!acs) {
+ return false;
+ }
+ auto outer = GetOuterWindow();
+ if (!outer) {
+ // We've been unlinked and are about to die. Not a good time to pretend to
+ // be playing audio.
+ return false;
+ }
+ return acs->IsWindowActive(outer);
+}
+
+bool nsPIDOMWindowInner::IsDocumentLoaded() const { return mIsDocumentLoaded; }
+
+mozilla::dom::TimeoutManager& nsPIDOMWindowInner::TimeoutManager() {
+ return *mTimeoutManager;
+}
+
+bool nsPIDOMWindowInner::IsRunningTimeout() {
+ return TimeoutManager().IsRunningTimeout();
+}
+
+void nsPIDOMWindowInner::TryToCacheTopInnerWindow() {
+ if (mHasTriedToCacheTopInnerWindow) {
+ return;
+ }
+
+ nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(this);
+
+ MOZ_ASSERT(!window->IsDying());
+
+ mHasTriedToCacheTopInnerWindow = true;
+
+ MOZ_ASSERT(window);
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> topOutter =
+ window->GetInProcessScriptableTop()) {
+ mTopInnerWindow = topOutter->GetCurrentInnerWindow();
+ }
+}
+
+void nsPIDOMWindowInner::UpdateActiveIndexedDBDatabaseCount(int32_t aDelta) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aDelta == 0) {
+ return;
+ }
+
+ // We count databases but not transactions because only active databases
+ // could block throttling.
+ uint32_t& counter = mTopInnerWindow
+ ? mTopInnerWindow->mNumOfIndexedDBDatabases
+ : mNumOfIndexedDBDatabases;
+
+ counter += aDelta;
+}
+
+bool nsPIDOMWindowInner::HasActiveIndexedDBDatabases() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mTopInnerWindow ? mTopInnerWindow->mNumOfIndexedDBDatabases > 0
+ : mNumOfIndexedDBDatabases > 0;
+}
+
+void nsPIDOMWindowInner::UpdateWebSocketCount(int32_t aDelta) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aDelta == 0) {
+ return;
+ }
+
+ if (mTopInnerWindow && !IsTopInnerWindow()) {
+ mTopInnerWindow->UpdateWebSocketCount(aDelta);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ aDelta > 0 || ((aDelta + mNumOfOpenWebSockets) < mNumOfOpenWebSockets));
+
+ mNumOfOpenWebSockets += aDelta;
+}
+
+bool nsPIDOMWindowInner::HasOpenWebSockets() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mNumOfOpenWebSockets ||
+ (mTopInnerWindow && mTopInnerWindow->mNumOfOpenWebSockets);
+}
+
+bool nsPIDOMWindowInner::IsCurrentInnerWindow() const {
+ if (mozilla::SessionHistoryInParent() && mBrowsingContext &&
+ mBrowsingContext->IsInBFCache()) {
+ return false;
+ }
+
+ if (!mBrowsingContext || mBrowsingContext->IsDiscarded()) {
+ // If our BrowsingContext has been discarded, we consider ourselves
+ // still-current if we were current at the time it was discarded.
+ return mOuterWindow && WasCurrentInnerWindow();
+ }
+
+ nsPIDOMWindowOuter* outer = mBrowsingContext->GetDOMWindow();
+ return outer && outer->GetCurrentInnerWindow() == this;
+}
+
+bool nsPIDOMWindowInner::IsFullyActive() const {
+ WindowContext* wc = GetWindowContext();
+ if (!wc || wc->IsDiscarded() || !wc->IsCurrent()) {
+ return false;
+ }
+ return GetBrowsingContext()->AncestorsAreCurrent();
+}
+
+void nsPIDOMWindowInner::SetAudioCapture(bool aCapture) {
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
+ }
+}
+
+void nsGlobalWindowInner::SetActiveLoadingState(bool aIsLoading) {
+ MOZ_LOG(
+ gTimeoutLog, mozilla::LogLevel::Debug,
+ ("SetActiveLoadingState innerwindow %p: %d", (void*)this, aIsLoading));
+ if (GetBrowsingContext()) {
+ // Setting loading on a discarded context has no effect.
+ Unused << GetBrowsingContext()->SetLoading(aIsLoading);
+ }
+
+ if (!nsGlobalWindowInner::Cast(this)->IsChromeWindow()) {
+ mTimeoutManager->SetLoading(aIsLoading);
+ }
+
+ HintIsLoading(aIsLoading);
+}
+
+void nsGlobalWindowInner::HintIsLoading(bool aIsLoading) {
+ // Hint to tell the JS GC to use modified triggers during pageload.
+ if (mHintedWasLoading != aIsLoading) {
+ using namespace js::gc;
+ SetPerformanceHint(danger::GetJSContext(), aIsLoading
+ ? PerformanceHint::InPageLoad
+ : PerformanceHint::Normal);
+ mHintedWasLoading = aIsLoading;
+ }
+}
+
+// nsISpeechSynthesisGetter
+
+#ifdef MOZ_WEBSPEECH
+SpeechSynthesis* nsGlobalWindowInner::GetSpeechSynthesis(ErrorResult& aError) {
+ if (!mSpeechSynthesis) {
+ mSpeechSynthesis = new SpeechSynthesis(this);
+ }
+
+ return mSpeechSynthesis;
+}
+
+bool nsGlobalWindowInner::HasActiveSpeechSynthesis() {
+ if (mSpeechSynthesis) {
+ return !mSpeechSynthesis->HasEmptyQueue();
+ }
+
+ return false;
+}
+
+#endif
+
+mozilla::glean::Glean* nsGlobalWindowInner::Glean() {
+ if (!mGlean) {
+ mGlean = new mozilla::glean::Glean();
+ }
+
+ return mGlean;
+}
+
+mozilla::glean::GleanPings* nsGlobalWindowInner::GleanPings() {
+ if (!mGleanPings) {
+ mGleanPings = new mozilla::glean::GleanPings();
+ }
+
+ return mGleanPings;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::GetParent(
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr);
+}
+
+/**
+ * GetInProcessScriptableParent used to be called when a script read
+ * window.parent. Under Fission, that is now handled by
+ * BrowsingContext::GetParent, and the result is a WindowProxyHolder rather than
+ * an actual global window. This method still exists for legacy callers which
+ * relied on the old logic, and require in-process windows. However, it only
+ * works correctly when no out-of-process frames exist between this window and
+ * the top-level window, so it should not be used in new code.
+ *
+ * In contrast to GetRealParent, GetInProcessScriptableParent respects <iframe
+ * mozbrowser> boundaries, so if |this| is contained by an <iframe
+ * mozbrowser>, we will return |this| as its own parent.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessScriptableParent() {
+ FORWARD_TO_OUTER(GetInProcessScriptableParent, (), nullptr);
+}
+
+/**
+ * GetInProcessScriptableTop used to be called when a script read window.top.
+ * Under Fission, that is now handled by BrowsingContext::Top, and the result is
+ * a WindowProxyHolder rather than an actual global window. This method still
+ * exists for legacy callers which relied on the old logic, and require
+ * in-process windows. However, it only works correctly when no out-of-process
+ * frames exist between this window and the top-level window, so it should not
+ * be used in new code.
+ *
+ * In contrast to GetRealTop, GetInProcessScriptableTop respects <iframe
+ * mozbrowser> boundaries. If we encounter a window owned by an <iframe
+ * mozbrowser> while walking up the window hierarchy, we'll stop and return that
+ * window.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessScriptableTop() {
+ FORWARD_TO_OUTER(GetInProcessScriptableTop, (), nullptr);
+}
+
+void nsGlobalWindowInner::GetContent(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetContentOuter,
+ (aCx, aRetval, aCallerType, aError), aError, );
+}
+
+BarProp* nsGlobalWindowInner::GetMenubar(ErrorResult& aError) {
+ if (!mMenubar) {
+ mMenubar = new MenubarProp(this);
+ }
+
+ return mMenubar;
+}
+
+BarProp* nsGlobalWindowInner::GetToolbar(ErrorResult& aError) {
+ if (!mToolbar) {
+ mToolbar = new ToolbarProp(this);
+ }
+
+ return mToolbar;
+}
+
+BarProp* nsGlobalWindowInner::GetLocationbar(ErrorResult& aError) {
+ if (!mLocationbar) {
+ mLocationbar = new LocationbarProp(this);
+ }
+ return mLocationbar;
+}
+
+BarProp* nsGlobalWindowInner::GetPersonalbar(ErrorResult& aError) {
+ if (!mPersonalbar) {
+ mPersonalbar = new PersonalbarProp(this);
+ }
+ return mPersonalbar;
+}
+
+BarProp* nsGlobalWindowInner::GetStatusbar(ErrorResult& aError) {
+ if (!mStatusbar) {
+ mStatusbar = new StatusbarProp(this);
+ }
+ return mStatusbar;
+}
+
+BarProp* nsGlobalWindowInner::GetScrollbars(ErrorResult& aError) {
+ if (!mScrollbars) {
+ mScrollbars = new ScrollbarsProp(this);
+ }
+
+ return mScrollbars;
+}
+
+bool nsGlobalWindowInner::GetClosed(ErrorResult& aError) {
+ // If we're called from JS (which is the only way we should be getting called
+ // here) and we reach this point, that means our JS global is the current
+ // target of the WindowProxy, which means that we are the "current inner"
+ // of our outer. So if FORWARD_TO_OUTER fails to forward, that means the
+ // outer is already torn down, which corresponds to the closed state.
+ FORWARD_TO_OUTER(GetClosedOuter, (), true);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::IndexedGetter(
+ uint32_t aIndex) {
+ FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
+}
+
+namespace {
+
+struct InterfaceShimEntry {
+ const char* geckoName;
+ const char* domName;
+};
+
+} // anonymous namespace
+
+// We add shims from Components.interfaces.nsIDOMFoo to window.Foo for each
+// interface that has interface constants that sites might be getting off
+// of Ci.
+const InterfaceShimEntry kInterfaceShimMap[] = {
+ {"nsIXMLHttpRequest", "XMLHttpRequest"},
+ {"nsIDOMDOMException", "DOMException"},
+ {"nsIDOMNode", "Node"},
+ {"nsIDOMCSSRule", "CSSRule"},
+ {"nsIDOMEvent", "Event"},
+ {"nsIDOMNSEvent", "Event"},
+ {"nsIDOMKeyEvent", "KeyEvent"},
+ {"nsIDOMMouseEvent", "MouseEvent"},
+ {"nsIDOMMouseScrollEvent", "MouseScrollEvent"},
+ {"nsIDOMMutationEvent", "MutationEvent"},
+ {"nsIDOMUIEvent", "UIEvent"},
+ {"nsIDOMHTMLMediaElement", "HTMLMediaElement"},
+ {"nsIDOMRange", "Range"},
+ // Think about whether Ci.nsINodeFilter can just go away for websites!
+ {"nsIDOMNodeFilter", "NodeFilter"},
+ {"nsIDOMXPathResult", "XPathResult"}};
+
+bool nsGlobalWindowInner::ResolveComponentsShim(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) {
+ // Keep track of how often this happens.
+ Telemetry::Accumulate(Telemetry::COMPONENTS_SHIM_ACCESSED_BY_CONTENT, true);
+
+ // Warn once.
+ nsCOMPtr<Document> doc = GetExtantDoc();
+ if (doc) {
+ doc->WarnOnceAbout(DeprecatedOperations::eComponents, /* asError = */ true);
+ }
+
+ // Create a fake Components object.
+ AssertSameCompartment(aCx, aGlobal);
+ JS::Rooted<JSObject*> components(aCx, JS_NewPlainObject(aCx));
+ if (NS_WARN_IF(!components)) {
+ return false;
+ }
+
+ // Create a fake interfaces object.
+ JS::Rooted<JSObject*> interfaces(aCx, JS_NewPlainObject(aCx));
+ if (NS_WARN_IF(!interfaces)) {
+ return false;
+ }
+ bool ok =
+ JS_DefineProperty(aCx, components, "interfaces", interfaces,
+ JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
+ if (NS_WARN_IF(!ok)) {
+ return false;
+ }
+
+ // Define a bunch of shims from the Ci.nsIDOMFoo to window.Foo for DOM
+ // interfaces with constants.
+ for (uint32_t i = 0; i < ArrayLength(kInterfaceShimMap); ++i) {
+ // Grab the names from the table.
+ const char* geckoName = kInterfaceShimMap[i].geckoName;
+ const char* domName = kInterfaceShimMap[i].domName;
+
+ // Look up the appopriate interface object on the global.
+ JS::Rooted<JS::Value> v(aCx, JS::UndefinedValue());
+ ok = JS_GetProperty(aCx, aGlobal, domName, &v);
+ if (NS_WARN_IF(!ok)) {
+ return false;
+ }
+ if (!v.isObject()) {
+ NS_WARNING("Unable to find interface object on global");
+ continue;
+ }
+
+ // Define the shim on the interfaces object.
+ ok = JS_DefineProperty(
+ aCx, interfaces, geckoName, v,
+ JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
+ if (NS_WARN_IF(!ok)) {
+ return false;
+ }
+ }
+
+ aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*components),
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+}
+
+#ifdef RELEASE_OR_BETA
+# define USE_CONTROLLERS_SHIM
+#endif
+
+#ifdef USE_CONTROLLERS_SHIM
+static const JSClass ControllersShimClass = {"Controllers", 0};
+static const JSClass XULControllersShimClass = {"XULControllers", 0};
+#endif
+
+bool nsGlobalWindowInner::DoResolve(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) {
+ // Note: Keep this in sync with MayResolve.
+
+ // Note: The infallibleInit call in GlobalResolve depends on this check.
+ if (!aId.isString()) {
+ return true;
+ }
+
+ bool found;
+ if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) {
+ return false;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ // We support a cut-down Components.interfaces in case websites are
+ // using Components.interfaces.nsIFoo.CONSTANT_NAME for the ones
+ // that have constants.
+ if (StaticPrefs::dom_use_components_shim() &&
+ aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
+ return ResolveComponentsShim(aCx, aObj, aDesc);
+ }
+
+ // We also support a "window.controllers" thing; apparently some
+ // sites use it for browser-sniffing. See bug 1010577.
+#ifdef USE_CONTROLLERS_SHIM
+ // Note: We use |aObj| rather than |this| to get the principal here, because
+ // this is called during Window setup when the Document isn't necessarily
+ // hooked up yet.
+ if ((aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) ||
+ aId == XPCJSRuntime::Get()->GetStringID(
+ XPCJSContext::IDX_CONTROLLERS_CLASS)) &&
+ !xpc::IsXrayWrapper(aObj) &&
+ !nsContentUtils::ObjectPrincipal(aObj)->IsSystemPrincipal()) {
+ if (GetExtantDoc()) {
+ GetExtantDoc()->WarnOnceAbout(
+ DeprecatedOperations::eWindow_Cc_ontrollers);
+ }
+ const JSClass* clazz;
+ if (aId ==
+ XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) {
+ clazz = &XULControllersShimClass;
+ } else {
+ clazz = &ControllersShimClass;
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(aObj));
+ JS::Rooted<JSObject*> shim(aCx, JS_NewObject(aCx, clazz));
+ if (NS_WARN_IF(!shim)) {
+ return false;
+ }
+
+ aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*shim),
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+#endif
+
+ return true;
+}
+
+/* static */
+bool nsGlobalWindowInner::MayResolve(jsid aId) {
+ // Note: This function does not fail and may not have any side-effects.
+ // Note: Keep this in sync with DoResolve.
+ if (!aId.isString()) {
+ return false;
+ }
+
+ if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
+ return true;
+ }
+
+ if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) ||
+ aId == XPCJSRuntime::Get()->GetStringID(
+ XPCJSContext::IDX_CONTROLLERS_CLASS)) {
+ // We only resolve .controllers/.Controllers in release builds and on
+ // non-chrome windows, but let's not worry about any of that stuff.
+ return true;
+ }
+
+ return WebIDLGlobalNameHash::MayResolve(aId);
+}
+
+void nsGlobalWindowInner::GetOwnPropertyNames(
+ JSContext* aCx, JS::MutableHandleVector<jsid> aNames, bool aEnumerableOnly,
+ ErrorResult& aRv) {
+ if (aEnumerableOnly) {
+ // The names we would return from here get defined on the window via one of
+ // two codepaths. The ones coming from the WebIDLGlobalNameHash will end up
+ // in the DefineConstructor function in BindingUtils, which always defines
+ // things as non-enumerable. The ones coming from the script namespace
+ // manager get defined by our resolve hook using FillPropertyDescriptor with
+ // 0 for the property attributes, so non-enumerable as well.
+ //
+ // So in the aEnumerableOnly case we have nothing to do.
+ return;
+ }
+
+ // "Components" is marked as enumerable but only resolved on demand :-/.
+ // aNames.AppendElement(u"Components"_ns);
+
+ JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
+
+ // There are actually two ways we can get called here: For normal
+ // enumeration or for Xray enumeration. In the latter case, we want to
+ // return all possible WebIDL names, because we don't really support
+ // deleting these names off our Xray; trying to resolve them will just make
+ // them come back. In the former case, we want to avoid returning deleted
+ // names. But the JS engine already knows about the non-deleted
+ // already-resolved names, so we can just return the so-far-unresolved ones.
+ //
+ // We can tell which case we're in by whether aCx is in our wrapper's
+ // compartment. If not, we're in the Xray case.
+ WebIDLGlobalNameHash::NameType nameType =
+ js::IsObjectInContextCompartment(wrapper, aCx)
+ ? WebIDLGlobalNameHash::UnresolvedNamesOnly
+ : WebIDLGlobalNameHash::AllNames;
+ if (!WebIDLGlobalNameHash::GetNames(aCx, wrapper, nameType, aNames)) {
+ aRv.NoteJSContextException(aCx);
+ }
+}
+
+/* static */
+bool nsGlobalWindowInner::IsPrivilegedChromeWindow(JSContext*, JSObject* aObj) {
+ // For now, have to deal with XPConnect objects here.
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aObj);
+ return win && win->IsChromeWindow() &&
+ nsContentUtils::ObjectPrincipal(aObj) ==
+ nsContentUtils::GetSystemPrincipal();
+}
+
+/* static */
+bool nsGlobalWindowInner::IsRequestIdleCallbackEnabled(JSContext* aCx,
+ JSObject*) {
+ // The requestIdleCallback should always be enabled for system code.
+ return StaticPrefs::dom_requestIdleCallback_enabled() ||
+ nsContentUtils::IsSystemCaller(aCx);
+}
+
+/* static */
+bool nsGlobalWindowInner::DeviceSensorsEnabled(JSContext*, JSObject*) {
+ return Preferences::GetBool("device.sensors.enabled");
+}
+
+/* static */
+bool nsGlobalWindowInner::ContentPropertyEnabled(JSContext* aCx, JSObject*) {
+ return StaticPrefs::dom_window_content_untrusted_enabled() ||
+ nsContentUtils::IsSystemCaller(aCx);
+}
+
+/* static */
+bool nsGlobalWindowInner::CachesEnabled(JSContext* aCx, JSObject*) {
+ if (!StaticPrefs::dom_caches_enabled()) {
+ return false;
+ }
+ if (!JS::GetIsSecureContext(js::GetContextRealm(aCx))) {
+ return StaticPrefs::dom_caches_testing_enabled() ||
+ StaticPrefs::dom_serviceWorkers_testing_enabled();
+ }
+ return true;
+}
+
+Crypto* nsGlobalWindowInner::GetCrypto(ErrorResult& aError) {
+ if (!mCrypto) {
+ mCrypto = new Crypto(this);
+ }
+ return mCrypto;
+}
+
+nsIControllers* nsGlobalWindowInner::GetControllers(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetControllersOuter, (aError), aError, nullptr);
+}
+
+nsresult nsGlobalWindowInner::GetControllers(nsIControllers** aResult) {
+ ErrorResult rv;
+ nsCOMPtr<nsIControllers> controllers = GetControllers(rv);
+ controllers.forget(aResult);
+
+ return rv.StealNSResult();
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::GetOpenerWindow(
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetOpenerWindowOuter, (), aError, nullptr);
+}
+
+void nsGlobalWindowInner::GetOpener(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError) {
+ Nullable<WindowProxyHolder> opener = GetOpenerWindow(aError);
+ if (aError.Failed() || opener.IsNull()) {
+ aRetval.setNull();
+ return;
+ }
+
+ if (!ToJSValue(aCx, opener.Value(), aRetval)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+void nsGlobalWindowInner::SetOpener(JSContext* aCx,
+ JS::Handle<JS::Value> aOpener,
+ ErrorResult& aError) {
+ if (aOpener.isNull()) {
+ RefPtr<BrowsingContext> bc(GetBrowsingContext());
+ if (!bc->IsDiscarded()) {
+ bc->SetOpener(nullptr);
+ }
+ return;
+ }
+
+ // If something other than null is passed, just define aOpener on our inner
+ // window's JS object, wrapped into the current compartment so that for Xrays
+ // we define on the Xray expando object, but don't set it on the outer window,
+ // so that it'll get reset on navigation. This is just like replaceable
+ // properties, but we're not quite readonly.
+ RedefineProperty(aCx, "opener", aOpener, aError);
+}
+
+void nsGlobalWindowInner::GetEvent(OwningEventOrUndefined& aRetval) {
+ if (mEvent) {
+ aRetval.SetAsEvent() = mEvent;
+ } else {
+ aRetval.SetUndefined();
+ }
+}
+
+void nsGlobalWindowInner::GetStatus(nsAString& aStatus, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetStatusOuter, (aStatus), aError, );
+}
+
+void nsGlobalWindowInner::SetStatus(const nsAString& aStatus,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetStatusOuter, (aStatus), aError, );
+}
+
+void nsGlobalWindowInner::GetName(nsAString& aName, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetNameOuter, (aName), aError, );
+}
+
+void nsGlobalWindowInner::SetName(const nsAString& aName,
+ mozilla::ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, );
+}
+
+double nsGlobalWindowInner::GetInnerWidth(CallerType aCallerType,
+ ErrorResult& aError) {
+ // We ignore aCallerType; we only have that argument because some other things
+ // called by GetReplaceableWindowCoord need it. If this ever changes, fix
+ // nsresult nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth)
+ // to actually take a useful CallerType and pass it in here.
+ FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
+}
+
+void nsGlobalWindowInner::GetInnerWidth(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetInnerWidth, aValue,
+ aCallerType, aError);
+}
+
+nsresult nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth) {
+ ErrorResult rv;
+ // Callee doesn't care about the caller type, but play it safe.
+ *aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv);
+
+ return rv.StealNSResult();
+}
+
+void nsGlobalWindowInner::SetInnerWidth(double aInnerWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter,
+ (aInnerWidth, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::SetInnerWidth(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetInnerWidth, aValue,
+ "innerWidth", aCallerType, aError);
+}
+
+double nsGlobalWindowInner::GetInnerHeight(CallerType aCallerType,
+ ErrorResult& aError) {
+ // We ignore aCallerType; we only have that argument because some other things
+ // called by GetReplaceableWindowCoord need it. If this ever changes, fix
+ // nsresult nsGlobalWindowInner::GetInnerHeight(double* aInnerWidth)
+ // to actually take a useful CallerType and pass it in here.
+ FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
+}
+
+void nsGlobalWindowInner::GetInnerHeight(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetInnerHeight, aValue,
+ aCallerType, aError);
+}
+
+nsresult nsGlobalWindowInner::GetInnerHeight(double* aInnerHeight) {
+ ErrorResult rv;
+ // Callee doesn't care about the caller type, but play it safe.
+ *aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv);
+
+ return rv.StealNSResult();
+}
+
+void nsGlobalWindowInner::SetInnerHeight(double aInnerHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter,
+ (aInnerHeight, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::SetInnerHeight(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetInnerHeight, aValue,
+ "innerHeight", aCallerType, aError);
+}
+
+int32_t nsGlobalWindowInner::GetOuterWidth(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aCallerType, aError), aError,
+ 0);
+}
+
+void nsGlobalWindowInner::GetOuterWidth(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetOuterWidth, aValue,
+ aCallerType, aError);
+}
+
+int32_t nsGlobalWindowInner::GetOuterHeight(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aCallerType, aError), aError,
+ 0);
+}
+
+void nsGlobalWindowInner::GetOuterHeight(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetOuterHeight, aValue,
+ aCallerType, aError);
+}
+
+void nsGlobalWindowInner::SetOuterWidth(int32_t aOuterWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter,
+ (aOuterWidth, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::SetOuterWidth(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetOuterWidth, aValue,
+ "outerWidth", aCallerType, aError);
+}
+
+void nsGlobalWindowInner::SetOuterHeight(int32_t aOuterHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter,
+ (aOuterHeight, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::SetOuterHeight(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetOuterHeight, aValue,
+ "outerHeight", aCallerType, aError);
+}
+
+double nsGlobalWindowInner::ScreenEdgeSlopX() const {
+ FORWARD_TO_OUTER(ScreenEdgeSlopX, (), 0);
+}
+
+double nsGlobalWindowInner::ScreenEdgeSlopY() const {
+ FORWARD_TO_OUTER(ScreenEdgeSlopY, (), 0);
+}
+
+int32_t nsGlobalWindowInner::GetScreenX(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aCallerType, aError), aError, 0);
+}
+
+void nsGlobalWindowInner::GetScreenX(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetScreenX, aValue,
+ aCallerType, aError);
+}
+
+float nsGlobalWindowInner::GetMozInnerScreenX(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (aCallerType), aError, 0);
+}
+
+float nsGlobalWindowInner::GetMozInnerScreenY(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0);
+}
+
+static nsPresContext* GetPresContextForRatio(Document* aDoc) {
+ if (nsPresContext* presContext = aDoc->GetPresContext()) {
+ return presContext;
+ }
+ // We're in an undisplayed subdocument... There's not really an awesome way
+ // to tell what the right DPI is from here, so we try to walk up our parent
+ // document chain to the extent that the docs can observe each other.
+ Document* doc = aDoc;
+ while (doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+ doc = doc->GetInProcessParentDocument();
+ if (nsPresContext* presContext = doc->GetPresContext()) {
+ return presContext;
+ }
+ }
+ return nullptr;
+}
+
+double nsGlobalWindowInner::GetDevicePixelRatio(CallerType aCallerType,
+ ErrorResult& aError) {
+ ENSURE_ACTIVE_DOCUMENT(aError, 0.0);
+
+ RefPtr<nsPresContext> presContext = GetPresContextForRatio(mDoc);
+ if (NS_WARN_IF(!presContext)) {
+ // Still nothing, oh well.
+ return 1.0;
+ }
+
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ // Spoofing the DevicePixelRatio causes blurriness in some situations
+ // on HiDPI displays. pdf.js is a non-system caller; but it can't
+ // expose the fingerprintable information, so we can safely disable
+ // spoofing in this situation. It doesn't address the issue for
+ // web-rendered content (including pdf.js instances on the web.)
+ // In the future we hope to have a better solution to fix all HiDPI
+ // blurriness...
+ nsAutoCString origin;
+ nsresult rv = this->GetPrincipal()->GetOrigin(origin);
+ if (NS_FAILED(rv) || origin != "resource://pdf.js"_ns) {
+ return 1.0;
+ }
+ }
+
+ if (aCallerType == CallerType::NonSystem) {
+ float overrideDPPX = presContext->GetOverrideDPPX();
+ if (overrideDPPX > 0.0f) {
+ return overrideDPPX;
+ }
+ }
+
+ return double(AppUnitsPerCSSPixel()) /
+ double(presContext->AppUnitsPerDevPixel());
+}
+
+double nsGlobalWindowInner::GetDesktopToDeviceScale(ErrorResult& aError) {
+ ENSURE_ACTIVE_DOCUMENT(aError, 0.0);
+ nsPresContext* presContext = GetPresContextForRatio(mDoc);
+ if (!presContext) {
+ return 1.0;
+ }
+ return presContext->DeviceContext()->GetDesktopToDeviceScale().scale;
+}
+
+int32_t nsGlobalWindowInner::RequestAnimationFrame(
+ FrameRequestCallback& aCallback, ErrorResult& aError) {
+ if (!mDoc) {
+ return 0;
+ }
+
+ if (GetWrapperPreserveColor()) {
+ js::NotifyAnimationActivity(GetWrapperPreserveColor());
+ }
+
+ DebuggerNotificationDispatch(this,
+ DebuggerNotificationType::RequestAnimationFrame);
+
+ int32_t handle;
+ aError = mDoc->ScheduleFrameRequestCallback(aCallback, &handle);
+ return handle;
+}
+
+void nsGlobalWindowInner::CancelAnimationFrame(int32_t aHandle,
+ ErrorResult& aError) {
+ if (!mDoc) {
+ return;
+ }
+
+ DebuggerNotificationDispatch(this,
+ DebuggerNotificationType::CancelAnimationFrame);
+
+ mDoc->CancelFrameRequestCallback(aHandle);
+}
+
+already_AddRefed<MediaQueryList> nsGlobalWindowInner::MatchMedia(
+ const nsACString& aMediaQueryList, CallerType aCallerType,
+ ErrorResult& aError) {
+ ENSURE_ACTIVE_DOCUMENT(aError, nullptr);
+ return mDoc->MatchMedia(aMediaQueryList, aCallerType);
+}
+
+void nsGlobalWindowInner::SetScreenX(int32_t aScreenX, CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aCallerType, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::SetScreenX(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetScreenX, aValue,
+ "screenX", aCallerType, aError);
+}
+
+int32_t nsGlobalWindowInner::GetScreenY(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aCallerType, aError), aError, 0);
+}
+
+void nsGlobalWindowInner::GetScreenY(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetScreenY, aValue,
+ aCallerType, aError);
+}
+
+void nsGlobalWindowInner::SetScreenY(int32_t aScreenY, CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aCallerType, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::SetScreenY(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetScreenY, aValue,
+ "screenY", aCallerType, aError);
+}
+
+int32_t nsGlobalWindowInner::GetScrollMinX(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideLeft), aError, 0);
+}
+
+int32_t nsGlobalWindowInner::GetScrollMinY(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideTop), aError, 0);
+}
+
+int32_t nsGlobalWindowInner::GetScrollMaxX(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideRight), aError, 0);
+}
+
+int32_t nsGlobalWindowInner::GetScrollMaxY(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideBottom), aError, 0);
+}
+
+double nsGlobalWindowInner::GetScrollX(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0);
+}
+
+double nsGlobalWindowInner::GetScrollY(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
+}
+
+uint32_t nsGlobalWindowInner::Length() { FORWARD_TO_OUTER(Length, (), 0); }
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::GetTop(
+ mozilla::ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowInner::GetChildWindow(
+ const nsAString& aName) {
+ if (GetOuterWindowInternal()) {
+ return GetOuterWindowInternal()->GetChildWindow(aName);
+ }
+ return nullptr;
+}
+
+void nsGlobalWindowInner::RefreshRealmPrincipal() {
+ JS::SetRealmPrincipals(js::GetNonCCWObjectRealm(GetWrapperPreserveColor()),
+ nsJSPrincipals::get(mDoc->NodePrincipal()));
+}
+
+already_AddRefed<nsIWidget> nsGlobalWindowInner::GetMainWidget() {
+ FORWARD_TO_OUTER(GetMainWidget, (), nullptr);
+}
+
+nsIWidget* nsGlobalWindowInner::GetNearestWidget() const {
+ if (GetOuterWindowInternal()) {
+ return GetOuterWindowInternal()->GetNearestWidget();
+ }
+ return nullptr;
+}
+
+void nsGlobalWindowInner::SetFullScreen(bool aFullscreen,
+ mozilla::ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetFullscreenOuter, (aFullscreen, aError), aError,
+ /* void */);
+}
+
+bool nsGlobalWindowInner::GetFullScreen(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetFullscreenOuter, (), aError, false);
+}
+
+bool nsGlobalWindowInner::GetFullScreen() {
+ ErrorResult dummy;
+ bool fullscreen = GetFullScreen(dummy);
+ dummy.SuppressException();
+ return fullscreen;
+}
+
+void nsGlobalWindowInner::Dump(const nsAString& aStr) {
+ if (!nsJSUtils::DumpEnabled()) {
+ return;
+ }
+
+ char* cstr = ToNewUTF8String(aStr);
+
+#if defined(XP_MACOSX)
+ // have to convert \r to \n so that printing to the console works
+ char *c = cstr, *cEnd = cstr + strlen(cstr);
+ while (c < cEnd) {
+ if (*c == '\r') *c = '\n';
+ c++;
+ }
+#endif
+
+ if (cstr) {
+ MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug,
+ ("[Window.Dump] %s", cstr));
+#ifdef XP_WIN
+ PrintToDebugger(cstr);
+#endif
+#ifdef ANDROID
+ __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
+#endif
+ FILE* fp = gDumpFile ? gDumpFile : stdout;
+ fputs(cstr, fp);
+ fflush(fp);
+ free(cstr);
+ }
+}
+
+void nsGlobalWindowInner::Alert(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ Alert(u""_ns, aSubjectPrincipal, aError);
+}
+
+void nsGlobalWindowInner::Alert(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError),
+ aError, );
+}
+
+bool nsGlobalWindowInner::Confirm(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
+ aError, false);
+}
+
+already_AddRefed<Promise> nsGlobalWindowInner::Fetch(
+ const RequestOrUSVString& aInput, const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv) {
+ return FetchRequest(this, aInput, aInit, aCallerType, aRv);
+}
+
+void nsGlobalWindowInner::Prompt(const nsAString& aMessage,
+ const nsAString& aInitial, nsAString& aReturn,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ PromptOuter, (aMessage, aInitial, aReturn, aSubjectPrincipal, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::Focus(CallerType aCallerType, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(FocusOuter,
+ (aCallerType, /* aFromOtherProcess */ false,
+ nsFocusManager::GenerateFocusActionId()),
+ aError, );
+}
+
+nsresult nsGlobalWindowInner::Focus(CallerType aCallerType) {
+ ErrorResult rv;
+ Focus(aCallerType, rv);
+
+ return rv.StealNSResult();
+}
+
+void nsGlobalWindowInner::Blur(CallerType aCallerType, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(BlurOuter, (aCallerType), aError, );
+}
+
+void nsGlobalWindowInner::Stop(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(StopOuter, (aError), aError, );
+}
+
+void nsGlobalWindowInner::Print(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(PrintOuter, (aError), aError, );
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::PrintPreview(
+ nsIPrintSettings* aSettings, nsIWebProgressListener* aListener,
+ nsIDocShell* aDocShellToCloneInto, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ Print,
+ (aSettings,
+ /* aRemotePrintJob = */ nullptr, aListener, aDocShellToCloneInto,
+ nsGlobalWindowOuter::IsPreview::Yes,
+ nsGlobalWindowOuter::IsForWindowDotPrint::No,
+ /* aPrintPreviewCallback = */ nullptr, aError),
+ aError, nullptr);
+}
+
+void nsGlobalWindowInner::MoveTo(int32_t aXPos, int32_t aYPos,
+ CallerType aCallerType, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(MoveToOuter, (aXPos, aYPos, aCallerType, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::MoveBy(int32_t aXDif, int32_t aYDif,
+ CallerType aCallerType, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(MoveByOuter, (aXDif, aYDif, aCallerType, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::ResizeTo(int32_t aWidth, int32_t aHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(ResizeToOuter,
+ (aWidth, aHeight, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ ResizeByOuter, (aWidthDif, aHeightDif, aCallerType, aError), aError, );
+}
+
+void nsGlobalWindowInner::SizeToContent(CallerType aCallerType,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SizeToContentOuter, (aCallerType, {}, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::SizeToContentConstrained(
+ const SizeToContentConstraints& aConstraints, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ SizeToContentOuter, (CallerType::System, aConstraints, aError), aError, );
+}
+
+already_AddRefed<nsPIWindowRoot> nsGlobalWindowInner::GetTopWindowRoot() {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (!outer) {
+ return nullptr;
+ }
+ return outer->GetTopWindowRoot();
+}
+
+void nsGlobalWindowInner::Scroll(double aXScroll, double aYScroll) {
+ // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
+ auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
+ mozilla::ToZeroIfNonfinite(aYScroll));
+ ScrollTo(scrollPos, ScrollOptions());
+}
+
+void nsGlobalWindowInner::ScrollTo(double aXScroll, double aYScroll) {
+ // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
+ auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
+ mozilla::ToZeroIfNonfinite(aYScroll));
+ ScrollTo(scrollPos, ScrollOptions());
+}
+
+void nsGlobalWindowInner::ScrollTo(const ScrollToOptions& aOptions) {
+ // When scrolling to a non-zero offset, we need to determine whether that
+ // position is within our scrollable range, so we need updated layout
+ // information which requires a layout flush, otherwise all we need is to
+ // flush frames to be able to access our scrollable frame here.
+ FlushType flushType =
+ ((aOptions.mLeft.WasPassed() && aOptions.mLeft.Value() > 0) ||
+ (aOptions.mTop.WasPassed() && aOptions.mTop.Value() > 0))
+ ? FlushType::Layout
+ : FlushType::Frames;
+ FlushPendingNotifications(flushType);
+ nsIScrollableFrame* sf = GetScrollFrame();
+
+ if (sf) {
+ CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
+ if (aOptions.mLeft.WasPassed()) {
+ scrollPos.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+ }
+ if (aOptions.mTop.WasPassed()) {
+ scrollPos.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+ }
+
+ ScrollTo(scrollPos, aOptions);
+ }
+}
+
+void nsGlobalWindowInner::Scroll(const ScrollToOptions& aOptions) {
+ ScrollTo(aOptions);
+}
+
+void nsGlobalWindowInner::ScrollTo(const CSSIntPoint& aScroll,
+ const ScrollOptions& aOptions) {
+ // When scrolling to a non-zero offset, we need to determine whether that
+ // position is within our scrollable range, so we need updated layout
+ // information which requires a layout flush, otherwise all we need is to
+ // flush frames to be able to access our scrollable frame here.
+ FlushType flushType =
+ (aScroll.x || aScroll.y) ? FlushType::Layout : FlushType::Frames;
+ FlushPendingNotifications(flushType);
+ nsIScrollableFrame* sf = GetScrollFrame();
+
+ if (sf) {
+ // Here we calculate what the max pixel value is that we can
+ // scroll to, we do this by dividing maxint with the pixel to
+ // twips conversion factor, and subtracting 4, the 4 comes from
+ // experimenting with this value, anything less makes the view
+ // code not scroll correctly, I have no idea why. -- jst
+ const int32_t maxpx = nsPresContext::AppUnitsToIntCSSPixels(0x7fffffff) - 4;
+
+ CSSIntPoint scroll(aScroll);
+ if (scroll.x > maxpx) {
+ scroll.x = maxpx;
+ }
+
+ if (scroll.y > maxpx) {
+ scroll.y = maxpx;
+ }
+
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollToCSSPixels(scroll, scrollMode);
+ }
+}
+
+void nsGlobalWindowInner::ScrollBy(double aXScrollDif, double aYScrollDif) {
+ FlushPendingNotifications(FlushType::Layout);
+ nsIScrollableFrame* sf = GetScrollFrame();
+
+ if (sf) {
+ // It seems like it would make more sense for ScrollBy to use
+ // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
+ // Perhaps Web content does too.
+ ScrollToOptions options;
+ options.mLeft.Construct(aXScrollDif);
+ options.mTop.Construct(aYScrollDif);
+ ScrollBy(options);
+ }
+}
+
+void nsGlobalWindowInner::ScrollBy(const ScrollToOptions& aOptions) {
+ FlushPendingNotifications(FlushType::Layout);
+ nsIScrollableFrame* sf = GetScrollFrame();
+
+ if (sf) {
+ CSSIntPoint scrollDelta;
+ if (aOptions.mLeft.WasPassed()) {
+ scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+ }
+ if (aOptions.mTop.WasPassed()) {
+ scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+ }
+
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollByCSSPixels(scrollDelta, scrollMode);
+ }
+}
+
+void nsGlobalWindowInner::ScrollByLines(int32_t numLines,
+ const ScrollOptions& aOptions) {
+ FlushPendingNotifications(FlushType::Layout);
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ // It seems like it would make more sense for ScrollByLines to use
+ // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
+ // Perhaps Web content does too.
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollBy(nsIntPoint(0, numLines), ScrollUnit::LINES, scrollMode);
+ }
+}
+
+void nsGlobalWindowInner::ScrollByPages(int32_t numPages,
+ const ScrollOptions& aOptions) {
+ FlushPendingNotifications(FlushType::Layout);
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ // It seems like it would make more sense for ScrollByPages to use
+ // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
+ // Perhaps Web content does too.
+ ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
+ ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant;
+
+ sf->ScrollBy(nsIntPoint(0, numPages), ScrollUnit::PAGES, scrollMode);
+ }
+}
+
+void nsGlobalWindowInner::MozScrollSnap() {
+ FlushPendingNotifications(FlushType::Layout);
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
+void nsGlobalWindowInner::ClearTimeout(int32_t aHandle) {
+ DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearTimeout);
+
+ if (aHandle > 0) {
+ mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
+ }
+}
+
+void nsGlobalWindowInner::ClearInterval(int32_t aHandle) {
+ DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearInterval);
+
+ if (aHandle > 0) {
+ mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
+ }
+}
+
+void nsGlobalWindowInner::SetResizable(bool aResizable) const {
+ // nop
+}
+
+void nsGlobalWindowInner::CaptureEvents() {
+ if (mDoc) {
+ mDoc->WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
+ }
+}
+
+void nsGlobalWindowInner::ReleaseEvents() {
+ if (mDoc) {
+ mDoc->WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
+ }
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::Open(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError,
+ nullptr);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowInner::OpenDialog(
+ JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ OpenDialogOuter, (aCx, aUrl, aName, aOptions, aExtraArgument, aError),
+ aError, nullptr);
+}
+
+WindowProxyHolder nsGlobalWindowInner::GetFrames(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, Window());
+}
+
+void nsGlobalWindowInner::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(
+ PostMessageMozOuter,
+ (aCx, aMessage, aTargetOrigin, aTransfer, aSubjectPrincipal, aError),
+ aError, );
+}
+
+void nsGlobalWindowInner::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
+
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer,
+ &transferArray);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray, aSubjectPrincipal,
+ aRv);
+}
+
+void nsGlobalWindowInner::PostMessageMoz(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const WindowPostMessageOptions& aOptions, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
+
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(
+ aCx, aOptions.mTransfer, &transferArray);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, transferArray,
+ aSubjectPrincipal, aRv);
+}
+
+void nsGlobalWindowInner::Close(CallerType aCallerType, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(CloseOuter, (aCallerType == CallerType::System),
+ aError, );
+}
+
+nsresult nsGlobalWindowInner::Close() {
+ FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED);
+}
+
+bool nsGlobalWindowInner::IsInModalState() {
+ FORWARD_TO_OUTER(IsInModalState, (), false);
+}
+
+// static
+void nsGlobalWindowInner::NotifyDOMWindowDestroyed(
+ nsGlobalWindowInner* aWindow) {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_DESTROYED_TOPIC, nullptr);
+ }
+}
+
+void nsGlobalWindowInner::NotifyWindowIDDestroyed(const char* aTopic) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new WindowDestroyedEvent(this, mWindowID, aTopic);
+ Dispatch(TaskCategory::Other, runnable.forget());
+}
+
+// static
+void nsGlobalWindowInner::NotifyDOMWindowFrozen(nsGlobalWindowInner* aWindow) {
+ if (aWindow) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_FROZEN_TOPIC, nullptr);
+ }
+ }
+}
+
+// static
+void nsGlobalWindowInner::NotifyDOMWindowThawed(nsGlobalWindowInner* aWindow) {
+ if (aWindow) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_THAWED_TOPIC, nullptr);
+ }
+ }
+}
+
+Element* nsGlobalWindowInner::GetFrameElement(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetFrameElement, (aSubjectPrincipal), aError,
+ nullptr);
+}
+
+Element* nsGlobalWindowInner::GetRealFrameElement(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetFrameElement, (), aError, nullptr);
+}
+
+void nsGlobalWindowInner::UpdateCommands(const nsAString& anAction,
+ Selection* aSel, int16_t aReason) {
+ if (GetOuterWindowInternal()) {
+ GetOuterWindowInternal()->UpdateCommands(anAction, aSel, aReason);
+ }
+}
+
+Selection* nsGlobalWindowInner::GetSelection(ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr);
+}
+
+WebTaskScheduler* nsGlobalWindowInner::Scheduler() {
+ if (!mWebTaskScheduler) {
+ mWebTaskScheduler = WebTaskScheduler::CreateForMainThread(this);
+ }
+ MOZ_ASSERT(mWebTaskScheduler);
+ return mWebTaskScheduler;
+}
+
+bool nsGlobalWindowInner::Find(const nsAString& aString, bool aCaseSensitive,
+ bool aBackwards, bool aWrapAround,
+ bool aWholeWord, bool aSearchInFrames,
+ bool aShowDialog, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(FindOuter,
+ (aString, aCaseSensitive, aBackwards, aWrapAround,
+ aWholeWord, aSearchInFrames, aShowDialog, aError),
+ aError, false);
+}
+
+void nsGlobalWindowInner::GetOrigin(nsAString& aOrigin) {
+ nsContentUtils::GetUTFOrigin(GetPrincipal(), aOrigin);
+}
+
+// See also AutoJSAPI::ReportException
+void nsGlobalWindowInner::ReportError(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (MOZ_UNLIKELY(!HasActiveDocument())) {
+ return aRv.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);
+ }
+
+ JS::ErrorReportBuilder jsReport(aCx);
+ JS::ExceptionStack exnStack(aCx, aError, nullptr);
+ if (!jsReport.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
+ return aRv.NoteJSContextException(aCx);
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ bool isChrome = aCallerType == CallerType::System;
+ xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(),
+ isChrome, WindowID());
+
+ JS::RootingContext* rcx = JS::RootingContext::get(aCx);
+ DispatchScriptErrorEvent(this, rcx, xpcReport, exnStack.exception(),
+ exnStack.stack());
+}
+
+void nsGlobalWindowInner::Atob(const nsAString& aAsciiBase64String,
+ nsAString& aBinaryData, ErrorResult& aError) {
+ aError = nsContentUtils::Atob(aAsciiBase64String, aBinaryData);
+}
+
+void nsGlobalWindowInner::Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String,
+ ErrorResult& aError) {
+ aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
+}
+
+//*****************************************************************************
+// EventTarget
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowInner::GetOwnerGlobalForBindingsInternal() {
+ return nsPIDOMWindowOuter::GetFromCurrentInner(this);
+}
+
+bool nsGlobalWindowInner::DispatchEvent(Event& aEvent, CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (!IsCurrentInnerWindow()) {
+ NS_WARNING(
+ "DispatchEvent called on non-current inner window, dropping. "
+ "Please check the window in the caller instead.");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ if (!mDoc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ // Obtain a presentation shell
+ RefPtr<nsPresContext> presContext = mDoc->GetPresContext();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // TODO: Bug 1506441
+ nsresult rv = EventDispatcher::DispatchDOMEvent(
+ MOZ_KnownLive(ToSupports(this)), nullptr, &aEvent, presContext, &status);
+ bool retval = !aEvent.DefaultPrevented(aCallerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ return retval;
+}
+
+mozilla::Maybe<mozilla::dom::EventCallbackDebuggerNotificationType>
+nsGlobalWindowInner::GetDebuggerNotificationType() const {
+ return mozilla::Some(
+ mozilla::dom::EventCallbackDebuggerNotificationType::Global);
+}
+
+bool nsGlobalWindowInner::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
+ return !nsContentUtils::IsChromeDoc(mDoc);
+}
+
+EventListenerManager* nsGlobalWindowInner::GetOrCreateListenerManager() {
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager* nsGlobalWindowInner::GetExistingListenerManager() const {
+ return mListenerManager;
+}
+
+mozilla::dom::DebuggerNotificationManager*
+nsGlobalWindowInner::GetOrCreateDebuggerNotificationManager() {
+ if (!mDebuggerNotificationManager) {
+ mDebuggerNotificationManager = new DebuggerNotificationManager(this);
+ }
+
+ return mDebuggerNotificationManager;
+}
+
+mozilla::dom::DebuggerNotificationManager*
+nsGlobalWindowInner::GetExistingDebuggerNotificationManager() {
+ return mDebuggerNotificationManager;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsPIDOMWindow
+//*****************************************************************************
+
+Location* nsGlobalWindowInner::Location() {
+ if (!mLocation) {
+ mLocation = new dom::Location(this, GetBrowsingContext());
+ }
+
+ return mLocation;
+}
+
+void nsGlobalWindowInner::MaybeUpdateTouchState() {
+ if (mMayHaveTouchEventListener) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this),
+ DOM_TOUCH_LISTENER_ADDED, nullptr);
+ }
+ }
+}
+
+void nsGlobalWindowInner::EnableGamepadUpdates() {
+ if (mHasGamepad) {
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ if (gamepadManager) {
+ gamepadManager->AddListener(this);
+ }
+ }
+}
+
+void nsGlobalWindowInner::DisableGamepadUpdates() {
+ if (mHasGamepad) {
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ if (gamepadManager) {
+ gamepadManager->RemoveListener(this);
+ }
+ }
+}
+
+void nsGlobalWindowInner::EnableVRUpdates() {
+ // We need to create a VREventObserver before we can either detect XR runtimes
+ // or start an XR session
+ if (!mVREventObserver && (mHasXRSession || mXRRuntimeDetectionInFlight)) {
+ // Assert that we are not creating the observer while IsDying() as
+ // that would result in a leak. VREventObserver holds a RefPtr to
+ // this nsGlobalWindowInner and would prevent it from being deallocated.
+ MOZ_ASSERT(!IsDying(),
+ "Creating a VREventObserver for an nsGlobalWindow that is "
+ "dying would cause it to leak.");
+ mVREventObserver = new VREventObserver(this);
+ }
+ // If the content has an XR session, then we need to tell
+ // VREventObserver that there is VR activity.
+ if (mHasXRSession) {
+ nsPIDOMWindowOuter* outer = GetOuterWindow();
+ if (outer && !outer->IsBackground()) {
+ StartVRActivity();
+ }
+ }
+}
+
+void nsGlobalWindowInner::DisableVRUpdates() {
+ if (mVREventObserver) {
+ mVREventObserver->DisconnectFromOwner();
+ mVREventObserver = nullptr;
+ }
+}
+
+void nsGlobalWindowInner::ResetVRTelemetry(bool aUpdate) {
+ if (mVREventObserver) {
+ mVREventObserver->UpdateSpentTimeIn2DTelemetry(aUpdate);
+ }
+}
+
+void nsGlobalWindowInner::StartVRActivity() {
+ /**
+ * If the content has an XR session, tell
+ * the VREventObserver that the window is accessing
+ * VR devices.
+ *
+ * It's possible to have a VREventObserver without
+ * and XR session, if we are using it to get updates
+ * about XR runtime enumeration. In this case,
+ * we would not tell the VREventObserver that
+ * we are accessing VR devices.
+ */
+ if (mVREventObserver && mHasXRSession) {
+ mVREventObserver->StartActivity();
+ }
+}
+
+void nsGlobalWindowInner::StopVRActivity() {
+ /**
+ * If the content has an XR session, tell
+ * the VReventObserver that the window is no longer
+ * accessing VR devices. This does not stop the
+ * XR session itself, which may be resumed with
+ * EnableVRUpdates.
+ * It's possible to have a VREventObserver without
+ * and XR session, if we are using it to get updates
+ * about XR runtime enumeration. In this case,
+ * we would not tell the VREventObserver that
+ * we ending an activity that accesses VR devices.
+ */
+ if (mVREventObserver && mHasXRSession) {
+ mVREventObserver->StopActivity();
+ }
+}
+
+void nsGlobalWindowInner::SetFocusedElement(Element* aElement,
+ uint32_t aFocusMethod,
+ bool aNeedsFocus) {
+ if (aElement && aElement->GetComposedDoc() != mDoc) {
+ NS_WARNING("Trying to set focus to a node from a wrong document");
+ return;
+ }
+
+ if (IsDying()) {
+ NS_ASSERTION(!aElement, "Trying to focus cleaned up window!");
+ aElement = nullptr;
+ aNeedsFocus = false;
+ }
+ if (mFocusedElement != aElement) {
+ UpdateCanvasFocus(false, aElement);
+ mFocusedElement = aElement;
+ // TODO: Maybe this should be set on refocus too?
+ mFocusMethod = aFocusMethod & nsIFocusManager::METHOD_MASK;
+ }
+
+ if (mFocusedElement) {
+ // if a node was focused by a keypress, turn on focus rings for the
+ // window.
+ if (mFocusMethod & nsIFocusManager::FLAG_BYKEY) {
+ mUnknownFocusMethodShouldShowOutline = true;
+ mFocusByKeyOccurred = true;
+ } else if (nsFocusManager::GetFocusMoveActionCause(mFocusMethod) !=
+ widget::InputContextAction::CAUSE_UNKNOWN) {
+ mUnknownFocusMethodShouldShowOutline = false;
+ } else if (aFocusMethod & nsIFocusManager::FLAG_NOSHOWRING) {
+ // If we get focused via script, and script has explicitly opted out of
+ // outlines via FLAG_NOSHOWRING, we don't want to make a refocus start
+ // showing outlines.
+ mUnknownFocusMethodShouldShowOutline = false;
+ }
+ }
+
+ if (aNeedsFocus) {
+ mNeedsFocus = aNeedsFocus;
+ }
+}
+
+uint32_t nsGlobalWindowInner::GetFocusMethod() { return mFocusMethod; }
+
+bool nsGlobalWindowInner::ShouldShowFocusRing() {
+ if (mFocusByKeyOccurred &&
+ StaticPrefs::browser_display_always_show_rings_after_key_focus()) {
+ return true;
+ }
+ return StaticPrefs::browser_display_show_focus_rings();
+}
+
+bool nsGlobalWindowInner::TakeFocus(bool aFocus, uint32_t aFocusMethod) {
+ if (IsDying()) {
+ return false;
+ }
+
+ if (aFocus) {
+ mFocusMethod = aFocusMethod & nsIFocusManager::METHOD_MASK;
+ }
+
+ if (mHasFocus != aFocus) {
+ mHasFocus = aFocus;
+ UpdateCanvasFocus(true, mFocusedElement);
+ }
+
+ // if mNeedsFocus is true, then the document has not yet received a
+ // document-level focus event. If there is a root content node, then return
+ // true to tell the calling focus manager that a focus event is expected. If
+ // there is no root content node, the document hasn't loaded enough yet, or
+ // there isn't one and there is no point in firing a focus event.
+ if (aFocus && mNeedsFocus && mDoc && mDoc->GetRootElement() != nullptr) {
+ mNeedsFocus = false;
+ return true;
+ }
+
+ mNeedsFocus = false;
+ return false;
+}
+
+void nsGlobalWindowInner::SetReadyForFocus() {
+ bool oldNeedsFocus = mNeedsFocus;
+ mNeedsFocus = false;
+
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = GetOuterWindow();
+ fm->WindowShown(outerWindow, oldNeedsFocus);
+ }
+}
+
+void nsGlobalWindowInner::PageHidden() {
+ // the window is being hidden, so tell the focus manager that the frame is
+ // no longer valid. Use the persisted field to determine if the document
+ // is being destroyed.
+
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = GetOuterWindow();
+ fm->WindowHidden(outerWindow, nsFocusManager::GenerateFocusActionId());
+ }
+
+ mNeedsFocus = true;
+}
+
+class HashchangeCallback : public Runnable {
+ public:
+ HashchangeCallback(const nsAString& aOldURL, const nsAString& aNewURL,
+ nsGlobalWindowInner* aWindow)
+ : mozilla::Runnable("HashchangeCallback"), mWindow(aWindow) {
+ MOZ_ASSERT(mWindow);
+ mOldURL.Assign(aOldURL);
+ mNewURL.Assign(aNewURL);
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
+ return mWindow->FireHashchange(mOldURL, mNewURL);
+ }
+
+ private:
+ nsString mOldURL;
+ nsString mNewURL;
+ RefPtr<nsGlobalWindowInner> mWindow;
+};
+
+nsresult nsGlobalWindowInner::DispatchAsyncHashchange(nsIURI* aOldURI,
+ nsIURI* aNewURI) {
+ // Make sure that aOldURI and aNewURI are identical up to the '#', and that
+ // their hashes are different.
+ bool equal = false;
+ NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->EqualsExceptRef(aNewURI, &equal)) &&
+ equal);
+ nsAutoCString oldHash, newHash;
+ bool oldHasHash, newHasHash;
+ NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->GetRef(oldHash)) &&
+ NS_SUCCEEDED(aNewURI->GetRef(newHash)) &&
+ NS_SUCCEEDED(aOldURI->GetHasRef(&oldHasHash)) &&
+ NS_SUCCEEDED(aNewURI->GetHasRef(&newHasHash)) &&
+ (oldHasHash != newHasHash || !oldHash.Equals(newHash)));
+
+ nsAutoCString oldSpec, newSpec;
+ nsresult rv = aOldURI->GetSpec(oldSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aNewURI->GetSpec(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 oldWideSpec(oldSpec);
+ NS_ConvertUTF8toUTF16 newWideSpec(newSpec);
+
+ nsCOMPtr<nsIRunnable> callback =
+ new HashchangeCallback(oldWideSpec, newWideSpec, this);
+ return Dispatch(TaskCategory::Other, callback.forget());
+}
+
+nsresult nsGlobalWindowInner::FireHashchange(const nsAString& aOldURL,
+ const nsAString& aNewURL) {
+ // Don't do anything if the window is frozen.
+ if (IsFrozen()) {
+ return NS_OK;
+ }
+
+ // Get a presentation shell for use in creating the hashchange event.
+ NS_ENSURE_STATE(IsCurrentInnerWindow());
+
+ HashChangeEventInit init;
+ init.mNewURL = aNewURL;
+ init.mOldURL = aOldURL;
+
+ RefPtr<HashChangeEvent> event =
+ HashChangeEvent::Constructor(this, u"hashchange"_ns, init);
+
+ event->SetTrusted(true);
+
+ ErrorResult rv;
+ DispatchEvent(*event, rv);
+ return rv.StealNSResult();
+}
+
+nsresult nsGlobalWindowInner::DispatchSyncPopState() {
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Must be safe to run script here.");
+
+ // Bail if the window is frozen.
+ if (IsFrozen()) {
+ return NS_OK;
+ }
+
+ AutoJSAPI jsapi;
+ bool result = jsapi.Init(this);
+ NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
+
+ JSContext* cx = jsapi.cx();
+
+ // Get the document's pending state object -- it contains the data we're
+ // going to send along with the popstate event. The object is serialized
+ // using structured clone.
+ JS::Rooted<JS::Value> stateJSValue(cx);
+ nsresult rv = mDoc->GetStateObject(&stateJSValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!JS_WrapValue(cx, &stateJSValue)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RootedDictionary<PopStateEventInit> init(cx);
+ init.mState = stateJSValue;
+
+ RefPtr<PopStateEvent> event =
+ PopStateEvent::Constructor(this, u"popstate"_ns, init);
+ event->SetTrusted(true);
+ event->SetTarget(this);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+//-------------------------------------------------------
+// Tells the HTMLFrame/CanvasFrame that is now has focus
+void nsGlobalWindowInner::UpdateCanvasFocus(bool aFocusChanged,
+ nsIContent* aNewContent) {
+ // this is called from the inner window so use GetDocShell
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) return;
+
+ bool editable;
+ docShell->GetEditable(&editable);
+ if (editable) return;
+
+ PresShell* presShell = docShell->GetPresShell();
+ if (!presShell || !mDoc) {
+ return;
+ }
+
+ Element* rootElement = mDoc->GetRootElement();
+ if (rootElement) {
+ if ((mHasFocus || aFocusChanged) &&
+ (mFocusedElement == rootElement || aNewContent == rootElement)) {
+ nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame();
+ if (canvasFrame) {
+ canvasFrame->SetHasFocus(mHasFocus && rootElement == aNewContent);
+ }
+ }
+ } else {
+ // XXXbz I would expect that there is never a canvasFrame in this case...
+ nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame();
+ if (canvasFrame) {
+ canvasFrame->SetHasFocus(false);
+ }
+ }
+}
+
+already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyle(
+ Element& aElt, const nsAString& aPseudoElt, ErrorResult& aError) {
+ return GetComputedStyleHelper(aElt, aPseudoElt, false, aError);
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindowInner::GetDefaultComputedStyle(Element& aElt,
+ const nsAString& aPseudoElt,
+ ErrorResult& aError) {
+ return GetComputedStyleHelper(aElt, aPseudoElt, true, aError);
+}
+
+already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyleHelper(
+ Element& aElt, const nsAString& aPseudoElt, bool aDefaultStylesOnly,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter,
+ (aElt, aPseudoElt, aDefaultStylesOnly, aError),
+ aError, nullptr);
+}
+
+Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
+ nsIPrincipal* principal = GetPrincipal();
+ nsIPrincipal* storagePrincipal;
+ if (StaticPrefs::
+ privacy_partition_always_partition_third_party_non_cookie_storage_exempt_sessionstorage()) {
+ storagePrincipal = GetEffectiveCookiePrincipal();
+ } else {
+ storagePrincipal = GetEffectiveStoragePrincipal();
+ }
+ BrowsingContext* browsingContext = GetBrowsingContext();
+
+ if (!principal || !storagePrincipal || !browsingContext ||
+ !Storage::StoragePrefIsEnabled()) {
+ return nullptr;
+ }
+
+ if (mSessionStorage) {
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("nsGlobalWindowInner %p has %p sessionStorage", this,
+ mSessionStorage.get()));
+ bool canAccess =
+ principal->Subsumes(mSessionStorage->Principal()) &&
+ storagePrincipal->Subsumes(mSessionStorage->StoragePrincipal());
+ if (!canAccess) {
+ mSessionStorage = nullptr;
+ }
+ }
+
+ if (!mSessionStorage) {
+ nsString documentURI;
+ if (mDoc) {
+ aError = mDoc->GetDocumentURI(documentURI);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+
+ if (!mDoc) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // If the document's sandboxed origin flag is set, then accessing
+ // sessionStorage is prohibited.
+ if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ aError.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return nullptr;
+ }
+
+ uint32_t rejectedReason = 0;
+ StorageAccess access = StorageAllowedForWindow(this, &rejectedReason);
+
+ // SessionStorage is an ephemeral per-tab per-origin storage that only lives
+ // as long as the tab is open, although it may survive browser restarts
+ // thanks to the session store. So we interpret storage access differently
+ // than we would for persistent per-origin storage like LocalStorage and so
+ // it may be okay to provide SessionStorage even when we receive a value of
+ // eDeny.
+ //
+ // ContentBlocking::ShouldAllowAccessFor will return false for 3 main
+ // reasons.
+ //
+ // 1. Cookies are entirely blocked due to a per-origin permission
+ // (nsICookiePermission::ACCESS_DENY for the top-level principal or this
+ // window's principal) or the very broad BEHAVIOR_REJECT. This will return
+ // eDeny with a reason of STATE_COOKIES_BLOCKED_BY_PERMISSION or
+ // STATE_COOKIES_BLOCKED_ALL.
+ //
+ // 2. Third-party cookies are limited via BEHAVIOR_REJECT_FOREIGN and
+ // BEHAVIOR_LIMIT_FOREIGN and this is a third-party window. This will return
+ // eDeny with a reason of STATE_COOKIES_BLOCKED_FOREIGN.
+ //
+ // 3. Tracking protection (BEHAVIOR_REJECT_TRACKER and
+ // BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) is in effect and
+ // IsThirdPartyTrackingResourceWindow() returned true and there wasn't a
+ // permission that allows it. This will return ePartitionTrackersOrDeny with
+ // a reason of STATE_COOKIES_BLOCKED_TRACKER or
+ // STATE_COOKIES_BLOCKED_SOCIALTRACKER.
+ //
+ // In the 1st case, the user has explicitly indicated that they don't want
+ // to allow any storage to the origin or all origins and so we throw an
+ // error and deny access to SessionStorage. In the 2nd case, a legacy
+ // decision reasoned that there's no harm in providing SessionStorage
+ // because the information is not durable and cannot escape the current tab.
+ // The rationale is similar for the 3rd case.
+ if (access == StorageAccess::eDeny &&
+ rejectedReason !=
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ const RefPtr<SessionStorageManager> storageManager =
+ browsingContext->GetSessionStorageManager();
+ if (!storageManager) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ RefPtr<Storage> storage;
+ aError = storageManager->CreateStorage(this, principal, storagePrincipal,
+ documentURI, IsPrivateBrowsing(),
+ getter_AddRefs(storage));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mSessionStorage = storage;
+ MOZ_ASSERT(mSessionStorage);
+
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("nsGlobalWindowInner %p tried to get a new sessionStorage %p",
+ this, mSessionStorage.get()));
+
+ if (!mSessionStorage) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ }
+
+ MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
+ ("nsGlobalWindowInner %p returns %p sessionStorage", this,
+ mSessionStorage.get()));
+
+ return mSessionStorage;
+}
+
+Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
+ if (!Storage::StoragePrefIsEnabled()) {
+ return nullptr;
+ }
+
+ // If the document's sandboxed origin flag is set, then accessing localStorage
+ // is prohibited.
+ if (mDoc && mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ aError.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return nullptr;
+ }
+
+ // LocalStorage needs to be exposed in every context except for sandboxes and
+ // NullPrincipals (data: URLs, for instance). But we need to keep data
+ // separate in some scenarios: private-browsing and partitioned trackers.
+ // In private-browsing, LocalStorage keeps data in memory, and it shares
+ // StorageEvents just with other origins in the same private-browsing
+ // environment.
+ // For Partitioned Trackers, we expose a partitioned LocalStorage, which
+ // doesn't share data with other contexts, and it's just in memory.
+ // Partitioned localStorage is available only for trackers listed in the
+ // privacy.restrict3rdpartystorage.partitionedHosts pref. See
+ // nsContentUtils::IsURIInPrefList to know the syntax for the pref value.
+ // This is a temporary web-compatibility hack.
+
+ StorageAccess access = StorageAllowedForWindow(this);
+
+ // We allow partitioned localStorage only to some hosts.
+ bool isolated = false;
+ if (ShouldPartitionStorage(access)) {
+ if (!mDoc) {
+ access = StorageAccess::eDeny;
+ } else if (!StoragePartitioningEnabled(access, mDoc->CookieJarSettings())) {
+ static const char* kPrefName =
+ "privacy.restrict3rdpartystorage.partitionedHosts";
+
+ bool isInList = false;
+ mDoc->NodePrincipal()->IsURIInPrefList(kPrefName, &isInList);
+ if (!isInList) {
+ access = StorageAccess::eDeny;
+ } else {
+ isolated = true;
+ }
+ }
+ }
+
+ if (access == StorageAccess::eDeny) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ if (mDoc) {
+ cookieJarSettings = mDoc->CookieJarSettings();
+ } else {
+ cookieJarSettings =
+ net::CookieJarSettings::GetBlockingAll(ShouldResistFingerprinting());
+ }
+
+ // Note that this behavior is observable: if we grant storage permission to a
+ // tracker, we pass from the partitioned LocalStorage (or a partitioned cookie
+ // jar) to the 'normal' one. The previous data is lost and the 2
+ // window.localStorage objects, before and after the permission granted, will
+ // be different.
+ if (mLocalStorage) {
+ if ((mLocalStorage->Type() == (isolated ? Storage::ePartitionedLocalStorage
+ : Storage::eLocalStorage)) &&
+ (mLocalStorage->StoragePrincipal() == GetEffectiveStoragePrincipal())) {
+ return mLocalStorage;
+ }
+
+ // storage needs change
+ mLocalStorage = nullptr;
+ }
+
+ MOZ_ASSERT(!mLocalStorage);
+
+ if (!isolated) {
+ RefPtr<Storage> storage;
+
+ if (NextGenLocalStorageEnabled()) {
+ aError = LSObject::CreateForWindow(this, getter_AddRefs(storage));
+ } else {
+ nsresult rv;
+ nsCOMPtr<nsIDOMStorageManager> storageManager =
+ do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ nsString documentURI;
+ if (mDoc) {
+ aError = mDoc->GetDocumentURI(documentURI);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+
+ nsIPrincipal* principal = GetPrincipal();
+ if (!principal) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
+ if (!storagePrincipal) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ aError = storageManager->CreateStorage(this, principal, storagePrincipal,
+ documentURI, IsPrivateBrowsing(),
+ getter_AddRefs(storage));
+ }
+
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mLocalStorage = storage;
+ } else {
+ nsresult rv;
+ nsCOMPtr<nsIDOMSessionStorageManager> storageManager =
+ do_GetService("@mozilla.org/dom/sessionStorage-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ nsIPrincipal* principal = GetPrincipal();
+ if (!principal) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
+ if (!storagePrincipal) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<SessionStorageCache> cache;
+ if (isolated) {
+ cache = new SessionStorageCache();
+ } else {
+ // This will clone the session storage if it exists.
+ rv = storageManager->GetSessionStorageCache(principal, storagePrincipal,
+ &cache);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ mLocalStorage =
+ new PartitionedLocalStorage(this, principal, storagePrincipal, cache);
+ }
+
+ MOZ_ASSERT(mLocalStorage);
+ MOZ_ASSERT(
+ mLocalStorage->Type() ==
+ (isolated ? Storage::ePartitionedLocalStorage : Storage::eLocalStorage));
+ return mLocalStorage;
+}
+
+IDBFactory* nsGlobalWindowInner::GetIndexedDB(JSContext* aCx,
+ ErrorResult& aError) {
+ if (!mIndexedDB) {
+ // This may keep mIndexedDB null without setting an error.
+ auto res = IDBFactory::CreateForWindow(this);
+ if (res.isErr()) {
+ aError = res.unwrapErr();
+ } else {
+ mIndexedDB = res.unwrap();
+ }
+ }
+
+ return mIndexedDB;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsGlobalWindowInner::GetInterface(const nsIID& aIID, void** aSink) {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = outer->GetInterfaceInternal(aIID, aSink);
+ if (rv == NS_ERROR_NO_INTERFACE) {
+ return QueryInterface(aIID, aSink);
+ }
+ return rv;
+}
+
+void nsGlobalWindowInner::GetInterface(JSContext* aCx,
+ JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError) {
+ dom::GetInterface(aCx, this, aIID, aRetval, aError);
+}
+
+already_AddRefed<CacheStorage> nsGlobalWindowInner::GetCaches(
+ ErrorResult& aRv) {
+ if (!mCacheStorage) {
+ bool forceTrustedOrigin =
+ GetBrowsingContext() &&
+ GetBrowsingContext()->Top()->GetServiceWorkersTestingEnabled();
+ mCacheStorage = CacheStorage::CreateOnMainThread(
+ cache::DEFAULT_NAMESPACE, this, GetEffectiveStoragePrincipal(),
+ forceTrustedOrigin, aRv);
+ }
+
+ RefPtr<CacheStorage> ref = mCacheStorage;
+ return ref.forget();
+}
+
+void nsGlobalWindowInner::FireOfflineStatusEventIfChanged() {
+ if (!IsCurrentInnerWindow()) return;
+
+ // Don't fire an event if the status hasn't changed
+ if (mWasOffline == NS_IsOffline()) {
+ return;
+ }
+
+ mWasOffline = !mWasOffline;
+
+ nsAutoString name;
+ if (mWasOffline) {
+ name.AssignLiteral("offline");
+ } else {
+ name.AssignLiteral("online");
+ }
+ nsContentUtils::DispatchTrustedEvent(mDoc, static_cast<EventTarget*>(this),
+ name, CanBubble::eNo, Cancelable::eNo);
+}
+
+nsGlobalWindowInner::SlowScriptResponse
+nsGlobalWindowInner::ShowSlowScriptDialog(JSContext* aCx,
+ const nsString& aAddonId,
+ const double aDuration) {
+ nsresult rv;
+
+ if (Preferences::GetBool("dom.always_stop_slow_scripts")) {
+ return KillSlowScript;
+ }
+
+ // If it isn't safe to run script, then it isn't safe to bring up the prompt
+ // (since that spins the event loop). In that (rare) case, we just kill the
+ // script and report a warning.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ JS::WarnASCII(aCx, "A long running script was terminated");
+ return KillSlowScript;
+ }
+
+ // If our document is not active, just kill the script: we've been unloaded
+ if (!HasActiveDocument()) {
+ return KillSlowScript;
+ }
+
+ // Check if we should offer the option to debug
+ JS::AutoFilename filename;
+ unsigned lineno;
+ // Computing the line number can be very expensive (see bug 1330231 for
+ // example), and we don't use the line number anywhere except than in the
+ // parent process, so we avoid computing it elsewhere. This gives us most of
+ // the wins we are interested in, since the source of the slowness here is
+ // minified scripts which is more common in Web content that is loaded in the
+ // content process.
+ unsigned* linenop = XRE_IsParentProcess() ? &lineno : nullptr;
+ bool hasFrame = JS::DescribeScriptedCaller(aCx, &filename, linenop);
+
+ // Record the slow script event if we haven't done so already for this inner
+ // window (which represents a particular page to the user).
+ if (!mHasHadSlowScript) {
+ Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_PAGE_COUNT, 1);
+ }
+ mHasHadSlowScript = true;
+
+ // Override the cursor to something that we're sure the user can see.
+ SetCursor("auto"_ns, IgnoreErrors());
+
+ if (XRE_IsContentProcess() && ProcessHangMonitor::Get()) {
+ ProcessHangMonitor::SlowScriptAction action;
+ RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
+ nsIDocShell* docShell = GetDocShell();
+ nsCOMPtr<nsIBrowserChild> child =
+ docShell ? docShell->GetBrowserChild() : nullptr;
+ action =
+ monitor->NotifySlowScript(child, filename.get(), aAddonId, aDuration);
+ if (action == ProcessHangMonitor::Terminate) {
+ return KillSlowScript;
+ }
+
+ if (action == ProcessHangMonitor::StartDebugger) {
+ // Spin a nested event loop so that the debugger in the parent can fetch
+ // any information it needs. Once the debugger has started, return to the
+ // script.
+ RefPtr<nsGlobalWindowOuter> outer = GetOuterWindowInternal();
+ outer->EnterModalState();
+ SpinEventLoopUntil("nsGlobalWindowInner::ShowSlowScriptDialog"_ns, [&]() {
+ return monitor->IsDebuggerStartupComplete();
+ });
+ outer->LeaveModalState();
+ return ContinueSlowScript;
+ }
+
+ return ContinueSlowScriptAndKeepNotifying;
+ }
+
+ // Reached only on non-e10s - once per slow script dialog.
+ // On e10s - we probe once at ProcessHangsMonitor.jsm
+ Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTICE_COUNT, 1);
+
+ // Get the nsIPrompt interface from the docshell
+ nsCOMPtr<nsIDocShell> ds = GetDocShell();
+ NS_ENSURE_TRUE(ds, KillSlowScript);
+ nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds);
+ NS_ENSURE_TRUE(prompt, KillSlowScript);
+
+ // Prioritize the SlowScriptDebug interface over JSD1.
+ nsCOMPtr<nsISlowScriptDebugCallback> debugCallback;
+
+ if (hasFrame) {
+ const char* debugCID = "@mozilla.org/dom/slow-script-debug;1";
+ nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ debugService->GetActivationHandler(getter_AddRefs(debugCallback));
+ }
+ }
+
+ bool failed = false;
+ auto getString = [&](const char* name,
+ nsContentUtils::PropertiesFile propFile =
+ nsContentUtils::eDOM_PROPERTIES) {
+ nsAutoString result;
+ nsresult rv = nsContentUtils::GetLocalizedString(propFile, name, result);
+
+ // GetStringFromName can return NS_OK and still give nullptr string
+ failed = failed || NS_FAILED(rv) || result.IsEmpty();
+ return result;
+ };
+
+ bool isAddonScript = !aAddonId.IsEmpty();
+ bool showDebugButton = debugCallback && !isAddonScript;
+
+ // Get localizable strings
+
+ nsAutoString title, checkboxMsg, debugButton, msg;
+ if (isAddonScript) {
+ title = getString("KillAddonScriptTitle");
+ checkboxMsg = getString("KillAddonScriptGlobalMessage");
+
+ auto appName =
+ getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);
+
+ nsCOMPtr<nsIAddonPolicyService> aps =
+ do_GetService("@mozilla.org/addons/policy-service;1");
+ nsString addonName;
+ if (!aps || NS_FAILED(aps->GetExtensionName(aAddonId, addonName))) {
+ addonName = aAddonId;
+ }
+
+ rv = nsContentUtils::FormatLocalizedString(
+ msg, nsContentUtils::eDOM_PROPERTIES, "KillAddonScriptMessage",
+ addonName, appName);
+
+ failed = failed || NS_FAILED(rv);
+ } else {
+ title = getString("KillScriptTitle");
+ checkboxMsg = getString("DontAskAgain");
+
+ if (showDebugButton) {
+ debugButton = getString("DebugScriptButton");
+ msg = getString("KillScriptWithDebugMessage");
+ } else {
+ msg = getString("KillScriptMessage");
+ }
+ }
+
+ auto stopButton = getString("StopScriptButton");
+ auto waitButton = getString("WaitForScriptButton");
+
+ if (failed) {
+ NS_ERROR("Failed to get localized strings.");
+ return ContinueSlowScript;
+ }
+
+ // Append file and line number information, if available
+ if (filename.get()) {
+ nsAutoString scriptLocation;
+ // We want to drop the middle part of too-long locations. We'll
+ // define "too-long" as longer than 60 UTF-16 code units. Just
+ // have to be a bit careful about unpaired surrogates.
+ NS_ConvertUTF8toUTF16 filenameUTF16(filename.get());
+ if (filenameUTF16.Length() > 60) {
+ // XXXbz Do we need to insert any bidi overrides here?
+ size_t cutStart = 30;
+ size_t cutLength = filenameUTF16.Length() - 60;
+ MOZ_ASSERT(cutLength > 0);
+ if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart])) {
+ // Don't truncate before the low surrogate, in case it's preceded by a
+ // high surrogate and forms a single Unicode character. Instead, just
+ // include the low surrogate.
+ ++cutStart;
+ --cutLength;
+ }
+ if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart + cutLength])) {
+ // Likewise, don't drop a trailing low surrogate here. We want to
+ // increase cutLength, since it might be 0 already so we can't very well
+ // decrease it.
+ ++cutLength;
+ }
+
+ // Insert U+2026 HORIZONTAL ELLIPSIS
+ filenameUTF16.ReplaceLiteral(cutStart, cutLength, u"\x2026");
+ }
+ rv = nsContentUtils::FormatLocalizedString(
+ scriptLocation, nsContentUtils::eDOM_PROPERTIES, "KillScriptLocation",
+ filenameUTF16);
+
+ if (NS_SUCCEEDED(rv)) {
+ msg.AppendLiteral("\n\n");
+ msg.Append(scriptLocation);
+ msg.Append(':');
+ msg.AppendInt(lineno);
+ }
+ }
+
+ uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
+ (nsIPrompt::BUTTON_TITLE_IS_STRING *
+ (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
+
+ // Add a third button if necessary.
+ if (showDebugButton)
+ buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
+
+ bool checkboxValue = false;
+ int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
+ {
+ // Null out the operation callback while we're re-entering JS here.
+ AutoDisableJSInterruptCallback disabler(aCx);
+
+ // Open the dialog.
+ rv = prompt->ConfirmEx(
+ title.get(), msg.get(), buttonFlags, waitButton.get(), stopButton.get(),
+ debugButton.get(), checkboxMsg.get(), &checkboxValue, &buttonPressed);
+ }
+
+ if (buttonPressed == 0) {
+ if (checkboxValue && !isAddonScript && NS_SUCCEEDED(rv))
+ return AlwaysContinueSlowScript;
+ return ContinueSlowScript;
+ }
+
+ if (buttonPressed == 2) {
+ MOZ_RELEASE_ASSERT(debugCallback);
+
+ rv = debugCallback->HandleSlowScriptDebug(this);
+ return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
+ }
+
+ JS_ClearPendingException(aCx);
+
+ return KillSlowScript;
+}
+
+nsresult nsGlobalWindowInner::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
+ if (!IsFrozen()) {
+ // Fires an offline status event if the offline status has changed
+ FireOfflineStatusEventIfChanged();
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
+ if (mPerformance) {
+ mPerformance->MemoryPressure();
+ }
+ RemoveReportRecords();
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, PERMISSION_CHANGED_TOPIC)) {
+ nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
+ if (!perm) {
+ // A null permission indicates that the entire permission list
+ // was cleared.
+ MOZ_ASSERT(!nsCRT::strcmp(aData, u"cleared"));
+ UpdatePermissions();
+ return NS_OK;
+ }
+
+ nsAutoCString type;
+ perm->GetType(type);
+ if (type == "autoplay-media"_ns) {
+ UpdateAutoplayPermission();
+ } else if (type == "shortcuts"_ns) {
+ UpdateShortcutsPermission();
+ } else if (type == "popup"_ns) {
+ UpdatePopupPermission();
+ }
+
+ if (!mDoc) {
+ return NS_OK;
+ }
+
+ RefPtr<PermissionDelegateHandler> permDelegateHandler =
+ mDoc->GetPermissionDelegateHandler();
+
+ if (permDelegateHandler) {
+ permDelegateHandler->UpdateDelegatedPermission(type);
+ }
+
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "screen-information-changed")) {
+ if (mScreen) {
+ if (RefPtr<ScreenOrientation> orientation =
+ mScreen->GetOrientationIfExists()) {
+ orientation->MaybeChanged();
+ }
+ }
+ if (mHasOrientationChangeListeners) {
+ int32_t oldAngle = mOrientationAngle;
+ mOrientationAngle = Orientation(CallerType::System);
+ if (mOrientationAngle != oldAngle && IsCurrentInnerWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outer = GetOuterWindow();
+ outer->DispatchCustomEvent(u"orientationchange"_ns);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));
+
+ // The user preferred languages have changed, we need to fire an event on
+ // Window object and invalidate the cache for navigator.languages. It is
+ // done for every change which can be a waste of cycles but those should be
+ // fairly rare.
+ // We MUST invalidate navigator.languages before sending the event in the
+ // very likely situation where an event handler will try to read its value.
+
+ if (mNavigator) {
+ Navigator_Binding::ClearCachedLanguageValue(mNavigator);
+ Navigator_Binding::ClearCachedLanguagesValue(mNavigator);
+ }
+
+ // The event has to be dispatched only to the current inner window.
+ if (!IsCurrentInnerWindow()) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(u"languagechange"_ns, false, false);
+ event->SetTrusted(true);
+
+ ErrorResult rv;
+ DispatchEvent(*event, rv);
+ return rv.StealNSResult();
+ }
+
+ NS_WARNING("unrecognized topic in nsGlobalWindowInner::Observe");
+ return NS_ERROR_FAILURE;
+}
+
+void nsGlobalWindowInner::ObserveStorageNotification(
+ StorageEvent* aEvent, const char16_t* aStorageType, bool aPrivateBrowsing) {
+ MOZ_ASSERT(aEvent);
+
+ // The private browsing check must be done here again because this window
+ // could have changed its state before the notification check and now. This
+ // happens in case this window did have a docShell at that time.
+ if (aPrivateBrowsing != IsPrivateBrowsing()) {
+ return;
+ }
+
+ // LocalStorage can only exist on an inner window, and we don't want to
+ // generate events on frozen or otherwise-navigated-away from windows.
+ // (Actually, this code used to try and buffer events for frozen windows,
+ // but it never worked, so we've removed it. See bug 1285898.)
+ if (!IsCurrentInnerWindow() || IsFrozen()) {
+ return;
+ }
+
+ nsIPrincipal* principal = GetPrincipal();
+ if (!principal) {
+ return;
+ }
+
+ bool fireMozStorageChanged = false;
+ nsAutoString eventType;
+ eventType.AssignLiteral("storage");
+
+ if (!NS_strcmp(aStorageType, u"sessionStorage")) {
+ RefPtr<Storage> changingStorage = aEvent->GetStorageArea();
+ MOZ_ASSERT(changingStorage);
+
+ bool check = false;
+
+ if (const RefPtr<SessionStorageManager> storageManager =
+ GetBrowsingContext()->GetSessionStorageManager()) {
+ nsresult rv = storageManager->CheckStorage(GetEffectiveStoragePrincipal(),
+ changingStorage, &check);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ if (!check) {
+ // This storage event is not coming from our storage or is coming
+ // from a different docshell, i.e. it is a clone, ignore this event.
+ return;
+ }
+
+ MOZ_LOG(
+ gDOMLeakPRLogInner, LogLevel::Debug,
+ ("nsGlobalWindowInner %p with sessionStorage %p passing event from %p",
+ this, mSessionStorage.get(), changingStorage.get()));
+
+ fireMozStorageChanged = mSessionStorage == changingStorage;
+ if (fireMozStorageChanged) {
+ eventType.AssignLiteral("MozSessionStorageChanged");
+ }
+ }
+
+ else {
+ MOZ_ASSERT(!NS_strcmp(aStorageType, u"localStorage"));
+
+ nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
+ if (!storagePrincipal) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(StorageUtils::PrincipalsEqual(aEvent->GetPrincipal(),
+ storagePrincipal));
+
+ fireMozStorageChanged =
+ mLocalStorage && mLocalStorage == aEvent->GetStorageArea();
+
+ if (fireMozStorageChanged) {
+ eventType.AssignLiteral("MozLocalStorageChanged");
+ }
+ }
+
+ // Clone the storage event included in the observer notification. We want
+ // to dispatch clones rather than the original event.
+ IgnoredErrorResult error;
+ RefPtr<StorageEvent> clonedEvent =
+ CloneStorageEvent(eventType, aEvent, error);
+ if (error.Failed() || !clonedEvent) {
+ return;
+ }
+
+ clonedEvent->SetTrusted(true);
+
+ if (fireMozStorageChanged) {
+ WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
+ internalEvent->mFlags.mOnlyChromeDispatch = true;
+ }
+
+ DispatchEvent(*clonedEvent);
+}
+
+already_AddRefed<StorageEvent> nsGlobalWindowInner::CloneStorageEvent(
+ const nsAString& aType, const RefPtr<StorageEvent>& aEvent,
+ ErrorResult& aRv) {
+ StorageEventInit dict;
+
+ dict.mBubbles = aEvent->Bubbles();
+ dict.mCancelable = aEvent->Cancelable();
+ aEvent->GetKey(dict.mKey);
+ aEvent->GetOldValue(dict.mOldValue);
+ aEvent->GetNewValue(dict.mNewValue);
+ aEvent->GetUrl(dict.mUrl);
+
+ RefPtr<Storage> storageArea = aEvent->GetStorageArea();
+
+ RefPtr<Storage> storage;
+
+ // If null, this is a localStorage event received by IPC.
+ if (!storageArea) {
+ storage = GetLocalStorage(aRv);
+ if (!NextGenLocalStorageEnabled()) {
+ if (aRv.Failed() || !storage) {
+ return nullptr;
+ }
+
+ if (storage->Type() == Storage::eLocalStorage) {
+ RefPtr<LocalStorage> localStorage =
+ static_cast<LocalStorage*>(storage.get());
+
+ // We must apply the current change to the 'local' localStorage.
+ localStorage->ApplyEvent(aEvent);
+ }
+ }
+ } else if (storageArea->Type() == Storage::eSessionStorage) {
+ storage = GetSessionStorage(aRv);
+ } else {
+ MOZ_ASSERT(storageArea->Type() == Storage::eLocalStorage);
+ storage = GetLocalStorage(aRv);
+ }
+
+ if (aRv.Failed() || !storage) {
+ return nullptr;
+ }
+
+ if (storage->Type() == Storage::ePartitionedLocalStorage) {
+ // This error message is not exposed.
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(storage);
+ MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
+
+ dict.mStorageArea = storage;
+
+ RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict);
+ return event.forget();
+}
+
+void nsGlobalWindowInner::Suspend(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We can only safely suspend windows that are the current inner window. If
+ // its not the current inner, then we are in one of two different cases.
+ // Either we are in the bfcache or we are doomed window that is going away.
+ // When a window becomes inactive we purposely avoid placing already suspended
+ // windows into the bfcache. It only expects windows suspended due to the
+ // Freeze() method which occurs while the window is still the current inner.
+ // So we must not call Suspend() on bfcache windows at this point or this
+ // invariant will be broken. If the window is doomed there is no point in
+ // suspending it since it will soon be gone.
+ if (!IsCurrentInnerWindow()) {
+ return;
+ }
+
+ // All in-process descendants are also suspended. This ensure mSuspendDepth
+ // is set properly and the timers are properly canceled for each in-process
+ // descendant.
+ if (aIncludeSubWindows) {
+ CallOnInProcessDescendants(&nsGlobalWindowInner::Suspend, false);
+ }
+
+ mSuspendDepth += 1;
+ if (mSuspendDepth != 1) {
+ return;
+ }
+
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::SUSPENDED);
+ }
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) {
+ for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
+ ac->RemoveWindowListener(mEnabledSensors[i], this);
+ }
+ DisableGamepadUpdates();
+ DisableVRUpdates();
+
+ SuspendWorkersForWindow(*this);
+
+ for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
+ mSharedWorkers.ForwardRange()) {
+ pinnedWorker->Suspend();
+ }
+
+ SuspendIdleRequests();
+
+ mTimeoutManager->Suspend();
+
+ // Suspend all of the AudioContexts for this window
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ mAudioContexts[i]->SuspendFromChrome();
+ }
+}
+
+void nsGlobalWindowInner::Resume(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We can only safely resume a window if its the current inner window. If
+ // its not the current inner, then we are in one of two different cases.
+ // Either we are in the bfcache or we are doomed window that is going away.
+ // If a window is suspended when it becomes inactive we purposely do not
+ // put it in the bfcache, so Resume should never be needed in that case.
+ // If the window is doomed then there is no point in resuming it.
+ if (!IsCurrentInnerWindow()) {
+ return;
+ }
+
+ // Resume all in-process descendants. This restores timers recursively
+ // canceled in Suspend() and ensures all in-process descendants have the
+ // correct mSuspendDepth.
+ if (aIncludeSubWindows) {
+ CallOnInProcessDescendants(&nsGlobalWindowInner::Resume, false);
+ }
+
+ if (mSuspendDepth == 0) {
+ // Ignore if the window is not suspended.
+ return;
+ }
+
+ mSuspendDepth -= 1;
+
+ if (mSuspendDepth != 0) {
+ return;
+ }
+
+ // We should not be able to resume a frozen window. It must be Thaw()'d
+ // first.
+ MOZ_ASSERT(mFreezeDepth == 0);
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) {
+ for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
+ ac->AddWindowListener(mEnabledSensors[i], this);
+ }
+ EnableGamepadUpdates();
+ EnableVRUpdates();
+
+ // Resume all of the AudioContexts for this window
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ mAudioContexts[i]->ResumeFromChrome();
+ }
+
+ if (RefPtr<MediaDevices> devices = GetExtantMediaDevices()) {
+ devices->WindowResumed();
+ }
+
+ mTimeoutManager->Resume();
+
+ ResumeIdleRequests();
+
+ // Resume all of the workers for this window. We must do this
+ // after timeouts since workers may have queued events that can trigger
+ // a setTimeout().
+ ResumeWorkersForWindow(*this);
+
+ for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
+ mSharedWorkers.ForwardRange()) {
+ pinnedWorker->Resume();
+ }
+
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::SUSPENDED);
+ }
+}
+
+bool nsGlobalWindowInner::IsSuspended() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSuspendDepth != 0;
+}
+
+void nsGlobalWindowInner::Freeze(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ Suspend(aIncludeSubWindows);
+ FreezeInternal(aIncludeSubWindows);
+}
+
+void nsGlobalWindowInner::FreezeInternal(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
+
+ HintIsLoading(false);
+
+ if (aIncludeSubWindows) {
+ CallOnInProcessChildren(&nsGlobalWindowInner::FreezeInternal,
+ aIncludeSubWindows);
+ }
+
+ mFreezeDepth += 1;
+ MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+ if (mFreezeDepth != 1) {
+ return;
+ }
+
+ FreezeWorkersForWindow(*this);
+
+ for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
+ mSharedWorkers.ForwardRange()) {
+ pinnedWorker->Freeze();
+ }
+
+ mTimeoutManager->Freeze();
+ if (mClientSource) {
+ mClientSource->Freeze();
+ }
+
+ NotifyDOMWindowFrozen(this);
+}
+
+void nsGlobalWindowInner::Thaw(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ ThawInternal(aIncludeSubWindows);
+ Resume(aIncludeSubWindows);
+}
+
+void nsGlobalWindowInner::ThawInternal(bool aIncludeSubWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
+
+ if (aIncludeSubWindows) {
+ CallOnInProcessChildren(&nsGlobalWindowInner::ThawInternal,
+ aIncludeSubWindows);
+ }
+
+ MOZ_ASSERT(mFreezeDepth != 0);
+ mFreezeDepth -= 1;
+ MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+ if (mFreezeDepth != 0) {
+ return;
+ }
+
+ if (mClientSource) {
+ mClientSource->Thaw();
+ }
+ mTimeoutManager->Thaw();
+
+ ThawWorkersForWindow(*this);
+
+ for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
+ mSharedWorkers.ForwardRange()) {
+ pinnedWorker->Thaw();
+ }
+
+ NotifyDOMWindowThawed(this);
+}
+
+bool nsGlobalWindowInner::IsFrozen() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ bool frozen = mFreezeDepth != 0;
+ MOZ_ASSERT_IF(frozen, IsSuspended());
+ return frozen;
+}
+
+void nsGlobalWindowInner::SyncStateFromParentWindow() {
+ // This method should only be called on an inner window that has been
+ // assigned to an outer window already.
+ MOZ_ASSERT(IsCurrentInnerWindow());
+ nsPIDOMWindowOuter* outer = GetOuterWindow();
+ MOZ_ASSERT(outer);
+
+ // Attempt to find our parent windows.
+ nsCOMPtr<Element> frame = outer->GetFrameElementInternal();
+ nsPIDOMWindowOuter* parentOuter =
+ frame ? frame->OwnerDoc()->GetWindow() : nullptr;
+ nsGlobalWindowInner* parentInner =
+ parentOuter
+ ? nsGlobalWindowInner::Cast(parentOuter->GetCurrentInnerWindow())
+ : nullptr;
+
+ // If our outer is in a modal state, but our parent is not in a modal
+ // state, then we must apply the suspend directly. If our parent is
+ // in a modal state then we should get the suspend automatically
+ // via the parentSuspendDepth application below.
+ if ((!parentInner || !parentInner->IsInModalState()) && IsInModalState()) {
+ Suspend();
+ }
+
+ uint32_t parentFreezeDepth = parentInner ? parentInner->mFreezeDepth : 0;
+ uint32_t parentSuspendDepth = parentInner ? parentInner->mSuspendDepth : 0;
+
+ // Since every Freeze() calls Suspend(), the suspend count must
+ // be equal or greater to the freeze count.
+ MOZ_ASSERT(parentFreezeDepth <= parentSuspendDepth);
+
+ // First apply the Freeze() calls.
+ for (uint32_t i = 0; i < parentFreezeDepth; ++i) {
+ Freeze();
+ }
+
+ // Now apply only the number of Suspend() calls to reach the target
+ // suspend count after applying the Freeze() calls.
+ for (uint32_t i = 0; i < (parentSuspendDepth - parentFreezeDepth); ++i) {
+ Suspend();
+ }
+}
+
+void nsGlobalWindowInner::UpdateBackgroundState() {
+ if (RefPtr<MediaDevices> devices = GetExtantMediaDevices()) {
+ devices->BackgroundStateChanged();
+ }
+ mTimeoutManager->UpdateBackgroundState();
+}
+
+template <typename Method, typename... Args>
+CallState nsGlobalWindowInner::CallOnInProcessDescendantsInternal(
+ BrowsingContext* aBrowsingContext, bool aChildOnly, Method aMethod,
+ Args&&... aArgs) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBrowsingContext);
+
+ CallState state = CallState::Continue;
+ for (const RefPtr<BrowsingContext>& bc : aBrowsingContext->Children()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> pWin = bc->GetDOMWindow()) {
+ auto* win = nsGlobalWindowOuter::Cast(pWin);
+ if (nsGlobalWindowInner* inner = win->GetCurrentInnerWindowInternal()) {
+ // Call the descendant method using our helper CallDescendant() template
+ // method. This allows us to handle both void returning methods and
+ // methods that return CallState explicitly. For void returning methods
+ // we assume CallState::Continue.
+ using returnType = decltype((inner->*aMethod)(aArgs...));
+ state = CallDescendant<returnType>(inner, aMethod, aArgs...);
+
+ if (state == CallState::Stop) {
+ return state;
+ }
+ }
+ }
+
+ if (!aChildOnly) {
+ state = CallOnInProcessDescendantsInternal(bc.get(), aChildOnly, aMethod,
+ aArgs...);
+ if (state == CallState::Stop) {
+ return state;
+ }
+ }
+ }
+
+ return state;
+}
+
+Maybe<ClientInfo> nsGlobalWindowInner::GetClientInfo() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDoc && mDoc->IsStaticDocument()) {
+ if (Maybe<ClientInfo> info = mDoc->GetOriginalDocument()->GetClientInfo()) {
+ return info;
+ }
+ }
+
+ Maybe<ClientInfo> clientInfo;
+ if (mClientSource) {
+ clientInfo.emplace(mClientSource->Info());
+ }
+ return clientInfo;
+}
+
+Maybe<ClientState> nsGlobalWindowInner::GetClientState() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDoc && mDoc->IsStaticDocument()) {
+ if (Maybe<ClientState> state =
+ mDoc->GetOriginalDocument()->GetClientState()) {
+ return state;
+ }
+ }
+
+ Maybe<ClientState> clientState;
+ if (mClientSource) {
+ Result<ClientState, ErrorResult> res = mClientSource->SnapshotState();
+ if (res.isOk()) {
+ clientState.emplace(res.unwrap());
+ } else {
+ res.unwrapErr().SuppressException();
+ }
+ }
+ return clientState;
+}
+
+Maybe<ServiceWorkerDescriptor> nsGlobalWindowInner::GetController() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDoc && mDoc->IsStaticDocument()) {
+ if (Maybe<ServiceWorkerDescriptor> controller =
+ mDoc->GetOriginalDocument()->GetController()) {
+ return controller;
+ }
+ }
+
+ Maybe<ServiceWorkerDescriptor> controller;
+ if (mClientSource) {
+ controller = mClientSource->GetController();
+ }
+ return controller;
+}
+
+void nsGlobalWindowInner::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ if (!mClientSource) {
+ return;
+ }
+ mClientSource->SetCsp(aCsp);
+ // Also cache the CSP within the document
+ mDoc->SetCsp(aCsp);
+
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
+ }
+}
+
+void nsGlobalWindowInner::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp) {
+ if (!mClientSource) {
+ return;
+ }
+ mClientSource->SetPreloadCsp(aPreloadCsp);
+ // Also cache the preload CSP within the document
+ mDoc->SetPreloadCsp(aPreloadCsp);
+
+ if (mWindowGlobalChild) {
+ mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
+ }
+}
+
+nsIContentSecurityPolicy* nsGlobalWindowInner::GetCsp() {
+ if (mDoc) {
+ return mDoc->GetCsp();
+ }
+
+ // If the window is partially torn down and has its document nulled out,
+ // we query the CSP we snapshot in FreeInnerObjects.
+ if (mDocumentCsp) {
+ return mDocumentCsp;
+ }
+ return nullptr;
+}
+
+RefPtr<ServiceWorker> nsGlobalWindowInner::GetOrCreateServiceWorker(
+ const ServiceWorkerDescriptor& aDescriptor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<ServiceWorker> ref;
+ ForEachGlobalTeardownObserver(
+ [&](GlobalTeardownObserver* aObserver, bool* aDoneOut) {
+ RefPtr<ServiceWorker> sw = do_QueryObject(aObserver);
+ if (!sw || !sw->Descriptor().Matches(aDescriptor)) {
+ return;
+ }
+
+ ref = std::move(sw);
+ *aDoneOut = true;
+ });
+
+ if (!ref) {
+ ref = ServiceWorker::Create(this, aDescriptor);
+ }
+
+ return ref;
+}
+
+RefPtr<mozilla::dom::ServiceWorkerRegistration>
+nsGlobalWindowInner::GetServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<ServiceWorkerRegistration> ref;
+ ForEachGlobalTeardownObserver(
+ [&](GlobalTeardownObserver* aObserver, bool* aDoneOut) {
+ RefPtr<ServiceWorkerRegistration> swr = do_QueryObject(aObserver);
+ if (!swr || !swr->MatchesDescriptor(aDescriptor)) {
+ return;
+ }
+
+ ref = std::move(swr);
+ *aDoneOut = true;
+ });
+ return ref;
+}
+
+RefPtr<ServiceWorkerRegistration>
+nsGlobalWindowInner::GetOrCreateServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<ServiceWorkerRegistration> ref =
+ GetServiceWorkerRegistration(aDescriptor);
+ if (!ref) {
+ ref = ServiceWorkerRegistration::CreateForMainThread(this, aDescriptor);
+ }
+ return ref;
+}
+
+StorageAccess nsGlobalWindowInner::GetStorageAccess() {
+ return StorageAllowedForWindow(this);
+}
+
+nsresult nsGlobalWindowInner::FireDelayedDOMEvents(bool aIncludeSubWindows) {
+ // Fires an offline status event if the offline status has changed
+ FireOfflineStatusEventIfChanged();
+
+ if (!aIncludeSubWindows) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (docShell) {
+ int32_t childCount = 0;
+ docShell->GetInProcessChildCount(&childCount);
+
+ // Take a copy of the current children so that modifications to
+ // the child list don't affect to the iteration.
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> children;
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childShell;
+ docShell->GetInProcessChildAt(i, getter_AddRefs(childShell));
+ if (childShell) {
+ children.AppendElement(childShell);
+ }
+ }
+
+ for (nsCOMPtr<nsIDocShellTreeItem> childShell : children) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow()) {
+ auto* win = nsGlobalWindowOuter::Cast(pWin);
+ win->FireDelayedDOMEvents(true);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner: Window Control Functions
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessParentInternal() {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (!outer) {
+ // No outer window available!
+ return nullptr;
+ }
+ return outer->GetInProcessParentInternal();
+}
+
+nsIPrincipal* nsGlobalWindowInner::GetTopLevelAntiTrackingPrincipal() {
+ nsPIDOMWindowOuter* outerWindow = GetOuterWindowInternal();
+ if (!outerWindow) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowOuter* topLevelOuterWindow =
+ GetBrowsingContext()->Top()->GetDOMWindow();
+ if (!topLevelOuterWindow) {
+ return nullptr;
+ }
+
+ bool stopAtOurLevel =
+ mDoc && mDoc->CookieJarSettings()->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER;
+
+ if (stopAtOurLevel && topLevelOuterWindow == outerWindow) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* topLevelInnerWindow =
+ topLevelOuterWindow->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!topLevelInnerWindow)) {
+ return nullptr;
+ }
+
+ nsIPrincipal* topLevelPrincipal =
+ nsGlobalWindowInner::Cast(topLevelInnerWindow)->GetPrincipal();
+ if (NS_WARN_IF(!topLevelPrincipal)) {
+ return nullptr;
+ }
+
+ return topLevelPrincipal;
+}
+
+nsIPrincipal* nsGlobalWindowInner::GetClientPrincipal() {
+ return mClientSource ? mClientSource->GetPrincipal() : nullptr;
+}
+
+bool nsGlobalWindowInner::IsInFullScreenTransition() {
+ if (!mIsChrome) {
+ return false;
+ }
+
+ nsGlobalWindowOuter* outerWindow = GetOuterWindowInternal();
+ if (!outerWindow) {
+ return false;
+ }
+
+ return outerWindow->mIsInFullScreenTransition;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner: Timeout Functions
+//*****************************************************************************
+
+class WindowScriptTimeoutHandler final : public ScriptTimeoutHandler {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WindowScriptTimeoutHandler,
+ ScriptTimeoutHandler)
+
+ WindowScriptTimeoutHandler(JSContext* aCx, nsIGlobalObject* aGlobal,
+ const nsAString& aExpression)
+ : ScriptTimeoutHandler(aCx, aGlobal, aExpression),
+ mInitiatingScript(ScriptLoader::GetActiveScript(aCx)) {}
+
+ MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* aExecutionReason) override;
+
+ private:
+ virtual ~WindowScriptTimeoutHandler() = default;
+
+ // Initiating script for use when evaluating mExpr on the main thread.
+ RefPtr<JS::loader::LoadedScript> mInitiatingScript;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowScriptTimeoutHandler,
+ ScriptTimeoutHandler, mInitiatingScript)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowScriptTimeoutHandler)
+NS_INTERFACE_MAP_END_INHERITING(ScriptTimeoutHandler)
+
+NS_IMPL_ADDREF_INHERITED(WindowScriptTimeoutHandler, ScriptTimeoutHandler)
+NS_IMPL_RELEASE_INHERITED(WindowScriptTimeoutHandler, ScriptTimeoutHandler)
+
+bool WindowScriptTimeoutHandler::Call(const char* aExecutionReason) {
+ // New script entry point required, due to the "Create a script" sub-step
+ // of
+ // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(mGlobal, aExecutionReason, true);
+ JS::CompileOptions options(aes.cx());
+ options.setFileAndLine(mFileName.get(), mLineNo);
+ options.setNoScriptRval(true);
+ options.setIntroductionType("domTimer");
+ JS::Rooted<JSObject*> global(aes.cx(), mGlobal->GetGlobalJSObject());
+ {
+ JSExecutionContext exec(aes.cx(), global, options);
+ nsresult rv = exec.Compile(mExpr);
+
+ JS::Rooted<JSScript*> script(aes.cx(), exec.MaybeGetScript());
+ if (script) {
+ if (mInitiatingScript) {
+ mInitiatingScript->AssociateWithScript(script);
+ }
+
+ rv = exec.ExecScript();
+ }
+
+ if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+nsGlobalWindowInner* nsGlobalWindowInner::InnerForSetTimeoutOrInterval(
+ ErrorResult& aError) {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ nsGlobalWindowInner* currentInner =
+ outer ? outer->GetCurrentInnerWindowInternal() : this;
+
+ // If forwardTo is not the window with an active document then we want the
+ // call to setTimeout/Interval to be a noop, so return null but don't set an
+ // error.
+ return HasActiveDocument() ? currentInner : nullptr;
+}
+
+int32_t nsGlobalWindowInner::SetTimeout(JSContext* aCx, Function& aFunction,
+ int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aError) {
+ return SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, false,
+ aError);
+}
+
+int32_t nsGlobalWindowInner::SetTimeout(JSContext* aCx,
+ const nsAString& aHandler,
+ int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aError) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError);
+}
+
+int32_t nsGlobalWindowInner::SetInterval(JSContext* aCx, Function& aFunction,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aError) {
+ return SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, true,
+ aError);
+}
+
+int32_t nsGlobalWindowInner::SetInterval(
+ JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */, ErrorResult& aError) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, true, aError);
+}
+
+int32_t nsGlobalWindowInner::SetTimeoutOrInterval(
+ JSContext* aCx, Function& aFunction, int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments, bool aIsInterval,
+ ErrorResult& aError) {
+ nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
+ if (!inner) {
+ return -1;
+ }
+
+ if (inner != this) {
+ RefPtr<nsGlobalWindowInner> innerRef(inner);
+ return innerRef->SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments,
+ aIsInterval, aError);
+ }
+
+ DebuggerNotificationDispatch(
+ this, aIsInterval ? DebuggerNotificationType::SetInterval
+ : DebuggerNotificationType::SetTimeout);
+
+ if (!GetContextInternal() || !HasJSGlobal()) {
+ // This window was already closed, or never properly initialized,
+ // don't let a timer be scheduled on such a window.
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ nsTArray<JS::Heap<JS::Value>> args;
+ if (!args.AppendElements(aArguments, fallible)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return 0;
+ }
+
+ RefPtr<TimeoutHandler> handler =
+ new CallbackTimeoutHandler(aCx, this, &aFunction, std::move(args));
+
+ int32_t result;
+ aError =
+ mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
+ return result;
+}
+
+int32_t nsGlobalWindowInner::SetTimeoutOrInterval(JSContext* aCx,
+ const nsAString& aHandler,
+ int32_t aTimeout,
+ bool aIsInterval,
+ ErrorResult& aError) {
+ nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
+ if (!inner) {
+ return -1;
+ }
+
+ if (inner != this) {
+ RefPtr<nsGlobalWindowInner> innerRef(inner);
+ return innerRef->SetTimeoutOrInterval(aCx, aHandler, aTimeout, aIsInterval,
+ aError);
+ }
+
+ DebuggerNotificationDispatch(
+ this, aIsInterval ? DebuggerNotificationType::SetInterval
+ : DebuggerNotificationType::SetTimeout);
+
+ if (!GetContextInternal() || !HasJSGlobal()) {
+ // This window was already closed, or never properly initialized,
+ // don't let a timer be scheduled on such a window.
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ bool allowEval = false;
+ aError = CSPEvalChecker::CheckForWindow(aCx, this, aHandler, &allowEval);
+ if (NS_WARN_IF(aError.Failed()) || !allowEval) {
+ return 0;
+ }
+
+ RefPtr<TimeoutHandler> handler =
+ new WindowScriptTimeoutHandler(aCx, this, aHandler);
+
+ int32_t result;
+ aError =
+ mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
+ return result;
+}
+
+static const char* GetTimeoutReasonString(Timeout* aTimeout) {
+ switch (aTimeout->mReason) {
+ case Timeout::Reason::eTimeoutOrInterval:
+ if (aTimeout->mIsInterval) {
+ return "setInterval handler";
+ }
+ return "setTimeout handler";
+ case Timeout::Reason::eIdleCallbackTimeout:
+ return "setIdleCallback handler (timed out)";
+ case Timeout::Reason::eAbortSignalTimeout:
+ return "AbortSignal timeout";
+ case Timeout::Reason::eDelayedWebTaskTimeout:
+ return "delayedWebTaskCallback handler (timed out)";
+ default:
+ MOZ_CRASH("Unexpected enum value");
+ return "";
+ }
+ MOZ_CRASH("Unexpected enum value");
+ return "";
+}
+
+bool nsGlobalWindowInner::RunTimeoutHandler(Timeout* aTimeout,
+ nsIScriptContext* aScx) {
+ // Hold on to the timeout in case mExpr or mFunObj releases its
+ // doc.
+ // XXXbz Our caller guarantees it'll hold on to the timeout (because
+ // we're MOZ_CAN_RUN_SCRIPT), so we can probably stop doing that...
+ RefPtr<Timeout> timeout = aTimeout;
+ Timeout* last_running_timeout = mTimeoutManager->BeginRunningTimeout(timeout);
+ timeout->mRunning = true;
+
+ // Push this timeout's popup control state, which should only be
+ // enabled the first time a timeout fires that was created while
+ // popups were enabled and with a delay less than
+ // "dom.disable_open_click_delay".
+ AutoPopupStatePusher popupStatePusher(timeout->mPopupState);
+
+ // Clear the timeout's popup state, if any, to prevent interval
+ // timeouts from repeatedly opening poups.
+ timeout->mPopupState = PopupBlocker::openAbused;
+
+ uint32_t nestingLevel = TimeoutManager::GetNestingLevel();
+ TimeoutManager::SetNestingLevel(timeout->mNestingLevel);
+
+ const char* reason = GetTimeoutReasonString(timeout);
+
+ nsCString str;
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ TimeDuration originalInterval = timeout->When() - timeout->SubmitTime();
+ str.Append(reason);
+ str.Append(" with interval ");
+ str.AppendInt(int(originalInterval.ToMilliseconds()));
+ str.Append("ms: ");
+ nsCString handlerDescription;
+ timeout->mScriptHandler->GetDescription(handlerDescription);
+ str.Append(handlerDescription);
+ }
+ AUTO_PROFILER_MARKER_TEXT("setTimeout callback", DOM,
+ MarkerOptions(MarkerStack::TakeBacktrace(
+ timeout->TakeProfilerBacktrace()),
+ MarkerInnerWindowId(mWindowID)),
+ str);
+
+ bool abortIntervalHandler;
+ {
+ RefPtr<TimeoutHandler> handler(timeout->mScriptHandler);
+
+ CallbackDebuggerNotificationGuard guard(
+ this, timeout->mIsInterval
+ ? DebuggerNotificationType::SetIntervalCallback
+ : DebuggerNotificationType::SetTimeoutCallback);
+ abortIntervalHandler = !handler->Call(reason);
+ }
+
+ // If we received an uncatchable exception, do not schedule the timeout again.
+ // This allows the slow script dialog to break easy DoS attacks like
+ // setInterval(function() { while(1); }, 100);
+ if (abortIntervalHandler) {
+ // If it wasn't an interval timer to begin with, this does nothing. If it
+ // was, we'll treat it as a timeout that we just ran and discard it when
+ // we return.
+ timeout->mIsInterval = false;
+ }
+
+ // We ignore any failures from calling EvaluateString() on the context or
+ // Call() on a Function here since we're in a loop
+ // where we're likely to be running timeouts whose OS timers
+ // didn't fire in time and we don't want to not fire those timers
+ // now just because execution of one timer failed. We can't
+ // propagate the error to anyone who cares about it from this
+ // point anyway, and the script context should have already reported
+ // the script error in the usual way - so we just drop it.
+
+ TimeoutManager::SetNestingLevel(nestingLevel);
+
+ mTimeoutManager->EndRunningTimeout(last_running_timeout);
+ timeout->mRunning = false;
+
+ return timeout->mCleared;
+}
+
+//*****************************************************************************
+// nsGlobalWindowInner: Helper Functions
+//*****************************************************************************
+
+already_AddRefed<nsIDocShellTreeOwner> nsGlobalWindowInner::GetTreeOwner() {
+ FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsGlobalWindowInner::GetWebBrowserChrome() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
+ return browserChrome.forget();
+}
+
+nsIScrollableFrame* nsGlobalWindowInner::GetScrollFrame() {
+ FORWARD_TO_OUTER(GetScrollFrame, (), nullptr);
+}
+
+bool nsGlobalWindowInner::IsPrivateBrowsing() {
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+void nsGlobalWindowInner::FlushPendingNotifications(FlushType aType) {
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(aType);
+ }
+}
+
+void nsGlobalWindowInner::EnableDeviceSensor(uint32_t aType) {
+ bool alreadyEnabled = false;
+ for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) {
+ if (mEnabledSensors[i] == aType) {
+ alreadyEnabled = true;
+ break;
+ }
+ }
+
+ mEnabledSensors.AppendElement(aType);
+
+ if (alreadyEnabled) {
+ return;
+ }
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) {
+ ac->AddWindowListener(aType, this);
+ }
+}
+
+void nsGlobalWindowInner::DisableDeviceSensor(uint32_t aType) {
+ int32_t doomedElement = -1;
+ int32_t listenerCount = 0;
+ for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) {
+ if (mEnabledSensors[i] == aType) {
+ doomedElement = i;
+ listenerCount++;
+ }
+ }
+
+ if (doomedElement == -1) {
+ return;
+ }
+
+ mEnabledSensors.RemoveElementAt(doomedElement);
+
+ if (listenerCount > 1) {
+ return;
+ }
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) {
+ ac->RemoveWindowListener(aType, this);
+ }
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void nsGlobalWindowInner::EnableOrientationChangeListener() {
+ if (!ShouldResistFingerprinting()) {
+ mHasOrientationChangeListeners = true;
+ mOrientationAngle = Orientation(CallerType::System);
+ }
+}
+
+void nsGlobalWindowInner::DisableOrientationChangeListener() {
+ mHasOrientationChangeListeners = false;
+}
+#endif
+
+void nsGlobalWindowInner::SetHasGamepadEventListener(
+ bool aHasGamepad /* = true*/) {
+ mHasGamepad = aHasGamepad;
+ if (aHasGamepad) {
+ EnableGamepadUpdates();
+ }
+}
+
+void nsGlobalWindowInner::NotifyDetectXRRuntimesCompleted() {
+ if (!mXRRuntimeDetectionInFlight) {
+ return;
+ }
+ mXRRuntimeDetectionInFlight = false;
+ if (mXRPermissionRequestInFlight) {
+ return;
+ }
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ bool supported = vm->RuntimeSupportsVR();
+ if (!supported) {
+ // A VR runtime was not installed; we can suppress
+ // the permission prompt
+ OnXRPermissionRequestCancel();
+ return;
+ }
+ // A VR runtime was found. Display a permission prompt before
+ // allowing it to be accessed.
+ // Connect to the VRManager in order to receive the runtime
+ // detection results.
+ mXRPermissionRequestInFlight = true;
+ RefPtr<XRPermissionRequest> request =
+ new XRPermissionRequest(this, WindowID());
+ Unused << NS_WARN_IF(NS_FAILED(request->Start()));
+}
+
+void nsGlobalWindowInner::RequestXRPermission() {
+ if (IsDying()) {
+ // Do not proceed if the window is dying, as that will result
+ // in leaks of objects that get re-allocated after FreeInnerObjects
+ // has been called, including mVREventObserver.
+ return;
+ }
+ if (mXRPermissionGranted) {
+ // Don't prompt redundantly once permission to
+ // access XR devices has been granted.
+ OnXRPermissionRequestAllow();
+ return;
+ }
+ if (mXRRuntimeDetectionInFlight || mXRPermissionRequestInFlight) {
+ // Don't allow multiple simultaneous permissions requests;
+ return;
+ }
+ // Before displaying a permission prompt, detect
+ // if there is any VR runtime installed.
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ mXRRuntimeDetectionInFlight = true;
+ EnableVRUpdates();
+ vm->DetectRuntimes();
+}
+
+void nsGlobalWindowInner::OnXRPermissionRequestAllow() {
+ mXRPermissionRequestInFlight = false;
+ if (IsDying()) {
+ // The window may have started dying while the permission request
+ // is in flight.
+ // Do not proceed if the window is dying, as that will result
+ // in leaks of objects that get re-allocated after FreeInnerObjects
+ // has been called, including mNavigator.
+ return;
+ }
+ mXRPermissionGranted = true;
+
+ NotifyHasXRSession();
+
+ dom::Navigator* nav = Navigator();
+ MOZ_ASSERT(nav != nullptr);
+ nav->OnXRPermissionRequestAllow();
+}
+
+void nsGlobalWindowInner::OnXRPermissionRequestCancel() {
+ mXRPermissionRequestInFlight = false;
+ if (IsDying()) {
+ // The window may have started dying while the permission request
+ // is in flight.
+ // Do not proceed if the window is dying, as that will result
+ // in leaks of objects that get re-allocated after FreeInnerObjects
+ // has been called, including mNavigator.
+ return;
+ }
+ dom::Navigator* nav = Navigator();
+ MOZ_ASSERT(nav != nullptr);
+ nav->OnXRPermissionRequestCancel();
+}
+
+void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
+ if (aType == nsGkAtoms::onvrdisplayactivate ||
+ aType == nsGkAtoms::onvrdisplayconnect ||
+ aType == nsGkAtoms::onvrdisplaydeactivate ||
+ aType == nsGkAtoms::onvrdisplaydisconnect ||
+ aType == nsGkAtoms::onvrdisplaypresentchange) {
+ RequestXRPermission();
+ }
+
+ if (aType == nsGkAtoms::onvrdisplayactivate) {
+ mHasVRDisplayActivateEvents = true;
+ }
+
+ if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
+ if (++mUnloadOrBeforeUnloadListenerCount == 1) {
+ mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
+ }
+ }
+
+ if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
+ if (!mozilla::SessionHistoryInParent() ||
+ !StaticPrefs::
+ docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
+ if (++mUnloadOrBeforeUnloadListenerCount == 1) {
+ mWindowGlobalChild->BlockBFCacheFor(
+ BFCacheStatus::BEFOREUNLOAD_LISTENER);
+ }
+ }
+ if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
+ mWindowGlobalChild->BeforeUnloadAdded();
+ MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() > 0);
+ }
+ }
+
+ // We need to initialize localStorage in order to receive notifications.
+ if (aType == nsGkAtoms::onstorage) {
+ ErrorResult rv;
+ GetLocalStorage(rv);
+ rv.SuppressException();
+
+ if (NextGenLocalStorageEnabled() && mLocalStorage &&
+ mLocalStorage->Type() == Storage::eLocalStorage) {
+ auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+ Unused << NS_WARN_IF(NS_FAILED(object->EnsureObserver()));
+ }
+ }
+}
+
+void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) {
+ if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
+ MOZ_ASSERT(mUnloadOrBeforeUnloadListenerCount > 0);
+ if (--mUnloadOrBeforeUnloadListenerCount == 0) {
+ mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
+ }
+ }
+
+ if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
+ if (!mozilla::SessionHistoryInParent() ||
+ !StaticPrefs::
+ docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
+ if (--mUnloadOrBeforeUnloadListenerCount == 0) {
+ mWindowGlobalChild->UnblockBFCacheFor(
+ BFCacheStatus::BEFOREUNLOAD_LISTENER);
+ }
+ }
+ if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
+ mWindowGlobalChild->BeforeUnloadRemoved();
+ MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() >= 0);
+ }
+ }
+
+ if (aType == nsGkAtoms::onstorage) {
+ if (NextGenLocalStorageEnabled() && mLocalStorage &&
+ mLocalStorage->Type() == Storage::eLocalStorage &&
+ // The remove event is fired even if this isn't the last listener, so
+ // only remove if there are no other listeners left.
+ mListenerManager &&
+ !mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
+ auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+ object->DropObserver();
+ }
+ }
+}
+
+void nsGlobalWindowInner::NotifyHasXRSession() {
+ if (IsDying()) {
+ // Do not proceed if the window is dying, as that will result
+ // in leaks of objects that get re-allocated after FreeInnerObjects
+ // has been called, including mVREventObserver.
+ return;
+ }
+ if (mWindowGlobalChild && !mHasXRSession) {
+ mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::HAS_USED_VR);
+ }
+ mHasXRSession = true;
+ EnableVRUpdates();
+}
+
+bool nsGlobalWindowInner::HasUsedVR() const {
+ // Returns true only if content has enumerated and activated
+ // XR devices. Detection of XR runtimes without activation
+ // will not cause true to be returned.
+ return mHasXRSession;
+}
+
+bool nsGlobalWindowInner::IsVRContentDetected() const {
+ // Returns true only if the content will respond to
+ // the VRDisplayActivate event.
+ return mHasVRDisplayActivateEvents;
+}
+
+bool nsGlobalWindowInner::IsVRContentPresenting() const {
+ for (const auto& display : mVRDisplays) {
+ if (display->IsAnyPresenting(gfx::kVRGroupAll)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsGlobalWindowInner::AddSizeOfIncludingThis(
+ nsWindowSizes& aWindowSizes) const {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ aWindowSizes.mState.mMallocSizeOf(this);
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ nsIGlobalObject::ShallowSizeOfExcludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+
+ EventListenerManager* elm = GetExistingListenerManager();
+ if (elm) {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ elm->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+ if (mDoc) {
+ // Multiple global windows can share a document. So only measure the
+ // document if it (a) doesn't have a global window, or (b) it's the
+ // primary document for the window.
+ if (!mDoc->GetInnerWindow() || mDoc->GetInnerWindow() == this) {
+ mDoc->DocAddSizeOfIncludingThis(aWindowSizes);
+ }
+ }
+
+ if (mNavigator) {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ mNavigator->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
+ }
+
+ ForEachGlobalTeardownObserver([&](GlobalTeardownObserver* et,
+ bool* aDoneOut) {
+ if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) {
+ aWindowSizes.mDOMSizes.mDOMEventTargetsSize +=
+ iSizeOf->SizeOfEventTargetIncludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+ }
+ if (nsCOMPtr<DOMEventTargetHelper> helper = do_QueryObject(et)) {
+ if (EventListenerManager* elm = helper->GetExistingListenerManager()) {
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+ }
+ ++aWindowSizes.mDOMEventTargetsCount;
+ });
+
+ if (mPerformance) {
+ aWindowSizes.mDOMSizes.mDOMPerformanceUserEntries =
+ mPerformance->SizeOfUserEntries(aWindowSizes.mState.mMallocSizeOf);
+ aWindowSizes.mDOMSizes.mDOMPerformanceResourceEntries =
+ mPerformance->SizeOfResourceEntries(aWindowSizes.mState.mMallocSizeOf);
+ aWindowSizes.mDOMSizes.mDOMPerformanceEventEntries =
+ mPerformance->SizeOfEventEntries(aWindowSizes.mState.mMallocSizeOf);
+ }
+}
+
+void nsGlobalWindowInner::RegisterDataDocumentForMemoryReporting(
+ Document* aDocument) {
+ aDocument->SetAddedToMemoryReportAsDataDocument();
+ mDataDocumentsForMemoryReporting.AppendElement(
+ do_GetWeakReference(aDocument));
+}
+
+void nsGlobalWindowInner::UnregisterDataDocumentForMemoryReporting(
+ Document* aDocument) {
+ nsWeakPtr doc = do_GetWeakReference(aDocument);
+ MOZ_ASSERT(mDataDocumentsForMemoryReporting.Contains(doc));
+ mDataDocumentsForMemoryReporting.RemoveElement(doc);
+}
+
+void nsGlobalWindowInner::CollectDOMSizesForDataDocuments(
+ nsWindowSizes& aSize) const {
+ for (const nsWeakPtr& ptr : mDataDocumentsForMemoryReporting) {
+ if (nsCOMPtr<Document> doc = do_QueryReferent(ptr)) {
+ doc->DocAddSizeOfIncludingThis(aSize);
+ }
+ }
+}
+
+void nsGlobalWindowInner::AddGamepad(GamepadHandle aHandle, Gamepad* aGamepad) {
+ // Create the index we will present to content based on which indices are
+ // already taken, as required by the spec.
+ // https://w3c.github.io/gamepad/gamepad.html#widl-Gamepad-index
+ int index = 0;
+ while (mGamepadIndexSet.Contains(index)) {
+ ++index;
+ }
+ mGamepadIndexSet.Put(index);
+ aGamepad->SetIndex(index);
+ mGamepads.InsertOrUpdate(aHandle, RefPtr{aGamepad});
+}
+
+void nsGlobalWindowInner::RemoveGamepad(GamepadHandle aHandle) {
+ RefPtr<Gamepad> gamepad;
+ if (!mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
+ return;
+ }
+ // Free up the index we were using so it can be reused
+ mGamepadIndexSet.Remove(gamepad->Index());
+ mGamepads.Remove(aHandle);
+}
+
+void nsGlobalWindowInner::GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads) {
+ aGamepads.Clear();
+
+ // navigator.getGamepads() always returns an empty array when
+ // privacy.resistFingerprinting is true.
+ if (ShouldResistFingerprinting()) {
+ return;
+ }
+
+ // mGamepads.Count() may not be sufficient, but it's not harmful.
+ aGamepads.SetCapacity(mGamepads.Count());
+ for (const auto& entry : mGamepads) {
+ Gamepad* gamepad = entry.GetWeak();
+ aGamepads.EnsureLengthAtLeast(gamepad->Index() + 1);
+ aGamepads[gamepad->Index()] = gamepad;
+ }
+}
+
+already_AddRefed<Gamepad> nsGlobalWindowInner::GetGamepad(
+ GamepadHandle aHandle) {
+ RefPtr<Gamepad> gamepad;
+
+ if (mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
+ return gamepad.forget();
+ }
+
+ return nullptr;
+}
+
+void nsGlobalWindowInner::SetHasSeenGamepadInput(bool aHasSeen) {
+ mHasSeenGamepadInput = aHasSeen;
+}
+
+bool nsGlobalWindowInner::HasSeenGamepadInput() { return mHasSeenGamepadInput; }
+
+void nsGlobalWindowInner::SyncGamepadState() {
+ if (mHasSeenGamepadInput) {
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ for (const auto& entry : mGamepads) {
+ gamepadManager->SyncGamepadState(entry.GetKey(), this, entry.GetWeak());
+ }
+ }
+}
+
+void nsGlobalWindowInner::StopGamepadHaptics() {
+ if (mHasSeenGamepadInput) {
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ gamepadManager->StopHaptics();
+ }
+}
+
+bool nsGlobalWindowInner::UpdateVRDisplays(
+ nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices) {
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+ aDevices = mVRDisplays.Clone();
+ return true;
+}
+
+void nsGlobalWindowInner::NotifyActiveVRDisplaysChanged() {
+ if (mNavigator) {
+ mNavigator->NotifyActiveVRDisplaysChanged();
+ }
+}
+
+void nsGlobalWindowInner::NotifyPresentationGenerationChanged(
+ uint32_t aDisplayID) {
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID) {
+ display->OnPresentationGenerationChanged();
+ }
+ }
+}
+
+void nsGlobalWindowInner::DispatchVRDisplayActivate(
+ uint32_t aDisplayID, mozilla::dom::VRDisplayEventReason aReason) {
+ // Ensure that our list of displays is up to date
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+
+ // Search for the display identified with aDisplayID and fire the
+ // event if found.
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID) {
+ if (aReason != VRDisplayEventReason::Navigation &&
+ display->IsAnyPresenting(gfx::kVRGroupContent)) {
+ // We only want to trigger this event if nobody is presenting to the
+ // display already or when a page is loaded by navigating away
+ // from a page with an active VR Presentation.
+ continue;
+ }
+
+ VRDisplayEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mDisplay = display;
+ init.mReason.Construct(aReason);
+
+ RefPtr<VRDisplayEvent> event =
+ VRDisplayEvent::Constructor(this, u"vrdisplayactivate"_ns, init);
+ // vrdisplayactivate is a trusted event, allowing VRDisplay.requestPresent
+ // to be used in response to link traversal, user request (chrome UX), and
+ // HMD mounting detection sensors.
+ event->SetTrusted(true);
+ // VRDisplay.requestPresent normally requires a user gesture; however, an
+ // exception is made to allow it to be called in response to
+ // vrdisplayactivate during VR link traversal.
+ display->StartHandlingVRNavigationEvent();
+ DispatchEvent(*event);
+ display->StopHandlingVRNavigationEvent();
+ // Once we dispatch the event, we must not access any members as an event
+ // listener can do anything, including closing windows.
+ return;
+ }
+ }
+}
+
+void nsGlobalWindowInner::DispatchVRDisplayDeactivate(
+ uint32_t aDisplayID, mozilla::dom::VRDisplayEventReason aReason) {
+ // Ensure that our list of displays is up to date
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+
+ // Search for the display identified with aDisplayID and fire the
+ // event if found.
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID && display->IsPresenting()) {
+ // We only want to trigger this event to content that is presenting to
+ // the display already.
+
+ VRDisplayEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mDisplay = display;
+ init.mReason.Construct(aReason);
+
+ RefPtr<VRDisplayEvent> event =
+ VRDisplayEvent::Constructor(this, u"vrdisplaydeactivate"_ns, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ // Once we dispatch the event, we must not access any members as an event
+ // listener can do anything, including closing windows.
+ return;
+ }
+ }
+}
+
+void nsGlobalWindowInner::DispatchVRDisplayConnect(uint32_t aDisplayID) {
+ // Ensure that our list of displays is up to date
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+
+ // Search for the display identified with aDisplayID and fire the
+ // event if found.
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID) {
+ // Fire event even if not presenting to the display.
+ VRDisplayEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mDisplay = display;
+ // VRDisplayEvent.reason is not set for vrdisplayconnect
+
+ RefPtr<VRDisplayEvent> event =
+ VRDisplayEvent::Constructor(this, u"vrdisplayconnect"_ns, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ // Once we dispatch the event, we must not access any members as an event
+ // listener can do anything, including closing windows.
+ return;
+ }
+ }
+}
+
+void nsGlobalWindowInner::DispatchVRDisplayDisconnect(uint32_t aDisplayID) {
+ // Ensure that our list of displays is up to date
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+
+ // Search for the display identified with aDisplayID and fire the
+ // event if found.
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID) {
+ // Fire event even if not presenting to the display.
+ VRDisplayEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mDisplay = display;
+ // VRDisplayEvent.reason is not set for vrdisplaydisconnect
+
+ RefPtr<VRDisplayEvent> event =
+ VRDisplayEvent::Constructor(this, u"vrdisplaydisconnect"_ns, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ // Once we dispatch the event, we must not access any members as an event
+ // listener can do anything, including closing windows.
+ return;
+ }
+ }
+}
+
+void nsGlobalWindowInner::DispatchVRDisplayPresentChange(uint32_t aDisplayID) {
+ // Ensure that our list of displays is up to date
+ VRDisplay::UpdateVRDisplays(mVRDisplays, this);
+
+ // Search for the display identified with aDisplayID and fire the
+ // event if found.
+ for (const auto& display : mVRDisplays) {
+ if (display->DisplayId() == aDisplayID) {
+ // Fire event even if not presenting to the display.
+ VRDisplayEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mDisplay = display;
+ // VRDisplayEvent.reason is not set for vrdisplaypresentchange
+ RefPtr<VRDisplayEvent> event =
+ VRDisplayEvent::Constructor(this, u"vrdisplaypresentchange"_ns, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ // Once we dispatch the event, we must not access any members as an event
+ // listener can do anything, including closing windows.
+ return;
+ }
+ }
+}
+
+enum WindowState {
+ // These constants need to match the constants in Window.webidl
+ STATE_MAXIMIZED = 1,
+ STATE_MINIMIZED = 2,
+ STATE_NORMAL = 3,
+ STATE_FULLSCREEN = 4
+};
+
+uint16_t nsGlobalWindowInner::WindowState() {
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ int32_t mode = widget ? widget->SizeMode() : 0;
+
+ switch (mode) {
+ case nsSizeMode_Minimized:
+ return STATE_MINIMIZED;
+ case nsSizeMode_Maximized:
+ return STATE_MAXIMIZED;
+ case nsSizeMode_Fullscreen:
+ return STATE_FULLSCREEN;
+ case nsSizeMode_Normal:
+ return STATE_NORMAL;
+ default:
+ NS_WARNING("Illegal window state for this chrome window");
+ break;
+ }
+
+ return STATE_NORMAL;
+}
+
+bool nsGlobalWindowInner::IsFullyOccluded() {
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+ return widget && widget->IsFullyOccluded();
+}
+
+void nsGlobalWindowInner::Maximize() {
+ if (nsCOMPtr<nsIWidget> widget = GetMainWidget()) {
+ widget->SetSizeMode(nsSizeMode_Maximized);
+ }
+}
+
+void nsGlobalWindowInner::Minimize() {
+ if (nsCOMPtr<nsIWidget> widget = GetMainWidget()) {
+ widget->SetSizeMode(nsSizeMode_Minimized);
+ }
+}
+
+void nsGlobalWindowInner::Restore() {
+ if (nsCOMPtr<nsIWidget> widget = GetMainWidget()) {
+ widget->SetSizeMode(nsSizeMode_Normal);
+ }
+}
+
+void nsGlobalWindowInner::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+ if (nsCOMPtr<nsIWidget> widget = GetMainWidget()) {
+ return widget->GetWorkspaceID(workspaceID);
+ }
+}
+
+void nsGlobalWindowInner::MoveToWorkspace(const nsAString& workspaceID) {
+ if (nsCOMPtr<nsIWidget> widget = GetMainWidget()) {
+ widget->MoveToWorkspace(workspaceID);
+ }
+}
+
+void nsGlobalWindowInner::GetAttention(ErrorResult& aResult) {
+ return GetAttentionWithCycleCount(-1, aResult);
+}
+
+void nsGlobalWindowInner::GetAttentionWithCycleCount(int32_t aCycleCount,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ if (widget) {
+ aError = widget->GetAttention(aCycleCount);
+ }
+}
+
+already_AddRefed<Promise> nsGlobalWindowInner::PromiseDocumentFlushed(
+ PromiseDocumentFlushedCallback& aCallback, ErrorResult& aError) {
+ MOZ_RELEASE_ASSERT(IsChromeWindow());
+
+ if (!IsCurrentInnerWindow()) {
+ aError.ThrowInvalidStateError("Not the current inner window");
+ return nullptr;
+ }
+ if (!mDoc) {
+ aError.ThrowInvalidStateError("No document");
+ return nullptr;
+ }
+
+ if (mIteratingDocumentFlushedResolvers) {
+ aError.ThrowInvalidStateError("Already iterating through resolvers");
+ return nullptr;
+ }
+
+ PresShell* presShell = mDoc->GetPresShell();
+ if (!presShell) {
+ aError.ThrowInvalidStateError("No pres shell");
+ return nullptr;
+ }
+
+ // We need to associate the lifetime of the Promise to the lifetime
+ // of the caller's global. That way, if the window we're observing
+ // refresh driver ticks on goes away before our observer is fired,
+ // we can still resolve the Promise.
+ nsIGlobalObject* global = GetIncumbentGlobal();
+ if (!global) {
+ aError.ThrowInvalidStateError("No incumbent global");
+ return nullptr;
+ }
+
+ RefPtr<Promise> resultPromise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
+ new PromiseDocumentFlushedResolver(resultPromise, aCallback));
+
+ if (!presShell->NeedStyleFlush() && !presShell->NeedLayoutFlush()) {
+ flushResolver->Call();
+ return resultPromise.forget();
+ }
+
+ if (!TryToObserveRefresh()) {
+ aError.ThrowInvalidStateError("Couldn't observe refresh");
+ return nullptr;
+ }
+
+ mDocumentFlushedResolvers.AppendElement(std::move(flushResolver));
+ return resultPromise.forget();
+}
+
+bool nsGlobalWindowInner::TryToObserveRefresh() {
+ if (mObservingRefresh) {
+ return true;
+ }
+
+ if (!mDoc) {
+ return false;
+ }
+
+ nsPresContext* pc = mDoc->GetPresContext();
+ if (!pc) {
+ return false;
+ }
+
+ mObservingRefresh = true;
+ auto observer = MakeRefPtr<ManagedPostRefreshObserver>(
+ pc, [win = RefPtr{this}](bool aWasCanceled) {
+ if (win->MaybeCallDocumentFlushedResolvers(
+ /* aUntilExhaustion = */ aWasCanceled)) {
+ return ManagedPostRefreshObserver::Unregister::No;
+ }
+ win->mObservingRefresh = false;
+ return ManagedPostRefreshObserver::Unregister::Yes;
+ });
+ pc->RegisterManagedPostRefreshObserver(observer.get());
+ return mObservingRefresh;
+}
+
+void nsGlobalWindowInner::CallDocumentFlushedResolvers(bool aUntilExhaustion) {
+ while (true) {
+ {
+ // To coalesce MicroTask checkpoints inside callback call, enclose the
+ // inner loop with nsAutoMicroTask, and perform a MicroTask checkpoint
+ // after the loop.
+ nsAutoMicroTask mt;
+
+ mIteratingDocumentFlushedResolvers = true;
+
+ auto resolvers = std::move(mDocumentFlushedResolvers);
+ for (const auto& resolver : resolvers) {
+ resolver->Call();
+ }
+
+ mIteratingDocumentFlushedResolvers = false;
+ }
+
+ // Leaving nsAutoMicroTask above will perform MicroTask checkpoint, and
+ // Promise callbacks there may create mDocumentFlushedResolvers items.
+
+ // If there's no new resolvers, or we're not exhausting the queue, there's
+ // nothing to do (we'll keep observing if there's any new observer).
+ //
+ // Otherwise, keep looping to call all promises. This case can happen while
+ // destroying the window. This violates the constraint that the
+ // promiseDocumentFlushed callback only ever run when no flush is needed,
+ // but it's necessary to resolve the Promise returned by that.
+ if (!aUntilExhaustion || mDocumentFlushedResolvers.IsEmpty()) {
+ break;
+ }
+ }
+}
+
+bool nsGlobalWindowInner::MaybeCallDocumentFlushedResolvers(
+ bool aUntilExhaustion) {
+ MOZ_ASSERT(mDoc);
+
+ PresShell* presShell = mDoc->GetPresShell();
+ if (!presShell || aUntilExhaustion) {
+ CallDocumentFlushedResolvers(/* aUntilExhaustion = */ true);
+ return false;
+ }
+
+ if (presShell->NeedStyleFlush() || presShell->NeedLayoutFlush()) {
+ // By the time our observer fired, something has already invalidated
+ // style or layout - or perhaps we're still in the middle of a flush that
+ // was interrupted. In either case, we'll wait until the next refresh driver
+ // tick instead and try again.
+ return true;
+ }
+
+ CallDocumentFlushedResolvers(/* aUntilExhaustion = */ false);
+ return !mDocumentFlushedResolvers.IsEmpty();
+}
+
+already_AddRefed<nsWindowRoot> nsGlobalWindowInner::GetWindowRoot(
+ mozilla::ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr);
+}
+
+void nsGlobalWindowInner::SetCursor(const nsACString& aCursor,
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetCursorOuter, (aCursor, aError), aError, );
+}
+
+NS_IMETHODIMP
+nsGlobalWindowInner::GetBrowserDOMWindow(nsIBrowserDOMWindow** aBrowserWindow) {
+ MOZ_RELEASE_ASSERT(IsChromeWindow());
+
+ ErrorResult rv;
+ NS_IF_ADDREF(*aBrowserWindow = GetBrowserDOMWindow(rv));
+ return rv.StealNSResult();
+}
+
+nsIBrowserDOMWindow* nsGlobalWindowInner::GetBrowserDOMWindow(
+ ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr);
+}
+
+void nsGlobalWindowInner::SetBrowserDOMWindow(
+ nsIBrowserDOMWindow* aBrowserWindow, ErrorResult& aError) {
+ FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow),
+ aError, );
+}
+
+void nsGlobalWindowInner::NotifyDefaultButtonLoaded(Element& aDefaultButton,
+ ErrorResult& aError) {
+ // Don't snap to a disabled button.
+ nsCOMPtr<nsIDOMXULControlElement> xulControl = aDefaultButton.AsXULControl();
+ if (!xulControl) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ bool disabled;
+ aError = xulControl->GetDisabled(&disabled);
+ if (aError.Failed() || disabled) {
+ return;
+ }
+
+ // Get the button rect in screen coordinates.
+ nsIFrame* frame = aDefaultButton.GetPrimaryFrame();
+ if (!frame) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ LayoutDeviceIntRect buttonRect = LayoutDeviceIntRect::FromAppUnitsToNearest(
+ frame->GetScreenRectInAppUnits(),
+ frame->PresContext()->AppUnitsPerDevPixel());
+
+ // Get the widget rect in screen coordinates.
+ nsIWidget* widget = GetNearestWidget();
+ if (!widget) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ LayoutDeviceIntRect widgetRect = widget->GetScreenBounds();
+
+ // Convert the buttonRect coordinates from screen to the widget.
+ buttonRect -= widgetRect.TopLeft();
+ nsresult rv = widget->OnDefaultButtonLoaded(buttonRect);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ aError.Throw(rv);
+ }
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowInner::MessageManager() {
+ MOZ_ASSERT(IsChromeWindow());
+ if (!mChromeFields.mMessageManager) {
+ RefPtr<ChromeMessageBroadcaster> globalMM =
+ nsFrameMessageManager::GetGlobalMessageManager();
+ mChromeFields.mMessageManager = new ChromeMessageBroadcaster(globalMM);
+ }
+ return mChromeFields.mMessageManager;
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowInner::GetGroupMessageManager(
+ const nsAString& aGroup) {
+ MOZ_ASSERT(IsChromeWindow());
+
+ return mChromeFields.mGroupMessageManagers
+ .LookupOrInsertWith(
+ aGroup,
+ [&] {
+ return MakeAndAddRef<ChromeMessageBroadcaster>(MessageManager());
+ })
+ .get();
+}
+
+void nsGlobalWindowInner::InitWasOffline() { mWasOffline = NS_IsOffline(); }
+
+int16_t nsGlobalWindowInner::Orientation(CallerType aCallerType) {
+ // GetOrientationAngle() returns 0, 90, 180 or 270.
+ // window.orientation returns -90, 0, 90 or 180.
+ if (nsIGlobalObject::ShouldResistFingerprinting(
+ aCallerType, RFPTarget::ScreenOrientation)) {
+ return 0;
+ }
+ nsScreen* s = GetScreen(IgnoreErrors());
+ if (!s) {
+ return 0;
+ }
+ int16_t angle = AssertedCast<int16_t>(s->GetOrientationAngle());
+ return angle <= 180 ? angle : angle - 360;
+}
+
+already_AddRefed<Console> nsGlobalWindowInner::GetConsole(JSContext* aCx,
+ ErrorResult& aRv) {
+ if (!mConsole) {
+ mConsole = Console::Create(aCx, this, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<Console> console = mConsole;
+ return console.forget();
+}
+
+bool nsGlobalWindowInner::IsSecureContext() const {
+ JS::Realm* realm = js::GetNonCCWObjectRealm(GetWrapperPreserveColor());
+ return JS::GetIsSecureContext(realm);
+}
+
+External* nsGlobalWindowInner::External() {
+ if (!mExternal) {
+ mExternal = new dom::External(ToSupports(this));
+ }
+
+ return mExternal;
+}
+
+void nsGlobalWindowInner::GetSidebar(OwningExternalOrWindowProxy& aResult) {
+ // First check for a named frame named "sidebar"
+ RefPtr<BrowsingContext> domWindow = GetChildWindow(u"sidebar"_ns);
+ if (domWindow) {
+ aResult.SetAsWindowProxy() = std::move(domWindow);
+ return;
+ }
+
+ RefPtr<dom::External> external = External();
+ if (external) {
+ aResult.SetAsExternal() = external;
+ }
+}
+
+void nsGlobalWindowInner::ClearDocumentDependentSlots(JSContext* aCx) {
+ // If JSAPI OOMs here, there is basically nothing we can do to recover safely.
+ if (!Window_Binding::ClearCachedDocumentValue(aCx, this) ||
+ !Window_Binding::ClearCachedPerformanceValue(aCx, this)) {
+ MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
+ }
+}
+
+/* static */
+JSObject* nsGlobalWindowInner::CreateNamedPropertiesObject(
+ JSContext* aCx, JS::Handle<JSObject*> aProto) {
+ return WindowNamedPropertiesHandler::Create(aCx, aProto);
+}
+
+void nsGlobalWindowInner::RedefineProperty(JSContext* aCx,
+ const char* aPropName,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aError) {
+ JS::Rooted<JSObject*> thisObj(aCx, GetWrapperPreserveColor());
+ if (!thisObj) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (!JS_WrapObject(aCx, &thisObj) ||
+ !JS_DefineProperty(aCx, thisObj, aPropName, aValue, JSPROP_ENUMERATE)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+template <typename T>
+void nsGlobalWindowInner::GetReplaceableWindowCoord(
+ JSContext* aCx, nsGlobalWindowInner::WindowCoordGetter<T> aGetter,
+ JS::MutableHandle<JS::Value> aRetval, CallerType aCallerType,
+ ErrorResult& aError) {
+ T coord = (this->*aGetter)(aCallerType, aError);
+ if (!aError.Failed() && !ToJSValue(aCx, coord, aRetval)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+template <typename T>
+void nsGlobalWindowInner::SetReplaceableWindowCoord(
+ JSContext* aCx, nsGlobalWindowInner::WindowCoordSetter<T> aSetter,
+ JS::Handle<JS::Value> aValue, const char* aPropName, CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * just treat this the way we would an IDL replaceable property.
+ */
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ if (StaticPrefs::dom_window_position_size_properties_replaceable_enabled() ||
+ !outer || !outer->CanMoveResizeWindows(aCallerType) ||
+ mBrowsingContext->IsSubframe()) {
+ MOZ_DIAGNOSTIC_ASSERT(aCallerType != CallerType::System,
+ "Setting this property in chrome code does nothing "
+ "anymore, use resizeTo/moveTo as needed");
+ RedefineProperty(aCx, aPropName, aValue, aError);
+ return;
+ }
+
+ T value;
+ if (!ValueToPrimitive<T, eDefault>(aCx, aValue, aPropName, &value)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ (this->*aSetter)(value, aCallerType, aError);
+}
+
+void nsGlobalWindowInner::FireOnNewGlobalObject() {
+ // AutoEntryScript required to invoke debugger hook, which is a
+ // Gecko-specific concept at present.
+ AutoEntryScript aes(this, "nsGlobalWindowInner report new global");
+ JS::Rooted<JSObject*> global(aes.cx(), GetWrapper());
+ JS_FireOnNewGlobalObject(aes.cx(), global);
+}
+
+#if defined(_WINDOWS_) && !defined(MOZ_WRAPPED_WINDOWS_H)
+# pragma message( \
+ "wrapper failure reason: " MOZ_WINDOWS_WRAPPER_DISABLED_REASON)
+# error "Never include unwrapped windows.h in this file!"
+#endif
+
+already_AddRefed<Promise> nsGlobalWindowInner::CreateImageBitmap(
+ const ImageBitmapSource& aImage, const ImageBitmapOptions& aOptions,
+ ErrorResult& aRv) {
+ return ImageBitmap::Create(this, aImage, Nothing(), aOptions, aRv);
+}
+
+already_AddRefed<Promise> nsGlobalWindowInner::CreateImageBitmap(
+ const ImageBitmapSource& aImage, int32_t aSx, int32_t aSy, int32_t aSw,
+ int32_t aSh, const ImageBitmapOptions& aOptions, ErrorResult& aRv) {
+ return ImageBitmap::Create(
+ this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aOptions, aRv);
+}
+
+// https://html.spec.whatwg.org/#structured-cloning
+void nsGlobalWindowInner::StructuredClone(
+ JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) {
+ nsContentUtils::StructuredClone(aCx, this, aValue, aOptions, aRetval, aError);
+}
+
+nsresult nsGlobalWindowInner::Dispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
+ }
+ return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* nsGlobalWindowInner::EventTargetFor(
+ TaskCategory aCategory) const {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->EventTargetFor(aCategory);
+ }
+ return DispatcherTrait::EventTargetFor(aCategory);
+}
+
+AbstractThread* nsGlobalWindowInner::AbstractMainThreadFor(
+ TaskCategory aCategory) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->AbstractMainThreadFor(aCategory);
+ }
+ return DispatcherTrait::AbstractMainThreadFor(aCategory);
+}
+
+Worklet* nsGlobalWindowInner::GetPaintWorklet(ErrorResult& aRv) {
+ if (!mPaintWorklet) {
+ nsIPrincipal* principal = GetPrincipal();
+ if (!principal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mPaintWorklet = PaintWorkletImpl::CreateWorklet(this, principal);
+ }
+
+ return mPaintWorklet;
+}
+
+void nsGlobalWindowInner::GetRegionalPrefsLocales(
+ nsTArray<nsString>& aLocales) {
+ MOZ_ASSERT(mozilla::intl::LocaleService::GetInstance());
+
+ AutoTArray<nsCString, 10> rpLocales;
+ mozilla::intl::LocaleService::GetInstance()->GetRegionalPrefsLocales(
+ rpLocales);
+
+ for (const auto& loc : rpLocales) {
+ aLocales.AppendElement(NS_ConvertUTF8toUTF16(loc));
+ }
+}
+
+void nsGlobalWindowInner::GetWebExposedLocales(nsTArray<nsString>& aLocales) {
+ MOZ_ASSERT(mozilla::intl::LocaleService::GetInstance());
+
+ AutoTArray<nsCString, 10> rpLocales;
+ mozilla::intl::LocaleService::GetInstance()->GetWebExposedLocales(rpLocales);
+
+ for (const auto& loc : rpLocales) {
+ aLocales.AppendElement(NS_ConvertUTF8toUTF16(loc));
+ }
+}
+
+IntlUtils* nsGlobalWindowInner::GetIntlUtils(ErrorResult& aError) {
+ if (!mIntlUtils) {
+ mIntlUtils = new IntlUtils(this);
+ }
+
+ return mIntlUtils;
+}
+
+void nsGlobalWindowInner::StoreSharedWorker(SharedWorker* aSharedWorker) {
+ MOZ_ASSERT(aSharedWorker);
+ MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker));
+
+ mSharedWorkers.AppendElement(aSharedWorker);
+}
+
+void nsGlobalWindowInner::ForgetSharedWorker(SharedWorker* aSharedWorker) {
+ MOZ_ASSERT(aSharedWorker);
+ MOZ_ASSERT(mSharedWorkers.Contains(aSharedWorker));
+
+ mSharedWorkers.RemoveElement(aSharedWorker);
+}
+
+void nsGlobalWindowInner::StorageAccessPermissionGranted() {
+ // Invalidate cached StorageAllowed field so that calls to GetLocalStorage
+ // give us the updated localStorage object.
+ ClearStorageAllowedCache();
+
+ // If we're always partitioning non-cookie third party storage then
+ // there is no need to clear it when the user accepts requestStorageAccess.
+ if (StaticPrefs::
+ privacy_partition_always_partition_third_party_non_cookie_storage()) {
+ // Just reset the active cookie and storage principals
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ if (mDoc) {
+ cjs = mDoc->CookieJarSettings();
+ }
+ StorageAccess storageAccess = StorageAllowedForWindow(this);
+ if (ShouldPartitionStorage(storageAccess) &&
+ StoragePartitioningEnabled(storageAccess, cjs)) {
+ if (mDoc) {
+ mDoc->ClearActiveCookieAndStoragePrincipals();
+ }
+ return;
+ }
+ }
+
+ PropagateStorageAccessPermissionGrantedToWorkers(*this);
+
+ // If we have a partitioned localStorage, it's time to replace it with a real
+ // one in order to receive notifications.
+
+ if (mLocalStorage) {
+ IgnoredErrorResult error;
+ GetLocalStorage(error);
+ if (NS_WARN_IF(error.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(mLocalStorage &&
+ mLocalStorage->Type() == Storage::eLocalStorage);
+
+ if (NextGenLocalStorageEnabled() && mListenerManager &&
+ mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
+ auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+ object->EnsureObserver();
+ }
+ }
+
+ // Reset the IndexedDB factory.
+ mIndexedDB = nullptr;
+
+ // Reset DOM Cache
+ mCacheStorage = nullptr;
+
+ // Reset the active cookie and storage principals
+ if (mDoc) {
+ mDoc->ClearActiveCookieAndStoragePrincipals();
+ if (mWindowGlobalChild) {
+ // XXX(farre): This is a bit backwards, but clearing the cookie
+ // principal might make us end up with a new effective storage
+ // principal on the child side than on the parent side, which
+ // means that we need to sync it. See bug 1705359.
+ mWindowGlobalChild->SetDocumentPrincipal(
+ mDoc->NodePrincipal(), mDoc->EffectiveStoragePrincipal());
+ }
+ }
+}
+
+ContentMediaController* nsGlobalWindowInner::GetContentMediaController() {
+ if (mContentMediaController) {
+ return mContentMediaController;
+ }
+ if (!mBrowsingContext) {
+ return nullptr;
+ }
+
+ mContentMediaController = new ContentMediaController(mBrowsingContext->Id());
+ return mContentMediaController;
+}
+
+void nsGlobalWindowInner::SetScrollMarks(const nsTArray<uint32_t>& aScrollMarks,
+ bool aOnHScrollbar) {
+ mScrollMarks.Assign(aScrollMarks);
+ mScrollMarksOnHScrollbar = aOnHScrollbar;
+
+ // Mark the scrollbar for repainting.
+ if (mDoc) {
+ PresShell* presShell = mDoc->GetPresShell();
+ if (presShell) {
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ sf->InvalidateScrollbars();
+ }
+ }
+ }
+}
+
+/* static */
+already_AddRefed<nsGlobalWindowInner> nsGlobalWindowInner::Create(
+ nsGlobalWindowOuter* aOuterWindow, bool aIsChrome,
+ WindowGlobalChild* aActor) {
+ RefPtr<nsGlobalWindowInner> window =
+ new nsGlobalWindowInner(aOuterWindow, aActor);
+ if (aIsChrome) {
+ window->mIsChrome = true;
+ window->mCleanMessageManager = true;
+ }
+
+ if (aActor) {
+ aActor->InitWindowGlobal(window);
+ }
+
+ window->InitWasOffline();
+ return window.forget();
+}
+
+JS::loader::ModuleLoaderBase* nsGlobalWindowInner::GetModuleLoader(
+ JSContext* aCx) {
+ Document* document = GetDocument();
+ if (!document) {
+ return nullptr;
+ }
+
+ ScriptLoader* loader = document->ScriptLoader();
+ if (!loader) {
+ return nullptr;
+ }
+
+ return loader->GetModuleLoader();
+}
+
+nsIURI* nsPIDOMWindowInner::GetDocumentURI() const {
+ return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
+}
+
+nsIURI* nsPIDOMWindowInner::GetDocBaseURI() const {
+ return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get();
+}
+
+mozilla::dom::WindowContext* nsPIDOMWindowInner::GetWindowContext() const {
+ return mWindowGlobalChild ? mWindowGlobalChild->WindowContext() : nullptr;
+}
+
+bool nsPIDOMWindowInner::RemoveFromBFCacheSync() {
+ if (Document* doc = GetExtantDoc()) {
+ return doc->RemoveFromBFCacheSync();
+ }
+ return false;
+}
+
+void nsPIDOMWindowInner::MaybeCreateDoc() {
+ // XXX: Forward to outer?
+ MOZ_ASSERT(!mDoc);
+ if (nsIDocShell* docShell = GetDocShell()) {
+ // Note that |document| here is the same thing as our mDoc, but we
+ // don't have to explicitly set the member variable because the docshell
+ // has already called SetNewDocument().
+ nsCOMPtr<Document> document = docShell->GetDocument();
+ Unused << document;
+ }
+}
+
+mozilla::dom::DocGroup* nsPIDOMWindowInner::GetDocGroup() const {
+ Document* doc = GetExtantDoc();
+ if (doc) {
+ return doc->GetDocGroup();
+ }
+ return nullptr;
+}
+
+mozilla::dom::BrowsingContextGroup*
+nsPIDOMWindowInner::GetBrowsingContextGroup() const {
+ return mBrowsingContext ? mBrowsingContext->Group() : nullptr;
+}
+
+nsIGlobalObject* nsPIDOMWindowInner::AsGlobal() {
+ return nsGlobalWindowInner::Cast(this);
+}
+
+const nsIGlobalObject* nsPIDOMWindowInner::AsGlobal() const {
+ return nsGlobalWindowInner::Cast(this);
+}
+
+void nsPIDOMWindowInner::SaveStorageAccessPermissionGranted() {
+ mStorageAccessPermissionGranted = true;
+
+ nsGlobalWindowInner::Cast(this)->StorageAccessPermissionGranted();
+}
+
+bool nsPIDOMWindowInner::HasStorageAccessPermissionGranted() {
+ return mStorageAccessPermissionGranted;
+}
+
+nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter* aOuterWindow,
+ WindowGlobalChild* aActor)
+ : mMutationBits(0),
+ mIsDocumentLoaded(false),
+ mIsHandlingResizeEvent(false),
+ mMayHavePaintEventListener(false),
+ mMayHaveTouchEventListener(false),
+ mMayHaveSelectionChangeEventListener(false),
+ mMayHaveFormSelectEventListener(false),
+ mMayHaveMouseEnterLeaveEventListener(false),
+ mMayHavePointerEnterLeaveEventListener(false),
+ mMayHaveTransitionEventListener(false),
+ mMayHaveBeforeInputEventListenerForTelemetry(false),
+ mMutationObserverHasObservedNodeForTelemetry(false),
+ mOuterWindow(aOuterWindow),
+ mWindowID(0),
+ mHasNotifiedGlobalCreated(false),
+ mMarkedCCGeneration(0),
+ mHasTriedToCacheTopInnerWindow(false),
+ mNumOfIndexedDBDatabases(0),
+ mNumOfOpenWebSockets(0),
+ mEvent(nullptr),
+ mStorageAccessPermissionGranted(false),
+ mWindowGlobalChild(aActor),
+ mWasSuspendedByGroup(false) {
+ MOZ_ASSERT(aOuterWindow);
+ mBrowsingContext = aOuterWindow->GetBrowsingContext();
+
+ if (mWindowGlobalChild) {
+ mWindowID = aActor->InnerWindowId();
+
+ MOZ_ASSERT(mWindowGlobalChild->BrowsingContext() == mBrowsingContext);
+ } else {
+ mWindowID = nsContentUtils::GenerateWindowId();
+ }
+}
+
+nsPIDOMWindowInner::~nsPIDOMWindowInner() = default;
+
+#undef FORWARD_TO_OUTER
+#undef FORWARD_TO_OUTER_OR_THROW
+#undef FORWARD_TO_OUTER_VOID
diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h
new file mode 100644
index 0000000000..d419041b20
--- /dev/null
+++ b/dom/base/nsGlobalWindowInner.h
@@ -0,0 +1,1616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGlobalWindowInner_h___
+#define nsGlobalWindowInner_h___
+
+#include "nsPIDOMWindow.h"
+
+#include "nsHashKeys.h"
+
+// Local Includes
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsTHashMap.h"
+#include "nsCycleCollectionParticipant.h"
+
+// Interfaces Needed
+#include "nsIBrowserDOMWindow.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIPrincipal.h"
+#include "nsSize.h"
+#include "mozilla/FlushType.h"
+#include "prclist.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/DebuggerNotificationManager.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/CallState.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/TimeStamp.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "Units.h"
+#include "nsCheapSets.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/UniquePtr.h"
+#include "nsThreadUtils.h"
+
+class nsIArray;
+class nsIBaseWindow;
+class nsIContent;
+class nsICSSDeclaration;
+class nsIDocShellTreeOwner;
+class nsIDOMWindowUtils;
+class nsIScrollableFrame;
+class nsIControllers;
+class nsIScriptContext;
+class nsIScriptTimeoutHandler;
+class nsIBrowserChild;
+class nsIPrintSettings;
+class nsITimeoutHandler;
+class nsIWebBrowserChrome;
+class nsIWebProgressListener;
+class mozIDOMWindowProxy;
+
+class nsScreen;
+class nsHistory;
+class nsGlobalWindowObserver;
+class nsGlobalWindowOuter;
+class nsDOMWindowUtils;
+class nsIUserIdleService;
+struct nsRect;
+
+class nsWindowSizes;
+
+class IdleRequestExecutor;
+
+class DialogValueHolder;
+
+class PromiseDocumentFlushedResolver;
+
+namespace mozilla {
+class AbstractThread;
+class ErrorResult;
+
+namespace glean {
+class Glean;
+class GleanPings;
+} // namespace glean
+
+namespace hal {
+enum class ScreenOrientation : uint32_t;
+}
+
+namespace dom {
+class BarProp;
+class BrowsingContext;
+struct ChannelPixelLayout;
+class ClientSource;
+class Console;
+class Crypto;
+class CustomElementRegistry;
+class DocGroup;
+class External;
+class Function;
+class Gamepad;
+class ContentMediaController;
+enum class ImageBitmapFormat : uint8_t;
+class IdleRequest;
+class IdleRequestCallback;
+class IncrementalRunnable;
+class InstallTriggerImpl;
+class IntlUtils;
+class MediaQueryList;
+class OwningExternalOrWindowProxy;
+class Promise;
+class PostMessageEvent;
+struct RequestInit;
+class RequestOrUSVString;
+class SharedWorker;
+class Selection;
+struct SizeToContentConstraints;
+class WebTaskScheduler;
+class WebTaskSchedulerMainThread;
+class SpeechSynthesis;
+class Timeout;
+class VisualViewport;
+class VRDisplay;
+enum class VRDisplayEventReason : uint8_t;
+class VREventObserver;
+struct WindowPostMessageOptions;
+class Worklet;
+namespace cache {
+class CacheStorage;
+} // namespace cache
+class IDBFactory;
+} // namespace dom
+} // namespace mozilla
+
+extern already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
+ JSContext* aCx, nsGlobalWindowInner* aWindow,
+ mozilla::dom::Function& aFunction,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+extern already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
+ JSContext* aCx, nsGlobalWindowInner* aWindow, const nsAString& aExpression,
+ mozilla::ErrorResult& aError);
+
+extern const JSClass OuterWindowProxyClass;
+
+//*****************************************************************************
+// nsGlobalWindowInner: Global Object for Scripting
+//*****************************************************************************
+
+// nsGlobalWindowInner inherits PRCList for maintaining a list of all inner
+// windows still in memory for any given outer window. This list is needed to
+// ensure that mOuterWindow doesn't end up dangling. The nature of PRCList means
+// that the window itself is always in the list, and an outer window's list will
+// also contain all inner window objects that are still in memory (and in
+// reality all inner window object's lists also contain its outer and all other
+// inner windows belonging to the same outer window, but that's an unimportant
+// side effect of inheriting PRCList).
+
+class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
+ public nsPIDOMWindowInner,
+ private nsIDOMWindow
+ // NOTE: This interface is private, as it's only
+ // implemented on chrome windows.
+ ,
+ private nsIDOMChromeWindow,
+ public nsIScriptGlobalObject,
+ public nsIScriptObjectPrincipal,
+ public nsSupportsWeakReference,
+ public nsIInterfaceRequestor,
+ public PRCListStr {
+ public:
+ using RemoteProxy = mozilla::dom::BrowsingContext;
+
+ using TimeStamp = mozilla::TimeStamp;
+ using TimeDuration = mozilla::TimeDuration;
+
+ using InnerWindowByIdTable =
+ nsTHashMap<nsUint64HashKey, nsGlobalWindowInner*>;
+
+ static void AssertIsOnMainThread()
+#ifdef DEBUG
+ ;
+#else
+ {
+ }
+#endif
+
+ bool IsInnerWindow() const final { return true; } // Overriding EventTarget
+
+ static nsGlobalWindowInner* Cast(nsPIDOMWindowInner* aPIWin) {
+ return static_cast<nsGlobalWindowInner*>(aPIWin);
+ }
+ static const nsGlobalWindowInner* Cast(const nsPIDOMWindowInner* aPIWin) {
+ return static_cast<const nsGlobalWindowInner*>(aPIWin);
+ }
+ static nsGlobalWindowInner* Cast(mozIDOMWindow* aWin) {
+ return Cast(nsPIDOMWindowInner::From(aWin));
+ }
+
+ static nsGlobalWindowInner* GetInnerWindowWithId(uint64_t aInnerWindowID) {
+ AssertIsOnMainThread();
+
+ if (!sInnerWindowsById) {
+ return nullptr;
+ }
+
+ nsGlobalWindowInner* innerWindow = sInnerWindowsById->Get(aInnerWindowID);
+ return innerWindow;
+ }
+
+ static InnerWindowByIdTable* GetWindowsTable() {
+ AssertIsOnMainThread();
+
+ return sInnerWindowsById;
+ }
+
+ static nsGlobalWindowInner* FromSupports(nsISupports* supports) {
+ // Make sure this matches the casts we do in QueryInterface().
+ return (nsGlobalWindowInner*)(mozilla::dom::EventTarget*)supports;
+ }
+
+ static already_AddRefed<nsGlobalWindowInner> Create(
+ nsGlobalWindowOuter* aOuter, bool aIsChrome,
+ mozilla::dom::WindowGlobalChild* aActor);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return GetWrapper();
+ }
+
+ // nsIGlobalObject
+ bool ShouldResistFingerprinting(
+ RFPTarget aTarget = RFPTarget::Unknown) const final;
+ mozilla::OriginTrials Trials() const final;
+ mozilla::dom::FontFaceSet* GetFonts() final;
+
+ JSObject* GetGlobalJSObject() final { return GetWrapper(); }
+ JSObject* GetGlobalJSObjectPreserveColor() const final {
+ return GetWrapperPreserveColor();
+ }
+ // The HasJSGlobal on nsIGlobalObject ends up having to do a virtual
+ // call to GetGlobalJSObjectPreserveColor(), because when it's
+ // making the call it doesn't know it's doing it on an
+ // nsGlobalWindowInner. Add a version here that can be entirely
+ // non-virtual.
+ bool HasJSGlobal() const { return GetGlobalJSObjectPreserveColor(); }
+
+ mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> GetStorageKey()
+ override;
+
+ mozilla::dom::StorageManager* GetStorageManager() override;
+
+ void TraceGlobalJSObject(JSTracer* aTrc);
+
+ virtual nsresult EnsureScriptEnvironment() override;
+
+ virtual nsIScriptContext* GetScriptContext() override;
+
+ virtual bool IsBlackForCC(bool aTracingNeeded = true) override;
+
+ // nsIScriptObjectPrincipal
+ virtual nsIPrincipal* GetPrincipal() override;
+
+ virtual nsIPrincipal* GetEffectiveCookiePrincipal() override;
+
+ virtual nsIPrincipal* GetEffectiveStoragePrincipal() override;
+
+ virtual nsIPrincipal* PartitionedPrincipal() override;
+
+ // nsIDOMWindow
+ NS_DECL_NSIDOMWINDOW
+
+ // nsIDOMChromeWindow (only implemented on chrome windows)
+ NS_DECL_NSIDOMCHROMEWINDOW
+
+ void CaptureEvents();
+ void ReleaseEvents();
+ void Dump(const nsAString& aStr);
+ void SetResizable(bool aResizable) const;
+
+ virtual mozilla::EventListenerManager* GetExistingListenerManager()
+ const override;
+
+ virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
+
+ mozilla::Maybe<mozilla::dom::EventCallbackDebuggerNotificationType>
+ GetDebuggerNotificationType() const override;
+
+ bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
+
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
+
+ virtual nsIGlobalObject* GetOwnerGlobal() const override;
+
+ EventTarget* GetTargetForDOMEvent() override;
+
+ using mozilla::dom::EventTarget::DispatchEvent;
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool DispatchEvent(
+ mozilla::dom::Event& aEvent, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv) override;
+
+ void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
+
+ void Suspend(bool aIncludeSubWindows = true);
+ void Resume(bool aIncludeSubWindows = true);
+ virtual bool IsSuspended() const override;
+
+ // Calling Freeze() on a window will automatically Suspend() it. In
+ // addition, the window and its children (if aIncludeSubWindows is true) are
+ // further treated as no longer suitable for interaction with the user. For
+ // example, it may be marked non-visible, cannot be focused, etc. All worker
+ // threads are also frozen bringing them to a complete stop. A window can
+ // have Freeze() called multiple times and will only thaw after a matching
+ // number of Thaw() calls.
+ void Freeze(bool aIncludeSubWindows = true);
+ void Thaw(bool aIncludeSubWindows = true);
+ virtual bool IsFrozen() const override;
+ void SyncStateFromParentWindow();
+
+ // Called on the current inner window of a browsing context when its
+ // background state changes according to selected tab or visibility of the
+ // browser window. Used with Suspend()/Resume() or Freeze()/Thaw() because
+ // background state may change while the inner window is not current.
+ void UpdateBackgroundState();
+
+ mozilla::dom::DebuggerNotificationManager*
+ GetOrCreateDebuggerNotificationManager() override;
+
+ mozilla::dom::DebuggerNotificationManager*
+ GetExistingDebuggerNotificationManager() override;
+
+ mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const override;
+ mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+ mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController()
+ const override;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+ void SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp);
+ nsIContentSecurityPolicy* GetCsp();
+
+ virtual RefPtr<mozilla::dom::ServiceWorker> GetOrCreateServiceWorker(
+ const mozilla::dom::ServiceWorkerDescriptor& aDescriptor) override;
+
+ RefPtr<mozilla::dom::ServiceWorkerRegistration> GetServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
+ const override;
+
+ RefPtr<mozilla::dom::ServiceWorkerRegistration>
+ GetOrCreateServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
+ override;
+
+ mozilla::StorageAccess GetStorageAccess() final;
+
+ void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
+
+ void NoteDOMContentLoaded();
+
+ virtual nsresult FireDelayedDOMEvents(bool aIncludeSubWindows) override;
+
+ virtual void MaybeUpdateTouchState() override;
+
+ // Inner windows only.
+ void RefreshRealmPrincipal();
+
+ // For accessing protected field mFullscreen
+ friend class FullscreenTransitionTask;
+
+ // Inner windows only.
+ virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
+ void NotifyHasXRSession();
+ bool HasUsedVR() const;
+ bool IsVRContentDetected() const;
+ bool IsVRContentPresenting() const;
+ void RequestXRPermission();
+ void OnXRPermissionRequestAllow();
+ void OnXRPermissionRequestCancel();
+
+ using EventTarget::EventListenerAdded;
+ virtual void EventListenerAdded(nsAtom* aType) override;
+ using EventTarget::EventListenerRemoved;
+ virtual void EventListenerRemoved(nsAtom* aType) override;
+
+ // nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // WebIDL interface.
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetter(
+ uint32_t aIndex);
+
+ static bool IsPrivilegedChromeWindow(JSContext*, JSObject* aObj);
+
+ static bool IsRequestIdleCallbackEnabled(JSContext* aCx, JSObject*);
+
+ static bool DeviceSensorsEnabled(JSContext*, JSObject*);
+
+ static bool ContentPropertyEnabled(JSContext* aCx, JSObject*);
+
+ static bool CachesEnabled(JSContext* aCx, JSObject*);
+
+ bool DoResolve(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc);
+ // The return value is whether DoResolve might end up resolving the given id.
+ // If in doubt, return true.
+ static bool MayResolve(jsid aId);
+
+ void GetOwnPropertyNames(JSContext* aCx, JS::MutableHandleVector<jsid> aNames,
+ bool aEnumerableOnly, mozilla::ErrorResult& aRv);
+
+ nsPIDOMWindowOuter* GetInProcessScriptableTop() override;
+ inline nsGlobalWindowOuter* GetInProcessTopInternal();
+
+ inline nsGlobalWindowOuter* GetInProcessScriptableTopInternal();
+
+ already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
+ const nsAString& aName);
+
+ inline nsIBrowserChild* GetBrowserChild() { return mBrowserChild.get(); }
+
+ nsIScriptContext* GetContextInternal();
+
+ nsGlobalWindowOuter* GetOuterWindowInternal() const;
+
+ bool IsChromeWindow() const { return mIsChrome; }
+
+ // GetScrollFrame does not flush. Callers should do it themselves as needed,
+ // depending on which info they actually want off the scrollable frame.
+ nsIScrollableFrame* GetScrollFrame();
+
+ nsresult Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData);
+
+ void ObserveStorageNotification(mozilla::dom::StorageEvent* aEvent,
+ const char16_t* aStorageType,
+ bool aPrivateBrowsing);
+
+ static void Init();
+ static void ShutDown();
+ static bool IsCallerChrome();
+
+ friend class WindowStateHolder;
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
+ nsGlobalWindowInner, mozilla::dom::EventTarget)
+
+#ifdef DEBUG
+ // Call Unlink on this window. This may cause bad things to happen, so use
+ // with caution.
+ void RiskyUnlink();
+#endif
+
+ virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void SetReadyForFocus() override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void PageHidden() override;
+ virtual nsresult DispatchAsyncHashchange(nsIURI* aOldURI,
+ nsIURI* aNewURI) override;
+ virtual nsresult DispatchSyncPopState() override;
+
+ // Inner windows only.
+ virtual void EnableDeviceSensor(uint32_t aType) override;
+ virtual void DisableDeviceSensor(uint32_t aType) override;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ virtual void EnableOrientationChangeListener() override;
+ virtual void DisableOrientationChangeListener() override;
+#endif
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
+
+ void CollectDOMSizesForDataDocuments(nsWindowSizes&) const;
+ void RegisterDataDocumentForMemoryReporting(Document*);
+ void UnregisterDataDocumentForMemoryReporting(Document*);
+
+ enum SlowScriptResponse {
+ ContinueSlowScript = 0,
+ ContinueSlowScriptAndKeepNotifying,
+ AlwaysContinueSlowScript,
+ KillSlowScript,
+ KillScriptGlobal
+ };
+ SlowScriptResponse ShowSlowScriptDialog(JSContext* aCx,
+ const nsString& aAddonId,
+ const double aDuration);
+
+ // Inner windows only.
+ void AddGamepad(mozilla::dom::GamepadHandle aHandle,
+ mozilla::dom::Gamepad* aGamepad);
+ void RemoveGamepad(mozilla::dom::GamepadHandle aHandle);
+ void GetGamepads(nsTArray<RefPtr<mozilla::dom::Gamepad>>& aGamepads);
+ already_AddRefed<mozilla::dom::Gamepad> GetGamepad(
+ mozilla::dom::GamepadHandle aHandle);
+ void SetHasSeenGamepadInput(bool aHasSeen);
+ bool HasSeenGamepadInput();
+ void SyncGamepadState();
+ void StopGamepadHaptics();
+
+ // Inner windows only.
+ // Enable/disable updates for gamepad input.
+ void EnableGamepadUpdates();
+ void DisableGamepadUpdates();
+
+ // Inner windows only.
+ // Enable/disable updates for VR
+ void EnableVRUpdates();
+ void DisableVRUpdates();
+ // Reset telemetry data when switching windows.
+ // aUpdate, true for accumulating the result to the histogram.
+ // false for only resetting the timestamp.
+ void ResetVRTelemetry(bool aUpdate);
+
+ void StartVRActivity();
+ void StopVRActivity();
+
+ // Update the VR displays for this window
+ bool UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDisplays);
+
+ // Inner windows only.
+ // Called to inform that the set of active VR displays has changed.
+ void NotifyActiveVRDisplaysChanged();
+ void NotifyDetectXRRuntimesCompleted();
+ void NotifyPresentationGenerationChanged(uint32_t aDisplayID);
+
+ void DispatchVRDisplayActivate(uint32_t aDisplayID,
+ mozilla::dom::VRDisplayEventReason aReason);
+ void DispatchVRDisplayDeactivate(uint32_t aDisplayID,
+ mozilla::dom::VRDisplayEventReason aReason);
+ void DispatchVRDisplayConnect(uint32_t aDisplayID);
+ void DispatchVRDisplayDisconnect(uint32_t aDisplayID);
+ void DispatchVRDisplayPresentChange(uint32_t aDisplayID);
+
+#define EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::EventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetEventHandler(nsGkAtoms::on##name_) : nullptr; \
+ } \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(nsGkAtoms::on##name_, handler); \
+ } \
+ }
+#define ERROR_EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::OnErrorEventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetOnErrorEventHandler() : nullptr; \
+ } \
+ void SetOn##name_(mozilla::dom::OnErrorEventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(handler); \
+ } \
+ }
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::OnBeforeUnloadEventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetOnBeforeUnloadEventHandler() : nullptr; \
+ } \
+ void SetOn##name_( \
+ mozilla::dom::OnBeforeUnloadEventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(handler); \
+ } \
+ }
+#define WINDOW_ONLY_EVENT EVENT
+#define TOUCH_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef TOUCH_EVENT
+#undef WINDOW_ONLY_EVENT
+#undef BEFOREUNLOAD_EVENT
+#undef ERROR_EVENT
+#undef EVENT
+
+ nsISupports* GetParentObject() { return nullptr; }
+
+ static JSObject* CreateNamedPropertiesObject(JSContext* aCx,
+ JS::Handle<JSObject*> aProto);
+
+ mozilla::dom::WindowProxyHolder Window();
+ mozilla::dom::WindowProxyHolder Self() { return Window(); }
+ Document* GetDocument() { return GetDoc(); }
+ void GetName(nsAString& aName, mozilla::ErrorResult& aError);
+ void SetName(const nsAString& aName, mozilla::ErrorResult& aError);
+ mozilla::dom::Location* Location() override;
+ nsHistory* GetHistory(mozilla::ErrorResult& aError);
+ mozilla::dom::CustomElementRegistry* CustomElements() override;
+ mozilla::dom::CustomElementRegistry* GetExistingCustomElements();
+ mozilla::dom::BarProp* GetLocationbar(mozilla::ErrorResult& aError);
+ mozilla::dom::BarProp* GetMenubar(mozilla::ErrorResult& aError);
+ mozilla::dom::BarProp* GetPersonalbar(mozilla::ErrorResult& aError);
+ mozilla::dom::BarProp* GetScrollbars(mozilla::ErrorResult& aError);
+ mozilla::dom::BarProp* GetStatusbar(mozilla::ErrorResult& aError);
+ mozilla::dom::BarProp* GetToolbar(mozilla::ErrorResult& aError);
+ void GetStatus(nsAString& aStatus, mozilla::ErrorResult& aError);
+ void SetStatus(const nsAString& aStatus, mozilla::ErrorResult& aError);
+ void Close(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult Close() override;
+ bool GetClosed(mozilla::ErrorResult& aError);
+ void Stop(mozilla::ErrorResult& aError);
+ void Focus(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult Focus(mozilla::dom::CallerType aCallerType) override;
+ void Blur(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aError);
+ mozilla::dom::WindowProxyHolder GetFrames(mozilla::ErrorResult& aError);
+ uint32_t Length();
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTop(
+ mozilla::ErrorResult& aError);
+
+ protected:
+ explicit nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
+ mozilla::dom::WindowGlobalChild* aActor);
+ // Initializes the mWasOffline member variable
+ void InitWasOffline();
+
+ public:
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetOpenerWindow(
+ mozilla::ErrorResult& aError);
+ void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+ void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
+ mozilla::ErrorResult& aError);
+ void GetEvent(mozilla::dom::OwningEventOrUndefined& aRetval);
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetParent(
+ mozilla::ErrorResult& aError);
+ nsPIDOMWindowOuter* GetInProcessScriptableParent() override;
+ mozilla::dom::Element* GetFrameElement(nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Open(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ mozilla::ErrorResult& aError);
+ int16_t Orientation(mozilla::dom::CallerType aCallerType);
+
+ already_AddRefed<mozilla::dom::Console> GetConsole(JSContext* aCx,
+ mozilla::ErrorResult& aRv);
+
+ // https://w3c.github.io/webappsec-secure-contexts/#dom-window-issecurecontext
+ bool IsSecureContext() const;
+
+ void GetSidebar(mozilla::dom::OwningExternalOrWindowProxy& aResult);
+ mozilla::dom::External* External();
+
+ mozilla::dom::Worklet* GetPaintWorklet(mozilla::ErrorResult& aRv);
+
+ void GetRegionalPrefsLocales(nsTArray<nsString>& aLocales);
+
+ void GetWebExposedLocales(nsTArray<nsString>& aLocales);
+
+ mozilla::dom::IntlUtils* GetIntlUtils(mozilla::ErrorResult& aRv);
+
+ void StoreSharedWorker(mozilla::dom::SharedWorker* aSharedWorker);
+
+ void ForgetSharedWorker(mozilla::dom::SharedWorker* aSharedWorker);
+
+ public:
+ void Alert(nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aError);
+ void Alert(const nsAString& aMessage, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ bool Confirm(const nsAString& aMessage, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void Prompt(const nsAString& aMessage, const nsAString& aInitial,
+ nsAString& aReturn, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<mozilla::dom::cache::CacheStorage> GetCaches(
+ mozilla::ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::Promise> Fetch(
+ const mozilla::dom::RequestOrUSVString& aInput,
+ const mozilla::dom::RequestInit& aInit,
+ mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void Print(mozilla::ErrorResult& aError);
+ MOZ_CAN_RUN_SCRIPT mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder>
+ PrintPreview(nsIPrintSettings*, nsIWebProgressListener*, nsIDocShell*,
+ mozilla::ErrorResult&);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const mozilla::dom::Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const mozilla::dom::WindowPostMessageOptions& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeout(JSContext* aCx, mozilla::dom::Function& aFunction,
+ int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& /* unused */,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ClearTimeout(int32_t aHandle);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetInterval(JSContext* aCx, mozilla::dom::Function& aFunction,
+ const int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetInterval(JSContext* aCx, const nsAString& aHandler,
+ const int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& /* unused */,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ClearInterval(int32_t aHandle);
+ void GetOrigin(nsAString& aOrigin);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ReportError(JSContext* aCx, JS::Handle<JS::Value> aError,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv);
+
+ void Atob(const nsAString& aAsciiBase64String, nsAString& aBinaryData,
+ mozilla::ErrorResult& aError);
+ void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
+ mozilla::ErrorResult& aError);
+ mozilla::dom::Storage* GetSessionStorage(mozilla::ErrorResult& aError);
+ mozilla::dom::Storage* GetLocalStorage(mozilla::ErrorResult& aError);
+ mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
+ mozilla::dom::IDBFactory* GetIndexedDB(JSContext* aCx,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsICSSDeclaration> GetComputedStyle(
+ mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError) override;
+ mozilla::dom::VisualViewport* VisualViewport();
+ already_AddRefed<mozilla::dom::MediaQueryList> MatchMedia(
+ const nsACString& aQuery, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsScreen* GetScreen(mozilla::ErrorResult& aError);
+ void MoveTo(int32_t aXPos, int32_t aYPos,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void MoveBy(int32_t aXDif, int32_t aYDif,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void ResizeTo(int32_t aWidth, int32_t aHeight,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void Scroll(double aXScroll, double aYScroll);
+ void Scroll(const mozilla::dom::ScrollToOptions& aOptions);
+ void ScrollTo(double aXScroll, double aYScroll);
+ void ScrollTo(const mozilla::dom::ScrollToOptions& aOptions);
+ void ScrollBy(double aXScrollDif, double aYScrollDif);
+ void ScrollBy(const mozilla::dom::ScrollToOptions& aOptions);
+ void ScrollByLines(int32_t numLines,
+ const mozilla::dom::ScrollOptions& aOptions);
+ void ScrollByPages(int32_t numPages,
+ const mozilla::dom::ScrollOptions& aOptions);
+ void MozScrollSnap();
+ void GetInnerWidth(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void GetInnerHeight(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ double GetScrollX(mozilla::ErrorResult& aError);
+ double GetPageXOffset(mozilla::ErrorResult& aError) {
+ return GetScrollX(aError);
+ }
+ double GetScrollY(mozilla::ErrorResult& aError);
+ double GetPageYOffset(mozilla::ErrorResult& aError) {
+ return GetScrollY(aError);
+ }
+
+ int32_t GetScreenLeft(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError) {
+ return GetScreenX(aCallerType, aError);
+ }
+
+ double ScreenEdgeSlopX() const;
+ double ScreenEdgeSlopY() const;
+
+ int32_t GetScreenTop(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError) {
+ return GetScreenY(aCallerType, aError);
+ }
+
+ void GetScreenX(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void GetScreenY(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void GetOuterWidth(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void GetOuterHeight(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
+
+ uint32_t RequestIdleCallback(JSContext* aCx,
+ mozilla::dom::IdleRequestCallback& aCallback,
+ const mozilla::dom::IdleRequestOptions& aOptions,
+ mozilla::ErrorResult& aError);
+ void CancelIdleCallback(uint32_t aHandle);
+
+#ifdef MOZ_WEBSPEECH
+ mozilla::dom::SpeechSynthesis* GetSpeechSynthesis(
+ mozilla::ErrorResult& aError);
+ bool HasActiveSpeechSynthesis();
+#endif
+
+ mozilla::glean::Glean* Glean();
+ mozilla::glean::GleanPings* GleanPings();
+
+ already_AddRefed<nsICSSDeclaration> GetDefaultComputedStyle(
+ mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError);
+ void SizeToContent(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SizeToContentConstrained(const mozilla::dom::SizeToContentConstraints&,
+ mozilla::ErrorResult&);
+ mozilla::dom::Crypto* GetCrypto(mozilla::ErrorResult& aError);
+ nsIControllers* GetControllers(mozilla::ErrorResult& aError);
+ nsresult GetControllers(nsIControllers** aControllers) override;
+ mozilla::dom::Element* GetRealFrameElement(mozilla::ErrorResult& aError);
+ float GetMozInnerScreenX(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ float GetMozInnerScreenY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ double GetDevicePixelRatio(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ double GetDesktopToDeviceScale(mozilla::ErrorResult& aError);
+ int32_t GetScrollMinX(mozilla::ErrorResult& aError);
+ int32_t GetScrollMinY(mozilla::ErrorResult& aError);
+ int32_t GetScrollMaxX(mozilla::ErrorResult& aError);
+ int32_t GetScrollMaxY(mozilla::ErrorResult& aError);
+ bool GetFullScreen(mozilla::ErrorResult& aError);
+ bool GetFullScreen() override;
+ void SetFullScreen(bool aFullscreen, mozilla::ErrorResult& aError);
+ bool Find(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
+ bool aShowDialog, mozilla::ErrorResult& aError);
+
+ bool DidFireDocElemInserted() const { return mDidFireDocElemInserted; }
+ void SetDidFireDocElemInserted() { mDidFireDocElemInserted = true; }
+
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenDialog(
+ JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
+ mozilla::ErrorResult& aError);
+ void UpdateCommands(const nsAString& anAction, mozilla::dom::Selection* aSel,
+ int16_t aReason);
+
+ void GetContent(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<mozilla::dom::Promise> CreateImageBitmap(
+ const mozilla::dom::ImageBitmapSource& aImage,
+ const mozilla::dom::ImageBitmapOptions& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ already_AddRefed<mozilla::dom::Promise> CreateImageBitmap(
+ const mozilla::dom::ImageBitmapSource& aImage, int32_t aSx, int32_t aSy,
+ int32_t aSw, int32_t aSh,
+ const mozilla::dom::ImageBitmapOptions& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ void StructuredClone(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const mozilla::dom::StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+
+ // ChromeWindow bits. Do NOT call these unless your window is in
+ // fact chrome.
+ uint16_t WindowState();
+ bool IsFullyOccluded();
+ nsIBrowserDOMWindow* GetBrowserDOMWindow(mozilla::ErrorResult& aError);
+ void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
+ mozilla::ErrorResult& aError);
+ void GetAttention(mozilla::ErrorResult& aError);
+ void GetAttentionWithCycleCount(int32_t aCycleCount,
+ mozilla::ErrorResult& aError);
+ void SetCursor(const nsACString& aCursor, mozilla::ErrorResult& aError);
+ void Maximize();
+ void Minimize();
+ void Restore();
+ void GetWorkspaceID(nsAString& workspaceID);
+ void MoveToWorkspace(const nsAString& workspaceID);
+ void NotifyDefaultButtonLoaded(mozilla::dom::Element& aDefaultButton,
+ mozilla::ErrorResult& aError);
+ mozilla::dom::ChromeMessageBroadcaster* MessageManager();
+ mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(
+ const nsAString& aGroup);
+
+ already_AddRefed<mozilla::dom::Promise> PromiseDocumentFlushed(
+ mozilla::dom::PromiseDocumentFlushedCallback& aCallback,
+ mozilla::ErrorResult& aError);
+
+ void GetReturnValueOuter(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void GetReturnValue(JSContext* aCx, JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void SetReturnValueOuter(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void SetReturnValue(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ void GetInterface(JSContext* aCx, JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
+
+ bool ShouldReportForServiceWorkerScope(const nsAString& aScope);
+
+ mozilla::dom::InstallTriggerImpl* GetInstallTrigger();
+
+ nsIDOMWindowUtils* GetWindowUtils(mozilla::ErrorResult& aRv);
+
+ void UpdateTopInnerWindow();
+
+ virtual bool IsInSyncOperation() override;
+
+ // Early during inner window creation, `IsSharedMemoryAllowedInternal`
+ // is called before the `mDoc` field has been initialized in order to
+ // determine whether to expose the `SharedArrayBuffer` constructor on the
+ // JS global. We still want to consider the document's principal to see if
+ // it is a privileged extension which should be exposed to
+ // `SharedArrayBuffer`, however the inner window doesn't know the document's
+ // principal yet. `aPrincipalOverride` is used in that situation to provide
+ // the principal for the to-be-loaded document.
+ bool IsSharedMemoryAllowed() const override {
+ return IsSharedMemoryAllowedInternal(
+ const_cast<nsGlobalWindowInner*>(this)->GetPrincipal());
+ }
+
+ bool IsSharedMemoryAllowedInternal(nsIPrincipal* aPrincipal = nullptr) const;
+
+ // https://whatpr.org/html/4734/structured-data.html#cross-origin-isolated
+ bool CrossOriginIsolated() const override;
+
+ mozilla::dom::WebTaskScheduler* Scheduler();
+
+ protected:
+ // Web IDL helpers
+
+ // Redefine the property called aPropName on this window object to be a value
+ // property with the value aValue, much like we would do for a [Replaceable]
+ // property in IDL.
+ void RedefineProperty(JSContext* aCx, const char* aPropName,
+ JS::Handle<JS::Value> aValue,
+ mozilla::ErrorResult& aError);
+
+ // Implementation guts for our writable IDL attributes that are really
+ // supposed to be readonly replaceable.
+ template <typename T>
+ using WindowCoordGetter = T (nsGlobalWindowInner::*)(
+ mozilla::dom::CallerType aCallerType, mozilla::ErrorResult&);
+ template <typename T>
+ using WindowCoordSetter = void (nsGlobalWindowInner::*)(
+ T, mozilla::dom::CallerType aCallerType, mozilla::ErrorResult&);
+
+ template <typename T>
+ void GetReplaceableWindowCoord(JSContext* aCx, WindowCoordGetter<T> aGetter,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ template <typename T>
+ void SetReplaceableWindowCoord(JSContext* aCx, WindowCoordSetter<T> aSetter,
+ JS::Handle<JS::Value> aValue,
+ const char* aPropName,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ // And the implementations of WindowCoordGetter/WindowCoordSetter.
+ protected:
+ double GetInnerWidth(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult GetInnerWidth(double* aWidth) override;
+ void SetInnerWidth(double aInnerWidth, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ double GetInnerHeight(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult GetInnerHeight(double* aHeight) override;
+ void SetInnerHeight(double aInnerHeight, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenX(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenX(int32_t aScreenX, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenY(int32_t aScreenY, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterWidth(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterWidth(int32_t aOuterWidth, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterHeight(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterHeight(int32_t aOuterHeight,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ friend class HashchangeCallback;
+ friend class mozilla::dom::BarProp;
+
+ // Object Management
+ virtual ~nsGlobalWindowInner();
+
+ void FreeInnerObjects();
+
+ // Initialize state that depends on the document. By this point, mDoc should
+ // be set correctly and have us set as its script global object.
+ void InitDocumentDependentState(JSContext* aCx);
+
+ nsresult EnsureClientSource();
+ nsresult ExecutionReady();
+
+ // Inner windows only.
+ nsresult DefineArgumentsProperty(nsIArray* aArguments);
+
+ // Get the parent, returns null if this is a toplevel window
+ nsPIDOMWindowOuter* GetInProcessParentInternal();
+
+ private:
+ template <typename Method, typename... Args>
+ mozilla::CallState CallOnInProcessDescendantsInternal(
+ mozilla::dom::BrowsingContext* aBrowsingContext, bool aChildOnly,
+ Method aMethod, Args&&... aArgs);
+
+ // Call the given method on the immediate children of this window. The
+ // CallState returned by the last child method invocation is returned or
+ // CallState::Continue if the method returns void.
+ template <typename Method, typename... Args>
+ mozilla::CallState CallOnInProcessChildren(Method aMethod, Args&&... aArgs) {
+ MOZ_ASSERT(IsCurrentInnerWindow());
+ return CallOnInProcessDescendantsInternal(GetBrowsingContext(), true,
+ aMethod, aArgs...);
+ }
+
+ // Call the given method on the descendant of this window. The CallState
+ // returned by the last descendant method invocation is returned or
+ // CallState::Continue if the method returns void.
+ template <typename Method, typename... Args>
+ mozilla::CallState CallOnInProcessDescendants(Method aMethod,
+ Args&&... aArgs) {
+ MOZ_ASSERT(IsCurrentInnerWindow());
+ return CallOnInProcessDescendantsInternal(GetBrowsingContext(), false,
+ aMethod, aArgs...);
+ }
+
+ // Helper to convert a void returning child method into an implicit
+ // CallState::Continue value.
+ template <typename Return, typename Method, typename... Args>
+ typename std::enable_if<std::is_void<Return>::value, mozilla::CallState>::type
+ CallDescendant(nsGlobalWindowInner* aWindow, Method aMethod,
+ Args&&... aArgs) {
+ (aWindow->*aMethod)(aArgs...);
+ return mozilla::CallState::Continue;
+ }
+
+ // Helper that passes through the CallState value from a child method.
+ template <typename Return, typename Method, typename... Args>
+ typename std::enable_if<std::is_same<Return, mozilla::CallState>::value,
+ mozilla::CallState>::type
+ CallDescendant(nsGlobalWindowInner* aWindow, Method aMethod,
+ Args&&... aArgs) {
+ return (aWindow->*aMethod)(aArgs...);
+ }
+
+ void FreezeInternal(bool aIncludeSubWindows);
+ void ThawInternal(bool aIncludeSubWindows);
+
+ mozilla::CallState ShouldReportForServiceWorkerScopeInternal(
+ const nsACString& aScope, bool* aResultOut);
+
+ public:
+ // Timeout Functions
+ // |interval| is in milliseconds.
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeoutOrInterval(
+ JSContext* aCx, mozilla::dom::Function& aFunction, int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments, bool aIsInterval,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ mozilla::ErrorResult& aError);
+
+ // Return true if |aTimeout| was cleared while its handler ran.
+ MOZ_CAN_RUN_SCRIPT
+ bool RunTimeoutHandler(mozilla::dom::Timeout* aTimeout,
+ nsIScriptContext* aScx);
+
+ // Helper Functions
+ already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+ bool IsPrivateBrowsing();
+
+ void FireOfflineStatusEventIfChanged();
+
+ public:
+ // Inner windows only.
+ nsresult FireHashchange(const nsAString& aOldURL, const nsAString& aNewURL);
+
+ void FlushPendingNotifications(mozilla::FlushType aType);
+
+ void ScrollTo(const mozilla::CSSIntPoint& aScroll,
+ const mozilla::dom::ScrollOptions& aOptions);
+
+ already_AddRefed<nsIWidget> GetMainWidget();
+ nsIWidget* GetNearestWidget() const;
+
+ bool IsInModalState();
+
+ void SetFocusedElement(mozilla::dom::Element* aElement,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) override;
+
+ uint32_t GetFocusMethod() override;
+
+ bool ShouldShowFocusRing() override;
+
+ // Inner windows only.
+ void UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent);
+
+ public:
+ virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
+
+ // Get the toplevel principal, returns null if this is a toplevel window.
+ nsIPrincipal* GetTopLevelAntiTrackingPrincipal();
+
+ // Get the client principal, returns null if the clientSource is not
+ // available.
+ nsIPrincipal* GetClientPrincipal();
+
+ // Whether the chrome window is currently in a full screen transition. This
+ // flag is updated from FullscreenTransitionTask.
+ bool IsInFullScreenTransition();
+
+ // This method is called if this window loads a 3rd party tracking resource
+ // and the storage is just been granted. The window can reset the partitioned
+ // storage objects and switch to the first party cookie jar.
+ void StorageAccessPermissionGranted();
+
+ protected:
+ static void NotifyDOMWindowDestroyed(nsGlobalWindowInner* aWindow);
+ void NotifyWindowIDDestroyed(const char* aTopic);
+
+ static void NotifyDOMWindowFrozen(nsGlobalWindowInner* aWindow);
+ static void NotifyDOMWindowThawed(nsGlobalWindowInner* aWindow);
+
+ virtual void UpdateParentTarget() override;
+
+ // Clear the document-dependent slots on our JS wrapper. Inner windows only.
+ void ClearDocumentDependentSlots(JSContext* aCx);
+
+ // Inner windows only.
+ already_AddRefed<mozilla::dom::StorageEvent> CloneStorageEvent(
+ const nsAString& aType, const RefPtr<mozilla::dom::StorageEvent>& aEvent,
+ mozilla::ErrorResult& aRv);
+
+ protected:
+ already_AddRefed<nsICSSDeclaration> GetComputedStyleHelper(
+ mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly, mozilla::ErrorResult& aError);
+
+ nsGlobalWindowInner* InnerForSetTimeoutOrInterval(
+ mozilla::ErrorResult& aError);
+
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ private:
+ // Fire the JS engine's onNewGlobalObject hook. Only used on inner windows.
+ void FireOnNewGlobalObject();
+
+ // Helper for resolving the components shim.
+ bool ResolveComponentsShim(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc);
+
+ // nsPIDOMWindow{Inner,Outer} should be able to see these helper methods.
+ friend class nsPIDOMWindowInner;
+ friend class nsPIDOMWindowOuter;
+
+ bool IsBackgroundInternal() const;
+
+ // NOTE: Chrome Only
+ void DisconnectAndClearGroupMessageManagers() {
+ MOZ_RELEASE_ASSERT(IsChromeWindow());
+ for (const auto& entry : mChromeFields.mGroupMessageManagers) {
+ mozilla::dom::ChromeMessageBroadcaster* mm = entry.GetWeak();
+ if (mm) {
+ mm->Disconnect();
+ }
+ }
+ mChromeFields.mGroupMessageManagers.Clear();
+ }
+
+ // Call mDocumentFlushedResolvers items, and perform MicroTask checkpoint
+ // after that.
+ //
+ // If aUntilExhaustion is true, then we call resolvers that get added as a
+ // result synchronously, otherwise we wait until the next refresh driver tick.
+ void CallDocumentFlushedResolvers(bool aUntilExhaustion);
+
+ // Called after a refresh driver tick. See documentation of
+ // CallDocumentFlushedResolvers for the meaning of aUntilExhaustion.
+ //
+ // Returns whether we need to keep observing the refresh driver or not.
+ bool MaybeCallDocumentFlushedResolvers(bool aUntilExhaustion);
+
+ // Try to fire the "load" event on our content embedder if we're an iframe.
+ MOZ_CAN_RUN_SCRIPT void FireFrameLoadEvent();
+
+ void UpdateAutoplayPermission();
+ void UpdateShortcutsPermission();
+ void UpdatePopupPermission();
+
+ void UpdatePermissions();
+
+ public:
+ static uint32_t GetShortcutsPermission(nsIPrincipal* aPrincipal);
+
+ // Dispatch a runnable related to the global.
+ virtual nsresult Dispatch(mozilla::TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) override;
+
+ virtual nsISerialEventTarget* EventTargetFor(
+ mozilla::TaskCategory aCategory) const override;
+
+ virtual mozilla::AbstractThread* AbstractMainThreadFor(
+ mozilla::TaskCategory aCategory) override;
+
+ void DisableIdleCallbackRequests();
+ uint32_t LastIdleRequestHandle() const {
+ return mIdleRequestCallbackCounter - 1;
+ }
+ MOZ_CAN_RUN_SCRIPT
+ void RunIdleRequest(mozilla::dom::IdleRequest* aRequest,
+ DOMHighResTimeStamp aDeadline, bool aDidTimeout);
+ MOZ_CAN_RUN_SCRIPT
+ void ExecuteIdleRequest(TimeStamp aDeadline);
+ void ScheduleIdleRequestDispatch();
+ void SuspendIdleRequests();
+ void ResumeIdleRequests();
+
+ using IdleRequests = mozilla::LinkedList<RefPtr<mozilla::dom::IdleRequest>>;
+ void RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest);
+
+ void SetActiveLoadingState(bool aIsLoading) override;
+
+ // Hint to the JS engine whether we are currently loading.
+ void HintIsLoading(bool aIsLoading);
+
+ mozilla::dom::ContentMediaController* GetContentMediaController();
+
+ bool TryOpenExternalProtocolIframe() {
+ if (mHasOpenedExternalProtocolFrame) {
+ return false;
+ }
+ mHasOpenedExternalProtocolFrame = true;
+ return true;
+ }
+
+ nsTArray<uint32_t>& GetScrollMarks() { return mScrollMarks; }
+ bool GetScrollMarksOnHScrollbar() const { return mScrollMarksOnHScrollbar; }
+ void SetScrollMarks(const nsTArray<uint32_t>& aScrollMarks,
+ bool aOnHScrollbar);
+
+ // Don't use this value directly, call StorageAccess::StorageAllowedForWindow
+ // instead.
+ mozilla::Maybe<mozilla::StorageAccess> GetStorageAllowedCache(
+ uint32_t& aRejectedReason) {
+ if (mStorageAllowedCache.isSome()) {
+ aRejectedReason = mStorageAllowedReasonCache;
+ }
+ return mStorageAllowedCache;
+ }
+ void SetStorageAllowedCache(const mozilla::StorageAccess& storageAllowed,
+ uint32_t aRejectedReason) {
+ mStorageAllowedCache = Some(storageAllowed);
+ mStorageAllowedReasonCache = aRejectedReason;
+ }
+ void ClearStorageAllowedCache() {
+ mStorageAllowedCache = mozilla::Nothing();
+ mStorageAllowedReasonCache = 0;
+ }
+
+ virtual JS::loader::ModuleLoaderBase* GetModuleLoader(
+ JSContext* aCx) override;
+
+ private:
+ RefPtr<mozilla::dom::ContentMediaController> mContentMediaController;
+
+ RefPtr<mozilla::dom::WebTaskSchedulerMainThread> mWebTaskScheduler;
+
+ protected:
+ // Whether we need to care about orientation changes.
+ bool mHasOrientationChangeListeners : 1;
+
+ // Window offline status. Checked to see if we need to fire offline event
+ bool mWasOffline : 1;
+
+ // Represents whether the inner window's page has had a slow script notice.
+ // Only used by inner windows; will always be false for outer windows.
+ // This is used to implement Telemetry measures such as
+ // SLOW_SCRIPT_PAGE_COUNT.
+ bool mHasHadSlowScript : 1;
+
+ // Fast way to tell if this is a chrome window (without having to QI).
+ bool mIsChrome : 1;
+
+ // Hack to indicate whether a chrome window needs its message manager
+ // to be disconnected, since clean up code is shared in the global
+ // window superclass.
+ bool mCleanMessageManager : 1;
+
+ // Indicates that the current document has never received a document focus
+ // event.
+ bool mNeedsFocus : 1;
+ bool mHasFocus : 1;
+
+ // true if tab navigation has occurred for this window. Focus rings
+ // should be displayed.
+ bool mFocusByKeyOccurred : 1;
+
+ // True if we have notified document-element-inserted observers for this
+ // document.
+ bool mDidFireDocElemInserted : 1;
+
+ // Indicates whether this window wants gamepad input events
+ bool mHasGamepad : 1;
+
+ // Indicates whether this window has content that has an XR session
+ // An XR session results in enumeration and activation of XR devices.
+ bool mHasXRSession : 1;
+
+ // Indicates whether this window wants VRDisplayActivate events
+ bool mHasVRDisplayActivateEvents : 1;
+
+ // Indicates that a request for XR runtime detection has been
+ // requested, but has not yet been resolved
+ bool mXRRuntimeDetectionInFlight : 1;
+
+ // Indicates that an XR permission request has been requested
+ // but has not yet been resolved.
+ bool mXRPermissionRequestInFlight : 1;
+
+ // Indicates that an XR permission request has been granted.
+ // The page should not request permission multiple times.
+ bool mXRPermissionGranted : 1;
+
+ // True if this was the currently-active inner window for a BrowsingContext at
+ // the time it was discarded.
+ bool mWasCurrentInnerWindow : 1;
+ void SetWasCurrentInnerWindow() { mWasCurrentInnerWindow = true; }
+ bool WasCurrentInnerWindow() const override { return mWasCurrentInnerWindow; }
+
+ bool mHasSeenGamepadInput : 1;
+
+ // Whether we told the JS engine that we were in pageload.
+ bool mHintedWasLoading : 1;
+
+ // Whether this window has opened an external-protocol iframe without user
+ // activation once already. Only relevant for top windows.
+ bool mHasOpenedExternalProtocolFrame : 1;
+
+ bool mScrollMarksOnHScrollbar : 1;
+
+ nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
+ nsRefPtrHashtable<nsGenericHashKey<mozilla::dom::GamepadHandle>,
+ mozilla::dom::Gamepad>
+ mGamepads;
+
+ RefPtr<nsScreen> mScreen;
+
+ RefPtr<mozilla::dom::BarProp> mMenubar;
+ RefPtr<mozilla::dom::BarProp> mToolbar;
+ RefPtr<mozilla::dom::BarProp> mLocationbar;
+ RefPtr<mozilla::dom::BarProp> mPersonalbar;
+ RefPtr<mozilla::dom::BarProp> mStatusbar;
+ RefPtr<mozilla::dom::BarProp> mScrollbars;
+
+ RefPtr<nsGlobalWindowObserver> mObserver;
+ RefPtr<mozilla::dom::Crypto> mCrypto;
+ RefPtr<mozilla::dom::cache::CacheStorage> mCacheStorage;
+ RefPtr<mozilla::dom::Console> mConsole;
+ RefPtr<mozilla::dom::Worklet> mPaintWorklet;
+ RefPtr<mozilla::dom::External> mExternal;
+ RefPtr<mozilla::dom::InstallTriggerImpl> mInstallTrigger;
+
+ RefPtr<mozilla::dom::Storage> mLocalStorage;
+ RefPtr<mozilla::dom::Storage> mSessionStorage;
+
+ RefPtr<mozilla::EventListenerManager> mListenerManager;
+ RefPtr<mozilla::dom::Location> mLocation;
+ RefPtr<nsHistory> mHistory;
+ RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
+
+ nsTObserverArray<RefPtr<mozilla::dom::SharedWorker>> mSharedWorkers;
+
+ RefPtr<mozilla::dom::VisualViewport> mVisualViewport;
+
+ // The document's principals and CSP are only stored if
+ // FreeInnerObjects has been called.
+ nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentCookiePrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentPartitionedPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> mDocumentCsp;
+
+ // Used to cache the result of StorageAccess::StorageAllowedForWindow.
+ // Don't use this field directly, use StorageAccess::StorageAllowedForWindow
+ // instead.
+ mozilla::Maybe<mozilla::StorageAccess> mStorageAllowedCache;
+ uint32_t mStorageAllowedReasonCache;
+
+ RefPtr<mozilla::dom::DebuggerNotificationManager>
+ mDebuggerNotificationManager;
+
+ // mBrowserChild is only ever populated in the content process.
+ nsCOMPtr<nsIBrowserChild> mBrowserChild;
+
+ uint32_t mSuspendDepth;
+ uint32_t mFreezeDepth;
+
+#ifdef DEBUG
+ uint32_t mSerial;
+#endif
+
+ // the method that was used to focus mFocusedElement
+ uint32_t mFocusMethod;
+
+ // Only relevant if we're listening for orientation changes.
+ int16_t mOrientationAngle = 0;
+
+ // The current idle request callback handle
+ uint32_t mIdleRequestCallbackCounter;
+ IdleRequests mIdleRequestCallbacks;
+ RefPtr<IdleRequestExecutor> mIdleRequestExecutor;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+ RefPtr<mozilla::dom::IDBFactory> mIndexedDB;
+
+ // This flag keeps track of whether this window is currently
+ // observing refresh notifications from the refresh driver.
+ bool mObservingRefresh;
+
+ bool mIteratingDocumentFlushedResolvers;
+
+ bool TryToObserveRefresh();
+
+ nsTArray<uint32_t> mEnabledSensors;
+
+#ifdef MOZ_WEBSPEECH
+ RefPtr<mozilla::dom::SpeechSynthesis> mSpeechSynthesis;
+#endif
+
+ RefPtr<mozilla::glean::Glean> mGlean;
+ RefPtr<mozilla::glean::GleanPings> mGleanPings;
+
+ // This is the CC generation the last time we called CanSkip.
+ uint32_t mCanSkipCCGeneration;
+
+ // The VR Displays for this window
+ nsTArray<RefPtr<mozilla::dom::VRDisplay>> mVRDisplays;
+
+ RefPtr<mozilla::dom::VREventObserver> mVREventObserver;
+
+ // The number of unload and beforeunload event listeners registered on this
+ // window.
+ uint64_t mUnloadOrBeforeUnloadListenerCount = 0;
+
+ RefPtr<mozilla::dom::IntlUtils> mIntlUtils;
+
+ mozilla::UniquePtr<mozilla::dom::ClientSource> mClientSource;
+
+ nsTArray<mozilla::UniquePtr<PromiseDocumentFlushedResolver>>
+ mDocumentFlushedResolvers;
+
+ nsTArray<uint32_t> mScrollMarks;
+
+ nsTArray<nsWeakPtr> mDataDocumentsForMemoryReporting;
+
+ static InnerWindowByIdTable* sInnerWindowsById;
+
+ // Members in the mChromeFields member should only be used in chrome windows.
+ // All accesses to this field should be guarded by a check of mIsChrome.
+ struct ChromeFields {
+ RefPtr<mozilla::dom::ChromeMessageBroadcaster> mMessageManager;
+ nsRefPtrHashtable<nsStringHashKey, mozilla::dom::ChromeMessageBroadcaster>
+ mGroupMessageManagers{1};
+ } mChromeFields;
+
+ // These fields are used by the inner and outer windows to prevent
+ // programatically moving the window while the mouse is down.
+ static bool sMouseDown;
+ static bool sDragServiceDisabled;
+
+ friend class nsDOMScriptableHelper;
+ friend class nsDOMWindowUtils;
+ friend class mozilla::dom::PostMessageEvent;
+ friend class DesktopNotification;
+ friend class mozilla::dom::TimeoutManager;
+ friend class IdleRequestExecutor;
+ friend class nsGlobalWindowOuter;
+};
+
+inline nsISupports* ToSupports(nsGlobalWindowInner* p) {
+ return static_cast<mozilla::dom::EventTarget*>(p);
+}
+
+inline nsISupports* ToCanonicalSupports(nsGlobalWindowInner* p) {
+ return static_cast<mozilla::dom::EventTarget*>(p);
+}
+
+// XXX: EWW - This is an awful hack - let's not do this
+#include "nsGlobalWindowOuter.h"
+
+inline nsIGlobalObject* nsGlobalWindowInner::GetOwnerGlobal() const {
+ return const_cast<nsGlobalWindowInner*>(this);
+}
+
+inline nsGlobalWindowOuter* nsGlobalWindowInner::GetInProcessTopInternal() {
+ nsGlobalWindowOuter* outer = GetOuterWindowInternal();
+ nsCOMPtr<nsPIDOMWindowOuter> top = outer ? outer->GetInProcessTop() : nullptr;
+ if (top) {
+ return nsGlobalWindowOuter::Cast(top);
+ }
+ return nullptr;
+}
+
+inline nsGlobalWindowOuter*
+nsGlobalWindowInner::GetInProcessScriptableTopInternal() {
+ nsPIDOMWindowOuter* top = GetInProcessScriptableTop();
+ return nsGlobalWindowOuter::Cast(top);
+}
+
+inline nsIScriptContext* nsGlobalWindowInner::GetContextInternal() {
+ if (mOuterWindow) {
+ return GetOuterWindowInternal()->mContext;
+ }
+
+ return nullptr;
+}
+
+inline nsGlobalWindowOuter* nsGlobalWindowInner::GetOuterWindowInternal()
+ const {
+ return nsGlobalWindowOuter::Cast(GetOuterWindow());
+}
+
+#endif /* nsGlobalWindowInner_h___ */
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
new file mode 100644
index 0000000000..1dc31e8c3b
--- /dev/null
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -0,0 +1,7600 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+#include "nsGlobalWindow.h"
+
+#include <algorithm>
+
+#include "mozilla/MemoryReporting.h"
+
+// Local Includes
+#include "Navigator.h"
+#include "mozilla/Encoding.h"
+#include "nsContentSecurityManager.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsScreen.h"
+#include "nsHistory.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIDOMStorageManager.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIWebProgressListener.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/dom/AutoPrintEventDispatcher.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/LocalStorage.h"
+#include "mozilla/dom/LSObject.h"
+#include "mozilla/dom/Storage.h"
+#include "mozilla/dom/MaybeCrossOriginObject.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/StorageNotifierService.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "mozilla/dom/Timeout.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowFeatures.h" // WindowFeatures
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StorageAccessAPIHelper.h"
+#include "nsBaseCommandController.h"
+#include "nsError.h"
+#include "nsICookieService.h"
+#include "nsISizeOfEventTarget.h"
+#include "nsDOMJSUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIScriptContext.h"
+#include "nsWindowMemoryReporter.h"
+#include "nsWindowSizes.h"
+#include "WindowNamedPropertiesHandler.h"
+#include "nsFrameSelection.h"
+#include "nsNetUtil.h"
+#include "nsVariant.h"
+#include "nsPrintfCString.h"
+#include "mozilla/intl/LocaleService.h"
+#include "WindowDestroyedEvent.h"
+#include "nsDocShellLoadState.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+// Helper Classes
+#include "nsJSUtils.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/CallAndConstruct.h" // JS::Call
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
+#include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxy
+#include "js/PropertyAndElement.h" // JS_DefineObject, JS_GetProperty
+#include "js/PropertySpec.h"
+#include "js/RealmIterators.h"
+#include "js/Wrapper.h"
+#include "nsLayoutUtils.h"
+#include "nsReadableUtils.h"
+#include "nsJSEnvironment.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Likely.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+// Other Classes
+#include "mozilla/dom/BarProps.h"
+#include "nsContentCID.h"
+#include "nsLayoutStatics.h"
+#include "nsCCUncollectableMarker.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsJSPrincipals.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Components.h"
+#include "mozilla/Debug.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_full_screen_api.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "AudioChannelService.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE
+#include "PostMessageEvent.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/net/CookieJarSettings.h"
+
+// Interfaces Needed
+#include "nsIFrame.h"
+#include "nsCanvasFrame.h"
+#include "nsIWidget.h"
+#include "nsIWidgetListener.h"
+#include "nsIBaseWindow.h"
+#include "nsIDeviceSensors.h"
+#include "nsIContent.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "Crypto.h"
+#include "nsDOMString.h"
+#include "nsThreadUtils.h"
+#include "nsILoadContext.h"
+#include "nsIScrollableFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIPrompt.h"
+#include "nsIPromptService.h"
+#include "nsIPromptFactory.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWebBrowserFind.h" // For window.find()
+#include "nsComputedDOMStyle.h"
+#include "nsDOMCID.h"
+#include "nsDOMWindowUtils.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIWindowWatcher.h"
+#include "nsIContentViewer.h"
+#include "nsIScriptError.h"
+#include "nsISHistory.h"
+#include "nsIControllers.h"
+#include "nsGlobalWindowCommands.h"
+#include "nsQueryObject.h"
+#include "nsContentUtils.h"
+#include "nsCSSProps.h"
+#include "nsIURIFixup.h"
+#include "nsIURIMutator.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIObserverService.h"
+#include "nsFocusManager.h"
+#include "nsIAppWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "nsIScreenManager.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIXULRuntime.h"
+#include "xpcprivate.h"
+
+#ifdef NS_PRINTING
+# include "nsIPrintSettings.h"
+# include "nsIPrintSettingsService.h"
+# include "nsIWebBrowserPrint.h"
+#endif
+
+#include "nsWindowRoot.h"
+#include "nsNetCID.h"
+#include "nsIArray.h"
+
+#include "nsIDOMXULCommandDispatcher.h"
+
+#include "mozilla/GlobalKeyListener.h"
+
+#include "nsIDragService.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Selection.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsXPCOMCID.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "prenv.h"
+
+#include "mozilla/dom/IDBFactory.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/Promise.h"
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadManager.h"
+
+#include "gfxVR.h"
+#include "VRShMem.h"
+#include "FxRWindowManager.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRDisplayEvent.h"
+#include "mozilla/dom/VRDisplayEventBinding.h"
+#include "mozilla/dom/VREventObserver.h"
+
+#include "nsRefreshDriver.h"
+
+#include "mozilla/extensions/WebExtensionPolicy.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/Location.h"
+#include "nsHTMLDocument.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "prrng.h"
+#include "nsSandboxFlags.h"
+#include "nsXULControllers.h"
+#include "mozilla/dom/AudioContext.h"
+#include "mozilla/dom/BrowserElementDictionariesBinding.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/HashChangeEvent.h"
+#include "mozilla/dom/IntlUtils.h"
+#include "mozilla/dom/PopStateEvent.h"
+#include "mozilla/dom/PopupBlockedEvent.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "nsIBrowserChild.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/dom/Worklet.h"
+#include "AccessCheck.h"
+
+#ifdef MOZ_WEBSPEECH
+# include "mozilla/dom/SpeechSynthesis.h"
+#endif
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using mozilla::BasePrincipal;
+using mozilla::OriginAttributes;
+using mozilla::TimeStamp;
+using mozilla::layout::RemotePrintJobChild;
+
+#define FORWARD_TO_INNER(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (!mInnerWindow) { \
+ NS_WARNING("No inner window available!"); \
+ return err_rval; \
+ } \
+ return GetCurrentInnerWindowInternal()->method args; \
+ PR_END_MACRO
+
+#define FORWARD_TO_INNER_VOID(method, args) \
+ PR_BEGIN_MACRO \
+ if (!mInnerWindow) { \
+ NS_WARNING("No inner window available!"); \
+ return; \
+ } \
+ GetCurrentInnerWindowInternal()->method args; \
+ return; \
+ PR_END_MACRO
+
+// Same as FORWARD_TO_INNER, but this will create a fresh inner if an
+// inner doesn't already exists.
+#define FORWARD_TO_INNER_CREATE(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (!mInnerWindow) { \
+ if (mIsClosed) { \
+ return err_rval; \
+ } \
+ nsCOMPtr<Document> kungFuDeathGrip = GetDoc(); \
+ ::mozilla::Unused << kungFuDeathGrip; \
+ if (!mInnerWindow) { \
+ return err_rval; \
+ } \
+ } \
+ return GetCurrentInnerWindowInternal()->method args; \
+ PR_END_MACRO
+
+static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
+extern LazyLogModule gPageCacheLog;
+
+#ifdef DEBUG
+static LazyLogModule gDocShellAndDOMWindowLeakLogging(
+ "DocShellAndDOMWindowLeak");
+#endif
+
+nsGlobalWindowOuter::OuterWindowByIdTable*
+ nsGlobalWindowOuter::sOuterWindowsById = nullptr;
+
+/* static */
+nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner(
+ nsPIDOMWindowInner* aInner) {
+ if (!aInner) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowOuter* outer = aInner->GetOuterWindow();
+ if (!outer || outer->GetCurrentInnerWindow() != aInner) {
+ return nullptr;
+ }
+
+ return outer;
+}
+
+//*****************************************************************************
+// nsOuterWindowProxy: Outer Window Proxy
+//*****************************************************************************
+
+// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
+// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+//
+// We store the nsGlobalWindowOuter* in our first slot.
+//
+// We store our holder weakmap in the second slot.
+const JSClass OuterWindowProxyClass = PROXY_CLASS_DEF(
+ "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */
+
+static const size_t OUTER_WINDOW_SLOT = 0;
+static const size_t HOLDER_WEAKMAP_SLOT = 1;
+
+class nsOuterWindowProxy : public MaybeCrossOriginObject<js::Wrapper> {
+ using Base = MaybeCrossOriginObject<js::Wrapper>;
+
+ public:
+ constexpr nsOuterWindowProxy() : Base(0) {}
+
+ bool finalizeInBackground(const JS::Value& priv) const override {
+ return false;
+ }
+
+ // Standard internal methods
+ /**
+ * Implementation of [[GetOwnProperty]] as defined at
+ * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ */
+ bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override;
+
+ /*
+ * Implementation of the same-origin case of
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
+ */
+ bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override;
+
+ /**
+ * Implementation of [[OwnPropertyKeys]] as defined at
+ *
+ * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ */
+ bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const override;
+ /**
+ * Implementation of [[Delete]] as defined at
+ * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ */
+ bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const override;
+
+ /**
+ * Implementaton of hook for superclass getPrototype() method.
+ */
+ JSObject* getSameOriginPrototype(JSContext* cx) const override;
+
+ /**
+ * Implementation of [[HasProperty]] internal method as defined at
+ * https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ *
+ * Note that the HTML spec does not define an override for this internal
+ * method, so we just want the "normal object" behavior. We have to override
+ * it, because js::Wrapper also overrides, with "not normal" behavior.
+ */
+ bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ bool* bp) const override;
+
+ /**
+ * Implementation of [[Get]] internal method as defined at
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
+ *
+ * "proxy" is the WindowProxy object involved. It may or may not be
+ * same-compartment with "cx".
+ *
+ * "receiver" is the receiver ("this") for the get. It will be
+ * same-compartment with "cx".
+ *
+ * "vp" is the return value. It will be same-compartment with "cx".
+ */
+ bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override;
+
+ /**
+ * Implementation of [[Set]] internal method as defined at
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
+ *
+ * "proxy" is the WindowProxy object involved. It may or may not be
+ * same-compartment with "cx".
+ *
+ * "v" is the value being set. It will be same-compartment with "cx".
+ *
+ * "receiver" is the receiver ("this") for the set. It will be
+ * same-compartment with "cx".
+ */
+ bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const override;
+
+ // SpiderMonkey extensions
+ /**
+ * Implementation of SpiderMonkey extension which just checks whether this
+ * object has the property. Basically Object.getOwnPropertyDescriptor(obj,
+ * prop) !== undefined. but does not require reifying the descriptor.
+ *
+ * We have to override this because js::Wrapper overrides it, but we want
+ * different behavior from js::Wrapper.
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ */
+ bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ bool* bp) const override;
+
+ /**
+ * Implementation of SpiderMonkey extension which is used as a fast path for
+ * enumerating.
+ *
+ * We have to override this because js::Wrapper overrides it, but we want
+ * different behavior from js::Wrapper.
+ *
+ * "proxy" is the WindowProxy object involved. It may not be same-compartment
+ * with cx.
+ */
+ bool getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const override;
+
+ /**
+ * Hook used by SpiderMonkey to implement Object.prototype.toString.
+ */
+ const char* className(JSContext* cx,
+ JS::Handle<JSObject*> wrapper) const override;
+
+ void finalize(JS::GCContext* gcx, JSObject* proxy) const override;
+ size_t objectMoved(JSObject* proxy, JSObject* old) const override;
+
+ bool isCallable(JSObject* obj) const override { return false; }
+ bool isConstructor(JSObject* obj) const override { return false; }
+
+ static const nsOuterWindowProxy singleton;
+
+ static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) {
+ nsGlobalWindowOuter* outerWindow =
+ nsGlobalWindowOuter::FromSupports(static_cast<nsISupports*>(
+ js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate()));
+ return outerWindow;
+ }
+
+ protected:
+ // False return value means we threw an exception. True return value
+ // but false "found" means we didn't have a subframe at that index.
+ bool GetSubframeWindow(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
+ bool& found) const;
+
+ // Returns a non-null window only if id is an index and we have a
+ // window at that index.
+ Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const;
+
+ bool AppendIndexedPropertyNames(JSObject* proxy,
+ JS::MutableHandleVector<jsid> props) const;
+
+ using MaybeCrossOriginObjectMixins::EnsureHolder;
+ bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> holder) const override;
+
+ // Helper method for creating a special "print" method that allows printing
+ // our PDF-viewer documents even if you're not same-origin with them.
+ //
+ // aProxy must be our nsOuterWindowProxy. It will not be same-compartment
+ // with aCx, since we only use this on the different-origin codepath!
+ //
+ // Can return true without filling in aDesc, which corresponds to not exposing
+ // a "print" method.
+ static bool MaybeGetPDFJSPrintMethod(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc);
+
+ // The actual "print" method we use for the PDFJS case.
+ static bool PDFJSPrintMethod(JSContext* cx, unsigned argc, JS::Value* vp);
+
+ // Helper method to get the pre-PDF-viewer-messing-with-it principal from an
+ // inner window. Will return null if this is not a PDF-viewer inner or if the
+ // principal could not be found for some reason.
+ static already_AddRefed<nsIPrincipal> GetNoPDFJSPrincipal(
+ nsGlobalWindowInner* inner);
+};
+
+const char* nsOuterWindowProxy::className(JSContext* cx,
+ JS::Handle<JSObject*> proxy) const {
+ MOZ_ASSERT(js::IsProxy(proxy));
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return "Object";
+ }
+
+ return "Window";
+}
+
+void nsOuterWindowProxy::finalize(JS::GCContext* gcx, JSObject* proxy) const {
+ nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
+ if (outerWindow) {
+ outerWindow->ClearWrapper(proxy);
+ BrowsingContext* bc = outerWindow->GetBrowsingContext();
+ if (bc) {
+ bc->ClearWindowProxy();
+ }
+
+ // Ideally we would use OnFinalize here, but it's possible that
+ // EnsureScriptEnvironment will later be called on the window, and we don't
+ // want to create a new script object in that case. Therefore, we need to
+ // write a non-null value that will reliably crash when dereferenced.
+ outerWindow->PoisonOuterWindowProxy(proxy);
+ }
+}
+
+bool nsOuterWindowProxy::getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const {
+ // First check for indexed access. This is
+ // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
+ // step 2, mostly.
+ JS::Rooted<JS::Value> subframe(cx);
+ bool found;
+ if (!GetSubframeWindow(cx, proxy, id, &subframe, found)) {
+ return false;
+ }
+ if (found) {
+ // Step 2.4.
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ subframe, {
+ JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Enumerable,
+ })));
+ return true;
+ }
+
+ bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy);
+
+ // If we did not find a subframe, we could still have an indexed property
+ // access. In that case we should throw a SecurityError in the cross-origin
+ // case.
+ if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) {
+ // Step 2.5.2.
+ return ReportCrossOriginDenial(cx, id, "access"_ns);
+ }
+
+ // Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an
+ // IsArrayIndex(GetArrayIndexFromId(id)) here. We'll never have a property on
+ // the Window whose name is an index, because our defineProperty doesn't pass
+ // those on to the Window.
+
+ // Step 3.
+ if (isSameOrigin) {
+ if (StaticPrefs::dom_missing_prop_counters_enabled() && id.isAtom()) {
+ Window_Binding::CountMaybeMissingProperty(proxy, id);
+ }
+
+ // Fall through to js::Wrapper.
+ { // Scope for JSAutoRealm while we are dealing with js::Wrapper.
+ // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+ // for now. That's what js::Wrapper expects, and since we're same-origin
+ // anyway this is not changing any security behavior.
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+ if (!ok) {
+ return false;
+ }
+
+#if 0
+ // See https://github.com/tc39/ecma262/issues/672 for more information.
+ if (desc.isSome() &&
+ !IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
+ (*desc).setConfigurable(true);
+ }
+#endif
+ }
+
+ // Now wrap our descriptor back into the Realm that asked for it.
+ return JS_WrapPropertyDescriptor(cx, desc);
+ }
+
+ // Step 4.
+ if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
+ return false;
+ }
+
+ // Step 5
+ if (desc.isSome()) {
+ return true;
+ }
+
+ // Non-spec step for the PDF viewer's window.print(). This comes before we
+ // check for named subframes, because in the same-origin case print() would
+ // shadow those.
+ if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) {
+ if (!MaybeGetPDFJSPrintMethod(cx, proxy, desc)) {
+ return false;
+ }
+
+ if (desc.isSome()) {
+ return true;
+ }
+ }
+
+ // Step 6 -- check for named subframes.
+ if (id.isString()) {
+ nsAutoJSString name;
+ if (!name.init(cx, id.toString())) {
+ return false;
+ }
+ nsGlobalWindowOuter* win = GetOuterWindow(proxy);
+ if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
+ JS::Rooted<JS::Value> childValue(cx);
+ if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
+ return false;
+ }
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ childValue, {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+ }
+
+ // And step 7.
+ return CrossOriginPropertyFallback(cx, proxy, id, desc);
+}
+
+bool nsOuterWindowProxy::definePropertySameOrigin(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
+ if (IsArrayIndex(GetArrayIndexFromId(id))) {
+ // Spec says to Reject whether this is a supported index or not,
+ // since we have no indexed setter or indexed creator. It is up
+ // to the caller to decide whether to throw a TypeError.
+ return result.failCantDefineWindowElement();
+ }
+
+ JS::ObjectOpResult ourResult;
+ bool ok = js::Wrapper::defineProperty(cx, proxy, id, desc, ourResult);
+ if (!ok) {
+ return false;
+ }
+
+ if (!ourResult.ok()) {
+ // It's possible that this failed because the page got the existing
+ // descriptor (which we force to claim to be configurable) and then tried to
+ // redefine the property with the descriptor it got but a different value.
+ // We want to allow this case to succeed, so check for it and if we're in
+ // that case try again but now with an attempt to define a non-configurable
+ // property.
+ if (!desc.hasConfigurable() || !desc.configurable()) {
+ // The incoming descriptor was not explicitly marked "configurable: true",
+ // so it failed for some other reason. Just propagate that reason out.
+ result = ourResult;
+ return true;
+ }
+
+ JS::Rooted<Maybe<JS::PropertyDescriptor>> existingDesc(cx);
+ ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc);
+ if (!ok) {
+ return false;
+ }
+ if (existingDesc.isNothing() || existingDesc->configurable()) {
+ // We have no existing property, or its descriptor is already configurable
+ // (on the Window itself, where things really can be non-configurable).
+ // So we failed for some other reason, which we should propagate out.
+ result = ourResult;
+ return true;
+ }
+
+ JS::Rooted<JS::PropertyDescriptor> updatedDesc(cx, desc);
+ updatedDesc.setConfigurable(false);
+
+ JS::ObjectOpResult ourNewResult;
+ ok = js::Wrapper::defineProperty(cx, proxy, id, updatedDesc, ourNewResult);
+ if (!ok) {
+ return false;
+ }
+
+ if (!ourNewResult.ok()) {
+ // Twiddling the configurable flag didn't help. Just return this failure
+ // out to the caller.
+ result = ourNewResult;
+ return true;
+ }
+ }
+
+#if 0
+ // See https://github.com/tc39/ecma262/issues/672 for more information.
+ if (desc.hasConfigurable() && !desc.configurable() &&
+ !IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
+ // Give callers a way to detect that they failed to "really" define a
+ // non-configurable property.
+ result.failCantDefineWindowNonConfigurable();
+ return true;
+ }
+#endif
+
+ result.succeed();
+ return true;
+}
+
+bool nsOuterWindowProxy::ownPropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const {
+ // Just our indexed stuff followed by our "normal" own property names.
+ if (!AppendIndexedPropertyNames(proxy, props)) {
+ return false;
+ }
+
+ if (IsPlatformObjectSameOrigin(cx, proxy)) {
+ // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+ // for now. That's what js::Wrapper expects, and since we're same-origin
+ // anyway this is not changing any security behavior.
+ JS::RootedVector<jsid> innerProps(cx);
+ { // Scope for JSAutoRealm so we can mark the ids once we exit it
+ JSAutoRealm ar(cx, proxy);
+ if (!js::Wrapper::ownPropertyKeys(cx, proxy, &innerProps)) {
+ return false;
+ }
+ }
+ for (auto& id : innerProps) {
+ JS_MarkCrossZoneId(cx, id);
+ }
+ return js::AppendUnique(cx, props, innerProps);
+ }
+
+ // In the cross-origin case we purposefully exclude subframe names from the
+ // list of property names we report here.
+ JS::Rooted<JSObject*> holder(cx);
+ if (!EnsureHolder(cx, proxy, &holder)) {
+ return false;
+ }
+
+ JS::RootedVector<jsid> crossOriginProps(cx);
+ if (!js::GetPropertyKeys(cx, holder,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
+ &crossOriginProps) ||
+ !js::AppendUnique(cx, props, crossOriginProps)) {
+ return false;
+ }
+
+ // Add the "print" property if needed.
+ nsGlobalWindowOuter* outer = GetOuterWindow(proxy);
+ nsGlobalWindowInner* inner =
+ nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
+ if (inner) {
+ nsCOMPtr<nsIPrincipal> targetPrincipal = GetNoPDFJSPrincipal(inner);
+ if (targetPrincipal &&
+ nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) {
+ JS::RootedVector<jsid> printProp(cx);
+ if (!printProp.append(GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) ||
+ !js::AppendUnique(cx, props, printProp)) {
+ return false;
+ }
+ }
+ }
+
+ return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
+}
+
+bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const {
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return ReportCrossOriginDenial(cx, id, "delete"_ns);
+ }
+
+ if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+ // Fail (which means throw if strict, else return false).
+ return result.failCantDeleteWindowElement();
+ }
+
+ if (IsArrayIndex(GetArrayIndexFromId(id))) {
+ // Indexed, but not supported. Spec says return true.
+ return result.succeed();
+ }
+
+ // We're same-origin, so it should be safe to enter the Realm of "proxy".
+ // Let's do that, just in case, to avoid cross-compartment issues in our
+ // js::Wrapper caller..
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ return js::Wrapper::delete_(cx, proxy, id, result);
+}
+
+JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const {
+ return Window_Binding::GetProtoObjectHandle(cx);
+}
+
+bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool* bp) const {
+ // We could just directly forward this method to js::BaseProxyHandler, but
+ // that involves reifying the actual property descriptor, which might be more
+ // work than we have to do for has() on the Window.
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ // In the cross-origin case we only have own properties. Just call hasOwn
+ // directly.
+ return hasOwn(cx, proxy, id, bp);
+ }
+
+ if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+ *bp = true;
+ return true;
+ }
+
+ // Just to be safe in terms of compartment asserts, enter the Realm of
+ // "proxy". We're same-origin with it, so this should be safe.
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ return js::Wrapper::has(cx, proxy, id, bp);
+}
+
+bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool* bp) const {
+ // We could just directly forward this method to js::BaseProxyHandler, but
+ // that involves reifying the actual property descriptor, which might be more
+ // work than we have to do for hasOwn() on the Window.
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ // Avoiding reifying the property descriptor here would require duplicating
+ // a bunch of "is this property exposed cross-origin" logic, which is
+ // probably not worth it. Just forward this along to the base
+ // implementation.
+ //
+ // It's very important to not forward this to js::Wrapper, because that will
+ // not do the right security and cross-origin checks and will pass through
+ // the call to the Window.
+ //
+ // The BaseProxyHandler code is OK with this happening without entering the
+ // compartment of "proxy".
+ return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
+ }
+
+ if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+ *bp = true;
+ return true;
+ }
+
+ // Just to be safe in terms of compartment asserts, enter the Realm of
+ // "proxy". We're same-origin with it, so this should be safe.
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ return js::Wrapper::hasOwn(cx, proxy, id, bp);
+}
+
+bool nsOuterWindowProxy::get(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const {
+ if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
+ xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
+ vp.set(JS::ObjectValue(*proxy));
+ return MaybeWrapValue(cx, vp);
+ }
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return CrossOriginGet(cx, proxy, receiver, id, vp);
+ }
+
+ bool found;
+ if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
+ return false;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ if (StaticPrefs::dom_missing_prop_counters_enabled() && id.isAtom()) {
+ Window_Binding::CountMaybeMissingProperty(proxy, id);
+ }
+
+ { // Scope for JSAutoRealm
+ // Enter "proxy"'s Realm. We're in the same-origin case, so this should be
+ // safe.
+ JSAutoRealm ar(cx, proxy);
+
+ JS_MarkCrossZoneId(cx, id);
+
+ JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+ if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+ return false;
+ }
+
+ // Fall through to js::Wrapper.
+ if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) {
+ return false;
+ }
+ }
+
+ // Make sure our return value is in the caller compartment.
+ return MaybeWrapValue(cx, vp);
+}
+
+bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const {
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return CrossOriginSet(cx, proxy, id, v, receiver, result);
+ }
+
+ if (IsArrayIndex(GetArrayIndexFromId(id))) {
+ // Reject the set. It's up to the caller to decide whether to throw a
+ // TypeError. If the caller is strict mode JS code, it'll throw.
+ return result.failReadOnly();
+ }
+
+ // Do the rest in the Realm of "proxy", since we're in the same-origin case.
+ JSAutoRealm ar(cx, proxy);
+ JS::Rooted<JS::Value> wrappedArg(cx, v);
+ if (!MaybeWrapValue(cx, &wrappedArg)) {
+ return false;
+ }
+ JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+ if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+ return false;
+ }
+
+ JS_MarkCrossZoneId(cx, id);
+
+ return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
+}
+
+bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const {
+ // We could just stop overring getOwnEnumerablePropertyKeys and let our
+ // superclasses deal (by falling back on the BaseProxyHandler implementation
+ // that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to
+ // only return the enumerable ones. But maybe there's value in having
+ // somewhat faster for-in iteration on Window objects...
+
+ // Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable
+ // own property names.
+ if (!AppendIndexedPropertyNames(proxy, props)) {
+ return false;
+ }
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ // All the cross-origin properties other than the indexed props are
+ // non-enumerable, so we're done here.
+ return true;
+ }
+
+ // When forwarding to js::Wrapper, we should just enter the Realm of proxy
+ // for now. That's what js::Wrapper expects, and since we're same-origin
+ // anyway this is not changing any security behavior.
+ JS::RootedVector<jsid> innerProps(cx);
+ { // Scope for JSAutoRealm so we can mark the ids once we exit it.
+ JSAutoRealm ar(cx, proxy);
+ if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, &innerProps)) {
+ return false;
+ }
+ }
+
+ for (auto& id : innerProps) {
+ JS_MarkCrossZoneId(cx, id);
+ }
+
+ return js::AppendUnique(cx, props, innerProps);
+}
+
+bool nsOuterWindowProxy::GetSubframeWindow(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp,
+ bool& found) const {
+ Nullable<WindowProxyHolder> frame = GetSubframeWindow(cx, proxy, id);
+ if (frame.IsNull()) {
+ found = false;
+ return true;
+ }
+
+ found = true;
+ return WrapObject(cx, frame.Value(), vp);
+}
+
+Nullable<WindowProxyHolder> nsOuterWindowProxy::GetSubframeWindow(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const {
+ uint32_t index = GetArrayIndexFromId(id);
+ if (!IsArrayIndex(index)) {
+ return nullptr;
+ }
+
+ nsGlobalWindowOuter* win = GetOuterWindow(proxy);
+ return win->IndexedGetterOuter(index);
+}
+
+bool nsOuterWindowProxy::AppendIndexedPropertyNames(
+ JSObject* proxy, JS::MutableHandleVector<jsid> props) const {
+ uint32_t length = GetOuterWindow(proxy)->Length();
+ MOZ_ASSERT(int32_t(length) >= 0);
+ if (!props.reserve(props.length() + length)) {
+ return false;
+ }
+ for (int32_t i = 0; i < int32_t(length); ++i) {
+ if (!props.append(JS::PropertyKey::Int(i))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nsOuterWindowProxy::EnsureHolder(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> holder) const {
+ return EnsureHolder(cx, proxy, HOLDER_WEAKMAP_SLOT,
+ Window_Binding::sCrossOriginProperties, holder);
+}
+
+size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const {
+ nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj);
+ if (outerWindow) {
+ outerWindow->UpdateWrapper(obj, old);
+ BrowsingContext* bc = outerWindow->GetBrowsingContext();
+ if (bc) {
+ bc->UpdateWindowProxy(obj, old);
+ }
+ }
+ return 0;
+}
+
+enum { PDFJS_SLOT_CALLEE = 0 };
+
+// static
+bool nsOuterWindowProxy::MaybeGetPDFJSPrintMethod(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) {
+ MOZ_ASSERT(proxy);
+ MOZ_ASSERT(!desc.isSome());
+
+ nsGlobalWindowOuter* outer = GetOuterWindow(proxy);
+ nsGlobalWindowInner* inner =
+ nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
+ if (!inner) {
+ // No print method to expose.
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> targetPrincipal = GetNoPDFJSPrincipal(inner);
+ if (!targetPrincipal) {
+ // Nothing special to be done.
+ return true;
+ }
+
+ if (!nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) {
+ // Not our origin's PDF document.
+ return true;
+ }
+
+ // Get the function we plan to actually call.
+ JS::Rooted<JSObject*> innerObj(cx, inner->GetGlobalJSObject());
+ if (!innerObj) {
+ // Really should not happen, but ok, let's just return.
+ return true;
+ }
+
+ JS::Rooted<JS::Value> targetFunc(cx);
+ {
+ JSAutoRealm ar(cx, innerObj);
+ if (!JS_GetProperty(cx, innerObj, "print", &targetFunc)) {
+ return false;
+ }
+ }
+
+ if (!targetFunc.isObject()) {
+ // Who knows what's going on. Just return.
+ return true;
+ }
+
+ // The Realm of cx is the realm our caller is in and the realm we
+ // should create our function in. Note that we can't use the
+ // standard XPConnect function forwarder machinery because our
+ // "this" is cross-origin, so we have to do thus by hand.
+
+ // Make sure targetFunc is wrapped into the right compartment.
+ if (!MaybeWrapValue(cx, &targetFunc)) {
+ return false;
+ }
+
+ JSFunction* fun =
+ js::NewFunctionWithReserved(cx, PDFJSPrintMethod, 0, 0, "print");
+ if (!fun) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
+ js::SetFunctionNativeReserved(funObj, PDFJS_SLOT_CALLEE, targetFunc);
+
+ // { value: <print>, writable: true, enumerable: true, configurable: true }
+ // because that's what it would have been in the same-origin case without
+ // the PDF viewer messing with things.
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*funObj),
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+}
+
+// static
+bool nsOuterWindowProxy::PDFJSPrintMethod(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::Rooted<JSObject*> realCallee(
+ cx, &js::GetFunctionNativeReserved(&args.callee(), PDFJS_SLOT_CALLEE)
+ .toObject());
+ // Unchecked unwrap, because we want to extract the thing we really had
+ // before.
+ realCallee = js::UncheckedUnwrap(realCallee);
+
+ JS::Rooted<JS::Value> thisv(cx, args.thisv());
+ if (thisv.isNullOrUndefined()) {
+ // Replace it with the global of our stashed callee, simulating the
+ // global-assuming behavior of DOM methods.
+ JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(realCallee));
+ if (!MaybeWrapObject(cx, &global)) {
+ return false;
+ }
+ thisv.setObject(*global);
+ } else if (!thisv.isObject()) {
+ return ThrowInvalidThis(cx, args, false, prototypes::id::Window);
+ }
+
+ // We want to do an UncheckedUnwrap here, because we're going to directly
+ // examine the principal of the inner window, if we have an inner window.
+ JS::Rooted<JSObject*> unwrappedObj(cx,
+ js::UncheckedUnwrap(&thisv.toObject()));
+ nsGlobalWindowInner* inner = nullptr;
+ {
+ // Do the unwrap in the Realm of the object we're looking at.
+ JSAutoRealm ar(cx, unwrappedObj);
+ UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &unwrappedObj, inner, cx);
+ }
+ if (!inner) {
+ return ThrowInvalidThis(cx, args, false, prototypes::id::Window);
+ }
+
+ nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal(cx);
+ if (!callerPrincipal->SubsumesConsideringDomain(inner->GetPrincipal())) {
+ // Check whether it's a PDF viewer from our origin.
+ nsCOMPtr<nsIPrincipal> pdfPrincipal = GetNoPDFJSPrincipal(inner);
+ if (!pdfPrincipal || !callerPrincipal->Equals(pdfPrincipal)) {
+ // Security error.
+ return ThrowInvalidThis(cx, args, true, prototypes::id::Window);
+ }
+ }
+
+ // Go ahead and enter the Realm of our real callee to call it. We'll pass it
+ // our "thisv", just in case someone grabs a "print" method off one PDF
+ // document and .call()s it on another one.
+ {
+ JSAutoRealm ar(cx, realCallee);
+ if (!MaybeWrapValue(cx, &thisv)) {
+ return false;
+ }
+
+ // Don't bother passing through the args; they will get ignored anyway.
+
+ if (!JS::Call(cx, thisv, realCallee, JS::HandleValueArray::empty(),
+ args.rval())) {
+ return false;
+ }
+ }
+
+ // Wrap the return value (not that there should be any!) into the right
+ // compartment.
+ return MaybeWrapValue(cx, args.rval());
+}
+
+// static
+already_AddRefed<nsIPrincipal> nsOuterWindowProxy::GetNoPDFJSPrincipal(
+ nsGlobalWindowInner* inner) {
+ if (!nsContentUtils::IsPDFJS(inner->GetPrincipal())) {
+ return nullptr;
+ }
+
+ if (Document* doc = inner->GetExtantDoc()) {
+ if (nsCOMPtr<nsIPropertyBag2> propBag =
+ do_QueryInterface(doc->GetChannel())) {
+ nsCOMPtr<nsIPrincipal> principal(
+ do_GetProperty(propBag, u"noPDFJSPrincipal"_ns));
+ return principal.forget();
+ }
+ }
+ return nullptr;
+}
+
+const nsOuterWindowProxy nsOuterWindowProxy::singleton;
+
+class nsChromeOuterWindowProxy : public nsOuterWindowProxy {
+ public:
+ constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() {}
+
+ const char* className(JSContext* cx,
+ JS::Handle<JSObject*> wrapper) const override;
+
+ static const nsChromeOuterWindowProxy singleton;
+};
+
+const char* nsChromeOuterWindowProxy::className(
+ JSContext* cx, JS::Handle<JSObject*> proxy) const {
+ MOZ_ASSERT(js::IsProxy(proxy));
+
+ return "ChromeWindow";
+}
+
+const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton;
+
+static JSObject* NewOuterWindowProxy(JSContext* cx,
+ JS::Handle<JSObject*> global,
+ bool isChrome) {
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+
+ JSAutoRealm ar(cx, global);
+
+ js::WrapperOptions options;
+ options.setClass(&OuterWindowProxyClass);
+ JSObject* obj =
+ js::Wrapper::New(cx, global,
+ isChrome ? &nsChromeOuterWindowProxy::singleton
+ : &nsOuterWindowProxy::singleton,
+ options);
+ MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
+ return obj;
+}
+
+//*****************************************************************************
+//*** nsGlobalWindowOuter: Object Management
+//*****************************************************************************
+
+nsGlobalWindowOuter::nsGlobalWindowOuter(uint64_t aWindowID)
+ : nsPIDOMWindowOuter(aWindowID),
+ mFullscreenHasChangedDuringProcessing(false),
+ mForceFullScreenInWidget(false),
+ mIsClosed(false),
+ mInClose(false),
+ mHavePendingClose(false),
+ mBlockScriptedClosingFlag(false),
+ mWasOffline(false),
+ mCreatingInnerWindow(false),
+ mIsChrome(false),
+ mAllowScriptsToClose(false),
+ mTopLevelOuterContentWindow(false),
+ mDelayedPrintUntilAfterLoad(false),
+ mDelayedCloseForPrinting(false),
+ mShouldDelayPrintUntilAfterLoad(false),
+#ifdef DEBUG
+ mSerial(0),
+ mSetOpenerWindowCalled(false),
+#endif
+ mCleanedUp(false),
+ mCanSkipCCGeneration(0),
+ mAutoActivateVRDisplayID(0) {
+ AssertIsOnMainThread();
+
+ nsLayoutStatics::AddRef();
+
+ // Initialize the PRCList (this).
+ PR_INIT_CLIST(this);
+
+ // |this| is an outer window. Outer windows start out frozen and
+ // remain frozen until they get an inner window.
+ MOZ_ASSERT(IsFrozen());
+
+ // We could have failed the first time through trying
+ // to create the entropy collector, so we should
+ // try to get one until we succeed.
+
+#ifdef DEBUG
+ mSerial = nsContentUtils::InnerOrOuterWindowCreated();
+
+ MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
+ nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
+ static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
+ nullptr));
+#endif
+
+ MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
+ ("DOMWINDOW %p created outer=nullptr", this));
+
+ // Add ourselves to the outer windows list.
+ MOZ_ASSERT(sOuterWindowsById, "Outer Windows hash table must be created!");
+
+ // |this| is an outer window, add to the outer windows list.
+ MOZ_ASSERT(!sOuterWindowsById->Contains(mWindowID),
+ "This window shouldn't be in the hash table yet!");
+ // We seem to see crashes in release builds because of null
+ // |sOuterWindowsById|.
+ if (sOuterWindowsById) {
+ sOuterWindowsById->InsertOrUpdate(mWindowID, this);
+ }
+}
+
+#ifdef DEBUG
+
+/* static */
+void nsGlobalWindowOuter::AssertIsOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+#endif // DEBUG
+
+/* static */
+void nsGlobalWindowOuter::Init() {
+ AssertIsOnMainThread();
+
+ NS_ASSERTION(gDOMLeakPRLogOuter,
+ "gDOMLeakPRLogOuter should have been initialized!");
+
+ sOuterWindowsById = new OuterWindowByIdTable();
+}
+
+nsGlobalWindowOuter::~nsGlobalWindowOuter() {
+ AssertIsOnMainThread();
+
+ if (sOuterWindowsById) {
+ sOuterWindowsById->Remove(mWindowID);
+ }
+
+ nsContentUtils::InnerOrOuterWindowDestroyed();
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) {
+ nsAutoCString url;
+ if (mLastOpenedURI) {
+ url = mLastOpenedURI->GetSpecOrDefault();
+
+ // Data URLs can be very long, so truncate to avoid flooding the log.
+ const uint32_t maxURLLength = 1000;
+ if (url.Length() > maxURLLength) {
+ url.Truncate(maxURLLength);
+ }
+ }
+
+ MOZ_LOG(
+ gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = "
+ "%s]\n",
+ nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
+ static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
+ nullptr, url.get()));
+ }
+#endif
+
+ MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
+ ("DOMWINDOW %p destroyed", this));
+
+ JSObject* proxy = GetWrapperMaybeDead();
+ if (proxy) {
+ if (mBrowsingContext && mBrowsingContext->GetUnbarrieredWindowProxy()) {
+ nsGlobalWindowOuter* outer = nsOuterWindowProxy::GetOuterWindow(
+ mBrowsingContext->GetUnbarrieredWindowProxy());
+ // Check that the current WindowProxy object corresponds to this
+ // nsGlobalWindowOuter, because we don't want to clear the WindowProxy if
+ // we've replaced it with a cross-process WindowProxy.
+ if (outer == this) {
+ mBrowsingContext->ClearWindowProxy();
+ }
+ }
+ js::SetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(nullptr));
+ }
+
+ // An outer window is destroyed with inner windows still possibly
+ // alive, iterate through the inner windows and null out their
+ // back pointer to this outer, and pull them out of the list of
+ // inner windows.
+ //
+ // Our linked list of inner windows both contains (an nsGlobalWindowOuter),
+ // and our inner windows (nsGlobalWindowInners). This means that we need to
+ // use PRCList*. We can then compare that PRCList* to `this` to see if its an
+ // inner or outer window.
+ PRCList* w;
+ while ((w = PR_LIST_HEAD(this)) != this) {
+ PR_REMOVE_AND_INIT_LINK(w);
+ }
+
+ DropOuterWindowDocs();
+
+ // Outer windows are always supposed to call CleanUp before letting themselves
+ // be destroyed.
+ MOZ_ASSERT(mCleanedUp);
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac) ac->RemoveWindowAsListener(this);
+
+ nsLayoutStatics::Release();
+}
+
+// static
+void nsGlobalWindowOuter::ShutDown() {
+ AssertIsOnMainThread();
+
+ delete sOuterWindowsById;
+ sOuterWindowsById = nullptr;
+}
+
+void nsGlobalWindowOuter::DropOuterWindowDocs() {
+ MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
+ mDoc = nullptr;
+ mSuspendedDocs.Clear();
+}
+
+void nsGlobalWindowOuter::CleanUp() {
+ // Guarantee idempotence.
+ if (mCleanedUp) return;
+ mCleanedUp = true;
+
+ StartDying();
+
+ mWindowUtils = nullptr;
+
+ ClearControllers();
+
+ mContext = nullptr; // Forces Release
+ mChromeEventHandler = nullptr; // Forces Release
+ mParentTarget = nullptr;
+ mMessageManager = nullptr;
+
+ mArguments = nullptr;
+}
+
+void nsGlobalWindowOuter::ClearControllers() {
+ if (mControllers) {
+ uint32_t count;
+ mControllers->GetControllerCount(&count);
+
+ while (count--) {
+ nsCOMPtr<nsIController> controller;
+ mControllers->GetControllerAt(count, getter_AddRefs(controller));
+
+ nsCOMPtr<nsIControllerContext> context = do_QueryInterface(controller);
+ if (context) context->SetCommandContext(nullptr);
+ }
+
+ mControllers = nullptr;
+ }
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsISupports
+//*****************************************************************************
+
+// QueryInterface implementation for nsGlobalWindowOuter
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowOuter)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowOuter)
+ NS_INTERFACE_MAP_ENTRY(mozIDOMWindowProxy)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowOuter)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowOuter)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowOuter)
+ if (tmp->IsBlackForCC(false)) {
+ if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
+ return true;
+ }
+ tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
+ if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
+ elm->MarkForCC();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindowOuter)
+ return tmp->IsBlackForCC(true);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindowOuter)
+ return tmp->IsBlackForCC(false);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowOuter)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowOuter)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ nsAutoCString uri;
+ if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
+ uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
+ }
+ SprintfLiteral(name, "nsGlobalWindowOuter # %" PRIu64 " outer %s",
+ tmp->mWindowID, uri.get());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowOuter, tmp->mRefCnt.get())
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDocs)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentCookiePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPartitionedPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
+
+ // Traverse stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
+
+ tmp->TraverseObjectsInGlobal(cb);
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mBrowserDOMWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+ if (sOuterWindowsById) {
+ sOuterWindowsById->Remove(tmp->mWindowID);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDocs)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentCookiePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPartitionedPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
+
+ // Unlink stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
+ if (tmp->mBrowsingContext) {
+ if (tmp->mBrowsingContext->GetUnbarrieredWindowProxy()) {
+ nsGlobalWindowOuter* outer = nsOuterWindowProxy::GetOuterWindow(
+ tmp->mBrowsingContext->GetUnbarrieredWindowProxy());
+ // Check that the current WindowProxy object corresponds to this
+ // nsGlobalWindowOuter, because we don't want to clear the WindowProxy if
+ // we've replaced it with a cross-process WindowProxy.
+ if (outer == tmp) {
+ tmp->mBrowsingContext->ClearWindowProxy();
+ }
+ }
+ tmp->mBrowsingContext = nullptr;
+ }
+
+ tmp->UnlinkObjectsInGlobal();
+
+ if (tmp->IsChromeWindow()) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow)
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowOuter)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+bool nsGlobalWindowOuter::IsBlackForCC(bool aTracingNeeded) {
+ if (!nsCCUncollectableMarker::sGeneration) {
+ return false;
+ }
+
+ // Unlike most wrappers, the outer window wrapper is not a wrapper for
+ // the outer window. Instead, the outer window wrapper holds the inner
+ // window binding object, which in turn holds the nsGlobalWindowInner, which
+ // has a strong reference to the nsGlobalWindowOuter. We're using the
+ // mInnerWindow pointer as a flag for that whole chain.
+ return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
+ (mInnerWindow && HasKnownLiveWrapper())) &&
+ (!aTracingNeeded || HasNothingToTrace(ToSupports(this)));
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIScriptGlobalObject
+//*****************************************************************************
+
+bool nsGlobalWindowOuter::ShouldResistFingerprinting(RFPTarget aTarget) const {
+ if (mDoc) {
+ return mDoc->ShouldResistFingerprinting(aTarget);
+ }
+ return nsContentUtils::ShouldResistFingerprinting(
+ "If we do not have a document then we do not have any context"
+ "to make an informed RFP choice, so we fall back to the global pref",
+ aTarget);
+}
+
+OriginTrials nsGlobalWindowOuter::Trials() const {
+ return mInnerWindow ? nsGlobalWindowInner::Cast(mInnerWindow)->Trials()
+ : OriginTrials();
+}
+
+FontFaceSet* nsGlobalWindowOuter::GetFonts() {
+ if (mDoc) {
+ return mDoc->Fonts();
+ }
+ return nullptr;
+}
+
+nsresult nsGlobalWindowOuter::EnsureScriptEnvironment() {
+ if (GetWrapperPreserveColor()) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(!mCleanedUp);
+
+ NS_ASSERTION(!GetCurrentInnerWindowInternal(),
+ "No cached wrapper, but we have an inner window?");
+ NS_ASSERTION(!mContext, "Will overwrite mContext!");
+
+ // If this window is an [i]frame, don't bother GC'ing when the frame's context
+ // is destroyed since a GC will happen when the frameset or host document is
+ // destroyed anyway.
+ mContext = new nsJSContext(mBrowsingContext->IsTop(), this);
+ return NS_OK;
+}
+
+nsIScriptContext* nsGlobalWindowOuter::GetScriptContext() { return mContext; }
+
+bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) {
+ // We reuse the inner window when:
+ // a. We are currently at our original document.
+ // b. At least one of the following conditions are true:
+ // -- The new document is the same as the old document. This means that we're
+ // getting called from document.open().
+ // -- The new document has the same origin as what we have loaded right now.
+
+ if (!mDoc || !aNewDocument) {
+ return false;
+ }
+
+ if (!mDoc->IsInitialDocument()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIURI> uri;
+ NS_GetURIWithoutRef(mDoc->GetDocumentURI(), getter_AddRefs(uri));
+ NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?");
+ }
+#endif
+
+ // Great, we're the original document, check for one of the other
+ // conditions.
+
+ if (mDoc == aNewDocument) {
+ return true;
+ }
+
+ if (aNewDocument->IsStaticDocument()) {
+ return false;
+ }
+
+ if (BasePrincipal::Cast(mDoc->NodePrincipal())
+ ->FastEqualsConsideringDomain(aNewDocument->NodePrincipal())) {
+ // The origin is the same.
+ return true;
+ }
+
+ return false;
+}
+
+void nsGlobalWindowOuter::SetInitialPrincipal(
+ nsIPrincipal* aNewWindowPrincipal, nsIContentSecurityPolicy* aCSP,
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP) {
+ // We should never create windows with an expanded principal.
+ // If we have a system principal, make sure we're not using it for a content
+ // docshell.
+ // NOTE: Please keep this logic in sync with
+ // nsAppShellService::JustCreateTopWindow
+ if (nsContentUtils::IsExpandedPrincipal(aNewWindowPrincipal) ||
+ (aNewWindowPrincipal->IsSystemPrincipal() &&
+ GetBrowsingContext()->IsContent())) {
+ aNewWindowPrincipal = nullptr;
+ }
+
+ // If there's an existing document, bail if it either:
+ if (mDoc) {
+ // (a) is not an initial about:blank document, or
+ if (!mDoc->IsInitialDocument()) return;
+ // (b) already has the correct principal.
+ if (mDoc->NodePrincipal() == aNewWindowPrincipal) return;
+
+#ifdef DEBUG
+ // If we have a document loaded at this point, it had better be about:blank.
+ // Otherwise, something is really weird. An about:blank page has a
+ // NullPrincipal.
+ bool isNullPrincipal;
+ MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal(
+ &isNullPrincipal)) &&
+ isNullPrincipal);
+#endif
+ }
+
+ // Use the subject (or system) principal as the storage principal too until
+ // the new window finishes navigating and gets a real storage principal.
+ nsDocShell::Cast(GetDocShell())
+ ->CreateAboutBlankContentViewer(aNewWindowPrincipal, aNewWindowPrincipal,
+ aCSP, nullptr,
+ /* aIsInitialDocument */ true, aCOEP);
+
+ if (mDoc) {
+ MOZ_ASSERT(mDoc->IsInitialDocument(),
+ "document should be initial document");
+ }
+
+ RefPtr<PresShell> presShell = GetDocShell()->GetPresShell();
+ if (presShell && !presShell->DidInitialize()) {
+ // Ensure that if someone plays with this document they will get
+ // layout happening.
+ presShell->Initialize();
+ }
+}
+
+#define WINDOWSTATEHOLDER_IID \
+ { \
+ 0x0b917c3e, 0xbd50, 0x4683, { \
+ 0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26 \
+ } \
+ }
+
+class WindowStateHolder final : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID)
+ NS_DECL_ISUPPORTS
+
+ explicit WindowStateHolder(nsGlobalWindowInner* aWindow);
+
+ nsGlobalWindowInner* GetInnerWindow() { return mInnerWindow; }
+
+ void DidRestoreWindow() {
+ mInnerWindow = nullptr;
+ mInnerWindowReflector = nullptr;
+ }
+
+ protected:
+ ~WindowStateHolder();
+
+ nsGlobalWindowInner* mInnerWindow;
+ // We hold onto this to make sure the inner window doesn't go away. The outer
+ // window ends up recalculating it anyway.
+ JS::PersistentRooted<JSObject*> mInnerWindowReflector;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID)
+
+WindowStateHolder::WindowStateHolder(nsGlobalWindowInner* aWindow)
+ : mInnerWindow(aWindow),
+ mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) {
+ MOZ_ASSERT(aWindow, "null window");
+
+ aWindow->Suspend();
+
+ // When a global goes into the bfcache, we disable script.
+ xpc::Scriptability::Get(mInnerWindowReflector).SetWindowAllowsScript(false);
+}
+
+WindowStateHolder::~WindowStateHolder() {
+ if (mInnerWindow) {
+ // This window was left in the bfcache and is now going away. We need to
+ // free it up.
+ // Note that FreeInnerObjects may already have been called on the
+ // inner window if its outer has already had SetDocShell(null)
+ // called.
+ mInnerWindow->FreeInnerObjects();
+ }
+}
+
+NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder)
+
+bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument,
+ SecureContextFlags aFlags) {
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+ if (principal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object
+ // With some modifications to allow for aFlags.
+
+ bool hadNonSecureContextCreator = false;
+
+ if (WindowContext* parentWindow =
+ GetBrowsingContext()->GetParentWindowContext()) {
+ hadNonSecureContextCreator = !parentWindow->GetIsSecureContext();
+ }
+
+ if (hadNonSecureContextCreator) {
+ return false;
+ }
+
+ if (nsContentUtils::HttpsStateIsModern(aDocument)) {
+ return true;
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ // If the NullPrincipal has a valid precursor URI we want to use it to
+ // construct the principal otherwise we fall back to the original document
+ // URI.
+ nsCOMPtr<nsIPrincipal> precursorPrin = principal->GetPrecursorPrincipal();
+ nsCOMPtr<nsIURI> uri = precursorPrin ? precursorPrin->GetURI() : nullptr;
+ if (!uri) {
+ uri = aDocument->GetOriginalURI();
+ }
+ // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so
+ // it doesn't actually matter what we use here, but reusing the document
+ // principal's attributes is convenient.
+ const OriginAttributes& attrs = principal->OriginAttributesRef();
+ // CreateContentPrincipal correctly gets a useful principal for blob: and
+ // other URI_INHERITS_SECURITY_CONTEXT URIs.
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ if (NS_WARN_IF(!principal)) {
+ return false;
+ }
+ }
+
+ return principal->GetIsOriginPotentiallyTrustworthy();
+}
+
+static bool InitializeLegacyNetscapeObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal) {
+ JSAutoRealm ar(aCx, aGlobal);
+
+ // Note: MathJax depends on window.netscape being exposed. See bug 791526.
+ JS::Rooted<JSObject*> obj(aCx);
+ obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr);
+ NS_ENSURE_TRUE(obj, false);
+
+ obj = JS_DefineObject(aCx, obj, "security", nullptr);
+ NS_ENSURE_TRUE(obj, false);
+
+ return true;
+}
+
+struct MOZ_STACK_CLASS CompartmentFinderState {
+ explicit CompartmentFinderState(nsIPrincipal* aPrincipal)
+ : principal(aPrincipal), compartment(nullptr) {}
+
+ // Input: we look for a compartment which is same-origin with the
+ // given principal.
+ nsIPrincipal* principal;
+
+ // Output: We set this member if we find a compartment.
+ JS::Compartment* compartment;
+};
+
+static JS::CompartmentIterResult FindSameOriginCompartment(
+ JSContext* aCx, void* aData, JS::Compartment* aCompartment) {
+ auto* data = static_cast<CompartmentFinderState*>(aData);
+ MOZ_ASSERT(!data->compartment, "Why are we getting called?");
+
+ // If this compartment is not safe to share across globals, don't do
+ // anything with it; in particular we should not be getting a
+ // CompartmentPrivate from such a compartment, because it may be in
+ // the middle of being collected and its CompartmentPrivate may no
+ // longer be valid.
+ if (!js::IsSharableCompartment(aCompartment)) {
+ return JS::CompartmentIterResult::KeepGoing;
+ }
+
+ auto* compartmentPrivate = xpc::CompartmentPrivate::Get(aCompartment);
+ if (!compartmentPrivate->CanShareCompartmentWith(data->principal)) {
+ // Can't reuse this one, keep going.
+ return JS::CompartmentIterResult::KeepGoing;
+ }
+
+ // We have a winner!
+ data->compartment = aCompartment;
+ return JS::CompartmentIterResult::Stop;
+}
+
+static JS::RealmCreationOptions& SelectZone(
+ JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
+ JS::RealmCreationOptions& aOptions) {
+ // Use the shared system compartment for chrome windows.
+ if (aPrincipal->IsSystemPrincipal()) {
+ return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
+ }
+
+ BrowsingContext* bc = aNewInner->GetBrowsingContext();
+ if (bc->IsTop()) {
+ // We're a toplevel load. Use a new zone. This way, when we do
+ // zone-based compartment sharing we won't share compartments
+ // across navigations.
+ return aOptions.setNewCompartmentAndZone();
+ }
+
+ // Find the in-process ancestor highest in the hierarchy.
+ nsGlobalWindowInner* ancestor = nullptr;
+ for (WindowContext* wc = bc->GetParentWindowContext(); wc;
+ wc = wc->GetParentWindowContext()) {
+ if (nsGlobalWindowInner* win = wc->GetInnerWindow()) {
+ ancestor = win;
+ }
+ }
+
+ // If we have an ancestor window, use its zone.
+ if (ancestor && ancestor->GetGlobalJSObject()) {
+ JS::Zone* zone = JS::GetObjectZone(ancestor->GetGlobalJSObject());
+ // Now try to find an existing compartment that's same-origin
+ // with our principal.
+ CompartmentFinderState data(aPrincipal);
+ JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment);
+ if (data.compartment) {
+ return aOptions.setExistingCompartment(data.compartment);
+ }
+ return aOptions.setNewCompartmentInExistingZone(
+ ancestor->GetGlobalJSObject());
+ }
+
+ return aOptions.setNewCompartmentAndZone();
+}
+
+/**
+ * Create a new global object that will be used for an inner window.
+ * Return the native global and an nsISupports 'holder' that can be used
+ * to manage the lifetime of it.
+ */
+static nsresult CreateNativeGlobalForInner(
+ JSContext* aCx, nsGlobalWindowInner* aNewInner, Document* aDocument,
+ JS::MutableHandle<JSObject*> aGlobal, bool aIsSecureContext,
+ bool aDefineSharedArrayBufferConstructor) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aNewInner);
+
+ nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+ MOZ_ASSERT(principal);
+
+ // DOMWindow with nsEP is not supported, we have to make sure
+ // no one creates one accidentally.
+ nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(principal);
+ MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
+
+ JS::RealmOptions options;
+ JS::RealmCreationOptions& creationOptions = options.creationOptions();
+
+ SelectZone(aCx, principal, aNewInner, creationOptions);
+
+ creationOptions.setSecureContext(aIsSecureContext);
+
+ // Define the SharedArrayBuffer global constructor property only if shared
+ // memory may be used and structured-cloned (e.g. through postMessage).
+ //
+ // When the global constructor property isn't defined, the SharedArrayBuffer
+ // constructor can still be reached through Web Assembly. Omitting the global
+ // property just prevents feature-tests from being misled. See bug 1624266.
+ creationOptions.setDefineSharedArrayBufferConstructor(
+ aDefineSharedArrayBufferConstructor);
+
+ // TODO(bug 1834744) we will need some way of passing different targets to the
+ // JS engine
+ xpc::InitGlobalObjectOptions(options, principal->IsSystemPrincipal(),
+ aDocument->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute));
+
+ // Determine if we need the Components object.
+ bool needComponents = principal->IsSystemPrincipal();
+ uint32_t flags = needComponents ? 0 : xpc::OMIT_COMPONENTS_OBJECT;
+ flags |= xpc::DONT_FIRE_ONNEWGLOBALHOOK;
+
+ if (!Window_Binding::Wrap(aCx, aNewInner, aNewInner, options,
+ nsJSPrincipals::get(principal), false, aGlobal) ||
+ !xpc::InitGlobalObject(aCx, aGlobal, flags)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal);
+
+ // Set the location information for the new global, so that tools like
+ // about:memory may use that information
+ xpc::SetLocationForGlobal(aGlobal, uri);
+
+ if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
+ nsISupports* aState,
+ bool aForceReuseInnerWindow,
+ WindowGlobalChild* aActor) {
+ MOZ_ASSERT(mDocumentPrincipal == nullptr,
+ "mDocumentPrincipal prematurely set!");
+ MOZ_ASSERT(mDocumentCookiePrincipal == nullptr,
+ "mDocumentCookiePrincipal prematurely set!");
+ MOZ_ASSERT(mDocumentStoragePrincipal == nullptr,
+ "mDocumentStoragePrincipal prematurely set!");
+ MOZ_ASSERT(mDocumentPartitionedPrincipal == nullptr,
+ "mDocumentPartitionedPrincipal prematurely set!");
+ MOZ_ASSERT(aDocument);
+
+ // Bail out early if we're in process of closing down the window.
+ NS_ENSURE_STATE(!mCleanedUp);
+
+ NS_ASSERTION(!GetCurrentInnerWindow() ||
+ GetCurrentInnerWindow()->GetExtantDoc() == mDoc,
+ "Uh, mDoc doesn't match the current inner window "
+ "document!");
+ bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument);
+ if (aForceReuseInnerWindow && !wouldReuseInnerWindow && mDoc &&
+ mDoc->NodePrincipal() != aDocument->NodePrincipal()) {
+ NS_ERROR("Attempted forced inner window reuse while changing principal");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mBrowsingContext->AncestorsAreCurrent()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Document> oldDoc = mDoc;
+ MOZ_RELEASE_ASSERT(oldDoc != aDocument);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ // Check if we're anywhere near the stack limit before we reach the
+ // transplanting code, since it has no good way to handle errors. This uses
+ // the untrusted script limit, which is not strictly necessary since no
+ // actual script should run.
+ js::AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.checkConservativeDontReport(cx)) {
+ NS_WARNING("Overrecursion in SetNewDocument");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mDoc) {
+ // First document load.
+
+ // Get our private root. If it is equal to us, then we need to
+ // attach our global key bindings that handles browser scrolling
+ // and other browser commands.
+ nsPIDOMWindowOuter* privateRoot = GetPrivateRoot();
+
+ if (privateRoot == this) {
+ RootWindowGlobalKeyListener::AttachKeyHandler(mChromeEventHandler);
+ }
+ }
+
+ MaybeResetWindowName(aDocument);
+
+ /* No mDocShell means we're already been partially closed down. When that
+ happens, setting status isn't a big requirement, so don't. (Doesn't happen
+ under normal circumstances, but bug 49615 describes a case.) */
+
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("nsGlobalWindowOuter::ClearStatus", this,
+ &nsGlobalWindowOuter::ClearStatus));
+
+ // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner
+ // window (see bug 776497). Be safe.
+ bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) &&
+ GetCurrentInnerWindowInternal();
+
+ nsresult rv;
+
+ // We set mDoc even though this is an outer window to avoid
+ // having to *always* reach into the inner window to find the
+ // document.
+ mDoc = aDocument;
+
+ nsDocShell::Cast(mDocShell)->MaybeRestoreWindowName();
+
+ // We drop the print request for the old document on the floor, it never made
+ // it. We don't close the window here either even if we were asked to.
+ mShouldDelayPrintUntilAfterLoad = true;
+ mDelayedCloseForPrinting = false;
+ mDelayedPrintUntilAfterLoad = false;
+
+ // Take this opportunity to clear mSuspendedDocs. Our old inner window is now
+ // responsible for unsuspending it.
+ mSuspendedDocs.Clear();
+
+#ifdef DEBUG
+ mLastOpenedURI = aDocument->GetDocumentURI();
+#endif
+
+ RefPtr<nsGlobalWindowInner> currentInner = GetCurrentInnerWindowInternal();
+
+ if (currentInner && currentInner->mNavigator) {
+ currentInner->mNavigator->OnNavigation();
+ }
+
+ RefPtr<nsGlobalWindowInner> newInnerWindow;
+ bool createdInnerWindow = false;
+
+ bool thisChrome = IsChromeWindow();
+
+ nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState);
+ NS_ASSERTION(!aState || wsh,
+ "What kind of weird state are you giving me here?");
+
+ bool doomCurrentInner = false;
+
+ // Only non-gray (i.e. exposed to JS) objects should be assigned to
+ // newInnerGlobal.
+ JS::Rooted<JSObject*> newInnerGlobal(cx);
+ if (reUseInnerWindow) {
+ // We're reusing the current inner window.
+ NS_ASSERTION(!currentInner->IsFrozen(),
+ "We should never be reusing a shared inner window");
+ newInnerWindow = currentInner;
+ newInnerGlobal = currentInner->GetWrapper();
+
+ // We're reusing the inner window, but this still counts as a navigation,
+ // so all expandos and such defined on the outer window should go away.
+ // Force all Xray wrappers to be recomputed.
+ JS::Rooted<JSObject*> rootedObject(cx, GetWrapper());
+ if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Inner windows are only reused for same-origin principals, but the
+ // principals don't necessarily match exactly. Update the principal on the
+ // realm to match the new document. NB: We don't just call
+ // currentInner->RefreshRealmPrincipals() here because we haven't yet set
+ // its mDoc to aDocument.
+ JS::Realm* realm = js::GetNonCCWObjectRealm(newInnerGlobal);
+#ifdef DEBUG
+ bool sameOrigin = false;
+ nsIPrincipal* existing = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
+ aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
+ MOZ_ASSERT(sameOrigin);
+#endif
+ JS::SetRealmPrincipals(realm,
+ nsJSPrincipals::get(aDocument->NodePrincipal()));
+ } else {
+ if (aState) {
+ newInnerWindow = wsh->GetInnerWindow();
+ newInnerGlobal = newInnerWindow->GetWrapper();
+ } else {
+ newInnerWindow = nsGlobalWindowInner::Create(this, thisChrome, aActor);
+ if (StaticPrefs::dom_timeout_defer_during_load()) {
+ // ensure the initial loading state is known
+ newInnerWindow->SetActiveLoadingState(
+ aDocument->GetReadyStateEnum() ==
+ Document::ReadyState::READYSTATE_LOADING);
+ }
+
+ // The outer window is automatically treated as frozen when we
+ // null out the inner window. As a result, initializing classes
+ // on the new inner won't end up reaching into the old inner
+ // window for classes etc.
+ //
+ // [This happens with Object.prototype when XPConnect creates
+ // a temporary global while initializing classes; the reason
+ // being that xpconnect creates the temp global w/o a parent
+ // and proto, which makes the JS engine look up classes in
+ // cx->globalObject, i.e. this outer window].
+
+ mInnerWindow = nullptr;
+
+ mCreatingInnerWindow = true;
+
+ // The SharedArrayBuffer global constructor property should not be present
+ // in a fresh global object when shared memory objects aren't allowed
+ // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
+ // act to isolate this page to a separate process).
+
+ // Every script context we are initialized with must create a
+ // new global.
+ rv = CreateNativeGlobalForInner(
+ cx, newInnerWindow, aDocument, &newInnerGlobal,
+ ComputeIsSecureContext(aDocument),
+ newInnerWindow->IsSharedMemoryAllowedInternal(
+ aDocument->NodePrincipal()));
+ NS_ASSERTION(
+ NS_SUCCEEDED(rv) && newInnerGlobal &&
+ newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal,
+ "Failed to get script global");
+
+ mCreatingInnerWindow = false;
+ createdInnerWindow = true;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentInner && currentInner->GetWrapperPreserveColor()) {
+ // Don't free objects on our current inner window if it's going to be
+ // held in the bfcache.
+ if (!currentInner->IsFrozen()) {
+ doomCurrentInner = true;
+ }
+ }
+
+ mInnerWindow = newInnerWindow;
+ MOZ_ASSERT(mInnerWindow);
+ mInnerWindow->TryToCacheTopInnerWindow();
+
+ if (!GetWrapperPreserveColor()) {
+ JS::Rooted<JSObject*> outer(
+ cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
+ NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
+
+ mBrowsingContext->CleanUpDanglingRemoteOuterWindowProxies(cx, &outer);
+ MOZ_ASSERT(js::IsWindowProxy(outer));
+
+ js::SetProxyReservedSlot(outer, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(ToSupports(this)));
+
+ // Inform the nsJSContext, which is the canonical holder of the outer.
+ mContext->SetWindowProxy(outer);
+
+ SetWrapper(mContext->GetWindowProxy());
+ } else {
+ JS::Rooted<JSObject*> outerObject(
+ cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
+ if (!outerObject) {
+ NS_ERROR("out of memory");
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> obj(cx, GetWrapper());
+
+ MOZ_ASSERT(js::IsWindowProxy(obj));
+
+ js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(nullptr));
+ js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(nullptr));
+ js::SetProxyReservedSlot(obj, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue());
+
+ outerObject = xpc::TransplantObjectNukingXrayWaiver(cx, obj, outerObject);
+
+ if (!outerObject) {
+ mBrowsingContext->ClearWindowProxy();
+ NS_ERROR("unable to transplant wrappers, probably OOM");
+ return NS_ERROR_FAILURE;
+ }
+
+ js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(ToSupports(this)));
+
+ SetWrapper(outerObject);
+
+ MOZ_ASSERT(JS::GetNonCCWObjectGlobal(outerObject) == newInnerGlobal);
+
+ // Inform the nsJSContext, which is the canonical holder of the outer.
+ mContext->SetWindowProxy(outerObject);
+ }
+
+ // Enter the new global's realm.
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ {
+ JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor());
+ js::SetWindowProxy(cx, newInnerGlobal, outer);
+ mBrowsingContext->SetWindowProxy(outer);
+ }
+
+ // Set scriptability based on the state of the WindowContext.
+ WindowContext* wc = mInnerWindow->GetWindowContext();
+ bool allow =
+ wc ? wc->CanExecuteScripts() : mBrowsingContext->CanExecuteScripts();
+ xpc::Scriptability::Get(GetWrapperPreserveColor())
+ .SetWindowAllowsScript(allow);
+
+ if (!aState) {
+ // Get the "window" property once so it will be cached on our inner. We
+ // have to do this here, not in binding code, because this has to happen
+ // after we've created the outer window proxy and stashed it in the outer
+ // nsGlobalWindowOuter, so GetWrapperPreserveColor() on that outer
+ // nsGlobalWindowOuter doesn't return null and
+ // nsGlobalWindowOuter::OuterObject works correctly.
+ JS::Rooted<JS::Value> unused(cx);
+ if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) {
+ NS_ERROR("can't create the 'window' property");
+ return NS_ERROR_FAILURE;
+ }
+
+ // And same thing for the "self" property.
+ if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) {
+ NS_ERROR("can't create the 'self' property");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ if (!aState && !reUseInnerWindow) {
+ // Loading a new page and creating a new inner window, *not*
+ // restoring from session history.
+
+ // Now that both the the inner and outer windows are initialized
+ // let the script context do its magic to hook them together.
+ MOZ_ASSERT(mContext->GetWindowProxy() == GetWrapperPreserveColor());
+#ifdef DEBUG
+ JS::Rooted<JSObject*> rootedJSObject(cx, GetWrapperPreserveColor());
+ JS::Rooted<JSObject*> proto1(cx), proto2(cx);
+ JS_GetPrototype(cx, rootedJSObject, &proto1);
+ JS_GetPrototype(cx, newInnerGlobal, &proto2);
+ NS_ASSERTION(proto1 == proto2,
+ "outer and inner globals should have the same prototype");
+#endif
+
+ mInnerWindow->SyncStateFromParentWindow();
+ }
+
+ // Add an extra ref in case we release mContext during GC.
+ nsCOMPtr<nsIScriptContext> kungFuDeathGrip(mContext);
+
+ // Make sure the inner's document is set correctly before we call
+ // SetScriptGlobalObject, because that might try to examine document-dependent
+ // state. Unfortunately, we can't do some of the other clearing/resetting
+ // work we do below until after SetScriptGlobalObject(), because it might
+ // depend on the document having the right scope object.
+ if (aState) {
+ MOZ_RELEASE_ASSERT(newInnerWindow->mDoc == aDocument);
+ } else {
+ if (reUseInnerWindow) {
+ MOZ_RELEASE_ASSERT(newInnerWindow->mDoc != aDocument);
+ }
+ newInnerWindow->mDoc = aDocument;
+ }
+
+ aDocument->SetScriptGlobalObject(newInnerWindow);
+
+ MOZ_RELEASE_ASSERT(newInnerWindow->mDoc == aDocument);
+
+ if (!aState) {
+ if (reUseInnerWindow) {
+ // The StorageAccess state may have changed. Invalidate the cached
+ // StorageAllowed field, so that the next call to StorageAllowedForWindow
+ // recomputes it.
+ newInnerWindow->ClearStorageAllowedCache();
+
+ // The storage objects contain the URL of the window. We have to
+ // recreate them when the innerWindow is reused.
+ newInnerWindow->mLocalStorage = nullptr;
+ newInnerWindow->mSessionStorage = nullptr;
+ newInnerWindow->mPerformance = nullptr;
+
+ // This must be called after nullifying the internal objects because
+ // here we could recreate them, calling the getter methods, and store
+ // them into the JS slots. If we nullify them after, the slot values and
+ // the objects will be out of sync.
+ newInnerWindow->ClearDocumentDependentSlots(cx);
+ } else {
+ newInnerWindow->InitDocumentDependentState(cx);
+
+ // Initialize DOM classes etc on the inner window.
+ JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
+ rv = kungFuDeathGrip->InitClasses(obj);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // When replacing an initial about:blank document we call
+ // ExecutionReady again to update the client creation URL.
+ rv = newInnerWindow->ExecutionReady();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mArguments) {
+ newInnerWindow->DefineArgumentsProperty(mArguments);
+ mArguments = nullptr;
+ }
+
+ // Give the new inner window our chrome event handler (since it
+ // doesn't have one).
+ newInnerWindow->mChromeEventHandler = mChromeEventHandler;
+ }
+
+ if (!aState && reUseInnerWindow) {
+ // Notify our WindowGlobalChild that it has a new document. If `aState` was
+ // passed, we're restoring the window from the BFCache, so the document
+ // hasn't changed.
+ // If we didn't have a window global child before, then initializing
+ // it will have set all the required state, so we don't need to do
+ // it again.
+ mInnerWindow->GetWindowGlobalChild()->OnNewDocument(aDocument);
+ }
+
+ // Update the current window for our BrowsingContext.
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+
+ if (bc->IsOwnedByProcess()) {
+ MOZ_ALWAYS_SUCCEEDS(bc->SetCurrentInnerWindowId(mInnerWindow->WindowID()));
+ }
+
+ // We no longer need the old inner window. Start its destruction if
+ // its not being reused and clear our reference.
+ if (doomCurrentInner) {
+ currentInner->FreeInnerObjects();
+ }
+ currentInner = nullptr;
+
+ // We wait to fire the debugger hook until the window is all set up and hooked
+ // up with the outer. See bug 969156.
+ if (createdInnerWindow) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(
+ "nsGlobalWindowInner::FireOnNewGlobalObject", newInnerWindow,
+ &nsGlobalWindowInner::FireOnNewGlobalObject));
+ }
+
+ if (newInnerWindow && !newInnerWindow->mHasNotifiedGlobalCreated && mDoc) {
+ // We should probably notify. However if this is the, arguably bad,
+ // situation when we're creating a temporary non-chrome-about-blank
+ // document in a chrome docshell, don't notify just yet. Instead wait
+ // until we have a real chrome doc.
+ const bool isContentAboutBlankInChromeDocshell = [&] {
+ if (!mDocShell) {
+ return false;
+ }
+
+ RefPtr<BrowsingContext> bc = mDocShell->GetBrowsingContext();
+ if (!bc || bc->GetType() != BrowsingContext::Type::Chrome) {
+ return false;
+ }
+
+ return !mDoc->NodePrincipal()->IsSystemPrincipal();
+ }();
+
+ if (!isContentAboutBlankInChromeDocshell) {
+ newInnerWindow->mHasNotifiedGlobalCreated = true;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(
+ "nsGlobalWindowOuter::DispatchDOMWindowCreated", this,
+ &nsGlobalWindowOuter::DispatchDOMWindowCreated));
+ }
+ }
+
+ PreloadLocalStorage();
+
+ // Do this here rather than in say the Document constructor, since
+ // we need a WindowContext available.
+ mDoc->InitUseCounters();
+
+ return NS_OK;
+}
+
+/* static */
+void nsGlobalWindowOuter::PrepareForProcessChange(JSObject* aProxy) {
+ JS::Rooted<JSObject*> localProxy(RootingCx(), aProxy);
+ MOZ_ASSERT(js::IsWindowProxy(localProxy));
+
+ RefPtr<nsGlobalWindowOuter> outerWindow =
+ nsOuterWindowProxy::GetOuterWindow(localProxy);
+ if (!outerWindow) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JSAutoRealm ar(cx, localProxy);
+
+ // Clear out existing references from the browsing context and outer window to
+ // the proxy, and from the proxy to the outer window. These references will
+ // become invalid once the proxy is transplanted. Clearing the window proxy
+ // from the browsing context is also necessary to indicate that it is for an
+ // out of process window.
+ outerWindow->ClearWrapper(localProxy);
+ RefPtr<BrowsingContext> bc = outerWindow->GetBrowsingContext();
+ MOZ_ASSERT(bc);
+ MOZ_ASSERT(bc->GetWindowProxy() == localProxy);
+ bc->ClearWindowProxy();
+ js::SetProxyReservedSlot(localProxy, OUTER_WINDOW_SLOT,
+ JS::PrivateValue(nullptr));
+ js::SetProxyReservedSlot(localProxy, HOLDER_WEAKMAP_SLOT,
+ JS::UndefinedValue());
+
+ // Create a new remote outer window proxy, and transplant to it.
+ JS::Rooted<JSObject*> remoteProxy(cx);
+
+ if (!mozilla::dom::GetRemoteOuterWindowProxy(cx, bc, localProxy,
+ &remoteProxy)) {
+ MOZ_CRASH("PrepareForProcessChange GetRemoteOuterWindowProxy");
+ }
+
+ if (!xpc::TransplantObjectNukingXrayWaiver(cx, localProxy, remoteProxy)) {
+ MOZ_CRASH("PrepareForProcessChange TransplantObject");
+ }
+}
+
+void nsGlobalWindowOuter::PreloadLocalStorage() {
+ if (!Storage::StoragePrefIsEnabled()) {
+ return;
+ }
+
+ if (IsChromeWindow()) {
+ return;
+ }
+
+ nsIPrincipal* principal = GetPrincipal();
+ nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
+ if (!principal || !storagePrincipal) {
+ return;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDOMStorageManager> storageManager =
+ do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // private browsing windows do not persist local storage to disk so we should
+ // only try to precache storage when we're not a private browsing window.
+ if (principal->GetPrivateBrowsingId() == 0) {
+ RefPtr<Storage> storage;
+ rv = storageManager->PrecacheStorage(principal, storagePrincipal,
+ getter_AddRefs(storage));
+ if (NS_SUCCEEDED(rv)) {
+ mLocalStorage = storage;
+ }
+ }
+}
+
+void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
+ if (!mDoc) {
+ return;
+ }
+
+ // Fire DOMWindowCreated at chrome event listeners
+ nsContentUtils::DispatchChromeEvent(mDoc, ToSupports(mDoc),
+ u"DOMWindowCreated"_ns, CanBubble::eYes,
+ Cancelable::eNo);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ // The event dispatching could possibly cause docshell destory, and
+ // consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(),
+ // so check it again here.
+ if (observerService && mDoc) {
+ nsAutoString origin;
+ nsIPrincipal* principal = mDoc->NodePrincipal();
+ nsContentUtils::GetUTFOrigin(principal, origin);
+ observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this),
+ principal->IsSystemPrincipal()
+ ? "chrome-document-global-created"
+ : "content-document-global-created",
+ origin.get());
+ }
+}
+
+void nsGlobalWindowOuter::ClearStatus() { SetStatusOuter(u""_ns); }
+
+void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) {
+ MOZ_ASSERT(aDocShell);
+
+ if (aDocShell == mDocShell) {
+ return;
+ }
+
+ mDocShell = aDocShell;
+ mBrowsingContext = aDocShell->GetBrowsingContext();
+
+ RefPtr<BrowsingContext> parentContext = mBrowsingContext->GetParent();
+
+ MOZ_RELEASE_ASSERT(!parentContext ||
+ GetBrowsingContextGroup() == parentContext->Group());
+
+ mTopLevelOuterContentWindow = mBrowsingContext->IsTopContent();
+
+ // Get our enclosing chrome shell and retrieve its global window impl, so
+ // that we can do some forwarding to the chrome document.
+ RefPtr<EventTarget> chromeEventHandler;
+ mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
+ mChromeEventHandler = chromeEventHandler;
+ if (!mChromeEventHandler) {
+ // We have no chrome event handler. If we have a parent,
+ // get our chrome event handler from the parent. If
+ // we don't have a parent, then we need to make a new
+ // window root object that will function as a chrome event
+ // handler and receive all events that occur anywhere inside
+ // our window.
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetInProcessParent();
+ if (parentWindow.get() != this) {
+ mChromeEventHandler = parentWindow->GetChromeEventHandler();
+ } else {
+ mChromeEventHandler = NS_NewWindowRoot(this);
+ mIsRootOuterWindow = true;
+ }
+ }
+
+ SetIsBackgroundInternal(!mBrowsingContext->IsActive());
+}
+
+void nsGlobalWindowOuter::DetachFromDocShell(bool aIsBeingDiscarded) {
+ // DetachFromDocShell means the window is being torn down. Drop our
+ // reference to the script context, allowing it to be deleted
+ // later. Meanwhile, keep our weak reference to the script object
+ // so that it can be retrieved later (until it is finalized by the JS GC).
+
+ if (mDoc && DocGroup::TryToLoadIframesInBackground()) {
+ DocGroup* docGroup = GetDocGroup();
+ RefPtr<nsIDocShell> docShell = GetDocShell();
+ RefPtr<nsDocShell> dShell = nsDocShell::Cast(docShell);
+ if (dShell) {
+ docGroup->TryFlushIframePostMessages(dShell->GetOuterWindowID());
+ }
+ }
+
+ // Call FreeInnerObjects on all inner windows, not just the current
+ // one, since some could be held by WindowStateHolder objects that
+ // are GC-owned.
+ RefPtr<nsGlobalWindowInner> inner;
+ for (PRCList* node = PR_LIST_HEAD(this); node != this;
+ node = PR_NEXT_LINK(inner)) {
+ // This cast is safe because `node != this`. Non-this nodes are inner
+ // windows.
+ inner = static_cast<nsGlobalWindowInner*>(node);
+ MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == this);
+ inner->FreeInnerObjects();
+ }
+
+ // Don't report that we were detached to the nsWindowMemoryReporter, as it
+ // only tracks inner windows.
+
+ NotifyWindowIDDestroyed("outer-window-destroyed");
+
+ nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();
+
+ if (currentInner) {
+ NS_ASSERTION(mDoc, "Must have doc!");
+
+ // Remember the document's principal and URI.
+ mDocumentPrincipal = mDoc->NodePrincipal();
+ mDocumentCookiePrincipal = mDoc->EffectiveCookiePrincipal();
+ mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
+ mDocumentPartitionedPrincipal = mDoc->PartitionedPrincipal();
+ mDocumentURI = mDoc->GetDocumentURI();
+
+ // Release our document reference
+ DropOuterWindowDocs();
+ }
+
+ ClearControllers();
+
+ mChromeEventHandler = nullptr; // force release now
+
+ if (mContext) {
+ // When we're about to destroy a top level content window
+ // (for example a tab), we trigger a full GC by passing null as the last
+ // param. We also trigger a full GC for chrome windows.
+ nsJSContext::PokeGC(JS::GCReason::SET_DOC_SHELL,
+ (mTopLevelOuterContentWindow || mIsChrome)
+ ? nullptr
+ : GetWrapperPreserveColor());
+ mContext = nullptr;
+ }
+
+ if (aIsBeingDiscarded) {
+ // If our BrowsingContext is being discarded, make a note that our current
+ // inner window was active at the time it went away.
+ if (GetCurrentInnerWindow()) {
+ GetCurrentInnerWindowInternal()->SetWasCurrentInnerWindow();
+ }
+ }
+
+ mDocShell = nullptr;
+ mBrowsingContext->ClearDocShell();
+
+ CleanUp();
+}
+
+void nsGlobalWindowOuter::UpdateParentTarget() {
+ // NOTE: This method is nearly identical to
+ // nsGlobalWindowInner::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
+ // UPDATE THE OTHER ONE TOO! The one difference is that this method updates
+ // mMessageManager as well, which inner windows don't have.
+
+ // Try to get our frame element's tab child global (its in-process message
+ // manager). If that fails, fall back to the chrome event handler's tab
+ // child global, and if it doesn't have one, just use the chrome event
+ // handler itself.
+
+ nsCOMPtr<Element> frameElement = GetFrameElementInternal();
+ mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
+
+ if (!mMessageManager) {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+ if (topWin) {
+ frameElement = topWin->GetFrameElementInternal();
+ mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
+ }
+ }
+
+ if (!mMessageManager) {
+ mMessageManager =
+ nsContentUtils::TryGetBrowserChildGlobal(mChromeEventHandler);
+ }
+
+ if (mMessageManager) {
+ mParentTarget = mMessageManager;
+ } else {
+ mParentTarget = mChromeEventHandler;
+ }
+}
+
+EventTarget* nsGlobalWindowOuter::GetTargetForEventTargetChain() {
+ return GetCurrentInnerWindowInternal();
+}
+
+void nsGlobalWindowOuter::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ MOZ_CRASH("The outer window should not be part of an event path");
+}
+
+bool nsGlobalWindowOuter::ShouldPromptToBlockDialogs() {
+ if (!nsContentUtils::GetCurrentJSContext()) {
+ return false; // non-scripted caller.
+ }
+
+ BrowsingContextGroup* group = GetBrowsingContextGroup();
+ if (!group) {
+ return true;
+ }
+
+ return group->DialogsAreBeingAbused();
+}
+
+bool nsGlobalWindowOuter::AreDialogsEnabled() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("AreDialogsEnabled() called without a browsing context group?");
+ return false;
+ }
+
+ // Dialogs are blocked if the content viewer is hidden
+ if (mDocShell) {
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+
+ bool isHidden;
+ cv->GetIsHidden(&isHidden);
+ if (isHidden) {
+ return false;
+ }
+ }
+
+ // Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS
+ // (or if we have no document, of course). Which document? Who knows; the
+ // spec is daft. See <https://github.com/whatwg/html/issues/1206>. For now
+ // just go ahead and check mDoc, since in everything except edge cases in
+ // which a frame is allow-same-origin but not allow-scripts and is being poked
+ // at by some other window this should be the right thing anyway.
+ if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
+ return false;
+ }
+
+ return group->GetAreDialogsEnabled();
+}
+
+bool nsGlobalWindowOuter::ConfirmDialogIfNeeded() {
+ NS_ENSURE_TRUE(mDocShell, false);
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/prompter;1");
+
+ if (!promptSvc) {
+ return true;
+ }
+
+ // Reset popup state while opening a modal dialog, and firing events
+ // about the dialog, to prevent the current state from being active
+ // the whole time a modal dialog is open.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ bool disableDialog = false;
+ nsAutoString label, title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogLabel", label);
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogPreventTitle", title);
+ promptSvc->Confirm(this, title.get(), label.get(), &disableDialog);
+ if (disableDialog) {
+ DisableDialogs();
+ return false;
+ }
+
+ return true;
+}
+
+void nsGlobalWindowOuter::DisableDialogs() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("DisableDialogs() called without a browsing context group?");
+ return;
+ }
+
+ if (group) {
+ group->SetAreDialogsEnabled(false);
+ }
+}
+
+void nsGlobalWindowOuter::EnableDialogs() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("EnableDialogs() called without a browsing context group?");
+ return;
+ }
+
+ if (group) {
+ group->SetAreDialogsEnabled(true);
+ }
+}
+
+nsresult nsGlobalWindowOuter::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ MOZ_CRASH("The outer window should not be part of an event path");
+}
+
+void nsGlobalWindowOuter::PoisonOuterWindowProxy(JSObject* aObject) {
+ if (aObject == GetWrapperMaybeDead()) {
+ PoisonWrapper();
+ }
+}
+
+nsresult nsGlobalWindowOuter::SetArguments(nsIArray* aArguments) {
+ nsresult rv;
+
+ // We've now mostly separated them, but the difference is still opaque to
+ // nsWindowWatcher (the caller of SetArguments in this little back-and-forth
+ // embedding waltz we do here).
+ //
+ // So we need to demultiplex the two cases here.
+ nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();
+
+ mArguments = aArguments;
+ rv = currentInner->DefineArgumentsProperty(aArguments);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIScriptObjectPrincipal
+//*****************************************************************************
+
+nsIPrincipal* nsGlobalWindowOuter::GetPrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->NodePrincipal();
+ }
+
+ if (mDocumentPrincipal) {
+ return mDocumentPrincipal;
+ }
+
+ // If we don't have a principal and we don't have a document we
+ // ask the parent window for the principal. This can happen when
+ // loading a frameset that has a <frame src="javascript:xxx">, in
+ // that case the global window is used in JS before we've loaded
+ // a document into the window.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetPrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowOuter::GetEffectiveCookiePrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->EffectiveCookiePrincipal();
+ }
+
+ if (mDocumentCookiePrincipal) {
+ return mDocumentCookiePrincipal;
+ }
+
+ // If we don't have a cookie principal and we don't have a document we ask
+ // the parent window for the cookie principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetEffectiveCookiePrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowOuter::GetEffectiveStoragePrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->EffectiveStoragePrincipal();
+ }
+
+ if (mDocumentStoragePrincipal) {
+ return mDocumentStoragePrincipal;
+ }
+
+ // If we don't have a storage principal and we don't have a document we ask
+ // the parent window for the storage principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetEffectiveStoragePrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowOuter::PartitionedPrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->PartitionedPrincipal();
+ }
+
+ if (mDocumentPartitionedPrincipal) {
+ return mDocumentPartitionedPrincipal;
+ }
+
+ // If we don't have a partitioned principal and we don't have a document we
+ // ask the parent window for the partitioned principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->PartitionedPrincipal();
+ }
+
+ return nullptr;
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIDOMWindow
+//*****************************************************************************
+
+Element* nsPIDOMWindowOuter::GetFrameElementInternal() const {
+ return mFrameElement;
+}
+
+void nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement) {
+ mFrameElement = aFrameElement;
+}
+
+Navigator* nsGlobalWindowOuter::GetNavigator() {
+ FORWARD_TO_INNER(Navigator, (), nullptr);
+}
+
+nsScreen* nsGlobalWindowOuter::GetScreen() {
+ FORWARD_TO_INNER(GetScreen, (IgnoreErrors()), nullptr);
+}
+
+void nsPIDOMWindowOuter::ActivateMediaComponents() {
+ if (!ShouldDelayMediaFromStart()) {
+ return;
+ }
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("nsPIDOMWindowOuter, ActiveMediaComponents, "
+ "no longer to delay media from start, this = %p\n",
+ this));
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ Unused << bc->Top()->SetShouldDelayMediaFromStart(false);
+ }
+ NotifyResumingDelayedMedia();
+}
+
+bool nsPIDOMWindowOuter::ShouldDelayMediaFromStart() const {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc && bc->Top()->GetShouldDelayMediaFromStart();
+}
+
+void nsPIDOMWindowOuter::NotifyResumingDelayedMedia() {
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->NotifyResumingDelayedMedia(this);
+ }
+}
+
+bool nsPIDOMWindowOuter::GetAudioMuted() const {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc && bc->Top()->GetMuted();
+}
+
+void nsPIDOMWindowOuter::RefreshMediaElementsVolume() {
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ // TODO: RefreshAgentsVolume can probably be simplified further.
+ service->RefreshAgentsVolume(this, 1.0f, GetAudioMuted());
+ }
+}
+
+mozilla::dom::BrowsingContextGroup*
+nsPIDOMWindowOuter::GetBrowsingContextGroup() const {
+ return mBrowsingContext ? mBrowsingContext->Group() : nullptr;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetParentOuter() {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc ? bc->GetParent(IgnoreErrors()) : nullptr;
+}
+
+/**
+ * GetInProcessScriptableParent used to be called when a script read
+ * window.parent. Under Fission, that is now handled by
+ * BrowsingContext::GetParent, and the result is a WindowProxyHolder rather than
+ * an actual global window. This method still exists for legacy callers which
+ * relied on the old logic, and require in-process windows. However, it only
+ * works correctly when no out-of-process frames exist between this window and
+ * the top-level window, so it should not be used in new code.
+ *
+ * In contrast to GetRealParent, GetInProcessScriptableParent respects <iframe
+ * mozbrowser> boundaries, so if |this| is contained by an <iframe
+ * mozbrowser>, we will return |this| as its own parent.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableParent() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ if (BrowsingContext* parentBC = GetBrowsingContext()->GetParent()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> parent = parentBC->GetDOMWindow()) {
+ return parent;
+ }
+ }
+ return this;
+}
+
+/**
+ * Behavies identically to GetInProcessScriptableParent extept that it returns
+ * null if GetInProcessScriptableParent would return this window.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableParentOrNull() {
+ nsPIDOMWindowOuter* parent = GetInProcessScriptableParent();
+ return (nsGlobalWindowOuter::Cast(parent) == this) ? nullptr : parent;
+}
+
+/**
+ * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
+ * GetRealParent.
+ */
+already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetInProcessParent() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> parent;
+ mDocShell->GetSameTypeInProcessParentIgnoreBrowserBoundaries(
+ getter_AddRefs(parent));
+
+ if (parent) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
+ return win.forget();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(this);
+ return win.forget();
+}
+
+static nsresult GetTopImpl(nsGlobalWindowOuter* aWin, nsIURI* aURIBeingLoaded,
+ nsPIDOMWindowOuter** aTop, bool aScriptable,
+ bool aExcludingExtensionAccessibleContentFrames) {
+ *aTop = nullptr;
+
+ MOZ_ASSERT_IF(aExcludingExtensionAccessibleContentFrames, !aScriptable);
+
+ // Walk up the parent chain.
+
+ nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin;
+ nsCOMPtr<nsPIDOMWindowOuter> parent = aWin;
+ do {
+ if (!parent) {
+ break;
+ }
+
+ prevParent = parent;
+
+ if (aScriptable) {
+ parent = parent->GetInProcessScriptableParent();
+ } else {
+ parent = parent->GetInProcessParent();
+ }
+
+ if (aExcludingExtensionAccessibleContentFrames) {
+ if (auto* p = nsGlobalWindowOuter::Cast(parent)) {
+ nsGlobalWindowInner* currentInner = p->GetCurrentInnerWindowInternal();
+ nsIURI* uri = prevParent->GetDocumentURI();
+ if (!uri) {
+ // If our parent doesn't have a URI yet, we have a document that is in
+ // the process of being loaded. In that case, our caller is
+ // responsible for passing in the URI for the document that is being
+ // loaded, so we fall back to using that URI here.
+ uri = aURIBeingLoaded;
+ }
+
+ if (currentInner && uri) {
+ // If we find an inner window, we better find the uri for the current
+ // window we're looking at. If we can't find it directly, it is the
+ // responsibility of our caller to provide it to us.
+ MOZ_DIAGNOSTIC_ASSERT(uri);
+
+ // If the new parent has permission to load the current page, we're
+ // at a moz-extension:// frame which has a host permission that allows
+ // it to load the document that we've loaded. In that case, stop at
+ // this frame and consider it the top-level frame.
+ //
+ // Note that it's possible for the set of URIs accepted by
+ // AddonAllowsLoad() to change at runtime, but we don't need to cache
+ // the result of this check, since the important consumer of this code
+ // (which is nsIHttpChannelInternal.topWindowURI) already caches the
+ // result after computing it the first time.
+ if (BasePrincipal::Cast(p->GetPrincipal())
+ ->AddonAllowsLoad(uri, true)) {
+ parent = prevParent;
+ break;
+ }
+ }
+ }
+ }
+
+ } while (parent != prevParent);
+
+ if (parent) {
+ parent.swap(*aTop);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * GetInProcessScriptableTop used to be called when a script read window.top.
+ * Under Fission, that is now handled by BrowsingContext::Top, and the result is
+ * a WindowProxyHolder rather than an actual global window. This method still
+ * exists for legacy callers which relied on the old logic, and require
+ * in-process windows. However, it only works correctly when no out-of-process
+ * frames exist between this window and the top-level window, so it should not
+ * be used in new code.
+ *
+ * In contrast to GetRealTop, GetInProcessScriptableTop respects <iframe
+ * mozbrowser> boundaries. If we encounter a window owned by an <iframe
+ * mozbrowser> while walking up the window hierarchy, we'll stop and return that
+ * window.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableTop() {
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
+ /* aScriptable = */ true,
+ /* aExcludingExtensionAccessibleContentFrames = */ false);
+ return window.get();
+}
+
+already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetInProcessTop() {
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
+ /* aScriptable = */ false,
+ /* aExcludingExtensionAccessibleContentFrames = */ false);
+ return window.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindowOuter::GetTopExcludingExtensionAccessibleContentFrames(
+ nsIURI* aURIBeingLoaded) {
+ // There is a parent-process equivalent of this in DocumentLoadListener.cpp
+ // GetTopWindowExcludingExtensionAccessibleContentFrames
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, aURIBeingLoaded, getter_AddRefs(window),
+ /* aScriptable = */ false,
+ /* aExcludingExtensionAccessibleContentFrames = */ true);
+ return window.forget();
+}
+
+void nsGlobalWindowOuter::GetContentOuter(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> content = GetContentInternal(aCallerType, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (!content) {
+ aRetval.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JS::Value> val(aCx);
+ if (!ToJSValue(aCx, WindowProxyHolder{content}, &val)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ MOZ_ASSERT(val.isObjectOrNull());
+ aRetval.set(val.toObjectOrNull());
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetContentInternal(
+ CallerType aCallerType, ErrorResult& aError) {
+ // First check for a named frame named "content"
+ if (RefPtr<BrowsingContext> named = GetChildWindow(u"content"_ns)) {
+ return named.forget();
+ }
+
+ // If we're in the parent process, and being called by system code, `content`
+ // should return the current primary content frame (if it's in-process).
+ //
+ // We return `nullptr` if the current primary content frame is out-of-process,
+ // rather than a remote window proxy, as that is the existing behaviour as of
+ // bug 1597437.
+ if (XRE_IsParentProcess() && aCallerType == CallerType::System) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> primaryContent;
+ treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
+ if (!primaryContent) {
+ return nullptr;
+ }
+
+ return do_AddRef(primaryContent->GetBrowsingContext());
+ }
+
+ // For legacy untrusted callers we always return the same value as
+ // `window.top`
+ if (mDoc && aCallerType != CallerType::System) {
+ mDoc->WarnOnceAbout(DeprecatedOperations::eWindowContentUntrusted);
+ }
+
+ MOZ_ASSERT(mBrowsingContext->IsContent());
+ return do_AddRef(mBrowsingContext->Top());
+}
+
+nsresult nsGlobalWindowOuter::GetPrompter(nsIPrompt** aPrompt) {
+ if (!mDocShell) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell));
+ NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE);
+
+ prompter.forget(aPrompt);
+ return NS_OK;
+}
+
+bool nsGlobalWindowOuter::GetClosedOuter() {
+ // If someone called close(), or if we don't have a docshell, we're closed.
+ return mIsClosed || !mDocShell;
+}
+
+bool nsGlobalWindowOuter::Closed() { return GetClosedOuter(); }
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::IndexedGetterOuter(
+ uint32_t aIndex) {
+ BrowsingContext* bc = GetBrowsingContext();
+ NS_ENSURE_TRUE(bc, nullptr);
+
+ Span<RefPtr<BrowsingContext>> children = bc->NonSyntheticChildren();
+
+ if (aIndex < children.Length()) {
+ return WindowProxyHolder(children[aIndex]);
+ }
+ return nullptr;
+}
+
+nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
+ if (!mControllers) {
+ mControllers = new nsXULControllers();
+ if (!mControllers) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Add in the default controller
+ RefPtr<nsBaseCommandController> commandController =
+ nsBaseCommandController::CreateWindowController();
+ if (!commandController) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mControllers->InsertControllerAt(0, commandController);
+ commandController->SetCommandContext(static_cast<nsIDOMWindow*>(this));
+ }
+
+ return mControllers;
+}
+
+nsresult nsGlobalWindowOuter::GetControllers(nsIControllers** aResult) {
+ FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED);
+}
+
+already_AddRefed<BrowsingContext>
+nsGlobalWindowOuter::GetOpenerBrowsingContext() {
+ RefPtr<BrowsingContext> opener = GetBrowsingContext()->GetOpener();
+ MOZ_DIAGNOSTIC_ASSERT(!opener ||
+ opener->Group() == GetBrowsingContext()->Group());
+ if (!opener || opener->Group() != GetBrowsingContext()->Group()) {
+ return nullptr;
+ }
+
+ // Catch the case where we're chrome but the opener is not...
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode() &&
+ GetPrincipal() == nsContentUtils::GetSystemPrincipal()) {
+ auto* openerWin = nsGlobalWindowOuter::Cast(opener->GetDOMWindow());
+ if (!openerWin ||
+ openerWin->GetPrincipal() != nsContentUtils::GetSystemPrincipal()) {
+ return nullptr;
+ }
+ }
+
+ return opener.forget();
+}
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetSameProcessOpener() {
+ if (RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext()) {
+ return opener->GetDOMWindow();
+ }
+ return nullptr;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetOpenerWindowOuter() {
+ if (RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext()) {
+ return WindowProxyHolder(std::move(opener));
+ }
+ return nullptr;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetOpener() {
+ return GetOpenerWindowOuter();
+}
+
+void nsGlobalWindowOuter::GetStatusOuter(nsAString& aStatus) {
+ aStatus = mStatus;
+}
+
+void nsGlobalWindowOuter::SetStatusOuter(const nsAString& aStatus) {
+ mStatus = aStatus;
+
+ // We don't support displaying window.status in the UI, so there's nothing
+ // left to do here.
+}
+
+void nsGlobalWindowOuter::GetNameOuter(nsAString& aName) {
+ if (mDocShell) {
+ mDocShell->GetName(aName);
+ }
+}
+
+void nsGlobalWindowOuter::SetNameOuter(const nsAString& aName,
+ mozilla::ErrorResult& aError) {
+ if (mDocShell) {
+ aError = mDocShell->SetName(aName);
+ }
+}
+
+// NOTE: The idea of this function is that it should return the same as
+// nsPresContext::CSSToDeviceScale() if it was in aWindow synchronously. For
+// that, we use the UnscaledDevicePixelsPerCSSPixel() (which contains the device
+// scale and the OS zoom scale) and then account for the browsing context full
+// zoom. See the declaration of this function for context about why this is
+// needed.
+CSSToLayoutDeviceScale nsGlobalWindowOuter::CSSToDevScaleForBaseWindow(
+ nsIBaseWindow* aWindow) {
+ MOZ_ASSERT(aWindow);
+ auto scale = aWindow->UnscaledDevicePixelsPerCSSPixel();
+ if (mBrowsingContext) {
+ scale.scale *= mBrowsingContext->FullZoom();
+ }
+ return scale;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerSize(CSSSize& aSize) {
+ EnsureSizeAndPositionUpToDate();
+
+ NS_ENSURE_STATE(mDocShell);
+
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ PresShell* presShell = mDocShell->GetPresShell();
+
+ if (!presContext || !presShell) {
+ aSize = {};
+ return NS_OK;
+ }
+
+ // Whether or not the css viewport has been overridden, we can get the
+ // correct value by looking at the visible area of the presContext.
+ if (RefPtr<nsViewManager> viewManager = presShell->GetViewManager()) {
+ viewManager->FlushDelayedResize();
+ }
+
+ // FIXME: Bug 1598487 - Return the layout viewport instead of the ICB.
+ nsSize viewportSize = presContext->GetVisibleArea().Size();
+ if (presContext->GetDynamicToolbarState() == DynamicToolbarState::Collapsed) {
+ viewportSize =
+ nsLayoutUtils::ExpandHeightForViewportUnits(presContext, viewportSize);
+ }
+
+ aSize = CSSPixel::FromAppUnits(viewportSize);
+
+ if (StaticPrefs::dom_innerSize_rounded()) {
+ aSize.width = std::roundf(aSize.width);
+ aSize.height = std::roundf(aSize.height);
+ }
+
+ return NS_OK;
+}
+
+double nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return size.width;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerWidth(double* aInnerWidth) {
+ FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::SetInnerSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError) {
+ if (!mDocShell) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ CSSIntCoord length(aLengthCSSPixels);
+
+ CheckSecurityWidthAndHeight((aIsWidth ? &length.value : nullptr),
+ (aIsWidth ? nullptr : &length.value),
+ aCallerType);
+
+ RefPtr<PresShell> presShell = mDocShell->GetPresShell();
+
+ // Setting inner size should set the CSS viewport. If the CSS viewport
+ // has been overridden, change the override.
+ if (presShell && presShell->UsesMobileViewportSizing()) {
+ RefPtr<nsPresContext> presContext;
+ presContext = presShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ if (aIsWidth) {
+ shellArea.width = CSSPixel::ToAppUnits(CSSCoord(length));
+ } else {
+ shellArea.height = CSSPixel::ToAppUnits(CSSCoord(length));
+ }
+
+ SetCSSViewportWidthAndHeight(shellArea.Width(), shellArea.Height());
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord valueDev = (CSSCoord(length) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> width, height;
+ if (aIsWidth) {
+ width.emplace(valueDev);
+ } else {
+ height.emplace(valueDev);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Inner, Nothing(), Nothing(), width, height});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetInnerWidthOuter(double aInnerWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetInnerSize(NSToIntRound(ToZeroIfNonfinite(aInnerWidth)),
+ /* aIsWidth */ true, aCallerType, aError);
+}
+
+double nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return size.height;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerHeight(double* aInnerHeight) {
+ FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::SetInnerHeightOuter(double aInnerHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetInnerSize(NSToIntRound(ToZeroIfNonfinite(aInnerHeight)),
+ /* aIsWidth */ false, aCallerType, aError);
+}
+
+CSSIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
+ ErrorResult& aError) {
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return RoundedToInt(size);
+ }
+
+ // Windows showing documents in RDM panes and any subframes within them
+ // return the simulated device size.
+ if (mDoc) {
+ Maybe<CSSIntSize> deviceSize = GetRDMDeviceSize(*mDoc);
+ if (deviceSize.isSome()) {
+ return *deviceSize;
+ }
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return {};
+ }
+
+ return RoundedToInt(treeOwnerAsWin->GetSize() /
+ CSSToDevScaleForBaseWindow(treeOwnerAsWin));
+}
+
+int32_t nsGlobalWindowOuter::GetOuterWidthOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetOuterSize(aCallerType, aError).width;
+}
+
+int32_t nsGlobalWindowOuter::GetOuterHeightOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetOuterSize(aCallerType, aError).height;
+}
+
+void nsGlobalWindowOuter::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
+ aIsWidth ? nullptr : &aLengthCSSPixels,
+ aCallerType);
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord value =
+ (CSSCoord(CSSIntCoord(aLengthCSSPixels)) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> width, height;
+ if (aIsWidth) {
+ width.emplace(value);
+ } else {
+ height.emplace(value);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Outer, Nothing(), Nothing(), width, height});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetOuterWidthOuter(int32_t aOuterWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetOuterSize(aOuterWidth, true, aCallerType, aError);
+}
+
+void nsGlobalWindowOuter::SetOuterHeightOuter(int32_t aOuterHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetOuterSize(aOuterHeight, false, aCallerType, aError);
+}
+
+CSSPoint nsGlobalWindowOuter::ScreenEdgeSlop() {
+ if (NS_WARN_IF(!mDocShell)) {
+ return {};
+ }
+ RefPtr<nsPresContext> pc = mDocShell->GetPresContext();
+ if (NS_WARN_IF(!pc)) {
+ return {};
+ }
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+ if (NS_WARN_IF(!widget)) {
+ return {};
+ }
+ LayoutDeviceIntPoint pt = widget->GetScreenEdgeSlop();
+ auto auPoint =
+ LayoutDeviceIntPoint::ToAppUnits(pt, pc->AppUnitsPerDevPixel());
+ return CSSPoint::FromAppUnits(auPoint);
+}
+
+CSSIntPoint nsGlobalWindowOuter::GetScreenXY(CallerType aCallerType,
+ ErrorResult& aError) {
+ // When resisting fingerprinting, always return (0,0)
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return CSSIntPoint(0, 0);
+ }
+
+ LayoutDeviceIntPoint windowPos;
+ aError = treeOwnerAsWin->GetPosition(&windowPos.x.value, &windowPos.y.value);
+
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (!presContext) {
+ // XXX Fishy LayoutDevice to CSS conversion?
+ return CSSIntPoint(windowPos.x, windowPos.y);
+ }
+
+ nsDeviceContext* context = presContext->DeviceContext();
+ auto windowPosAppUnits = LayoutDeviceIntPoint::ToAppUnits(
+ windowPos, context->AppUnitsPerDevPixel());
+ return CSSIntPoint::FromAppUnitsRounded(windowPosAppUnits);
+}
+
+int32_t nsGlobalWindowOuter::GetScreenXOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetScreenXY(aCallerType, aError).x;
+}
+
+nsRect nsGlobalWindowOuter::GetInnerScreenRect() {
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ EnsureSizeAndPositionUpToDate();
+
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nsRect();
+ }
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return nsRect();
+ }
+
+ return rootFrame->GetScreenRectInAppUnits();
+}
+
+Maybe<CSSIntSize> nsGlobalWindowOuter::GetRDMDeviceSize(
+ const Document& aDocument) {
+ // RDM device size should reflect the simulated device resolution, and
+ // be independent of any full zoom or resolution zoom applied to the
+ // content. To get this value, we get the "unscaled" browser child size,
+ // and divide by the full zoom. "Unscaled" in this case means unscaled
+ // from device to screen but it has been affected (multiplied) by the
+ // full zoom and we need to compensate for that.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ // Bug 1576256: This does not work for cross-process subframes.
+ const Document* topInProcessContentDoc =
+ aDocument.GetTopLevelContentDocumentIfSameProcess();
+ BrowsingContext* bc = topInProcessContentDoc
+ ? topInProcessContentDoc->GetBrowsingContext()
+ : nullptr;
+ if (bc && bc->InRDMPane()) {
+ nsIDocShell* docShell = topInProcessContentDoc->GetDocShell();
+ if (docShell) {
+ nsPresContext* presContext = docShell->GetPresContext();
+ if (presContext) {
+ nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild();
+ if (child) {
+ // We intentionally use GetFullZoom here instead of
+ // GetDeviceFullZoom, because the unscaledInnerSize is based
+ // on the full zoom and not the device full zoom (which is
+ // rounded to result in integer device pixels).
+ float zoom = presContext->GetFullZoom();
+ BrowserChild* bc = static_cast<BrowserChild*>(child.get());
+ CSSSize unscaledSize = bc->GetUnscaledInnerSize();
+ return Some(CSSIntSize(gfx::RoundedToInt(unscaledSize / zoom)));
+ }
+ }
+ }
+ }
+ return Nothing();
+}
+
+float nsGlobalWindowOuter::GetMozInnerScreenXOuter(CallerType aCallerType) {
+ // When resisting fingerprinting, always return 0.
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
+}
+
+float nsGlobalWindowOuter::GetMozInnerScreenYOuter(CallerType aCallerType) {
+ // Return 0 to prevent fingerprinting.
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
+}
+
+void nsGlobalWindowOuter::SetScreenCoord(int32_t aCoordCSSPixels, bool aIsX,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CheckSecurityLeftAndTop(aIsX ? &aCoordCSSPixels : nullptr,
+ aIsX ? nullptr : &aCoordCSSPixels, aCallerType);
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord coord =
+ (CSSCoord(CSSIntCoord(aCoordCSSPixels)) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> x, y;
+ if (aIsX) {
+ x.emplace(coord);
+ } else {
+ y.emplace(coord);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Outer, x, y, Nothing(), Nothing()});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetScreenXOuter(int32_t aScreenX,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetScreenCoord(aScreenX, /* aIsX */ true, aCallerType, aError);
+}
+
+int32_t nsGlobalWindowOuter::GetScreenYOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetScreenXY(aCallerType, aError).y;
+}
+
+void nsGlobalWindowOuter::SetScreenYOuter(int32_t aScreenY,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetScreenCoord(aScreenY, /* aIsX */ false, aCallerType, aError);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void nsGlobalWindowOuter::CheckSecurityWidthAndHeight(int32_t* aWidth,
+ int32_t* aHeight,
+ CallerType aCallerType) {
+ if (aCallerType != CallerType::System) {
+ // if attempting to resize the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+ }
+
+ // This one is easy. Just ensure the variable is greater than 100;
+ if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
+ // Check security state for use in determing window dimensions
+
+ if (aCallerType != CallerType::System) {
+ // sec check failed
+ if (aWidth && *aWidth < 100) {
+ *aWidth = 100;
+ }
+ if (aHeight && *aHeight < 100) {
+ *aHeight = 100;
+ }
+ }
+ }
+}
+
+// NOTE: Arguments to this function should have values in app units
+void nsGlobalWindowOuter::SetCSSViewportWidthAndHeight(nscoord aInnerWidth,
+ nscoord aInnerHeight) {
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ shellArea.SetHeight(aInnerHeight);
+ shellArea.SetWidth(aInnerWidth);
+
+ // FIXME(emilio): This doesn't seem to be ok, this doesn't reflow or
+ // anything... Should go through PresShell::ResizeReflow.
+ //
+ // But I don't think this can be reached by content, as we don't allow to set
+ // inner{Width,Height}.
+ presContext->SetVisibleArea(shellArea);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void nsGlobalWindowOuter::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop,
+ CallerType aCallerType) {
+ // This one is harder. We have to get the screen size and window dimensions.
+
+ // Check security state for use in determing window dimensions
+
+ if (aCallerType != CallerType::System) {
+ // if attempting to move the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+
+ if (nsGlobalWindowOuter* rootWindow =
+ nsGlobalWindowOuter::Cast(GetPrivateRoot())) {
+ rootWindow->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ RefPtr<nsScreen> screen = GetScreen();
+
+ if (treeOwnerAsWin && screen) {
+ CSSToLayoutDeviceScale scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntRect winRect =
+ CSSIntRect::Round(treeOwnerAsWin->GetPositionAndSize() / scale);
+
+ // Get the screen dimensions
+ // XXX This should use nsIScreenManager once it's fully fleshed out.
+ int32_t screenLeft = screen->GetAvailLeft(IgnoreErrors());
+ int32_t screenWidth = screen->GetAvailWidth(IgnoreErrors());
+ int32_t screenHeight = screen->GetAvailHeight(IgnoreErrors());
+#if defined(XP_MACOSX)
+ /* The mac's coordinate system is different from the assumed Windows'
+ system. It offsets by the height of the menubar so that a window
+ placed at (0,0) will be entirely visible. Unfortunately that
+ correction is made elsewhere (in Widget) and the meaning of
+ the Avail... coordinates is overloaded. Here we allow a window
+ to be placed at (0,0) because it does make sense to do so.
+ */
+ int32_t screenTop = screen->GetTop(IgnoreErrors());
+#else
+ int32_t screenTop = screen->GetAvailTop(IgnoreErrors());
+#endif
+
+ if (aLeft) {
+ if (screenLeft + screenWidth < *aLeft + winRect.width)
+ *aLeft = screenLeft + screenWidth - winRect.width;
+ if (screenLeft > *aLeft) *aLeft = screenLeft;
+ }
+ if (aTop) {
+ if (screenTop + screenHeight < *aTop + winRect.height)
+ *aTop = screenTop + screenHeight - winRect.height;
+ if (screenTop > *aTop) *aTop = screenTop;
+ }
+ } else {
+ if (aLeft) *aLeft = 0;
+ if (aTop) *aTop = 0;
+ }
+ }
+}
+
+int32_t nsGlobalWindowOuter::GetScrollBoundaryOuter(Side aSide) {
+ FlushPendingNotifications(FlushType::Layout);
+ if (nsIScrollableFrame* sf = GetScrollFrame()) {
+ return nsPresContext::AppUnitsToIntCSSPixels(
+ sf->GetScrollRange().Edge(aSide));
+ }
+ return 0;
+}
+
+CSSPoint nsGlobalWindowOuter::GetScrollXY(bool aDoFlush) {
+ if (aDoFlush) {
+ FlushPendingNotifications(FlushType::Layout);
+ } else {
+ EnsureSizeAndPositionUpToDate();
+ }
+
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsPoint scrollPos = sf->GetScrollPosition();
+ if (scrollPos != nsPoint(0, 0) && !aDoFlush) {
+ // Oh, well. This is the expensive case -- the window is scrolled and we
+ // didn't actually flush yet. Repeat, but with a flush, since the content
+ // may get shorter and hence our scroll position may decrease.
+ return GetScrollXY(true);
+ }
+
+ return CSSPoint::FromAppUnits(scrollPos);
+}
+
+double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }
+
+double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
+
+uint32_t nsGlobalWindowOuter::Length() {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc ? bc->NonSyntheticChildren().Length() : 0;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc ? bc->GetTop(IgnoreErrors()) : nullptr;
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
+ const nsAString& aName) {
+ NS_ENSURE_TRUE(mBrowsingContext, nullptr);
+ NS_ENSURE_TRUE(mInnerWindow, nullptr);
+ NS_ENSURE_TRUE(mInnerWindow->GetWindowGlobalChild(), nullptr);
+
+ return do_AddRef(mBrowsingContext->FindChildWithName(
+ aName, *mInnerWindow->GetWindowGlobalChild()));
+}
+
+bool nsGlobalWindowOuter::DispatchCustomEvent(
+ const nsAString& aEventName, ChromeOnlyDispatch aChromeOnlyDispatch) {
+ bool defaultActionEnabled = true;
+
+ if (aChromeOnlyDispatch == ChromeOnlyDispatch::eYes) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ mDoc, ToSupports(this), aEventName, CanBubble::eYes, Cancelable::eYes,
+ &defaultActionEnabled);
+ } else {
+ nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
+ CanBubble::eYes, Cancelable::eYes,
+ &defaultActionEnabled);
+ }
+
+ return defaultActionEnabled;
+}
+
+bool nsGlobalWindowOuter::DispatchResizeEvent(const CSSIntSize& aSize) {
+ ErrorResult res;
+ RefPtr<Event> domEvent =
+ mDoc->CreateEvent(u"CustomEvent"_ns, CallerType::System, res);
+ if (res.Failed()) {
+ return false;
+ }
+
+ // We don't init the AutoJSAPI with ourselves because we don't want it
+ // reporting errors to our onerror handlers.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ DOMWindowResizeEventDetail detail;
+ detail.mWidth = aSize.width;
+ detail.mHeight = aSize.height;
+ JS::Rooted<JS::Value> detailValue(cx);
+ if (!ToJSValue(cx, detail, &detailValue)) {
+ return false;
+ }
+
+ CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
+ customEvent->InitCustomEvent(cx, u"DOMWindowResize"_ns,
+ /* aCanBubble = */ true,
+ /* aCancelable = */ true, detailValue);
+
+ domEvent->SetTrusted(true);
+ domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = this;
+ domEvent->SetTarget(target);
+
+ return target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
+}
+
+bool nsGlobalWindowOuter::WindowExists(const nsAString& aName,
+ bool aForceNoOpener,
+ bool aLookForCallerOnJSStack) {
+ MOZ_ASSERT(mDocShell, "Must have docshell");
+
+ if (aForceNoOpener) {
+ return aName.LowerCaseEqualsLiteral("_self") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent");
+ }
+
+ if (WindowGlobalChild* wgc = mInnerWindow->GetWindowGlobalChild()) {
+ return wgc->FindBrowsingContextWithName(aName, aLookForCallerOnJSStack);
+ }
+ return false;
+}
+
+already_AddRefed<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ nsCOMPtr<nsIWidget> widget;
+
+ if (treeOwnerAsWin) {
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ }
+
+ return widget.forget();
+}
+
+nsIWidget* nsGlobalWindowOuter::GetNearestWidget() const {
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ PresShell* presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return nullptr;
+ }
+ return rootFrame->GetView()->GetNearestWidget(nullptr);
+}
+
+void nsGlobalWindowOuter::SetFullscreenOuter(bool aFullscreen,
+ mozilla::ErrorResult& aError) {
+ aError =
+ SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullscreen);
+}
+
+nsresult nsGlobalWindowOuter::SetFullScreen(bool aFullscreen) {
+ return SetFullscreenInternal(FullscreenReason::ForFullscreenMode,
+ aFullscreen);
+}
+
+static void FinishDOMFullscreenChange(Document* aDoc, bool aInDOMFullscreen) {
+ if (aInDOMFullscreen) {
+ // Ask the document to handle any pending DOM fullscreen change.
+ if (!Document::HandlePendingFullscreenRequests(aDoc)) {
+ // If we don't end up having anything in fullscreen,
+ // async request exiting fullscreen.
+ Document::AsyncExitFullscreen(aDoc);
+ }
+ } else {
+ // If the window is leaving fullscreen state, also ask the document
+ // to exit from DOM Fullscreen.
+ Document::ExitFullscreenInDocTree(aDoc);
+ }
+}
+
+struct FullscreenTransitionDuration {
+ // The unit of the durations is millisecond
+ uint16_t mFadeIn = 0;
+ uint16_t mFadeOut = 0;
+ bool IsSuppressed() const { return mFadeIn == 0 && mFadeOut == 0; }
+};
+
+static void GetFullscreenTransitionDuration(
+ bool aEnterFullscreen, FullscreenTransitionDuration* aDuration) {
+ const char* pref = aEnterFullscreen
+ ? "full-screen-api.transition-duration.enter"
+ : "full-screen-api.transition-duration.leave";
+ nsAutoCString prefValue;
+ Preferences::GetCString(pref, prefValue);
+ if (!prefValue.IsEmpty()) {
+ sscanf(prefValue.get(), "%hu%hu", &aDuration->mFadeIn,
+ &aDuration->mFadeOut);
+ }
+}
+
+class FullscreenTransitionTask : public Runnable {
+ public:
+ FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
+ nsGlobalWindowOuter* aWindow, bool aFullscreen,
+ nsIWidget* aWidget, nsISupports* aTransitionData)
+ : mozilla::Runnable("FullscreenTransitionTask"),
+ mWindow(aWindow),
+ mWidget(aWidget),
+ mTransitionData(aTransitionData),
+ mDuration(aDuration),
+ mStage(eBeforeToggle),
+ mFullscreen(aFullscreen) {}
+
+ NS_IMETHOD Run() override;
+
+ private:
+ ~FullscreenTransitionTask() override = default;
+
+ /**
+ * The flow of fullscreen transition:
+ *
+ * parent process | child process
+ * ----------------------------------------------------------------
+ *
+ * | request/exit fullscreen
+ * <-----|
+ * BeforeToggle stage |
+ * |
+ * ToggleFullscreen stage *1 |----->
+ * | HandleFullscreenRequests
+ * |
+ * <-----| MozAfterPaint event
+ * AfterToggle stage *2 |
+ * |
+ * End stage |
+ *
+ * Note we also start a timer at *1 so that if we don't get MozAfterPaint
+ * from the child process in time, we continue going to *2.
+ */
+ enum Stage {
+ // BeforeToggle stage happens before we enter or leave fullscreen
+ // state. In this stage, the task triggers the pre-toggle fullscreen
+ // transition on the widget.
+ eBeforeToggle,
+ // ToggleFullscreen stage actually executes the fullscreen toggle,
+ // and wait for the next paint on the content to continue.
+ eToggleFullscreen,
+ // AfterToggle stage happens after we toggle the fullscreen state.
+ // In this stage, the task triggers the post-toggle fullscreen
+ // transition on the widget.
+ eAfterToggle,
+ // End stage is triggered after the final transition finishes.
+ eEnd
+ };
+
+ class Observer final : public nsIObserver, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ explicit Observer(FullscreenTransitionTask* aTask) : mTask(aTask) {}
+
+ private:
+ ~Observer() = default;
+
+ RefPtr<FullscreenTransitionTask> mTask;
+ };
+
+ static const char* const kPaintedTopic;
+
+ RefPtr<nsGlobalWindowOuter> mWindow;
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsISupports> mTransitionData;
+
+ TimeStamp mFullscreenChangeStartTime;
+ FullscreenTransitionDuration mDuration;
+ Stage mStage;
+ bool mFullscreen;
+};
+
+const char* const FullscreenTransitionTask::kPaintedTopic =
+ "fullscreen-painted";
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Run() {
+ Stage stage = mStage;
+ mStage = Stage(mStage + 1);
+ if (MOZ_UNLIKELY(mWidget->Destroyed())) {
+ // If the widget has been destroyed before we get here, don't try to
+ // do anything more. Just let it go and release ourselves.
+ NS_WARNING("The widget to fullscreen has been destroyed");
+ mWindow->mIsInFullScreenTransition = false;
+ return NS_OK;
+ }
+ if (stage == eBeforeToggle) {
+ PROFILER_MARKER_UNTYPED("Fullscreen transition start", DOM);
+
+ mWindow->mIsInFullScreenTransition = true;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+ obs->NotifyObservers(nullptr, "fullscreen-transition-start", nullptr);
+
+ mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
+ mDuration.mFadeIn, mTransitionData,
+ this);
+ } else if (stage == eToggleFullscreen) {
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle start", DOM);
+ mFullscreenChangeStartTime = TimeStamp::Now();
+ // Toggle the fullscreen state on the widget
+ if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
+ mFullscreen, mWidget)) {
+ // Fail to setup the widget, call FinishFullscreenChange to
+ // complete fullscreen change directly.
+ mWindow->FinishFullscreenChange(mFullscreen);
+ }
+ // Set observer for the next content paint.
+ nsCOMPtr<nsIObserver> observer = new Observer(this);
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(observer, kPaintedTopic, false);
+ // There are several edge cases where we may never get the paint
+ // notification, including:
+ // 1. the window/tab is closed before the next paint;
+ // 2. the user has switched to another tab before we get here.
+ // Completely fixing those cases seems to be tricky, and since they
+ // should rarely happen, it probably isn't worth to fix. Hence we
+ // simply add a timeout here to ensure we never hang forever.
+ // In addition, if the page is complicated or the machine is less
+ // powerful, layout could take a long time, in which case, staying
+ // in black screen for that long could hurt user experience even
+ // more than exposing an intermediate state.
+ uint32_t timeout =
+ Preferences::GetUint("full-screen-api.transition.timeout", 1000);
+ NS_NewTimerWithObserver(getter_AddRefs(mTimer), observer, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ } else if (stage == eAfterToggle) {
+ Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS,
+ mFullscreenChangeStartTime);
+ mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
+ mDuration.mFadeOut, mTransitionData,
+ this);
+ } else if (stage == eEnd) {
+ PROFILER_MARKER_UNTYPED("Fullscreen transition end", DOM);
+
+ mWindow->mIsInFullScreenTransition = false;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+ obs->NotifyObservers(nullptr, "fullscreen-transition-end", nullptr);
+
+ mWidget->CleanupFullscreenTransition();
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver, nsINamed)
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ bool shouldContinue = false;
+ if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject));
+ nsCOMPtr<nsIWidget> widget =
+ win ? nsGlobalWindowInner::Cast(win)->GetMainWidget() : nullptr;
+ if (widget == mTask->mWidget) {
+ // The paint notification arrives first. Cancel the timer.
+ mTask->mTimer->Cancel();
+ shouldContinue = true;
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle end", DOM);
+ }
+ } else {
+#ifdef DEBUG
+ MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
+ "Should only get fullscreen-painted or timer-callback");
+ nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
+ MOZ_ASSERT(timer && timer == mTask->mTimer,
+ "Should only trigger this with the timer the task created");
+#endif
+ shouldContinue = true;
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle timeout", DOM);
+ }
+ if (shouldContinue) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kPaintedTopic);
+ mTask->mTimer = nullptr;
+ mTask->Run();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Observer::GetName(nsACString& aName) {
+ aName.AssignLiteral("FullscreenTransitionTask");
+ return NS_OK;
+}
+
+static bool MakeWidgetFullscreen(nsGlobalWindowOuter* aWindow,
+ FullscreenReason aReason, bool aFullscreen) {
+ nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
+ if (!widget) {
+ return false;
+ }
+
+ FullscreenTransitionDuration duration;
+ bool performTransition = false;
+ nsCOMPtr<nsISupports> transitionData;
+ if (aReason == FullscreenReason::ForFullscreenAPI) {
+ GetFullscreenTransitionDuration(aFullscreen, &duration);
+ if (!duration.IsSuppressed()) {
+ performTransition = widget->PrepareForFullscreenTransition(
+ getter_AddRefs(transitionData));
+ }
+ }
+
+ if (!performTransition) {
+ return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget);
+ }
+
+ nsCOMPtr<nsIRunnable> task = new FullscreenTransitionTask(
+ duration, aWindow, aFullscreen, widget, transitionData);
+ task->Run();
+ return true;
+}
+
+nsresult nsGlobalWindowOuter::ProcessWidgetFullscreenRequest(
+ FullscreenReason aReason, bool aFullscreen) {
+ mInProcessFullscreenRequest.emplace(aReason, aFullscreen);
+
+ // Prevent chrome documents which are still loading from resizing
+ // the window after we set fullscreen mode.
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(treeOwnerAsWin));
+ if (aFullscreen && appWin) {
+ appWin->SetIntrinsicallySized(false);
+ }
+
+ // Sometimes we don't want the top-level widget to actually go fullscreen:
+ // - in the B2G desktop client, we don't want the emulated screen dimensions
+ // to appear to increase when entering fullscreen mode; we just want the
+ // content to fill the entire client area of the emulator window.
+ // - in FxR Desktop, we don't want fullscreen to take over the monitor, but
+ // instead we want fullscreen to fill the FxR window in the the headset.
+ if (!StaticPrefs::full_screen_api_ignore_widgets() &&
+ !mForceFullScreenInWidget) {
+ if (MakeWidgetFullscreen(this, aReason, aFullscreen)) {
+ // The rest of code for switching fullscreen is in nsGlobalWindowOuter::
+ // FinishFullscreenChange() which will be called after sizemodechange
+ // event is dispatched.
+ return NS_OK;
+ }
+ }
+
+#if defined(NIGHTLY_BUILD) && defined(XP_WIN)
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(mWindowID)) {
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendFullscreenState(mWindowID, aFullscreen);
+ }
+#endif // NIGHTLY_BUILD && XP_WIN
+ FinishFullscreenChange(aFullscreen);
+ return NS_OK;
+}
+
+nsresult nsGlobalWindowOuter::SetFullscreenInternal(FullscreenReason aReason,
+ bool aFullscreen) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
+ "Requires safe to run script as it "
+ "may call FinishDOMFullscreenChange");
+
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ MOZ_ASSERT(
+ aReason != FullscreenReason::ForForceExitFullscreen || !aFullscreen,
+ "FullscreenReason::ForForceExitFullscreen can "
+ "only be used with exiting fullscreen");
+
+ // Only chrome can change our fullscreen mode. Otherwise, the state
+ // can only be changed for DOM fullscreen.
+ if (aReason == FullscreenReason::ForFullscreenMode &&
+ !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_OK;
+ }
+
+ // SetFullscreen needs to be called on the root window, so get that
+ // via the DocShell tree, and if we are not already the root,
+ // call SetFullscreen on that window instead.
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ rootItem ? rootItem->GetWindow() : nullptr;
+ if (!window) return NS_ERROR_FAILURE;
+ if (rootItem != mDocShell)
+ return window->SetFullscreenInternal(aReason, aFullscreen);
+
+ // make sure we don't try to set full screen on a non-chrome window,
+ // which might happen in embedding world
+ if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
+ return NS_ERROR_FAILURE;
+
+ // FullscreenReason::ForForceExitFullscreen can only be used with exiting
+ // fullscreen
+ MOZ_ASSERT_IF(
+ mFullscreen.isSome(),
+ mFullscreen.value() != FullscreenReason::ForForceExitFullscreen);
+
+ // If we are already in full screen mode, just return, we don't care about the
+ // reason here, because,
+ // - If we are in fullscreen mode due to browser fullscreen mode, requesting
+ // DOM fullscreen does not change anything.
+ // - If we are in fullscreen mode due to DOM fullscreen, requesting browser
+ // fullscreen should not change anything, either. Note that we should not
+ // update reason to ForFullscreenMode, otherwise the subsequent DOM
+ // fullscreen exit will be ignored and user will be confused. And ideally
+ // this should never happen as `window.fullscreen` returns `true` for DOM
+ // fullscreen as well.
+ if (mFullscreen.isSome() == aFullscreen) {
+ // How come we get browser fullscreen request while we are already in DOM
+ // fullscreen?
+ MOZ_ASSERT_IF(aFullscreen && aReason == FullscreenReason::ForFullscreenMode,
+ mFullscreen.value() != FullscreenReason::ForFullscreenAPI);
+ return NS_OK;
+ }
+
+ // Note that although entering DOM fullscreen could also cause
+ // consequential calls to this method, those calls will be skipped
+ // at the condition above.
+ if (aReason == FullscreenReason::ForFullscreenMode) {
+ if (!aFullscreen && mFullscreen &&
+ mFullscreen.value() == FullscreenReason::ForFullscreenAPI) {
+ // If we are exiting fullscreen mode, but we actually didn't
+ // entered browser fullscreen mode, the fullscreen state was only for
+ // the Fullscreen API. Change the reason here so that we can
+ // perform transition for it.
+ aReason = FullscreenReason::ForFullscreenAPI;
+ }
+ } else {
+ // If we are exiting from DOM fullscreen while we initially make
+ // the window fullscreen because of browser fullscreen mode, don't restore
+ // the window. But we still need to exit the DOM fullscreen state.
+ if (!aFullscreen && mFullscreen &&
+ mFullscreen.value() == FullscreenReason::ForFullscreenMode) {
+ // If there is a in-process fullscreen request, FinishDOMFullscreenChange
+ // will be called when the request is finished.
+ if (!mInProcessFullscreenRequest.isSome()) {
+ FinishDOMFullscreenChange(mDoc, false);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Set this before so if widget sends an event indicating its
+ // gone full screen, the state trap above works.
+ if (aFullscreen) {
+ mFullscreen.emplace(aReason);
+ } else {
+ mFullscreen.reset();
+ }
+
+ // If we are in process of fullscreen request, only keep the latest fullscreen
+ // state, we will sync up later while the processing request is finished.
+ if (mInProcessFullscreenRequest.isSome()) {
+ mFullscreenHasChangedDuringProcessing = true;
+ return NS_OK;
+ }
+
+ return ProcessWidgetFullscreenRequest(aReason, aFullscreen);
+}
+
+// Support a per-window, dynamic equivalent of enabling
+// full-screen-api.ignore-widgets
+void nsGlobalWindowOuter::ForceFullScreenInWidget() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ mForceFullScreenInWidget = true;
+}
+
+bool nsGlobalWindowOuter::SetWidgetFullscreen(FullscreenReason aReason,
+ bool aIsFullscreen,
+ nsIWidget* aWidget) {
+ MOZ_ASSERT(this == GetInProcessTopInternal(),
+ "Only topmost window should call this");
+ MOZ_ASSERT(!GetFrameElementInternal(), "Content window should not call this");
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ if (!NS_WARN_IF(mChromeFields.mFullscreenPresShell)) {
+ if (PresShell* presShell = mDocShell->GetPresShell()) {
+ if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
+ mChromeFields.mFullscreenPresShell = do_GetWeakReference(presShell);
+ MOZ_ASSERT(mChromeFields.mFullscreenPresShell);
+ rd->SetIsResizeSuppressed();
+ rd->Freeze();
+ }
+ }
+ }
+ }
+ nsresult rv = aReason == FullscreenReason::ForFullscreenMode
+ ?
+ // If we enter fullscreen for fullscreen mode, we want
+ // the native system behavior.
+ aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen)
+ : aWidget->MakeFullScreen(aIsFullscreen);
+ return NS_SUCCEEDED(rv);
+}
+
+/* virtual */
+void nsGlobalWindowOuter::FullscreenWillChange(bool aIsFullscreen) {
+ if (!mInProcessFullscreenRequest.isSome()) {
+ // If there is no in-process fullscreen request, the fullscreen state change
+ // is triggered from the OS directly, e.g. user use built-in window button
+ // to enter/exit fullscreen on macOS.
+ mInProcessFullscreenRequest.emplace(FullscreenReason::ForFullscreenMode,
+ aIsFullscreen);
+ if (mFullscreen.isSome() != aIsFullscreen) {
+ if (aIsFullscreen) {
+ mFullscreen.emplace(FullscreenReason::ForFullscreenMode);
+ } else {
+ mFullscreen.reset();
+ }
+ } else {
+ // It is possible that FullscreenWillChange is notified with current
+ // fullscreen state, e.g. browser goes into fullscreen when widget
+ // fullscreen is prevented, and then user triggers fullscreen from the OS
+ // directly again.
+ MOZ_ASSERT(StaticPrefs::full_screen_api_ignore_widgets() ||
+ mForceFullScreenInWidget,
+ "This should only happen when widget fullscreen is prevented");
+ }
+ }
+ if (aIsFullscreen) {
+ DispatchCustomEvent(u"willenterfullscreen"_ns, ChromeOnlyDispatch::eYes);
+ } else {
+ DispatchCustomEvent(u"willexitfullscreen"_ns, ChromeOnlyDispatch::eYes);
+ }
+}
+
+/* virtual */
+void nsGlobalWindowOuter::FinishFullscreenChange(bool aIsFullscreen) {
+ mozilla::Maybe<FullscreenRequest> currentInProcessRequest =
+ std::move(mInProcessFullscreenRequest);
+ if (!mFullscreenHasChangedDuringProcessing &&
+ aIsFullscreen != mFullscreen.isSome()) {
+ NS_WARNING("Failed to toggle fullscreen state of the widget");
+ // We failed to make the widget enter fullscreen.
+ // Stop further changes and restore the state.
+ if (!aIsFullscreen) {
+ mFullscreen.reset();
+ } else {
+#ifndef XP_MACOSX
+ MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
+#endif
+ // Restore fullscreen state with FullscreenReason::ForFullscreenAPI reason
+ // in order to make subsequent DOM fullscreen exit request can exit
+ // browser fullscreen mode.
+ mFullscreen.emplace(FullscreenReason::ForFullscreenAPI);
+ }
+ return;
+ }
+
+ // Note that we must call this to toggle the DOM fullscreen state
+ // of the document before dispatching the "fullscreen" event, so
+ // that the chrome can distinguish between browser fullscreen mode
+ // and DOM fullscreen.
+ FinishDOMFullscreenChange(mDoc, aIsFullscreen);
+
+ // dispatch a "fullscreen" DOM event so that XUL apps can
+ // respond visually if we are kicked into full screen mode
+ DispatchCustomEvent(u"fullscreen"_ns, ChromeOnlyDispatch::eYes);
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ if (RefPtr<PresShell> presShell =
+ do_QueryReferent(mChromeFields.mFullscreenPresShell)) {
+ if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
+ rd->Thaw();
+ }
+ mChromeFields.mFullscreenPresShell = nullptr;
+ }
+ }
+
+ // If fullscreen state has changed during processing fullscreen request, we
+ // need to ensure widget matches our latest fullscreen state here.
+ if (mFullscreenHasChangedDuringProcessing) {
+ mFullscreenHasChangedDuringProcessing = false;
+ // Widget doesn't care about the reason that makes it entering/exiting
+ // fullscreen, so here we just need to ensure the fullscreen state is
+ // matched.
+ if (aIsFullscreen != mFullscreen.isSome()) {
+ // If we end up need to exit fullscreen, use the same reason that brings
+ // us into fullscreen mode, so that we will perform the same fullscreen
+ // transistion effect for exiting.
+ ProcessWidgetFullscreenRequest(
+ mFullscreen.isSome() ? mFullscreen.value()
+ : currentInProcessRequest.value().mReason,
+ mFullscreen.isSome());
+ }
+ }
+}
+
+/* virtual */
+void nsGlobalWindowOuter::MacFullscreenMenubarOverlapChanged(
+ mozilla::DesktopCoord aOverlapAmount) {
+ ErrorResult res;
+ RefPtr<Event> domEvent =
+ mDoc->CreateEvent(u"CustomEvent"_ns, CallerType::System, res);
+ if (res.Failed()) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ JS::Rooted<JS::Value> detailValue(cx);
+ if (!ToJSValue(cx, aOverlapAmount, &detailValue)) {
+ return;
+ }
+
+ CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
+ customEvent->InitCustomEvent(cx, u"MacFullscreenMenubarRevealUpdate"_ns,
+ /* aCanBubble = */ true,
+ /* aCancelable = */ true, detailValue);
+ domEvent->SetTrusted(true);
+ domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = this;
+ domEvent->SetTarget(target);
+
+ target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
+}
+
+bool nsGlobalWindowOuter::Fullscreen() const {
+ NS_ENSURE_TRUE(mDocShell, mFullscreen.isSome());
+
+ // Get the fullscreen value of the root window, to always have the value
+ // accurate, even when called from content.
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ if (rootItem == mDocShell) {
+ if (!XRE_IsContentProcess()) {
+ // We are the root window. Return our internal value.
+ return mFullscreen.isSome();
+ }
+ if (nsCOMPtr<nsIWidget> widget = GetNearestWidget()) {
+ // We are in content process, figure out the value from
+ // the sizemode of the puppet widget.
+ return widget->SizeMode() == nsSizeMode_Fullscreen;
+ }
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = rootItem->GetWindow();
+ NS_ENSURE_TRUE(window, mFullscreen.isSome());
+
+ return nsGlobalWindowOuter::Cast(window)->Fullscreen();
+}
+
+bool nsGlobalWindowOuter::GetFullscreenOuter() { return Fullscreen(); }
+
+bool nsGlobalWindowOuter::GetFullScreen() {
+ FORWARD_TO_INNER(GetFullScreen, (), false);
+}
+
+void nsGlobalWindowOuter::EnsureReflowFlushAndPaint() {
+ NS_ASSERTION(mDocShell,
+ "EnsureReflowFlushAndPaint() called with no "
+ "docshell!");
+
+ if (!mDocShell) return;
+
+ RefPtr<PresShell> presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ // Flush pending reflows.
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ // Unsuppress painting.
+ presShell->UnsuppressPainting();
+}
+
+// static
+void nsGlobalWindowOuter::MakeMessageWithPrincipal(
+ nsAString& aOutMessage, nsIPrincipal* aSubjectPrincipal, bool aUseHostPort,
+ const char* aNullMessage, const char* aContentMessage,
+ const char* aFallbackMessage) {
+ MOZ_ASSERT(aSubjectPrincipal);
+
+ aOutMessage.Truncate();
+
+ // Try to get a host from the running principal -- this will do the
+ // right thing for javascript: and data: documents.
+
+ nsAutoCString contentDesc;
+
+ if (aSubjectPrincipal->GetIsNullPrincipal()) {
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, aNullMessage, aOutMessage);
+ } else {
+ auto* addonPolicy = BasePrincipal::Cast(aSubjectPrincipal)->AddonPolicy();
+ if (addonPolicy) {
+ nsContentUtils::FormatLocalizedString(
+ aOutMessage, nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ aContentMessage, addonPolicy->Name());
+ } else {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aUseHostPort) {
+ nsCOMPtr<nsIURI> uri = aSubjectPrincipal->GetURI();
+ if (uri) {
+ rv = uri->GetDisplayHostPort(contentDesc);
+ }
+ }
+ if (!aUseHostPort || NS_FAILED(rv)) {
+ rv = aSubjectPrincipal->GetExposablePrePath(contentDesc);
+ }
+ if (NS_SUCCEEDED(rv) && !contentDesc.IsEmpty()) {
+ NS_ConvertUTF8toUTF16 ucsPrePath(contentDesc);
+ nsContentUtils::FormatLocalizedString(
+ aOutMessage, nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ aContentMessage, ucsPrePath);
+ }
+ }
+ }
+
+ if (aOutMessage.IsEmpty()) {
+ // We didn't find a host so use the generic heading
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, aFallbackMessage,
+ aOutMessage);
+ }
+
+ // Just in case
+ if (aOutMessage.IsEmpty()) {
+ NS_WARNING(
+ "could not get ScriptDlgGenericHeading string from string bundle");
+ aOutMessage.AssignLiteral("[Script]");
+ }
+}
+
+bool nsGlobalWindowOuter::CanMoveResizeWindows(CallerType aCallerType) {
+ // When called from chrome, we can avoid the following checks.
+ if (aCallerType != CallerType::System) {
+ // Don't allow scripts to move or resize windows that were not opened by a
+ // script.
+ if (!mBrowsingContext->HadOriginalOpener()) {
+ return false;
+ }
+
+ if (!CanSetProperty("dom.disable_window_move_resize")) {
+ return false;
+ }
+
+ // Ignore the request if we have more than one tab in the window.
+ if (mBrowsingContext->Top()->HasSiblings()) {
+ return false;
+ }
+ }
+
+ if (mDocShell) {
+ bool allow;
+ nsresult rv = mDocShell->GetAllowWindowControl(&allow);
+ if (NS_SUCCEEDED(rv) && !allow) return false;
+ }
+
+ if (nsGlobalWindowInner::sMouseDown &&
+ !nsGlobalWindowInner::sDragServiceDisabled) {
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ nsGlobalWindowInner::sDragServiceDisabled = true;
+ ds->Suppress();
+ }
+ }
+ return true;
+}
+
+bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ // XXX This method is very similar to nsGlobalWindowOuter::Prompt, make
+ // sure any modifications here don't need to happen over there!
+ if (!AreDialogsEnabled()) {
+ // Just silently return. In the case of alert(), the return value is
+ // ignored. In the case of confirm(), returning false is the same thing as
+ // would happen if the user cancels.
+ return false;
+ }
+
+ // Reset popup state while opening a modal dialog, and firing events
+ // about the dialog, to prevent the current state from being active
+ // the whole time a modal dialog is open.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeMessageWithPrincipal(title, &aSubjectPrincipal, false,
+ "ScriptDlgNullPrincipalHeading", "ScriptDlgHeading",
+ "ScriptDlgGenericHeading");
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString final;
+ nsContentUtils::StripNullChars(aMessage, final);
+ nsContentUtils::PlatformToDOMLineBreaks(final);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return false;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ aError =
+ promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return false;
+ }
+
+ // Always allow content modal prompts for alert and confirm.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsUint32(u"modalType"_ns,
+ nsIPrompt::MODAL_TYPE_CONTENT);
+ }
+
+ bool result = false;
+ nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput);
+ if (ShouldPromptToBlockDialogs()) {
+ bool disallowDialog = false;
+ nsAutoString label;
+ MakeMessageWithPrincipal(
+ label, &aSubjectPrincipal, true, "ScriptDialogLabelNullPrincipal",
+ "ScriptDialogLabelContentPrincipal", "ScriptDialogLabelNullPrincipal");
+
+ aError = aAlert
+ ? prompt->AlertCheck(title.get(), final.get(), label.get(),
+ &disallowDialog)
+ : prompt->ConfirmCheck(title.get(), final.get(), label.get(),
+ &disallowDialog, &result);
+
+ if (disallowDialog) {
+ DisableDialogs();
+ }
+ } else {
+ aError = aAlert ? prompt->Alert(title.get(), final.get())
+ : prompt->Confirm(title.get(), final.get(), &result);
+ }
+
+ return result;
+}
+
+void nsGlobalWindowOuter::AlertOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
+}
+
+bool nsGlobalWindowOuter::ConfirmOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
+ aError);
+}
+
+void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
+ const nsAString& aInitial,
+ nsAString& aReturn,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ // XXX This method is very similar to nsGlobalWindowOuter::AlertOrConfirm,
+ // make sure any modifications here don't need to happen over there!
+ SetDOMStringToNull(aReturn);
+
+ if (!AreDialogsEnabled()) {
+ // Return null, as if the user just canceled the prompt.
+ return;
+ }
+
+ // Reset popup state while opening a modal dialog, and firing events
+ // about the dialog, to prevent the current state from being active
+ // the whole time a modal dialog is open.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeMessageWithPrincipal(title, &aSubjectPrincipal, false,
+ "ScriptDlgNullPrincipalHeading", "ScriptDlgHeading",
+ "ScriptDlgGenericHeading");
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString fixedMessage, fixedInitial;
+ nsContentUtils::StripNullChars(aMessage, fixedMessage);
+ nsContentUtils::PlatformToDOMLineBreaks(fixedMessage);
+ nsContentUtils::StripNullChars(aInitial, fixedInitial);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ aError =
+ promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Always allow content modal prompts for prompt.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsUint32(u"modalType"_ns,
+ nsIPrompt::MODAL_TYPE_CONTENT);
+ }
+
+ // Pass in the default value, if any.
+ char16_t* inoutValue = ToNewUnicode(fixedInitial);
+ bool disallowDialog = false;
+
+ nsAutoString label;
+ label.SetIsVoid(true);
+ if (ShouldPromptToBlockDialogs()) {
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);
+ }
+
+ nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput);
+ bool ok;
+ aError = prompt->Prompt(title.get(), fixedMessage.get(), &inoutValue,
+ label.IsVoid() ? nullptr : label.get(),
+ &disallowDialog, &ok);
+
+ if (disallowDialog) {
+ DisableDialogs();
+ }
+
+ // XXX Doesn't this leak inoutValue?
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsString outValue;
+ outValue.Adopt(inoutValue);
+ if (ok && inoutValue) {
+ aReturn = std::move(outValue);
+ }
+}
+
+void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType,
+ bool aFromOtherProcess,
+ uint64_t aActionId) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (MOZ_UNLIKELY(!fm)) {
+ return;
+ }
+
+ auto [canFocus, isActive] = GetBrowsingContext()->CanFocusCheck(aCallerType);
+ if (aFromOtherProcess) {
+ // We trust that the check passed in a process that's, in principle,
+ // untrusted, because we don't have the required caller context available
+ // here. Also, the worst that the other process can do in this case is to
+ // raise a window it's not supposed to be allowed to raise.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1677899
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "Parent should not trust other processes.");
+ canFocus = true;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (treeOwnerAsWin && (canFocus || isActive)) {
+ bool isEnabled = true;
+ if (NS_SUCCEEDED(treeOwnerAsWin->GetEnabled(&isEnabled)) && !isEnabled) {
+ NS_WARNING("Should not try to set the focus on a disabled window");
+ return;
+ }
+ }
+
+ if (!mDocShell) {
+ return;
+ }
+
+ // If the window has a child frame focused, clear the focus. This
+ // ensures that focus will be in this frame and not in a child.
+ if (nsIContent* content = GetFocusedElement()) {
+ if (HTMLIFrameElement::FromNode(content)) {
+ fm->ClearFocus(this);
+ }
+ }
+
+ RefPtr<BrowsingContext> parent;
+ BrowsingContext* bc = GetBrowsingContext();
+ if (bc) {
+ parent = bc->GetParent();
+ if (!parent && XRE_IsParentProcess()) {
+ parent = bc->Canonical()->GetParentCrossChromeBoundary();
+ }
+ }
+ if (parent) {
+ if (!parent->IsInProcess()) {
+ if (isActive) {
+ OwningNonNull<nsGlobalWindowOuter> kungFuDeathGrip(*this);
+ fm->WindowRaised(kungFuDeathGrip, aActionId);
+ } else {
+ ContentChild* contentChild = ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendFinalizeFocusOuter(bc, canFocus, aCallerType);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(mDoc, "Call chain should have ensured document creation.");
+ if (mDoc) {
+ if (Element* frame = mDoc->GetEmbedderElement()) {
+ nsContentUtils::RequestFrameFocus(*frame, canFocus, aCallerType);
+ }
+ }
+ return;
+ }
+
+ if (canFocus) {
+ // if there is no parent, this must be a toplevel window, so raise the
+ // window if canFocus is true. If this is a child process, the raise
+ // window request will get forwarded to the parent by the puppet widget.
+ OwningNonNull<nsGlobalWindowOuter> kungFuDeathGrip(*this);
+ fm->RaiseWindow(kungFuDeathGrip, aCallerType, aActionId);
+ }
+}
+
+nsresult nsGlobalWindowOuter::Focus(CallerType aCallerType) {
+ FORWARD_TO_INNER(Focus, (aCallerType), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::BlurOuter(CallerType aCallerType) {
+ if (!GetBrowsingContext()->CanBlurCheck(aCallerType)) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome();
+ if (chrome) {
+ chrome->Blur();
+ }
+}
+
+void nsGlobalWindowOuter::StopOuter(ErrorResult& aError) {
+ // IsNavigationAllowed checks are usually done in nsDocShell directly,
+ // however nsDocShell::Stop has a bunch of internal users that would fail
+ // the IsNavigationAllowed check.
+ if (!mDocShell || !nsDocShell::Cast(mDocShell)->IsNavigationAllowed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (webNav) {
+ aError = webNav->Stop(nsIWebNavigation::STOP_ALL);
+ }
+}
+
+void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
+ if (!AreDialogsEnabled()) {
+ // Per spec, silently return. https://github.com/whatwg/html/commit/21a1de1
+ return;
+ }
+
+ // Printing is disabled, silently return.
+ if (!StaticPrefs::print_enabled()) {
+ return;
+ }
+
+ // If we're loading, queue the print for later. This is a special-case that
+ // only applies to the window.print() call, for compat with other engines and
+ // pre-existing behavior.
+ if (mShouldDelayPrintUntilAfterLoad) {
+ if (nsIDocShell* docShell = GetDocShell()) {
+ if (docShell->GetBusyFlags() & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) {
+ mDelayedPrintUntilAfterLoad = true;
+ return;
+ }
+ }
+ }
+
+#ifdef NS_PRINTING
+ RefPtr<BrowsingContext> top =
+ mBrowsingContext ? mBrowsingContext->Top() : nullptr;
+ if (NS_WARN_IF(top && top->GetIsPrinting())) {
+ return;
+ }
+
+ if (top) {
+ Unused << top->SetIsPrinting(true);
+ }
+
+ auto unset = MakeScopeExit([&] {
+ if (top) {
+ Unused << top->SetIsPrinting(false);
+ }
+ });
+
+ const bool forPreview = !StaticPrefs::print_always_print_silent();
+ Print(nullptr, nullptr, nullptr, nullptr, IsPreview(forPreview),
+ IsForWindowDotPrint::Yes, nullptr, aError);
+#endif
+}
+
+class MOZ_RAII AutoModalState {
+ public:
+ explicit AutoModalState(nsGlobalWindowOuter& aWin)
+ : mModalStateWin(aWin.EnterModalState()) {}
+
+ ~AutoModalState() {
+ if (mModalStateWin) {
+ mModalStateWin->LeaveModalState();
+ }
+ }
+
+ RefPtr<nsGlobalWindowOuter> mModalStateWin;
+};
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
+ nsIPrintSettings* aPrintSettings, RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener* aListener, nsIDocShell* aDocShellToCloneInto,
+ IsPreview aIsPreview, IsForWindowDotPrint aForWindowDotPrint,
+ PrintPreviewResolver&& aPrintPreviewCallback, ErrorResult& aError) {
+#ifdef NS_PRINTING
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (!printSettingsService) {
+ // we currently return here in headless mode - should we?
+ aError.ThrowNotSupportedError("No print settings service");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrintSettings> ps = aPrintSettings;
+ if (!ps) {
+ // We shouldn't need this once bug 1776169 is fixed.
+ printSettingsService->GetDefaultPrintSettingsForPrinting(
+ getter_AddRefs(ps));
+ }
+
+ RefPtr<Document> docToPrint = mDoc;
+ if (NS_WARN_IF(!docToPrint)) {
+ aError.ThrowNotSupportedError("Document is gone");
+ return nullptr;
+ }
+
+ RefPtr<BrowsingContext> sourceBC = docToPrint->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ if (!sourceBC) {
+ aError.ThrowNotSupportedError("No browsing context for source document");
+ return nullptr;
+ }
+
+ nsAutoSyncOperation sync(docToPrint, SyncOperationBehavior::eAllowInput);
+ AutoModalState modalState(*this);
+
+ nsCOMPtr<nsIContentViewer> cv;
+ RefPtr<BrowsingContext> bc;
+ bool hasPrintCallbacks = false;
+ if (docToPrint->IsStaticDocument()) {
+ if (aForWindowDotPrint == IsForWindowDotPrint::Yes) {
+ aError.ThrowNotSupportedError(
+ "Calling print() from a print preview is unsupported, did you intend "
+ "to call printPreview() instead?");
+ return nullptr;
+ }
+ // We're already a print preview window, just reuse our browsing context /
+ // content viewer.
+ bc = sourceBC;
+ nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
+ if (!docShell) {
+ aError.ThrowNotSupportedError("No docshell");
+ return nullptr;
+ }
+ // We could handle this if needed.
+ if (aDocShellToCloneInto && aDocShellToCloneInto != docShell) {
+ aError.ThrowNotSupportedError(
+ "We don't handle cloning a print preview doc into a different "
+ "docshell");
+ return nullptr;
+ }
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ MOZ_DIAGNOSTIC_ASSERT(cv);
+ } else {
+ if (aDocShellToCloneInto) {
+ // Ensure the content viewer is created if needed.
+ Unused << aDocShellToCloneInto->GetDocument();
+ bc = aDocShellToCloneInto->GetBrowsingContext();
+ } else {
+ AutoNoJSAPI nojsapi;
+ auto printKind = aForWindowDotPrint == IsForWindowDotPrint::Yes
+ ? PrintKind::WindowDotPrint
+ : PrintKind::InternalPrint;
+ // For PrintKind::WindowDotPrint, this call will not only make the parent
+ // process create a CanonicalBrowsingContext for the returned `bc`, but
+ // it will also make the parent process initiate the print/print preview.
+ // See the handling of OPEN_PRINT_BROWSER in browser.js.
+ aError = OpenInternal(u""_ns, u""_ns, u""_ns,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ printKind, getter_AddRefs(bc));
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ if (!bc) {
+ aError.ThrowNotAllowedError("No browsing context");
+ return nullptr;
+ }
+
+ Unused << bc->Top()->SetIsPrinting(true);
+ nsCOMPtr<nsIDocShell> cloneDocShell = bc->GetDocShell();
+ MOZ_DIAGNOSTIC_ASSERT(cloneDocShell);
+ cloneDocShell->GetContentViewer(getter_AddRefs(cv));
+ MOZ_DIAGNOSTIC_ASSERT(cv);
+ if (!cv) {
+ aError.ThrowNotSupportedError("Didn't end up with a content viewer");
+ return nullptr;
+ }
+
+ if (bc != sourceBC) {
+ MOZ_ASSERT(bc->IsTopContent());
+ // If we are cloning from a document in a different BrowsingContext, we
+ // need to make sure to copy over our opener policy information from that
+ // BrowsingContext. In the case where the source is an iframe, this
+ // information needs to be copied from the toplevel source
+ // BrowsingContext, as we may be making a static clone of a single
+ // subframe.
+ MOZ_ALWAYS_SUCCEEDS(
+ bc->SetOpenerPolicy(sourceBC->Top()->GetOpenerPolicy()));
+ MOZ_DIAGNOSTIC_ASSERT(bc->Group() == sourceBC->Group());
+ }
+
+ if (RefPtr<Document> doc = cv->GetDocument()) {
+ if (doc->IsShowing()) {
+ // We're going to drop this document on the floor, in the SetDocument
+ // call below. Make sure to run OnPageHide() to keep state consistent
+ // and avoids assertions in the document destructor.
+ doc->OnPageHide(false, nullptr);
+ }
+ }
+
+ AutoPrintEventDispatcher dispatcher(*docToPrint);
+
+ nsAutoScriptBlocker blockScripts;
+ RefPtr<Document> clone = docToPrint->CreateStaticClone(
+ cloneDocShell, cv, ps, &hasPrintCallbacks);
+ if (!clone) {
+ aError.ThrowNotSupportedError("Clone operation for printing failed");
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint = do_QueryInterface(cv);
+ if (!webBrowserPrint) {
+ aError.ThrowNotSupportedError(
+ "Content viewer didn't implement nsIWebBrowserPrint");
+ return nullptr;
+ }
+
+ // For window.print(), we postpone making these calls until the round-trip to
+ // the parent process (triggered by the OpenInternal call above) calls us
+ // again. Only a call from the parent can provide a valid nsPrintSettings
+ // object and RemotePrintJobChild object.
+ if (aForWindowDotPrint == IsForWindowDotPrint::No) {
+ if (aIsPreview == IsPreview::Yes) {
+ aError = webBrowserPrint->PrintPreview(ps, aListener,
+ std::move(aPrintPreviewCallback));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ } else {
+ // Historically we've eaten this error.
+ webBrowserPrint->Print(ps, aRemotePrintJob, aListener);
+ }
+ }
+
+ // When using window.print() with the new UI, we usually want to block until
+ // the print dialog is hidden. But we can't really do that if we have print
+ // callbacks, because we are inside a sync operation, and we want to run
+ // microtasks / etc that the print callbacks may create. It is really awkward
+ // to have this subtle behavior difference...
+ //
+ // We also want to do this for fuzzing, so that they can test window.print().
+ const bool shouldBlock = [&] {
+ if (aForWindowDotPrint == IsForWindowDotPrint::No) {
+ return false;
+ }
+ if (aIsPreview == IsPreview::Yes) {
+ return !hasPrintCallbacks;
+ }
+ return StaticPrefs::dom_window_print_fuzzing_block_while_printing();
+ }();
+
+ if (shouldBlock) {
+ SpinEventLoopUntil("nsGlobalWindowOuter::Print"_ns,
+ [&] { return bc->IsDiscarded(); });
+ }
+
+ return WindowProxyHolder(std::move(bc));
+#else
+ return nullptr;
+#endif // NS_PRINTING
+}
+
+void nsGlobalWindowOuter::MoveToOuter(int32_t aXPos, int32_t aYPos,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // We need to do the same transformation GetScreenXY does.
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ CSSIntPoint cssPos(aXPos, aYPos);
+ CheckSecurityLeftAndTop(&cssPos.x.value, &cssPos.y.value, aCallerType);
+
+ nsDeviceContext* context = presContext->DeviceContext();
+
+ auto devPos = LayoutDeviceIntPoint::FromAppUnitsRounded(
+ CSSIntPoint::ToAppUnits(cssPos), context->AppUnitsPerDevPixel());
+
+ aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y);
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::MoveByOuter(int32_t aXDif, int32_t aYDif,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // To do this correctly we have to convert what we get from GetPosition
+ // into CSS pixels, add the arguments, do the security check, and
+ // then convert back to device pixels for the call to SetPosition.
+
+ int32_t x, y;
+ aError = treeOwnerAsWin->GetPosition(&x, &y);
+ if (aError.Failed()) {
+ return;
+ }
+
+ auto cssScale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntPoint cssPos = RoundedToInt(treeOwnerAsWin->GetPosition() / cssScale);
+
+ cssPos.x += aXDif;
+ cssPos.y += aYDif;
+
+ CheckSecurityLeftAndTop(&cssPos.x.value, &cssPos.y.value, aCallerType);
+
+ LayoutDeviceIntPoint newDevPos = RoundedToInt(cssPos * cssScale);
+ aError = treeOwnerAsWin->SetPosition(newDevPos.x, newDevPos.y);
+
+ CheckForDPIChange();
+}
+
+nsresult nsGlobalWindowOuter::MoveBy(int32_t aXDif, int32_t aYDif) {
+ ErrorResult rv;
+ MoveByOuter(aXDif, aYDif, CallerType::System, rv);
+
+ return rv.StealNSResult();
+}
+
+void nsGlobalWindowOuter::ResizeToOuter(int32_t aWidth, int32_t aHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CSSIntSize cssSize(aWidth, aHeight);
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize devSize =
+ RoundedToInt(cssSize * CSSToDevScaleForBaseWindow(treeOwnerAsWin));
+ aError = treeOwnerAsWin->SetSize(devSize.width, devSize.height, true);
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ LayoutDeviceIntSize size = treeOwnerAsWin->GetSize();
+
+ // To do this correctly we have to convert what we got from GetSize
+ // into CSS pixels, add the arguments, do the security check, and
+ // then convert back to device pixels for the call to SetSize.
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntSize cssSize = RoundedToInt(size / scale);
+
+ cssSize.width += aWidthDif;
+ cssSize.height += aHeightDif;
+
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize newDevSize = RoundedToInt(cssSize * scale);
+
+ aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SizeToContentOuter(
+ CallerType aCallerType, const SizeToContentConstraints& aConstraints,
+ ErrorResult& aError) {
+ if (!mDocShell) {
+ return;
+ }
+
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.sizeToContent() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ // The content viewer does a check to make sure that it's a content
+ // viewer for a toplevel docshell.
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (!cv) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ auto contentSize = cv->GetContentSize(
+ aConstraints.mMaxWidth, aConstraints.mMaxHeight, aConstraints.mPrefWidth);
+ if (!contentSize) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ // Make sure the new size is following the CheckSecurityWidthAndHeight
+ // rules.
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ // Don't use DevToCSSIntPixelsForBaseWindow() nor
+ // CSSToDevIntPixelsForBaseWindow() here because contentSize is comes from
+ // nsIContentViewer::GetContentSize() and it's computed with nsPresContext so
+ // that we need to work with nsPresContext here too.
+ RefPtr<nsPresContext> presContext = cv->GetPresContext();
+ MOZ_ASSERT(
+ presContext,
+ "Should be non-nullptr if nsIContentViewer::GetContentSize() succeeded");
+ CSSIntSize cssSize = *contentSize;
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize newDevSize(
+ presContext->CSSPixelsToDevPixels(cssSize.width),
+ presContext->CSSPixelsToDevPixels(cssSize.height));
+
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ aError =
+ treeOwner->SizeShellTo(docShell, newDevSize.width, newDevSize.height);
+}
+
+already_AddRefed<nsPIWindowRoot> nsGlobalWindowOuter::GetTopWindowRoot() {
+ nsPIDOMWindowOuter* piWin = GetPrivateRoot();
+ if (!piWin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIWindowRoot> window =
+ do_QueryInterface(piWin->GetChromeEventHandler());
+ return window.forget();
+}
+
+void nsGlobalWindowOuter::FirePopupBlockedEvent(
+ Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) {
+ MOZ_ASSERT(aDoc);
+
+ // Fire a "DOMPopupBlocked" event so that the UI can hear about
+ // blocked popups.
+ PopupBlockedEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ // XXX: This is a different object, but webidl requires an inner window here
+ // now.
+ init.mRequestingWindow = GetCurrentInnerWindowInternal();
+ init.mPopupWindowURI = aPopupURI;
+ init.mPopupWindowName = aPopupWindowName;
+ init.mPopupWindowFeatures = aPopupWindowFeatures;
+
+ RefPtr<PopupBlockedEvent> event =
+ PopupBlockedEvent::Constructor(aDoc, u"DOMPopupBlocked"_ns, init);
+
+ event->SetTrusted(true);
+
+ aDoc->DispatchEvent(*event);
+}
+
+// static
+bool nsGlobalWindowOuter::CanSetProperty(const char* aPrefName) {
+ // Chrome can set any property.
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return true;
+ }
+
+ // If the pref is set to true, we can not set the property
+ // and vice versa.
+ return !Preferences::GetBool(aPrefName, true);
+}
+
+/* If a window open is blocked, fire the appropriate DOM events. */
+void nsGlobalWindowOuter::FireAbuseEvents(
+ const nsAString& aPopupURL, const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) {
+ // fetch the URI of the window requesting the opened window
+ nsCOMPtr<Document> currentDoc = GetDoc();
+ nsCOMPtr<nsIURI> popupURI;
+
+ // build the URI of the would-have-been popup window
+ // (see nsWindowWatcher::URIfromURL)
+
+ // first, fetch the opener's base URI
+
+ nsIURI* baseURL = nullptr;
+
+ nsCOMPtr<Document> doc = GetEntryDocument();
+ if (doc) baseURL = doc->GetDocBaseURI();
+
+ // use the base URI to build what would have been the popup's URI
+ nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
+ if (ios)
+ ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
+ getter_AddRefs(popupURI));
+
+ // fire an event block full of informative URIs
+ FirePopupBlockedEvent(currentDoc, popupURI, aPopupWindowName,
+ aPopupWindowFeatures);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenOuter(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> bc;
+ nsresult rv = OpenJS(aUrl, aName, aOptions, getter_AddRefs(bc));
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ aError.ThrowSyntaxError("Unable to open a window with invalid URL '"_ns +
+ NS_ConvertUTF16toUTF8(aUrl) + "'."_ns);
+ return nullptr;
+ }
+
+ // XXX Is it possible that some internal errors are thrown here?
+ aError = rv;
+
+ if (!bc) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(bc));
+}
+
+nsresult nsGlobalWindowOuter::Open(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsDocShellLoadState* aLoadState,
+ bool aForceNoOpener,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ aLoadState, aForceNoOpener, PrintKind::None, _retval);
+}
+
+nsresult nsGlobalWindowOuter::OpenJS(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ true, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+// like Open, but attaches to the new window any extra parameters past
+// [features] as a JS property named "arguments"
+nsresult nsGlobalWindowOuter::OpenDialog(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsISupports* aExtraArgument,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, aExtraArgument, // Arguments
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+// Like Open, but passes aNavigate=false.
+/* virtual */
+nsresult nsGlobalWindowOuter::OpenNoNavigate(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ false, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenDialogOuter(
+ JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIJSArgArray> argvArray;
+ aError =
+ NS_CreateJSArgv(aCx, aExtraArgument.Length(), aExtraArgument.Elements(),
+ getter_AddRefs(argvArray));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<BrowsingContext> dialog;
+ aError = OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ argvArray, nullptr, // Arguments
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, getter_AddRefs(dialog));
+ if (!dialog) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(dialog));
+}
+
+WindowProxyHolder nsGlobalWindowOuter::GetFramesOuter() {
+ RefPtr<nsPIDOMWindowOuter> frames(this);
+ FlushPendingNotifications(FlushType::ContentAndNotify);
+ return WindowProxyHolder(mBrowsingContext);
+}
+
+/* static */
+bool nsGlobalWindowOuter::GatherPostMessageData(
+ JSContext* aCx, const nsAString& aTargetOrigin, BrowsingContext** aSource,
+ nsAString& aOrigin, nsIURI** aTargetOriginURI,
+ nsIPrincipal** aCallerPrincipal, nsGlobalWindowInner** aCallerInnerWindow,
+ nsIURI** aCallerURI, Maybe<nsID>* aCallerAgentClusterId,
+ nsACString* aScriptLocation, ErrorResult& aError) {
+ //
+ // Window.postMessage is an intentional subversion of the same-origin policy.
+ // As such, this code must be particularly careful in the information it
+ // exposes to calling code.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
+ //
+
+ // First, get the caller's window
+ RefPtr<nsGlobalWindowInner> callerInnerWin =
+ nsContentUtils::IncumbentInnerWindow();
+ nsIPrincipal* callerPrin;
+ if (callerInnerWin) {
+ RefPtr<Document> doc = callerInnerWin->GetExtantDoc();
+ if (!doc) {
+ return false;
+ }
+ NS_IF_ADDREF(*aCallerURI = doc->GetDocumentURI());
+
+ // Compute the caller's origin either from its principal or, in the case the
+ // principal doesn't carry a URI (e.g. the system principal), the caller's
+ // document. We must get this now instead of when the event is created and
+ // dispatched, because ultimately it is the identity of the calling window
+ // *now* that determines who sent the message (and not an identity which
+ // might have changed due to intervening navigations).
+ callerPrin = callerInnerWin->GetPrincipal();
+ } else {
+ // In case the global is not a window, it can be a sandbox, and the
+ // sandbox's principal can be used for the security check.
+ nsIGlobalObject* global = GetIncumbentGlobal();
+ NS_ASSERTION(global, "Why is there no global object?");
+ callerPrin = global->PrincipalOrNull();
+ if (callerPrin) {
+ BasePrincipal::Cast(callerPrin)->GetScriptLocation(*aScriptLocation);
+ }
+ }
+ if (!callerPrin) {
+ return false;
+ }
+
+ // if the principal has a URI, use that to generate the origin
+ if (!callerPrin->IsSystemPrincipal()) {
+ nsAutoCString asciiOrigin;
+ callerPrin->GetAsciiOrigin(asciiOrigin);
+ CopyUTF8toUTF16(asciiOrigin, aOrigin);
+ } else if (callerInnerWin) {
+ if (!*aCallerURI) {
+ return false;
+ }
+ // otherwise use the URI of the document to generate origin
+ nsContentUtils::GetUTFOrigin(*aCallerURI, aOrigin);
+ } else {
+ // in case of a sandbox with a system principal origin can be empty
+ if (!callerPrin->IsSystemPrincipal()) {
+ return false;
+ }
+ }
+ NS_IF_ADDREF(*aCallerPrincipal = callerPrin);
+
+ // "/" indicates same origin as caller, "*" indicates no specific origin is
+ // required.
+ if (!aTargetOrigin.EqualsASCII("/") && !aTargetOrigin.EqualsASCII("*")) {
+ nsCOMPtr<nsIURI> targetOriginURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(targetOriginURI), aTargetOrigin))) {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return false;
+ }
+
+ nsresult rv = NS_MutateURI(targetOriginURI)
+ .SetUserPass(""_ns)
+ .SetPathQueryRef(""_ns)
+ .Finalize(aTargetOriginURI);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+
+ if (!nsContentUtils::IsCallerChrome() && callerInnerWin &&
+ callerInnerWin->GetOuterWindowInternal()) {
+ NS_ADDREF(*aSource = callerInnerWin->GetOuterWindowInternal()
+ ->GetBrowsingContext());
+ } else {
+ *aSource = nullptr;
+ }
+
+ if (aCallerAgentClusterId && callerInnerWin &&
+ callerInnerWin->GetDocGroup()) {
+ *aCallerAgentClusterId =
+ Some(callerInnerWin->GetDocGroup()->AgentClusterId());
+ }
+
+ callerInnerWin.forget(aCallerInnerWindow);
+
+ return true;
+}
+
+bool nsGlobalWindowOuter::GetPrincipalForPostMessage(
+ const nsAString& aTargetOrigin, nsIURI* aTargetOriginURI,
+ nsIPrincipal* aCallerPrincipal, nsIPrincipal& aSubjectPrincipal,
+ nsIPrincipal** aProvidedPrincipal) {
+ //
+ // Window.postMessage is an intentional subversion of the same-origin policy.
+ // As such, this code must be particularly careful in the information it
+ // exposes to calling code.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
+ //
+
+ // Convert the provided origin string into a URI for comparison purposes.
+ nsCOMPtr<nsIPrincipal> providedPrincipal;
+
+ if (aTargetOrigin.EqualsASCII("/")) {
+ providedPrincipal = aCallerPrincipal;
+ }
+ // "*" indicates no specific origin is required.
+ else if (!aTargetOrigin.EqualsASCII("*")) {
+ OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
+ if (aSubjectPrincipal.IsSystemPrincipal()) {
+ auto principal = BasePrincipal::Cast(GetPrincipal());
+
+ if (attrs != principal->OriginAttributesRef()) {
+ nsAutoCString targetURL;
+ nsAutoCString sourceOrigin;
+ nsAutoCString targetOrigin;
+
+ if (NS_FAILED(principal->GetAsciiSpec(targetURL)) ||
+ NS_FAILED(principal->GetOrigin(targetOrigin)) ||
+ NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
+ NS_WARNING("Failed to get source and target origins");
+ return false;
+ }
+
+ nsContentUtils::LogSimpleConsoleError(
+ NS_ConvertUTF8toUTF16(nsPrintfCString(
+ R"(Attempting to post a message to window with url "%s" and )"
+ R"(origin "%s" from a system principal scope with mismatched )"
+ R"(origin "%s".)",
+ targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
+ "DOM"_ns, !!principal->PrivateBrowsingId(),
+ principal->IsSystemPrincipal());
+
+ attrs = principal->OriginAttributesRef();
+ }
+ }
+
+ // Create a nsIPrincipal inheriting the app/browser attributes from the
+ // caller.
+ providedPrincipal =
+ BasePrincipal::CreateContentPrincipal(aTargetOriginURI, attrs);
+ if (NS_WARN_IF(!providedPrincipal)) {
+ return false;
+ }
+ } else {
+ // We still need to check the originAttributes if the target origin is '*'.
+ // But we will ingore the FPD here since the FPDs are possible to be
+ // different.
+ auto principal = BasePrincipal::Cast(GetPrincipal());
+ NS_ENSURE_TRUE(principal, false);
+
+ OriginAttributes targetAttrs = principal->OriginAttributesRef();
+ OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
+ // We have to exempt the check of OA if the subject prioncipal is a system
+ // principal since there are many tests try to post messages to content from
+ // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
+ // post a message into a private browsing window. The injected code in
+ // ContentTask.spawn() will be executed under the system principal and the
+ // OA of the system principal mismatches with the OA of a private browsing
+ // window.
+ MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.IsSystemPrincipal() ||
+ sourceAttrs.EqualsIgnoringFPD(targetAttrs));
+
+ // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
+ // postMessage across different first party domains.
+ if (OriginAttributes::IsBlockPostMessageForFPI() &&
+ !aSubjectPrincipal.IsSystemPrincipal() &&
+ sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
+ return false;
+ }
+ }
+
+ providedPrincipal.forget(aProvidedPrincipal);
+ return true;
+}
+
+void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> sourceBc;
+ nsAutoString origin;
+ nsCOMPtr<nsIURI> targetOriginURI;
+ nsCOMPtr<nsIPrincipal> callerPrincipal;
+ RefPtr<nsGlobalWindowInner> callerInnerWindow;
+ nsCOMPtr<nsIURI> callerURI;
+ Maybe<nsID> callerAgentClusterId = Nothing();
+ nsAutoCString scriptLocation;
+ if (!GatherPostMessageData(
+ aCx, aTargetOrigin, getter_AddRefs(sourceBc), origin,
+ getter_AddRefs(targetOriginURI), getter_AddRefs(callerPrincipal),
+ getter_AddRefs(callerInnerWindow), getter_AddRefs(callerURI),
+ &callerAgentClusterId, &scriptLocation, aError)) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> providedPrincipal;
+ if (!GetPrincipalForPostMessage(aTargetOrigin, targetOriginURI,
+ callerPrincipal, aSubjectPrincipal,
+ getter_AddRefs(providedPrincipal))) {
+ return;
+ }
+
+ // Create and asynchronously dispatch a runnable which will handle actual DOM
+ // event creation and dispatch.
+ RefPtr<PostMessageEvent> event = new PostMessageEvent(
+ sourceBc, origin, this, providedPrincipal,
+ callerInnerWindow ? callerInnerWindow->WindowID() : 0, callerURI,
+ scriptLocation, callerAgentClusterId);
+
+ JS::CloneDataPolicy clonePolicy;
+
+ if (GetDocGroup() && callerAgentClusterId.isSome() &&
+ GetDocGroup()->AgentClusterId().Equals(callerAgentClusterId.value())) {
+ clonePolicy.allowIntraClusterClonableSharedObjects();
+ }
+
+ if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ event->Write(aCx, aMessage, aTransfer, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ event->DispatchToTargetThread(aError);
+}
+
+class nsCloseEvent : public Runnable {
+ RefPtr<nsGlobalWindowOuter> mWindow;
+ bool mIndirect;
+
+ nsCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect)
+ : mozilla::Runnable("nsCloseEvent"),
+ mWindow(aWindow),
+ mIndirect(aIndirect) {}
+
+ public:
+ static nsresult PostCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect);
+ nsresult rv = aWindow->Dispatch(TaskCategory::Other, ev.forget());
+ return rv;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mWindow) {
+ if (mIndirect) {
+ return PostCloseEvent(mWindow, false);
+ }
+ mWindow->ReallyCloseWindow();
+ }
+ return NS_OK;
+ }
+};
+
+bool nsGlobalWindowOuter::CanClose() {
+ if (mIsChrome) {
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ bool canClose = true;
+ if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
+ return canClose;
+ }
+ }
+
+ if (!mDocShell) {
+ return true;
+ }
+
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ bool canClose;
+ nsresult rv = cv->PermitUnload(&canClose);
+ if (NS_SUCCEEDED(rv) && !canClose) return false;
+ }
+
+ // If we still have to print, we delay the closing until print has happened.
+ if (mShouldDelayPrintUntilAfterLoad && mDelayedPrintUntilAfterLoad) {
+ mDelayedCloseForPrinting = true;
+ return false;
+ }
+
+ return true;
+}
+
+void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
+ if (!mDocShell || IsInModalState() || mBrowsingContext->IsSubframe()) {
+ // window.close() is called on a frame in a frameset, on a window
+ // that's already closed, or on a window for which there's
+ // currently a modal dialog open. Ignore such calls.
+ return;
+ }
+
+ if (mHavePendingClose) {
+ // We're going to be closed anyway; do nothing since we don't want
+ // to double-close
+ return;
+ }
+
+ if (mBlockScriptedClosingFlag) {
+ // A script's popup has been blocked and we don't want
+ // the window to be closed directly after this event,
+ // so the user can see that there was a blocked popup.
+ return;
+ }
+
+ // Don't allow scripts from content to close non-neterror windows that
+ // were not opened by script.
+ if (mDoc) {
+ nsAutoString url;
+ nsresult rv = mDoc->GetURL(url);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!StringBeginsWith(url, u"about:neterror"_ns) &&
+ !mBrowsingContext->HadOriginalOpener() && !aTrustedCaller &&
+ !IsOnlyTopLevelDocumentInSHistory()) {
+ bool allowClose =
+ mAllowScriptsToClose ||
+ Preferences::GetBool("dom.allow_scripts_to_close_windows", true);
+ if (!allowClose) {
+ // We're blocking the close operation
+ // report localized error msg in JS console
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Window"_ns,
+ mDoc, // Better name for the category?
+ nsContentUtils::eDOM_PROPERTIES, "WindowCloseBlockedWarning");
+
+ return;
+ }
+ }
+ }
+
+ if (!mInClose && !mIsClosed && !CanClose()) {
+ return;
+ }
+
+ // Fire a DOM event notifying listeners that this window is about to
+ // be closed. The tab UI code may choose to cancel the default
+ // action for this event, if so, we won't actually close the window
+ // (since the tab UI code will close the tab in stead). Sure, this
+ // could be abused by content code, but do we care? I don't think
+ // so...
+
+ bool wasInClose = mInClose;
+ mInClose = true;
+
+ if (!DispatchCustomEvent(u"DOMWindowClose"_ns, ChromeOnlyDispatch::eYes)) {
+ // Someone chose to prevent the default action for this event, if
+ // so, let's not close this window after all...
+
+ mInClose = wasInClose;
+ return;
+ }
+
+ FinalClose();
+}
+
+bool nsGlobalWindowOuter::IsOnlyTopLevelDocumentInSHistory() {
+ NS_ENSURE_TRUE(mDocShell && mBrowsingContext, false);
+ // Disabled since IsFrame() is buggy in Fission
+ // MOZ_ASSERT(mBrowsingContext->IsTop());
+
+ if (mozilla::SessionHistoryInParent()) {
+ return mBrowsingContext->GetIsSingleToplevelInHistory();
+ }
+
+ RefPtr<ChildSHistory> csh = nsDocShell::Cast(mDocShell)->GetSessionHistory();
+ if (csh && csh->LegacySHistory()) {
+ return csh->LegacySHistory()->IsEmptyOrHasEntriesForSingleTopLevelPage();
+ }
+
+ return false;
+}
+
+nsresult nsGlobalWindowOuter::Close() {
+ CloseOuter(/* aTrustedCaller = */ true);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::ForceClose() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (mBrowsingContext->IsSubframe() || !mDocShell) {
+ // This may be a frame in a frameset, or a window that's already closed.
+ // Ignore such calls.
+ return;
+ }
+
+ if (mHavePendingClose) {
+ // We're going to be closed anyway; do nothing since we don't want
+ // to double-close
+ return;
+ }
+
+ mInClose = true;
+
+ DispatchCustomEvent(u"DOMWindowClose"_ns, ChromeOnlyDispatch::eYes);
+
+ FinalClose();
+}
+
+void nsGlobalWindowOuter::FinalClose() {
+ // Flag that we were closed.
+ mIsClosed = true;
+
+ if (!mBrowsingContext->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetClosed(true));
+ }
+
+ // If we get here from CloseOuter then it means that the parent process is
+ // going to close our window for us. It's just important to set mIsClosed.
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ return;
+ }
+
+ // This stuff is non-sensical but incredibly fragile. The reasons for the
+ // behavior here don't make sense today and may not have ever made sense,
+ // but various bits of frontend code break when you change them. If you need
+ // to fix up this behavior, feel free to. It's a righteous task, but involves
+ // wrestling with various download manager tests, frontend code, and possible
+ // broken addons. The chrome tests in toolkit/mozapps/downloads are a good
+ // testing ground.
+ //
+ // In particular, if some inner of |win| is the entry global, we must
+ // complete _two_ round-trips to the event loop before the call to
+ // ReallyCloseWindow. This allows setTimeout handlers that are set after
+ // FinalClose() is called to run before the window is torn down.
+ nsCOMPtr<nsPIDOMWindowInner> entryWindow =
+ do_QueryInterface(GetEntryGlobal());
+ bool indirect = entryWindow && entryWindow->GetOuterWindow() == this;
+ if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
+ ReallyCloseWindow();
+ } else {
+ mHavePendingClose = true;
+ }
+}
+
+void nsGlobalWindowOuter::ReallyCloseWindow() {
+ // Make sure we never reenter this method.
+ mHavePendingClose = true;
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ return;
+ }
+
+ treeOwnerAsWin->Destroy();
+ CleanUp();
+}
+
+void nsGlobalWindowOuter::SuppressEventHandling() {
+ if (mSuppressEventHandlingDepth == 0) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
+ if (RefPtr<Document> doc = win->GetExtantDoc()) {
+ mSuspendedDocs.AppendElement(doc);
+ // Note: Document::SuppressEventHandling will also automatically
+ // suppress event handling for any in-process sub-documents.
+ // However, since we need to deal with cases where remote
+ // BrowsingContexts may be interleaved with in-process ones, we
+ // still need to walk the entire tree ourselves. This may be
+ // slightly redundant in some cases, but since event handling
+ // suppressions maintain a count of current blockers, it does not
+ // cause any problems.
+ doc->SuppressEventHandling();
+ }
+ }
+ });
+ }
+ }
+ mSuppressEventHandlingDepth++;
+}
+
+void nsGlobalWindowOuter::UnsuppressEventHandling() {
+ MOZ_ASSERT(mSuppressEventHandlingDepth != 0);
+ mSuppressEventHandlingDepth--;
+
+ if (mSuppressEventHandlingDepth == 0 && mSuspendedDocs.Length()) {
+ RefPtr<Document> currentDoc = GetExtantDoc();
+ bool fireEvent = currentDoc == mSuspendedDocs[0];
+ nsTArray<RefPtr<Document>> suspendedDocs = std::move(mSuspendedDocs);
+ for (const auto& doc : suspendedDocs) {
+ doc->UnsuppressEventHandlingAndFireEvents(fireEvent);
+ }
+ }
+}
+
+nsGlobalWindowOuter* nsGlobalWindowOuter::EnterModalState() {
+ // GetInProcessScriptableTop, not GetInProcessTop, so that EnterModalState
+ // works properly with <iframe mozbrowser>.
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+
+ if (!topWin) {
+ NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
+ return nullptr;
+ }
+
+ // If there is an active ESM in this window, clear it. Otherwise, this can
+ // cause a problem if a modal state is entered during a mouseup event.
+ EventStateManager* activeESM = static_cast<EventStateManager*>(
+ EventStateManager::GetActiveEventStateManager());
+ if (activeESM && activeESM->GetPresContext()) {
+ PresShell* activePresShell = activeESM->GetPresContext()->GetPresShell();
+ if (activePresShell && (nsContentUtils::ContentIsCrossDocDescendantOf(
+ activePresShell->GetDocument(), mDoc) ||
+ nsContentUtils::ContentIsCrossDocDescendantOf(
+ mDoc, activePresShell->GetDocument()))) {
+ EventStateManager::ClearGlobalActiveContent(activeESM);
+
+ PresShell::ReleaseCapturingContent();
+
+ if (activePresShell) {
+ RefPtr<nsFrameSelection> frameSelection =
+ activePresShell->FrameSelection();
+ frameSelection->SetDragState(false);
+ }
+ }
+ }
+
+ // If there are any drag and drop operations in flight, try to end them.
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ ds->EndDragSession(true, 0);
+ }
+
+ // Clear the capturing content if it is under topDoc.
+ // Usually the activeESM check above does that, but there are cases when
+ // we don't have activeESM, or it is for different document.
+ Document* topDoc = topWin->GetExtantDoc();
+ nsIContent* capturingContent = PresShell::GetCapturingContent();
+ if (capturingContent && topDoc &&
+ nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
+ PresShell::ReleaseCapturingContent();
+ }
+
+ if (topWin->mModalStateDepth == 0) {
+ topWin->SuppressEventHandling();
+
+ if (nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal()) {
+ inner->Suspend();
+ }
+ }
+ topWin->mModalStateDepth++;
+ return topWin;
+}
+
+void nsGlobalWindowOuter::LeaveModalState() {
+ {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+ if (!topWin) {
+ NS_WARNING("Uh, LeaveModalState() called w/o a reachable top window?");
+ return;
+ }
+
+ if (topWin != this) {
+ MOZ_ASSERT(IsSuspended());
+ return topWin->LeaveModalState();
+ }
+ }
+
+ MOZ_ASSERT(mModalStateDepth != 0);
+ MOZ_ASSERT(IsSuspended());
+ mModalStateDepth--;
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ if (mModalStateDepth == 0) {
+ if (inner) {
+ inner->Resume();
+ }
+
+ UnsuppressEventHandling();
+ }
+
+ // Remember the time of the last dialog quit.
+ if (auto* bcg = GetBrowsingContextGroup()) {
+ bcg->SetLastDialogQuitTime(TimeStamp::Now());
+ }
+
+ if (mModalStateDepth == 0) {
+ RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr);
+ event->InitEvent(u"endmodalstate"_ns, true, false);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ DispatchEvent(*event);
+ }
+}
+
+bool nsGlobalWindowOuter::IsInModalState() {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+
+ if (!topWin) {
+ // IsInModalState() getting called w/o a reachable top window is a bit
+ // iffy, but valid enough not to make noise about it. See bug 404828
+ return false;
+ }
+
+ return topWin->mModalStateDepth != 0;
+}
+
+void nsGlobalWindowOuter::NotifyWindowIDDestroyed(const char* aTopic) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new WindowDestroyedEvent(this, mWindowID, aTopic);
+ Dispatch(TaskCategory::Other, runnable.forget());
+}
+
+Element* nsGlobalWindowOuter::GetFrameElement(nsIPrincipal& aSubjectPrincipal) {
+ // Per HTML5, the frameElement getter returns null in cross-origin situations.
+ Element* element = GetFrameElement();
+ if (!element) {
+ return nullptr;
+ }
+
+ if (!aSubjectPrincipal.SubsumesConsideringDomain(element->NodePrincipal())) {
+ return nullptr;
+ }
+
+ return element;
+}
+
+Element* nsGlobalWindowOuter::GetFrameElement() {
+ if (!mBrowsingContext || mBrowsingContext->IsTop()) {
+ return nullptr;
+ }
+ return mBrowsingContext->GetEmbedderElement();
+}
+
+namespace {
+class ChildCommandDispatcher : public Runnable {
+ public:
+ ChildCommandDispatcher(nsPIWindowRoot* aRoot, nsIBrowserChild* aBrowserChild,
+ nsPIDOMWindowOuter* aWindow, const nsAString& aAction)
+ : mozilla::Runnable("ChildCommandDispatcher"),
+ mRoot(aRoot),
+ mBrowserChild(aBrowserChild),
+ mWindow(aWindow),
+ mAction(aAction) {}
+
+ NS_IMETHOD Run() override {
+ AutoTArray<nsCString, 70> enabledCommands, disabledCommands;
+ mRoot->GetEnabledDisabledCommands(enabledCommands, disabledCommands);
+ if (enabledCommands.Length() || disabledCommands.Length()) {
+ BrowserChild* bc = static_cast<BrowserChild*>(mBrowserChild.get());
+ bc->SendEnableDisableCommands(mWindow->GetBrowsingContext(), mAction,
+ enabledCommands, disabledCommands);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsPIWindowRoot> mRoot;
+ nsCOMPtr<nsIBrowserChild> mBrowserChild;
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsString mAction;
+};
+
+class CommandDispatcher : public Runnable {
+ public:
+ CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher,
+ const nsAString& aAction)
+ : mozilla::Runnable("CommandDispatcher"),
+ mDispatcher(aDispatcher),
+ mAction(aAction) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ return mDispatcher->UpdateCommands(mAction);
+ }
+
+ const nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
+ nsString mAction;
+};
+} // anonymous namespace
+
+void nsGlobalWindowOuter::UpdateCommands(const nsAString& anAction,
+ Selection* aSel, int16_t aReason) {
+ // If this is a child process, redirect to the parent process.
+ if (nsIDocShell* docShell = GetDocShell()) {
+ if (nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild()) {
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ if (root) {
+ nsContentUtils::AddScriptRunner(
+ new ChildCommandDispatcher(root, child, this, anAction));
+ }
+ return;
+ }
+ }
+
+ nsPIDOMWindowOuter* rootWindow = GetPrivateRoot();
+ if (!rootWindow) {
+ return;
+ }
+
+ Document* doc = rootWindow->GetExtantDoc();
+
+ if (!doc) {
+ return;
+ }
+ // selectionchange action is only used for mozbrowser, not for XUL. So we
+ // bypass XUL command dispatch if anAction is "selectionchange".
+ if (!anAction.EqualsLiteral("selectionchange")) {
+ // Retrieve the command dispatcher and call updateCommands on it.
+ nsIDOMXULCommandDispatcher* xulCommandDispatcher =
+ doc->GetCommandDispatcher();
+ if (xulCommandDispatcher) {
+ nsContentUtils::AddScriptRunner(
+ new CommandDispatcher(xulCommandDispatcher, anAction));
+ }
+ }
+}
+
+Selection* nsGlobalWindowOuter::GetSelectionOuter() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetCurrentSelection(SelectionType::eNormal);
+}
+
+already_AddRefed<Selection> nsGlobalWindowOuter::GetSelection() {
+ RefPtr<Selection> selection = GetSelectionOuter();
+ return selection.forget();
+}
+
+bool nsGlobalWindowOuter::FindOuter(const nsAString& aString,
+ bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord,
+ bool aSearchInFrames, bool aShowDialog,
+ ErrorResult& aError) {
+ Unused << aShowDialog;
+
+ nsCOMPtr<nsIWebBrowserFind> finder(do_GetInterface(mDocShell));
+ if (!finder) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return false;
+ }
+
+ // Set the options of the search
+ aError = finder->SetSearchString(aString);
+ if (aError.Failed()) {
+ return false;
+ }
+ finder->SetMatchCase(aCaseSensitive);
+ finder->SetFindBackwards(aBackwards);
+ finder->SetWrapFind(aWrapAround);
+ finder->SetEntireWord(aWholeWord);
+ finder->SetSearchFrames(aSearchInFrames);
+
+ // the nsIWebBrowserFind is initialized to use this window
+ // as the search root, but uses focus to set the current search
+ // frame. If we're being called from JS (as here), this window
+ // should be the current search frame.
+ nsCOMPtr<nsIWebBrowserFindInFrames> framesFinder(do_QueryInterface(finder));
+ if (framesFinder) {
+ framesFinder->SetRootSearchFrame(this); // paranoia
+ framesFinder->SetCurrentSearchFrame(this);
+ }
+
+ if (aString.IsEmpty()) {
+ return false;
+ }
+
+ // Launch the search with the passed in search string
+ bool didFind = false;
+ aError = finder->FindNext(&didFind);
+ return didFind;
+}
+
+//*****************************************************************************
+// EventTarget
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOwnerGlobalForBindingsInternal() {
+ return this;
+}
+
+bool nsGlobalWindowOuter::DispatchEvent(Event& aEvent, CallerType aCallerType,
+ ErrorResult& aRv) {
+ FORWARD_TO_INNER(DispatchEvent, (aEvent, aCallerType, aRv), false);
+}
+
+bool nsGlobalWindowOuter::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
+ // It's OK that we just return false here on failure to create an
+ // inner. GetOrCreateListenerManager() will likewise fail, and then
+ // we won't be adding any listeners anyway.
+ FORWARD_TO_INNER_CREATE(ComputeDefaultWantsUntrusted, (aRv), false);
+}
+
+EventListenerManager* nsGlobalWindowOuter::GetOrCreateListenerManager() {
+ FORWARD_TO_INNER_CREATE(GetOrCreateListenerManager, (), nullptr);
+}
+
+EventListenerManager* nsGlobalWindowOuter::GetExistingListenerManager() const {
+ FORWARD_TO_INNER(GetExistingListenerManager, (), nullptr);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsPIDOMWindow
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateParent() {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetInProcessParent();
+
+ if (this == parent) {
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (!chromeElement)
+ return nullptr; // This is ok, just means a null parent.
+
+ Document* doc = chromeElement->GetComposedDoc();
+ if (!doc) return nullptr; // This is ok, just means a null parent.
+
+ return doc->GetWindow();
+ }
+
+ return parent;
+}
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateRoot() {
+ nsCOMPtr<nsPIDOMWindowOuter> top = GetInProcessTop();
+
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (chromeElement) {
+ Document* doc = chromeElement->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = doc->GetWindow();
+ if (parent) {
+ top = parent->GetInProcessTop();
+ }
+ }
+ }
+
+ return top;
+}
+
+// This has a caller in Windows-only code (nsNativeAppSupportWin).
+Location* nsGlobalWindowOuter::GetLocation() {
+ // This method can be called on the outer window as well.
+ FORWARD_TO_INNER(Location, (), nullptr);
+}
+
+void nsGlobalWindowOuter::SetIsBackground(bool aIsBackground) {
+ bool changed = aIsBackground != IsBackground();
+ SetIsBackgroundInternal(aIsBackground);
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+
+ if (inner && changed) {
+ inner->UpdateBackgroundState();
+ }
+
+ if (aIsBackground) {
+ // Notify gamepadManager we are at the background window,
+ // we need to stop vibrate.
+ // Stop the vr telemery time spent when it switches to
+ // the background window.
+ if (inner && changed) {
+ inner->StopGamepadHaptics();
+ inner->StopVRActivity();
+ // true is for asking to set the delta time to
+ // the telemetry.
+ inner->ResetVRTelemetry(true);
+ }
+ return;
+ }
+
+ if (inner) {
+ // When switching to be as a top tab, restart the telemetry.
+ // false is for only resetting the timestamp.
+ inner->ResetVRTelemetry(false);
+ inner->SyncGamepadState();
+ inner->StartVRActivity();
+ }
+}
+
+void nsGlobalWindowOuter::SetIsBackgroundInternal(bool aIsBackground) {
+ mIsBackground = aIsBackground;
+}
+
+void nsGlobalWindowOuter::SetChromeEventHandler(
+ EventTarget* aChromeEventHandler) {
+ SetChromeEventHandlerInternal(aChromeEventHandler);
+ // update the chrome event handler on all our inner windows
+ RefPtr<nsGlobalWindowInner> inner;
+ for (PRCList* node = PR_LIST_HEAD(this); node != this;
+ node = PR_NEXT_LINK(inner)) {
+ // This cast is only safe if `node != this`, as nsGlobalWindowOuter is also
+ // in the list.
+ inner = static_cast<nsGlobalWindowInner*>(node);
+ NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this,
+ "bad outer window pointer");
+ inner->SetChromeEventHandlerInternal(aChromeEventHandler);
+ }
+}
+
+void nsGlobalWindowOuter::SetFocusedElement(Element* aElement,
+ uint32_t aFocusMethod,
+ bool aNeedsFocus) {
+ FORWARD_TO_INNER_VOID(SetFocusedElement,
+ (aElement, aFocusMethod, aNeedsFocus));
+}
+
+uint32_t nsGlobalWindowOuter::GetFocusMethod() {
+ FORWARD_TO_INNER(GetFocusMethod, (), 0);
+}
+
+bool nsGlobalWindowOuter::ShouldShowFocusRing() {
+ FORWARD_TO_INNER(ShouldShowFocusRing, (), false);
+}
+
+bool nsGlobalWindowOuter::TakeFocus(bool aFocus, uint32_t aFocusMethod) {
+ FORWARD_TO_INNER(TakeFocus, (aFocus, aFocusMethod), false);
+}
+
+void nsGlobalWindowOuter::SetReadyForFocus() {
+ FORWARD_TO_INNER_VOID(SetReadyForFocus, ());
+}
+
+void nsGlobalWindowOuter::PageHidden() {
+ FORWARD_TO_INNER_VOID(PageHidden, ());
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindowOuter::GetComputedStyleHelperOuter(Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ ErrorResult& aRv) {
+ if (!mDoc) {
+ return nullptr;
+ }
+
+ RefPtr<nsICSSDeclaration> compStyle = NS_NewComputedDOMStyle(
+ &aElt, aPseudoElt, mDoc,
+ aDefaultStylesOnly ? nsComputedDOMStyle::StyleType::DefaultOnly
+ : nsComputedDOMStyle::StyleType::All,
+ aRv);
+
+ return compStyle.forget();
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIInterfaceRequestor
+//*****************************************************************************
+
+nsresult nsGlobalWindowOuter::GetInterfaceInternal(const nsIID& aIID,
+ void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+ *aSink = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ webNav.forget(aSink);
+ } else if (aIID.Equals(NS_GET_IID(nsIDocShell))) {
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ docShell.forget(aSink);
+ }
+#ifdef NS_PRINTING
+ else if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
+ if (mDocShell) {
+ nsCOMPtr<nsIContentViewer> viewer;
+ mDocShell->GetContentViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
+ webBrowserPrint.forget(aSink);
+ }
+ }
+ }
+#endif
+ else if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(mDocShell));
+ loadContext.forget(aSink);
+ }
+
+ return *aSink ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsGlobalWindowOuter::GetInterface(const nsIID& aIID, void** aSink) {
+ nsresult rv = GetInterfaceInternal(aIID, aSink);
+ if (rv == NS_ERROR_NO_INTERFACE) {
+ return QueryInterface(aIID, aSink);
+ }
+ return rv;
+}
+
+bool nsGlobalWindowOuter::IsSuspended() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively suspended
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsSuspended();
+}
+
+bool nsGlobalWindowOuter::IsFrozen() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively frozen
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsFrozen();
+}
+
+nsresult nsGlobalWindowOuter::FireDelayedDOMEvents(bool aIncludeSubWindows) {
+ FORWARD_TO_INNER(FireDelayedDOMEvents, (aIncludeSubWindows),
+ NS_ERROR_UNEXPECTED);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter: Window Control Functions
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessParentInternal() {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetInProcessParent();
+
+ if (parent && parent != this) {
+ return parent;
+ }
+
+ return nullptr;
+}
+
+void nsGlobalWindowOuter::UnblockScriptedClosing() {
+ mBlockScriptedClosingFlag = false;
+}
+
+class AutoUnblockScriptClosing {
+ private:
+ RefPtr<nsGlobalWindowOuter> mWin;
+
+ public:
+ explicit AutoUnblockScriptClosing(nsGlobalWindowOuter* aWin) : mWin(aWin) {
+ MOZ_ASSERT(mWin);
+ }
+ ~AutoUnblockScriptClosing() {
+ void (nsGlobalWindowOuter::*run)() =
+ &nsGlobalWindowOuter::UnblockScriptedClosing;
+ nsCOMPtr<nsIRunnable> caller = NewRunnableMethod(
+ "AutoUnblockScriptClosing::~AutoUnblockScriptClosing", mWin, run);
+ mWin->Dispatch(TaskCategory::Other, caller.forget());
+ }
+};
+
+nsresult nsGlobalWindowOuter::OpenInternal(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ bool aDialog, bool aContentModal, bool aCalledNoScript, bool aDoJSFixups,
+ bool aNavigate, nsIArray* argv, nsISupports* aExtraArgument,
+ nsDocShellLoadState* aLoadState, bool aForceNoOpener, PrintKind aPrintKind,
+ BrowsingContext** aReturn) {
+#ifdef DEBUG
+ uint32_t argc = 0;
+ if (argv) argv->GetLength(&argc);
+#endif
+
+ MOZ_ASSERT(!aExtraArgument || (!argv && argc == 0),
+ "Can't pass in arguments both ways");
+ MOZ_ASSERT(!aCalledNoScript || (!argv && argc == 0),
+ "Can't pass JS args when called via the noscript methods");
+
+ mozilla::Maybe<AutoUnblockScriptClosing> closeUnblocker;
+
+ // Calls to window.open from script should navigate.
+ MOZ_ASSERT(aCalledNoScript || aNavigate);
+
+ *aReturn = nullptr;
+
+ nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome();
+ if (!chrome) {
+ // No chrome means we don't want to go through with this open call
+ // -- see nsIWindowWatcher.idl
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ASSERTION(mDocShell, "Must have docshell here");
+
+ NS_ConvertUTF16toUTF8 optionsUtf8(aOptions);
+
+ WindowFeatures features;
+ if (!features.Tokenize(optionsUtf8)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool forceNoOpener = aForceNoOpener;
+ if (features.Exists("noopener")) {
+ forceNoOpener = features.GetBool("noopener");
+ features.Remove("noopener");
+ }
+
+ bool forceNoReferrer = false;
+ if (features.Exists("noreferrer")) {
+ forceNoReferrer = features.GetBool("noreferrer");
+ if (forceNoReferrer) {
+ // noreferrer implies noopener
+ forceNoOpener = true;
+ }
+ features.Remove("noreferrer");
+ }
+
+ nsAutoCString options;
+ features.Stringify(options);
+
+ // If noopener is force-enabled for the current document, then set noopener to
+ // true, and clear the name to "_blank".
+ nsAutoString windowName(aName);
+ if (nsDocShell::Cast(GetDocShell())->NoopenerForceEnabled()) {
+ // FIXME: Eventually bypass force-enabling noopener if `aPrintKind !=
+ // PrintKind::None`, so that we can print pages with noopener force-enabled.
+ // This will require relaxing assertions elsewhere.
+ if (aPrintKind != PrintKind::None) {
+ NS_WARNING(
+ "printing frames with noopener force-enabled isn't supported yet");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aNavigate,
+ "cannot OpenNoNavigate if noopener is force-enabled");
+
+ forceNoOpener = true;
+ windowName = u"_blank"_ns;
+ }
+
+ bool windowExists = WindowExists(windowName, forceNoOpener, !aCalledNoScript);
+
+ // XXXbz When this gets fixed to not use LegacyIsCallerNativeCode()
+ // (indirectly) maybe we can nix the AutoJSAPI usage OnLinkClickEvent::Run.
+ // But note that if you change this to GetEntryGlobal(), say, then
+ // OnLinkClickEvent::Run will need a full-blown AutoEntryScript.
+ const bool checkForPopup =
+ !nsContentUtils::LegacyIsCallerChromeOrNativeCode() && !aDialog &&
+ !windowExists;
+
+ // Note: the Void handling here is very important, because the window watcher
+ // expects a null URL string (not an empty string) if there is no URL to load.
+ nsCString url;
+ url.SetIsVoid(true);
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIURI> uri;
+
+ // It's important to do this security check before determining whether this
+ // window opening should be blocked, to ensure that we don't FireAbuseEvents
+ // for a window opening that wouldn't have succeeded in the first place.
+ if (!aUrl.IsEmpty()) {
+ AppendUTF16toUTF8(aUrl, url);
+
+ // It's safe to skip the security check below if we're not a dialog
+ // because window.openDialog is not callable from content script. See bug
+ // 56851.
+ //
+ // If we're not navigating, we assume that whoever *does* navigate the
+ // window will do a security check of their own.
+ if (!url.IsVoid() && !aDialog && aNavigate)
+ rv = SecurityCheckURL(url.get(), getter_AddRefs(uri));
+ } else if (mDoc) {
+ mDoc->SetUseCounter(eUseCounter_custom_WindowOpenEmptyUrl);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ PopupBlocker::PopupControlState abuseLevel =
+ PopupBlocker::GetPopupControlState();
+ if (checkForPopup) {
+ abuseLevel = mBrowsingContext->RevisePopupAbuseLevel(abuseLevel);
+ if (abuseLevel >= PopupBlocker::openBlocked) {
+ if (!aCalledNoScript) {
+ // If script in some other window is doing a window.open on us and
+ // it's being blocked, then it's OK to close us afterwards, probably.
+ // But if we're doing a window.open on ourselves and block the popup,
+ // prevent this window from closing until after this script terminates
+ // so that whatever popup blocker UI the app has will be visible.
+ nsCOMPtr<nsPIDOMWindowInner> entryWindow =
+ do_QueryInterface(GetEntryGlobal());
+ // Note that entryWindow can be null here if some JS component was the
+ // place where script was entered for this JS execution.
+ if (entryWindow && entryWindow->GetOuterWindow() == this) {
+ mBlockScriptedClosingFlag = true;
+ closeUnblocker.emplace(this);
+ }
+ }
+
+ FireAbuseEvents(aUrl, windowName, aOptions);
+ return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE;
+ }
+ }
+
+ RefPtr<BrowsingContext> domReturn;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(wwatch, rv);
+
+ NS_ConvertUTF16toUTF8 name(windowName);
+
+ nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
+ NS_ENSURE_STATE(pwwatch);
+
+ MOZ_ASSERT_IF(checkForPopup, abuseLevel < PopupBlocker::openBlocked);
+ // At this point we should know for a fact that if checkForPopup then
+ // abuseLevel < PopupBlocker::openBlocked, so we could just check for
+ // abuseLevel == PopupBlocker::openControlled. But let's be defensive just in
+ // case and treat anything that fails the above assert as a spam popup too, if
+ // it ever happens.
+ bool isPopupSpamWindow =
+ checkForPopup && (abuseLevel >= PopupBlocker::openControlled);
+
+ const auto wwPrintKind = [&] {
+ switch (aPrintKind) {
+ case PrintKind::None:
+ return nsPIWindowWatcher::PRINT_NONE;
+ case PrintKind::InternalPrint:
+ return nsPIWindowWatcher::PRINT_INTERNAL;
+ case PrintKind::WindowDotPrint:
+ return nsPIWindowWatcher::PRINT_WINDOW_DOT_PRINT;
+ }
+ MOZ_ASSERT_UNREACHABLE("Wat");
+ return nsPIWindowWatcher::PRINT_NONE;
+ }();
+
+ {
+ // Reset popup state while opening a window to prevent the
+ // current state from being active the whole time a modal
+ // dialog is open.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ if (!aCalledNoScript) {
+ // We asserted at the top of this function that aNavigate is true for
+ // !aCalledNoScript.
+ rv = pwwatch->OpenWindow2(this, url, name, options,
+ /* aCalledFromScript = */ true, aDialog,
+ aNavigate, argv, isPopupSpamWindow,
+ forceNoOpener, forceNoReferrer, wwPrintKind,
+ aLoadState, getter_AddRefs(domReturn));
+ } else {
+ // Force a system caller here so that the window watcher won't screw us
+ // up. We do NOT want this case looking at the JS context on the stack
+ // when searching. Compare comments on
+ // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow.
+
+ // Note: Because nsWindowWatcher is so broken, it's actually important
+ // that we don't force a system caller here, because that screws it up
+ // when it tries to compute the caller principal to associate with dialog
+ // arguments. That whole setup just really needs to be rewritten. :-(
+ Maybe<AutoNoJSAPI> nojsapi;
+ if (!aContentModal) {
+ nojsapi.emplace();
+ }
+
+ rv = pwwatch->OpenWindow2(this, url, name, options,
+ /* aCalledFromScript = */ false, aDialog,
+ aNavigate, aExtraArgument, isPopupSpamWindow,
+ forceNoOpener, forceNoReferrer, wwPrintKind,
+ aLoadState, getter_AddRefs(domReturn));
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // success!
+
+ if (!aCalledNoScript && !windowExists && uri && !forceNoOpener) {
+ MaybeAllowStorageForOpenedWindow(uri);
+ }
+
+ if (domReturn && aDoJSFixups) {
+ nsCOMPtr<nsIDOMChromeWindow> chrome_win(
+ do_QueryInterface(domReturn->GetDOMWindow()));
+ if (!chrome_win) {
+ // A new non-chrome window was created from a call to
+ // window.open() from JavaScript, make sure there's a document in
+ // the new window. We do this by simply asking the new window for
+ // its document, this will synchronously create an empty document
+ // if there is no document in the window.
+ // XXXbz should this just use EnsureInnerWindow()?
+
+ // Force document creation.
+ if (nsPIDOMWindowOuter* win = domReturn->GetDOMWindow()) {
+ nsCOMPtr<Document> doc = win->GetDoc();
+ Unused << doc;
+ }
+ }
+ }
+
+ domReturn.forget(aReturn);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::MaybeAllowStorageForOpenedWindow(nsIURI* aURI) {
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ // No 3rd party URL/window.
+ if (!AntiTrackingUtils::IsThirdPartyWindow(inner, aURI)) {
+ return;
+ }
+
+ Document* doc = inner->GetDoc();
+ if (!doc) {
+ return;
+ }
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ aURI, doc->NodePrincipal()->OriginAttributesRef());
+
+ // We don't care when the asynchronous work finishes here.
+ Unused << StorageAccessAPIHelper::AllowAccessFor(
+ principal, GetBrowsingContext(), ContentBlockingNotifier::eOpener);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter: Helper Functions
+//*****************************************************************************
+
+already_AddRefed<nsIDocShellTreeOwner> nsPIDOMWindowOuter::GetTreeOwner() {
+ // If there's no docShellAsItem, this window must have been closed,
+ // in that case there is no tree owner.
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ return treeOwner.forget();
+}
+
+already_AddRefed<nsIBaseWindow> nsPIDOMWindowOuter::GetTreeOwnerWindow() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+
+ // If there's no mDocShell, this window must have been closed,
+ // in that case there is no tree owner.
+
+ if (mDocShell) {
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ return baseWindow.forget();
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsPIDOMWindowOuter::GetWebBrowserChrome() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
+ return browserChrome.forget();
+}
+
+nsIScrollableFrame* nsGlobalWindowOuter::GetScrollFrame() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (presShell) {
+ return presShell->GetRootScrollFrameAsScrollable();
+ }
+ return nullptr;
+}
+
+nsresult nsGlobalWindowOuter::SecurityCheckURL(const char* aURL,
+ nsIURI** aURI) {
+ nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
+ do_QueryInterface(GetEntryGlobal());
+ if (!sourceWindow) {
+ sourceWindow = GetCurrentInnerWindow();
+ }
+ AutoJSContext cx;
+ nsGlobalWindowInner* sourceWin = nsGlobalWindowInner::Cast(sourceWindow);
+ JSAutoRealm ar(cx, sourceWin->GetGlobalJSObject());
+
+ // Resolve the baseURI, which could be relative to the calling window.
+ //
+ // Note the algorithm to get the base URI should match the one
+ // used to actually kick off the load in nsWindowWatcher.cpp.
+ nsCOMPtr<Document> doc = sourceWindow->GetDoc();
+ nsIURI* baseURI = nullptr;
+ auto encoding = UTF_8_ENCODING; // default to utf-8
+ if (doc) {
+ baseURI = doc->GetDocBaseURI();
+ encoding = doc->GetDocumentCharacterSet();
+ }
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL),
+ encoding, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckLoadURIFromScript(
+ cx, uri))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::FlushPendingNotifications(FlushType aType) {
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(aType);
+ }
+}
+
+void nsGlobalWindowOuter::EnsureSizeAndPositionUpToDate() {
+ // If we're a subframe, make sure our size is up to date. Make sure to go
+ // through the document chain rather than the window chain to not flush on
+ // detached iframes, see bug 1545516.
+ if (mDoc && mDoc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+ RefPtr<Document> parent = mDoc->GetInProcessParentDocument();
+ parent->FlushPendingNotifications(FlushType::Layout);
+ }
+}
+
+already_AddRefed<nsISupports> nsGlobalWindowOuter::SaveWindowState() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (!mContext || !GetWrapperPreserveColor()) {
+ // The window may be getting torn down; don't bother saving state.
+ return nullptr;
+ }
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ NS_ASSERTION(inner, "No inner window to save");
+
+ if (WindowContext* wc = inner->GetWindowContext()) {
+ MOZ_ASSERT(!wc->GetWindowStateSaved());
+ Unused << wc->SetWindowStateSaved(true);
+ }
+
+ // Don't do anything else to this inner window! After this point, all
+ // calls to SetTimeoutOrInterval will create entries in the timeout
+ // list that will only run after this window has come out of the bfcache.
+ // Also, while we're frozen, we won't dispatch online/offline events
+ // to the page.
+ inner->Freeze();
+
+ nsCOMPtr<nsISupports> state = new WindowStateHolder(inner);
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("saving window state, state = %p", (void*)state));
+
+ return state.forget();
+}
+
+nsresult nsGlobalWindowOuter::RestoreWindowState(nsISupports* aState) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (!mContext || !GetWrapperPreserveColor()) {
+ // The window may be getting torn down; don't bother restoring state.
+ return NS_OK;
+ }
+
+ nsCOMPtr<WindowStateHolder> holder = do_QueryInterface(aState);
+ NS_ENSURE_TRUE(holder, NS_ERROR_FAILURE);
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("restoring window state, state = %p", (void*)holder));
+
+ // And we're ready to go!
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+
+ // if a link is focused, refocus with the FLAG_SHOWRING flag set. This makes
+ // it easy to tell which link was last clicked when going back a page.
+ RefPtr<Element> focusedElement = inner->GetFocusedElement();
+ if (nsContentUtils::ContentIsLink(focusedElement)) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ fm->SetFocus(focusedElement, nsIFocusManager::FLAG_NOSCROLL |
+ nsIFocusManager::FLAG_SHOWRING);
+ }
+ }
+
+ if (WindowContext* wc = inner->GetWindowContext()) {
+ MOZ_ASSERT(wc->GetWindowStateSaved());
+ Unused << wc->SetWindowStateSaved(false);
+ }
+
+ inner->Thaw();
+
+ holder->DidRestoreWindow();
+
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::AddSizeOfIncludingThis(
+ nsWindowSizes& aWindowSizes) const {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ aWindowSizes.mState.mMallocSizeOf(this);
+}
+
+uint32_t nsGlobalWindowOuter::GetAutoActivateVRDisplayID() {
+ uint32_t retVal = mAutoActivateVRDisplayID;
+ mAutoActivateVRDisplayID = 0;
+ return retVal;
+}
+
+void nsGlobalWindowOuter::SetAutoActivateVRDisplayID(
+ uint32_t aAutoActivateVRDisplayID) {
+ mAutoActivateVRDisplayID = aAutoActivateVRDisplayID;
+}
+
+already_AddRefed<nsWindowRoot> nsGlobalWindowOuter::GetWindowRootOuter() {
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ return root.forget().downcast<nsWindowRoot>();
+}
+
+nsIDOMWindowUtils* nsGlobalWindowOuter::WindowUtils() {
+ if (!mWindowUtils) {
+ mWindowUtils = new nsDOMWindowUtils(this);
+ }
+ return mWindowUtils;
+}
+
+bool nsGlobalWindowOuter::IsInSyncOperation() {
+ return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
+}
+
+// Note: This call will lock the cursor, it will not change as it moves.
+// To unlock, the cursor must be set back to Auto.
+void nsGlobalWindowOuter::SetCursorOuter(const nsACString& aCursor,
+ ErrorResult& aError) {
+ auto cursor = StyleCursorKind::Auto;
+ if (!Servo_CursorKind_Parse(&aCursor, &cursor)) {
+ // FIXME: It's a bit weird that this doesn't throw but stuff below does, but
+ // matches previous behavior so...
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext;
+ if (mDocShell) {
+ presContext = mDocShell->GetPresContext();
+ }
+
+ if (presContext) {
+ // Need root widget.
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsView* rootView = vm->GetRootView();
+ if (!rootView) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIWidget* widget = rootView->GetNearestWidget(nullptr);
+ if (!widget) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Call esm and set cursor.
+ aError = presContext->EventStateManager()->SetCursor(
+ cursor, nullptr, {}, Nothing(), widget, true);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalWindowOuter::GetBrowserDOMWindow(nsIBrowserDOMWindow** aBrowserWindow) {
+ MOZ_RELEASE_ASSERT(IsChromeWindow());
+ FORWARD_TO_INNER(GetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
+}
+
+nsIBrowserDOMWindow* nsGlobalWindowOuter::GetBrowserDOMWindowOuter() {
+ MOZ_ASSERT(IsChromeWindow());
+ return mChromeFields.mBrowserDOMWindow;
+}
+
+void nsGlobalWindowOuter::SetBrowserDOMWindowOuter(
+ nsIBrowserDOMWindow* aBrowserWindow) {
+ MOZ_ASSERT(IsChromeWindow());
+ mChromeFields.mBrowserDOMWindow = aBrowserWindow;
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowOuter::GetMessageManager() {
+ if (!mInnerWindow) {
+ NS_WARNING("No inner window available!");
+ return nullptr;
+ }
+ return GetCurrentInnerWindowInternal()->MessageManager();
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowOuter::GetGroupMessageManager(
+ const nsAString& aGroup) {
+ if (!mInnerWindow) {
+ NS_WARNING("No inner window available!");
+ return nullptr;
+ }
+ return GetCurrentInnerWindowInternal()->GetGroupMessageManager(aGroup);
+}
+
+void nsGlobalWindowOuter::InitWasOffline() { mWasOffline = NS_IsOffline(); }
+
+#if defined(_WINDOWS_) && !defined(MOZ_WRAPPED_WINDOWS_H)
+# pragma message( \
+ "wrapper failure reason: " MOZ_WINDOWS_WRAPPER_DISABLED_REASON)
+# error "Never include unwrapped windows.h in this file!"
+#endif
+
+// Helper called by methods that move/resize the window,
+// to ensure the presContext (if any) is aware of resolution
+// change that may happen in multi-monitor configuration.
+void nsGlobalWindowOuter::CheckForDPIChange() {
+ if (mDocShell) {
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (presContext) {
+ if (presContext->DeviceContext()->CheckDPIChange()) {
+ presContext->UIResolutionChanged();
+ }
+ }
+ }
+}
+
+nsresult nsGlobalWindowOuter::Dispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
+ }
+ return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* nsGlobalWindowOuter::EventTargetFor(
+ TaskCategory aCategory) const {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->EventTargetFor(aCategory);
+ }
+ return DispatcherTrait::EventTargetFor(aCategory);
+}
+
+AbstractThread* nsGlobalWindowOuter::AbstractMainThreadFor(
+ TaskCategory aCategory) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->AbstractMainThreadFor(aCategory);
+ }
+ return DispatcherTrait::AbstractMainThreadFor(aCategory);
+}
+
+void nsGlobalWindowOuter::MaybeResetWindowName(Document* aNewDocument) {
+ MOZ_ASSERT(aNewDocument);
+
+ if (!StaticPrefs::privacy_window_name_update_enabled()) {
+ return;
+ }
+
+ const LoadingSessionHistoryInfo* info =
+ nsDocShell::Cast(mDocShell)->GetLoadingSessionHistoryInfo();
+ if (!info || info->mForceMaybeResetName.isNothing()) {
+ // We only reset the window name for the top-level content as well as
+ // storing in session entries.
+ if (!GetBrowsingContext()->IsTopContent()) {
+ return;
+ }
+
+ // Following implements https://html.spec.whatwg.org/#history-traversal:
+ // Step 4.2. Check if the loading document has a different origin than the
+ // previous document.
+
+ // We don't need to do anything if we haven't loaded a non-initial document.
+ if (!GetBrowsingContext()->GetHasLoadedNonInitialDocument()) {
+ return;
+ }
+
+ // If we have an existing document, directly check the document prinicpals
+ // with the new document to know if it is cross-origin.
+ //
+ // Note that there will be an issue of initial document handling in Fission
+ // when running the WPT unset_context_name-1.html. In the test, the first
+ // about:blank page would be loaded with the principal of the testing domain
+ // in Fission and the window.name will be set there. Then, The window.name
+ // won't be reset after navigating to the testing page because the principal
+ // is the same. But, it won't be the case for non-Fission mode that the
+ // first about:blank will be loaded with a null principal and the
+ // window.name will be reset when loading the test page.
+ if (mDoc && mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal())) {
+ return;
+ }
+
+ // If we don't have an existing document, and if it's not the initial
+ // about:blank, we could be loading a document because of the
+ // process-switching. In this case, this should be a cross-origin
+ // navigation.
+ } else if (!info->mForceMaybeResetName.ref()) {
+ return;
+ }
+
+ // Step 4.2.2 Store the window.name into all session history entries that have
+ // the same origin as the previous document.
+ nsDocShell::Cast(mDocShell)->StoreWindowNameToSHEntries();
+
+ // Step 4.2.3 Clear the window.name if the browsing context is the top-level
+ // content and doesn't have an opener.
+
+ // We need to reset the window name in case of a cross-origin navigation,
+ // without an opener.
+ RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext();
+ if (opener) {
+ return;
+ }
+
+ Unused << mBrowsingContext->SetName(EmptyString());
+}
+
+nsGlobalWindowOuter::TemporarilyDisableDialogs::TemporarilyDisableDialogs(
+ BrowsingContext* aBC) {
+ BrowsingContextGroup* group = aBC->Group();
+ if (!group) {
+ NS_ERROR(
+ "nsGlobalWindowOuter::TemporarilyDisableDialogs called without a "
+ "browsing context group?");
+ return;
+ }
+
+ if (group) {
+ mGroup = group;
+ mSavedDialogsEnabled = group->GetAreDialogsEnabled();
+ group->SetAreDialogsEnabled(false);
+ }
+}
+
+nsGlobalWindowOuter::TemporarilyDisableDialogs::~TemporarilyDisableDialogs() {
+ if (mGroup) {
+ mGroup->SetAreDialogsEnabled(mSavedDialogsEnabled);
+ }
+}
+
+/* static */
+already_AddRefed<nsGlobalWindowOuter> nsGlobalWindowOuter::Create(
+ nsDocShell* aDocShell, bool aIsChrome) {
+ uint64_t outerWindowID = aDocShell->GetOuterWindowID();
+ RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
+ if (aIsChrome) {
+ window->mIsChrome = true;
+ }
+ window->SetDocShell(aDocShell);
+
+ window->InitWasOffline();
+ return window.forget();
+}
+
+nsIURI* nsPIDOMWindowOuter::GetDocumentURI() const {
+ return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
+}
+
+void nsPIDOMWindowOuter::MaybeCreateDoc() {
+ MOZ_ASSERT(!mDoc);
+ if (nsIDocShell* docShell = GetDocShell()) {
+ // Note that |document| here is the same thing as our mDoc, but we
+ // don't have to explicitly set the member variable because the docshell
+ // has already called SetNewDocument().
+ nsCOMPtr<Document> document = docShell->GetDocument();
+ Unused << document;
+ }
+}
+
+void nsPIDOMWindowOuter::SetChromeEventHandlerInternal(
+ EventTarget* aChromeEventHandler) {
+ // Out-of-line so we don't need to include ContentFrameMessageManager.h in
+ // nsPIDOMWindow.h.
+ mChromeEventHandler = aChromeEventHandler;
+
+ // mParentTarget and mMessageManager will be set when the next event is
+ // dispatched or someone asks for our message manager.
+ mParentTarget = nullptr;
+ mMessageManager = nullptr;
+}
+
+mozilla::dom::DocGroup* nsPIDOMWindowOuter::GetDocGroup() const {
+ Document* doc = GetExtantDoc();
+ if (doc) {
+ return doc->GetDocGroup();
+ }
+ return nullptr;
+}
+
+nsPIDOMWindowOuter::nsPIDOMWindowOuter(uint64_t aWindowID)
+ : mFrameElement(nullptr),
+ mModalStateDepth(0),
+ mSuppressEventHandlingDepth(0),
+ mIsBackground(false),
+ mIsRootOuterWindow(false),
+ mInnerWindow(nullptr),
+ mWindowID(aWindowID),
+ mMarkedCCGeneration(0) {}
+
+nsPIDOMWindowOuter::~nsPIDOMWindowOuter() = default;
diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h
new file mode 100644
index 0000000000..6b3001df08
--- /dev/null
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -0,0 +1,1236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGlobalWindowOuter_h___
+#define nsGlobalWindowOuter_h___
+
+#include "nsPIDOMWindow.h"
+
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+#include "nsInterfaceHashtable.h"
+
+// Local Includes
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsTHashMap.h"
+#include "nsCycleCollectionParticipant.h"
+
+// Interfaces Needed
+#include "nsIBrowserDOMWindow.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIPrincipal.h"
+#include "nsSize.h"
+#include "mozilla/FlushType.h"
+#include "prclist.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "Units.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCheapSets.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "X11UndefineNone.h"
+
+class nsDocShell;
+class nsIArray;
+class nsIBaseWindow;
+class nsIContent;
+class nsICSSDeclaration;
+class nsIDocShellTreeOwner;
+class nsIDOMWindowUtils;
+class nsIScrollableFrame;
+class nsIControllers;
+class nsIPrintSettings;
+class nsIScriptContext;
+class nsIScriptTimeoutHandler;
+class nsIBrowserChild;
+class nsITimeoutHandler;
+class nsIWebBrowserChrome;
+class nsIWebProgressListener;
+class mozIDOMWindowProxy;
+
+class nsDocShellLoadState;
+class nsScreen;
+class nsHistory;
+class nsGlobalWindowObserver;
+class nsGlobalWindowInner;
+class nsDOMWindowUtils;
+struct nsRect;
+
+class nsWindowSizes;
+
+namespace mozilla {
+class AbstractThread;
+class DOMEventTargetHelper;
+class ErrorResult;
+class ThrottledEventQueue;
+namespace dom {
+class BarProp;
+struct ChannelPixelLayout;
+class Console;
+class Crypto;
+class CustomElementRegistry;
+class DocGroup;
+class Document;
+class External;
+class Function;
+class Gamepad;
+enum class ImageBitmapFormat : uint8_t;
+class IncrementalRunnable;
+class IntlUtils;
+class Location;
+class MediaQueryList;
+class Navigator;
+class OwningExternalOrWindowProxy;
+class Promise;
+class PostMessageData;
+class PostMessageEvent;
+class PrintPreviewResultInfo;
+struct RequestInit;
+class RequestOrUSVString;
+class Selection;
+struct SizeToContentConstraints;
+class SpeechSynthesis;
+class Timeout;
+class U2F;
+class VRDisplay;
+enum class VRDisplayEventReason : uint8_t;
+class VREventObserver;
+class WakeLock;
+class Worklet;
+namespace cache {
+class CacheStorage;
+} // namespace cache
+class IDBFactory;
+} // namespace dom
+namespace layout {
+class RemotePrintJobChild;
+} // namespace layout
+} // namespace mozilla
+
+extern already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
+ JSContext* aCx, nsGlobalWindowInner* aWindow,
+ mozilla::dom::Function& aFunction,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+extern already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
+ JSContext* aCx, nsGlobalWindowInner* aWindow, const nsAString& aExpression,
+ mozilla::ErrorResult& aError);
+
+extern const JSClass OuterWindowProxyClass;
+
+//*****************************************************************************
+// nsGlobalWindowOuter
+//*****************************************************************************
+
+// nsGlobalWindowOuter inherits PRCList for maintaining a list of all inner
+// windows still in memory for any given outer window. This list is needed to
+// ensure that mOuterWindow doesn't end up dangling. The nature of PRCList means
+// that the window itself is always in the list, and an outer window's list will
+// also contain all inner window objects that are still in memory (and in
+// reality all inner window object's lists also contain its outer and all other
+// inner windows belonging to the same outer window, but that's an unimportant
+// side effect of inheriting PRCList).
+
+class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
+ public nsPIDOMWindowOuter,
+ private nsIDOMWindow
+ // NOTE: This interface is private, as it's only
+ // implemented on chrome windows.
+ ,
+ private nsIDOMChromeWindow,
+ public nsIScriptGlobalObject,
+ public nsIScriptObjectPrincipal,
+ public nsSupportsWeakReference,
+ public nsIInterfaceRequestor,
+ public PRCListStr {
+ public:
+ using OuterWindowByIdTable =
+ nsTHashMap<nsUint64HashKey, nsGlobalWindowOuter*>;
+
+ using PrintPreviewResolver =
+ std::function<void(const mozilla::dom::PrintPreviewResultInfo&)>;
+
+ static void AssertIsOnMainThread()
+#ifdef DEBUG
+ ;
+#else
+ {
+ }
+#endif
+
+ static nsGlobalWindowOuter* Cast(nsPIDOMWindowOuter* aPIWin) {
+ return static_cast<nsGlobalWindowOuter*>(aPIWin);
+ }
+ static const nsGlobalWindowOuter* Cast(const nsPIDOMWindowOuter* aPIWin) {
+ return static_cast<const nsGlobalWindowOuter*>(aPIWin);
+ }
+ static nsGlobalWindowOuter* Cast(mozIDOMWindowProxy* aWin) {
+ return Cast(nsPIDOMWindowOuter::From(aWin));
+ }
+
+ bool IsOuterWindow() const final { return true; } // Overriding EventTarget
+
+ static nsGlobalWindowOuter* GetOuterWindowWithId(uint64_t aWindowID) {
+ AssertIsOnMainThread();
+
+ if (!sOuterWindowsById) {
+ return nullptr;
+ }
+
+ nsGlobalWindowOuter* outerWindow = sOuterWindowsById->Get(aWindowID);
+ return outerWindow;
+ }
+
+ static OuterWindowByIdTable* GetWindowsTable() {
+ AssertIsOnMainThread();
+
+ return sOuterWindowsById;
+ }
+
+ static nsGlobalWindowOuter* FromSupports(nsISupports* supports) {
+ // Make sure this matches the casts we do in QueryInterface().
+ return (nsGlobalWindowOuter*)(mozilla::dom::EventTarget*)supports;
+ }
+
+ static already_AddRefed<nsGlobalWindowOuter> Create(nsDocShell* aDocShell,
+ bool aIsChrome);
+
+ // public methods
+ nsPIDOMWindowOuter* GetPrivateParent();
+
+ // callback for close event
+ void ReallyCloseWindow();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return EnsureInnerWindow() ? GetWrapper() : nullptr;
+ }
+
+ // nsIGlobalObject
+ bool ShouldResistFingerprinting(
+ RFPTarget aTarget = RFPTarget::Unknown) const final;
+ mozilla::OriginTrials Trials() const final;
+ mozilla::dom::FontFaceSet* GetFonts() final;
+
+ // nsIGlobalJSObjectHolder
+ JSObject* GetGlobalJSObject() final { return GetWrapper(); }
+ JSObject* GetGlobalJSObjectPreserveColor() const final {
+ return GetWrapperPreserveColor();
+ }
+
+ virtual nsresult EnsureScriptEnvironment() override;
+
+ virtual nsIScriptContext* GetScriptContext() override;
+
+ void PoisonOuterWindowProxy(JSObject* aObject);
+
+ virtual bool IsBlackForCC(bool aTracingNeeded = true) override;
+
+ // nsIScriptObjectPrincipal
+ virtual nsIPrincipal* GetPrincipal() override;
+
+ virtual nsIPrincipal* GetEffectiveCookiePrincipal() override;
+
+ virtual nsIPrincipal* GetEffectiveStoragePrincipal() override;
+
+ virtual nsIPrincipal* PartitionedPrincipal() override;
+
+ // nsIDOMWindow
+ NS_DECL_NSIDOMWINDOW
+
+ // nsIDOMChromeWindow (only implemented on chrome windows)
+ NS_DECL_NSIDOMCHROMEWINDOW
+
+ mozilla::dom::ChromeMessageBroadcaster* GetMessageManager();
+ mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(
+ const nsAString& aGroup);
+
+ nsresult OpenJS(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ mozilla::dom::BrowsingContext** _retval);
+
+ virtual mozilla::EventListenerManager* GetExistingListenerManager()
+ const override;
+
+ virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
+
+ bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
+
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
+
+ virtual nsIGlobalObject* GetOwnerGlobal() const override;
+
+ EventTarget* GetTargetForEventTargetChain() override;
+
+ using mozilla::dom::EventTarget::DispatchEvent;
+ bool DispatchEvent(mozilla::dom::Event& aEvent,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv) override;
+
+ void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
+
+ nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
+
+ // nsPIDOMWindow
+ virtual nsPIDOMWindowOuter* GetPrivateRoot() override;
+
+ // Outer windows only.
+ virtual void SetIsBackground(bool aIsBackground) override;
+ virtual void SetChromeEventHandler(
+ mozilla::dom::EventTarget* aChromeEventHandler) override;
+
+ // Outer windows only.
+ virtual void SetInitialPrincipal(
+ nsIPrincipal* aNewWindowPrincipal, nsIContentSecurityPolicy* aCSP,
+ const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCoep)
+ override;
+
+ virtual already_AddRefed<nsISupports> SaveWindowState() override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult RestoreWindowState(
+ nsISupports* aState) override;
+
+ virtual bool IsSuspended() const override;
+ virtual bool IsFrozen() const override;
+
+ virtual nsresult FireDelayedDOMEvents(bool aIncludeSubWindows) override;
+
+ // Outer windows only.
+ bool WouldReuseInnerWindow(Document* aNewDocument);
+
+ void DetachFromDocShell(bool aIsBeingDiscarded);
+
+ virtual nsresult SetNewDocument(
+ Document* aDocument, nsISupports* aState, bool aForceReuseInnerWindow,
+ mozilla::dom::WindowGlobalChild* aActor = nullptr) override;
+
+ // Outer windows only.
+ static void PrepareForProcessChange(JSObject* aProxy);
+
+ // Outer windows only.
+ void DispatchDOMWindowCreated();
+
+ // Outer windows only.
+ virtual void EnsureSizeAndPositionUpToDate() override;
+
+ virtual void SuppressEventHandling() override;
+ virtual void UnsuppressEventHandling() override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsGlobalWindowOuter* EnterModalState()
+ override;
+ virtual void LeaveModalState() override;
+
+ // Outer windows only.
+ virtual bool CanClose() override;
+ virtual void ForceClose() override;
+
+ // Outer windows only.
+ virtual bool DispatchCustomEvent(
+ const nsAString& aEventName,
+ mozilla::ChromeOnlyDispatch aChromeOnlyDispatch) override;
+ bool DispatchResizeEvent(const mozilla::CSSIntSize& aSize);
+
+ // For accessing protected field mFullscreen
+ friend class FullscreenTransitionTask;
+
+ // Outer windows only.
+ nsresult SetFullscreenInternal(FullscreenReason aReason,
+ bool aIsFullscreen) final;
+ void FullscreenWillChange(bool aIsFullscreen) final;
+ void FinishFullscreenChange(bool aIsFullscreen) final;
+ void ForceFullScreenInWidget() final;
+ void MacFullscreenMenubarOverlapChanged(
+ mozilla::DesktopCoord aOverlapAmount) final;
+ bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
+ nsIWidget* aWidget);
+ bool Fullscreen() const;
+
+ // nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetterOuter(
+ uint32_t aIndex);
+
+ already_AddRefed<nsPIDOMWindowOuter> GetInProcessTop() override;
+ // Similar to GetInProcessTop() except that it stops at content frames that
+ // an extension has permission to access. This is used by the third-party
+ // util service in order to determine the top window for a channel which is
+ // used in third-partiness checks.
+ already_AddRefed<nsPIDOMWindowOuter>
+ GetTopExcludingExtensionAccessibleContentFrames(nsIURI* aURIBeingLoaded);
+ nsPIDOMWindowOuter* GetInProcessScriptableTop() override;
+ inline nsGlobalWindowOuter* GetInProcessTopInternal();
+
+ inline nsGlobalWindowOuter* GetInProcessScriptableTopInternal();
+
+ already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
+ const nsAString& aName);
+
+ // Returns true if we've reached the state in windows of this BC group
+ // where we ask the user if further dialogs should be blocked.
+ //
+ // This function is implemented in terms of
+ // BrowsingContextGroup::DialogsAreBeingAbused.
+ bool ShouldPromptToBlockDialogs();
+
+ // These functions are used for controlling and determining whether dialogs
+ // (alert, prompt, confirm) are currently allowed in this browsing context
+ // group. If you want to temporarily disable dialogs, please use
+ // TemporarilyDisableDialogs, not EnableDialogs/DisableDialogs, because
+ // correctly determining whether to re-enable dialogs is actually quite
+ // difficult.
+ void EnableDialogs();
+ void DisableDialogs();
+ // Outer windows only.
+ bool AreDialogsEnabled();
+
+ class MOZ_RAII TemporarilyDisableDialogs {
+ public:
+ explicit TemporarilyDisableDialogs(mozilla::dom::BrowsingContext* aBC);
+ ~TemporarilyDisableDialogs();
+
+ private:
+ // This is the browsing context group whose dialog state we messed
+ // with. We just want to keep it alive, because we plan to poke at its
+ // members in our destructor.
+ RefPtr<mozilla::dom::BrowsingContextGroup> mGroup;
+ // This is not a AutoRestore<bool> because that would require careful
+ // member destructor ordering, which is a bit fragile. This way we can
+ // explicitly restore things before we drop our ref to mGroup.
+ bool mSavedDialogsEnabled = false;
+ };
+ friend class TemporarilyDisableDialogs;
+
+ nsIScriptContext* GetContextInternal();
+
+ nsGlobalWindowInner* GetCurrentInnerWindowInternal() const;
+
+ nsGlobalWindowInner* EnsureInnerWindowInternal();
+
+ bool IsCreatingInnerWindow() const { return mCreatingInnerWindow; }
+
+ bool IsChromeWindow() const { return mIsChrome; }
+
+ // GetScrollFrame does not flush. Callers should do it themselves as needed,
+ // depending on which info they actually want off the scrollable frame.
+ nsIScrollableFrame* GetScrollFrame();
+
+ // Outer windows only.
+ void UnblockScriptedClosing();
+
+ static void Init();
+ static void ShutDown();
+ static bool IsCallerChrome();
+
+ friend class WindowStateHolder;
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
+ nsGlobalWindowOuter, mozilla::dom::EventTarget)
+
+ virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod) override;
+ virtual void SetReadyForFocus() override;
+ virtual void PageHidden() override;
+
+ /**
+ * Set a arguments for this window. This will be set on the window
+ * right away (if there's an existing document) and it will also be
+ * installed on the window when the next document is loaded.
+ *
+ * This function passes |arguments| back from nsWindowWatcher to
+ * nsGlobalWindow.
+ */
+ nsresult SetArguments(nsIArray* aArguments);
+
+ bool IsClosedOrClosing() {
+ return (mIsClosed || mInClose || mHavePendingClose || mCleanedUp);
+ }
+
+ bool IsCleanedUp() const { return mCleanedUp; }
+
+ virtual void FirePopupBlockedEvent(
+ Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) override;
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
+
+ void AllowScriptsToClose() { mAllowScriptsToClose = true; }
+
+ // Outer windows only.
+ uint32_t GetAutoActivateVRDisplayID();
+ // Outer windows only.
+ void SetAutoActivateVRDisplayID(uint32_t aAutoActivateVRDisplayID);
+
+#define EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::EventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetEventHandler(nsGkAtoms::on##name_) : nullptr; \
+ } \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(nsGkAtoms::on##name_, handler); \
+ } \
+ }
+#define ERROR_EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::OnErrorEventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetOnErrorEventHandler() : nullptr; \
+ } \
+ void SetOn##name_(mozilla::dom::OnErrorEventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(handler); \
+ } \
+ }
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::OnBeforeUnloadEventHandlerNonNull* GetOn##name_() { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetOnBeforeUnloadEventHandler() : nullptr; \
+ } \
+ void SetOn##name_( \
+ mozilla::dom::OnBeforeUnloadEventHandlerNonNull* handler) { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(handler); \
+ } \
+ }
+#define WINDOW_ONLY_EVENT EVENT
+#define TOUCH_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef TOUCH_EVENT
+#undef WINDOW_ONLY_EVENT
+#undef BEFOREUNLOAD_EVENT
+#undef ERROR_EVENT
+#undef EVENT
+
+ nsISupports* GetParentObject() { return nullptr; }
+
+ Document* GetDocument() { return GetDoc(); }
+ void GetNameOuter(nsAString& aName);
+ void SetNameOuter(const nsAString& aName, mozilla::ErrorResult& aError);
+ mozilla::dom::Location* GetLocation() override;
+ void GetStatusOuter(nsAString& aStatus);
+ void SetStatusOuter(const nsAString& aStatus);
+ void CloseOuter(bool aTrustedCaller);
+ nsresult Close() override;
+ bool GetClosedOuter();
+ bool Closed() override;
+ void StopOuter(mozilla::ErrorResult& aError);
+ // TODO: Convert FocusOuter() to MOZ_CAN_RUN_SCRIPT and get rid of the
+ // kungFuDeathGrip in it.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FocusOuter(
+ mozilla::dom::CallerType aCallerType, bool aFromOtherProcess,
+ uint64_t aActionId);
+ nsresult Focus(mozilla::dom::CallerType aCallerType) override;
+ // TODO: Convert BlurOuter() to MOZ_CAN_RUN_SCRIPT and get rid of the
+ // kungFuDeathGrip in it.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void BlurOuter(
+ mozilla::dom::CallerType aCallerType);
+ mozilla::dom::WindowProxyHolder GetFramesOuter();
+ uint32_t Length();
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTopOuter();
+
+ nsresult GetPrompter(nsIPrompt** aPrompt) override;
+
+ protected:
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder>
+ GetOpenerWindowOuter();
+ // Initializes the mWasOffline member variable
+ void InitWasOffline();
+
+ public:
+ nsPIDOMWindowOuter* GetSameProcessOpener();
+ already_AddRefed<mozilla::dom::BrowsingContext> GetOpenerBrowsingContext();
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetOpener() override;
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetParentOuter();
+ already_AddRefed<nsPIDOMWindowOuter> GetInProcessParent() override;
+ nsPIDOMWindowOuter* GetInProcessScriptableParent() override;
+ nsPIDOMWindowOuter* GetInProcessScriptableParentOrNull() override;
+ mozilla::dom::Element* GetFrameElement(nsIPrincipal& aSubjectPrincipal);
+ mozilla::dom::Element* GetFrameElement() override;
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenOuter(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ mozilla::ErrorResult& aError);
+ nsresult Open(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, nsDocShellLoadState* aLoadState,
+ bool aForceNoOpener,
+ mozilla::dom::BrowsingContext** _retval) override;
+ mozilla::dom::Navigator* GetNavigator() override;
+
+ protected:
+ bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ public:
+ void AlertOuter(const nsAString& aMessage, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ bool ConfirmOuter(const nsAString& aMessage, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void PromptOuter(const nsAString& aMessage, const nsAString& aInitial,
+ nsAString& aReturn, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT void PrintOuter(mozilla::ErrorResult& aError);
+
+ enum class IsPreview : bool { No, Yes };
+ enum class IsForWindowDotPrint : bool { No, Yes };
+ MOZ_CAN_RUN_SCRIPT mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder>
+ Print(nsIPrintSettings*,
+ mozilla::layout::RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener*, nsIDocShell*, IsPreview, IsForWindowDotPrint,
+ PrintPreviewResolver&&, mozilla::ErrorResult&);
+ mozilla::dom::Selection* GetSelectionOuter();
+ already_AddRefed<mozilla::dom::Selection> GetSelection() override;
+ nsScreen* GetScreen();
+ void MoveToOuter(int32_t aXPos, int32_t aYPos,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void MoveByOuter(int32_t aXDif, int32_t aYDif,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult MoveBy(int32_t aXDif, int32_t aYDif) override;
+ void ResizeToOuter(int32_t aWidth, int32_t aHeight,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ double GetScrollXOuter();
+ double GetScrollYOuter();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void SizeToContentOuter(mozilla::dom::CallerType,
+ const mozilla::dom::SizeToContentConstraints&,
+ mozilla::ErrorResult&);
+ nsIControllers* GetControllersOuter(mozilla::ErrorResult& aError);
+ nsresult GetControllers(nsIControllers** aControllers) override;
+ float GetMozInnerScreenXOuter(mozilla::dom::CallerType aCallerType);
+ float GetMozInnerScreenYOuter(mozilla::dom::CallerType aCallerType);
+ bool GetFullscreenOuter();
+ bool GetFullScreen() override;
+ void SetFullscreenOuter(bool aFullscreen, mozilla::ErrorResult& aError);
+ nsresult SetFullScreen(bool aFullscreen) override;
+ bool FindOuter(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
+ bool aShowDialog, mozilla::ErrorResult& aError);
+
+ mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenDialogOuter(
+ JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
+ mozilla::ErrorResult& aError);
+ nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, nsISupports* aExtraArgument,
+ mozilla::dom::BrowsingContext** _retval) override;
+ void UpdateCommands(const nsAString& anAction, mozilla::dom::Selection* aSel,
+ int16_t aReason) override;
+
+ already_AddRefed<mozilla::dom::BrowsingContext> GetContentInternal(
+ mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aError);
+ void GetContentOuter(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ // ChromeWindow bits. Do NOT call these unless your window is in
+ // fact chrome.
+ nsIBrowserDOMWindow* GetBrowserDOMWindowOuter();
+ void SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow);
+ void SetCursorOuter(const nsACString& aCursor, mozilla::ErrorResult& aError);
+
+ void GetReturnValueOuter(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void GetReturnValue(JSContext* aCx, JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void SetReturnValueOuter(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void SetReturnValue(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsWindowRoot> GetWindowRootOuter();
+
+ nsIDOMWindowUtils* WindowUtils();
+
+ virtual bool IsInSyncOperation() override;
+
+ public:
+ double GetInnerWidthOuter(mozilla::ErrorResult& aError);
+
+ protected:
+ nsresult GetInnerWidth(double* aInnerWidth) override;
+ void SetInnerWidthOuter(double aInnerWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ public:
+ double GetInnerHeightOuter(mozilla::ErrorResult& aError);
+
+ protected:
+ nsresult GetInnerHeight(double* aInnerHeight) override;
+ void SetInnerHeightOuter(double aInnerHeight,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenXOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenXOuter(int32_t aScreenX, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenYOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenYOuter(int32_t aScreenY, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterWidthOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterWidthOuter(int32_t aOuterWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterHeightOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterHeightOuter(int32_t aOuterHeight,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ friend class HashchangeCallback;
+ friend class mozilla::dom::BarProp;
+
+ // Object Management
+ virtual ~nsGlobalWindowOuter();
+ void DropOuterWindowDocs();
+ void CleanUp();
+ void ClearControllers();
+ // Outer windows only.
+ void FinalClose();
+
+ inline void MaybeClearInnerWindow(nsGlobalWindowInner* aExpectedInner);
+
+ // Get the parent, returns null if this is a toplevel window
+ nsPIDOMWindowOuter* GetInProcessParentInternal();
+
+ protected:
+ // Window Control Functions
+
+ // Outer windows only.
+ virtual nsresult OpenNoNavigate(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ mozilla::dom::BrowsingContext** _retval) override;
+
+ private:
+ explicit nsGlobalWindowOuter(uint64_t aWindowID);
+
+ enum class PrintKind : uint8_t { None, InternalPrint, WindowDotPrint };
+
+ /**
+ * @param aUrl the URL we intend to load into the window. If aNavigate is
+ * true, we'll actually load this URL into the window. Otherwise,
+ * aUrl is advisory; OpenInternal will not load the URL into the
+ * new window.
+ *
+ * @param aName the name to use for the new window
+ *
+ * @param aOptions the window options to use for the new window
+ *
+ * @param aDialog true when called from variants of OpenDialog. If this is
+ * true, this method will skip popup blocking checks. The aDialog
+ * argument is passed on to the window watcher.
+ *
+ * @param aCalledNoScript true when called via the [noscript] open()
+ * and openDialog() methods. When this is true, we do NOT want to use
+ * the JS stack for things like caller determination.
+ *
+ * @param aDoJSFixups true when this is the content-accessible JS version of
+ * window opening. When true, popups do not cause us to throw, we save
+ * the caller's principal in the new window for later consumption, and
+ * we make sure that there is a document in the newly-opened window.
+ * Note that this last will only be done if the newly-opened window is
+ * non-chrome.
+ *
+ * @param aNavigate true if we should navigate to the provided URL, false
+ * otherwise. When aNavigate is false, we also skip our can-load
+ * security check, on the assumption that whoever *actually* loads this
+ * page will do their own security check.
+ *
+ * @param argv The arguments to pass to the new window. The first
+ * three args, if present, will be aUrl, aName, and aOptions. So this
+ * param only matters if there are more than 3 arguments.
+ *
+ * @param aExtraArgument Another way to pass arguments in. This is mutually
+ * exclusive with the argv approach.
+ *
+ * @param aLoadState to be passed on along to the windowwatcher.
+ *
+ * @param aForceNoOpener if true, will act as if "noopener" were passed in
+ * aOptions, but without affecting any other window
+ * features.
+ *
+ * @param aPrintKind Whether this is a browser created for printing, and
+ * if so for which kind of print.
+ *
+ * @param aReturn [out] The window that was opened, if any. Will be null if
+ * aForceNoOpener is true of if aOptions contains
+ * "noopener".
+ *
+ * Outer windows only.
+ */
+ nsresult OpenInternal(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, bool aDialog,
+ bool aContentModal, bool aCalledNoScript,
+ bool aDoJSFixups, bool aNavigate, nsIArray* argv,
+ nsISupports* aExtraArgument,
+ nsDocShellLoadState* aLoadState, bool aForceNoOpener,
+ PrintKind aPrintKind,
+ mozilla::dom::BrowsingContext** aReturn);
+
+ public:
+ nsresult SecurityCheckURL(const char* aURL, nsIURI** aURI);
+
+ mozilla::dom::PopupBlocker::PopupControlState RevisePopupAbuseLevel(
+ mozilla::dom::PopupBlocker::PopupControlState aState);
+ void FireAbuseEvents(const nsAString& aPopupURL,
+ const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures);
+
+ void FlushPendingNotifications(mozilla::FlushType aType);
+
+ // Outer windows only.
+ void EnsureReflowFlushAndPaint();
+ void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height,
+ mozilla::dom::CallerType aCallerType);
+ void CheckSecurityLeftAndTop(int32_t* left, int32_t* top,
+ mozilla::dom::CallerType aCallerType);
+
+ // Outer windows only.
+ // Arguments to this function should have values in app units
+ void SetCSSViewportWidthAndHeight(nscoord width, nscoord height);
+
+ static bool CanSetProperty(const char* aPrefName);
+
+ static void MakeMessageWithPrincipal(nsAString& aOutMessage,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aUseHostPort,
+ const char* aNullMessage,
+ const char* aContentMessage,
+ const char* aFallbackMessage);
+
+ // Outer windows only.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ bool CanMoveResizeWindows(mozilla::dom::CallerType aCallerType);
+
+ // If aDoFlush is true, we'll flush our own layout; otherwise we'll try to
+ // just flush our parent and only flush ourselves if we think we need to.
+ // Outer windows only.
+ mozilla::CSSPoint GetScrollXY(bool aDoFlush);
+
+ int32_t GetScrollBoundaryOuter(mozilla::Side aSide);
+
+ // Outer windows only.
+ nsresult GetInnerSize(mozilla::CSSSize& aSize);
+ mozilla::CSSIntSize GetOuterSize(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetInnerSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenCoord(int32_t aCoordCSSPixels, bool aIsX,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsRect GetInnerScreenRect();
+ static mozilla::Maybe<mozilla::CSSIntSize> GetRDMDeviceSize(
+ const Document& aDocument);
+
+ // Outer windows only.
+ // If aLookForCallerOnJSStack is true, this method will look at the JS stack
+ // to determine who the caller is. If it's false, it'll use |this| as the
+ // caller.
+ bool WindowExists(const nsAString& aName, bool aForceNoOpener,
+ bool aLookForCallerOnJSStack);
+
+ already_AddRefed<nsIWidget> GetMainWidget();
+ nsIWidget* GetNearestWidget() const;
+
+ bool IsInModalState();
+
+ // Convenience functions for the methods which call methods of nsIBaseWindow
+ // because it takes/returns device pixels. Unfortunately, mPresContext may
+ // have older scale value for the corresponding widget. Therefore, these
+ // helper methods convert between CSS pixels and device pixels with aWindow.
+ //
+ // FIXME(emilio): Seems like updating the pres context dpi sync shouldn't be
+ // all that much work and should avoid some hackiness?
+ mozilla::CSSToLayoutDeviceScale CSSToDevScaleForBaseWindow(nsIBaseWindow*);
+
+ void SetFocusedElement(mozilla::dom::Element* aElement,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) override;
+
+ uint32_t GetFocusMethod() override;
+
+ bool ShouldShowFocusRing() override;
+
+ public:
+ already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
+
+ protected:
+ void NotifyWindowIDDestroyed(const char* aTopic);
+
+ void ClearStatus();
+
+ void UpdateParentTarget() override;
+
+ protected:
+ // Helper for getComputedStyle and getDefaultComputedStyle
+ already_AddRefed<nsICSSDeclaration> GetComputedStyleHelperOuter(
+ mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly, mozilla::ErrorResult& aRv);
+
+ // Outer windows only.
+ void PreloadLocalStorage();
+
+ mozilla::CSSPoint ScreenEdgeSlop();
+ mozilla::CSSCoord ScreenEdgeSlopX() { return ScreenEdgeSlop().X(); }
+ mozilla::CSSCoord ScreenEdgeSlopY() { return ScreenEdgeSlop().Y(); }
+
+ // Returns CSS pixels based on primary screen. Outer windows only.
+ mozilla::CSSIntPoint GetScreenXY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ void PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ public:
+ /**
+ * Compute the principal to use for checking against the target principal in a
+ * postMessage call.
+ *
+ * @param aTargetOrigin The value passed as the targetOrigin argument to the
+ * postMessage call.
+ *
+ * @param aTargetOriginURI The origin of the URI contained in aTargetOrigin
+ * (see GatherPostMessageData).
+ *
+ * @param aCallerPrincipal The principal of the incumbent global of the
+ * postMessage call (see GatherPostMessageData).
+ *
+ * @param aSubjectPrincipal The subject principal for the postMessage call.
+ *
+ * @param aProvidedPrincipal [out] The principal to use for checking against
+ * the target's principal.
+ *
+ * @return Whether the postMessage call should continue or return now.
+ */
+ bool GetPrincipalForPostMessage(const nsAString& aTargetOrigin,
+ nsIURI* aTargetOriginURI,
+ nsIPrincipal* aCallerPrincipal,
+ nsIPrincipal& aSubjectPrincipal,
+ nsIPrincipal** aProvidedPrincipal);
+
+ private:
+ /**
+ * Gather the necessary data from the caller for a postMessage call.
+ *
+ * @param aCx The JSContext.
+ *
+ * @param aTargetOrigin The value passed as the targetOrigin argument to the
+ * postMessage call.
+ *
+ * @param aSource [out] The browsing context for the incumbent global.
+ *
+ * @param aOrigin [out] The value to use for the origin property of the
+ * MessageEvent object.
+ *
+ * @param aTargetOriginURI [out] The origin of the URI contained in
+ * aTargetOrigin, null if aTargetOrigin is "/" or "*".
+ *
+ * @param aCallerPrincipal [out] The principal of the incumbent global of the
+ * postMessage call.
+ *
+ * @param aCallerInnerWindow [out] Inner window of the caller of
+ * postMessage, or null if the incumbent global is not a Window.
+ *
+ * @param aCallerURI [out] The URI of the document of the incumbent
+ * global if it's a Window, null otherwise.
+ *
+ * @param aCallerAgentCluterId [out] If a non-nullptr is passed, it would
+ * return the caller's agent cluster id.
+ *
+ * @param aScriptLocation [out] If we do not have a caller's URI, then
+ * use script location as a sourcename for creating an error object.
+ *
+ * @param aError [out] The error, if any.
+ *
+ * @return Whether the postMessage call should continue or return now.
+ */
+ static bool GatherPostMessageData(
+ JSContext* aCx, const nsAString& aTargetOrigin,
+ mozilla::dom::BrowsingContext** aSource, nsAString& aOrigin,
+ nsIURI** aTargetOriginURI, nsIPrincipal** aCallerPrincipal,
+ nsGlobalWindowInner** aCallerInnerWindow, nsIURI** aCallerURI,
+ mozilla::Maybe<nsID>* aCallerAgentClusterId, nsACString* aScriptLocation,
+ mozilla::ErrorResult& aError);
+
+ // Ask the user if further dialogs should be blocked, if dialogs are currently
+ // being abused. This is used in the cases where we have no modifiable UI to
+ // show, in that case we show a separate dialog to ask this question.
+ bool ConfirmDialogIfNeeded();
+
+ // Helper called after moving/resizing, to update docShell's presContext
+ // if we have caused a resolution change by moving across monitors.
+ void CheckForDPIChange();
+
+ private:
+ enum class SecureContextFlags { eDefault, eIgnoreOpener };
+ // Called only on outer windows to compute the value that will be returned by
+ // IsSecureContext() for the inner window that corresponds to aDocument.
+ bool ComputeIsSecureContext(
+ Document* aDocument,
+ SecureContextFlags aFlags = SecureContextFlags::eDefault);
+
+ void SetDocShell(nsDocShell* aDocShell);
+
+ // nsPIDOMWindow{Inner,Outer} should be able to see these helper methods.
+ friend class nsPIDOMWindowInner;
+ friend class nsPIDOMWindowOuter;
+
+ void SetIsBackgroundInternal(bool aIsBackground);
+
+ nsresult GetInterfaceInternal(const nsIID& aIID, void** aSink);
+
+ void MaybeAllowStorageForOpenedWindow(nsIURI* aURI);
+
+ bool IsOnlyTopLevelDocumentInSHistory();
+
+ void MaybeResetWindowName(Document* aNewDocument);
+
+ public:
+ bool DelayedPrintUntilAfterLoad() const {
+ return mDelayedPrintUntilAfterLoad;
+ }
+
+ bool DelayedCloseForPrinting() const { return mDelayedCloseForPrinting; }
+
+ void StopDelayingPrintingUntilAfterLoad() {
+ mShouldDelayPrintUntilAfterLoad = false;
+ }
+
+ // Dispatch a runnable related to the global.
+ virtual nsresult Dispatch(mozilla::TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) override;
+
+ virtual nsISerialEventTarget* EventTargetFor(
+ mozilla::TaskCategory aCategory) const override;
+
+ virtual mozilla::AbstractThread* AbstractMainThreadFor(
+ mozilla::TaskCategory aCategory) override;
+
+ protected:
+ nsresult ProcessWidgetFullscreenRequest(FullscreenReason aReason,
+ bool aFullscreen);
+
+ // Indicates whether browser window should be in fullscreen mode and the
+ // reason, e.g. browser fullscreen mode or DOM fullscreen API, which should
+ // never be ForForceExitFullscreen. Nothing if browser window should not be in
+ // fullscreen mode.
+ mozilla::Maybe<FullscreenReason> mFullscreen;
+
+ // Indicates whether new fullscreen request have been made when previous
+ // fullscreen request is still in-process.
+ bool mFullscreenHasChangedDuringProcessing : 1;
+
+ using FullscreenRequest = struct FullscreenRequest {
+ FullscreenRequest(FullscreenReason aReason, bool aFullscreen)
+ : mReason(aReason), mFullscreen(aFullscreen) {
+ MOZ_ASSERT(
+ mReason != FullscreenReason::ForForceExitFullscreen || !mFullscreen,
+ "FullscreenReason::ForForceExitFullscreen can only be used with "
+ "exiting fullscreen");
+ }
+ FullscreenReason mReason;
+ bool mFullscreen : 1;
+ };
+ // The current in-process fullscreen request. Nothing if there is no
+ // in-process request.
+ mozilla::Maybe<FullscreenRequest> mInProcessFullscreenRequest;
+
+ bool mForceFullScreenInWidget : 1;
+ bool mIsClosed : 1;
+ bool mInClose : 1;
+ // mHavePendingClose means we've got a termination function set to
+ // close us when the JS stops executing or that we have a close
+ // event posted. If this is set, just ignore window.close() calls.
+ bool mHavePendingClose : 1;
+
+ // Indicates whether scripts are allowed to close this window.
+ bool mBlockScriptedClosingFlag : 1;
+
+ // Window offline status. Checked to see if we need to fire offline event
+ bool mWasOffline : 1;
+
+ // Indicates whether we're in the middle of creating an initializing
+ // a new inner window object.
+ bool mCreatingInnerWindow : 1;
+
+ // Fast way to tell if this is a chrome window (without having to QI).
+ bool mIsChrome : 1;
+
+ // whether scripts may close the window,
+ // even if "dom.allow_scripts_to_close_windows" is false.
+ bool mAllowScriptsToClose : 1;
+
+ bool mTopLevelOuterContentWindow : 1;
+
+ // Whether we've delayed a print until after load.
+ bool mDelayedPrintUntilAfterLoad : 1;
+ // Whether we've delayed a close() operation because there was a pending
+ // print() operation.
+ bool mDelayedCloseForPrinting : 1;
+ // Whether we should delay printing until after load.
+ bool mShouldDelayPrintUntilAfterLoad : 1;
+
+ nsCOMPtr<nsIScriptContext> mContext;
+ nsCOMPtr<nsIControllers> mControllers;
+
+ // For |window.arguments|, via |openDialog|.
+ nsCOMPtr<nsIArray> mArguments;
+
+ RefPtr<nsDOMWindowUtils> mWindowUtils;
+ nsString mStatus;
+
+ RefPtr<mozilla::dom::Storage> mLocalStorage;
+
+ nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentCookiePrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal;
+ nsCOMPtr<nsIPrincipal> mDocumentPartitionedPrincipal;
+
+#ifdef DEBUG
+ uint32_t mSerial;
+
+ bool mSetOpenerWindowCalled;
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+ bool mCleanedUp;
+
+ // It's useful when we get matched EnterModalState/LeaveModalState calls, in
+ // which case the outer window is responsible for unsuspending events on the
+ // documents. If we don't (for example, if the outer window is closed before
+ // the LeaveModalState call), then the inner window whose mDoc is in our
+ // mSuspendedDocs is responsible for unsuspending.
+ nsTArray<RefPtr<Document>> mSuspendedDocs;
+
+ // This is the CC generation the last time we called CanSkip.
+ uint32_t mCanSkipCCGeneration;
+
+ // When non-zero, the document should receive a vrdisplayactivate event
+ // after loading. The value is the ID of the VRDisplay that content should
+ // begin presentation on.
+ uint32_t mAutoActivateVRDisplayID;
+
+ static OuterWindowByIdTable* sOuterWindowsById;
+
+ // Members in the mChromeFields member should only be used in chrome windows.
+ // All accesses to this field should be guarded by a check of mIsChrome.
+ struct ChromeFields {
+ nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
+ // A weak pointer to the PresShell that we are doing fullscreen for.
+ // The pointer being set indicates we've set the IsInFullscreenChange
+ // flag on this pres shell.
+ nsWeakPtr mFullscreenPresShell;
+ } mChromeFields;
+
+ // Whether the chrome window is currently in a full screen transition. This
+ // flag is updated from FullscreenTransitionTask.
+ bool mIsInFullScreenTransition = false;
+
+ friend class nsDOMScriptableHelper;
+ friend class nsDOMWindowUtils;
+ friend class mozilla::dom::BrowsingContext;
+ friend class mozilla::dom::PostMessageEvent;
+ friend class DesktopNotification;
+ friend class mozilla::dom::TimeoutManager;
+ friend class nsGlobalWindowInner;
+};
+
+// XXX: EWW - This is an awful hack - let's not do this
+#include "nsGlobalWindowInner.h"
+
+inline nsISupports* ToSupports(nsGlobalWindowOuter* p) {
+ return static_cast<mozilla::dom::EventTarget*>(p);
+}
+
+inline nsISupports* ToCanonicalSupports(nsGlobalWindowOuter* p) {
+ return static_cast<mozilla::dom::EventTarget*>(p);
+}
+
+inline nsIGlobalObject* nsGlobalWindowOuter::GetOwnerGlobal() const {
+ return GetCurrentInnerWindowInternal();
+}
+
+inline nsGlobalWindowOuter* nsGlobalWindowOuter::GetInProcessTopInternal() {
+ nsCOMPtr<nsPIDOMWindowOuter> top = GetInProcessTop();
+ if (top) {
+ return nsGlobalWindowOuter::Cast(top);
+ }
+ return nullptr;
+}
+
+inline nsGlobalWindowOuter*
+nsGlobalWindowOuter::GetInProcessScriptableTopInternal() {
+ nsPIDOMWindowOuter* top = GetInProcessScriptableTop();
+ return nsGlobalWindowOuter::Cast(top);
+}
+
+inline nsIScriptContext* nsGlobalWindowOuter::GetContextInternal() {
+ return mContext;
+}
+
+inline nsGlobalWindowInner* nsGlobalWindowOuter::GetCurrentInnerWindowInternal()
+ const {
+ return nsGlobalWindowInner::Cast(mInnerWindow);
+}
+
+inline nsGlobalWindowInner* nsGlobalWindowOuter::EnsureInnerWindowInternal() {
+ return nsGlobalWindowInner::Cast(EnsureInnerWindow());
+}
+
+inline void nsGlobalWindowOuter::MaybeClearInnerWindow(
+ nsGlobalWindowInner* aExpectedInner) {
+ if (mInnerWindow == aExpectedInner) {
+ mInnerWindow = nullptr;
+ }
+}
+
+#endif /* nsGlobalWindowOuter_h___ */
diff --git a/dom/base/nsHistory.cpp b/dom/base/nsHistory.cpp
new file mode 100644
index 0000000000..0b1b08609d
--- /dev/null
+++ b/dom/base/nsHistory.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHistory.h"
+
+#include "jsapi.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIDocShell.h"
+#include "nsIWebNavigation.h"
+#include "nsReadableUtils.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+extern LazyLogModule gSHistoryLog;
+
+#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
+
+//
+// History class implementation
+//
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsHistory)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHistory)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHistory)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsHistory::nsHistory(nsPIDOMWindowInner* aInnerWindow)
+ : mInnerWindow(do_GetWeakReference(aInnerWindow)) {}
+
+nsHistory::~nsHistory() = default;
+
+nsPIDOMWindowInner* nsHistory::GetParentObject() const {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ return win;
+}
+
+JSObject* nsHistory::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return History_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+uint32_t nsHistory::GetLength(ErrorResult& aRv) const {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return 0;
+ }
+
+ // Get session History from docshell
+ RefPtr<ChildSHistory> sHistory = GetSessionHistory();
+ if (!sHistory) {
+ return 1;
+ }
+
+ int32_t len = sHistory->Count();
+ return len >= 0 ? len : 0;
+}
+
+ScrollRestoration nsHistory::GetScrollRestoration(mozilla::ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return mozilla::dom::ScrollRestoration::Auto;
+ }
+
+ bool currentScrollRestorationIsManual = false;
+ win->GetDocShell()->GetCurrentScrollRestorationIsManual(
+ &currentScrollRestorationIsManual);
+ return currentScrollRestorationIsManual
+ ? mozilla::dom::ScrollRestoration::Manual
+ : mozilla::dom::ScrollRestoration::Auto;
+}
+
+void nsHistory::SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
+ mozilla::ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ win->GetDocShell()->SetCurrentScrollRestorationIsManual(
+ aMode == mozilla::dom::ScrollRestoration::Manual);
+}
+
+void nsHistory::GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv) const {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ if (!win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<Document> doc = win->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ aRv = doc->GetStateObject(aResult);
+}
+
+void nsHistory::Go(int32_t aDelta, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ LOG(("nsHistory::Go(%d)", aDelta));
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ return aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ if (!aDelta) {
+ // https://html.spec.whatwg.org/multipage/history.html#the-history-interface
+ // "When the go(delta) method is invoked, if delta is zero, the user agent
+ // must act as if the location.reload() method was called instead."
+ RefPtr<Location> location = win->Location();
+ return location->Reload(false, aSubjectPrincipal, aRv);
+ }
+
+ RefPtr<ChildSHistory> session_history = GetSessionHistory();
+ if (!session_history) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ bool userActivation =
+ win->GetWindowContext()
+ ? win->GetWindowContext()->HasValidTransientUserGestureActivation()
+ : false;
+
+ CallerType callerType = aSubjectPrincipal.IsSystemPrincipal()
+ ? CallerType::System
+ : CallerType::NonSystem;
+
+ // Ignore the return value from Go(), since returning errors from Go() can
+ // lead to exceptions and a possible leak of history length
+ // AsyncGo throws if we hit the location change rate limit.
+ if (StaticPrefs::dom_window_history_async()) {
+ session_history->AsyncGo(aDelta, /* aRequireUserInteraction = */ false,
+ userActivation, callerType, aRv);
+ } else {
+ session_history->Go(aDelta, /* aRequireUserInteraction = */ false,
+ userActivation, IgnoreErrors());
+ }
+}
+
+void nsHistory::Back(CallerType aCallerType, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ RefPtr<ChildSHistory> sHistory = GetSessionHistory();
+ if (!sHistory) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ bool userActivation =
+ win->GetWindowContext()
+ ? win->GetWindowContext()->HasValidTransientUserGestureActivation()
+ : false;
+
+ if (StaticPrefs::dom_window_history_async()) {
+ sHistory->AsyncGo(-1, /* aRequireUserInteraction = */ false, userActivation,
+ aCallerType, aRv);
+ } else {
+ sHistory->Go(-1, /* aRequireUserInteraction = */ false, userActivation,
+ IgnoreErrors());
+ }
+}
+
+void nsHistory::Forward(CallerType aCallerType, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ RefPtr<ChildSHistory> sHistory = GetSessionHistory();
+ if (!sHistory) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ bool userActivation =
+ win->GetWindowContext()
+ ? win->GetWindowContext()->HasValidTransientUserGestureActivation()
+ : false;
+
+ if (StaticPrefs::dom_window_history_async()) {
+ sHistory->AsyncGo(1, /* aRequireUserInteraction = */ false, userActivation,
+ aCallerType, aRv);
+ } else {
+ sHistory->Go(1, /* aRequireUserInteraction = */ false, userActivation,
+ IgnoreErrors());
+ }
+}
+
+void nsHistory::PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ CallerType aCallerType, ErrorResult& aRv) {
+ PushOrReplaceState(aCx, aData, aTitle, aUrl, aCallerType, aRv, false);
+}
+
+void nsHistory::ReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ CallerType aCallerType, ErrorResult& aRv) {
+ PushOrReplaceState(aCx, aData, aTitle, aUrl, aCallerType, aRv, true);
+}
+
+void nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle,
+ const nsAString& aUrl,
+ CallerType aCallerType, ErrorResult& aRv,
+ bool aReplace) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+
+ return;
+ }
+
+ if (!win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ BrowsingContext* bc = win->GetBrowsingContext();
+ if (bc) {
+ nsresult rv = bc->CheckLocationChangeRateLimit(aCallerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ // AddState might run scripts, so we need to hold a strong reference to the
+ // docShell here to keep it from going away.
+ nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
+
+ if (!docShell) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ // The "replace" argument tells the docshell to whether to add a new
+ // history entry or modify the current one.
+
+ aRv = docShell->AddState(aData, aTitle, aUrl, aReplace, aCx);
+}
+
+already_AddRefed<ChildSHistory> nsHistory::GetSessionHistory() const {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mInnerWindow);
+ NS_ENSURE_TRUE(win, nullptr);
+
+ BrowsingContext* bc = win->GetBrowsingContext();
+ NS_ENSURE_TRUE(bc, nullptr);
+
+ RefPtr<ChildSHistory> childSHistory = bc->Top()->GetChildSessionHistory();
+ return childSHistory.forget();
+}
diff --git a/dom/base/nsHistory.h b/dom/base/nsHistory.h
new file mode 100644
index 0000000000..157052fe44
--- /dev/null
+++ b/dom/base/nsHistory.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsHistory_h___
+#define nsHistory_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HistoryBinding.h"
+#include "mozilla/dom/ChildSHistory.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsPIDOMWindow.h" // for GetParentObject
+#include "nsStringFwd.h"
+#include "nsWrapperCache.h"
+
+class nsIDocShell;
+class nsISHistory;
+class nsIWeakReference;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class ErrorResult;
+}
+
+// Script "History" object
+class nsHistory final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsHistory)
+
+ public:
+ explicit nsHistory(nsPIDOMWindowInner* aInnerWindow);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint32_t GetLength(mozilla::ErrorResult& aRv) const;
+ mozilla::dom::ScrollRestoration GetScrollRestoration(
+ mozilla::ErrorResult& aRv);
+ void SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
+ mozilla::ErrorResult& aRv);
+ void GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ mozilla::ErrorResult& aRv) const;
+ void Go(int32_t aDelta, nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aRv);
+ void Back(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv);
+ void Forward(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv);
+ void PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv);
+ void ReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv);
+
+ protected:
+ virtual ~nsHistory();
+
+ void PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv, bool aReplace);
+
+ already_AddRefed<mozilla::dom::ChildSHistory> GetSessionHistory() const;
+
+ nsWeakPtr mInnerWindow;
+};
+
+#endif /* nsHistory_h___ */
diff --git a/dom/base/nsIAnimationObserver.h b/dom/base/nsIAnimationObserver.h
new file mode 100644
index 0000000000..a692d2944c
--- /dev/null
+++ b/dom/base/nsIAnimationObserver.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIAnimationObserver_h___
+#define nsIAnimationObserver_h___
+
+#include "nsIMutationObserver.h"
+
+namespace mozilla::dom {
+class Animation;
+} // namespace mozilla::dom
+
+#define NS_IANIMATION_OBSERVER_IID \
+ { \
+ 0xed025fc7, 0xdeda, 0x48b9, { \
+ 0x9c, 0x35, 0xf2, 0xb6, 0x1e, 0xeb, 0xd0, 0x8d \
+ } \
+ }
+
+class nsIAnimationObserver : public nsIMutationObserver {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IANIMATION_OBSERVER_IID)
+
+ virtual void AnimationAdded(mozilla::dom::Animation* aAnimation) = 0;
+ virtual void AnimationChanged(mozilla::dom::Animation* aAnimation) = 0;
+ virtual void AnimationRemoved(mozilla::dom::Animation* aAnimation) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAnimationObserver, NS_IANIMATION_OBSERVER_IID)
+
+#define NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED \
+ virtual void AnimationAdded(mozilla::dom::Animation* aAnimation) override;
+
+#define NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED \
+ virtual void AnimationChanged(mozilla::dom::Animation* aAnimation) override;
+
+#define NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED \
+ virtual void AnimationRemoved(mozilla::dom::Animation* aAnimation) override;
+
+#define NS_IMPL_NSIANIMATIONOBSERVER_STUB(class_) \
+ void class_::AnimationAdded(mozilla::dom::Animation* aAnimation) {} \
+ void class_::AnimationChanged(mozilla::dom::Animation* aAnimation) {} \
+ void class_::AnimationRemoved(mozilla::dom::Animation* aAnimation) {} \
+ NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(class_) \
+ NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(class_)
+
+#define NS_DECL_NSIANIMATIONOBSERVER \
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED \
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED \
+ NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED \
+ NS_DECL_NSIMUTATIONOBSERVER
+
+#endif // nsIAnimationObserver_h___
diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h
new file mode 100644
index 0000000000..d0b4dc7c0d
--- /dev/null
+++ b/dom/base/nsIContent.h
@@ -0,0 +1,800 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIContent_h___
+#define nsIContent_h___
+
+#include "mozilla/FlushType.h"
+#include "nsINode.h"
+#include "nsStringFwd.h"
+
+// Forward declarations
+class nsIURI;
+class nsTextFragment;
+class nsIFrame;
+
+namespace mozilla {
+class EventChainPreVisitor;
+class HTMLEditor;
+struct URLExtraData;
+namespace dom {
+struct BindContext;
+class ShadowRoot;
+class HTMLSlotElement;
+} // namespace dom
+namespace widget {
+enum class IMEEnabled;
+struct IMEState;
+} // namespace widget
+} // namespace mozilla
+
+// IID for the nsIContent interface
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ICONTENT_IID \
+ { \
+ 0x8e1bab9d, 0x8815, 0x4d2c, { \
+ 0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22 \
+ } \
+ }
+
+/**
+ * A node of content in a document's content model. This interface
+ * is supported by all content objects.
+ */
+class nsIContent : public nsINode {
+ public:
+ using IMEEnabled = mozilla::widget::IMEEnabled;
+ using IMEState = mozilla::widget::IMEState;
+ using BindContext = mozilla::dom::BindContext;
+
+ void ConstructUbiNode(void* storage) override;
+
+#ifdef MOZILLA_INTERNAL_API
+ // If you're using the external API, the only thing you can know about
+ // nsIContent is that it exists with an IID
+
+ explicit nsIContent(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsINode(std::move(aNodeInfo)) {
+ MOZ_ASSERT(mNodeInfo);
+ MOZ_ASSERT(static_cast<nsINode*>(this) == reinterpret_cast<nsINode*>(this));
+ SetNodeIsContent();
+ }
+#endif // MOZILLA_INTERNAL_API
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL_DELETECYCLECOLLECTABLE
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsIContent)
+
+ NS_DECL_DOMARENA_DESTROY
+
+ NS_IMPL_FROMNODE_HELPER(nsIContent, IsContent())
+
+ /**
+ * Bind this content node to a tree. If this method throws, the caller must
+ * call UnbindFromTree() on the node. In the typical case of a node being
+ * appended to a parent, this will be called after the node has been added to
+ * the parent's child list and before nsIDocumentObserver notifications for
+ * the addition are dispatched.
+ * BindContext propagates various information down the subtree; see its
+ * documentation to know how to set it up.
+ * @param aParent The new parent node for the content node. May be a document.
+ * @note This method must not be called by consumers of nsIContent on a node
+ * that is already bound to a tree. Call UnbindFromTree first.
+ * @note This method will handle rebinding descendants appropriately (eg
+ * changing their binding parent as needed).
+ * @note This method does not add the content node to aParent's child list
+ * @throws NS_ERROR_OUT_OF_MEMORY if that happens
+ *
+ * TODO(emilio): Should we move to nsIContent::BindToTree most of the
+ * FragmentOrElement / CharacterData duplicated code?
+ */
+ virtual nsresult BindToTree(BindContext&, nsINode& aParent) = 0;
+
+ /**
+ * Unbind this content node from a tree. This will set its current document
+ * and binding parent to null. In the typical case of a node being removed
+ * from a parent, this will be called after it has been removed from the
+ * parent's child list and after the nsIDocumentObserver notifications for
+ * the removal have been dispatched.
+ * @param aDeep Whether to recursively unbind the entire subtree rooted at
+ * this node. The only time false should be passed is when the
+ * parent node of the content is being destroyed.
+ * @param aNullParent Whether to null out the parent pointer as well. This
+ * is usually desirable. This argument should only be false while
+ * recursively calling UnbindFromTree when a subtree is detached.
+ * @note This method is safe to call on nodes that are not bound to a tree.
+ */
+ virtual void UnbindFromTree(bool aNullParent = true) = 0;
+
+ enum {
+ /**
+ * All XBL flattened tree children of the node, as well as :before and
+ * :after anonymous content and native anonymous children.
+ *
+ * @note the result children order is
+ * 1. :before generated node
+ * 2. Shadow DOM flattened tree children of this node
+ * 3. native anonymous nodes
+ * 4. :after generated node
+ */
+ eAllChildren = 0,
+
+ /**
+ * Skip native anonymous content created for placeholder of HTML input.
+ */
+ eSkipPlaceholderContent = 1 << 0,
+
+ /**
+ * Skip native anonymous content created by ancestor frames of the root
+ * element's primary frame, such as scrollbar elements created by the root
+ * scroll frame.
+ */
+ eSkipDocumentLevelNativeAnonymousContent = 1 << 1,
+ };
+
+ /**
+ * Makes this content anonymous
+ * @see nsIAnonymousContentCreator
+ */
+ void SetIsNativeAnonymousRoot() {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
+ NODE_IS_NATIVE_ANONYMOUS_ROOT);
+ }
+
+ /**
+ * Returns |this| if it is not chrome-only/native anonymous, otherwise
+ * first non chrome-only/native anonymous ancestor.
+ */
+ nsIContent* FindFirstNonChromeOnlyAccessContent() const;
+
+ /**
+ * Return true iff this node is in an HTML document (in the HTML5 sense of
+ * the term, i.e. not in an XHTML/XML document).
+ */
+ inline bool IsInHTMLDocument() const;
+
+ /**
+ * Returns true if in a chrome document
+ */
+ inline bool IsInChromeDocument() const;
+
+ /**
+ * Get the namespace that this element's tag is defined in
+ * @return the namespace
+ */
+ inline int32_t GetNameSpaceID() const { return mNodeInfo->NamespaceID(); }
+
+ inline bool IsHTMLElement() const {
+ return IsInNamespace(kNameSpaceID_XHTML);
+ }
+
+ inline bool IsHTMLElement(const nsAtom* aTag) const {
+ return mNodeInfo->Equals(aTag, kNameSpaceID_XHTML);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfHTMLElements(First aFirst, Args... aArgs) const {
+ return IsHTMLElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ inline bool IsSVGElement() const { return IsInNamespace(kNameSpaceID_SVG); }
+
+ inline bool IsSVGElement(const nsAtom* aTag) const {
+ return mNodeInfo->Equals(aTag, kNameSpaceID_SVG);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfSVGElements(First aFirst, Args... aArgs) const {
+ return IsSVGElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ inline bool IsXULElement() const { return IsInNamespace(kNameSpaceID_XUL); }
+
+ inline bool IsXULElement(const nsAtom* aTag) const {
+ return mNodeInfo->Equals(aTag, kNameSpaceID_XUL);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfXULElements(First aFirst, Args... aArgs) const {
+ return IsXULElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ inline bool IsMathMLElement() const {
+ return IsInNamespace(kNameSpaceID_MathML);
+ }
+
+ inline bool IsMathMLElement(const nsAtom* aTag) const {
+ return mNodeInfo->Equals(aTag, kNameSpaceID_MathML);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfMathMLElements(First aFirst, Args... aArgs) const {
+ return IsMathMLElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ bool IsGeneratedContentContainerForBefore() const {
+ return IsRootOfNativeAnonymousSubtree() &&
+ mNodeInfo->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore;
+ }
+
+ bool IsGeneratedContentContainerForAfter() const {
+ return IsRootOfNativeAnonymousSubtree() &&
+ mNodeInfo->NameAtom() == nsGkAtoms::mozgeneratedcontentafter;
+ }
+
+ bool IsGeneratedContentContainerForMarker() const {
+ return IsRootOfNativeAnonymousSubtree() &&
+ mNodeInfo->NameAtom() == nsGkAtoms::mozgeneratedcontentmarker;
+ }
+
+ /**
+ * Get direct access (but read only) to the text in the text content.
+ * NOTE: For elements this is *not* the concatenation of all text children,
+ * it is simply null;
+ */
+ virtual const nsTextFragment* GetText() = 0;
+
+ /**
+ * Get the length of the text content.
+ * NOTE: This should not be called on elements.
+ */
+ virtual uint32_t TextLength() const = 0;
+
+ /**
+ * Determines if an event attribute name (such as onclick) is valid for
+ * a given element type.
+ * @note calls nsContentUtils::IsEventAttributeName with right flag
+ * @note *Internal is overridden by subclasses as needed
+ * @param aName the event name to look up
+ */
+ bool IsEventAttributeName(nsAtom* aName);
+
+ virtual bool IsEventAttributeNameInternal(nsAtom* aName) { return false; }
+
+ /**
+ * Query method to see if the frame is nothing but whitespace
+ * NOTE: Always returns false for elements
+ */
+ virtual bool TextIsOnlyWhitespace() = 0;
+
+ /**
+ * Thread-safe version of TextIsOnlyWhitespace.
+ */
+ virtual bool ThreadSafeTextIsOnlyWhitespace() const = 0;
+
+ /**
+ * Check if this content is focusable and in the current tab order.
+ * Note: most callers should use nsIFrame::IsFocusable() instead as it
+ * checks visibility and other layout factors as well.
+ * Tabbable is indicated by a nonnegative tabindex & is a subset of focusable.
+ * For example, only the selected radio button in a group is in the
+ * tab order, unless the radio group has no selection in which case
+ * all of the visible, non-disabled radio buttons in the group are
+ * in the tab order. On the other hand, all of the visible, non-disabled
+ * radio buttons are always focusable via clicking or script.
+ * Also, depending on either the accessibility.tabfocus pref or
+ * a system setting (nowadays: Full keyboard access, mac only)
+ * some widgets may be focusable but removed from the tab order.
+ * @param [inout, optional] aTabIndex the computed tab index
+ * In: default tabindex for element (-1 nonfocusable, == 0 focusable)
+ * Out: computed tabindex
+ * @param [optional] aTabIndex the computed tab index
+ * < 0 if not tabbable
+ * == 0 if in normal tab order
+ * > 0 can be tabbed to in the order specified by this value
+ * @return whether the content is focusable via mouse, kbd or script.
+ */
+ bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false);
+ virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse);
+
+ // https://html.spec.whatwg.org/multipage/interaction.html#focus-delegate
+ mozilla::dom::Element* GetFocusDelegate(bool aWithMouse,
+ bool aAutofocusOnly = false) const;
+
+ /*
+ * Get desired IME state for the content.
+ *
+ * @return The desired IME status for the content.
+ * This is a combination of an IME enabled value and
+ * an IME open value of widget::IMEState.
+ * If you return IMEEnabled::Disabled, you should not set the OPEN
+ * nor CLOSE value.
+ * IMEEnabled::Password should be returned only from password editor,
+ * this value has a special meaning. It is used as alternative of
+ * IMEEnabled::Disabled. IMEENabled::Plugin should be returned only
+ * when plug-in has focus. When a plug-in is focused content, we
+ * should send native events directly. Because we don't process some
+ * native events, but they may be needed by the plug-in.
+ */
+ virtual IMEState GetDesiredIMEState();
+
+ /**
+ * Gets the ShadowRoot binding for this element.
+ *
+ * @return The ShadowRoot currently bound to this element.
+ */
+ inline mozilla::dom::ShadowRoot* GetShadowRoot() const;
+
+ /**
+ * Gets the root of the node tree for this content if it is in a shadow tree.
+ *
+ * @return The ShadowRoot that is the root of the node tree.
+ */
+ mozilla::dom::ShadowRoot* GetContainingShadow() const {
+ const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
+ return slots ? slots->mContainingShadow.get() : nullptr;
+ }
+
+ /**
+ * Gets the assigned slot associated with this content.
+ *
+ * @return The assigned slot element or null.
+ */
+ mozilla::dom::HTMLSlotElement* GetAssignedSlot() const {
+ const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
+ return slots ? slots->mAssignedSlot.get() : nullptr;
+ }
+
+ /**
+ * Sets the assigned slot associated with this content.
+ *
+ * @param aSlot The assigned slot.
+ */
+ void SetAssignedSlot(mozilla::dom::HTMLSlotElement* aSlot);
+
+ /**
+ * Gets the assigned slot associated with this content based on parent's
+ * shadow root mode. Returns null if parent's shadow root is "closed".
+ * https://dom.spec.whatwg.org/#dom-slotable-assignedslot
+ *
+ * @return The assigned slot element or null.
+ */
+ mozilla::dom::HTMLSlotElement* GetAssignedSlotByMode() const;
+
+ mozilla::dom::HTMLSlotElement* GetManualSlotAssignment() const {
+ const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
+ return slots ? slots->mManualSlotAssignment : nullptr;
+ }
+
+ void SetManualSlotAssignment(mozilla::dom::HTMLSlotElement* aSlot) {
+ MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots());
+ ExtendedContentSlots()->mManualSlotAssignment = aSlot;
+ }
+
+ /**
+ * Same as GetFlattenedTreeParentNode, but returns null if the parent is
+ * non-nsIContent.
+ */
+ inline nsIContent* GetFlattenedTreeParent() const;
+
+ /**
+ * Get the index of a child within this content's flat tree children.
+ *
+ * @param aPossibleChild the child to get the index of.
+ * @return the index of the child, or Nothing if not a child. Be aware that
+ * anonymous children (e.g. a <div> child of an <input> element) will
+ * result in Nothing.
+ */
+ mozilla::Maybe<uint32_t> ComputeFlatTreeIndexOf(
+ const nsINode* aPossibleChild) const;
+
+ protected:
+ // Handles getting inserted or removed directly under a <slot> element.
+ // This is meant to only be called from the two functions below.
+ inline void HandleInsertionToOrRemovalFromSlot();
+
+ // Handles Shadow-DOM-related state tracking. Meant to be called near the
+ // end of BindToTree(), only if the tree we're in actually changed, that is,
+ // after the subtree has been bound to the new parent.
+ inline void HandleShadowDOMRelatedInsertionSteps(bool aHadParent);
+
+ // Handles Shadow-DOM related state tracking. Meant to be called near the
+ // beginning of UnbindFromTree(), before the node has lost the reference to
+ // its parent.
+ inline void HandleShadowDOMRelatedRemovalSteps(bool aNullParent);
+
+ public:
+ /**
+ * This method is called when the parser finishes creating the element. This
+ * particularly means that it has done everything you would expect it to have
+ * done after it encounters the > at the end of the tag (for HTML or XML).
+ * This includes setting the attributes, setting the document / form, and
+ * placing the element into the tree at its proper place.
+ *
+ * For container elements, this is called *before* any of the children are
+ * created or added into the tree.
+ *
+ * NOTE: this is only called for elements listed in
+ * RequiresDoneCreatingElement. This is an efficiency measure.
+ *
+ * If you also need to determine whether the parser is the one creating your
+ * element (through createElement() or cloneNode() generally) then add a
+ * uint32_t aFromParser to the NS_NewXXX() constructor for your element and
+ * have the parser pass the appropriate flags. See HTMLInputElement.cpp and
+ * nsHTMLContentSink::MakeContentObject().
+ *
+ * DO NOT USE THIS METHOD to get around the fact that it's hard to deal with
+ * attributes dynamically. If you make attributes affect your element from
+ * this method, it will only happen on initialization and JavaScript will not
+ * be able to create elements (which requires them to first create the
+ * element and then call setAttribute() directly, at which point
+ * DoneCreatingElement() has already been called and is out of the picture).
+ */
+ virtual void DoneCreatingElement() {}
+
+ /**
+ * This method is called when the parser finishes creating the element's
+ * children, if any are present.
+ *
+ * NOTE: this is only called for elements listed in
+ * RequiresDoneAddingChildren. This is an efficiency measure.
+ *
+ * If you also need to determine whether the parser is the one creating your
+ * element (through createElement() or cloneNode() generally) then add a
+ * boolean aFromParser to the NS_NewXXX() constructor for your element and
+ * have the parser pass true. See HTMLInputElement.cpp and
+ * nsHTMLContentSink::MakeContentObject().
+ *
+ * @param aHaveNotified Whether there has been a
+ * ContentInserted/ContentAppended notification for this content node
+ * yet.
+ */
+ virtual void DoneAddingChildren(bool aHaveNotified) {}
+
+ /**
+ * For HTML textarea, select, and object elements, returns true if all
+ * children have been added OR if the element was not created by the parser.
+ * Returns true for all other elements.
+ *
+ * @returns false if the element was created by the parser and
+ * it is an HTML textarea, select, or object
+ * element and not all children have been added.
+ *
+ * @returns true otherwise.
+ */
+ virtual bool IsDoneAddingChildren() { return true; }
+
+ /**
+ * Returns true if an element needs its DoneCreatingElement method to be
+ * called after it has been created.
+ * @see nsIContent::DoneCreatingElement
+ *
+ * @param aNamespaceID the node's namespace ID
+ * @param aName the node's tag name
+ */
+ static inline bool RequiresDoneCreatingElement(int32_t aNamespace,
+ nsAtom* aName) {
+ if (aNamespace == kNameSpaceID_XHTML &&
+ (aName == nsGkAtoms::input || aName == nsGkAtoms::button ||
+ aName == nsGkAtoms::audio || aName == nsGkAtoms::video)) {
+ MOZ_ASSERT(
+ !RequiresDoneAddingChildren(aNamespace, aName),
+ "Both DoneCreatingElement and DoneAddingChildren on a same element "
+ "isn't supported.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if an element needs its DoneAddingChildren method to be
+ * called after all of its children have been added.
+ * @see nsIContent::DoneAddingChildren
+ *
+ * @param aNamespace the node's namespace ID
+ * @param aName the node's tag name
+ */
+ static inline bool RequiresDoneAddingChildren(int32_t aNamespace,
+ nsAtom* aName) {
+ return (aNamespace == kNameSpaceID_XHTML &&
+ (aName == nsGkAtoms::select || aName == nsGkAtoms::textarea ||
+ aName == nsGkAtoms::head || aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::object || aName == nsGkAtoms::output)) ||
+ (aNamespace == kNameSpaceID_SVG && aName == nsGkAtoms::title) ||
+ (aNamespace == kNameSpaceID_XUL && aName == nsGkAtoms::linkset);
+ }
+
+ /**
+ * Get the ID of this content node (the atom corresponding to the
+ * value of the id attribute). This may be null if there is no ID.
+ */
+ nsAtom* GetID() const {
+ if (HasID()) {
+ return DoGetID();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Should be called when the node can become editable or when it can stop
+ * being editable (for example when its contentEditable attribute changes,
+ * when it is moved into an editable parent, ...). If aNotify is true and
+ * the node is an element, this will notify the state change.
+ */
+ virtual void UpdateEditableState(bool aNotify);
+
+ /**
+ * Destroy this node and its children. Ideally this shouldn't be needed
+ * but for now we need to do it to break cycles.
+ */
+ virtual void DestroyContent() {}
+
+ /**
+ * Saves the form state of this node and its children.
+ */
+ virtual void SaveSubtreeState() = 0;
+
+ /**
+ * Getter and setter for our primary frame pointer. This is the frame that
+ * is most closely associated with the content. A frame is more closely
+ * associated with the content than another frame if the one frame contains
+ * directly or indirectly the other frame (e.g., when a frame is scrolled
+ * there is a scroll frame that contains the frame being scrolled). This
+ * frame is always the first continuation.
+ *
+ * In the case of absolutely positioned elements and floated elements, this
+ * frame is the out of flow frame, not the placeholder.
+ */
+ nsIFrame* GetPrimaryFrame() const {
+ return (IsInUncomposedDoc() || IsInShadowTree()) ? mPrimaryFrame : nullptr;
+ }
+
+ /**
+ * Get the primary frame for this content with flushing
+ *
+ * @param aType the kind of flush to do, typically FlushType::Frames or
+ * FlushType::Layout
+ * @return the primary frame
+ */
+ nsIFrame* GetPrimaryFrame(mozilla::FlushType aType);
+
+ // Defined in nsIContentInlines.h because it needs nsIFrame.
+ inline void SetPrimaryFrame(nsIFrame* aFrame);
+
+ nsresult LookupNamespaceURIInternal(const nsAString& aNamespacePrefix,
+ nsAString& aNamespaceURI) const;
+
+ /**
+ * If this content has independent selection, e.g., if this is input field
+ * or textarea, this return TRUE. Otherwise, false.
+ */
+ bool HasIndependentSelection() const;
+
+ /**
+ * If the content is a part of HTML editor, this returns editing
+ * host content. When the content is in designMode, this returns its body
+ * element. Also, when the content isn't editable, this returns null.
+ */
+ mozilla::dom::Element* GetEditingHost();
+
+ bool SupportsLangAttr() const {
+ return IsHTMLElement() || IsSVGElement() || IsXULElement();
+ }
+
+ /**
+ * Determining language. Look at the nearest ancestor element that has a lang
+ * attribute in the XML namespace or is an HTML/SVG element and has a lang in
+ * no namespace attribute.
+ *
+ * Returns null if no language was specified. Can return the empty atom.
+ */
+ nsAtom* GetLang() const;
+
+ bool GetLang(nsAString& aResult) const {
+ if (auto* lang = GetLang()) {
+ aResult.Assign(nsDependentAtomString(lang));
+ return true;
+ }
+
+ return false;
+ }
+
+ // Overloaded from nsINode
+ nsIURI* GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
+
+ // Returns base URI for style attribute.
+ nsIURI* GetBaseURIForStyleAttr() const;
+
+ // Returns the URL data for style attribute.
+ // If aSubjectPrincipal is passed, it should be the scripted principal
+ // responsible for generating the URL data.
+ already_AddRefed<mozilla::URLExtraData> GetURLDataForStyleAttr(
+ nsIPrincipal* aSubjectPrincipal = nullptr) const;
+
+ void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
+
+ bool IsPurple() const { return mRefCnt.IsPurple(); }
+
+ void RemovePurple() { mRefCnt.RemovePurple(); }
+
+ bool OwnedOnlyByTheDOMTree() {
+ uint32_t rc = mRefCnt.get();
+ if (GetParent()) {
+ --rc;
+ }
+ rc -= GetChildCount();
+ return rc == 0;
+ }
+
+ /**
+ * Use this method with designMode and contentEditable to check if the
+ * node may need spellchecking.
+ */
+ bool InclusiveDescendantMayNeedSpellchecking(mozilla::HTMLEditor* aEditor);
+
+ protected:
+ /**
+ * Lazily allocated extended slots to avoid
+ * that may only be instantiated when a content object is accessed
+ * through the DOM. Rather than burn actual slots in the content
+ * objects for each of these instance variables, we put them off
+ * in a side structure that's only allocated when the content is
+ * accessed through the DOM.
+ */
+ class nsExtendedContentSlots {
+ public:
+ nsExtendedContentSlots();
+ virtual ~nsExtendedContentSlots();
+
+ virtual void TraverseExtendedSlots(nsCycleCollectionTraversalCallback&);
+ virtual void UnlinkExtendedSlots(nsIContent&);
+
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * @see nsIContent::GetContainingShadow
+ */
+ RefPtr<mozilla::dom::ShadowRoot> mContainingShadow;
+
+ /**
+ * @see nsIContent::GetAssignedSlot
+ */
+ RefPtr<mozilla::dom::HTMLSlotElement> mAssignedSlot;
+
+ mozilla::dom::HTMLSlotElement* mManualSlotAssignment = nullptr;
+ };
+
+ class nsContentSlots : public nsINode::nsSlots {
+ public:
+ nsContentSlots() : nsINode::nsSlots(), mExtendedSlots(0) {}
+
+ ~nsContentSlots() {
+ if (!(mExtendedSlots & sNonOwningExtendedSlotsFlag)) {
+ delete GetExtendedContentSlots();
+ }
+ }
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) override {
+ nsINode::nsSlots::Traverse(aCb);
+ if (mExtendedSlots) {
+ GetExtendedContentSlots()->TraverseExtendedSlots(aCb);
+ }
+ }
+
+ void Unlink(nsINode& aNode) override {
+ nsINode::nsSlots::Unlink(aNode);
+ if (mExtendedSlots) {
+ GetExtendedContentSlots()->UnlinkExtendedSlots(*aNode.AsContent());
+ }
+ }
+
+ void SetExtendedContentSlots(nsExtendedContentSlots* aSlots, bool aOwning) {
+ mExtendedSlots = reinterpret_cast<uintptr_t>(aSlots);
+ if (!aOwning) {
+ mExtendedSlots |= sNonOwningExtendedSlotsFlag;
+ }
+ }
+
+ // OwnsExtendedSlots returns true if we have no extended slots or if we
+ // have extended slots and own them.
+ bool OwnsExtendedSlots() const {
+ return !(mExtendedSlots & sNonOwningExtendedSlotsFlag);
+ }
+
+ nsExtendedContentSlots* GetExtendedContentSlots() const {
+ return reinterpret_cast<nsExtendedContentSlots*>(
+ mExtendedSlots & ~sNonOwningExtendedSlotsFlag);
+ }
+
+ private:
+ static const uintptr_t sNonOwningExtendedSlotsFlag = 1u;
+
+ uintptr_t mExtendedSlots;
+ };
+
+ // Override from nsINode
+ nsContentSlots* CreateSlots() override { return new nsContentSlots(); }
+
+ nsContentSlots* ContentSlots() {
+ return static_cast<nsContentSlots*>(Slots());
+ }
+
+ const nsContentSlots* GetExistingContentSlots() const {
+ return static_cast<nsContentSlots*>(GetExistingSlots());
+ }
+
+ nsContentSlots* GetExistingContentSlots() {
+ return static_cast<nsContentSlots*>(GetExistingSlots());
+ }
+
+ virtual nsExtendedContentSlots* CreateExtendedSlots() {
+ return new nsExtendedContentSlots();
+ }
+
+ const nsExtendedContentSlots* GetExistingExtendedContentSlots() const {
+ const nsContentSlots* slots = GetExistingContentSlots();
+ return slots ? slots->GetExtendedContentSlots() : nullptr;
+ }
+
+ nsExtendedContentSlots* GetExistingExtendedContentSlots() {
+ nsContentSlots* slots = GetExistingContentSlots();
+ return slots ? slots->GetExtendedContentSlots() : nullptr;
+ }
+
+ nsExtendedContentSlots* ExtendedContentSlots() {
+ nsContentSlots* slots = ContentSlots();
+ if (!slots->GetExtendedContentSlots()) {
+ slots->SetExtendedContentSlots(CreateExtendedSlots(), true);
+ }
+ return slots->GetExtendedContentSlots();
+ }
+
+ /**
+ * Hook for implementing GetID. This is guaranteed to only be
+ * called if HasID() is true.
+ */
+ nsAtom* DoGetID() const;
+
+ ~nsIContent() = default;
+
+ public:
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+# define MOZ_DOM_LIST
+#endif
+
+#ifdef MOZ_DOM_LIST
+ /**
+ * An alias for List() with default arguments. Since some debuggers can't
+ * figure the default arguments easily, having an out-of-line, non-static
+ * function helps quite a lot.
+ */
+ void Dump();
+
+ /**
+ * List the content (and anything it contains) out to the given
+ * file stream. Use aIndent as the base indent during formatting.
+ */
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const = 0;
+
+ /**
+ * Dump the content (and anything it contains) out to the given
+ * file stream. Use aIndent as the base indent during formatting.
+ */
+ virtual void DumpContent(FILE* out = stdout, int32_t aIndent = 0,
+ bool aDumpAll = true) const = 0;
+#endif
+
+ enum ETabFocusType {
+ eTabFocus_textControlsMask =
+ (1 << 0), // textboxes and lists always tabbable
+ eTabFocus_formElementsMask = (1 << 1), // non-text form elements
+ eTabFocus_linksMask = (1 << 2), // links
+ eTabFocus_any = 1 + (1 << 1) + (1 << 2) // everything that can be focused
+ };
+
+ // Tab focus model bit field:
+ static int32_t sTabFocusModel;
+
+ // accessibility.tabfocus_applies_to_xul pref - if it is set to true,
+ // the tabfocus bit field applies to xul elements.
+ static bool sTabFocusModelAppliesToXUL;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIContent, NS_ICONTENT_IID)
+
+#endif /* nsIContent_h___ */
diff --git a/dom/base/nsIContentInlines.h b/dom/base/nsIContentInlines.h
new file mode 100644
index 0000000000..a0d090740f
--- /dev/null
+++ b/dom/base/nsIContentInlines.h
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIContentInlines_h
+#define nsIContentInlines_h
+
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsAtom.h"
+#include "nsIFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+
+inline bool nsIContent::IsInHTMLDocument() const {
+ return OwnerDoc()->IsHTMLDocument();
+}
+
+inline bool nsIContent::IsInChromeDocument() const {
+ return nsContentUtils::IsChromeDoc(OwnerDoc());
+}
+
+inline void nsIContent::SetPrimaryFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsInUncomposedDoc() || IsInShadowTree(), "This will end badly!");
+
+ // <area> is known to trigger this, see bug 749326 and bug 135040.
+ MOZ_ASSERT(IsHTMLElement(nsGkAtoms::area) || !aFrame || !mPrimaryFrame ||
+ aFrame == mPrimaryFrame,
+ "Losing track of existing primary frame");
+
+ if (aFrame) {
+ MOZ_ASSERT(!aFrame->IsPlaceholderFrame());
+ if (MOZ_LIKELY(!IsHTMLElement(nsGkAtoms::area)) ||
+ aFrame->GetContent() == this) {
+ aFrame->SetIsPrimaryFrame(true);
+ }
+ } else if (nsIFrame* currentPrimaryFrame = GetPrimaryFrame()) {
+ if (MOZ_LIKELY(!IsHTMLElement(nsGkAtoms::area)) ||
+ currentPrimaryFrame->GetContent() == this) {
+ currentPrimaryFrame->SetIsPrimaryFrame(false);
+ }
+ }
+
+ mPrimaryFrame = aFrame;
+}
+
+inline mozilla::dom::ShadowRoot* nsIContent::GetShadowRoot() const {
+ if (!IsElement()) {
+ return nullptr;
+ }
+
+ return AsElement()->GetShadowRoot();
+}
+
+template <nsINode::FlattenedParentType aType>
+static inline nsINode* GetFlattenedTreeParentNode(const nsINode* aNode) {
+ if (!aNode->IsContent()) {
+ return nullptr;
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent || !parent->IsContent()) {
+ return parent;
+ }
+
+ const nsIContent* content = aNode->AsContent();
+ nsIContent* parentAsContent = parent->AsContent();
+
+ if (aType == nsINode::eForStyle &&
+ content->IsRootOfNativeAnonymousSubtree() &&
+ parentAsContent == content->OwnerDoc()->GetRootElement()) {
+ const bool docLevel =
+ content->GetProperty(nsGkAtoms::docLevelNativeAnonymousContent);
+ return docLevel ? content->OwnerDocAsNode() : parent;
+ }
+
+ if (content->IsRootOfNativeAnonymousSubtree()) {
+ return parent;
+ }
+
+ if (parentAsContent->GetShadowRoot()) {
+ // If it's not assigned to any slot it's not part of the flat tree, and thus
+ // we return null.
+ return content->GetAssignedSlot();
+ }
+
+ if (parentAsContent->IsInShadowTree()) {
+ if (auto* slot = mozilla::dom::HTMLSlotElement::FromNode(parentAsContent)) {
+ // If the assigned nodes list is empty, we're fallback content which is
+ // active, otherwise we are not part of the flat tree.
+ return slot->AssignedNodes().IsEmpty() ? parent : nullptr;
+ }
+
+ if (auto* shadowRoot =
+ mozilla::dom::ShadowRoot::FromNode(parentAsContent)) {
+ return shadowRoot->GetHost();
+ }
+ }
+
+ // Common case.
+ return parent;
+}
+
+inline nsINode* nsINode::GetFlattenedTreeParentNode() const {
+ return ::GetFlattenedTreeParentNode<nsINode::eNotForStyle>(this);
+}
+
+inline nsIContent* nsIContent::GetFlattenedTreeParent() const {
+ nsINode* parent = GetFlattenedTreeParentNode();
+ return (parent && parent->IsContent()) ? parent->AsContent() : nullptr;
+}
+
+inline bool nsIContent::IsEventAttributeName(nsAtom* aName) {
+ const char16_t* name = aName->GetUTF16String();
+ if (name[0] != 'o' || name[1] != 'n') {
+ return false;
+ }
+
+ return IsEventAttributeNameInternal(aName);
+}
+
+inline nsINode* nsINode::GetFlattenedTreeParentNodeForStyle() const {
+ return ::GetFlattenedTreeParentNode<nsINode::eForStyle>(this);
+}
+
+inline bool nsINode::NodeOrAncestorHasDirAuto() const {
+ return AncestorHasDirAuto() || (IsElement() && AsElement()->HasDirAuto());
+}
+
+inline bool nsINode::IsEditable() const {
+ if (HasFlag(NODE_IS_EDITABLE)) {
+ // The node is in an editable contentEditable subtree.
+ return true;
+ }
+
+ // All editable anonymous content should be made explicitly editable via the
+ // NODE_IS_EDITABLE flag.
+ if (IsInNativeAnonymousSubtree()) {
+ return false;
+ }
+
+ // Check if the node is in a document and the document is in designMode.
+ return IsInDesignMode();
+}
+
+inline bool nsINode::IsInDesignMode() const {
+ if (!OwnerDoc()->HasFlag(NODE_IS_EDITABLE)) {
+ return false;
+ }
+
+ if (IsDocument()) {
+ return HasFlag(NODE_IS_EDITABLE);
+ }
+
+ // NOTE(emilio): If you change this to be the composed doc you also need to
+ // change NotifyEditableStateChange() in Document.cpp.
+ // NOTE(masayuki): Perhaps, we should keep this behavior because of
+ // web-compat.
+ if (IsInUncomposedDoc() && GetUncomposedDoc()->HasFlag(NODE_IS_EDITABLE)) {
+ return true;
+ }
+
+ // FYI: In design mode, form controls don't work as usual. For example,
+ // <input type=text> isn't focusable but can be deleted and replaced
+ // with typed text. <select> is also not focusable but always selected
+ // all to be deleted or replaced. On the other hand, newer controls
+ // don't behave as the traditional controls. For example, data/time
+ // picker can be opened and change the value from the picker. And also
+ // the buttons of <video controls> work as usual. On the other hand,
+ // their UI (i.e., nodes in their shadow tree) are not editable.
+ // Therefore, we need special handling for nodes in anonymous subtree
+ // unless we fix <https://bugzilla.mozilla.org/show_bug.cgi?id=1734512>.
+
+ // If the shadow host is not in design mode, this can never be in design
+ // mode. Otherwise, the content is never editable by design mode of
+ // composed document. If we're in a native anonymous subtree, we should
+ // consider it with the host.
+ if (IsInNativeAnonymousSubtree()) {
+ nsIContent* host = GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ MOZ_DIAGNOSTIC_ASSERT(host != this);
+ return host && host->IsInDesignMode();
+ }
+
+ // Otherwise, i.e., when it's in a shadow tree which is not created by us,
+ // the node is not editable by design mode (but it's possible that it may be
+ // editable if this node is in `contenteditable` element in the shadow tree).
+ return false;
+}
+
+inline void nsIContent::HandleInsertionToOrRemovalFromSlot() {
+ using mozilla::dom::HTMLSlotElement;
+
+ MOZ_ASSERT(GetParentElement());
+ if (!IsInShadowTree() || IsRootOfNativeAnonymousSubtree()) {
+ return;
+ }
+ HTMLSlotElement* slot = HTMLSlotElement::FromNode(mParent);
+ if (!slot) {
+ return;
+ }
+ // If parent's root is a shadow root, and parent is a slot whose
+ // assigned nodes is the empty list, then run signal a slot change for
+ // parent.
+ if (slot->AssignedNodes().IsEmpty()) {
+ slot->EnqueueSlotChangeEvent();
+ }
+}
+
+inline void nsIContent::HandleShadowDOMRelatedInsertionSteps(bool aHadParent) {
+ using mozilla::dom::Element;
+ using mozilla::dom::ShadowRoot;
+
+ if (!aHadParent) {
+ if (Element* parentElement = Element::FromNode(mParent)) {
+ if (ShadowRoot* shadow = parentElement->GetShadowRoot()) {
+ shadow->MaybeSlotHostChild(*this);
+ }
+ HandleInsertionToOrRemovalFromSlot();
+ }
+ }
+}
+
+inline void nsIContent::HandleShadowDOMRelatedRemovalSteps(bool aNullParent) {
+ using mozilla::dom::Element;
+ using mozilla::dom::ShadowRoot;
+
+ if (aNullParent) {
+ // FIXME(emilio, bug 1577141): FromNodeOrNull rather than just FromNode
+ // because XBL likes to call UnbindFromTree at very odd times (with already
+ // disconnected anonymous content subtrees).
+ if (Element* parentElement = Element::FromNodeOrNull(mParent)) {
+ if (ShadowRoot* shadow = parentElement->GetShadowRoot()) {
+ shadow->MaybeUnslotHostChild(*this);
+ }
+ HandleInsertionToOrRemovalFromSlot();
+ }
+ }
+}
+
+#endif // nsIContentInlines_h
diff --git a/dom/base/nsIContentPolicy.idl b/dom/base/nsIContentPolicy.idl
new file mode 100644
index 0000000000..93baafc583
--- /dev/null
+++ b/dom/base/nsIContentPolicy.idl
@@ -0,0 +1,616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=cpp tw=78 sw=2 et ts=8 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsILoadInfo;
+
+/**
+ * Interface for content policy mechanism. Implementations of this
+ * interface can be used to control loading of various types of out-of-line
+ * content, or processing of certain types of in-line content.
+ *
+ * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
+ * by launching a dialog to prompt the user for something).
+ */
+
+[scriptable, uuid(caad4f1f-d047-46ac-ae9d-dc598e4fb91b)]
+interface nsIContentPolicy : nsISupports
+{
+ /**
+ * The type of nsIContentPolicy::TYPE_*
+ */
+ cenum nsContentPolicyType : 8 {
+ /**
+ * Indicates a unset or bogus policy type.
+ */
+ TYPE_INVALID = 0,
+
+ /**
+ * Gecko/Firefox developers: Avoid using TYPE_OTHER. Especially for
+ * requests that are coming from webpages. Or requests in general which
+ * you expect that security checks will be done on.
+ * Always use a more specific type if one is available. And do not hesitate
+ * to add more types as appropriate.
+ * But if you are fairly sure that no one would care about your more specific
+ * type, then it's ok to use TYPE_OTHER.
+ *
+ * Extension developers: Whenever it is reasonable, use one of the existing
+ * content types. If none of the existing content types are right for
+ * something you are doing, file a bug in the Core/DOM component that
+ * includes a patch that adds your new content type to the end of the list of
+ * TYPE_* constants here. But, don't start using your new content type until
+ * your patch has been accepted, because it will be uncertain what exact
+ * value and name your new content type will have; in that interim period,
+ * use TYPE_OTHER. In your patch, document your new content type in the style
+ * of the existing ones. In the bug you file, provide a more detailed
+ * description of the new type of content you want Gecko to support, so that
+ * the existing implementations of nsIContentPolicy can be properly modified
+ * to deal with that new type of content.
+ *
+ * Implementations of nsIContentPolicy should treat this the same way they
+ * treat unknown types, because existing users of TYPE_OTHER may be converted
+ * to use new content types.
+ *
+ * Note that the TYPE_INTERNAL_* constants are never passed to content
+ * policy implementations. They are mapped to other TYPE_* constants, and
+ * are only intended for internal usage inside Gecko.
+ */
+ TYPE_OTHER = 1,
+
+ /**
+ * Indicates an executable script (such as JavaScript).
+ */
+ TYPE_SCRIPT = 2,
+
+ /**
+ * Indicates an image (e.g., IMG elements).
+ */
+ TYPE_IMAGE = 3,
+
+ /**
+ * Indicates a stylesheet (e.g., STYLE elements).
+ */
+ TYPE_STYLESHEET = 4,
+
+ /**
+ * Indicates a generic object (plugin-handled content typically falls under
+ * this category).
+ */
+ TYPE_OBJECT = 5,
+
+ /**
+ * Indicates a document at the top-level (i.e., in a browser).
+ */
+ TYPE_DOCUMENT = 6,
+
+ /**
+ * Indicates a document contained within another document (e.g., IFRAMEs,
+ * FRAMES, and OBJECTs).
+ */
+ TYPE_SUBDOCUMENT = 7,
+
+ /*
+ * XXX: nsContentPolicyType = 8 used to inicate a timed refresh request.
+ */
+
+ /*
+ * XXX: nsContentPolicyType = 9 used to inicate an XBL binding request.
+ */
+
+ /**
+ * Indicates a ping triggered by a click on <A PING="..."> element.
+ */
+ TYPE_PING = 10,
+
+ /**
+ * Indicates an XMLHttpRequest. Also used for document.load and for EventSource.
+ */
+ TYPE_XMLHTTPREQUEST = 11,
+
+ /**
+ * Indicates a request by a plugin.
+ */
+ TYPE_OBJECT_SUBREQUEST = 12,
+
+ /**
+ * Indicates a DTD loaded by an XML document.
+ */
+ TYPE_DTD = 13,
+
+ /**
+ * Indicates a font loaded via @font-face rule.
+ */
+ TYPE_FONT = 14,
+
+ /**
+ * Indicates a video or audio load.
+ */
+ TYPE_MEDIA = 15,
+
+ /**
+ * Indicates a WebSocket load.
+ */
+ TYPE_WEBSOCKET = 16,
+
+ /**
+ * Indicates a Content Security Policy report.
+ */
+ TYPE_CSP_REPORT = 17,
+
+ /**
+ * Indicates a style sheet transformation.
+ */
+ TYPE_XSLT = 18,
+
+ /**
+ * Indicates a beacon post.
+ */
+ TYPE_BEACON = 19,
+
+ /**
+ * Indicates a load initiated by the fetch() function from the Fetch
+ * specification.
+ */
+ TYPE_FETCH = 20,
+
+ /**
+ * Indicates a <img srcset> or <picture> request.
+ */
+ TYPE_IMAGESET = 21,
+
+ /**
+ * Indicates a web manifest.
+ */
+ TYPE_WEB_MANIFEST = 22,
+
+ /**
+ * Indicates an internal constant for scripts loaded through script
+ * elements.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_SCRIPT = 23,
+
+ /**
+ * Indicates an internal constant for scripts loaded through a dedicated
+ * worker.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_WORKER = 24,
+
+ /**
+ * Indicates an internal constant for scripts loaded through a shared
+ * worker.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_SHARED_WORKER = 25,
+
+ /**
+ * Indicates an internal constant for content loaded from embed elements.
+ *
+ * This will be mapped to TYPE_OBJECT.
+ */
+ TYPE_INTERNAL_EMBED = 26,
+
+ /**
+ * Indicates an internal constant for content loaded from object elements.
+ *
+ * This will be mapped to TYPE_OBJECT.
+ */
+ TYPE_INTERNAL_OBJECT = 27,
+
+ /**
+ * Indicates an internal constant for content loaded from frame elements.
+ *
+ * This will be mapped to TYPE_SUBDOCUMENT.
+ */
+ TYPE_INTERNAL_FRAME = 28,
+
+ /**
+ * Indicates an internal constant for content loaded from iframe elements.
+ *
+ * This will be mapped to TYPE_SUBDOCUMENT.
+ */
+ TYPE_INTERNAL_IFRAME = 29,
+
+ /**
+ * Indicates an internal constant for content loaded from audio elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ TYPE_INTERNAL_AUDIO = 30,
+
+ /**
+ * Indicates an internal constant for content loaded from video elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ TYPE_INTERNAL_VIDEO = 31,
+
+ /**
+ * Indicates an internal constant for content loaded from track elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ TYPE_INTERNAL_TRACK = 32,
+
+ /**
+ * Indicates an internal constant for an XMLHttpRequest.
+ *
+ * This will be mapped to TYPE_XMLHTTPREQUEST.
+ */
+ TYPE_INTERNAL_XMLHTTPREQUEST = 33,
+
+ /**
+ * Indicates an internal constant for EventSource.
+ *
+ * This will be mapped to TYPE_XMLHTTPREQUEST.
+ */
+ TYPE_INTERNAL_EVENTSOURCE = 34,
+
+ /**
+ * Indicates an internal constant for scripts loaded through a service
+ * worker.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_SERVICE_WORKER = 35,
+
+ /**
+ * Indicates an internal constant for *preloaded* scripts
+ * loaded through script elements.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_SCRIPT_PRELOAD = 36,
+
+ /**
+ * Indicates an internal constant for normal images.
+ *
+ * This will be mapped to TYPE_IMAGE before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_IMAGE = 37,
+
+ /**
+ * Indicates an internal constant for *preloaded* images.
+ *
+ * This will be mapped to TYPE_IMAGE before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_IMAGE_PRELOAD = 38,
+
+ /**
+ * Indicates an internal constant for normal stylesheets.
+ *
+ * This will be mapped to TYPE_STYLESHEET before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_STYLESHEET = 39,
+
+ /**
+ * Indicates an internal constant for *preloaded* stylesheets.
+ *
+ * This will be mapped to TYPE_STYLESHEET before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_STYLESHEET_PRELOAD = 40,
+
+ /**
+ * Indicates an internal constant for favicon.
+ *
+ * This will be mapped to TYPE_IMAGE before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_IMAGE_FAVICON = 41,
+
+ /**
+ * Indicates an importScripts() inside a worker script.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS = 42,
+
+ /**
+ * Indicates an save-as link download from the front-end code.
+ */
+ TYPE_SAVEAS_DOWNLOAD = 43,
+
+ /**
+ * Indicates a speculative connection.
+ */
+ TYPE_SPECULATIVE = 44,
+
+ /**
+ * Indicates an internal constant for ES6 module scripts
+ * loaded through script elements or an import statement.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_MODULE = 45,
+
+ /**
+ * Indicates an internal constant for *preloaded* ES6 module scripts
+ * loaded through script elements or an import statement.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_MODULE_PRELOAD = 46,
+
+ /**
+ * Indicates a DTD loaded by an XML document the URI of which could
+ * not be mapped to a known local DTD.
+ */
+ TYPE_INTERNAL_DTD = 47,
+
+ /**
+ * Indicates a TYPE_INTERNAL_DTD which will not be blocked no matter
+ * what principal is being loaded from.
+ */
+ TYPE_INTERNAL_FORCE_ALLOWED_DTD = 48,
+
+ /**
+ * Indicates an internal constant for scripts loaded through an
+ * audioWorklet.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_AUDIOWORKLET = 49,
+
+ /**
+ * Indicates an internal constant for scripts loaded through an
+ * paintWorklet.
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_PAINTWORKLET = 50,
+
+ /**
+ * Same as TYPE_FONT but indicates this is a <link rel=preload as=font>
+ * preload initiated load.
+ */
+ TYPE_INTERNAL_FONT_PRELOAD = 51,
+
+ /**
+ * Indicates the load of a (Firefox-internal) script through ChromeUtils
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT = 52,
+
+ /**
+ * Indicates the load of a script through FrameMessageManager
+ *
+ * This will be mapped to TYPE_SCRIPT before being passed to content policy
+ * implementations.
+ */
+ TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT = 53,
+
+ /**
+ * Indicates an internal constant for *preloaded* fetch
+ * loaded through link elements.
+ *
+ * This will be mapped to TYPE_FETCH before being passed
+ * to content policy implementations.
+ */
+ TYPE_INTERNAL_FETCH_PRELOAD = 54,
+
+ /**
+ * Indicates a font loaded via @font-face rule in an UA style sheet.
+ * (CSP does not apply.)
+ */
+ TYPE_UA_FONT = 55,
+
+ /**
+ * Indicates the establishment of a TCP or TLS connection via an
+ * http/https proxy that will be used for webrtc media. When no web proxy
+ * is involved, webrtc uses lower level sockets that are not subject to
+ * any sort of content policy.
+ */
+ TYPE_PROXIED_WEBRTC_MEDIA = 56,
+
+ /**
+ * Indicates the load of data via the Federated Credential Management API
+ * with data destined for a browser context.
+ */
+ TYPE_WEB_IDENTITY = 57,
+
+ /**
+ * Indicates the load of a static module on workers.
+ */
+ TYPE_INTERNAL_WORKER_STATIC_MODULE = 58,
+
+ /**
+ * Indicates Webtransport request
+ */
+ TYPE_WEB_TRANSPORT = 59,
+
+ /**
+ * Used to indicate the end of this list, not a content policy. If you want
+ * to add a new content policy type, place it before this sentinel value
+ * TYPE_END, have it use TYPE_END's current value, and increment TYPE_END by
+ * one. (TYPE_END should always have the highest numerical value.)
+ */
+ TYPE_END = 60,
+
+
+ /* When adding new content types, please update
+ * NS_CP_ContentTypeName, nsCSPContext, CSP_ContentTypeToDirective,
+ * DoContentSecurityChecks, all nsIContentPolicy implementations, the
+ * static_assert in dom/cache/DBSchema.cpp, ChannelWrapper.webidl,
+ * ChannelWrapper.cpp, PermissionManager.cpp,
+ * IPCMessageUtilsSpecializations.h, and other things that are not
+ * listed here that are related to nsIContentPolicy. */
+ };
+
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load or process request
+ * is rejected based on details of the request.
+ */
+ const short REJECT_REQUEST = -1;
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load/process is rejected
+ * based solely on its type (of the above flags).
+ *
+ * NOTE that it is not meant to stop future requests for this type--only the
+ * current request.
+ */
+ const short REJECT_TYPE = -2;
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load/process is rejected
+ * based on the server it is hosted on or requested from (aContentLocation or
+ * aRequestOrigin), e.g., if you block an IMAGE because it is served from
+ * goatse.cx (even if you don't necessarily block other types from that
+ * server/domain).
+ *
+ * NOTE that it is not meant to stop future requests for this server--only the
+ * current request.
+ */
+ const short REJECT_SERVER = -3;
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load/process is rejected
+ * based on some other criteria. Mozilla callers will handle this like
+ * REJECT_REQUEST; third-party implementors may, for example, use this to
+ * direct their own callers to consult the extra parameter for additional
+ * details.
+ */
+ const short REJECT_OTHER = -4;
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load/process is forbiddden
+ * based on enterprise policy.
+ */
+ const short REJECT_POLICY = -5;
+
+ /**
+ * Returned from shouldLoad or shouldProcess if the load or process request
+ * is not rejected.
+ */
+ const short ACCEPT = 1;
+
+ /**
+ * Should the resource at this location be loaded?
+ * ShouldLoad will be called before loading the resource at aContentLocation
+ * to determine whether to start the load at all.
+ *
+ * @param aContentLocation the location of the content being checked; must
+ * not be null
+ *
+ * @param aLoadInfo the loadinfo of the channel being evaluated.
+ *
+ * @param aMimeTypeGuess OPTIONAL. a guess for the requested content's
+ * MIME type, based on information available to
+ * the request initiator (e.g., an OBJECT's type
+ * attribute); does not reliably reflect the
+ * actual MIME type of the requested content
+ *
+ * @return ACCEPT or REJECT_*
+ *
+ * @note shouldLoad can be called while the DOM and layout of the document
+ * involved is in an inconsistent state. This means that implementors of
+ * this method MUST NOT do any of the following:
+ * 1) Modify the DOM in any way (e.g. setting attributes is a no-no).
+ * 2) Query any DOM properties that depend on layout (e.g. offset*
+ * properties).
+ * 3) Query any DOM properties that depend on style (e.g. computed style).
+ * 4) Query any DOM properties that depend on the current state of the DOM
+ * outside the "context" node (e.g. lengths of node lists).
+ * 5) [JavaScript implementations only] Access properties of any sort on any
+ * object without using XPCNativeWrapper (either explicitly or
+ * implicitly). Due to various DOM0 things, this leads to item 4.
+ * If you do any of these things in your shouldLoad implementation, expect
+ * unpredictable behavior, possibly including crashes, content not showing
+ * up, content showing up doubled, etc. If you need to do any of the things
+ * above, do them off timeout or event.
+ */
+ short shouldLoad(in nsIURI aContentLocation,
+ in nsILoadInfo aLoadInfo,
+ in ACString aMimeTypeGuess);
+
+ /**
+ * Should the resource be processed?
+ * ShouldProcess will be called once all the information passed to it has
+ * been determined about the resource, typically after part of the resource
+ * has been loaded.
+ *
+ * @param aContentLocation OPTIONAL; the location of the resource being
+ * requested: MAY be, e.g., a post-redirection URI
+ * for the resource.
+ *
+ * @param aLoadInfo the loadinfo of the channel being evaluated.
+ *
+ * @param aMimeType the MIME type of the requested resource (e.g.,
+ * image/png), as reported by the networking library,
+ * if available (may be empty if inappropriate for
+ * the type).
+ *
+ * @return ACCEPT or REJECT_*
+ *
+ * @note shouldProcess can be called while the DOM and layout of the document
+ * involved is in an inconsistent state. See the note on shouldLoad to see
+ * what this means for implementors of this method.
+ */
+ short shouldProcess(in nsIURI aContentLocation,
+ in nsILoadInfo aLoadInfo,
+ in ACString aMimeType);
+};
+
+typedef nsIContentPolicy_nsContentPolicyType nsContentPolicyType;
+
+%{C++
+enum class ExtContentPolicyType : uint8_t {
+ /**
+ * The type of ExtContentPolicy::TYPE_*
+ */
+ TYPE_INVALID = nsIContentPolicy::TYPE_INVALID,
+ TYPE_OTHER = nsIContentPolicy::TYPE_OTHER,
+ TYPE_SCRIPT = nsIContentPolicy::TYPE_SCRIPT,
+ TYPE_IMAGE = nsIContentPolicy::TYPE_IMAGE,
+ TYPE_STYLESHEET = nsIContentPolicy::TYPE_STYLESHEET,
+ TYPE_OBJECT = nsIContentPolicy::TYPE_OBJECT,
+ TYPE_DOCUMENT = nsIContentPolicy::TYPE_DOCUMENT,
+ TYPE_SUBDOCUMENT = nsIContentPolicy::TYPE_SUBDOCUMENT,
+ TYPE_PING = nsIContentPolicy::TYPE_PING,
+ TYPE_XMLHTTPREQUEST = nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+ TYPE_OBJECT_SUBREQUEST = nsIContentPolicy::TYPE_OBJECT_SUBREQUEST,
+ TYPE_DTD = nsIContentPolicy::TYPE_DTD,
+ TYPE_FONT = nsIContentPolicy::TYPE_FONT,
+ TYPE_MEDIA = nsIContentPolicy::TYPE_MEDIA,
+ TYPE_WEBSOCKET = nsIContentPolicy::TYPE_WEBSOCKET,
+ TYPE_CSP_REPORT = nsIContentPolicy::TYPE_CSP_REPORT,
+ TYPE_XSLT = nsIContentPolicy::TYPE_XSLT,
+ TYPE_BEACON = nsIContentPolicy::TYPE_BEACON,
+ TYPE_FETCH = nsIContentPolicy::TYPE_FETCH,
+ TYPE_IMAGESET = nsIContentPolicy::TYPE_IMAGESET,
+ TYPE_WEB_MANIFEST = nsIContentPolicy::TYPE_WEB_MANIFEST,
+ TYPE_SAVEAS_DOWNLOAD = nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD,
+ TYPE_SPECULATIVE = nsIContentPolicy::TYPE_SPECULATIVE,
+ TYPE_UA_FONT = nsIContentPolicy::TYPE_UA_FONT,
+ TYPE_PROXIED_WEBRTC_MEDIA = nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA,
+};
+
+typedef ExtContentPolicyType ExtContentPolicy;
+%}
diff --git a/dom/base/nsIDOMRequestService.idl b/dom/base/nsIDOMRequestService.idl
new file mode 100644
index 0000000000..4f6fcd5784
--- /dev/null
+++ b/dom/base/nsIDOMRequestService.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+webidl DOMRequest;
+
+[scriptable, builtinclass, uuid(9a57e5de-ce93-45fa-8145-755722834f7c)]
+interface nsIDOMRequestService : nsISupports
+{
+ DOMRequest createRequest(in mozIDOMWindow window);
+
+ void fireSuccess(in DOMRequest request, in jsval result);
+ void fireError(in DOMRequest request, in AString error);
+ void fireSuccessAsync(in DOMRequest request, in jsval result);
+ void fireErrorAsync(in DOMRequest request, in AString error);
+};
diff --git a/dom/base/nsIDocumentObserver.h b/dom/base/nsIDocumentObserver.h
new file mode 100644
index 0000000000..ff20f18343
--- /dev/null
+++ b/dom/base/nsIDocumentObserver.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIDocumentObserver_h___
+#define nsIDocumentObserver_h___
+
+#include "nsISupports.h"
+#include "nsIMutationObserver.h"
+#include "mozilla/dom/RustTypes.h"
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_IDOCUMENT_OBSERVER_IID \
+ { \
+ 0x71041fa3, 0x6dd7, 0x4cde, { \
+ 0xbb, 0x76, 0xae, 0xcc, 0x69, 0xe1, 0x75, 0x78 \
+ } \
+ }
+
+// Document observer interface
+class nsIDocumentObserver : public nsIMutationObserver {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_OBSERVER_IID)
+
+ /**
+ * Notify that a content model update is beginning. This call can be
+ * nested.
+ */
+ virtual void BeginUpdate(mozilla::dom::Document*) = 0;
+
+ /**
+ * Notify that a content model update is finished. This call can be
+ * nested.
+ */
+ virtual void EndUpdate(mozilla::dom::Document*) = 0;
+
+ /**
+ * Notify the observer that a document load is beginning.
+ */
+ virtual void BeginLoad(mozilla::dom::Document*) = 0;
+
+ /**
+ * Notify the observer that a document load has finished. Note that
+ * the associated reflow of the document will be done <b>before</b>
+ * EndLoad is invoked, not after.
+ */
+ virtual void EndLoad(mozilla::dom::Document*) = 0;
+
+ /**
+ * Notification that the state of an element has changed. (ie: gained or lost
+ * focus, became active or hovered over)
+ *
+ * This method is called automatically by elements when their state is changed
+ * (therefore there is normally no need to invoke this method directly).
+ *
+ * This notification is not sent when elements are added/removed from the
+ * document (the other notifications are used for that).
+ *
+ * @param Document The document being observed
+ * @param Element the piece of content that changed
+ * @param ElementState the element states that changed
+ */
+ virtual void ElementStateChanged(mozilla::dom::Document*,
+ mozilla::dom::Element*,
+ mozilla::dom::ElementState) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentObserver, NS_IDOCUMENT_OBSERVER_IID)
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE \
+ virtual void BeginUpdate(mozilla::dom::Document*) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE \
+ virtual void EndUpdate(mozilla::dom::Document*) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD \
+ virtual void BeginLoad(mozilla::dom::Document*) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD \
+ virtual void EndLoad(mozilla::dom::Document*) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED \
+ virtual void ElementStateChanged(mozilla::dom::Document*, \
+ mozilla::dom::Element*, \
+ mozilla::dom::ElementState) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER \
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE \
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE \
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD \
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD \
+ NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(_class) \
+ void _class::BeginUpdate(mozilla::dom::Document*) {} \
+ void _class::EndUpdate(mozilla::dom::Document*) {} \
+ NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(_class)
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(_class) \
+ void _class::BeginLoad(mozilla::dom::Document*) {} \
+ void _class::EndLoad(mozilla::dom::Document*) {}
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_STATE_STUB(_class) \
+ void _class::ElementStateChanged(mozilla::dom::Document*, \
+ mozilla::dom::Element*, \
+ mozilla::dom::ElementState) {}
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_CONTENT(_class) \
+ NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(_class)
+
+#endif /* nsIDocumentObserver_h___ */
diff --git a/dom/base/nsIDroppedLinkHandler.idl b/dom/base/nsIDroppedLinkHandler.idl
new file mode 100644
index 0000000000..da2f539310
--- /dev/null
+++ b/dom/base/nsIDroppedLinkHandler.idl
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIPrincipal.idl"
+#include "nsIContentSecurityPolicy.idl"
+
+webidl DragEvent;
+webidl DataTransfer;
+
+[scriptable, uuid(69E14F91-2E09-4CA6-A511-A715C99A2804)]
+interface nsIDroppedLinkItem : nsISupports
+{
+ /**
+ * Returns the URL of the link.
+ */
+ readonly attribute AString url;
+
+ /**
+ * Returns the link name.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Returns the MIME-Type.
+ */
+ readonly attribute AString type;
+};
+
+[scriptable, uuid(21B5C25A-28A9-47BD-8431-FA9116305DED)]
+interface nsIDroppedLinkHandler : nsISupports
+{
+ /**
+ * Determines if a link being dragged can be dropped and returns true if so.
+ * aEvent should be a dragenter or dragover event.
+ *
+ * If aAllowSameDocument is false, drops are only allowed if the document
+ * of the source of the drag is different from the destination. This check
+ * includes any parent, sibling and child frames in the same content tree.
+ * If true, the source is not checked.
+ */
+ boolean canDropLink(in DragEvent aEvent, in boolean aAllowSameDocument);
+
+ /**
+ * Given a drop event aEvent, determines links being dragged and returns
+ * them. If links are returned the caller can, for instance, load them. If
+ * the returned array is empty, there is no valid link to be dropped.
+ *
+ * A NS_ERROR_DOM_SECURITY_ERR error will be thrown and the event cancelled if
+ * the receiving target should not load the uri for security reasons. This
+ * will occur if any of the following conditions are true:
+ * - the source of the drag initiated a link for dragging that
+ * it itself cannot access. This prevents a source document from tricking
+ * the user into a dragging a chrome url, for example.
+ * - aDisallowInherit is true, and the URI being dropped would inherit the
+ * current document's security context (URI_INHERITS_SECURITY_CONTEXT).
+ */
+ Array<nsIDroppedLinkItem> dropLinks(in DragEvent aEvent,
+ [optional] in boolean aDisallowInherit);
+
+ /**
+ * Given a drop event aEvent, validate the extra URIs for the event,
+ * this is used when the caller extracts yet another URIs from the dropped
+ * text, like home button that splits the text with "|".
+ */
+ void validateURIsForDrop(in DragEvent aEvent,
+ in Array<AString> aURIs,
+ [optional] in boolean aDisallowInherit);
+
+ /**
+ * Given a dataTransfer, allows caller to determine and verify links being
+ * dragged. Since drag/drop performs a roundtrip of parent, child, parent,
+ * it allows the parent to verify that the child did not modify links
+ * being dropped.
+ */
+ Array<nsIDroppedLinkItem> queryLinks(in DataTransfer aDataTransfer);
+
+ /**
+ * Given a drop event aEvent, determines the triggering principal for the
+ * event and returns it.
+ */
+ nsIPrincipal getTriggeringPrincipal(in DragEvent aEvent);
+
+ /**
+ * Given a drop event aEvent, determines the CSP for the event and returns it.
+ */
+ nsIContentSecurityPolicy getCsp(in DragEvent aEvent);
+};
diff --git a/dom/base/nsIEventSourceEventService.idl b/dom/base/nsIEventSourceEventService.idl
new file mode 100644
index 0000000000..6131424669
--- /dev/null
+++ b/dom/base/nsIEventSourceEventService.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+#include "nsISupports.idl"
+
+[scriptable, uuid(d2cc6222-b7f2-490d-adc2-497d89878fa2)]
+interface nsIEventSourceEventListener : nsISupports
+{
+ [must_use] void eventSourceConnectionOpened(in uint64_t aHttpChannelId);
+
+ [must_use] void eventSourceConnectionClosed(in uint64_t aHttpChannelId);
+
+ [must_use] void eventReceived(in uint64_t aHttpChannelId,
+ in AString aEventName,
+ in AString aLastEventID,
+ in AString aData,
+ in uint32_t aRetry,
+ in DOMHighResTimeStamp aTimeStamp);
+};
+
+[scriptable, builtinclass, uuid(c0378840-8a74-4b0a-9225-c3a0ac1fac41)]
+interface nsIEventSourceEventService : nsISupports
+{
+ [must_use] void addListener(in unsigned long long aInnerWindowID,
+ in nsIEventSourceEventListener aListener);
+
+ [must_use] void removeListener(in unsigned long long aInnerWindowID,
+ in nsIEventSourceEventListener aListener);
+
+ [must_use] bool hasListenerFor(in unsigned long long aInnerWindowID);
+};
diff --git a/dom/base/nsIGlobalObject.cpp b/dom/base/nsIGlobalObject.cpp
new file mode 100644
index 0000000000..c346db4f71
--- /dev/null
+++ b/dom/base/nsIGlobalObject.cpp
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIGlobalObject.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/GlobalTeardownObserver.h"
+#include "mozilla/Result.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/Report.h"
+#include "mozilla/dom/ReportingObserver.h"
+#include "mozilla/dom/ServiceWorker.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+#include "nsGlobalWindowInner.h"
+
+// Max number of Report objects
+constexpr auto MAX_REPORT_RECORDS = 100;
+
+using mozilla::AutoSlowOperation;
+using mozilla::CycleCollectedJSContext;
+using mozilla::DOMEventTargetHelper;
+using mozilla::ErrorResult;
+using mozilla::GlobalTeardownObserver;
+using mozilla::IgnoredErrorResult;
+using mozilla::MallocSizeOf;
+using mozilla::Maybe;
+using mozilla::MicroTaskRunnable;
+using mozilla::dom::BlobURLProtocolHandler;
+using mozilla::dom::CallerType;
+using mozilla::dom::ClientInfo;
+using mozilla::dom::Report;
+using mozilla::dom::ReportingObserver;
+using mozilla::dom::ServiceWorker;
+using mozilla::dom::ServiceWorkerDescriptor;
+using mozilla::dom::ServiceWorkerRegistration;
+using mozilla::dom::ServiceWorkerRegistrationDescriptor;
+using mozilla::dom::VoidFunction;
+
+nsIGlobalObject::nsIGlobalObject()
+ : mIsDying(false), mIsScriptForbidden(false), mIsInnerWindow(false) {}
+
+bool nsIGlobalObject::IsScriptForbidden(JSObject* aCallback,
+ bool aIsJSImplementedWebIDL) const {
+ if (mIsScriptForbidden || mIsDying) {
+ return true;
+ }
+
+ if (NS_IsMainThread()) {
+ if (aIsJSImplementedWebIDL) {
+ return false;
+ }
+
+ if (!xpc::Scriptability::AllowedIfExists(aCallback)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsIGlobalObject::~nsIGlobalObject() {
+ UnlinkObjectsInGlobal();
+ DisconnectGlobalTeardownObservers();
+ MOZ_DIAGNOSTIC_ASSERT(mGlobalTeardownObservers.isEmpty());
+}
+
+nsIPrincipal* nsIGlobalObject::PrincipalOrNull() const {
+ JSObject* global = GetGlobalJSObjectPreserveColor();
+ if (NS_WARN_IF(!global)) return nullptr;
+
+ return nsContentUtils::ObjectPrincipal(global);
+}
+
+void nsIGlobalObject::RegisterHostObjectURI(const nsACString& aURI) {
+ MOZ_ASSERT(!mHostObjectURIs.Contains(aURI));
+ mHostObjectURIs.AppendElement(aURI);
+}
+
+void nsIGlobalObject::UnregisterHostObjectURI(const nsACString& aURI) {
+ mHostObjectURIs.RemoveElement(aURI);
+}
+
+namespace {
+
+class UnlinkHostObjectURIsRunnable final : public mozilla::Runnable {
+ public:
+ explicit UnlinkHostObjectURIsRunnable(nsTArray<nsCString>&& aURIs)
+ : mozilla::Runnable("UnlinkHostObjectURIsRunnable"),
+ mURIs(std::move(aURIs)) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (uint32_t index = 0; index < mURIs.Length(); ++index) {
+ BlobURLProtocolHandler::RemoveDataEntry(mURIs[index]);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~UnlinkHostObjectURIsRunnable() = default;
+
+ const nsTArray<nsCString> mURIs;
+};
+
+} // namespace
+
+void nsIGlobalObject::UnlinkObjectsInGlobal() {
+ if (!mHostObjectURIs.IsEmpty()) {
+ // BlobURLProtocolHandler is main-thread only.
+ if (NS_IsMainThread()) {
+ for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) {
+ BlobURLProtocolHandler::RemoveDataEntry(mHostObjectURIs[index]);
+ }
+
+ mHostObjectURIs.Clear();
+ } else {
+ RefPtr<UnlinkHostObjectURIsRunnable> runnable =
+ new UnlinkHostObjectURIsRunnable(std::move(mHostObjectURIs));
+ MOZ_ASSERT(mHostObjectURIs.IsEmpty());
+
+ nsresult rv = NS_DispatchToMainThread(runnable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch a runnable to the main-thread.");
+ }
+ }
+ }
+
+ mReportRecords.Clear();
+ mReportingObservers.Clear();
+ mCountQueuingStrategySizeFunction = nullptr;
+ mByteLengthQueuingStrategySizeFunction = nullptr;
+}
+
+void nsIGlobalObject::TraverseObjectsInGlobal(
+ nsCycleCollectionTraversalCallback& cb) {
+ // Currently we only store BlobImpl objects off the the main-thread and they
+ // are not CCed.
+ if (!mHostObjectURIs.IsEmpty() && NS_IsMainThread()) {
+ for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) {
+ BlobURLProtocolHandler::Traverse(mHostObjectURIs[index], cb);
+ }
+ }
+
+ nsIGlobalObject* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportRecords)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportingObservers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCountQueuingStrategySizeFunction)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByteLengthQueuingStrategySizeFunction)
+}
+
+void nsIGlobalObject::AddGlobalTeardownObserver(
+ GlobalTeardownObserver* aObject) {
+ MOZ_DIAGNOSTIC_ASSERT(aObject);
+ MOZ_ASSERT(!aObject->isInList());
+ mGlobalTeardownObservers.insertBack(aObject);
+}
+
+void nsIGlobalObject::RemoveGlobalTeardownObserver(
+ GlobalTeardownObserver* aObject) {
+ MOZ_DIAGNOSTIC_ASSERT(aObject);
+ MOZ_ASSERT(aObject->isInList());
+ MOZ_ASSERT(aObject->GetOwnerGlobal() == this);
+ aObject->remove();
+}
+
+void nsIGlobalObject::ForEachGlobalTeardownObserver(
+ const std::function<void(GlobalTeardownObserver*, bool* aDoneOut)>& aFunc)
+ const {
+ // Protect against the function call triggering a mutation of the list
+ // while we are iterating by copying the DETH references to a temporary
+ // list.
+ AutoTArray<RefPtr<GlobalTeardownObserver>, 64> targetList;
+ for (const GlobalTeardownObserver* deth = mGlobalTeardownObservers.getFirst();
+ deth; deth = deth->getNext()) {
+ targetList.AppendElement(const_cast<GlobalTeardownObserver*>(deth));
+ }
+
+ // Iterate the target list and call the function on each one.
+ bool done = false;
+ for (auto target : targetList) {
+ // Check to see if a previous iteration's callback triggered the removal
+ // of this target as a side-effect. If it did, then just ignore it.
+ if (target->GetOwnerGlobal() != this) {
+ continue;
+ }
+ aFunc(target, &done);
+ if (done) {
+ break;
+ }
+ }
+}
+
+void nsIGlobalObject::DisconnectGlobalTeardownObservers() {
+ ForEachGlobalTeardownObserver(
+ [&](GlobalTeardownObserver* aTarget, bool* aDoneOut) {
+ aTarget->DisconnectFromOwner();
+
+ // Calling DisconnectFromOwner() should result in
+ // RemoveGlobalTeardownObserver() being called.
+ MOZ_DIAGNOSTIC_ASSERT(aTarget->GetOwnerGlobal() != this);
+ });
+}
+
+Maybe<ClientInfo> nsIGlobalObject::GetClientInfo() const {
+ // By default globals do not expose themselves as a client. Only real
+ // window and worker globals are currently considered clients.
+ return Maybe<ClientInfo>();
+}
+
+Maybe<nsID> nsIGlobalObject::GetAgentClusterId() const {
+ Maybe<ClientInfo> ci = GetClientInfo();
+ if (ci.isSome()) {
+ return ci.value().AgentClusterId();
+ }
+ return mozilla::Nothing();
+}
+
+Maybe<ServiceWorkerDescriptor> nsIGlobalObject::GetController() const {
+ // By default globals do not have a service worker controller. Only real
+ // window and worker globals can currently be controlled as a client.
+ return Maybe<ServiceWorkerDescriptor>();
+}
+
+RefPtr<ServiceWorker> nsIGlobalObject::GetOrCreateServiceWorker(
+ const ServiceWorkerDescriptor& aDescriptor) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "this global should not have any service workers");
+ return nullptr;
+}
+
+RefPtr<ServiceWorkerRegistration> nsIGlobalObject::GetServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
+ const {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "this global should not have any service workers");
+ return nullptr;
+}
+
+RefPtr<ServiceWorkerRegistration>
+nsIGlobalObject::GetOrCreateServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "this global should not have any service worker registrations");
+ return nullptr;
+}
+
+mozilla::StorageAccess nsIGlobalObject::GetStorageAccess() {
+ return mozilla::StorageAccess::eDeny;
+}
+
+nsPIDOMWindowInner* nsIGlobalObject::AsInnerWindow() {
+ if (MOZ_LIKELY(mIsInnerWindow)) {
+ return static_cast<nsPIDOMWindowInner*>(
+ static_cast<nsGlobalWindowInner*>(this));
+ }
+ return nullptr;
+}
+
+size_t nsIGlobalObject::ShallowSizeOfExcludingThis(MallocSizeOf aSizeOf) const {
+ size_t rtn = mHostObjectURIs.ShallowSizeOfExcludingThis(aSizeOf);
+ return rtn;
+}
+
+class QueuedMicrotask : public MicroTaskRunnable {
+ public:
+ QueuedMicrotask(nsIGlobalObject* aGlobal, VoidFunction& aCallback)
+ : mGlobal(aGlobal), mCallback(&aCallback) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {
+ IgnoredErrorResult rv;
+ MOZ_KnownLive(mCallback)->Call(static_cast<ErrorResult&>(rv));
+ }
+
+ bool Suppressed() final { return mGlobal->IsInSyncOperation(); }
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<VoidFunction> mCallback;
+};
+
+void nsIGlobalObject::QueueMicrotask(VoidFunction& aCallback) {
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ if (context) {
+ RefPtr<MicroTaskRunnable> mt = new QueuedMicrotask(this, aCallback);
+ context->DispatchToMicroTask(mt.forget());
+ }
+}
+
+void nsIGlobalObject::RegisterReportingObserver(ReportingObserver* aObserver,
+ bool aBuffered) {
+ MOZ_ASSERT(aObserver);
+
+ if (mReportingObservers.Contains(aObserver)) {
+ return;
+ }
+
+ if (NS_WARN_IF(
+ !mReportingObservers.AppendElement(aObserver, mozilla::fallible))) {
+ return;
+ }
+
+ if (!aBuffered) {
+ return;
+ }
+
+ for (Report* report : mReportRecords) {
+ aObserver->MaybeReport(report);
+ }
+}
+
+void nsIGlobalObject::UnregisterReportingObserver(
+ ReportingObserver* aObserver) {
+ MOZ_ASSERT(aObserver);
+ mReportingObservers.RemoveElement(aObserver);
+}
+
+void nsIGlobalObject::BroadcastReport(Report* aReport) {
+ MOZ_ASSERT(aReport);
+
+ for (ReportingObserver* observer : mReportingObservers) {
+ observer->MaybeReport(aReport);
+ }
+
+ if (NS_WARN_IF(!mReportRecords.AppendElement(aReport, mozilla::fallible))) {
+ return;
+ }
+
+ while (mReportRecords.Length() > MAX_REPORT_RECORDS) {
+ mReportRecords.RemoveElementAt(0);
+ }
+}
+
+void nsIGlobalObject::NotifyReportingObservers() {
+ for (auto& observer : mReportingObservers.Clone()) {
+ // MOZ_KnownLive because the clone of 'mReportingObservers' is guaranteed to
+ // keep it alive.
+ //
+ // This can go away once
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
+ MOZ_KnownLive(observer)->MaybeNotify();
+ }
+}
+
+void nsIGlobalObject::RemoveReportRecords() {
+ mReportRecords.Clear();
+
+ for (auto& observer : mReportingObservers) {
+ observer->ForgetReports();
+ }
+}
+
+already_AddRefed<mozilla::dom::Function>
+nsIGlobalObject::GetCountQueuingStrategySizeFunction() {
+ return do_AddRef(mCountQueuingStrategySizeFunction);
+}
+
+void nsIGlobalObject::SetCountQueuingStrategySizeFunction(
+ mozilla::dom::Function* aFunction) {
+ mCountQueuingStrategySizeFunction = aFunction;
+}
+
+already_AddRefed<mozilla::dom::Function>
+nsIGlobalObject::GetByteLengthQueuingStrategySizeFunction() {
+ return do_AddRef(mByteLengthQueuingStrategySizeFunction);
+}
+
+void nsIGlobalObject::SetByteLengthQueuingStrategySizeFunction(
+ mozilla::dom::Function* aFunction) {
+ mByteLengthQueuingStrategySizeFunction = aFunction;
+}
+
+mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult>
+nsIGlobalObject::GetStorageKey() {
+ return mozilla::Err(NS_ERROR_NOT_AVAILABLE);
+}
+
+mozilla::Result<bool, nsresult> nsIGlobalObject::HasEqualStorageKey(
+ const mozilla::ipc::PrincipalInfo& aStorageKey) {
+ auto result = GetStorageKey();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ const auto& storageKey = result.inspect();
+
+ return mozilla::ipc::StorageKeysEqual(storageKey, aStorageKey);
+}
+
+mozilla::RTPCallerType nsIGlobalObject::GetRTPCallerType() const {
+ if (PrincipalOrNull() && PrincipalOrNull()->IsSystemPrincipal()) {
+ return RTPCallerType::SystemPrincipal;
+ }
+
+ if (ShouldResistFingerprinting(RFPTarget::Unknown)) {
+ return RTPCallerType::ResistFingerprinting;
+ }
+
+ if (CrossOriginIsolated()) {
+ return RTPCallerType::CrossOriginIsolated;
+ }
+
+ return RTPCallerType::Normal;
+}
+
+bool nsIGlobalObject::ShouldResistFingerprinting(CallerType aCallerType,
+ RFPTarget aTarget) const {
+ return aCallerType != CallerType::System &&
+ ShouldResistFingerprinting(aTarget);
+}
diff --git a/dom/base/nsIGlobalObject.h b/dom/base/nsIGlobalObject.h
new file mode 100644
index 0000000000..79c87bb120
--- /dev/null
+++ b/dom/base/nsIGlobalObject.h
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIGlobalObject_h__
+#define nsIGlobalObject_h__
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/DispatcherTrait.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/OriginTrials.h"
+#include "nsContentUtils.h"
+#include "nsHashKeys.h"
+#include "nsISupports.h"
+#include "nsRFPService.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "js/TypeDecls.h"
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_IGLOBALOBJECT_IID \
+ { \
+ 0x11afa8be, 0xd997, 0x4e07, { \
+ 0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f \
+ } \
+ }
+
+class nsCycleCollectionTraversalCallback;
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class DOMEventTargetHelper;
+class GlobalTeardownObserver;
+template <typename V, typename E>
+class Result;
+enum class StorageAccess;
+namespace dom {
+class VoidFunction;
+class DebuggerNotificationManager;
+class FontFaceSet;
+class Function;
+class Report;
+class ReportBody;
+class ReportingObserver;
+class ServiceWorker;
+class ServiceWorkerRegistration;
+class ServiceWorkerRegistrationDescriptor;
+class StorageManager;
+enum class CallerType : uint32_t;
+} // namespace dom
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+} // namespace mozilla
+
+namespace JS::loader {
+class ModuleLoaderBase;
+} // namespace JS::loader
+
+/**
+ * See <https://developer.mozilla.org/en-US/docs/Glossary/Global_object>.
+ */
+class nsIGlobalObject : public nsISupports,
+ public mozilla::dom::DispatcherTrait {
+ private:
+ nsTArray<nsCString> mHostObjectURIs;
+
+ // Raw pointers to bound DETH objects. These are added by
+ // AddGlobalTeardownObserver().
+ mozilla::LinkedList<mozilla::GlobalTeardownObserver> mGlobalTeardownObservers;
+
+ bool mIsDying;
+ bool mIsScriptForbidden;
+
+ protected:
+ bool mIsInnerWindow;
+
+ nsIGlobalObject();
+
+ public:
+ using RTPCallerType = mozilla::RTPCallerType;
+ using RFPTarget = mozilla::RFPTarget;
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IGLOBALOBJECT_IID)
+
+ /**
+ * This check is added to deal with Promise microtask queues. On the main
+ * thread, we do not impose restrictions about when script stops running or
+ * when runnables can no longer be dispatched to the main thread. This means
+ * it is possible for a Promise chain to keep resolving an infinite chain of
+ * promises, preventing the browser from shutting down. See Bug 1058695. To
+ * prevent this, the nsGlobalWindow subclass sets this flag when it is
+ * closed. The Promise implementation checks this and prohibits new runnables
+ * from being dispatched.
+ *
+ * We pair this with checks during processing the promise microtask queue
+ * that pops up the slow script dialog when the Promise queue is preventing
+ * a window from going away.
+ */
+ bool IsDying() const { return mIsDying; }
+
+ /**
+ * Is it currently forbidden to call into script? JS-implemented WebIDL is
+ * a special case that's always allowed because it has the system principal,
+ * and callers should indicate this.
+ */
+ bool IsScriptForbidden(JSObject* aCallback,
+ bool aIsJSImplementedWebIDL = false) const;
+
+ /**
+ * Return the JSObject for this global, if it still has one. Otherwise return
+ * null.
+ *
+ * If non-null is returned, then the returned object will have been already
+ * exposed to active JS, so callers do not need to do it.
+ */
+ virtual JSObject* GetGlobalJSObject() = 0;
+
+ /**
+ * Return the JSObject for this global _without_ exposing it to active JS.
+ * This may return a gray object.
+ *
+ * This method is appropriate to use in assertions (so there is less of a
+ * difference in GC/CC marking between debug and optimized builds) and in
+ * situations where we are sure no CC activity can happen while the return
+ * value is used and the return value does not end up escaping to the heap in
+ * any way. In all other cases, and in particular in cases where the return
+ * value is held in a JS::Rooted or passed to the JSAutoRealm constructor, use
+ * GetGlobalJSObject.
+ */
+ virtual JSObject* GetGlobalJSObjectPreserveColor() const = 0;
+
+ /**
+ * Check whether this nsIGlobalObject still has a JSObject associated with it,
+ * or whether it's torn-down enough that the JSObject is gone.
+ */
+ bool HasJSGlobal() const { return GetGlobalJSObjectPreserveColor(); }
+
+ // This method is not meant to be overridden.
+ nsIPrincipal* PrincipalOrNull() const;
+
+ void RegisterHostObjectURI(const nsACString& aURI);
+
+ void UnregisterHostObjectURI(const nsACString& aURI);
+
+ // Any CC class inheriting nsIGlobalObject should call these 2 methods to
+ // cleanup objects stored in nsIGlobalObject such as blobURLs and Reports.
+ void UnlinkObjectsInGlobal();
+ void TraverseObjectsInGlobal(nsCycleCollectionTraversalCallback& aCb);
+
+ // GlobalTeardownObservers must register themselves on the global when they
+ // bind to it in order to get the DisconnectFromOwner() method
+ // called correctly. RemoveGlobalTeardownObserver() must be called
+ // before the GlobalTeardownObserver is destroyed.
+ void AddGlobalTeardownObserver(mozilla::GlobalTeardownObserver* aObject);
+ void RemoveGlobalTeardownObserver(mozilla::GlobalTeardownObserver* aObject);
+
+ // Iterate the registered GlobalTeardownObservers and call the given function
+ // for each one.
+ void ForEachGlobalTeardownObserver(
+ const std::function<void(mozilla::GlobalTeardownObserver*,
+ bool* aDoneOut)>& aFunc) const;
+
+ virtual bool IsInSyncOperation() { return false; }
+
+ virtual mozilla::dom::DebuggerNotificationManager*
+ GetOrCreateDebuggerNotificationManager() {
+ return nullptr;
+ }
+
+ virtual mozilla::dom::DebuggerNotificationManager*
+ GetExistingDebuggerNotificationManager() {
+ return nullptr;
+ }
+
+ virtual mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+
+ virtual mozilla::Maybe<nsID> GetAgentClusterId() const;
+
+ virtual bool CrossOriginIsolated() const { return false; }
+
+ virtual bool IsSharedMemoryAllowed() const { return false; }
+
+ virtual mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController()
+ const;
+
+ // Get the DOM object for the given descriptor or attempt to create one.
+ // Creation can still fail and return nullptr during shutdown, etc.
+ virtual RefPtr<mozilla::dom::ServiceWorker> GetOrCreateServiceWorker(
+ const mozilla::dom::ServiceWorkerDescriptor& aDescriptor);
+
+ // Get the DOM object for the given descriptor or return nullptr if it does
+ // not exist.
+ virtual RefPtr<mozilla::dom::ServiceWorkerRegistration>
+ GetServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
+ const;
+
+ // Get the DOM object for the given descriptor or attempt to create one.
+ // Creation can still fail and return nullptr during shutdown, etc.
+ virtual RefPtr<mozilla::dom::ServiceWorkerRegistration>
+ GetOrCreateServiceWorkerRegistration(
+ const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor);
+
+ /**
+ * Returns the storage access of this global.
+ *
+ * If you have a global that needs storage access, you should be overriding
+ * this method in your subclass of this class!
+ */
+ virtual mozilla::StorageAccess GetStorageAccess();
+
+ // Returns the set of active origin trials for this global.
+ virtual mozilla::OriginTrials Trials() const = 0;
+
+ // Returns a pointer to this object as an inner window if this is one or
+ // nullptr otherwise.
+ nsPIDOMWindowInner* AsInnerWindow();
+
+ void QueueMicrotask(mozilla::dom::VoidFunction& aCallback);
+
+ void RegisterReportingObserver(mozilla::dom::ReportingObserver* aObserver,
+ bool aBuffered);
+
+ void UnregisterReportingObserver(mozilla::dom::ReportingObserver* aObserver);
+
+ void BroadcastReport(mozilla::dom::Report* aReport);
+
+ MOZ_CAN_RUN_SCRIPT void NotifyReportingObservers();
+
+ void RemoveReportRecords();
+
+ // https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
+ // This function is set once by CountQueuingStrategy::GetSize.
+ already_AddRefed<mozilla::dom::Function>
+ GetCountQueuingStrategySizeFunction();
+ void SetCountQueuingStrategySizeFunction(mozilla::dom::Function* aFunction);
+
+ already_AddRefed<mozilla::dom::Function>
+ GetByteLengthQueuingStrategySizeFunction();
+ void SetByteLengthQueuingStrategySizeFunction(
+ mozilla::dom::Function* aFunction);
+
+ /**
+ * Check whether we should avoid leaking distinguishing information to JS/CSS.
+ * https://w3c.github.io/fingerprinting-guidance/
+ */
+ virtual bool ShouldResistFingerprinting(RFPTarget aTarget) const = 0;
+
+ // CallerType::System callers never have to resist fingerprinting.
+ bool ShouldResistFingerprinting(mozilla::dom::CallerType aCallerType,
+ RFPTarget aTarget) const;
+
+ RTPCallerType GetRTPCallerType() const;
+
+ /**
+ * Get the module loader to use for this global, if any. By default this
+ * returns null.
+ */
+ virtual JS::loader::ModuleLoaderBase* GetModuleLoader(JSContext* aCx) {
+ return nullptr;
+ }
+
+ virtual mozilla::dom::FontFaceSet* GetFonts() { return nullptr; }
+
+ virtual mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult>
+ GetStorageKey();
+ mozilla::Result<bool, nsresult> HasEqualStorageKey(
+ const mozilla::ipc::PrincipalInfo& aStorageKey);
+
+ virtual mozilla::dom::StorageManager* GetStorageManager() { return nullptr; }
+
+ protected:
+ virtual ~nsIGlobalObject();
+
+ void StartDying() { mIsDying = true; }
+
+ void StartForbiddingScript() { mIsScriptForbidden = true; }
+ void StopForbiddingScript() { mIsScriptForbidden = false; }
+
+ void DisconnectGlobalTeardownObservers();
+
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aSizeOf) const;
+
+ private:
+ // List of Report objects for ReportingObservers.
+ nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers;
+ nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords;
+
+ // https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
+ RefPtr<mozilla::dom::Function> mCountQueuingStrategySizeFunction;
+
+ // https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
+ RefPtr<mozilla::dom::Function> mByteLengthQueuingStrategySizeFunction;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIGlobalObject, NS_IGLOBALOBJECT_IID)
+
+#endif // nsIGlobalObject_h__
diff --git a/dom/base/nsIImageLoadingContent.idl b/dom/base/nsIImageLoadingContent.idl
new file mode 100644
index 0000000000..cdb2f10f2a
--- /dev/null
+++ b/dom/base/nsIImageLoadingContent.idl
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgINotificationObserver.idl"
+
+%{C++
+#include "mozilla/Maybe.h"
+#include "Visibility.h"
+%}
+
+interface imgIRequest;
+interface nsIChannel;
+interface nsIStreamListener;
+interface nsIURI;
+interface nsIFrame;
+
+[ref] native MaybeOnNonvisible(const mozilla::Maybe<mozilla::OnNonvisible>);
+native Visibility(mozilla::Visibility);
+
+/**
+ * This interface represents a content node that loads images. The interface
+ * exists to allow getting information on the images that the content node
+ * loads and to allow registration of observers for the image loads.
+ *
+ * Implementors of this interface should handle all the mechanics of actually
+ * loading an image -- getting the URI, checking with content policies and
+ * the security manager to see whether loading the URI is allowed, performing
+ * the load, firing any DOM events as needed.
+ *
+ * An implementation of this interface may support the concepts of a
+ * "current" image and a "pending" image. If it does, a request to change
+ * the currently loaded image will start a "pending" request which will
+ * become current only when the image is loaded. It is the responsibility of
+ * observers to check which request they are getting notifications for.
+ *
+ * Please make sure to update the MozImageLoadingContent WebIDL
+ * mixin to mirror this interface when changing it.
+ */
+
+// We can't make this interface noscript yet, because there is JS code doing
+// "instanceof Ci.nsIImageLoadingContent" and using the nsIImageLoadingContent
+// constants.
+[scriptable, builtinclass, uuid(0357123d-9224-4d12-a47e-868c32689777)]
+interface nsIImageLoadingContent : imgINotificationObserver
+{
+ /**
+ * Request types. Image loading content nodes attempt to do atomic
+ * image changes when the image url is changed. This means that
+ * when the url changes the new image load will start, but the old
+ * image will remain the "current" request until the new image is
+ * fully loaded. At that point, the old "current" request will be
+ * discarded and the "pending" request will become "current".
+ */
+ const long UNKNOWN_REQUEST = -1;
+ const long CURRENT_REQUEST = 0;
+ const long PENDING_REQUEST = 1;
+
+ /**
+ * setLoadingEnabled is used to enable and disable loading in
+ * situations where loading images is unwanted. Note that enabling
+ * loading will *not* automatically trigger an image load.
+ */
+ [notxpcom, nostdcall] void setLoadingEnabled(in boolean aEnabled);
+
+ /**
+ * Used to register an image decoder observer. Typically, this will
+ * be a proxy for a frame that wants to paint the image.
+ * Notifications from ongoing image loads will be passed to all
+ * registered observers. Notifications for all request types,
+ * current and pending, will be passed through.
+ *
+ * @param aObserver the observer to register
+ */
+ [notxpcom, nostdcall] void addNativeObserver(in imgINotificationObserver aObserver);
+
+ /**
+ * Used to unregister an image decoder observer.
+ *
+ * @param aObserver the observer to unregister
+ */
+ [notxpcom, nostdcall] void removeNativeObserver(in imgINotificationObserver aObserver);
+
+ /**
+ * Accessor to get the image requests
+ *
+ * @param aRequestType a value saying which request is wanted
+ *
+ * @return the imgIRequest object (may be null, even when no error
+ * is thrown)
+ *
+ * @throws NS_ERROR_UNEXPECTED if the request type requested is not
+ * known
+ */
+ [noscript] imgIRequest getRequest(in long aRequestType);
+
+ /**
+ * Used to notify the image loading content node that a frame has been
+ * created.
+ */
+ [notxpcom] void frameCreated(in nsIFrame aFrame);
+
+ /**
+ * Used to notify the image loading content node that a frame has been
+ * destroyed.
+ */
+ [notxpcom] void frameDestroyed(in nsIFrame aFrame);
+
+ /**
+ * Used to find out what type of request one is dealing with (eg
+ * which request got passed through to the imgINotificationObserver
+ * interface of an observer)
+ *
+ * @param aRequest the request whose type we want to know
+ *
+ * @return an enum value saying what type this request is
+ *
+ * @throws NS_ERROR_UNEXPECTED if aRequest is not known
+ */
+ [noscript] long getRequestType(in imgIRequest aRequest);
+
+ /**
+ * Gets the URI of the current request, if available.
+ * Otherwise, returns the last URI that this content tried to load, or
+ * null if there haven't been any such attempts.
+ */
+ [noscript, infallible] readonly attribute nsIURI currentURI;
+
+ /**
+ * Gets the sync-decoding hint set by the decoding attribute.
+ */
+ [noscript, infallible] readonly attribute boolean syncDecodingHint;
+
+ /**
+ * loadImageWithChannel allows data from an existing channel to be
+ * used as the image data for this content node.
+ *
+ * @param aChannel the channel that will deliver the data
+ *
+ * @return a stream listener to pump the image data into
+ *
+ * @see imgILoader::loadImageWithChannel
+ *
+ * @throws NS_ERROR_NULL_POINTER if aChannel is null
+ */
+ [noscript] nsIStreamListener loadImageWithChannel(in nsIChannel aChannel);
+
+ /**
+ * Called by layout to announce when the frame associated with this content
+ * has changed its visibility state.
+ *
+ * @param aNewVisibility The new visibility state.
+ * @param aNonvisibleAction A requested action if the frame has become
+ * nonvisible. If Nothing(), no action is
+ * requested. If DISCARD_IMAGES is specified, the
+ * frame is requested to ask any images it's
+ * associated with to discard their surfaces if
+ * possible.
+ */
+ [noscript, notxpcom] void onVisibilityChange(in Visibility aNewVisibility,
+ in MaybeOnNonvisible aNonvisibleAction);
+};
diff --git a/dom/base/nsIMessageManager.idl b/dom/base/nsIMessageManager.idl
new file mode 100644
index 0000000000..e288bf089c
--- /dev/null
+++ b/dom/base/nsIMessageManager.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIContent;
+
+// NOTE: Only scriptable for Ci.nsIMessageSender
+[scriptable, builtinclass, uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
+interface nsIMessageSender : nsISupports
+{
+};
+
+[uuid(b39a3324-b574-4f85-8cdb-274d04f807ef)]
+interface nsIInProcessContentFrameMessageManager : nsIMessageSender
+{
+ [notxpcom] nsIContent getOwnerContent();
+};
diff --git a/dom/base/nsIMutationObserver.h b/dom/base/nsIMutationObserver.h
new file mode 100644
index 0000000000..71abf05925
--- /dev/null
+++ b/dom/base/nsIMutationObserver.h
@@ -0,0 +1,432 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIMutationObserver_h
+#define nsIMutationObserver_h
+
+#include "nsISupports.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DoublyLinkedList.h"
+
+class nsAttrValue;
+class nsAtom;
+class nsIContent;
+class nsINode;
+
+namespace mozilla::dom {
+class Element;
+} // namespace mozilla::dom
+
+#define NS_IMUTATION_OBSERVER_IID \
+ { \
+ 0x6d674c17, 0x0fbc, 0x4633, { \
+ 0x8f, 0x46, 0x73, 0x4e, 0x87, 0xeb, 0xf0, 0xc7 \
+ } \
+ }
+
+/**
+ * Information details about a characterdata change. Basically, we
+ * view all changes as replacements of a length of text at some offset
+ * with some other text (of possibly some other length).
+ */
+struct CharacterDataChangeInfo {
+ /**
+ * True if this character data change is just an append.
+ */
+ bool mAppend;
+
+ /**
+ * The offset in the text where the change occurred.
+ */
+ uint32_t mChangeStart;
+
+ /**
+ * The offset such that mChangeEnd - mChangeStart is equal to the length of
+ * the text we removed. If this was a pure insert, append or a result of
+ * `splitText()` this is equal to mChangeStart.
+ */
+ uint32_t mChangeEnd;
+
+ uint32_t LengthOfRemovedText() const {
+ MOZ_ASSERT(mChangeStart <= mChangeEnd);
+
+ return mChangeEnd - mChangeStart;
+ }
+
+ /**
+ * The length of the text that was inserted in place of the removed text. If
+ * this was a pure text removal, this is 0.
+ */
+ uint32_t mReplaceLength;
+
+ /**
+ * The net result is that mChangeStart characters at the beginning of the
+ * text remained as they were. The next mChangeEnd - mChangeStart characters
+ * were removed, and mReplaceLength characters were inserted in their place.
+ * The text that used to begin at mChangeEnd now begins at
+ * mChangeStart + mReplaceLength.
+ */
+
+ struct MOZ_STACK_CLASS Details {
+ enum {
+ eMerge, // two text nodes are merged as a result of normalize()
+ eSplit // a text node is split as a result of splitText()
+ } mType;
+ /**
+ * For eMerge it's the text node that will be removed, for eSplit it's the
+ * new text node.
+ */
+ nsIContent* MOZ_NON_OWNING_REF mNextSibling;
+ };
+
+ /**
+ * Used for splitText() and normalize(), otherwise null.
+ */
+ Details* mDetails;
+};
+
+/**
+ * Mutation observer interface
+ *
+ * See nsINode::AddMutationObserver, nsINode::RemoveMutationObserver for how to
+ * attach or remove your observers. nsINode stores mutation observers using a
+ * mozilla::SafeDoublyLinkedList, which is a specialization of the
+ * DoublyLinkedList allowing for adding/removing elements while iterating.
+ * If a mutation observer is intended to be added to multiple nsINode instances,
+ * derive from nsMultiMutationObserver.
+ *
+ * WARNING: During these notifications, you are not allowed to perform
+ * any mutations to the current or any other document, or start a
+ * network load. If you need to perform such operations do that
+ * during the _last_ nsIDocumentObserver::EndUpdate notification. The
+ * exception for this is ParentChainChanged, where mutations should be
+ * done from an async event, as the notification might not be
+ * surrounded by BeginUpdate/EndUpdate calls.
+ */
+class nsIMutationObserver
+ : public nsISupports,
+ mozilla::DoublyLinkedListElement<nsIMutationObserver> {
+ friend struct mozilla::GetDoublyLinkedListElement<nsIMutationObserver>;
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMUTATION_OBSERVER_IID)
+
+ /**
+ * Notification that the node value of a data node (text, cdata, pi, comment)
+ * will be changed.
+ *
+ * This notification is not sent when a piece of content is
+ * added/removed from the document (the other notifications are used
+ * for that).
+ *
+ * @param aContent The piece of content that changed. Is never null.
+ * @param aInfo The structure with information details about the change.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void CharacterDataWillChange(nsIContent* aContent,
+ const CharacterDataChangeInfo&) = 0;
+
+ /**
+ * Notification that the node value of a data node (text, cdata, pi, comment)
+ * has changed.
+ *
+ * This notification is not sent when a piece of content is
+ * added/removed from the document (the other notifications are used
+ * for that).
+ *
+ * @param aContent The piece of content that changed. Is never null.
+ * @param aInfo The structure with information details about the change.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) = 0;
+
+ /**
+ * Notification that an attribute of an element will change. This
+ * can happen before the BeginUpdate for the change and may not
+ * always be followed by an AttributeChanged (in particular, if the
+ * attribute doesn't actually change there will be no corresponding
+ * AttributeChanged).
+ *
+ * @param aContent The element whose attribute will change
+ * @param aNameSpaceID The namespace id of the changing attribute
+ * @param aAttribute The name of the changing attribute
+ * @param aModType Whether or not the attribute will be added, changed, or
+ * removed. The constants are defined in
+ * MutationEvent.webidl.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void AttributeWillChange(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) = 0;
+
+ /**
+ * Notification that an attribute of an element has changed.
+ *
+ * @param aElement The element whose attribute changed
+ * @param aNameSpaceID The namespace id of the changed attribute
+ * @param aAttribute The name of the changed attribute
+ * @param aModType Whether or not the attribute was added, changed, or
+ * removed. The constants are defined in
+ * MutationEvent.webidl.
+ * @param aOldValue The old value, if either the old value or the new
+ * value are StoresOwnData() (or absent); null otherwise.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void AttributeChanged(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) = 0;
+
+ /**
+ * Notification that an attribute of an element has been
+ * set to the value it already had.
+ *
+ * @param aElement The element whose attribute changed
+ * @param aNameSpaceID The namespace id of the changed attribute
+ * @param aAttribute The name of the changed attribute
+ */
+ virtual void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) {}
+
+ /**
+ * Notification that one or more content nodes have been appended to the
+ * child list of another node in the tree.
+ *
+ * @param aFirstNewContent the first node appended.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void ContentAppended(nsIContent* aFirstNewContent) = 0;
+
+ /**
+ * Notification that a content node has been inserted as child to another
+ * node in the tree.
+ *
+ * @param aChild The newly inserted child.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void ContentInserted(nsIContent* aChild) = 0;
+
+ /**
+ * Notification that a content node has been removed from the child list of
+ * another node in the tree.
+ *
+ * @param aChild The child that was removed.
+ * @param aPreviousSibling The previous sibling to the child that was removed.
+ * Can be null if there was no previous sibling.
+ *
+ * @note Callers of this method might not hold a strong reference to the
+ * observer. The observer is responsible for making sure it stays
+ * alive for the duration of the call as needed. The observer may
+ * assume that this call will happen when there are script blockers on
+ * the stack.
+ */
+ virtual void ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) = 0;
+
+ /**
+ * The node is in the process of being destroyed. Calling QI on the node is
+ * not supported, however it is possible to get children and flags through
+ * nsINode as well as calling IsContent and casting to nsIContent to get
+ * attributes.
+ *
+ * NOTE: This notification is only called on observers registered directly
+ * on the node. This is because when the node is destroyed it can not have
+ * any ancestors. If you want to know when a descendant node is being
+ * removed from the observed node, use the ContentRemoved notification.
+ *
+ * @param aNode The node being destroyed.
+ *
+ * @note Callers of this method might not hold a strong reference to
+ * the observer. The observer is responsible for making sure it
+ * stays alive for the duration of the call as needed.
+ */
+ virtual void NodeWillBeDestroyed(nsINode* aNode) = 0;
+
+ /**
+ * Notification that the node's parent chain has changed. This
+ * happens when either the node or one of its ancestors is inserted
+ * or removed as a child of another node.
+ *
+ * Note that when a node is inserted this notification is sent to
+ * all descendants of that node, since all such nodes have their
+ * parent chain changed.
+ *
+ * @param aContent The piece of content that had its parent changed.
+ *
+ * @note Callers of this method might not hold a strong reference to
+ * the observer. The observer is responsible for making sure it
+ * stays alive for the duration of the call as needed.
+ */
+
+ virtual void ParentChainChanged(nsIContent* aContent) = 0;
+
+ virtual void ARIAAttributeDefaultWillChange(mozilla::dom::Element* aElement,
+ nsAtom* aAttribute,
+ int32_t aModType) = 0;
+ virtual void ARIAAttributeDefaultChanged(mozilla::dom::Element* aElement,
+ nsAtom* aAttribute,
+ int32_t aModType) = 0;
+
+ enum : uint32_t {
+ kNone = 0,
+ kCharacterDataWillChange = 1 << 0,
+ kCharacterDataChanged = 1 << 1,
+ kAttributeWillChange = 1 << 2,
+ kAttributeChanged = 1 << 3,
+ kAttributeSetToCurrentValue = 1 << 4,
+ kContentAppended = 1 << 5,
+ kContentInserted = 1 << 6,
+ kContentRemoved = 1 << 7,
+ kNodeWillBeDestroyed = 1 << 8,
+ kParentChainChanged = 1 << 9,
+ kARIAAttributeDefaultWillChange = 1 << 10,
+ kARIAAttributeDefaultChanged = 1 << 11,
+
+ kBeginUpdate = 1 << 12,
+ kEndUpdate = 1 << 13,
+ kBeginLoad = 1 << 14,
+ kEndLoad = 1 << 15,
+ kElementStateChanged = 1 << 16,
+
+ kAnimationAdded = 1 << 17,
+ kAnimationChanged = 1 << 18,
+ kAnimationRemoved = 1 << 19,
+
+ kAll = 0xFFFFFFFF
+ };
+
+ void SetEnabledCallbacks(uint32_t aCallbacks) {
+ mEnabledCallbacks = aCallbacks;
+ }
+
+ bool IsCallbackEnabled(uint32_t aCallback) const {
+ return mEnabledCallbacks & aCallback;
+ }
+
+ private:
+ uint32_t mEnabledCallbacks = kAll;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
+ virtual void CharacterDataWillChange( \
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
+ virtual void CharacterDataChanged( \
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
+ virtual void AttributeWillChange(mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, nsAtom* aAttribute, \
+ int32_t aModType) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
+ virtual void AttributeChanged(mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, nsAtom* aAttribute, \
+ int32_t aModType, \
+ const nsAttrValue* aOldValue) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
+ virtual void ContentAppended(nsIContent* aFirstNewContent) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
+ virtual void ContentInserted(nsIContent* aChild) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED \
+ virtual void ContentRemoved(nsIContent* aChild, \
+ nsIContent* aPreviousSibling) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED \
+ virtual void NodeWillBeDestroyed(nsINode* aNode) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED \
+ virtual void ParentChainChanged(nsIContent* aContent) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ARIAATTRIBUTEDEFAULTWILLCHANGE \
+ virtual void ARIAAttributeDefaultWillChange( \
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) \
+ override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ARIAATTRIBUTEDEFAULTCHANGED \
+ virtual void ARIAAttributeDefaultChanged( \
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) \
+ override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER \
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED \
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED \
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER_ARIAATTRIBUTEDEFAULTWILLCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_ARIAATTRIBUTEDEFAULTCHANGED
+
+#define NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(_class) \
+ void _class::NodeWillBeDestroyed(nsINode* aNode) {}
+
+#define NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(_class) \
+ void _class::CharacterDataWillChange( \
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {} \
+ void _class::CharacterDataChanged(nsIContent* aContent, \
+ const CharacterDataChangeInfo& aInfo) {} \
+ void _class::AttributeWillChange(mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, nsAtom* aAttribute, \
+ int32_t aModType) {} \
+ void _class::AttributeChanged( \
+ mozilla::dom::Element* aElement, int32_t aNameSpaceID, \
+ nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) {} \
+ void _class::ContentAppended(nsIContent* aFirstNewContent) {} \
+ void _class::ContentInserted(nsIContent* aChild) {} \
+ void _class::ContentRemoved(nsIContent* aChild, \
+ nsIContent* aPreviousSibling) {} \
+ void _class::ParentChainChanged(nsIContent* aContent) {} \
+ void _class::ARIAAttributeDefaultWillChange( \
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) { \
+ } \
+ void _class::ARIAAttributeDefaultChanged( \
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) { \
+ }
+
+#endif /* nsIMutationObserver_h */
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
new file mode 100644
index 0000000000..1f18279ece
--- /dev/null
+++ b/dom/base/nsINode.cpp
@@ -0,0 +1,3703 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base class for all DOM nodes.
+ */
+
+#include "nsINode.h"
+
+#include "AccessCheck.h"
+#include "jsapi.h"
+#include "js/ForOfIterator.h" // JS::ForOfIterator
+#include "js/JSON.h" // JS_ParseJSON
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DebuggerNotificationBinding.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/SVGUseElement.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/L10nOverlays.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsAttrValueOrString.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/Attr.h"
+#include "nsDOMAttributeMap.h"
+#include "nsDOMCID.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsError.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMString.h"
+#include "nsDOMTokenList.h"
+#include "nsFocusManager.h"
+#include "nsFrameSelection.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsAtom.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScrollableFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIWidget.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsObjectLoadingContent.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsRange.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+#include "nsTextNode.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowSizes.h"
+#include "mozilla/Preferences.h"
+#include "xpcpublic.h"
+#include "HTMLLegendElement.h"
+#include "nsWrapperCacheInlines.h"
+#include "WrapperFactory.h"
+#include <algorithm>
+#include "nsGlobalWindow.h"
+#include "GeometryUtils.h"
+#include "nsIAnimationObserver.h"
+#include "nsChildContentList.h"
+#include "mozilla/dom/NodeBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "xpcprivate.h"
+
+#include "XPathGenerator.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/dom/AccessibleNode.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static bool ShouldUseNACScope(const nsINode* aNode) {
+ return aNode->IsInNativeAnonymousSubtree();
+}
+
+static bool ShouldUseUAWidgetScope(const nsINode* aNode) {
+ return aNode->HasBeenInUAWidget();
+}
+
+void* nsINode::operator new(size_t aSize, nsNodeInfoManager* aManager) {
+ if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
+ MOZ_ASSERT(aManager, "nsNodeInfoManager needs to be initialized");
+ return aManager->Allocate(aSize);
+ }
+ return ::operator new(aSize);
+}
+void nsINode::operator delete(void* aPtr) { free_impl(aPtr); }
+
+bool nsINode::IsInclusiveDescendantOf(const nsINode* aNode) const {
+ MOZ_ASSERT(aNode, "The node is nullptr.");
+
+ if (aNode == this) {
+ return true;
+ }
+
+ if (!aNode->HasFlag(NODE_MAY_HAVE_ELEMENT_CHILDREN)) {
+ return GetParentNode() == aNode;
+ }
+
+ for (nsINode* node : Ancestors(*this)) {
+ if (node == aNode) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsINode::IsInclusiveFlatTreeDescendantOf(const nsINode* aNode) const {
+ MOZ_ASSERT(aNode, "The node is nullptr.");
+
+ for (nsINode* node : InclusiveFlatTreeAncestors(*this)) {
+ if (node == aNode) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsINode::IsShadowIncludingInclusiveDescendantOf(
+ const nsINode* aNode) const {
+ MOZ_ASSERT(aNode, "The node is nullptr.");
+
+ if (this->GetComposedDoc() == aNode) {
+ return true;
+ }
+
+ const nsINode* node = this;
+ do {
+ if (node == aNode) {
+ return true;
+ }
+
+ node = node->GetParentOrShadowHostNode();
+ } while (node);
+
+ return false;
+}
+
+nsINode::nsSlots::nsSlots() : mWeakReference(nullptr) {}
+
+nsINode::nsSlots::~nsSlots() {
+ if (mChildNodes) {
+ mChildNodes->InvalidateCacheIfAvailable();
+ }
+
+ if (mWeakReference) {
+ mWeakReference->NoticeNodeDestruction();
+ }
+}
+
+void nsINode::nsSlots::Traverse(nsCycleCollectionTraversalCallback& cb) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildNodes");
+ cb.NoteXPCOMChild(mChildNodes);
+}
+
+void nsINode::nsSlots::Unlink(nsINode&) {
+ if (mChildNodes) {
+ mChildNodes->InvalidateCacheIfAvailable();
+ ImplCycleCollectionUnlink(mChildNodes);
+ }
+}
+
+//----------------------------------------------------------------------
+
+#ifdef MOZILLA_INTERNAL_API
+nsINode::nsINode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : mNodeInfo(std::move(aNodeInfo)),
+ mParent(nullptr)
+# ifndef BOOL_FLAGS_ON_WRAPPER_CACHE
+ ,
+ mBoolFlags(0)
+# endif
+ ,
+ mChildCount(0),
+ mPreviousOrLastSibling(nullptr),
+ mSubtreeRoot(this),
+ mSlots(nullptr) {
+}
+#endif
+
+nsINode::~nsINode() {
+ MOZ_ASSERT(!HasSlots(), "LastRelease was not called?");
+ MOZ_ASSERT(mSubtreeRoot == this, "Didn't restore state properly?");
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void nsINode::AssertInvariantsOnNodeInfoChange() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsInComposedDoc());
+ if (nsCOMPtr<Link> link = do_QueryInterface(this)) {
+ MOZ_DIAGNOSTIC_ASSERT(!link->HasPendingLinkUpdate());
+ }
+}
+#endif
+
+void* nsINode::GetProperty(const nsAtom* aPropertyName,
+ nsresult* aStatus) const {
+ if (!HasProperties()) { // a fast HasFlag() test
+ if (aStatus) {
+ *aStatus = NS_PROPTABLE_PROP_NOT_THERE;
+ }
+ return nullptr;
+ }
+ return OwnerDoc()->PropertyTable().GetProperty(this, aPropertyName, aStatus);
+}
+
+nsresult nsINode::SetProperty(nsAtom* aPropertyName, void* aValue,
+ NSPropertyDtorFunc aDtor, bool aTransfer) {
+ nsresult rv = OwnerDoc()->PropertyTable().SetProperty(
+ this, aPropertyName, aValue, aDtor, nullptr, aTransfer);
+ if (NS_SUCCEEDED(rv)) {
+ SetFlags(NODE_HAS_PROPERTIES);
+ }
+
+ return rv;
+}
+
+void nsINode::RemoveProperty(const nsAtom* aPropertyName) {
+ OwnerDoc()->PropertyTable().RemoveProperty(this, aPropertyName);
+}
+
+void* nsINode::TakeProperty(const nsAtom* aPropertyName, nsresult* aStatus) {
+ return OwnerDoc()->PropertyTable().TakeProperty(this, aPropertyName, aStatus);
+}
+
+nsIContentSecurityPolicy* nsINode::GetCsp() const {
+ return OwnerDoc()->GetCsp();
+}
+
+nsINode::nsSlots* nsINode::CreateSlots() { return new nsSlots(); }
+
+static const nsINode* GetClosestCommonInclusiveAncestorForRangeInSelection(
+ const nsINode* aNode) {
+ while (aNode &&
+ !aNode->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ if (!aNode
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ return nullptr;
+ }
+ aNode = aNode->GetParentNode();
+ }
+ return aNode;
+}
+
+/**
+ * A Comparator suitable for mozilla::BinarySearchIf for searching a collection
+ * of nsRange* for an overlap of (mNode, mStartOffset) .. (mNode, mEndOffset).
+ */
+class IsItemInRangeComparator {
+ public:
+ // @param aStartOffset has to be less or equal to aEndOffset.
+ IsItemInRangeComparator(const nsINode& aNode, const uint32_t aStartOffset,
+ const uint32_t aEndOffset,
+ nsContentUtils::ComparePointsCache* aCache)
+ : mNode(aNode),
+ mStartOffset(aStartOffset),
+ mEndOffset(aEndOffset),
+ mCache(aCache) {
+ MOZ_ASSERT(aStartOffset <= aEndOffset);
+ }
+
+ int operator()(const AbstractRange* const aRange) const {
+ int32_t cmp = nsContentUtils::ComparePoints_Deprecated(
+ &mNode, mEndOffset, aRange->GetStartContainer(), aRange->StartOffset(),
+ nullptr, mCache);
+ if (cmp == 1) {
+ cmp = nsContentUtils::ComparePoints_Deprecated(
+ &mNode, mStartOffset, aRange->GetEndContainer(), aRange->EndOffset(),
+ nullptr, mCache);
+ if (cmp == -1) {
+ return 0;
+ }
+ return 1;
+ }
+ return -1;
+ }
+
+ private:
+ const nsINode& mNode;
+ const uint32_t mStartOffset;
+ const uint32_t mEndOffset;
+ nsContentUtils::ComparePointsCache* mCache;
+};
+
+bool nsINode::IsSelected(const uint32_t aStartOffset,
+ const uint32_t aEndOffset) const {
+ MOZ_ASSERT(aStartOffset <= aEndOffset);
+
+ const nsINode* n = GetClosestCommonInclusiveAncestorForRangeInSelection(this);
+ NS_ASSERTION(n || !IsMaybeSelected(),
+ "A node without a common inclusive ancestor for a range in "
+ "Selection is for sure not selected.");
+
+ // Collect the selection objects for potential ranges.
+ nsTHashSet<Selection*> ancestorSelections;
+ for (; n; n = GetClosestCommonInclusiveAncestorForRangeInSelection(
+ n->GetParentNode())) {
+ const LinkedList<AbstractRange>* ranges =
+ n->GetExistingClosestCommonInclusiveAncestorRanges();
+ if (!ranges) {
+ continue;
+ }
+ for (const AbstractRange* range : *ranges) {
+ MOZ_ASSERT(range->IsInAnySelection(),
+ "Why is this range registered with a node?");
+ // Looks like that IsInSelection() assert fails sometimes...
+ if (range->IsInAnySelection()) {
+ for (const WeakPtr<Selection>& selection : range->GetSelections()) {
+ ancestorSelections.Insert(selection);
+ }
+ }
+ }
+ }
+
+ nsContentUtils::ComparePointsCache cache;
+ IsItemInRangeComparator comparator{*this, aStartOffset, aEndOffset, &cache};
+ for (Selection* selection : ancestorSelections) {
+ // Binary search the sorted ranges in this selection.
+ // (Selection::GetRangeAt returns its ranges ordered).
+ size_t low = 0;
+ size_t high = selection->RangeCount();
+
+ while (high != low) {
+ size_t middle = low + (high - low) / 2;
+
+ const AbstractRange* const range = selection->GetAbstractRangeAt(middle);
+ int result = comparator(range);
+ if (result == 0) {
+ if (!range->Collapsed()) {
+ return true;
+ }
+
+ const AbstractRange* middlePlus1;
+ const AbstractRange* middleMinus1;
+ // if node end > start of middle+1, result = 1
+ if (middle + 1 < high &&
+ (middlePlus1 = selection->GetAbstractRangeAt(middle + 1)) &&
+ nsContentUtils::ComparePoints_Deprecated(
+ this, aEndOffset, middlePlus1->GetStartContainer(),
+ middlePlus1->StartOffset(), nullptr, &cache) > 0) {
+ result = 1;
+ // if node start < end of middle - 1, result = -1
+ } else if (middle >= 1 &&
+ (middleMinus1 = selection->GetAbstractRangeAt(middle - 1)) &&
+ nsContentUtils::ComparePoints_Deprecated(
+ this, aStartOffset, middleMinus1->GetEndContainer(),
+ middleMinus1->EndOffset(), nullptr, &cache) < 0) {
+ result = -1;
+ } else {
+ break;
+ }
+ }
+
+ if (result < 0) {
+ high = middle;
+ } else {
+ low = middle + 1;
+ }
+ }
+ }
+
+ return false;
+}
+
+Element* nsINode::GetAnonymousRootElementOfTextEditor(
+ TextEditor** aTextEditor) {
+ if (aTextEditor) {
+ *aTextEditor = nullptr;
+ }
+ RefPtr<TextControlElement> textControlElement;
+ if (IsInNativeAnonymousSubtree()) {
+ textControlElement = TextControlElement::FromNodeOrNull(
+ GetClosestNativeAnonymousSubtreeRootParentOrHost());
+ } else {
+ textControlElement = TextControlElement::FromNode(this);
+ }
+ if (!textControlElement) {
+ return nullptr;
+ }
+ RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
+ if (!textEditor) {
+ // The found `TextControlElement` may be an input element which is not a
+ // text control element. In this case, such element must not be in a
+ // native anonymous tree of a `TextEditor` so this node is not in any
+ // `TextEditor`.
+ return nullptr;
+ }
+
+ Element* rootElement = textEditor->GetRoot();
+ if (aTextEditor) {
+ textEditor.forget(aTextEditor);
+ }
+ return rootElement;
+}
+
+void nsINode::QueueDevtoolsAnonymousEvent(bool aIsRemove) {
+ MOZ_ASSERT(IsRootOfNativeAnonymousSubtree());
+ MOZ_ASSERT(OwnerDoc()->DevToolsAnonymousAndShadowEventsEnabled());
+ AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+ this, aIsRemove ? u"anonymousrootremoved"_ns : u"anonymousrootcreated"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes, Composed::eYes);
+ dispatcher->PostDOMEvent();
+}
+
+nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) {
+ if (aOptions.mComposed) {
+ if (Document* doc = GetComposedDoc()) {
+ return doc;
+ }
+
+ nsINode* node = this;
+ while (node) {
+ node = node->SubtreeRoot();
+ ShadowRoot* shadow = ShadowRoot::FromNode(node);
+ if (!shadow) {
+ break;
+ }
+ node = shadow->GetHost();
+ }
+
+ return node;
+ }
+
+ return SubtreeRoot();
+}
+
+nsIContent* nsINode::GetFirstChildOfTemplateOrNode() {
+ if (IsTemplateElement()) {
+ DocumentFragment* frag = static_cast<HTMLTemplateElement*>(this)->Content();
+ return frag->GetFirstChild();
+ }
+
+ return GetFirstChild();
+}
+
+nsINode* nsINode::SubtreeRoot() const {
+ auto RootOfNode = [](const nsINode* aStart) -> nsINode* {
+ const nsINode* node = aStart;
+ const nsINode* iter = node;
+ while ((iter = iter->GetParentNode())) {
+ node = iter;
+ }
+ return const_cast<nsINode*>(node);
+ };
+
+ // There are four cases of interest here. nsINodes that are really:
+ // 1. Document nodes - Are always in the document.
+ // 2.a nsIContent nodes not in a shadow tree - Are either in the document,
+ // or mSubtreeRoot is updated in BindToTree/UnbindFromTree.
+ // 2.b nsIContent nodes in a shadow tree - Are never in the document,
+ // ignore mSubtreeRoot and return the containing shadow root.
+ // 4. Attr nodes - Are never in the document, and mSubtreeRoot
+ // is always 'this' (as set in nsINode's ctor).
+ nsINode* node;
+ if (IsInUncomposedDoc()) {
+ node = OwnerDocAsNode();
+ } else if (IsContent()) {
+ ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
+ node = containingShadow ? containingShadow : mSubtreeRoot;
+ if (!node) {
+ NS_WARNING("Using SubtreeRoot() on unlinked element?");
+ node = RootOfNode(this);
+ }
+ } else {
+ node = mSubtreeRoot;
+ }
+ MOZ_ASSERT(node, "Should always have a node here!");
+#ifdef DEBUG
+ {
+ const nsINode* slowNode = RootOfNode(this);
+ MOZ_ASSERT(slowNode == node, "These should always be in sync!");
+ }
+#endif
+ return node;
+}
+
+static nsIContent* GetRootForContentSubtree(nsIContent* aContent) {
+ NS_ENSURE_TRUE(aContent, nullptr);
+
+ // Special case for ShadowRoot because the ShadowRoot itself is
+ // the root. This is necessary to prevent selection from crossing
+ // the ShadowRoot boundary.
+ //
+ // FIXME(emilio): The NAC check should probably be done before this? We can
+ // have NAC inside shadow DOM.
+ if (ShadowRoot* containingShadow = aContent->GetContainingShadow()) {
+ return containingShadow;
+ }
+ if (nsIContent* nativeAnonRoot =
+ aContent->GetClosestNativeAnonymousSubtreeRoot()) {
+ return nativeAnonRoot;
+ }
+ if (Document* doc = aContent->GetUncomposedDoc()) {
+ return doc->GetRootElement();
+ }
+ return nsIContent::FromNode(aContent->SubtreeRoot());
+}
+
+nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
+ NS_ENSURE_TRUE(aPresShell, nullptr);
+
+ if (IsDocument()) return AsDocument()->GetRootElement();
+ if (!IsContent()) return nullptr;
+
+ if (GetComposedDoc() != aPresShell->GetDocument()) {
+ return nullptr;
+ }
+
+ if (AsContent()->HasIndependentSelection() || IsInNativeAnonymousSubtree()) {
+ // This node should be an inclusive descendant of input/textarea editor.
+ // In that case, the anonymous <div> for TextEditor should be always the
+ // selection root.
+ // FIXME: If Selection for the document is collapsed in <input> or
+ // <textarea>, returning anonymous <div> may make the callers confused.
+ // Perhaps, we should do this only when this is in the native anonymous
+ // subtree unless the callers explicitly want to retrieve the anonymous
+ // <div> from a text control element.
+ if (Element* anonymousDivElement = GetAnonymousRootElementOfTextEditor()) {
+ return anonymousDivElement;
+ }
+ }
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ if (presContext) {
+ HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(presContext);
+ if (htmlEditor) {
+ // This node is in HTML editor.
+ if (!IsInComposedDoc() || IsInDesignMode() ||
+ !HasFlag(NODE_IS_EDITABLE)) {
+ nsIContent* editorRoot = htmlEditor->GetRoot();
+ NS_ENSURE_TRUE(editorRoot, nullptr);
+ return nsContentUtils::IsInSameAnonymousTree(this, editorRoot)
+ ? editorRoot
+ : GetRootForContentSubtree(AsContent());
+ }
+ // If the document isn't editable but this is editable, this is in
+ // contenteditable. Use the editing host element for selection root.
+ return static_cast<nsIContent*>(this)->GetEditingHost();
+ }
+ }
+
+ RefPtr<nsFrameSelection> fs = aPresShell->FrameSelection();
+ nsIContent* content = fs->GetLimiter();
+ if (!content) {
+ content = fs->GetAncestorLimiter();
+ if (!content) {
+ Document* doc = aPresShell->GetDocument();
+ NS_ENSURE_TRUE(doc, nullptr);
+ content = doc->GetRootElement();
+ if (!content) return nullptr;
+ }
+ }
+
+ // This node might be in another subtree, if so, we should find this subtree's
+ // root. Otherwise, we can return the content simply.
+ NS_ENSURE_TRUE(content, nullptr);
+ if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
+ content = GetRootForContentSubtree(AsContent());
+ // Fixup for ShadowRoot because the ShadowRoot itself does not have a frame.
+ // Use the host as the root.
+ if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(content)) {
+ content = shadowRoot->GetHost();
+ }
+ }
+
+ return content;
+}
+
+nsINodeList* nsINode::ChildNodes() {
+ nsSlots* slots = Slots();
+ if (!slots->mChildNodes) {
+ slots->mChildNodes = IsAttr() ? new nsAttrChildContentList(this)
+ : new nsParentNodeChildContentList(this);
+ }
+
+ return slots->mChildNodes;
+}
+
+nsIContent* nsINode::GetLastChild() const {
+ return mFirstChild ? mFirstChild->mPreviousOrLastSibling : nullptr;
+}
+
+void nsINode::InvalidateChildNodes() {
+ MOZ_ASSERT(!IsAttr());
+
+ nsSlots* slots = GetExistingSlots();
+ if (!slots || !slots->mChildNodes) {
+ return;
+ }
+
+ auto childNodes =
+ static_cast<nsParentNodeChildContentList*>(slots->mChildNodes.get());
+ childNodes->InvalidateCache();
+}
+
+void nsINode::GetTextContentInternal(nsAString& aTextContent,
+ OOMReporter& aError) {
+ SetDOMStringToNull(aTextContent);
+}
+
+DocumentOrShadowRoot* nsINode::GetContainingDocumentOrShadowRoot() const {
+ if (IsInUncomposedDoc()) {
+ return OwnerDoc();
+ }
+
+ if (IsInShadowTree()) {
+ return AsContent()->GetContainingShadow();
+ }
+
+ return nullptr;
+}
+
+DocumentOrShadowRoot* nsINode::GetUncomposedDocOrConnectedShadowRoot() const {
+ if (IsInUncomposedDoc()) {
+ return OwnerDoc();
+ }
+
+ if (IsInComposedDoc() && IsInShadowTree()) {
+ return AsContent()->GetContainingShadow();
+ }
+
+ return nullptr;
+}
+
+mozilla::SafeDoublyLinkedList<nsIMutationObserver>*
+nsINode::GetMutationObservers() {
+ return HasSlots() ? &GetExistingSlots()->mMutationObservers : nullptr;
+}
+
+void nsINode::LastRelease() {
+ nsINode::nsSlots* slots = GetExistingSlots();
+ if (slots) {
+ if (!slots->mMutationObservers.isEmpty()) {
+ for (auto iter = slots->mMutationObservers.begin();
+ iter != slots->mMutationObservers.end(); ++iter) {
+ iter->NodeWillBeDestroyed(this);
+ }
+ }
+
+ if (IsContent()) {
+ nsIContent* content = AsContent();
+ if (HTMLSlotElement* slot = content->GetManualSlotAssignment()) {
+ content->SetManualSlotAssignment(nullptr);
+ slot->RemoveManuallyAssignedNode(*content);
+ }
+ }
+
+ if (Element* element = Element::FromNode(this)) {
+ if (CustomElementData* data = element->GetCustomElementData()) {
+ data->Unlink();
+ }
+ }
+
+ delete slots;
+ mSlots = nullptr;
+ }
+
+ // Kill properties first since that may run external code, so we want to
+ // be in as complete state as possible at that time.
+ if (IsDocument()) {
+ // Delete all properties before tearing down the document. Some of the
+ // properties are bound to nsINode objects and the destructor functions of
+ // the properties may want to use the owner document of the nsINode.
+ AsDocument()->RemoveAllProperties();
+ } else {
+ if (HasProperties()) {
+ // Strong reference to the document so that deleting properties can't
+ // delete the document.
+ nsCOMPtr<Document> document = OwnerDoc();
+ document->RemoveAllPropertiesFor(this);
+ }
+
+ if (HasFlag(ADDED_TO_FORM)) {
+ if (nsGenericHTMLFormControlElement* formControl =
+ nsGenericHTMLFormControlElement::FromNode(this)) {
+ // Tell the form (if any) this node is going away. Don't
+ // notify, since we're being destroyed in any case.
+ formControl->ClearForm(true, true);
+ } else if (HTMLImageElement* imageElem =
+ HTMLImageElement::FromNode(this)) {
+ imageElem->ClearForm(true);
+ }
+ }
+ }
+ UnsetFlags(NODE_HAS_PROPERTIES);
+
+ if (NodeType() != nsINode::DOCUMENT_NODE &&
+ HasFlag(NODE_HAS_LISTENERMANAGER)) {
+#ifdef DEBUG
+ if (nsContentUtils::IsInitialized()) {
+ EventListenerManager* manager =
+ nsContentUtils::GetExistingListenerManagerForNode(this);
+ if (!manager) {
+ NS_ERROR(
+ "Huh, our bit says we have a listener manager list, "
+ "but there's nothing in the hash!?!!");
+ }
+ }
+#endif
+
+ nsContentUtils::RemoveListenerManager(this);
+ UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ ReleaseWrapper(this);
+
+ FragmentOrElement::RemoveBlackMarkedNode(this);
+}
+
+std::ostream& operator<<(std::ostream& aStream, const nsINode& aNode) {
+ nsAutoString elemDesc;
+ const nsINode* curr = &aNode;
+ while (curr) {
+ nsString id;
+ if (curr->IsElement()) {
+ curr->AsElement()->GetId(id);
+ }
+
+ if (!elemDesc.IsEmpty()) {
+ elemDesc = elemDesc + u"."_ns;
+ }
+
+ if (!curr->LocalName().IsEmpty()) {
+ elemDesc.Append(curr->LocalName());
+ } else {
+ elemDesc.Append(curr->NodeName());
+ }
+
+ if (!id.IsEmpty()) {
+ elemDesc = elemDesc + u"['"_ns + id + u"']"_ns;
+ }
+
+ if (curr->IsElement() &&
+ curr->AsElement()->HasAttr(nsGkAtoms::contenteditable)) {
+ nsAutoString val;
+ curr->AsElement()->GetAttr(nsGkAtoms::contenteditable, val);
+ elemDesc = elemDesc + u"[contenteditable=\""_ns + val + u"\"]"_ns;
+ }
+ if (curr->IsDocument() && curr->IsInDesignMode()) {
+ elemDesc.Append(u"[designMode=\"on\"]"_ns);
+ }
+
+ curr = curr->GetParentNode();
+ }
+
+ NS_ConvertUTF16toUTF8 str(elemDesc);
+ return aStream << str.get();
+}
+
+nsIContent* nsINode::DoGetShadowHost() const {
+ MOZ_ASSERT(IsShadowRoot());
+ return static_cast<const ShadowRoot*>(this)->GetHost();
+}
+
+ShadowRoot* nsINode::GetContainingShadow() const {
+ if (!IsInShadowTree()) {
+ return nullptr;
+ }
+ return AsContent()->GetContainingShadow();
+}
+
+nsIContent* nsINode::GetContainingShadowHost() const {
+ if (ShadowRoot* shadow = GetContainingShadow()) {
+ return shadow->GetHost();
+ }
+ return nullptr;
+}
+
+SVGUseElement* nsINode::DoGetContainingSVGUseShadowHost() const {
+ MOZ_ASSERT(IsInShadowTree());
+ return SVGUseElement::FromNodeOrNull(GetContainingShadowHost());
+}
+
+void nsINode::GetNodeValueInternal(nsAString& aNodeValue) {
+ SetDOMStringToNull(aNodeValue);
+}
+
+static const char* NodeTypeAsString(nsINode* aNode) {
+ static const char* NodeTypeStrings[] = {
+ "", // No nodes of type 0
+ "an Element",
+ "an Attribute",
+ "a Text",
+ "a CDATASection",
+ "an EntityReference",
+ "an Entity",
+ "a ProcessingInstruction",
+ "a Comment",
+ "a Document",
+ "a DocumentType",
+ "a DocumentFragment",
+ "a Notation",
+ };
+ static_assert(ArrayLength(NodeTypeStrings) == nsINode::MAX_NODE_TYPE + 1,
+ "Max node type out of range for our array");
+
+ uint16_t nodeType = aNode->NodeType();
+ MOZ_RELEASE_ASSERT(nodeType < ArrayLength(NodeTypeStrings),
+ "Uknown out-of-range node type");
+ return NodeTypeStrings[nodeType];
+}
+
+nsINode* nsINode::RemoveChild(nsINode& aOldChild, ErrorResult& aError) {
+ if (!aOldChild.IsContent()) {
+ // aOldChild can't be one of our children.
+ aError.ThrowNotFoundError(
+ "The node to be removed is not a child of this node");
+ return nullptr;
+ }
+
+ if (aOldChild.GetParentNode() == this) {
+ nsContentUtils::MaybeFireNodeRemoved(&aOldChild, this);
+ }
+
+ // Check again, we may not be the child's parent anymore.
+ // Can be triggered by dom/base/crashtests/293388-1.html
+ if (aOldChild.IsRootOfNativeAnonymousSubtree() ||
+ aOldChild.GetParentNode() != this) {
+ // aOldChild isn't one of our children.
+ aError.ThrowNotFoundError(
+ "The node to be removed is not a child of this node");
+ return nullptr;
+ }
+
+ RemoveChildNode(aOldChild.AsContent(), true);
+ return &aOldChild;
+}
+
+void nsINode::Normalize() {
+ // First collect list of nodes to be removed
+ AutoTArray<nsCOMPtr<nsIContent>, 50> nodes;
+
+ bool canMerge = false;
+ for (nsIContent* node = this->GetFirstChild(); node;
+ node = node->GetNextNode(this)) {
+ if (node->NodeType() != TEXT_NODE) {
+ canMerge = false;
+ continue;
+ }
+
+ if (canMerge || node->TextLength() == 0) {
+ // No need to touch canMerge. That way we can merge across empty
+ // textnodes if and only if the node before is a textnode
+ nodes.AppendElement(node);
+ } else {
+ canMerge = true;
+ }
+
+ // If there's no following sibling, then we need to ensure that we don't
+ // collect following siblings of our (grand)parent as to-be-removed
+ canMerge = canMerge && !!node->GetNextSibling();
+ }
+
+ if (nodes.IsEmpty()) {
+ return;
+ }
+
+ // We're relying on mozAutoSubtreeModified to keep the doc alive here.
+ RefPtr<Document> doc = OwnerDoc();
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ // Fire all DOMNodeRemoved events. Optimize the common case of there being
+ // no listeners
+ bool hasRemoveListeners = nsContentUtils::HasMutationListeners(
+ doc, NS_EVENT_BITS_MUTATION_NODEREMOVED);
+ if (hasRemoveListeners) {
+ for (nsCOMPtr<nsIContent>& node : nodes) {
+ // Node may have already been removed.
+ if (nsCOMPtr<nsINode> parentNode = node->GetParentNode()) {
+ // TODO: Bug 1622253
+ nsContentUtils::MaybeFireNodeRemoved(MOZ_KnownLive(node), parentNode);
+ }
+ }
+ }
+
+ mozAutoDocUpdate batch(doc, true);
+
+ // Merge and remove all nodes
+ nsAutoString tmpStr;
+ for (uint32_t i = 0; i < nodes.Length(); ++i) {
+ nsIContent* node = nodes[i];
+ // Merge with previous node unless empty
+ const nsTextFragment* text = node->GetText();
+ if (text->GetLength()) {
+ nsIContent* target = node->GetPreviousSibling();
+ NS_ASSERTION(
+ (target && target->NodeType() == TEXT_NODE) || hasRemoveListeners,
+ "Should always have a previous text sibling unless "
+ "mutation events messed us up");
+ if (!hasRemoveListeners || (target && target->NodeType() == TEXT_NODE)) {
+ nsTextNode* t = static_cast<nsTextNode*>(target);
+ if (text->Is2b()) {
+ t->AppendTextForNormalize(text->Get2b(), text->GetLength(), true,
+ node);
+ } else {
+ tmpStr.Truncate();
+ text->AppendTo(tmpStr);
+ t->AppendTextForNormalize(tmpStr.get(), tmpStr.Length(), true, node);
+ }
+ }
+ }
+
+ // Remove node
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+ NS_ASSERTION(parent || hasRemoveListeners,
+ "Should always have a parent unless "
+ "mutation events messed us up");
+ if (parent) {
+ parent->RemoveChildNode(node, true);
+ }
+ }
+}
+
+nsresult nsINode::GetBaseURI(nsAString& aURI) const {
+ nsIURI* baseURI = GetBaseURI();
+
+ nsAutoCString spec;
+ if (baseURI) {
+ nsresult rv = baseURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CopyUTF8toUTF16(spec, aURI);
+ return NS_OK;
+}
+
+void nsINode::GetBaseURIFromJS(nsAString& aURI, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ nsIURI* baseURI = GetBaseURI(aCallerType == CallerType::System);
+ nsAutoCString spec;
+ if (baseURI) {
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ CopyUTF8toUTF16(spec, aURI);
+}
+
+nsIURI* nsINode::GetBaseURIObject() const { return GetBaseURI(true); }
+
+void nsINode::LookupPrefix(const nsAString& aNamespaceURI, nsAString& aPrefix) {
+ if (Element* nsElement = GetNameSpaceElement()) {
+ // XXX Waiting for DOM spec to list error codes.
+
+ // Trace up the content parent chain looking for the namespace
+ // declaration that defines the aNamespaceURI namespace. Once found,
+ // return the prefix (i.e. the attribute localName).
+ for (Element* element : nsElement->InclusiveAncestorsOfType<Element>()) {
+ uint32_t attrCount = element->GetAttrCount();
+
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const nsAttrName* name = element->GetAttrNameAt(i);
+
+ if (name->NamespaceEquals(kNameSpaceID_XMLNS) &&
+ element->AttrValueIs(kNameSpaceID_XMLNS, name->LocalName(),
+ aNamespaceURI, eCaseMatters)) {
+ // If the localName is "xmlns", the prefix we output should be
+ // null.
+ nsAtom* localName = name->LocalName();
+
+ if (localName != nsGkAtoms::xmlns) {
+ localName->ToString(aPrefix);
+ } else {
+ SetDOMStringToNull(aPrefix);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ SetDOMStringToNull(aPrefix);
+}
+
+uint16_t nsINode::CompareDocumentPosition(nsINode& aOtherNode,
+ Maybe<uint32_t>* aThisIndex,
+ Maybe<uint32_t>* aOtherIndex) const {
+ if (this == &aOtherNode) {
+ return 0;
+ }
+ if (GetPreviousSibling() == &aOtherNode) {
+ MOZ_ASSERT(GetParentNode() == aOtherNode.GetParentNode());
+ return Node_Binding::DOCUMENT_POSITION_PRECEDING;
+ }
+ if (GetNextSibling() == &aOtherNode) {
+ MOZ_ASSERT(GetParentNode() == aOtherNode.GetParentNode());
+ return Node_Binding::DOCUMENT_POSITION_FOLLOWING;
+ }
+
+ AutoTArray<const nsINode*, 32> parents1, parents2;
+
+ const nsINode* node1 = &aOtherNode;
+ const nsINode* node2 = this;
+
+ // Check if either node is an attribute
+ const Attr* attr1 = Attr::FromNode(node1);
+ if (attr1) {
+ const Element* elem = attr1->GetElement();
+ // If there is an owner element add the attribute
+ // to the chain and walk up to the element
+ if (elem) {
+ node1 = elem;
+ parents1.AppendElement(attr1);
+ }
+ }
+ if (auto* attr2 = Attr::FromNode(node2)) {
+ const Element* elem = attr2->GetElement();
+ if (elem == node1 && attr1) {
+ // Both nodes are attributes on the same element.
+ // Compare position between the attributes.
+
+ uint32_t i;
+ const nsAttrName* attrName;
+ for (i = 0; (attrName = elem->GetAttrNameAt(i)); ++i) {
+ if (attrName->Equals(attr1->NodeInfo())) {
+ NS_ASSERTION(!attrName->Equals(attr2->NodeInfo()),
+ "Different attrs at same position");
+ return Node_Binding::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
+ Node_Binding::DOCUMENT_POSITION_PRECEDING;
+ }
+ if (attrName->Equals(attr2->NodeInfo())) {
+ return Node_Binding::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
+ Node_Binding::DOCUMENT_POSITION_FOLLOWING;
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("neither attribute in the element");
+ return Node_Binding::DOCUMENT_POSITION_DISCONNECTED;
+ }
+
+ if (elem) {
+ node2 = elem;
+ parents2.AppendElement(attr2);
+ }
+ }
+
+ // We now know that both nodes are either nsIContents or Documents.
+ // If either node started out as an attribute, that attribute will have
+ // the same relative position as its ownerElement, except if the
+ // ownerElement ends up being the container for the other node
+
+ // Build the chain of parents
+ do {
+ parents1.AppendElement(node1);
+ node1 = node1->GetParentNode();
+ } while (node1);
+ do {
+ parents2.AppendElement(node2);
+ node2 = node2->GetParentNode();
+ } while (node2);
+
+ // Check if the nodes are disconnected.
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ const nsINode* top1 = parents1.ElementAt(--pos1);
+ const nsINode* top2 = parents2.ElementAt(--pos2);
+ if (top1 != top2) {
+ return top1 < top2
+ ? (Node_Binding::DOCUMENT_POSITION_PRECEDING |
+ Node_Binding::DOCUMENT_POSITION_DISCONNECTED |
+ Node_Binding::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC)
+ : (Node_Binding::DOCUMENT_POSITION_FOLLOWING |
+ Node_Binding::DOCUMENT_POSITION_DISCONNECTED |
+ Node_Binding::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC);
+ }
+
+ // Find where the parent chain differs and check indices in the parent.
+ const nsINode* parent = top1;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ const nsINode* child1 = parents1.ElementAt(--pos1);
+ const nsINode* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ // child1 or child2 can be an attribute here. This will work fine since
+ // ComputeIndexOf will return Nothing for the attribute making the
+ // attribute be considered before any child.
+ Maybe<uint32_t> child1Index;
+ bool cachedChild1Index = false;
+ if (&aOtherNode == child1 && aOtherIndex) {
+ cachedChild1Index = true;
+ child1Index = aOtherIndex->isSome() ? *aOtherIndex
+ : parent->ComputeIndexOf(child1);
+ } else {
+ child1Index = parent->ComputeIndexOf(child1);
+ }
+
+ Maybe<uint32_t> child2Index;
+ bool cachedChild2Index = false;
+ if (this == child2 && aThisIndex) {
+ cachedChild2Index = true;
+ child2Index =
+ aThisIndex->isSome() ? *aThisIndex : parent->ComputeIndexOf(child2);
+ } else {
+ child2Index = parent->ComputeIndexOf(child2);
+ }
+
+ uint16_t retVal = child1Index < child2Index
+ ? Node_Binding::DOCUMENT_POSITION_PRECEDING
+ : Node_Binding::DOCUMENT_POSITION_FOLLOWING;
+
+ if (cachedChild1Index) {
+ *aOtherIndex = child1Index;
+ }
+ if (cachedChild2Index) {
+ *aThisIndex = child2Index;
+ }
+
+ return retVal;
+ }
+ parent = child1;
+ }
+
+ // We hit the end of one of the parent chains without finding a difference
+ // between the chains. That must mean that one node is an ancestor of the
+ // other. The one with the shortest chain must be the ancestor.
+ return pos1 < pos2 ? (Node_Binding::DOCUMENT_POSITION_PRECEDING |
+ Node_Binding::DOCUMENT_POSITION_CONTAINS)
+ : (Node_Binding::DOCUMENT_POSITION_FOLLOWING |
+ Node_Binding::DOCUMENT_POSITION_CONTAINED_BY);
+}
+
+bool nsINode::IsSameNode(nsINode* other) { return other == this; }
+
+bool nsINode::IsEqualNode(nsINode* aOther) {
+ if (!aOther) {
+ return false;
+ }
+
+ // Might as well do a quick check to avoid walking our kids if we're
+ // obviously the same.
+ if (aOther == this) {
+ return true;
+ }
+
+ nsAutoString string1, string2;
+
+ nsINode* node1 = this;
+ nsINode* node2 = aOther;
+ do {
+ uint16_t nodeType = node1->NodeType();
+ if (nodeType != node2->NodeType()) {
+ return false;
+ }
+
+ mozilla::dom::NodeInfo* nodeInfo1 = node1->mNodeInfo;
+ mozilla::dom::NodeInfo* nodeInfo2 = node2->mNodeInfo;
+ if (!nodeInfo1->Equals(nodeInfo2) ||
+ nodeInfo1->GetExtraName() != nodeInfo2->GetExtraName()) {
+ return false;
+ }
+
+ switch (nodeType) {
+ case ELEMENT_NODE: {
+ // Both are elements (we checked that their nodeinfos are equal). Do the
+ // check on attributes.
+ Element* element1 = node1->AsElement();
+ Element* element2 = node2->AsElement();
+ uint32_t attrCount = element1->GetAttrCount();
+ if (attrCount != element2->GetAttrCount()) {
+ return false;
+ }
+
+ // Iterate over attributes.
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const nsAttrName* attrName = element1->GetAttrNameAt(i);
+#ifdef DEBUG
+ bool hasAttr =
+#endif
+ element1->GetAttr(attrName->NamespaceID(), attrName->LocalName(),
+ string1);
+ NS_ASSERTION(hasAttr, "Why don't we have an attr?");
+
+ if (!element2->AttrValueIs(attrName->NamespaceID(),
+ attrName->LocalName(), string1,
+ eCaseMatters)) {
+ return false;
+ }
+ }
+ break;
+ }
+ case TEXT_NODE:
+ case COMMENT_NODE:
+ case CDATA_SECTION_NODE:
+ case PROCESSING_INSTRUCTION_NODE: {
+ MOZ_ASSERT(node1->IsCharacterData());
+ MOZ_ASSERT(node2->IsCharacterData());
+ auto* data1 = static_cast<CharacterData*>(node1);
+ auto* data2 = static_cast<CharacterData*>(node2);
+
+ if (!data1->TextEquals(data2)) {
+ return false;
+ }
+
+ break;
+ }
+ case DOCUMENT_NODE:
+ case DOCUMENT_FRAGMENT_NODE:
+ break;
+ case ATTRIBUTE_NODE: {
+ NS_ASSERTION(node1 == this && node2 == aOther,
+ "Did we come upon an attribute node while walking a "
+ "subtree?");
+ node1->GetNodeValue(string1);
+ node2->GetNodeValue(string2);
+
+ // Returning here as to not bother walking subtree. And there is no
+ // risk that we're half way through walking some other subtree since
+ // attribute nodes doesn't appear in subtrees.
+ return string1.Equals(string2);
+ }
+ case DOCUMENT_TYPE_NODE: {
+ DocumentType* docType1 = static_cast<DocumentType*>(node1);
+ DocumentType* docType2 = static_cast<DocumentType*>(node2);
+
+ // Public ID
+ docType1->GetPublicId(string1);
+ docType2->GetPublicId(string2);
+ if (!string1.Equals(string2)) {
+ return false;
+ }
+
+ // System ID
+ docType1->GetSystemId(string1);
+ docType2->GetSystemId(string2);
+ if (!string1.Equals(string2)) {
+ return false;
+ }
+
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Unknown node type");
+ }
+
+ nsINode* nextNode = node1->GetFirstChild();
+ if (nextNode) {
+ node1 = nextNode;
+ node2 = node2->GetFirstChild();
+ } else {
+ if (node2->GetFirstChild()) {
+ // node2 has a firstChild, but node1 doesn't
+ return false;
+ }
+
+ // Find next sibling, possibly walking parent chain.
+ while (1) {
+ if (node1 == this) {
+ NS_ASSERTION(node2 == aOther,
+ "Should have reached the start node "
+ "for both trees at the same time");
+ return true;
+ }
+
+ nextNode = node1->GetNextSibling();
+ if (nextNode) {
+ node1 = nextNode;
+ node2 = node2->GetNextSibling();
+ break;
+ }
+
+ if (node2->GetNextSibling()) {
+ // node2 has a nextSibling, but node1 doesn't
+ return false;
+ }
+
+ node1 = node1->GetParentNode();
+ node2 = node2->GetParentNode();
+ NS_ASSERTION(node1 && node2, "no parent while walking subtree");
+ }
+ }
+ } while (node2);
+
+ return false;
+}
+
+void nsINode::LookupNamespaceURI(const nsAString& aNamespacePrefix,
+ nsAString& aNamespaceURI) {
+ Element* element = GetNameSpaceElement();
+ if (!element || NS_FAILED(element->LookupNamespaceURIInternal(
+ aNamespacePrefix, aNamespaceURI))) {
+ SetDOMStringToNull(aNamespaceURI);
+ }
+}
+
+mozilla::Maybe<mozilla::dom::EventCallbackDebuggerNotificationType>
+nsINode::GetDebuggerNotificationType() const {
+ return mozilla::Some(
+ mozilla::dom::EventCallbackDebuggerNotificationType::Node);
+}
+
+bool nsINode::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
+ return !nsContentUtils::IsChromeDoc(OwnerDoc());
+}
+
+void nsINode::GetBoxQuads(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad>>& aResult,
+ CallerType aCallerType, mozilla::ErrorResult& aRv) {
+ mozilla::GetBoxQuads(this, aOptions, aResult, aCallerType, aRv);
+}
+
+void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad>>& aResult,
+ mozilla::ErrorResult& aRv) {
+ mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv);
+}
+
+already_AddRefed<DOMQuad> nsINode::ConvertQuadFromNode(
+ DOMQuad& aQuad, const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aCallerType,
+ aRv);
+}
+
+already_AddRefed<DOMQuad> nsINode::ConvertRectFromNode(
+ DOMRectReadOnly& aRect, const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aCallerType,
+ aRv);
+}
+
+already_AddRefed<DOMPoint> nsINode::ConvertPointFromNode(
+ const DOMPointInit& aPoint, const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions,
+ aCallerType, aRv);
+}
+
+bool nsINode::DispatchEvent(Event& aEvent, CallerType aCallerType,
+ ErrorResult& aRv) {
+ // XXX sXBL/XBL2 issue -- do we really want the owner here? What
+ // if that's the XBL document? Would we want its presshell? Or what?
+ nsCOMPtr<Document> document = OwnerDoc();
+
+ // Do nothing if the element does not belong to a document
+ if (!document) {
+ return true;
+ }
+
+ // Obtain a presentation shell
+ RefPtr<nsPresContext> context = document->GetPresContext();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(this, nullptr, &aEvent,
+ context, &status);
+ bool retval = !aEvent.DefaultPrevented(aCallerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ return retval;
+}
+
+nsresult nsINode::PostHandleEvent(EventChainPostVisitor& /*aVisitor*/) {
+ return NS_OK;
+}
+
+EventListenerManager* nsINode::GetOrCreateListenerManager() {
+ return nsContentUtils::GetListenerManagerForNode(this);
+}
+
+EventListenerManager* nsINode::GetExistingListenerManager() const {
+ return nsContentUtils::GetExistingListenerManagerForNode(this);
+}
+
+nsPIDOMWindowOuter* nsINode::GetOwnerGlobalForBindingsInternal() {
+ bool dummy;
+ // FIXME(bz): This cast is a bit bogus. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1515709
+ auto* window = static_cast<nsGlobalWindowInner*>(
+ OwnerDoc()->GetScriptHandlingObject(dummy));
+ return window ? nsPIDOMWindowOuter::GetFromCurrentInner(window) : nullptr;
+}
+
+nsIGlobalObject* nsINode::GetOwnerGlobal() const {
+ bool dummy;
+ return OwnerDoc()->GetScriptHandlingObject(dummy);
+}
+
+bool nsINode::UnoptimizableCCNode() const {
+ return IsInNativeAnonymousSubtree() || IsAttr();
+}
+
+/* static */
+bool nsINode::Traverse(nsINode* tmp, nsCycleCollectionTraversalCallback& cb) {
+ if (MOZ_LIKELY(!cb.WantAllTraces())) {
+ Document* currentDoc = tmp->GetComposedDoc();
+ if (currentDoc && nsCCUncollectableMarker::InGeneration(
+ currentDoc->GetMarkedCCGeneration())) {
+ return false;
+ }
+
+ if (nsCCUncollectableMarker::sGeneration) {
+ // If we're black no need to traverse.
+ if (tmp->HasKnownLiveWrapper() || tmp->InCCBlackTree()) {
+ return false;
+ }
+
+ if (!tmp->UnoptimizableCCNode()) {
+ // If we're in a black document, return early.
+ if ((currentDoc && currentDoc->HasKnownLiveWrapper())) {
+ return false;
+ }
+ // If we're not in anonymous content and we have a black parent,
+ // return early.
+ nsIContent* parent = tmp->GetParent();
+ if (parent && !parent->UnoptimizableCCNode() &&
+ parent->HasKnownLiveWrapper()) {
+ MOZ_ASSERT(parent->ComputeIndexOf(tmp).isSome(),
+ "Parent doesn't own us?");
+ return false;
+ }
+ }
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfo)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstChild)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextSibling)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetParent())
+
+ nsSlots* slots = tmp->GetExistingSlots();
+ if (slots) {
+ slots->Traverse(cb);
+ }
+
+ if (tmp->HasProperties()) {
+ nsCOMArray<nsISupports>* objects = static_cast<nsCOMArray<nsISupports>*>(
+ tmp->GetProperty(nsGkAtoms::keepobjectsalive));
+ if (objects) {
+ for (int32_t i = 0; i < objects->Count(); ++i) {
+ cb.NoteXPCOMChild(objects->ObjectAt(i));
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ AccessibleNode* anode = static_cast<AccessibleNode*>(
+ tmp->GetProperty(nsGkAtoms::accessiblenode));
+ if (anode) {
+ cb.NoteXPCOMChild(anode);
+ }
+#endif
+ }
+
+ if (tmp->NodeType() != DOCUMENT_NODE &&
+ tmp->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ nsContentUtils::TraverseListenerManager(tmp, cb);
+ }
+
+ return true;
+}
+
+/* static */
+void nsINode::Unlink(nsINode* tmp) {
+ tmp->ReleaseWrapper(tmp);
+
+ if (nsSlots* slots = tmp->GetExistingSlots()) {
+ slots->Unlink(*tmp);
+ }
+
+ if (tmp->NodeType() != DOCUMENT_NODE &&
+ tmp->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ nsContentUtils::RemoveListenerManager(tmp);
+ tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ if (tmp->HasProperties()) {
+ tmp->RemoveProperty(nsGkAtoms::keepobjectsalive);
+ tmp->RemoveProperty(nsGkAtoms::accessiblenode);
+ }
+}
+
+static void AdoptNodeIntoOwnerDoc(nsINode* aParent, nsINode* aNode,
+ ErrorResult& aError) {
+ NS_ASSERTION(!aNode->GetParentNode(),
+ "Should have removed from parent already");
+
+ Document* doc = aParent->OwnerDoc();
+
+ DebugOnly<nsINode*> adoptedNode = doc->AdoptNode(*aNode, aError);
+
+#ifdef DEBUG
+ if (!aError.Failed()) {
+ MOZ_ASSERT(aParent->OwnerDoc() == doc, "ownerDoc chainged while adopting");
+ MOZ_ASSERT(adoptedNode == aNode, "Uh, adopt node changed nodes?");
+ MOZ_ASSERT(aParent->OwnerDoc() == aNode->OwnerDoc(),
+ "ownerDocument changed again after adopting!");
+ }
+#endif // DEBUG
+}
+
+static nsresult UpdateGlobalsInSubtree(nsIContent* aRoot) {
+ MOZ_ASSERT(ShouldUseNACScope(aRoot));
+ // Start off with no global so we don't fire any error events on failure.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JSContext* cx = jsapi.cx();
+
+ ErrorResult rv;
+ JS::Rooted<JSObject*> reflector(cx);
+ for (nsIContent* cur = aRoot; cur; cur = cur->GetNextNode(aRoot)) {
+ if ((reflector = cur->GetWrapper())) {
+ JSAutoRealm ar(cx, reflector);
+ UpdateReflectorGlobal(cx, reflector, rv);
+ rv.WouldReportJSException();
+ if (rv.Failed()) {
+ // We _could_ consider BlastSubtreeToPieces here, but it's not really
+ // needed. Having some nodes in here accessible to content while others
+ // are not is probably OK. We just need to fail out of the actual
+ // insertion, so they're not in the DOM. Returning a failure here will
+ // do that.
+ return rv.StealNSResult();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsINode::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
+ bool aNotify, ErrorResult& aRv) {
+ if (!IsContainerNode()) {
+ aRv.ThrowHierarchyRequestError(
+ "Parent is not a Document, DocumentFragment, or Element node.");
+ return;
+ }
+
+ MOZ_ASSERT(!aKid->GetParentNode(), "Inserting node that already has parent");
+ MOZ_ASSERT(!IsAttr());
+
+ // The id-handling code, and in the future possibly other code, need to
+ // react to unexpected attribute changes.
+ nsMutationGuard::DidMutate();
+
+ // Do this before checking the child-count since this could cause mutations
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), aNotify);
+
+ if (OwnerDoc() != aKid->OwnerDoc()) {
+ AdoptNodeIntoOwnerDoc(this, aKid, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (!aBeforeThis) {
+ AppendChildToChildList(aKid);
+ } else {
+ InsertChildToChildList(aKid, aBeforeThis);
+ }
+
+ nsIContent* parent = IsContent() ? AsContent() : nullptr;
+
+ // XXXbz Do we even need this code anymore?
+ bool wasInNACScope = ShouldUseNACScope(aKid);
+ BindContext context(*this);
+ aRv = aKid->BindToTree(context, *this);
+ if (!aRv.Failed() && !wasInNACScope && ShouldUseNACScope(aKid)) {
+ MOZ_ASSERT(ShouldUseNACScope(this),
+ "Why does the kid need to use an the anonymous content scope?");
+ aRv = UpdateGlobalsInSubtree(aKid);
+ }
+ if (aRv.Failed()) {
+ DisconnectChild(aKid);
+ aKid->UnbindFromTree();
+ return;
+ }
+
+ // Invalidate cached array of child nodes
+ InvalidateChildNodes();
+
+ NS_ASSERTION(aKid->GetParentNode() == this,
+ "Did we run script inappropriately?");
+
+ if (aNotify) {
+ // Note that we always want to call ContentInserted when things are added
+ // as kids to documents
+ if (parent && !aBeforeThis) {
+ MutationObservers::NotifyContentAppended(parent, aKid);
+ } else {
+ MutationObservers::NotifyContentInserted(this, aKid);
+ }
+
+ if (nsContentUtils::HasMutationListeners(
+ aKid, NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
+ InternalMutationEvent mutation(true, eLegacyNodeInserted);
+ mutation.mRelatedNode = this;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*aKid, mutation);
+ }
+ }
+}
+
+nsIContent* nsINode::GetPreviousSibling() const {
+ // Do not expose circular linked list
+ if (mPreviousOrLastSibling && !mPreviousOrLastSibling->mNextSibling) {
+ return nullptr;
+ }
+ return mPreviousOrLastSibling;
+}
+
+// CACHE_POINTER_SHIFT indicates how many steps to downshift the |this| pointer.
+// It should be small enough to not cause collisions between adjecent objects,
+// and large enough to make sure that all indexes are used.
+#define CACHE_POINTER_SHIFT 6
+#define CACHE_NUM_SLOTS 128
+#define CACHE_CHILD_LIMIT 10
+
+#define CACHE_GET_INDEX(_parent) \
+ ((NS_PTR_TO_INT32(_parent) >> CACHE_POINTER_SHIFT) & (CACHE_NUM_SLOTS - 1))
+
+struct IndexCacheSlot {
+ const nsINode* mParent;
+ const nsINode* mChild;
+ uint32_t mChildIndex;
+};
+
+static IndexCacheSlot sIndexCache[CACHE_NUM_SLOTS];
+
+static inline void AddChildAndIndexToCache(const nsINode* aParent,
+ const nsINode* aChild,
+ uint32_t aChildIndex) {
+ uint32_t index = CACHE_GET_INDEX(aParent);
+ sIndexCache[index].mParent = aParent;
+ sIndexCache[index].mChild = aChild;
+ sIndexCache[index].mChildIndex = aChildIndex;
+}
+
+static inline void GetChildAndIndexFromCache(const nsINode* aParent,
+ const nsINode** aChild,
+ Maybe<uint32_t>* aChildIndex) {
+ uint32_t index = CACHE_GET_INDEX(aParent);
+ if (sIndexCache[index].mParent == aParent) {
+ *aChild = sIndexCache[index].mChild;
+ *aChildIndex = Some(sIndexCache[index].mChildIndex);
+ } else {
+ *aChild = nullptr;
+ *aChildIndex = Nothing();
+ }
+}
+
+static inline void RemoveFromCache(const nsINode* aParent) {
+ uint32_t index = CACHE_GET_INDEX(aParent);
+ if (sIndexCache[index].mParent == aParent) {
+ sIndexCache[index] = {nullptr, nullptr, UINT32_MAX};
+ }
+}
+
+void nsINode::AppendChildToChildList(nsIContent* aKid) {
+ MOZ_ASSERT(aKid);
+ MOZ_ASSERT(!aKid->mNextSibling);
+
+ RemoveFromCache(this);
+
+ if (mFirstChild) {
+ nsIContent* lastChild = GetLastChild();
+ lastChild->mNextSibling = aKid;
+ aKid->mPreviousOrLastSibling = lastChild;
+ } else {
+ mFirstChild = aKid;
+ }
+
+ // Maintain link to the last child
+ mFirstChild->mPreviousOrLastSibling = aKid;
+ ++mChildCount;
+}
+
+void nsINode::InsertChildToChildList(nsIContent* aKid,
+ nsIContent* aNextSibling) {
+ MOZ_ASSERT(aKid);
+ MOZ_ASSERT(aNextSibling);
+
+ RemoveFromCache(this);
+
+ nsIContent* previousSibling = aNextSibling->mPreviousOrLastSibling;
+ aNextSibling->mPreviousOrLastSibling = aKid;
+ aKid->mPreviousOrLastSibling = previousSibling;
+ aKid->mNextSibling = aNextSibling;
+
+ if (aNextSibling == mFirstChild) {
+ MOZ_ASSERT(!previousSibling->mNextSibling);
+ mFirstChild = aKid;
+ } else {
+ previousSibling->mNextSibling = aKid;
+ }
+
+ ++mChildCount;
+}
+
+void nsINode::DisconnectChild(nsIContent* aKid) {
+ MOZ_ASSERT(aKid);
+ MOZ_ASSERT(GetChildCount() > 0);
+
+ RemoveFromCache(this);
+
+ nsIContent* previousSibling = aKid->GetPreviousSibling();
+ nsCOMPtr<nsIContent> ref = aKid;
+
+ if (aKid->mNextSibling) {
+ aKid->mNextSibling->mPreviousOrLastSibling = aKid->mPreviousOrLastSibling;
+ } else {
+ // aKid is the last child in the list
+ mFirstChild->mPreviousOrLastSibling = aKid->mPreviousOrLastSibling;
+ }
+ aKid->mPreviousOrLastSibling = nullptr;
+
+ if (previousSibling) {
+ previousSibling->mNextSibling = std::move(aKid->mNextSibling);
+ } else {
+ // aKid is the first child in the list
+ mFirstChild = std::move(aKid->mNextSibling);
+ }
+
+ --mChildCount;
+}
+
+nsIContent* nsINode::GetChildAt_Deprecated(uint32_t aIndex) const {
+ if (aIndex >= GetChildCount()) {
+ return nullptr;
+ }
+
+ nsIContent* child = mFirstChild;
+ while (aIndex--) {
+ child = child->GetNextSibling();
+ }
+
+ return child;
+}
+
+int32_t nsINode::ComputeIndexOf_Deprecated(
+ const nsINode* aPossibleChild) const {
+ Maybe<uint32_t> maybeIndex = ComputeIndexOf(aPossibleChild);
+ if (!maybeIndex) {
+ return -1;
+ }
+ MOZ_ASSERT(*maybeIndex <= INT32_MAX,
+ "ComputeIndexOf_Deprecated() returns unsupported index value, use "
+ "ComputeIndex() instead");
+ return static_cast<int32_t>(*maybeIndex);
+}
+
+Maybe<uint32_t> nsINode::ComputeIndexOf(const nsINode* aPossibleChild) const {
+ if (!aPossibleChild) {
+ return Nothing();
+ }
+
+ if (aPossibleChild->GetParentNode() != this) {
+ return Nothing();
+ }
+
+ if (aPossibleChild == GetLastChild()) {
+ MOZ_ASSERT(GetChildCount());
+ return Some(GetChildCount() - 1);
+ }
+
+ if (mChildCount >= CACHE_CHILD_LIMIT) {
+ const nsINode* child;
+ Maybe<uint32_t> maybeChildIndex;
+ GetChildAndIndexFromCache(this, &child, &maybeChildIndex);
+ if (child) {
+ if (child == aPossibleChild) {
+ return maybeChildIndex;
+ }
+
+ uint32_t nextIndex = *maybeChildIndex;
+ uint32_t prevIndex = *maybeChildIndex;
+ nsINode* prev = child->GetPreviousSibling();
+ nsINode* next = child->GetNextSibling();
+ do {
+ if (next) {
+ MOZ_ASSERT(nextIndex < UINT32_MAX);
+ ++nextIndex;
+ if (next == aPossibleChild) {
+ AddChildAndIndexToCache(this, aPossibleChild, nextIndex);
+ return Some(nextIndex);
+ }
+ next = next->GetNextSibling();
+ }
+ if (prev) {
+ MOZ_ASSERT(prevIndex > 0);
+ --prevIndex;
+ if (prev == aPossibleChild) {
+ AddChildAndIndexToCache(this, aPossibleChild, prevIndex);
+ return Some(prevIndex);
+ }
+ prev = prev->GetPreviousSibling();
+ }
+ } while (prev || next);
+ }
+ }
+
+ uint32_t index = 0u;
+ nsINode* current = mFirstChild;
+ while (current) {
+ MOZ_ASSERT(current->GetParentNode() == this);
+ if (current == aPossibleChild) {
+ if (mChildCount >= CACHE_CHILD_LIMIT) {
+ AddChildAndIndexToCache(this, current, index);
+ }
+ return Some(index);
+ }
+ current = current->GetNextSibling();
+ MOZ_ASSERT(index < UINT32_MAX);
+ ++index;
+ }
+
+ return Nothing();
+}
+
+Maybe<uint32_t> nsINode::ComputeIndexInParentNode() const {
+ nsINode* parent = GetParentNode();
+ if (MOZ_UNLIKELY(!parent)) {
+ return Nothing();
+ }
+ return parent->ComputeIndexOf(this);
+}
+
+Maybe<uint32_t> nsINode::ComputeIndexInParentContent() const {
+ nsIContent* parent = GetParent();
+ if (MOZ_UNLIKELY(!parent)) {
+ return Nothing();
+ }
+ return parent->ComputeIndexOf(this);
+}
+
+static already_AddRefed<nsINode> GetNodeFromNodeOrString(
+ const OwningNodeOrString& aNode, Document* aDocument) {
+ if (aNode.IsNode()) {
+ nsCOMPtr<nsINode> node = aNode.GetAsNode();
+ return node.forget();
+ }
+
+ if (aNode.IsString()) {
+ RefPtr<nsTextNode> textNode =
+ aDocument->CreateTextNode(aNode.GetAsString());
+ return textNode.forget();
+ }
+
+ MOZ_CRASH("Impossible type");
+}
+
+/**
+ * Implement the algorithm specified at
+ * https://dom.spec.whatwg.org/#converting-nodes-into-a-node for |prepend()|,
+ * |append()|, |before()|, |after()|, and |replaceWith()| APIs.
+ */
+MOZ_CAN_RUN_SCRIPT static already_AddRefed<nsINode>
+ConvertNodesOrStringsIntoNode(const Sequence<OwningNodeOrString>& aNodes,
+ Document* aDocument, ErrorResult& aRv) {
+ if (aNodes.Length() == 1) {
+ return GetNodeFromNodeOrString(aNodes[0], aDocument);
+ }
+
+ nsCOMPtr<nsINode> fragment = aDocument->CreateDocumentFragment();
+
+ for (const auto& node : aNodes) {
+ nsCOMPtr<nsINode> childNode = GetNodeFromNodeOrString(node, aDocument);
+ fragment->AppendChild(*childNode, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return fragment.forget();
+}
+
+static void InsertNodesIntoHashset(const Sequence<OwningNodeOrString>& aNodes,
+ nsTHashSet<nsINode*>& aHashset) {
+ for (const auto& node : aNodes) {
+ if (node.IsNode()) {
+ aHashset.Insert(node.GetAsNode());
+ }
+ }
+}
+
+static nsINode* FindViablePreviousSibling(
+ const nsINode& aNode, const Sequence<OwningNodeOrString>& aNodes) {
+ nsTHashSet<nsINode*> nodeSet(16);
+ InsertNodesIntoHashset(aNodes, nodeSet);
+
+ nsINode* viablePreviousSibling = nullptr;
+ for (nsINode* sibling = aNode.GetPreviousSibling(); sibling;
+ sibling = sibling->GetPreviousSibling()) {
+ if (!nodeSet.Contains(sibling)) {
+ viablePreviousSibling = sibling;
+ break;
+ }
+ }
+
+ return viablePreviousSibling;
+}
+
+static nsINode* FindViableNextSibling(
+ const nsINode& aNode, const Sequence<OwningNodeOrString>& aNodes) {
+ nsTHashSet<nsINode*> nodeSet(16);
+ InsertNodesIntoHashset(aNodes, nodeSet);
+
+ nsINode* viableNextSibling = nullptr;
+ for (nsINode* sibling = aNode.GetNextSibling(); sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (!nodeSet.Contains(sibling)) {
+ viableNextSibling = sibling;
+ break;
+ }
+ }
+
+ return viableNextSibling;
+}
+
+void nsINode::Before(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> viablePreviousSibling =
+ FindViablePreviousSibling(*this, aNodes);
+
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ viablePreviousSibling = viablePreviousSibling
+ ? viablePreviousSibling->GetNextSibling()
+ : parent->GetFirstChild();
+
+ parent->InsertBefore(*node, viablePreviousSibling, aRv);
+}
+
+void nsINode::After(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> viableNextSibling = FindViableNextSibling(*this, aNodes);
+
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ parent->InsertBefore(*node, viableNextSibling, aRv);
+}
+
+void nsINode::ReplaceWith(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> viableNextSibling = FindViableNextSibling(*this, aNodes);
+
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (parent == GetParentNode()) {
+ parent->ReplaceChild(*node, *this, aRv);
+ } else {
+ parent->InsertBefore(*node, viableNextSibling, aRv);
+ }
+}
+
+void nsINode::Remove() {
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (!parent) {
+ return;
+ }
+
+ parent->RemoveChild(*this, IgnoreErrors());
+}
+
+Element* nsINode::GetFirstElementChild() const {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ return child->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+Element* nsINode::GetLastElementChild() const {
+ for (nsIContent* child = GetLastChild(); child;
+ child = child->GetPreviousSibling()) {
+ if (child->IsElement()) {
+ return child->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+static bool MatchAttribute(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAttrName, void* aData) {
+ MOZ_ASSERT(aElement, "Must have content node to work with!");
+ nsString* attrValue = static_cast<nsString*>(aData);
+ if (aNamespaceID != kNameSpaceID_Unknown &&
+ aNamespaceID != kNameSpaceID_Wildcard) {
+ return attrValue->EqualsLiteral("*")
+ ? aElement->HasAttr(aNamespaceID, aAttrName)
+ : aElement->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
+ eCaseMatters);
+ }
+
+ // Qualified name match. This takes more work.
+ uint32_t count = aElement->GetAttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* name = aElement->GetAttrNameAt(i);
+ bool nameMatch;
+ if (name->IsAtom()) {
+ nameMatch = name->Atom() == aAttrName;
+ } else if (aNamespaceID == kNameSpaceID_Wildcard) {
+ nameMatch = name->NodeInfo()->Equals(aAttrName);
+ } else {
+ nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
+ }
+
+ if (nameMatch) {
+ return attrValue->EqualsLiteral("*") ||
+ aElement->AttrValueIs(name->NamespaceID(), name->LocalName(),
+ *attrValue, eCaseMatters);
+ }
+ }
+
+ return false;
+}
+
+already_AddRefed<nsIHTMLCollection> nsINode::GetElementsByAttribute(
+ const nsAString& aAttribute, const nsAString& aValue) {
+ RefPtr<nsAtom> attrAtom(NS_Atomize(aAttribute));
+ RefPtr<nsContentList> list = new nsContentList(
+ this, MatchAttribute, nsContentUtils::DestroyMatchString,
+ new nsString(aValue), true, attrAtom, kNameSpaceID_Unknown);
+
+ return list.forget();
+}
+
+already_AddRefed<nsIHTMLCollection> nsINode::GetElementsByAttributeNS(
+ const nsAString& aNamespaceURI, const nsAString& aAttribute,
+ const nsAString& aValue, ErrorResult& aRv) {
+ RefPtr<nsAtom> attrAtom(NS_Atomize(aAttribute));
+
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ aNamespaceURI, nameSpaceId);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ RefPtr<nsContentList> list = new nsContentList(
+ this, MatchAttribute, nsContentUtils::DestroyMatchString,
+ new nsString(aValue), true, attrAtom, nameSpaceId);
+ return list.forget();
+}
+
+void nsINode::Prepend(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> refNode = mFirstChild;
+ InsertBefore(*node, refNode, aRv);
+}
+
+void nsINode::Append(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ AppendChild(*node, aRv);
+}
+
+// https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
+void nsINode::ReplaceChildren(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv) {
+ nsCOMPtr<Document> doc = OwnerDoc();
+ nsCOMPtr<nsINode> node = ConvertNodesOrStringsIntoNode(aNodes, doc, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ MOZ_ASSERT(node);
+ return ReplaceChildren(node, aRv);
+}
+
+void nsINode::ReplaceChildren(nsINode* aNode, ErrorResult& aRv) {
+ if (aNode) {
+ EnsurePreInsertionValidity(*aNode, nullptr, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ nsCOMPtr<nsINode> node = aNode;
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
+
+ if (nsContentUtils::HasMutationListeners(
+ OwnerDoc(), NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ FireNodeRemovedForChildren();
+ if (node) {
+ if (node->NodeType() == DOCUMENT_FRAGMENT_NODE) {
+ node->FireNodeRemovedForChildren();
+ } else if (nsCOMPtr<nsINode> parent = node->GetParentNode()) {
+ nsContentUtils::MaybeFireNodeRemoved(node, parent);
+ }
+ }
+ }
+
+ // Needed when used in combination with contenteditable (maybe)
+ mozAutoDocUpdate updateBatch(OwnerDoc(), true);
+
+ nsAutoMutationBatch mb(this, true, true);
+
+ // The code above explicitly dispatched DOMNodeRemoved events if needed.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ // Replace all with node within this.
+ while (mFirstChild) {
+ RemoveChildNode(mFirstChild, true);
+ }
+ mb.RemovalDone();
+
+ if (aNode) {
+ AppendChild(*aNode, aRv);
+ mb.NodesAdded();
+ }
+}
+
+void nsINode::RemoveChildNode(nsIContent* aKid, bool aNotify) {
+ // NOTE: This function must not trigger any calls to
+ // Document::GetRootElement() calls until *after* it has removed aKid from
+ // aChildArray. Any calls before then could potentially restore a stale
+ // value for our cached root element, per note in
+ // Document::RemoveChildNode().
+ MOZ_ASSERT(aKid && aKid->GetParentNode() == this, "Bogus aKid");
+ MOZ_ASSERT(!IsAttr());
+
+ nsMutationGuard::DidMutate();
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), aNotify);
+
+ nsIContent* previousSibling = aKid->GetPreviousSibling();
+
+ // Since aKid is use also after DisconnectChild, ensure it stays alive.
+ nsCOMPtr<nsIContent> kungfuDeathGrip = aKid;
+ DisconnectChild(aKid);
+
+ // Invalidate cached array of child nodes
+ InvalidateChildNodes();
+
+ if (aNotify) {
+ MutationObservers::NotifyContentRemoved(this, aKid, previousSibling);
+ }
+
+ aKid->UnbindFromTree();
+}
+
+// When replacing, aRefChild is the content being replaced; when
+// inserting it's the content before which we're inserting. In the
+// latter case it may be null.
+//
+// If aRv is a failure after this call, the insertion should not happen.
+//
+// This implements the parts of
+// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity and
+// the checks in https://dom.spec.whatwg.org/#concept-node-replace that
+// depend on the child nodes or come after steps that depend on the child nodes
+// (steps 2-6 in both cases).
+static void EnsureAllowedAsChild(nsINode* aNewChild, nsINode* aParent,
+ bool aIsReplace, nsINode* aRefChild,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aNewChild, "Must have new child");
+ MOZ_ASSERT_IF(aIsReplace, aRefChild);
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aParent->IsDocument() || aParent->IsDocumentFragment() ||
+ aParent->IsElement(),
+ "Nodes that are not documents, document fragments or elements "
+ "can't be parents!");
+
+ // Step 2.
+ // A common case is that aNewChild has no kids, in which case
+ // aParent can't be a descendant of aNewChild unless they're
+ // actually equal to each other. Fast-path that case, since aParent
+ // could be pretty deep in the DOM tree.
+ if (aNewChild == aParent ||
+ ((aNewChild->GetFirstChild() ||
+ // HTML template elements and ShadowRoot hosts need
+ // to be checked to ensure that they are not inserted into
+ // the hosted content.
+ aNewChild->NodeInfo()->NameAtom() == nsGkAtoms::_template ||
+ (aNewChild->IsElement() && aNewChild->AsElement()->GetShadowRoot())) &&
+ nsContentUtils::ContentIsHostIncludingDescendantOf(aParent,
+ aNewChild))) {
+ aRv.ThrowHierarchyRequestError(
+ "The new child is an ancestor of the parent");
+ return;
+ }
+
+ // Step 3.
+ if (aRefChild && aRefChild->GetParentNode() != aParent) {
+ if (aIsReplace) {
+ if (aNewChild->GetParentNode() == aParent) {
+ aRv.ThrowNotFoundError(
+ "New child already has this parent and old child does not. Please "
+ "check the order of replaceChild's arguments.");
+ } else {
+ aRv.ThrowNotFoundError(
+ "Child to be replaced is not a child of this node");
+ }
+ } else {
+ aRv.ThrowNotFoundError(
+ "Child to insert before is not a child of this node");
+ }
+ return;
+ }
+
+ // Step 4.
+ if (!aNewChild->IsContent()) {
+ aRv.ThrowHierarchyRequestError(nsPrintfCString(
+ "May not add %s as a child", NodeTypeAsString(aNewChild)));
+ return;
+ }
+
+ // Steps 5 and 6 combined.
+ // The allowed child nodes differ for documents and elements
+ switch (aNewChild->NodeType()) {
+ case nsINode::COMMENT_NODE:
+ case nsINode::PROCESSING_INSTRUCTION_NODE:
+ // OK in both cases
+ return;
+ case nsINode::TEXT_NODE:
+ case nsINode::CDATA_SECTION_NODE:
+ case nsINode::ENTITY_REFERENCE_NODE:
+ // Allowed under Elements and DocumentFragments
+ if (aParent->NodeType() == nsINode::DOCUMENT_NODE) {
+ aRv.ThrowHierarchyRequestError(
+ nsPrintfCString("Cannot insert %s as a child of a Document",
+ NodeTypeAsString(aNewChild)));
+ }
+ return;
+ case nsINode::ELEMENT_NODE: {
+ if (!aParent->IsDocument()) {
+ // Always ok to have elements under other elements or document fragments
+ return;
+ }
+
+ Document* parentDocument = aParent->AsDocument();
+ Element* rootElement = parentDocument->GetRootElement();
+ if (rootElement) {
+ // Already have a documentElement, so this is only OK if we're
+ // replacing it.
+ if (!aIsReplace || rootElement != aRefChild) {
+ aRv.ThrowHierarchyRequestError(
+ "Cannot have more than one Element child of a Document");
+ }
+ return;
+ }
+
+ // We don't have a documentElement yet. Our one remaining constraint is
+ // that the documentElement must come after the doctype.
+ if (!aRefChild) {
+ // Appending is just fine.
+ return;
+ }
+
+ nsIContent* docTypeContent = parentDocument->GetDoctype();
+ if (!docTypeContent) {
+ // It's all good.
+ return;
+ }
+
+ // The docTypeContent is retrived from the child list of the Document
+ // node so that doctypeIndex is never Nothing.
+ const Maybe<uint32_t> doctypeIndex =
+ aParent->ComputeIndexOf(docTypeContent);
+ MOZ_ASSERT(doctypeIndex.isSome());
+ // If aRefChild is an NAC, its index can be Nothing.
+ const Maybe<uint32_t> insertIndex = aParent->ComputeIndexOf(aRefChild);
+
+ // Now we're OK in the following two cases only:
+ // 1) We're replacing something that's not before the doctype
+ // 2) We're inserting before something that comes after the doctype
+ const bool ok = MOZ_LIKELY(insertIndex.isSome()) &&
+ (aIsReplace ? *insertIndex >= *doctypeIndex
+ : *insertIndex > *doctypeIndex);
+ if (!ok) {
+ aRv.ThrowHierarchyRequestError(
+ "Cannot insert a root element before the doctype");
+ }
+ return;
+ }
+ case nsINode::DOCUMENT_TYPE_NODE: {
+ if (!aParent->IsDocument()) {
+ // doctypes only allowed under documents
+ aRv.ThrowHierarchyRequestError(
+ nsPrintfCString("Cannot insert a DocumentType as a child of %s",
+ NodeTypeAsString(aParent)));
+ return;
+ }
+
+ Document* parentDocument = aParent->AsDocument();
+ nsIContent* docTypeContent = parentDocument->GetDoctype();
+ if (docTypeContent) {
+ // Already have a doctype, so this is only OK if we're replacing it
+ if (!aIsReplace || docTypeContent != aRefChild) {
+ aRv.ThrowHierarchyRequestError(
+ "Cannot have more than one DocumentType child of a Document");
+ }
+ return;
+ }
+
+ // We don't have a doctype yet. Our one remaining constraint is
+ // that the doctype must come before the documentElement.
+ Element* rootElement = parentDocument->GetRootElement();
+ if (!rootElement) {
+ // It's all good
+ return;
+ }
+
+ if (!aRefChild) {
+ // Trying to append a doctype, but have a documentElement
+ aRv.ThrowHierarchyRequestError(
+ "Cannot have a DocumentType node after the root element");
+ return;
+ }
+
+ // rootElement is now in the child list of the Document node so that
+ // ComputeIndexOf must success to find it.
+ const Maybe<uint32_t> rootIndex = aParent->ComputeIndexOf(rootElement);
+ MOZ_ASSERT(rootIndex.isSome());
+ const Maybe<uint32_t> insertIndex = aParent->ComputeIndexOf(aRefChild);
+
+ // Now we're OK if and only if insertIndex <= rootIndex. Indeed, either
+ // we end up replacing aRefChild or we end up before it. Either one is
+ // ok as long as aRefChild is not after rootElement.
+ if (MOZ_LIKELY(insertIndex.isSome()) && *insertIndex > *rootIndex) {
+ aRv.ThrowHierarchyRequestError(
+ "Cannot have a DocumentType node after the root element");
+ }
+ return;
+ }
+ case nsINode::DOCUMENT_FRAGMENT_NODE: {
+ // Note that for now we only allow nodes inside document fragments if
+ // they're allowed inside elements. If we ever change this to allow
+ // doctype nodes in document fragments, we'll need to update this code.
+ // Also, there's a version of this code in ReplaceOrInsertBefore. If you
+ // change this code, change that too.
+ if (!aParent->IsDocument()) {
+ // All good here
+ return;
+ }
+
+ bool sawElement = false;
+ for (nsIContent* child = aNewChild->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ if (sawElement) {
+ // Can't put two elements into a document
+ aRv.ThrowHierarchyRequestError(
+ "Cannot have more than one Element child of a Document");
+ return;
+ }
+ sawElement = true;
+ }
+ // If we can put this content at the right place, we might be ok;
+ // if not, we bail out.
+ EnsureAllowedAsChild(child, aParent, aIsReplace, aRefChild, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Everything in the fragment checked out ok, so we can stick it in here
+ return;
+ }
+ default:
+ /*
+ * aNewChild is of invalid type.
+ */
+ break;
+ }
+
+ // XXXbz when can we reach this?
+ aRv.ThrowHierarchyRequestError(nsPrintfCString("Cannot insert %s inside %s",
+ NodeTypeAsString(aNewChild),
+ NodeTypeAsString(aParent)));
+}
+
+// Implements
+// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
+void nsINode::EnsurePreInsertionValidity(nsINode& aNewChild, nsINode* aRefChild,
+ ErrorResult& aError) {
+ EnsurePreInsertionValidity1(aError);
+ if (aError.Failed()) {
+ return;
+ }
+ EnsurePreInsertionValidity2(false, aNewChild, aRefChild, aError);
+}
+
+// Implements the parts of
+// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity and
+// the checks in https://dom.spec.whatwg.org/#concept-node-replace that can be
+// evaluated before ever looking at the child nodes (step 1 in both cases).
+void nsINode::EnsurePreInsertionValidity1(ErrorResult& aError) {
+ if (!IsDocument() && !IsDocumentFragment() && !IsElement()) {
+ aError.ThrowHierarchyRequestError(
+ nsPrintfCString("Cannot add children to %s", NodeTypeAsString(this)));
+ return;
+ }
+}
+
+void nsINode::EnsurePreInsertionValidity2(bool aReplace, nsINode& aNewChild,
+ nsINode* aRefChild,
+ ErrorResult& aError) {
+ if (aNewChild.IsRootOfNativeAnonymousSubtree()) {
+ // This is anonymous content. Don't allow its insertion
+ // anywhere, since it might have UnbindFromTree calls coming
+ // its way.
+ aError.ThrowNotSupportedError(
+ "Inserting anonymous content manually is not supported");
+ return;
+ }
+
+ // Make sure that the inserted node is allowed as a child of its new parent.
+ EnsureAllowedAsChild(&aNewChild, this, aReplace, aRefChild, aError);
+}
+
+nsINode* nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
+ nsINode* aRefChild,
+ ErrorResult& aError) {
+ // XXXbz I wish I could assert that nsContentUtils::IsSafeToRunScript() so we
+ // could rely on scriptblockers going out of scope to actually run XBL
+ // teardown, but various crud adds nodes under scriptblockers (e.g. native
+ // anonymous content). The only good news is those insertions can't trigger
+ // the bad XBL cases.
+ MOZ_ASSERT_IF(aReplace, aRefChild);
+
+ // Before firing DOMNodeRemoved events, make sure this is actually an insert
+ // we plan to do.
+ EnsurePreInsertionValidity1(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ EnsurePreInsertionValidity2(aReplace, *aNewChild, aRefChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ uint16_t nodeType = aNewChild->NodeType();
+
+ // Before we do anything else, fire all DOMNodeRemoved mutation events
+ // We do this up front as to avoid having to deal with script running
+ // at random places further down.
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ nsMutationGuard guard;
+
+ // If we're replacing, fire for node-to-be-replaced.
+ // If aRefChild == aNewChild then we'll fire for it in check below
+ if (aReplace && aRefChild != aNewChild) {
+ nsContentUtils::MaybeFireNodeRemoved(aRefChild, this);
+ }
+
+ // If the new node already has a parent, fire for removing from old
+ // parent
+ if (nsCOMPtr<nsINode> oldParent = aNewChild->GetParentNode()) {
+ nsContentUtils::MaybeFireNodeRemoved(aNewChild, oldParent);
+ }
+
+ // If we're inserting a fragment, fire for all the children of the
+ // fragment
+ if (nodeType == DOCUMENT_FRAGMENT_NODE) {
+ static_cast<FragmentOrElement*>(aNewChild)->FireNodeRemovedForChildren();
+ }
+
+ if (guard.Mutated(0)) {
+ // Re-check the parts of our pre-insertion validity that might depend on
+ // the tree shape.
+ EnsurePreInsertionValidity2(aReplace, *aNewChild, aRefChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Record the node to insert before, if any
+ nsIContent* nodeToInsertBefore;
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ // Since aRefChild is our child, it must be an nsIContent object.
+ nodeToInsertBefore = aRefChild ? aRefChild->AsContent() : nullptr;
+ }
+ if (nodeToInsertBefore == aNewChild) {
+ // We're going to remove aNewChild from its parent, so use its next sibling
+ // as the node to insert before.
+ nodeToInsertBefore = nodeToInsertBefore->GetNextSibling();
+ }
+
+ Maybe<AutoTArray<nsCOMPtr<nsIContent>, 50>> fragChildren;
+
+ // Remove the new child from the old parent if one exists
+ nsIContent* newContent = aNewChild->AsContent();
+ nsCOMPtr<nsINode> oldParent = newContent->GetParentNode();
+ if (oldParent) {
+ // Hold a strong ref to nodeToInsertBefore across the removal of newContent
+ nsCOMPtr<nsINode> kungFuDeathGrip = nodeToInsertBefore;
+
+ // Removing a child can run script, via XBL destructors.
+ nsMutationGuard guard;
+
+ // Scope for the mutation batch and scriptblocker, so they go away
+ // while kungFuDeathGrip is still alive.
+ {
+ mozAutoDocUpdate batch(newContent->GetComposedDoc(), true);
+ nsAutoMutationBatch mb(oldParent, true, true);
+ // ScriptBlocker ensures previous and next stay alive.
+ nsIContent* previous = aNewChild->GetPreviousSibling();
+ nsIContent* next = aNewChild->GetNextSibling();
+ oldParent->RemoveChildNode(aNewChild->AsContent(), true);
+ if (nsAutoMutationBatch::GetCurrentBatch() == &mb) {
+ mb.RemovalDone();
+ mb.SetPrevSibling(previous);
+ mb.SetNextSibling(next);
+ }
+ }
+
+ // We expect one mutation (the removal) to have happened.
+ if (guard.Mutated(1)) {
+ // XBL destructors, yuck.
+
+ // Verify that newContent has no parent.
+ if (newContent->GetParentNode()) {
+ aError.ThrowHierarchyRequestError(
+ "New child was inserted somewhere else");
+ return nullptr;
+ }
+
+ // And verify that newContent is still allowed as our child.
+ if (aNewChild == aRefChild) {
+ // We've already removed aRefChild. So even if we were doing a replace,
+ // now we're doing a simple insert before nodeToInsertBefore.
+ EnsureAllowedAsChild(newContent, this, false, nodeToInsertBefore,
+ aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ } else {
+ EnsureAllowedAsChild(newContent, this, aReplace, aRefChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // And recompute nodeToInsertBefore, just in case.
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ nodeToInsertBefore = aRefChild ? aRefChild->AsContent() : nullptr;
+ }
+ }
+ }
+ } else if (nodeType == DOCUMENT_FRAGMENT_NODE) {
+ // Make sure to remove all the fragment's kids. We need to do this before
+ // we start inserting anything, so we will run out XBL destructors and
+ // binding teardown (GOD, I HATE THESE THINGS) before we insert anything
+ // into the DOM.
+ uint32_t count = newContent->GetChildCount();
+
+ fragChildren.emplace();
+
+ // Copy the children into a separate array to avoid having to deal with
+ // mutations to the fragment later on here.
+ fragChildren->SetCapacity(count);
+ for (nsIContent* child = newContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ NS_ASSERTION(child->GetUncomposedDoc() == nullptr,
+ "How did we get a child with a current doc?");
+ fragChildren->AppendElement(child);
+ }
+
+ // Hold a strong ref to nodeToInsertBefore across the removals
+ nsCOMPtr<nsINode> kungFuDeathGrip = nodeToInsertBefore;
+
+ nsMutationGuard guard;
+
+ // Scope for the mutation batch and scriptblocker, so they go away
+ // while kungFuDeathGrip is still alive.
+ {
+ mozAutoDocUpdate batch(newContent->GetComposedDoc(), true);
+ nsAutoMutationBatch mb(newContent, false, true);
+
+ while (newContent->HasChildren()) {
+ newContent->RemoveChildNode(newContent->GetLastChild(), true);
+ }
+ }
+
+ // We expect |count| removals
+ if (guard.Mutated(count)) {
+ // XBL destructors, yuck.
+
+ // Verify that nodeToInsertBefore, if non-null, is still our child. If
+ // it's not, there's no way we can do this insert sanely; just bail out.
+ if (nodeToInsertBefore && nodeToInsertBefore->GetParent() != this) {
+ aError.ThrowHierarchyRequestError("Don't know where to insert child");
+ return nullptr;
+ }
+
+ // Verify that all the things in fragChildren have no parent.
+ for (uint32_t i = 0; i < count; ++i) {
+ if (fragChildren->ElementAt(i)->GetParentNode()) {
+ aError.ThrowHierarchyRequestError(
+ "New child was inserted somewhere else");
+ return nullptr;
+ }
+ }
+
+ // Note that unlike the single-element case above, none of our kids can
+ // be aRefChild, so we can always pass through aReplace in the
+ // EnsureAllowedAsChild checks below and don't have to worry about whether
+ // recomputing nodeToInsertBefore is OK.
+
+ // Verify that our aRefChild is still sensible
+ if (aRefChild && aRefChild->GetParent() != this) {
+ aError.ThrowHierarchyRequestError("Don't know where to insert child");
+ return nullptr;
+ }
+
+ // Recompute nodeToInsertBefore, just in case.
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ // If aRefChild has 'this' as a parent, it must be an nsIContent.
+ nodeToInsertBefore = aRefChild ? aRefChild->AsContent() : nullptr;
+ }
+
+ // And verify that newContent is still allowed as our child. Sadly, we
+ // need to reimplement the relevant part of EnsureAllowedAsChild() because
+ // now our nodes are in an array and all. If you change this code,
+ // change the code there.
+ if (IsDocument()) {
+ bool sawElement = false;
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* child = fragChildren->ElementAt(i);
+ if (child->IsElement()) {
+ if (sawElement) {
+ // No good
+ aError.ThrowHierarchyRequestError(
+ "Cannot have more than one Element child of a Document");
+ return nullptr;
+ }
+ sawElement = true;
+ }
+ EnsureAllowedAsChild(child, this, aReplace, aRefChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ mozAutoDocUpdate batch(GetComposedDoc(), true);
+ nsAutoMutationBatch mb;
+
+ // If we're replacing and we haven't removed aRefChild yet, do so now
+ if (aReplace && aRefChild != aNewChild) {
+ mb.Init(this, true, true);
+
+ // Since aRefChild is never null in the aReplace case, we know that at
+ // this point nodeToInsertBefore is the next sibling of aRefChild.
+ NS_ASSERTION(aRefChild->GetNextSibling() == nodeToInsertBefore,
+ "Unexpected nodeToInsertBefore");
+
+ nsIContent* toBeRemoved = nodeToInsertBefore
+ ? nodeToInsertBefore->GetPreviousSibling()
+ : GetLastChild();
+ MOZ_ASSERT(toBeRemoved);
+
+ RemoveChildNode(toBeRemoved, true);
+ }
+
+ // Move new child over to our document if needed. Do this after removing
+ // it from its parent so that AdoptNode doesn't fire DOMNodeRemoved
+ // DocumentType nodes are the only nodes that can have a null
+ // ownerDocument according to the DOM spec, and we need to allow
+ // inserting them w/o calling AdoptNode().
+ Document* doc = OwnerDoc();
+ if (doc != newContent->OwnerDoc()) {
+ AdoptNodeIntoOwnerDoc(this, aNewChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ /*
+ * Check if we're inserting a document fragment. If we are, we need
+ * to actually add its children individually (i.e. we don't add the
+ * actual document fragment).
+ */
+ nsINode* result = aReplace ? aRefChild : aNewChild;
+ if (nodeType == DOCUMENT_FRAGMENT_NODE) {
+ nsAutoMutationBatch* mutationBatch = nsAutoMutationBatch::GetCurrentBatch();
+ if (mutationBatch && mutationBatch != &mb) {
+ mutationBatch = nullptr;
+ } else if (!aReplace) {
+ mb.Init(this, true, true);
+ mutationBatch = nsAutoMutationBatch::GetCurrentBatch();
+ }
+
+ if (mutationBatch) {
+ mutationBatch->RemovalDone();
+ mutationBatch->SetPrevSibling(
+ nodeToInsertBefore ? nodeToInsertBefore->GetPreviousSibling()
+ : GetLastChild());
+ mutationBatch->SetNextSibling(nodeToInsertBefore);
+ }
+
+ uint32_t count = fragChildren->Length();
+ if (!count) {
+ return result;
+ }
+
+ bool appending = !IsDocument() && !nodeToInsertBefore;
+ nsIContent* firstInsertedContent = fragChildren->ElementAt(0);
+
+ // Iterate through the fragment's children, and insert them in the new
+ // parent
+ for (uint32_t i = 0; i < count; ++i) {
+ // XXXbz how come no reparenting here? That seems odd...
+ // Insert the child.
+ InsertChildBefore(fragChildren->ElementAt(i), nodeToInsertBefore,
+ !appending, aError);
+ if (aError.Failed()) {
+ // Make sure to notify on any children that we did succeed to insert
+ if (appending && i != 0) {
+ MutationObservers::NotifyContentAppended(
+ static_cast<nsIContent*>(this), firstInsertedContent);
+ }
+ return nullptr;
+ }
+ }
+
+ if (mutationBatch && !appending) {
+ mutationBatch->NodesAdded();
+ }
+
+ // Notify and fire mutation events when appending
+ if (appending) {
+ MutationObservers::NotifyContentAppended(static_cast<nsIContent*>(this),
+ firstInsertedContent);
+ if (mutationBatch) {
+ mutationBatch->NodesAdded();
+ }
+ // Optimize for the case when there are no listeners
+ if (nsContentUtils::HasMutationListeners(
+ doc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
+ Element::FireNodeInserted(doc, this, *fragChildren);
+ }
+ }
+ } else {
+ // Not inserting a fragment but rather a single node.
+
+ // FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=544654
+ // We need to reparent here for nodes for which the parent of their
+ // wrapper is not the wrapper for their ownerDocument (XUL elements,
+ // form controls, ...). Also applies in the fragment code above.
+ if (nsAutoMutationBatch::GetCurrentBatch() == &mb) {
+ mb.RemovalDone();
+ mb.SetPrevSibling(nodeToInsertBefore
+ ? nodeToInsertBefore->GetPreviousSibling()
+ : GetLastChild());
+ mb.SetNextSibling(nodeToInsertBefore);
+ }
+ InsertChildBefore(newContent, nodeToInsertBefore, true, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return result;
+}
+
+void nsINode::BindObject(nsISupports* aObject) {
+ nsCOMArray<nsISupports>* objects = static_cast<nsCOMArray<nsISupports>*>(
+ GetProperty(nsGkAtoms::keepobjectsalive));
+ if (!objects) {
+ objects = new nsCOMArray<nsISupports>();
+ SetProperty(nsGkAtoms::keepobjectsalive, objects,
+ nsINode::DeleteProperty<nsCOMArray<nsISupports>>, true);
+ }
+ objects->AppendObject(aObject);
+}
+
+void nsINode::UnbindObject(nsISupports* aObject) {
+ nsCOMArray<nsISupports>* objects = static_cast<nsCOMArray<nsISupports>*>(
+ GetProperty(nsGkAtoms::keepobjectsalive));
+ if (objects) {
+ objects->RemoveObject(aObject);
+ }
+}
+
+already_AddRefed<AccessibleNode> nsINode::GetAccessibleNode() {
+#ifdef ACCESSIBILITY
+ nsresult rv = NS_OK;
+
+ RefPtr<AccessibleNode> anode =
+ static_cast<AccessibleNode*>(GetProperty(nsGkAtoms::accessiblenode, &rv));
+ if (NS_FAILED(rv)) {
+ anode = new AccessibleNode(this);
+ RefPtr<AccessibleNode> temp = anode;
+ rv = SetProperty(nsGkAtoms::accessiblenode, temp.forget().take(),
+ nsPropertyTable::SupportsDtorFunc, true);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("SetProperty failed");
+ return nullptr;
+ }
+ }
+ return anode.forget();
+#else
+ return nullptr;
+#endif
+}
+
+void nsINode::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ EventListenerManager* elm = GetExistingListenerManager();
+ if (elm) {
+ *aNodeSize += elm->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mNodeInfo
+ // - mSlots
+ //
+ // The following members are not measured:
+ // - mParent, mNextSibling, mPreviousOrLastSibling, mFirstChild: because
+ // they're non-owning, from "exclusive ownership" point of view.
+}
+
+void nsINode::AddSizeOfIncludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ *aNodeSize += aSizes.mState.mMallocSizeOf(this);
+ AddSizeOfExcludingThis(aSizes, aNodeSize);
+}
+
+bool nsINode::Contains(const nsINode* aOther) const {
+ if (aOther == this) {
+ return true;
+ }
+
+ if (!aOther || OwnerDoc() != aOther->OwnerDoc() ||
+ IsInUncomposedDoc() != aOther->IsInUncomposedDoc() ||
+ !aOther->IsContent() || !HasChildren()) {
+ return false;
+ }
+
+ if (IsDocument()) {
+ // document.contains(aOther) returns true if aOther is in the document,
+ // but is not in any anonymous subtree.
+ // IsInUncomposedDoc() check is done already before this.
+ return !aOther->IsInNativeAnonymousSubtree();
+ }
+
+ if (!IsElement() && !IsDocumentFragment()) {
+ return false;
+ }
+
+ if (IsInShadowTree() != aOther->IsInShadowTree() ||
+ IsInNativeAnonymousSubtree() != aOther->IsInNativeAnonymousSubtree()) {
+ return false;
+ }
+
+ if (IsInNativeAnonymousSubtree()) {
+ if (GetClosestNativeAnonymousSubtreeRoot() !=
+ aOther->GetClosestNativeAnonymousSubtreeRoot()) {
+ return false;
+ }
+ }
+
+ if (IsInShadowTree()) {
+ ShadowRoot* otherRoot = aOther->GetContainingShadow();
+ if (IsShadowRoot()) {
+ return otherRoot == this;
+ }
+ if (otherRoot != GetContainingShadow()) {
+ return false;
+ }
+ }
+
+ return aOther->IsInclusiveDescendantOf(this);
+}
+
+uint32_t nsINode::Length() const {
+ switch (NodeType()) {
+ case DOCUMENT_TYPE_NODE:
+ return 0;
+
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case COMMENT_NODE:
+ MOZ_ASSERT(IsContent());
+ return AsContent()->TextLength();
+
+ default:
+ return GetChildCount();
+ }
+}
+
+const StyleSelectorList* nsINode::ParseSelectorList(
+ const nsACString& aSelectorString, ErrorResult& aRv) {
+ Document* doc = OwnerDoc();
+
+ Document::SelectorCache& cache = doc->GetSelectorCache();
+ StyleSelectorList* list = cache.GetListOrInsertFrom(aSelectorString, [&] {
+ // Note that we want to cache even if null was returned, because we
+ // want to cache the "This is not a valid selector" result.
+ //
+ // NOTE(emilio): Off-hand, getting a CallerType here might seem like a
+ // better idea than using ChromeRulesEnabled(), but that would mean
+ // that we'd need to key the selector cache by that.
+ // ChromeRulesEnabled() gives us the same semantics as any inline
+ // style associated to a document, which seems reasonable.
+ return WrapUnique(
+ Servo_SelectorList_Parse(&aSelectorString, doc->ChromeRulesEnabled()));
+ });
+
+ if (!list) {
+ // Invalid selector.
+ aRv.ThrowSyntaxError("'"_ns + aSelectorString +
+ "' is not a valid selector"_ns);
+ }
+
+ return list;
+}
+
+// Given an id, find first element with that id under aRoot.
+// If none found, return nullptr. aRoot must be in the document.
+inline static Element* FindMatchingElementWithId(
+ const nsAString& aId, const Element& aRoot,
+ const DocumentOrShadowRoot& aContainingDocOrShadowRoot) {
+ MOZ_ASSERT(aRoot.SubtreeRoot() == &aContainingDocOrShadowRoot.AsNode());
+ MOZ_ASSERT(
+ aRoot.IsInUncomposedDoc() || aRoot.IsInShadowTree(),
+ "Don't call me if the root is not in the document or in a shadow tree");
+
+ const nsTArray<Element*>* elements =
+ aContainingDocOrShadowRoot.GetAllElementsForId(aId);
+ if (!elements) {
+ // Nothing to do; we're done
+ return nullptr;
+ }
+
+ // XXXbz: Should we fall back to the tree walk if |elements| is long,
+ // for some value of "long"?
+ for (Element* element : *elements) {
+ if (MOZ_UNLIKELY(element == &aRoot)) {
+ continue;
+ }
+
+ if (!element->IsInclusiveDescendantOf(&aRoot)) {
+ continue;
+ }
+
+ // We have an element with the right id and it's a strict descendant
+ // of aRoot.
+ return element;
+ }
+
+ return nullptr;
+}
+
+Element* nsINode::QuerySelector(const nsACString& aSelector,
+ ErrorResult& aResult) {
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("nsINode::QuerySelector",
+ LAYOUT_SelectorQuery, aSelector);
+
+ const StyleSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return nullptr;
+ }
+ const bool useInvalidation = false;
+ return const_cast<Element*>(
+ Servo_SelectorList_QueryFirst(this, list, useInvalidation));
+}
+
+already_AddRefed<nsINodeList> nsINode::QuerySelectorAll(
+ const nsACString& aSelector, ErrorResult& aResult) {
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("nsINode::QuerySelectorAll",
+ LAYOUT_SelectorQuery, aSelector);
+
+ RefPtr<nsSimpleContentList> contentList = new nsSimpleContentList(this);
+ const StyleSelectorList* list = ParseSelectorList(aSelector, aResult);
+ if (!list) {
+ return contentList.forget();
+ }
+
+ const bool useInvalidation = false;
+ Servo_SelectorList_QueryAll(this, list, contentList.get(), useInvalidation);
+ return contentList.forget();
+}
+
+Element* nsINode::GetElementById(const nsAString& aId) {
+ MOZ_ASSERT(!IsShadowRoot(), "Should use the faster version");
+ MOZ_ASSERT(IsElement() || IsDocumentFragment(),
+ "Bogus this object for GetElementById call");
+ if (IsInUncomposedDoc()) {
+ MOZ_ASSERT(IsElement(), "Huh? A fragment in a document?");
+ return FindMatchingElementWithId(aId, *AsElement(), *OwnerDoc());
+ }
+
+ if (ShadowRoot* containingShadow = AsContent()->GetContainingShadow()) {
+ MOZ_ASSERT(IsElement(), "Huh? A fragment in a ShadowRoot?");
+ return FindMatchingElementWithId(aId, *AsElement(), *containingShadow);
+ }
+
+ for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextNode(this)) {
+ if (!kid->IsElement()) {
+ continue;
+ }
+ nsAtom* id = kid->AsElement()->GetID();
+ if (id && id->Equals(aId)) {
+ return kid->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+JSObject* nsINode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ // Make sure one of these is true
+ // (1) our owner document has a script handling object,
+ // (2) Our owner document has had a script handling object, or has been marked
+ // to have had one,
+ // (3) we are running a privileged script.
+ // Event handling is possible only if (1). If (2) event handling is
+ // prevented.
+ // If the document has never had a script handling object, untrusted
+ // scripts (3) shouldn't touch it!
+ bool hasHadScriptHandlingObject = false;
+ if (!OwnerDoc()->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
+ !hasHadScriptHandlingObject && !nsContentUtils::IsSystemCaller(aCx)) {
+ Throw(aCx, NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
+ if (obj && ChromeOnlyAccess()) {
+ MOZ_RELEASE_ASSERT(
+ xpc::IsUnprivilegedJunkScope(JS::GetNonCCWObjectGlobal(obj)) ||
+ xpc::IsInUAWidgetScope(obj) || xpc::AccessCheck::isChrome(obj));
+ }
+ return obj;
+}
+
+already_AddRefed<nsINode> nsINode::CloneNode(bool aDeep, ErrorResult& aError) {
+ return Clone(aDeep, nullptr, aError);
+}
+
+nsDOMAttributeMap* nsINode::GetAttributes() {
+ if (!IsElement()) {
+ return nullptr;
+ }
+ return AsElement()->Attributes();
+}
+
+Element* nsINode::GetParentElementCrossingShadowRoot() const {
+ if (!mParent) {
+ return nullptr;
+ }
+
+ if (mParent->IsElement()) {
+ return mParent->AsElement();
+ }
+
+ if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(mParent)) {
+ MOZ_ASSERT(shadowRoot->GetHost(), "ShowRoots should always have a host");
+ return shadowRoot->GetHost();
+ }
+
+ return nullptr;
+}
+
+bool nsINode::HasBoxQuadsSupport(JSContext* aCx, JSObject* /* unused */) {
+ return xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) ||
+ StaticPrefs::layout_css_getBoxQuads_enabled();
+}
+
+nsINode* nsINode::GetScopeChainParent() const { return nullptr; }
+
+Element* nsINode::GetParentFlexElement() {
+ if (!IsContent()) {
+ return nullptr;
+ }
+
+ nsIFrame* primaryFrame = AsContent()->GetPrimaryFrame(FlushType::Frames);
+
+ // Walk up the parent chain and pierce through any anonymous boxes
+ // that might be between this frame and a possible flex parent.
+ for (nsIFrame* f = primaryFrame; f; f = f->GetParent()) {
+ if (f != primaryFrame && !f->Style()->IsAnonBox()) {
+ // We hit a non-anonymous ancestor before finding a flex item.
+ // Bail out.
+ break;
+ }
+ if (f->IsFlexItem()) {
+ return f->GetParent()->GetContent()->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+Element* nsINode::GetNearestInclusiveOpenPopover() const {
+ for (auto* el : InclusiveFlatTreeAncestorsOfType<Element>()) {
+ if (el->IsAutoPopover() && el->IsPopoverOpen()) {
+ return el;
+ }
+ }
+ return nullptr;
+}
+
+Element* nsINode::GetNearestInclusiveTargetPopoverForInvoker() const {
+ for (auto* el : InclusiveFlatTreeAncestorsOfType<Element>()) {
+ if (auto* popover = el->GetEffectivePopoverTargetElement()) {
+ if (popover->IsAutoPopover() && popover->IsPopoverOpen()) {
+ return popover;
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsGenericHTMLElement* nsINode::GetEffectivePopoverTargetElement() const {
+ const auto* formControl =
+ nsGenericHTMLFormControlElementWithState::FromNode(this);
+ if (!formControl || formControl->IsDisabled() ||
+ !formControl->IsButtonControl()) {
+ return nullptr;
+ }
+ if (auto* popover = nsGenericHTMLElement::FromNodeOrNull(
+ formControl->GetPopoverTargetElement())) {
+ if (popover->GetPopoverAttributeState() != PopoverAttributeState::None) {
+ return popover;
+ }
+ }
+ return nullptr;
+}
+
+Element* nsINode::GetTopmostClickedPopover() const {
+ Element* clickedPopover = GetNearestInclusiveOpenPopover();
+ Element* invokedPopover = GetNearestInclusiveTargetPopoverForInvoker();
+ if (!clickedPopover) {
+ return invokedPopover;
+ }
+ auto autoPopoverList = clickedPopover->OwnerDoc()->AutoPopoverList();
+ for (Element* el : Reversed(autoPopoverList)) {
+ if (el == clickedPopover || el == invokedPopover) {
+ return el;
+ }
+ }
+ return nullptr;
+}
+
+void nsINode::AddAnimationObserver(nsIAnimationObserver* aAnimationObserver) {
+ AddMutationObserver(aAnimationObserver);
+ OwnerDoc()->SetMayHaveAnimationObservers();
+}
+
+void nsINode::AddAnimationObserverUnlessExists(
+ nsIAnimationObserver* aAnimationObserver) {
+ AddMutationObserverUnlessExists(aAnimationObserver);
+ OwnerDoc()->SetMayHaveAnimationObservers();
+}
+
+already_AddRefed<nsINode> nsINode::CloneAndAdopt(
+ nsINode* aNode, bool aClone, bool aDeep,
+ nsNodeInfoManager* aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope, nsINode* aParent,
+ ErrorResult& aError) {
+ MOZ_ASSERT((!aClone && aNewNodeInfoManager) || !aReparentScope,
+ "If cloning or not getting a new nodeinfo we shouldn't rewrap");
+ MOZ_ASSERT(!aParent || aNode->IsContent(),
+ "Can't insert document or attribute nodes into a parent");
+
+ // First deal with aNode and walk its attributes (and their children). Then,
+ // if aDeep is true, deal with aNode's children (and recurse into their
+ // attributes and children).
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsNodeInfoManager* nodeInfoManager = aNewNodeInfoManager;
+
+ // aNode.
+ class NodeInfo* nodeInfo = aNode->mNodeInfo;
+ RefPtr<class NodeInfo> newNodeInfo;
+ if (nodeInfoManager) {
+ // Don't allow importing/adopting nodes from non-privileged "scriptable"
+ // documents to "non-scriptable" documents.
+ Document* newDoc = nodeInfoManager->GetDocument();
+ if (NS_WARN_IF(!newDoc)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ bool hasHadScriptHandlingObject = false;
+ if (!newDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
+ !hasHadScriptHandlingObject) {
+ Document* currentDoc = aNode->OwnerDoc();
+ if (NS_WARN_IF(!nsContentUtils::IsChromeDoc(currentDoc) &&
+ (currentDoc->GetScriptHandlingObject(
+ hasHadScriptHandlingObject) ||
+ hasHadScriptHandlingObject))) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ }
+
+ newNodeInfo = nodeInfoManager->GetNodeInfo(
+ nodeInfo->NameAtom(), nodeInfo->GetPrefixAtom(),
+ nodeInfo->NamespaceID(), nodeInfo->NodeType(),
+ nodeInfo->GetExtraName());
+
+ nodeInfo = newNodeInfo;
+ }
+
+ Element* elem = Element::FromNode(aNode);
+
+ nsCOMPtr<nsINode> clone;
+ if (aClone) {
+ nsresult rv = aNode->Clone(nodeInfo, getter_AddRefs(clone));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ if (aParent) {
+ // If we're cloning we need to insert the cloned children into the cloned
+ // parent.
+ aParent->AppendChildTo(static_cast<nsIContent*>(clone.get()), false,
+ aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ } else if (aDeep && clone->IsDocument()) {
+ // After cloning the document itself, we want to clone the children into
+ // the cloned document (somewhat like cloning and importing them into the
+ // cloned document).
+ nodeInfoManager = clone->mNodeInfo->NodeInfoManager();
+ }
+ } else if (nodeInfoManager) {
+ Document* oldDoc = aNode->OwnerDoc();
+
+ DOMArena* domArenaToStore =
+ !aNode->HasFlag(NODE_KEEPS_DOMARENA)
+ ? aNode->NodeInfo()->NodeInfoManager()->GetArenaAllocator()
+ : nullptr;
+
+ Document* newDoc = nodeInfoManager->GetDocument();
+ MOZ_ASSERT(newDoc);
+
+ bool wasRegistered = false;
+ if (elem) {
+ wasRegistered = oldDoc->UnregisterActivityObserver(elem);
+ }
+
+ const bool hadProperties = aNode->HasProperties();
+ if (hadProperties) {
+ // NOTE: We want this to happen before NodeInfoChanged so that
+ // NodeInfoChanged can use node properties normally.
+ //
+ // When this fails, it removes all properties for the node anyway, so no
+ // extra error handling needed.
+ Unused << oldDoc->PropertyTable().TransferOrRemoveAllPropertiesFor(
+ aNode, newDoc->PropertyTable());
+ }
+
+ aNode->mNodeInfo.swap(newNodeInfo);
+ aNode->NodeInfoChanged(oldDoc);
+
+ MOZ_ASSERT(newDoc != oldDoc);
+ if (elem) {
+ // Adopted callback must be enqueued whenever a node’s
+ // shadow-including inclusive descendants that is custom.
+ CustomElementData* data = elem->GetCustomElementData();
+ if (data && data->mState == CustomElementData::State::eCustom) {
+ LifecycleCallbackArgs args;
+ args.mOldDocument = oldDoc;
+ args.mNewDocument = newDoc;
+
+ nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eAdopted,
+ elem, args);
+ }
+ }
+
+ // XXX what if oldDoc is null, we don't know if this should be
+ // registered or not! Can that really happen?
+ if (wasRegistered) {
+ newDoc->RegisterActivityObserver(aNode->AsElement());
+ }
+
+ if (nsPIDOMWindowInner* window = newDoc->GetInnerWindow()) {
+ EventListenerManager* elm = aNode->GetExistingListenerManager();
+ if (elm) {
+ window->SetMutationListeners(elm->MutationListenerBits());
+ if (elm->MayHavePaintEventListener()) {
+ window->SetHasPaintEventListeners();
+ }
+ if (elm->MayHaveTouchEventListener()) {
+ window->SetHasTouchEventListeners();
+ }
+ if (elm->MayHaveMouseEnterLeaveEventListener()) {
+ window->SetHasMouseEnterLeaveEventListeners();
+ }
+ if (elm->MayHavePointerEnterLeaveEventListener()) {
+ window->SetHasPointerEnterLeaveEventListeners();
+ }
+ if (elm->MayHaveSelectionChangeEventListener()) {
+ window->SetHasSelectionChangeEventListeners();
+ }
+ if (elm->MayHaveFormSelectEventListener()) {
+ window->SetHasFormSelectEventListeners();
+ }
+ if (elm->MayHaveTransitionEventListener()) {
+ window->SetHasTransitionEventListeners();
+ }
+ }
+ }
+ if (wasRegistered) {
+ nsIContent* content = aNode->AsContent();
+ if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
+ mediaElem->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
+ do_QueryInterface(aNode));
+ if (objectLoadingContent) {
+ nsObjectLoadingContent* olc =
+ static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
+ olc->NotifyOwnerDocumentActivityChanged();
+ } else {
+ // HTMLImageElement::FromNode is insufficient since we need this for
+ // <svg:image> as well.
+ nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
+ do_QueryInterface(aNode));
+ if (imageLoadingContent) {
+ auto ilc =
+ static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
+ ilc->NotifyOwnerDocumentActivityChanged();
+ }
+ }
+ }
+
+ if (oldDoc->MayHaveDOMMutationObservers()) {
+ newDoc->SetMayHaveDOMMutationObservers();
+ }
+
+ if (oldDoc->MayHaveAnimationObservers()) {
+ newDoc->SetMayHaveAnimationObservers();
+ }
+
+ if (elem) {
+ elem->RecompileScriptEventListeners();
+ }
+
+ if (aReparentScope) {
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> wrapper(cx);
+ if ((wrapper = aNode->GetWrapper())) {
+ MOZ_ASSERT(IsDOMObject(wrapper));
+ JSAutoRealm ar(cx, wrapper);
+ UpdateReflectorGlobal(cx, wrapper, aError);
+ if (aError.Failed()) {
+ if (wasRegistered) {
+ newDoc->UnregisterActivityObserver(aNode->AsElement());
+ }
+ if (hadProperties) {
+ // NOTE: When it fails it removes all properties for the node
+ // anyway, so no extra error handling needed.
+ Unused << newDoc->PropertyTable().TransferOrRemoveAllPropertiesFor(
+ aNode, oldDoc->PropertyTable());
+ }
+ aNode->mNodeInfo.swap(newNodeInfo);
+ aNode->NodeInfoChanged(newDoc);
+ if (wasRegistered) {
+ oldDoc->RegisterActivityObserver(aNode->AsElement());
+ }
+ return nullptr;
+ }
+ }
+ }
+
+ // At this point, a new node is added to the document, and this
+ // node isn't allocated by the NodeInfoManager of this document,
+ // so we need to do this SetArenaAllocator logic to bypass
+ // the !HasChildren() check in NodeInfoManager::Allocate.
+ if (mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
+ if (!newDoc->NodeInfoManager()->HasAllocated()) {
+ if (DocGroup* docGroup = newDoc->GetDocGroup()) {
+ newDoc->NodeInfoManager()->SetArenaAllocator(
+ docGroup->ArenaAllocator());
+ }
+ }
+
+ if (domArenaToStore && newDoc->GetDocGroup() != oldDoc->GetDocGroup()) {
+ nsContentUtils::AddEntryToDOMArenaTable(aNode, domArenaToStore);
+ }
+ }
+ }
+
+ if (aDeep && (!aClone || !aNode->IsAttr())) {
+ // aNode's children.
+ for (nsIContent* cloneChild = aNode->GetFirstChild(); cloneChild;
+ cloneChild = cloneChild->GetNextSibling()) {
+ nsCOMPtr<nsINode> child =
+ CloneAndAdopt(cloneChild, aClone, true, nodeInfoManager,
+ aReparentScope, clone, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (aDeep && aNode->IsElement()) {
+ if (aClone) {
+ if (nodeInfo->GetDocument()->IsStaticDocument()) {
+ // Clone any animations to the node in the static document, including
+ // the current timing. They will need to be paused later after the new
+ // document's pres shell gets initialized.
+ //
+ // This needs to be done here rather than in Element::CopyInnerTo
+ // because the animations clone code relies on the target (that is,
+ // `clone`) being connected already.
+ clone->AsElement()->CloneAnimationsFrom(*aNode->AsElement());
+
+ // Clone the Shadow DOM
+ ShadowRoot* originalShadowRoot = aNode->AsElement()->GetShadowRoot();
+ if (originalShadowRoot) {
+ RefPtr<ShadowRoot> newShadowRoot =
+ clone->AsElement()->AttachShadowWithoutNameChecks(
+ originalShadowRoot->Mode());
+
+ newShadowRoot->CloneInternalDataFrom(originalShadowRoot);
+ for (nsIContent* origChild = originalShadowRoot->GetFirstChild();
+ origChild; origChild = origChild->GetNextSibling()) {
+ nsCOMPtr<nsINode> child =
+ CloneAndAdopt(origChild, aClone, aDeep, nodeInfoManager,
+ aReparentScope, newShadowRoot, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+ }
+ } else {
+ if (ShadowRoot* shadowRoot = aNode->AsElement()->GetShadowRoot()) {
+ nsCOMPtr<nsINode> child =
+ CloneAndAdopt(shadowRoot, aClone, aDeep, nodeInfoManager,
+ aReparentScope, clone, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+ }
+
+ // Cloning template element.
+ if (aDeep && aClone && aNode->IsTemplateElement()) {
+ DocumentFragment* origContent =
+ static_cast<HTMLTemplateElement*>(aNode)->Content();
+ DocumentFragment* cloneContent =
+ static_cast<HTMLTemplateElement*>(clone.get())->Content();
+
+ // Clone the children into the clone's template content owner
+ // document's nodeinfo manager.
+ nsNodeInfoManager* ownerNodeInfoManager =
+ cloneContent->mNodeInfo->NodeInfoManager();
+
+ for (nsIContent* cloneChild = origContent->GetFirstChild(); cloneChild;
+ cloneChild = cloneChild->GetNextSibling()) {
+ nsCOMPtr<nsINode> child =
+ CloneAndAdopt(cloneChild, aClone, aDeep, ownerNodeInfoManager,
+ aReparentScope, cloneContent, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+
+ return clone.forget();
+}
+
+void nsINode::Adopt(nsNodeInfoManager* aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope,
+ mozilla::ErrorResult& aError) {
+ if (aNewNodeInfoManager) {
+ Document* beforeAdoptDoc = OwnerDoc();
+ Document* afterAdoptDoc = aNewNodeInfoManager->GetDocument();
+
+ MOZ_ASSERT(beforeAdoptDoc);
+ MOZ_ASSERT(afterAdoptDoc);
+ MOZ_ASSERT(beforeAdoptDoc != afterAdoptDoc);
+
+ if (afterAdoptDoc->GetDocGroup() != beforeAdoptDoc->GetDocGroup()) {
+ // This is a temporary solution for Bug 1590526 to only limit
+ // the restriction to chrome level documents because web extensions
+ // rely on content to content node adoption.
+ if (nsContentUtils::IsChromeDoc(afterAdoptDoc) ||
+ nsContentUtils::IsChromeDoc(beforeAdoptDoc)) {
+ return aError.ThrowSecurityError(
+ "Adopting nodes across docgroups in chrome documents "
+ "is unsupported");
+ }
+ }
+ }
+
+ // Just need to store the return value of CloneAndAdopt in a
+ // temporary nsCOMPtr to make sure we release it.
+ nsCOMPtr<nsINode> node = CloneAndAdopt(this, false, true, aNewNodeInfoManager,
+ aReparentScope, nullptr, aError);
+
+ nsMutationGuard::DidMutate();
+}
+
+already_AddRefed<nsINode> nsINode::Clone(bool aDeep,
+ nsNodeInfoManager* aNewNodeInfoManager,
+ ErrorResult& aError) {
+ return CloneAndAdopt(this, true, aDeep, aNewNodeInfoManager, nullptr, nullptr,
+ aError);
+}
+
+void nsINode::GenerateXPath(nsAString& aResult) {
+ XPathGenerator::Generate(this, aResult);
+}
+
+bool nsINode::IsApzAware() const { return IsNodeApzAware(); }
+
+bool nsINode::IsNodeApzAwareInternal() const {
+ return EventTarget::IsApzAware();
+}
+
+DocGroup* nsINode::GetDocGroup() const { return OwnerDoc()->GetDocGroup(); }
+
+nsINode* nsINode::GetFlattenedTreeParentNodeNonInline() const {
+ return GetFlattenedTreeParentNode();
+}
+
+ParentObject nsINode::GetParentObject() const {
+ ParentObject p(OwnerDoc());
+ // Note that mReflectionScope is a no-op for chrome, and other places where we
+ // don't check this value.
+ if (IsInNativeAnonymousSubtree()) {
+ if (ShouldUseUAWidgetScope(this)) {
+ p.mReflectionScope = ReflectionScope::UAWidget;
+ } else {
+ MOZ_ASSERT(ShouldUseNACScope(this));
+ p.mReflectionScope = ReflectionScope::NAC;
+ }
+ } else {
+ MOZ_ASSERT(!ShouldUseNACScope(this));
+ MOZ_ASSERT(!ShouldUseUAWidgetScope(this));
+ }
+ return p;
+}
+
+void nsINode::AddMutationObserver(
+ nsMultiMutationObserver* aMultiMutationObserver) {
+ if (aMultiMutationObserver) {
+ NS_ASSERTION(!aMultiMutationObserver->ContainsNode(this),
+ "Observer already in the list");
+ aMultiMutationObserver->AddMutationObserverToNode(this);
+ }
+}
+
+void nsINode::AddMutationObserverUnlessExists(
+ nsMultiMutationObserver* aMultiMutationObserver) {
+ if (aMultiMutationObserver && !aMultiMutationObserver->ContainsNode(this)) {
+ aMultiMutationObserver->AddMutationObserverToNode(this);
+ }
+}
+
+void nsINode::RemoveMutationObserver(
+ nsMultiMutationObserver* aMultiMutationObserver) {
+ if (aMultiMutationObserver) {
+ aMultiMutationObserver->RemoveMutationObserverFromNode(this);
+ }
+}
+
+void nsINode::FireNodeRemovedForChildren() {
+ Document* doc = OwnerDoc();
+ // Optimize the common case
+ if (!nsContentUtils::HasMutationListeners(
+ doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> child;
+ for (child = GetFirstChild(); child && child->GetParentNode() == this;
+ child = child->GetNextSibling()) {
+ nsContentUtils::MaybeFireNodeRemoved(child, this);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNodeWeakReference, nsIWeakReference)
+
+nsNodeWeakReference::nsNodeWeakReference(nsINode* aNode)
+ : nsIWeakReference(aNode) {}
+
+nsNodeWeakReference::~nsNodeWeakReference() {
+ nsINode* node = static_cast<nsINode*>(mObject);
+
+ if (node) {
+ NS_ASSERTION(node->Slots()->mWeakReference == this,
+ "Weak reference has wrong value");
+ node->Slots()->mWeakReference = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsNodeWeakReference::QueryReferentFromScript(const nsIID& aIID,
+ void** aInstancePtr) {
+ return QueryReferent(aIID, aInstancePtr);
+}
+
+size_t nsNodeWeakReference::SizeOfOnlyThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this);
+}
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
new file mode 100644
index 0000000000..be77a5e084
--- /dev/null
+++ b/dom/base/nsINode.h
@@ -0,0 +1,2470 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsINode_h___
+#define nsINode_h___
+
+#include "mozilla/DoublyLinkedList.h"
+#include "mozilla/Likely.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h" // for member, local
+#include "nsGkAtoms.h" // for nsGkAtoms::baseURIProperty
+#include "mozilla/dom/NodeInfo.h" // member (in nsCOMPtr)
+#include "nsIWeakReference.h"
+#include "nsIMutationObserver.h"
+#include "nsNodeInfoManager.h" // for use in NodePrincipal()
+#include "nsPropertyTable.h" // for typedefs
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/EventTarget.h" // for base class
+#include "js/TypeDecls.h" // for Handle, Value, JSObject, JSContext
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/NodeBinding.h"
+#include "nsTHashtable.h"
+#include <iosfwd>
+
+// Including 'windows.h' will #define GetClassInfo to something else.
+#ifdef XP_WIN
+# ifdef GetClassInfo
+# undef GetClassInfo
+# endif
+#endif
+
+class AttrArray;
+class nsAttrChildContentList;
+template <typename T>
+class nsCOMArray;
+class nsDOMAttributeMap;
+class nsGenericHTMLElement;
+class nsIAnimationObserver;
+class nsIContent;
+class nsIContentSecurityPolicy;
+class nsIFrame;
+class nsIHTMLCollection;
+class nsMultiMutationObserver;
+class nsINode;
+class nsINodeList;
+class nsIPrincipal;
+class nsIURI;
+class nsNodeSupportsWeakRefTearoff;
+class nsDOMMutationObserver;
+class nsRange;
+class nsWindowSizes;
+
+namespace mozilla {
+class EventListenerManager;
+struct StyleSelectorList;
+template <typename T>
+class Maybe;
+class PresShell;
+class TextEditor;
+namespace dom {
+/**
+ * @return true if aChar is what the WHATWG defines as a 'ascii whitespace'.
+ * https://infra.spec.whatwg.org/#ascii-whitespace
+ */
+inline bool IsSpaceCharacter(char16_t aChar) {
+ return aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r' ||
+ aChar == '\f';
+}
+inline bool IsSpaceCharacter(char aChar) {
+ return aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r' ||
+ aChar == '\f';
+}
+class AbstractRange;
+class AccessibleNode;
+template <typename T>
+class AncestorsOfTypeIterator;
+struct BoxQuadOptions;
+struct ConvertCoordinateOptions;
+class DocGroup;
+class Document;
+class DocumentFragment;
+class DocumentOrShadowRoot;
+class DOMPoint;
+class DOMQuad;
+class DOMRectReadOnly;
+class Element;
+class EventHandlerNonNull;
+template <typename T>
+class FlatTreeAncestorsOfTypeIterator;
+template <typename T>
+class InclusiveAncestorsOfTypeIterator;
+template <typename T>
+class InclusiveFlatTreeAncestorsOfTypeIterator;
+class LinkStyle;
+class MutationObservers;
+template <typename T>
+class Optional;
+class OwningNodeOrString;
+template <typename>
+class Sequence;
+class ShadowRoot;
+class SVGUseElement;
+class Text;
+class TextOrElementOrDocument;
+struct DOMPointInit;
+struct GetRootNodeOptions;
+enum class CallerType : uint32_t;
+} // namespace dom
+} // namespace mozilla
+
+#define NODE_FLAG_BIT(n_) \
+ (nsWrapperCache::FlagsType(1U) << (WRAPPER_CACHE_FLAGS_BITS_USED + (n_)))
+
+enum : uint32_t {
+ // This bit will be set if the node has a listener manager.
+ NODE_HAS_LISTENERMANAGER = NODE_FLAG_BIT(0),
+
+ // Whether this node has had any properties set on it
+ NODE_HAS_PROPERTIES = NODE_FLAG_BIT(1),
+
+ // Whether the node has some ancestor, possibly itself, that is native
+ // anonymous. This includes ancestors crossing XBL scopes, in cases when an
+ // XBL binding is attached to an element which has a native anonymous
+ // ancestor. This flag is set-once: once a node has it, it must not be
+ // removed.
+ // NOTE: Should only be used on nsIContent nodes
+ NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE = NODE_FLAG_BIT(2),
+
+ // Whether this node is the root of a native anonymous (from the perspective
+ // of its parent) subtree. This flag is set-once: once a node has it, it
+ // must not be removed.
+ // NOTE: Should only be used on nsIContent nodes
+ NODE_IS_NATIVE_ANONYMOUS_ROOT = NODE_FLAG_BIT(3),
+
+ NODE_IS_EDITABLE = NODE_FLAG_BIT(4),
+
+ // Whether the node participates in a shadow tree.
+ NODE_IS_IN_SHADOW_TREE = NODE_FLAG_BIT(5),
+
+ // Node has an :empty or :-moz-only-whitespace selector
+ NODE_HAS_EMPTY_SELECTOR = NODE_FLAG_BIT(6),
+
+ // A child of the node has a selector such that any insertion,
+ // removal, or appending of children requires restyling the parent, if the
+ // parent is an element. If the parent is the shadow root, the child's
+ // siblings are restyled.
+ NODE_HAS_SLOW_SELECTOR = NODE_FLAG_BIT(7),
+
+ // A child of the node has a :first-child, :-moz-first-node,
+ // :only-child, :last-child or :-moz-last-node selector.
+ NODE_HAS_EDGE_CHILD_SELECTOR = NODE_FLAG_BIT(8),
+
+ // A child of the node has a selector such that any insertion or
+ // removal of children requires restyling later siblings of that
+ // element. Additionally (in this manner it is stronger than
+ // NODE_HAS_SLOW_SELECTOR), if a child's style changes due to any
+ // other content tree changes (e.g., the child changes to or from
+ // matching :empty due to a grandchild insertion or removal), the
+ // child's later siblings must also be restyled.
+ NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS = NODE_FLAG_BIT(9),
+
+ // A child of this node might be matched by :nth-child(.. of <selector>) or
+ // :nth-last-child(.. of <selector>). If a DOM mutation may have caused the
+ // selector to either match or no longer match that child, the child's
+ // siblings are restyled.
+ NODE_HAS_SLOW_SELECTOR_NTH_OF = NODE_FLAG_BIT(10),
+
+ // Set of selector flags that may trigger a restyle as a result of DOM
+ // mutation.
+ NODE_RESTYLE_SELECTOR_FLAGS =
+ NODE_HAS_EMPTY_SELECTOR | NODE_HAS_SLOW_SELECTOR |
+ NODE_HAS_EDGE_CHILD_SELECTOR | NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS |
+ NODE_HAS_SLOW_SELECTOR_NTH_OF,
+
+ // Set if this node tried anchoring a relative selector.
+ NODE_ANCHORS_RELATIVE_SELECTOR = NODE_FLAG_BIT(11),
+
+ // This node needs to go through frame construction to get a frame (or
+ // undisplayed entry).
+ NODE_NEEDS_FRAME = NODE_FLAG_BIT(12),
+
+ // At least one descendant in the flattened tree has NODE_NEEDS_FRAME set.
+ // This should be set on every node on the flattened tree path between the
+ // node(s) with NODE_NEEDS_FRAME and the root content.
+ NODE_DESCENDANTS_NEED_FRAMES = NODE_FLAG_BIT(13),
+
+ // Set if the node has the accesskey attribute set.
+ NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(14),
+
+ // Set if the node has right-to-left directionality
+ NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(15),
+
+ // Set if the node has left-to-right directionality
+ NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(16),
+
+ NODE_ALL_DIRECTION_FLAGS = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL,
+
+ NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(17),
+
+ // Set if the node has a nonce value and a header delivered CSP.
+ NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(18),
+
+ NODE_KEEPS_DOMARENA = NODE_FLAG_BIT(19),
+
+ NODE_MAY_HAVE_ELEMENT_CHILDREN = NODE_FLAG_BIT(20),
+
+ // Remaining bits are node type specific.
+ NODE_TYPE_SPECIFIC_BITS_OFFSET = 21
+};
+
+// Make sure we have space for our bits
+#define ASSERT_NODE_FLAGS_SPACE(n) \
+ static_assert(WRAPPER_CACHE_FLAGS_BITS_USED + (n) <= \
+ sizeof(nsWrapperCache::FlagsType) * 8, \
+ "Not enough space for our bits")
+ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET);
+
+/**
+ * Class used to detect unexpected mutations. To use the class create an
+ * nsMutationGuard on the stack before unexpected mutations could occur.
+ * You can then at any time call Mutated to check if any unexpected mutations
+ * have occurred.
+ */
+class nsMutationGuard {
+ public:
+ nsMutationGuard() { mStartingGeneration = sGeneration; }
+
+ /**
+ * Returns true if any unexpected mutations have occurred. You can pass in
+ * an 8-bit ignore count to ignore a number of expected mutations.
+ *
+ * We don't need to care about overflow because subtraction of uint64_t's is
+ * finding the difference between two elements of the group Z < 2^64. Once
+ * we know the difference between two elements we only need to check that is
+ * less than the given number of mutations to know less than that many
+ * mutations occured. Assuming constant 1ns mutations it would take 584
+ * years for sGeneration to fully wrap around so we can ignore a guard living
+ * through a full wrap around.
+ */
+ bool Mutated(uint8_t aIgnoreCount) {
+ return (sGeneration - mStartingGeneration) > aIgnoreCount;
+ }
+
+ // This function should be called whenever a mutation that we want to keep
+ // track of happen. For now this is only done when children are added or
+ // removed, but we might do it for attribute changes too in the future.
+ static void DidMutate() { sGeneration++; }
+
+ private:
+ // This is the value sGeneration had when the guard was constructed.
+ uint64_t mStartingGeneration;
+
+ // This value is incremented on every mutation, for the life of the process.
+ static uint64_t sGeneration;
+};
+
+/**
+ * A class that implements nsIWeakReference
+ */
+class nsNodeWeakReference final : public nsIWeakReference {
+ public:
+ explicit nsNodeWeakReference(nsINode* aNode);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIWeakReference
+ NS_DECL_NSIWEAKREFERENCE
+
+ void NoticeNodeDestruction() { mObject = nullptr; }
+
+ private:
+ ~nsNodeWeakReference();
+};
+
+// This should be used for any nsINode sub-class that has fields of its own
+// that it needs to measure; any sub-class that doesn't use it will inherit
+// AddSizeOfExcludingThis from its super-class. AddSizeOfIncludingThis() need
+// not be defined, it is inherited from nsINode.
+#define NS_DECL_ADDSIZEOFEXCLUDINGTHIS \
+ virtual void AddSizeOfExcludingThis(nsWindowSizes& aSizes, \
+ size_t* aNodeSize) const override;
+
+// IID for the nsINode interface
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_INODE_IID \
+ { \
+ 0x70ba4547, 0x7699, 0x44fc, { \
+ 0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a \
+ } \
+ }
+
+/**
+ * An internal interface that abstracts some DOMNode-related parts that both
+ * nsIContent and Document share. An instance of this interface has a list
+ * of nsIContent children and provides access to them.
+ */
+class nsINode : public mozilla::dom::EventTarget {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ void AssertInvariantsOnNodeInfoChange();
+#endif
+ public:
+ using BoxQuadOptions = mozilla::dom::BoxQuadOptions;
+ using ConvertCoordinateOptions = mozilla::dom::ConvertCoordinateOptions;
+ using DocGroup = mozilla::dom::DocGroup;
+ using Document = mozilla::dom::Document;
+ using DOMPoint = mozilla::dom::DOMPoint;
+ using DOMPointInit = mozilla::dom::DOMPointInit;
+ using DOMQuad = mozilla::dom::DOMQuad;
+ using DOMRectReadOnly = mozilla::dom::DOMRectReadOnly;
+ using OwningNodeOrString = mozilla::dom::OwningNodeOrString;
+ using TextOrElementOrDocument = mozilla::dom::TextOrElementOrDocument;
+ using CallerType = mozilla::dom::CallerType;
+ using ErrorResult = mozilla::ErrorResult;
+
+ // XXXbz Maybe we should codegen a class holding these constants and
+ // inherit from it...
+ static const auto ELEMENT_NODE = mozilla::dom::Node_Binding::ELEMENT_NODE;
+ static const auto ATTRIBUTE_NODE = mozilla::dom::Node_Binding::ATTRIBUTE_NODE;
+ static const auto TEXT_NODE = mozilla::dom::Node_Binding::TEXT_NODE;
+ static const auto CDATA_SECTION_NODE =
+ mozilla::dom::Node_Binding::CDATA_SECTION_NODE;
+ static const auto ENTITY_REFERENCE_NODE =
+ mozilla::dom::Node_Binding::ENTITY_REFERENCE_NODE;
+ static const auto ENTITY_NODE = mozilla::dom::Node_Binding::ENTITY_NODE;
+ static const auto PROCESSING_INSTRUCTION_NODE =
+ mozilla::dom::Node_Binding::PROCESSING_INSTRUCTION_NODE;
+ static const auto COMMENT_NODE = mozilla::dom::Node_Binding::COMMENT_NODE;
+ static const auto DOCUMENT_NODE = mozilla::dom::Node_Binding::DOCUMENT_NODE;
+ static const auto DOCUMENT_TYPE_NODE =
+ mozilla::dom::Node_Binding::DOCUMENT_TYPE_NODE;
+ static const auto DOCUMENT_FRAGMENT_NODE =
+ mozilla::dom::Node_Binding::DOCUMENT_FRAGMENT_NODE;
+ static const auto NOTATION_NODE = mozilla::dom::Node_Binding::NOTATION_NODE;
+ static const auto MAX_NODE_TYPE = NOTATION_NODE;
+
+ void* operator new(size_t aSize, nsNodeInfoManager* aManager);
+ void* operator new(size_t aSize) = delete;
+ void operator delete(void* aPtr);
+
+ template <class T>
+ using Sequence = mozilla::dom::Sequence<T>;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
+
+ // The |aNodeSize| outparam on this function is where the actual node size
+ // value is put. It gets added to the appropriate value within |aSizes| by
+ // AddSizeOfNodeTree().
+ //
+ // Among the sub-classes that inherit (directly or indirectly) from nsINode,
+ // measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - nsGenericHTMLElement: mForm, mFieldSet
+ // - nsGenericHTMLFrameElement: mFrameLoader (bug 672539)
+ // - HTMLBodyElement: mContentStyleRule
+ // - HTMLDataListElement: mOptions
+ // - HTMLFieldSetElement: mElements, mDependentElements, mFirstLegend
+ // - HTMLFormElement: many!
+ // - HTMLFrameSetElement: mRowSpecs, mColSpecs
+ // - HTMLInputElement: mInputData, mFiles, mFileList, mStaticDocfileList
+ // - nsHTMLMapElement: mAreas
+ // - HTMLMediaElement: many!
+ // - nsHTMLOutputElement: mDefaultValue, mTokenList
+ // - nsHTMLRowElement: mCells
+ // - nsHTMLSelectElement: mOptions, mRestoreState
+ // - nsHTMLTableElement: mTBodies, mRows, mTableInheritedAttributes
+ // - nsHTMLTableSectionElement: mRows
+ // - nsHTMLTextAreaElement: mControllers, mState
+ //
+ // The following members don't need to be measured:
+ // - nsIContent: mPrimaryFrame, because it's non-owning and measured elsewhere
+ //
+ virtual void AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const;
+
+ // SizeOfIncludingThis doesn't need to be overridden by sub-classes because
+ // sub-classes of nsINode are guaranteed to be laid out in memory in such a
+ // way that |this| points to the start of the allocated object, even in
+ // methods of nsINode's sub-classes, so aSizes.mState.mMallocSizeOf(this) is
+ // always safe to call no matter which object it was invoked on.
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes, size_t* aNodeSize) const;
+
+ friend class nsNodeWeakReference;
+ friend class nsNodeSupportsWeakRefTearoff;
+ friend class AttrArray;
+
+#ifdef MOZILLA_INTERNAL_API
+ explicit nsINode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+#endif
+
+ virtual ~nsINode();
+
+ bool IsContainerNode() const {
+ return IsElement() || IsDocument() || IsDocumentFragment();
+ }
+
+ /**
+ * Returns true if the node is a HTMLTemplate element.
+ */
+ bool IsTemplateElement() const { return IsHTMLElement(nsGkAtoms::_template); }
+
+ bool IsSlotable() const { return IsElement() || IsText(); }
+
+ /**
+ * Returns true if this is a document node.
+ */
+ bool IsDocument() const {
+ // One less pointer-chase than checking NodeType().
+ return !GetParentNode() && IsInUncomposedDoc();
+ }
+
+ /**
+ * Return this node as a document. Asserts IsDocument().
+ *
+ * This is defined inline in Document.h.
+ */
+ inline Document* AsDocument();
+ inline const Document* AsDocument() const;
+
+ /**
+ * Returns true if this is a document fragment node.
+ */
+ bool IsDocumentFragment() const {
+ return NodeType() == DOCUMENT_FRAGMENT_NODE;
+ }
+
+ virtual bool IsHTMLFormControlElement() const { return false; }
+
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant
+ *
+ * @param aNode must not be nullptr.
+ */
+ bool IsInclusiveDescendantOf(const nsINode* aNode) const;
+
+ /**
+ * https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
+ *
+ * @param aNode must not be nullptr.
+ */
+ bool IsShadowIncludingInclusiveDescendantOf(const nsINode* aNode) const;
+
+ /**
+ * Returns true if the given node is this node or one of its descendants
+ * in the "flat tree."
+ *
+ * @param aNode must not be nullptr.
+ */
+ bool IsInclusiveFlatTreeDescendantOf(const nsINode* aNode) const;
+
+ /**
+ * Return this node as a document fragment. Asserts IsDocumentFragment().
+ *
+ * This is defined inline in DocumentFragment.h.
+ */
+ inline mozilla::dom::DocumentFragment* AsDocumentFragment();
+ inline const mozilla::dom::DocumentFragment* AsDocumentFragment() const;
+
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) final;
+
+ /**
+ * Hook for constructing JS::ubi::Concrete specializations for memory
+ * reporting. Specializations are defined in NodeUbiReporting.h.
+ */
+ virtual void ConstructUbiNode(void* storage) = 0;
+
+ /**
+ * returns true if we are in priviliged code or
+ * layout.css.getBoxQuads.enabled == true.
+ */
+ static bool HasBoxQuadsSupport(JSContext* aCx, JSObject* /* unused */);
+
+ protected:
+ /**
+ * WrapNode is called from WrapObject to actually wrap this node, WrapObject
+ * does some additional checks and fix-up that's common to all nodes. WrapNode
+ * should just call the DOM binding's Wrap function.
+ *
+ * aGivenProto is the prototype to use (or null if the default one should be
+ * used) and should just be passed directly on to the DOM binding's Wrap
+ * function.
+ */
+ virtual JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) = 0;
+
+ public:
+ mozilla::dom::ParentObject GetParentObject()
+ const; // Implemented in Document.h
+
+ /**
+ * Returns the first child of a node or the first child of
+ * a template element's content if the provided node is a
+ * template element.
+ */
+ nsIContent* GetFirstChildOfTemplateOrNode();
+
+ /**
+ * Return the scope chain parent for this node, for use in things
+ * like event handler compilation. Returning null means to use the
+ * global object as the scope chain parent.
+ */
+ virtual nsINode* GetScopeChainParent() const;
+
+ MOZ_CAN_RUN_SCRIPT mozilla::dom::Element* GetParentFlexElement();
+
+ /**
+ * Returns the nearest inclusive open popover for a given node, see
+ * https://html.spec.whatwg.org/multipage/popover.html#nearest-inclusive-open-popover
+ */
+ mozilla::dom::Element* GetNearestInclusiveOpenPopover() const;
+
+ /**
+ * https://html.spec.whatwg.org/multipage/popover.html#nearest-inclusive-target-popover-for-invoker
+ */
+ mozilla::dom::Element* GetNearestInclusiveTargetPopoverForInvoker() const;
+
+ /**
+ * https://html.spec.whatwg.org/multipage/popover.html#popover-target-element
+ */
+ nsGenericHTMLElement* GetEffectivePopoverTargetElement() const;
+
+ /**
+ * https://html.spec.whatwg.org/multipage/popover.html#topmost-clicked-popover
+ */
+ mozilla::dom::Element* GetTopmostClickedPopover() const;
+
+ bool IsNode() const final { return true; }
+
+ NS_IMPL_FROMEVENTTARGET_HELPER(nsINode, IsNode())
+
+ /**
+ * Return whether the node is an Element node. Faster than using `NodeType()`.
+ */
+ bool IsElement() const { return GetBoolFlag(NodeIsElement); }
+
+ virtual bool IsTextControlElement() const { return false; }
+ virtual bool IsGenericHTMLFormControlElementWithState() const {
+ return false;
+ }
+
+ // Returns non-null if this element subclasses `LinkStyle`.
+ virtual const mozilla::dom::LinkStyle* AsLinkStyle() const { return nullptr; }
+ mozilla::dom::LinkStyle* AsLinkStyle() {
+ return const_cast<mozilla::dom::LinkStyle*>(
+ static_cast<const nsINode*>(this)->AsLinkStyle());
+ }
+
+ /**
+ * Return this node as an Element. Should only be used for nodes
+ * for which IsElement() is true. This is defined inline in Element.h.
+ */
+ inline mozilla::dom::Element* AsElement();
+ inline const mozilla::dom::Element* AsElement() const;
+
+ /**
+ * Return whether the node is an nsStyledElement instance or not.
+ */
+ virtual bool IsStyledElement() const { return false; }
+
+ /**
+ * Return this node as nsIContent. Should only be used for nodes for which
+ * IsContent() is true.
+ *
+ * The assertion in nsIContent's constructor makes this safe.
+ */
+ nsIContent* AsContent() {
+ MOZ_ASSERT(IsContent());
+ return reinterpret_cast<nsIContent*>(this);
+ }
+ const nsIContent* AsContent() const {
+ MOZ_ASSERT(IsContent());
+ return reinterpret_cast<const nsIContent*>(this);
+ }
+
+ /*
+ * Return whether the node is a Text node (which might be an actual
+ * textnode, or might be a CDATA section).
+ */
+ bool IsText() const {
+ uint32_t nodeType = NodeType();
+ return nodeType == TEXT_NODE || nodeType == CDATA_SECTION_NODE;
+ }
+
+ /**
+ * Return this node as Text if it is one, otherwise null. This is defined
+ * inline in Text.h.
+ */
+ inline mozilla::dom::Text* GetAsText();
+ inline const mozilla::dom::Text* GetAsText() const;
+
+ /**
+ * Return this node as Text. Asserts IsText(). This is defined inline in
+ * Text.h.
+ */
+ inline mozilla::dom::Text* AsText();
+ inline const mozilla::dom::Text* AsText() const;
+
+ /*
+ * Return whether the node is a ProcessingInstruction node.
+ */
+ bool IsProcessingInstruction() const {
+ return NodeType() == PROCESSING_INSTRUCTION_NODE;
+ }
+
+ /*
+ * Return whether the node is a CharacterData node (text, cdata,
+ * comment, processing instruction)
+ */
+ bool IsCharacterData() const {
+ uint32_t nodeType = NodeType();
+ return nodeType == TEXT_NODE || nodeType == CDATA_SECTION_NODE ||
+ nodeType == PROCESSING_INSTRUCTION_NODE || nodeType == COMMENT_NODE;
+ }
+
+ /**
+ * Return whether the node is a Comment node.
+ */
+ bool IsComment() const { return NodeType() == COMMENT_NODE; }
+
+ /**
+ * Return whether the node is an Attr node.
+ */
+ bool IsAttr() const { return NodeType() == ATTRIBUTE_NODE; }
+
+ /**
+ * Return if this node has any children.
+ */
+ bool HasChildren() const { return !!mFirstChild; }
+
+ /**
+ * Get the number of children
+ * @return the number of children
+ */
+ uint32_t GetChildCount() const { return mChildCount; }
+
+ /**
+ * NOTE: this function is going to be removed soon (hopefully!) Don't use it
+ * in new code.
+ *
+ * Get a child by index
+ * @param aIndex the index of the child to get
+ * @return the child, or null if index out of bounds
+ */
+ nsIContent* GetChildAt_Deprecated(uint32_t aIndex) const;
+
+ /**
+ * Get the index of a child within this content.
+ *
+ * @param aPossibleChild the child to get the index of.
+ * @return the index of the child, or Nothing if not a child. Be aware that
+ * anonymous children (e.g. a <div> child of an <input> element) will
+ * result in Nothing.
+ *
+ * If the return value is Some, then calling GetChildAt_Deprecated() with
+ * that value will return aPossibleChild.
+ */
+ mozilla::Maybe<uint32_t> ComputeIndexOf(const nsINode* aPossibleChild) const;
+
+ /**
+ * Get the index of this within parent node (ComputeIndexInParentNode) or
+ * parent content (nsIContent) node (ComputeIndexInParentContent).
+ *
+ * @return the index of this node in the parent, or Nothing there is no
+ * parent (content) node or the parent does not have this node anymore
+ * (e.g., being removed from the parent). Be aware that anonymous
+ * children (e.g. a <div> child of an <input> element) will result in
+ * Nothing.
+ *
+ * If the return value is Some, then calling GetChildAt_Deprecated() with
+ * that value will return this.
+ */
+ mozilla::Maybe<uint32_t> ComputeIndexInParentNode() const;
+ mozilla::Maybe<uint32_t> ComputeIndexInParentContent() const;
+
+ /**
+ * Get the index of a child within this content.
+ *
+ * @param aPossibleChild the child to get the index of.
+ * @return the index of the child, or -1 if not a child. Be aware that
+ * anonymous children (e.g. a <div> child of an <input> element) will
+ * result in -1.
+ *
+ * If the return value is not -1, then calling GetChildAt_Deprecated() with
+ * that value will return aPossibleChild.
+ */
+ int32_t ComputeIndexOf_Deprecated(const nsINode* aPossibleChild) const;
+
+ /**
+ * Returns the "node document" of this node.
+ *
+ * https://dom.spec.whatwg.org/#concept-node-document
+ *
+ * Note that in the case that this node is a document node this method
+ * will return |this|. That is different to the Node.ownerDocument DOM
+ * attribute (implemented by nsINode::GetOwnerDocument) which is specified to
+ * be null in that case:
+ *
+ * https://dom.spec.whatwg.org/#dom-node-ownerdocument
+ *
+ * For all other cases OwnerDoc and GetOwnerDocument behave identically.
+ */
+ Document* OwnerDoc() const MOZ_NONNULL_RETURN {
+ return mNodeInfo->GetDocument();
+ }
+
+ /**
+ * Return the "owner document" of this node as an nsINode*. Implemented
+ * in Document.h.
+ */
+ inline nsINode* OwnerDocAsNode() const MOZ_NONNULL_RETURN;
+
+ /**
+ * Returns true if the content has an ancestor that is a document.
+ *
+ * @return whether this content is in a document tree
+ */
+ bool IsInUncomposedDoc() const { return GetBoolFlag(IsInDocument); }
+
+ /**
+ * Get the document that this content is currently in, if any. This will be
+ * null if the content has no ancestor that is a document.
+ *
+ * @return the current document
+ */
+
+ Document* GetUncomposedDoc() const {
+ return IsInUncomposedDoc() ? OwnerDoc() : nullptr;
+ }
+
+ /**
+ * Returns true if we're connected, and thus GetComposedDoc() would return a
+ * non-null value.
+ */
+ bool IsInComposedDoc() const { return GetBoolFlag(IsConnected); }
+
+ /**
+ * This method returns the owner document if the node is connected to it
+ * (as defined in the DOM spec), otherwise it returns null.
+ * In other words, returns non-null even in the case the node is in
+ * Shadow DOM, if there is a possibly shadow boundary crossing path from
+ * the node to its owner document.
+ */
+ Document* GetComposedDoc() const {
+ return IsInComposedDoc() ? OwnerDoc() : nullptr;
+ }
+
+ /**
+ * Returns OwnerDoc() if the node is in uncomposed document and ShadowRoot if
+ * the node is in Shadow DOM.
+ */
+ mozilla::dom::DocumentOrShadowRoot* GetContainingDocumentOrShadowRoot() const;
+
+ /**
+ * Returns OwnerDoc() if the node is in uncomposed document and ShadowRoot if
+ * the node is in Shadow DOM and is in composed document.
+ */
+ mozilla::dom::DocumentOrShadowRoot* GetUncomposedDocOrConnectedShadowRoot()
+ const;
+
+ /**
+ * To be called when reference count of the node drops to zero.
+ */
+ void LastRelease();
+
+ /**
+ * The values returned by this function are the ones defined for
+ * Node.nodeType
+ */
+ uint16_t NodeType() const { return mNodeInfo->NodeType(); }
+ const nsString& NodeName() const { return mNodeInfo->NodeName(); }
+ const nsString& LocalName() const { return mNodeInfo->LocalName(); }
+
+ /**
+ * Get the NodeInfo for this element
+ * @return the nodes node info
+ */
+ inline mozilla::dom::NodeInfo* NodeInfo() const { return mNodeInfo; }
+
+ /**
+ * Called when we have been adopted, and the information of the
+ * node has been changed.
+ *
+ * The new document can be reached via OwnerDoc().
+ *
+ * If you override this method,
+ * please call up to the parent NodeInfoChanged.
+ *
+ * If you change this, change also the similar method in Link.
+ */
+ virtual void NodeInfoChanged(Document* aOldDoc) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ AssertInvariantsOnNodeInfoChange();
+#endif
+ }
+
+ inline bool IsInNamespace(int32_t aNamespace) const {
+ return mNodeInfo->NamespaceID() == aNamespace;
+ }
+
+ /**
+ * Returns the DocGroup of the "node document" of this node.
+ */
+ DocGroup* GetDocGroup() const;
+
+ /**
+ * Print a debugger friendly descriptor of this element. This will describe
+ * the position of this element in the document.
+ */
+ friend std::ostream& operator<<(std::ostream& aStream, const nsINode& aNode);
+
+ protected:
+ // These 2 methods are useful for the recursive templates IsHTMLElement,
+ // IsSVGElement, etc.
+ inline bool IsNodeInternal() const { return false; }
+
+ template <typename First, typename... Args>
+ inline bool IsNodeInternal(First aFirst, Args... aArgs) const {
+ return mNodeInfo->Equals(aFirst) || IsNodeInternal(aArgs...);
+ }
+
+ public:
+ inline bool IsHTMLElement() const {
+ return IsElement() && IsInNamespace(kNameSpaceID_XHTML);
+ }
+
+ inline bool IsHTMLElement(const nsAtom* aTag) const {
+ return IsElement() && mNodeInfo->Equals(aTag, kNameSpaceID_XHTML);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfHTMLElements(First aFirst, Args... aArgs) const {
+ return IsHTMLElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ inline bool IsSVGElement() const {
+ return IsElement() && IsInNamespace(kNameSpaceID_SVG);
+ }
+
+ inline bool IsSVGElement(const nsAtom* aTag) const {
+ return IsElement() && mNodeInfo->Equals(aTag, kNameSpaceID_SVG);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfSVGElements(First aFirst, Args... aArgs) const {
+ return IsSVGElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ virtual bool IsSVGAnimationElement() const { return false; }
+ virtual bool IsSVGGeometryElement() const { return false; }
+ virtual bool IsSVGGraphicsElement() const { return false; }
+
+ inline bool IsXULElement() const {
+ return IsElement() && IsInNamespace(kNameSpaceID_XUL);
+ }
+
+ inline bool IsXULElement(const nsAtom* aTag) const {
+ return IsElement() && mNodeInfo->Equals(aTag, kNameSpaceID_XUL);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfXULElements(First aFirst, Args... aArgs) const {
+ return IsXULElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ inline bool IsMathMLElement() const {
+ return IsElement() && IsInNamespace(kNameSpaceID_MathML);
+ }
+
+ inline bool IsMathMLElement(const nsAtom* aTag) const {
+ return IsElement() && mNodeInfo->Equals(aTag, kNameSpaceID_MathML);
+ }
+
+ template <typename First, typename... Args>
+ inline bool IsAnyOfMathMLElements(First aFirst, Args... aArgs) const {
+ return IsMathMLElement() && IsNodeInternal(aFirst, aArgs...);
+ }
+
+ bool IsShadowRoot() const {
+ const bool isShadowRoot = IsInShadowTree() && !GetParentNode();
+ MOZ_ASSERT_IF(isShadowRoot, IsDocumentFragment());
+ return isShadowRoot;
+ }
+
+ bool IsHTMLHeadingElement() const {
+ return IsAnyOfHTMLElements(nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3,
+ nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6);
+ }
+
+ /**
+ * Insert a content node before another or at the end.
+ * This method handles calling BindToTree on the child appropriately.
+ *
+ * @param aKid the content to insert
+ * @param aBeforeThis an existing node. Use nullptr if you want to
+ * add aKid at the end.
+ * @param aNotify whether to notify the document (current document for
+ * nsIContent, and |this| for Document) that the insert has occurred
+ * @param aRv The error, if any.
+ * Throw NS_ERROR_DOM_HIERARCHY_REQUEST_ERR if one attempts to have
+ * more than one element node as a child of a document. Doing this
+ * will also assert -- you shouldn't be doing it! Check with
+ * Document::GetRootElement() first if you're not sure. Apart from
+ * this one constraint, this doesn't do any checking on whether aKid is
+ * a valid child of |this|.
+ * Throw NS_ERROR_OUT_OF_MEMORY in some cases (from BindToTree).
+ */
+ virtual void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
+ bool aNotify, mozilla::ErrorResult& aRv);
+
+ /**
+ * Append a content node to the end of the child list. This method handles
+ * calling BindToTree on the child appropriately.
+ *
+ * @param aKid the content to append
+ * @param aNotify whether to notify the document (current document for
+ * nsIContent, and |this| for Document) that the append has occurred
+ * @param aRv The error, if any.
+ * Throw NS_ERROR_DOM_HIERARCHY_REQUEST_ERR if one attempts to have
+ * more than one element node as a child of a document. Doing this
+ * will also assert -- you shouldn't be doing it! Check with
+ * Document::GetRootElement() first if you're not sure. Apart from
+ * this one constraint, this doesn't do any checking on whether aKid is
+ * a valid child of |this|.
+ * Throw NS_ERROR_OUT_OF_MEMORY in some cases (from BindToTree).
+ */
+ void AppendChildTo(nsIContent* aKid, bool aNotify,
+ mozilla::ErrorResult& aRv) {
+ InsertChildBefore(aKid, nullptr, aNotify, aRv);
+ }
+
+ /**
+ * Remove a child from this node. This method handles calling UnbindFromTree
+ * on the child appropriately.
+ *
+ * @param aKid the content to remove
+ * @param aNotify whether to notify the document (current document for
+ * nsIContent, and |this| for Document) that the remove has occurred
+ */
+ virtual void RemoveChildNode(nsIContent* aKid, bool aNotify);
+
+ /**
+ * Get a property associated with this node.
+ *
+ * @param aPropertyName name of property to get.
+ * @param aStatus out parameter for storing resulting status.
+ * Set to NS_PROPTABLE_PROP_NOT_THERE if the property
+ * is not set.
+ * @return the property. Null if the property is not set
+ * (though a null return value does not imply the
+ * property was not set, i.e. it can be set to null).
+ */
+ void* GetProperty(const nsAtom* aPropertyName,
+ nsresult* aStatus = nullptr) const;
+
+ /**
+ * Set a property to be associated with this node. This will overwrite an
+ * existing value if one exists. The existing value is destroyed using the
+ * destructor function given when that value was set.
+ *
+ * @param aPropertyName name of property to set.
+ * @param aValue new value of property.
+ * @param aDtor destructor function to be used when this property
+ * is destroyed.
+ * @param aTransfer if true the property will not be deleted when the
+ * ownerDocument of the node changes, if false it
+ * will be deleted.
+ *
+ * @return NS_PROPTABLE_PROP_OVERWRITTEN (success value) if the property
+ * was already set
+ * @throws NS_ERROR_OUT_OF_MEMORY if that occurs
+ */
+ nsresult SetProperty(nsAtom* aPropertyName, void* aValue,
+ NSPropertyDtorFunc aDtor = nullptr,
+ bool aTransfer = false);
+
+ /**
+ * A generic destructor for property values allocated with new.
+ */
+ template <class T>
+ static void DeleteProperty(void*, nsAtom*, void* aPropertyValue, void*) {
+ delete static_cast<T*>(aPropertyValue);
+ }
+
+ /**
+ * Removes a property associated with this node. The value is destroyed using
+ * the destruction function given when that value was set.
+ *
+ * @param aPropertyName name of property to destroy.
+ */
+ void RemoveProperty(const nsAtom* aPropertyName);
+
+ /**
+ * Take a property associated with this node. The value will not be destroyed
+ * but rather returned. It is the caller's responsibility to destroy the value
+ * after that point.
+ *
+ * @param aPropertyName name of property to unset.
+ * @param aStatus out parameter for storing resulting status.
+ * Set to NS_PROPTABLE_PROP_NOT_THERE if the property
+ * is not set.
+ * @return the property. Null if the property is not set
+ * (though a null return value does not imply the
+ * property was not set, i.e. it can be set to null).
+ */
+ void* TakeProperty(const nsAtom* aPropertyName, nsresult* aStatus = nullptr);
+
+ bool HasProperties() const { return HasFlag(NODE_HAS_PROPERTIES); }
+
+ /**
+ * Return the principal of this node. This is guaranteed to never be a null
+ * pointer.
+ */
+ nsIPrincipal* NodePrincipal() const {
+ return mNodeInfo->NodeInfoManager()->DocumentPrincipal();
+ }
+
+ /**
+ * Return the CSP of this node's document, if any.
+ */
+ nsIContentSecurityPolicy* GetCsp() const;
+
+ /**
+ * Get the parent nsIContent for this node.
+ * @return the parent, or null if no parent or the parent is not an nsIContent
+ */
+ nsIContent* GetParent() const {
+ return MOZ_LIKELY(GetBoolFlag(ParentIsContent)) ? mParent->AsContent()
+ : nullptr;
+ }
+
+ /**
+ * Get the parent nsINode for this node. This can be either an nsIContent, a
+ * Document or an Attr.
+ * @return the parent node
+ */
+ nsINode* GetParentNode() const { return mParent; }
+
+ private:
+ nsIContent* DoGetShadowHost() const;
+
+ public:
+ nsINode* GetParentOrShadowHostNode() const {
+ if (MOZ_LIKELY(mParent)) {
+ return mParent;
+ }
+ // We could put this in nsIContentInlines.h or such to avoid this
+ // reinterpret_cast, but it doesn't seem worth it.
+ return IsInShadowTree() ? reinterpret_cast<nsINode*>(DoGetShadowHost())
+ : nullptr;
+ }
+
+ enum FlattenedParentType { eNotForStyle, eForStyle };
+
+ /**
+ * Returns the node that is the parent of this node in the flattened
+ * tree. This differs from the normal parent if the node is filtered
+ * into an insertion point, or if the node is a direct child of a
+ * shadow root.
+ *
+ * @return the flattened tree parent
+ */
+ inline nsINode* GetFlattenedTreeParentNode() const;
+
+ nsINode* GetFlattenedTreeParentNodeNonInline() const;
+
+ /**
+ * Like GetFlattenedTreeParentNode, but returns the document for any native
+ * anonymous content that was generated for ancestor frames of the document
+ * element's primary frame, such as scrollbar elements created by the root
+ * scroll frame.
+ */
+ inline nsINode* GetFlattenedTreeParentNodeForStyle() const;
+
+ inline mozilla::dom::Element* GetFlattenedTreeParentElement() const;
+ inline mozilla::dom::Element* GetFlattenedTreeParentElementForStyle() const;
+
+ /**
+ * Get the parent nsINode for this node if it is an Element.
+ *
+ * Defined inline in Element.h
+ *
+ * @return the parent node
+ */
+ inline mozilla::dom::Element* GetParentElement() const;
+
+ /**
+ * Get the parent Element of this node, traversing over a ShadowRoot
+ * to its host if necessary.
+ */
+ mozilla::dom::Element* GetParentElementCrossingShadowRoot() const;
+
+ /**
+ * Get closest element node for the node. Meaning that if the node is an
+ * element node, returns itself. Otherwise, returns parent element or null.
+ */
+ inline mozilla::dom::Element* GetAsElementOrParentElement() const;
+
+ /**
+ * Get the root of the subtree this node belongs to. This never returns
+ * null. It may return 'this' (e.g. for document nodes, and nodes that
+ * are the roots of disconnected subtrees).
+ */
+ nsINode* SubtreeRoot() const;
+
+ /*
+ * Get context object's shadow-including root if options's composed is true,
+ * and context object's root otherwise.
+ */
+ nsINode* GetRootNode(const mozilla::dom::GetRootNodeOptions& aOptions);
+
+ virtual mozilla::EventListenerManager* GetExistingListenerManager()
+ const override;
+ virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
+
+ mozilla::Maybe<mozilla::dom::EventCallbackDebuggerNotificationType>
+ GetDebuggerNotificationType() const override;
+
+ bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
+
+ virtual bool IsApzAware() const override;
+
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
+ virtual nsIGlobalObject* GetOwnerGlobal() const override;
+
+ using mozilla::dom::EventTarget::DispatchEvent;
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool DispatchEvent(
+ mozilla::dom::Event& aEvent, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
+
+ /**
+ * Adds a mutation observer to be notified when this node, or any of its
+ * descendants, are modified. The node will hold a weak reference to the
+ * observer, which means that it is the responsibility of the observer to
+ * remove itself in case it dies before the node. If an observer is added
+ * while observers are being notified, it may also be notified. In general,
+ * adding observers while inside a notification is not a good idea. An
+ * observer that is already observing the node must not be added without
+ * being removed first.
+ *
+ * For mutation observers that implement nsIAnimationObserver, use
+ * AddAnimationObserver instead.
+ */
+ void AddMutationObserver(nsIMutationObserver* aMutationObserver) {
+ nsSlots* s = Slots();
+ if (aMutationObserver) {
+ NS_ASSERTION(!s->mMutationObservers.contains(aMutationObserver),
+ "Observer already in the list");
+
+ s->mMutationObservers.pushBack(aMutationObserver);
+ }
+ }
+
+ void AddMutationObserver(nsMultiMutationObserver* aMultiMutationObserver);
+
+ /**
+ * Same as above, but only adds the observer if its not observing
+ * the node already.
+ *
+ * For mutation observers that implement nsIAnimationObserver, use
+ * AddAnimationObserverUnlessExists instead.
+ */
+ void AddMutationObserverUnlessExists(nsIMutationObserver* aMutationObserver) {
+ nsSlots* s = Slots();
+ if (aMutationObserver &&
+ !s->mMutationObservers.contains(aMutationObserver)) {
+ s->mMutationObservers.pushBack(aMutationObserver);
+ }
+ }
+
+ void AddMutationObserverUnlessExists(
+ nsMultiMutationObserver* aMultiMutationObserver);
+ /**
+ * Same as AddMutationObserver, but for nsIAnimationObservers. This
+ * additionally records on the document that animation observers have
+ * been registered, which is used to determine whether notifications
+ * must be fired when animations are added, removed or changed.
+ */
+ void AddAnimationObserver(nsIAnimationObserver* aAnimationObserver);
+
+ /**
+ * Same as above, but only adds the observer if its not observing
+ * the node already.
+ */
+ void AddAnimationObserverUnlessExists(
+ nsIAnimationObserver* aAnimationObserver);
+
+ /**
+ * Removes a mutation observer.
+ */
+ void RemoveMutationObserver(nsIMutationObserver* aMutationObserver) {
+ nsSlots* s = GetExistingSlots();
+ if (s) {
+ s->mMutationObservers.remove(aMutationObserver);
+ }
+ }
+
+ void RemoveMutationObserver(nsMultiMutationObserver* aMultiMutationObserver);
+
+ mozilla::SafeDoublyLinkedList<nsIMutationObserver>* GetMutationObservers();
+
+ /**
+ * Helper methods to access ancestor node(s) of type T.
+ * The implementations of the methods are in mozilla/dom/AncestorIterator.h.
+ */
+ template <typename T>
+ inline mozilla::dom::AncestorsOfTypeIterator<T> AncestorsOfType() const;
+
+ template <typename T>
+ inline mozilla::dom::InclusiveAncestorsOfTypeIterator<T>
+ InclusiveAncestorsOfType() const;
+
+ template <typename T>
+ inline mozilla::dom::FlatTreeAncestorsOfTypeIterator<T>
+ FlatTreeAncestorsOfType() const;
+
+ template <typename T>
+ inline mozilla::dom::InclusiveFlatTreeAncestorsOfTypeIterator<T>
+ InclusiveFlatTreeAncestorsOfType() const;
+
+ template <typename T>
+ T* FirstAncestorOfType() const;
+
+ private:
+ /**
+ * Walks aNode, its attributes and, if aDeep is true, its descendant nodes.
+ * If aClone is true the nodes will be cloned. If aNewNodeInfoManager is
+ * not null, it is used to create new nodeinfos for the nodes. Also reparents
+ * the XPConnect wrappers for the nodes into aReparentScope if non-null.
+ *
+ * @param aNode Node to adopt/clone.
+ * @param aClone If true the node will be cloned and the cloned node will
+ * be returned.
+ * @param aDeep If true the function will be called recursively on
+ * descendants of the node
+ * @param aNewNodeInfoManager The nodeinfo manager to use to create new
+ * nodeinfos for aNode and its attributes and
+ * descendants. May be null if the nodeinfos
+ * shouldn't be changed.
+ * @param aReparentScope Scope into which wrappers should be reparented, or
+ * null if no reparenting should be done.
+ * @param aParent If aClone is true the cloned node will be appended to
+ * aParent's children. May be null. If not null then aNode
+ * must be an nsIContent.
+ * @param aError The error, if any.
+ *
+ * @return If aClone is true then the cloned node will be returned,
+ * unless an error occurred. In error conditions, null
+ * will be returned.
+ */
+ static already_AddRefed<nsINode> CloneAndAdopt(
+ nsINode* aNode, bool aClone, bool aDeep,
+ nsNodeInfoManager* aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope, nsINode* aParent,
+ mozilla::ErrorResult& aError);
+
+ public:
+ /**
+ * Walks the node, its attributes and descendant nodes. If aNewNodeInfoManager
+ * is not null, it is used to create new nodeinfos for the nodes. Also
+ * reparents the XPConnect wrappers for the nodes into aReparentScope if
+ * non-null.
+ *
+ * @param aNewNodeInfoManager The nodeinfo manager to use to create new
+ * nodeinfos for the node and its attributes and
+ * descendants. May be null if the nodeinfos
+ * shouldn't be changed.
+ * @param aReparentScope New scope for the wrappers, or null if no reparenting
+ * should be done.
+ * @param aError The error, if any.
+ */
+ void Adopt(nsNodeInfoManager* aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope,
+ mozilla::ErrorResult& aError);
+
+ /**
+ * Clones the node, its attributes and, if aDeep is true, its descendant nodes
+ * If aNewNodeInfoManager is not null, it is used to create new nodeinfos for
+ * the clones.
+ *
+ * @param aDeep If true the function will be called recursively on
+ * descendants of the node
+ * @param aNewNodeInfoManager The nodeinfo manager to use to create new
+ * nodeinfos for the node and its attributes and
+ * descendants. May be null if the nodeinfos
+ * shouldn't be changed.
+ * @param aError The error, if any.
+ *
+ * @return The newly created node. Null in error conditions.
+ */
+ already_AddRefed<nsINode> Clone(bool aDeep,
+ nsNodeInfoManager* aNewNodeInfoManager,
+ mozilla::ErrorResult& aError);
+
+ /**
+ * Clones this node. This needs to be overriden by all node classes. aNodeInfo
+ * should be identical to this node's nodeInfo, except for the document which
+ * may be different. When cloning an element, all attributes of the element
+ * will be cloned. The children of the node will not be cloned.
+ *
+ * @param aNodeInfo the nodeinfo to use for the clone
+ * @param aResult the clone
+ */
+ virtual nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const = 0;
+
+ // This class can be extended by subclasses that wish to store more
+ // information in the slots.
+ class nsSlots {
+ public:
+ nsSlots();
+
+ // If needed we could remove the vtable pointer this dtor causes by
+ // putting a DestroySlots function on nsINode
+ virtual ~nsSlots();
+
+ virtual void Traverse(nsCycleCollectionTraversalCallback&);
+ virtual void Unlink(nsINode&);
+
+ /**
+ * A list of mutation observers
+ */
+ mozilla::SafeDoublyLinkedList<nsIMutationObserver> mMutationObservers;
+
+ /**
+ * An object implementing NodeList for this content (childNodes)
+ * @see NodeList
+ * @see nsGenericHTMLElement::GetChildNodes
+ */
+ RefPtr<nsAttrChildContentList> mChildNodes;
+
+ /**
+ * Weak reference to this node. This is cleared by the destructor of
+ * nsNodeWeakReference.
+ */
+ nsNodeWeakReference* MOZ_NON_OWNING_REF mWeakReference;
+
+ /**
+ * A set of ranges which are in the selection and which have this node as
+ * their endpoints' closest common inclusive ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor). This is
+ * a UniquePtr instead of just a LinkedList, because that prevents us from
+ * pushing DOMSlots up to the next allocation bucket size, at the cost of
+ * some complexity.
+ */
+ mozilla::UniquePtr<mozilla::LinkedList<mozilla::dom::AbstractRange>>
+ mClosestCommonInclusiveAncestorRanges;
+ };
+
+ /**
+ * Functions for managing flags and slots
+ */
+#ifdef DEBUG
+ nsSlots* DebugGetSlots() { return Slots(); }
+#endif
+
+ void SetFlags(FlagsType aFlagsToSet) {
+ NS_ASSERTION(
+ !(aFlagsToSet &
+ (NODE_IS_NATIVE_ANONYMOUS_ROOT | NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
+ NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME |
+ NODE_HAS_BEEN_IN_UA_WIDGET)) ||
+ IsContent(),
+ "Flag only permitted on nsIContent nodes");
+ nsWrapperCache::SetFlags(aFlagsToSet);
+ }
+
+ void UnsetFlags(FlagsType aFlagsToUnset) {
+ NS_ASSERTION(!(aFlagsToUnset & (NODE_HAS_BEEN_IN_UA_WIDGET |
+ NODE_IS_NATIVE_ANONYMOUS_ROOT)),
+ "Trying to unset write-only flags");
+ nsWrapperCache::UnsetFlags(aFlagsToUnset);
+ }
+
+ void SetEditableFlag(bool aEditable) {
+ if (aEditable) {
+ SetFlags(NODE_IS_EDITABLE);
+ } else {
+ UnsetFlags(NODE_IS_EDITABLE);
+ }
+ }
+
+ inline bool IsEditable() const;
+
+ /**
+ * Check if this node is in design mode or not. When this returns true and:
+ * - if this is a Document node, it's the design mode root.
+ * - if this is a content node, it's connected, it's not in a shadow tree
+ * (except shadow tree for UI widget and native anonymous subtree) and its
+ * uncomposed document is in design mode.
+ * Note that returning true does NOT mean the node or its children is
+ * editable. E.g., when this node is in a shadow tree of a UA widget and its
+ * host is in design mode.
+ */
+ inline bool IsInDesignMode() const;
+
+ /**
+ * Returns true if |this| or any of its ancestors is native anonymous.
+ */
+ bool IsInNativeAnonymousSubtree() const {
+ return HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+
+ /**
+ * If |this| or any ancestor is native anonymous, return the root of the
+ * native anonymous subtree. Note that in case of nested native anonymous
+ * content, this returns the innermost root, not the outermost.
+ */
+ nsIContent* GetClosestNativeAnonymousSubtreeRoot() const {
+ if (!IsInNativeAnonymousSubtree()) {
+ MOZ_ASSERT(!HasBeenInUAWidget(), "UA widget implies anonymous");
+ return nullptr;
+ }
+ MOZ_ASSERT(IsContent(), "How did non-content end up in NAC?");
+ if (HasBeenInUAWidget()) {
+ // reinterpret_cast because in this header we don't know ShadowRoot is an
+ // nsIContent. ShadowRoot constructor asserts this is correct.
+ return reinterpret_cast<nsIContent*>(GetContainingShadow());
+ }
+ for (const nsINode* node = this; node; node = node->GetParentNode()) {
+ if (node->IsRootOfNativeAnonymousSubtree()) {
+ return const_cast<nsINode*>(node)->AsContent();
+ }
+ }
+ // FIXME(emilio): This should not happen, usually, but editor removes nodes
+ // in native anonymous subtrees, and we don't clean nodes from the current
+ // event content stack from ContentRemoved, so it can actually happen, see
+ // bug 1510208.
+ NS_WARNING("GetClosestNativeAnonymousSubtreeRoot on disconnected NAC!");
+ return nullptr;
+ }
+
+ /**
+ * If |this| or any ancestor is native anonymous, return the parent of the
+ * native anonymous subtree. Note that in case of nested native anonymous
+ * content, this returns the parent or host of the innermost root, not the
+ * outermost.
+ */
+ nsIContent* GetClosestNativeAnonymousSubtreeRootParentOrHost() const {
+ // We could put this in nsIContentInlines.h or such to avoid this
+ // reinterpret_cast, but it doesn't seem worth it.
+ const auto* root = reinterpret_cast<const nsINode*>(
+ GetClosestNativeAnonymousSubtreeRoot());
+ if (!root) {
+ return nullptr;
+ }
+ if (nsIContent* parent = root->GetParent()) {
+ return parent;
+ }
+ if (MOZ_UNLIKELY(root->IsInShadowTree())) {
+ return root->DoGetShadowHost();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Gets the root of the node tree for this content if it is in a shadow tree.
+ */
+ mozilla::dom::ShadowRoot* GetContainingShadow() const;
+ /**
+ * Gets the shadow host if this content is in a shadow tree. That is, the host
+ * of |GetContainingShadow|, if its not null.
+ *
+ * @return The shadow host, if this is in shadow tree, or null.
+ */
+ nsIContent* GetContainingShadowHost() const;
+
+ bool IsInSVGUseShadowTree() const {
+ return !!GetContainingSVGUseShadowHost();
+ }
+
+ mozilla::dom::SVGUseElement* GetContainingSVGUseShadowHost() const {
+ if (!IsInShadowTree()) {
+ return nullptr;
+ }
+ return DoGetContainingSVGUseShadowHost();
+ }
+
+ // Whether this node has ever been part of a UA widget shadow tree.
+ bool HasBeenInUAWidget() const { return HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET); }
+
+ // True for native anonymous content and for content in UA widgets.
+ // Only nsIContent can fulfill this condition.
+ bool ChromeOnlyAccess() const { return IsInNativeAnonymousSubtree(); }
+
+ const nsIContent* GetChromeOnlyAccessSubtreeRootParent() const {
+ return GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ }
+
+ bool IsInShadowTree() const { return HasFlag(NODE_IS_IN_SHADOW_TREE); }
+
+ /**
+ * Get whether this node is C++-generated anonymous content
+ * @see nsIAnonymousContentCreator
+ * @return whether this content is anonymous
+ */
+ bool IsRootOfNativeAnonymousSubtree() const {
+ NS_ASSERTION(
+ !HasFlag(NODE_IS_NATIVE_ANONYMOUS_ROOT) || IsInNativeAnonymousSubtree(),
+ "Some flags seem to be missing!");
+ return HasFlag(NODE_IS_NATIVE_ANONYMOUS_ROOT);
+ }
+
+ // Whether this node is the root of a ChromeOnlyAccess DOM subtree.
+ bool IsRootOfChromeAccessOnlySubtree() const {
+ return IsRootOfNativeAnonymousSubtree();
+ }
+
+ /**
+ * Returns true if |this| node is the closest common inclusive ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) of the
+ * start/end nodes of a Range in a Selection or a descendant of such a common
+ * ancestor. This node is definitely not selected when |false| is returned,
+ * but it may or may not be selected when |true| is returned.
+ */
+ bool IsMaybeSelected() const {
+ return IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection() ||
+ IsClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+
+ /**
+ * Return true if any part of (this, aStartOffset) .. (this, aEndOffset)
+ * overlaps any nsRange in
+ * GetClosestCommonInclusiveAncestorForRangeInSelection ranges (i.e.
+ * where this is a descendant of a range's common inclusive ancestor node).
+ * If a nsRange starts in (this, aEndOffset) or if it ends in
+ * (this, aStartOffset) then it is non-overlapping and the result is false
+ * for that nsRange. Collapsed ranges always counts as non-overlapping.
+ *
+ * @param aStartOffset has to be less or equal to aEndOffset.
+ */
+ bool IsSelected(uint32_t aStartOffset, uint32_t aEndOffset) const;
+
+ /**
+ * Get the root element of the text editor associated with this node or the
+ * root element of the text editor of the ancestor 'TextControlElement' if
+ * this is in its native anonymous subtree. I.e., this returns anonymous
+ * `<div>` element of a `TextEditor`. Note that this can be used only for
+ * getting root content of `<input>` or `<textarea>`. I.e., this method
+ * doesn't support HTML editors. Note that this may create a `TextEditor`
+ * instance, and it means that the `TextEditor` may modify its native
+ * anonymous subtree and may run selection listeners.
+ */
+ MOZ_CAN_RUN_SCRIPT mozilla::dom::Element* GetAnonymousRootElementOfTextEditor(
+ mozilla::TextEditor** aTextEditor = nullptr);
+
+ /**
+ * Get the nearest selection root, ie. the node that will be selected if the
+ * user does "Select All" while the focus is in this node. Note that if this
+ * node is not in an editor, the result comes from the nsFrameSelection that
+ * is related to aPresShell, so the result might not be the ancestor of this
+ * node. Be aware that if this node and the computed selection limiter are
+ * not in same subtree, this returns the root content of the closeset subtree.
+ */
+ MOZ_CAN_RUN_SCRIPT nsIContent* GetSelectionRootContent(
+ mozilla::PresShell* aPresShell);
+
+ nsINodeList* ChildNodes();
+
+ nsIContent* GetFirstChild() const { return mFirstChild; }
+
+ nsIContent* GetLastChild() const;
+
+ /**
+ * Implementation is in Document.h, because it needs to cast from
+ * Document* to nsINode*.
+ */
+ Document* GetOwnerDocument() const;
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Normalize();
+
+ /**
+ * Get the base URI for any relative URIs within this piece of
+ * content. Generally, this is the document's base URI, but certain
+ * content carries a local base for backward compatibility.
+ *
+ * @return the base URI. May return null.
+ */
+ virtual nsIURI* GetBaseURI(bool aTryUseXHRDocBaseURI = false) const = 0;
+ nsIURI* GetBaseURIObject() const;
+
+ /**
+ * Return true if the node may be apz aware. There are two cases. One is that
+ * the node is apz aware (such as HTMLInputElement with number type). The
+ * other is that the node has apz aware listeners. This is a non-virtual
+ * function which calls IsNodeApzAwareInternal only when the MayBeApzAware is
+ * set. We check the details in IsNodeApzAwareInternal which may be overriden
+ * by child classes
+ */
+ bool IsNodeApzAware() const {
+ return NodeMayBeApzAware() ? IsNodeApzAwareInternal() : false;
+ }
+
+ /**
+ * Override this function and set the flag MayBeApzAware in case the node has
+ * to let APZC be aware of it. It's used when the node may handle the apz
+ * aware events and may do preventDefault to stop APZC to do default actions.
+ *
+ * For example, instead of scrolling page by APZ, we handle mouse wheel event
+ * in HTMLInputElement with number type as increasing / decreasing its value.
+ */
+ virtual bool IsNodeApzAwareInternal() const;
+
+ void GetTextContent(nsAString& aTextContent, mozilla::OOMReporter& aError) {
+ GetTextContentInternal(aTextContent, aError);
+ }
+ void SetTextContent(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aError) {
+ SetTextContentInternal(aTextContent, aSubjectPrincipal, aError);
+ }
+ void SetTextContent(const nsAString& aTextContent,
+ mozilla::ErrorResult& aError) {
+ SetTextContentInternal(aTextContent, nullptr, aError);
+ }
+
+ mozilla::dom::Element* QuerySelector(const nsACString& aSelector,
+ mozilla::ErrorResult& aResult);
+ already_AddRefed<nsINodeList> QuerySelectorAll(const nsACString& aSelector,
+ mozilla::ErrorResult& aResult);
+
+ protected:
+ // Document and ShadowRoot override this with its own (faster) version.
+ // This should really only be called for elements and document fragments.
+ mozilla::dom::Element* GetElementById(const nsAString& aId);
+
+ void AppendChildToChildList(nsIContent* aKid);
+ void InsertChildToChildList(nsIContent* aKid, nsIContent* aNextSibling);
+ void DisconnectChild(nsIContent* aKid);
+
+ public:
+ void LookupPrefix(const nsAString& aNamespace, nsAString& aResult);
+ bool IsDefaultNamespace(const nsAString& aNamespaceURI) {
+ nsAutoString defaultNamespace;
+ LookupNamespaceURI(u""_ns, defaultNamespace);
+ return aNamespaceURI.Equals(defaultNamespace);
+ }
+ void LookupNamespaceURI(const nsAString& aNamespacePrefix,
+ nsAString& aNamespaceURI);
+
+ nsIContent* GetNextSibling() const { return mNextSibling; }
+ nsIContent* GetPreviousSibling() const;
+
+ /**
+ * Return true if the node is being removed from the parent, it means that
+ * the node still knows the container which it's disconnected from, but the
+ * node has already been removed from the child node chain of the container.
+ * I.e., Return true between a call of DisconnectChild of the parent and
+ * a call of UnbindFromTree of the node.
+ */
+ bool IsBeingRemoved() const {
+ return mParent && !mNextSibling && !mPreviousOrLastSibling;
+ }
+
+ /**
+ * Get the next node in the pre-order tree traversal of the DOM. If
+ * aRoot is non-null, then it must be an ancestor of |this|
+ * (possibly equal to |this|) and only nodes that are descendants of
+ * aRoot, not including aRoot itself, will be returned. Returns
+ * null if there are no more nodes to traverse.
+ */
+ nsIContent* GetNextNode(const nsINode* aRoot = nullptr) const {
+ return GetNextNodeImpl(aRoot, false);
+ }
+
+ /**
+ * Get the next node in the pre-order tree traversal of the DOM but ignoring
+ * the children of this node. If aRoot is non-null, then it must be an
+ * ancestor of |this| (possibly equal to |this|) and only nodes that are
+ * descendants of aRoot, not including aRoot itself, will be returned.
+ * Returns null if there are no more nodes to traverse.
+ */
+ nsIContent* GetNextNonChildNode(const nsINode* aRoot = nullptr) const {
+ return GetNextNodeImpl(aRoot, true);
+ }
+
+ /**
+ * Returns true if 'this' is either document or element or
+ * document fragment and aOther is a descendant in the same
+ * anonymous tree.
+ */
+ bool Contains(const nsINode* aOther) const;
+
+ bool UnoptimizableCCNode() const;
+
+ /**
+ * Fire a DOMNodeRemoved mutation event for all children of this node
+ * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FireNodeRemovedForChildren();
+
+ void QueueDevtoolsAnonymousEvent(bool aIsRemove);
+
+ private:
+ mozilla::dom::SVGUseElement* DoGetContainingSVGUseShadowHost() const;
+
+ nsIContent* GetNextNodeImpl(const nsINode* aRoot,
+ const bool aSkipChildren) const {
+#ifdef DEBUG
+ if (aRoot) {
+ // TODO: perhaps nsINode::IsInclusiveDescendantOf could be used instead.
+ const nsINode* cur = this;
+ for (; cur; cur = cur->GetParentNode())
+ if (cur == aRoot) break;
+ NS_ASSERTION(cur, "aRoot not an ancestor of |this|?");
+ }
+#endif
+ if (!aSkipChildren) {
+ nsIContent* kid = GetFirstChild();
+ if (kid) {
+ return kid;
+ }
+ }
+ if (this == aRoot) {
+ return nullptr;
+ }
+ const nsINode* cur = this;
+ while (1) {
+ nsIContent* next = cur->GetNextSibling();
+ if (next) {
+ return next;
+ }
+ nsINode* parent = cur->GetParentNode();
+ if (parent == aRoot) {
+ return nullptr;
+ }
+ cur = parent;
+ }
+ MOZ_ASSERT_UNREACHABLE("How did we get here?");
+ }
+
+ public:
+ /**
+ * Get the previous nsIContent in the pre-order tree traversal of the DOM. If
+ * aRoot is non-null, then it must be an ancestor of |this|
+ * (possibly equal to |this|) and only nsIContents that are descendants of
+ * aRoot, including aRoot itself, will be returned. Returns
+ * null if there are no more nsIContents to traverse.
+ */
+ nsIContent* GetPreviousContent(const nsINode* aRoot = nullptr) const {
+#ifdef DEBUG
+ if (aRoot) {
+ // TODO: perhaps nsINode::IsInclusiveDescendantOf could be used instead.
+ const nsINode* cur = this;
+ for (; cur; cur = cur->GetParentNode())
+ if (cur == aRoot) break;
+ NS_ASSERTION(cur, "aRoot not an ancestor of |this|?");
+ }
+#endif
+
+ if (this == aRoot) {
+ return nullptr;
+ }
+ nsIContent* cur = this->GetParent();
+ nsIContent* iter = this->GetPreviousSibling();
+ while (iter) {
+ cur = iter;
+ iter = reinterpret_cast<nsINode*>(iter)->GetLastChild();
+ }
+ return cur;
+ }
+
+ /**
+ * Boolean flags
+ */
+ private:
+ enum BooleanFlag {
+ // Set if we're being used from -moz-element
+ NodeHasRenderingObservers,
+ // Set if our parent chain (including this node itself) terminates
+ // in a document
+ IsInDocument,
+ // Set if we're part of the composed doc.
+ // https://dom.spec.whatwg.org/#connected
+ IsConnected,
+ // Set if mParent is an nsIContent
+ ParentIsContent,
+ // Set if this node is an Element
+ NodeIsElement,
+ // Set if the element has a non-empty id attribute. This can in rare
+ // cases lie for nsXMLElement, such as when the node has been moved between
+ // documents with different id mappings.
+ ElementHasID,
+ // Set if the element might have a class.
+ ElementMayHaveClass,
+ // Set if the element might have inline style.
+ ElementMayHaveStyle,
+ // Set if the element has a name attribute set.
+ ElementHasName,
+ // Set if the element has a part attribute set.
+ ElementHasPart,
+ // Set if the element might have a contenteditable attribute set.
+ ElementMayHaveContentEditableAttr,
+ // Set if the node is the closest common inclusive ancestor of the start/end
+ // nodes of a Range that is in a Selection.
+ NodeIsClosestCommonInclusiveAncestorForRangeInSelection,
+ // Set if the node is a descendant of a node with the above bit set.
+ NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection,
+ // Set if CanSkipInCC check has been done for this subtree root.
+ NodeIsCCMarkedRoot,
+ // Maybe set if this node is in black subtree.
+ NodeIsCCBlackTree,
+ // Maybe set if the node is a root of a subtree
+ // which needs to be kept in the purple buffer.
+ NodeIsPurpleRoot,
+ // Set if the element has some style states locked
+ ElementHasLockedStyleStates,
+ // Set if element has pointer locked
+ ElementHasPointerLock,
+ // Set if the node may have DOMMutationObserver attached to it.
+ NodeMayHaveDOMMutationObserver,
+ // Set if node is Content
+ NodeIsContent,
+ // Set if the node has animations or transitions
+ ElementHasAnimations,
+ // Set if node has a dir attribute with a valid value (ltr, rtl, or auto).
+ // Note that we cannot compute this from the dir attribute event state
+ // flags, because we can't use those to distinguish
+ // <bdi dir="some-invalid-value"> and <bdi dir="auto">.
+ NodeHasValidDirAttribute,
+ // Set if the node has dir=auto and has a property pointing to the text
+ // node that determines its direction
+ NodeHasDirAutoSet,
+ // Set if the node is a text node descendant of a node with dir=auto
+ // and has a TextNodeDirectionalityMap property listing the elements whose
+ // direction it determines.
+ NodeHasTextNodeDirectionalityMap,
+ // Set if a node in the node's parent chain has dir=auto.
+ NodeAncestorHasDirAuto,
+ // Set if the node is handling a click.
+ NodeHandlingClick,
+ // Set if the element has a parser insertion mode other than "in body",
+ // per the HTML5 "Parse state" section.
+ ElementHasWeirdParserInsertionMode,
+ // Parser sets this flag if it has notified about the node.
+ ParserHasNotified,
+ // Sets if the node is apz aware or we have apz aware listeners.
+ MayBeApzAware,
+ // Set if the element might have any kind of anonymous content children,
+ // which would not be found through the element's children list.
+ ElementMayHaveAnonymousChildren,
+ // Set if element has CustomElementData.
+ ElementHasCustomElementData,
+ // Set if the element was created from prototype cache and
+ // its l10n attributes haven't been changed.
+ ElementCreatedFromPrototypeAndHasUnmodifiedL10n,
+ // Guard value
+ BooleanFlagCount
+ };
+
+ void SetBoolFlag(BooleanFlag name, bool value) {
+ static_assert(BooleanFlagCount <= 8 * sizeof(mBoolFlags),
+ "Too many boolean flags");
+ mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
+ }
+
+ void SetBoolFlag(BooleanFlag name) {
+ static_assert(BooleanFlagCount <= 8 * sizeof(mBoolFlags),
+ "Too many boolean flags");
+ mBoolFlags |= (1 << name);
+ }
+
+ void ClearBoolFlag(BooleanFlag name) {
+ static_assert(BooleanFlagCount <= 8 * sizeof(mBoolFlags),
+ "Too many boolean flags");
+ mBoolFlags &= ~(1 << name);
+ }
+
+ bool GetBoolFlag(BooleanFlag name) const {
+ static_assert(BooleanFlagCount <= 8 * sizeof(mBoolFlags),
+ "Too many boolean flags");
+ return mBoolFlags & (1 << name);
+ }
+
+ public:
+ bool HasRenderingObservers() const {
+ return GetBoolFlag(NodeHasRenderingObservers);
+ }
+ void SetHasRenderingObservers(bool aValue) {
+ SetBoolFlag(NodeHasRenderingObservers, aValue);
+ }
+ bool IsContent() const { return GetBoolFlag(NodeIsContent); }
+ bool HasID() const { return GetBoolFlag(ElementHasID); }
+ bool MayHaveClass() const { return GetBoolFlag(ElementMayHaveClass); }
+ void SetMayHaveClass() { SetBoolFlag(ElementMayHaveClass); }
+ bool MayHaveStyle() const { return GetBoolFlag(ElementMayHaveStyle); }
+ bool HasName() const { return GetBoolFlag(ElementHasName); }
+ bool HasPartAttribute() const { return GetBoolFlag(ElementHasPart); }
+ bool MayHaveContentEditableAttr() const {
+ return GetBoolFlag(ElementMayHaveContentEditableAttr);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ bool IsClosestCommonInclusiveAncestorForRangeInSelection() const {
+ return GetBoolFlag(NodeIsClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void SetClosestCommonInclusiveAncestorForRangeInSelection() {
+ SetBoolFlag(NodeIsClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void ClearClosestCommonInclusiveAncestorForRangeInSelection() {
+ ClearBoolFlag(NodeIsClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ bool IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection() const {
+ return GetBoolFlag(
+ NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection() {
+ SetBoolFlag(
+ NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+ /**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
+ */
+ void ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection() {
+ ClearBoolFlag(
+ NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection);
+ }
+
+ void SetCCMarkedRoot(bool aValue) { SetBoolFlag(NodeIsCCMarkedRoot, aValue); }
+ bool CCMarkedRoot() const { return GetBoolFlag(NodeIsCCMarkedRoot); }
+ void SetInCCBlackTree(bool aValue) { SetBoolFlag(NodeIsCCBlackTree, aValue); }
+ bool InCCBlackTree() const { return GetBoolFlag(NodeIsCCBlackTree); }
+ void SetIsPurpleRoot(bool aValue) { SetBoolFlag(NodeIsPurpleRoot, aValue); }
+ bool IsPurpleRoot() const { return GetBoolFlag(NodeIsPurpleRoot); }
+ bool MayHaveDOMMutationObserver() {
+ return GetBoolFlag(NodeMayHaveDOMMutationObserver);
+ }
+ void SetMayHaveDOMMutationObserver() {
+ SetBoolFlag(NodeMayHaveDOMMutationObserver, true);
+ }
+ bool HasListenerManager() { return HasFlag(NODE_HAS_LISTENERMANAGER); }
+ bool HasPointerLock() const { return GetBoolFlag(ElementHasPointerLock); }
+ void SetPointerLock() { SetBoolFlag(ElementHasPointerLock); }
+ void ClearPointerLock() { ClearBoolFlag(ElementHasPointerLock); }
+ bool MayHaveAnimations() const { return GetBoolFlag(ElementHasAnimations); }
+ void SetMayHaveAnimations() { SetBoolFlag(ElementHasAnimations); }
+ void ClearMayHaveAnimations() { ClearBoolFlag(ElementHasAnimations); }
+ void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); }
+ void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
+ bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
+ void SetHasDirAutoSet() {
+ MOZ_ASSERT(NodeType() != TEXT_NODE, "SetHasDirAutoSet on text node");
+ SetBoolFlag(NodeHasDirAutoSet);
+ }
+ void ClearHasDirAutoSet() {
+ MOZ_ASSERT(NodeType() != TEXT_NODE, "ClearHasDirAutoSet on text node");
+ ClearBoolFlag(NodeHasDirAutoSet);
+ }
+ bool HasDirAutoSet() const { return GetBoolFlag(NodeHasDirAutoSet); }
+ void SetHasTextNodeDirectionalityMap() {
+ MOZ_ASSERT(NodeType() == TEXT_NODE,
+ "SetHasTextNodeDirectionalityMap on non-text node");
+ SetBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+ void ClearHasTextNodeDirectionalityMap() {
+ MOZ_ASSERT(NodeType() == TEXT_NODE,
+ "ClearHasTextNodeDirectionalityMap on non-text node");
+ ClearBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+ bool HasTextNodeDirectionalityMap() const {
+ MOZ_ASSERT(NodeType() == TEXT_NODE,
+ "HasTextNodeDirectionalityMap on non-text node");
+ return GetBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+
+ void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); }
+ void ClearAncestorHasDirAuto() { ClearBoolFlag(NodeAncestorHasDirAuto); }
+ bool AncestorHasDirAuto() const {
+ return GetBoolFlag(NodeAncestorHasDirAuto);
+ }
+
+ // Implemented in nsIContentInlines.h.
+ inline bool NodeOrAncestorHasDirAuto() const;
+
+ void SetParserHasNotified() { SetBoolFlag(ParserHasNotified); };
+ bool HasParserNotified() { return GetBoolFlag(ParserHasNotified); }
+
+ void SetMayBeApzAware() { SetBoolFlag(MayBeApzAware); }
+ bool NodeMayBeApzAware() const { return GetBoolFlag(MayBeApzAware); }
+
+ void SetMayHaveAnonymousChildren() {
+ SetBoolFlag(ElementMayHaveAnonymousChildren);
+ }
+ bool MayHaveAnonymousChildren() const {
+ return GetBoolFlag(ElementMayHaveAnonymousChildren);
+ }
+
+ void SetHasCustomElementData() { SetBoolFlag(ElementHasCustomElementData); }
+ bool HasCustomElementData() const {
+ return GetBoolFlag(ElementHasCustomElementData);
+ }
+
+ void SetElementCreatedFromPrototypeAndHasUnmodifiedL10n() {
+ SetBoolFlag(ElementCreatedFromPrototypeAndHasUnmodifiedL10n);
+ }
+ bool HasElementCreatedFromPrototypeAndHasUnmodifiedL10n() {
+ return GetBoolFlag(ElementCreatedFromPrototypeAndHasUnmodifiedL10n);
+ }
+ void ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n() {
+ ClearBoolFlag(ElementCreatedFromPrototypeAndHasUnmodifiedL10n);
+ }
+
+ protected:
+ void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
+ void SetIsInDocument() { SetBoolFlag(IsInDocument); }
+ void ClearInDocument() { ClearBoolFlag(IsInDocument); }
+ void SetIsConnected(bool aConnected) { SetBoolFlag(IsConnected, aConnected); }
+ void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
+ void SetIsElement() { SetBoolFlag(NodeIsElement); }
+ void SetHasID() { SetBoolFlag(ElementHasID); }
+ void ClearHasID() { ClearBoolFlag(ElementHasID); }
+ void SetMayHaveStyle() { SetBoolFlag(ElementMayHaveStyle); }
+ void SetHasName() { SetBoolFlag(ElementHasName); }
+ void ClearHasName() { ClearBoolFlag(ElementHasName); }
+ void SetHasPartAttribute(bool aPart) { SetBoolFlag(ElementHasPart, aPart); }
+ void SetMayHaveContentEditableAttr() {
+ SetBoolFlag(ElementMayHaveContentEditableAttr);
+ }
+ void SetHasLockedStyleStates() { SetBoolFlag(ElementHasLockedStyleStates); }
+ void ClearHasLockedStyleStates() {
+ ClearBoolFlag(ElementHasLockedStyleStates);
+ }
+ bool HasLockedStyleStates() const {
+ return GetBoolFlag(ElementHasLockedStyleStates);
+ }
+ void SetHasWeirdParserInsertionMode() {
+ SetBoolFlag(ElementHasWeirdParserInsertionMode);
+ }
+ bool HasWeirdParserInsertionMode() const {
+ return GetBoolFlag(ElementHasWeirdParserInsertionMode);
+ }
+ bool HandlingClick() const { return GetBoolFlag(NodeHandlingClick); }
+ void SetHandlingClick() { SetBoolFlag(NodeHandlingClick); }
+ void ClearHandlingClick() { ClearBoolFlag(NodeHandlingClick); }
+
+ void SetSubtreeRootPointer(nsINode* aSubtreeRoot) {
+ NS_ASSERTION(aSubtreeRoot, "aSubtreeRoot can never be null!");
+ NS_ASSERTION(!(IsContent() && IsInUncomposedDoc()) && !IsInShadowTree(),
+ "Shouldn't be here!");
+ mSubtreeRoot = aSubtreeRoot;
+ }
+
+ void ClearSubtreeRootPointer() { mSubtreeRoot = nullptr; }
+
+ public:
+ // Makes nsINode object to keep aObject alive.
+ void BindObject(nsISupports* aObject);
+ // After calling UnbindObject nsINode object doesn't keep
+ // aObject alive anymore.
+ void UnbindObject(nsISupports* aObject);
+
+ void GenerateXPath(nsAString& aResult);
+
+ already_AddRefed<mozilla::dom::AccessibleNode> GetAccessibleNode();
+
+ /**
+ * Returns the length of this node, as specified at
+ * <http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length>
+ */
+ uint32_t Length() const;
+
+ void GetNodeName(mozilla::dom::DOMString& aNodeName) {
+ const nsString& nodeName = NodeName();
+ aNodeName.SetKnownLiveString(nodeName);
+ }
+ [[nodiscard]] nsresult GetBaseURI(nsAString& aBaseURI) const;
+ // Return the base URI for the document.
+ // The returned value may differ if the document is loaded via XHR, and
+ // when accessed from chrome privileged script and
+ // from content privileged script for compatibility.
+ void GetBaseURIFromJS(nsAString& aBaseURI, CallerType aCallerType,
+ ErrorResult& aRv) const;
+ bool HasChildNodes() const { return HasChildren(); }
+
+ // See nsContentUtils::PositionIsBefore for aThisIndex and aOtherIndex usage.
+ uint16_t CompareDocumentPosition(
+ nsINode& aOther, mozilla::Maybe<uint32_t>* aThisIndex = nullptr,
+ mozilla::Maybe<uint32_t>* aOtherIndex = nullptr) const;
+ void GetNodeValue(nsAString& aNodeValue) { GetNodeValueInternal(aNodeValue); }
+ void SetNodeValue(const nsAString& aNodeValue, mozilla::ErrorResult& aError) {
+ SetNodeValueInternal(aNodeValue, aError);
+ }
+ virtual void GetNodeValueInternal(nsAString& aNodeValue);
+ virtual void SetNodeValueInternal(const nsAString& aNodeValue,
+ mozilla::ErrorResult& aError) {
+ // The DOM spec says that when nodeValue is defined to be null "setting it
+ // has no effect", so we don't throw an exception.
+ }
+ void EnsurePreInsertionValidity(nsINode& aNewChild, nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+ nsINode* InsertBefore(nsINode& aNode, nsINode* aChild,
+ mozilla::ErrorResult& aError) {
+ return ReplaceOrInsertBefore(false, &aNode, aChild, aError);
+ }
+
+ /**
+ * See <https://dom.spec.whatwg.org/#dom-node-appendchild>.
+ */
+ nsINode* AppendChild(nsINode& aNode, mozilla::ErrorResult& aError) {
+ return InsertBefore(aNode, nullptr, aError);
+ }
+
+ nsINode* ReplaceChild(nsINode& aNode, nsINode& aChild,
+ mozilla::ErrorResult& aError) {
+ return ReplaceOrInsertBefore(true, &aNode, &aChild, aError);
+ }
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsINode* RemoveChild(
+ nsINode& aChild, mozilla::ErrorResult& aError);
+ already_AddRefed<nsINode> CloneNode(bool aDeep, mozilla::ErrorResult& aError);
+ bool IsSameNode(nsINode* aNode);
+ bool IsEqualNode(nsINode* aNode);
+ void GetNamespaceURI(nsAString& aNamespaceURI) const {
+ mNodeInfo->GetNamespaceURI(aNamespaceURI);
+ }
+#ifdef MOZILLA_INTERNAL_API
+ void GetPrefix(nsAString& aPrefix) { mNodeInfo->GetPrefix(aPrefix); }
+#endif
+ void GetLocalName(mozilla::dom::DOMString& aLocalName) const {
+ const nsString& localName = LocalName();
+ aLocalName.SetKnownLiveString(localName);
+ }
+
+ nsDOMAttributeMap* GetAttributes();
+
+ // Helper method to remove this node from its parent. This is not exposed
+ // through WebIDL.
+ // Only call this if the node has a parent node.
+ nsresult RemoveFromParent() {
+ nsINode* parent = GetParentNode();
+ mozilla::ErrorResult rv;
+ parent->RemoveChild(*this, rv);
+ return rv.StealNSResult();
+ }
+
+ // ChildNode methods
+ inline mozilla::dom::Element* GetPreviousElementSibling() const;
+ inline mozilla::dom::Element* GetNextElementSibling() const;
+
+ MOZ_CAN_RUN_SCRIPT void Before(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void After(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void ReplaceWith(
+ const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+ /**
+ * Remove this node from its parent, if any.
+ */
+ void Remove();
+
+ // ParentNode methods
+ mozilla::dom::Element* GetFirstElementChild() const;
+ mozilla::dom::Element* GetLastElementChild() const;
+
+ already_AddRefed<nsIHTMLCollection> GetElementsByAttribute(
+ const nsAString& aAttribute, const nsAString& aValue);
+ already_AddRefed<nsIHTMLCollection> GetElementsByAttributeNS(
+ const nsAString& aNamespaceURI, const nsAString& aAttribute,
+ const nsAString& aValue, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void Prepend(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void Append(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void ReplaceChildren(
+ const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void ReplaceChildren(nsINode* aNode, ErrorResult& aRv);
+
+ void GetBoxQuads(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad>>& aResult, CallerType aCallerType,
+ ErrorResult& aRv);
+
+ void GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad>>& aResult,
+ ErrorResult& aRv);
+
+ already_AddRefed<DOMQuad> ConvertQuadFromNode(
+ DOMQuad& aQuad, const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv);
+ already_AddRefed<DOMQuad> ConvertRectFromNode(
+ DOMRectReadOnly& aRect, const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv);
+ already_AddRefed<DOMPoint> ConvertPointFromNode(
+ const DOMPointInit& aPoint, const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv);
+
+ /**
+ * See nsSlots::mClosestCommonInclusiveAncestorRanges.
+ */
+ const mozilla::LinkedList<mozilla::dom::AbstractRange>*
+ GetExistingClosestCommonInclusiveAncestorRanges() const {
+ if (!HasSlots()) {
+ return nullptr;
+ }
+ return GetExistingSlots()->mClosestCommonInclusiveAncestorRanges.get();
+ }
+
+ /**
+ * See nsSlots::mClosestCommonInclusiveAncestorRanges.
+ */
+ mozilla::LinkedList<mozilla::dom::AbstractRange>*
+ GetExistingClosestCommonInclusiveAncestorRanges() {
+ if (!HasSlots()) {
+ return nullptr;
+ }
+ return GetExistingSlots()->mClosestCommonInclusiveAncestorRanges.get();
+ }
+
+ /**
+ * See nsSlots::mClosestCommonInclusiveAncestorRanges.
+ */
+ mozilla::UniquePtr<mozilla::LinkedList<mozilla::dom::AbstractRange>>&
+ GetClosestCommonInclusiveAncestorRangesPtr() {
+ return Slots()->mClosestCommonInclusiveAncestorRanges;
+ }
+
+ nsIWeakReference* GetExistingWeakReference() {
+ return HasSlots() ? GetExistingSlots()->mWeakReference : nullptr;
+ }
+
+ protected:
+ // Override this function to create a custom slots class.
+ // Must not return null.
+ virtual nsINode::nsSlots* CreateSlots();
+
+ bool HasSlots() const { return mSlots != nullptr; }
+
+ nsSlots* GetExistingSlots() const { return mSlots; }
+
+ nsSlots* Slots() {
+ if (!HasSlots()) {
+ mSlots = CreateSlots();
+ MOZ_ASSERT(mSlots);
+ }
+ return GetExistingSlots();
+ }
+
+ /**
+ * Invalidate cached child array inside mChildNodes
+ * of type nsParentNodeChildContentList.
+ */
+ void InvalidateChildNodes();
+
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ mozilla::OOMReporter& aError);
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aError) {}
+
+ void EnsurePreInsertionValidity1(mozilla::ErrorResult& aError);
+ void EnsurePreInsertionValidity2(bool aReplace, nsINode& aNewChild,
+ nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsINode* ReplaceOrInsertBefore(
+ bool aReplace, nsINode* aNewChild, nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+
+ /**
+ * Returns the Element that should be used for resolving namespaces
+ * on this node (ie the ownerElement for attributes, the documentElement for
+ * documents, the node itself for elements and for other nodes the parentNode
+ * if it is an element).
+ */
+ virtual mozilla::dom::Element* GetNameSpaceElement() = 0;
+
+ /**
+ * Parse the given selector string into a servo SelectorList.
+ *
+ * Never returns null if aRv is not failing.
+ *
+ * Note that the selector list returned here is owned by the owner doc's
+ * selector cache.
+ */
+ const mozilla::StyleSelectorList* ParseSelectorList(
+ const nsACString& aSelectorString, mozilla::ErrorResult&);
+
+ public:
+ /* Event stuff that documents and elements share.
+
+ Note that we include DOCUMENT_ONLY_EVENT events here so that we
+ can forward all the document stuff to this implementation.
+ */
+#define EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::EventHandlerNonNull* GetOn##name_() { \
+ return GetEventHandler(nsGkAtoms::on##name_); \
+ } \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler) { \
+ SetEventHandler(nsGkAtoms::on##name_, handler); \
+ }
+#define TOUCH_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef DOCUMENT_ONLY_EVENT
+#undef TOUCH_EVENT
+#undef EVENT
+
+ protected:
+ static bool Traverse(nsINode* tmp, nsCycleCollectionTraversalCallback& cb);
+ static void Unlink(nsINode* tmp);
+
+ RefPtr<mozilla::dom::NodeInfo> mNodeInfo;
+
+ // mParent is an owning ref most of the time, except for the case of document
+ // nodes, so it cannot be represented by nsCOMPtr, so mark is as
+ // MOZ_OWNING_REF.
+ nsINode* MOZ_OWNING_REF mParent;
+
+ private:
+#ifndef BOOL_FLAGS_ON_WRAPPER_CACHE
+ // Boolean flags.
+ uint32_t mBoolFlags;
+#endif
+
+ // NOTE, there are 32 bits left here, at least in 64 bit builds.
+
+ uint32_t mChildCount;
+
+ protected:
+ // mNextSibling and mFirstChild are strong references while
+ // mPreviousOrLastSibling is a weak ref. |mFirstChild->mPreviousOrLastSibling|
+ // points to the last child node.
+ nsCOMPtr<nsIContent> mFirstChild;
+ nsCOMPtr<nsIContent> mNextSibling;
+ nsIContent* MOZ_NON_OWNING_REF mPreviousOrLastSibling;
+
+ union {
+ // Pointer to our primary frame. Might be null.
+ nsIFrame* mPrimaryFrame;
+
+ // Pointer to the root of our subtree. Might be null.
+ // This reference is non-owning and safe, since it either points to the
+ // object itself, or is reset by ClearSubtreeRootPointer.
+ nsINode* MOZ_NON_OWNING_REF mSubtreeRoot;
+ };
+
+ // Storage for more members that are usually not needed; allocated lazily.
+ nsSlots* mSlots;
+};
+
+inline nsINode* mozilla::dom::EventTarget::GetAsNode() {
+ return IsNode() ? AsNode() : nullptr;
+}
+
+inline const nsINode* mozilla::dom::EventTarget::GetAsNode() const {
+ return const_cast<mozilla::dom::EventTarget*>(this)->GetAsNode();
+}
+
+inline nsINode* mozilla::dom::EventTarget::AsNode() {
+ MOZ_DIAGNOSTIC_ASSERT(IsNode());
+ return static_cast<nsINode*>(this);
+}
+
+inline const nsINode* mozilla::dom::EventTarget::AsNode() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsNode());
+ return static_cast<const nsINode*>(this);
+}
+
+// Useful inline function for getting a node given an nsIContent and a Document.
+// Returns the first argument cast to nsINode if it is non-null, otherwise
+// returns the second (which may be null). We use type variables instead of
+// nsIContent* and Document* because the actual types must be
+// known for the cast to work.
+template <class C, class D>
+inline nsINode* NODE_FROM(C& aContent, D& aDocument) {
+ if (aContent) return static_cast<nsINode*>(aContent);
+ return static_cast<nsINode*>(aDocument);
+}
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsINode, NS_INODE_IID)
+
+inline nsISupports* ToSupports(nsINode* aPointer) { return aPointer; }
+
+// Some checks are faster to do on nsIContent or Element than on
+// nsINode, so spit out FromNode versions taking those types too.
+#define NS_IMPL_FROMNODE_GENERIC(_class, _check, _const) \
+ template <typename T> \
+ static auto FromNode(_const T& aNode) \
+ ->decltype(static_cast<_const _class*>(&aNode)) { \
+ return aNode._check ? static_cast<_const _class*>(&aNode) : nullptr; \
+ } \
+ template <typename T> \
+ static _const _class* FromNode(_const T* aNode) { \
+ return FromNode(*aNode); \
+ } \
+ template <typename T> \
+ static _const _class* FromNodeOrNull(_const T* aNode) { \
+ return aNode ? FromNode(*aNode) : nullptr; \
+ } \
+ template <typename T> \
+ static auto FromEventTarget(_const T& aEventTarget) \
+ ->decltype(static_cast<_const _class*>(&aEventTarget)) { \
+ return aEventTarget.IsNode() && aEventTarget.AsNode()->_check \
+ ? static_cast<_const _class*>(&aEventTarget) \
+ : nullptr; \
+ } \
+ template <typename T> \
+ static _const _class* FromEventTarget(_const T* aEventTarget) { \
+ return FromEventTarget(*aEventTarget); \
+ } \
+ template <typename T> \
+ static _const _class* FromEventTargetOrNull(_const T* aEventTarget) { \
+ return aEventTarget ? FromEventTarget(*aEventTarget) : nullptr; \
+ }
+
+#define NS_IMPL_FROMNODE_HELPER(_class, _check) \
+ NS_IMPL_FROMNODE_GENERIC(_class, _check, ) \
+ NS_IMPL_FROMNODE_GENERIC(_class, _check, const) \
+ \
+ template <typename T> \
+ static _class* FromNode(T&& aNode) { \
+ /* We need the double-cast in case aNode is a smartptr. Those */ \
+ /* can cast to superclasses of the type they're templated on, */ \
+ /* but not directly to subclasses. */ \
+ return aNode->_check ? static_cast<_class*>(static_cast<nsINode*>(aNode)) \
+ : nullptr; \
+ } \
+ template <typename T> \
+ static _class* FromNodeOrNull(T&& aNode) { \
+ return aNode ? FromNode(aNode) : nullptr; \
+ } \
+ template <typename T> \
+ static _class* FromEventTarget(T&& aEventTarget) { \
+ /* We need the double-cast in case aEventTarget is a smartptr. Those */ \
+ /* can cast to superclasses of the type they're templated on, */ \
+ /* but not directly to subclasses. */ \
+ return aEventTarget->IsNode() && aEventTarget->AsNode()->_check \
+ ? static_cast<_class*>(static_cast<EventTarget*>(aEventTarget)) \
+ : nullptr; \
+ } \
+ template <typename T> \
+ static _class* FromEventTargetOrNull(T&& aEventTarget) { \
+ return aEventTarget ? FromEventTarget(aEventTarget) : nullptr; \
+ }
+
+#define NS_IMPL_FROMNODE(_class, _nsid) \
+ NS_IMPL_FROMNODE_HELPER(_class, IsInNamespace(_nsid))
+
+#define NS_IMPL_FROMNODE_WITH_TAG(_class, _nsid, _tag) \
+ NS_IMPL_FROMNODE_HELPER(_class, NodeInfo()->Equals(nsGkAtoms::_tag, _nsid))
+
+#define NS_IMPL_FROMNODE_HTML_WITH_TAG(_class, _tag) \
+ NS_IMPL_FROMNODE_WITH_TAG(_class, kNameSpaceID_XHTML, _tag)
+
+#endif /* nsINode_h___ */
diff --git a/dom/base/nsINodeList.h b/dom/base/nsINodeList.h
new file mode 100644
index 0000000000..329c06df9b
--- /dev/null
+++ b/dom/base/nsINodeList.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsINodeList_h___
+#define nsINodeList_h___
+
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsIContent.h"
+
+// IID for the nsINodeList interface
+#define NS_INODELIST_IID \
+ { \
+ 0xadb5e54c, 0x6e96, 0x4102, { \
+ 0x8d, 0x40, 0xe0, 0x12, 0x3d, 0xcf, 0x48, 0x7a \
+ } \
+ }
+
+class nsIContent;
+class nsINode;
+
+/**
+ * An internal interface for a reasonably fast indexOf.
+ */
+class nsINodeList : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODELIST_IID)
+
+ /**
+ * Get the index of the given node in the list. Will return -1 if the node
+ * is not in the list.
+ */
+ virtual int32_t IndexOf(nsIContent* aContent) = 0;
+
+ /**
+ * Get the root node for this nodelist.
+ */
+ virtual nsINode* GetParentObject() = 0;
+
+ virtual uint32_t Length() = 0;
+ virtual nsIContent* Item(uint32_t aIndex) = 0;
+ nsIContent* IndexedGetter(uint32_t aIndex, bool& aFound) {
+ nsIContent* item = Item(aIndex);
+ aFound = !!item;
+ return item;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsINodeList, NS_INODELIST_IID)
+
+#endif /* nsINodeList_h___ */
diff --git a/dom/base/nsIObjectLoadingContent.idl b/dom/base/nsIObjectLoadingContent.idl
new file mode 100644
index 0000000000..ded79acd21
--- /dev/null
+++ b/dom/base/nsIObjectLoadingContent.idl
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIRequest;
+interface nsIFrame;
+interface nsIPluginTag;
+interface nsIURI;
+
+webidl BrowsingContext;
+
+/**
+ * This interface represents a content node that loads objects.
+ *
+ * Please make sure to update the MozObjectLoadingContent WebIDL
+ * mixin to mirror this interface when changing it.
+ */
+
+[scriptable, builtinclass, uuid(2eb3195e-3eea-4083-bb1d-d2d70fa35ccb)]
+interface nsIObjectLoadingContent : nsISupports
+{
+ /**
+ * See notes in nsObjectLoadingContent.h
+ */
+ const unsigned long TYPE_LOADING = 0;
+ const unsigned long TYPE_IMAGE = 1;
+ const unsigned long TYPE_FALLBACK = 2;
+ const unsigned long TYPE_FAKE_PLUGIN = 3;
+ const unsigned long TYPE_DOCUMENT = 4;
+ const unsigned long TYPE_NULL = 5;
+
+ const unsigned long PLUGIN_ACTIVE = 0xFF;
+
+ // Plugins-specific permission indicating that we want to prompt the user
+ // to decide whether they want to allow a plugin, but to do so in a less
+ // intrusive way than PROMPT_ACTION would entail. At the time of writing,
+ // this means hiding all in-content plugin overlays, but still showing the
+ // plugin badge in the URL bar.
+ const unsigned long PLUGIN_PERMISSION_PROMPT_ACTION_QUIET = 8;
+
+ /**
+ * The actual mime type (the one we got back from the network
+ * request) for the element.
+ */
+ readonly attribute ACString actualType;
+
+ /**
+ * Gets the type of the content that's currently loaded. See
+ * the constants above for the list of possible values.
+ */
+ readonly attribute unsigned long displayedType;
+
+ /**
+ * Gets the content type that corresponds to the give MIME type. See the
+ * constants above for the list of possible values. If nothing else fits,
+ * TYPE_FALLBACK will be returned.
+ */
+ unsigned long getContentTypeForMIMEType(in AUTF8String aMimeType);
+
+ /**
+ * Forces a re-evaluation and reload of the tag, optionally invalidating its
+ * click-to-play state. This can be used when the MIME type that provides a
+ * type has changed, for instance, to force the tag to re-evalulate the
+ * handler to use.
+ */
+ void reload(in boolean aClearActivation);
+
+ /**
+ * Puts the tag in the "waiting on a channel" state and adopts this
+ * channel. This does not override the normal logic of examining attributes
+ * and the channel type, so the load may cancel this channel if it decides not
+ * to use one.
+ *
+ * This assumes:
+ * - This tag has not begun loading yet
+ * - This channel has not yet hit OnStartRequest
+ * - The caller will continue to pass channel events to us as a listener
+ */
+ [noscript] void initializeFromChannel(in nsIRequest request);
+
+ /**
+ * The URL of the data/src loaded in the object. This may be null (i.e.
+ * an <embed> with no src).
+ */
+ readonly attribute nsIURI srcURI;
+
+ /**
+ * Disable the use of fake plugins and reload the tag if necessary.
+ */
+ void skipFakePlugins();
+
+ /**
+ * Switch the tag into the TYPE_DOCUMENT state, and returns the
+ * BrowsingContext which the load should complete in.
+ */
+ [noscript] BrowsingContext upgradeLoadToDocument(in nsIChannel channel);
+};
diff --git a/dom/base/nsIScriptChannel.idl b/dom/base/nsIScriptChannel.idl
new file mode 100644
index 0000000000..3a07904f91
--- /dev/null
+++ b/dom/base/nsIScriptChannel.idl
@@ -0,0 +1,75 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface representing a channel which will have to execute some sort of
+ * program provided via its URI to compute the data it should return.
+ *
+ * If a channel implements this interface, the execution of the program in
+ * question will be restricted in the following ways:
+ *
+ * - If the channel does not have an owner principal, the program will not be
+ * executed at all, no matter what. This is necessary because in this
+ * circumstance we have no way to tell whether script execution is allowed at
+ * all for the originating security context of this channel.
+ * - If the channel has an owner principal, how it is executed is controlled by
+ * this interface. However if the owner principal does not subsume the
+ * principal of the environment in which the program is to be executed the
+ * execution will be forced to happen in a sandbox.
+ */
+[uuid(33234b99-9588-4c7d-9da6-86b8b7cba565)]
+interface nsIScriptChannel : nsISupports
+{
+ /**
+ * Possible ways of executing the program.
+ */
+
+ /**
+ * Don't execute at all.
+ */
+ const unsigned long NO_EXECUTION = 0;
+
+ /**
+ * There used to be an EXECUTE_IN_SANDBOX = 1 value. It has been removed, but
+ * we're not changing the value of EXECUTE_NORMAL to avoid breaking compat.
+ */
+
+ /**
+ * Execute against the target environment if the principals allow it.
+ */
+ const unsigned long EXECUTE_NORMAL = 2;
+
+ /**
+ * Whether and how the program represented by this channel is to be executed.
+ * The default value if this property has never been set on this channel MUST
+ * be either EXECUTE_IN_SANDBOX or NO_EXECUTION.
+ *
+ * @throws NS_ERROR_INVALID_ARG when set to an unrecognized value.
+ */
+ attribute unsigned long executionPolicy;
+
+ /**
+ * Control whether the program should be executed synchronosly when
+ * the channel's AsyncOpen method is called or whether it should be
+ * executed asynchronously. In both cases, any data that the
+ * channel returns will be returned asynchronously; the only thing
+ * this property affects is when the program executes.
+ *
+ * The default value of this property is TRUE.
+ *
+ * Setting this property after asyncOpen has been called on the
+ * channel has no effect.
+ */
+ attribute boolean executeAsync;
+
+ /**
+ * Check whether this script channel is a document load. This is
+ * needed because script channels can lie about their
+ * LOAD_DOCUMENT_URI flag until they have run the script.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isDocumentLoad;
+};
diff --git a/dom/base/nsIScriptContext.h b/dom/base/nsIScriptContext.h
new file mode 100644
index 0000000000..a43f5316eb
--- /dev/null
+++ b/dom/base/nsIScriptContext.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScriptContext_h__
+#define nsIScriptContext_h__
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "jspubtd.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.h"
+
+class nsIScriptGlobalObject;
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCRIPTCONTEXT_IID \
+ { \
+ 0x54cbe9cf, 0x7282, 0x421a, { \
+ 0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0 \
+ } \
+ }
+
+class nsIOffThreadScriptReceiver;
+
+/**
+ * It is used by the application to initialize a runtime and run scripts.
+ * A script runtime would implement this interface.
+ */
+class nsIScriptContext : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTCONTEXT_IID)
+
+ /**
+ * Return the global object.
+ *
+ **/
+ virtual nsIScriptGlobalObject* GetGlobalObject() = 0;
+
+ // SetProperty is suspect and jst believes should not be needed. Currenly
+ // used only for "arguments".
+ virtual nsresult SetProperty(JS::Handle<JSObject*> aTarget,
+ const char* aPropName, nsISupports* aVal) = 0;
+ /**
+ * Called to set/get information if the script context is
+ * currently processing a script tag
+ */
+ virtual bool GetProcessingScriptTag() = 0;
+ virtual void SetProcessingScriptTag(bool aResult) = 0;
+
+ /**
+ * Initialize DOM classes on aGlobalObj.
+ */
+ virtual nsresult InitClasses(JS::Handle<JSObject*> aGlobalObj) = 0;
+
+ /**
+ * Access the Window Proxy. The setter should only be called by
+ * nsGlobalWindow.
+ */
+ virtual void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) = 0;
+ virtual JSObject* GetWindowProxy() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptContext, NS_ISCRIPTCONTEXT_IID)
+
+#define NS_IOFFTHREADSCRIPTRECEIVER_IID \
+ { \
+ 0x3a980010, 0x878d, 0x46a9, { \
+ 0x93, 0xad, 0xbc, 0xfd, 0xd3, 0x8e, 0xa0, 0xc2 \
+ } \
+ }
+
+class nsIOffThreadScriptReceiver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IOFFTHREADSCRIPTRECEIVER_IID)
+
+ /**
+ * Notify this object that a previous Compile call specifying this as
+ * aOffThreadReceiver has completed. The script being passed in must be
+ * rooted before any call which could trigger GC.
+ */
+ NS_IMETHOD OnScriptCompileComplete(JS::Stencil* aStencil,
+ nsresult aStatus) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIOffThreadScriptReceiver,
+ NS_IOFFTHREADSCRIPTRECEIVER_IID)
+
+#endif // nsIScriptContext_h__
diff --git a/dom/base/nsIScriptGlobalObject.h b/dom/base/nsIScriptGlobalObject.h
new file mode 100644
index 0000000000..51c122f8ad
--- /dev/null
+++ b/dom/base/nsIScriptGlobalObject.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScriptGlobalObject_h__
+#define nsIScriptGlobalObject_h__
+
+#include "nsISupports.h"
+#include "nsIGlobalObject.h"
+#include "js/TypeDecls.h"
+#include "mozilla/EventForwards.h"
+
+class nsIScriptContext;
+class nsIScriptGlobalObject;
+
+namespace mozilla::dom {
+struct ErrorEventInit;
+} // namespace mozilla::dom
+
+// A helper function for nsIScriptGlobalObject implementations to use
+// when handling a script error. Generally called by the global when a context
+// notifies it of an error via nsIScriptGlobalObject::HandleScriptError.
+// Returns true if HandleDOMEvent was actually called, in which case
+// aStatus will be filled in with the status.
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY bool NS_HandleScriptError(
+ nsIScriptGlobalObject* aScriptGlobal,
+ const mozilla::dom::ErrorEventInit& aErrorEvent, nsEventStatus* aStatus);
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCRIPTGLOBALOBJECT_IID \
+ { \
+ 0x876f83bd, 0x6314, 0x460a, { \
+ 0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1 \
+ } \
+ }
+
+/**
+ * The global object which keeps a script context for each supported script
+ * language. This often used to store per-window global state.
+ * This is a heavyweight interface implemented only by DOM globals, and
+ * it might go away some time in the future.
+ */
+
+class nsIScriptGlobalObject : public nsIGlobalObject {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTGLOBALOBJECT_IID)
+
+ /**
+ * Ensure that the script global object is initialized for working with the
+ * specified script language ID. This will set up the nsIScriptContext
+ * and 'script global' for that language, allowing these to be fetched
+ * and manipulated.
+ * @return NS_OK if successful; error conditions include that the language
+ * has not been registered, as well as 'normal' errors, such as
+ * out-of-memory
+ */
+ virtual nsresult EnsureScriptEnvironment() = 0;
+ /**
+ * Get a script context (WITHOUT added reference) for the specified language.
+ */
+ virtual nsIScriptContext* GetScriptContext() = 0;
+
+ nsIScriptContext* GetContext() { return GetScriptContext(); }
+
+ /**
+ * Handle a script error. Generally called by a script context.
+ */
+ bool HandleScriptError(const mozilla::dom::ErrorEventInit& aErrorEventInit,
+ nsEventStatus* aEventStatus) {
+ return NS_HandleScriptError(this, aErrorEventInit, aEventStatus);
+ }
+
+ virtual bool IsBlackForCC(bool aTracingNeeded = true) { return false; }
+
+ protected:
+ virtual ~nsIScriptGlobalObject() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptGlobalObject, NS_ISCRIPTGLOBALOBJECT_IID)
+
+#endif
diff --git a/dom/base/nsIScriptObjectPrincipal.h b/dom/base/nsIScriptObjectPrincipal.h
new file mode 100644
index 0000000000..67b8bffb43
--- /dev/null
+++ b/dom/base/nsIScriptObjectPrincipal.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScriptObjectPrincipal_h__
+#define nsIScriptObjectPrincipal_h__
+
+#include "nsISupports.h"
+
+class nsIPrincipal;
+
+#define NS_ISCRIPTOBJECTPRINCIPAL_IID \
+ { \
+ 0x3eedba38, 0x8d22, 0x41e1, { \
+ 0x81, 0x7a, 0x0e, 0x43, 0xe1, 0x65, 0xb6, 0x64 \
+ } \
+ }
+
+/**
+ * JS Object Principal information.
+ */
+class nsIScriptObjectPrincipal : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTOBJECTPRINCIPAL_IID)
+
+ virtual nsIPrincipal* GetPrincipal() = 0;
+
+ virtual nsIPrincipal* GetEffectiveCookiePrincipal() = 0;
+
+ virtual nsIPrincipal* GetEffectiveStoragePrincipal() = 0;
+
+ virtual nsIPrincipal* PartitionedPrincipal() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptObjectPrincipal,
+ NS_ISCRIPTOBJECTPRINCIPAL_IID)
+
+#endif // nsIScriptObjectPrincipal_h__
diff --git a/dom/base/nsIScriptableContentIterator.idl b/dom/base/nsIScriptableContentIterator.idl
new file mode 100644
index 0000000000..caf689f550
--- /dev/null
+++ b/dom/base/nsIScriptableContentIterator.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl Node;
+webidl Range;
+
+/**
+ * nsIScriptableContentIterator is designed to testing concrete classes of
+ * ContentIteratorBase.
+ */
+[scriptable, builtinclass, uuid(9f25fb2a-265f-44f9-a122-62bbf443239e)]
+interface nsIScriptableContentIterator : nsISupports
+{
+ cenum IteratorType : 8 {
+ NOT_INITIALIZED,
+ POST_ORDER_ITERATOR,
+ PRE_ORDER_ITERATOR,
+ SUBTREE_ITERATOR
+ };
+
+ /**
+ * You need to call initWith*() first. Then, the instance of this interface
+ * decides the type of iterator with its aType argument. You can call
+ * initWith*() multiple times, but you need to keep setting same type as
+ * previous call. If you set different type, these method with throw an
+ * exception.
+ */
+
+ // See ContentIteratorBase::Init(nsINode*)
+ void initWithRootNode(in nsIScriptableContentIterator_IteratorType aType,
+ in Node aRoot);
+
+ // See ContentIteratorBase::Init(nsRange*)
+ void initWithRange(in nsIScriptableContentIterator_IteratorType aType,
+ in Range aRange);
+
+ // See ContentIteratorBase::Init(nsINode*, uint32_t, nsINode*, uint32_t)
+ void initWithPositions(in nsIScriptableContentIterator_IteratorType aType,
+ in Node aStartContainer, in unsigned long aStartOffset,
+ in Node aEndContainer, in unsigned long aEndOffset);
+
+ // See ContentIteratorBase::First()
+ void first();
+
+ // See ContentIteratorBase::Last()
+ void last();
+
+ // See ContentIteratorBase::Next()
+ void next();
+
+ // See ContentIteratorBase::Prev()
+ void prev();
+
+ // See ContentIteratorBase::GetCurrentNode()
+ readonly attribute Node currentNode;
+
+ // See ContentIteratorBase::IsDone()
+ readonly attribute bool isDone;
+
+ // See ContentIteratorBase::PositionAt(nsINode*)
+ void positionAt(in Node aNode);
+};
+
+%{C++
+#define SCRIPTABLE_CONTENT_ITERATOR_CID \
+ { 0xf68037ec, 0x2790, 0x44c5, \
+ { 0x8e, 0x5f, 0xdf, 0x5d, 0xa5, 0x8b, 0x93, 0xa7 } }
+#define SCRIPTABLE_CONTENT_ITERATOR_CONTRACTID \
+ "@mozilla.org/scriptable-content-iterator;1"
+%}
diff --git a/dom/base/nsISelectionController.idl b/dom/base/nsISelectionController.idl
new file mode 100644
index 0000000000..e7f6dd05c4
--- /dev/null
+++ b/dom/base/nsISelectionController.idl
@@ -0,0 +1,352 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISelectionDisplay.idl"
+
+%{C++
+typedef short SelectionRegion;
+namespace mozilla {
+namespace dom {
+class Selection;
+} // namespace dom
+} // namespace mozilla
+%}
+
+interface nsIContent;
+interface nsISelectionDisplay;
+
+webidl Node;
+webidl Selection;
+
+[builtinclass, scriptable, uuid(3801c9d4-8e69-4bfc-9edb-b58278621f8f)]
+interface nsISelectionController : nsISelectionDisplay
+{
+ // Begin of RawSelectionType values.
+ const short SELECTION_NONE = 0;
+ // Corresponds to the Selection exposed via window.getSelection() and
+ // document.getSelection().
+ const short SELECTION_NORMAL = 1;
+ // Corresponds to the Selection used for spellchecking in <textarea>s and
+ // "contentEditable" elements.
+ const short SELECTION_SPELLCHECK = 2;
+ const short SELECTION_IME_RAWINPUT = 3;
+ const short SELECTION_IME_SELECTEDRAWTEXT = 4;
+ const short SELECTION_IME_CONVERTEDTEXT = 5;
+ const short SELECTION_IME_SELECTEDCONVERTEDTEXT = 6;
+ // For accessibility API usage
+ const short SELECTION_ACCESSIBILITY = 7;
+ const short SELECTION_FIND = 8;
+ const short SELECTION_URLSECONDARY = 9;
+ const short SELECTION_URLSTRIKEOUT = 10;
+ // Custom Highlight API
+ // (see https://drafts.csswg.org/css-highlight-api-1/#enumdef-highlighttype)
+ const short SELECTION_HIGHLIGHT = 11;
+ // End of RawSelectionType values.
+ const short NUM_SELECTIONTYPES = 12;
+
+ // SelectionRegion values:
+ const short SELECTION_ANCHOR_REGION = 0;
+ const short SELECTION_FOCUS_REGION = 1;
+ const short SELECTION_WHOLE_SELECTION = 2;
+ const short NUM_SELECTION_REGIONS = 3;
+
+ const short SELECTION_OFF = 0;
+ const short SELECTION_HIDDEN =1;//>HIDDEN displays selection
+ const short SELECTION_ON = 2;
+ const short SELECTION_DISABLED = 3;
+ const short SELECTION_ATTENTION = 4;
+
+ /**
+ * SetDisplaySelection will set the display mode for the selection. OFF,ON,DISABLED
+ */
+ void setDisplaySelection(in short toggle);
+
+ /**
+ * GetDisplaySelection will get the display mode for the selection. OFF,ON,DISABLED
+ */
+ short getDisplaySelection();
+
+ /**
+ * GetSelection will return the selection that the presentation
+ * shell may implement.
+ *
+ * @param aType This will hold the type of selection. This value must be one
+ * of RawSelectionType values.
+ * @param _return will hold the return value
+ */
+ [binaryname(GetSelectionFromScript)]
+ Selection getSelection(in short type);
+
+ /**
+ * Return the selection object corresponding to a selection type.
+ */
+ [noscript,nostdcall,notxpcom,binaryname(GetSelection)]
+ Selection getDOMSelection(in short aType);
+
+ /**
+ * Called when the selection controller should take the focus.
+ *
+ * This will take care to hide the previously-focused selection, show this
+ * selection, and repaint both.
+ */
+ [noscript,nostdcall,notxpcom]
+ void selectionWillTakeFocus();
+
+ /**
+ * Called when the selection controller has lost the focus.
+ *
+ * This will take care to hide and repaint the selection.
+ */
+ [noscript,nostdcall,notxpcom]
+ void selectionWillLoseFocus();
+
+ const short SCROLL_SYNCHRONOUS = 1<<1;
+ const short SCROLL_FIRST_ANCESTOR_ONLY = 1<<2;
+ const short SCROLL_CENTER_VERTICALLY = 1<<4;
+ const short SCROLL_OVERFLOW_HIDDEN = 1<<5;
+ const short SCROLL_FOR_CARET_MOVE = 1<<6;
+
+ /**
+ * ScrollSelectionIntoView scrolls a region of the selection,
+ * so that it is visible in the scrolled view.
+ *
+ * @param aType the selection to scroll into view. This value must be one
+ * of RawSelectionType values.
+ * @param aRegion the region inside the selection to scroll into view. //SelectionRegion
+ * @param aFlags the scroll flags. Valid bits include:
+ * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
+ * before returning. If not set, posts a request which is processed
+ * at some point after the method returns.
+ * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be scrolled
+ * into view.
+ * SCROLL_OVERFLOW_HIDDEN: if set, scrolls even if the overflow is specified
+ * as hidden.
+ * SCROLL_FOR_CARET_MOVE: set to indicate whether scrolling is in response
+ * to the caret being moved. Does not affect behavior (used for telemetry
+ * purposes only).
+ *
+ * Note that if isSynchronous is true, then this might flush the pending
+ * reflow. It's dangerous for some objects. See bug 418470 comment 12.
+ */
+ void scrollSelectionIntoView(in short type, in short region, in short flags);
+
+ /**
+ * RepaintSelection repaints the selection specified by aType.
+ *
+ * @param aType specifies the selection to repaint.
+ */
+ void repaintSelection(in short type);
+
+ /**
+ * Set the caret as enabled or disabled. An enabled caret will
+ * draw or blink when made visible. A disabled caret will never show up.
+ * Can be called any time.
+ * @param aEnable PR_TRUE to enable caret. PR_FALSE to disable.
+ * @return always NS_OK
+ */
+
+ void setCaretEnabled(in boolean enabled);
+
+ /**
+ * Set the caret readonly or not. An readonly caret will
+ * draw but not blink when made visible.
+ * @param aReadOnly PR_TRUE to enable caret. PR_FALSE to disable.
+ * @return always NS_OK
+ */
+ void setCaretReadOnly(in boolean readOnly);
+
+ /**
+ * Gets the current state of the caret.
+ * @param aEnabled [OUT] set to the current caret state, as set by SetCaretEnabled
+ * @return if aOutEnabled==null, returns NS_ERROR_INVALID_ARG
+ * else NS_OK
+ */
+ boolean getCaretEnabled();
+
+ /**
+ * This is true if the caret is enabled, visible, and currently blinking.
+ * This is still true when the caret is enabled, visible, but in its "off"
+ * blink cycle.
+ */
+ readonly attribute boolean caretVisible;
+
+ /**
+ * Show the caret even in selections. By default the caret is hidden unless the
+ * selection is collapsed. Use this function to show the caret even in selections.
+ * @param aVisibility PR_TRUE to show the caret in selections. PR_FALSE to hide.
+ * @return always NS_OK
+ */
+ void setCaretVisibilityDuringSelection(in boolean visibility);
+
+ /** CharacterMove will move the selection one character forward/backward in the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ void characterMove(in boolean forward, in boolean extend);
+
+ /** PhysicalMove will move the selection one "unit" in a given direction
+ * within the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aDirection
+ * @param aAmount character/line; word/lineBoundary
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ void physicalMove(in short direction, in short amount, in boolean extend);
+
+ /**
+ * nsFrameSelection::PhysicalMove depends on the ordering of these values;
+ * do not change without checking there!
+ */
+ const short MOVE_LEFT = 0;
+ const short MOVE_RIGHT = 1;
+ const short MOVE_UP = 2;
+ const short MOVE_DOWN = 3;
+
+ /** WordMove will move the selection one word forward/backward in the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+
+ void wordMove(in boolean forward, in boolean extend);
+
+ /** LineMove will move the selection one line forward/backward in the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ void lineMove(in boolean forward, in boolean extend);
+
+ /** IntraLineMove will move the selection to the front of the line or end of the line
+ * in the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ void intraLineMove(in boolean forward, in boolean extend);
+
+ /** PageMove will move the selection one page forward/backward in the document.
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ [can_run_script]
+ void pageMove(in boolean forward, in boolean extend);
+
+ /** CompleteScroll will move page view to the top or bottom of the document
+ * @param aForward forward or backward if PR_FALSE
+ */
+ void completeScroll(in boolean forward);
+
+ /** CompleteMove will move page view to the top or bottom of the document
+ * this will also have the effect of collapsing the selection if the aExtend = PR_FALSE
+ * the "point" of selection that is extended is considered the "focus" point.
+ * or the last point adjusted by the selection.
+ * @param aForward forward or backward if PR_FALSE
+ * @param aExtend should it collapse the selection of extend it?
+ */
+ [can_run_script]
+ void completeMove(in boolean forward, in boolean extend);
+
+
+ /** ScrollPage will scroll the page without affecting the selection.
+ * @param aForward scroll forward or backwards in selection
+ */
+ void scrollPage(in boolean forward);
+
+ /** ScrollLine will scroll line up or down dependent on the boolean
+ * @param aForward scroll forward or backwards in selection
+ */
+ void scrollLine(in boolean forward);
+
+ /** ScrollCharacter will scroll right or left dependent on the boolean
+ * @param aRight if true will scroll right. if not will scroll left.
+ */
+ void scrollCharacter(in boolean right);
+};
+%{ C++
+ #define NS_ISELECTIONCONTROLLER_CID \
+ { 0x513b9460, 0xd56a, 0x4c4e, \
+ { 0xb6, 0xf9, 0x0b, 0x8a, 0xe4, 0x37, 0x2a, 0x3b }}
+
+namespace mozilla {
+
+// RawSelectionType should be used to store nsISelectionController::SELECTION_*.
+typedef short RawSelectionType;
+
+// SelectionTypeMask should be used to store bit-mask of selection types.
+// The value can be retrieved with ToSelectionTypeMask() and checking if
+// a selection type is in a mask with |SelectionType & SelectionTypeMask|.
+typedef uint16_t SelectionTypeMask;
+
+// SelectionType should be used in internal handling because of type safe.
+enum class SelectionType : RawSelectionType
+{
+ eInvalid = -1,
+ eNone = nsISelectionController::SELECTION_NONE,
+ eNormal = nsISelectionController::SELECTION_NORMAL,
+ eSpellCheck = nsISelectionController::SELECTION_SPELLCHECK,
+ eIMERawClause = nsISelectionController::SELECTION_IME_RAWINPUT,
+ eIMESelectedRawClause = nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
+ eIMEConvertedClause = nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
+ eIMESelectedClause =
+ nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT,
+ eAccessibility = nsISelectionController::SELECTION_ACCESSIBILITY,
+ eFind = nsISelectionController::SELECTION_FIND,
+ eURLSecondary = nsISelectionController::SELECTION_URLSECONDARY,
+ eURLStrikeout = nsISelectionController::SELECTION_URLSTRIKEOUT,
+ eHighlight = nsISelectionController::SELECTION_HIGHLIGHT,
+};
+
+// Using anonymous enum to define constants because these constants may be
+// used at defining fixed size array in some header files (e.g.,
+// nsFrameSelection.h). So, the values needs to be defined here, but we cannot
+// use static/const even with extern since it causes failing to link or
+// initializes them after such headers.
+enum : size_t
+{
+ // kSelectionTypeCount is number of SelectionType.
+ kSelectionTypeCount = nsISelectionController::NUM_SELECTIONTYPES,
+};
+
+// kPresentSelectionTypes is selection types which may be displayed.
+// I.e., selection types except eNone.
+static const SelectionType kPresentSelectionTypes[] = {
+ SelectionType::eNormal,
+ SelectionType::eSpellCheck,
+ SelectionType::eIMERawClause,
+ SelectionType::eIMESelectedRawClause,
+ SelectionType::eIMEConvertedClause,
+ SelectionType::eIMESelectedClause,
+ SelectionType::eAccessibility,
+ SelectionType::eFind,
+ SelectionType::eURLSecondary,
+ SelectionType::eURLStrikeout,
+ SelectionType::eHighlight,
+};
+
+// Please include mozilla/dom/Selection.h for the following APIs.
+const char* ToChar(SelectionType aSelectionType);
+inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType);
+inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType);
+inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType);
+inline SelectionTypeMask ToSelectionTypeMask(SelectionType aSelectionType);
+
+} // namespace mozilla
+%}
diff --git a/dom/base/nsISelectionDisplay.idl b/dom/base/nsISelectionDisplay.idl
new file mode 100644
index 0000000000..2434122caf
--- /dev/null
+++ b/dom/base/nsISelectionDisplay.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(0DDF9E1C-1DD2-11B2-A183-908A08AA75AE)]
+interface nsISelectionDisplay : nsISupports
+{
+ const short DISPLAY_TEXT = 1; //display text selected.
+ const short DISPLAY_IMAGES = 2; //show images selected
+ const short DISPLAY_FRAMES = 4; //display hrules ect.
+ const short DISPLAY_ALL = 7; //display all. used for isEditor as well
+
+ /*
+ SetSelectionFlags used to set whether you want to see HRULES/IMAGES with border.
+ also used to tell if the presshell is an editor right now. this should change
+
+ @param aToggle -either DISPLAY_(TEXT,IMAGES,FRAMES,ALL)
+ This will tell the rendering engine to draw the different
+ selection types.
+
+ */
+ void setSelectionFlags(in short toggle);
+
+ /*
+ GetSelectionFlags used to get whether you want to see HRULES/IMAGES with border.
+ also used to tell if the presshell is an editor right now. this should change
+
+ @param short *aReturn - This will be filled with DISPLAY_(TEXT,IMAGE,FRAMES,ALL)
+ bit flags.
+ */
+ short getSelectionFlags();
+
+};
diff --git a/dom/base/nsISelectionListener.idl b/dom/base/nsISelectionListener.idl
new file mode 100644
index 0000000000..1adc640812
--- /dev/null
+++ b/dom/base/nsISelectionListener.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl Document;
+webidl Selection;
+
+[scriptable, uuid(45686299-ae2b-46bc-9502-c56c35691ab9)]
+interface nsISelectionListener : nsISupports
+{
+ const short NO_REASON=0;
+ const short DRAG_REASON=1;
+ const short MOUSEDOWN_REASON=2;/*bitflags*/
+ const short MOUSEUP_REASON=4;/*bitflags*/
+ const short KEYPRESS_REASON=8;/*bitflags*/
+ const short SELECTALL_REASON=16;
+ const short COLLAPSETOSTART_REASON=32;
+ const short COLLAPSETOEND_REASON=64;
+ const short IME_REASON=128;
+ // JS_REASON is set if the selection change is directly caused by a call
+ // of Selection API or Range API.
+ const short JS_REASON=256;
+
+ // Values of nsSelectionAmount.
+ // Reflects the granularity in which the selection caret has moved.
+ const long CHARACTER_AMOUNT = 0;
+ const long CLUSTER_AMOUNT = 1;
+ const long WORD_AMOUNT = 2;
+ const long WORDNOSPACE_AMOUNT = 3;
+ const long LINE_AMOUNT = 4;
+ const long BEGINLINE_AMOUNT = 5;
+ const long ENDLINE_AMOUNT = 6;
+ const long NO_AMOUNT = 7;
+ const long PARAGRAPH_AMOUNT = 8;
+
+ [can_run_script]
+ void notifySelectionChanged(in Document doc, in Selection sel,
+ in short reason, in long amount);
+};
+
+%{C++
+namespace mozilla {
+
+/**
+ * Returning names of `nsISelectionListener::*_REASON` in aReasons.
+ */
+nsCString SelectionChangeReasonsToCString(int16_t aReasons);
+
+} // namespace mozilla
+%}
diff --git a/dom/base/nsISizeOfEventTarget.h b/dom/base/nsISizeOfEventTarget.h
new file mode 100644
index 0000000000..1e58fc24eb
--- /dev/null
+++ b/dom/base/nsISizeOfEventTarget.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsISizeOfEventTarget_h___
+#define nsISizeOfEventTarget_h___
+
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+
+#define NS_ISIZEOFEVENTTARGET_IID \
+ { \
+ 0xa1e08cb9, 0x5455, 0x4593, { \
+ 0xb4, 0x1f, 0x38, 0x7a, 0x85, 0x44, 0xd0, 0xb5 \
+ } \
+ }
+
+/**
+ * This class is much the same as nsISizeOf, but is specifically for measuring
+ * the contents of nsGlobalWindow::mEventTargetObjects.
+ *
+ * We don't use nsISizeOf because if we did, any object belonging to
+ * mEventTargetObjects that implements nsISizeOf would be measured, which we
+ * may not want (perhaps because the object is also measured elsewhere).
+ */
+class nsISizeOfEventTarget : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISIZEOFEVENTTARGET_IID)
+
+ /**
+ * Measures the size of the things pointed to by the object, plus the object
+ * itself.
+ */
+ virtual size_t SizeOfEventTargetIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsISizeOfEventTarget, NS_ISIZEOFEVENTTARGET_IID)
+
+#endif /* nsISizeOfEventTarget_h___ */
diff --git a/dom/base/nsISlowScriptDebug.idl b/dom/base/nsISlowScriptDebug.idl
new file mode 100644
index 0000000000..39cbc8206e
--- /dev/null
+++ b/dom/base/nsISlowScriptDebug.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+
+webidl EventTarget;
+
+[scriptable, function, uuid(f7dbb80c-5d1e-4fd9-b55c-a9ffda4a75b1)]
+interface nsISlowScriptDebugCallback : nsISupports
+{
+ void handleSlowScriptDebug(in nsIDOMWindow aWindow);
+};
+
+[scriptable, function, uuid(b1c6ecd0-8fa4-11e4-b4a9-0800200c9a66)]
+interface nsISlowScriptDebuggerStartupCallback : nsISupports
+{
+ void finishDebuggerStartup();
+};
+
+[scriptable, function, uuid(dbee14b0-8fa0-11e4-b4a9-0800200c9a66)]
+interface nsISlowScriptDebugRemoteCallback : nsISupports
+{
+ void handleSlowScriptDebug(in EventTarget aBrowser,
+ in nsISlowScriptDebuggerStartupCallback aCallback);
+};
+
+[scriptable, uuid(f75d4164-3aa7-4395-ba44-a5f95b2e8427)]
+interface nsISlowScriptDebug : nsISupports
+{
+ attribute nsISlowScriptDebugCallback activationHandler;
+ attribute nsISlowScriptDebugRemoteCallback remoteActivationHandler;
+};
diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp
new file mode 100644
index 0000000000..0eac1c69f7
--- /dev/null
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -0,0 +1,1873 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A base class which implements nsIImageLoadingContent and can be
+ * subclassed by various content nodes that want to provide image
+ * loading functionality (eg <img>, <object>, etc).
+ */
+
+#include "nsImageLoadingContent.h"
+#include "nsError.h"
+#include "nsIContent.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentList.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIURI.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsImageFrame.h"
+
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+
+#include "nsIFrame.h"
+
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIContentPolicy.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/SVGImageFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PContent.h" // For TextRecognitionResult
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/ImageTextBinding.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/Locale.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/widget/TextRecognition.h"
+
+#include "Orientation.h"
+
+#ifdef LoadImage
+// Undefine LoadImage to prevent naming conflict with Windows.
+# undef LoadImage
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#ifdef DEBUG_chb
+static void PrintReqURL(imgIRequest* req) {
+ if (!req) {
+ printf("(null req)\n");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ req->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ printf("(null uri)\n");
+ return;
+ }
+
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ printf("spec='%s'\n", spec.get());
+}
+#endif /* DEBUG_chb */
+
+const nsAttrValue::EnumTable nsImageLoadingContent::kDecodingTable[] = {
+ {"auto", nsImageLoadingContent::ImageDecodingType::Auto},
+ {"async", nsImageLoadingContent::ImageDecodingType::Async},
+ {"sync", nsImageLoadingContent::ImageDecodingType::Sync},
+ {nullptr, 0}};
+
+const nsAttrValue::EnumTable* nsImageLoadingContent::kDecodingTableDefault =
+ &nsImageLoadingContent::kDecodingTable[0];
+
+nsImageLoadingContent::nsImageLoadingContent()
+ : mObserverList(nullptr),
+ mOutstandingDecodePromises(0),
+ mRequestGeneration(0),
+ mLoadingEnabled(true),
+ mLoading(false),
+ // mBroken starts out true, since an image without a URI is broken....
+ mBroken(true),
+ mNewRequestsWillNeedAnimationReset(false),
+ mUseUrgentStartForChannel(false),
+ mLazyLoading(false),
+ mStateChangerDepth(0),
+ mCurrentRequestRegistered(false),
+ mPendingRequestRegistered(false),
+ mIsStartingImageLoad(false),
+ mSyncDecodingHint(false) {
+ if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = false;
+ }
+
+ mMostRecentRequestChange = TimeStamp::ProcessCreation();
+}
+
+void nsImageLoadingContent::Destroy() {
+ // Cancel our requests so they won't hold stale refs to us
+ // NB: Don't ask to discard the images here.
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
+ ClearCurrentRequest(NS_BINDING_ABORTED);
+ ClearPendingRequest(NS_BINDING_ABORTED);
+}
+
+nsImageLoadingContent::~nsImageLoadingContent() {
+ MOZ_ASSERT(!mCurrentRequest && !mPendingRequest, "Destroy not called");
+ MOZ_ASSERT(!mObserverList.mObserver && !mObserverList.mNext,
+ "Observers still registered?");
+ MOZ_ASSERT(mScriptedObservers.IsEmpty(),
+ "Scripted observers still registered?");
+ MOZ_ASSERT(mOutstandingDecodePromises == 0,
+ "Decode promises still unfulfilled?");
+ MOZ_ASSERT(mDecodePromises.IsEmpty(), "Decode promises still unfulfilled?");
+}
+
+/*
+ * imgINotificationObserver impl
+ */
+void nsImageLoadingContent::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ MOZ_ASSERT(aRequest, "no request?");
+ MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+ "Forgot to cancel a previous request?");
+
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return OnImageIsAnimated(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
+ return OnUnlockedDraw();
+ }
+
+ {
+ // Calling Notify on observers can modify the list of observers so make
+ // a local copy.
+ AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
+ for (ImageObserver *observer = &mObserverList, *next; observer;
+ observer = next) {
+ next = observer->mNext;
+ if (observer->mObserver) {
+ observers.AppendElement(observer->mObserver);
+ }
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ for (auto& observer : observers) {
+ observer->Notify(aRequest, aType, aData);
+ }
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Have to check for state changes here, since we might have been in
+ // the LOADING state before.
+ UpdateImageState(true);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t reqStatus;
+ aRequest->GetImageStatus(&reqStatus);
+ /* triage STATUS_ERROR */
+ if (reqStatus & imgIRequest::STATUS_ERROR) {
+ nsresult errorCode = NS_OK;
+ aRequest->GetImageErrorCode(&errorCode);
+
+ /* Handle image not loading error because source was a tracking URL (or
+ * fingerprinting, cryptomining, etc).
+ * We make a note of this image node by including it in a dedicated
+ * array of blocked tracking nodes under its parent document.
+ */
+ if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ errorCode)) {
+ Document* doc = GetOurOwnerDoc();
+ doc->AddBlockedNodeByClassifier(AsContent());
+ }
+ }
+ nsresult status =
+ reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+
+ if ((aType == imgINotificationObserver::FRAME_COMPLETE ||
+ aType == imgINotificationObserver::FRAME_UPDATE) &&
+ mCurrentRequest == aRequest) {
+ MaybeResolveDecodePromises();
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ nsCOMPtr<imgIContainer> container;
+ aRequest->GetImage(getter_AddRefs(container));
+ if (container) {
+ container->PropagateUseCounters(GetOurOwnerDoc());
+ }
+
+ UpdateImageState(true);
+ }
+}
+
+void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest,
+ nsresult aStatus) {
+ uint32_t oldStatus;
+ aRequest->GetImageStatus(&oldStatus);
+
+ // XXXjdm This occurs when we have a pending request created, then another
+ // pending request replaces it before the first one is finished.
+ // This begs the question of what the correct behaviour is; we used
+ // to not have to care because we ran this code in OnStopDecode which
+ // wasn't called when the first request was cancelled. For now, I choose
+ // to punt when the given request doesn't appear to have terminated in
+ // an expected state.
+ if (!(oldStatus &
+ (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) {
+ return;
+ }
+
+ // Our state may change. Watch it.
+ AutoStateChanger changer(this, true);
+
+ // If the pending request is loaded, switch to it.
+ if (aRequest == mPendingRequest) {
+ MakePendingRequestCurrent();
+ }
+ MOZ_ASSERT(aRequest == mCurrentRequest,
+ "One way or another, we should be current by now");
+
+ // Fire the appropriate DOM event.
+ if (NS_SUCCEEDED(aStatus)) {
+ FireEvent(u"load"_ns);
+ } else {
+ FireEvent(u"error"_ns);
+ }
+
+ SVGObserverUtils::InvalidateDirectRenderingObservers(
+ AsContent()->AsElement());
+ MaybeResolveDecodePromises();
+}
+
+void nsImageLoadingContent::OnUnlockedDraw() {
+ // This notification is only sent for animated images. It's OK for
+ // non-animated images to wait until the next frame visibility update to
+ // become locked. (And that's preferable, since in the case of scrolling it
+ // keeps memory usage minimal.)
+ //
+ // For animated images, though, we want to mark them visible right away so we
+ // can call IncrementAnimationConsumers() on them and they'll start animating.
+
+ nsIFrame* frame = GetOurPrimaryImageFrame();
+ if (!frame) {
+ return;
+ }
+
+ if (frame->GetVisibility() == Visibility::ApproximatelyVisible) {
+ // This frame is already marked visible; there's nothing to do.
+ return;
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ if (!presContext) {
+ return;
+ }
+
+ PresShell* presShell = presContext->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ presShell->EnsureFrameInApproximatelyVisibleList(frame);
+}
+
+void nsImageLoadingContent::OnImageIsAnimated(imgIRequest* aRequest) {
+ bool* requestFlag = nullptr;
+ if (aRequest == mCurrentRequest) {
+ requestFlag = &mCurrentRequestRegistered;
+ } else if (aRequest == mPendingRequest) {
+ requestFlag = &mPendingRequestRegistered;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Which image is this?");
+ return;
+ }
+ nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest,
+ requestFlag);
+}
+
+static bool IsOurImageFrame(nsIFrame* aFrame) {
+ if (nsImageFrame* f = do_QueryFrame(aFrame)) {
+ return f->IsForImageLoadingContent();
+ }
+ return aFrame->IsSVGImageFrame() || aFrame->IsSVGFEImageFrame();
+}
+
+nsIFrame* nsImageLoadingContent::GetOurPrimaryImageFrame() {
+ nsIFrame* frame = AsContent()->GetPrimaryFrame();
+ if (!frame || !IsOurImageFrame(frame)) {
+ return nullptr;
+ }
+ return frame;
+}
+
+/*
+ * nsIImageLoadingContent impl
+ */
+
+void nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled) {
+ if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = aLoadingEnabled;
+ }
+}
+
+nsresult nsImageLoadingContent::GetSyncDecodingHint(bool* aHint) {
+ *aHint = mSyncDecodingHint;
+ return NS_OK;
+}
+
+already_AddRefed<Promise> nsImageLoadingContent::QueueDecodeAsync(
+ ErrorResult& aRv) {
+ Document* doc = GetOurOwnerDoc();
+ RefPtr<Promise> promise = Promise::Create(doc->GetScopeObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ class QueueDecodeTask final : public MicroTaskRunnable {
+ public:
+ QueueDecodeTask(nsImageLoadingContent* aOwner, Promise* aPromise,
+ uint32_t aRequestGeneration)
+ : MicroTaskRunnable(),
+ mOwner(aOwner),
+ mPromise(aPromise),
+ mRequestGeneration(aRequestGeneration) {}
+
+ virtual void Run(AutoSlowOperation& aAso) override {
+ mOwner->DecodeAsync(std::move(mPromise), mRequestGeneration);
+ }
+
+ virtual bool Suppressed() override {
+ nsIGlobalObject* global = mOwner->GetOurOwnerDoc()->GetScopeObject();
+ return global && global->IsInSyncOperation();
+ }
+
+ private:
+ RefPtr<nsImageLoadingContent> mOwner;
+ RefPtr<Promise> mPromise;
+ uint32_t mRequestGeneration;
+ };
+
+ if (++mOutstandingDecodePromises == 1) {
+ MOZ_ASSERT(mDecodePromises.IsEmpty());
+ doc->RegisterActivityObserver(AsContent()->AsElement());
+ }
+
+ auto task = MakeRefPtr<QueueDecodeTask>(this, promise, mRequestGeneration);
+ CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
+ return promise.forget();
+}
+
+void nsImageLoadingContent::DecodeAsync(RefPtr<Promise>&& aPromise,
+ uint32_t aRequestGeneration) {
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mOutstandingDecodePromises > mDecodePromises.Length());
+
+ // The request may have gotten updated since the decode call was issued.
+ if (aRequestGeneration != mRequestGeneration) {
+ aPromise->MaybeReject(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
+ // We never got placed in mDecodePromises, so we must ensure we decrement
+ // the counter explicitly.
+ --mOutstandingDecodePromises;
+ MaybeDeregisterActivityObserver();
+ return;
+ }
+
+ bool wasEmpty = mDecodePromises.IsEmpty();
+ mDecodePromises.AppendElement(std::move(aPromise));
+ if (wasEmpty) {
+ MaybeResolveDecodePromises();
+ }
+}
+
+void nsImageLoadingContent::MaybeResolveDecodePromises() {
+ if (mDecodePromises.IsEmpty()) {
+ return;
+ }
+
+ if (!mCurrentRequest) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
+ return;
+ }
+
+ // Only can resolve if our document is the active document. If not we are
+ // supposed to reject the promise, even if it was fulfilled successfully.
+ if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
+ return;
+ }
+
+ // If any error occurred while decoding, we need to reject first.
+ uint32_t status = imgIRequest::STATUS_NONE;
+ mCurrentRequest->GetImageStatus(&status);
+ if (status & imgIRequest::STATUS_ERROR) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
+ return;
+ }
+
+ // We need the size to bother with requesting a decode, as we are either
+ // blocked on validation or metadata decoding.
+ if (!(status & imgIRequest::STATUS_SIZE_AVAILABLE)) {
+ return;
+ }
+
+ // Check the surface cache status and/or request decoding begin. We do this
+ // before LOAD_COMPLETE because we want to start as soon as possible.
+ uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
+ imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE;
+ imgIContainer::DecodeResult decodeResult =
+ mCurrentRequest->RequestDecodeWithResult(flags);
+ if (decodeResult == imgIContainer::DECODE_REQUESTED) {
+ return;
+ }
+ if (decodeResult == imgIContainer::DECODE_REQUEST_FAILED) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
+ return;
+ }
+ MOZ_ASSERT(decodeResult == imgIContainer::DECODE_SURFACE_AVAILABLE);
+
+ // We can only fulfill the promises once we have all the data.
+ if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ return;
+ }
+
+ for (auto& promise : mDecodePromises) {
+ promise->MaybeResolveWithUndefined();
+ }
+
+ MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
+ mOutstandingDecodePromises -= mDecodePromises.Length();
+ mDecodePromises.Clear();
+ MaybeDeregisterActivityObserver();
+}
+
+void nsImageLoadingContent::RejectDecodePromises(nsresult aStatus) {
+ if (mDecodePromises.IsEmpty()) {
+ return;
+ }
+
+ for (auto& promise : mDecodePromises) {
+ promise->MaybeReject(aStatus);
+ }
+
+ MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
+ mOutstandingDecodePromises -= mDecodePromises.Length();
+ mDecodePromises.Clear();
+ MaybeDeregisterActivityObserver();
+}
+
+void nsImageLoadingContent::MaybeAgeRequestGeneration(nsIURI* aNewURI) {
+ MOZ_ASSERT(mCurrentRequest);
+
+ // If the current request is about to change, we need to verify if the new
+ // URI matches the existing current request's URI. If it doesn't, we need to
+ // reject any outstanding promises due to the current request mutating as per
+ // step 2.2 of the decode API requirements.
+ //
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode
+ if (aNewURI) {
+ nsCOMPtr<nsIURI> currentURI;
+ mCurrentRequest->GetURI(getter_AddRefs(currentURI));
+
+ bool equal = false;
+ if (NS_SUCCEEDED(aNewURI->Equals(currentURI, &equal)) && equal) {
+ return;
+ }
+ }
+
+ ++mRequestGeneration;
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
+}
+
+void nsImageLoadingContent::MaybeDeregisterActivityObserver() {
+ if (mOutstandingDecodePromises == 0) {
+ MOZ_ASSERT(mDecodePromises.IsEmpty());
+ GetOurOwnerDoc()->UnregisterActivityObserver(AsContent()->AsElement());
+ }
+}
+
+void nsImageLoadingContent::SetSyncDecodingHint(bool aHint) {
+ if (mSyncDecodingHint == aHint) {
+ return;
+ }
+
+ mSyncDecodingHint = aHint;
+ MaybeForceSyncDecoding(/* aPrepareNextRequest */ false);
+}
+
+void nsImageLoadingContent::MaybeForceSyncDecoding(
+ bool aPrepareNextRequest, nsIFrame* aFrame /* = nullptr */) {
+ // GetOurPrimaryImageFrame() might not return the frame during frame init.
+ nsIFrame* frame = aFrame ? aFrame : GetOurPrimaryImageFrame();
+ if (!frame) {
+ return;
+ }
+
+ bool forceSync = mSyncDecodingHint;
+ if (!forceSync && aPrepareNextRequest) {
+ // Detect JavaScript-based animations created by changing the |src|
+ // attribute on a timer.
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration threshold = TimeDuration::FromMilliseconds(
+ StaticPrefs::image_infer_src_animation_threshold_ms());
+
+ // If the length of time between request changes is less than the threshold,
+ // then force sync decoding to eliminate flicker from the animation.
+ forceSync = (now - mMostRecentRequestChange < threshold);
+ mMostRecentRequestChange = now;
+ }
+
+ if (nsImageFrame* imageFrame = do_QueryFrame(frame)) {
+ imageFrame->SetForceSyncDecoding(forceSync);
+ } else if (SVGImageFrame* svgImageFrame = do_QueryFrame(frame)) {
+ svgImageFrame->SetForceSyncDecoding(forceSync);
+ }
+}
+
+static void ReplayImageStatus(imgIRequest* aRequest,
+ imgINotificationObserver* aObserver) {
+ if (!aRequest) {
+ return;
+ }
+
+ uint32_t status = 0;
+ nsresult rv = aRequest->GetImageStatus(&status);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE,
+ nullptr);
+ }
+ if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE,
+ nullptr);
+ }
+ if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
+ aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY,
+ nullptr);
+ }
+ if (status & imgIRequest::STATUS_IS_ANIMATED) {
+ aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
+ }
+ if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE,
+ nullptr);
+ }
+ if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE,
+ nullptr);
+ }
+}
+
+void nsImageLoadingContent::AddNativeObserver(
+ imgINotificationObserver* aObserver) {
+ if (NS_WARN_IF(!aObserver)) {
+ return;
+ }
+
+ if (!mObserverList.mObserver) {
+ // Don't touch the linking of the list!
+ mObserverList.mObserver = aObserver;
+
+ ReplayImageStatus(mCurrentRequest, aObserver);
+ ReplayImageStatus(mPendingRequest, aObserver);
+
+ return;
+ }
+
+ // otherwise we have to create a new entry
+
+ ImageObserver* observer = &mObserverList;
+ while (observer->mNext) {
+ observer = observer->mNext;
+ }
+
+ observer->mNext = new ImageObserver(aObserver);
+ ReplayImageStatus(mCurrentRequest, aObserver);
+ ReplayImageStatus(mPendingRequest, aObserver);
+}
+
+void nsImageLoadingContent::RemoveNativeObserver(
+ imgINotificationObserver* aObserver) {
+ if (NS_WARN_IF(!aObserver)) {
+ return;
+ }
+
+ if (mObserverList.mObserver == aObserver) {
+ mObserverList.mObserver = nullptr;
+ // Don't touch the linking of the list!
+ return;
+ }
+
+ // otherwise have to find it and splice it out
+ ImageObserver* observer = &mObserverList;
+ while (observer->mNext && observer->mNext->mObserver != aObserver) {
+ observer = observer->mNext;
+ }
+
+ // At this point, we are pointing to the list element whose mNext is
+ // the right observer (assuming of course that mNext is not null)
+ if (observer->mNext) {
+ // splice it out
+ ImageObserver* oldObserver = observer->mNext;
+ observer->mNext = oldObserver->mNext;
+ oldObserver->mNext = nullptr; // so we don't destroy them all
+ delete oldObserver;
+ }
+#ifdef DEBUG
+ else {
+ NS_WARNING("Asked to remove nonexistent observer");
+ }
+#endif
+}
+
+void nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) {
+ if (NS_WARN_IF(!aObserver)) {
+ return;
+ }
+
+ RefPtr<imgRequestProxy> currentReq;
+ if (mCurrentRequest) {
+ // Scripted observers may not belong to the same document as us, so when we
+ // create the imgRequestProxy, we shouldn't use any. This allows the request
+ // to dispatch notifications from the correct scheduler group.
+ nsresult rv =
+ mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ RefPtr<imgRequestProxy> pendingReq;
+ if (mPendingRequest) {
+ // See above for why we don't use the loading document.
+ nsresult rv =
+ mPendingRequest->Clone(aObserver, nullptr, getter_AddRefs(pendingReq));
+ if (NS_FAILED(rv)) {
+ mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ return;
+ }
+ }
+
+ mScriptedObservers.AppendElement(new ScriptedImageObserver(
+ aObserver, std::move(currentReq), std::move(pendingReq)));
+}
+
+void nsImageLoadingContent::RemoveObserver(
+ imgINotificationObserver* aObserver) {
+ if (NS_WARN_IF(!aObserver)) {
+ return;
+ }
+
+ if (NS_WARN_IF(mScriptedObservers.IsEmpty())) {
+ return;
+ }
+
+ RefPtr<ScriptedImageObserver> observer;
+ auto i = mScriptedObservers.Length();
+ do {
+ --i;
+ if (mScriptedObservers[i]->mObserver == aObserver) {
+ observer = std::move(mScriptedObservers[i]);
+ mScriptedObservers.RemoveElementAt(i);
+ break;
+ }
+ } while (i > 0);
+
+ if (NS_WARN_IF(!observer)) {
+ return;
+ }
+
+ // If the cancel causes a mutation, it will be harmless, because we have
+ // already removed the observer from the list.
+ observer->CancelRequests();
+}
+
+void nsImageLoadingContent::ClearScriptedRequests(int32_t aRequestType,
+ nsresult aReason) {
+ if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
+ return;
+ }
+
+ nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
+ auto i = observers.Length();
+ do {
+ --i;
+
+ RefPtr<imgRequestProxy> req;
+ switch (aRequestType) {
+ case CURRENT_REQUEST:
+ req = std::move(observers[i]->mCurrentRequest);
+ break;
+ case PENDING_REQUEST:
+ req = std::move(observers[i]->mPendingRequest);
+ break;
+ default:
+ NS_ERROR("Unknown request type");
+ return;
+ }
+
+ if (req) {
+ req->CancelAndForgetObserver(aReason);
+ }
+ } while (i > 0);
+}
+
+void nsImageLoadingContent::CloneScriptedRequests(imgRequestProxy* aRequest) {
+ MOZ_ASSERT(aRequest);
+
+ if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
+ return;
+ }
+
+ bool current;
+ if (aRequest == mCurrentRequest) {
+ current = true;
+ } else if (aRequest == mPendingRequest) {
+ current = false;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown request type");
+ return;
+ }
+
+ nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
+ auto i = observers.Length();
+ do {
+ --i;
+
+ ScriptedImageObserver* observer = observers[i];
+ RefPtr<imgRequestProxy>& req =
+ current ? observer->mCurrentRequest : observer->mPendingRequest;
+ if (NS_WARN_IF(req)) {
+ MOZ_ASSERT_UNREACHABLE("Should have cancelled original request");
+ req->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ req = nullptr;
+ }
+
+ nsresult rv =
+ aRequest->Clone(observer->mObserver, nullptr, getter_AddRefs(req));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ } while (i > 0);
+}
+
+void nsImageLoadingContent::MakePendingScriptedRequestsCurrent() {
+ if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
+ return;
+ }
+
+ nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
+ auto i = observers.Length();
+ do {
+ --i;
+
+ ScriptedImageObserver* observer = observers[i];
+ if (observer->mCurrentRequest) {
+ observer->mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+ observer->mCurrentRequest = std::move(observer->mPendingRequest);
+ } while (i > 0);
+}
+
+already_AddRefed<imgIRequest> nsImageLoadingContent::GetRequest(
+ int32_t aRequestType, ErrorResult& aError) {
+ nsCOMPtr<imgIRequest> request;
+ switch (aRequestType) {
+ case CURRENT_REQUEST:
+ request = mCurrentRequest;
+ break;
+ case PENDING_REQUEST:
+ request = mPendingRequest;
+ break;
+ default:
+ NS_ERROR("Unknown request type");
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ }
+
+ return request.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetRequest(int32_t aRequestType,
+ imgIRequest** aRequest) {
+ NS_ENSURE_ARG_POINTER(aRequest);
+
+ ErrorResult result;
+ *aRequest = GetRequest(aRequestType, result).take();
+
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "aFrame is null");
+ MOZ_ASSERT(IsOurImageFrame(aFrame));
+
+ MaybeForceSyncDecoding(/* aPrepareNextRequest */ false, aFrame);
+ TrackImage(mCurrentRequest, aFrame);
+ TrackImage(mPendingRequest, aFrame);
+
+ // We need to make sure that our image request is registered, if it should
+ // be registered.
+ nsPresContext* presContext = aFrame->PresContext();
+ if (mCurrentRequest) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest,
+ &mCurrentRequestRegistered);
+ }
+
+ if (mPendingRequest) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest,
+ &mPendingRequestRegistered);
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) {
+ NS_ASSERTION(aFrame, "aFrame is null");
+
+ // We need to make sure that our image request is deregistered.
+ nsPresContext* presContext = GetFramePresContext();
+ if (mCurrentRequest) {
+ nsLayoutUtils::DeregisterImageRequest(presContext, mCurrentRequest,
+ &mCurrentRequestRegistered);
+ }
+
+ if (mPendingRequest) {
+ nsLayoutUtils::DeregisterImageRequest(presContext, mPendingRequest,
+ &mPendingRequestRegistered);
+ }
+
+ UntrackImage(mCurrentRequest);
+ UntrackImage(mPendingRequest);
+
+ PresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
+ if (presShell) {
+ presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
+ }
+}
+
+/* static */
+nsContentPolicyType nsImageLoadingContent::PolicyTypeForLoad(
+ ImageLoadType aImageLoadType) {
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ return nsIContentPolicy::TYPE_IMAGESET;
+ }
+
+ MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
+ "Unknown ImageLoadType type in PolicyTypeForLoad");
+ return nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+}
+
+int32_t nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
+ ErrorResult& aError) {
+ if (aRequest == mCurrentRequest) {
+ return CURRENT_REQUEST;
+ }
+
+ if (aRequest == mPendingRequest) {
+ return PENDING_REQUEST;
+ }
+
+ NS_ERROR("Unknown request");
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return UNKNOWN_REQUEST;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
+ int32_t* aRequestType) {
+ MOZ_ASSERT(aRequestType, "Null out param");
+
+ ErrorResult result;
+ *aRequestType = GetRequestType(aRequest, result);
+ return result.StealNSResult();
+}
+
+already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentURI() {
+ nsCOMPtr<nsIURI> uri;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetURI(getter_AddRefs(uri));
+ } else {
+ uri = mCurrentURI;
+ }
+
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetCurrentURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ *aURI = GetCurrentURI().take();
+ return NS_OK;
+}
+
+already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentRequestFinalURI() {
+ nsCOMPtr<nsIURI> uri;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetFinalURI(getter_AddRefs(uri));
+ }
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
+ nsIStreamListener** aListener) {
+ imgLoader* loader =
+ nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
+ if (!loader) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<Document> doc = GetOurOwnerDoc();
+ if (!doc) {
+ // Don't bother
+ *aListener = nullptr;
+ return NS_OK;
+ }
+
+ // XXX what should we do with content policies here, if anything?
+ // Shouldn't that be done before the start of the load?
+ // XXX what about shouldProcess?
+
+ // If we have a current request without a size, we know we will replace it
+ // with the PrepareNextRequest below. If the new current request is for a
+ // different URI, then we need to reject any outstanding promises.
+ if (mCurrentRequest && !HaveSize(mCurrentRequest)) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetOriginalURI(getter_AddRefs(uri));
+ MaybeAgeRequestGeneration(uri);
+ }
+
+ // Our state might change. Watch it.
+ AutoStateChanger changer(this, true);
+
+ // Do the load.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal);
+ nsresult rv = loader->LoadImageWithChannel(aChannel, this, doc, aListener,
+ getter_AddRefs(req));
+ if (NS_SUCCEEDED(rv)) {
+ CloneScriptedRequests(req);
+ TrackImage(req);
+ ResetAnimationIfNeeded();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ // If we don't have a current URI, we might as well store this URI so people
+ // know what we tried (and failed) to load.
+ if (!mCurrentRequest) aChannel->GetURI(getter_AddRefs(mCurrentURI));
+
+ FireEvent(u"error"_ns);
+ return rv;
+}
+
+void nsImageLoadingContent::ForceReload(bool aNotify, ErrorResult& aError) {
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ if (!currentURI) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ // We keep this flag around along with the old URI even for failed requests
+ // without a live request object
+ ImageLoadType loadType = (mCurrentRequestFlags & REQUEST_IS_IMAGESET)
+ ? eImageLoadType_Imageset
+ : eImageLoadType_Normal;
+ nsresult rv = LoadImage(currentURI, true, aNotify, loadType,
+ nsIRequest::VALIDATE_ALWAYS | LoadFlags());
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+/*
+ * Non-interface methods
+ */
+
+nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType,
+ nsIPrincipal* aTriggeringPrincipal) {
+ // First, get a document (needed for security checks and the like)
+ Document* doc = GetOurOwnerDoc();
+ if (!doc) {
+ // No reason to bother, I think...
+ return NS_OK;
+ }
+
+ // Parse the URI string to get image URI
+ nsCOMPtr<nsIURI> imageURI;
+ if (!aNewURI.IsEmpty()) {
+ Unused << StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
+ }
+
+ return LoadImage(imageURI, aForce, aNotify, aImageLoadType, LoadFlags(), doc,
+ aTriggeringPrincipal);
+}
+
+nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType,
+ nsLoadFlags aLoadFlags,
+ Document* aDocument,
+ nsIPrincipal* aTriggeringPrincipal) {
+ MOZ_ASSERT(!mIsStartingImageLoad, "some evil code is reentering LoadImage.");
+ if (mIsStartingImageLoad) {
+ return NS_OK;
+ }
+
+ // Pending load/error events need to be canceled in some situations. This
+ // is not documented in the spec, but can cause site compat problems if not
+ // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
+ CancelPendingEvent();
+
+ if (!aNewURI) {
+ // Cancel image requests and then fire only error event per spec.
+ CancelImageRequests(aNotify);
+ if (aImageLoadType == eImageLoadType_Normal) {
+ // Mark error event as cancelable only for src="" case, since only this
+ // error causes site compat problem (bug 1308069) for now.
+ FireEvent(u"error"_ns, true);
+ }
+ return NS_OK;
+ }
+
+ if (!mLoadingEnabled) {
+ // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
+ // don't want/need it.
+ FireEvent(u"error"_ns);
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(),
+ "Bogus document passed in");
+ // First, get a document (needed for security checks and the like)
+ if (!aDocument) {
+ aDocument = GetOurOwnerDoc();
+ if (!aDocument) {
+ // No reason to bother, I think...
+ return NS_OK;
+ }
+ }
+
+ AutoRestore<bool> guard(mIsStartingImageLoad);
+ mIsStartingImageLoad = true;
+
+ // Data documents, or documents from DOMParser shouldn't perform image
+ // loading.
+ //
+ // FIXME(emilio): Shouldn't this check be part of
+ // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here
+ // instead? (It seems we only check ShouldLoadImages in HTMLImageElement,
+ // which seems wrong...)
+ if (aDocument->IsLoadedAsData() && !aDocument->IsStaticDocument()) {
+ // Clear our pending request if we do have one.
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
+
+ FireEvent(u"error"_ns);
+ return NS_OK;
+ }
+
+ // URI equality check.
+ //
+ // We skip the equality check if we don't have a current image, since in that
+ // case we really do want to try loading again.
+ if (!aForce && mCurrentRequest) {
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ bool equal;
+ if (currentURI && NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) &&
+ equal) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ }
+
+ // If we have a current request without a size, we know we will replace it
+ // with the PrepareNextRequest below. If the new current request is for a
+ // different URI, then we need to reject any outstanding promises.
+ if (mCurrentRequest && !HaveSize(mCurrentRequest)) {
+ MaybeAgeRequestGeneration(aNewURI);
+ }
+
+ // From this point on, our image state could change. Watch it.
+ AutoStateChanger changer(this, aNotify);
+
+ // Sanity check.
+ //
+ // We use the principal of aDocument to avoid having to QI |this| an extra
+ // time. It should always be the same as the principal of this node.
+ Element* element = AsContent()->AsElement();
+ MOZ_ASSERT(element->NodePrincipal() == aDocument->NodePrincipal(),
+ "Principal mismatch?");
+
+ nsLoadFlags loadFlags =
+ aLoadFlags | nsContentUtils::CORSModeToLoadImageFlags(GetCORSMode());
+
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ bool result = nsContentUtils::QueryTriggeringPrincipal(
+ element, aTriggeringPrincipal, getter_AddRefs(triggeringPrincipal));
+
+ // If result is true, which means this node has specified
+ // 'triggeringprincipal' attribute on it, so we use favicon as the policy
+ // type.
+ nsContentPolicyType policyType =
+ result ? nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
+ : PolicyTypeForLoad(aImageLoadType);
+
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*element);
+ nsresult rv = nsContentUtils::LoadImage(
+ aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo, this,
+ loadFlags, element->LocalName(), getter_AddRefs(req), policyType,
+ mUseUrgentStartForChannel);
+
+ // Reset the flag to avoid loading from XPCOM or somewhere again else without
+ // initiated by user interaction.
+ mUseUrgentStartForChannel = false;
+
+ // Tell the document to forget about the image preload, if any, for
+ // this URI, now that we might have another imgRequestProxy for it.
+ // That way if we get canceled later the image load won't continue.
+ aDocument->ForgetImagePreload(aNewURI);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Based on performance testing unsuppressing painting soon after the page
+ // has gotten an image may improve visual metrics.
+ if (Document* doc = element->GetComposedDoc()) {
+ if (PresShell* shell = doc->GetPresShell()) {
+ shell->TryUnsuppressPaintingSoon();
+ }
+ }
+
+ CloneScriptedRequests(req);
+ TrackImage(req);
+ ResetAnimationIfNeeded();
+
+ // Handle cases when we just ended up with a request but it's already done.
+ // In that situation we have to synchronously switch that request to being
+ // the current request, because websites depend on that behavior.
+ {
+ uint32_t loadStatus;
+ if (NS_SUCCEEDED(req->GetImageStatus(&loadStatus)) &&
+ (loadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ if (req == mPendingRequest) {
+ MakePendingRequestCurrent();
+ }
+ MOZ_ASSERT(mCurrentRequest,
+ "How could we not have a current request here?");
+
+ if (nsImageFrame* f = do_QueryFrame(GetOurPrimaryImageFrame())) {
+ f->NotifyNewCurrentRequest(mCurrentRequest, NS_OK);
+ }
+ }
+ }
+ } else {
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ // If we don't have a current URI, we might as well store this URI so people
+ // know what we tried (and failed) to load.
+ if (!mCurrentRequest) {
+ mCurrentURI = aNewURI;
+ }
+
+ FireEvent(u"error"_ns);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<Promise> nsImageLoadingContent::RecognizeCurrentImageText(
+ ErrorResult& aRv) {
+ using widget::TextRecognition;
+
+ if (!mCurrentRequest) {
+ aRv.ThrowInvalidStateError("No current request");
+ return nullptr;
+ }
+ nsCOMPtr<imgIContainer> image;
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ if (!image) {
+ aRv.ThrowInvalidStateError("No image");
+ return nullptr;
+ }
+
+ RefPtr<Promise> domPromise =
+ Promise::Create(GetOurOwnerDoc()->GetScopeObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // The list of ISO 639-1 language tags to pass to the text recognition API.
+ AutoTArray<nsCString, 4> languages;
+ {
+ // The document's locale should be the top language to use. Parse the BCP 47
+ // locale and extract the ISO 639-1 language tag. e.g. "en-US" -> "en".
+ nsAutoCString elementLanguage;
+ nsAtom* imgLanguage = AsContent()->GetLang();
+ intl::Locale locale;
+ if (imgLanguage) {
+ imgLanguage->ToUTF8String(elementLanguage);
+ auto result = intl::LocaleParser::TryParse(elementLanguage, locale);
+ if (result.isOk()) {
+ languages.AppendElement(locale.Language().Span());
+ }
+ }
+ }
+
+ {
+ // The app locales should also be included after the document's locales.
+ // Extract the language tag like above.
+ nsTArray<nsCString> appLocales;
+ intl::LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
+
+ for (const auto& localeString : appLocales) {
+ intl::Locale locale;
+ auto result = intl::LocaleParser::TryParse(localeString, locale);
+ if (result.isErr()) {
+ NS_WARNING("Could not parse an app locale string, ignoring it.");
+ continue;
+ }
+ languages.AppendElement(locale.Language().Span());
+ }
+ }
+
+ TextRecognition::FindText(*image, languages)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [weak = RefPtr{do_GetWeakReference(this)},
+ request = RefPtr{mCurrentRequest}, domPromise](
+ TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsReject()) {
+ domPromise->MaybeRejectWithNotSupportedError(
+ aValue.RejectValue());
+ return;
+ }
+ RefPtr<nsIImageLoadingContent> iilc = do_QueryReferent(weak.get());
+ if (!iilc) {
+ domPromise->MaybeRejectWithInvalidStateError(
+ "Element was dead when we got the results");
+ return;
+ }
+ auto* ilc = static_cast<nsImageLoadingContent*>(iilc.get());
+ if (ilc->mCurrentRequest != request) {
+ domPromise->MaybeRejectWithInvalidStateError(
+ "Request not current");
+ return;
+ }
+ auto& textRecognitionResult = aValue.ResolveValue();
+ Element* el = ilc->AsContent()->AsElement();
+
+ // When enabled, this feature will place the recognized text as
+ // spans inside of the shadow dom of the img element. These are then
+ // positioned so that the user can select the text.
+ if (Preferences::GetBool("dom.text-recognition.shadow-dom-enabled",
+ false)) {
+ el->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::Yes);
+ TextRecognition::FillShadow(*el->GetShadowRoot(),
+ textRecognitionResult);
+ el->NotifyUAWidgetSetupOrChange();
+ }
+
+ nsTArray<ImageText> imageTexts(
+ textRecognitionResult.quads().Length());
+ nsIGlobalObject* global = el->OwnerDoc()->GetOwnerGlobal();
+
+ for (const auto& quad : textRecognitionResult.quads()) {
+ NotNull<ImageText*> imageText = imageTexts.AppendElement();
+
+ // Note: These points are not actually CSSPixels, but a DOMQuad is
+ // a conveniently similar structure that can store these values.
+ CSSPoint points[4];
+ points[0] = CSSPoint(quad.points()[0].x, quad.points()[0].y);
+ points[1] = CSSPoint(quad.points()[1].x, quad.points()[1].y);
+ points[2] = CSSPoint(quad.points()[2].x, quad.points()[2].y);
+ points[3] = CSSPoint(quad.points()[3].x, quad.points()[3].y);
+
+ imageText->mQuad = new DOMQuad(global, points);
+ imageText->mConfidence = quad.confidence();
+ imageText->mString = quad.string();
+ }
+ domPromise->MaybeResolve(std::move(imageTexts));
+ });
+ return domPromise.forget();
+}
+
+CSSIntSize nsImageLoadingContent::GetWidthHeightForImage() {
+ Element* element = AsContent()->AsElement();
+ if (nsIFrame* frame = element->GetPrimaryFrame(FlushType::Layout)) {
+ return CSSIntSize::FromAppUnitsRounded(frame->GetContentRect().Size());
+ }
+ const nsAttrValue* value;
+ nsCOMPtr<imgIContainer> image;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ }
+
+ CSSIntSize size;
+ if ((value = element->GetParsedAttr(nsGkAtoms::width)) &&
+ value->Type() == nsAttrValue::eInteger) {
+ size.width = value->GetIntegerValue();
+ } else if (image) {
+ image->GetWidth(&size.width);
+ }
+
+ if ((value = element->GetParsedAttr(nsGkAtoms::height)) &&
+ value->Type() == nsAttrValue::eInteger) {
+ size.height = value->GetIntegerValue();
+ } else if (image) {
+ image->GetHeight(&size.height);
+ }
+
+ NS_ASSERTION(size.width >= 0, "negative width");
+ NS_ASSERTION(size.height >= 0, "negative height");
+ return size;
+}
+
+ElementState nsImageLoadingContent::ImageState() const {
+ ElementState states;
+
+ if (mBroken) {
+ states |= ElementState::BROKEN;
+ }
+ if (mLoading) {
+ states |= ElementState::LOADING;
+ }
+
+ return states;
+}
+
+void nsImageLoadingContent::UpdateImageState(bool aNotify) {
+ if (mStateChangerDepth > 0) {
+ // Ignore this call; we'll update our state when the outermost state changer
+ // is destroyed. Need this to work around the fact that some ImageLib
+ // stuff is actually sync and hence we can get OnStopDecode called while
+ // we're still under LoadImage, and OnStopDecode doesn't know anything about
+ // aNotify.
+ // XXX - This machinery should be removed after bug 521604.
+ return;
+ }
+
+ nsIContent* thisContent = AsContent();
+
+ mLoading = mBroken = false;
+
+ // If we were blocked, we're broken, so are we if we don't have an image
+ // request at all or the image has errored.
+ if (!mCurrentRequest) {
+ if (!mLazyLoading) {
+ // In case of non-lazy loading, no current request means error, since we
+ // weren't disabled or suppressed
+ mBroken = true;
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
+ }
+ } else {
+ uint32_t currentLoadStatus;
+ nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
+ if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) {
+ mBroken = true;
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
+ } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
+ mLoading = true;
+ }
+ }
+
+ thisContent->AsElement()->UpdateState(aNotify);
+}
+
+void nsImageLoadingContent::CancelImageRequests(bool aNotify) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
+ AutoStateChanger changer(this, aNotify);
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
+}
+
+Document* nsImageLoadingContent::GetOurOwnerDoc() {
+ return AsContent()->OwnerDoc();
+}
+
+Document* nsImageLoadingContent::GetOurCurrentDoc() {
+ return AsContent()->GetComposedDoc();
+}
+
+nsPresContext* nsImageLoadingContent::GetFramePresContext() {
+ nsIFrame* frame = GetOurPrimaryImageFrame();
+ if (!frame) {
+ return nullptr;
+ }
+ return frame->PresContext();
+}
+
+nsresult nsImageLoadingContent::StringToURI(const nsAString& aSpec,
+ Document* aDocument,
+ nsIURI** aURI) {
+ MOZ_ASSERT(aDocument, "Must have a document");
+ MOZ_ASSERT(aURI, "Null out param");
+
+ // (1) Get the base URI
+ nsIContent* thisContent = AsContent();
+ nsIURI* baseURL = thisContent->GetBaseURI();
+
+ // (2) Get the charset
+ auto encoding = aDocument->GetDocumentCharacterSet();
+
+ // (3) Construct the silly thing
+ return NS_NewURI(aURI, aSpec, encoding, baseURL);
+}
+
+nsresult nsImageLoadingContent::FireEvent(const nsAString& aEventType,
+ bool aIsCancelable) {
+ if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
+ // Don't bother to fire any events, especially error events.
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
+ return NS_OK;
+ }
+
+ // We have to fire the event asynchronously so that we won't go into infinite
+ // loops in cases when onLoad handlers reset the src and the new src is in
+ // cache.
+
+ nsCOMPtr<nsINode> thisNode = AsContent();
+
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, CanBubble::eNo,
+ ChromeOnlyDispatch::eNo);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+
+ if (aIsCancelable) {
+ mPendingEvent = loadBlockingAsyncDispatcher;
+ }
+
+ return NS_OK;
+}
+
+void nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
+ if (mPendingEvent == aEvent) {
+ mPendingEvent = nullptr;
+ }
+}
+
+void nsImageLoadingContent::CancelPendingEvent() {
+ if (mPendingEvent) {
+ mPendingEvent->Cancel();
+ mPendingEvent = nullptr;
+ }
+}
+
+RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareNextRequest(
+ ImageLoadType aImageLoadType) {
+ MaybeForceSyncDecoding(/* aPrepareNextRequest */ true);
+
+ // We only want to cancel the existing current request if size is not
+ // available. bz says the web depends on this behavior.
+ // Otherwise, we get rid of any half-baked request that might be sitting there
+ // and make this one current.
+ return HaveSize(mCurrentRequest) ? PreparePendingRequest(aImageLoadType)
+ : PrepareCurrentRequest(aImageLoadType);
+}
+
+RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest(
+ ImageLoadType aImageLoadType) {
+ // Get rid of anything that was there previously.
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
+
+ if (mNewRequestsWillNeedAnimationReset) {
+ mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
+ }
+
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
+ }
+
+ // Return a reference.
+ return mCurrentRequest;
+}
+
+RefPtr<imgRequestProxy>& nsImageLoadingContent::PreparePendingRequest(
+ ImageLoadType aImageLoadType) {
+ // Get rid of anything that was there previously.
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
+
+ if (mNewRequestsWillNeedAnimationReset) {
+ mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
+ }
+
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ mPendingRequestFlags |= REQUEST_IS_IMAGESET;
+ }
+
+ // Return a reference.
+ return mPendingRequest;
+}
+
+namespace {
+
+class ImageRequestAutoLock {
+ public:
+ explicit ImageRequestAutoLock(imgIRequest* aRequest) : mRequest(aRequest) {
+ if (mRequest) {
+ mRequest->LockImage();
+ }
+ }
+
+ ~ImageRequestAutoLock() {
+ if (mRequest) {
+ mRequest->UnlockImage();
+ }
+ }
+
+ private:
+ nsCOMPtr<imgIRequest> mRequest;
+};
+
+} // namespace
+
+void nsImageLoadingContent::MakePendingRequestCurrent() {
+ MOZ_ASSERT(mPendingRequest);
+
+ // If we have a pending request, we know that there is an existing current
+ // request with size information. If the pending request is for a different
+ // URI, then we need to reject any outstanding promises.
+ nsCOMPtr<nsIURI> uri;
+ mPendingRequest->GetURI(getter_AddRefs(uri));
+ MaybeAgeRequestGeneration(uri);
+
+ // Lock mCurrentRequest for the duration of this method. We do this because
+ // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest
+ // and mPendingRequest are both requests for the same image, unlocking
+ // mCurrentRequest before we lock mPendingRequest can cause the lock count
+ // to go to 0 and the image to be discarded!
+ ImageRequestAutoLock autoLock(mCurrentRequest);
+
+ ImageLoadType loadType = (mPendingRequestFlags & REQUEST_IS_IMAGESET)
+ ? eImageLoadType_Imageset
+ : eImageLoadType_Normal;
+
+ PrepareCurrentRequest(loadType) = mPendingRequest;
+ MakePendingScriptedRequestsCurrent();
+ mPendingRequest = nullptr;
+ mCurrentRequestFlags = mPendingRequestFlags;
+ mPendingRequestFlags = 0;
+ mCurrentRequestRegistered = mPendingRequestRegistered;
+ mPendingRequestRegistered = false;
+ ResetAnimationIfNeeded();
+}
+
+void nsImageLoadingContent::ClearCurrentRequest(
+ nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ if (!mCurrentRequest) {
+ // Even if we didn't have a current request, we might have been keeping
+ // a URI and flags as a placeholder for a failed load. Clear that now.
+ mCurrentURI = nullptr;
+ mCurrentRequestFlags = 0;
+ return;
+ }
+ MOZ_ASSERT(!mCurrentURI,
+ "Shouldn't have both mCurrentRequest and mCurrentURI!");
+
+ // Deregister this image from the refresh driver so it no longer receives
+ // notifications.
+ nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
+ &mCurrentRequestRegistered);
+
+ // Clean up the request.
+ UntrackImage(mCurrentRequest, aNonvisibleAction);
+ ClearScriptedRequests(CURRENT_REQUEST, aReason);
+ mCurrentRequest->CancelAndForgetObserver(aReason);
+ mCurrentRequest = nullptr;
+ mCurrentRequestFlags = 0;
+}
+
+void nsImageLoadingContent::ClearPendingRequest(
+ nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ if (!mPendingRequest) return;
+
+ // Deregister this image from the refresh driver so it no longer receives
+ // notifications.
+ nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
+ &mPendingRequestRegistered);
+
+ UntrackImage(mPendingRequest, aNonvisibleAction);
+ ClearScriptedRequests(PENDING_REQUEST, aReason);
+ mPendingRequest->CancelAndForgetObserver(aReason);
+ mPendingRequest = nullptr;
+ mPendingRequestFlags = 0;
+}
+
+void nsImageLoadingContent::ResetAnimationIfNeeded() {
+ if (mCurrentRequest &&
+ (mCurrentRequestFlags & REQUEST_NEEDS_ANIMATION_RESET)) {
+ nsCOMPtr<imgIContainer> container;
+ mCurrentRequest->GetImage(getter_AddRefs(container));
+ if (container) container->ResetAnimation();
+ mCurrentRequestFlags &= ~REQUEST_NEEDS_ANIMATION_RESET;
+ }
+}
+
+bool nsImageLoadingContent::HaveSize(imgIRequest* aImage) {
+ // Handle the null case
+ if (!aImage) return false;
+
+ // Query the image
+ uint32_t status;
+ nsresult rv = aImage->GetImageStatus(&status);
+ return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE));
+}
+
+void nsImageLoadingContent::NotifyOwnerDocumentActivityChanged() {
+ if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
+ RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
+ }
+}
+
+void nsImageLoadingContent::BindToTree(BindContext& aContext,
+ nsINode& aParent) {
+ // We may be getting connected, if so our image should be tracked,
+ if (aContext.InComposedDoc()) {
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+ }
+}
+
+void nsImageLoadingContent::UnbindFromTree(bool aNullParent) {
+ // We may be leaving the document, so if our image is tracked, untrack it.
+ nsCOMPtr<Document> doc = GetOurCurrentDoc();
+ if (!doc) return;
+
+ UntrackImage(mCurrentRequest);
+ UntrackImage(mPendingRequest);
+}
+
+void nsImageLoadingContent::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ switch (aNewVisibility) {
+ case Visibility::ApproximatelyVisible:
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+ break;
+
+ case Visibility::ApproximatelyNonVisible:
+ UntrackImage(mCurrentRequest, aNonvisibleAction);
+ UntrackImage(mPendingRequest, aNonvisibleAction);
+ break;
+
+ case Visibility::Untracked:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
+ break;
+ }
+}
+
+void nsImageLoadingContent::TrackImage(imgIRequest* aImage,
+ nsIFrame* aFrame /*= nullptr */) {
+ if (!aImage) return;
+
+ MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
+ "Why haven't we heard of this request?");
+
+ Document* doc = GetOurCurrentDoc();
+ if (!doc) {
+ return;
+ }
+
+ if (!aFrame) {
+ aFrame = GetOurPrimaryImageFrame();
+ }
+
+ /* This line is deceptively simple. It hides a lot of subtlety. Before we
+ * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor
+ * to determine if we should create an nsImageFrame or create a frame based
+ * on the display of the element (ie inline, block, etc). Inline, block, etc
+ * frames don't register for visibility tracking so they will return UNTRACKED
+ * from GetVisibility(). So this line is choosing to mark such images as
+ * visible. Once the image loads we will get an nsImageFrame and the proper
+ * visibility. This is a pitfall of tracking the visibility on the frames
+ * instead of the content node.
+ */
+ if (!aFrame ||
+ aFrame->GetVisibility() == Visibility::ApproximatelyNonVisible) {
+ return;
+ }
+
+ if (aImage == mCurrentRequest &&
+ !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
+ mCurrentRequestFlags |= REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Add(mCurrentRequest);
+ }
+ if (aImage == mPendingRequest &&
+ !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
+ mPendingRequestFlags |= REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Add(mPendingRequest);
+ }
+}
+
+void nsImageLoadingContent::UntrackImage(
+ imgIRequest* aImage, const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */) {
+ if (!aImage) return;
+
+ MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
+ "Why haven't we heard of this request?");
+
+ // We may not be in the document. If we outlived our document that's fine,
+ // because the document empties out the tracker and unlocks all locked images
+ // on destruction. But if we were never in the document we may need to force
+ // discarding the image here, since this is the only chance we have.
+ Document* doc = GetOurCurrentDoc();
+ if (aImage == mCurrentRequest) {
+ if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
+ mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Remove(
+ mCurrentRequest,
+ aNonvisibleAction == Some(OnNonvisible::DiscardImages)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
+ // If we're not in the document we may still need to be discarded.
+ aImage->RequestDiscard();
+ }
+ }
+ if (aImage == mPendingRequest) {
+ if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
+ mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Remove(
+ mPendingRequest,
+ aNonvisibleAction == Some(OnNonvisible::DiscardImages)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
+ // If we're not in the document we may still need to be discarded.
+ aImage->RequestDiscard();
+ }
+ }
+}
+
+CORSMode nsImageLoadingContent::GetCORSMode() { return CORS_NONE; }
+
+nsImageLoadingContent::ImageObserver::ImageObserver(
+ imgINotificationObserver* aObserver)
+ : mObserver(aObserver), mNext(nullptr) {
+ MOZ_COUNT_CTOR(ImageObserver);
+}
+
+nsImageLoadingContent::ImageObserver::~ImageObserver() {
+ MOZ_COUNT_DTOR(ImageObserver);
+ NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
+}
+
+nsImageLoadingContent::ScriptedImageObserver::ScriptedImageObserver(
+ imgINotificationObserver* aObserver,
+ RefPtr<imgRequestProxy>&& aCurrentRequest,
+ RefPtr<imgRequestProxy>&& aPendingRequest)
+ : mObserver(aObserver),
+ mCurrentRequest(aCurrentRequest),
+ mPendingRequest(aPendingRequest) {}
+
+nsImageLoadingContent::ScriptedImageObserver::~ScriptedImageObserver() {
+ // We should have cancelled any requests before getting released.
+ DebugOnly<bool> cancel = CancelRequests();
+ MOZ_ASSERT(!cancel, "Still have requests in ~ScriptedImageObserver!");
+}
+
+bool nsImageLoadingContent::ScriptedImageObserver::CancelRequests() {
+ bool cancelled = false;
+ if (mCurrentRequest) {
+ mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mCurrentRequest = nullptr;
+ cancelled = true;
+ }
+ if (mPendingRequest) {
+ mPendingRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mPendingRequest = nullptr;
+ cancelled = true;
+ }
+ return cancelled;
+}
+
+Element* nsImageLoadingContent::FindImageMap() {
+ nsIContent* thisContent = AsContent();
+ Element* thisElement = thisContent->AsElement();
+
+ nsAutoString useMap;
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
+ if (useMap.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsAString::const_iterator start, end;
+ useMap.BeginReading(start);
+ useMap.EndReading(end);
+
+ int32_t hash = useMap.FindChar('#');
+ if (hash < 0) {
+ return nullptr;
+ }
+ // useMap contains a '#', set start to point right after the '#'
+ start.advance(hash + 1);
+
+ if (start == end) {
+ return nullptr; // useMap == "#"
+ }
+
+ RefPtr<nsContentList> imageMapList;
+ if (thisElement->IsInUncomposedDoc()) {
+ // Optimize the common case and use document level image map.
+ imageMapList = thisElement->OwnerDoc()->ImageMapList();
+ } else {
+ // Per HTML spec image map should be searched in the element's scope,
+ // so using SubtreeRoot() here.
+ // Because this is a temporary list, we don't need to make it live.
+ imageMapList =
+ new nsContentList(thisElement->SubtreeRoot(), kNameSpaceID_XHTML,
+ nsGkAtoms::map, nsGkAtoms::map, true, /* deep */
+ false /* live */);
+ }
+
+ nsAutoString mapName(Substring(start, end));
+
+ uint32_t i, n = imageMapList->Length(true);
+ for (i = 0; i < n; ++i) {
+ nsIContent* map = imageMapList->Item(i);
+ if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
+ eCaseMatters) ||
+ map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ mapName, eCaseMatters)) {
+ return map->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+nsLoadFlags nsImageLoadingContent::LoadFlags() {
+ auto* image = HTMLImageElement::FromNode(AsContent());
+ if (image && image->OwnerDoc()->IsScriptEnabled() &&
+ !image->OwnerDoc()->IsStaticDocument() &&
+ image->LoadingState() == HTMLImageElement::Loading::Lazy) {
+ // Note that LOAD_BACKGROUND is not about priority of the load, but about
+ // whether it blocks the load event (by bypassing the loadgroup).
+ return nsIRequest::LOAD_BACKGROUND;
+ }
+ return nsIRequest::LOAD_NORMAL;
+}
diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h
new file mode 100644
index 0000000000..34a7ff38fc
--- /dev/null
+++ b/dom/base/nsImageLoadingContent.h
@@ -0,0 +1,611 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A base class which implements nsIImageLoadingContent and can be
+ * subclassed by various content nodes that want to provide image
+ * loading functionality (eg <img>, <object>, etc).
+ */
+
+#ifndef nsImageLoadingContent_h__
+#define nsImageLoadingContent_h__
+
+#include "imgINotificationObserver.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIContentPolicy.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIRequest.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RustTypes.h"
+#include "nsAttrValue.h"
+#include "Units.h"
+
+class nsINode;
+class nsIURI;
+class nsPresContext;
+class nsIContent;
+class imgRequestProxy;
+
+namespace mozilla {
+class AsyncEventDispatcher;
+class ErrorResult;
+
+namespace dom {
+struct BindContext;
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+#ifdef LoadImage
+// Undefine LoadImage to prevent naming conflict with Windows.
+# undef LoadImage
+#endif
+
+class nsImageLoadingContent : public nsIImageLoadingContent {
+ protected:
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ using Nothing = mozilla::Nothing;
+ using OnNonvisible = mozilla::OnNonvisible;
+ using Visibility = mozilla::Visibility;
+
+ /* METHODS */
+ public:
+ nsImageLoadingContent();
+ virtual ~nsImageLoadingContent();
+
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+ NS_DECL_NSIIMAGELOADINGCONTENT
+
+ // Web IDL binding methods.
+ // Note that the XPCOM SetLoadingEnabled method is OK for Web IDL bindings
+ // to use as well, since it does not throw when called via the Web IDL
+ // bindings.
+
+ bool LoadingEnabled() const { return mLoadingEnabled; }
+ void AddObserver(imgINotificationObserver* aObserver);
+ void RemoveObserver(imgINotificationObserver* aObserver);
+ already_AddRefed<imgIRequest> GetRequest(int32_t aRequestType,
+ mozilla::ErrorResult& aError);
+ int32_t GetRequestType(imgIRequest* aRequest, mozilla::ErrorResult& aError);
+ already_AddRefed<nsIURI> GetCurrentURI();
+ already_AddRefed<nsIURI> GetCurrentRequestFinalURI();
+ void ForceReload(bool aNotify, mozilla::ErrorResult& aError);
+
+ mozilla::dom::Element* FindImageMap();
+
+ /**
+ * Toggle whether or not to synchronously decode an image on draw.
+ */
+ void SetSyncDecodingHint(bool aHint);
+
+ /**
+ * Notify us that the document state has changed. Called by nsDocument so that
+ * we may reject any promises which require the document to be active.
+ */
+ void NotifyOwnerDocumentActivityChanged();
+
+ // Trigger text recognition for the current image request.
+ already_AddRefed<mozilla::dom::Promise> RecognizeCurrentImageText(
+ mozilla::ErrorResult&);
+
+ protected:
+ enum ImageLoadType {
+ // Most normal image loads
+ eImageLoadType_Normal,
+ // From a <img srcset> or <picture> context. Affects type given to content
+ // policy.
+ eImageLoadType_Imageset
+ };
+
+ /**
+ * LoadImage is called by subclasses when the appropriate
+ * attributes (eg 'src' for <img> tags) change. The string passed
+ * in is the new uri string; this consolidates the code for getting
+ * the charset, constructing URI objects, and any other incidentals
+ * into this superclass.
+ *
+ * @param aNewURI the URI spec to be loaded (may be a relative URI)
+ * @param aForce If true, make sure to load the URI. If false, only
+ * load if the URI is different from the currently loaded URI.
+ * @param aNotify If true, nsIDocumentObserver state change notifications
+ * will be sent as needed.
+ * @param aImageLoadType The ImageLoadType for this request
+ * @param aTriggeringPrincipal Optional parameter specifying the triggering
+ * principal to use for the image load
+ */
+ nsresult LoadImage(const nsAString& aNewURI, bool aForce, bool aNotify,
+ ImageLoadType aImageLoadType,
+ nsIPrincipal* aTriggeringPrincipal = nullptr);
+
+ /**
+ * ImageState is called by subclasses that are computing their content state.
+ * The return value will have the ElementState::BROKEN bit set as needed.
+ *
+ * Note that this state assumes that this node is "trying" to be an
+ * image (so for example complete lack of attempt to load an image will lead
+ * to ElementState::BROKEN being set). Subclasses that are not "trying" to
+ * be an image (eg an HTML <input> of type other than "image") should just
+ * not call this method when computing their intrinsic state.
+ */
+ mozilla::dom::ElementState ImageState() const;
+
+ /**
+ * LoadImage is called by subclasses when the appropriate
+ * attributes (eg 'src' for <img> tags) change. If callers have an
+ * URI object already available, they should use this method.
+ *
+ * @param aNewURI the URI to be loaded
+ * @param aForce If true, make sure to load the URI. If false, only
+ * load if the URI is different from the currently loaded URI.
+ * @param aNotify If true, nsIDocumentObserver state change notifications
+ * will be sent as needed.
+ * @param aImageLoadType The ImageLoadType for this request
+ * @param aDocument Optional parameter giving the document this node is in.
+ * This is purely a performance optimization.
+ * @param aLoadFlags Optional parameter specifying load flags to use for
+ * the image load
+ * @param aTriggeringPrincipal Optional parameter specifying the triggering
+ * principal to use for the image load
+ */
+ nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify,
+ ImageLoadType aImageLoadType, nsLoadFlags aLoadFlags,
+ mozilla::dom::Document* aDocument = nullptr,
+ nsIPrincipal* aTriggeringPrincipal = nullptr);
+
+ nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify,
+ ImageLoadType aImageLoadType,
+ nsIPrincipal* aTriggeringPrincipal) {
+ return LoadImage(aNewURI, aForce, aNotify, aImageLoadType, LoadFlags(),
+ nullptr, aTriggeringPrincipal);
+ }
+
+ /**
+ * helpers to get the document for this content (from the nodeinfo
+ * and such). Not named GetOwnerDoc/GetCurrentDoc to prevent ambiguous
+ * method names in subclasses
+ *
+ * @return the document we belong to
+ */
+ mozilla::dom::Document* GetOurOwnerDoc();
+ mozilla::dom::Document* GetOurCurrentDoc();
+
+ /**
+ * Helper function to get the frame associated with this content. Not named
+ * GetPrimaryFrame to prevent ambiguous method names in subclasses.
+ *
+ * @return The frame we own, or nullptr if it doesn't exist, or isn't
+ * associated with any of our requests.
+ */
+ nsIFrame* GetOurPrimaryImageFrame();
+
+ /**
+ * Helper function to get the PresContext associated with this content's
+ * frame. Not named GetPresContext to prevent ambiguous method names in
+ * subclasses.
+ *
+ * @return The nsPresContext associated with our frame, or nullptr if either
+ * the frame doesn't exist, or the frame's prescontext doesn't exist.
+ */
+ nsPresContext* GetFramePresContext();
+
+ /**
+ * CancelImageRequests is called by subclasses when they want to
+ * cancel all image requests (for example when the subclass is
+ * somehow not an image anymore).
+ */
+ void CancelImageRequests(bool aNotify);
+
+ /**
+ * Derived classes of nsImageLoadingContent MUST call Destroy from their
+ * destructor, or earlier. It does things that cannot be done in
+ * ~nsImageLoadingContent because they rely on being able to QueryInterface to
+ * other derived classes, which cannot happen once the derived class
+ * destructor has started calling the base class destructors.
+ */
+ void Destroy();
+
+ /**
+ * Returns the CORS mode that will be used for all future image loads. The
+ * default implementation returns CORS_NONE unconditionally.
+ */
+ virtual mozilla::CORSMode GetCORSMode();
+
+ // Subclasses are *required* to call BindToTree/UnbindFromTree.
+ void BindToTree(mozilla::dom::BindContext&, nsINode& aParent);
+ void UnbindFromTree(bool aNullParent);
+
+ void OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+ void OnUnlockedDraw();
+ void OnImageIsAnimated(imgIRequest* aRequest);
+
+ // The nsContentPolicyType we would use for this ImageLoadType
+ static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType);
+
+ void AsyncEventRunning(mozilla::AsyncEventDispatcher* aEvent);
+
+ // Get ourselves as an nsIContent*. Not const because some of the callers
+ // want a non-const nsIContent.
+ virtual nsIContent* AsContent() = 0;
+
+ /**
+ * Get width and height of the current request, using given image request if
+ * attributes are unset.
+ */
+ MOZ_CAN_RUN_SCRIPT mozilla::CSSIntSize GetWidthHeightForImage();
+
+ /**
+ * Create a promise and queue a microtask which will ensure the current
+ * request (after any pending loads are applied) has requested a full decode.
+ * The promise is fulfilled once the request has a fully decoded surface that
+ * is available for drawing, or an error condition occurrs (e.g. broken image,
+ * current request is updated, etc).
+ *
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode
+ */
+ already_AddRefed<mozilla::dom::Promise> QueueDecodeAsync(
+ mozilla::ErrorResult& aRv);
+
+ enum class ImageDecodingType : uint8_t {
+ Auto,
+ Async,
+ Sync,
+ };
+
+ static const nsAttrValue::EnumTable kDecodingTable[];
+ static const nsAttrValue::EnumTable* kDecodingTableDefault;
+
+ private:
+ /**
+ * Enqueue and/or fulfill a promise created by QueueDecodeAsync.
+ */
+ void DecodeAsync(RefPtr<mozilla::dom::Promise>&& aPromise,
+ uint32_t aRequestGeneration);
+
+ /**
+ * Attempt to resolve all queued promises based on the state of the current
+ * request. If the current request does not yet have all of the encoded data,
+ * or the decoding has not yet completed, it will return without changing the
+ * promise states.
+ */
+ void MaybeResolveDecodePromises();
+
+ /**
+ * Reject all queued promises with the given status.
+ */
+ void RejectDecodePromises(nsresult aStatus);
+
+ /**
+ * Age the generation counter if we have a new current request with a
+ * different URI. If the generation counter is aged, then all queued promises
+ * will also be rejected.
+ */
+ void MaybeAgeRequestGeneration(nsIURI* aNewURI);
+
+ /**
+ * Deregister as an observer for the owner document's activity notifications
+ * if we have no outstanding decode promises.
+ */
+ void MaybeDeregisterActivityObserver();
+
+ /**
+ * Struct used to manage the native image observers.
+ */
+ struct ImageObserver {
+ explicit ImageObserver(imgINotificationObserver* aObserver);
+ ~ImageObserver();
+
+ nsCOMPtr<imgINotificationObserver> mObserver;
+ ImageObserver* mNext;
+ };
+
+ /**
+ * Struct used to manage the scripted/XPCOM image observers.
+ */
+ class ScriptedImageObserver final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ScriptedImageObserver)
+
+ ScriptedImageObserver(imgINotificationObserver* aObserver,
+ RefPtr<imgRequestProxy>&& aCurrentRequest,
+ RefPtr<imgRequestProxy>&& aPendingRequest);
+ bool CancelRequests();
+
+ nsCOMPtr<imgINotificationObserver> mObserver;
+ RefPtr<imgRequestProxy> mCurrentRequest;
+ RefPtr<imgRequestProxy> mPendingRequest;
+
+ private:
+ ~ScriptedImageObserver();
+ };
+
+ /**
+ * Struct to report state changes
+ */
+ struct AutoStateChanger {
+ AutoStateChanger(nsImageLoadingContent* aImageContent, bool aNotify)
+ : mImageContent(aImageContent), mNotify(aNotify) {
+ mImageContent->mStateChangerDepth++;
+ }
+ ~AutoStateChanger() {
+ mImageContent->mStateChangerDepth--;
+ mImageContent->UpdateImageState(mNotify);
+ }
+
+ nsImageLoadingContent* mImageContent;
+ bool mNotify;
+ };
+
+ friend struct AutoStateChanger;
+
+ /**
+ * Method to fire an event once we know what's going on with the image load.
+ *
+ * @param aEventType "load", or "error" depending on how things went
+ * @param aIsCancelable true if event is cancelable.
+ */
+ nsresult FireEvent(const nsAString& aEventType, bool aIsCancelable = false);
+
+ /**
+ * Method to cancel and null-out pending event if they exist.
+ */
+ void CancelPendingEvent();
+
+ RefPtr<mozilla::AsyncEventDispatcher> mPendingEvent;
+
+ protected:
+ /**
+ * UpdateImageState recomputes the current state of this image loading
+ * content and updates what ImageState() returns accordingly. It will also
+ * fire a ContentStatesChanged() notification as needed if aNotify is true.
+ */
+ void UpdateImageState(bool aNotify);
+
+ /**
+ * Method to create an nsIURI object from the given string (will
+ * handle getting the right charset, base, etc). You MUST pass in a
+ * non-null document to this function.
+ *
+ * @param aSpec the string spec (from an HTML attribute, eg)
+ * @param aDocument the document we belong to
+ * @return the URI we want to be loading
+ */
+ nsresult StringToURI(const nsAString& aSpec,
+ mozilla::dom::Document* aDocument, nsIURI** aURI);
+
+ /**
+ * Prepare and returns a reference to the "next request". If there's already
+ * a _usable_ current request (one with SIZE_AVAILABLE), this request is
+ * "pending" until it becomes usable. Otherwise, this becomes the current
+ * request.
+ *
+ * @param aImageLoadType The ImageLoadType for this request
+ */
+ RefPtr<imgRequestProxy>& PrepareNextRequest(ImageLoadType aImageLoadType);
+
+ /**
+ * Returns a COMPtr reference to the current/pending image requests, cleaning
+ * up and canceling anything that was there before. Note that if you just want
+ * to get rid of one of the requests, you should call
+ * Clear*Request(NS_BINDING_ABORTED) instead.
+ *
+ * @param aImageLoadType The ImageLoadType for this request
+ */
+ RefPtr<imgRequestProxy>& PrepareCurrentRequest(ImageLoadType aImageLoadType);
+ RefPtr<imgRequestProxy>& PreparePendingRequest(ImageLoadType aImageLoadType);
+
+ /**
+ * Switch our pending request to be our current request.
+ * mPendingRequest must be non-null!
+ */
+ void MakePendingRequestCurrent();
+
+ /**
+ * Cancels and nulls-out the "current" and "pending" requests if they exist.
+ *
+ * @param aNonvisibleAction An action to take if the image is no longer
+ * visible as a result; see |UntrackImage|.
+ */
+ void ClearCurrentRequest(
+ nsresult aReason,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+ void ClearPendingRequest(
+ nsresult aReason,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+
+ /**
+ * Reset animation of the current request if
+ * |mNewRequestsWillNeedAnimationReset| was true when the request was
+ * prepared.
+ */
+ void ResetAnimationIfNeeded();
+
+ /**
+ * Static helper method to tell us if we have the size of a request. The
+ * image may be null.
+ */
+ static bool HaveSize(imgIRequest* aImage);
+
+ /**
+ * Adds/Removes a given imgIRequest from our document's tracker.
+ *
+ * No-op if aImage is null.
+ *
+ * @param aFrame If called from FrameCreated the frame passed to FrameCreated.
+ * This is our frame, but at the time of the FrameCreated call
+ * our primary frame pointer hasn't been set yet, so this is
+ * only way to get our frame.
+ *
+ * @param aNonvisibleAction A requested action if the frame has become
+ * nonvisible. If Nothing(), no action is
+ * requested. If DISCARD_IMAGES is specified, the
+ * frame is requested to ask any images it's
+ * associated with to discard their surfaces if
+ * possible.
+ */
+ void TrackImage(imgIRequest* aImage, nsIFrame* aFrame = nullptr);
+ void UntrackImage(imgIRequest* aImage,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+
+ nsLoadFlags LoadFlags();
+
+ /* MEMBERS */
+ RefPtr<imgRequestProxy> mCurrentRequest;
+ RefPtr<imgRequestProxy> mPendingRequest;
+ uint8_t mCurrentRequestFlags = 0;
+ uint8_t mPendingRequestFlags = 0;
+
+ enum {
+ // Set if the request needs ResetAnimation called on it.
+ REQUEST_NEEDS_ANIMATION_RESET = 1 << 0,
+ // Set if the request is currently tracked with the document.
+ REQUEST_IS_TRACKED = 1 << 1,
+ // Set if this is an imageset request, such as from <img srcset> or
+ // <picture>
+ REQUEST_IS_IMAGESET = 1 << 2,
+ };
+
+ // If the image was blocked or if there was an error loading, it's nice to
+ // still keep track of what the URI was despite not having an imgIRequest.
+ // We only maintain this in those situations (in the common case, this is
+ // always null).
+ nsCOMPtr<nsIURI> mCurrentURI;
+
+ private:
+ /**
+ * Clones the given "current" or "pending" request for each scripted observer.
+ */
+ void CloneScriptedRequests(imgRequestProxy* aRequest);
+
+ /**
+ * Cancels and nulls-out the "current" or "pending" requests if they exist
+ * for each scripted observer.
+ */
+ void ClearScriptedRequests(int32_t aRequestType, nsresult aReason);
+
+ /**
+ * Moves the "pending" request into the "current" request for each scripted
+ * observer. If there is an existing "current" request, it will cancel it
+ * first.
+ */
+ void MakePendingScriptedRequestsCurrent();
+
+ /**
+ * Depending on the configured decoding hint, and/or how recently we updated
+ * the image request, force or stop the frame from decoding the image
+ * synchronously when it is drawn.
+ * @param aPrepareNextRequest True if this is when updating the image request.
+ * @param aFrame If called from FrameCreated the frame passed to FrameCreated.
+ * This is our frame, but at the time of the FrameCreated call
+ * our primary frame pointer hasn't been set yet, so this is
+ * only way to get our frame.
+ */
+ void MaybeForceSyncDecoding(bool aPrepareNextRequest,
+ nsIFrame* aFrame = nullptr);
+
+ /**
+ * Typically we will have only one observer (our frame in the screen
+ * prescontext), so we want to only make space for one and to
+ * heap-allocate anything past that (saves memory and malloc churn
+ * in the common case). The storage is a linked list, we just
+ * happen to actually hold the first observer instead of a pointer
+ * to it.
+ */
+ ImageObserver mObserverList;
+
+ /**
+ * Typically we will have no scripted observers, as this is only used by
+ * chrome, legacy extensions, and some mochitests. An empty array reserves
+ * minimal memory.
+ */
+ nsTArray<RefPtr<ScriptedImageObserver>> mScriptedObservers;
+
+ /**
+ * Promises created by QueueDecodeAsync that are still waiting to be
+ * fulfilled by the image being fully decoded.
+ */
+ nsTArray<RefPtr<mozilla::dom::Promise>> mDecodePromises;
+
+ mozilla::TimeStamp mMostRecentRequestChange;
+
+ /**
+ * Total number of outstanding decode promises, including those stored in
+ * mDecodePromises and those embedded in runnables waiting to be enqueued.
+ * This is used to determine whether we need to register as an observer for
+ * document activity notifications.
+ */
+ size_t mOutstandingDecodePromises;
+
+ /**
+ * An incrementing counter representing the current request generation;
+ * Each time mCurrentRequest is modified with a different URI, this will
+ * be incremented. Each QueueDecodeAsync call will cache the generation
+ * of the current request so that when it is processed, it knows if it
+ * should have rejected because the request changed.
+ */
+ uint32_t mRequestGeneration;
+
+ bool mLoadingEnabled : 1;
+
+ /**
+ * The state we had the last time we checked whether we needed to notify the
+ * document of a state change. These are maintained by UpdateImageState.
+ */
+ bool mLoading : 1;
+ bool mBroken : 1;
+
+ protected:
+ /**
+ * A hack to get animations to reset, see bug 594771. On requests
+ * that originate from setting .src, we mark them for needing their animation
+ * reset when they are ready. mNewRequestsWillNeedAnimationReset is set to
+ * true while preparing such requests (as a hack around needing to change an
+ * interface), and the other two booleans store which of the current
+ * and pending requests are of the sort that need their animation restarted.
+ */
+ bool mNewRequestsWillNeedAnimationReset : 1;
+
+ /**
+ * Flag to indicate whether the channel should be mark as urgent-start.
+ * It should be set in *Element and passed to nsContentUtils::LoadImage.
+ * True if we want to set nsIClassOfService::UrgentStart to the channel to
+ * get the response ASAP for better user responsiveness.
+ */
+ bool mUseUrgentStartForChannel : 1;
+
+ // Represents the image is deferred loading until this element gets visible.
+ bool mLazyLoading : 1;
+
+ private:
+ /* The number of nested AutoStateChangers currently tracking our state. */
+ uint8_t mStateChangerDepth;
+
+ // Flags to indicate whether each of the current and pending requests are
+ // registered with the refresh driver.
+ bool mCurrentRequestRegistered;
+ bool mPendingRequestRegistered;
+
+ // TODO:
+ // Bug 1353685: Should ServiceWorker call SetBlockedRequest?
+ //
+ // This member is used in SetBlockedRequest, if it's true, then this call is
+ // triggered from LoadImage.
+ // If this is false, it means this call is from other places like
+ // ServiceWorker, then we will ignore call to SetBlockedRequest for now.
+ //
+ // Also we use this variable to check if some evil code is reentering
+ // LoadImage.
+ bool mIsStartingImageLoad;
+
+ // If true, force frames to synchronously decode images on draw.
+ bool mSyncDecodingHint;
+};
+
+#endif // nsImageLoadingContent_h__
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
new file mode 100644
index 0000000000..cd930688de
--- /dev/null
+++ b/dom/base/nsJSEnvironment.cpp
@@ -0,0 +1,2396 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsError.h"
+#include "nsJSEnvironment.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsPIDOMWindow.h"
+#include "nsDOMCID.h"
+#include "nsIXPConnect.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsReadableUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsJSUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsPresContext.h"
+#include "nsIConsoleService.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsAtom.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsIContent.h"
+#include "nsCycleCollector.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFormatter.h"
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+#include "xpcpublic.h"
+
+#include "jsapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/PropertySpec.h"
+#include "js/SliceBudget.h"
+#include "js/Wrapper.h"
+#include "nsIArray.h"
+#include "CCGCScheduler.h"
+#include "WrapperFactory.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "nsRefreshDriver.h"
+#include "nsJSPrincipals.h"
+#include "AccessCheck.h"
+#include "mozilla/Logging.h"
+#include "prthread.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsViewManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#if defined(MOZ_MEMORY)
+# include "mozmemory.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Thank you Microsoft!
+#ifdef CompareString
+# undef CompareString
+#endif
+
+static JS::GCSliceCallback sPrevGCSliceCallback;
+
+static bool sIncrementalCC = false;
+
+static bool sIsInitialized;
+static bool sShuttingDown;
+
+static CCGCScheduler sScheduler;
+
+struct CycleCollectorStats {
+ constexpr CycleCollectorStats() = default;
+ void Init();
+ void Clear();
+ void PrepareForCycleCollection(TimeStamp aNow);
+ void AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline,
+ TimeStamp aBeginTime,
+ TimeStamp aMaybeAfterGCTime);
+ void AfterCycleCollectionSlice();
+ void AfterSyncForgetSkippable(TimeStamp beginTime);
+ void AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples);
+ void AfterCycleCollection();
+
+ void SendTelemetry(TimeDuration aCCNowDuration, TimeStamp aPrevCCEnd) const;
+ void MaybeLogStats(const CycleCollectorResults& aResults,
+ uint32_t aCleanups) const;
+ void MaybeNotifyStats(const CycleCollectorResults& aResults,
+ TimeDuration aCCNowDuration, uint32_t aCleanups) const;
+
+ // Time the current slice began, including any GC finishing.
+ TimeStamp mBeginSliceTime;
+
+ // Time the previous slice of the current CC ended.
+ TimeStamp mEndSliceTime;
+
+ // Time the current cycle collection began.
+ TimeStamp mBeginTime;
+
+ // The longest GC finishing duration for any slice of the current CC.
+ TimeDuration mMaxGCDuration;
+
+ // True if we ran sync forget skippable in any slice of the current CC.
+ bool mRanSyncForgetSkippable = false;
+
+ // Number of suspected objects at the start of the current CC.
+ uint32_t mSuspected = 0;
+
+ // The longest duration spent on sync forget skippable in any slice of the
+ // current CC.
+ TimeDuration mMaxSkippableDuration;
+
+ // The longest pause of any slice in the current CC.
+ TimeDuration mMaxSliceTime;
+
+ // The longest slice time since ClearMaxCCSliceTime() was called.
+ TimeDuration mMaxSliceTimeSinceClear;
+
+ // The total amount of time spent actually running the current CC.
+ TimeDuration mTotalSliceTime;
+
+ // True if we were locked out by the GC in any slice of the current CC.
+ bool mAnyLockedOut = false;
+
+ // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
+ FILE* mFile = nullptr;
+
+ // In case CC slice was triggered during idle time, set to the end of the idle
+ // period.
+ TimeStamp mIdleDeadline;
+
+ TimeDuration mMinForgetSkippableTime;
+ TimeDuration mMaxForgetSkippableTime;
+ TimeDuration mTotalForgetSkippableTime;
+ uint32_t mForgetSkippableBeforeCC = 0;
+
+ uint32_t mRemovedPurples = 0;
+};
+
+static CycleCollectorStats sCCStats;
+
+static const char* ProcessNameForCollectorLog() {
+ return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
+ : "content";
+}
+
+namespace xpc {
+
+// This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
+// Exceptions, and arbitrary values that were associated with a stack by the
+// JS engine when they were thrown, as specified by exceptionStack.
+//
+// Note that the returned stackObj and stackGlobal are _not_ wrapped into the
+// compartment of exceptionValue.
+void FindExceptionStackForConsoleReport(
+ nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue,
+ JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj,
+ JS::MutableHandle<JSObject*> stackGlobal) {
+ stackObj.set(nullptr);
+ stackGlobal.set(nullptr);
+
+ if (!exceptionValue.isObject()) {
+ // Use the stack provided by the JS engine, if available. This will not be
+ // a wrapper.
+ if (exceptionStack) {
+ stackObj.set(exceptionStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
+ }
+ return;
+ }
+
+ if (win && win->AsGlobal()->IsDying()) {
+ // Pretend like we have no stack, so we don't end up keeping the global
+ // alive via the stack.
+ return;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JSObject*> exceptionObject(rcx, &exceptionValue.toObject());
+ if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
+ // At this point we know exceptionObject is a possibly-wrapped
+ // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
+ // but excStack must be same-compartment with the unwrapped ErrorObject.
+ // Return the ErrorObject's global as stackGlobal. This matches what we do
+ // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
+ // are same-compartment.
+ JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
+ stackObj.set(excStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
+ return;
+ }
+
+ // It is not a JS Exception, try DOM Exception.
+ RefPtr<Exception> exception;
+ UNWRAP_OBJECT(DOMException, exceptionObject, exception);
+ if (!exception) {
+ // Not a DOM Exception, try XPC Exception.
+ UNWRAP_OBJECT(Exception, exceptionObject, exception);
+ if (!exception) {
+ // As above, use the stack provided by the JS engine, if available.
+ if (exceptionStack) {
+ stackObj.set(exceptionStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
+ }
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
+ if (!stack) {
+ return;
+ }
+ JS::Rooted<JS::Value> value(rcx);
+ stack->GetNativeSavedFrame(&value);
+ if (value.isObject()) {
+ stackObj.set(&value.toObject());
+ MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
+ return;
+ }
+}
+
+} /* namespace xpc */
+
+static TimeDuration GetCollectionTimeDelta() {
+ static TimeStamp sFirstCollectionTime;
+ TimeStamp now = TimeStamp::Now();
+ if (sFirstCollectionTime) {
+ return now - sFirstCollectionTime;
+ }
+ sFirstCollectionTime = now;
+ return TimeDuration();
+}
+
+class nsJSEnvironmentObserver final : public nsIObserver {
+ ~nsJSEnvironmentObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
+ if (sShuttingDown) {
+ // Don't GC/CC if we're already shutting down.
+ return NS_OK;
+ }
+ nsDependentString data(aData);
+ if (data.EqualsLiteral("low-memory-ongoing")) {
+ // Don't GC/CC if we are in an ongoing low-memory state since its very
+ // slow and it likely won't help us anyway.
+ return NS_OK;
+ }
+ if (data.EqualsLiteral("heap-minimize")) {
+ // heap-minimize notifiers expect this to run synchronously
+ nsJSContext::DoLowMemoryGC();
+ return NS_OK;
+ }
+ if (data.EqualsLiteral("low-memory")) {
+ nsJSContext::SetLowMemoryState(true);
+ }
+ // Asynchronously GC.
+ nsJSContext::LowMemoryGC();
+ }
+ } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
+ nsJSContext::SetLowMemoryState(false);
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
+ sScheduler.UserIsInactive();
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
+ sScheduler.UserIsActive();
+ } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
+ !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
+ !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
+ sShuttingDown = true;
+ sScheduler.Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/****************************************************************
+ ************************** AutoFree ****************************
+ ****************************************************************/
+
+class AutoFree {
+ public:
+ explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
+ ~AutoFree() {
+ if (mPtr) free(mPtr);
+ }
+ void Invalidate() { mPtr = nullptr; }
+
+ private:
+ void* mPtr;
+};
+
+// A utility function for script languages to call. Although it looks small,
+// the use of nsIDocShell and nsPresContext triggers a huge number of
+// dependencies that most languages would not otherwise need.
+// XXXmarkh - This function is mis-placed!
+bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
+ const ErrorEventInit& aErrorEventInit,
+ nsEventStatus* aStatus) {
+ bool called = false;
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
+ nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
+ if (docShell) {
+ RefPtr<nsPresContext> presContext = docShell->GetPresContext();
+
+ static int32_t errorDepth; // Recursion prevention
+ ++errorDepth;
+
+ if (errorDepth < 2) {
+ // Dispatch() must be synchronous for the recursion block
+ // (errorDepth) to work.
+ RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
+ nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ aStatus);
+ called = true;
+ }
+ --errorDepth;
+ }
+ return called;
+}
+
+class ScriptErrorEvent : public Runnable {
+ public:
+ ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
+ xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
+ JS::Handle<JSObject*> aErrorStack)
+ : mozilla::Runnable("ScriptErrorEvent"),
+ mWindow(aWindow),
+ mReport(aReport),
+ mError(aRootingCx, aError),
+ mErrorStack(aRootingCx, aErrorStack) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
+ MOZ_ASSERT(win);
+ MOZ_ASSERT(NS_IsMainThread());
+ // First, notify the DOM that we have a script error, but only if
+ // our window is still the current inner.
+ JS::RootingContext* rootingCx = RootingCx();
+ if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
+ !sHandlingScriptError) {
+ AutoRestore<bool> recursionGuard(sHandlingScriptError);
+ sHandlingScriptError = true;
+
+ RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
+
+ RootedDictionary<ErrorEventInit> init(rootingCx);
+ init.mCancelable = true;
+ init.mFilename = mReport->mFileName;
+ init.mBubbles = true;
+
+ constexpr auto xoriginMsg = u"Script error."_ns;
+ if (!mReport->mIsMuted) {
+ init.mMessage = mReport->mErrorMsg;
+ init.mLineno = mReport->mLineNumber;
+ init.mColno = mReport->mColumn;
+ init.mError = mError;
+ } else {
+ NS_WARNING("Not same origin error!");
+ init.mMessage = xoriginMsg;
+ init.mLineno = 0;
+ }
+
+ RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
+ nsGlobalWindowInner::Cast(win), u"error"_ns, init);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ &status);
+ }
+
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ JS::Rooted<JSObject*> stack(rootingCx);
+ JS::Rooted<JSObject*> stackGlobal(rootingCx);
+ xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
+ &stackGlobal);
+ JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
+ nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
+ mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<xpc::ErrorReport> mReport;
+ JS::PersistentRooted<JS::Value> mError;
+ JS::PersistentRooted<JSObject*> mErrorStack;
+
+ static bool sHandlingScriptError;
+};
+
+bool ScriptErrorEvent::sHandlingScriptError = false;
+
+// This temporarily lives here to avoid code churn. It will go away entirely
+// soon.
+namespace xpc {
+
+void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
+ JS::RootingContext* rootingCx,
+ xpc::ErrorReport* xpcReport,
+ JS::Handle<JS::Value> exception,
+ JS::Handle<JSObject*> exceptionStack) {
+ nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
+ win, rootingCx, xpcReport, exception, exceptionStack));
+}
+
+} /* namespace xpc */
+
+#ifdef DEBUG
+// A couple of useful functions to call when you're debugging.
+nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
+ return xpc::WindowOrNull(obj);
+}
+
+template <typename T>
+void PrintWinURI(T* win) {
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsCOMPtr<Document> doc = win->GetExtantDoc();
+ if (!doc) {
+ printf("No document in the window.\n");
+ return;
+ }
+
+ nsIURI* uri = doc->GetDocumentURI();
+ if (!uri) {
+ printf("Document doesn't have a URI.\n");
+ return;
+ }
+
+ printf("%s\n", uri->GetSpecOrDefault().get());
+}
+
+void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
+
+void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
+
+template <typename T>
+void PrintWinCodebase(T* win) {
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsIPrincipal* prin = win->GetPrincipal();
+ if (!prin) {
+ printf("Window doesn't have principals.\n");
+ return;
+ }
+ if (prin->IsSystemPrincipal()) {
+ printf("No URI, it's the system principal.\n");
+ return;
+ }
+ nsCString spec;
+ prin->GetAsciiSpec(spec);
+ printf("%s\n", spec.get());
+}
+
+void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
+ return PrintWinCodebase(aWin);
+}
+
+void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
+ return PrintWinCodebase(aWin);
+}
+
+void DumpString(const nsAString& str) {
+ printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
+}
+#endif
+
+nsJSContext::nsJSContext(bool aGCOnDestruction,
+ nsIScriptGlobalObject* aGlobalObject)
+ : mWindowProxy(nullptr),
+ mGCOnDestruction(aGCOnDestruction),
+ mGlobalObjectRef(aGlobalObject) {
+ EnsureStatics();
+
+ mProcessingScriptTag = false;
+ HoldJSObjects(this);
+}
+
+nsJSContext::~nsJSContext() {
+ mGlobalObjectRef = nullptr;
+
+ Destroy();
+}
+
+void nsJSContext::Destroy() {
+ if (mGCOnDestruction) {
+ sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
+ }
+
+ DropJSObjects(this);
+}
+
+// QueryInterface implementation for nsJSContext
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
+ tmp->mGCOnDestruction = false;
+ tmp->mWindowProxy = nullptr;
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
+
+#ifdef DEBUG
+bool AtomIsEventHandlerName(nsAtom* aName) {
+ const char16_t* name = aName->GetUTF16String();
+
+ const char16_t* cp;
+ char16_t c;
+ for (cp = name; *cp != '\0'; ++cp) {
+ c = *cp;
+ if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
+ }
+
+ return true;
+}
+#endif
+
+nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
+ // Note: this could probably be simplified somewhat more; see bug 974327
+ // comments 1 and 3.
+ if (!mWindowProxy) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mGlobalObjectRef);
+ return mGlobalObjectRef;
+}
+
+nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
+ const char* aPropName, nsISupports* aArgs) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::RootedVector<JS::Value> args(cx);
+
+ JS::Rooted<JSObject*> global(cx, GetWindowProxy());
+ nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // got the arguments, now attach them.
+
+ for (uint32_t i = 0; i < args.length(); ++i) {
+ if (!JS_WrapValue(cx, args[i])) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
+ if (!array) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+nsresult nsJSContext::ConvertSupportsTojsvals(
+ JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
+ JS::MutableHandleVector<JS::Value> aArgsOut) {
+ nsresult rv = NS_OK;
+
+ // If the array implements nsIJSArgArray, copy the contents and return.
+ nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
+ if (fastArray) {
+ uint32_t argc;
+ JS::Value* argv;
+ rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
+ if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+ }
+
+ // Take the slower path converting each item.
+ // Handle only nsIArray and nsIVariant. nsIArray is only needed for
+ // SetProperty('arguments', ...);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
+
+ if (!aArgs) return NS_OK;
+ uint32_t argCount;
+ // This general purpose function may need to convert an arg array
+ // (window.arguments, event-handler args) and a generic property.
+ nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
+
+ if (argsArray) {
+ rv = argsArray->GetLength(&argCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (argCount == 0) return NS_OK;
+ } else {
+ argCount = 1; // the nsISupports which is not an array
+ }
+
+ // Use the caller's auto guards to release and unroot.
+ if (!aArgsOut.resize(argCount)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (argsArray) {
+ for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
+ nsCOMPtr<nsISupports> arg;
+ JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
+ argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
+ getter_AddRefs(arg));
+ if (!arg) {
+ thisVal.setNull();
+ continue;
+ }
+ nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
+ if (variant != nullptr) {
+ rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
+ } else {
+ // And finally, support the nsISupportsPrimitives supplied
+ // by the AppShell. It generally will pass only strings, but
+ // as we have code for handling all, we may as well use it.
+ rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
+ if (rv == NS_ERROR_NO_INTERFACE) {
+ // something else - probably an event object or similar -
+ // just wrap it.
+#ifdef DEBUG
+ // but first, check its not another nsISupportsPrimitive, as
+ // these are now deprecated for use with script contexts.
+ nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
+ NS_ASSERTION(prim == nullptr,
+ "Don't pass nsISupportsPrimitives - use nsIVariant!");
+#endif
+ JSAutoRealm ar(aCx, aScope);
+ rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
+ }
+ }
+ }
+ } else {
+ nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
+ if (variant) {
+ rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
+ } else {
+ NS_ERROR("Not an array, not an interface?");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ return rv;
+}
+
+// This really should go into xpconnect somewhere...
+nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
+ nsISupports* aArg,
+ JS::Value* aArgv) {
+ MOZ_ASSERT(aArg, "Empty arg");
+
+ nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
+ if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
+
+ uint16_t type;
+ argPrimitive->GetType(&type);
+
+ switch (type) {
+ case nsISupportsPrimitive::TYPE_CSTRING: {
+ nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString data;
+
+ p->GetData(data);
+
+ JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_STRING: {
+ nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsAutoString data;
+
+ p->GetData(data);
+
+ // cast is probably safe since wchar_t and char16_t are expected
+ // to be equivalent; both unsigned 16-bit entities
+ JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRBOOL: {
+ nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ bool data;
+
+ p->GetData(&data);
+
+ aArgv->setBoolean(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT8: {
+ nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint8_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT16: {
+ nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint16_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT32: {
+ nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint32_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_CHAR: {
+ nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ char data;
+
+ p->GetData(&data);
+
+ JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRINT16: {
+ nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ int16_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRINT32: {
+ nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ int32_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_FLOAT: {
+ nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ float data;
+
+ p->GetData(&data);
+
+ *aArgv = ::JS_NumberValue(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_DOUBLE: {
+ nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ double data;
+
+ p->GetData(&data);
+
+ *aArgv = ::JS_NumberValue(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
+ nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsISupports> data;
+ nsIID* iid = nullptr;
+
+ p->GetData(getter_AddRefs(data));
+ p->GetDataIID(&iid);
+ NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
+
+ AutoFree iidGuard(iid); // Free iid upon destruction.
+
+ JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
+ JS::Rooted<JS::Value> v(aCx);
+ JSAutoRealm ar(aCx, scope);
+ nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aArgv = v;
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_ID:
+ case nsISupportsPrimitive::TYPE_PRUINT64:
+ case nsISupportsPrimitive::TYPE_PRINT64:
+ case nsISupportsPrimitive::TYPE_PRTIME: {
+ NS_WARNING("Unsupported primitive type used");
+ aArgv->setNull();
+ break;
+ }
+ default: {
+ NS_WARNING("Unknown primitive type used");
+ aArgv->setNull();
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+#ifdef MOZ_JPROF
+
+# include <signal.h>
+
+inline bool IsJProfAction(struct sigaction* action) {
+ return (action->sa_sigaction &&
+ (action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
+ (SA_RESTART | SA_SIGINFO));
+}
+
+void NS_JProfStartProfiling();
+void NS_JProfStopProfiling();
+void NS_JProfClearCircular();
+
+static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfStartProfiling();
+ return true;
+}
+
+void NS_JProfStartProfiling() {
+ // Figure out whether we're dealing with SIGPROF, SIGALRM, or
+ // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
+ // JP_RTC_HZ)
+ struct sigaction action;
+
+ // Must check ALRM before PROF since both are enabled for real-time
+ sigaction(SIGALRM, nullptr, &action);
+ // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning real-time jprof profiling.\n");
+ raise(SIGALRM);
+ return;
+ }
+
+ sigaction(SIGPROF, nullptr, &action);
+ // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning process-time jprof profiling.\n");
+ raise(SIGPROF);
+ return;
+ }
+
+ sigaction(SIGPOLL, nullptr, &action);
+ // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning rtc-based jprof profiling.\n");
+ raise(SIGPOLL);
+ return;
+ }
+
+ printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
+}
+
+static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfStopProfiling();
+ return true;
+}
+
+void NS_JProfStopProfiling() {
+ raise(SIGUSR1);
+ // printf("Stopped jprof profiling.\n");
+}
+
+static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfClearCircular();
+ return true;
+}
+
+void NS_JProfClearCircular() {
+ raise(SIGUSR2);
+ // printf("cleared jprof buffer\n");
+}
+
+static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ // Not ideal...
+ NS_JProfStopProfiling();
+ NS_JProfStartProfiling();
+ return true;
+}
+
+static const JSFunctionSpec JProfFunctions[] = {
+ JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
+ JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
+ JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
+ JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
+
+#endif /* defined(MOZ_JPROF) */
+
+nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, aGlobalObj);
+
+#ifdef MOZ_JPROF
+ // Attempt to initialize JProf functions
+ ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
+#endif
+
+ return NS_OK;
+}
+
+bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
+
+void nsJSContext::SetProcessingScriptTag(bool aFlag) {
+ mProcessingScriptTag = aFlag;
+}
+
+// static
+void nsJSContext::SetLowMemoryState(bool aState) {
+ JSContext* cx = danger::GetJSContext();
+ JS::SetLowMemoryState(cx, aState);
+}
+
+static void GarbageCollectImpl(JS::GCReason aReason,
+ nsJSContext::IsShrinking aShrinking,
+ const js::SliceBudget& aBudget) {
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
+ "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
+
+ bool wantIncremental = !aBudget.isUnlimited();
+
+ // We use danger::GetJSContext() since AutoJSAPI will assert if the current
+ // thread's context is null (such as during shutdown).
+ JSContext* cx = danger::GetJSContext();
+
+ if (!nsContentUtils::XPConnect() || !cx) {
+ return;
+ }
+
+ if (sScheduler.InIncrementalGC() && wantIncremental) {
+ // We're in the middle of incremental GC. Do another slice.
+ JS::PrepareForIncrementalGC(cx);
+ JS::IncrementalGCSlice(cx, aReason, aBudget);
+ return;
+ }
+
+ JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
+ ? JS::GCOptions::Shrink
+ : JS::GCOptions::Normal;
+
+ if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
+ sScheduler.SetNeedsFullGC();
+ }
+
+ if (sScheduler.NeedsFullGC()) {
+ JS::PrepareForFullGC(cx);
+ }
+
+ if (wantIncremental) {
+ // Incremental GC slices will be triggered by the GC Runner. If one doesn't
+ // already exist, create it in the GC_SLICE_END callback for the first
+ // slice being executed here.
+ JS::StartIncrementalGC(cx, options, aReason, aBudget);
+ } else {
+ JS::NonIncrementalGC(cx, options, aReason);
+ }
+}
+
+// static
+void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
+ IsShrinking aShrinking) {
+ GarbageCollectImpl(aReason, aShrinking, js::SliceBudget::unlimited());
+}
+
+// static
+void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
+ IsShrinking aShrinking,
+ js::SliceBudget& aBudget) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
+ GarbageCollectImpl(aReason, aShrinking, aBudget);
+}
+
+static void FinishAnyIncrementalGC() {
+ AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
+
+ if (sScheduler.InIncrementalGC()) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ // We're in the middle of an incremental GC, so finish it.
+ JS::PrepareForIncrementalGC(jsapi.cx());
+ JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
+ }
+}
+
+namespace geckoprofiler::markers {
+class CCSliceMarker {
+ public:
+ static constexpr Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("CCSlice");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ bool aIsDuringIdle) {
+ aWriter.BoolProperty("idle", aIsDuringIdle);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ schema.SetAllLabels("{marker.name} (idle={marker.data.idle})");
+ schema.AddKeyLabelFormat("idle", "Idle", MS::Format::Integer);
+ return schema;
+ }
+};
+} // namespace geckoprofiler::markers
+
+static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
+ TimeStamp startTimeStamp = TimeStamp::Now();
+ FinishAnyIncrementalGC();
+
+ uint32_t suspectedBefore = nsCycleCollector_suspectedCount();
+ js::SliceBudget budget =
+ sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
+ bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable();
+ nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
+ earlyForgetSkippable);
+ TimeStamp now = TimeStamp::Now();
+ uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete(
+ now, suspectedBefore, nsCycleCollector_suspectedCount());
+
+ TimeDuration duration = now - startTimeStamp;
+
+ sCCStats.AfterForgetSkippable(duration, removedPurples);
+
+ if (duration.ToSeconds()) {
+ TimeDuration idleDuration;
+ if (!aDeadline.IsNull()) {
+ if (aDeadline < now) {
+ // This slice overflowed the idle period.
+ if (aDeadline > startTimeStamp) {
+ idleDuration = aDeadline - startTimeStamp;
+ }
+ } else {
+ idleDuration = duration;
+ }
+ }
+
+ uint32_t percent =
+ uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
+ }
+
+ PROFILER_MARKER("ForgetSkippable", GCCC,
+ MarkerTiming::IntervalUntilNowFrom(startTimeStamp),
+ CCSliceMarker, !aDeadline.IsNull());
+}
+
+MOZ_ALWAYS_INLINE
+static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
+ MOZ_ASSERT(aEnd >= aStart);
+ return aEnd - aStart;
+}
+
+static TimeDuration TimeUntilNow(TimeStamp start) {
+ if (start.IsNull()) {
+ return TimeDuration();
+ }
+ return TimeBetween(start, TimeStamp::Now());
+}
+
+void CycleCollectorStats::Init() {
+ Clear();
+
+ char* env = getenv("MOZ_CCTIMER");
+ if (!env) {
+ return;
+ }
+ if (strcmp(env, "none") == 0) {
+ mFile = nullptr;
+ } else if (strcmp(env, "stdout") == 0) {
+ mFile = stdout;
+ } else if (strcmp(env, "stderr") == 0) {
+ mFile = stderr;
+ } else {
+ mFile = fopen(env, "a");
+ if (!mFile) {
+ MOZ_CRASH("Failed to open MOZ_CCTIMER log file.");
+ }
+ }
+}
+
+void CycleCollectorStats::Clear() {
+ if (mFile && mFile != stdout && mFile != stderr) {
+ fclose(mFile);
+ }
+ *this = CycleCollectorStats();
+}
+
+void CycleCollectorStats::AfterCycleCollectionSlice() {
+ if (mBeginSliceTime.IsNull()) {
+ // We already called this method from EndCycleCollectionCallback for this
+ // slice.
+ return;
+ }
+
+ mEndSliceTime = TimeStamp::Now();
+ TimeDuration duration = mEndSliceTime - mBeginSliceTime;
+
+ PROFILER_MARKER(
+ "CCSlice", GCCC, MarkerTiming::Interval(mBeginSliceTime, mEndSliceTime),
+ CCSliceMarker, !mIdleDeadline.IsNull() && mIdleDeadline >= mEndSliceTime);
+
+ if (duration.ToSeconds()) {
+ TimeDuration idleDuration;
+ if (!mIdleDeadline.IsNull()) {
+ if (mIdleDeadline < mEndSliceTime) {
+ // This slice overflowed the idle period.
+ if (mIdleDeadline > mBeginSliceTime) {
+ idleDuration = mIdleDeadline - mBeginSliceTime;
+ }
+ } else {
+ idleDuration = duration;
+ }
+ }
+
+ uint32_t percent =
+ uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE,
+ percent);
+ }
+
+ TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
+ mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
+ mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
+ mTotalSliceTime += sliceTime;
+ mBeginSliceTime = TimeStamp();
+}
+
+void CycleCollectorStats::PrepareForCycleCollection(TimeStamp aNow) {
+ mBeginTime = aNow;
+ mSuspected = nsCycleCollector_suspectedCount();
+}
+
+void CycleCollectorStats::AfterPrepareForCycleCollectionSlice(
+ TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime) {
+ mBeginSliceTime = aBeginTime;
+ mIdleDeadline = aDeadline;
+
+ if (!aMaybeAfterGCTime.IsNull()) {
+ mAnyLockedOut = true;
+ mMaxGCDuration = std::max(mMaxGCDuration, aMaybeAfterGCTime - aBeginTime);
+ }
+}
+
+void CycleCollectorStats::AfterSyncForgetSkippable(TimeStamp beginTime) {
+ mMaxSkippableDuration =
+ std::max(mMaxSkippableDuration, TimeUntilNow(beginTime));
+ mRanSyncForgetSkippable = true;
+}
+
+void CycleCollectorStats::AfterForgetSkippable(TimeDuration duration,
+ uint32_t aRemovedPurples) {
+ if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) {
+ mMinForgetSkippableTime = duration;
+ }
+ if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) {
+ mMaxForgetSkippableTime = duration;
+ }
+ mTotalForgetSkippableTime += duration;
+ ++mForgetSkippableBeforeCC;
+
+ mRemovedPurples += aRemovedPurples;
+}
+
+void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration,
+ TimeStamp aPrevCCEnd) const {
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE,
+ mRanSyncForgetSkippable);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL,
+ aCCNowDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE,
+ mMaxSliceTime.ToMilliseconds());
+
+ if (!aPrevCCEnd.IsNull()) {
+ TimeDuration timeBetween = TimeBetween(aPrevCCEnd, mBeginTime);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN,
+ timeBetween.ToSeconds());
+ }
+
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
+ mMaxForgetSkippableTime.ToMilliseconds());
+}
+
+void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
+ uint32_t aCleanups) const {
+ if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) {
+ return;
+ }
+
+ TimeDuration delta = GetCollectionTimeDelta();
+
+ nsCString mergeMsg;
+ if (aResults.mMergedZones) {
+ mergeMsg.AssignLiteral(" merged");
+ }
+
+ nsCString gcMsg;
+ if (aResults.mForcedGC) {
+ gcMsg.AssignLiteral(", forced a GC");
+ }
+
+ const char16_t* kFmt =
+ u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
+ u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
+ u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
+ u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
+ u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
+ nsString msg;
+ nsTextFormatter::ssprintf(
+ msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
+ mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sScheduler.mCCollectedWaitingForGC,
+ sScheduler.mCCollectedZonesWaitingForGC,
+ sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
+ mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
+ mMaxForgetSkippableTime.ToMilliseconds(),
+ mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
+ mTotalForgetSkippableTime.ToMilliseconds(),
+ mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples);
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+ if (mFile) {
+ fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
+ }
+}
+
+void CycleCollectorStats::MaybeNotifyStats(
+ const CycleCollectorResults& aResults, TimeDuration aCCNowDuration,
+ uint32_t aCleanups) const {
+ if (!StaticPrefs::javascript_options_mem_notify()) {
+ return;
+ }
+
+ const char16_t* kJSONFmt =
+ u"{ \"timestamp\": %llu, "
+ u"\"duration\": %.f, "
+ u"\"max_slice_pause\": %.f, "
+ u"\"total_slice_pause\": %.f, "
+ u"\"max_finish_gc_duration\": %.f, "
+ u"\"max_sync_skippable_duration\": %.f, "
+ u"\"suspected\": %lu, "
+ u"\"visited\": { "
+ u"\"RCed\": %lu, "
+ u"\"GCed\": %lu }, "
+ u"\"collected\": { "
+ u"\"RCed\": %lu, "
+ u"\"GCed\": %lu }, "
+ u"\"waiting_for_gc\": %lu, "
+ u"\"zones_waiting_for_gc\": %lu, "
+ u"\"short_living_objects_waiting_for_gc\": %lu, "
+ u"\"forced_gc\": %d, "
+ u"\"forget_skippable\": { "
+ u"\"times_before_cc\": %lu, "
+ u"\"min\": %.f, "
+ u"\"max\": %.f, "
+ u"\"avg\": %.f, "
+ u"\"total\": %.f, "
+ u"\"removed\": %lu } "
+ u"}";
+
+ nsString json;
+ nsTextFormatter::ssprintf(
+ json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
+ mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
+ mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
+ mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sScheduler.mCCollectedWaitingForGC,
+ sScheduler.mCCollectedZonesWaitingForGC,
+ sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
+ mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
+ mMaxForgetSkippableTime.ToMilliseconds(),
+ mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
+ mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
+ json.get());
+ }
+}
+
+// static
+void nsJSContext::CycleCollectNow(CCReason aReason,
+ nsICycleCollectorListener* aListener) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
+
+ PrepareForCycleCollectionSlice(aReason, TimeStamp());
+ nsCycleCollector_collect(aReason, aListener);
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+// static
+void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason,
+ TimeStamp aDeadline) {
+ TimeStamp beginTime = TimeStamp::Now();
+
+ // Before we begin the cycle collection, make sure there is no active GC.
+ TimeStamp afterGCTime;
+ if (sScheduler.InIncrementalGC()) {
+ FinishAnyIncrementalGC();
+ afterGCTime = TimeStamp::Now();
+ }
+
+ if (!sScheduler.IsCollectingCycles()) {
+ sCCStats.PrepareForCycleCollection(beginTime);
+ sScheduler.NoteCCBegin(aReason, beginTime,
+ sCCStats.mForgetSkippableBeforeCC,
+ sCCStats.mSuspected, sCCStats.mRemovedPurples);
+ }
+
+ sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime,
+ afterGCTime);
+}
+
+// static
+void nsJSContext::RunCycleCollectorSlice(CCReason aReason,
+ TimeStamp aDeadline) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PrepareForCycleCollectionSlice(aReason, aDeadline);
+
+ // Decide how long we want to budget for this slice.
+ if (sIncrementalCC) {
+ bool preferShorterSlices;
+ js::SliceBudget budget = sScheduler.ComputeCCSliceBudget(
+ aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime,
+ TimeStamp::Now(), &preferShorterSlices);
+ nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices);
+ } else {
+ js::SliceBudget budget = js::SliceBudget::unlimited();
+ nsCycleCollector_collectSlice(budget, aReason, false);
+ }
+
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+// static
+void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
+
+ PrepareForCycleCollectionSlice(CCReason::API, TimeStamp());
+
+ js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
+ nsCycleCollector_collectSlice(budget, CCReason::API);
+
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+void nsJSContext::ClearMaxCCSliceTime() {
+ sCCStats.mMaxSliceTimeSinceClear = TimeDuration();
+}
+
+uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
+ return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
+}
+
+// static
+void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TimeStamp startTime = TimeStamp::Now();
+ sCCStats.PrepareForCycleCollection(startTime);
+
+ // Run forgetSkippable synchronously to reduce the size of the CC graph. This
+ // is particularly useful if we recently finished a GC.
+ if (sScheduler.IsEarlyForgetSkippable()) {
+ while (sScheduler.IsEarlyForgetSkippable()) {
+ FireForgetSkippable(false, TimeStamp());
+ }
+ sCCStats.AfterSyncForgetSkippable(startTime);
+ }
+
+ if (sShuttingDown) {
+ return;
+ }
+
+ sScheduler.InitCCRunnerStateMachine(
+ mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason);
+ sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
+}
+
+// static
+void nsJSContext::EndCycleCollectionCallback(
+ const CycleCollectorResults& aResults) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sScheduler.KillCCRunner();
+
+ // Update timing information for the current slice before we log it, if
+ // we previously called PrepareForCycleCollectionSlice(). During shutdown
+ // CCs, this won't happen.
+ sCCStats.AfterCycleCollectionSlice();
+
+ TimeStamp endCCTimeStamp = TimeStamp::Now();
+ TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
+ TimeStamp prevCCEnd = sScheduler.GetLastCCEndTime();
+
+ sScheduler.NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime);
+
+ // Log information about the CC via telemetry, JSON and the console.
+
+ sCCStats.SendTelemetry(ccNowDuration, prevCCEnd);
+
+ uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u);
+
+ sCCStats.MaybeLogStats(aResults, cleanups);
+
+ sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups);
+
+ // Update global state to indicate we have just run a cycle collection.
+ sCCStats.Clear();
+
+ // If we need a GC after this CC (typically because lots of GCed objects or
+ // zones have been collected in the CC), schedule it.
+
+ if (sScheduler.NeedsGCAfterCC()) {
+ MOZ_ASSERT(
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration,
+ "A max duration ICC shouldn't reduce GC delay to 0");
+
+ sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr,
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay()) -
+ std::min(ccNowDuration, kMaxICCDuration));
+ }
+#if defined(MOZ_MEMORY)
+ else if (
+ StaticPrefs::
+ dom_memory_foreground_content_processes_have_larger_page_cache()) {
+ jemalloc_free_dirty_pages();
+ }
+#endif
+}
+
+/* static */
+bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
+
+ bool didDoWork = false;
+
+ // The CC/GC scheduler (sScheduler) decides what action(s) to take during
+ // this invocation of the CC runner.
+ //
+ // This may be zero, one, or multiple actions. (Zero is when CC is blocked by
+ // incremental GC, or when the scheduler determined that a CC is no longer
+ // needed.) Loop until the scheduler finishes this invocation by returning
+ // `Yield` in step.mYield.
+ CCRunnerStep step;
+ do {
+ step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(),
+ nsCycleCollector_suspectedCount());
+ switch (step.mAction) {
+ case CCRunnerAction::None:
+ break;
+
+ case CCRunnerAction::MinorGC:
+ JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(),
+ step.mParam.mReason);
+ sScheduler.NoteMinorGCEnd();
+ break;
+
+ case CCRunnerAction::ForgetSkippable:
+ // 'Forget skippable' only, then end this invocation.
+ FireForgetSkippable(bool(step.mParam.mRemoveChildless), aDeadline);
+ break;
+
+ case CCRunnerAction::CleanupContentUnbinder:
+ // Clear content unbinder before the first actual CC slice.
+ Element::ClearContentUnbinder();
+ break;
+
+ case CCRunnerAction::CleanupDeferred:
+ // and if time still permits, perform deferred deletions.
+ nsCycleCollector_doDeferredDeletion();
+ break;
+
+ case CCRunnerAction::CycleCollect:
+ // Cycle collection slice.
+ nsJSContext::RunCycleCollectorSlice(step.mParam.mCCReason, aDeadline);
+ break;
+
+ case CCRunnerAction::StopRunning:
+ // End this CC, either because we have run a cycle collection slice, or
+ // because a CC is no longer needed.
+ sScheduler.KillCCRunner();
+ break;
+ }
+
+ if (step.mAction != CCRunnerAction::None) {
+ didDoWork = true;
+ }
+ } while (step.mYield == CCRunnerYield::Continue);
+
+ return didDoWork;
+}
+
+// static
+bool nsJSContext::HasHadCleanupSinceLastGC() {
+ return sScheduler.IsEarlyForgetSkippable(1);
+}
+
+// static
+void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
+ mozilla::TimeStamp aDeadline) {
+ sScheduler.RunNextCollectorTimer(aReason, aDeadline);
+}
+
+// static
+void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
+ JS::GCReason aReason) {
+ if (!aDocShell || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ BrowsingContext* bc = aDocShell->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ BrowsingContext* root = bc->Top();
+ if (bc == root) {
+ // We don't want to run collectors when loading the top level page.
+ return;
+ }
+
+ nsIDocShell* rootDocShell = root->GetDocShell();
+ if (!rootDocShell) {
+ return;
+ }
+
+ Document* rootDocument = rootDocShell->GetDocument();
+ if (!rootDocument ||
+ rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
+ rootDocument->IsInBackgroundWindow()) {
+ return;
+ }
+
+ PresShell* presShell = rootDocument->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ return;
+ }
+
+ if (!sScheduler.IsUserActive()) {
+ Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
+ // Try to not delay the next RefreshDriver tick, so give a reasonable
+ // deadline for collectors.
+ if (next.isSome()) {
+ sScheduler.RunNextCollectorTimer(aReason, next.value());
+ }
+ }
+}
+
+// static
+void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
+ TimeDuration aDelay) {
+ sScheduler.PokeGC(aReason, aObj, aDelay);
+}
+
+// static
+void nsJSContext::MaybePokeGC() {
+ if (sShuttingDown) {
+ return;
+ }
+
+ JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
+ JS::GCReason reason = JS::WantEagerMinorGC(rt);
+ if (reason != JS::GCReason::NO_REASON) {
+ MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION);
+ sScheduler.PokeMinorGC(reason);
+ }
+
+ // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some
+ // benchmarks. Hopefully that will be worked out and this will check for
+ // whether an eager major GC is needed.
+}
+
+void nsJSContext::DoLowMemoryGC() {
+ if (sShuttingDown) {
+ return;
+ }
+ nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
+ nsJSContext::ShrinkingGC);
+ nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE);
+ if (sScheduler.NeedsGCAfterCC()) {
+ nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
+ nsJSContext::ShrinkingGC);
+ }
+}
+
+// static
+void nsJSContext::LowMemoryGC() {
+ RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
+ CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE);
+ if (!mbPromise) {
+ // Normally when the promise is null it means that IPC failed, that probably
+ // means that something bad happened, don't bother with the GC.
+ return;
+ }
+ mbPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](bool aIgnored) { DoLowMemoryGC(); },
+ [](mozilla::ipc::ResponseRejectReason r) {});
+}
+
+// static
+void nsJSContext::MaybePokeCC() {
+ sScheduler.MaybePokeCC(TimeStamp::Now(), nsCycleCollector_suspectedCount());
+}
+
+static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
+ const JS::GCDescription& aDesc) {
+ NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
+
+ static TimeStamp sCurrentGCStartTime;
+
+ switch (aProgress) {
+ case JS::GC_CYCLE_BEGIN: {
+ // Prevent cycle collections and shrinking during incremental GC.
+ sScheduler.NoteGCBegin(aDesc.reason_);
+ sCurrentGCStartTime = TimeStamp::Now();
+ break;
+ }
+
+ case JS::GC_CYCLE_END: {
+ TimeDuration delta = GetCollectionTimeDelta();
+
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsString gcstats;
+ gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
+ nsAutoString prefix;
+ nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
+ delta.ToSeconds(),
+ ProcessNameForCollectorLog(), getpid());
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ sScheduler.NoteGCEnd();
+
+ // May need to kill the GC runner
+ sScheduler.KillGCRunner();
+
+ nsJSContext::MaybePokeCC();
+
+#if defined(MOZ_MEMORY)
+ bool freeDirty = false;
+#endif
+ if (aDesc.isZone_) {
+ sScheduler.PokeFullGC();
+ } else {
+#if defined(MOZ_MEMORY)
+ freeDirty = true;
+#endif
+ sScheduler.SetNeedsFullGC(false);
+ sScheduler.KillFullGCTimer();
+ }
+
+ if (sScheduler.IsCCNeeded(TimeStamp::Now(),
+ nsCycleCollector_suspectedCount()) !=
+ CCReason::NO_REASON) {
+#if defined(MOZ_MEMORY)
+ // We're likely to free the dirty pages after CC.
+ freeDirty = false;
+#endif
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
+ TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
+
+#if defined(MOZ_MEMORY)
+ if (freeDirty &&
+ StaticPrefs::
+ dom_memory_foreground_content_processes_have_larger_page_cache()) {
+ jemalloc_free_dirty_pages();
+ }
+#endif
+ break;
+ }
+
+ case JS::GC_SLICE_BEGIN:
+ break;
+
+ case JS::GC_SLICE_END:
+ sScheduler.NoteGCSliceEnd(aDesc.lastSliceStart(aCx),
+ aDesc.lastSliceEnd(aCx));
+
+ if (sShuttingDown) {
+ sScheduler.KillGCRunner();
+ } else {
+ // If incremental GC wasn't triggered by GCTimerFired, we may not have a
+ // runner to ensure all the slices are handled. So, create the runner
+ // here.
+ sScheduler.EnsureGCRunner(0);
+ }
+
+ if (sScheduler.IsCCNeeded(TimeStamp::Now(),
+ nsCycleCollector_suspectedCount()) !=
+ CCReason::NO_REASON) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsString gcstats;
+ gcstats.Adopt(aDesc.formatSliceMessage(aCx));
+ nsAutoString prefix;
+ nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
+ ProcessNameForCollectorLog(), getpid());
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected GCProgress value");
+ }
+
+ if (sPrevGCSliceCallback) {
+ (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
+ }
+}
+
+void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
+ mWindowProxy = aWindowProxy;
+}
+
+JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
+
+void nsJSContext::LikelyShortLivingObjectCreated() {
+ ++sScheduler.mLikelyShortLivingObjectsNeedingGC;
+}
+
+void mozilla::dom::StartupJSEnvironment() {
+ // initialize all our statics, so that we can restart XPCOM
+ sIsInitialized = false;
+ sShuttingDown = false;
+ new (&sScheduler) CCGCScheduler(); // Reset the scheduler state.
+ sCCStats.Init();
+}
+
+static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JS_SetGCParameter(jsapi.cx(), aParam, aValue);
+}
+
+static void ResetGCParameter(JSGCParamKey aParam) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JS_ResetGCParameter(jsapi.cx(), aParam);
+}
+
+static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
+ void* aClosure) {
+ int32_t prefMB = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
+ if (prefB.isValid() && prefB.value() >= 0) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ int32_t prefKB = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
+ if (prefB.isValid() && prefB.value() >= 0) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
+ void* aClosure) {
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref >= 0 && pref < 10000) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
+ void* aClosure) {
+ bool pref = Preferences::GetBool(aPrefName);
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
+}
+
+static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref > 0 && pref < 100000) {
+ sScheduler.SetActiveIntersliceGCBudget(
+ TimeDuration::FromMilliseconds(pref));
+ SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
+ } else {
+ ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
+ }
+}
+
+static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ bool pref = Preferences::GetBool(aPrefName);
+ sIncrementalCC = pref;
+}
+
+class JSDispatchableRunnable final : public Runnable {
+ ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
+
+ public:
+ explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
+ : mozilla::Runnable("JSDispatchableRunnable"),
+ mDispatchable(aDispatchable) {
+ MOZ_ASSERT(mDispatchable);
+ }
+
+ protected:
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
+ sShuttingDown ? JS::Dispatchable::ShuttingDown
+ : JS::Dispatchable::NotShuttingDown;
+
+ mDispatchable->run(jsapi.cx(), maybeShuttingDown);
+ mDispatchable = nullptr; // mDispatchable may delete itself
+
+ return NS_OK;
+ }
+
+ private:
+ JS::Dispatchable* mDispatchable;
+};
+
+static bool DispatchToEventLoop(void* closure,
+ JS::Dispatchable* aDispatchable) {
+ MOZ_ASSERT(!closure);
+
+ // This callback may execute either on the main thread or a random JS-internal
+ // helper thread. This callback can be called during shutdown so we cannot
+ // simply NS_DispatchToMainThread. Failure during shutdown is expected and
+ // properly handled by the JS engine.
+
+ nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ if (!mainTarget) {
+ return false;
+ }
+
+ RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
+ MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+ return true;
+}
+
+static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MimeType aMimeType,
+ JS::StreamConsumer* aConsumer) {
+ return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
+ nullptr);
+}
+
+static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason,
+ int64_t aMillis) {
+ return sScheduler.CreateGCSliceBudget(
+ mozilla::TimeDuration::FromMilliseconds(aMillis), false, false);
+}
+
+void nsJSContext::EnsureStatics() {
+ if (sIsInitialized) {
+ if (!nsContentUtils::XPConnect()) {
+ MOZ_CRASH();
+ }
+ return;
+ }
+
+ // Let's make sure that our main thread is the same as the xpcom main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
+
+ JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget);
+
+ JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
+ JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
+ FetchUtil::ReportJSStreamError);
+
+ // Set these global xpconnect options...
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
+ "javascript.options.mem.max",
+ (void*)JSGC_MAX_BYTES);
+ Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
+ "javascript.options.mem.nursery.min_kb",
+ (void*)JSGC_MIN_NURSERY_BYTES);
+ Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
+ "javascript.options.mem.nursery.max_kb",
+ (void*)JSGC_MAX_NURSERY_BYTES);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_per_zone",
+ (void*)JSGC_PER_ZONE_GC_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_incremental",
+ (void*)JSGC_INCREMENTAL_GC_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_compacting",
+ (void*)JSGC_COMPACTING_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_parallel_marking",
+ (void*)JSGC_PARALLEL_MARKING_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryGCSliceTimePrefChangedCallback,
+ "javascript.options.mem.gc_incremental_slice_ms");
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.incremental_weakmap",
+ (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_time_limit_ms",
+ (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_low_frequency_heap_growth",
+ (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_large_heap_growth",
+ (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_small_heap_growth",
+ (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_balanced_heap_limits",
+ (void*)JSGC_BALANCED_HEAP_LIMITS_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_heap_growth_factor",
+ (void*)JSGC_HEAP_GROWTH_FACTOR);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_small_heap_size_max_mb",
+ (void*)JSGC_SMALL_HEAP_SIZE_MAX);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_large_heap_size_min_mb",
+ (void*)JSGC_LARGE_HEAP_SIZE_MIN);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_allocation_threshold_mb",
+ (void*)JSGC_ALLOCATION_THRESHOLD);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_malloc_threshold_base_mb",
+ (void*)JSGC_MALLOC_THRESHOLD_BASE);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_small_heap_incremental_limit",
+ (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_large_heap_incremental_limit",
+ (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_urgent_threshold_mb",
+ (void*)JSGC_URGENT_THRESHOLD_MB);
+
+ Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
+ "dom.cycle_collector.incremental");
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_min_empty_chunk_count",
+ (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_max_empty_chunk_count",
+ (void*)JSGC_MAX_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_helper_thread_ratio",
+ (void*)JSGC_HELPER_THREAD_RATIO);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_max_helper_threads",
+ (void*)JSGC_MAX_HELPER_THREADS);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ MOZ_CRASH();
+ }
+
+ nsIObserver* observer = new nsJSEnvironmentObserver();
+ obs->AddObserver(observer, "memory-pressure", false);
+ obs->AddObserver(observer, "user-interaction-inactive", false);
+ obs->AddObserver(observer, "user-interaction-active", false);
+ obs->AddObserver(observer, "quit-application", false);
+ obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(observer, "content-child-will-shutdown", false);
+
+ sIsInitialized = true;
+}
+
+void mozilla::dom::ShutdownJSEnvironment() {
+ sShuttingDown = true;
+ sScheduler.Shutdown();
+}
+
+AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
+ : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
+
+void AsyncErrorReporter::SerializeStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack) {
+ mStackHolder = MakeUnique<SerializedStackHolder>();
+ mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
+}
+
+void AsyncErrorReporter::SetException(JSContext* aCx,
+ JS::Handle<JS::Value> aException) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mException.init(aCx, aException);
+ mHasException = true;
+}
+
+NS_IMETHODIMP AsyncErrorReporter::Run() {
+ AutoJSAPI jsapi;
+ // We're only using this context to deserialize a stack to report to the
+ // console, so the scope we use doesn't matter. Stack frame filtering happens
+ // based on the principal encoded into the frame and the caller compartment,
+ // not the compartment of the frame object, and the console reporting code
+ // will not be using our context, and therefore will not care what compartment
+ // it has entered.
+ DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
+ MOZ_ASSERT(ok, "Problem with system global?");
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> stack(cx);
+ JS::Rooted<JSObject*> stackGlobal(cx);
+ if (mStackHolder) {
+ stack = mStackHolder->ReadStack(cx);
+ if (stack) {
+ stackGlobal = JS::CurrentGlobalOrNull(cx);
+ }
+ }
+
+ JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
+ if (mHasException) {
+ MOZ_ASSERT(NS_IsMainThread());
+ exception = Some(mException);
+ // Remove our reference to the exception.
+ mException.setUndefined();
+ mHasException = false;
+ }
+
+ mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
+ return NS_OK;
+}
+
+// A fast-array class for JS. This class supports both nsIJSScriptArray and
+// nsIArray. If it is JS itself providing and consuming this class, all work
+// can be done via nsIJSScriptArray, and avoid the conversion of elements
+// to/from nsISupports.
+// When consumed by non-JS (eg, another script language), conversion is done
+// on-the-fly.
+class nsJSArgArray final : public nsIJSArgArray {
+ public:
+ nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
+ nsresult* prv);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
+ nsIJSArgArray)
+
+ // nsIArray
+ NS_DECL_NSIARRAY
+
+ // nsIJSArgArray
+ nsresult GetArgs(uint32_t* argc, void** argv) override;
+
+ void ReleaseJSObjects();
+
+ protected:
+ ~nsJSArgArray();
+ JSContext* mContext;
+ JS::Heap<JS::Value>* mArgv;
+ uint32_t mArgc;
+};
+
+nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
+ const JS::Value* argv, nsresult* prv)
+ : mContext(aContext), mArgv(nullptr), mArgc(argc) {
+ // copy the array - we don't know its lifetime, and ours is tied to xpcom
+ // refcounting.
+ if (argc) {
+ mArgv = new (fallible) JS::Heap<JS::Value>[argc];
+ if (!mArgv) {
+ *prv = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+ }
+
+ // Callers are allowed to pass in a null argv even for argc > 0. They can
+ // then use GetArgs to initialize the values.
+ if (argv) {
+ for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
+ }
+
+ if (argc > 0) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ *prv = NS_OK;
+}
+
+nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
+
+void nsJSArgArray::ReleaseJSObjects() {
+ delete[] mArgv;
+
+ if (mArgc > 0) {
+ mArgc = 0;
+ mozilla::DropJSObjects(this);
+ }
+}
+
+// QueryInterface implementation for nsJSArgArray
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
+ tmp->ReleaseJSObjects();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
+ if (tmp->mArgv) {
+ for (uint32_t i = 0; i < tmp->mArgc; ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
+ NS_INTERFACE_MAP_ENTRY(nsIArray)
+ NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
+
+nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
+ *argv = (void*)mArgv;
+ *argc = mArgc;
+ return NS_OK;
+}
+
+// nsIArray impl
+NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
+ *aLength = mArgc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
+ void** result) {
+ *result = nullptr;
+ if (index >= mArgc) return NS_ERROR_INVALID_ARG;
+
+ if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
+ uuid.Equals(NS_GET_IID(nsISupports))) {
+ // Have to copy a Heap into a Rooted to work with it.
+ JS::Rooted<JS::Value> val(mContext, mArgv[index]);
+ return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
+ (nsIVariant**)result);
+ }
+ NS_WARNING("nsJSArgArray only handles nsIVariant");
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
+ uint8_t aArgc,
+ nsISimpleEnumerator** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
+ nsISimpleEnumerator** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// The factory function
+nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
+ const JS::Value* argv, nsIJSArgArray** aArray) {
+ nsresult rv;
+ nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ ret.forget(aArray);
+ return NS_OK;
+}
diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h
new file mode 100644
index 0000000000..41e7123a62
--- /dev/null
+++ b/dom/base/nsJSEnvironment.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsJSEnvironment_h
+#define nsJSEnvironment_h
+
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsCOMPtr.h"
+#include "prtime.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
+
+class nsICycleCollectorListener;
+class nsIDocShell;
+
+namespace mozilla {
+
+template <class>
+class Maybe;
+struct CycleCollectorResults;
+
+static const uint32_t kMajorForgetSkippableCalls = 5;
+
+} // namespace mozilla
+
+class nsJSContext : public nsIScriptContext {
+ public:
+ nsJSContext(bool aGCOnDestruction, nsIScriptGlobalObject* aGlobalObject);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSContext,
+ nsIScriptContext)
+
+ virtual nsIScriptGlobalObject* GetGlobalObject() override;
+ inline nsIScriptGlobalObject* GetGlobalObjectRef() {
+ return mGlobalObjectRef;
+ }
+
+ virtual nsresult SetProperty(JS::Handle<JSObject*> aTarget,
+ const char* aPropName,
+ nsISupports* aVal) override;
+
+ virtual bool GetProcessingScriptTag() override;
+ virtual void SetProcessingScriptTag(bool aResult) override;
+
+ virtual nsresult InitClasses(JS::Handle<JSObject*> aGlobalObj) override;
+
+ virtual void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) override;
+ virtual JSObject* GetWindowProxy() override;
+
+ enum IsShrinking { ShrinkingGC, NonShrinkingGC };
+
+ // Setup all the statics etc - safe to call multiple times after Startup().
+ static void EnsureStatics();
+
+ static void SetLowMemoryState(bool aState);
+
+ static void GarbageCollectNow(JS::GCReason reason,
+ IsShrinking aShrinking = NonShrinkingGC);
+
+ static void RunIncrementalGCSlice(JS::GCReason aReason,
+ IsShrinking aShrinking,
+ js::SliceBudget& aBudget);
+
+ static void CycleCollectNow(mozilla::CCReason aReason,
+ nsICycleCollectorListener* aListener = nullptr);
+
+ // Finish up any in-progress incremental GC.
+ static void PrepareForCycleCollectionSlice(mozilla::CCReason aReason,
+ mozilla::TimeStamp aDeadline);
+
+ // Run a cycle collector slice, using a heuristic to decide how long to run
+ // it.
+ static void RunCycleCollectorSlice(mozilla::CCReason aReason,
+ mozilla::TimeStamp aDeadline);
+
+ // Run a cycle collector slice, using the given work budget.
+ static void RunCycleCollectorWorkSlice(int64_t aWorkBudget);
+
+ static void BeginCycleCollectionCallback(mozilla::CCReason aReason);
+ static void EndCycleCollectionCallback(
+ const mozilla::CycleCollectorResults& aResults);
+
+ // Return the longest CC slice time since ClearMaxCCSliceTime() was last
+ // called.
+ static uint32_t GetMaxCCSliceTimeSinceClear();
+ static void ClearMaxCCSliceTime();
+
+ // If there is some pending CC or GC timer/runner, this will run it.
+ static void RunNextCollectorTimer(
+ JS::GCReason aReason,
+ mozilla::TimeStamp aDeadline = mozilla::TimeStamp());
+ // If user has been idle and aDocShell is for an iframe being loaded in an
+ // already loaded top level docshell, this will run a CC or GC
+ // timer/runner if there is such pending.
+ static void MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
+ JS::GCReason aReason);
+
+ // The GC should run soon, in the zone of aObj if given. If aObj is
+ // nullptr, collect all Zones.
+ static void PokeGC(JS::GCReason aReason, JSObject* aObj,
+ mozilla::TimeDuration aDelay = 0);
+
+ // If usage is nearing a threshold, request idle-only GC work. (This is called
+ // when a collection would be relatively convenient.)
+ static void MaybePokeGC();
+
+ // Immediately perform a non-incremental shrinking GC and CC.
+ static void DoLowMemoryGC();
+
+ // Perform a non-incremental shrinking GC and CC according to
+ // IdleScheduler.
+ static void LowMemoryGC();
+
+ static void MaybePokeCC();
+
+ // Calling LikelyShortLivingObjectCreated() makes a GC more likely.
+ static void LikelyShortLivingObjectCreated();
+
+ static bool HasHadCleanupSinceLastGC();
+
+ nsIScriptGlobalObject* GetCachedGlobalObject() {
+ // Verify that we have a global so that this
+ // does always return a null when GetGlobalObject() is null.
+ JSObject* global = GetWindowProxy();
+ return global ? mGlobalObjectRef.get() : nullptr;
+ }
+
+ protected:
+ virtual ~nsJSContext();
+
+ // Helper to convert xpcom datatypes to jsvals.
+ nsresult ConvertSupportsTojsvals(JSContext* aCx, nsISupports* aArgs,
+ JS::Handle<JSObject*> aScope,
+ JS::MutableHandleVector<JS::Value> aArgsOut);
+
+ nsresult AddSupportsPrimitiveTojsvals(JSContext* aCx, nsISupports* aArg,
+ JS::Value* aArgv);
+
+ private:
+ void Destroy();
+
+ JS::Heap<JSObject*> mWindowProxy;
+
+ bool mGCOnDestruction;
+ bool mProcessingScriptTag;
+
+ // mGlobalObjectRef ensures that the outer window stays alive as long as the
+ // context does. It is eventually collected by the cycle collector.
+ nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef;
+
+ static bool DOMOperationCallback(JSContext* cx);
+};
+
+namespace mozilla::dom {
+
+class SerializedStackHolder;
+
+void StartupJSEnvironment();
+void ShutdownJSEnvironment();
+
+// Runnable that's used to do async error reporting
+class AsyncErrorReporter final : public mozilla::Runnable {
+ public:
+ explicit AsyncErrorReporter(xpc::ErrorReport* aReport);
+ // SerializeStack is suitable for main or worklet thread use.
+ // Stacks from worker threads are not supported.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1578968
+ void SerializeStack(JSContext* aCx, JS::Handle<JSObject*> aStack);
+
+ // Set the exception value associated with this error report.
+ // Should only be called from the main thread.
+ void SetException(JSContext* aCx, JS::Handle<JS::Value> aException);
+
+ protected:
+ NS_IMETHOD Run() override;
+
+ // This is only used on main thread!
+ JS::PersistentRooted<JS::Value> mException;
+ bool mHasException = false;
+
+ RefPtr<xpc::ErrorReport> mReport;
+ // This may be used to marshal a stack from an arbitrary thread/runtime into
+ // the main thread/runtime where the console service runs.
+ UniquePtr<SerializedStackHolder> mStackHolder;
+};
+
+} // namespace mozilla::dom
+
+// An interface for fast and native conversion to/from nsIArray. If an object
+// supports this interface, JS can reach directly in for the argv, and avoid
+// nsISupports conversion. If this interface is not supported, the object will
+// be queried for nsIArray, and everything converted via xpcom objects.
+#define NS_IJSARGARRAY_IID \
+ { \
+ 0xb6acdac8, 0xf5c6, 0x432c, { \
+ 0xa8, 0x6e, 0x33, 0xee, 0xb1, 0xb0, 0xcd, 0xdc \
+ } \
+ }
+
+class nsIJSArgArray : public nsIArray {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IJSARGARRAY_IID)
+ // Bug 312003 describes why this must be "void **", but after calling argv
+ // may be cast to JS::Value* and the args found at:
+ // ((JS::Value*)argv)[0], ..., ((JS::Value*)argv)[argc - 1]
+ virtual nsresult GetArgs(uint32_t* argc, void** argv) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIJSArgArray, NS_IJSARGARRAY_IID)
+
+#endif /* nsJSEnvironment_h */
diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp
new file mode 100644
index 0000000000..86fe04583c
--- /dev/null
+++ b/dom/base/nsJSUtils.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is not a generated file. It contains common utility functions
+ * invoked from the JavaScript code generated from IDL interfaces.
+ * The goal of the utility functions is to cut down on the size of
+ * the generated code itself.
+ */
+
+#include "nsJSUtils.h"
+
+#include <utility>
+#include "MainThreadUtils.h"
+#include "js/ComparisonOperators.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/Date.h"
+#include "js/GCVector.h"
+#include "js/HeapAPI.h"
+#include "js/Modules.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/TypeDecls.h"
+#include "jsfriendapi.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/fallible.h"
+#include "mozilla/ProfilerLabels.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsGlobalWindowInner.h"
+#include "nsINode.h"
+#include "nsString.h"
+#include "nsTPromiseFlatString.h"
+#include "nscore.h"
+#include "prenv.h"
+
+#if !defined(DEBUG) && !defined(MOZ_ENABLE_JS_DUMP)
+# include "mozilla/StaticPrefs_browser.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool nsJSUtils::GetCallingLocation(JSContext* aContext, nsACString& aFilename,
+ uint32_t* aLineno, uint32_t* aColumn) {
+ JS::AutoFilename filename;
+ if (!JS::DescribeScriptedCaller(aContext, &filename, aLineno, aColumn)) {
+ return false;
+ }
+
+ return aFilename.Assign(filename.get(), fallible);
+}
+
+bool nsJSUtils::GetCallingLocation(JSContext* aContext, nsAString& aFilename,
+ uint32_t* aLineno, uint32_t* aColumn) {
+ JS::AutoFilename filename;
+ if (!JS::DescribeScriptedCaller(aContext, &filename, aLineno, aColumn)) {
+ return false;
+ }
+
+ return aFilename.Assign(NS_ConvertUTF8toUTF16(filename.get()), fallible);
+}
+
+uint64_t nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext) {
+ if (!aContext) return 0;
+
+ nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(aContext);
+ return win ? win->WindowID() : 0;
+}
+
+nsresult nsJSUtils::UpdateFunctionDebugMetadata(
+ AutoJSAPI& jsapi, JS::Handle<JSObject*> aFun, JS::CompileOptions& aOptions,
+ JS::Handle<JSString*> aElementAttributeName,
+ JS::Handle<JS::Value> aPrivateValue) {
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSFunction*> fun(cx, JS_GetObjectFunction(aFun));
+ if (!fun) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSScript*> script(cx, JS_GetFunctionScript(cx, fun));
+ if (!script) {
+ return NS_OK;
+ }
+
+ JS::InstantiateOptions instantiateOptions(aOptions);
+ if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, aPrivateValue,
+ aElementAttributeName, nullptr, nullptr)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
+ JS::HandleVector<JSObject*> aScopeChain,
+ JS::CompileOptions& aOptions,
+ const nsACString& aName, uint32_t aArgCount,
+ const char** aArgArray,
+ const nsAString& aBody,
+ JSObject** aFunctionObject) {
+ JSContext* cx = jsapi.cx();
+ MOZ_ASSERT(js::GetContextRealm(cx));
+ MOZ_ASSERT_IF(aScopeChain.length() != 0,
+ js::IsObjectInContextCompartment(aScopeChain[0], cx));
+
+ // Do the junk Gecko is supposed to do before calling into JSAPI.
+ for (size_t i = 0; i < aScopeChain.length(); ++i) {
+ JS::ExposeObjectToActiveJS(aScopeChain[i]);
+ }
+
+ // Compile.
+ const nsPromiseFlatString& flatBody = PromiseFlatString(aBody);
+
+ JS::SourceText<char16_t> source;
+ if (!source.init(cx, flatBody.get(), flatBody.Length(),
+ JS::SourceOwnership::Borrowed)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSFunction*> fun(
+ cx, JS::CompileFunction(cx, aScopeChain, aOptions,
+ PromiseFlatCString(aName).get(), aArgCount,
+ aArgArray, source));
+ if (!fun) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aFunctionObject = JS_GetFunctionObject(fun);
+ return NS_OK;
+}
+
+/* static */
+bool nsJSUtils::IsScriptable(JS::Handle<JSObject*> aEvaluationGlobal) {
+ return xpc::Scriptability::AllowedIfExists(aEvaluationGlobal);
+}
+
+static bool AddScopeChainItem(JSContext* aCx, nsINode* aNode,
+ JS::MutableHandleVector<JSObject*> aScopeChain) {
+ JS::Rooted<JS::Value> val(aCx);
+ if (!GetOrCreateDOMReflector(aCx, aNode, &val)) {
+ return false;
+ }
+
+ if (!aScopeChain.append(&val.toObject())) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+bool nsJSUtils::GetScopeChainForElement(
+ JSContext* aCx, Element* aElement,
+ JS::MutableHandleVector<JSObject*> aScopeChain) {
+ for (nsINode* cur = aElement; cur; cur = cur->GetScopeChainParent()) {
+ if (!AddScopeChainItem(aCx, cur, aScopeChain)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+void nsJSUtils::ResetTimeZone() { JS::ResetTimeZone(); }
+
+/* static */
+bool nsJSUtils::DumpEnabled() {
+#ifdef FUZZING
+ static bool mozFuzzDebug = !!PR_GetEnv("MOZ_FUZZ_DEBUG");
+ return mozFuzzDebug;
+#endif
+
+#if defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)
+ return true;
+#else
+ return StaticPrefs::browser_dom_window_dump_enabled();
+#endif
+}
+
+JSObject* nsJSUtils::MoveBufferAsUint8Array(JSContext* aCx, size_t aSize,
+ UniquePtr<uint8_t>& aBuffer) {
+ JS::Rooted<JSObject*> arrayBuffer(
+ aCx, JS::NewArrayBufferWithContents(aCx, aSize, aBuffer.get()));
+ if (!arrayBuffer) {
+ return nullptr;
+ }
+
+ // Now the ArrayBuffer owns the buffer, so let's release our ownership
+ (void)aBuffer.release();
+
+ return JS_NewUint8ArrayWithBuffer(aCx, arrayBuffer, 0,
+ static_cast<int64_t>(aSize));
+}
+
+//
+// nsDOMJSUtils.h
+//
+
+template <typename T>
+bool nsTAutoJSString<T>::init(const JS::Value& v) {
+ // Note: it's okay to use danger::GetJSContext here instead of AutoJSAPI,
+ // because the init() call below is careful not to run script (for instance,
+ // it only calls JS::ToString for non-object values).
+ JSContext* cx = danger::GetJSContext();
+ if (!init(cx, v)) {
+ JS_ClearPendingException(cx);
+ return false;
+ }
+ return true;
+}
+
+template bool nsTAutoJSString<char16_t>::init(const JS::Value&);
+template bool nsTAutoJSString<char>::init(const JS::Value&);
diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h
new file mode 100644
index 0000000000..67682173f4
--- /dev/null
+++ b/dom/base/nsJSUtils.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJSUtils_h__
+#define nsJSUtils_h__
+
+/**
+ * This is not a generated file. It contains common utility functions
+ * invoked from the JavaScript code generated from IDL interfaces.
+ * The goal of the utility functions is to cut down on the size of
+ * the generated code itself.
+ */
+
+#include "mozilla/Assertions.h"
+
+#include "jsapi.h"
+#include "js/CompileOptions.h"
+#include "js/Conversions.h"
+#include "js/SourceText.h"
+#include "js/String.h" // JS::{,Lossy}CopyLinearStringChars, JS::CopyStringChars, JS::Get{,Linear}StringLength, JS::MaxStringLength, JS::StringHasLatin1Chars
+#include "nsString.h"
+#include "xpcpublic.h"
+
+class nsIScriptContext;
+class nsIScriptElement;
+class nsIScriptGlobalObject;
+class nsXBLPrototypeBinding;
+
+namespace mozilla {
+union Utf8Unit;
+
+namespace dom {
+class AutoJSAPI;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsJSUtils {
+ public:
+ static bool GetCallingLocation(JSContext* aContext, nsACString& aFilename,
+ uint32_t* aLineno = nullptr,
+ uint32_t* aColumn = nullptr);
+ static bool GetCallingLocation(JSContext* aContext, nsAString& aFilename,
+ uint32_t* aLineno = nullptr,
+ uint32_t* aColumn = nullptr);
+
+ /**
+ * Retrieve the inner window ID based on the given JSContext.
+ *
+ * @param JSContext aContext
+ * The JSContext from which you want to find the inner window ID.
+ *
+ * @returns uint64_t the inner window ID.
+ */
+ static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext);
+
+ static nsresult CompileFunction(mozilla::dom::AutoJSAPI& jsapi,
+ JS::HandleVector<JSObject*> aScopeChain,
+ JS::CompileOptions& aOptions,
+ const nsACString& aName, uint32_t aArgCount,
+ const char** aArgArray,
+ const nsAString& aBody,
+ JSObject** aFunctionObject);
+
+ static nsresult UpdateFunctionDebugMetadata(
+ mozilla::dom::AutoJSAPI& jsapi, JS::Handle<JSObject*> aFun,
+ JS::CompileOptions& aOptions, JS::Handle<JSString*> aElementAttributeName,
+ JS::Handle<JS::Value> aPrivateValue);
+
+ static bool IsScriptable(JS::Handle<JSObject*> aEvaluationGlobal);
+
+ // Returns false if an exception got thrown on aCx. Passing a null
+ // aElement is allowed; that wil produce an empty aScopeChain.
+ static bool GetScopeChainForElement(
+ JSContext* aCx, mozilla::dom::Element* aElement,
+ JS::MutableHandleVector<JSObject*> aScopeChain);
+
+ static void ResetTimeZone();
+
+ static bool DumpEnabled();
+
+ // A helper function that receives buffer pointer, creates ArrayBuffer, and
+ // convert it to Uint8Array.
+ // Note that the buffer needs to be created by JS_malloc (or at least can be
+ // freed by JS_free), as the resulting Uint8Array takes the ownership of the
+ // buffer.
+ static JSObject* MoveBufferAsUint8Array(JSContext* aCx, size_t aSize,
+ mozilla::UniquePtr<uint8_t>& aBuffer);
+};
+
+inline void AssignFromStringBuffer(nsStringBuffer* buffer, size_t len,
+ nsAString& dest) {
+ buffer->ToString(len, dest);
+}
+
+template <typename T, typename std::enable_if_t<std::is_same<
+ typename T::char_type, char16_t>::value>* = nullptr>
+inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
+ size_t len = JS::GetStringLength(s);
+ static_assert(JS::MaxStringLength < (1 << 30),
+ "Shouldn't overflow here or in SetCapacity");
+
+ const char16_t* chars;
+ if (XPCStringConvert::MaybeGetDOMStringChars(s, &chars)) {
+ // The characters represent an existing string buffer that we shared with
+ // JS. We can share that buffer ourselves if the string corresponds to the
+ // whole buffer; otherwise we have to copy.
+ if (chars[len] == '\0') {
+ AssignFromStringBuffer(
+ nsStringBuffer::FromData(const_cast<char16_t*>(chars)), len, dest);
+ return true;
+ }
+ } else if (XPCStringConvert::MaybeGetLiteralStringChars(s, &chars)) {
+ // The characters represent a literal char16_t string constant
+ // compiled into libxul; we can just use it as-is.
+ dest.AssignLiteral(chars, len);
+ return true;
+ }
+
+ // We don't bother checking for a dynamic-atom external string, because we'd
+ // just need to copy out of it anyway.
+
+ if (MOZ_UNLIKELY(!dest.SetLength(len, mozilla::fallible))) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ return JS::CopyStringChars(cx, dest.BeginWriting(), s, len);
+}
+
+// Specialization for UTF8String.
+template <typename T, typename std::enable_if_t<std::is_same<
+ typename T::char_type, char>::value>* = nullptr>
+inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
+ using namespace mozilla;
+ CheckedInt<size_t> bufLen(JS::GetStringLength(s));
+ // From the contract for JS_EncodeStringToUTF8BufferPartial, to guarantee that
+ // the whole string is converted.
+ if (JS::StringHasLatin1Chars(s)) {
+ bufLen *= 2;
+ } else {
+ bufLen *= 3;
+ }
+
+ if (MOZ_UNLIKELY(!bufLen.isValid())) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Shouldn't really matter, but worth being safe.
+ const bool kAllowShrinking = true;
+
+ auto handleOrErr = dest.BulkWrite(bufLen.value(), 0, kAllowShrinking);
+ if (MOZ_UNLIKELY(handleOrErr.isErr())) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ auto handle = handleOrErr.unwrap();
+
+ auto maybe = JS_EncodeStringToUTF8BufferPartial(cx, s, handle.AsSpan());
+ if (MOZ_UNLIKELY(!maybe)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ size_t read;
+ size_t written;
+ std::tie(read, written) = *maybe;
+
+ MOZ_ASSERT(read == JS::GetStringLength(s));
+ handle.Finish(written, kAllowShrinking);
+ return true;
+}
+
+inline void AssignJSLinearString(nsAString& dest, JSLinearString* s) {
+ size_t len = JS::GetLinearStringLength(s);
+ static_assert(JS::MaxStringLength < (1 << 30),
+ "Shouldn't overflow here or in SetCapacity");
+ dest.SetLength(len);
+ JS::CopyLinearStringChars(dest.BeginWriting(), s, len);
+}
+
+inline void AssignJSLinearString(nsACString& dest, JSLinearString* s) {
+ size_t len = JS::GetLinearStringLength(s);
+ static_assert(JS::MaxStringLength < (1 << 30),
+ "Shouldn't overflow here or in SetCapacity");
+ dest.SetLength(len);
+ JS::LossyCopyLinearStringChars(dest.BeginWriting(), s, len);
+}
+
+template <typename T>
+class nsTAutoJSLinearString : public nsTAutoString<T> {
+ public:
+ explicit nsTAutoJSLinearString(JSLinearString* str) {
+ AssignJSLinearString(*this, str);
+ }
+};
+
+using nsAutoJSLinearString = nsTAutoJSLinearString<char16_t>;
+using nsAutoJSLinearCString = nsTAutoJSLinearString<char>;
+
+template <typename T>
+class nsTAutoJSString : public nsTAutoString<T> {
+ public:
+ /**
+ * nsTAutoJSString should be default constructed, which leaves it empty
+ * (this->IsEmpty()), and initialized with one of the init() methods below.
+ */
+ nsTAutoJSString() = default;
+
+ bool init(JSContext* aContext, JSString* str) {
+ return AssignJSString(aContext, *this, str);
+ }
+
+ bool init(JSContext* aContext, const JS::Value& v) {
+ if (v.isString()) {
+ return init(aContext, v.toString());
+ }
+
+ // Stringify, making sure not to run script.
+ JS::Rooted<JSString*> str(aContext);
+ if (v.isObject()) {
+ str = JS_NewStringCopyZ(aContext, "[Object]");
+ } else {
+ JS::Rooted<JS::Value> rootedVal(aContext, v);
+ str = JS::ToString(aContext, rootedVal);
+ }
+
+ return str && init(aContext, str);
+ }
+
+ bool init(JSContext* aContext, jsid id) {
+ JS::Rooted<JS::Value> v(aContext);
+ return JS_IdToValue(aContext, id, &v) && init(aContext, v);
+ }
+
+ bool init(const JS::Value& v);
+
+ ~nsTAutoJSString() = default;
+};
+
+using nsAutoJSString = nsTAutoJSString<char16_t>;
+
+// Note that this is guaranteed to be UTF-8.
+using nsAutoJSCString = nsTAutoJSString<char>;
+
+#endif /* nsJSUtils_h__ */
diff --git a/dom/base/nsLineBreaker.cpp b/dom/base/nsLineBreaker.cpp
new file mode 100644
index 0000000000..a20e2d9921
--- /dev/null
+++ b/dom/base/nsLineBreaker.cpp
@@ -0,0 +1,553 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsLineBreaker.h"
+#include "nsContentUtils.h"
+#include "gfxTextRun.h" // for the gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_* values
+#include "nsHyphenationManager.h"
+#include "nsHyphenator.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/LineBreaker.h" // for LineBreaker::ComputeBreakPositions
+#include "mozilla/intl/Locale.h"
+#include "mozilla/intl/UnicodeProperties.h"
+
+using mozilla::AutoRestore;
+using mozilla::intl::LineBreaker;
+using mozilla::intl::LineBreakRule;
+using mozilla::intl::Locale;
+using mozilla::intl::LocaleParser;
+using mozilla::intl::UnicodeProperties;
+using mozilla::intl::WordBreakRule;
+
+nsLineBreaker::nsLineBreaker()
+ : mCurrentWordLanguage(nullptr),
+ mCurrentWordContainsMixedLang(false),
+ mCurrentWordContainsComplexChar(false),
+ mScriptIsChineseOrJapanese(false),
+ mAfterBreakableSpace(false),
+ mBreakHere(false),
+ mWordBreak(WordBreakRule::Normal),
+ mLineBreak(LineBreakRule::Auto),
+ mWordContinuation(false) {}
+
+nsLineBreaker::~nsLineBreaker() {
+ NS_ASSERTION(mCurrentWord.Length() == 0,
+ "Should have Reset() before destruction!");
+}
+
+static void SetupCapitalization(const char16_t* aWord, uint32_t aLength,
+ bool* aCapitalization) {
+ // Capitalize the first alphanumeric character after a space or punctuation.
+ using mozilla::intl::GeneralCategory;
+ bool capitalizeNextChar = true;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ uint32_t ch = aWord[i];
+ if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aWord[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aWord[i + 1]);
+ }
+ auto category = UnicodeProperties::CharType(ch);
+ switch (category) {
+ case GeneralCategory::Uppercase_Letter:
+ case GeneralCategory::Lowercase_Letter:
+ case GeneralCategory::Titlecase_Letter:
+ case GeneralCategory::Modifier_Letter:
+ case GeneralCategory::Other_Letter:
+ case GeneralCategory::Decimal_Number:
+ case GeneralCategory::Letter_Number:
+ case GeneralCategory::Other_Number:
+ if (capitalizeNextChar) {
+ aCapitalization[i] = true;
+ capitalizeNextChar = false;
+ }
+ break;
+ case GeneralCategory::Space_Separator:
+ case GeneralCategory::Line_Separator:
+ case GeneralCategory::Paragraph_Separator:
+ case GeneralCategory::Dash_Punctuation:
+ case GeneralCategory::Initial_Punctuation:
+ /* These punctuation categories are excluded, for examples like
+ * "what colo[u]r" -> "What Colo[u]r?" (rather than "What Colo[U]R?")
+ * and
+ * "snake_case" -> "Snake_case" (to match word selection behavior)
+ case GeneralCategory::Open_Punctuation:
+ case GeneralCategory::Close_Punctuation:
+ case GeneralCategory::Connector_Punctuation:
+ */
+ capitalizeNextChar = true;
+ break;
+ case GeneralCategory::Final_Punctuation:
+ /* Special-case: exclude Unicode single-close-quote/apostrophe,
+ for examples like "Lowe’s" etc. */
+ if (ch != 0x2019) {
+ capitalizeNextChar = true;
+ }
+ break;
+ case GeneralCategory::Other_Punctuation:
+ /* Special-case: exclude ASCII apostrophe, for "Lowe's" etc. */
+ if (ch != '\'') {
+ capitalizeNextChar = true;
+ }
+ break;
+ default:
+ break;
+ }
+ if (!IS_IN_BMP(ch)) {
+ ++i;
+ }
+ }
+}
+
+nsresult nsLineBreaker::FlushCurrentWord() {
+ uint32_t length = mCurrentWord.Length();
+ AutoTArray<uint8_t, 4000> breakState;
+ if (!breakState.AppendElements(length, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mLineBreak == LineBreakRule::Anywhere) {
+ memset(breakState.Elements(),
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
+ length * sizeof(uint8_t));
+ } else if (!mCurrentWordContainsComplexChar) {
+ // For break-strict set everything internal to "break", otherwise
+ // to "no break"!
+ memset(breakState.Elements(),
+ mWordBreak == WordBreakRule::BreakAll
+ ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
+ : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
+ length * sizeof(uint8_t));
+ } else {
+ LineBreaker::ComputeBreakPositions(
+ mCurrentWord.Elements(), length, mWordBreak, mLineBreak,
+ mScriptIsChineseOrJapanese, breakState.Elements());
+ }
+
+ bool autoHyphenate = mCurrentWordLanguage && !mCurrentWordContainsMixedLang;
+ uint32_t i;
+ for (i = 0; autoHyphenate && i < mTextItems.Length(); ++i) {
+ TextItem* ti = &mTextItems[i];
+ if (!(ti->mFlags & BREAK_USE_AUTO_HYPHENATION)) {
+ autoHyphenate = false;
+ }
+ }
+ if (autoHyphenate) {
+ RefPtr<nsHyphenator> hyphenator =
+ nsHyphenationManager::Instance()->GetHyphenator(mCurrentWordLanguage);
+ if (hyphenator) {
+ FindHyphenationPoints(hyphenator, mCurrentWord.Elements(),
+ mCurrentWord.Elements() + length,
+ breakState.Elements());
+ }
+ }
+
+ nsTArray<bool> capitalizationState;
+ uint32_t offset = 0;
+ for (i = 0; i < mTextItems.Length(); ++i) {
+ TextItem* ti = &mTextItems[i];
+ NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?");
+
+ if ((ti->mFlags & BREAK_SUPPRESS_INITIAL) && ti->mSinkOffset == 0) {
+ breakState[offset] = gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
+ }
+ if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
+ uint32_t exclude = ti->mSinkOffset == 0 ? 1 : 0;
+ memset(breakState.Elements() + offset + exclude,
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
+ (ti->mLength - exclude) * sizeof(uint8_t));
+ }
+
+ // Don't set the break state for the first character of the word, because
+ // it was already set correctly earlier and we don't know what the true
+ // value should be.
+ uint32_t skipSet = i == 0 ? 1 : 0;
+ if (ti->mSink) {
+ ti->mSink->SetBreaks(ti->mSinkOffset + skipSet, ti->mLength - skipSet,
+ breakState.Elements() + offset + skipSet);
+
+ if (!mWordContinuation && (ti->mFlags & BREAK_NEED_CAPITALIZATION)) {
+ if (capitalizationState.Length() == 0) {
+ if (!capitalizationState.AppendElements(length, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ };
+ memset(capitalizationState.Elements(), false, length * sizeof(bool));
+ SetupCapitalization(mCurrentWord.Elements(), length,
+ capitalizationState.Elements());
+ }
+ ti->mSink->SetCapitalization(ti->mSinkOffset, ti->mLength,
+ capitalizationState.Elements() + offset);
+ }
+ }
+
+ offset += ti->mLength;
+ }
+
+ mCurrentWord.Clear();
+ mTextItems.Clear();
+ mCurrentWordContainsComplexChar = false;
+ mCurrentWordContainsMixedLang = false;
+ mCurrentWordLanguage = nullptr;
+ mWordContinuation = false;
+ return NS_OK;
+}
+
+// If the aFlags parameter to AppendText has all these bits set,
+// then we don't need to worry about finding break opportunities
+// in the appended text.
+#define NO_BREAKS_NEEDED_FLAGS \
+ (BREAK_SUPPRESS_INITIAL | BREAK_SUPPRESS_INSIDE | \
+ BREAK_SKIP_SETTING_NO_BREAKS)
+
+nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
+ const char16_t* aText, uint32_t aLength,
+ uint32_t aFlags, nsILineBreakSink* aSink) {
+ NS_ASSERTION(aLength > 0, "Appending empty text...");
+
+ uint32_t offset = 0;
+
+ // Continue the current word
+ if (mCurrentWord.Length() > 0) {
+ NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
+ "These should not be set");
+
+ while (offset < aLength && !IsSpace(aText[offset])) {
+ mCurrentWord.AppendElement(aText[offset]);
+ if (!mCurrentWordContainsComplexChar && IsComplexChar(aText[offset])) {
+ mCurrentWordContainsComplexChar = true;
+ }
+ UpdateCurrentWordLanguage(aHyphenationLanguage);
+ ++offset;
+ }
+
+ if (offset > 0) {
+ mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
+ }
+
+ if (offset == aLength) return NS_OK;
+
+ // We encountered whitespace, so we're done with this word
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ AutoTArray<uint8_t, 4000> breakState;
+ if (aSink) {
+ if (!breakState.AppendElements(aLength, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ bool noCapitalizationNeeded = true;
+ nsTArray<bool> capitalizationState;
+ if (aSink && (aFlags & BREAK_NEED_CAPITALIZATION)) {
+ if (!capitalizationState.AppendElements(aLength, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ memset(capitalizationState.Elements(), false, aLength * sizeof(bool));
+ noCapitalizationNeeded = false;
+ }
+
+ uint32_t start = offset;
+ bool noBreaksNeeded =
+ !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
+ !mBreakHere && !mAfterBreakableSpace);
+ if (noBreaksNeeded && noCapitalizationNeeded) {
+ // Skip to the space before the last word, since either the break data
+ // here is not needed, or no breaks are set in the sink and there cannot
+ // be any breaks in this chunk; and we don't need to do word-initial
+ // capitalization. All we need is the context for the next chunk (if any).
+ offset = aLength;
+ while (offset > start) {
+ --offset;
+ if (IsSpace(aText[offset])) break;
+ }
+ }
+ uint32_t wordStart = offset;
+ bool wordHasComplexChar = false;
+
+ RefPtr<nsHyphenator> hyphenator;
+ if ((aFlags & BREAK_USE_AUTO_HYPHENATION) &&
+ !(aFlags & BREAK_SUPPRESS_INSIDE) && aHyphenationLanguage) {
+ hyphenator =
+ nsHyphenationManager::Instance()->GetHyphenator(aHyphenationLanguage);
+ }
+
+ for (;;) {
+ char16_t ch = aText[offset];
+ bool isSpace = IsSpace(ch);
+ bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
+
+ if (aSink && !noBreaksNeeded) {
+ breakState[offset] =
+ mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
+ mWordBreak == WordBreakRule::BreakAll ||
+ mLineBreak == LineBreakRule::Anywhere
+ ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
+ : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
+ }
+ mBreakHere = false;
+ mAfterBreakableSpace = isBreakableSpace;
+
+ if (isSpace || ch == '\n') {
+ if (offset > wordStart && aSink) {
+ if (!(aFlags & BREAK_SUPPRESS_INSIDE)) {
+ if (mLineBreak == LineBreakRule::Anywhere) {
+ memset(breakState.Elements() + wordStart,
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
+ offset - wordStart);
+ } else if (wordHasComplexChar) {
+ // Save current start-of-word state because ComputeBreakPositions()
+ // will set it to false.
+ AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
+ LineBreaker::ComputeBreakPositions(
+ aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
+ mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
+ }
+ if (hyphenator) {
+ FindHyphenationPoints(hyphenator, aText + wordStart, aText + offset,
+ breakState.Elements() + wordStart);
+ }
+ }
+ if (!mWordContinuation && !noCapitalizationNeeded) {
+ SetupCapitalization(aText + wordStart, offset - wordStart,
+ capitalizationState.Elements() + wordStart);
+ }
+ }
+ wordHasComplexChar = false;
+ mWordContinuation = false;
+ ++offset;
+ if (offset >= aLength) break;
+ wordStart = offset;
+ } else {
+ if (!wordHasComplexChar && IsComplexChar(ch)) {
+ wordHasComplexChar = true;
+ }
+ ++offset;
+ if (offset >= aLength) {
+ // Save this word
+ mCurrentWordContainsComplexChar = wordHasComplexChar;
+ uint32_t len = offset - wordStart;
+ char16_t* elems = mCurrentWord.AppendElements(len);
+ if (!elems) return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(elems, aText + wordStart, sizeof(char16_t) * len);
+ mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
+ // Ensure that the break-before for this word is written out
+ offset = wordStart + 1;
+ UpdateCurrentWordLanguage(aHyphenationLanguage);
+ break;
+ }
+ }
+ }
+
+ if (aSink) {
+ if (!noBreaksNeeded) {
+ aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
+ }
+ if (!noCapitalizationNeeded) {
+ aSink->SetCapitalization(start, offset - start,
+ capitalizationState.Elements() + start);
+ }
+ }
+ return NS_OK;
+}
+
+void nsLineBreaker::FindHyphenationPoints(nsHyphenator* aHyphenator,
+ const char16_t* aTextStart,
+ const char16_t* aTextLimit,
+ uint8_t* aBreakState) {
+ nsDependentSubstring string(aTextStart, aTextLimit);
+ AutoTArray<bool, 200> hyphens;
+ if (NS_SUCCEEDED(aHyphenator->Hyphenate(string, hyphens))) {
+ for (uint32_t i = 0; i + 1 < string.Length(); ++i) {
+ if (hyphens[i]) {
+ aBreakState[i + 1] =
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
+ }
+ }
+ }
+}
+
+nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
+ const uint8_t* aText, uint32_t aLength,
+ uint32_t aFlags, nsILineBreakSink* aSink) {
+ NS_ASSERTION(aLength > 0, "Appending empty text...");
+
+ if (aFlags & (BREAK_NEED_CAPITALIZATION | BREAK_USE_AUTO_HYPHENATION)) {
+ // Defer to the Unicode path if capitalization or hyphenation is required
+ nsAutoString str;
+ const char* cp = reinterpret_cast<const char*>(aText);
+ CopyASCIItoUTF16(nsDependentCSubstring(cp, cp + aLength), str);
+ return AppendText(aHyphenationLanguage, str.get(), aLength, aFlags, aSink);
+ }
+
+ uint32_t offset = 0;
+
+ // Continue the current word
+ if (mCurrentWord.Length() > 0) {
+ NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
+ "These should not be set");
+
+ while (offset < aLength && !IsSpace(aText[offset])) {
+ mCurrentWord.AppendElement(aText[offset]);
+ if (!mCurrentWordContainsComplexChar &&
+ IsComplexASCIIChar(aText[offset])) {
+ mCurrentWordContainsComplexChar = true;
+ }
+ ++offset;
+ }
+
+ if (offset > 0) {
+ mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
+ }
+
+ if (offset == aLength) {
+ // We did not encounter whitespace so the word hasn't finished yet.
+ return NS_OK;
+ }
+
+ // We encountered whitespace, so we're done with this word
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ AutoTArray<uint8_t, 4000> breakState;
+ if (aSink) {
+ if (!breakState.AppendElements(aLength, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t start = offset;
+ bool noBreaksNeeded =
+ !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
+ !mBreakHere && !mAfterBreakableSpace);
+ if (noBreaksNeeded) {
+ // Skip to the space before the last word, since either the break data
+ // here is not needed, or no breaks are set in the sink and there cannot
+ // be any breaks in this chunk; all we need is the context for the next
+ // chunk (if any)
+ offset = aLength;
+ while (offset > start) {
+ --offset;
+ if (IsSpace(aText[offset])) break;
+ }
+ }
+ uint32_t wordStart = offset;
+ bool wordHasComplexChar = false;
+
+ for (;;) {
+ uint8_t ch = aText[offset];
+ bool isSpace = IsSpace(ch);
+ bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
+
+ if (aSink) {
+ // Consider word-break style. Since the break position of CJK scripts
+ // will be set by nsILineBreaker, we don't consider CJK at this point.
+ breakState[offset] =
+ mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
+ mWordBreak == WordBreakRule::BreakAll ||
+ mLineBreak == LineBreakRule::Anywhere
+ ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
+ : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
+ }
+ mBreakHere = false;
+ mAfterBreakableSpace = isBreakableSpace;
+
+ if (isSpace) {
+ if (offset > wordStart && aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
+ if (mLineBreak == LineBreakRule::Anywhere) {
+ memset(breakState.Elements() + wordStart,
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
+ offset - wordStart);
+ } else if (wordHasComplexChar) {
+ // Save current start-of-word state because ComputeBreakPositions()
+ // will set it to false.
+ AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
+ LineBreaker::ComputeBreakPositions(
+ aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
+ mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
+ }
+ }
+
+ wordHasComplexChar = false;
+ ++offset;
+ if (offset >= aLength) break;
+ wordStart = offset;
+ } else {
+ if (!wordHasComplexChar && IsComplexASCIIChar(ch)) {
+ wordHasComplexChar = true;
+ }
+ ++offset;
+ if (offset >= aLength) {
+ // Save this word
+ mCurrentWordContainsComplexChar = wordHasComplexChar;
+ uint32_t len = offset - wordStart;
+ char16_t* elems = mCurrentWord.AppendElements(len);
+ if (!elems) return NS_ERROR_OUT_OF_MEMORY;
+ uint32_t i;
+ for (i = wordStart; i < offset; ++i) {
+ elems[i - wordStart] = aText[i];
+ }
+ mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
+ // Ensure that the break-before for this word is written out
+ offset = wordStart + 1;
+ break;
+ }
+ }
+ }
+
+ if (!noBreaksNeeded) {
+ aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
+ }
+ return NS_OK;
+}
+
+void nsLineBreaker::UpdateCurrentWordLanguage(nsAtom* aHyphenationLanguage) {
+ if (mCurrentWordLanguage && mCurrentWordLanguage != aHyphenationLanguage) {
+ mCurrentWordContainsMixedLang = true;
+ mScriptIsChineseOrJapanese = false;
+ } else {
+ if (aHyphenationLanguage && !mCurrentWordLanguage) {
+ Locale loc;
+ auto result =
+ LocaleParser::TryParse(nsAtomCString(aHyphenationLanguage), loc);
+
+ if (result.isErr()) {
+ return;
+ }
+ if (loc.Script().Missing() && loc.AddLikelySubtags().isErr()) {
+ return;
+ }
+ mScriptIsChineseOrJapanese =
+ loc.Script().EqualTo("Hans") || loc.Script().EqualTo("Hant") ||
+ loc.Script().EqualTo("Jpan") || loc.Script().EqualTo("Hrkt");
+ }
+ mCurrentWordLanguage = aHyphenationLanguage;
+ }
+}
+
+nsresult nsLineBreaker::AppendInvisibleWhitespace(uint32_t aFlags) {
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) return rv;
+
+ bool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
+ if (mAfterBreakableSpace && !isBreakableSpace) {
+ mBreakHere = true;
+ }
+ mAfterBreakableSpace = isBreakableSpace;
+ mWordContinuation = false;
+ return NS_OK;
+}
+
+nsresult nsLineBreaker::Reset(bool* aTrailingBreak) {
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) return rv;
+
+ *aTrailingBreak = mBreakHere || mAfterBreakableSpace;
+ mBreakHere = false;
+ mAfterBreakableSpace = false;
+ return NS_OK;
+}
diff --git a/dom/base/nsLineBreaker.h b/dom/base/nsLineBreaker.h
new file mode 100644
index 0000000000..a437bb7846
--- /dev/null
+++ b/dom/base/nsLineBreaker.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSLINEBREAKER_H_
+#define NSLINEBREAKER_H_
+
+#include "mozilla/intl/LineBreaker.h"
+#include "mozilla/intl/Segmenter.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsAtom;
+class nsHyphenator;
+
+/**
+ * A receiver of line break data.
+ */
+class nsILineBreakSink {
+ public:
+ /**
+ * Sets the break data for a substring of the associated text chunk.
+ * One or more of these calls will be performed; the union of all substrings
+ * will cover the entire text chunk. Substrings may overlap (i.e., we may
+ * set the break-before state of a character more than once).
+ * @param aBreakBefore the break-before states for the characters in the
+ * substring. These are enum values from gfxTextRun::CompressedGlyph:
+ * FLAG_BREAK_TYPE_NONE - no linebreak is allowed here
+ * FLAG_BREAK_TYPE_NORMAL - a normal (whitespace) linebreak
+ * FLAG_BREAK_TYPE_HYPHEN - a hyphenation point
+ */
+ virtual void SetBreaks(uint32_t aStart, uint32_t aLength,
+ uint8_t* aBreakBefore) = 0;
+
+ /**
+ * Indicates which characters should be capitalized. Only called if
+ * BREAK_NEED_CAPITALIZATION was requested.
+ */
+ virtual void SetCapitalization(uint32_t aStart, uint32_t aLength,
+ bool* aCapitalize) = 0;
+};
+
+/**
+ * A line-breaking state machine. You feed text into it via AppendText calls
+ * and it computes the possible line breaks. Because break decisions can
+ * require a lot of context, the breaks for a piece of text are sometimes not
+ * known until later text has been seen (or all text ends). So breaks are
+ * returned via a call to SetBreaks on the nsILineBreakSink object passed
+ * with each text chunk, which might happen during the corresponding AppendText
+ * call, or might happen during a later AppendText call or even a Reset()
+ * call.
+ *
+ * The linebreak results MUST NOT depend on how the text is broken up
+ * into AppendText calls.
+ *
+ * The current strategy is that we break the overall text into
+ * whitespace-delimited "words". Then those words are passed to the LineBreaker
+ * service for deeper analysis if they contain a "complex" character as
+ * described below.
+ *
+ * This class also handles detection of which characters should be capitalized
+ * for text-transform:capitalize. This is a good place to handle that because
+ * we have all the context we need.
+ */
+class nsLineBreaker {
+ public:
+ nsLineBreaker();
+ ~nsLineBreaker();
+
+ static inline bool IsSpace(char16_t u) {
+ return mozilla::intl::NS_IsSpace(u);
+ }
+
+ static inline bool IsComplexASCIIChar(char16_t u) {
+ return !((0x0030 <= u && u <= 0x0039) || (0x0041 <= u && u <= 0x005A) ||
+ (0x0061 <= u && u <= 0x007A) || (0x000a == u));
+ }
+
+ static inline bool IsComplexChar(char16_t u) {
+ return IsComplexASCIIChar(u) ||
+ mozilla::intl::NS_NeedsPlatformNativeHandling(u) ||
+ (0x1100 <= u && u <= 0x11ff) || // Hangul Jamo
+ (0x2000 <= u && u <= 0x21ff) || // Punctuations and Symbols
+ (0x2e80 <= u && u <= 0xd7ff) || // several CJK blocks
+ (0xf900 <= u && u <= 0xfaff) || // CJK Compatibility Idographs
+ (0xff00 <= u && u <= 0xffef); // Halfwidth and Fullwidth Forms
+ }
+
+ // Break opportunities exist at the end of each run of breakable whitespace
+ // (see IsSpace above). Break opportunities can also exist between pairs of
+ // non-whitespace characters, as determined by mozilla::intl::LineBreaker.
+ // We pass a whitespace-
+ // delimited word to LineBreaker if it contains at least one character
+ // matching IsComplexChar.
+ // We provide flags to control on a per-chunk basis where breaks are allowed.
+ // At any character boundary, exactly one text chunk governs whether a
+ // break is allowed at that boundary.
+ //
+ // We operate on text after whitespace processing has been applied, so
+ // other characters (e.g. tabs and newlines) may have been converted to
+ // spaces.
+
+ /**
+ * Flags passed with each chunk of text.
+ */
+ enum {
+ /*
+ * Do not introduce a break opportunity at the start of this chunk of text.
+ */
+ BREAK_SUPPRESS_INITIAL = 0x01,
+ /**
+ * Do not introduce a break opportunity in the interior of this chunk of
+ * text. Also, whitespace in this chunk is treated as non-breakable.
+ */
+ BREAK_SUPPRESS_INSIDE = 0x02,
+ /**
+ * The sink currently is already set up to have no breaks in it;
+ * if no breaks are possible, nsLineBreaker does not need to call
+ * SetBreaks on it. This is useful when handling large quantities of
+ * preformatted text; the textruns will never have any breaks set on them,
+ * and there is no need to ever actually scan the text for breaks, except
+ * at the end of textruns in case context is needed for following breakable
+ * text.
+ */
+ BREAK_SKIP_SETTING_NO_BREAKS = 0x04,
+ /**
+ * We need to be notified of characters that should be capitalized
+ * (as in text-transform:capitalize) in this chunk of text.
+ */
+ BREAK_NEED_CAPITALIZATION = 0x08,
+ /**
+ * Auto-hyphenation is enabled, so we need to get a hyphenator
+ * (if available) and use it to find breakpoints.
+ */
+ BREAK_USE_AUTO_HYPHENATION = 0x10
+ };
+
+ /**
+ * Append "invisible whitespace". This acts like whitespace, but there is
+ * no actual text associated with it. Only the BREAK_SUPPRESS_INSIDE flag
+ * is relevant here.
+ */
+ nsresult AppendInvisibleWhitespace(uint32_t aFlags);
+
+ /**
+ * Feed Unicode text into the linebreaker for analysis. aLength must be
+ * nonzero.
+ * @param aSink can be null if the breaks are not actually needed (we may
+ * still be setting up state for later breaks)
+ */
+ nsresult AppendText(nsAtom* aHyphenationLanguage, const char16_t* aText,
+ uint32_t aLength, uint32_t aFlags,
+ nsILineBreakSink* aSink);
+ /**
+ * Feed 8-bit text into the linebreaker for analysis. aLength must be nonzero.
+ * @param aSink can be null if the breaks are not actually needed (we may
+ * still be setting up state for later breaks)
+ */
+ nsresult AppendText(nsAtom* aHyphenationLanguage, const uint8_t* aText,
+ uint32_t aLength, uint32_t aFlags,
+ nsILineBreakSink* aSink);
+ /**
+ * Reset all state. This means the current run has ended; any outstanding
+ * calls through nsILineBreakSink are made, and all outstanding references to
+ * nsILineBreakSink objects are dropped.
+ * After this call, this linebreaker can be reused.
+ * This must be called at least once between any call to AppendText() and
+ * destroying the object.
+ * @param aTrailingBreak this is set to true when there is a break opportunity
+ * at the end of the text. This will normally only be declared true when there
+ * is breakable whitespace at the end.
+ */
+ nsresult Reset(bool* aTrailingBreak);
+
+ /*
+ * Set word-break mode for line breaker. This is set by word-break property.
+ */
+ void SetWordBreak(mozilla::intl::WordBreakRule aMode) {
+ // If current word is non-empty and mode is changing, flush the breaker.
+ if (aMode != mWordBreak && !mCurrentWord.IsEmpty()) {
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("FlushCurrentWord failed, line-breaks may be wrong");
+ }
+ // If previous mode was break-all, we should allow a break here.
+ // XXX (jfkthame) css-text spec seems unclear on this, raised question in
+ // https://github.com/w3c/csswg-drafts/issues/3897
+ if (mWordBreak == mozilla::intl::WordBreakRule::BreakAll) {
+ mBreakHere = true;
+ }
+ }
+ mWordBreak = aMode;
+ }
+
+ /*
+ * Set line-break rule strictness mode for line breaker. This is set by the
+ * line-break property.
+ */
+ void SetStrictness(mozilla::intl::LineBreakRule aMode) {
+ if (aMode != mLineBreak && !mCurrentWord.IsEmpty()) {
+ nsresult rv = FlushCurrentWord();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("FlushCurrentWord failed, line-breaks may be wrong");
+ }
+ // If previous mode was anywhere, we should allow a break here.
+ if (mLineBreak == mozilla::intl::LineBreakRule::Anywhere) {
+ mBreakHere = true;
+ }
+ }
+ mLineBreak = aMode;
+ }
+
+ /**
+ * Return whether the line-breaker has a buffered "current word" that may
+ * be extended with additional word-forming characters.
+ */
+ bool InWord() const { return !mCurrentWord.IsEmpty(); }
+
+ /**
+ * Set the word-continuation state, which will suppress capitalization of
+ * the next letter that might otherwise apply.
+ */
+ void SetWordContinuation(bool aContinuation) {
+ mWordContinuation = aContinuation;
+ }
+
+ private:
+ // This is a list of text sources that make up the "current word" (i.e.,
+ // run of text which does not contain any whitespace). All the mLengths
+ // are are nonzero, these cannot overlap.
+ struct TextItem {
+ TextItem(nsILineBreakSink* aSink, uint32_t aSinkOffset, uint32_t aLength,
+ uint32_t aFlags)
+ : mSink(aSink),
+ mSinkOffset(aSinkOffset),
+ mLength(aLength),
+ mFlags(aFlags) {}
+
+ nsILineBreakSink* mSink;
+ uint32_t mSinkOffset;
+ uint32_t mLength;
+ uint32_t mFlags;
+ };
+
+ // State for the nonwhitespace "word" that started in previous text and hasn't
+ // finished yet.
+
+ // When the current word ends, this computes the linebreak opportunities
+ // *inside* the word (excluding either end) and sets them through the
+ // appropriate sink(s). Then we clear the current word state.
+ nsresult FlushCurrentWord();
+
+ void UpdateCurrentWordLanguage(nsAtom* aHyphenationLanguage);
+
+ void FindHyphenationPoints(nsHyphenator* aHyphenator,
+ const char16_t* aTextStart,
+ const char16_t* aTextLimit, uint8_t* aBreakState);
+
+ AutoTArray<char16_t, 100> mCurrentWord;
+ // All the items that contribute to mCurrentWord
+ AutoTArray<TextItem, 2> mTextItems;
+ nsAtom* mCurrentWordLanguage;
+ bool mCurrentWordContainsMixedLang;
+ bool mCurrentWordContainsComplexChar;
+ bool mScriptIsChineseOrJapanese;
+
+ // True if the previous character was breakable whitespace
+ bool mAfterBreakableSpace;
+ // True if a break must be allowed at the current position because
+ // a run of breakable whitespace ends here
+ bool mBreakHere;
+ // Break rules for letters from the "word-break" property.
+ mozilla::intl::WordBreakRule mWordBreak;
+ // Line breaking strictness from the "line-break" property.
+ mozilla::intl::LineBreakRule mLineBreak;
+ // Should the text be treated as continuing a word-in-progress (for purposes
+ // of initial capitalization)? Normally this is set to false whenever we
+ // start using a linebreaker, but it may be set to true if the line-breaker
+ // has been explicitly flushed mid-word.
+ bool mWordContinuation;
+};
+
+#endif /*NSLINEBREAKER_H_*/
diff --git a/dom/base/nsMappedAttributeElement.cpp b/dom/base/nsMappedAttributeElement.cpp
new file mode 100644
index 0000000000..d29890cad8
--- /dev/null
+++ b/dom/base/nsMappedAttributeElement.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMappedAttributeElement.h"
+#include "mozilla/dom/Document.h"
+
+bool nsMappedAttributeElement::SetAndSwapMappedAttribute(nsAtom* aName,
+ nsAttrValue& aValue,
+ bool* aValueWasSet,
+ nsresult* aRetval) {
+ nsHTMLStyleSheet* sheet = OwnerDoc()->GetAttributeStyleSheet();
+ *aRetval =
+ mAttrs.SetAndSwapMappedAttr(aName, aValue, this, sheet, aValueWasSet);
+ return true;
+}
+
+nsMapRuleToAttributesFunc
+nsMappedAttributeElement::GetAttributeMappingFunction() const {
+ return &MapNoAttributesInto;
+}
+
+void nsMappedAttributeElement::MapNoAttributesInto(
+ const nsMappedAttributes*, mozilla::MappedDeclarations&) {}
+
+void nsMappedAttributeElement::NodeInfoChanged(Document* aOldDoc) {
+ nsHTMLStyleSheet* sheet = OwnerDoc()->GetAttributeStyleSheet();
+ mAttrs.SetMappedAttrStyleSheet(sheet);
+ nsMappedAttributeElementBase::NodeInfoChanged(aOldDoc);
+}
diff --git a/dom/base/nsMappedAttributeElement.h b/dom/base/nsMappedAttributeElement.h
new file mode 100644
index 0000000000..b1389ac210
--- /dev/null
+++ b/dom/base/nsMappedAttributeElement.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsMappedAttributeElement is the base for elements supporting style mapped
+ * attributes via nsMappedAttributes (HTML and MathML).
+ */
+
+#ifndef NS_MAPPEDATTRIBUTEELEMENT_H_
+#define NS_MAPPEDATTRIBUTEELEMENT_H_
+
+#include "mozilla/Attributes.h"
+#include "nsStyledElement.h"
+
+namespace mozilla {
+class MappedDeclarations;
+}
+
+class nsMappedAttributes;
+struct nsRuleData;
+
+using nsMapRuleToAttributesFunc = void (*)(
+ const nsMappedAttributes* aAttributes, mozilla::MappedDeclarations&);
+
+using nsMappedAttributeElementBase = nsStyledElement;
+
+class nsMappedAttributeElement : public nsMappedAttributeElementBase {
+ protected:
+ explicit nsMappedAttributeElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsMappedAttributeElementBase(std::move(aNodeInfo)) {}
+
+ public:
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
+
+ static void MapNoAttributesInto(const nsMappedAttributes* aAttributes,
+ mozilla::MappedDeclarations&);
+
+ virtual bool SetAndSwapMappedAttribute(nsAtom* aName, nsAttrValue& aValue,
+ bool* aValueWasSet,
+ nsresult* aRetval) override;
+
+ virtual void NodeInfoChanged(mozilla::dom::Document* aOldDoc) override;
+};
+
+#endif // NS_MAPPEDATTRIBUTEELEMENT_H_
diff --git a/dom/base/nsMappedAttributes.cpp b/dom/base/nsMappedAttributes.cpp
new file mode 100644
index 0000000000..43322f4d9a
--- /dev/null
+++ b/dom/base/nsMappedAttributes.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A unique per-element set of attributes that is used as an
+ * nsIStyleRule; used to implement presentational attributes.
+ */
+
+#include "nsMappedAttributes.h"
+#include "mozilla/Assertions.h"
+#include "nsHTMLStyleSheet.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MappedDeclarations.h"
+#include "mozilla/MemoryReporting.h"
+
+using namespace mozilla;
+
+bool nsMappedAttributes::sShuttingDown = false;
+nsTArray<void*>* nsMappedAttributes::sCachedMappedAttributeAllocations =
+ nullptr;
+
+void nsMappedAttributes::Shutdown() {
+ sShuttingDown = true;
+ if (sCachedMappedAttributeAllocations) {
+ for (uint32_t i = 0; i < sCachedMappedAttributeAllocations->Length(); ++i) {
+ void* cachedValue = (*sCachedMappedAttributeAllocations)[i];
+ ::operator delete(cachedValue);
+ }
+ }
+
+ delete sCachedMappedAttributeAllocations;
+ sCachedMappedAttributeAllocations = nullptr;
+}
+
+nsMappedAttributes::nsMappedAttributes(nsHTMLStyleSheet* aSheet,
+ nsMapRuleToAttributesFunc aMapRuleFunc)
+ : mAttrCount(0),
+ mSheet(aSheet),
+ mRuleMapper(aMapRuleFunc),
+ mServoStyle(nullptr) {
+ MOZ_ASSERT(mRefCnt == 0); // Ensure caching works as expected.
+}
+
+nsMappedAttributes::nsMappedAttributes(const nsMappedAttributes& aCopy)
+ : mAttrCount(aCopy.mAttrCount),
+ mSheet(aCopy.mSheet),
+ mRuleMapper(aCopy.mRuleMapper),
+ // This is only called by ::Clone, which is used to create independent
+ // nsMappedAttributes objects which should not share a DeclarationBlock
+ mServoStyle(nullptr) {
+ MOZ_ASSERT(mBufferSize >= aCopy.mAttrCount, "can't fit attributes");
+ MOZ_ASSERT(mRefCnt == 0); // Ensure caching works as expected.
+
+ uint32_t i = 0;
+ for (const InternalAttr& attr : aCopy.Attrs()) {
+ new (&mBuffer[i++]) InternalAttr(attr);
+ }
+}
+
+nsMappedAttributes::~nsMappedAttributes() {
+ if (mSheet) {
+ mSheet->DropMappedAttributes(this);
+ }
+
+ for (InternalAttr& attr : Attrs()) {
+ attr.~InternalAttr();
+ }
+}
+
+nsMappedAttributes* nsMappedAttributes::Clone(bool aWillAddAttr) {
+ uint32_t extra = aWillAddAttr ? 1 : 0;
+
+ // This will call the overridden operator new
+ return new (mAttrCount + extra) nsMappedAttributes(*this);
+}
+
+void* nsMappedAttributes::operator new(size_t aSize,
+ uint32_t aAttrCount) noexcept(true) {
+ size_t size = aSize + aAttrCount * sizeof(InternalAttr);
+
+ if (sCachedMappedAttributeAllocations) {
+ void* cached = sCachedMappedAttributeAllocations->SafeElementAt(aAttrCount);
+ if (cached) {
+ (*sCachedMappedAttributeAllocations)[aAttrCount] = nullptr;
+ return cached;
+ }
+ }
+
+ void* newAttrs = ::operator new(size);
+
+#ifdef DEBUG
+ static_cast<nsMappedAttributes*>(newAttrs)->mBufferSize = aAttrCount;
+#endif
+ return newAttrs;
+}
+
+void nsMappedAttributes::LastRelease() {
+ if (!sShuttingDown) {
+ if (!sCachedMappedAttributeAllocations) {
+ sCachedMappedAttributeAllocations = new nsTArray<void*>();
+ }
+
+ // Ensure the cache array is at least mAttrCount + 1 long and
+ // that each item is either null or pointing to a cached item.
+ // The size of the array is capped because mapped attributes are defined
+ // statically in element implementations.
+ sCachedMappedAttributeAllocations->SetCapacity(mAttrCount + 1);
+ for (uint32_t i = sCachedMappedAttributeAllocations->Length();
+ i < (uint32_t(mAttrCount) + 1); ++i) {
+ sCachedMappedAttributeAllocations->AppendElement(nullptr);
+ }
+
+ if (!(*sCachedMappedAttributeAllocations)[mAttrCount]) {
+ void* memoryToCache = this;
+ this->~nsMappedAttributes();
+ (*sCachedMappedAttributeAllocations)[mAttrCount] = memoryToCache;
+ return;
+ }
+ }
+
+ delete this;
+}
+
+void nsMappedAttributes::SetAndSwapAttr(nsAtom* aAttrName, nsAttrValue& aValue,
+ bool* aValueWasSet) {
+ MOZ_ASSERT(aAttrName, "null name");
+ *aValueWasSet = false;
+ uint32_t i;
+ for (i = 0; i < mAttrCount && !mBuffer[i].mName.IsSmaller(aAttrName); ++i) {
+ if (mBuffer[i].mName.Equals(aAttrName)) {
+ mBuffer[i].mValue.SwapValueWith(aValue);
+ *aValueWasSet = true;
+ return;
+ }
+ }
+
+ MOZ_ASSERT(mBufferSize >= mAttrCount + 1, "can't fit attributes");
+
+ if (mAttrCount != i) {
+ memmove(&mBuffer[i + 1], &mBuffer[i],
+ (mAttrCount - i) * sizeof(InternalAttr));
+ }
+
+ new (&mBuffer[i].mName) nsAttrName(aAttrName);
+ new (&mBuffer[i].mValue) nsAttrValue();
+ mBuffer[i].mValue.SwapValueWith(aValue);
+ ++mAttrCount;
+}
+
+const nsAttrValue* nsMappedAttributes::GetAttr(const nsAtom* aAttrName) const {
+ MOZ_ASSERT(aAttrName, "null name");
+ for (const InternalAttr& attr : Attrs()) {
+ if (attr.mName.Equals(aAttrName)) {
+ return &attr.mValue;
+ }
+ }
+ return nullptr;
+}
+
+const nsAttrValue* nsMappedAttributes::GetAttr(
+ const nsAString& aAttrName) const {
+ for (const InternalAttr& attr : Attrs()) {
+ if (attr.mName.Atom()->Equals(aAttrName)) {
+ return &attr.mValue;
+ }
+ }
+ return nullptr;
+}
+
+bool nsMappedAttributes::Equals(const nsMappedAttributes* aOther) const {
+ if (this == aOther) {
+ return true;
+ }
+
+ if (mRuleMapper != aOther->mRuleMapper || mAttrCount != aOther->mAttrCount) {
+ return false;
+ }
+
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ if (!mBuffer[i].mName.Equals(aOther->mBuffer[i].mName) ||
+ !mBuffer[i].mValue.Equals(aOther->mBuffer[i].mValue)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+PLDHashNumber nsMappedAttributes::HashValue() const {
+ PLDHashNumber hash = HashGeneric(mRuleMapper);
+ for (const InternalAttr& attr : Attrs()) {
+ hash = AddToHash(hash, attr.mName.HashValue(), attr.mValue.HashValue());
+ }
+ return hash;
+}
+
+void nsMappedAttributes::SetStyleSheet(nsHTMLStyleSheet* aSheet) {
+ MOZ_ASSERT(!mSheet,
+ "Should either drop the sheet reference manually, "
+ "or drop the mapped attributes");
+ mSheet = aSheet; // not ref counted
+}
+
+void nsMappedAttributes::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
+ mBuffer[aPos].mValue.SwapValueWith(aValue);
+ mBuffer[aPos].~InternalAttr();
+ memmove(&mBuffer[aPos], &mBuffer[aPos + 1],
+ (mAttrCount - aPos - 1) * sizeof(InternalAttr));
+ mAttrCount--;
+}
+
+const nsAttrName* nsMappedAttributes::GetExistingAttrNameFromQName(
+ const nsAString& aName) const {
+ for (const InternalAttr& attr : Attrs()) {
+ if (attr.mName.IsAtom()) {
+ if (attr.mName.Atom()->Equals(aName)) {
+ return &attr.mName;
+ }
+ } else {
+ if (attr.mName.NodeInfo()->QualifiedNameEquals(aName)) {
+ return &attr.mName;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+int32_t nsMappedAttributes::IndexOfAttr(const nsAtom* aLocalName) const {
+ for (uint32_t i = 0; i < mAttrCount; ++i) {
+ if (mBuffer[i].mName.Equals(aLocalName)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+size_t nsMappedAttributes::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ MOZ_ASSERT(mBufferSize >= mAttrCount, "can't fit attributes");
+
+ size_t n = aMallocSizeOf(this);
+ for (const InternalAttr& attr : Attrs()) {
+ n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+void nsMappedAttributes::LazilyResolveServoDeclaration(dom::Document* aDoc) {
+ MOZ_ASSERT(!mServoStyle,
+ "LazilyResolveServoDeclaration should not be called if "
+ "mServoStyle is already set");
+ if (mRuleMapper) {
+ MappedDeclarations declarations(
+ aDoc, Servo_DeclarationBlock_CreateEmpty().Consume());
+ (*mRuleMapper)(this, declarations);
+ mServoStyle = declarations.TakeDeclarationBlock();
+ }
+}
diff --git a/dom/base/nsMappedAttributes.h b/dom/base/nsMappedAttributes.h
new file mode 100644
index 0000000000..faf627478a
--- /dev/null
+++ b/dom/base/nsMappedAttributes.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A unique per-element set of attributes that is used as an
+ * nsIStyleRule; used to implement presentational attributes.
+ */
+
+#ifndef nsMappedAttributes_h___
+#define nsMappedAttributes_h___
+
+#include "AttrArray.h"
+#include "nsMappedAttributeElement.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsAtom;
+class nsHTMLStyleSheet;
+
+class nsMappedAttributes final {
+ using InternalAttr = AttrArray::InternalAttr;
+
+ public:
+ nsMappedAttributes(nsHTMLStyleSheet* aSheet,
+ nsMapRuleToAttributesFunc aMapRuleFunc);
+
+ // Do not return null.
+ void* operator new(size_t size, uint32_t aAttrCount = 1) noexcept(true);
+ nsMappedAttributes* Clone(bool aWillAddAttr);
+
+ NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(nsMappedAttributes, LastRelease())
+
+ void SetAndSwapAttr(nsAtom* aAttrName, nsAttrValue& aValue,
+ bool* aValueWasSet);
+ const nsAttrValue* GetAttr(const nsAtom* aAttrName) const;
+ const nsAttrValue* GetAttr(const nsAString& aAttrName) const;
+
+ uint32_t Count() const { return mAttrCount; }
+
+ bool Equals(const nsMappedAttributes* aAttributes) const;
+ PLDHashNumber HashValue() const;
+
+ void DropStyleSheetReference() { mSheet = nullptr; }
+ void SetStyleSheet(nsHTMLStyleSheet* aSheet);
+ nsHTMLStyleSheet* GetStyleSheet() { return mSheet; }
+
+ void SetRuleMapper(nsMapRuleToAttributesFunc aRuleMapper) {
+ mRuleMapper = aRuleMapper;
+ }
+
+ auto Attrs() const {
+ return mozilla::Span<const InternalAttr>{mBuffer, mAttrCount};
+ }
+ auto Attrs() { return mozilla::Span<InternalAttr>{mBuffer, mAttrCount}; }
+ const nsAttrName* NameAt(uint32_t aPos) const {
+ NS_ASSERTION(aPos < mAttrCount, "out-of-bounds");
+ return &Attrs()[aPos].mName;
+ }
+ const nsAttrValue* AttrAt(uint32_t aPos) const {
+ NS_ASSERTION(aPos < mAttrCount, "out-of-bounds");
+ return &Attrs()[aPos].mValue;
+ }
+ // Remove the attr at position aPos. The value of the attr is placed in
+ // aValue; any value that was already in aValue is destroyed.
+ void RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue);
+ const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const;
+ int32_t IndexOfAttr(const nsAtom* aLocalName) const;
+
+ // Apply the contained mapper to the contained set of servo rules,
+ // unless the servo rules have already been initialized.
+ void LazilyResolveServoDeclaration(mozilla::dom::Document* aDocument);
+
+ // Obtain the contained servo declaration block
+ // May return null if called before the inner block
+ // has been (lazily) resolved
+ const mozilla::StyleLockedDeclarationBlock* GetServoStyle() const {
+ return mServoStyle;
+ }
+
+ void ClearServoStyle() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mServoStyle = nullptr;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ static void Shutdown();
+
+ private:
+ void LastRelease();
+
+ nsMappedAttributes(const nsMappedAttributes& aCopy);
+ ~nsMappedAttributes();
+
+ uint16_t mAttrCount;
+#ifdef DEBUG
+ uint16_t mBufferSize;
+#endif
+ nsHTMLStyleSheet* mSheet; // weak
+ nsMapRuleToAttributesFunc mRuleMapper;
+ RefPtr<mozilla::StyleLockedDeclarationBlock> mServoStyle;
+ InternalAttr mBuffer[0];
+
+ static bool sShuttingDown;
+
+ // We're caching some memory to avoid trashing the allocator.
+ // The memory stored at index N can hold N attribute values.
+ static nsTArray<void*>* sCachedMappedAttributeAllocations;
+};
+
+#endif /* nsMappedAttributes_h___ */
diff --git a/dom/base/nsMimeTypeArray.cpp b/dom/base/nsMimeTypeArray.cpp
new file mode 100644
index 0000000000..75fe5a809e
--- /dev/null
+++ b/dom/base/nsMimeTypeArray.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMimeTypeArray.h"
+
+#include "mozilla/dom/MimeTypeArrayBinding.h"
+#include "mozilla/dom/MimeTypeBinding.h"
+#include "nsGlobalWindowInner.h"
+#include "nsPluginArray.h"
+#include "mozilla/StaticPrefs_pdfjs.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMimeTypeArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMimeTypeArray)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMimeTypeArray)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray, mWindow, mMimeTypes[0],
+ mMimeTypes[1])
+
+nsMimeTypeArray::nsMimeTypeArray(
+ nsPIDOMWindowInner* aWindow,
+ const mozilla::Array<RefPtr<nsMimeType>, 2>& aMimeTypes)
+ : mWindow(aWindow), mMimeTypes(aMimeTypes) {}
+
+nsMimeTypeArray::~nsMimeTypeArray() = default;
+
+JSObject* nsMimeTypeArray::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MimeTypeArray_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* nsMimeTypeArray::GetParentObject() const {
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+nsMimeType* nsMimeTypeArray::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ if (!ForceNoPlugins() && aIndex < ArrayLength(mMimeTypes)) {
+ aFound = true;
+ return mMimeTypes[aIndex];
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+nsMimeType* nsMimeTypeArray::NamedGetter(const nsAString& aName, bool& aFound) {
+ if (ForceNoPlugins()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ for (const auto& mimeType : mMimeTypes) {
+ if (mimeType->Name().Equals(aName)) {
+ aFound = true;
+ return mimeType;
+ }
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+void nsMimeTypeArray::GetSupportedNames(nsTArray<nsString>& retval) {
+ if (ForceNoPlugins()) {
+ return;
+ }
+
+ for (auto& mimeType : mMimeTypes) {
+ retval.AppendElement(mimeType->Name());
+ }
+}
+
+bool nsMimeTypeArray::ForceNoPlugins() {
+ return StaticPrefs::pdfjs_disabled() &&
+ !nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeType, mPluginElement)
+
+nsMimeType::nsMimeType(nsPluginElement* aPluginElement, const nsAString& aName)
+ : mPluginElement(aPluginElement), mName(aName) {
+ MOZ_ASSERT(aPluginElement);
+}
+
+nsMimeType::~nsMimeType() = default;
+
+JSObject* nsMimeType::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MimeType_Binding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/base/nsMimeTypeArray.h b/dom/base/nsMimeTypeArray.h
new file mode 100644
index 0000000000..aaeedba3ae
--- /dev/null
+++ b/dom/base/nsMimeTypeArray.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMimeTypeArray_h___
+#define nsMimeTypeArray_h___
+
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+class nsMimeType;
+class nsPluginElement;
+
+/**
+ * Array class backing HTML's navigator.mimeTypes. This always holds
+ * references to the hard-coded set of PDF MIME types defined by HTML but it
+ * only consults them if "pdfjs.disabled" is false. There is never more
+ * than one of these per DOM window.
+ */
+class nsMimeTypeArray final : public nsISupports, public nsWrapperCache {
+ public:
+ nsMimeTypeArray(nsPIDOMWindowInner* aWindow,
+ const mozilla::Array<RefPtr<nsMimeType>, 2>& aMimeTypes);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsMimeTypeArray)
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // MimeTypeArray WebIDL methods
+ uint32_t Length() { return ForceNoPlugins() ? 0 : ArrayLength(mMimeTypes); }
+
+ nsMimeType* Item(uint32_t aIndex) {
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+ }
+
+ nsMimeType* NamedItem(const nsAString& aName) {
+ bool unused;
+ return NamedGetter(aName, unused);
+ }
+
+ nsMimeType* IndexedGetter(uint32_t index, bool& found);
+
+ nsMimeType* NamedGetter(const nsAString& name, bool& found);
+
+ void GetSupportedNames(nsTArray<nsString>& retval);
+
+ protected:
+ virtual ~nsMimeTypeArray();
+
+ bool ForceNoPlugins();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ mozilla::Array<RefPtr<nsMimeType>, 2> mMimeTypes;
+};
+
+/**
+ * Mime type class backing entries in HTML's navigator.mimeTypes array. There
+ * is a fixed set of these, as defined by HTML.
+ */
+class nsMimeType final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsMimeType)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(nsMimeType)
+
+ nsMimeType(nsPluginElement* aPluginElement, const nsAString& aName);
+
+ nsPluginElement* GetParentObject() const { return mPluginElement; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // MimeType WebIDL methods
+ void GetDescription(mozilla::dom::DOMString& retval) const {
+ retval.SetKnownLiveString(kMimeDescription);
+ }
+
+ already_AddRefed<nsPluginElement> EnabledPlugin() const {
+ return do_AddRef(mPluginElement);
+ }
+
+ void GetSuffixes(mozilla::dom::DOMString& retval) const {
+ retval.SetKnownLiveString(kMimeSuffix);
+ }
+
+ void GetType(nsString& retval) const { retval = mName; }
+ const nsString& Name() const { return mName; }
+
+ protected:
+ virtual ~nsMimeType();
+
+ static constexpr nsLiteralString kMimeDescription =
+ u"Portable Document Format"_ns;
+ static constexpr nsLiteralString kMimeSuffix = u"pdf"_ns;
+
+ // Note that this creates an explicit reference cycle:
+ //
+ // nsMimeType -> nsPluginElement -> nsPluginArray ->
+ // nsMimeTypeArray -> nsMimeType
+ //
+ // We rely on the cycle collector to break this cycle.
+ RefPtr<nsPluginElement> mPluginElement;
+ nsString mName;
+};
+
+#endif /* nsMimeTypeArray_h___ */
diff --git a/dom/base/nsNameSpaceManager.cpp b/dom/base/nsNameSpaceManager.cpp
new file mode 100644
index 0000000000..ffcd4f810d
--- /dev/null
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class for managing namespace IDs and mapping back and forth
+ * between namespace IDs and namespace URIs.
+ */
+
+#include "nsNameSpaceManager.h"
+
+#include "nscore.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsAtom.h"
+#include "nsCOMArray.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/Document.h"
+#include "nsString.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static const char* kPrefSVGDisabled = "svg.disabled";
+static const char* kPrefMathMLDisabled = "mathml.disabled";
+static const char* kObservedNSPrefs[] = {kPrefMathMLDisabled, kPrefSVGDisabled,
+ nullptr};
+StaticRefPtr<nsNameSpaceManager> nsNameSpaceManager::sInstance;
+
+/* static */
+nsNameSpaceManager* nsNameSpaceManager::GetInstance() {
+ if (!sInstance) {
+ sInstance = new nsNameSpaceManager();
+ if (sInstance->Init()) {
+ ClearOnShutdown(&sInstance);
+ } else {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+ }
+
+ return sInstance;
+}
+
+bool nsNameSpaceManager::Init() {
+ nsresult rv;
+#define REGISTER_NAMESPACE(uri, id) \
+ rv = AddNameSpace(dont_AddRef(uri), id); \
+ NS_ENSURE_SUCCESS(rv, false)
+
+#define REGISTER_DISABLED_NAMESPACE(uri, id) \
+ rv = AddDisabledNameSpace(dont_AddRef(uri), id); \
+ NS_ENSURE_SUCCESS(rv, false)
+
+ mozilla::Preferences::RegisterCallbacks(nsNameSpaceManager::PrefChanged,
+ kObservedNSPrefs, this);
+
+ PrefChanged(nullptr);
+
+ // Need to be ordered according to ID.
+ MOZ_ASSERT(mURIArray.IsEmpty());
+ REGISTER_NAMESPACE(nsGkAtoms::_empty, kNameSpaceID_None);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xmlns, kNameSpaceID_XMLNS);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xml, kNameSpaceID_XML);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xhtml, kNameSpaceID_XHTML);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xlink, kNameSpaceID_XLink);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xslt, kNameSpaceID_XSLT);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_MathML);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_rdf, kNameSpaceID_RDF);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_xul, kNameSpaceID_XUL);
+ REGISTER_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_SVG);
+ REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_mathml,
+ kNameSpaceID_disabled_MathML);
+ REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_disabled_SVG);
+
+#undef REGISTER_NAMESPACE
+#undef REGISTER_DISABLED_NAMESPACE
+
+ return true;
+}
+
+nsresult nsNameSpaceManager::RegisterNameSpace(const nsAString& aURI,
+ int32_t& aNameSpaceID) {
+ RefPtr<nsAtom> atom = NS_Atomize(aURI);
+ return RegisterNameSpace(atom.forget(), aNameSpaceID);
+}
+
+nsresult nsNameSpaceManager::RegisterNameSpace(already_AddRefed<nsAtom> aURI,
+ int32_t& aNameSpaceID) {
+ RefPtr<nsAtom> atom = aURI;
+ nsresult rv = NS_OK;
+ if (atom == nsGkAtoms::_empty) {
+ aNameSpaceID = kNameSpaceID_None; // xmlns="", see bug 75700 for details
+ return NS_OK;
+ }
+
+ if (!mURIToIDTable.Get(atom, &aNameSpaceID)) {
+ aNameSpaceID = mURIArray.Length();
+
+ rv = AddNameSpace(atom.forget(), aNameSpaceID);
+ if (NS_FAILED(rv)) {
+ aNameSpaceID = kNameSpaceID_Unknown;
+ }
+ }
+
+ MOZ_ASSERT(aNameSpaceID >= -1, "Bogus namespace ID");
+
+ return rv;
+}
+
+nsresult nsNameSpaceManager::GetNameSpaceURI(int32_t aNameSpaceID,
+ nsAString& aURI) {
+ MOZ_ASSERT(aNameSpaceID >= 0, "Bogus namespace ID");
+
+ // We have historically treated GetNameSpaceURI calls for kNameSpaceID_None
+ // as erroneous.
+ if (aNameSpaceID <= 0 || aNameSpaceID >= int32_t(mURIArray.Length())) {
+ aURI.Truncate();
+
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mURIArray.ElementAt(aNameSpaceID)->ToString(aURI);
+
+ return NS_OK;
+}
+
+int32_t nsNameSpaceManager::GetNameSpaceID(const nsAString& aURI,
+ bool aInChromeDoc) {
+ if (aURI.IsEmpty()) {
+ return kNameSpaceID_None; // xmlns="", see bug 75700 for details
+ }
+
+ RefPtr<nsAtom> atom = NS_Atomize(aURI);
+ return GetNameSpaceID(atom, aInChromeDoc);
+}
+
+int32_t nsNameSpaceManager::GetNameSpaceID(nsAtom* aURI, bool aInChromeDoc) {
+ if (aURI == nsGkAtoms::_empty) {
+ return kNameSpaceID_None; // xmlns="", see bug 75700 for details
+ }
+
+ int32_t nameSpaceID;
+ if (!aInChromeDoc && (mMathMLDisabled || mSVGDisabled) &&
+ mDisabledURIToIDTable.Get(aURI, &nameSpaceID) &&
+ ((mMathMLDisabled && kNameSpaceID_disabled_MathML == nameSpaceID) ||
+ (mSVGDisabled && kNameSpaceID_disabled_SVG == nameSpaceID))) {
+ MOZ_ASSERT(nameSpaceID >= 0, "Bogus namespace ID");
+ return nameSpaceID;
+ }
+ if (mURIToIDTable.Get(aURI, &nameSpaceID)) {
+ MOZ_ASSERT(nameSpaceID >= 0, "Bogus namespace ID");
+ return nameSpaceID;
+ }
+
+ return kNameSpaceID_Unknown;
+}
+
+// static
+const char* nsNameSpaceManager::GetNameSpaceDisplayName(uint32_t aNameSpaceID) {
+ static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)", "(xhtml)",
+ "(XLink)", "(XSLT)", "(MathML)", "(RDF)",
+ "(XUL)", "(SVG)"};
+ if (aNameSpaceID < ArrayLength(kNSURIs)) {
+ return kNSURIs[aNameSpaceID];
+ }
+ return "";
+}
+
+nsresult NS_NewElement(Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ FromParser aFromParser, const nsAString* aIs) {
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ int32_t ns = ni->NamespaceID();
+ RefPtr<nsAtom> isAtom = aIs ? NS_AtomizeMainThread(*aIs) : nullptr;
+ if (ns == kNameSpaceID_XHTML) {
+ return NS_NewHTMLElement(aResult, ni.forget(), aFromParser, isAtom);
+ }
+ if (ns == kNameSpaceID_XUL) {
+ return NS_NewXULElement(aResult, ni.forget(), aFromParser, isAtom);
+ }
+ if (ns == kNameSpaceID_MathML) {
+ // If the mathml.disabled pref. is true, convert all MathML nodes into
+ // disabled MathML nodes by swapping the namespace.
+ if (ni->NodeInfoManager()->MathMLEnabled()) {
+ return NS_NewMathMLElement(aResult, ni.forget());
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
+ ni->NodeInfoManager()->GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
+ kNameSpaceID_disabled_MathML,
+ ni->NodeType(), ni->GetExtraName());
+ return NS_NewXMLElement(aResult, genericXMLNI.forget());
+ }
+ if (ns == kNameSpaceID_SVG) {
+ // If the svg.disabled pref. is true, convert all SVG nodes into
+ // disabled SVG nodes by swapping the namespace.
+ if (ni->NodeInfoManager()->SVGEnabled()) {
+ return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+ }
+ RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
+ ni->NodeInfoManager()->GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
+ kNameSpaceID_disabled_SVG,
+ ni->NodeType(), ni->GetExtraName());
+ return NS_NewXMLElement(aResult, genericXMLNI.forget());
+ }
+
+ return NS_NewXMLElement(aResult, ni.forget());
+}
+
+bool nsNameSpaceManager::HasElementCreator(int32_t aNameSpaceID) {
+ return aNameSpaceID == kNameSpaceID_XHTML ||
+ aNameSpaceID == kNameSpaceID_XUL ||
+ aNameSpaceID == kNameSpaceID_MathML ||
+ aNameSpaceID == kNameSpaceID_SVG || false;
+}
+
+nsresult nsNameSpaceManager::AddNameSpace(already_AddRefed<nsAtom> aURI,
+ const int32_t aNameSpaceID) {
+ RefPtr<nsAtom> uri = aURI;
+ if (aNameSpaceID < 0) {
+ // We've wrapped... Can't do anything else here; just bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(aNameSpaceID == (int32_t)mURIArray.Length());
+ mURIArray.AppendElement(uri.forget());
+ mURIToIDTable.InsertOrUpdate(mURIArray.LastElement(), aNameSpaceID);
+
+ return NS_OK;
+}
+
+nsresult nsNameSpaceManager::AddDisabledNameSpace(already_AddRefed<nsAtom> aURI,
+ const int32_t aNameSpaceID) {
+ RefPtr<nsAtom> uri = aURI;
+ if (aNameSpaceID < 0) {
+ // We've wrapped... Can't do anything else here; just bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(aNameSpaceID == (int32_t)mURIArray.Length());
+ mURIArray.AppendElement(uri.forget());
+ mDisabledURIToIDTable.InsertOrUpdate(mURIArray.LastElement(), aNameSpaceID);
+
+ return NS_OK;
+}
+
+// static
+void nsNameSpaceManager::PrefChanged(const char* aPref, void* aSelf) {
+ static_cast<nsNameSpaceManager*>(aSelf)->PrefChanged(aPref);
+}
+
+void nsNameSpaceManager::PrefChanged(const char* aPref) {
+ mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+ mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
+}
diff --git a/dom/base/nsNameSpaceManager.h b/dom/base/nsNameSpaceManager.h
new file mode 100644
index 0000000000..aa4ec51104
--- /dev/null
+++ b/dom/base/nsNameSpaceManager.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNameSpaceManager_h___
+#define nsNameSpaceManager_h___
+
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsAtom.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+#include "mozilla/StaticPtr.h"
+
+/**
+ * The Name Space Manager tracks the association between a NameSpace
+ * URI and the int32_t runtime id. Mappings between NameSpaces and
+ * NameSpace prefixes are managed by nsINameSpaces.
+ *
+ * All NameSpace URIs are stored in a global table so that IDs are
+ * consistent accross the app. NameSpace IDs are only consistent at runtime
+ * ie: they are not guaranteed to be consistent accross app sessions.
+ *
+ * The nsNameSpaceManager needs to have a live reference for as long as
+ * the NameSpace IDs are needed.
+ *
+ */
+
+class nsNameSpaceManager final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsNameSpaceManager)
+
+ nsresult RegisterNameSpace(const nsAString& aURI, int32_t& aNameSpaceID);
+ nsresult RegisterNameSpace(already_AddRefed<nsAtom> aURI,
+ int32_t& aNameSpaceID);
+
+ nsresult GetNameSpaceURI(int32_t aNameSpaceID, nsAString& aURI);
+
+ // Returns the atom for the namespace URI associated with the given ID. The
+ // ID must be within range and not be kNameSpaceID_None (i.e. zero);
+ //
+ // NB: The requirement of mapping from the first entry to the empty atom is
+ // necessary for Servo, though it can be removed if needed adding a branch in
+ // GeckoElement::get_namespace().
+ nsAtom* NameSpaceURIAtom(int32_t aNameSpaceID) {
+ MOZ_ASSERT(aNameSpaceID > 0);
+ MOZ_ASSERT((int64_t)aNameSpaceID < (int64_t)mURIArray.Length());
+ return mURIArray.ElementAt(aNameSpaceID);
+ }
+
+ int32_t GetNameSpaceID(const nsAString& aURI, bool aInChromeDoc);
+ int32_t GetNameSpaceID(nsAtom* aURI, bool aInChromeDoc);
+
+ static const char* GetNameSpaceDisplayName(uint32_t aNameSpaceID);
+
+ bool HasElementCreator(int32_t aNameSpaceID);
+
+ static nsNameSpaceManager* GetInstance();
+ bool mMathMLDisabled;
+ bool mSVGDisabled;
+
+ private:
+ static void PrefChanged(const char* aPref, void* aSelf);
+ void PrefChanged(const char* aPref);
+
+ bool Init();
+ nsresult AddNameSpace(already_AddRefed<nsAtom> aURI,
+ const int32_t aNameSpaceID);
+ nsresult AddDisabledNameSpace(already_AddRefed<nsAtom> aURI,
+ const int32_t aNameSpaceID);
+ ~nsNameSpaceManager() = default;
+
+ nsTHashMap<nsRefPtrHashKey<nsAtom>, int32_t> mURIToIDTable;
+ nsTHashMap<nsRefPtrHashKey<nsAtom>, int32_t> mDisabledURIToIDTable;
+ nsTArray<RefPtr<nsAtom>> mURIArray;
+
+ static mozilla::StaticRefPtr<nsNameSpaceManager> sInstance;
+};
+
+#endif // nsNameSpaceManager_h___
diff --git a/dom/base/nsNoDataProtocolContentPolicy.cpp b/dom/base/nsNoDataProtocolContentPolicy.cpp
new file mode 100644
index 0000000000..1911c613cf
--- /dev/null
+++ b/dom/base/nsNoDataProtocolContentPolicy.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Content policy implementation that prevents all loads of images,
+ * subframes, etc from protocols that don't return data but rather open
+ * applications (such as mailto).
+ */
+
+#include "nsNoDataProtocolContentPolicy.h"
+#include "nsString.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_ISUPPORTS(nsNoDataProtocolContentPolicy, nsIContentPolicy)
+
+NS_IMETHODIMP
+nsNoDataProtocolContentPolicy::ShouldLoad(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
+
+ *aDecision = nsIContentPolicy::ACCEPT;
+
+ // Don't block for TYPE_OBJECT since such URIs are sometimes loaded by the
+ // plugin, so they don't necessarily open external apps
+ // TYPE_WEBSOCKET loads can only go to ws:// or wss://, so we don't need to
+ // concern ourselves with them.
+ if (contentType != ExtContentPolicy::TYPE_DOCUMENT &&
+ contentType != ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ contentType != ExtContentPolicy::TYPE_OBJECT &&
+ contentType != ExtContentPolicy::TYPE_WEBSOCKET) {
+ // The following are just quick-escapes for the most common cases
+ // where we would allow the content to be loaded anyway.
+ nsAutoCString scheme;
+ aContentLocation->GetScheme(scheme);
+ if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") ||
+ scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") ||
+ scheme.EqualsLiteral("chrome")) {
+ return NS_OK;
+ }
+
+ if (nsContentUtils::IsExternalProtocol(aContentLocation)) {
+ NS_SetRequestBlockingReason(
+ aLoadInfo,
+ nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_NO_DATA_PROTOCOL);
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoDataProtocolContentPolicy::ShouldProcess(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ return ShouldLoad(aContentLocation, aLoadInfo, aMimeGuess, aDecision);
+}
diff --git a/dom/base/nsNoDataProtocolContentPolicy.h b/dom/base/nsNoDataProtocolContentPolicy.h
new file mode 100644
index 0000000000..716a96dfc0
--- /dev/null
+++ b/dom/base/nsNoDataProtocolContentPolicy.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Content policy implementation that prevents all loads of images,
+ * subframes, etc from documents loaded as data (eg documents loaded
+ * via XMLHttpRequest).
+ */
+
+#ifndef nsNoDataProtocolContentPolicy_h__
+#define nsNoDataProtocolContentPolicy_h__
+
+/* ac9e3e82-bfbd-4f26-941e-f58c8ee178c1 */
+#define NS_NODATAPROTOCOLCONTENTPOLICY_CID \
+ { \
+ 0xac9e3e82, 0xbfbd, 0x4f26, { \
+ 0x94, 0x1e, 0xf5, 0x8c, 0x8e, 0xe1, 0x78, 0xc1 \
+ } \
+ }
+#define NS_NODATAPROTOCOLCONTENTPOLICY_CONTRACTID \
+ "@mozilla.org/no-data-protocol-content-policy;1"
+
+#include "nsIContentPolicy.h"
+#include "mozilla/Attributes.h"
+
+class nsNoDataProtocolContentPolicy final : public nsIContentPolicy {
+ ~nsNoDataProtocolContentPolicy() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+
+ nsNoDataProtocolContentPolicy() = default;
+};
+
+#endif /* nsNoDataProtocolContentPolicy_h__ */
diff --git a/dom/base/nsNodeInfoManager.cpp b/dom/base/nsNodeInfoManager.cpp
new file mode 100644
index 0000000000..e9c275579c
--- /dev/null
+++ b/dom/base/nsNodeInfoManager.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class for handing out nodeinfos and ensuring sharing of them as needed.
+ */
+
+#include "nsNodeInfoManager.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsGkAtoms.h"
+#include "nsComponentManagerUtils.h"
+#include "nsLayoutStatics.h"
+#include "nsHashKeys.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsNameSpaceManager.h"
+#include "nsWindowSizes.h"
+
+using namespace mozilla;
+using mozilla::dom::NodeInfo;
+
+#include "mozilla/Logging.h"
+
+static LazyLogModule gNodeInfoManagerLeakPRLog("NodeInfoManagerLeak");
+static const uint32_t kInitialNodeInfoHashSize = 32;
+
+nsNodeInfoManager::nsNodeInfoManager(mozilla::dom::Document* aDocument)
+ : mNodeInfoHash(kInitialNodeInfoHashSize),
+ mDocument(aDocument),
+ mNonDocumentNodeInfos(0),
+ mPrincipal(NullPrincipal::CreateWithoutOriginAttributes()),
+ mDefaultPrincipal(mPrincipal),
+ mTextNodeInfo(nullptr),
+ mCommentNodeInfo(nullptr),
+ mDocumentNodeInfo(nullptr),
+ mRecentlyUsedNodeInfos(),
+ mArena(nullptr) {
+ nsLayoutStatics::AddRef();
+
+ if (gNodeInfoManagerLeakPRLog) {
+ MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
+ ("NODEINFOMANAGER %p created, document=%p", this, aDocument));
+ }
+}
+
+nsNodeInfoManager::~nsNodeInfoManager() {
+ // Note: mPrincipal may be null here if we never got inited correctly
+ mPrincipal = nullptr;
+
+ mArena = nullptr;
+
+ if (gNodeInfoManagerLeakPRLog)
+ MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
+ ("NODEINFOMANAGER %p destroyed", this));
+
+ nsLayoutStatics::Release();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager)
+ if (tmp->mNonDocumentNodeInfos) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager)
+ if (tmp->mDocument) {
+ return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
+ ->CanSkip(tmp->mDocument, aRemovingAllowed);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsNodeInfoManager)
+ if (tmp->mDocument) {
+ return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
+ ->CanSkipInCC(tmp->mDocument);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsNodeInfoManager)
+ if (tmp->mDocument) {
+ return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
+ ->CanSkipThis(tmp->mDocument);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+void nsNodeInfoManager::DropDocumentReference() {
+ // This is probably not needed anymore.
+ for (const auto& entry : mNodeInfoHash.Values()) {
+ entry->mDocument = nullptr;
+ }
+
+ NS_ASSERTION(!mNonDocumentNodeInfos,
+ "Shouldn't have non-document nodeinfos!");
+ mDocument = nullptr;
+}
+
+already_AddRefed<mozilla::dom::NodeInfo> nsNodeInfoManager::GetNodeInfo(
+ nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID, uint16_t aNodeType,
+ nsAtom* aExtraName /* = nullptr */) {
+ CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName);
+
+ NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType,
+ aExtraName);
+
+ auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey);
+ if (p) {
+ RefPtr<NodeInfo> nodeInfo = p.Data();
+ return nodeInfo.forget();
+ }
+
+ // We don't use WithEntryHandle here as that would end up storing the
+ // temporary key instead of using `mInner`.
+ RefPtr<NodeInfo> nodeInfo = mNodeInfoHash.Get(&tmpKey);
+ if (!nodeInfo) {
+ ++mNonDocumentNodeInfos;
+ if (mNonDocumentNodeInfos == 1) {
+ NS_IF_ADDREF(mDocument);
+ }
+
+ nodeInfo =
+ new NodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this);
+ mNodeInfoHash.InsertOrUpdate(&nodeInfo->mInner, nodeInfo);
+ }
+
+ // Have to do the swap thing, because already_AddRefed<nsNodeInfo>
+ // doesn't cast to already_AddRefed<mozilla::dom::NodeInfo>
+ p.Set(nodeInfo);
+ return nodeInfo.forget();
+}
+
+nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
+ int32_t aNamespaceID,
+ uint16_t aNodeType,
+ NodeInfo** aNodeInfo) {
+ // TODO(erahm): Combine this with the atom version.
+#ifdef DEBUG
+ {
+ RefPtr<nsAtom> nameAtom = NS_Atomize(aName);
+ CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr);
+ }
+#endif
+
+ NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType);
+
+ auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey);
+ if (p) {
+ RefPtr<NodeInfo> nodeInfo = p.Data();
+ nodeInfo.forget(aNodeInfo);
+ return NS_OK;
+ }
+
+ RefPtr<NodeInfo> nodeInfo = mNodeInfoHash.Get(&tmpKey);
+ if (!nodeInfo) {
+ ++mNonDocumentNodeInfos;
+ if (mNonDocumentNodeInfos == 1) {
+ NS_IF_ADDREF(mDocument);
+ }
+
+ RefPtr<nsAtom> nameAtom = NS_Atomize(aName);
+ nodeInfo =
+ new NodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this);
+ mNodeInfoHash.InsertOrUpdate(&nodeInfo->mInner, nodeInfo);
+ }
+
+ p.Set(nodeInfo);
+ nodeInfo.forget(aNodeInfo);
+
+ return NS_OK;
+}
+
+nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
+ const nsAString& aNamespaceURI,
+ uint16_t aNodeType,
+ NodeInfo** aNodeInfo) {
+ int32_t nsid = kNameSpaceID_None;
+
+ if (!aNamespaceURI.IsEmpty()) {
+ nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ aNamespaceURI, nsid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return GetNodeInfo(aName, aPrefix, nsid, aNodeType, aNodeInfo);
+}
+
+already_AddRefed<NodeInfo> nsNodeInfoManager::GetTextNodeInfo() {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+
+ if (!mTextNodeInfo) {
+ nodeInfo = GetNodeInfo(nsGkAtoms::textTagName, nullptr, kNameSpaceID_None,
+ nsINode::TEXT_NODE, nullptr);
+ // Hold a weak ref; the nodeinfo will let us know when it goes away
+ mTextNodeInfo = nodeInfo;
+ } else {
+ nodeInfo = mTextNodeInfo;
+ }
+
+ return nodeInfo.forget();
+}
+
+already_AddRefed<NodeInfo> nsNodeInfoManager::GetCommentNodeInfo() {
+ RefPtr<NodeInfo> nodeInfo;
+
+ if (!mCommentNodeInfo) {
+ nodeInfo = GetNodeInfo(nsGkAtoms::commentTagName, nullptr,
+ kNameSpaceID_None, nsINode::COMMENT_NODE, nullptr);
+ // Hold a weak ref; the nodeinfo will let us know when it goes away
+ mCommentNodeInfo = nodeInfo;
+ } else {
+ nodeInfo = mCommentNodeInfo;
+ }
+
+ return nodeInfo.forget();
+}
+
+already_AddRefed<NodeInfo> nsNodeInfoManager::GetDocumentNodeInfo() {
+ RefPtr<NodeInfo> nodeInfo;
+
+ if (!mDocumentNodeInfo) {
+ NS_ASSERTION(mDocument, "Should have mDocument!");
+ nodeInfo = GetNodeInfo(nsGkAtoms::documentNodeName, nullptr,
+ kNameSpaceID_None, nsINode::DOCUMENT_NODE, nullptr);
+ // Hold a weak ref; the nodeinfo will let us know when it goes away
+ mDocumentNodeInfo = nodeInfo;
+
+ --mNonDocumentNodeInfos;
+ if (!mNonDocumentNodeInfos) {
+ mDocument->Release(); // Don't set mDocument to null!
+ }
+ } else {
+ nodeInfo = mDocumentNodeInfo;
+ }
+
+ return nodeInfo.forget();
+}
+
+void* nsNodeInfoManager::Allocate(size_t aSize) {
+ if (!mHasAllocated) {
+ if (mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
+ if (!mArena) {
+ mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroupOrCreate();
+ if (docGroup) {
+ MOZ_ASSERT(!GetDocument()->HasChildren());
+ mArena = docGroup->ArenaAllocator();
+ }
+ }
+#ifdef DEBUG
+ else {
+ mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroup();
+ MOZ_ASSERT(docGroup);
+ MOZ_ASSERT(mArena == docGroup->ArenaAllocator());
+ }
+#endif
+ }
+ mHasAllocated = true;
+ }
+
+#ifdef DEBUG
+ if (!mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
+ MOZ_ASSERT(!mArena, "mArena should not set if the pref is not on");
+ };
+#endif
+
+ if (mArena) {
+ return mArena->Allocate(aSize);
+ }
+ return malloc(aSize);
+}
+
+void nsNodeInfoManager::SetArenaAllocator(mozilla::dom::DOMArena* aArena) {
+ MOZ_DIAGNOSTIC_ASSERT_IF(mArena, mArena == aArena);
+ MOZ_DIAGNOSTIC_ASSERT(!mHasAllocated);
+ MOZ_DIAGNOSTIC_ASSERT(
+ mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+
+ if (!mArena) {
+ mArena = aArena;
+ }
+}
+
+void nsNodeInfoManager::SetDocumentPrincipal(nsIPrincipal* aPrincipal) {
+ mPrincipal = nullptr;
+ if (!aPrincipal) {
+ aPrincipal = mDefaultPrincipal;
+ }
+
+ NS_ASSERTION(aPrincipal, "Must have principal by this point!");
+ MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsExpandedPrincipal(aPrincipal),
+ "Documents shouldn't have an expanded principal");
+
+ mPrincipal = aPrincipal;
+}
+
+void nsNodeInfoManager::RemoveNodeInfo(NodeInfo* aNodeInfo) {
+ MOZ_ASSERT(aNodeInfo, "Trying to remove null nodeinfo from manager!");
+
+ if (aNodeInfo == mDocumentNodeInfo) {
+ mDocumentNodeInfo = nullptr;
+ mDocument = nullptr;
+ } else {
+ if (--mNonDocumentNodeInfos == 0) {
+ if (mDocument) {
+ // Note, whoever calls this method should keep NodeInfoManager alive,
+ // even if mDocument gets deleted.
+ mDocument->Release();
+ }
+ }
+ // Drop weak reference if needed
+ if (aNodeInfo == mTextNodeInfo) {
+ mTextNodeInfo = nullptr;
+ } else if (aNodeInfo == mCommentNodeInfo) {
+ mCommentNodeInfo = nullptr;
+ }
+ }
+
+ mRecentlyUsedNodeInfos.Remove(aNodeInfo->mInner);
+ DebugOnly<bool> ret = mNodeInfoHash.Remove(&aNodeInfo->mInner);
+ MOZ_ASSERT(ret, "Can't find mozilla::dom::NodeInfo to remove!!!");
+}
+
+static bool IsSystemOrAddonOrAboutPrincipal(nsIPrincipal* aPrincipal) {
+ return aPrincipal->IsSystemPrincipal() ||
+ BasePrincipal::Cast(aPrincipal)->AddonPolicy() ||
+ // NOTE: about:blank and about:srcdoc inherit the principal of their
+ // parent, so aPrincipal->SchemeIs("about") returns false for them.
+ aPrincipal->SchemeIs("about");
+}
+
+bool nsNodeInfoManager::InternalSVGEnabled() {
+ MOZ_ASSERT(!mSVGEnabled, "Caller should use the cached mSVGEnabled!");
+
+ // If the svg.disabled pref. is true, convert all SVG nodes into
+ // disabled SVG nodes by swapping the namespace.
+ nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ bool SVGEnabled = false;
+
+ if (nsmgr && !nsmgr->mSVGDisabled) {
+ SVGEnabled = true;
+ } else {
+ nsCOMPtr<nsIChannel> channel = mDocument->GetChannel();
+ // We don't have a channel for SVGs constructed inside a SVG script
+ if (channel) {
+ loadInfo = channel->LoadInfo();
+ }
+ }
+
+ // We allow SVG (regardless of the pref) if this is a system or add-on
+ // principal or about: page, or if this load was requested for a system or
+ // add-on principal or about: page (e.g. a remote image being served as part
+ // of system or add-on UI or about: page)
+ bool conclusion =
+ (SVGEnabled || IsSystemOrAddonOrAboutPrincipal(mPrincipal) ||
+ (loadInfo &&
+ (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGE ||
+ loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_OTHER) &&
+ (IsSystemOrAddonOrAboutPrincipal(loadInfo->GetLoadingPrincipal()) ||
+ IsSystemOrAddonOrAboutPrincipal(loadInfo->TriggeringPrincipal()))));
+ mSVGEnabled = Some(conclusion);
+ return conclusion;
+}
+
+bool nsNodeInfoManager::InternalMathMLEnabled() {
+ MOZ_ASSERT(!mMathMLEnabled, "Caller should use the cached mMathMLEnabled!");
+
+ // If the mathml.disabled pref. is true, convert all MathML nodes into
+ // disabled MathML nodes by swapping the namespace.
+ nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
+ bool conclusion =
+ ((nsmgr && !nsmgr->mMathMLDisabled) || mPrincipal->IsSystemPrincipal());
+ mMathMLEnabled = Some(conclusion);
+ return conclusion;
+}
+
+void nsNodeInfoManager::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
+ aSizes.mDOMSizes.mDOMOtherSize += aSizes.mState.mMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mNodeInfoHash
+}
diff --git a/dom/base/nsNodeInfoManager.h b/dom/base/nsNodeInfoManager.h
new file mode 100644
index 0000000000..4e24406752
--- /dev/null
+++ b/dom/base/nsNodeInfoManager.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class for handing out nodeinfos and ensuring sharing of them as needed.
+ */
+
+#ifndef nsNodeInfoManager_h___
+#define nsNodeInfoManager_h___
+
+#include "mozilla/Attributes.h" // for final
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/DOMArena.h"
+#include "mozilla/MruCache.h"
+#include "nsCOMPtr.h" // for member
+#include "nsCycleCollectionParticipant.h" // for NS_DECL_CYCLE_*
+#include "nsTHashMap.h"
+#include "nsStringFwd.h"
+
+class nsAtom;
+class nsIPrincipal;
+class nsWindowSizes;
+template <class T>
+struct already_AddRefed;
+
+namespace mozilla::dom {
+class Document;
+} // namespace mozilla::dom
+
+class nsNodeInfoManager final {
+ private:
+ ~nsNodeInfoManager();
+
+ public:
+ explicit nsNodeInfoManager(mozilla::dom::Document* aDocument);
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(nsNodeInfoManager)
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsNodeInfoManager)
+
+ /**
+ * Release the reference to the document, this will be called when
+ * the document is going away.
+ */
+ void DropDocumentReference();
+
+ /**
+ * Methods for creating nodeinfo's from atoms and/or strings.
+ */
+ already_AddRefed<mozilla::dom::NodeInfo> GetNodeInfo(
+ nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID, uint16_t aNodeType,
+ nsAtom* aExtraName = nullptr);
+ nsresult GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo);
+ nsresult GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
+ const nsAString& aNamespaceURI, uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo);
+
+ /**
+ * Returns the nodeinfo for text nodes. Can return null if OOM.
+ */
+ already_AddRefed<mozilla::dom::NodeInfo> GetTextNodeInfo();
+
+ /**
+ * Returns the nodeinfo for comment nodes. Can return null if OOM.
+ */
+ already_AddRefed<mozilla::dom::NodeInfo> GetCommentNodeInfo();
+
+ /**
+ * Returns the nodeinfo for the document node. Can return null if OOM.
+ */
+ already_AddRefed<mozilla::dom::NodeInfo> GetDocumentNodeInfo();
+
+ /**
+ * Retrieve a pointer to the document that owns this node info
+ * manager.
+ */
+ mozilla::dom::Document* GetDocument() const { return mDocument; }
+
+ /**
+ * Gets the principal of the document this nodeinfo manager belongs to.
+ */
+ nsIPrincipal* DocumentPrincipal() const {
+ NS_ASSERTION(mPrincipal, "How'd that happen?");
+ return mPrincipal;
+ }
+
+ void RemoveNodeInfo(mozilla::dom::NodeInfo* aNodeInfo);
+
+ /**
+ * Returns true if SVG nodes in this document have real SVG semantics.
+ */
+ bool SVGEnabled() {
+ return mSVGEnabled.valueOrFrom([this] { return InternalSVGEnabled(); });
+ }
+
+ /**
+ * Returns true if MathML nodes in this document have real MathML semantics.
+ */
+ bool MathMLEnabled() {
+ return mMathMLEnabled.valueOrFrom(
+ [this] { return InternalMathMLEnabled(); });
+ }
+
+ mozilla::dom::DOMArena* GetArenaAllocator() { return mArena; }
+ void SetArenaAllocator(mozilla::dom::DOMArena* aArena);
+
+ void* Allocate(size_t aSize);
+
+ void Free(void* aPtr) { free(aPtr); }
+
+ bool HasAllocated() { return mHasAllocated; }
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
+
+ protected:
+ friend class mozilla::dom::Document;
+ friend class nsXULPrototypeDocument;
+
+ /**
+ * Sets the principal of the document this nodeinfo manager belongs to.
+ */
+ void SetDocumentPrincipal(nsIPrincipal* aPrincipal);
+
+ private:
+ bool InternalSVGEnabled();
+ bool InternalMathMLEnabled();
+
+ class NodeInfoInnerKey
+ : public nsPtrHashKey<mozilla::dom::NodeInfo::NodeInfoInner> {
+ public:
+ explicit NodeInfoInnerKey(KeyTypePointer aKey) : nsPtrHashKey(aKey) {}
+ NodeInfoInnerKey(NodeInfoInnerKey&&) = default;
+ ~NodeInfoInnerKey() = default;
+ bool KeyEquals(KeyTypePointer aKey) const { return *mKey == *aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->Hash(); }
+ };
+
+ struct NodeInfoCache
+ : public mozilla::MruCache<mozilla::dom::NodeInfo::NodeInfoInner,
+ mozilla::dom::NodeInfo*, NodeInfoCache> {
+ static mozilla::HashNumber Hash(
+ const mozilla::dom::NodeInfo::NodeInfoInner& aKey) {
+ return aKey.Hash();
+ }
+ static bool Match(const mozilla::dom::NodeInfo::NodeInfoInner& aKey,
+ const mozilla::dom::NodeInfo* aVal) {
+ return aKey == aVal->mInner;
+ }
+ };
+
+ nsTHashMap<NodeInfoInnerKey, mozilla::dom::NodeInfo*> mNodeInfoHash;
+ mozilla::dom::Document* MOZ_NON_OWNING_REF mDocument; // WEAK
+ uint32_t mNonDocumentNodeInfos;
+
+ // Note: it's important that mPrincipal is declared before mDefaultPrincipal,
+ // since the latter is initialized to the value of the former in the
+ // constructor's init list:
+ nsCOMPtr<nsIPrincipal> mPrincipal; // Never null
+ nsCOMPtr<nsIPrincipal> mDefaultPrincipal; // Never null
+
+ mozilla::dom::NodeInfo* MOZ_NON_OWNING_REF
+ mTextNodeInfo; // WEAK to avoid circular ownership
+ mozilla::dom::NodeInfo* MOZ_NON_OWNING_REF
+ mCommentNodeInfo; // WEAK to avoid circular ownership
+ mozilla::dom::NodeInfo* MOZ_NON_OWNING_REF
+ mDocumentNodeInfo; // WEAK to avoid circular ownership
+ NodeInfoCache mRecentlyUsedNodeInfos;
+ mozilla::Maybe<bool> mSVGEnabled; // Lazily initialized.
+ mozilla::Maybe<bool> mMathMLEnabled; // Lazily initialized.
+
+ // For dom_arena_allocator_enabled
+ RefPtr<mozilla::dom::DOMArena> mArena;
+ bool mHasAllocated = false;
+};
+
+#endif /* nsNodeInfoManager_h___ */
diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp
new file mode 100644
index 0000000000..002d774bd1
--- /dev/null
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -0,0 +1,2247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * A base class implementing nsIObjectLoadingContent for use by
+ * various content nodes that want to provide plugin/document/image
+ * loading functionality (eg <embed>, <object>, etc).
+ */
+
+// Interface headers
+#include "imgLoader.h"
+#include "nsIClassOfService.h"
+#include "nsIConsoleService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIDocShell.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/Document.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIOService.h"
+#include "nsIPermissionManager.h"
+#include "nsPluginHost.h"
+#include "nsIHttpChannel.h"
+#include "nsINestedURI.h"
+#include "nsScriptSecurityManager.h"
+#include "nsIURILoader.h"
+#include "nsIURL.h"
+#include "nsIScriptChannel.h"
+#include "nsIBlocklistService.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAppShell.h"
+#include "nsIXULRuntime.h"
+#include "nsIScriptError.h"
+#include "nsSubDocumentFrame.h"
+
+#include "nsError.h"
+
+// Util headers
+#include "prenv.h"
+#include "mozilla/Logging.h"
+
+#include "nsCURILoader.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDocShellCID.h"
+#include "nsDocShellLoadState.h"
+#include "nsGkAtoms.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsMimeTypes.h"
+#include "nsStyleUtil.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsSandboxFlags.h"
+#include "nsQueryObject.h"
+
+// Concrete classes
+#include "nsFrameLoader.h"
+
+#include "nsObjectLoadingContent.h"
+#include "mozAutoDocUpdate.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsDOMJSUtils.h"
+#include "js/Object.h" // JS::GetClass
+
+#include "nsWidgetsCID.h"
+#include "nsContentCID.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Components.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/widget/IMEData.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HTMLObjectElementBinding.h"
+#include "mozilla/dom/HTMLEmbedElement.h"
+#include "mozilla/dom/HTMLObjectElement.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "nsChannelClassifier.h"
+#include "nsFocusManager.h"
+#include "ReferrerInfo.h"
+#include "nsIEffectiveTLDService.h"
+
+#ifdef XP_WIN
+// Thanks so much, Microsoft! :(
+# ifdef CreateEvent
+# undef CreateEvent
+# endif
+#endif // XP_WIN
+
+static const char kPrefYoutubeRewrite[] = "plugins.rewrite_youtube_embeds";
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+static LogModule* GetObjectLog() {
+ static LazyLogModule sLog("objlc");
+ return sLog;
+}
+
+#define LOG(args) MOZ_LOG(GetObjectLog(), mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(GetObjectLog(), mozilla::LogLevel::Debug)
+
+static bool IsFlashMIME(const nsACString& aMIMEType) {
+ return nsPluginHost::GetSpecialType(aMIMEType) ==
+ nsPluginHost::eSpecialType_Flash;
+}
+
+static bool IsPluginType(nsObjectLoadingContent::ObjectType type) {
+ return type == nsObjectLoadingContent::eType_Fallback ||
+ type == nsObjectLoadingContent::eType_FakePlugin;
+}
+
+///
+/// Runnables and helper classes
+///
+
+// Sets a object's mIsLoading bit to false when destroyed
+class AutoSetLoadingToFalse {
+ public:
+ explicit AutoSetLoadingToFalse(nsObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+ ~AutoSetLoadingToFalse() { mContent->mIsLoading = false; }
+
+ private:
+ nsObjectLoadingContent* mContent;
+};
+
+///
+/// Helper functions
+///
+
+bool nsObjectLoadingContent::IsSuccessfulRequest(nsIRequest* aRequest,
+ nsresult* aStatus) {
+ nsresult rv = aRequest->GetStatus(aStatus);
+ if (NS_FAILED(rv) || NS_FAILED(*aStatus)) {
+ return false;
+ }
+
+ // This may still be an error page or somesuch
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(aRequest));
+ if (httpChan) {
+ bool success;
+ rv = httpChan->GetRequestSucceeded(&success);
+ if (NS_FAILED(rv) || !success) {
+ return false;
+ }
+ }
+
+ // Otherwise, the request is successful
+ return true;
+}
+
+static bool CanHandleURI(nsIURI* aURI) {
+ nsAutoCString scheme;
+ if (NS_FAILED(aURI->GetScheme(scheme))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIIOService> ios = mozilla::components::IO::Service();
+ if (!ios) {
+ return false;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (!handler) {
+ return false;
+ }
+
+ nsCOMPtr<nsIExternalProtocolHandler> extHandler = do_QueryInterface(handler);
+ // We can handle this URI if its protocol handler is not the external one
+ return extHandler == nullptr;
+}
+
+// Helper for tedious URI equality syntax when one or both arguments may be
+// null and URIEquals(null, null) should be true
+static bool inline URIEquals(nsIURI* a, nsIURI* b) {
+ bool equal;
+ return (!a && !b) || (a && b && NS_SUCCEEDED(a->Equals(b, &equal)) && equal);
+}
+
+///
+/// Member Functions
+///
+
+// Helper to spawn the frameloader.
+void nsObjectLoadingContent::SetupFrameLoader(int32_t aJSPluginId) {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ mFrameLoader =
+ nsFrameLoader::Create(thisContent->AsElement(), mNetworkCreated);
+ MOZ_ASSERT(mFrameLoader, "nsFrameLoader::Create failed");
+}
+
+// Helper to spawn the frameloader and return a pointer to its docshell.
+already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell(
+ nsIURI* aRecursionCheckURI) {
+ SetupFrameLoader(nsFakePluginTag::NOT_JSPLUGIN);
+ if (!mFrameLoader) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell;
+
+ if (aRecursionCheckURI) {
+ nsresult rv = mFrameLoader->CheckForRecursiveLoad(aRecursionCheckURI);
+ if (NS_SUCCEEDED(rv)) {
+ IgnoredErrorResult result;
+ docShell = mFrameLoader->GetDocShell(result);
+ if (result.Failed()) {
+ MOZ_ASSERT_UNREACHABLE("Could not get DocShell from mFrameLoader?");
+ }
+ } else {
+ LOG(("OBJLC [%p]: Aborting recursive load", this));
+ }
+ }
+
+ if (!docShell) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ return nullptr;
+ }
+
+ MaybeStoreCrossOriginFeaturePolicy();
+
+ return docShell.forget();
+}
+
+void nsObjectLoadingContent::UnbindFromTree(bool aNullParent) {
+ nsImageLoadingContent::UnbindFromTree(aNullParent);
+
+ if (mType != eType_Image) {
+ // nsImageLoadingContent handles the image case.
+ // Reset state and clear pending events
+ /// XXX(johns): The implementation for GenericFrame notes that ideally we
+ /// would keep the docshell around, but trash the frameloader
+ UnloadObject();
+ }
+}
+
+nsObjectLoadingContent::nsObjectLoadingContent()
+ : mType(eType_Loading),
+ mRunID(0),
+ mHasRunID(false),
+ mChannelLoaded(false),
+ mNetworkCreated(true),
+ mContentBlockingEnabled(false),
+ mSkipFakePlugins(false),
+ mIsStopping(false),
+ mIsLoading(false),
+ mScriptRequested(false),
+ mRewrittenYoutubeEmbed(false),
+ mLoadingSyntheticDocument(false) {}
+
+nsObjectLoadingContent::~nsObjectLoadingContent() {
+ // Should have been unbound from the tree at this point, and
+ // CheckPluginStopEvent keeps us alive
+ if (mFrameLoader) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Should not be tearing down frame loaders at this point");
+ mFrameLoader->Destroy();
+ }
+
+ nsImageLoadingContent::Destroy();
+}
+
+void nsObjectLoadingContent::GetPluginAttributes(
+ nsTArray<MozPluginParameter>& aAttributes) {
+ aAttributes = mCachedAttributes.Clone();
+}
+
+void nsObjectLoadingContent::GetPluginParameters(
+ nsTArray<MozPluginParameter>& aParameters) {
+ aParameters = mCachedParameters.Clone();
+}
+
+void nsObjectLoadingContent::GetNestedParams(
+ nsTArray<MozPluginParameter>& aParameters) {
+ nsCOMPtr<Element> ourElement =
+ do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+
+ nsCOMPtr<nsIHTMLCollection> allParams;
+ constexpr auto xhtml_ns = u"http://www.w3.org/1999/xhtml"_ns;
+ ErrorResult rv;
+ allParams = ourElement->GetElementsByTagNameNS(xhtml_ns, u"param"_ns, rv);
+ if (rv.Failed()) {
+ return;
+ }
+ MOZ_ASSERT(allParams);
+
+ uint32_t numAllParams = allParams->Length();
+ for (uint32_t i = 0; i < numAllParams; i++) {
+ RefPtr<Element> element = allParams->Item(i);
+
+ nsAutoString name;
+ element->GetAttr(nsGkAtoms::name, name);
+
+ if (name.IsEmpty()) continue;
+
+ nsCOMPtr<nsIContent> parent = element->GetParent();
+ RefPtr<HTMLObjectElement> objectElement;
+ while (!objectElement && parent) {
+ objectElement = HTMLObjectElement::FromNode(parent);
+ parent = parent->GetParent();
+ }
+
+ if (objectElement) {
+ parent = objectElement;
+ } else {
+ continue;
+ }
+
+ if (parent == ourElement) {
+ MozPluginParameter param;
+ element->GetAttr(nsGkAtoms::name, param.mName);
+ element->GetAttr(nsGkAtoms::value, param.mValue);
+
+ param.mName.Trim(" \n\r\t\b", true, true, false);
+ param.mValue.Trim(" \n\r\t\b", true, true, false);
+
+ aParameters.AppendElement(param);
+ }
+ }
+}
+
+nsresult nsObjectLoadingContent::BuildParametersArray() {
+ if (mCachedAttributes.Length() || mCachedParameters.Length()) {
+ MOZ_ASSERT(false, "Parameters array should be empty.");
+ return NS_OK;
+ }
+
+ nsCOMPtr<Element> element =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ for (uint32_t i = 0; i != element->GetAttrCount(); i += 1) {
+ MozPluginParameter param;
+ const nsAttrName* attrName = element->GetAttrNameAt(i);
+ nsAtom* atom = attrName->LocalName();
+ element->GetAttr(attrName->NamespaceID(), atom, param.mValue);
+ atom->ToString(param.mName);
+ mCachedAttributes.AppendElement(param);
+ }
+
+ nsAutoCString wmodeOverride;
+ Preferences::GetCString("plugins.force.wmode", wmodeOverride);
+
+ for (uint32_t i = 0; i < mCachedAttributes.Length(); i++) {
+ if (!wmodeOverride.IsEmpty() &&
+ mCachedAttributes[i].mName.EqualsIgnoreCase("wmode")) {
+ CopyASCIItoUTF16(wmodeOverride, mCachedAttributes[i].mValue);
+ wmodeOverride.Truncate();
+ }
+ }
+
+ if (!wmodeOverride.IsEmpty()) {
+ MozPluginParameter param;
+ param.mName = u"wmode"_ns;
+ CopyASCIItoUTF16(wmodeOverride, param.mValue);
+ mCachedAttributes.AppendElement(param);
+ }
+
+ // Some plugins were never written to understand the "data" attribute of the
+ // OBJECT tag. Real and WMP will not play unless they find a "src" attribute,
+ // see bug 152334. Nav 4.x would simply replace the "data" with "src". Because
+ // some plugins correctly look for "data", lets instead copy the "data"
+ // attribute and add another entry to the bottom of the array if there isn't
+ // already a "src" specified.
+ if (element->IsHTMLElement(nsGkAtoms::object) &&
+ !element->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ MozPluginParameter param;
+ element->GetAttr(kNameSpaceID_None, nsGkAtoms::data, param.mValue);
+ if (!param.mValue.IsEmpty()) {
+ param.mName = u"SRC"_ns;
+ mCachedAttributes.AppendElement(param);
+ }
+ }
+
+ GetNestedParams(mCachedParameters);
+
+ return NS_OK;
+}
+
+void nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged() {
+ // XXX(johns): We cannot touch plugins or run arbitrary script from this call,
+ // as Document is in a non-reentrant state.
+
+ nsImageLoadingContent::NotifyOwnerDocumentActivityChanged();
+}
+
+// nsIRequestObserver
+NS_IMETHODIMP
+nsObjectLoadingContent::OnStartRequest(nsIRequest* aRequest) {
+ AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStartRequest", NETWORK);
+
+ LOG(("OBJLC [%p]: Channel OnStartRequest", this));
+
+ if (aRequest != mChannel || !aRequest) {
+ // happens when a new load starts before the previous one got here
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+ NS_ASSERTION(chan, "Why is our request not a channel?");
+
+ nsresult status = NS_OK;
+ bool success = IsSuccessfulRequest(aRequest, &status);
+
+ // If we have already switched to type document, we're doing a
+ // process-switching DocumentChannel load. We should be able to pass down the
+ // load to our inner listener, but should also make sure to update our local
+ // state.
+ if (mType == eType_Document) {
+ if (!mFinalListener) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Already are eType_Document, but don't have final listener yet?");
+ return NS_BINDING_ABORTED;
+ }
+
+ // If the load looks successful, fix up some of our local state before
+ // forwarding the request to the final URI loader.
+ //
+ // Forward load errors down to the document loader, so we don't tear down
+ // the nsDocShell ourselves.
+ if (success) {
+ LOG(("OBJLC [%p]: OnStartRequest: DocumentChannel request succeeded\n",
+ this));
+ nsCString channelType;
+ MOZ_ALWAYS_SUCCEEDS(mChannel->GetContentType(channelType));
+
+ if (GetTypeOfContent(channelType, mSkipFakePlugins) != eType_Document) {
+ MOZ_CRASH("DocumentChannel request with non-document MIME");
+ }
+ mContentType = channelType;
+
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_GetFinalChannelURI(mChannel, getter_AddRefs(mURI)));
+ }
+
+ return mFinalListener->OnStartRequest(aRequest);
+ }
+
+ // Otherwise we should be state loading, and call LoadObject with the channel
+ if (mType != eType_Loading) {
+ MOZ_ASSERT_UNREACHABLE("Should be type loading at this point");
+ return NS_BINDING_ABORTED;
+ }
+ NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
+ NS_ASSERTION(!mFinalListener, "mFinalListener exists already?");
+
+ mChannelLoaded = true;
+
+ if (status == NS_ERROR_BLOCKED_URI) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (console) {
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ nsString message =
+ u"Blocking "_ns +
+ NS_ConvertASCIItoUTF16(uri->GetSpecOrDefault().get()) +
+ nsLiteralString(
+ u" since it was found on an internal Firefox blocklist.");
+ console->LogStringMessage(message.get());
+ }
+ mContentBlockingEnabled = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
+ mContentBlockingEnabled = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!success) {
+ LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this));
+ // If the request fails, we still call LoadObject() to handle fallback
+ // content and notifying of failure. (mChannelLoaded && !mChannel) indicates
+ // the bad state.
+ mChannel = nullptr;
+ LoadObject(true, false);
+ return NS_ERROR_FAILURE;
+ }
+
+ return LoadObject(true, false, aRequest);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStopRequest", NETWORK);
+
+ // Handle object not loading error because source was a tracking URL (or
+ // fingerprinting, cryptomining, etc.).
+ // We make a note of this object node by including it in a dedicated
+ // array of blocked tracking nodes under its parent document.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatusCode)) {
+ nsCOMPtr<nsIContent> thisNode =
+ do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+ if (thisNode && thisNode->IsInComposedDoc()) {
+ thisNode->GetComposedDoc()->AddBlockedNodeByClassifier(thisNode);
+ }
+ }
+
+ if (aRequest != mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ mChannel = nullptr;
+
+ if (mFinalListener) {
+ // This may re-enter in the case of plugin listeners
+ nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
+ mFinalListener = nullptr;
+ listenerGrip->OnStopRequest(aRequest, aStatusCode);
+ }
+
+ // Return value doesn't matter
+ return NS_OK;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+nsObjectLoadingContent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (aRequest != mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ if (mFinalListener) {
+ // This may re-enter in the case of plugin listeners
+ nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
+ return listenerGrip->OnDataAvailable(aRequest, aInputStream, aOffset,
+ aCount);
+ }
+
+ // We shouldn't have a connected channel with no final listener
+ MOZ_ASSERT_UNREACHABLE(
+ "Got data for channel with no connected final "
+ "listener");
+ mChannel = nullptr;
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsObjectLoadingContent::PresetOpenerWindow(
+ const Nullable<WindowProxyHolder>& aOpenerWindow, ErrorResult& aRv) {
+ aRv.Throw(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetActualType(nsACString& aType) {
+ aType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetDisplayedType(uint32_t* aType) {
+ *aType = DisplayedType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetContentTypeForMIMEType(const nsACString& aMIMEType,
+ uint32_t* aType) {
+ *aType = GetTypeOfContent(PromiseFlatCString(aMIMEType), false);
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+// We use a shim class to implement this so that JS consumers still
+// see an interface requestor even though WebIDL bindings don't expose
+// that stuff.
+class ObjectInterfaceRequestorShim final : public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIStreamListener {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ObjectInterfaceRequestorShim,
+ nsIInterfaceRequestor)
+ NS_DECL_NSIINTERFACEREQUESTOR
+ // RefPtr<nsObjectLoadingContent> fails due to ambiguous AddRef/Release,
+ // hence the ugly static cast :(
+ NS_FORWARD_NSICHANNELEVENTSINK(
+ static_cast<nsObjectLoadingContent*>(mContent.get())->)
+ NS_FORWARD_NSISTREAMLISTENER(
+ static_cast<nsObjectLoadingContent*>(mContent.get())->)
+ NS_FORWARD_NSIREQUESTOBSERVER(
+ static_cast<nsObjectLoadingContent*>(mContent.get())->)
+
+ explicit ObjectInterfaceRequestorShim(nsIObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+
+ protected:
+ ~ObjectInterfaceRequestorShim() = default;
+ nsCOMPtr<nsIObjectLoadingContent> mContent;
+};
+
+NS_IMPL_CYCLE_COLLECTION(ObjectInterfaceRequestorShim, mContent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ObjectInterfaceRequestorShim)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ObjectInterfaceRequestorShim)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ObjectInterfaceRequestorShim)
+
+NS_IMETHODIMP
+ObjectInterfaceRequestorShim::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ nsIChannelEventSink* sink = this;
+ *aResult = sink;
+ NS_ADDREF(sink);
+ return NS_OK;
+ }
+ if (aIID.Equals(NS_GET_IID(nsIObjectLoadingContent))) {
+ nsIObjectLoadingContent* olc = mContent;
+ *aResult = olc;
+ NS_ADDREF(olc);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+// nsIChannelEventSink
+NS_IMETHODIMP
+nsObjectLoadingContent::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ // If we're already busy with a new load, or have no load at all,
+ // cancel the redirect.
+ if (!mChannel || aOldChannel != mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ mChannel = aNewChannel;
+
+ if (mFinalListener) {
+ nsCOMPtr<nsIChannelEventSink> sink(do_QueryInterface(mFinalListener));
+ MOZ_RELEASE_ASSERT(sink, "mFinalListener isn't nsIChannelEventSink?");
+ if (mType != eType_Document) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Not a DocumentChannel load, but we're getting a "
+ "AsyncOnChannelRedirect with a mFinalListener?");
+ return NS_BINDING_ABORTED;
+ }
+
+ return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
+ }
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+// <public>
+ElementState nsObjectLoadingContent::ObjectState() const {
+ switch (mType) {
+ case eType_Loading:
+ return ElementState::LOADING;
+ case eType_Image:
+ return ImageState();
+ case eType_FakePlugin:
+ case eType_Document: {
+ // These are OK. If documents start to load successfully, they display
+ // something, and are thus not broken in this sense. The same goes for
+ // plugins.
+ ElementState states = ElementState();
+ if (mLoadingSyntheticDocument) {
+ states |= ElementState::LOADING;
+ states |= ImageState();
+ }
+ return states;
+ }
+ case eType_Fallback:
+ // This may end up handled as TYPE_NULL or as a "special" type, as
+ // chosen by the layout.use-plugin-fallback pref.
+ return ElementState();
+ case eType_Null:
+ return ElementState::BROKEN;
+ }
+ MOZ_ASSERT_UNREACHABLE("unknown type?");
+ return ElementState::LOADING;
+}
+
+void nsObjectLoadingContent::MaybeRewriteYoutubeEmbed(nsIURI* aURI,
+ nsIURI* aBaseURI,
+ nsIURI** aRewrittenURI) {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ // We're only interested in switching out embed and object tags
+ if (!thisContent->NodeInfo()->Equals(nsGkAtoms::embed) &&
+ !thisContent->NodeInfo()->Equals(nsGkAtoms::object)) {
+ return;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ // If we can't analyze the URL, just pass on through.
+ if (!tldService) {
+ NS_WARNING("Could not get TLD service!");
+ return;
+ }
+
+ nsAutoCString currentBaseDomain;
+ bool ok = NS_SUCCEEDED(tldService->GetBaseDomain(aURI, 0, currentBaseDomain));
+ if (!ok) {
+ // Data URIs (commonly used for things like svg embeds) won't parse
+ // correctly, so just fail silently here.
+ return;
+ }
+
+ // See if URL is referencing youtube
+ if (!currentBaseDomain.EqualsLiteral("youtube.com") &&
+ !currentBaseDomain.EqualsLiteral("youtube-nocookie.com")) {
+ return;
+ }
+
+ // We should only rewrite URLs with paths starting with "/v/", as we shouldn't
+ // touch object nodes with "/embed/" urls that already do that right thing.
+ nsAutoCString path;
+ aURI->GetPathQueryRef(path);
+ if (!StringBeginsWith(path, "/v/"_ns)) {
+ return;
+ }
+
+ // See if requester is planning on using the JS API.
+ nsAutoCString uri;
+ nsresult rv = aURI->GetSpec(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Some YouTube urls have parameters in path components, e.g.
+ // http://youtube.com/embed/7LcUOEP7Brc&start=35. These URLs work with flash,
+ // but break iframe/object embedding. If this situation occurs with rewritten
+ // URLs, convert the parameters to query in order to make the video load
+ // correctly as an iframe. In either case, warn about it in the
+ // developer console.
+ int32_t ampIndex = uri.FindChar('&', 0);
+ bool replaceQuery = false;
+ if (ampIndex != -1) {
+ int32_t qmIndex = uri.FindChar('?', 0);
+ if (qmIndex == -1 || qmIndex > ampIndex) {
+ replaceQuery = true;
+ }
+ }
+
+ // If we're pref'd off, return after telemetry has been logged.
+ if (!Preferences::GetBool(kPrefYoutubeRewrite)) {
+ return;
+ }
+
+ nsAutoString utf16OldURI = NS_ConvertUTF8toUTF16(uri);
+ // If we need to convert the URL, it means an ampersand comes first.
+ // Use the index we found earlier.
+ if (replaceQuery) {
+ // Replace question marks with ampersands.
+ uri.ReplaceChar('?', '&');
+ // Replace the first ampersand with a question mark.
+ uri.SetCharAt('?', ampIndex);
+ }
+ // Switch out video access url formats, which should possibly allow HTML5
+ // video loading.
+ uri.ReplaceSubstring("/v/"_ns, "/embed/"_ns);
+ nsAutoString utf16URI = NS_ConvertUTF8toUTF16(uri);
+ rv = nsContentUtils::NewURIWithDocumentCharset(
+ aRewrittenURI, utf16URI, thisContent->OwnerDoc(), aBaseURI);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ AutoTArray<nsString, 2> params = {utf16OldURI, utf16URI};
+ const char* msgName;
+ // If there's no query to rewrite, just notify in the developer console
+ // that we're changing the embed.
+ if (!replaceQuery) {
+ msgName = "RewriteYouTubeEmbed";
+ } else {
+ msgName = "RewriteYouTubeEmbedPathParams";
+ }
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Plugins"_ns, thisContent->OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES, msgName, params);
+}
+
+bool nsObjectLoadingContent::CheckLoadPolicy(int16_t* aContentPolicy) {
+ if (!aContentPolicy || !mURI) {
+ MOZ_ASSERT_UNREACHABLE("Doing it wrong");
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ Document* doc = thisContent->OwnerDoc();
+
+ nsContentPolicyType contentPolicyType = GetContentPolicyType();
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
+ doc->NodePrincipal(), // loading principal
+ doc->NodePrincipal(), // triggering principal
+ thisContent, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ contentPolicyType);
+
+ *aContentPolicy = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(mURI, secCheckLoadInfo, mContentType,
+ aContentPolicy,
+ nsContentUtils::GetContentPolicy());
+ NS_ENSURE_SUCCESS(rv, false);
+ if (NS_CP_REJECTED(*aContentPolicy)) {
+ LOG(("OBJLC [%p]: Content policy denied load of %s", this,
+ mURI->GetSpecOrDefault().get()));
+ return false;
+ }
+
+ return true;
+}
+
+bool nsObjectLoadingContent::CheckProcessPolicy(int16_t* aContentPolicy) {
+ if (!aContentPolicy) {
+ MOZ_ASSERT_UNREACHABLE("Null out variable");
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ Document* doc = thisContent->OwnerDoc();
+
+ nsContentPolicyType objectType;
+ switch (mType) {
+ case eType_Image:
+ objectType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+ break;
+ case eType_Document:
+ objectType = nsIContentPolicy::TYPE_DOCUMENT;
+ break;
+ case eType_Fallback:
+ case eType_FakePlugin:
+ objectType = GetContentPolicyType();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Calling checkProcessPolicy with an unloadable "
+ "type");
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
+ doc->NodePrincipal(), // loading principal
+ doc->NodePrincipal(), // triggering principal
+ thisContent, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ objectType);
+
+ *aContentPolicy = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentProcessPolicy(
+ mURI ? mURI : mBaseURI, secCheckLoadInfo, mContentType, aContentPolicy,
+ nsContentUtils::GetContentPolicy());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (NS_CP_REJECTED(*aContentPolicy)) {
+ LOG(("OBJLC [%p]: CheckContentProcessPolicy rejected load", this));
+ return false;
+ }
+
+ return true;
+}
+
+nsObjectLoadingContent::ParameterUpdateFlags
+nsObjectLoadingContent::UpdateObjectParameters() {
+ nsCOMPtr<Element> thisElement =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ MOZ_ASSERT(thisElement, "Must be an Element");
+
+ uint32_t caps = GetCapabilities();
+ LOG(("OBJLC [%p]: Updating object parameters", this));
+
+ nsresult rv;
+ nsAutoCString newMime;
+ nsAutoString typeAttr;
+ nsCOMPtr<nsIURI> newURI;
+ nsCOMPtr<nsIURI> newBaseURI;
+ ObjectType newType;
+ // Set if this state can't be used to load anything, forces eType_Null
+ bool stateInvalid = false;
+ // Indicates what parameters changed.
+ // eParamChannelChanged - means parameters that affect channel opening
+ // decisions changed
+ // eParamStateChanged - means anything that affects what content we load
+ // changed, even if the channel we'd open remains the
+ // same.
+ //
+ // State changes outside of the channel parameters only matter if we've
+ // already opened a channel or tried to instantiate content, whereas channel
+ // parameter changes require re-opening the channel even if we haven't gotten
+ // that far.
+ nsObjectLoadingContent::ParameterUpdateFlags retval = eParamNoChange;
+
+ ///
+ /// Initial MIME Type
+ ///
+
+ if (caps & eFallbackIfClassIDPresent) {
+ nsAutoString classIDAttr;
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classIDAttr);
+ // We don't support class ID plugin references, so we should always treat
+ // having class Ids as attributes as invalid, and fallback accordingly.
+ if (!classIDAttr.IsEmpty()) {
+ newMime.Truncate();
+ stateInvalid = true;
+ }
+ }
+
+ ///
+ /// Codebase
+ ///
+
+ nsAutoString codebaseStr;
+ nsIURI* docBaseURI = thisElement->GetBaseURI();
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebaseStr);
+
+ if (!codebaseStr.IsEmpty()) {
+ rv = nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(newBaseURI), codebaseStr, thisElement->OwnerDoc(),
+ docBaseURI);
+ if (NS_FAILED(rv)) {
+ // Malformed URI
+ LOG(
+ ("OBJLC [%p]: Could not parse plugin's codebase as a URI, "
+ "will use document baseURI instead",
+ this));
+ }
+ }
+
+ // If we failed to build a valid URI, use the document's base URI
+ if (!newBaseURI) {
+ newBaseURI = docBaseURI;
+ }
+
+ nsAutoString rawTypeAttr;
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::type, rawTypeAttr);
+ if (!rawTypeAttr.IsEmpty()) {
+ typeAttr = rawTypeAttr;
+ nsAutoString params;
+ nsAutoString mime;
+ nsContentUtils::SplitMimeType(rawTypeAttr, mime, params);
+ CopyUTF16toUTF8(mime, newMime);
+ }
+
+ ///
+ /// URI
+ ///
+
+ nsAutoString uriStr;
+ // Different elements keep this in various locations
+ if (thisElement->NodeInfo()->Equals(nsGkAtoms::object)) {
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr);
+ } else if (thisElement->NodeInfo()->Equals(nsGkAtoms::embed)) {
+ thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized plugin-loading tag");
+ }
+
+ mRewrittenYoutubeEmbed = false;
+ // Note that the baseURI changing could affect the newURI, even if uriStr did
+ // not change.
+ if (!uriStr.IsEmpty()) {
+ rv = nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(newURI), uriStr, thisElement->OwnerDoc(), newBaseURI);
+ nsCOMPtr<nsIURI> rewrittenURI;
+ MaybeRewriteYoutubeEmbed(newURI, newBaseURI, getter_AddRefs(rewrittenURI));
+ if (rewrittenURI) {
+ newURI = rewrittenURI;
+ mRewrittenYoutubeEmbed = true;
+ newMime = "text/html"_ns;
+ }
+
+ if (NS_FAILED(rv)) {
+ stateInvalid = true;
+ }
+ }
+
+ ///
+ /// Check if the original (pre-channel) content-type or URI changed, and
+ /// record mOriginal{ContentType,URI}
+ ///
+
+ if ((mOriginalContentType != newMime) || !URIEquals(mOriginalURI, newURI)) {
+ // These parameters changing requires re-opening the channel, so don't
+ // consider the currently-open channel below
+ // XXX(johns): Changing the mime type might change our decision on whether
+ // or not we load a channel, so we count changes to it as a
+ // channel parameter change for the sake of simplicity.
+ retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
+ LOG(("OBJLC [%p]: Channel parameters changed", this));
+ }
+ mOriginalContentType = newMime;
+ mOriginalURI = newURI;
+
+ ///
+ /// If we have a channel, see if its MIME type should take precendence and
+ /// check the final (redirected) URL
+ ///
+
+ // If we have a loaded channel and channel parameters did not change, use it
+ // to determine what we would load.
+ bool useChannel = mChannelLoaded && !(retval & eParamChannelChanged);
+ // If we have a channel and are type loading, as opposed to having an existing
+ // channel for a previous load.
+ bool newChannel = useChannel && mType == eType_Loading;
+
+ RefPtr<DocumentChannel> documentChannel = do_QueryObject(mChannel);
+ if (newChannel && documentChannel) {
+ // If we've got a DocumentChannel which is marked as loaded using
+ // `mChannelLoaded`, we are currently in the middle of a
+ // `UpgradeLoadToDocument`.
+ //
+ // As we don't have the real mime-type from the channel, handle this by
+ // using `newMime`.
+ newMime = TEXT_HTML;
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ GetTypeOfContent(newMime, mSkipFakePlugins) == eType_Document,
+ "How is text/html not eType_Document?");
+ } else if (newChannel && mChannel) {
+ nsCString channelType;
+ rv = mChannel->GetContentType(channelType);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE("GetContentType failed");
+ stateInvalid = true;
+ channelType.Truncate();
+ }
+
+ LOG(("OBJLC [%p]: Channel has a content type of %s", this,
+ channelType.get()));
+
+ bool binaryChannelType = false;
+ if (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT)) {
+ channelType = APPLICATION_OCTET_STREAM;
+ mChannel->SetContentType(channelType);
+ binaryChannelType = true;
+ } else if (channelType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
+ channelType.EqualsASCII(BINARY_OCTET_STREAM)) {
+ binaryChannelType = true;
+ }
+
+ // Channel can change our URI through redirection
+ rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE("NS_GetFinalChannelURI failure");
+ stateInvalid = true;
+ }
+
+ ObjectType typeHint = newMime.IsEmpty()
+ ? eType_Null
+ : GetTypeOfContent(newMime, mSkipFakePlugins);
+
+ //
+ // In order of preference:
+ //
+ // 1) Use our type hint if it matches a plugin
+ // 2) If we have eAllowPluginSkipChannel, use the uri file extension if
+ // it matches a plugin
+ // 3) If the channel returns a binary stream type:
+ // 3a) If we have a type non-null non-document type hint, use that
+ // 3b) If the uri file extension matches a plugin type, use that
+ // 4) Use the channel type
+
+ bool overrideChannelType = false;
+ if (IsPluginType(typeHint)) {
+ LOG(("OBJLC [%p]: Using plugin type hint in favor of any channel type",
+ this));
+ overrideChannelType = true;
+ } else if (binaryChannelType && typeHint != eType_Null) {
+ if (typeHint == eType_Document) {
+ if (StaticPrefs::
+ browser_opaqueResponseBlocking_syntheticBrowsingContext_AtStartup() &&
+ imgLoader::SupportImageWithMimeType(newMime)) {
+ LOG(
+ ("OBJLC [%p]: Using type hint in favor of binary channel type "
+ "(Image Document)",
+ this));
+ overrideChannelType = true;
+ }
+ } else {
+ LOG(
+ ("OBJLC [%p]: Using type hint in favor of binary channel type "
+ "(Non-Image Document)",
+ this));
+ overrideChannelType = true;
+ }
+ }
+
+ if (overrideChannelType) {
+ // Set the type we'll use for dispatch on the channel. Otherwise we could
+ // end up trying to dispatch to a nsFrameLoader, which will complain that
+ // it couldn't find a way to handle application/octet-stream
+ nsAutoCString parsedMime, dummy;
+ NS_ParseResponseContentType(newMime, parsedMime, dummy);
+ if (!parsedMime.IsEmpty()) {
+ mChannel->SetContentType(parsedMime);
+ }
+ } else {
+ newMime = channelType;
+ }
+ } else if (newChannel) {
+ LOG(("OBJLC [%p]: We failed to open a channel, marking invalid", this));
+ stateInvalid = true;
+ }
+
+ ///
+ /// Determine final type
+ ///
+ // In order of preference:
+ // 1) If we have attempted channel load, or set stateInvalid above, the type
+ // is always null (fallback)
+ // 2) If we have a loaded channel, we grabbed its mimeType above, use that
+ // type.
+ // 3) If we have a plugin type and no URI, use that type.
+ // 4) If we have a plugin type and eAllowPluginSkipChannel, use that type.
+ // 5) if we have a URI, set type to loading to indicate we'd need a channel
+ // to proceed.
+ // 6) Otherwise, type null to indicate unloadable content (fallback)
+ //
+
+ ObjectType newMime_Type = GetTypeOfContent(newMime, mSkipFakePlugins);
+
+ if (stateInvalid) {
+ newType = eType_Null;
+ LOG(("OBJLC [%p]: NewType #0: %s - %u", this, newMime.get(), newType));
+ newMime.Truncate();
+ } else if (newChannel) {
+ // If newChannel is set above, we considered it in setting newMime
+ newType = newMime_Type;
+ LOG(("OBJLC [%p]: NewType #1: %s - %u", this, newMime.get(), newType));
+ LOG(("OBJLC [%p]: Using channel type", this));
+ } else if (((caps & eAllowPluginSkipChannel) || !newURI) &&
+ IsPluginType(newMime_Type)) {
+ newType = newMime_Type;
+ LOG(("OBJLC [%p]: NewType #2: %s - %u", this, newMime.get(), newType));
+ LOG(("OBJLC [%p]: Plugin type with no URI, skipping channel load", this));
+ } else if (newURI &&
+ (mOriginalContentType.IsEmpty() || newMime_Type != eType_Null)) {
+ // We could potentially load this if we opened a channel on mURI, indicate
+ // this by leaving type as loading.
+ //
+ // If a MIME type was requested in the tag, but we have decided to set load
+ // type to null, ignore (otherwise we'll default to document type loading).
+ newType = eType_Loading;
+ LOG(("OBJLC [%p]: NewType #3: %u", this, newType));
+ } else {
+ // Unloadable - no URI, and no plugin/MIME type. Non-plugin types (images,
+ // documents) always load with a channel.
+ newType = eType_Null;
+ LOG(("OBJLC [%p]: NewType #4: %u", this, newType));
+ }
+
+ mLoadingSyntheticDocument =
+ newType == eType_Document &&
+ StaticPrefs::
+ browser_opaqueResponseBlocking_syntheticBrowsingContext_AtStartup() &&
+ imgLoader::SupportImageWithMimeType(newMime);
+
+ ///
+ /// Handle existing channels
+ ///
+
+ if (useChannel && newType == eType_Loading) {
+ // We decided to use a channel, and also that the previous channel is still
+ // usable, so re-use the existing values.
+ newType = mType;
+ LOG(("OBJLC [%p]: NewType #5: %u", this, newType));
+ newMime = mContentType;
+ newURI = mURI;
+ } else if (useChannel && !newChannel) {
+ // We have an existing channel, but did not decide to use one.
+ retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
+ useChannel = false;
+ }
+
+ ///
+ /// Update changed values
+ ///
+
+ if (newType != mType) {
+ retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
+ LOG(("OBJLC [%p]: Type changed from %u -> %u", this, mType, newType));
+ mType = newType;
+ }
+
+ if (!URIEquals(mBaseURI, newBaseURI)) {
+ LOG(("OBJLC [%p]: Object effective baseURI changed", this));
+ mBaseURI = newBaseURI;
+ }
+
+ if (!URIEquals(newURI, mURI)) {
+ retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
+ LOG(("OBJLC [%p]: Object effective URI changed", this));
+ mURI = newURI;
+ }
+
+ // We don't update content type when loading, as the type is not final and we
+ // don't want to superfluously change between mOriginalContentType ->
+ // mContentType when doing |obj.data = obj.data| with a channel and differing
+ // type.
+ if (mType != eType_Loading && mContentType != newMime) {
+ retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
+ retval = (ParameterUpdateFlags)(retval | eParamContentTypeChanged);
+ LOG(("OBJLC [%p]: Object effective mime type changed (%s -> %s)", this,
+ mContentType.get(), newMime.get()));
+ mContentType = newMime;
+ }
+
+ // If we decided to keep using info from an old channel, but also that state
+ // changed, we need to invalidate it.
+ if (useChannel && !newChannel && (retval & eParamStateChanged)) {
+ mType = eType_Loading;
+ retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
+ }
+
+ return retval;
+}
+
+// Used by PluginDocument to kick off our initial load from the already-opened
+// channel.
+NS_IMETHODIMP
+nsObjectLoadingContent::InitializeFromChannel(nsIRequest* aChannel) {
+ LOG(("OBJLC [%p] InitializeFromChannel: %p", this, aChannel));
+ if (mType != eType_Loading || mChannel) {
+ // We could technically call UnloadObject() here, if consumers have a valid
+ // reason for wanting to call this on an already-loaded tag.
+ MOZ_ASSERT_UNREACHABLE("Should not have begun loading at this point");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Because we didn't open this channel from an initial LoadObject, we'll
+ // update our parameters now, so the OnStartRequest->LoadObject doesn't
+ // believe our src/type suddenly changed.
+ UpdateObjectParameters();
+ // But we always want to load from a channel, in this case.
+ mType = eType_Loading;
+ mChannel = do_QueryInterface(aChannel);
+ NS_ASSERTION(mChannel, "passed a request that is not a channel");
+
+ // OnStartRequest will now see we have a channel in the loading state, and
+ // call into LoadObject. There's a possibility LoadObject will decide not to
+ // load anything from a channel - it will call CloseChannel() in that case.
+ return NS_OK;
+}
+
+// Only OnStartRequest should be passing the channel parameter
+nsresult nsObjectLoadingContent::LoadObject(bool aNotify, bool aForceLoad) {
+ return LoadObject(aNotify, aForceLoad, nullptr);
+}
+
+nsresult nsObjectLoadingContent::LoadObject(bool aNotify, bool aForceLoad,
+ nsIRequest* aLoadingChannel) {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+ Document* doc = thisContent->OwnerDoc();
+ nsresult rv = NS_OK;
+
+ // Per bug 1318303, if the parent document is not active, load the alternative
+ // and return.
+ if (!doc->IsCurrentActiveDocument()) {
+ // Since this can be triggered on change of attributes, make sure we've
+ // unloaded whatever is loaded first.
+ UnloadObject();
+ ObjectType oldType = mType;
+ mType = eType_Fallback;
+ ConfigureFallback();
+ NotifyStateChanged(oldType, ObjectState(), true, false);
+ return NS_OK;
+ }
+
+ // XXX(johns): In these cases, we refuse to touch our content and just
+ // remain unloaded, as per legacy behavior. It would make more sense to
+ // load fallback content initially and refuse to ever change state again.
+ if (doc->IsBeingUsedAsImage()) {
+ return NS_OK;
+ }
+
+ if (doc->IsLoadedAsData() && !doc->IsStaticDocument()) {
+ return NS_OK;
+ }
+ if (doc->IsStaticDocument()) {
+ // We only allow image loads in static documents, but we need to let the
+ // eType_Loading state go through too while we do so.
+ if (mType != eType_Image && mType != eType_Loading) {
+ return NS_OK;
+ }
+ }
+
+ LOG(("OBJLC [%p]: LoadObject called, notify %u, forceload %u, channel %p",
+ this, aNotify, aForceLoad, aLoadingChannel));
+
+ // We can't re-use an already open channel, but aForceLoad may make us try
+ // to load a plugin without any changes in channel state.
+ if (aForceLoad && mChannelLoaded) {
+ CloseChannel();
+ mChannelLoaded = false;
+ }
+
+ // Save these for NotifyStateChanged();
+ ElementState oldState = ObjectState();
+ ObjectType oldType = mType;
+
+ ParameterUpdateFlags stateChange = UpdateObjectParameters();
+
+ if (!stateChange && !aForceLoad) {
+ return NS_OK;
+ }
+
+ ///
+ /// State has changed, unload existing content and attempt to load new type
+ ///
+ LOG(("OBJLC [%p]: LoadObject - plugin state changed (%u)", this,
+ stateChange));
+
+ // We synchronously start/stop plugin instances below, which may spin the
+ // event loop. Re-entering into the load is fine, but at that point the
+ // original load call needs to abort when unwinding
+ // NOTE this is located *after* the state change check, a subsequent load
+ // with no subsequently changed state will be a no-op.
+ if (mIsLoading) {
+ LOG(("OBJLC [%p]: Re-entering into LoadObject", this));
+ }
+ mIsLoading = true;
+ AutoSetLoadingToFalse reentryCheck(this);
+
+ // Unload existing content, keeping in mind stopping plugins might spin the
+ // event loop. Note that we check for still-open channels below
+ UnloadObject(false); // Don't reset state
+ if (!mIsLoading) {
+ // The event loop must've spun and re-entered into LoadObject, which
+ // finished the load
+ LOG(("OBJLC [%p]: Re-entered into LoadObject, aborting outer load", this));
+ return NS_OK;
+ }
+
+ // Determine what's going on with our channel.
+ if (stateChange & eParamChannelChanged) {
+ // If the channel params changed, throw away the channel, but unset
+ // mChannelLoaded so we'll still try to open a new one for this load if
+ // necessary
+ CloseChannel();
+ mChannelLoaded = false;
+ } else if (mType == eType_Null && mChannel) {
+ // If we opened a channel but then failed to find a loadable state, throw it
+ // away. mChannelLoaded will indicate that we tried to load a channel at one
+ // point so we wont recurse
+ CloseChannel();
+ } else if (mType == eType_Loading && mChannel) {
+ // We're still waiting on a channel load, already opened one, and
+ // channel parameters didn't change
+ return NS_OK;
+ } else if (mChannelLoaded && mChannel != aLoadingChannel) {
+ // The only time we should have a loaded channel with a changed state is
+ // when the channel has just opened -- in which case this call should
+ // have originated from OnStartRequest
+ MOZ_ASSERT_UNREACHABLE(
+ "Loading with a channel, but state doesn't make sense");
+ return NS_OK;
+ }
+
+ //
+ // Security checks
+ //
+
+ if (mType != eType_Null && mType != eType_Fallback) {
+ bool allowLoad = true;
+ int16_t contentPolicy = nsIContentPolicy::ACCEPT;
+ // If mChannelLoaded is set we presumably already passed load policy
+ // If mType == eType_Loading then we call OpenChannel() which internally
+ // creates a new channel and calls asyncOpen() on that channel which
+ // then enforces content policy checks.
+ if (allowLoad && mURI && !mChannelLoaded && mType != eType_Loading) {
+ allowLoad = CheckLoadPolicy(&contentPolicy);
+ }
+ // If we're loading a type now, check ProcessPolicy. Note that we may check
+ // both now in the case of plugins whose type is determined before opening a
+ // channel.
+ if (allowLoad && mType != eType_Loading) {
+ allowLoad = CheckProcessPolicy(&contentPolicy);
+ }
+
+ // Content policy implementations can mutate the DOM, check for re-entry
+ if (!mIsLoading) {
+ LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load",
+ this));
+ return NS_OK;
+ }
+
+ // Load denied, switch to null
+ if (!allowLoad) {
+ LOG(("OBJLC [%p]: Load denied by policy", this));
+ mType = eType_Null;
+ }
+ }
+
+ // Don't allow view-source scheme.
+ // view-source is the only scheme to which this applies at the moment due to
+ // potential timing attacks to read data from cross-origin documents. If this
+ // widens we should add a protocol flag for whether the scheme is only allowed
+ // in top and use something like nsNetUtil::NS_URIChainHasFlags.
+ if (mType != eType_Null) {
+ nsCOMPtr<nsIURI> tempURI = mURI;
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
+ while (nestedURI) {
+ // view-source should always be an nsINestedURI, loop and check the
+ // scheme on this and all inner URIs that are also nested URIs.
+ if (tempURI->SchemeIs("view-source")) {
+ LOG(("OBJLC [%p]: Blocking as effective URI has view-source scheme",
+ this));
+ mType = eType_Null;
+ break;
+ }
+
+ nestedURI->GetInnerURI(getter_AddRefs(tempURI));
+ nestedURI = do_QueryInterface(tempURI);
+ }
+ }
+
+ // Items resolved as Image/Document are not candidates for content blocking,
+ // as well as invalid plugins (they will not have the mContentType set).
+ if ((mType == eType_Null || IsPluginType(mType)) && ShouldBlockContent()) {
+ LOG(("OBJLC [%p]: Enable content blocking", this));
+ mType = eType_Loading;
+ }
+
+ // Sanity check: We shouldn't have any loaded resources, pending events, or
+ // a final listener at this point
+ if (mFrameLoader || mFinalListener) {
+ MOZ_ASSERT_UNREACHABLE("Trying to load new plugin with existing content");
+ return NS_OK;
+ }
+
+ // More sanity-checking:
+ // If mChannel is set, mChannelLoaded should be set, and vice-versa
+ if (mType != eType_Null && !!mChannel != mChannelLoaded) {
+ MOZ_ASSERT_UNREACHABLE("Trying to load with bad channel state");
+ return NS_OK;
+ }
+
+ ///
+ /// Attempt to load new type
+ ///
+
+ // Cache the current attributes and parameters.
+ if (mType == eType_Null) {
+ rv = BuildParametersArray();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We don't set mFinalListener until OnStartRequest has been called, to
+ // prevent re-entry ugliness with CloseChannel()
+ nsCOMPtr<nsIStreamListener> finalListener;
+ switch (mType) {
+ case eType_Image:
+ if (!mChannel) {
+ // We have a LoadImage() call, but UpdateObjectParameters requires a
+ // channel for images, so this is not a valid state.
+ MOZ_ASSERT_UNREACHABLE("Attempting to load image without a channel?");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ rv = LoadImageWithChannel(mChannel, getter_AddRefs(finalListener));
+ // finalListener will receive OnStartRequest below
+ break;
+ case eType_Document: {
+ if (!mChannel) {
+ // We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters
+ // requires documents have a channel, so this is not a valid state.
+ MOZ_ASSERT_UNREACHABLE(
+ "Attempting to load a document without a "
+ "channel");
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = SetupDocShell(mURI);
+ if (!docShell) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ // We're loading a document, so we have to set LOAD_DOCUMENT_URI
+ // (especially important for firing onload)
+ nsLoadFlags flags = 0;
+ mChannel->GetLoadFlags(&flags);
+ flags |= nsIChannel::LOAD_DOCUMENT_URI;
+ mChannel->SetLoadFlags(flags);
+
+ nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(docShell));
+ NS_ASSERTION(req, "Docshell must be an ifreq");
+
+ nsCOMPtr<nsIURILoader> uriLoader(components::URILoader::Service());
+ if (NS_WARN_IF(!uriLoader)) {
+ MOZ_ASSERT_UNREACHABLE("Failed to get uriLoader service");
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ break;
+ }
+
+ rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req,
+ getter_AddRefs(finalListener));
+ // finalListener will receive OnStartRequest either below, or if
+ // `mChannel` is a `DocumentChannel`, it will be received after
+ // RedirectToRealChannel.
+ } break;
+ case eType_Loading:
+ // If our type remains Loading, we need a channel to proceed
+ rv = OpenChannel();
+ if (NS_FAILED(rv)) {
+ LOG(("OBJLC [%p]: OpenChannel returned failure (%" PRIu32 ")", this,
+ static_cast<uint32_t>(rv)));
+ }
+ break;
+ case eType_Null:
+ case eType_Fallback:
+ // Handled below, silence compiler warnings
+ break;
+ case eType_FakePlugin:
+ // We're now in the process of removing FakePlugin. See bug 1529133.
+ MOZ_CRASH(
+ "Shouldn't reach here! This means there's a fakeplugin trying to be "
+ "loaded.");
+ }
+
+ //
+ // Loaded, handle notifications and fallback
+ //
+ if (NS_FAILED(rv)) {
+ // If we failed in the loading hunk above, switch to null (empty) region
+ LOG(("OBJLC [%p]: Loading failed, switching to null", this));
+ mType = eType_Null;
+ }
+
+ // If we didn't load anything, handle switching to fallback state
+ if (mType == eType_Fallback || mType == eType_Null) {
+ LOG(("OBJLC [%p]: Switching to fallback state", this));
+ MOZ_ASSERT(!mFrameLoader, "switched to fallback but also loaded something");
+
+ MaybeFireErrorEvent();
+
+ if (mChannel) {
+ // If we were loading with a channel but then failed over, throw it away
+ CloseChannel();
+ }
+
+ // Don't try to initialize plugins or final listener below
+ finalListener = nullptr;
+
+ ConfigureFallback();
+ }
+
+ // Notify of our final state
+ NotifyStateChanged(oldType, oldState, aNotify, false);
+ NS_ENSURE_TRUE(mIsLoading, NS_OK);
+
+ //
+ // Spawning plugins and dispatching to the final listener may re-enter, so are
+ // delayed until after we fire a notification, to prevent missing
+ // notifications or firing them out of order.
+ //
+ // Note that we ensured that we entered into LoadObject() from
+ // ::OnStartRequest above when loading with a channel.
+ //
+
+ rv = NS_OK;
+ if (finalListener) {
+ NS_ASSERTION(mType != eType_Null && mType != eType_Loading,
+ "We should not have a final listener with a non-loaded type");
+ mFinalListener = finalListener;
+
+ // If we're a DocumentChannel load, hold off on firing the `OnStartRequest`
+ // callback, as we haven't received it yet from our caller.
+ RefPtr<DocumentChannel> documentChannel = do_QueryObject(mChannel);
+ if (documentChannel) {
+ MOZ_ASSERT(
+ mType == eType_Document,
+ "We have a DocumentChannel here but aren't loading a document?");
+ } else {
+ rv = finalListener->OnStartRequest(mChannel);
+ }
+ }
+
+ if ((NS_FAILED(rv) && rv != NS_ERROR_PARSED_DATA_CACHED) && mIsLoading) {
+ // Since we've already notified of our transition, we can just Unload and
+ // call ConfigureFallback (which will notify again)
+ oldType = mType;
+ mType = eType_Fallback;
+ UnloadObject(false);
+ NS_ENSURE_TRUE(mIsLoading, NS_OK);
+ CloseChannel();
+ ConfigureFallback();
+ NotifyStateChanged(oldType, ObjectState(), true, false);
+ }
+
+ return NS_OK;
+}
+
+// This call can re-enter when dealing with plugin listeners
+nsresult nsObjectLoadingContent::CloseChannel() {
+ if (mChannel) {
+ LOG(("OBJLC [%p]: Closing channel\n", this));
+ // Null the values before potentially-reentering, and ensure they survive
+ // the call
+ nsCOMPtr<nsIChannel> channelGrip(mChannel);
+ nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
+ mChannel = nullptr;
+ mFinalListener = nullptr;
+ channelGrip->CancelWithReason(NS_BINDING_ABORTED,
+ "nsObjectLoadingContent::CloseChannel"_ns);
+ if (listenerGrip) {
+ // mFinalListener is only set by LoadObject after OnStartRequest, or
+ // by OnStartRequest in the case of late-opened plugin streams
+ listenerGrip->OnStopRequest(channelGrip, NS_BINDING_ABORTED);
+ }
+ }
+ return NS_OK;
+}
+
+bool nsObjectLoadingContent::IsAboutBlankLoadOntoInitialAboutBlank(
+ nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) {
+ return NS_IsAboutBlank(aURI) && aInheritPrincipal &&
+ (!mFrameLoader || !mFrameLoader->GetExistingDocShell() ||
+ mFrameLoader->GetExistingDocShell()
+ ->IsAboutBlankLoadOntoInitialAboutBlank(aURI, aInheritPrincipal,
+ aPrincipalToInherit));
+}
+
+nsresult nsObjectLoadingContent::OpenChannel() {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+ Document* doc = thisContent->OwnerDoc();
+ NS_ASSERTION(doc, "No owner document?");
+
+ nsresult rv;
+ mChannel = nullptr;
+
+ // E.g. mms://
+ if (!mURI || !CanHandleURI(mURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
+ nsCOMPtr<nsIChannel> chan;
+ RefPtr<ObjectInterfaceRequestorShim> shim =
+ new ObjectInterfaceRequestorShim(this);
+
+ bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ thisContent->NodePrincipal(), // aLoadState->PrincipalToInherit()
+ mURI, // aLoadState->URI()
+ true, // aInheritForAboutBlank
+ false); // aForceInherit
+
+ bool inheritPrincipal = inheritAttrs && !SchemeIsData(mURI);
+
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ if (inheritPrincipal) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ nsContentPolicyType contentPolicyType = GetContentPolicyType();
+ // The setting of LOAD_BYPASS_SERVICE_WORKER here is now an optimization.
+ // ServiceWorkerInterceptController::ShouldPrepareForIntercept does a more
+ // expensive check of BrowsingContext ancestors to look for object/embed.
+ nsLoadFlags loadFlags = nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
+ nsIRequest::LOAD_HTML_OBJECT_DATA;
+ uint32_t sandboxFlags = doc->GetSandboxFlags();
+
+ // For object loads we store the CSP that potentially needs to
+ // be inherited, e.g. in case we are loading an opaque origin
+ // like a data: URI. The actual inheritance check happens within
+ // Document::InitCSP(). Please create an actual copy of the CSP
+ // (do not share the same reference) otherwise a Meta CSP of an
+ // opaque origin will incorrectly be propagated to the embedding
+ // document.
+ RefPtr<nsCSPContext> cspToInherit;
+ if (nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp()) {
+ cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
+ }
+
+ // --- Create LoadInfo
+ RefPtr<LoadInfo> loadInfo = new LoadInfo(
+ /*aLoadingPrincipal = aLoadingContext->NodePrincipal() */ nullptr,
+ /*aTriggeringPrincipal = aLoadingPrincipal */ nullptr,
+ /*aLoadingContext = */ thisContent,
+ /*aSecurityFlags = */ securityFlags,
+ /*aContentPolicyType = */ contentPolicyType,
+ /*aLoadingClientInfo = */ Nothing(),
+ /*aController = */ Nothing(),
+ /*aSandboxFlags = */ sandboxFlags);
+
+ if (inheritAttrs) {
+ loadInfo->SetPrincipalToInherit(thisContent->NodePrincipal());
+ }
+
+ if (cspToInherit) {
+ loadInfo->SetCSPToInherit(cspToInherit);
+ }
+
+ if (DocumentChannel::CanUseDocumentChannel(mURI) &&
+ !IsAboutBlankLoadOntoInitialAboutBlank(mURI, inheritPrincipal,
+ thisContent->NodePrincipal())) {
+ // --- Create LoadState
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(mURI);
+ loadState->SetPrincipalToInherit(thisContent->NodePrincipal());
+ loadState->SetTriggeringPrincipal(loadInfo->TriggeringPrincipal());
+ if (cspToInherit) {
+ loadState->SetCsp(cspToInherit);
+ }
+ loadState->SetTriggeringSandboxFlags(sandboxFlags);
+
+ // TODO(djg): This was httpChan->SetReferrerInfoWithoutClone(referrerInfo);
+ // Is the ...WithoutClone(...) important?
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
+ loadState->SetReferrerInfo(referrerInfo);
+
+ chan =
+ DocumentChannel::CreateForObject(loadState, loadInfo, loadFlags, shim);
+ MOZ_ASSERT(chan);
+ // NS_NewChannel sets the group on the channel. CreateDocumentChannel does
+ // not.
+ chan->SetLoadGroup(group);
+ } else {
+ rv = NS_NewChannelInternal(getter_AddRefs(chan), // outChannel
+ mURI, // aUri
+ loadInfo, // aLoadInfo
+ nullptr, // aPerformanceStorage
+ group, // aLoadGroup
+ shim, // aCallbacks
+ loadFlags, // aLoadFlags
+ nullptr); // aIoService
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (inheritAttrs) {
+ nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
+ loadinfo->SetPrincipalToInherit(thisContent->NodePrincipal());
+ }
+
+ // For object loads we store the CSP that potentially needs to
+ // be inherited, e.g. in case we are loading an opaque origin
+ // like a data: URI. The actual inheritance check happens within
+ // Document::InitCSP(). Please create an actual copy of the CSP
+ // (do not share the same reference) otherwise a Meta CSP of an
+ // opaque origin will incorrectly be propagated to the embedding
+ // document.
+ if (cspToInherit) {
+ nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
+ static_cast<LoadInfo*>(loadinfo.get())->SetCSPToInherit(cspToInherit);
+ }
+ };
+
+ // Referrer
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
+ if (httpChan) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
+
+ rv = httpChan->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
+ if (timedChannel) {
+ timedChannel->SetInitiatorType(thisContent->LocalName());
+ }
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChan));
+ if (cos && UserActivation::IsHandlingUserInput()) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+ }
+
+ nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
+ if (scriptChannel) {
+ // Allow execution against our context if the principals match
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ }
+
+ // AsyncOpen can fail if a file does not exist.
+ rv = chan->AsyncOpen(shim);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("OBJLC [%p]: Channel opened", this));
+ mChannel = chan;
+ return NS_OK;
+}
+
+uint32_t nsObjectLoadingContent::GetCapabilities() const {
+ return eSupportImages | eSupportPlugins | eSupportDocuments;
+}
+
+void nsObjectLoadingContent::Destroy() {
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ // Reset state so that if the element is re-appended to tree again (e.g.
+ // adopting to another document), it will reload resource again.
+ UnloadObject();
+
+ nsImageLoadingContent::Destroy();
+}
+
+/* static */
+void nsObjectLoadingContent::Traverse(nsObjectLoadingContent* tmp,
+ nsCycleCollectionTraversalCallback& cb) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader);
+}
+
+/* static */
+void nsObjectLoadingContent::Unlink(nsObjectLoadingContent* tmp) {
+ if (tmp->mFrameLoader) {
+ tmp->mFrameLoader->Destroy();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader);
+}
+
+void nsObjectLoadingContent::UnloadObject(bool aResetState) {
+ // Don't notify in CancelImageRequests until we transition to a new loaded
+ // state, but not if we've loaded the image in a synthetic browsing context.
+ CancelImageRequests(false);
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ if (aResetState) {
+ CloseChannel();
+ mChannelLoaded = false;
+ mType = eType_Loading;
+ mURI = mOriginalURI = mBaseURI = nullptr;
+ mContentType.Truncate();
+ mOriginalContentType.Truncate();
+ }
+
+ mScriptRequested = false;
+
+ mIsStopping = false;
+
+ mCachedAttributes.Clear();
+ mCachedParameters.Clear();
+
+ mSubdocumentIntrinsicSize.reset();
+ mSubdocumentIntrinsicRatio.reset();
+}
+
+void nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
+ ElementState aOldState,
+ bool aNotify,
+ bool aForceRestyle) {
+ LOG(("OBJLC [%p]: NotifyStateChanged: (%u, %" PRIx64 ") -> (%u, %" PRIx64 ")"
+ " (notify %i)",
+ this, aOldType, aOldState.GetInternalValue(), mType,
+ ObjectState().GetInternalValue(), aNotify));
+
+ nsCOMPtr<dom::Element> thisEl = AsContent()->AsElement();
+ MOZ_ASSERT(thisEl, "must be an element");
+
+ // XXX(johns): A good bit of the code below replicates UpdateState(true)
+
+ // Unfortunately, we do some state changes without notifying
+ // (e.g. in Fallback when canceling image requests), so we have to
+ // manually notify object state changes.
+ thisEl->UpdateState(aForceRestyle);
+
+ if (!aNotify) {
+ // We're done here
+ return;
+ }
+
+ Document* doc = thisEl->GetComposedDoc();
+ if (!doc) {
+ return; // Nothing to do
+ }
+
+ const ElementState newState = ObjectState();
+ if (newState == aOldState && mType == aOldType) {
+ return; // Also done.
+ }
+
+ RefPtr<PresShell> presShell = doc->GetPresShell();
+ // If there is no PresShell or it hasn't been initialized there isn't much to
+ // do.
+ if (!presShell || !presShell->DidInitialize()) {
+ return;
+ }
+
+ if (presShell && (aOldType != mType)) {
+ presShell->PostRecreateFramesFor(thisEl);
+ }
+}
+
+nsObjectLoadingContent::ObjectType nsObjectLoadingContent::GetTypeOfContent(
+ const nsCString& aMIMEType, bool aNoFakePlugin) {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ // Images, documents and (fake) plugins are always supported.
+ MOZ_ASSERT(GetCapabilities() &
+ (eSupportImages | eSupportDocuments | eSupportPlugins));
+
+ LOG(
+ ("OBJLC [%p]: calling HtmlObjectContentTypeForMIMEType: aMIMEType: %s - "
+ "thisContent: %p\n",
+ this, aMIMEType.get(), thisContent.get()));
+ auto ret =
+ static_cast<ObjectType>(nsContentUtils::HtmlObjectContentTypeForMIMEType(
+ aMIMEType, aNoFakePlugin));
+ LOG(("OBJLC [%p]: called HtmlObjectContentTypeForMIMEType\n", this));
+ return ret;
+}
+
+void nsObjectLoadingContent::CreateStaticClone(
+ nsObjectLoadingContent* aDest) const {
+ aDest->mType = mType;
+
+ if (mFrameLoader) {
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(aDest));
+ Document* doc = content->OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ doc->AddPendingFrameStaticClone(aDest, mFrameLoader);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = GetSrcURI());
+ return NS_OK;
+}
+
+void nsObjectLoadingContent::ConfigureFallback() {
+ MOZ_ASSERT(!mFrameLoader && !mChannel,
+ "ConfigureFallback called with loaded content");
+
+ // We only fallback in special cases where we are already of fallback
+ // type (e.g. removed Flash plugin use) or where something went wrong
+ // (e.g. unknown MIME type).
+ MOZ_ASSERT(mType == eType_Fallback || mType == eType_Null);
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ // There are two types of fallback:
+ // 1. HTML fallbacks are children of the <object> or <embed> DOM element.
+ // 2. The special transparent region fallback replacing Flash use.
+ // If our type is eType_Fallback (e.g. Flash use) then we use #1 if
+ // available, otherwise we use #2.
+ // If our type is eType_Null (e.g. unknown MIME type) then we use
+ // #1, otherwise the element has no size.
+ bool hasHtmlFallback = false;
+ if (thisContent->IsHTMLElement(nsGkAtoms::object)) {
+ // Do a depth-first traverse of node tree with the current element as root,
+ // looking for non-<param> elements. If we find some then we have an HTML
+ // fallback for this element.
+ for (nsIContent* child = thisContent->GetFirstChild(); child;) {
+ hasHtmlFallback =
+ hasHtmlFallback || (!child->IsHTMLElement(nsGkAtoms::param) &&
+ nsStyleUtil::IsSignificantChild(child, false));
+
+ // <object> and <embed> elements in the fallback need to StartObjectLoad.
+ // Their children should be ignored since they are part of those
+ // element's fallback.
+ if (auto embed = HTMLEmbedElement::FromNode(child)) {
+ embed->StartObjectLoad(true, true);
+ // Skip the children
+ child = child->GetNextNonChildNode(thisContent);
+ } else if (auto object = HTMLObjectElement::FromNode(child)) {
+ object->StartObjectLoad(true, true);
+ // Skip the children
+ child = child->GetNextNonChildNode(thisContent);
+ } else {
+ child = child->GetNextNode(thisContent);
+ }
+ }
+ }
+
+ // If we find an HTML fallback then we always switch type to null.
+ if (hasHtmlFallback) {
+ mType = eType_Null;
+ }
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::Reload(bool aClearActivation) {
+ if (aClearActivation) {
+ mSkipFakePlugins = false;
+ }
+
+ return LoadObject(true, true);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::SkipFakePlugins() {
+ if (!nsContentUtils::IsCallerChrome()) return NS_ERROR_NOT_AVAILABLE;
+
+ mSkipFakePlugins = true;
+
+ // If we're showing a fake plugin now, reload
+ if (mType == eType_FakePlugin) {
+ return LoadObject(true, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::UpgradeLoadToDocument(
+ nsIChannel* aRequest, BrowsingContext** aBrowsingContext) {
+ AUTO_PROFILER_LABEL("nsObjectLoadingContent::UpgradeLoadToDocument", NETWORK);
+
+ LOG(("OBJLC [%p]: UpgradeLoadToDocument", this));
+
+ if (aRequest != mChannel || !aRequest) {
+ // happens when a new load starts before the previous one got here.
+ return NS_BINDING_ABORTED;
+ }
+
+ // We should be state loading.
+ if (mType != eType_Loading) {
+ MOZ_ASSERT_UNREACHABLE("Should be type loading at this point");
+ return NS_BINDING_ABORTED;
+ }
+ MOZ_ASSERT(!mChannelLoaded, "mChannelLoaded set already?");
+ MOZ_ASSERT(!mFinalListener, "mFinalListener exists already?");
+
+ mChannelLoaded = true;
+
+ // We don't need to check for errors here, unlike in `OnStartRequest`, as
+ // `UpgradeLoadToDocument` is only called when the load is going to become a
+ // process-switching load. As we never process switch for failed object loads,
+ // we know our channel status is successful.
+
+ // Call `LoadObject` to trigger our nsObjectLoadingContext to switch into the
+ // specified new state.
+ nsresult rv = LoadObject(true, false, aRequest);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ if (!bc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bc.forget(aBrowsingContext);
+ return NS_OK;
+}
+
+uint32_t nsObjectLoadingContent::GetRunID(SystemCallerGuarantee,
+ ErrorResult& aRv) {
+ if (!mHasRunID) {
+ // The plugin instance must not have a run ID, so we must
+ // be running the plugin in-process.
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return 0;
+ }
+ return mRunID;
+}
+
+bool nsObjectLoadingContent::ShouldBlockContent() {
+ if (mContentBlockingEnabled && mURI && IsFlashMIME(mContentType) &&
+ StaticPrefs::browser_safebrowsing_blockedURIs_enabled()) {
+ return true;
+ }
+
+ return false;
+}
+
+Document* nsObjectLoadingContent::GetContentDocument(
+ nsIPrincipal& aSubjectPrincipal) {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ if (!thisContent->IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ Document* sub_doc = thisContent->OwnerDoc()->GetSubDocumentFor(thisContent);
+ if (!sub_doc) {
+ return nullptr;
+ }
+
+ // Return null for cross-origin contentDocument.
+ if (!aSubjectPrincipal.SubsumesConsideringDomain(sub_doc->NodePrincipal())) {
+ return nullptr;
+ }
+
+ return sub_doc;
+}
+
+bool nsObjectLoadingContent::DoResolve(
+ JSContext* aCx, JS::Handle<JSObject*> aObject, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) {
+ return true;
+}
+
+/* static */
+bool nsObjectLoadingContent::MayResolve(jsid aId) {
+ // We can resolve anything, really.
+ return true;
+}
+
+void nsObjectLoadingContent::GetOwnPropertyNames(
+ JSContext* aCx, JS::MutableHandleVector<jsid> /* unused */,
+ bool /* unused */, ErrorResult& aRv) {}
+
+void nsObjectLoadingContent::MaybeFireErrorEvent() {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ // Queue a task to fire an error event if we're an <object> element. The
+ // queueing is important, since then we don't have to worry about reentry.
+ if (thisContent->IsHTMLElement(nsGkAtoms::object)) {
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(
+ thisContent, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+ }
+}
+
+bool nsObjectLoadingContent::BlockEmbedOrObjectContentLoading() {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ // Traverse up the node tree to see if we have any ancestors that may block us
+ // from loading
+ for (nsIContent* parent = thisContent->GetParent(); parent;
+ parent = parent->GetParent()) {
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) {
+ return true;
+ }
+ // If we have an ancestor that is an object with a source, it'll have an
+ // associated displayed type. If that type is not null, don't load content
+ // for the embed.
+ if (HTMLObjectElement* object = HTMLObjectElement::FromNode(parent)) {
+ uint32_t type = object->DisplayedType();
+ if (type != eType_Null) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void nsObjectLoadingContent::SubdocumentIntrinsicSizeOrRatioChanged(
+ const Maybe<IntrinsicSize>& aIntrinsicSize,
+ const Maybe<AspectRatio>& aIntrinsicRatio) {
+ if (aIntrinsicSize == mSubdocumentIntrinsicSize &&
+ aIntrinsicRatio == mSubdocumentIntrinsicRatio) {
+ return;
+ }
+
+ mSubdocumentIntrinsicSize = aIntrinsicSize;
+ mSubdocumentIntrinsicRatio = aIntrinsicRatio;
+
+ if (nsSubDocumentFrame* sdf = do_QueryFrame(AsContent()->GetPrimaryFrame())) {
+ sdf->SubdocumentIntrinsicSizeOrRatioChanged();
+ }
+}
+
+void nsObjectLoadingContent::SubdocumentImageLoadComplete(nsresult aResult) {
+ ElementState oldState = ObjectState();
+ ObjectType oldType = mType;
+ mLoadingSyntheticDocument = false;
+
+ if (NS_FAILED(aResult)) {
+ UnloadObject();
+ mType = eType_Fallback;
+ ConfigureFallback();
+ NotifyStateChanged(oldType, oldState, true, false);
+ return;
+ }
+
+ // (mChannelLoaded && mChannel) indicates this is a good state, not any sort
+ // of failures.
+ MOZ_DIAGNOSTIC_ASSERT_IF(mChannelLoaded && mChannel, mType == eType_Document);
+
+ NotifyStateChanged(oldType, oldState, true, true);
+}
+
+void nsObjectLoadingContent::MaybeStoreCrossOriginFeaturePolicy() {
+ MOZ_DIAGNOSTIC_ASSERT(mFrameLoader);
+
+ // If the browsingContext is not ready (because docshell is dead), don't try
+ // to create one.
+ if (!mFrameLoader->IsRemoteFrame() && !mFrameLoader->GetExistingDocShell()) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> browsingContext = mFrameLoader->GetBrowsingContext();
+
+ if (!browsingContext || !browsingContext->IsContentSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> thisContent = AsContent();
+
+ if (!thisContent->IsInComposedDoc()) {
+ return;
+ }
+
+ FeaturePolicy* featurePolicy = thisContent->OwnerDoc()->FeaturePolicy();
+
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ Unused << cc->SendSetContainerFeaturePolicy(browsingContext, featurePolicy);
+ }
+}
diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h
new file mode 100644
index 0000000000..af8cc1db21
--- /dev/null
+++ b/dom/base/nsObjectLoadingContent.h
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A base class implementing nsIObjectLoadingContent for use by
+ * various content nodes that want to provide plugin/document/image
+ * loading functionality (eg <embed>, <object>, etc).
+ */
+
+#ifndef NSOBJECTLOADINGCONTENT_H_
+#define NSOBJECTLOADINGCONTENT_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsIFrame.h" // for WeakFrame only
+#include "nsImageLoadingContent.h"
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIRunnable.h"
+#include "nsFrameLoaderOwner.h"
+
+class nsStopPluginRunnable;
+class nsIPrincipal;
+class nsFrameLoader;
+
+namespace mozilla::dom {
+struct BindContext;
+template <typename T>
+class Sequence;
+struct MozPluginParameter;
+class HTMLIFrameElement;
+template <typename T>
+struct Nullable;
+class WindowProxyHolder;
+class XULFrameElement;
+} // namespace mozilla::dom
+
+class nsObjectLoadingContent : public nsImageLoadingContent,
+ public nsIStreamListener,
+ public nsFrameLoaderOwner,
+ public nsIObjectLoadingContent,
+ public nsIChannelEventSink {
+ friend class AutoSetLoadingToFalse;
+
+ public:
+ // This enum's values must be the same as the constants on
+ // nsIObjectLoadingContent
+ enum ObjectType {
+ // Loading, type not yet known. We may be waiting for a channel to open.
+ eType_Loading = TYPE_LOADING,
+ // Content is a *non-svg* image
+ eType_Image = TYPE_IMAGE,
+ // Content is a "special" plugin. Plugins are removed but these MIME
+ // types display an transparent region in their place.
+ // (Special plugins that have an HTML fallback are eType_Null)
+ eType_Fallback = TYPE_FALLBACK,
+ // Content is a fake plugin, which loads as a document but behaves as a
+ // plugin (see nsPluginHost::CreateFakePlugin). Currently only used for
+ // pdf.js.
+ eType_FakePlugin = TYPE_FAKE_PLUGIN,
+ // Content is a subdocument, possibly SVG
+ eType_Document = TYPE_DOCUMENT,
+ // Content is unknown and should be represented by an empty element,
+ // unless an HTML fallback is available.
+ eType_Null = TYPE_NULL
+ };
+
+ nsObjectLoadingContent();
+ virtual ~nsObjectLoadingContent();
+
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBJECTLOADINGCONTENT
+ NS_DECL_NSICHANNELEVENTSINK
+
+ /**
+ * Object state. This is a bitmask of NS_EVENT_STATEs epresenting the
+ * current state of the object.
+ */
+ mozilla::dom::ElementState ObjectState() const;
+
+ ObjectType Type() const { return mType; }
+
+ void SetIsNetworkCreated(bool aNetworkCreated) {
+ mNetworkCreated = aNetworkCreated;
+ }
+
+ /**
+ * When the object is loaded, the attributes and all nested <param>
+ * elements are cached as name:value string pairs to be passed as
+ * parameters when instantiating the plugin.
+ *
+ * Note: these cached values can be overriden for different quirk cases.
+ */
+ // Returns the cached attributes array.
+ void GetPluginAttributes(
+ nsTArray<mozilla::dom::MozPluginParameter>& aAttributes);
+
+ // Returns the cached <param> array.
+ void GetPluginParameters(
+ nsTArray<mozilla::dom::MozPluginParameter>& aParameters);
+
+ /**
+ * Notify this class the document state has changed
+ * Called by Document so we may suspend plugins in inactive documents)
+ */
+ void NotifyOwnerDocumentActivityChanged();
+
+ // Helper for WebIDL NeedResolve
+ bool DoResolve(
+ JSContext* aCx, JS::Handle<JSObject*> aObject, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc);
+ // The return value is whether DoResolve might end up resolving the given
+ // id. If in doubt, return true.
+ static bool MayResolve(jsid aId);
+
+ static bool IsSuccessfulRequest(nsIRequest*, nsresult* aStatus);
+
+ // Helper for WebIDL enumeration
+ void GetOwnPropertyNames(JSContext* aCx,
+ JS::MutableHandleVector<jsid> /* unused */,
+ bool /* unused */, mozilla::ErrorResult& aRv);
+
+ // WebIDL API
+ mozilla::dom::Document* GetContentDocument(nsIPrincipal& aSubjectPrincipal);
+ void GetActualType(nsAString& aType) const {
+ CopyUTF8toUTF16(mContentType, aType);
+ }
+ uint32_t DisplayedType() const { return mType; }
+ uint32_t GetContentTypeForMIMEType(const nsAString& aMIMEType) {
+ return GetTypeOfContent(NS_ConvertUTF16toUTF8(aMIMEType), false);
+ }
+ void Reload(bool aClearActivation, mozilla::ErrorResult& aRv) {
+ aRv = Reload(aClearActivation);
+ }
+ nsIURI* GetSrcURI() const { return mURI; }
+
+ // FIXME rename this
+ void SkipFakePlugins(mozilla::ErrorResult& aRv) { aRv = SkipFakePlugins(); }
+ void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aRv) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+ void SwapFrameLoaders(mozilla::dom::XULFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aRv) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ uint32_t GetRunID(mozilla::dom::SystemCallerGuarantee,
+ mozilla::ErrorResult& aRv);
+
+ bool IsRewrittenYoutubeEmbed() const { return mRewrittenYoutubeEmbed; }
+
+ void PresetOpenerWindow(const mozilla::dom::Nullable<
+ mozilla::dom::WindowProxyHolder>& aOpenerWindow,
+ mozilla::ErrorResult& aRv);
+
+ const mozilla::Maybe<mozilla::IntrinsicSize>& GetSubdocumentIntrinsicSize()
+ const {
+ return mSubdocumentIntrinsicSize;
+ }
+
+ const mozilla::Maybe<mozilla::AspectRatio>& GetSubdocumentIntrinsicRatio()
+ const {
+ return mSubdocumentIntrinsicRatio;
+ }
+
+ void SubdocumentIntrinsicSizeOrRatioChanged(
+ const mozilla::Maybe<mozilla::IntrinsicSize>& aIntrinsicSize,
+ const mozilla::Maybe<mozilla::AspectRatio>& aIntrinsicRatio);
+
+ void SubdocumentImageLoadComplete(nsresult aResult);
+
+ protected:
+ /**
+ * Begins loading the object when called
+ *
+ * Attributes of |this| QI'd to nsIContent will be inspected, depending on
+ * the node type. This function currently assumes it is a <object> or
+ * <embed> tag.
+ *
+ * The instantiated plugin depends on:
+ * - The URI (<embed src>, <object data>)
+ * - The type 'hint' (type attribute)
+ * - The mime type returned by opening the URI
+ * - Enabled plugins claiming the ultimate mime type
+ * - The capabilities returned by GetCapabilities
+ * - The classid attribute, if eFallbackIfClassIDPresent is among the
+ * capabilities
+ *
+ * If eAllowPluginSkipChannel is true, we may skip opening the URI if our
+ * type hint points to a valid plugin, deferring that responsibility to the
+ * plugin.
+ * Similarly, if no URI is provided, but a type hint for a valid plugin is
+ * present, that plugin will be instantiated
+ *
+ * Otherwise a request to that URI is made and the type sent by the server
+ * is used to find a suitable handler, EXCEPT when:
+ * - The type hint refers to a *supported* plugin, in which case that
+ * plugin will be instantiated regardless of the server provided type
+ * - The server returns a binary-stream type, and our type hint refers to
+ * a valid non-document type, we will use the type hint
+ *
+ * @param aNotify If we should send notifications. If false, content
+ * loading may be deferred while appropriate frames are
+ * created
+ * @param aForceLoad If we should reload this content (and re-attempt the
+ * channel open) even if our parameters did not change
+ */
+ nsresult LoadObject(bool aNotify, bool aForceLoad = false);
+
+ enum Capabilities {
+ eSupportImages = 1u << 0, // Images are supported (imgILoader)
+ eSupportPlugins = 1u << 1, // Plugins are supported (nsIPluginHost)
+ eSupportDocuments = 1u << 2, // Documents are supported
+ // (DocumentLoaderFactory)
+ // This flag always includes SVG
+
+ // Node supports class ID as an attribute, and should fallback if it is
+ // present, as class IDs are not supported.
+ eFallbackIfClassIDPresent = 1u << 3,
+
+ // If possible to get a *plugin* type from the type attribute *or* file
+ // extension, we can use that type and begin loading the plugin before
+ // opening a channel.
+ // A side effect of this is if the channel fails, the plugin is still
+ // running.
+ eAllowPluginSkipChannel = 1u << 4
+ };
+
+ /**
+ * Returns the list of capabilities this content node supports. This is a
+ * bitmask consisting of flags from the Capabilities enum.
+ *
+ * The default implementation supports all types but not
+ * eSupportClassID or eAllowPluginSkipChannel
+ */
+ virtual uint32_t GetCapabilities() const;
+
+ /**
+ * Destroys all loaded documents/plugins and releases references
+ */
+ void Destroy();
+
+ // Subclasses should call cycle collection methods from the respective
+ // traverse / unlink.
+ static void Traverse(nsObjectLoadingContent* tmp,
+ nsCycleCollectionTraversalCallback& cb);
+ static void Unlink(nsObjectLoadingContent* tmp);
+
+ void CreateStaticClone(nsObjectLoadingContent* aDest) const;
+
+ nsresult BindToTree(mozilla::dom::BindContext& aCxt, nsINode& aParent) {
+ nsImageLoadingContent::BindToTree(aCxt, aParent);
+ return NS_OK;
+ }
+ void UnbindFromTree(bool aNullParent = true);
+
+ /**
+ * Return the content policy type used for loading the element.
+ */
+ virtual nsContentPolicyType GetContentPolicyType() const = 0;
+
+ /**
+ * Decides whether we should load <embed>/<object> node content.
+ *
+ * If this is an <embed> or <object> node there are cases in which we should
+ * not try to load the content:
+ *
+ * - If the node is the child of a media element
+ * - If the node is the child of an <object> node that already has
+ * content being loaded.
+ *
+ * In these cases, this function will return false, which will cause
+ * us to skip calling LoadObject.
+ */
+ bool BlockEmbedOrObjectContentLoading();
+
+ private:
+ // Object parameter changes returned by UpdateObjectParameters
+ enum ParameterUpdateFlags {
+ eParamNoChange = 0,
+ // Parameters that potentially affect the channel changed
+ // - mOriginalURI, mOriginalContentType
+ eParamChannelChanged = 1u << 0,
+ // Parameters that affect displayed content changed
+ // - mURI, mContentType, mType, mBaseURI
+ eParamStateChanged = 1u << 1,
+ // The effective content type changed, independant of object type. This
+ // can happen when changing from Loading -> Final type, but doesn't
+ // necessarily happen when changing between object types. E.g., if a PDF
+ // handler was installed between the last load of this object and now, we
+ // might change from eType_Document -> eType_Plugin without changing
+ // ContentType
+ eParamContentTypeChanged = 1u << 2
+ };
+
+ /**
+ * Getter for child <param> elements that are not nested in another plugin
+ * dom element.
+ * This is an internal helper function and should not be used directly for
+ * passing parameters to the plugin instance.
+ *
+ * See GetPluginParameters and GetPluginAttributes, which also handle
+ * quirk-overrides.
+ *
+ * @param aParameters The array containing pairs of name/value strings
+ * from nested <param> objects.
+ */
+ void GetNestedParams(nsTArray<mozilla::dom::MozPluginParameter>& aParameters);
+
+ [[nodiscard]] nsresult BuildParametersArray();
+
+ /**
+ * Configure fallback for deprecated plugin and broken elements.
+ */
+ void ConfigureFallback();
+
+ /**
+ * Internal version of LoadObject that should only be used by this class
+ * aLoadingChannel is passed by the LoadObject call from OnStartRequest,
+ * primarily for sanity-preservation
+ */
+ nsresult LoadObject(bool aNotify, bool aForceLoad,
+ nsIRequest* aLoadingChannel);
+
+ /**
+ * Inspects the object and sets the following member variables:
+ * - mOriginalContentType : This is the type attribute on the element
+ * - mOriginalURI : The src or data attribute on the element
+ * - mURI : The final URI, considering mChannel if
+ * mChannelLoaded is set
+ * - mContentType : The final content type, considering mChannel if
+ * mChannelLoaded is set
+ * - mBaseURI : The object's base URI, which may be set by the
+ * object
+ * - mType : The type the object is determined to be based
+ * on the above
+ *
+ * NOTE The class assumes that mType is the currently loaded type at various
+ * points, so the caller of this function must take the appropriate
+ * actions to ensure this
+ *
+ * NOTE This function does not perform security checks, only determining the
+ * requested type and parameters of the object.
+ *
+ * @return Returns a bitmask of ParameterUpdateFlags values
+ */
+ ParameterUpdateFlags UpdateObjectParameters();
+
+ public:
+ bool IsAboutBlankLoadOntoInitialAboutBlank(nsIURI* aURI,
+ bool aInheritPrincipal,
+ nsIPrincipal* aPrincipalToInherit);
+
+ private:
+ /**
+ * Opens the channel pointed to by mURI into mChannel.
+ */
+ nsresult OpenChannel();
+
+ /**
+ * Closes and releases references to mChannel and, if opened, mFinalListener
+ */
+ nsresult CloseChannel();
+
+ /**
+ * If this object should be tested against blocking list.
+ */
+ bool ShouldBlockContent();
+
+ /**
+ * This method tells the final answer on whether this object's fallback
+ * content should be used instead of the original plugin content.
+ *
+ * @param aIsPluginClickToPlay Whether this object instance is CTP.
+ */
+ bool PreferFallback(bool aIsPluginClickToPlay);
+
+ /**
+ * Helper to check if our current URI passes policy
+ *
+ * @param aContentPolicy [out] The result of the content policy decision
+ *
+ * @return true if call succeeded and NS_CP_ACCEPTED(*aContentPolicy)
+ */
+ bool CheckLoadPolicy(int16_t* aContentPolicy);
+
+ /**
+ * Helper to check if the object passes process policy. Assumes we have a
+ * final determined type.
+ *
+ * @param aContentPolicy [out] The result of the content policy decision
+ *
+ * @return true if call succeeded and NS_CP_ACCEPTED(*aContentPolicy)
+ */
+ bool CheckProcessPolicy(int16_t* aContentPolicy);
+
+ void SetupFrameLoader(int32_t aJSPluginId);
+
+ /**
+ * Helper to spawn mFrameLoader and return a pointer to its docshell
+ *
+ * @param aURI URI we intend to load for the recursive load check (does not
+ * actually load anything)
+ */
+ already_AddRefed<nsIDocShell> SetupDocShell(nsIURI* aRecursionCheckURI);
+
+ /**
+ * Unloads all content and resets the object to a completely unloaded state
+ *
+ * NOTE Calls StopPluginInstance() and may spin the event loop
+ *
+ * @param aResetState Reset the object type to 'loading' and destroy channel
+ * as well
+ */
+ void UnloadObject(bool aResetState = true);
+
+ /**
+ * Notifies document observes about a new type/state of this object.
+ * Triggers frame construction as needed. mType must be set correctly when
+ * this method is called. This method is cheap if the type and state didn't
+ * actually change.
+ *
+ * @param aNotify if false, only need to update the state of our element.
+ */
+ void NotifyStateChanged(ObjectType aOldType,
+ mozilla::dom::ElementState aOldState, bool aNotify,
+ bool aForceRestyle);
+
+ /**
+ * Returns a ObjectType value corresponding to the type of content we would
+ * support the given MIME type as, taking capabilities and plugin state
+ * into account
+ *
+ * @param aNoFakePlugin Don't select a fake plugin handler as a valid type,
+ * as when SkipFakePlugins() is called.
+ * @return The ObjectType enum value that we would attempt to load
+ *
+ * NOTE this does not consider whether the content would be suppressed by
+ * click-to-play or other content policy checks
+ */
+ ObjectType GetTypeOfContent(const nsCString& aMIMEType, bool aNoFakePlugin);
+
+ /**
+ * Used for identifying whether we can rewrite a youtube flash embed to
+ * possibly use HTML5 instead.
+ *
+ * Returns true if plugin.rewrite_youtube_embeds pref is true and the
+ * element this nsObjectLoadingContent instance represents:
+ *
+ * - is an embed or object node
+ * - has a URL pointing at the youtube.com domain, using "/v/" style video
+ * path reference.
+ *
+ * Having the enablejsapi flag means the document that contains the element
+ * could possibly be manipulating the youtube video elsewhere on the page
+ * via javascript. In the context of embed elements, this usage has been
+ * deprecated by youtube, so we can just rewrite as normal.
+ *
+ * If we can rewrite the URL, we change the "/v/" to "/embed/", and change
+ * our type to eType_Document so that we render similarly to an iframe
+ * embed.
+ */
+ void MaybeRewriteYoutubeEmbed(nsIURI* aURI, nsIURI* aBaseURI,
+ nsIURI** aRewrittenURI);
+
+ // Utility for firing an error event, if we're an <object>.
+ void MaybeFireErrorEvent();
+
+ /**
+ * Store feature policy in container browsing context so that it can be
+ * accessed cross process.
+ */
+ void MaybeStoreCrossOriginFeaturePolicy();
+
+ // The final listener for mChannel (uriloader, pluginstreamlistener, etc.)
+ nsCOMPtr<nsIStreamListener> mFinalListener;
+
+ // The content type of our current load target, updated by
+ // UpdateObjectParameters(). Takes the channel's type into account once
+ // opened.
+ //
+ // May change if a channel is opened, does not imply a loaded state
+ nsCString mContentType;
+
+ // The content type 'hint' provided by the element's type attribute. May
+ // or may not be used as a final type
+ nsCString mOriginalContentType;
+
+ // The channel that's currently being loaded. If set, but mChannelLoaded is
+ // false, has not yet reached OnStartRequest
+ nsCOMPtr<nsIChannel> mChannel;
+
+ // The URI of the current content.
+ // May change as we open channels and encounter redirects - does not imply
+ // a loaded type
+ nsCOMPtr<nsIURI> mURI;
+
+ // The original URI obtained from inspecting the element. May differ from
+ // mURI due to redirects
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ // The baseURI used for constructing mURI.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ // Type of the currently-loaded content.
+ ObjectType mType : 8;
+
+ uint32_t mRunID;
+ bool mHasRunID : 1;
+
+ // If true, we have opened a channel as the listener and it has reached
+ // OnStartRequest. Does not get set for channels that are passed directly to
+ // the plugin listener.
+ bool mChannelLoaded : 1;
+
+ // True when the object is created for an element which the parser has
+ // created using NS_FROM_PARSER_NETWORK flag. If the element is modified,
+ // it may lose the flag.
+ bool mNetworkCreated : 1;
+
+ // Whether content blocking is enabled or not for this object.
+ bool mContentBlockingEnabled : 1;
+
+ // If we should not use fake plugins until the next type change
+ bool mSkipFakePlugins : 1;
+
+ // Protects DoStopPlugin from reentry (bug 724781).
+ bool mIsStopping : 1;
+
+ // Protects LoadObject from re-entry
+ bool mIsLoading : 1;
+
+ // For plugin stand-in types (click-to-play) tracks whether content js has
+ // tried to access the plugin script object.
+ bool mScriptRequested : 1;
+
+ // True if object represents an object/embed tag pointing to a flash embed
+ // for a youtube video. When possible (see IsRewritableYoutubeEmbed function
+ // comments for details), we change these to try to load HTML5 versions of
+ // videos.
+ bool mRewrittenYoutubeEmbed : 1;
+
+ bool mLoadingSyntheticDocument : 1;
+
+ nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
+ nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;
+
+ // The intrinsic size and aspect ratio from a child SVG document that
+ // we should use. These are only set when we are an <object> or <embed>
+ // and the inner document is SVG.
+ //
+ // We store these here rather than on nsSubDocumentFrame since we are
+ // sometimes notified of our child's intrinsics before we've constructed
+ // our own frame.
+ mozilla::Maybe<mozilla::IntrinsicSize> mSubdocumentIntrinsicSize;
+ mozilla::Maybe<mozilla::AspectRatio> mSubdocumentIntrinsicRatio;
+};
+
+#endif
diff --git a/dom/base/nsOpenURIInFrameParams.cpp b/dom/base/nsOpenURIInFrameParams.cpp
new file mode 100644
index 0000000000..a8e95170e6
--- /dev/null
+++ b/dom/base/nsOpenURIInFrameParams.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOpenURIInFrameParams.h"
+#include "nsIOpenWindowInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsOpenURIInFrameParams)
+ NS_INTERFACE_MAP_ENTRY(nsIOpenURIInFrameParams)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsOpenURIInFrameParams, mOpenerBrowser)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsOpenURIInFrameParams)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsOpenURIInFrameParams)
+
+nsOpenURIInFrameParams::nsOpenURIInFrameParams(
+ nsIOpenWindowInfo* aOpenWindowInfo, mozilla::dom::Element* aOpener)
+ : mOpenWindowInfo(aOpenWindowInfo), mOpenerBrowser(aOpener) {}
+
+nsOpenURIInFrameParams::~nsOpenURIInFrameParams() = default;
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetOpenWindowInfo(nsIOpenWindowInfo** aOpenWindowInfo) {
+ NS_IF_ADDREF(*aOpenWindowInfo = mOpenWindowInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ NS_IF_ADDREF(*aReferrerInfo = mReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetIsPrivate(bool* aIsPrivate) {
+ NS_ENSURE_ARG_POINTER(aIsPrivate);
+ *aIsPrivate = mOpenWindowInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetTriggeringPrincipal(
+ nsIPrincipal** aTriggeringPrincipal) {
+ NS_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ NS_ENSURE_TRUE(aTriggeringPrincipal, NS_ERROR_INVALID_ARG);
+ mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ NS_IF_ADDREF(*aCsp = mCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ NS_ENSURE_TRUE(aCsp, NS_ERROR_INVALID_ARG);
+ mCsp = aCsp;
+ return NS_OK;
+}
+
+nsresult nsOpenURIInFrameParams::GetOpenerBrowser(
+ mozilla::dom::Element** aOpenerBrowser) {
+ RefPtr<mozilla::dom::Element> owner = mOpenerBrowser;
+ owner.forget(aOpenerBrowser);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetOpenerOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aValue) {
+ return mOpenWindowInfo->GetScriptableOriginAttributes(aCx, aValue);
+}
diff --git a/dom/base/nsOpenURIInFrameParams.h b/dom/base/nsOpenURIInFrameParams.h
new file mode 100644
index 0000000000..1c353ddede
--- /dev/null
+++ b/dom/base/nsOpenURIInFrameParams.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/BasePrincipal.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIPrincipal.h"
+#include "nsIReferrerInfo.h"
+#include "nsString.h"
+
+namespace mozilla {
+class OriginAttributes;
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsOpenURIInFrameParams final : public nsIOpenURIInFrameParams {
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsOpenURIInFrameParams)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIOPENURIINFRAMEPARAMS
+
+ explicit nsOpenURIInFrameParams(nsIOpenWindowInfo* aOpenWindowInfo,
+ mozilla::dom::Element* aOpener);
+
+ private:
+ ~nsOpenURIInFrameParams();
+
+ nsCOMPtr<nsIOpenWindowInfo> mOpenWindowInfo;
+ RefPtr<mozilla::dom::Element> mOpenerBrowser;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+};
diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
new file mode 100644
index 0000000000..998161b550
--- /dev/null
+++ b/dom/base/nsPIDOMWindow.h
@@ -0,0 +1,1188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPIDOMWindow_h__
+#define nsPIDOMWindow_h__
+
+#include "nsIDOMWindow.h"
+#include "mozIDOMWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "Units.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TaskCategory.h"
+#include "js/TypeDecls.h"
+#include "nsRefPtrHashtable.h"
+#include "nsILoadInfo.h"
+
+// Only fired for inner windows.
+#define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
+#define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
+#define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
+
+class nsGlobalWindowInner;
+class nsGlobalWindowOuter;
+class nsIArray;
+class nsIBaseWindow;
+class nsIChannel;
+class nsIContent;
+class nsIContentSecurityPolicy;
+class nsICSSDeclaration;
+class nsIDocShell;
+class nsIDocShellTreeOwner;
+class nsDocShellLoadState;
+class nsIPrincipal;
+class nsIRunnable;
+class nsIScriptTimeoutHandler;
+class nsISerialEventTarget;
+class nsIURI;
+class nsIWebBrowserChrome;
+class nsPIDOMWindowInner;
+class nsPIDOMWindowOuter;
+class nsPIWindowRoot;
+
+using SuspendTypes = uint32_t;
+
+namespace mozilla::dom {
+class AudioContext;
+class BrowsingContext;
+class BrowsingContextGroup;
+class ClientInfo;
+class ClientState;
+class ContentFrameMessageManager;
+class DocGroup;
+class Document;
+class Element;
+class Location;
+class MediaDevices;
+class MediaKeys;
+class Navigator;
+class Performance;
+class Selection;
+class ServiceWorker;
+class ServiceWorkerDescriptor;
+class Timeout;
+class TimeoutManager;
+class WindowContext;
+class WindowGlobalChild;
+class CustomElementRegistry;
+enum class CallerType : uint32_t;
+} // namespace mozilla::dom
+
+enum class FullscreenReason {
+ // Toggling the fullscreen mode requires trusted context.
+ ForFullscreenMode,
+ // Fullscreen API is the API provided to untrusted content.
+ ForFullscreenAPI,
+ // This reason can only be used with exiting fullscreen.
+ // It is otherwise identical to eForFullscreenAPI except it would
+ // suppress the fullscreen transition.
+ ForForceExitFullscreen
+};
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_PIDOMWINDOWINNER_IID \
+ { \
+ 0x775dabc9, 0x8f43, 0x4277, { \
+ 0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb \
+ } \
+ }
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_PIDOMWINDOWOUTER_IID \
+ { \
+ 0x769693d4, 0xb009, 0x4fe2, { \
+ 0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf \
+ } \
+ }
+
+class nsPIDOMWindowInner : public mozIDOMWindow {
+ protected:
+ using Document = mozilla::dom::Document;
+ friend nsGlobalWindowInner;
+ friend nsGlobalWindowOuter;
+
+ nsPIDOMWindowInner(nsPIDOMWindowOuter* aOuterWindow,
+ mozilla::dom::WindowGlobalChild* aActor);
+
+ ~nsPIDOMWindowInner();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWINNER_IID)
+
+ nsIGlobalObject* AsGlobal();
+ const nsIGlobalObject* AsGlobal() const;
+
+ nsPIDOMWindowOuter* GetOuterWindow() const { return mOuterWindow; }
+
+ static nsPIDOMWindowInner* From(mozIDOMWindow* aFrom) {
+ return static_cast<nsPIDOMWindowInner*>(aFrom);
+ }
+
+ NS_IMPL_FROMEVENTTARGET_HELPER_WITH_GETTER(nsPIDOMWindowInner,
+ GetAsWindowInner())
+
+ // Returns true if this object is the currently-active inner window for its
+ // BrowsingContext.
+ bool IsCurrentInnerWindow() const;
+
+ // Returns true if the document of this window is the active document. This
+ // is identical to IsCurrentInnerWindow() now that document.open() no longer
+ // creates new inner windows for the document it is called on.
+ inline bool HasActiveDocument() const;
+
+ // Return true if this object is the currently-active inner window for its
+ // BrowsingContext and any container document is also fully active.
+ // For https://html.spec.whatwg.org/multipage/browsers.html#fully-active
+ bool IsFullyActive() const;
+
+ // Returns true if this window is the same as mTopInnerWindow
+ inline bool IsTopInnerWindow() const;
+
+ // Returns true if this was the current window for its BrowsingContext when it
+ // was discarded.
+ virtual bool WasCurrentInnerWindow() const = 0;
+
+ // Check whether a document is currently loading (really checks if the
+ // load event has completed). May not be reset to false on errors.
+ inline bool IsLoading() const;
+ inline bool IsHandlingResizeEvent() const;
+
+ // Note: not related to IsLoading. Set to false if there's an error, etc.
+ virtual void SetActiveLoadingState(bool aIsActiveLoading) = 0;
+
+ bool AddAudioContext(mozilla::dom::AudioContext* aAudioContext);
+ void RemoveAudioContext(mozilla::dom::AudioContext* aAudioContext);
+ void MuteAudioContexts();
+ void UnmuteAudioContexts();
+
+ void SetAudioCapture(bool aCapture);
+
+ /**
+ * Associate this inner window with a MediaKeys instance.
+ */
+ void AddMediaKeysInstance(mozilla::dom::MediaKeys* aMediaKeysInstance);
+
+ /**
+ * Remove an association between this inner window and a MediaKeys instance.
+ */
+ void RemoveMediaKeysInstance(mozilla::dom::MediaKeys* aMediaKeysInstance);
+
+ /**
+ * Return if any MediaKeys instances are associated with this window.
+ */
+ bool HasActiveMediaKeysInstance();
+
+ mozilla::dom::Performance* GetPerformance();
+
+ void QueuePerformanceNavigationTiming();
+
+ bool HasMutationListeners(uint32_t aMutationEventType) const {
+ if (!mOuterWindow) {
+ NS_ERROR("HasMutationListeners() called on orphan inner window!");
+
+ return false;
+ }
+
+ return (mMutationBits & aMutationEventType) != 0;
+ }
+
+ void SetMutationListeners(uint32_t aType) {
+ if (!mOuterWindow) {
+ NS_ERROR("HasMutationListeners() called on orphan inner window!");
+
+ return;
+ }
+
+ mMutationBits |= aType;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a mouseenter/leave event listener.
+ */
+ bool HasMouseEnterLeaveEventListeners() const {
+ return mMayHaveMouseEnterLeaveEventListener;
+ }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a mouseenter/leave event listener.
+ */
+ void SetHasMouseEnterLeaveEventListeners() {
+ mMayHaveMouseEnterLeaveEventListener = true;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a Pointerenter/leave event listener.
+ */
+ bool HasPointerEnterLeaveEventListeners() const {
+ return mMayHavePointerEnterLeaveEventListener;
+ }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a Pointerenter/leave event listener.
+ */
+ void SetHasPointerEnterLeaveEventListeners() {
+ mMayHavePointerEnterLeaveEventListener = true;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a transition* event listeners.
+ */
+ bool HasTransitionEventListeners() { return mMayHaveTransitionEventListener; }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a transition* event listener.
+ */
+ void SetHasTransitionEventListeners() {
+ mMayHaveTransitionEventListener = true;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a beforeinput event listener.
+ * Returing false may be wrong if some nodes have come from another document
+ * with `Document.adoptNode`.
+ */
+ bool HasBeforeInputEventListenersForTelemetry() const {
+ return mMayHaveBeforeInputEventListenerForTelemetry;
+ }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a beforeinput event listener.
+ */
+ void SetHasBeforeInputEventListenersForTelemetry() {
+ mMayHaveBeforeInputEventListenerForTelemetry = true;
+ }
+
+ /**
+ * Call this to check whether some node (The document, or content in the
+ * document) has been observed by web apps with a mutation observer.
+ * (i.e., `MutationObserver.observe()` called by chrome script and addon's
+ * script does not make this returns true).
+ * Returing false may be wrong if some nodes have come from another document
+ * with `Document.adoptNode`.
+ */
+ bool MutationObserverHasObservedNodeForTelemetry() const {
+ return mMutationObserverHasObservedNodeForTelemetry;
+ }
+
+ /**
+ * Call this to indicate that some node (The document, or content in the
+ * document) is observed by web apps with a mutation observer.
+ */
+ void SetMutationObserverHasObservedNodeForTelemetry() {
+ mMutationObserverHasObservedNodeForTelemetry = true;
+ }
+
+ // Sets the event for window.event. Does NOT take ownership, so
+ // the caller is responsible for clearing the event before the
+ // event gets deallocated. Pass nullptr to set window.event to
+ // undefined. Returns the previous value.
+ mozilla::dom::Event* SetEvent(mozilla::dom::Event* aEvent) {
+ mozilla::dom::Event* old = mEvent;
+ mEvent = aEvent;
+ return old;
+ }
+
+ /**
+ * Check whether this window is a secure context.
+ */
+ bool IsSecureContext() const;
+ bool IsSecureContextIfOpenerIgnored() const;
+
+ // Calling suspend should prevent any asynchronous tasks from
+ // executing javascript for this window. This means setTimeout,
+ // requestAnimationFrame, and events should not be fired. Suspending
+ // a window maybe also suspends its children. Workers may
+ // continue to perform computations in the background. A window
+ // can have Suspend() called multiple times and will only resume after
+ // a matching number of Resume() calls.
+ void Suspend(bool aIncludeSubWindows = true);
+ void Resume(bool aIncludeSubWindows = true);
+
+ // Whether or not this window was suspended by the BrowserContextGroup
+ bool GetWasSuspendedByGroup() const { return mWasSuspendedByGroup; }
+ void SetWasSuspendedByGroup(bool aSuspended) {
+ mWasSuspendedByGroup = aSuspended;
+ }
+
+ // Apply the parent window's suspend, freeze, and modal state to the current
+ // window.
+ void SyncStateFromParentWindow();
+
+ /**
+ * Increment active peer connection count.
+ */
+ void AddPeerConnection();
+
+ /**
+ * Decrement active peer connection count.
+ */
+ void RemovePeerConnection();
+
+ /**
+ * Check whether the active peer connection count is non-zero.
+ */
+ bool HasActivePeerConnections();
+
+ bool IsPlayingAudio();
+
+ bool IsDocumentLoaded() const;
+
+ mozilla::dom::TimeoutManager& TimeoutManager();
+
+ bool IsRunningTimeout();
+
+ // To cache top inner-window if available after constructed for tab-wised
+ // indexedDB counters.
+ void TryToCacheTopInnerWindow();
+
+ // Increase/Decrease the number of active IndexedDB databases for the
+ // decision making of timeout-throttling.
+ void UpdateActiveIndexedDBDatabaseCount(int32_t aDelta);
+
+ // Return true if there is any active IndexedDB databases which could block
+ // timeout-throttling.
+ bool HasActiveIndexedDBDatabases();
+
+ // Increase/Decrease the number of open WebSockets.
+ void UpdateWebSocketCount(int32_t aDelta);
+
+ // Return true if there are any open WebSockets that could block
+ // timeout-throttling.
+ bool HasOpenWebSockets() const;
+
+ mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+ mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+ mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+ void SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp);
+ nsIContentSecurityPolicy* GetCsp();
+
+ void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
+
+ void NoteDOMContentLoaded();
+
+ virtual mozilla::dom::CustomElementRegistry* CustomElements() = 0;
+
+ // XXX: This is called on inner windows
+ virtual nsPIDOMWindowOuter* GetInProcessScriptableTop() = 0;
+ virtual nsPIDOMWindowOuter* GetInProcessScriptableParent() = 0;
+ virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() = 0;
+
+ mozilla::dom::EventTarget* GetChromeEventHandler() const {
+ return mChromeEventHandler;
+ }
+
+ mozilla::dom::EventTarget* GetParentTarget() {
+ if (!mParentTarget) {
+ UpdateParentTarget();
+ }
+ return mParentTarget;
+ }
+
+ virtual void MaybeUpdateTouchState() {}
+
+ Document* GetExtantDoc() const { return mDoc; }
+ nsIURI* GetDocumentURI() const;
+ nsIURI* GetDocBaseURI() const;
+
+ Document* GetDoc() {
+ if (!mDoc) {
+ MaybeCreateDoc();
+ }
+ return mDoc;
+ }
+
+ mozilla::dom::WindowContext* GetWindowContext() const;
+ mozilla::dom::WindowGlobalChild* GetWindowGlobalChild() const {
+ return mWindowGlobalChild;
+ }
+
+ // Removes this inner window from the BFCache, if it is cached, and returns
+ // true if it was.
+ bool RemoveFromBFCacheSync();
+
+ // Determine if the window is suspended or frozen. Outer windows
+ // will forward this call to the inner window for convenience. If
+ // there is no inner window then the outer window is considered
+ // suspended and frozen by default.
+ virtual bool IsSuspended() const = 0;
+ virtual bool IsFrozen() const = 0;
+
+ // Fire any DOM notification events related to things that happened while
+ // the window was frozen.
+ virtual nsresult FireDelayedDOMEvents(bool aIncludeSubWindows) = 0;
+
+ /**
+ * Get the docshell in this window.
+ */
+ inline nsIDocShell* GetDocShell() const;
+
+ /**
+ * Get the browsing context in this window.
+ */
+ inline mozilla::dom::BrowsingContext* GetBrowsingContext() const;
+
+ /**
+ * Get the browsing context group this window belongs to.
+ */
+ mozilla::dom::BrowsingContextGroup* GetBrowsingContextGroup() const;
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a paint event listener.
+ */
+ void SetHasPaintEventListeners() { mMayHavePaintEventListener = true; }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a paint event listener.
+ */
+ bool HasPaintEventListeners() { return mMayHavePaintEventListener; }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a touch event listener.
+ */
+ void SetHasTouchEventListeners() {
+ if (!mMayHaveTouchEventListener) {
+ mMayHaveTouchEventListener = true;
+ MaybeUpdateTouchState();
+ }
+ }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a selectionchange event listener.
+ */
+ void SetHasSelectionChangeEventListeners() {
+ mMayHaveSelectionChangeEventListener = true;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a selectionchange event listener.
+ */
+ bool HasSelectionChangeEventListeners() const {
+ return mMayHaveSelectionChangeEventListener;
+ }
+
+ /**
+ * Call this to indicate that some node (this window, its document,
+ * or content in that document) has a select event listener of form controls.
+ */
+ void SetHasFormSelectEventListeners() {
+ mMayHaveFormSelectEventListener = true;
+ }
+
+ /**
+ * Call this to check whether some node (this window, its document,
+ * or content in that document) has a select event listener of form controls.
+ */
+ bool HasFormSelectEventListeners() const {
+ return mMayHaveFormSelectEventListener;
+ }
+
+ /*
+ * Get and set the currently focused element within the document. If
+ * aNeedsFocus is true, then set mNeedsFocus to true to indicate that a
+ * document focus event is needed.
+ *
+ * DO NOT CALL EITHER OF THESE METHODS DIRECTLY. USE THE FOCUS MANAGER
+ * INSTEAD.
+ */
+ mozilla::dom::Element* GetFocusedElement() const {
+ return mFocusedElement.get();
+ }
+
+ virtual void SetFocusedElement(mozilla::dom::Element* aElement,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) = 0;
+
+ bool UnknownFocusMethodShouldShowOutline() const {
+ return mUnknownFocusMethodShouldShowOutline;
+ }
+
+ /**
+ * Retrieves the method that was used to focus the current node.
+ */
+ virtual uint32_t GetFocusMethod() = 0;
+
+ /*
+ * Tells the window that it now has focus or has lost focus, based on the
+ * state of aFocus. If this method returns true, then the document loaded
+ * in the window has never received a focus event and expects to receive
+ * one. If false is returned, the document has received a focus event before
+ * and should only receive one if the window is being focused.
+ *
+ * aFocusMethod may be set to one of the focus method constants in
+ * nsIFocusManager to indicate how focus was set.
+ */
+ virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod) = 0;
+
+ /**
+ * Indicates that the window may now accept a document focus event. This
+ * should be called once a document has been loaded into the window.
+ */
+ virtual void SetReadyForFocus() = 0;
+
+ /**
+ * Whether the focused content within the window should show a focus ring.
+ */
+ virtual bool ShouldShowFocusRing() = 0;
+
+ /**
+ * Indicates that the page in the window has been hidden. This is used to
+ * reset the focus state.
+ */
+ virtual void PageHidden() = 0;
+
+ /**
+ * Instructs this window to asynchronously dispatch a hashchange event. This
+ * method must be called on an inner window.
+ */
+ virtual nsresult DispatchAsyncHashchange(nsIURI* aOldURI,
+ nsIURI* aNewURI) = 0;
+
+ /**
+ * Instructs this window to synchronously dispatch a popState event.
+ */
+ virtual nsresult DispatchSyncPopState() = 0;
+
+ /**
+ * Tell this window that it should listen for sensor changes of the given
+ * type.
+ */
+ virtual void EnableDeviceSensor(uint32_t aType) = 0;
+
+ /**
+ * Tell this window that it should remove itself from sensor change
+ * notifications.
+ */
+ virtual void DisableDeviceSensor(uint32_t aType) = 0;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ virtual void EnableOrientationChangeListener() = 0;
+ virtual void DisableOrientationChangeListener() = 0;
+#endif
+
+ /**
+ * Tell this window that there is an observer for gamepad input
+ *
+ * Inner windows only.
+ */
+ virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
+
+ /**
+ * Return the window id of this window
+ */
+ uint64_t WindowID() const { return mWindowID; }
+
+ // WebIDL-ish APIs
+ void MarkUncollectableForCCGeneration(uint32_t aGeneration) {
+ mMarkedCCGeneration = aGeneration;
+ }
+
+ uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
+
+ mozilla::dom::Navigator* Navigator();
+ mozilla::dom::MediaDevices* GetExtantMediaDevices() const;
+ virtual mozilla::dom::Location* Location() = 0;
+
+ virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
+
+ virtual nsresult GetInnerWidth(double* aWidth) = 0;
+ virtual nsresult GetInnerHeight(double* aHeight) = 0;
+
+ virtual already_AddRefed<nsICSSDeclaration> GetComputedStyle(
+ mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError) = 0;
+
+ virtual bool GetFullScreen() = 0;
+
+ virtual nsresult Focus(mozilla::dom::CallerType aCallerType) = 0;
+ virtual nsresult Close() = 0;
+
+ mozilla::dom::DocGroup* GetDocGroup() const;
+ virtual nsISerialEventTarget* EventTargetFor(
+ mozilla::TaskCategory aCategory) const = 0;
+
+ void SaveStorageAccessPermissionGranted();
+
+ bool HasStorageAccessPermissionGranted();
+
+ uint32_t UpdateLockCount(bool aIncrement) {
+ MOZ_ASSERT_IF(!aIncrement, mLockCount > 0);
+ mLockCount += aIncrement ? 1 : -1;
+ return mLockCount;
+ };
+ bool HasActiveLocks() { return mLockCount > 0; }
+
+ uint32_t UpdateWebTransportCount(bool aIncrement) {
+ MOZ_ASSERT_IF(!aIncrement, mWebTransportCount > 0);
+ mWebTransportCount += aIncrement ? 1 : -1;
+ return mWebTransportCount;
+ };
+ bool HasActiveWebTransports() { return mWebTransportCount > 0; }
+
+ protected:
+ void CreatePerformanceObjectIfNeeded();
+
+ // Lazily instantiate an about:blank document if necessary, and if
+ // we have what it takes to do so.
+ void MaybeCreateDoc();
+
+ void SetChromeEventHandlerInternal(
+ mozilla::dom::EventTarget* aChromeEventHandler) {
+ mChromeEventHandler = aChromeEventHandler;
+ // mParentTarget will be set when the next event is dispatched.
+ mParentTarget = nullptr;
+ }
+
+ virtual void UpdateParentTarget() = 0;
+
+ // These two variables are special in that they're set to the same
+ // value on both the outer window and the current inner window. Make
+ // sure you keep them in sync!
+ nsCOMPtr<mozilla::dom::EventTarget> mChromeEventHandler; // strong
+ RefPtr<Document> mDoc;
+ // Cache the URI when mDoc is cleared.
+ nsCOMPtr<nsIURI> mDocumentURI; // strong
+ nsCOMPtr<nsIURI> mDocBaseURI; // strong
+
+ nsCOMPtr<mozilla::dom::EventTarget> mParentTarget; // strong
+
+ RefPtr<mozilla::dom::Performance> mPerformance;
+ mozilla::UniquePtr<mozilla::dom::TimeoutManager> mTimeoutManager;
+
+ RefPtr<mozilla::dom::Navigator> mNavigator;
+
+ // These variables are only used on inner windows.
+ uint32_t mMutationBits;
+
+ uint32_t mActivePeerConnections = 0;
+
+ bool mIsDocumentLoaded;
+ bool mIsHandlingResizeEvent;
+ bool mMayHavePaintEventListener;
+ bool mMayHaveTouchEventListener;
+ bool mMayHaveSelectionChangeEventListener;
+ bool mMayHaveFormSelectEventListener;
+ bool mMayHaveMouseEnterLeaveEventListener;
+ bool mMayHavePointerEnterLeaveEventListener;
+ bool mMayHaveTransitionEventListener;
+ // Only used for telemetry probes. This may be wrong if some nodes have
+ // come from another document with `Document.adoptNode`.
+ bool mMayHaveBeforeInputEventListenerForTelemetry;
+ bool mMutationObserverHasObservedNodeForTelemetry;
+
+ // Our inner window's outer window.
+ nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
+
+ // The element within the document that is currently focused when this
+ // window is active.
+ RefPtr<mozilla::dom::Element> mFocusedElement;
+
+ // The AudioContexts created for the current document, if any.
+ nsTArray<mozilla::dom::AudioContext*> mAudioContexts; // Weak
+
+ // Instances of MediaKeys created in this inner window. Storing these allows
+ // us to shutdown MediaKeys when an inner windows is destroyed. We can also
+ // use the presence of MediaKeys to assess if a window has EME activity.
+ nsTArray<mozilla::dom::MediaKeys*> mMediaKeysInstances; // Weak
+
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ // A unique (as long as our 64-bit counter doesn't roll over) id for
+ // this window.
+ uint64_t mWindowID;
+
+ // Set to true once we've sent the (chrome|content)-document-global-created
+ // notification.
+ bool mHasNotifiedGlobalCreated;
+
+ // Whether when focused via an "unknown" focus method, we should show outlines
+ // by default or not. The initial value of this is true (so as to show
+ // outlines for stuff like html autofocus, or initial programmatic focus
+ // without any other user interaction).
+ bool mUnknownFocusMethodShouldShowOutline = true;
+
+ uint32_t mMarkedCCGeneration;
+
+ // mTopInnerWindow is used for tab-wise check by timeout throttling. It could
+ // be null.
+ nsCOMPtr<nsPIDOMWindowInner> mTopInnerWindow;
+
+ // The evidence that we have tried to cache mTopInnerWindow only once from
+ // SetNewDocument(). Note: We need this extra flag because mTopInnerWindow
+ // could be null and we don't want it to be set multiple times.
+ bool mHasTriedToCacheTopInnerWindow;
+
+ // The number of active IndexedDB databases.
+ uint32_t mNumOfIndexedDBDatabases;
+
+ // The number of open WebSockets.
+ uint32_t mNumOfOpenWebSockets;
+
+ // The event dispatch code sets and unsets this while keeping
+ // the event object alive.
+ mozilla::dom::Event* mEvent;
+
+ // A boolean flag indicating whether storage access is granted for the
+ // current window. These are also set as permissions, but it could happen
+ // that we need to access them synchronously in this context, and for
+ // this, we need a copy here.
+ bool mStorageAccessPermissionGranted;
+
+ // The WindowGlobalChild actor for this window.
+ //
+ // This will be non-null during the full lifetime of the window, initialized
+ // during SetNewDocument, and cleared during FreeInnerObjects.
+ RefPtr<mozilla::dom::WindowGlobalChild> mWindowGlobalChild;
+
+ bool mWasSuspendedByGroup;
+
+ /**
+ * Count of the number of active LockRequest objects, including ones from
+ * workers.
+ */
+ uint32_t mLockCount = 0;
+ /**
+ * Count of the number of active WebTransport objects, including ones from
+ * workers.
+ */
+ uint32_t mWebTransportCount = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+
+class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
+ protected:
+ using Document = mozilla::dom::Document;
+
+ explicit nsPIDOMWindowOuter(uint64_t aWindowID);
+
+ ~nsPIDOMWindowOuter();
+
+ void NotifyResumingDelayedMedia();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWOUTER_IID)
+
+ NS_IMPL_FROMEVENTTARGET_HELPER_WITH_GETTER(nsPIDOMWindowOuter,
+ GetAsWindowOuter())
+
+ static nsPIDOMWindowOuter* From(mozIDOMWindowProxy* aFrom) {
+ return static_cast<nsPIDOMWindowOuter*>(aFrom);
+ }
+
+ // Given an inner window, return its outer if the inner is the current inner.
+ // Otherwise (argument null or not an inner or not current) return null.
+ static nsPIDOMWindowOuter* GetFromCurrentInner(nsPIDOMWindowInner* aInner);
+
+ // Check whether a document is currently loading
+ inline bool IsLoading() const;
+ inline bool IsHandlingResizeEvent() const;
+
+ nsPIDOMWindowInner* GetCurrentInnerWindow() const { return mInnerWindow; }
+
+ nsPIDOMWindowInner* EnsureInnerWindow() {
+ // GetDoc forces inner window creation if there isn't one already
+ GetDoc();
+ return GetCurrentInnerWindow();
+ }
+
+ bool IsRootOuterWindow() { return mIsRootOuterWindow; }
+
+ // Internal getter/setter for the frame element, this version of the
+ // getter crosses chrome boundaries whereas the public scriptable
+ // one doesn't for security reasons.
+ mozilla::dom::Element* GetFrameElementInternal() const;
+ void SetFrameElementInternal(mozilla::dom::Element* aFrameElement);
+
+ bool IsBackground() { return mIsBackground; }
+
+ // Audio API
+ bool GetAudioMuted() const;
+
+ // No longer to delay media from starting for this window.
+ void ActivateMediaComponents();
+ bool ShouldDelayMediaFromStart() const;
+
+ void RefreshMediaElementsVolume();
+
+ virtual nsPIDOMWindowOuter* GetPrivateRoot() = 0;
+
+ /**
+ * |top| gets the root of the window hierarchy.
+ *
+ * This function does not cross chrome-content boundaries, so if this
+ * window's parent is of a different type, |top| will return this window.
+ *
+ * When script reads the top property, we run GetInProcessScriptableTop,
+ * which will not cross an <iframe mozbrowser> boundary.
+ *
+ * In contrast, C++ calls to GetTop are forwarded to GetRealTop, which
+ * ignores <iframe mozbrowser> boundaries.
+ */
+
+ virtual already_AddRefed<nsPIDOMWindowOuter>
+ GetInProcessTop() = 0; // Outer only
+ virtual already_AddRefed<nsPIDOMWindowOuter> GetInProcessParent() = 0;
+ virtual nsPIDOMWindowOuter* GetInProcessScriptableTop() = 0;
+ virtual nsPIDOMWindowOuter* GetInProcessScriptableParent() = 0;
+ virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() = 0;
+
+ /**
+ * Behaves identically to GetInProcessScriptableParent except that it
+ * returns null if GetInProcessScriptableParent would return this window.
+ */
+ virtual nsPIDOMWindowOuter* GetInProcessScriptableParentOrNull() = 0;
+
+ virtual void SetIsBackground(bool aIsBackground) = 0;
+
+ mozilla::dom::EventTarget* GetChromeEventHandler() const {
+ return mChromeEventHandler;
+ }
+
+ virtual void SetChromeEventHandler(
+ mozilla::dom::EventTarget* aChromeEventHandler) = 0;
+
+ mozilla::dom::EventTarget* GetParentTarget() {
+ if (!mParentTarget) {
+ UpdateParentTarget();
+ }
+ return mParentTarget;
+ }
+
+ mozilla::dom::ContentFrameMessageManager* GetMessageManager() {
+ // We maintain our mMessageManager state alongside mParentTarget.
+ if (!mParentTarget) {
+ UpdateParentTarget();
+ }
+ return mMessageManager;
+ }
+
+ Document* GetExtantDoc() const { return mDoc; }
+ nsIURI* GetDocumentURI() const;
+
+ Document* GetDoc() {
+ if (!mDoc) {
+ MaybeCreateDoc();
+ }
+ return mDoc;
+ }
+
+ // Set the window up with an about:blank document with the given principal and
+ // potentially a CSP and a COEP.
+ virtual void SetInitialPrincipal(
+ nsIPrincipal* aNewWindowPrincipal, nsIContentSecurityPolicy* aCSP,
+ const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCoep) = 0;
+
+ // Returns an object containing the window's state. This also suspends
+ // all running timeouts in the window.
+ virtual already_AddRefed<nsISupports> SaveWindowState() = 0;
+
+ // Restore the window state from aState.
+ virtual nsresult RestoreWindowState(nsISupports* aState) = 0;
+
+ // Determine if the window is suspended or frozen. Outer windows
+ // will forward this call to the inner window for convenience. If
+ // there is no inner window then the outer window is considered
+ // suspended and frozen by default.
+ virtual bool IsSuspended() const = 0;
+ virtual bool IsFrozen() const = 0;
+
+ // Fire any DOM notification events related to things that happened while
+ // the window was frozen.
+ virtual nsresult FireDelayedDOMEvents(bool aIncludeSubWindows) = 0;
+
+ /**
+ * Get the docshell in this window.
+ */
+ inline nsIDocShell* GetDocShell() const;
+
+ /**
+ * Get the browsing context in this window.
+ */
+ inline mozilla::dom::BrowsingContext* GetBrowsingContext() const;
+
+ /**
+ * Get the browsing context group this window belongs to.
+ */
+ mozilla::dom::BrowsingContextGroup* GetBrowsingContextGroup() const;
+
+ /**
+ * Set a new document in the window. Calling this method will in most cases
+ * create a new inner window. This may be called with a pointer to the current
+ * document, in that case the document remains unchanged, but a new inner
+ * window will be created.
+ *
+ * aDocument must not be null.
+ */
+ virtual nsresult SetNewDocument(
+ Document* aDocument, nsISupports* aState, bool aForceReuseInnerWindow,
+ mozilla::dom::WindowGlobalChild* aActor = nullptr) = 0;
+
+ /**
+ * Ensure the size and position of this window are up-to-date by doing
+ * a layout flush in the parent (which will in turn, do a layout flush
+ * in its parent, etc.).
+ */
+ virtual void EnsureSizeAndPositionUpToDate() = 0;
+
+ /**
+ * Suppresses/unsuppresses user initiated event handling in window's document
+ * and all in-process descendant documents.
+ */
+ virtual void SuppressEventHandling() = 0;
+ virtual void UnsuppressEventHandling() = 0;
+
+ /**
+ * Callback for notifying a window about a modal dialog being
+ * opened/closed with the window as a parent.
+ *
+ * If any script can run between the enter and leave modal states, and the
+ * window isn't top, the LeaveModalState() should be called on the window
+ * returned by EnterModalState().
+ */
+ virtual nsPIDOMWindowOuter* EnterModalState() = 0;
+ virtual void LeaveModalState() = 0;
+
+ virtual bool CanClose() = 0;
+ virtual void ForceClose() = 0;
+
+ /**
+ * Moves the top-level window into fullscreen mode if aIsFullScreen is true,
+ * otherwise exits fullscreen.
+ */
+ virtual nsresult SetFullscreenInternal(FullscreenReason aReason,
+ bool aIsFullscreen) = 0;
+ virtual void FullscreenWillChange(bool aIsFullscreen) = 0;
+ /**
+ * This function should be called when the fullscreen state is flipped.
+ * If no widget is involved the fullscreen change, this method is called
+ * by SetFullscreenInternal, otherwise, it is called when the widget
+ * finishes its change to or from fullscreen.
+ *
+ * @param aIsFullscreen indicates whether the widget is in fullscreen.
+ */
+ virtual void FinishFullscreenChange(bool aIsFullscreen) = 0;
+
+ virtual void ForceFullScreenInWidget() = 0;
+
+ virtual void MacFullscreenMenubarOverlapChanged(
+ mozilla::DesktopCoord aOverlapAmount) = 0;
+
+ // XXX: These focus methods all forward to the inner, could we change
+ // consumers to call these on the inner directly?
+
+ /*
+ * Get and set the currently focused element within the document. If
+ * aNeedsFocus is true, then set mNeedsFocus to true to indicate that a
+ * document focus event is needed.
+ *
+ * DO NOT CALL EITHER OF THESE METHODS DIRECTLY. USE THE FOCUS MANAGER
+ * INSTEAD.
+ */
+ inline mozilla::dom::Element* GetFocusedElement() const;
+
+ virtual void SetFocusedElement(mozilla::dom::Element* aElement,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) = 0;
+ /**
+ * Get whether a focused element focused by unknown reasons (like script
+ * focus) should match the :focus-visible pseudo-class.
+ */
+ bool UnknownFocusMethodShouldShowOutline() const;
+
+ /**
+ * Retrieves the method that was used to focus the current node.
+ */
+ virtual uint32_t GetFocusMethod() = 0;
+
+ /*
+ * Tells the window that it now has focus or has lost focus, based on the
+ * state of aFocus. If this method returns true, then the document loaded
+ * in the window has never received a focus event and expects to receive
+ * one. If false is returned, the document has received a focus event before
+ * and should only receive one if the window is being focused.
+ *
+ * aFocusMethod may be set to one of the focus method constants in
+ * nsIFocusManager to indicate how focus was set.
+ */
+ virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod) = 0;
+
+ /**
+ * Indicates that the window may now accept a document focus event. This
+ * should be called once a document has been loaded into the window.
+ */
+ virtual void SetReadyForFocus() = 0;
+
+ /**
+ * Whether the focused content within the window should show a focus ring.
+ */
+ virtual bool ShouldShowFocusRing() = 0;
+
+ /**
+ * Indicates that the page in the window has been hidden. This is used to
+ * reset the focus state.
+ */
+ virtual void PageHidden() = 0;
+
+ /**
+ * Return the window id of this window
+ */
+ uint64_t WindowID() const { return mWindowID; }
+
+ /**
+ * Dispatch a custom event with name aEventName targeted at this window.
+ * Returns whether the default action should be performed.
+ *
+ * Outer windows only.
+ */
+ virtual bool DispatchCustomEvent(
+ const nsAString& aEventName,
+ mozilla::ChromeOnlyDispatch aChromeOnlyDispatch =
+ mozilla::ChromeOnlyDispatch::eNo) = 0;
+
+ /**
+ * Like nsIDOMWindow::Open, except that we don't navigate to the given URL.
+ *
+ * Outer windows only.
+ */
+ virtual nsresult OpenNoNavigate(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ mozilla::dom::BrowsingContext** _retval) = 0;
+
+ /**
+ * Fire a popup blocked event on the document.
+ */
+ virtual void FirePopupBlockedEvent(Document* aDoc, nsIURI* aPopupURI,
+ const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) = 0;
+
+ // WebIDL-ish APIs
+ void MarkUncollectableForCCGeneration(uint32_t aGeneration) {
+ mMarkedCCGeneration = aGeneration;
+ }
+
+ uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
+
+ // XXX(nika): These feel like they should be inner window only, but they're
+ // called on the outer window.
+ virtual mozilla::dom::Navigator* GetNavigator() = 0;
+ virtual mozilla::dom::Location* GetLocation() = 0;
+
+ virtual nsresult GetPrompter(nsIPrompt** aPrompt) = 0;
+ virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
+ virtual already_AddRefed<mozilla::dom::Selection> GetSelection() = 0;
+ virtual mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder>
+ GetOpener() = 0;
+
+ // aLoadState will be passed on through to the windowwatcher.
+ // aForceNoOpener will act just like a "noopener" feature in aOptions except
+ // will not affect any other window features.
+ virtual nsresult Open(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ nsDocShellLoadState* aLoadState, bool aForceNoOpener,
+ mozilla::dom::BrowsingContext** _retval) = 0;
+ virtual nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ nsISupports* aExtraArgument,
+ mozilla::dom::BrowsingContext** _retval) = 0;
+
+ virtual nsresult GetInnerWidth(double* aWidth) = 0;
+ virtual nsresult GetInnerHeight(double* aHeight) = 0;
+
+ virtual mozilla::dom::Element* GetFrameElement() = 0;
+
+ virtual bool Closed() = 0;
+ virtual bool GetFullScreen() = 0;
+ virtual nsresult SetFullScreen(bool aFullscreen) = 0;
+
+ virtual nsresult Focus(mozilla::dom::CallerType aCallerType) = 0;
+ virtual nsresult Close() = 0;
+
+ virtual nsresult MoveBy(int32_t aXDif, int32_t aYDif) = 0;
+
+ virtual void UpdateCommands(const nsAString& anAction,
+ mozilla::dom::Selection* aSel,
+ int16_t aReason) = 0;
+
+ mozilla::dom::DocGroup* GetDocGroup() const;
+ virtual nsISerialEventTarget* EventTargetFor(
+ mozilla::TaskCategory aCategory) const = 0;
+
+ already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
+ already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+
+ protected:
+ // Lazily instantiate an about:blank document if necessary, and if
+ // we have what it takes to do so.
+ void MaybeCreateDoc();
+
+ void SetChromeEventHandlerInternal(
+ mozilla::dom::EventTarget* aChromeEventHandler);
+
+ virtual void UpdateParentTarget() = 0;
+
+ // These two variables are special in that they're set to the same
+ // value on both the outer window and the current inner window. Make
+ // sure you keep them in sync!
+ nsCOMPtr<mozilla::dom::EventTarget> mChromeEventHandler; // strong
+ RefPtr<Document> mDoc;
+ // Cache the URI when mDoc is cleared.
+ nsCOMPtr<nsIURI> mDocumentURI; // strong
+
+ nsCOMPtr<mozilla::dom::EventTarget> mParentTarget; // strong
+ RefPtr<mozilla::dom::ContentFrameMessageManager> mMessageManager; // strong
+
+ nsCOMPtr<mozilla::dom::Element> mFrameElement;
+
+ // These references are used by nsGlobalWindow.
+ nsCOMPtr<nsIDocShell> mDocShell;
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ uint32_t mModalStateDepth;
+
+ uint32_t mSuppressEventHandlingDepth;
+
+ // Tracks whether our docshell is active. If it is, mIsBackground
+ // is false. Too bad we have so many different concepts of
+ // "active".
+ bool mIsBackground;
+
+ bool mIsRootOuterWindow;
+
+ // And these are the references between inner and outer windows.
+ nsPIDOMWindowInner* MOZ_NON_OWNING_REF mInnerWindow;
+
+ // A unique (as long as our 64-bit counter doesn't roll over) id for
+ // this window.
+ uint64_t mWindowID;
+
+ uint32_t mMarkedCCGeneration;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
+
+#include "nsPIDOMWindowInlines.h"
+
+#endif // nsPIDOMWindow_h__
diff --git a/dom/base/nsPIDOMWindowInlines.h b/dom/base/nsPIDOMWindowInlines.h
new file mode 100644
index 0000000000..95848e2945
--- /dev/null
+++ b/dom/base/nsPIDOMWindowInlines.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_nsPIDOMWindowInlines_h___
+#define dom_base_nsPIDOMWindowInlines_h___
+
+inline bool nsPIDOMWindowOuter::IsLoading() const {
+ auto* win = GetCurrentInnerWindow();
+
+ if (!win) {
+ NS_ERROR("No current inner window available!");
+
+ return false;
+ }
+
+ return win->IsLoading();
+}
+
+inline bool nsPIDOMWindowInner::IsLoading() const {
+ if (!mOuterWindow) {
+ NS_ERROR("IsLoading() called on orphan inner window!");
+
+ return false;
+ }
+
+ return !mIsDocumentLoaded;
+}
+
+inline bool nsPIDOMWindowOuter::IsHandlingResizeEvent() const {
+ auto* win = GetCurrentInnerWindow();
+
+ if (!win) {
+ NS_ERROR("No current inner window available!");
+
+ return false;
+ }
+
+ return win->IsHandlingResizeEvent();
+}
+
+inline bool nsPIDOMWindowInner::IsHandlingResizeEvent() const {
+ if (!mOuterWindow) {
+ NS_ERROR("IsHandlingResizeEvent() called on orphan inner window!");
+
+ return false;
+ }
+
+ return mIsHandlingResizeEvent;
+}
+
+inline bool nsPIDOMWindowInner::HasActiveDocument() const {
+ return IsCurrentInnerWindow();
+}
+
+inline bool nsPIDOMWindowInner::IsTopInnerWindow() const {
+ return mTopInnerWindow == this;
+}
+
+inline nsIDocShell* nsPIDOMWindowOuter::GetDocShell() const {
+ return mDocShell;
+}
+
+inline nsIDocShell* nsPIDOMWindowInner::GetDocShell() const {
+ return mOuterWindow ? mOuterWindow->GetDocShell() : nullptr;
+}
+
+inline mozilla::dom::BrowsingContext* nsPIDOMWindowOuter::GetBrowsingContext()
+ const {
+ return mBrowsingContext;
+}
+
+inline mozilla::dom::BrowsingContext* nsPIDOMWindowInner::GetBrowsingContext()
+ const {
+ return mBrowsingContext;
+}
+
+inline mozilla::dom::Element* nsPIDOMWindowOuter::GetFocusedElement() const {
+ return mInnerWindow ? mInnerWindow->GetFocusedElement() : nullptr;
+}
+
+inline bool nsPIDOMWindowOuter::UnknownFocusMethodShouldShowOutline() const {
+ return mInnerWindow && mInnerWindow->UnknownFocusMethodShouldShowOutline();
+}
+
+#endif
diff --git a/dom/base/nsPIWindowRoot.h b/dom/base/nsPIWindowRoot.h
new file mode 100644
index 0000000000..fe4a0df484
--- /dev/null
+++ b/dom/base/nsPIWindowRoot.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPIWindowRoot_h__
+#define nsPIWindowRoot_h__
+
+#include "nsISupports.h"
+#include "mozilla/dom/EventTarget.h"
+
+class nsPIDOMWindowOuter;
+class nsIControllers;
+class nsIController;
+class nsINode;
+class nsIRemoteTab;
+
+#define NS_IWINDOWROOT_IID \
+ { \
+ 0xb8724c49, 0xc398, 0x4f9b, { \
+ 0x82, 0x59, 0x87, 0x27, 0xa6, 0x47, 0xdd, 0x0f \
+ } \
+ }
+
+class nsPIWindowRoot : public mozilla::dom::EventTarget {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
+
+ bool IsRootWindow() const final { return true; }
+
+ NS_IMPL_FROMEVENTTARGET_HELPER(nsPIWindowRoot, IsRootWindow())
+
+ virtual nsPIDOMWindowOuter* GetWindow() = 0;
+
+ // get and set the node that is the context of a popup menu
+ virtual already_AddRefed<nsINode> GetPopupNode() = 0;
+ virtual void SetPopupNode(nsINode* aNode) = 0;
+
+ /**
+ * @param aForVisibleWindow true if caller needs controller which is
+ * associated with visible window.
+ */
+ virtual nsresult GetControllerForCommand(const char* aCommand,
+ bool aForVisibleWindow,
+ nsIController** aResult) = 0;
+
+ /**
+ * @param aForVisibleWindow true if caller needs controllers which are
+ * associated with visible window.
+ */
+ virtual nsresult GetControllers(bool aForVisibleWindow,
+ nsIControllers** aResult) = 0;
+
+ virtual void GetEnabledDisabledCommands(
+ nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands) = 0;
+
+ virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0;
+ virtual mozilla::dom::EventTarget* GetParentTarget() = 0;
+
+ // Stores a weak reference to the browser.
+ virtual void AddBrowser(nsIRemoteTab* aBrowser) = 0;
+ virtual void RemoveBrowser(nsIRemoteTab* aBrowser) = 0;
+
+ using BrowserEnumerator = void (*)(nsIRemoteTab* aTab, void* aArg);
+
+ // Enumerate all stored browsers that for which the weak reference is valid.
+ virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) = 0;
+};
+
+namespace mozilla::dom {
+
+inline nsPIWindowRoot* EventTarget::GetAsWindowRoot() {
+ return IsRootWindow() ? static_cast<nsPIWindowRoot*>(this) : nullptr;
+}
+
+inline const nsPIWindowRoot* EventTarget::GetAsWindowRoot() const {
+ return IsRootWindow() ? static_cast<const nsPIWindowRoot*>(this) : nullptr;
+}
+
+inline nsPIWindowRoot* EventTarget::AsWindowRoot() {
+ MOZ_DIAGNOSTIC_ASSERT(IsRootWindow());
+ return static_cast<nsPIWindowRoot*>(this);
+}
+
+inline const nsPIWindowRoot* EventTarget::AsWindowRoot() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsRootWindow());
+ return static_cast<const nsPIWindowRoot*>(this);
+}
+
+} // namespace mozilla::dom
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
+
+#endif // nsPIWindowRoot_h__
diff --git a/dom/base/nsPluginArray.cpp b/dom/base/nsPluginArray.cpp
new file mode 100644
index 0000000000..90b498fdde
--- /dev/null
+++ b/dom/base/nsPluginArray.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPluginArray.h"
+
+#include "mozilla/dom/PluginArrayBinding.h"
+#include "mozilla/dom/PluginBinding.h"
+#include "mozilla/StaticPrefs_pdfjs.h"
+
+#include "nsMimeTypeArray.h"
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindowInner.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// These plugin and mime types are hard-coded by the HTML spec.
+// The "main" plugin name is used with the only plugin that is
+// referenced by MIME types (via nsMimeType::GetEnabledPlugin).
+// The "extra" of the plugin names are associated with MIME types that
+// reference the main plugin.
+// This is all defined in the HTML spec, section 8.9.1.6
+// "PDF Viewing Support".
+static const nsLiteralString kMainPluginName = u"PDF Viewer"_ns;
+static const nsLiteralString kExtraPluginNames[] = {
+ u"Chrome PDF Viewer"_ns, u"Chromium PDF Viewer"_ns,
+ u"Microsoft Edge PDF Viewer"_ns, u"WebKit built-in PDF"_ns};
+static const nsLiteralString kMimeTypeNames[] = {u"application/pdf"_ns,
+ u"text/pdf"_ns};
+
+nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {
+ // Create the hard-coded PDF plugin types that share MIME type arrays.
+ mPlugins[0] = MakeRefPtr<nsPluginElement>(this, aWindow, kMainPluginName);
+
+ mozilla::Array<RefPtr<nsMimeType>, 2> mimeTypes;
+ for (uint32_t i = 0; i < ArrayLength(kMimeTypeNames); ++i) {
+ mimeTypes[i] = MakeRefPtr<nsMimeType>(mPlugins[0], kMimeTypeNames[i]);
+ }
+ mMimeTypeArray = MakeRefPtr<nsMimeTypeArray>(aWindow, mimeTypes);
+
+ for (uint32_t i = 0; i < ArrayLength(kExtraPluginNames); ++i) {
+ mPlugins[i + 1] =
+ MakeRefPtr<nsPluginElement>(this, aWindow, kExtraPluginNames[i]);
+ }
+}
+
+nsPluginArray::~nsPluginArray() = default;
+
+nsPIDOMWindowInner* nsPluginArray::GetParentObject() const {
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject* nsPluginArray::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PluginArray_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPluginElement* nsPluginArray::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ if (!ForceNoPlugins() && aIndex < ArrayLength(mPlugins)) {
+ aFound = true;
+ return mPlugins[aIndex];
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+nsPluginElement* nsPluginArray::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ if (ForceNoPlugins()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ for (const auto& plugin : mPlugins) {
+ if (plugin->Name().Equals(aName)) {
+ aFound = true;
+ return plugin;
+ }
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+void nsPluginArray::GetSupportedNames(nsTArray<nsString>& aRetval) {
+ if (ForceNoPlugins()) {
+ return;
+ }
+
+ for (auto& plugin : mPlugins) {
+ aRetval.AppendElement(plugin->Name());
+ }
+}
+
+bool nsPluginArray::ForceNoPlugins() {
+ return StaticPrefs::pdfjs_disabled() &&
+ !nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting();
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginArray)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginArray)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK(nsPluginArray, mPlugins[0],
+ mPlugins[1], mPlugins[2],
+ mPlugins[3], mPlugins[4],
+ mMimeTypeArray, mWindow)
+
+// nsPluginElement implementation.
+
+nsPluginElement::nsPluginElement(nsPluginArray* aPluginArray,
+ nsPIDOMWindowInner* aWindow,
+ const nsAString& aName)
+ : mPluginArray(aPluginArray), mWindow(aWindow), mName(aName) {}
+
+nsPluginArray* nsPluginElement::GetParentObject() const { return mPluginArray; }
+
+JSObject* nsPluginElement::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Plugin_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsMimeType* nsPluginElement::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ return MimeTypeArray()->IndexedGetter(aIndex, aFound);
+}
+
+nsMimeType* nsPluginElement::NamedGetter(const nsAString& aName, bool& aFound) {
+ return MimeTypeArray()->NamedGetter(aName, aFound);
+}
+
+void nsPluginElement::GetSupportedNames(nsTArray<nsString>& retval) {
+ return MimeTypeArray()->GetSupportedNames(retval);
+}
+
+uint32_t nsPluginElement::Length() { return MimeTypeArray()->Length(); }
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginElement)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginElement)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginElement)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginElement, mWindow, mPluginArray)
diff --git a/dom/base/nsPluginArray.h b/dom/base/nsPluginArray.h
new file mode 100644
index 0000000000..7b1cc65440
--- /dev/null
+++ b/dom/base/nsPluginArray.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPluginArray_h___
+#define nsPluginArray_h___
+
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Array.h"
+
+class nsPIDOMWindowInner;
+class nsPluginElement;
+class nsMimeTypeArray;
+class nsMimeType;
+
+/**
+ * Array class backing HTML's navigator.plugins. This always holds references
+ * to the hard-coded set of PDF plugins defined by HTML but it only consults
+ * them if "pdfjs.disabled" is false. There is never more than one of these
+ * per DOM window.
+ */
+class nsPluginArray final : public nsSupportsWeakReference,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsPluginArray)
+
+ explicit nsPluginArray(nsPIDOMWindowInner* aWindow);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsMimeTypeArray* MimeTypeArray() { return mMimeTypeArray; }
+
+ // PluginArray WebIDL methods
+ uint32_t Length() { return ForceNoPlugins() ? 0 : ArrayLength(mPlugins); }
+
+ nsPluginElement* Item(uint32_t aIndex) {
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+ }
+
+ nsPluginElement* NamedItem(const nsAString& aName) {
+ bool unused;
+ return NamedGetter(aName, unused);
+ }
+
+ nsPluginElement* IndexedGetter(uint32_t aIndex, bool& aFound);
+
+ nsPluginElement* NamedGetter(const nsAString& aName, bool& aFound);
+
+ void GetSupportedNames(nsTArray<nsString>& aRetval);
+
+ void Refresh() {}
+
+ private:
+ virtual ~nsPluginArray();
+
+ bool ForceNoPlugins();
+
+ RefPtr<nsMimeTypeArray> mMimeTypeArray;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ mozilla::Array<RefPtr<nsPluginElement>, 5> mPlugins;
+};
+
+/**
+ * Plugin class backing entries in HTML's navigator.plugins array. There is
+ * a fixed set of these, as defined by HTML.
+ */
+class nsPluginElement final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsPluginElement)
+
+ explicit nsPluginElement(nsPluginArray* aPluginArray,
+ nsPIDOMWindowInner* aWindow, const nsAString& aName);
+
+ nsPluginArray* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Plugin WebIDL methods
+ void GetDescription(nsString& retval) const { retval = kDescription; }
+
+ void GetFilename(nsString& retval) const { retval = kFilename; }
+
+ void GetName(nsString& retval) const { retval = mName; }
+ const nsString& Name() const { return mName; }
+
+ nsMimeType* Item(uint32_t index) {
+ bool unused;
+ return IndexedGetter(index, unused);
+ }
+
+ nsMimeType* NamedItem(const nsAString& name) {
+ bool unused;
+ return NamedGetter(name, unused);
+ }
+
+ uint32_t Length();
+
+ nsMimeType* IndexedGetter(uint32_t index, bool& found);
+
+ nsMimeType* NamedGetter(const nsAString& name, bool& found);
+
+ void GetSupportedNames(nsTArray<nsString>& retval);
+
+ protected:
+ virtual ~nsPluginElement() = default;
+
+ nsMimeTypeArray* MimeTypeArray() { return mPluginArray->MimeTypeArray(); }
+
+ static constexpr nsLiteralString kDescription =
+ u"Portable Document Format"_ns;
+ static constexpr nsLiteralString kFilename = u"internal-pdf-viewer"_ns;
+
+ // Note that this creates an explicit reference cycle:
+ //
+ // nsPluginElement -> nsPluginArray -> nsPluginElement
+ //
+ // We rely on the cycle collector to break this cycle.
+ RefPtr<nsPluginArray> mPluginArray;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsString mName;
+};
+
+#endif /* nsPluginArray_h___ */
diff --git a/dom/base/nsPropertyTable.cpp b/dom/base/nsPropertyTable.cpp
new file mode 100644
index 0000000000..63343a8141
--- /dev/null
+++ b/dom/base/nsPropertyTable.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsPropertyTable allows a set of arbitrary key/value pairs to be stored
+ * for any number of nodes, in a global hashtable rather than on the nodes
+ * themselves. Nodes can be any type of object; the hashtable keys are
+ * nsAtom pointers, and the values are void pointers.
+ */
+
+#include "nsPropertyTable.h"
+
+#include "mozilla/MemoryReporting.h"
+
+#include "PLDHashTable.h"
+#include "nsError.h"
+#include "nsAtom.h"
+
+struct PropertyListMapEntry : public PLDHashEntryHdr {
+ const void* key;
+ void* value;
+};
+
+//----------------------------------------------------------------------
+
+class nsPropertyTable::PropertyList {
+ public:
+ PropertyList(nsAtom* aName, NSPropertyDtorFunc aDtorFunc, void* aDtorData,
+ bool aTransfer);
+ ~PropertyList();
+
+ // Removes the property associated with the given object, and destroys
+ // the property value
+ bool RemovePropertyFor(nsPropertyOwner aObject);
+
+ // Destroy all remaining properties (without removing them)
+ void Destroy();
+
+ bool Equals(const nsAtom* aPropertyName) { return mName == aPropertyName; }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ RefPtr<nsAtom> mName; // property name
+ PLDHashTable mObjectValueMap; // map of object/value pairs
+ NSPropertyDtorFunc mDtorFunc; // property specific value dtor function
+ void* mDtorData; // pointer to pass to dtor
+ bool mTransfer; // whether to transfer in
+ // TransferOrRemoveAllPropertiesFor
+
+ PropertyList* mNext;
+};
+
+void nsPropertyTable::RemoveAllProperties() {
+ while (mPropertyList) {
+ PropertyList* tmp = mPropertyList;
+
+ mPropertyList = mPropertyList->mNext;
+ tmp->Destroy();
+ delete tmp;
+ }
+}
+
+void nsPropertyTable::RemoveAllPropertiesFor(nsPropertyOwner aObject) {
+ for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+ prop->RemovePropertyFor(aObject);
+ }
+}
+
+nsresult nsPropertyTable::TransferOrRemoveAllPropertiesFor(
+ nsPropertyOwner aObject, nsPropertyTable& aOtherTable) {
+ nsresult rv = NS_OK;
+ for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+ if (prop->mTransfer) {
+ auto entry = static_cast<PropertyListMapEntry*>(
+ prop->mObjectValueMap.Search(aObject));
+ if (entry) {
+ rv = aOtherTable.SetProperty(aObject, prop->mName, entry->value,
+ prop->mDtorFunc, prop->mDtorData,
+ prop->mTransfer);
+ if (NS_FAILED(rv)) {
+ RemoveAllPropertiesFor(aObject);
+ aOtherTable.RemoveAllPropertiesFor(aObject);
+ break;
+ }
+
+ prop->mObjectValueMap.RemoveEntry(entry);
+ }
+ } else {
+ prop->RemovePropertyFor(aObject);
+ }
+ }
+
+ return rv;
+}
+
+void nsPropertyTable::Enumerate(nsPropertyOwner aObject,
+ NSPropertyFunc aCallback, void* aData) {
+ PropertyList* prop;
+ for (prop = mPropertyList; prop; prop = prop->mNext) {
+ auto entry = static_cast<PropertyListMapEntry*>(
+ prop->mObjectValueMap.Search(aObject));
+ if (entry) {
+ aCallback(const_cast<void*>(aObject.get()), prop->mName, entry->value,
+ aData);
+ }
+ }
+}
+
+void nsPropertyTable::EnumerateAll(NSPropertyFunc aCallBack, void* aData) {
+ for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+ for (auto iter = prop->mObjectValueMap.ConstIter(); !iter.Done();
+ iter.Next()) {
+ auto entry = static_cast<PropertyListMapEntry*>(iter.Get());
+ aCallBack(const_cast<void*>(entry->key), prop->mName, entry->value,
+ aData);
+ }
+ }
+}
+
+void* nsPropertyTable::GetPropertyInternal(nsPropertyOwner aObject,
+ const nsAtom* aPropertyName,
+ bool aRemove, nsresult* aResult) {
+ MOZ_ASSERT(aPropertyName && aObject, "unexpected null param");
+ nsresult rv = NS_PROPTABLE_PROP_NOT_THERE;
+ void* propValue = nullptr;
+
+ PropertyList* propertyList = GetPropertyListFor(aPropertyName);
+ if (propertyList) {
+ auto entry = static_cast<PropertyListMapEntry*>(
+ propertyList->mObjectValueMap.Search(aObject));
+ if (entry) {
+ propValue = entry->value;
+ if (aRemove) {
+ // don't call propertyList->mDtorFunc. That's the caller's job now.
+ propertyList->mObjectValueMap.RemoveEntry(entry);
+ }
+ rv = NS_OK;
+ }
+ }
+
+ if (aResult) *aResult = rv;
+
+ return propValue;
+}
+
+nsresult nsPropertyTable::SetPropertyInternal(
+ nsPropertyOwner aObject, nsAtom* aPropertyName, void* aPropertyValue,
+ NSPropertyDtorFunc aPropDtorFunc, void* aPropDtorData, bool aTransfer) {
+ MOZ_ASSERT(aPropertyName && aObject, "unexpected null param");
+
+ PropertyList* propertyList = GetPropertyListFor(aPropertyName);
+
+ if (propertyList) {
+ // Make sure the dtor function and data and the transfer flag match
+ if (aPropDtorFunc != propertyList->mDtorFunc ||
+ aPropDtorData != propertyList->mDtorData ||
+ aTransfer != propertyList->mTransfer) {
+ NS_WARNING("Destructor/data mismatch while setting property");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ } else {
+ propertyList = new PropertyList(aPropertyName, aPropDtorFunc, aPropDtorData,
+ aTransfer);
+ propertyList->mNext = mPropertyList;
+ mPropertyList = propertyList;
+ }
+
+ // The current property value (if there is one) is replaced and the current
+ // value is destroyed
+ nsresult result = NS_OK;
+ auto entry = static_cast<PropertyListMapEntry*>(
+ propertyList->mObjectValueMap.Add(aObject, mozilla::fallible));
+ if (!entry) return NS_ERROR_OUT_OF_MEMORY;
+ // A nullptr entry->key is the sign that the entry has just been allocated
+ // for us. If it's non-nullptr then we have an existing entry.
+ if (entry->key) {
+ if (propertyList->mDtorFunc) {
+ propertyList->mDtorFunc(const_cast<void*>(entry->key), aPropertyName,
+ entry->value, propertyList->mDtorData);
+ }
+ result = NS_PROPTABLE_PROP_OVERWRITTEN;
+ }
+ entry->key = aObject;
+ entry->value = aPropertyValue;
+
+ return result;
+}
+
+nsresult nsPropertyTable::RemoveProperty(nsPropertyOwner aObject,
+ const nsAtom* aPropertyName) {
+ MOZ_ASSERT(aPropertyName && aObject, "unexpected null param");
+
+ PropertyList* propertyList = GetPropertyListFor(aPropertyName);
+ if (propertyList) {
+ if (propertyList->RemovePropertyFor(aObject)) {
+ return NS_OK;
+ }
+ }
+
+ return NS_PROPTABLE_PROP_NOT_THERE;
+}
+
+nsPropertyTable::PropertyList* nsPropertyTable::GetPropertyListFor(
+ const nsAtom* aPropertyName) const {
+ PropertyList* result;
+
+ for (result = mPropertyList; result; result = result->mNext) {
+ if (result->Equals(aPropertyName)) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------
+
+nsPropertyTable::PropertyList::PropertyList(nsAtom* aName,
+ NSPropertyDtorFunc aDtorFunc,
+ void* aDtorData, bool aTransfer)
+ : mName(aName),
+ mObjectValueMap(PLDHashTable::StubOps(), sizeof(PropertyListMapEntry)),
+ mDtorFunc(aDtorFunc),
+ mDtorData(aDtorData),
+ mTransfer(aTransfer),
+ mNext(nullptr) {}
+
+nsPropertyTable::PropertyList::~PropertyList() = default;
+
+void nsPropertyTable::PropertyList::Destroy() {
+ // Enumerate any remaining object/value pairs and destroy the value object.
+ if (mDtorFunc) {
+ for (auto iter = mObjectValueMap.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PropertyListMapEntry*>(iter.Get());
+ mDtorFunc(const_cast<void*>(entry->key), mName, entry->value, mDtorData);
+ }
+ }
+}
+
+bool nsPropertyTable::PropertyList::RemovePropertyFor(nsPropertyOwner aObject) {
+ auto entry =
+ static_cast<PropertyListMapEntry*>(mObjectValueMap.Search(aObject));
+ if (!entry) return false;
+
+ void* value = entry->value;
+ mObjectValueMap.RemoveEntry(entry);
+
+ if (mDtorFunc)
+ mDtorFunc(const_cast<void*>(aObject.get()), mName, value, mDtorData);
+
+ return true;
+}
+
+size_t nsPropertyTable::PropertyList::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ size_t n = aMallocSizeOf(this);
+ n += mObjectValueMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+size_t nsPropertyTable::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+
+ for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+ n += prop->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+size_t nsPropertyTable::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+/* static */
+void nsPropertyTable::SupportsDtorFunc(void* aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, void* aData) {
+ nsISupports* propertyValue = static_cast<nsISupports*>(aPropertyValue);
+ NS_IF_RELEASE(propertyValue);
+}
diff --git a/dom/base/nsPropertyTable.h b/dom/base/nsPropertyTable.h
new file mode 100644
index 0000000000..7fb90f6b9f
--- /dev/null
+++ b/dom/base/nsPropertyTable.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsPropertyTable allows a set of arbitrary key/value pairs to be stored
+ * for any number of nodes, in a global hashtable rather than on the nodes
+ * themselves. Nodes can be any type of object; the hashtable keys are
+ * nsAtom pointers, and the values are void pointers.
+ */
+
+#ifndef nsPropertyTable_h_
+#define nsPropertyTable_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nscore.h"
+
+class nsAtom;
+
+using NSPropertyFunc = void (*)(void* aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, void* aData);
+
+/**
+ * Callback type for property destructors. |aObject| is the object
+ * the property is being removed for, |aPropertyName| is the property
+ * being removed, |aPropertyValue| is the value of the property, and |aData|
+ * is the opaque destructor data that was passed to SetProperty().
+ **/
+using NSPropertyDtorFunc = NSPropertyFunc;
+class nsINode;
+class nsIFrame;
+
+class nsPropertyOwner {
+ public:
+ nsPropertyOwner(const nsPropertyOwner& aOther) = default;
+
+ // These are the types of objects that can own properties. No object should
+ // inherit more then one of these classes.
+ // To add support for more types just add to this list.
+ MOZ_IMPLICIT nsPropertyOwner(const nsINode* aObject) : mObject(aObject) {}
+ MOZ_IMPLICIT nsPropertyOwner(const nsIFrame* aObject) : mObject(aObject) {}
+
+ operator const void*() { return mObject; }
+ const void* get() { return mObject; }
+
+ private:
+ const void* mObject;
+};
+
+class nsPropertyTable {
+ public:
+ /**
+ * Get the value of the property |aPropertyName| for node |aObject|.
+ * |aResult|, if supplied, is filled in with a return status code.
+ **/
+ void* GetProperty(const nsPropertyOwner& aObject, const nsAtom* aPropertyName,
+ nsresult* aResult = nullptr) {
+ return GetPropertyInternal(aObject, aPropertyName, false, aResult);
+ }
+
+ /**
+ * Set the value of the property |aPropertyName| to
+ * |aPropertyValue| for node |aObject|. |aDtor| is a destructor for the
+ * property value to be called if the property is removed. It can be null
+ * if no destructor is required. |aDtorData| is an optional pointer to an
+ * opaque context to be passed to the property destructor. Note that the
+ * destructor is global for each property name regardless of node; it is an
+ * error to set a given property with a different destructor than was used
+ * before (this will return NS_ERROR_INVALID_ARG). If |aTransfer| is true
+ * the property will be transfered to the new table when the property table
+ * for |aObject| changes (currently the tables for nodes are owned by their
+ * ownerDocument, so if the ownerDocument for a node changes, its property
+ * table changes too). If |aTransfer| is false the property will just be
+ * deleted instead.
+ */
+ nsresult SetProperty(const nsPropertyOwner& aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, NSPropertyDtorFunc aDtor,
+ void* aDtorData, bool aTransfer = false) {
+ return SetPropertyInternal(aObject, aPropertyName, aPropertyValue, aDtor,
+ aDtorData, aTransfer);
+ }
+
+ /**
+ * Remove the property |aPropertyName| in the global category for object
+ * |aObject|. The property's destructor function will be called.
+ */
+ nsresult RemoveProperty(nsPropertyOwner aObject, const nsAtom* aPropertyName);
+
+ /**
+ * Remove the property |aPropertyName| in the global category for object
+ * |aObject|, but do not call the property's destructor function. The
+ * property value is returned.
+ */
+ void* TakeProperty(const nsPropertyOwner& aObject,
+ const nsAtom* aPropertyName, nsresult* aStatus = nullptr) {
+ return GetPropertyInternal(aObject, aPropertyName, true, aStatus);
+ }
+
+ /**
+ * Removes all of the properties for object |aObject|, calling the destructor
+ * function for each property.
+ */
+ void RemoveAllPropertiesFor(nsPropertyOwner aObject);
+
+ /**
+ * Transfers all properties for object |aObject| that were set with the
+ * |aTransfer| argument as true to |aTable|. Removes the other properties for
+ * object |aObject|, calling the destructor function for each property.
+ * If transfering a property fails, this deletes all the properties for object
+ * |aObject|.
+ */
+ nsresult TransferOrRemoveAllPropertiesFor(nsPropertyOwner aObject,
+ nsPropertyTable& aOtherTable);
+
+ /**
+ * Enumerate the properties for object |aObject|.
+ * For every property |aCallback| will be called with as arguments |aObject|,
+ * the property name, the property value and |aData|.
+ */
+ void Enumerate(nsPropertyOwner aObject, NSPropertyFunc aCallback,
+ void* aData);
+
+ /**
+ * Enumerate all the properties.
+ * For every property |aCallback| will be called with arguments the owner,
+ * the property name, the property value and |aData|.
+ */
+ void EnumerateAll(NSPropertyFunc aCallback, void* aData);
+
+ /**
+ * Removes all of the properties for all objects in the property table,
+ * calling the destructor function for each property.
+ */
+ void RemoveAllProperties();
+
+ nsPropertyTable() : mPropertyList(nullptr) {}
+ ~nsPropertyTable() { RemoveAllProperties(); }
+
+ /**
+ * Function useable as destructor function for property data that is
+ * XPCOM objects. The function will call NS_IF_RELASE on the value
+ * to destroy it.
+ */
+ static void SupportsDtorFunc(void* aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, void* aData);
+
+ class PropertyList;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ void DestroyPropertyList();
+ PropertyList* GetPropertyListFor(const nsAtom* aPropertyName) const;
+ void* GetPropertyInternal(nsPropertyOwner aObject,
+ const nsAtom* aPropertyName, bool aRemove,
+ nsresult* aStatus);
+ nsresult SetPropertyInternal(nsPropertyOwner aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, NSPropertyDtorFunc aDtor,
+ void* aDtorData, bool aTransfer);
+
+ PropertyList* mPropertyList;
+};
+#endif
diff --git a/dom/base/nsQueryContentEventResult.cpp b/dom/base/nsQueryContentEventResult.cpp
new file mode 100644
index 0000000000..9996d1bf6b
--- /dev/null
+++ b/dom/base/nsQueryContentEventResult.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsQueryContentEventResult.h"
+
+#include <utility>
+
+#include "mozilla/TextEvents.h"
+#include "nsIWidget.h"
+#include "nsPoint.h"
+
+using namespace mozilla;
+
+/******************************************************************************
+ * Is*PropertyAvailable() methods which check if the property is available
+ * (valid) with the event message.
+ ******************************************************************************/
+
+static bool IsNotFoundPropertyAvailable(EventMessage aEventMessage) {
+ return aEventMessage == eQuerySelectedText ||
+ aEventMessage == eQueryCharacterAtPoint;
+}
+
+static bool IsOffsetPropertyAvailable(EventMessage aEventMessage) {
+ return aEventMessage == eQueryTextContent ||
+ aEventMessage == eQueryTextRect || aEventMessage == eQueryCaretRect ||
+ IsNotFoundPropertyAvailable(aEventMessage);
+}
+
+static bool IsRectRelatedPropertyAvailable(EventMessage aEventMessage) {
+ return aEventMessage == eQueryCaretRect || aEventMessage == eQueryTextRect ||
+ aEventMessage == eQueryEditorRect ||
+ aEventMessage == eQueryCharacterAtPoint;
+}
+
+/******************************************************************************
+ * nsQueryContentEventResult
+ ******************************************************************************/
+
+NS_INTERFACE_MAP_BEGIN(nsQueryContentEventResult)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIQueryContentEventResult)
+ NS_INTERFACE_MAP_ENTRY(nsIQueryContentEventResult)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsQueryContentEventResult)
+NS_IMPL_RELEASE(nsQueryContentEventResult)
+
+nsQueryContentEventResult::nsQueryContentEventResult(
+ mozilla::WidgetQueryContentEvent&& aEvent)
+ : mEventMessage(aEvent.mMessage),
+ mSucceeded(aEvent.Succeeded()),
+ mReversed(false) {
+ if (mSucceeded) {
+ mOffsetAndData = std::move(aEvent.mReply->mOffsetAndData);
+ mTentativeCaretOffset = std::move(aEvent.mReply->mTentativeCaretOffset);
+ mRect = std::move(aEvent.mReply->mRect);
+ mRectArray = std::move(aEvent.mReply->mRectArray);
+ mReversed = aEvent.mReply->mReversed;
+ }
+ // Mark as result that is longer used.
+ aEvent.mReply.reset();
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetOffset(uint32_t* aOffset) {
+ if (NS_WARN_IF(!mSucceeded)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_WARN_IF(!IsOffsetPropertyAvailable(mEventMessage))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // With some event message, both offset and notFound properties are available.
+ // In that case, offset value may mean "not found". If so, this method
+ // shouldn't return mOffset as the result because it's a special value for
+ // "not found".
+ if (IsNotFoundPropertyAvailable(mEventMessage)) {
+ bool notFound;
+ nsresult rv = GetNotFound(&notFound);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv; // Just an unexpected case...
+ }
+ // As said above, if mOffset means "not found", offset property shouldn't
+ // return its value without any errors.
+ if (NS_WARN_IF(notFound)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ *aOffset = mOffsetAndData->StartOffset();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffset(uint32_t* aOffset) {
+ bool notFound;
+ nsresult rv = GetTentativeCaretOffsetNotFound(&notFound);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(notFound)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aOffset = mTentativeCaretOffset.value();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetReversed(bool* aReversed) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mEventMessage == eQuerySelectedText, NS_ERROR_NOT_AVAILABLE);
+ *aReversed = mReversed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetLeft(int32_t* aLeft) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
+ NS_ERROR_NOT_AVAILABLE);
+ *aLeft = mRect.x;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetWidth(int32_t* aWidth) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
+ NS_ERROR_NOT_AVAILABLE);
+ *aWidth = mRect.Width();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTop(int32_t* aTop) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
+ NS_ERROR_NOT_AVAILABLE);
+ *aTop = mRect.y;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetHeight(int32_t* aHeight) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
+ NS_ERROR_NOT_AVAILABLE);
+ *aHeight = mRect.Height();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetText(nsAString& aText) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mEventMessage == eQuerySelectedText ||
+ mEventMessage == eQueryTextContent ||
+ mEventMessage == eQueryTextRect,
+ NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mOffsetAndData.isSome(), NS_ERROR_NOT_AVAILABLE);
+ aText = mOffsetAndData->DataRef();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetSucceeded(bool* aSucceeded) {
+ NS_ENSURE_TRUE(mEventMessage != eVoidEvent, NS_ERROR_NOT_INITIALIZED);
+ *aSucceeded = mSucceeded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetNotFound(bool* aNotFound) {
+ if (NS_WARN_IF(!mSucceeded) ||
+ NS_WARN_IF(!IsNotFoundPropertyAvailable(mEventMessage))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aNotFound = mOffsetAndData.isNothing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffsetNotFound(bool* aNotFound) {
+ if (NS_WARN_IF(!mSucceeded)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(mEventMessage != eQueryCharacterAtPoint)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aNotFound = mTentativeCaretOffset.isNothing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsQueryContentEventResult::GetCharacterRect(int32_t aOffset, int32_t* aLeft,
+ int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) {
+ NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mEventMessage == eQueryTextRectArray, NS_ERROR_NOT_AVAILABLE);
+
+ if (NS_WARN_IF(mRectArray.Length() <= static_cast<size_t>(aOffset))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aLeft = mRectArray[aOffset].x;
+ *aTop = mRectArray[aOffset].y;
+ *aWidth = mRectArray[aOffset].Width();
+ *aHeight = mRectArray[aOffset].Height();
+
+ return NS_OK;
+}
+
+void nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget) {
+ if (!IsRectRelatedPropertyAvailable(mEventMessage) || !aWidget ||
+ !mSucceeded) {
+ return;
+ }
+
+ nsIWidget* topWidget = aWidget->GetTopLevelWidget();
+ if (!topWidget || topWidget == aWidget) {
+ return;
+ }
+
+ // Convert the top widget related coordinates to the given widget's.
+ LayoutDeviceIntPoint offset =
+ aWidget->WidgetToScreenOffset() - topWidget->WidgetToScreenOffset();
+ mRect.MoveBy(-offset);
+ for (size_t i = 0; i < mRectArray.Length(); i++) {
+ mRectArray[i].MoveBy(-offset);
+ }
+}
diff --git a/dom/base/nsQueryContentEventResult.h b/dom/base/nsQueryContentEventResult.h
new file mode 100644
index 0000000000..dc535d4180
--- /dev/null
+++ b/dom/base/nsQueryContentEventResult.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_nsQueryContentEventResult_h
+#define mozilla_dom_nsQueryContentEventResult_h
+
+#include "nsIQueryContentEventResult.h"
+#include "nsString.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+#include "Units.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/widget/IMEData.h"
+
+class nsIWidget;
+
+class nsQueryContentEventResult final : public nsIQueryContentEventResult {
+ public:
+ explicit nsQueryContentEventResult(mozilla::WidgetQueryContentEvent&& aEvent);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIQUERYCONTENTEVENTRESULT
+
+ void SetEventResult(nsIWidget* aWidget);
+
+ protected:
+ ~nsQueryContentEventResult() = default;
+
+ mozilla::EventMessage mEventMessage;
+
+ mozilla::Maybe<mozilla::OffsetAndData<uint32_t>> mOffsetAndData;
+ mozilla::Maybe<uint32_t> mTentativeCaretOffset;
+ mozilla::LayoutDeviceIntRect mRect;
+ CopyableTArray<mozilla::LayoutDeviceIntRect> mRectArray;
+
+ bool mSucceeded;
+ bool mReversed;
+};
+
+#endif // mozilla_dom_nsQueryContentEventResult_h
diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp
new file mode 100644
index 0000000000..bbeda20207
--- /dev/null
+++ b/dom/base/nsRange.cpp
@@ -0,0 +1,3216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of the DOM Range object.
+ */
+
+#include "RangeBoundary.h"
+#include "nscore.h"
+#include "nsRange.h"
+
+#include "nsDebug.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsError.h"
+#include "nsINodeList.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
+#include "nsContainerFrame.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/RangeBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ToString.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Likely.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsComputedDOMStyle.h"
+#include "mozilla/dom/InspectorFontFace.h"
+
+namespace mozilla {
+extern LazyLogModule sSelectionAPILog;
+extern void LogStackForSelectionAPI();
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+static void LogSelectionAPI(const dom::Selection* aSelection,
+ const char* aFuncName, const char* aArgName1,
+ const RangeBoundaryBase<SPT, SRT>& aBoundary1,
+ const char* aArgName2,
+ const RangeBoundaryBase<EPT, ERT>& aBoundary2,
+ const char* aArgName3, bool aBoolArg) {
+ if (aBoundary1 == aBoundary2) {
+ MOZ_LOG(sSelectionAPILog, LogLevel::Info,
+ ("%p nsRange::%s(%s=%s=%s, %s=%s)", aSelection, aFuncName,
+ aArgName1, aArgName2, ToString(aBoundary1).c_str(), aArgName3,
+ aBoolArg ? "true" : "false"));
+ } else {
+ MOZ_LOG(
+ sSelectionAPILog, LogLevel::Info,
+ ("%p nsRange::%s(%s=%s, %s=%s, %s=%s)", aSelection, aFuncName,
+ aArgName1, ToString(aBoundary1).c_str(), aArgName2,
+ ToString(aBoundary2).c_str(), aArgName3, aBoolArg ? "true" : "false"));
+ }
+}
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+template already_AddRefed<nsRange> nsRange::Create(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<nsRange> nsRange::Create(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<nsRange> nsRange::Create(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ ErrorResult& aRv);
+template already_AddRefed<nsRange> nsRange::Create(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, ErrorResult& aRv);
+
+template nsresult nsRange::SetStartAndEnd(const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary);
+template nsresult nsRange::SetStartAndEnd(const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+template nsresult nsRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
+template nsresult nsRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+
+template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet);
+template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet);
+template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet);
+template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet);
+
+JSObject* nsRange::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Range_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+DocGroup* nsRange::GetDocGroup() const {
+ return mOwner ? mOwner->GetDocGroup() : nullptr;
+}
+
+/******************************************************
+ * stack based utility class for managing monitor
+ ******************************************************/
+
+static void InvalidateAllFrames(nsINode* aNode) {
+ MOZ_ASSERT(aNode, "bad arg");
+
+ nsIFrame* frame = nullptr;
+ switch (aNode->NodeType()) {
+ case nsINode::TEXT_NODE:
+ case nsINode::ELEMENT_NODE: {
+ nsIContent* content = static_cast<nsIContent*>(aNode);
+ frame = content->GetPrimaryFrame();
+ break;
+ }
+ case nsINode::DOCUMENT_NODE: {
+ Document* doc = static_cast<Document*>(aNode);
+ PresShell* presShell = doc ? doc->GetPresShell() : nullptr;
+ frame = presShell ? presShell->GetRootFrame() : nullptr;
+ break;
+ }
+ }
+ for (nsIFrame* f = frame; f; f = f->GetNextContinuation()) {
+ f->InvalidateFrameSubtree();
+ }
+}
+
+/******************************************************
+ * constructor/destructor
+ ******************************************************/
+
+nsTArray<RefPtr<nsRange>>* nsRange::sCachedRanges = nullptr;
+
+nsRange::~nsRange() {
+ NS_ASSERTION(!IsInAnySelection(), "deleting nsRange that is in use");
+
+ // we want the side effects (releases and list removals)
+ DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
+}
+
+nsRange::nsRange(nsINode* aNode)
+ : AbstractRange(aNode, /* aIsDynamicRange = */ true),
+ mNextStartRef(nullptr),
+ mNextEndRef(nullptr) {
+ // printf("Size of nsRange: %zu\n", sizeof(nsRange));
+
+ static_assert(sizeof(nsRange) <= 240,
+ "nsRange size shouldn't be increased as far as possible");
+}
+
+/* static */
+already_AddRefed<nsRange> nsRange::Create(nsINode* aNode) {
+ MOZ_ASSERT(aNode);
+ if (!sCachedRanges || sCachedRanges->IsEmpty()) {
+ return do_AddRef(new nsRange(aNode));
+ }
+ RefPtr<nsRange> range = sCachedRanges->PopLastElement().forget();
+ range->Init(aNode);
+ return range.forget();
+}
+
+/* static */
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<nsRange> nsRange::Create(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv) {
+ // If we fail to initialize the range a lot, nsRange should have a static
+ // initializer since the allocation cost is not cheap in hot path.
+ RefPtr<nsRange> range = nsRange::Create(aStartBoundary.Container());
+ aRv = range->SetStartAndEnd(aStartBoundary, aEndBoundary);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ return range.forget();
+}
+
+/******************************************************
+ * nsISupports
+ ******************************************************/
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(nsRange)
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
+ nsRange, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
+ MaybeInterruptLastRelease())
+
+// QueryInterface implementation for nsRange
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRange)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END_INHERITING(AbstractRange)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsRange, AbstractRange)
+ // `Reset()` unlinks `mStart`, `mEnd` and `mRoot`.
+ tmp->Reset();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange, AbstractRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange, AbstractRange)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+bool nsRange::MaybeInterruptLastRelease() {
+ bool interrupt = AbstractRange::MaybeCacheToReuse(*this);
+ MOZ_ASSERT(!interrupt || IsCleared());
+ return interrupt;
+}
+
+void nsRange::AdjustNextRefsOnCharacterDataSplit(
+ const nsIContent& aContent, const CharacterDataChangeInfo& aInfo) {
+ // If the splitted text node is immediately before a range boundary point
+ // that refers to a child index (i.e. its parent is the boundary container)
+ // then we need to adjust the corresponding boundary to account for the new
+ // text node that will be inserted. However, because the new sibling hasn't
+ // been inserted yet, that would result in an invalid boundary. Therefore,
+ // we store the new child in mNext*Ref to make sure we adjust the boundary
+ // in the next ContentInserted or ContentAppended call.
+ nsINode* parentNode = aContent.GetParentNode();
+ if (parentNode == mEnd.Container()) {
+ if (&aContent == mEnd.Ref()) {
+ MOZ_ASSERT(aInfo.mDetails->mNextSibling);
+ mNextEndRef = aInfo.mDetails->mNextSibling;
+ }
+ }
+
+ if (parentNode == mStart.Container()) {
+ if (&aContent == mStart.Ref()) {
+ MOZ_ASSERT(aInfo.mDetails->mNextSibling);
+ mNextStartRef = aInfo.mDetails->mNextSibling;
+ }
+ }
+}
+
+nsRange::RangeBoundariesAndRoot
+nsRange::DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const {
+ RawRangeBoundary newStart;
+ RawRangeBoundary newEnd;
+ nsINode* newRoot = nullptr;
+
+ // normalize(), aInfo.mDetails->mNextSibling is the merged text node
+ // that will be removed
+ nsIContent* removed = aInfo.mDetails->mNextSibling;
+ if (removed == mStart.Container()) {
+ CheckedUint32 newStartOffset{
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)};
+ newStartOffset += aInfo.mChangeStart;
+
+ // newStartOffset.isValid() isn't checked explicitly here, because
+ // newStartOffset.value() contains an assertion.
+ newStart = {aContent, newStartOffset.value()};
+ if (MOZ_UNLIKELY(removed == mRoot)) {
+ newRoot = RangeUtils::ComputeRootNode(newStart.Container());
+ }
+ }
+ if (removed == mEnd.Container()) {
+ CheckedUint32 newEndOffset{
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)};
+ newEndOffset += aInfo.mChangeStart;
+
+ // newEndOffset.isValid() isn't checked explicitly here, because
+ // newEndOffset.value() contains an assertion.
+ newEnd = {aContent, newEndOffset.value()};
+ if (MOZ_UNLIKELY(removed == mRoot)) {
+ newRoot = {RangeUtils::ComputeRootNode(newEnd.Container())};
+ }
+ }
+ // When the removed text node's parent is one of our boundary nodes we may
+ // need to adjust the offset to account for the removed node. However,
+ // there will also be a ContentRemoved notification later so the only cases
+ // we need to handle here is when the removed node is the text node after
+ // the boundary. (The m*Offset > 0 check is an optimization - a boundary
+ // point before the first child is never affected by normalize().)
+ nsINode* parentNode = aContent->GetParentNode();
+ if (parentNode == mStart.Container() &&
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) > 0 &&
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <
+ parentNode->GetChildCount() &&
+ removed == mStart.GetChildAtOffset()) {
+ newStart = {aContent, aInfo.mChangeStart};
+ }
+ if (parentNode == mEnd.Container() &&
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) > 0 &&
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <
+ parentNode->GetChildCount() &&
+ removed == mEnd.GetChildAtOffset()) {
+ newEnd = {aContent, aInfo.mChangeEnd};
+ }
+
+ return {newStart, newEnd, newRoot};
+}
+
+/******************************************************
+ * nsIMutationObserver implementation
+ ******************************************************/
+void nsRange::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo& aInfo) {
+ MOZ_ASSERT(aContent);
+ MOZ_ASSERT(mIsPositioned);
+ MOZ_ASSERT(!mNextEndRef);
+ MOZ_ASSERT(!mNextStartRef);
+
+ nsINode* newRoot = nullptr;
+ RawRangeBoundary newStart;
+ RawRangeBoundary newEnd;
+
+ if (aInfo.mDetails &&
+ aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit) {
+ AdjustNextRefsOnCharacterDataSplit(*aContent, aInfo);
+ }
+
+ // If the changed node contains our start boundary and the change starts
+ // before the boundary we'll need to adjust the offset.
+ if (aContent == mStart.Container() &&
+ aInfo.mChangeStart <
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)) {
+ if (aInfo.mDetails) {
+ // splitText(), aInfo->mDetails->mNextSibling is the new text node
+ NS_ASSERTION(
+ aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit,
+ "only a split can start before the end");
+ NS_ASSERTION(
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <=
+ aInfo.mChangeEnd + 1,
+ "mStart.Offset() is beyond the end of this node");
+ const uint32_t newStartOffset =
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) -
+ aInfo.mChangeStart;
+ newStart = {aInfo.mDetails->mNextSibling, newStartOffset};
+ if (MOZ_UNLIKELY(aContent == mRoot)) {
+ newRoot = RangeUtils::ComputeRootNode(newStart.Container());
+ }
+
+ bool isCommonAncestor =
+ IsInAnySelection() && mStart.Container() == mEnd.Container();
+ if (isCommonAncestor) {
+ UnregisterClosestCommonInclusiveAncestor(mStart.Container(), false);
+ RegisterClosestCommonInclusiveAncestor(newStart.Container());
+ }
+ if (mStart.Container()
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ newStart.Container()
+ ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+ } else {
+ // If boundary is inside changed text, position it before change
+ // else adjust start offset for the change in length.
+ CheckedUint32 newStartOffset{0};
+ if (*mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <=
+ aInfo.mChangeEnd) {
+ newStartOffset = aInfo.mChangeStart;
+ } else {
+ newStartOffset =
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets);
+ newStartOffset -= aInfo.LengthOfRemovedText();
+ newStartOffset += aInfo.mReplaceLength;
+ }
+
+ // newStartOffset.isValid() isn't checked explicitly here, because
+ // newStartOffset.value() contains an assertion.
+ newStart = {mStart.Container(), newStartOffset.value()};
+ }
+ }
+
+ // Do the same thing for the end boundary, except for splitText of a node
+ // with no parent then only switch to the new node if the start boundary
+ // did so too (otherwise the range would end up with disconnected nodes).
+ if (aContent == mEnd.Container() &&
+ aInfo.mChangeStart <
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)) {
+ if (aInfo.mDetails && (aContent->GetParentNode() || newStart.Container())) {
+ // splitText(), aInfo.mDetails->mNextSibling is the new text node
+ NS_ASSERTION(
+ aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit,
+ "only a split can start before the end");
+ MOZ_ASSERT(
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <=
+ aInfo.mChangeEnd + 1,
+ "mEnd.Offset() is beyond the end of this node");
+
+ const uint32_t newEndOffset{
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) -
+ aInfo.mChangeStart};
+ newEnd = {aInfo.mDetails->mNextSibling, newEndOffset};
+
+ bool isCommonAncestor =
+ IsInAnySelection() && mStart.Container() == mEnd.Container();
+ if (isCommonAncestor && !newStart.Container()) {
+ // The split occurs inside the range.
+ UnregisterClosestCommonInclusiveAncestor(mStart.Container(), false);
+ RegisterClosestCommonInclusiveAncestor(
+ mStart.Container()->GetParentNode());
+ newEnd.Container()
+ ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ } else if (
+ mEnd.Container()
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ newEnd.Container()
+ ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+ } else {
+ CheckedUint32 newEndOffset{0};
+ if (*mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets) <=
+ aInfo.mChangeEnd) {
+ newEndOffset = aInfo.mChangeStart;
+ } else {
+ newEndOffset =
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets);
+ newEndOffset -= aInfo.LengthOfRemovedText();
+ newEndOffset += aInfo.mReplaceLength;
+ }
+
+ // newEndOffset.isValid() isn't checked explicitly here, because
+ // newEndOffset.value() contains an assertion.
+ newEnd = {mEnd.Container(), newEndOffset.value()};
+ }
+ }
+
+ if (aInfo.mDetails &&
+ aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eMerge) {
+ MOZ_ASSERT(!newStart.IsSet());
+ MOZ_ASSERT(!newEnd.IsSet());
+
+ RangeBoundariesAndRoot rangeBoundariesAndRoot =
+ DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(aContent, aInfo);
+
+ newStart = rangeBoundariesAndRoot.mStart;
+ newEnd = rangeBoundariesAndRoot.mEnd;
+ newRoot = rangeBoundariesAndRoot.mRoot;
+ }
+
+ if (newStart.IsSet() || newEnd.IsSet()) {
+ if (!newStart.IsSet()) {
+ newStart.CopyFrom(mStart, RangeBoundaryIsMutationObserved::Yes);
+ }
+ if (!newEnd.IsSet()) {
+ newEnd.CopyFrom(mEnd, RangeBoundaryIsMutationObserved::Yes);
+ }
+ DoSetRange(newStart, newEnd, newRoot ? newRoot : mRoot.get(),
+ !newEnd.Container()->GetParentNode() ||
+ !newStart.Container()->GetParentNode());
+ } else {
+ nsRange::AssertIfMismatchRootAndRangeBoundaries(
+ mStart, mEnd, mRoot,
+ (mStart.IsSet() && !mStart.Container()->GetParentNode()) ||
+ (mEnd.IsSet() && !mEnd.Container()->GetParentNode()));
+ }
+}
+
+void nsRange::ContentAppended(nsIContent* aFirstNewContent) {
+ MOZ_ASSERT(mIsPositioned);
+
+ nsINode* container = aFirstNewContent->GetParentNode();
+ MOZ_ASSERT(container);
+ if (container->IsMaybeSelected() && IsInAnySelection()) {
+ nsINode* child = aFirstNewContent;
+ while (child) {
+ if (!child
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ MarkDescendants(*child);
+ child
+ ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+ child = child->GetNextSibling();
+ }
+ }
+
+ if (mNextStartRef || mNextEndRef) {
+ // A splitText has occurred, if any mNext*Ref was set, we need to adjust
+ // the range boundaries.
+ if (mNextStartRef) {
+ mStart = {mStart.Container(), mNextStartRef};
+ MOZ_ASSERT(mNextStartRef == aFirstNewContent);
+ mNextStartRef = nullptr;
+ }
+ if (mNextEndRef) {
+ mEnd = {mEnd.Container(), mNextEndRef};
+ MOZ_ASSERT(mNextEndRef == aFirstNewContent);
+ mNextEndRef = nullptr;
+ }
+ DoSetRange(mStart, mEnd, mRoot, true);
+ } else {
+ nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart, mEnd, mRoot);
+ }
+}
+
+void nsRange::ContentInserted(nsIContent* aChild) {
+ MOZ_ASSERT(mIsPositioned);
+
+ bool updateBoundaries = false;
+ nsINode* container = aChild->GetParentNode();
+ MOZ_ASSERT(container);
+ RawRangeBoundary newStart(mStart, RangeBoundaryIsMutationObserved::Yes);
+ RawRangeBoundary newEnd(mEnd, RangeBoundaryIsMutationObserved::Yes);
+ MOZ_ASSERT(aChild->GetParentNode() == container);
+
+ // Invalidate boundary offsets if a child that may have moved them was
+ // inserted.
+ if (container == mStart.Container()) {
+ newStart.InvalidateOffset();
+ updateBoundaries = true;
+ }
+
+ if (container == mEnd.Container()) {
+ newEnd.InvalidateOffset();
+ updateBoundaries = true;
+ }
+
+ if (container->IsMaybeSelected() &&
+ !aChild
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ MarkDescendants(*aChild);
+ aChild->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+
+ if (mNextStartRef || mNextEndRef) {
+ if (mNextStartRef) {
+ newStart = {mStart.Container(), mNextStartRef};
+ MOZ_ASSERT(mNextStartRef == aChild);
+ mNextStartRef = nullptr;
+ }
+ if (mNextEndRef) {
+ newEnd = {mEnd.Container(), mNextEndRef};
+ MOZ_ASSERT(mNextEndRef == aChild);
+ mNextEndRef = nullptr;
+ }
+
+ updateBoundaries = true;
+ }
+
+ if (updateBoundaries) {
+ DoSetRange(newStart, newEnd, mRoot);
+ } else {
+ nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart, mEnd, mRoot);
+ }
+}
+
+void nsRange::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) {
+ MOZ_ASSERT(mIsPositioned);
+
+ nsINode* container = aChild->GetParentNode();
+ MOZ_ASSERT(container);
+
+ nsINode* startContainer = mStart.Container();
+ nsINode* endContainer = mEnd.Container();
+
+ RawRangeBoundary newStart;
+ RawRangeBoundary newEnd;
+ Maybe<bool> gravitateStart;
+ bool gravitateEnd;
+
+ // Adjust position if a sibling was removed...
+ if (container == startContainer) {
+ // We're only interested if our boundary reference was removed, otherwise
+ // we can just invalidate the offset.
+ if (aChild == mStart.Ref()) {
+ newStart = {container, aPreviousSibling};
+ } else {
+ newStart.CopyFrom(mStart, RangeBoundaryIsMutationObserved::Yes);
+ newStart.InvalidateOffset();
+ }
+ } else {
+ gravitateStart = Some(startContainer->IsInclusiveDescendantOf(aChild));
+ if (gravitateStart.value()) {
+ newStart = {container, aPreviousSibling};
+ }
+ }
+
+ // Do same thing for end boundry.
+ if (container == endContainer) {
+ if (aChild == mEnd.Ref()) {
+ newEnd = {container, aPreviousSibling};
+ } else {
+ newEnd.CopyFrom(mEnd, RangeBoundaryIsMutationObserved::Yes);
+ newEnd.InvalidateOffset();
+ }
+ } else {
+ if (startContainer == endContainer && gravitateStart.isSome()) {
+ gravitateEnd = gravitateStart.value();
+ } else {
+ gravitateEnd = endContainer->IsInclusiveDescendantOf(aChild);
+ }
+ if (gravitateEnd) {
+ newEnd = {container, aPreviousSibling};
+ }
+ }
+
+ bool newStartIsSet = newStart.IsSet();
+ bool newEndIsSet = newEnd.IsSet();
+ if (newStartIsSet || newEndIsSet) {
+ DoSetRange(newStartIsSet ? newStart : mStart.AsRaw(),
+ newEndIsSet ? newEnd : mEnd.AsRaw(), mRoot);
+ } else {
+ nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart, mEnd, mRoot);
+ }
+
+ MOZ_ASSERT(mStart.Ref() != aChild);
+ MOZ_ASSERT(mEnd.Ref() != aChild);
+
+ if (container->IsMaybeSelected() &&
+ aChild
+ ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
+ aChild
+ ->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ UnmarkDescendants(*aChild);
+ }
+}
+
+void nsRange::ParentChainChanged(nsIContent* aContent) {
+ NS_ASSERTION(mRoot == aContent, "Wrong ParentChainChanged notification?");
+ nsINode* newRoot = RangeUtils::ComputeRootNode(mStart.Container());
+ NS_ASSERTION(newRoot, "No valid boundary or root found!");
+ if (newRoot != RangeUtils::ComputeRootNode(mEnd.Container())) {
+ // Sometimes ordering involved in cycle collection can lead to our
+ // start parent and/or end parent being disconnected from our root
+ // without our getting a ContentRemoved notification.
+ // See bug 846096 for more details.
+ NS_ASSERTION(mEnd.Container()->IsInNativeAnonymousSubtree(),
+ "This special case should happen only with "
+ "native-anonymous content");
+ // When that happens, bail out and set pointers to null; since we're
+ // in cycle collection and unreachable it shouldn't matter.
+ Reset();
+ return;
+ }
+ // This is safe without holding a strong ref to self as long as the change
+ // of mRoot is the last thing in DoSetRange.
+ DoSetRange(mStart, mEnd, newRoot);
+}
+
+bool nsRange::IsPointComparableToRange(const nsINode& aContainer,
+ uint32_t aOffset,
+ ErrorResult& aRv) const {
+ // our range is in a good state?
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return false;
+ }
+
+ if (!aContainer.IsInclusiveDescendantOf(mRoot)) {
+ // TODO(emilio): Switch to ThrowWrongDocumentError, but IsPointInRange
+ // relies on the error code right now in order to suppress the exception.
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return false;
+ }
+
+ auto chromeOnlyAccess = mStart.Container()->ChromeOnlyAccess();
+ NS_ASSERTION(chromeOnlyAccess == mEnd.Container()->ChromeOnlyAccess(),
+ "Start and end of a range must be either both native anonymous "
+ "content or not.");
+ if (aContainer.ChromeOnlyAccess() != chromeOnlyAccess) {
+ aRv.ThrowInvalidNodeTypeError(
+ "Trying to compare restricted with unrestricted nodes");
+ return false;
+ }
+
+ if (aContainer.NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
+ aRv.ThrowInvalidNodeTypeError("Trying to compare with a document");
+ return false;
+ }
+
+ if (aOffset > aContainer.Length()) {
+ aRv.ThrowIndexSizeError("Offset is out of bounds");
+ return false;
+ }
+
+ return true;
+}
+
+bool nsRange::IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const {
+ uint16_t compareResult = ComparePoint(aContainer, aOffset, aRv);
+ // If the node isn't in the range's document, it clearly isn't in the range.
+ if (aRv.ErrorCodeIs(NS_ERROR_DOM_WRONG_DOCUMENT_ERR)) {
+ aRv.SuppressException();
+ return false;
+ }
+
+ return compareResult == 0;
+}
+
+int16_t nsRange::ComparePoint(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const {
+ if (!IsPointComparableToRange(aContainer, aOffset, aRv)) {
+ return 0;
+ }
+
+ const RawRangeBoundary point{const_cast<nsINode*>(&aContainer), aOffset};
+
+ MOZ_ASSERT(point.IsSetAndValid());
+
+ if (Maybe<int32_t> order = nsContentUtils::ComparePoints(point, mStart);
+ order && *order <= 0) {
+ return int16_t(*order);
+ }
+ if (Maybe<int32_t> order = nsContentUtils::ComparePoints(mEnd, point);
+ order && *order == -1) {
+ return 1;
+ }
+ return 0;
+}
+
+bool nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv) {
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return false;
+ }
+
+ nsINode* parent = aNode.GetParentNode();
+ if (!parent) {
+ // |parent| is null, so |node|'s root is |node| itself.
+ return GetRoot() == &aNode;
+ }
+
+ const Maybe<uint32_t> nodeIndex = parent->ComputeIndexOf(&aNode);
+ if (nodeIndex.isNothing()) {
+ return false;
+ }
+
+ if (!IsPointComparableToRange(*parent, *nodeIndex, IgnoreErrors())) {
+ return false;
+ }
+
+ const Maybe<int32_t> startOrder = nsContentUtils::ComparePoints(
+ mStart.Container(),
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets), parent,
+ *nodeIndex + 1u);
+ if (startOrder && (*startOrder < 0)) {
+ const Maybe<int32_t> endOrder = nsContentUtils::ComparePoints(
+ parent, *nodeIndex, mEnd.Container(),
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets));
+ return endOrder && (*endOrder < 0);
+ }
+
+ return false;
+}
+
+void nsRange::NotifySelectionListenersAfterRangeSet() {
+ if (!mSelections.IsEmpty()) {
+ // Our internal code should not move focus with using this instance while
+ // it's calling Selection::NotifySelectionListeners() which may move focus
+ // or calls selection listeners. So, let's set mCalledByJS to false here
+ // since non-*JS() methods don't set it to false.
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = false;
+
+ // Notify all Selections. This may modify the range,
+ // remove it from the selection, or the selection itself may have gone after
+ // the call. Also, new selections may be added.
+ // To ensure that listeners are notified for all *current* selections,
+ // create a copy of the list of selections and use that for iterating. This
+ // way selections can be added or removed safely during iteration.
+ // To save allocation cost, the copy is only created if there is more than
+ // one Selection present (which will barely ever be the case).
+ if (IsPartOfOneSelectionOnly()) {
+ RefPtr<Selection> selection = mSelections[0].get();
+ selection->NotifySelectionListeners(calledByJSRestorer.SavedValue());
+ } else {
+ nsTArray<WeakPtr<Selection>> copiedSelections = mSelections.Clone();
+ for (const auto& weakSelection : copiedSelections) {
+ RefPtr<Selection> selection = weakSelection.get();
+ selection->NotifySelectionListeners(calledByJSRestorer.SavedValue());
+ }
+ }
+ }
+}
+
+/******************************************************
+ * Private helper routines
+ ******************************************************/
+
+// static
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void nsRange::AssertIfMismatchRootAndRangeBoundaries(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, const nsINode* aRootNode,
+ bool aNotInsertedYet /* = false */) {
+#ifdef DEBUG
+ if (!aRootNode) {
+ MOZ_ASSERT(!aStartBoundary.IsSet());
+ MOZ_ASSERT(!aEndBoundary.IsSet());
+ return;
+ }
+
+ MOZ_ASSERT(aStartBoundary.IsSet());
+ MOZ_ASSERT(aEndBoundary.IsSet());
+ if (!aNotInsertedYet) {
+ // Compute temporary root for given range boundaries. If a range in native
+ // anonymous subtree is being removed, tempRoot may return the fragment's
+ // root content, but it shouldn't be used for new root node because the node
+ // may be bound to the root element again.
+ nsINode* tempRoot = RangeUtils::ComputeRootNode(aStartBoundary.Container());
+ // The new range should be in the temporary root node at least.
+ MOZ_ASSERT(tempRoot ==
+ RangeUtils::ComputeRootNode(aEndBoundary.Container()));
+ MOZ_ASSERT(aStartBoundary.Container()->IsInclusiveDescendantOf(tempRoot));
+ MOZ_ASSERT(aEndBoundary.Container()->IsInclusiveDescendantOf(tempRoot));
+ // If the new range is not disconnected or not in native anonymous subtree,
+ // the temporary root must be same as the new root node. Otherwise,
+ // aRootNode should be the parent of root of the NAC (e.g., `<input>` if the
+ // range is in NAC under `<input>`), but tempRoot is now root content node
+ // of the disconnected subtree (e.g., `<div>` element in `<input>` element).
+ const bool tempRootIsDisconnectedNAC =
+ tempRoot->IsInNativeAnonymousSubtree() && !tempRoot->GetParentNode();
+ MOZ_ASSERT_IF(!tempRootIsDisconnectedNAC, tempRoot == aRootNode);
+ }
+ MOZ_ASSERT(aRootNode->IsDocument() || aRootNode->IsAttr() ||
+ aRootNode->IsDocumentFragment() || aRootNode->IsContent());
+#endif // #ifdef DEBUG
+}
+
+// It's important that all setting of the range start/end points
+// go through this function, which will do all the right voodoo
+// for content notification of range ownership.
+// Calling DoSetRange with either parent argument null will collapse
+// the range to have both endpoints point to the other node
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode,
+ bool aNotInsertedYet /* = false */) {
+ mIsPositioned = aStartBoundary.IsSetAndValid() &&
+ aEndBoundary.IsSetAndValid() && aRootNode;
+ MOZ_ASSERT_IF(!mIsPositioned, !aStartBoundary.IsSet());
+ MOZ_ASSERT_IF(!mIsPositioned, !aEndBoundary.IsSet());
+ MOZ_ASSERT_IF(!mIsPositioned, !aRootNode);
+
+ nsRange::AssertIfMismatchRootAndRangeBoundaries(aStartBoundary, aEndBoundary,
+ aRootNode, aNotInsertedYet);
+
+ if (mRoot != aRootNode) {
+ if (mRoot) {
+ mRoot->RemoveMutationObserver(this);
+ }
+ if (aRootNode) {
+ aRootNode->AddMutationObserver(this);
+ }
+ }
+ bool checkCommonAncestor =
+ (mStart.Container() != aStartBoundary.Container() ||
+ mEnd.Container() != aEndBoundary.Container()) &&
+ IsInAnySelection() && !aNotInsertedYet;
+
+ // GetClosestCommonInclusiveAncestor is unreliable while we're unlinking
+ // (could return null if our start/end have already been unlinked), so make
+ // sure to not use it here to determine our "old" current ancestor.
+ mStart.CopyFrom(aStartBoundary, RangeBoundaryIsMutationObserved::Yes);
+ mEnd.CopyFrom(aEndBoundary, RangeBoundaryIsMutationObserved::Yes);
+
+ if (checkCommonAncestor) {
+ UpdateCommonAncestorIfNecessary();
+ }
+
+ // This needs to be the last thing this function does, other than notifying
+ // selection listeners. See comment in ParentChainChanged.
+ if (mRoot != aRootNode) {
+ mRoot = aRootNode;
+ }
+
+ // Notify any selection listeners. This has to occur last because otherwise
+ // the world could be observed by a selection listener while the range was in
+ // an invalid state. So we run it off of a script runner to ensure it runs
+ // after the mutation observers have finished running.
+ if (!mSelections.IsEmpty()) {
+ if (MOZ_LOG_TEST(sSelectionAPILog, LogLevel::Info)) {
+ for (const auto& selection : mSelections) {
+ if (selection && selection->Type() == SelectionType::eNormal) {
+ LogSelectionAPI(selection, __FUNCTION__, "aStartBoundary",
+ aStartBoundary, "aEndBoundary", aEndBoundary,
+ "aNotInsertedYet", aNotInsertedYet);
+ LogStackForSelectionAPI();
+ }
+ }
+ }
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("NotifySelectionListenersAfterRangeSet", this,
+ &nsRange::NotifySelectionListenersAfterRangeSet));
+ }
+}
+
+void nsRange::Reset() {
+ DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
+}
+
+/******************************************************
+ * public functionality
+ ******************************************************/
+
+void nsRange::SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetStart(aNode, aOffset, aErr);
+}
+
+bool nsRange::CanAccess(const nsINode& aNode) const {
+ if (nsContentUtils::LegacyIsCallerNativeCode()) {
+ return true;
+ }
+ return nsContentUtils::CanCallerAccess(&aNode);
+}
+
+void nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ SetStart(RawRangeBoundary(&aNode, aOffset), aRv);
+}
+
+void nsRange::SetStart(const RawRangeBoundary& aPoint, ErrorResult& aRv) {
+ nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container());
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ if (!aPoint.IsSetAndValid()) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ // Collapse if not positioned yet, if positioned in another doc or
+ // if the new start is after end.
+ const bool collapse = [&]() {
+ if (!mIsPositioned || (newRoot != mRoot)) {
+ return true;
+ }
+
+ const Maybe<int32_t> order = nsContentUtils::ComparePoints(aPoint, mEnd);
+ if (order) {
+ return *order == 1;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return true;
+ }();
+
+ if (collapse) {
+ DoSetRange(aPoint, aPoint, newRoot);
+ return;
+ }
+
+ DoSetRange(aPoint, mEnd, mRoot);
+}
+
+void nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetStartBefore(aNode, aErr);
+}
+
+void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ // If the node is being removed from its parent, GetRawRangeBoundaryBefore()
+ // returns unset instance. Then, SetStart() will throw
+ // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
+ SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv);
+}
+
+void nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetStartAfter(aNode, aErr);
+}
+
+void nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ // If the node is being removed from its parent, GetRawRangeBoundaryAfter()
+ // returns unset instance. Then, SetStart() will throw
+ // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
+ SetStart(RangeUtils::GetRawRangeBoundaryAfter(&aNode), aRv);
+}
+
+void nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetEnd(aNode, aOffset, aErr);
+}
+
+void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ AutoInvalidateSelection atEndOfBlock(this);
+ SetEnd(RawRangeBoundary(&aNode, aOffset), aRv);
+}
+
+void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv) {
+ nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container());
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ if (!aPoint.IsSetAndValid()) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ // Collapse if not positioned yet, if positioned in another doc or
+ // if the new end is before start.
+ const bool collapse = [&]() {
+ if (!mIsPositioned || (newRoot != mRoot)) {
+ return true;
+ }
+
+ const Maybe<int32_t> order = nsContentUtils::ComparePoints(mStart, aPoint);
+ if (order) {
+ return *order == 1;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return true;
+ }();
+
+ if (collapse) {
+ DoSetRange(aPoint, aPoint, newRoot);
+ return;
+ }
+
+ DoSetRange(mStart, aPoint, mRoot);
+}
+
+void nsRange::SelectNodesInContainer(nsINode* aContainer,
+ nsIContent* aStartContent,
+ nsIContent* aEndContent) {
+ MOZ_ASSERT(aContainer);
+ MOZ_ASSERT(aContainer->ComputeIndexOf(aStartContent).valueOr(0) <=
+ aContainer->ComputeIndexOf(aEndContent).valueOr(0));
+ MOZ_ASSERT(aStartContent &&
+ aContainer->ComputeIndexOf(aStartContent).isSome());
+ MOZ_ASSERT(aEndContent && aContainer->ComputeIndexOf(aEndContent).isSome());
+
+ nsINode* newRoot = RangeUtils::ComputeRootNode(aContainer);
+ MOZ_ASSERT(newRoot);
+ if (!newRoot) {
+ return;
+ }
+
+ RawRangeBoundary start(aContainer, aStartContent->GetPreviousSibling());
+ RawRangeBoundary end(aContainer, aEndContent);
+ DoSetRange(start, end, newRoot);
+}
+
+void nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetEndBefore(aNode, aErr);
+}
+
+void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ // If the node is being removed from its parent, GetRawRangeBoundaryBefore()
+ // returns unset instance. Then, SetEnd() will throw
+ // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
+ SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv);
+}
+
+void nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SetEndAfter(aNode, aErr);
+}
+
+void nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ // If the node is being removed from its parent, GetRawRangeBoundaryAfter()
+ // returns unset instance. Then, SetEnd() will throw
+ // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
+ SetEnd(RangeUtils::GetRawRangeBoundaryAfter(&aNode), aRv);
+}
+
+void nsRange::Collapse(bool aToStart) {
+ if (!mIsPositioned) return;
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ if (aToStart) {
+ DoSetRange(mStart, mStart, mRoot);
+ } else {
+ DoSetRange(mEnd, mEnd, mRoot);
+ }
+}
+
+void nsRange::CollapseJS(bool aToStart) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ Collapse(aToStart);
+}
+
+void nsRange::SelectNodeJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SelectNode(aNode, aErr);
+}
+
+void nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsINode* container = aNode.GetParentNode();
+ nsINode* newRoot = RangeUtils::ComputeRootNode(container);
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ const Maybe<uint32_t> index = container->ComputeIndexOf(&aNode);
+ // MOZ_ASSERT(index.isSome());
+ // We need to compute the index here unfortunately, because, while we have
+ // support for XBL, |container| may be the node's binding parent without
+ // actually containing it.
+ if (MOZ_UNLIKELY(NS_WARN_IF(index.isNothing()))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ DoSetRange(RawRangeBoundary{container, *index},
+ RawRangeBoundary{container, *index + 1u}, newRoot);
+}
+
+void nsRange::SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr) {
+ AutoCalledByJSRestore calledByJSRestorer(*this);
+ mCalledByJS = true;
+ SelectNodeContents(aNode, aErr);
+}
+
+void nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsINode* newRoot = RangeUtils::ComputeRootNode(&aNode);
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ DoSetRange(RawRangeBoundary(&aNode, 0u),
+ RawRangeBoundary(&aNode, aNode.Length()), newRoot);
+}
+
+// The Subtree Content Iterator only returns subtrees that are
+// completely within a given range. It doesn't return a CharacterData
+// node that contains either the start or end point of the range.,
+// nor does it return element nodes when nothing in the element is selected.
+// We need an iterator that will also include these start/end points
+// so that our methods/algorithms aren't cluttered with special
+// case code that tries to include these points while iterating.
+//
+// The RangeSubtreeIterator class mimics the ContentSubtreeIterator
+// methods we need, so should the Content Iterator support the
+// start/end points in the future, we can switchover relatively
+// easy.
+
+class MOZ_STACK_CLASS RangeSubtreeIterator {
+ private:
+ enum RangeSubtreeIterState { eDone = 0, eUseStart, eUseIterator, eUseEnd };
+
+ Maybe<ContentSubtreeIterator> mSubtreeIter;
+ RangeSubtreeIterState mIterState;
+
+ nsCOMPtr<nsINode> mStart;
+ nsCOMPtr<nsINode> mEnd;
+
+ public:
+ RangeSubtreeIterator() : mIterState(eDone) {}
+ ~RangeSubtreeIterator() = default;
+
+ nsresult Init(nsRange* aRange);
+ already_AddRefed<nsINode> GetCurrentNode();
+ void First();
+ void Last();
+ void Next();
+ void Prev();
+
+ bool IsDone() { return mIterState == eDone; }
+};
+
+nsresult RangeSubtreeIterator::Init(nsRange* aRange) {
+ mIterState = eDone;
+ if (aRange->Collapsed()) {
+ return NS_OK;
+ }
+
+ // Grab the start point of the range and QI it to
+ // a CharacterData pointer. If it is CharacterData store
+ // a pointer to the node.
+
+ if (!aRange->IsPositioned()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsINode* node = aRange->GetStartContainer();
+ if (NS_WARN_IF(!node)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (node->IsCharacterData() ||
+ (node->IsElement() &&
+ node->AsElement()->GetChildCount() == aRange->StartOffset())) {
+ mStart = node;
+ }
+
+ // Grab the end point of the range and QI it to
+ // a CharacterData pointer. If it is CharacterData store
+ // a pointer to the node.
+
+ node = aRange->GetEndContainer();
+ if (NS_WARN_IF(!node)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (node->IsCharacterData() ||
+ (node->IsElement() && aRange->EndOffset() == 0)) {
+ mEnd = node;
+ }
+
+ if (mStart && mStart == mEnd) {
+ // The range starts and stops in the same CharacterData
+ // node. Null out the end pointer so we only visit the
+ // node once!
+
+ mEnd = nullptr;
+ } else {
+ // Now create a Content Subtree Iterator to be used
+ // for the subtrees between the end points!
+
+ mSubtreeIter.emplace();
+
+ nsresult res = mSubtreeIter->Init(aRange);
+ if (NS_FAILED(res)) return res;
+
+ if (mSubtreeIter->IsDone()) {
+ // The subtree iterator thinks there's nothing
+ // to iterate over, so just free it up so we
+ // don't accidentally call into it.
+
+ mSubtreeIter.reset();
+ }
+ }
+
+ // Initialize the iterator by calling First().
+ // Note that we are ignoring the return value on purpose!
+
+ First();
+
+ return NS_OK;
+}
+
+already_AddRefed<nsINode> RangeSubtreeIterator::GetCurrentNode() {
+ nsCOMPtr<nsINode> node;
+
+ if (mIterState == eUseStart && mStart) {
+ node = mStart;
+ } else if (mIterState == eUseEnd && mEnd) {
+ node = mEnd;
+ } else if (mIterState == eUseIterator && mSubtreeIter) {
+ node = mSubtreeIter->GetCurrentNode();
+ }
+
+ return node.forget();
+}
+
+void RangeSubtreeIterator::First() {
+ if (mStart)
+ mIterState = eUseStart;
+ else if (mSubtreeIter) {
+ mSubtreeIter->First();
+
+ mIterState = eUseIterator;
+ } else if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+}
+
+void RangeSubtreeIterator::Last() {
+ if (mEnd)
+ mIterState = eUseEnd;
+ else if (mSubtreeIter) {
+ mSubtreeIter->Last();
+
+ mIterState = eUseIterator;
+ } else if (mStart)
+ mIterState = eUseStart;
+ else
+ mIterState = eDone;
+}
+
+void RangeSubtreeIterator::Next() {
+ if (mIterState == eUseStart) {
+ if (mSubtreeIter) {
+ mSubtreeIter->First();
+
+ mIterState = eUseIterator;
+ } else if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+ } else if (mIterState == eUseIterator) {
+ mSubtreeIter->Next();
+
+ if (mSubtreeIter->IsDone()) {
+ if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+ }
+ } else
+ mIterState = eDone;
+}
+
+void RangeSubtreeIterator::Prev() {
+ if (mIterState == eUseEnd) {
+ if (mSubtreeIter) {
+ mSubtreeIter->Last();
+
+ mIterState = eUseIterator;
+ } else if (mStart)
+ mIterState = eUseStart;
+ else
+ mIterState = eDone;
+ } else if (mIterState == eUseIterator) {
+ mSubtreeIter->Prev();
+
+ if (mSubtreeIter->IsDone()) {
+ if (mStart)
+ mIterState = eUseStart;
+ else
+ mIterState = eDone;
+ }
+ } else
+ mIterState = eDone;
+}
+
+// CollapseRangeAfterDelete() is a utility method that is used by
+// DeleteContents() and ExtractContents() to collapse the range
+// in the correct place, under the range's root container (the
+// range end points common container) as outlined by the Range spec:
+//
+// http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html
+// The assumption made by this method is that the delete or extract
+// has been done already, and left the range in a state where there is
+// no content between the 2 end points.
+
+static nsresult CollapseRangeAfterDelete(nsRange* aRange) {
+ NS_ENSURE_ARG_POINTER(aRange);
+
+ // Check if range gravity took care of collapsing the range for us!
+ if (aRange->Collapsed()) {
+ // aRange is collapsed so there's nothing for us to do.
+ //
+ // There are 2 possible scenarios here:
+ //
+ // 1. aRange could've been collapsed prior to the delete/extract,
+ // which would've resulted in nothing being removed, so aRange
+ // is already where it should be.
+ //
+ // 2. Prior to the delete/extract, aRange's start and end were in
+ // the same container which would mean everything between them
+ // was removed, causing range gravity to collapse the range.
+
+ return NS_OK;
+ }
+
+ // aRange isn't collapsed so figure out the appropriate place to collapse!
+ // First get both end points and their common ancestor.
+
+ if (!aRange->IsPositioned()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsINode> commonAncestor =
+ aRange->GetClosestCommonInclusiveAncestor();
+
+ nsCOMPtr<nsINode> startContainer = aRange->GetStartContainer();
+ nsCOMPtr<nsINode> endContainer = aRange->GetEndContainer();
+
+ // Collapse to one of the end points if they are already in the
+ // commonAncestor. This should work ok since this method is called
+ // immediately after a delete or extract that leaves no content
+ // between the 2 end points!
+
+ if (startContainer == commonAncestor) {
+ aRange->Collapse(true);
+ return NS_OK;
+ }
+ if (endContainer == commonAncestor) {
+ aRange->Collapse(false);
+ return NS_OK;
+ }
+
+ // End points are at differing levels. We want to collapse to the
+ // point that is between the 2 subtrees that contain each point,
+ // under the common ancestor.
+
+ nsCOMPtr<nsINode> nodeToSelect(startContainer);
+
+ while (nodeToSelect) {
+ nsCOMPtr<nsINode> parent = nodeToSelect->GetParentNode();
+ if (parent == commonAncestor) break; // We found the nodeToSelect!
+
+ nodeToSelect = parent;
+ }
+
+ if (!nodeToSelect) return NS_ERROR_FAILURE; // This should never happen!
+
+ ErrorResult error;
+ aRange->SelectNode(*nodeToSelect, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ aRange->Collapse(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrependChild(nsINode* aContainer, nsINode* aChild) {
+ nsCOMPtr<nsINode> first = aContainer->GetFirstChild();
+ ErrorResult rv;
+ aContainer->InsertBefore(*aChild, first, rv);
+ return rv.StealNSResult();
+}
+
+// Helper function for CutContents, making sure that the current node wasn't
+// removed by mutation events (bug 766426)
+static bool ValidateCurrentNode(nsRange* aRange, RangeSubtreeIterator& aIter) {
+ bool before, after;
+ nsCOMPtr<nsINode> node = aIter.GetCurrentNode();
+ if (!node) {
+ // We don't have to worry that the node was removed if it doesn't exist,
+ // e.g., the iterator is done.
+ return true;
+ }
+
+ nsresult rv = RangeUtils::CompareNodeToRange(node, aRange, &before, &after);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ if (before || after) {
+ if (node->IsCharacterData()) {
+ // If we're dealing with the start/end container which is a character
+ // node, pretend that the node is in the range.
+ if (before && node == aRange->GetStartContainer()) {
+ before = false;
+ }
+ if (after && node == aRange->GetEndContainer()) {
+ after = false;
+ }
+ }
+ }
+
+ return !before && !after;
+}
+
+void nsRange::CutContents(DocumentFragment** aFragment, ErrorResult& aRv) {
+ if (aFragment) {
+ *aFragment = nullptr;
+ }
+
+ if (!CanAccess(*mStart.Container()) || !CanAccess(*mEnd.Container())) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<Document> doc = mStart.Container()->OwnerDoc();
+
+ nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // If aFragment isn't null, create a temporary fragment to hold our return.
+ RefPtr<DocumentFragment> retval;
+ if (aFragment) {
+ retval =
+ new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
+ }
+ nsCOMPtr<nsINode> commonCloneAncestor = retval.get();
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(mRoot ? mRoot->OwnerDoc() : nullptr, nullptr);
+
+ // Save the range end points locally to avoid interference
+ // of Range gravity during our edits!
+
+ nsCOMPtr<nsINode> startContainer = mStart.Container();
+ // `GetCommonAncestorContainer()` above ensures the range is positioned, hence
+ // there have to be valid offsets.
+ uint32_t startOffset =
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ nsCOMPtr<nsINode> endContainer = mEnd.Container();
+ uint32_t endOffset = *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+
+ if (retval) {
+ // For extractContents(), abort early if there's a doctype (bug 719533).
+ // This can happen only if the common ancestor is a document, in which case
+ // we just need to find its doctype child and check if that's in the range.
+ nsCOMPtr<Document> commonAncestorDocument =
+ do_QueryInterface(commonAncestor);
+ if (commonAncestorDocument) {
+ RefPtr<DocumentType> doctype = commonAncestorDocument->GetDoctype();
+
+ // `GetCommonAncestorContainer()` above ensured the range is positioned.
+ // Hence, start and end are both set and valid. If available, `doctype`
+ // has a common ancestor with start and end, hence both have to be
+ // comparable to it.
+ if (doctype &&
+ *nsContentUtils::ComparePoints(startContainer, startOffset, doctype,
+ 0) < 0 &&
+ *nsContentUtils::ComparePoints(doctype, 0, endContainer, endOffset) <
+ 0) {
+ aRv.ThrowHierarchyRequestError("Start or end position isn't valid.");
+ return;
+ }
+ }
+ }
+
+ // Create and initialize a subtree iterator that will give
+ // us all the subtrees within the range.
+
+ RangeSubtreeIterator iter;
+
+ aRv = iter.Init(this);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (iter.IsDone()) {
+ // There's nothing for us to delete.
+ aRv = CollapseRangeAfterDelete(this);
+ if (!aRv.Failed() && aFragment) {
+ retval.forget(aFragment);
+ }
+ return;
+ }
+
+ iter.First();
+
+ bool handled = false;
+
+ // With the exception of text nodes that contain one of the range
+ // end points, the subtree iterator should only give us back subtrees
+ // that are completely contained between the range's end points.
+
+ while (!iter.IsDone()) {
+ nsCOMPtr<nsINode> nodeToResult;
+ nsCOMPtr<nsINode> node = iter.GetCurrentNode();
+
+ // Before we delete anything, advance the iterator to the next node that's
+ // not a descendant of this one. XXX It's a bit silly to iterate through
+ // the descendants only to throw them out, we should use an iterator that
+ // skips the descendants to begin with.
+
+ iter.Next();
+ nsCOMPtr<nsINode> nextNode = iter.GetCurrentNode();
+ while (nextNode && nextNode->IsInclusiveDescendantOf(node)) {
+ iter.Next();
+ nextNode = iter.GetCurrentNode();
+ }
+
+ handled = false;
+
+ // If it's CharacterData, make sure we might need to delete
+ // part of the data, instead of removing the whole node.
+ //
+ // XXX_kin: We need to also handle ProcessingInstruction
+ // XXX_kin: according to the spec.
+
+ if (auto charData = CharacterData::FromNode(node)) {
+ uint32_t dataLength = 0;
+
+ if (node == startContainer) {
+ if (node == endContainer) {
+ // This range is completely contained within a single text node.
+ // Delete or extract the data between startOffset and endOffset.
+
+ if (endOffset > startOffset) {
+ if (retval) {
+ nsAutoString cutValue;
+ charData->SubstringData(startOffset, endOffset - startOffset,
+ cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nsCOMPtr<nsINode> clone = node->CloneNode(false, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ clone->SetNodeValue(cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nodeToResult = clone;
+ }
+
+ nsMutationGuard guard;
+ charData->DeleteData(startOffset, endOffset - startOffset, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (guard.Mutated(0) && !ValidateCurrentNode(this, iter)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ handled = true;
+ } else {
+ // Delete or extract everything after startOffset.
+
+ dataLength = charData->Length();
+
+ if (dataLength >= startOffset) {
+ if (retval) {
+ nsAutoString cutValue;
+ charData->SubstringData(startOffset, dataLength, cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nsCOMPtr<nsINode> clone = node->CloneNode(false, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ clone->SetNodeValue(cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nodeToResult = clone;
+ }
+
+ nsMutationGuard guard;
+ charData->DeleteData(startOffset, dataLength, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (guard.Mutated(0) && !ValidateCurrentNode(this, iter)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ handled = true;
+ }
+ } else if (node == endContainer) {
+ // Delete or extract everything before endOffset.
+ if (retval) {
+ nsAutoString cutValue;
+ charData->SubstringData(0, endOffset, cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nsCOMPtr<nsINode> clone = node->CloneNode(false, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ clone->SetNodeValue(cutValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nodeToResult = clone;
+ }
+
+ nsMutationGuard guard;
+ charData->DeleteData(0, endOffset, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (guard.Mutated(0) && !ValidateCurrentNode(this, iter)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ handled = true;
+ }
+ }
+
+ if (!handled && (node == endContainer || node == startContainer)) {
+ if (node && node->IsElement() &&
+ ((node == endContainer && endOffset == 0) ||
+ (node == startContainer &&
+ node->AsElement()->GetChildCount() == startOffset))) {
+ if (retval) {
+ nodeToResult = node->CloneNode(false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ handled = true;
+ }
+ }
+
+ if (!handled) {
+ // node was not handled above, so it must be completely contained
+ // within the range. Just remove it from the tree!
+ nodeToResult = node;
+ }
+
+ uint32_t parentCount = 0;
+ // Set the result to document fragment if we have 'retval'.
+ if (retval) {
+ nsCOMPtr<nsINode> oldCommonAncestor = commonAncestor;
+ if (!iter.IsDone()) {
+ // Setup the parameters for the next iteration of the loop.
+ if (!nextNode) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Get node's and nextNode's common parent. Do this before moving
+ // nodes from original DOM to result fragment.
+ commonAncestor =
+ nsContentUtils::GetClosestCommonInclusiveAncestor(node, nextNode);
+ if (!commonAncestor) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsCOMPtr<nsINode> parentCounterNode = node;
+ while (parentCounterNode && parentCounterNode != commonAncestor) {
+ ++parentCount;
+ parentCounterNode = parentCounterNode->GetParentNode();
+ if (!parentCounterNode) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+ }
+
+ // Clone the parent hierarchy between commonAncestor and node.
+ nsCOMPtr<nsINode> closestAncestor, farthestAncestor;
+ aRv = CloneParentsBetween(oldCommonAncestor, node,
+ getter_AddRefs(closestAncestor),
+ getter_AddRefs(farthestAncestor));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (farthestAncestor) {
+ commonCloneAncestor->AppendChild(*farthestAncestor, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ nsMutationGuard guard;
+ nsCOMPtr<nsINode> parent = nodeToResult->GetParentNode();
+ if (closestAncestor) {
+ closestAncestor->AppendChild(*nodeToResult, aRv);
+ } else {
+ commonCloneAncestor->AppendChild(*nodeToResult, aRv);
+ }
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (guard.Mutated(parent ? 2 : 1) && !ValidateCurrentNode(this, iter)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ } else if (nodeToResult) {
+ nsMutationGuard guard;
+ nsCOMPtr<nsINode> node = nodeToResult;
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+ if (parent) {
+ parent->RemoveChild(*node, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ if (guard.Mutated(1) && !ValidateCurrentNode(this, iter)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ if (!iter.IsDone() && retval) {
+ // Find the equivalent of commonAncestor in the cloned tree.
+ nsCOMPtr<nsINode> newCloneAncestor = nodeToResult;
+ for (uint32_t i = parentCount; i; --i) {
+ newCloneAncestor = newCloneAncestor->GetParentNode();
+ if (!newCloneAncestor) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+ commonCloneAncestor = newCloneAncestor;
+ }
+ }
+
+ aRv = CollapseRangeAfterDelete(this);
+ if (!aRv.Failed() && aFragment) {
+ retval.forget(aFragment);
+ }
+}
+
+void nsRange::DeleteContents(ErrorResult& aRv) { CutContents(nullptr, aRv); }
+
+already_AddRefed<DocumentFragment> nsRange::ExtractContents(ErrorResult& rv) {
+ RefPtr<DocumentFragment> fragment;
+ CutContents(getter_AddRefs(fragment), rv);
+ return fragment.forget();
+}
+
+int16_t nsRange::CompareBoundaryPoints(uint16_t aHow,
+ const nsRange& aOtherRange,
+ ErrorResult& aRv) {
+ if (!mIsPositioned || !aOtherRange.IsPositioned()) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ nsINode *ourNode, *otherNode;
+ uint32_t ourOffset, otherOffset;
+
+ switch (aHow) {
+ case Range_Binding::START_TO_START:
+ ourNode = mStart.Container();
+ ourOffset = *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ otherNode = aOtherRange.GetStartContainer();
+ otherOffset = aOtherRange.StartOffset();
+ break;
+ case Range_Binding::START_TO_END:
+ ourNode = mEnd.Container();
+ ourOffset = *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ otherNode = aOtherRange.GetStartContainer();
+ otherOffset = aOtherRange.StartOffset();
+ break;
+ case Range_Binding::END_TO_START:
+ ourNode = mStart.Container();
+ ourOffset = *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ otherNode = aOtherRange.GetEndContainer();
+ otherOffset = aOtherRange.EndOffset();
+ break;
+ case Range_Binding::END_TO_END:
+ ourNode = mEnd.Container();
+ ourOffset = *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+ otherNode = aOtherRange.GetEndContainer();
+ otherOffset = aOtherRange.EndOffset();
+ break;
+ default:
+ // We were passed an illegal value
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return 0;
+ }
+
+ if (mRoot != aOtherRange.GetRoot()) {
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return 0;
+ }
+
+ const Maybe<int32_t> order =
+ nsContentUtils::ComparePoints(ourNode, ourOffset, otherNode, otherOffset);
+
+ // `this` and `aOtherRange` share the same root and (ourNode, ourOffset),
+ // (otherNode, otherOffset) correspond to some of their boundaries. Hence,
+ // (ourNode, ourOffset) and (otherNode, otherOffset) have to be comparable.
+ return *order;
+}
+
+/* static */
+nsresult nsRange::CloneParentsBetween(nsINode* aAncestor, nsINode* aNode,
+ nsINode** aClosestAncestor,
+ nsINode** aFarthestAncestor) {
+ NS_ENSURE_ARG_POINTER(
+ (aAncestor && aNode && aClosestAncestor && aFarthestAncestor));
+
+ *aClosestAncestor = nullptr;
+ *aFarthestAncestor = nullptr;
+
+ if (aAncestor == aNode) return NS_OK;
+
+ AutoTArray<nsCOMPtr<nsINode>, 16> parentStack;
+
+ nsCOMPtr<nsINode> parent = aNode->GetParentNode();
+ while (parent && parent != aAncestor) {
+ parentStack.AppendElement(parent);
+ parent = parent->GetParentNode();
+ }
+
+ nsCOMPtr<nsINode> firstParent;
+ nsCOMPtr<nsINode> lastParent;
+ for (int32_t i = parentStack.Length() - 1; i >= 0; i--) {
+ ErrorResult rv;
+ nsCOMPtr<nsINode> clone = parentStack[i]->CloneNode(false, rv);
+
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ if (!clone) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!lastParent) {
+ lastParent = clone;
+ } else {
+ firstParent->AppendChild(*clone, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ }
+
+ firstParent = clone;
+ }
+
+ firstParent.forget(aClosestAncestor);
+ lastParent.forget(aFarthestAncestor);
+
+ return NS_OK;
+}
+
+already_AddRefed<DocumentFragment> nsRange::CloneContents(ErrorResult& aRv) {
+ nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(aRv);
+ MOZ_ASSERT(!aRv.Failed(), "GetCommonAncestorContainer() shouldn't fail!");
+
+ nsCOMPtr<Document> doc = mStart.Container()->OwnerDoc();
+ NS_ASSERTION(doc, "CloneContents needs a document to continue.");
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Create a new document fragment in the context of this document,
+ // which might be null
+
+ RefPtr<DocumentFragment> clonedFrag =
+ new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
+
+ if (Collapsed()) {
+ return clonedFrag.forget();
+ }
+
+ nsCOMPtr<nsINode> commonCloneAncestor = clonedFrag.get();
+
+ // Create and initialize a subtree iterator that will give
+ // us all the subtrees within the range.
+
+ RangeSubtreeIterator iter;
+
+ aRv = iter.Init(this);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (iter.IsDone()) {
+ // There's nothing to add to the doc frag, we must be done!
+ return clonedFrag.forget();
+ }
+
+ iter.First();
+
+ // With the exception of text nodes that contain one of the range
+ // end points and elements which don't have any content selected the subtree
+ // iterator should only give us back subtrees that are completely contained
+ // between the range's end points.
+ //
+ // Unfortunately these subtrees don't contain the parent hierarchy/context
+ // that the Range spec requires us to return. This loop clones the
+ // parent hierarchy, adds a cloned version of the subtree, to it, then
+ // correctly places this new subtree into the doc fragment.
+
+ while (!iter.IsDone()) {
+ nsCOMPtr<nsINode> node = iter.GetCurrentNode();
+ bool deepClone =
+ !node->IsElement() ||
+ (!(node == mEnd.Container() &&
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets) == 0) &&
+ !(node == mStart.Container() &&
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets) ==
+ node->AsElement()->GetChildCount()));
+
+ // Clone the current subtree!
+
+ nsCOMPtr<nsINode> clone = node->CloneNode(deepClone, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If it's CharacterData, make sure we only clone what
+ // is in the range.
+ //
+ // XXX_kin: We need to also handle ProcessingInstruction
+ // XXX_kin: according to the spec.
+
+ if (auto charData = CharacterData::FromNode(clone)) {
+ if (node == mEnd.Container()) {
+ // We only need the data before mEndOffset, so get rid of any
+ // data after it.
+
+ uint32_t dataLength = charData->Length();
+ if (dataLength >
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets)) {
+ charData->DeleteData(
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ dataLength -
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (node == mStart.Container()) {
+ // We don't need any data before mStartOffset, so just
+ // delete it!
+
+ if (*mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets) > 0) {
+ charData->DeleteData(
+ 0, *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+ }
+
+ // Clone the parent hierarchy between commonAncestor and node.
+
+ nsCOMPtr<nsINode> closestAncestor, farthestAncestor;
+
+ aRv = CloneParentsBetween(commonAncestor, node,
+ getter_AddRefs(closestAncestor),
+ getter_AddRefs(farthestAncestor));
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Hook the parent hierarchy/context of the subtree into the clone tree.
+
+ if (farthestAncestor) {
+ commonCloneAncestor->AppendChild(*farthestAncestor, aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ // Place the cloned subtree into the cloned doc frag tree!
+
+ nsCOMPtr<nsINode> cloneNode = clone;
+ if (closestAncestor) {
+ // Append the subtree under closestAncestor since it is the
+ // immediate parent of the subtree.
+
+ closestAncestor->AppendChild(*cloneNode, aRv);
+ } else {
+ // If we get here, there is no missing parent hierarchy between
+ // commonAncestor and node, so just append clone to commonCloneAncestor.
+
+ commonCloneAncestor->AppendChild(*cloneNode, aRv);
+ }
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Get the next subtree to be processed. The idea here is to setup
+ // the parameters for the next iteration of the loop.
+
+ iter.Next();
+
+ if (iter.IsDone()) break; // We must be done!
+
+ nsCOMPtr<nsINode> nextNode = iter.GetCurrentNode();
+ if (!nextNode) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Get node and nextNode's common parent.
+ commonAncestor =
+ nsContentUtils::GetClosestCommonInclusiveAncestor(node, nextNode);
+
+ if (!commonAncestor) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Find the equivalent of commonAncestor in the cloned tree!
+
+ while (node && node != commonAncestor) {
+ node = node->GetParentNode();
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!node) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ cloneNode = cloneNode->GetParentNode();
+ if (!cloneNode) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ commonCloneAncestor = cloneNode;
+ }
+
+ return clonedFrag.forget();
+}
+
+already_AddRefed<nsRange> nsRange::CloneRange() const {
+ RefPtr<nsRange> range = nsRange::Create(mOwner);
+ range->DoSetRange(mStart, mEnd, mRoot);
+ return range.forget();
+}
+
+void nsRange::InsertNode(nsINode& aNode, ErrorResult& aRv) {
+ if (!CanAccess(aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (!IsPositioned()) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ uint32_t tStartOffset = StartOffset();
+
+ nsCOMPtr<nsINode> tStartContainer = GetStartContainer();
+
+ if (!CanAccess(*tStartContainer)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (&aNode == tStartContainer) {
+ aRv.ThrowHierarchyRequestError(
+ "The inserted node can not be range's start node.");
+ return;
+ }
+
+ // This is the node we'll be inserting before, and its parent
+ nsCOMPtr<nsINode> referenceNode;
+ nsCOMPtr<nsINode> referenceParentNode = tStartContainer;
+
+ RefPtr<Text> startTextNode = tStartContainer->GetAsText();
+ nsCOMPtr<nsINodeList> tChildList;
+ if (startTextNode) {
+ referenceParentNode = tStartContainer->GetParentNode();
+ if (!referenceParentNode) {
+ aRv.ThrowHierarchyRequestError(
+ "Can not get range's start node's parent.");
+ return;
+ }
+
+ referenceParentNode->EnsurePreInsertionValidity(aNode, tStartContainer,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RefPtr<Text> secondPart = startTextNode->SplitText(tStartOffset, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ referenceNode = secondPart;
+ } else {
+ tChildList = tStartContainer->ChildNodes();
+
+ // find the insertion point in the DOM and insert the Node
+ referenceNode = tChildList->Item(tStartOffset);
+
+ tStartContainer->EnsurePreInsertionValidity(aNode, referenceNode, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // We might need to update the end to include the new node (bug 433662).
+ // Ideally we'd only do this if needed, but it's tricky to know when it's
+ // needed in advance (bug 765799).
+ uint32_t newOffset;
+
+ if (referenceNode) {
+ Maybe<uint32_t> indexInParent = referenceNode->ComputeIndexInParentNode();
+ if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ newOffset = *indexInParent;
+ } else {
+ newOffset = tChildList->Length();
+ }
+
+ if (aNode.NodeType() == nsINode::DOCUMENT_FRAGMENT_NODE) {
+ newOffset += aNode.GetChildCount();
+ } else {
+ newOffset++;
+ }
+
+ // Now actually insert the node
+ nsCOMPtr<nsINode> tResultNode;
+ tResultNode = referenceParentNode->InsertBefore(aNode, referenceNode, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (Collapsed()) {
+ aRv = SetEnd(referenceParentNode, newOffset);
+ }
+}
+
+void nsRange::SurroundContents(nsINode& aNewParent, ErrorResult& aRv) {
+ if (!CanAccess(aNewParent)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (!mRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ // INVALID_STATE_ERROR: Raised if the Range partially selects a non-text
+ // node.
+ if (mStart.Container() != mEnd.Container()) {
+ bool startIsText = mStart.Container()->IsText();
+ bool endIsText = mEnd.Container()->IsText();
+ nsINode* startGrandParent = mStart.Container()->GetParentNode();
+ nsINode* endGrandParent = mEnd.Container()->GetParentNode();
+ if (!((startIsText && endIsText && startGrandParent &&
+ startGrandParent == endGrandParent) ||
+ (startIsText && startGrandParent &&
+ startGrandParent == mEnd.Container()) ||
+ (endIsText && endGrandParent &&
+ endGrandParent == mStart.Container()))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ }
+
+ // INVALID_NODE_TYPE_ERROR if aNewParent is something that can't be inserted
+ // (Document, DocumentType, DocumentFragment)
+ uint16_t nodeType = aNewParent.NodeType();
+ if (nodeType == nsINode::DOCUMENT_NODE ||
+ nodeType == nsINode::DOCUMENT_TYPE_NODE ||
+ nodeType == nsINode::DOCUMENT_FRAGMENT_NODE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ // Extract the contents within the range.
+
+ RefPtr<DocumentFragment> docFrag = ExtractContents(aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!docFrag) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Spec says we need to remove all of aNewParent's
+ // children prior to insertion.
+
+ nsCOMPtr<nsINodeList> children = aNewParent.ChildNodes();
+ if (!children) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ uint32_t numChildren = children->Length();
+
+ while (numChildren) {
+ nsCOMPtr<nsINode> child = children->Item(--numChildren);
+ if (!child) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aNewParent.RemoveChild(*child, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Insert aNewParent at the range's start point.
+
+ InsertNode(aNewParent, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Append the content we extracted under aNewParent.
+ aNewParent.AppendChild(*docFrag, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Select aNewParent, and its contents.
+
+ SelectNode(aNewParent, aRv);
+}
+
+void nsRange::ToString(nsAString& aReturn, ErrorResult& aErr) {
+ // clear the string
+ aReturn.Truncate();
+
+ // If we're unpositioned, return the empty string
+ if (!mIsPositioned) {
+ return;
+ }
+
+#ifdef DEBUG_range
+ printf("Range dump: -----------------------\n");
+#endif /* DEBUG */
+
+ // effeciency hack for simple case
+ if (mStart.Container() == mEnd.Container()) {
+ Text* textNode =
+ mStart.Container() ? mStart.Container()->GetAsText() : nullptr;
+
+ if (textNode) {
+#ifdef DEBUG_range
+ // If debug, dump it:
+ textNode->List(stdout);
+ printf("End Range dump: -----------------------\n");
+#endif /* DEBUG */
+
+ // grab the text
+ textNode->SubstringData(
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets) -
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ aReturn, aErr);
+ return;
+ }
+ }
+
+ /* complex case: mStart.Container() != mEnd.Container(), or mStartParent not a
+ text node revisit - there are potential optimizations here and also
+ tradeoffs.
+ */
+
+ PostContentIterator postOrderIter;
+ nsresult rv = postOrderIter.Init(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aErr.Throw(rv);
+ return;
+ }
+
+ nsString tempString;
+
+ // loop through the content iterator, which returns nodes in the range in
+ // close tag order, and grab the text from any text node
+ for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+ nsINode* n = postOrderIter.GetCurrentNode();
+
+#ifdef DEBUG_range
+ // If debug, dump it:
+ n->List(stdout);
+#endif /* DEBUG */
+ Text* textNode = n->GetAsText();
+ if (textNode) // if it's a text node, get the text
+ {
+ if (n == mStart.Container()) { // only include text past start offset
+ uint32_t strLength = textNode->Length();
+ textNode->SubstringData(
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ strLength -
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ tempString, IgnoreErrors());
+ aReturn += tempString;
+ } else if (n ==
+ mEnd.Container()) { // only include text before end offset
+ textNode->SubstringData(
+ 0, *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ tempString, IgnoreErrors());
+ aReturn += tempString;
+ } else { // grab the whole kit-n-kaboodle
+ textNode->GetData(tempString);
+ aReturn += tempString;
+ }
+ }
+ }
+
+#ifdef DEBUG_range
+ printf("End Range dump: -----------------------\n");
+#endif /* DEBUG */
+}
+
+void nsRange::Detach() {}
+
+already_AddRefed<DocumentFragment> nsRange::CreateContextualFragment(
+ const nsAString& aFragment, ErrorResult& aRv) const {
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return nsContentUtils::CreateContextualFragment(mStart.Container(), aFragment,
+ false, aRv);
+}
+
+static void ExtractRectFromOffset(nsIFrame* aFrame, const int32_t aOffset,
+ nsRect* aR, bool aFlushToOriginEdge,
+ bool aClampToEdge) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aR);
+
+ nsPoint point;
+ aFrame->GetPointFromOffset(aOffset, &point);
+
+ // Determine if aFrame has a vertical writing mode, which will change our math
+ // on the output rect.
+ bool isVertical = aFrame->GetWritingMode().IsVertical();
+
+ if (!aClampToEdge && !aR->Contains(point)) {
+ // If point is outside aR, and we aren't clamping, output an empty rect
+ // with origin at the point.
+ if (isVertical) {
+ aR->SetHeight(0);
+ aR->y = point.y;
+ } else {
+ aR->SetWidth(0);
+ aR->x = point.x;
+ }
+ return;
+ }
+
+ if (aClampToEdge) {
+ point = aR->ClampPoint(point);
+ }
+
+ // point is within aR, and now we'll modify aR to output a rect that has point
+ // on one edge. But which edge?
+ if (aFlushToOriginEdge) {
+ // The output rect should be flush to the edge of aR that contains the
+ // origin.
+ if (isVertical) {
+ aR->SetHeight(point.y - aR->y);
+ } else {
+ aR->SetWidth(point.x - aR->x);
+ }
+ } else {
+ // The output rect should be flush to the edge of aR opposite the origin.
+ if (isVertical) {
+ aR->SetHeight(aR->YMost() - point.y);
+ aR->y = point.y;
+ } else {
+ aR->SetWidth(aR->XMost() - point.x);
+ aR->x = point.x;
+ }
+ }
+}
+
+static nsTextFrame* GetTextFrameForContent(nsIContent* aContent,
+ bool aFlushLayout) {
+ RefPtr<Document> doc = aContent->OwnerDoc();
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ // Try to un-suppress whitespace if needed, but only if we'll be able to flush
+ // to immediately see the results of the un-suppression. If we can't flush
+ // here, then calling EnsureFrameForTextNodeIsCreatedAfterFlush would be
+ // pointless anyway.
+ if (aFlushLayout) {
+ const bool frameWillBeUnsuppressed =
+ presShell->FrameConstructor()
+ ->EnsureFrameForTextNodeIsCreatedAfterFlush(
+ static_cast<CharacterData*>(aContent));
+ if (frameWillBeUnsuppressed) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame || !frame->IsTextFrame()) {
+ return nullptr;
+ }
+ return static_cast<nsTextFrame*>(frame);
+}
+
+static nsresult GetPartialTextRect(RectCallback* aCallback,
+ Sequence<nsString>* aTextList,
+ nsIContent* aContent, int32_t aStartOffset,
+ int32_t aEndOffset, bool aClampToEdge,
+ bool aFlushLayout) {
+ nsTextFrame* textFrame = GetTextFrameForContent(aContent, aFlushLayout);
+ if (textFrame) {
+ nsIFrame* relativeTo =
+ nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
+
+ for (nsTextFrame* f = textFrame->FindContinuationForOffset(aStartOffset); f;
+ f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
+ int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
+ if (fend <= aStartOffset) {
+ continue;
+ }
+ if (fstart >= aEndOffset) {
+ break;
+ }
+
+ // Calculate the text content offsets we'll need if text is requested.
+ int32_t textContentStart = fstart;
+ int32_t textContentEnd = fend;
+
+ // overlapping with the offset we want
+ f->EnsureTextRun(nsTextFrame::eInflated);
+ NS_ENSURE_TRUE(f->GetTextRun(nsTextFrame::eInflated),
+ NS_ERROR_OUT_OF_MEMORY);
+ bool topLeftToBottomRight =
+ !f->GetTextRun(nsTextFrame::eInflated)->IsInlineReversed();
+ nsRect r = f->GetRectRelativeToSelf();
+ if (fstart < aStartOffset) {
+ // aStartOffset is within this frame
+ ExtractRectFromOffset(f, aStartOffset, &r, !topLeftToBottomRight,
+ aClampToEdge);
+ textContentStart = aStartOffset;
+ }
+ if (fend > aEndOffset) {
+ // aEndOffset is in the middle of this frame
+ ExtractRectFromOffset(f, aEndOffset, &r, topLeftToBottomRight,
+ aClampToEdge);
+ textContentEnd = aEndOffset;
+ }
+ r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
+ aCallback->AddRect(r);
+
+ // Finally capture the text, if requested.
+ if (aTextList) {
+ nsIFrame::RenderedText renderedText =
+ f->GetRenderedText(textContentStart, textContentEnd,
+ nsIFrame::TextOffsetType::OffsetsInContentText,
+ nsIFrame::TrailingWhitespace::DontTrim);
+
+ NS_ENSURE_TRUE(aTextList->AppendElement(renderedText.mString, fallible),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+static void CollectClientRectsForSubtree(
+ nsINode* aNode, RectCallback* aCollector, Sequence<nsString>* aTextList,
+ nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
+ uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout) {
+ auto* content = nsIContent::FromNode(aNode);
+ if (!content) {
+ return;
+ }
+
+ if (content->IsText()) {
+ if (aNode == aStartContainer) {
+ int32_t offset = aStartContainer == aEndContainer
+ ? static_cast<int32_t>(aEndOffset)
+ : content->AsText()->TextDataLength();
+ GetPartialTextRect(aCollector, aTextList, content,
+ static_cast<int32_t>(aStartOffset), offset,
+ aClampToEdge, aFlushLayout);
+ return;
+ }
+
+ if (aNode == aEndContainer) {
+ GetPartialTextRect(aCollector, aTextList, content, 0,
+ static_cast<int32_t>(aEndOffset), aClampToEdge,
+ aFlushLayout);
+ return;
+ }
+ }
+
+ if (aNode->IsElement() && aNode->AsElement()->IsDisplayContents()) {
+ FlattenedChildIterator childIter(content);
+
+ for (nsIContent* child = childIter.GetNextChild(); child;
+ child = childIter.GetNextChild()) {
+ CollectClientRectsForSubtree(child, aCollector, aTextList,
+ aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset, aClampToEdge, aFlushLayout);
+ }
+ } else if (nsIFrame* frame = content->GetPrimaryFrame()) {
+ nsLayoutUtils::GetAllInFlowRectsAndTexts(
+ frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
+ aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ }
+}
+
+/* static */
+void nsRange::CollectClientRectsAndText(
+ RectCallback* aCollector, Sequence<nsString>* aTextList, nsRange* aRange,
+ nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
+ uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout) {
+ // Currently, this method is called with start of end offset of nsRange.
+ // So, they must be between 0 - INT32_MAX.
+ MOZ_ASSERT(RangeUtils::IsValidOffset(aStartOffset));
+ MOZ_ASSERT(RangeUtils::IsValidOffset(aEndOffset));
+
+ // Hold strong pointers across the flush
+ nsCOMPtr<nsINode> startContainer = aStartContainer;
+ nsCOMPtr<nsINode> endContainer = aEndContainer;
+
+ // Flush out layout so our frames are up to date.
+ if (!aStartContainer->IsInComposedDoc()) {
+ return;
+ }
+
+ if (aFlushLayout) {
+ aStartContainer->OwnerDoc()->FlushPendingNotifications(FlushType::Layout);
+ // Recheck whether we're still in the document
+ if (!aStartContainer->IsInComposedDoc()) {
+ return;
+ }
+ }
+
+ RangeSubtreeIterator iter;
+
+ nsresult rv = iter.Init(aRange);
+ if (NS_FAILED(rv)) return;
+
+ if (iter.IsDone()) {
+ // the range is collapsed, only continue if the cursor is in a text node
+ if (aStartContainer->IsText()) {
+ nsTextFrame* textFrame =
+ GetTextFrameForContent(aStartContainer->AsText(), aFlushLayout);
+ if (textFrame) {
+ int32_t outOffset;
+ nsIFrame* outFrame;
+ textFrame->GetChildFrameContainingOffset(
+ static_cast<int32_t>(aStartOffset), false, &outOffset, &outFrame);
+ if (outFrame) {
+ nsIFrame* relativeTo =
+ nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
+ nsRect r = outFrame->GetRectRelativeToSelf();
+ ExtractRectFromOffset(outFrame, static_cast<int32_t>(aStartOffset),
+ &r, false, aClampToEdge);
+ r.SetWidth(0);
+ r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r,
+ relativeTo);
+ aCollector->AddRect(r);
+ }
+ }
+ }
+ return;
+ }
+
+ do {
+ nsCOMPtr<nsINode> node = iter.GetCurrentNode();
+ iter.Next();
+
+ CollectClientRectsForSubtree(node, aCollector, aTextList, aStartContainer,
+ aStartOffset, aEndContainer, aEndOffset,
+ aClampToEdge, aFlushLayout);
+ } while (!iter.IsDone());
+}
+
+already_AddRefed<DOMRect> nsRange::GetBoundingClientRect(bool aClampToEdge,
+ bool aFlushLayout) {
+ RefPtr<DOMRect> rect = new DOMRect(ToSupports(mOwner));
+ if (!mIsPositioned) {
+ return rect.forget();
+ }
+
+ nsLayoutUtils::RectAccumulator accumulator;
+ CollectClientRectsAndText(
+ &accumulator, nullptr, this, mStart.Container(),
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ mEnd.Container(),
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
+ aFlushLayout);
+
+ nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
+ : accumulator.mResultRect;
+ rect->SetLayoutRect(r);
+ return rect.forget();
+}
+
+already_AddRefed<DOMRectList> nsRange::GetClientRects(bool aClampToEdge,
+ bool aFlushLayout) {
+ if (!mIsPositioned) {
+ return nullptr;
+ }
+
+ RefPtr<DOMRectList> rectList = new DOMRectList(ToSupports(mOwner));
+
+ nsLayoutUtils::RectListBuilder builder(rectList);
+
+ CollectClientRectsAndText(
+ &builder, nullptr, this, mStart.Container(),
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ mEnd.Container(),
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
+ aFlushLayout);
+ return rectList.forget();
+}
+
+void nsRange::GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts& aResult,
+ ErrorResult& aErr) {
+ if (!mIsPositioned) {
+ return;
+ }
+
+ aResult.mRectList = new DOMRectList(ToSupports(mOwner));
+
+ nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
+
+ CollectClientRectsAndText(
+ &builder, &aResult.mTextList, this, mStart.Container(),
+ *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ mEnd.Container(),
+ *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), true, true);
+}
+
+nsresult nsRange::GetUsedFontFaces(nsLayoutUtils::UsedFontFaceList& aResult,
+ uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace) {
+ NS_ENSURE_TRUE(mIsPositioned, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsINode> startContainer = mStart.Container();
+ nsCOMPtr<nsINode> endContainer = mEnd.Container();
+
+ // Flush out layout so our frames are up to date.
+ Document* doc = mStart.Container()->OwnerDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+ doc->FlushPendingNotifications(FlushType::Frames);
+
+ // Recheck whether we're still in the document
+ NS_ENSURE_TRUE(mStart.Container()->IsInComposedDoc(), NS_ERROR_UNEXPECTED);
+
+ // A table to map gfxFontEntry objects to InspectorFontFace objects.
+ // This table does NOT own the InspectorFontFace objects, it only holds
+ // raw pointers to them. They are owned by the aResult array.
+ nsLayoutUtils::UsedFontFaceTable fontFaces;
+
+ RangeSubtreeIterator iter;
+ nsresult rv = iter.Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!iter.IsDone()) {
+ // only collect anything if the range is not collapsed
+ nsCOMPtr<nsINode> node = iter.GetCurrentNode();
+ iter.Next();
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+ if (!content) {
+ continue;
+ }
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ continue;
+ }
+
+ if (content->IsText()) {
+ if (node == startContainer) {
+ int32_t offset =
+ startContainer == endContainer
+ ? *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets)
+ : content->AsText()->TextDataLength();
+ nsLayoutUtils::GetFontFacesForText(
+ frame, *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ offset, true, aResult, fontFaces, aMaxRanges,
+ aSkipCollapsedWhitespace);
+ continue;
+ }
+ if (node == endContainer) {
+ nsLayoutUtils::GetFontFacesForText(
+ frame, 0, *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
+ true, aResult, fontFaces, aMaxRanges, aSkipCollapsedWhitespace);
+ continue;
+ }
+ }
+
+ nsLayoutUtils::GetFontFacesForFrames(frame, aResult, fontFaces, aMaxRanges,
+ aSkipCollapsedWhitespace);
+ }
+
+ return NS_OK;
+}
+
+nsINode* nsRange::GetRegisteredClosestCommonInclusiveAncestor() {
+ MOZ_ASSERT(IsInAnySelection(),
+ "GetRegisteredClosestCommonInclusiveAncestor only valid for range "
+ "in selection");
+ MOZ_ASSERT(mRegisteredClosestCommonInclusiveAncestor);
+ return mRegisteredClosestCommonInclusiveAncestor;
+}
+
+/* static */
+bool nsRange::AutoInvalidateSelection::sIsNested;
+
+nsRange::AutoInvalidateSelection::~AutoInvalidateSelection() {
+ if (!mCommonAncestor) {
+ return;
+ }
+ sIsNested = false;
+ ::InvalidateAllFrames(mCommonAncestor);
+
+ // Our range might not be in a selection anymore, because one of our selection
+ // listeners might have gone ahead and run script of various sorts that messed
+ // with selections, ranges, etc. But if it still is, we should check whether
+ // we have a different common ancestor now, and if so invalidate its subtree
+ // so it paints the selection it's in now.
+ if (mRange->IsInAnySelection()) {
+ nsINode* commonAncestor =
+ mRange->GetRegisteredClosestCommonInclusiveAncestor();
+ // XXXbz can commonAncestor really be null here? I wouldn't think so! If
+ // it _were_, then in a debug build
+ // GetRegisteredClosestCommonInclusiveAncestor() would have fatally
+ // asserted.
+ if (commonAncestor && commonAncestor != mCommonAncestor) {
+ ::InvalidateAllFrames(commonAncestor);
+ }
+ }
+}
+
+/* static */
+already_AddRefed<nsRange> nsRange::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window || !window->GetDoc()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return window->GetDoc()->CreateRange(aRv);
+}
+
+static bool ExcludeIfNextToNonSelectable(nsIContent* aContent) {
+ return aContent->IsText() &&
+ aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE);
+}
+
+void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) {
+ if (!mIsPositioned) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ MOZ_ASSERT(mEnd.Container());
+ MOZ_ASSERT(mStart.Container());
+
+ nsRange* range = this;
+ RefPtr<nsRange> newRange;
+ while (range) {
+ PreContentIterator preOrderIter;
+ nsresult rv = preOrderIter.Init(range);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool added = false;
+ bool seenSelectable = false;
+ // |firstNonSelectableContent| is the first node in a consecutive sequence
+ // of non-IsSelectable nodes. When we find a selectable node after such
+ // a sequence we'll end the last nsRange, create a new one and restart
+ // the outer loop.
+ nsIContent* firstNonSelectableContent = nullptr;
+ while (true) {
+ nsINode* node = preOrderIter.GetCurrentNode();
+ preOrderIter.Next();
+ bool selectable = true;
+ nsIContent* content =
+ node && node->IsContent() ? node->AsContent() : nullptr;
+ if (content) {
+ if (firstNonSelectableContent &&
+ ExcludeIfNextToNonSelectable(content)) {
+ // Ignorable whitespace next to a sequence of non-selectable nodes
+ // counts as non-selectable (bug 1216001).
+ selectable = false;
+ }
+ if (selectable) {
+ nsIFrame* frame = content->GetPrimaryFrame();
+ for (nsIContent* p = content; !frame && (p = p->GetParent());) {
+ frame = p->GetPrimaryFrame();
+ }
+ if (frame) {
+ selectable = frame->IsSelectable(nullptr);
+ }
+ }
+ }
+
+ if (!selectable) {
+ if (!firstNonSelectableContent) {
+ firstNonSelectableContent = content;
+ }
+ if (preOrderIter.IsDone()) {
+ if (seenSelectable) {
+ // The tail end of the initial range is non-selectable - truncate
+ // the current range before the first non-selectable node.
+ range->SetEndBefore(*firstNonSelectableContent, IgnoreErrors());
+ }
+ return;
+ }
+ continue;
+ }
+
+ if (firstNonSelectableContent) {
+ if (range == this && !seenSelectable) {
+ // This is the initial range and all its nodes until now are
+ // non-selectable so just trim them from the start.
+ IgnoredErrorResult err;
+ range->SetStartBefore(*node, err);
+ if (err.Failed()) {
+ return;
+ }
+ break; // restart the same range with a new iterator
+ }
+
+ // Save the end point before truncating the range.
+ nsINode* endContainer = range->mEnd.Container();
+ const uint32_t endOffset =
+ *range->mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
+
+ // Truncate the current range before the first non-selectable node.
+ IgnoredErrorResult err;
+ range->SetEndBefore(*firstNonSelectableContent, err);
+
+ // Store it in the result (strong ref) - do this before creating
+ // a new range in |newRange| below so we don't drop the last ref
+ // to the range created in the previous iteration.
+ if (!added && !err.Failed()) {
+ aOutRanges->AppendElement(range);
+ }
+
+ // Create a new range for the remainder.
+ nsINode* startContainer = node;
+ Maybe<uint32_t> startOffset = Some(0);
+ // Don't start *inside* a node with independent selection though
+ // (e.g. <input>).
+ if (content && content->HasIndependentSelection()) {
+ nsINode* parent = startContainer->GetParent();
+ if (parent) {
+ startOffset = parent->ComputeIndexOf(startContainer);
+ startContainer = parent;
+ }
+ }
+ newRange =
+ nsRange::Create(startContainer, startOffset.valueOr(UINT32_MAX),
+ endContainer, endOffset, IgnoreErrors());
+ if (!newRange || newRange->Collapsed()) {
+ newRange = nullptr;
+ }
+ range = newRange;
+ break; // create a new iterator for the new range, if any
+ }
+
+ seenSelectable = true;
+ if (!added) {
+ added = true;
+ aOutRanges->AppendElement(range);
+ }
+ if (preOrderIter.IsDone()) {
+ return;
+ }
+ }
+ }
+}
+
+struct InnerTextAccumulator {
+ explicit InnerTextAccumulator(mozilla::dom::DOMString& aValue)
+ : mString(aValue.AsAString()), mRequiredLineBreakCount(0) {}
+ void FlushLineBreaks() {
+ while (mRequiredLineBreakCount > 0) {
+ // Required line breaks at the start of the text are suppressed.
+ if (!mString.IsEmpty()) {
+ mString.Append('\n');
+ }
+ --mRequiredLineBreakCount;
+ }
+ }
+ void Append(char aCh) { Append(nsAutoString(aCh)); }
+ void Append(const nsAString& aString) {
+ if (aString.IsEmpty()) {
+ return;
+ }
+ FlushLineBreaks();
+ mString.Append(aString);
+ }
+ void AddRequiredLineBreakCount(int8_t aCount) {
+ mRequiredLineBreakCount = std::max(mRequiredLineBreakCount, aCount);
+ }
+
+ nsAString& mString;
+ int8_t mRequiredLineBreakCount;
+};
+
+static bool IsVisibleAndNotInReplacedElement(nsIFrame* aFrame) {
+ if (!aFrame || !aFrame->StyleVisibility()->IsVisible() ||
+ aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ return false;
+ }
+ if (aFrame->HidesContent()) {
+ return false;
+ }
+ for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
+ if (f->HidesContent()) {
+ return false;
+ }
+ if (f->IsFrameOfType(nsIFrame::eReplaced) &&
+ !f->GetContent()->IsAnyOfHTMLElements(nsGkAtoms::button,
+ nsGkAtoms::select) &&
+ !f->GetContent()->IsSVGElement()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void AppendTransformedText(InnerTextAccumulator& aResult,
+ nsIContent* aContainer) {
+ auto textNode = static_cast<CharacterData*>(aContainer);
+
+ nsIFrame* frame = textNode->GetPrimaryFrame();
+ if (!IsVisibleAndNotInReplacedElement(frame)) {
+ return;
+ }
+
+ nsIFrame::RenderedText text =
+ frame->GetRenderedText(0, aContainer->GetChildCount());
+ aResult.Append(text.mString);
+}
+
+/**
+ * States for tree traversal. AT_NODE means that we are about to enter
+ * the current DOM node. AFTER_NODE means that we have just finished traversing
+ * the children of the current DOM node and are about to apply any
+ * "after processing the node's children" steps before we finish visiting
+ * the node.
+ */
+enum TreeTraversalState { AT_NODE, AFTER_NODE };
+
+static int8_t GetRequiredInnerTextLineBreakCount(nsIFrame* aFrame) {
+ if (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::p)) {
+ return 2;
+ }
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ if (styleDisplay->IsBlockOutside(aFrame) ||
+ styleDisplay->mDisplay == StyleDisplay::TableCaption) {
+ return 1;
+ }
+ return 0;
+}
+
+static bool IsLastCellOfRow(nsIFrame* aFrame) {
+ LayoutFrameType type = aFrame->Type();
+ if (type != LayoutFrameType::TableCell) {
+ return true;
+ }
+ for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
+ if (c->GetNextSibling()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool IsLastRowOfRowGroup(nsIFrame* aFrame) {
+ if (!aFrame->IsTableRowFrame()) {
+ return true;
+ }
+ for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
+ if (c->GetNextSibling()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool IsLastNonemptyRowGroupOfTable(nsIFrame* aFrame) {
+ if (!aFrame->IsTableRowGroupFrame()) {
+ return true;
+ }
+ for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
+ for (nsIFrame* next = c->GetNextSibling(); next;
+ next = next->GetNextSibling()) {
+ if (next->PrincipalChildList().FirstChild()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void nsRange::GetInnerTextNoFlush(DOMString& aValue, ErrorResult& aError,
+ nsIContent* aContainer) {
+ InnerTextAccumulator result(aValue);
+
+ if (aContainer->IsText()) {
+ AppendTransformedText(result, aContainer);
+ return;
+ }
+
+ nsIContent* currentNode = aContainer;
+ TreeTraversalState currentState = AFTER_NODE;
+
+ nsIContent* endNode = aContainer;
+ TreeTraversalState endState = AFTER_NODE;
+
+ nsIContent* firstChild = aContainer->GetFirstChild();
+ if (firstChild) {
+ currentNode = firstChild;
+ currentState = AT_NODE;
+ }
+
+ while (currentNode != endNode || currentState != endState) {
+ nsIFrame* f = currentNode->GetPrimaryFrame();
+ bool isVisibleAndNotReplaced = IsVisibleAndNotInReplacedElement(f);
+ if (currentState == AT_NODE) {
+ bool isText = currentNode->IsText();
+ if (isVisibleAndNotReplaced) {
+ result.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f));
+ if (isText) {
+ nsIFrame::RenderedText text = f->GetRenderedText();
+ result.Append(text.mString);
+ }
+ }
+ nsIContent* child = currentNode->GetFirstChild();
+ if (child) {
+ currentNode = child;
+ continue;
+ }
+ currentState = AFTER_NODE;
+ }
+ if (currentNode == endNode && currentState == endState) {
+ break;
+ }
+ if (isVisibleAndNotReplaced) {
+ if (currentNode->IsHTMLElement(nsGkAtoms::br)) {
+ result.Append('\n');
+ }
+ switch (f->StyleDisplay()->mDisplay) {
+ case StyleDisplay::TableCell:
+ if (!IsLastCellOfRow(f)) {
+ result.Append('\t');
+ }
+ break;
+ case StyleDisplay::TableRow:
+ if (!IsLastRowOfRowGroup(f) ||
+ !IsLastNonemptyRowGroupOfTable(f->GetParent())) {
+ result.Append('\n');
+ }
+ break;
+ default:
+ break; // Do nothing
+ }
+ result.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f));
+ }
+ nsIContent* next = currentNode->GetNextSibling();
+ if (next) {
+ currentNode = next;
+ currentState = AT_NODE;
+ } else {
+ currentNode = currentNode->GetParent();
+ }
+ }
+
+ // Do not flush trailing line breaks! Required breaks at the end of the text
+ // are suppressed.
+}
diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h
new file mode 100644
index 0000000000..97756d3afc
--- /dev/null
+++ b/dom/base/nsRange.h
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of the DOM Range object.
+ */
+
+#ifndef nsRange_h___
+#define nsRange_h___
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/AbstractRange.h"
+#include "prmon.h"
+#include "nsStubMutationObserver.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+class RectCallback;
+namespace dom {
+struct ClientRectsAndTexts;
+class DocGroup;
+class DocumentFragment;
+class DOMRect;
+class DOMRectList;
+class InspectorFontFace;
+class Selection;
+} // namespace dom
+} // namespace mozilla
+
+class nsRange final : public mozilla::dom::AbstractRange,
+ public nsStubMutationObserver {
+ using ErrorResult = mozilla::ErrorResult;
+ using AbstractRange = mozilla::dom::AbstractRange;
+ using DocGroup = mozilla::dom::DocGroup;
+ using DOMRect = mozilla::dom::DOMRect;
+ using DOMRectList = mozilla::dom::DOMRectList;
+ using RangeBoundary = mozilla::RangeBoundary;
+ using RawRangeBoundary = mozilla::RawRangeBoundary;
+
+ virtual ~nsRange();
+ explicit nsRange(nsINode* aNode);
+
+ public:
+ /**
+ * The following Create() returns `nsRange` instance which is initialized
+ * only with aNode. The result is never positioned.
+ */
+ static already_AddRefed<nsRange> Create(nsINode* aNode);
+
+ /**
+ * The following Create() may return `nsRange` instance which is initialized
+ * with given range or points. If it fails initializing new range with the
+ * arguments, returns `nullptr`. `ErrorResult` is set to an error only
+ * when this returns `nullptr`. The error code indicates the reason why
+ * it couldn't initialize the instance.
+ */
+ static already_AddRefed<nsRange> Create(const AbstractRange* aAbstractRange,
+ ErrorResult& aRv) {
+ return nsRange::Create(aAbstractRange->StartRef(), aAbstractRange->EndRef(),
+ aRv);
+ }
+ static already_AddRefed<nsRange> Create(nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ return nsRange::Create(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset), aRv);
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static already_AddRefed<nsRange> Create(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange, AbstractRange)
+
+ nsrefcnt GetRefCount() const { return mRefCnt; }
+
+ nsINode* GetRoot() const { return mRoot; }
+
+ /**
+ * Return true if this range was generated.
+ * @see SetIsGenerated
+ */
+ bool IsGenerated() const { return mIsGenerated; }
+
+ /**
+ * Mark this range as being generated or not.
+ * Currently it is used for marking ranges that are created when splitting up
+ * a range to exclude a -moz-user-select:none region.
+ * @see Selection::AddRangesForSelectableNodes
+ * @see ExcludeNonSelectableNodes
+ */
+ void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; }
+
+ void Reset();
+
+ /**
+ * SetStart() and SetEnd() sets start point or end point separately.
+ * However, this is expensive especially when it's a range of Selection.
+ * When you set both start and end of a range, you should use
+ * SetStartAndEnd() instead.
+ */
+ nsresult SetStart(nsINode* aContainer, uint32_t aOffset) {
+ ErrorResult error;
+ SetStart(RawRangeBoundary(aContainer, aOffset), error);
+ return error.StealNSResult();
+ }
+ nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) {
+ ErrorResult error;
+ SetEnd(RawRangeBoundary(aContainer, aOffset), error);
+ return error.StealNSResult();
+ }
+
+ already_AddRefed<nsRange> CloneRange() const;
+
+ /**
+ * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
+ * Different from calls them separately, this does nothing if either
+ * the start point or the end point is invalid point.
+ * If the specified start point is after the end point, the range will be
+ * collapsed at the end point. Similarly, if they are in different root,
+ * the range will be collapsed at the end point.
+ */
+ nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) {
+ return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ nsresult SetStartAndEnd(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ return AbstractRange::SetStartAndEndInternal(aStartBoundary, aEndBoundary,
+ this);
+ }
+
+ /**
+ * Adds all nodes between |aStartContent| and |aEndContent| to the range.
+ * The start offset will be set before |aStartContent|,
+ * while the end offset will be set immediately after |aEndContent|.
+ *
+ * Caller must guarantee both nodes are non null and
+ * children of |aContainer| and that |aEndContent| is after |aStartContent|.
+ */
+ void SelectNodesInContainer(nsINode* aContainer, nsIContent* aStartContent,
+ nsIContent* aEndContent);
+
+ /**
+ * CollapseTo() works similar to call both SetStart() and SetEnd() with
+ * same node and offset. This just calls SetStartAndParent() to set
+ * collapsed range at aContainer and aOffset.
+ */
+ nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset) {
+ return CollapseTo(RawRangeBoundary(aContainer, aOffset));
+ }
+ nsresult CollapseTo(const RawRangeBoundary& aPoint) {
+ return SetStartAndEnd(aPoint, aPoint);
+ }
+
+ // aMaxRanges is the maximum number of text ranges to record for each face
+ // (pass 0 to just get the list of faces, without recording exact ranges
+ // where each face was used).
+ nsresult GetUsedFontFaces(
+ nsTArray<mozilla::UniquePtr<mozilla::dom::InspectorFontFace>>& aResult,
+ uint32_t aMaxRanges, bool aSkipCollapsedWhitespace);
+
+ // nsIMutationObserver methods
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+
+ // WebIDL
+ static already_AddRefed<nsRange> Constructor(
+ const mozilla::dom::GlobalObject& global, mozilla::ErrorResult& aRv);
+
+ already_AddRefed<mozilla::dom::DocumentFragment> CreateContextualFragment(
+ const nsAString& aString, ErrorResult& aError) const;
+ already_AddRefed<mozilla::dom::DocumentFragment> CloneContents(
+ ErrorResult& aErr);
+ int16_t CompareBoundaryPoints(uint16_t aHow, const nsRange& aOtherRange,
+ ErrorResult& aRv);
+ int16_t ComparePoint(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const;
+ void DeleteContents(ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
+ ErrorResult& aErr);
+ nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const {
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ return GetClosestCommonInclusiveAncestor();
+ }
+ void InsertNode(nsINode& aNode, ErrorResult& aErr);
+ bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
+ bool IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const;
+ void ToString(nsAString& aReturn, ErrorResult& aErr);
+ void Detach();
+
+ // *JS() methods are mapped to Range.*() of DOM.
+ // They may move focus only when the range represents normal selection.
+ // These methods shouldn't be used from internal.
+ void CollapseJS(bool aToStart);
+ void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
+ void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
+ void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
+ void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
+ void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
+ void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
+
+ void SurroundContents(nsINode& aNode, ErrorResult& aErr);
+ already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
+ bool aFlushLayout = true);
+ already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
+ bool aFlushLayout = true);
+ void GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts& aResult,
+ ErrorResult& aErr);
+
+ // Following methods should be used for internal use instead of *JS().
+ void SelectNode(nsINode& aNode, ErrorResult& aErr);
+ void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
+ void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr);
+ void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
+ void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr);
+ void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
+ void Collapse(bool aToStart);
+
+ static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
+ mozilla::ErrorResult& aError,
+ nsIContent* aContainer);
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) final;
+ DocGroup* GetDocGroup() const;
+
+ private:
+ // no copy's or assigns
+ nsRange(const nsRange&);
+ nsRange& operator=(const nsRange&);
+
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static void AssertIfMismatchRootAndRangeBoundaries(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ const nsINode* aRootNode, bool aNotInsertedYet = false);
+
+ /**
+ * Cut or delete the range's contents.
+ *
+ * @param aFragment DocumentFragment containing the nodes.
+ * May be null to indicate the caller doesn't want a
+ * fragment.
+ * @param aRv The error if any.
+ */
+ void CutContents(mozilla::dom::DocumentFragment** aFragment,
+ ErrorResult& aRv);
+
+ static nsresult CloneParentsBetween(nsINode* aAncestor, nsINode* aNode,
+ nsINode** aClosestAncestor,
+ nsINode** aFarthestAncestor);
+
+ /**
+ * Returns whether a node is safe to be accessed by the current caller.
+ */
+ bool CanAccess(const nsINode&) const;
+
+ void AdjustNextRefsOnCharacterDataSplit(const nsIContent& aContent,
+ const CharacterDataChangeInfo& aInfo);
+
+ struct RangeBoundariesAndRoot {
+ RawRangeBoundary mStart;
+ RawRangeBoundary mEnd;
+ nsINode* mRoot = nullptr;
+ };
+
+ /**
+ * @param aContent Must be non-nullptr.
+ */
+ RangeBoundariesAndRoot DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const;
+
+ // @return true iff the range is positioned, aContainer belongs to the same
+ // document as the range, aContainer is a DOCUMENT_TYPE_NODE and
+ // aOffset doesn't exceed aContainer's length.
+ bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aErrorResult) const;
+
+ /**
+ * @brief Returns true if the range is part of exactly one |Selection|.
+ */
+ bool IsPartOfOneSelectionOnly() const { return mSelections.Length() == 1; };
+
+ public:
+ /**
+ * This helper function gets rects and correlated text for the given range.
+ * @param aTextList optional where nullptr = don't retrieve text
+ */
+ static void CollectClientRectsAndText(
+ mozilla::RectCallback* aCollector,
+ mozilla::dom::Sequence<nsString>* aTextList, nsRange* aRange,
+ nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
+ uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout);
+
+ /**
+ * Scan this range for -moz-user-select:none nodes and split it up into
+ * multiple ranges to exclude those nodes. The resulting ranges are put
+ * in aOutRanges. If no -moz-user-select:none node is found in the range
+ * then |this| is unmodified and is the only range in aOutRanges.
+ * Otherwise, |this| will be modified so that it ends before the first
+ * -moz-user-select:none node and additional ranges may also be created.
+ * If all nodes in the range are -moz-user-select:none then aOutRanges
+ * will be empty.
+ * @param aOutRanges the resulting set of ranges
+ */
+ void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges);
+
+ /**
+ * Notify the selection listeners after a range has been modified.
+ */
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListenersAfterRangeSet();
+
+ /**
+ * For a range for which IsInSelection() is true, return the closest common
+ * inclusive ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor)
+ * for the range, which we had to compute when the common ancestor changed or
+ * IsInSelection became true, so we could register with it. That is, it's a
+ * faster version of GetClosestCommonInclusiveAncestor that only works for
+ * ranges in a Selection. The method will assert and the behavior is undefined
+ * if called on a range where IsInSelection() is false.
+ */
+ nsINode* GetRegisteredClosestCommonInclusiveAncestor();
+
+ protected:
+ /**
+ * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
+ * mStart and mEnd, or some other internal methods modify `mStart` and/or
+ * `mEnd`. Therefore, this shouldn't be a virtual method.
+ *
+ * @param aStartBoundary Computed start point. This must equals or be
+ * before aEndBoundary in the DOM tree order.
+ * @param aEndBoundary Computed end point.
+ * @param aRootNode The root node.
+ * @param aNotInsertedYet true if this is called by CharacterDataChanged()
+ * to disable assertion and suppress re-registering
+ * a range common ancestor node since the new text
+ * node of a splitText hasn't been inserted yet.
+ * CharacterDataChanged() does the re-registering
+ * when needed. Otherwise, false.
+ */
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetRange(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet = false);
+
+ // Assume that this is guaranteed that this is held by the caller when
+ // this is used. (Note that we cannot use AutoRestore for mCalledByJS
+ // due to a bit field.)
+ class MOZ_RAII AutoCalledByJSRestore final {
+ private:
+ nsRange& mRange;
+ bool mOldValue;
+
+ public:
+ explicit AutoCalledByJSRestore(nsRange& aRange)
+ : mRange(aRange), mOldValue(aRange.mCalledByJS) {}
+ ~AutoCalledByJSRestore() { mRange.mCalledByJS = mOldValue; }
+ bool SavedValue() const { return mOldValue; }
+ };
+
+ struct MOZ_STACK_CLASS AutoInvalidateSelection {
+ explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) {
+ if (!mRange->IsInAnySelection() || sIsNested) {
+ return;
+ }
+ sIsNested = true;
+ mCommonAncestor = mRange->GetRegisteredClosestCommonInclusiveAncestor();
+ }
+ ~AutoInvalidateSelection();
+ nsRange* mRange;
+ RefPtr<nsINode> mCommonAncestor;
+ static bool sIsNested;
+ };
+
+ bool MaybeInterruptLastRelease();
+
+#ifdef DEBUG
+ bool IsCleared() const {
+ return !mRoot && !mRegisteredClosestCommonInclusiveAncestor &&
+ mSelections.IsEmpty() && !mNextStartRef && !mNextEndRef;
+ }
+#endif // #ifdef DEBUG
+
+ nsCOMPtr<nsINode> mRoot;
+
+ // These raw pointers are used to remember a child that is about
+ // to be inserted between a CharacterData call and a subsequent
+ // ContentInserted or ContentAppended call. It is safe to store
+ // these refs because the caller is guaranteed to trigger both
+ // notifications while holding a strong reference to the new child.
+ nsIContent* MOZ_NON_OWNING_REF mNextStartRef;
+ nsIContent* MOZ_NON_OWNING_REF mNextEndRef;
+
+ static nsTArray<RefPtr<nsRange>>* sCachedRanges;
+
+ friend class mozilla::dom::AbstractRange;
+};
+namespace mozilla::dom {
+inline nsRange* AbstractRange::AsDynamicRange() {
+ MOZ_ASSERT(IsDynamicRange());
+ return static_cast<nsRange*>(this);
+}
+inline const nsRange* AbstractRange::AsDynamicRange() const {
+ MOZ_ASSERT(IsDynamicRange());
+ return static_cast<const nsRange*>(this);
+}
+} // namespace mozilla::dom
+#endif /* nsRange_h___ */
diff --git a/dom/base/nsSandboxFlags.h b/dom/base/nsSandboxFlags.h
new file mode 100644
index 0000000000..789a9f6dd5
--- /dev/null
+++ b/dom/base/nsSandboxFlags.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Constant flags that describe how a document is sandboxed according to the
+ * HTML5 spec.
+ */
+
+#ifndef nsSandboxFlags_h___
+#define nsSandboxFlags_h___
+
+/**
+ * This constant denotes the lack of a sandbox attribute/directive.
+ */
+const unsigned long SANDBOXED_NONE = 0x0;
+
+/**
+ * This flag prevents content from navigating browsing contexts other than
+ * itself, browsing contexts nested inside it, the top-level browsing context
+ * and browsing contexts that it has opened.
+ * As it is always on for sandboxed browsing contexts, it is used implicitly
+ * within the code by checking that the overall flags are non-zero.
+ * It is only uesd directly when the sandbox flags are initially set up.
+ */
+const unsigned long SANDBOXED_NAVIGATION = 0x1;
+
+/**
+ * This flag prevents content from creating new auxiliary browsing contexts,
+ * e.g. using the target attribute, or the window.open() method.
+ */
+const unsigned long SANDBOXED_AUXILIARY_NAVIGATION = 0x2;
+
+/**
+ * This flag prevents content from navigating their top-level browsing
+ * context.
+ */
+const unsigned long SANDBOXED_TOPLEVEL_NAVIGATION = 0x4;
+
+/**
+ * This flag prevents content from instantiating plugins, whether using the
+ * embed element, the object element, or through navigation of a nested browsing
+ * context, unless those plugins can be secured.
+ */
+const unsigned long SANDBOXED_PLUGINS = 0x8;
+
+/**
+ * This flag forces content into a unique origin, thus preventing it from
+ * accessing other content from the same origin.
+ * This flag also prevents script from reading from or writing to the
+ * document.cookie IDL attribute, and blocks access to localStorage.
+ */
+const unsigned long SANDBOXED_ORIGIN = 0x10;
+
+/**
+ * This flag blocks form submission.
+ */
+const unsigned long SANDBOXED_FORMS = 0x20;
+
+/**
+ * This flag blocks the document from acquiring pointerlock.
+ */
+const unsigned long SANDBOXED_POINTER_LOCK = 0x40;
+
+/**
+ * This flag blocks script execution.
+ */
+const unsigned long SANDBOXED_SCRIPTS = 0x80;
+
+/**
+ * This flag blocks features that trigger automatically, such as
+ * automatically playing a video or automatically focusing a form control.
+ */
+const unsigned long SANDBOXED_AUTOMATIC_FEATURES = 0x100;
+
+/**
+ * This flag prevents URL schemes that use storage areas from being able to
+ * access the origin's data.
+ */
+// We don't have an explicit representation of this one, apparently?
+// const unsigned long SANDBOXED_STORAGE_AREA_URLS = 0x200;
+
+/**
+ * This flag blocks the document from changing document.domain.
+ */
+const unsigned long SANDBOXED_DOMAIN = 0x400;
+
+/**
+ * This flag prevents content from using window.alert(), window.confirm(),
+ * window.print(), window.prompt() and the beforeunload event from putting up
+ * dialogs.
+ */
+const unsigned long SANDBOXED_MODALS = 0x800;
+
+/**
+ * This flag prevents content from escaping the sandbox by ensuring that any
+ * auxiliary browsing context it creates inherits the content's active
+ * sandboxing flag set.
+ */
+const unsigned long SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS = 0x1000;
+
+/**
+ * This flag prevents locking screen orientation.
+ */
+const unsigned long SANDBOXED_ORIENTATION_LOCK = 0x2000;
+
+/**
+ * This flag disables the Presentation API.
+ */
+const unsigned long SANDBOXED_PRESENTATION = 0x4000;
+
+/**
+ * This flag disables access to the first-party storage area by user activation.
+ */
+const unsigned long SANDBOXED_STORAGE_ACCESS = 0x8000;
+
+/**
+ * This flag prevents content from navigating their top-level browsing
+ * context only when the user hasn't interacted with the browser.
+ */
+const unsigned long SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION = 0x20000;
+
+/**
+ * This flag disables content from initiating or instantiating downloads,
+ * whether through downloading hyperlinks or through navigation that gets
+ * handled as a download.
+ */
+const unsigned long SANDBOXED_ALLOW_DOWNLOADS = 0x10000;
+
+/**
+ * This flag prevents content from navigating to custom protocols.
+ */
+const unsigned long SANDBOXED_TOPLEVEL_NAVIGATION_CUSTOM_PROTOCOLS = 0x40000;
+
+const unsigned long SANDBOX_ALL_FLAGS = 0xFFFFF;
+#endif
diff --git a/dom/base/nsScreen.cpp b/dom/base/nsScreen.cpp
new file mode 100644
index 0000000000..b4ac142f35
--- /dev/null
+++ b/dom/base/nsScreen.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentUtils.h"
+#include "nsScreen.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocShell.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsLayoutUtils.h"
+#include "nsJSUtils.h"
+#include "nsDeviceContext.h"
+#include "mozilla/widget/ScreenManager.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* static */
+already_AddRefed<nsScreen> nsScreen::Create(nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ if (!aWindow->GetDocShell()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
+ NS_ENSURE_TRUE(sgo, nullptr);
+
+ RefPtr<nsScreen> screen = new nsScreen(aWindow);
+ return screen.forget();
+}
+
+nsScreen::nsScreen(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow),
+ mScreenOrientation(new ScreenOrientation(aWindow, this)) {}
+
+nsScreen::~nsScreen() = default;
+
+// QueryInterface implementation for nsScreen
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScreen)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(nsScreen, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(nsScreen, DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsScreen, DOMEventTargetHelper,
+ mScreenOrientation)
+
+int32_t nsScreen::GetPixelDepth(ErrorResult& aRv) {
+ // Return 24 to prevent fingerprinting.
+ if (ShouldResistFingerprinting()) {
+ return 24;
+ }
+
+ nsDeviceContext* context = GetDeviceContext();
+
+ if (!context) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return -1;
+ }
+
+ return context->GetDepth();
+}
+
+nsPIDOMWindowOuter* nsScreen::GetOuter() const {
+ if (nsPIDOMWindowInner* inner = GetOwner()) {
+ return inner->GetOuterWindow();
+ }
+
+ return nullptr;
+}
+
+nsDeviceContext* nsScreen::GetDeviceContext() const {
+ return nsLayoutUtils::GetDeviceContextForScreenInfo(GetOuter());
+}
+
+nsresult nsScreen::GetRect(CSSIntRect& aRect) {
+ // Return window inner rect to prevent fingerprinting.
+ if (ShouldResistFingerprinting()) {
+ return GetWindowInnerRect(aRect);
+ }
+
+ // Here we manipulate the value of aRect to represent the screen size,
+ // if in RDM.
+ if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+ if (Document* doc = owner->GetExtantDoc()) {
+ Maybe<CSSIntSize> deviceSize =
+ nsGlobalWindowOuter::GetRDMDeviceSize(*doc);
+ if (deviceSize.isSome()) {
+ const CSSIntSize& size = deviceSize.value();
+ aRect.SetRect(0, 0, size.width, size.height);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsDeviceContext* context = GetDeviceContext();
+
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsRect r;
+ context->GetRect(r);
+ aRect = CSSIntRect::FromAppUnitsRounded(r);
+ return NS_OK;
+}
+
+nsresult nsScreen::GetAvailRect(CSSIntRect& aRect) {
+ // Return window inner rect to prevent fingerprinting.
+ if (ShouldResistFingerprinting()) {
+ return GetWindowInnerRect(aRect);
+ }
+
+ // Here we manipulate the value of aRect to represent the screen size,
+ // if in RDM.
+ if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+ if (Document* doc = owner->GetExtantDoc()) {
+ Maybe<CSSIntSize> deviceSize =
+ nsGlobalWindowOuter::GetRDMDeviceSize(*doc);
+ if (deviceSize.isSome()) {
+ const CSSIntSize& size = deviceSize.value();
+ aRect.SetRect(0, 0, size.width, size.height);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsDeviceContext* context = GetDeviceContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsRect r;
+ context->GetClientRect(r);
+ aRect = CSSIntRect::FromAppUnitsRounded(r);
+ return NS_OK;
+}
+
+uint16_t nsScreen::GetOrientationAngle() const {
+ nsDeviceContext* context = GetDeviceContext();
+ if (context) {
+ return context->GetScreenOrientationAngle();
+ }
+ RefPtr<widget::Screen> s =
+ widget::ScreenManager::GetSingleton().GetPrimaryScreen();
+ return s->GetOrientationAngle();
+}
+
+hal::ScreenOrientation nsScreen::GetOrientationType() const {
+ nsDeviceContext* context = GetDeviceContext();
+ if (context) {
+ return context->GetScreenOrientationType();
+ }
+ RefPtr<widget::Screen> s =
+ widget::ScreenManager::GetSingleton().GetPrimaryScreen();
+ return s->GetOrientationType();
+}
+
+ScreenOrientation* nsScreen::Orientation() const { return mScreenOrientation; }
+
+void nsScreen::GetMozOrientation(nsString& aOrientation,
+ CallerType aCallerType) const {
+ switch (mScreenOrientation->DeviceType(aCallerType)) {
+ case OrientationType::Portrait_primary:
+ aOrientation.AssignLiteral("portrait-primary");
+ break;
+ case OrientationType::Portrait_secondary:
+ aOrientation.AssignLiteral("portrait-secondary");
+ break;
+ case OrientationType::Landscape_primary:
+ aOrientation.AssignLiteral("landscape-primary");
+ break;
+ case OrientationType::Landscape_secondary:
+ aOrientation.AssignLiteral("landscape-secondary");
+ break;
+ default:
+ MOZ_CRASH("Unacceptable screen orientation type.");
+ }
+}
+
+bool nsScreen::MozLockOrientation(const nsAString& aOrientation,
+ ErrorResult& aRv) {
+ nsString orientation(aOrientation);
+ Sequence<nsString> orientations;
+ if (!orientations.AppendElement(orientation, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ return MozLockOrientation(orientations, aRv);
+}
+
+// This function is deprecated, use ScreenOrientation API instead.
+bool nsScreen::MozLockOrientation(const Sequence<nsString>& aOrientations,
+ ErrorResult& aRv) {
+ return false;
+}
+
+void nsScreen::MozUnlockOrientation() {}
+
+/* virtual */
+JSObject* nsScreen::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Screen_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult nsScreen::GetWindowInnerRect(CSSIntRect& aRect) {
+ aRect.x = 0;
+ aRect.y = 0;
+ nsCOMPtr<nsPIDOMWindowInner> win = GetOwner();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+ double width;
+ double height;
+ nsresult rv = win->GetInnerWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = win->GetInnerHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aRect.SizeTo(std::round(width), std::round(height));
+ return NS_OK;
+}
+
+bool nsScreen::ShouldResistFingerprinting() const {
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ return owner &&
+ nsGlobalWindowInner::Cast(owner)->ShouldResistFingerprinting();
+}
diff --git a/dom/base/nsScreen.h b/dom/base/nsScreen.h
new file mode 100644
index 0000000000..733a9942cd
--- /dev/null
+++ b/dom/base/nsScreen.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsScreen_h___
+#define nsScreen_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/ScreenBinding.h"
+#include "mozilla/dom/ScreenLuminance.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsCOMPtr.h"
+#include "nsGlobalWindowOuter.h"
+#include "mozilla/gfx/Rect.h"
+#include "Units.h"
+
+class nsDeviceContext;
+
+// Script "screen" object
+class nsScreen : public mozilla::DOMEventTargetHelper {
+ using ErrorResult = mozilla::ErrorResult;
+
+ public:
+ static already_AddRefed<nsScreen> Create(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsScreen,
+ mozilla::DOMEventTargetHelper)
+
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ nsPIDOMWindowOuter* GetOuter() const;
+
+ int32_t GetTop(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetRect(rect);
+ return rect.y;
+ }
+
+ int32_t GetLeft(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetRect(rect);
+ return rect.x;
+ }
+
+ int32_t GetWidth(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetRect(rect);
+ return rect.Width();
+ }
+
+ int32_t GetHeight(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetRect(rect);
+ return rect.Height();
+ }
+
+ int32_t GetPixelDepth(ErrorResult& aRv);
+ int32_t GetColorDepth(ErrorResult& aRv) { return GetPixelDepth(aRv); }
+
+ int32_t GetAvailTop(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.y;
+ }
+
+ int32_t GetAvailLeft(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.x;
+ }
+
+ int32_t GetAvailWidth(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.Width();
+ }
+
+ int32_t GetAvailHeight(ErrorResult& aRv) {
+ mozilla::CSSIntRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.Height();
+ }
+
+ // Media Capabilities extension
+ mozilla::dom::ScreenColorGamut ColorGamut() const {
+ return mozilla::dom::ScreenColorGamut::Srgb;
+ }
+
+ already_AddRefed<mozilla::dom::ScreenLuminance> GetLuminance() const {
+ return nullptr;
+ }
+
+ static bool MediaCapabilitiesEnabled(JSContext* aCx, JSObject* aGlobal) {
+ return mozilla::StaticPrefs::media_media_capabilities_screen_enabled();
+ }
+
+ IMPL_EVENT_HANDLER(change);
+
+ uint16_t GetOrientationAngle() const;
+ mozilla::hal::ScreenOrientation GetOrientationType() const;
+
+ // Deprecated
+ void GetMozOrientation(nsString& aOrientation,
+ mozilla::dom::CallerType aCallerType) const;
+
+ IMPL_EVENT_HANDLER(mozorientationchange)
+
+ bool MozLockOrientation(const nsAString& aOrientation, ErrorResult& aRv);
+ bool MozLockOrientation(const mozilla::dom::Sequence<nsString>& aOrientations,
+ ErrorResult& aRv);
+ void MozUnlockOrientation();
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ mozilla::dom::ScreenOrientation* Orientation() const;
+ mozilla::dom::ScreenOrientation* GetOrientationIfExists() const {
+ return mScreenOrientation.get();
+ }
+
+ protected:
+ nsDeviceContext* GetDeviceContext() const;
+ nsresult GetRect(mozilla::CSSIntRect& aRect);
+ nsresult GetAvailRect(mozilla::CSSIntRect& aRect);
+ nsresult GetWindowInnerRect(mozilla::CSSIntRect& aRect);
+
+ private:
+ explicit nsScreen(nsPIDOMWindowInner* aWindow);
+ virtual ~nsScreen();
+
+ bool ShouldResistFingerprinting() const;
+
+ mozilla::dom::Document* TopContentDocumentInRDMPane() const;
+
+ RefPtr<mozilla::dom::ScreenOrientation> mScreenOrientation;
+};
+
+#endif /* nsScreen_h___ */
diff --git a/dom/base/nsStructuredCloneContainer.cpp b/dom/base/nsStructuredCloneContainer.cpp
new file mode 100644
index 0000000000..3a961f820d
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStructuredCloneContainer.h"
+
+#include <cstddef>
+#include <utility>
+#include "ErrorList.h"
+#include "js/RootingAPI.h"
+#include "js/StructuredClone.h"
+#include "js/Value.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/fallible.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIVariant.h"
+#include "nsIXPConnect.h"
+#include "nsString.h"
+#include "nscore.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ADDREF(nsStructuredCloneContainer)
+NS_IMPL_RELEASE(nsStructuredCloneContainer)
+
+NS_INTERFACE_MAP_BEGIN(nsStructuredCloneContainer)
+ NS_INTERFACE_MAP_ENTRY(nsIStructuredCloneContainer)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsStructuredCloneContainer::nsStructuredCloneContainer() : mVersion(0) {}
+nsStructuredCloneContainer::nsStructuredCloneContainer(uint32_t aVersion)
+ : mVersion(aVersion) {}
+
+nsStructuredCloneContainer::~nsStructuredCloneContainer() = default;
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::InitFromJSVal(JS::Handle<JS::Value> aData,
+ JSContext* aCx) {
+ if (DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult rv;
+ Write(aCx, aData, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ // XXX propagate the error message as well.
+ // We cannot StealNSResult because we threw a DOM exception.
+ rv.SuppressException();
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ mVersion = JS_STRUCTURED_CLONE_VERSION;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::InitFromBase64(const nsAString& aData,
+ uint32_t aFormatVersion) {
+ if (DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 data(aData);
+
+ nsAutoCString binaryData;
+ nsresult rv = Base64Decode(data, binaryData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!CopyExternalData(binaryData.get(), binaryData.Length())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mVersion = aFormatVersion;
+ return NS_OK;
+}
+
+nsresult nsStructuredCloneContainer::DeserializeToJsval(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aValue) {
+ aValue.setNull();
+ JS::Rooted<JS::Value> jsStateObj(aCx);
+
+ ErrorResult rv;
+ Read(aCx, &jsStateObj, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ // XXX propagate the error message as well.
+ // We cannot StealNSResult because we threw a DOM exception.
+ rv.SuppressException();
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ aValue.set(jsStateObj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::GetDataAsBase64(nsAString& aOut) {
+ aOut.Truncate();
+
+ if (!DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (HasClonedDOMObjects()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto iter = Data().Start();
+ size_t size = Data().Size();
+ CheckedInt<nsAutoCString::size_type> sizeCheck(size);
+ if (!sizeCheck.isValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString binaryData;
+ if (!binaryData.SetLength(size, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DebugOnly<bool> res = Data().ReadBytes(iter, binaryData.BeginWriting(), size);
+ MOZ_ASSERT(res);
+
+ nsresult rv = Base64Encode(binaryData, aOut);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::GetSerializedNBytes(uint64_t* aSize) {
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ if (!DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSize = DataLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::GetFormatVersion(uint32_t* aFormatVersion) {
+ NS_ENSURE_ARG_POINTER(aFormatVersion);
+
+ if (!DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aFormatVersion = mVersion;
+ return NS_OK;
+}
diff --git a/dom/base/nsStructuredCloneContainer.h b/dom/base/nsStructuredCloneContainer.h
new file mode 100644
index 0000000000..ae7146cbd6
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStructuredCloneContainer_h__
+#define nsStructuredCloneContainer_h__
+
+#include <cstdint>
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsISupports.h"
+
+class nsIVariant;
+
+#define NS_STRUCTUREDCLONECONTAINER_CONTRACTID \
+ "@mozilla.org/docshell/structured-clone-container;1"
+#define NS_STRUCTUREDCLONECONTAINER_CID \
+ { /* 38bd0634-0fd4-46f0-b85f-13ced889eeec */ \
+ 0x38bd0634, 0x0fd4, 0x46f0, { \
+ 0xb8, 0x5f, 0x13, 0xce, 0xd8, 0x89, 0xee, 0xec \
+ } \
+ }
+
+class nsStructuredCloneContainer final
+ : public nsIStructuredCloneContainer,
+ public mozilla::dom::ipc::StructuredCloneData {
+ public:
+ nsStructuredCloneContainer();
+ explicit nsStructuredCloneContainer(uint32_t aVersion);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTRUCTUREDCLONECONTAINER
+
+ private:
+ ~nsStructuredCloneContainer();
+
+ uint32_t mVersion;
+};
+
+#endif
diff --git a/dom/base/nsStubAnimationObserver.cpp b/dom/base/nsStubAnimationObserver.cpp
new file mode 100644
index 0000000000..3ded9b2d0a
--- /dev/null
+++ b/dom/base/nsStubAnimationObserver.cpp
@@ -0,0 +1,9 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStubAnimationObserver.h"
+
+NS_IMPL_NSIANIMATIONOBSERVER_STUB(nsStubAnimationObserver)
diff --git a/dom/base/nsStubAnimationObserver.h b/dom/base/nsStubAnimationObserver.h
new file mode 100644
index 0000000000..2c99194fa0
--- /dev/null
+++ b/dom/base/nsStubAnimationObserver.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStubAnimationObserver_h_
+#define nsStubAnimationObserver_h_
+
+#include "nsIAnimationObserver.h"
+
+class nsStubAnimationObserver : public nsIAnimationObserver {
+ public:
+ NS_DECL_NSIANIMATIONOBSERVER
+};
+
+#endif // !defined(nsStubAnimationObserver_h_)
diff --git a/dom/base/nsStubDocumentObserver.cpp b/dom/base/nsStubDocumentObserver.cpp
new file mode 100644
index 0000000000..36b8e1fe2e
--- /dev/null
+++ b/dom/base/nsStubDocumentObserver.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStubDocumentObserver is an implementation of the nsIDocumentObserver
+ * interface (except for the methods on nsISupports) that is intended to be
+ * used as a base class within the content/layout library. All methods do
+ * nothing.
+ */
+
+#include "nsStubDocumentObserver.h"
+
+NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(nsStubDocumentObserver)
+NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(nsStubDocumentObserver)
+NS_IMPL_NSIDOCUMENTOBSERVER_STATE_STUB(nsStubDocumentObserver)
+NS_IMPL_NSIDOCUMENTOBSERVER_CONTENT(nsStubDocumentObserver)
diff --git a/dom/base/nsStubDocumentObserver.h b/dom/base/nsStubDocumentObserver.h
new file mode 100644
index 0000000000..0b53279db5
--- /dev/null
+++ b/dom/base/nsStubDocumentObserver.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStubDocumentObserver is an implementation of the nsIDocumentObserver
+ * interface (except for the methods on nsISupports) that is intended to be
+ * used as a base class within the content/layout library. All methods do
+ * nothing.
+ */
+
+#ifndef nsStubDocumentObserver_h_
+#define nsStubDocumentObserver_h_
+
+#include "nsIDocumentObserver.h"
+
+/**
+ * There are two advantages to inheriting from nsStubDocumentObserver
+ * rather than directly from nsIDocumentObserver:
+ * 1. smaller compiled code size (since there's no need for the code
+ * for the empty virtual function implementations for every
+ * nsIDocumentObserver implementation)
+ * 2. the performance of document's loop over observers benefits from
+ * the fact that more of the functions called are the same (which
+ * can reduce instruction cache misses and perhaps improve branch
+ * prediction)
+ */
+class nsStubDocumentObserver : public nsIDocumentObserver {
+ public:
+ NS_DECL_NSIDOCUMENTOBSERVER
+};
+
+#endif /* !defined(nsStubDocumentObserver_h_) */
diff --git a/dom/base/nsStubMutationObserver.cpp b/dom/base/nsStubMutationObserver.cpp
new file mode 100644
index 0000000000..fcd8a8fdc5
--- /dev/null
+++ b/dom/base/nsStubMutationObserver.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStubMutationObserver is an implementation of the nsIMutationObserver
+ * interface (except for the methods on nsISupports) that is intended to be
+ * used as a base class within the content/layout library. All methods do
+ * nothing.
+ */
+
+#include "nsStubMutationObserver.h"
+#include "mozilla/RefCountType.h"
+#include "nsISupports.h"
+#include "nsINode.h"
+
+/******************************************************************************
+ * nsStubMutationObserver
+ *****************************************************************************/
+
+NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(nsStubMutationObserver)
+NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsStubMutationObserver)
+
+/******************************************************************************
+ * MutationObserverWrapper
+ *****************************************************************************/
+
+/**
+ * @brief Wrapper class for a mutation observer that observes multiple nodes.
+ *
+ * This wrapper implements all methods of the nsIMutationObserver interface
+ * and forwards all calls to its owner, which is an instance of
+ * nsMultiMutationObserver.
+ *
+ * This class holds a reference to the owner and AddRefs/Releases its owner
+ * as well to ensure lifetime.
+ */
+class MutationObserverWrapper final : public nsIMutationObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit MutationObserverWrapper(nsMultiMutationObserver* aOwner)
+ : mOwner(aOwner) {}
+
+ void CharacterDataWillChange(nsIContent* aContent,
+ const CharacterDataChangeInfo& aInfo) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->CharacterDataWillChange(aContent, aInfo);
+ }
+
+ void CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo& aInfo) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->CharacterDataChanged(aContent, aInfo);
+ }
+
+ void AttributeWillChange(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->AttributeWillChange(aElement, aNameSpaceID, aAttribute, aModType);
+ }
+
+ void AttributeChanged(mozilla::dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->AttributeChanged(aElement, aNameSpaceID, aAttribute, aModType,
+ aOldValue);
+ }
+
+ void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->AttributeSetToCurrentValue(aElement, aNameSpaceID, aAttribute);
+ }
+
+ void ContentAppended(nsIContent* aFirstNewContent) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ContentAppended(aFirstNewContent);
+ }
+
+ void ContentInserted(nsIContent* aChild) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ContentInserted(aChild);
+ }
+
+ void ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ContentRemoved(aChild, aPreviousSibling);
+ }
+
+ void NodeWillBeDestroyed(nsINode* aNode) override {
+ MOZ_ASSERT(mOwner);
+ AddRefWrapper();
+ RefPtr<nsMultiMutationObserver> owner = mOwner;
+ owner->NodeWillBeDestroyed(aNode);
+ owner->RemoveMutationObserverFromNode(aNode);
+ mOwner = nullptr;
+ ReleaseWrapper();
+ }
+
+ void ParentChainChanged(nsIContent* aContent) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ParentChainChanged(aContent);
+ }
+
+ void ARIAAttributeDefaultWillChange(mozilla::dom::Element* aElement,
+ nsAtom* aAttribute,
+ int32_t aModType) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ARIAAttributeDefaultWillChange(aElement, aAttribute, aModType);
+ }
+
+ void ARIAAttributeDefaultChanged(mozilla::dom::Element* aElement,
+ nsAtom* aAttribute,
+ int32_t aModType) override {
+ MOZ_ASSERT(mOwner);
+ mOwner->ARIAAttributeDefaultChanged(aElement, aAttribute, aModType);
+ }
+
+ MozExternalRefCountType AddRefWrapper() {
+ nsrefcnt count = ++mRefCnt;
+ NS_LOG_ADDREF(this, count, "MutationObserverWrapper", sizeof(*this));
+ return count;
+ }
+
+ MozExternalRefCountType ReleaseWrapper() {
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "MutationObserverWrapper");
+ if (mRefCnt == 0) {
+ mRefCnt = 1;
+ delete this;
+ return MozExternalRefCountType(0);
+ }
+ return mRefCnt;
+ }
+
+ private:
+ ~MutationObserverWrapper() = default;
+ nsMultiMutationObserver* mOwner{nullptr};
+};
+
+NS_IMPL_QUERY_INTERFACE(MutationObserverWrapper, nsIMutationObserver);
+
+MozExternalRefCountType MutationObserverWrapper::AddRef() {
+ MOZ_ASSERT(mOwner);
+ AddRefWrapper();
+ mOwner->AddRef();
+ return mRefCnt;
+}
+
+MozExternalRefCountType MutationObserverWrapper::Release() {
+ MOZ_ASSERT(mOwner);
+ mOwner->Release();
+ return ReleaseWrapper();
+}
+
+/******************************************************************************
+ * nsMultiMutationObserver
+ *****************************************************************************/
+
+void nsMultiMutationObserver::AddMutationObserverToNode(nsINode* aNode) {
+ if (!aNode) {
+ return;
+ }
+ if (mWrapperForNode.Contains(aNode)) {
+ return;
+ }
+ auto* newWrapper = new MutationObserverWrapper{this};
+ newWrapper->AddRefWrapper();
+ mWrapperForNode.InsertOrUpdate(aNode, newWrapper);
+ aNode->AddMutationObserver(newWrapper);
+}
+
+void nsMultiMutationObserver::RemoveMutationObserverFromNode(nsINode* aNode) {
+ if (!aNode) {
+ return;
+ }
+
+ if (auto obs = mWrapperForNode.MaybeGet(aNode); obs.isSome()) {
+ aNode->RemoveMutationObserver(*obs);
+ mWrapperForNode.Remove(aNode);
+ (*obs)->ReleaseWrapper();
+ }
+}
+
+bool nsMultiMutationObserver::ContainsNode(const nsINode* aNode) const {
+ return mWrapperForNode.Contains(const_cast<nsINode*>(aNode));
+}
+
+/******************************************************************************
+ * nsStubMultiMutationObserver
+ *****************************************************************************/
+
+NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(nsStubMultiMutationObserver)
+NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsStubMultiMutationObserver)
diff --git a/dom/base/nsStubMutationObserver.h b/dom/base/nsStubMutationObserver.h
new file mode 100644
index 0000000000..a58b1c6713
--- /dev/null
+++ b/dom/base/nsStubMutationObserver.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStubMutationObserver is an implementation of the nsIMutationObserver
+ * interface (except for the methods on nsISupports) that is intended to be
+ * used as a base class within the content/layout library. All methods do
+ * nothing.
+ */
+
+#ifndef nsStubMutationObserver_h_
+#define nsStubMutationObserver_h_
+
+#include "nsTHashMap.h"
+#include "nsIMutationObserver.h"
+
+/**
+ * There are two advantages to inheriting from nsStubMutationObserver
+ * rather than directly from nsIMutationObserver:
+ * 1. smaller compiled code size (since there's no need for the code
+ * for the empty virtual function implementations for every
+ * nsIMutationObserver implementation)
+ * 2. the performance of document's loop over observers benefits from
+ * the fact that more of the functions called are the same (which
+ * can reduce instruction cache misses and perhaps improve branch
+ * prediction)
+ */
+class nsStubMutationObserver : public nsIMutationObserver {
+ public:
+ NS_DECL_NSIMUTATIONOBSERVER
+};
+
+class MutationObserverWrapper;
+
+/**
+ * @brief Base class for MutationObservers that are used by multiple nodes.
+ *
+ * Mutation Observers are stored inside of a nsINode using a DoublyLinkedList,
+ * restricting the number of nodes a mutation observer can be inserted to one.
+ *
+ * To allow a mutation observer to be used by several nodes, this class
+ * provides a MutationObserverWrapper which implements the nsIMutationObserver
+ * interface and forwards all method calls to this class. For each node this
+ * mutation observer will be used for, a wrapper object is created.
+ */
+class nsMultiMutationObserver : public nsIMutationObserver {
+ public:
+ /**
+ * Adds the mutation observer to aNode by creating a MutationObserverWrapper
+ * and inserting it into aNode.
+ * Does nothing if there already is a mutation observer for aNode.
+ */
+ void AddMutationObserverToNode(nsINode* aNode);
+
+ /**
+ * Removes the mutation observer from aNode.
+ * Does nothing if there is no mutation observer for aNode.
+ */
+ void RemoveMutationObserverFromNode(nsINode* aNode);
+
+ /**
+ * Returns true if there is already a mutation observer for aNode.
+ */
+ bool ContainsNode(const nsINode* aNode) const;
+
+ private:
+ friend class MutationObserverWrapper;
+ nsTHashMap<nsINode*, MutationObserverWrapper*> mWrapperForNode;
+};
+
+/**
+ * Convenience class that provides support for multiple nodes and has
+ * default implementations for nsIMutationObserver.
+ */
+class nsStubMultiMutationObserver : public nsMultiMutationObserver {
+ public:
+ NS_DECL_NSIMUTATIONOBSERVER
+};
+
+#endif /* !defined(nsStubMutationObserver_h_) */
diff --git a/dom/base/nsStyledElement.cpp b/dom/base/nsStyledElement.cpp
new file mode 100644
index 0000000000..cb2524c32c
--- /dev/null
+++ b/dom/base/nsStyledElement.cpp
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStyledElement.h"
+#include "mozAutoDocUpdate.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/css/Loader.h"
+#include "nsXULElement.h"
+#include "nsContentUtils.h"
+#include "nsStyleUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Use the CC variant of this, even though this class does not define
+// a new CC participant, to make QIing to the CC interfaces faster.
+NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsStyledElement,
+ nsStyledElementBase,
+ nsStyledElement)
+
+//----------------------------------------------------------------------
+// nsIContent methods
+
+bool nsStyledElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) {
+ if (aAttribute == nsGkAtoms::style && aNamespaceID == kNameSpaceID_None) {
+ ParseStyleAttribute(aValue, aMaybeScriptedPrincipal, aResult, false);
+ return true;
+ }
+
+ return nsStyledElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aMaybeScriptedPrincipal, aResult);
+}
+
+void nsStyledElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::style &&
+ aValue) {
+ SetMayHaveStyle();
+ }
+
+ return nsStyledElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
+ aNotify);
+}
+
+void nsStyledElement::InlineStyleDeclarationWillChange(
+ MutationClosureData& aData) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(OwnerDoc()->UpdateNestingLevel() > 0,
+ "Should be inside document update!");
+ bool modification = false;
+ if (MayHaveStyle()) {
+ bool needsOldValue = !StaticPrefs::dom_mutation_events_cssom_disabled() &&
+ nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+
+ if (!needsOldValue) {
+ CustomElementDefinition* definition = GetCustomElementDefinition();
+ if (definition &&
+ definition->IsInObservedAttributeList(nsGkAtoms::style)) {
+ needsOldValue = true;
+ }
+ }
+
+ if (needsOldValue) {
+ nsAutoString oldValueStr;
+ modification = GetAttr(kNameSpaceID_None, nsGkAtoms::style, oldValueStr);
+ if (modification) {
+ aData.mOldValue.emplace();
+ aData.mOldValue->SetTo(oldValueStr);
+ }
+ } else {
+ modification = HasAttr(kNameSpaceID_None, nsGkAtoms::style);
+ }
+ }
+
+ aData.mModType =
+ modification ? static_cast<uint8_t>(MutationEvent_Binding::MODIFICATION)
+ : static_cast<uint8_t>(MutationEvent_Binding::ADDITION);
+ MutationObservers::NotifyAttributeWillChange(
+ this, kNameSpaceID_None, nsGkAtoms::style, aData.mModType);
+
+ // XXXsmaug In order to make attribute handling more consistent, consider to
+ // call BeforeSetAttr and pass kCallAfterSetAttr to
+ // SetAttrAndNotify in SetInlineStyleDeclaration.
+ // Handling of mozAutoDocUpdate may require changes in that case.
+}
+
+nsresult nsStyledElement::SetInlineStyleDeclaration(
+ DeclarationBlock& aDeclaration, MutationClosureData& aData) {
+ MOZ_ASSERT(OwnerDoc()->UpdateNestingLevel(),
+ "Should be inside document update!");
+
+ bool hasListeners = !StaticPrefs::dom_mutation_events_cssom_disabled() &&
+ nsContentUtils::HasMutationListeners(
+ this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
+
+ nsAttrValue attrValue(do_AddRef(&aDeclaration), nullptr);
+ SetMayHaveStyle();
+
+ Document* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, true);
+ return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr,
+ aData.mOldValue.ptrOr(nullptr), attrValue, nullptr,
+ aData.mModType, hasListeners, true,
+ kDontCallAfterSetAttr, document, updateBatch);
+}
+
+// ---------------------------------------------------------------
+// Others and helpers
+
+nsICSSDeclaration* nsStyledElement::Style() {
+ Element::nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mStyle) {
+ // Just in case...
+ ReparseStyleAttribute(/* aForceInDataDoc */ true);
+
+ slots->mStyle = new nsDOMCSSAttributeDeclaration(this, false);
+ SetMayHaveStyle();
+ }
+
+ return slots->mStyle;
+}
+
+nsresult nsStyledElement::ReparseStyleAttribute(bool aForceInDataDoc) {
+ if (!MayHaveStyle()) {
+ return NS_OK;
+ }
+ const nsAttrValue* oldVal = mAttrs.GetAttr(nsGkAtoms::style);
+ if (oldVal && oldVal->Type() != nsAttrValue::eCSSDeclaration) {
+ nsAttrValue attrValue;
+ nsAutoString stringValue;
+ oldVal->ToString(stringValue);
+ ParseStyleAttribute(stringValue, nullptr, attrValue, aForceInDataDoc);
+ // Don't bother going through SetInlineStyleDeclaration; we don't
+ // want to fire off mutation events or document notifications anyway
+ bool oldValueSet;
+ nsresult rv =
+ mAttrs.SetAndSwapAttr(nsGkAtoms::style, attrValue, &oldValueSet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsICSSDeclaration* nsStyledElement::GetExistingStyle() {
+ Element::nsDOMSlots* slots = GetExistingDOMSlots();
+ if (!slots) {
+ return nullptr;
+ }
+
+ return slots->mStyle;
+}
+
+void nsStyledElement::ParseStyleAttribute(const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult,
+ bool aForceInDataDoc) {
+ Document* doc = OwnerDoc();
+ bool isNativeAnon = IsInNativeAnonymousSubtree();
+
+ if (!isNativeAnon &&
+ !nsStyleUtil::CSPAllowsInlineStyle(this, doc, aMaybeScriptedPrincipal, 0,
+ 0, aValue, nullptr))
+ return;
+
+ if (aForceInDataDoc || !doc->IsLoadedAsData() || GetExistingStyle() ||
+ doc->IsStaticDocument()) {
+ bool isCSS = true; // assume CSS until proven otherwise
+
+ if (!isNativeAnon) { // native anonymous content always assumes CSS
+ nsAutoString styleType;
+ doc->GetHeaderData(nsGkAtoms::headerContentStyleType, styleType);
+ if (!styleType.IsEmpty()) {
+ isCSS = StringBeginsWith(styleType, u"text/css"_ns,
+ nsASCIICaseInsensitiveStringComparator);
+ }
+ }
+
+ if (isCSS &&
+ aResult.ParseStyleAttribute(aValue, aMaybeScriptedPrincipal, this)) {
+ return;
+ }
+ }
+
+ aResult.SetTo(aValue);
+}
+
+nsresult nsStyledElement::BindToTree(BindContext& aContext, nsINode& aParent) {
+ nsresult rv = nsStyledElementBase::BindToTree(aContext, aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (HasAttr(nsGkAtoms::autofocus) && aContext.AllowsAutoFocus() &&
+ (!IsSVGElement() || IsFocusable())) {
+ aContext.OwnerDoc().ElementWithAutoFocusInserted(this);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/nsStyledElement.h b/dom/base/nsStyledElement.h
new file mode 100644
index 0000000000..670c838fc9
--- /dev/null
+++ b/dom/base/nsStyledElement.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsStyledElement is the base for elements supporting styling via the
+ * id/class/style attributes; it is a common base for their support in HTML,
+ * SVG and MathML.
+ */
+
+#ifndef __NS_STYLEDELEMENT_H_
+#define __NS_STYLEDELEMENT_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Element.h"
+#include "nsString.h"
+
+namespace mozilla {
+class DeclarationBlock;
+struct MutationClosureData;
+} // namespace mozilla
+
+// IID for nsStyledElement interface
+#define NS_STYLED_ELEMENT_IID \
+ { \
+ 0xacbd9ea6, 0x15aa, 0x4f37, { \
+ 0x8c, 0xe0, 0x35, 0x1e, 0xd7, 0x21, 0xca, 0xe9 \
+ } \
+ }
+
+using nsStyledElementBase = mozilla::dom::Element;
+
+class nsStyledElement : public nsStyledElementBase {
+ protected:
+ inline explicit nsStyledElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsStyledElementBase(std::move(aNodeInfo)) {}
+
+ public:
+ // We don't want to implement AddRef/Release because that would add an extra
+ // function call for those on pretty much all elements. But we do need QI, so
+ // we can QI to nsStyledElement.
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ // Element interface methods
+ virtual void InlineStyleDeclarationWillChange(
+ mozilla::MutationClosureData& aData) override;
+ virtual nsresult SetInlineStyleDeclaration(
+ mozilla::DeclarationBlock& aDeclaration,
+ mozilla::MutationClosureData& aData) override;
+ virtual nsresult BindToTree(BindContext& aContext, nsINode& aParent) override;
+
+ nsICSSDeclaration* Style();
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_STYLED_ELEMENT_IID)
+ NS_IMPL_FROMNODE_HELPER(nsStyledElement, IsStyledElement());
+
+ bool IsStyledElement() const final { return true; }
+
+ protected:
+ nsICSSDeclaration* GetExistingStyle();
+
+ /**
+ * Parse a style attr value into a CSS rulestruct (or, if there is no
+ * document, leave it as a string) and return as nsAttrValue.
+ *
+ * @param aValue the value to parse
+ * @param aMaybeScriptedPrincipal if available, the scripted principal
+ * responsible for this attribute value, as passed to
+ * Element::ParseAttribute.
+ * @param aResult the resulting HTMLValue [OUT]
+ */
+ void ParseStyleAttribute(const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult, bool aForceInDataDoc);
+
+ bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) override;
+
+ friend class mozilla::dom::Element;
+
+ /**
+ * Create the style struct from the style attr. Used when an element is
+ * first put into a document. Only has an effect if the old value is a
+ * string. If aForceInDataDoc is true, will reparse even if we're in a data
+ * document.
+ */
+ nsresult ReparseStyleAttribute(bool aForceInDataDoc);
+
+ void BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsStyledElement, NS_STYLED_ELEMENT_IID)
+#endif // __NS_STYLEDELEMENT_H_
diff --git a/dom/base/nsSyncLoadService.cpp b/dom/base/nsSyncLoadService.cpp
new file mode 100644
index 0000000000..6db3c51bcf
--- /dev/null
+++ b/dom/base/nsSyncLoadService.cpp
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A service that provides methods for synchronously loading a DOM in various
+ * ways.
+ */
+
+#include "nsSyncLoadService.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/dom/Document.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h" // for kLoadAsData
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "ReferrerInfo.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+using mozilla::dom::ReferrerPolicy;
+
+/**
+ * This class manages loading a single XML document
+ */
+
+class nsSyncLoader : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference {
+ public:
+ nsSyncLoader()
+ : mLoading(false), mAsyncLoadStatus(NS_ERROR_NOT_INITIALIZED) {}
+
+ NS_DECL_ISUPPORTS
+
+ nsresult LoadDocument(nsIChannel* aChannel, bool aChannelIsSync,
+ bool aForceToXML, ReferrerPolicy aReferrerPolicy,
+ Document** aResult);
+
+ NS_FORWARD_NSISTREAMLISTENER(mListener->)
+ NS_DECL_NSIREQUESTOBSERVER
+
+ NS_DECL_NSICHANNELEVENTSINK
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ private:
+ virtual ~nsSyncLoader();
+
+ nsresult PushAsyncStream(nsIStreamListener* aListener);
+ nsresult PushSyncStream(nsIStreamListener* aListener);
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ bool mLoading;
+ nsresult mAsyncLoadStatus;
+};
+
+class nsForceXMLListener : public nsIStreamListener {
+ virtual ~nsForceXMLListener();
+
+ public:
+ explicit nsForceXMLListener(nsIStreamListener* aListener);
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSISTREAMLISTENER(mListener->)
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+nsForceXMLListener::nsForceXMLListener(nsIStreamListener* aListener)
+ : mListener(aListener) {}
+
+nsForceXMLListener::~nsForceXMLListener() = default;
+
+NS_IMPL_ISUPPORTS(nsForceXMLListener, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+nsForceXMLListener::OnStartRequest(nsIRequest* aRequest) {
+ nsresult status;
+ aRequest->GetStatus(&status);
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel && NS_SUCCEEDED(status)) {
+ channel->SetContentType("text/xml"_ns);
+ }
+
+ return mListener->OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+nsForceXMLListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ return mListener->OnStopRequest(aRequest, aStatusCode);
+}
+
+nsSyncLoader::~nsSyncLoader() {
+ if (mLoading && mChannel) {
+ mChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "nsSyncLoader::~nsSyncLoader"_ns);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSyncLoader, nsIStreamListener, nsIRequestObserver,
+ nsIChannelEventSink, nsIInterfaceRequestor,
+ nsISupportsWeakReference)
+
+nsresult nsSyncLoader::LoadDocument(nsIChannel* aChannel, bool aChannelIsSync,
+ bool aForceToXML,
+ ReferrerPolicy aReferrerPolicy,
+ Document** aResult) {
+ NS_ENSURE_ARG(aChannel);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+ nsresult rv = NS_OK;
+
+ mChannel = aChannel;
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(mChannel);
+ if (http) {
+ rv = http->SetRequestHeader(
+ "Accept"_ns,
+ nsLiteralCString(
+ "text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ loadInfo->TriggeringPrincipal()->CreateReferrerInfo(
+ aReferrerPolicy, getter_AddRefs(referrerInfo));
+ if (referrerInfo) {
+ rv = http->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Hook us up to listen to redirects and the like.
+ // Do this before setting up the cross-site proxy since
+ // that installs its own proxies.
+ mChannel->SetNotificationCallbacks(this);
+
+ // Get the loadgroup of the channel
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create document
+ nsCOMPtr<Document> document;
+ rv = NS_NewXMLDocument(getter_AddRefs(document));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Start the document load. Do this before we attach the load listener
+ // since we reset the document which drops all observers.
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = document->StartDocumentLoad(kLoadAsData, mChannel, loadGroup, nullptr,
+ getter_AddRefs(listener), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aForceToXML) {
+ nsCOMPtr<nsIStreamListener> forceListener =
+ new nsForceXMLListener(listener);
+ listener.swap(forceListener);
+ }
+
+ if (aChannelIsSync) {
+ rv = PushSyncStream(listener);
+ } else {
+ rv = PushAsyncStream(listener);
+ }
+
+ http = do_QueryInterface(mChannel);
+ if (NS_SUCCEEDED(rv) && http) {
+ bool succeeded;
+ if (NS_FAILED(http->GetRequestSucceeded(&succeeded)) || !succeeded) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ mChannel = nullptr;
+
+ // check that the load succeeded
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(document->GetRootElement(), NS_ERROR_FAILURE);
+
+ document.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult nsSyncLoader::PushAsyncStream(nsIStreamListener* aListener) {
+ mListener = aListener;
+
+ mAsyncLoadStatus = NS_OK;
+
+ // Start reading from the channel
+ nsresult rv = mChannel->AsyncOpen(this);
+
+ if (NS_SUCCEEDED(rv)) {
+ // process events until we're finished.
+ mLoading = true;
+ nsIThread* thread = NS_GetCurrentThread();
+ while (mLoading && NS_SUCCEEDED(rv)) {
+ bool processedEvent;
+ rv = thread->ProcessNextEvent(true, &processedEvent);
+ if (NS_SUCCEEDED(rv) && !processedEvent) rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ mListener = nullptr;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Note that if AsyncOpen failed that's ok -- the only caller of
+ // this method nulls out mChannel immediately after we return.
+
+ return mAsyncLoadStatus;
+}
+
+nsresult nsSyncLoader::PushSyncStream(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIInputStream> in;
+ nsresult rv = mChannel->Open(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLoading = true;
+ rv = nsSyncLoadService::PushSyncStreamToListener(in.forget(), aListener,
+ mChannel);
+ mLoading = false;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSyncLoader::OnStartRequest(nsIRequest* aRequest) {
+ return mListener->OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+nsSyncLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ if (NS_SUCCEEDED(mAsyncLoadStatus) && NS_FAILED(aStatusCode)) {
+ mAsyncLoadStatus = aStatusCode;
+ }
+ nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode);
+ if (NS_SUCCEEDED(mAsyncLoadStatus) && NS_FAILED(rv)) {
+ mAsyncLoadStatus = rv;
+ }
+ mLoading = false;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSyncLoader::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ MOZ_ASSERT(aNewChannel, "Redirecting to null channel?");
+
+ mChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncLoader::GetInterface(const nsIID& aIID, void** aResult) {
+ return QueryInterface(aIID, aResult);
+}
+
+/* static */
+nsresult nsSyncLoadService::LoadDocument(
+ nsIURI* aURI, nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aLoaderPrincipal, nsSecurityFlags aSecurityFlags,
+ nsILoadGroup* aLoadGroup, nsICookieJarSettings* aCookieJarSettings,
+ bool aForceToXML, ReferrerPolicy aReferrerPolicy, Document** aResult) {
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv =
+ NS_NewChannel(getter_AddRefs(channel), aURI, aLoaderPrincipal,
+ aSecurityFlags, aContentPolicyType, aCookieJarSettings,
+ nullptr, // PerformanceStorage
+ aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aForceToXML) {
+ channel->SetContentType("text/xml"_ns);
+ }
+
+ // if the load needs to enforce CORS, then force the load to be async
+ bool isSync =
+ !(aSecurityFlags & nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) &&
+ (aURI->SchemeIs("chrome") || aURI->SchemeIs("resource"));
+ RefPtr<nsSyncLoader> loader = new nsSyncLoader();
+ return loader->LoadDocument(channel, isSync, aForceToXML, aReferrerPolicy,
+ aResult);
+}
+
+/* static */
+nsresult nsSyncLoadService::PushSyncStreamToListener(
+ already_AddRefed<nsIInputStream> aIn, nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIInputStream> in = std::move(aIn);
+
+ // Set up buffering stream
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ if (!NS_InputStreamIsBuffered(in)) {
+ int64_t chunkSize;
+ rv = aChannel->GetContentLength(&chunkSize);
+ if (NS_FAILED(rv) || chunkSize < 1) {
+ chunkSize = 4096;
+ }
+ chunkSize = std::min(int64_t(UINT16_MAX), chunkSize);
+
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), in.forget(),
+ chunkSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ in = bufferedStream;
+ }
+
+ // Load
+ rv = aListener->OnStartRequest(aChannel);
+ if (NS_SUCCEEDED(rv)) {
+ uint64_t sourceOffset = 0;
+ while (1) {
+ uint64_t readCount = 0;
+ rv = in->Available(&readCount);
+ if (NS_FAILED(rv) || !readCount) {
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // End of file, but not an error
+ rv = NS_OK;
+ }
+ break;
+ }
+
+ if (readCount > UINT32_MAX) readCount = UINT32_MAX;
+
+ rv = aListener->OnDataAvailable(aChannel, in, sourceOffset,
+ (uint32_t)readCount);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ sourceOffset += readCount;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ aChannel->Cancel(rv);
+ }
+ aListener->OnStopRequest(aChannel, rv);
+
+ return rv;
+}
diff --git a/dom/base/nsSyncLoadService.h b/dom/base/nsSyncLoadService.h
new file mode 100644
index 0000000000..0f81f8ec80
--- /dev/null
+++ b/dom/base/nsSyncLoadService.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A service that provides methods for synchronously loading a DOM in various
+ * ways.
+ */
+
+#ifndef nsSyncLoadService_h__
+#define nsSyncLoadService_h__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nscore.h"
+#include "nsIContentPolicy.h"
+#include "nsILoadInfo.h"
+#include "nsIReferrerInfo.h"
+
+class nsICookieJarSettings;
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+class nsIURI;
+class nsIPrincipal;
+class nsIChannel;
+
+namespace mozilla::dom {
+class Document;
+} // namespace mozilla::dom
+
+class nsSyncLoadService {
+ public:
+ /**
+ * Synchronously load the document from the specified URI.
+ *
+ * @param aURI URI to load the document from.
+ * @param aContentPolicyType contentPolicyType to be set on the channel
+ * @param aLoaderPrincipal Principal of loading document. For security
+ * checks and referrer header.
+ * @param aSecurityFlags securityFlags to be set on the channel
+ * @param aLoadGroup The loadgroup to use for loading the document.
+ * @param aForceToXML Whether to parse the document as XML, regardless of
+ * content type.
+ * @param referrerPolicy Referrer policy.
+ * @param aResult [out] The document loaded from the URI.
+ */
+ static nsresult LoadDocument(
+ nsIURI* aURI, nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aLoaderPrincipal, nsSecurityFlags aSecurityFlags,
+ nsILoadGroup* aLoadGroup, nsICookieJarSettings* aCookieJarSettings,
+ bool aForceToXML, mozilla::dom::ReferrerPolicy aReferrerPolicy,
+ mozilla::dom::Document** aResult);
+
+ /**
+ * Read input stream aIn in chunks and deliver synchronously to aListener.
+ *
+ * @param aIn The stream to be read. The ownership of this stream is taken.
+ * @param aListener The listener that will receive
+ * OnStartRequest/OnDataAvailable/OnStopRequest
+ * notifications.
+ * @param aChannel The channel that aIn was opened from.
+ */
+ static nsresult PushSyncStreamToListener(already_AddRefed<nsIInputStream> aIn,
+ nsIStreamListener* aListener,
+ nsIChannel* aChannel);
+};
+
+#endif // nsSyncLoadService_h__
diff --git a/dom/base/nsTextFragment.cpp b/dom/base/nsTextFragment.cpp
new file mode 100644
index 0000000000..5cba2577b8
--- /dev/null
+++ b/dom/base/nsTextFragment.cpp
@@ -0,0 +1,538 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class which represents a fragment of text (eg inside a text
+ * node); if only codepoints below 256 are used, the text is stored as
+ * a char*; otherwise the text is stored as a char16_t*
+ */
+
+#include "nsTextFragment.h"
+#include "nsCRT.h"
+#include "nsReadableUtils.h"
+#include "nsBidiUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/SSE.h"
+#include "mozilla/ppc.h"
+#include "nsTextFragmentImpl.h"
+#include <algorithm>
+
+#define TEXTFRAG_WHITE_AFTER_NEWLINE 50
+#define TEXTFRAG_MAX_NEWLINES 7
+
+// Static buffer used for common fragments
+static char* sSpaceSharedString[TEXTFRAG_MAX_NEWLINES + 1];
+static char* sTabSharedString[TEXTFRAG_MAX_NEWLINES + 1];
+static char sSingleCharSharedString[256];
+
+using namespace mozilla;
+
+// static
+nsresult nsTextFragment::Init() {
+ // Create whitespace strings
+ uint32_t i;
+ for (i = 0; i <= TEXTFRAG_MAX_NEWLINES; ++i) {
+ sSpaceSharedString[i] = new char[1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE];
+ sTabSharedString[i] = new char[1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE];
+ sSpaceSharedString[i][0] = ' ';
+ sTabSharedString[i][0] = ' ';
+ uint32_t j;
+ for (j = 1; j < 1 + i; ++j) {
+ sSpaceSharedString[i][j] = '\n';
+ sTabSharedString[i][j] = '\n';
+ }
+ for (; j < (1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE); ++j) {
+ sSpaceSharedString[i][j] = ' ';
+ sTabSharedString[i][j] = '\t';
+ }
+ }
+
+ // Create single-char strings
+ for (i = 0; i < 256; ++i) {
+ sSingleCharSharedString[i] = i;
+ }
+
+ return NS_OK;
+}
+
+// static
+void nsTextFragment::Shutdown() {
+ uint32_t i;
+ for (i = 0; i <= TEXTFRAG_MAX_NEWLINES; ++i) {
+ delete[] sSpaceSharedString[i];
+ delete[] sTabSharedString[i];
+ sSpaceSharedString[i] = nullptr;
+ sTabSharedString[i] = nullptr;
+ }
+}
+
+nsTextFragment::~nsTextFragment() {
+ ReleaseText();
+ MOZ_COUNT_DTOR(nsTextFragment);
+}
+
+void nsTextFragment::ReleaseText() {
+ if (mState.mIs2b) {
+ NS_RELEASE(m2b);
+ } else if (mState.mLength && m1b && mState.mInHeap) {
+ free(const_cast<char*>(m1b));
+ }
+
+ m1b = nullptr;
+ mState.mIsBidi = false;
+
+ // Set mState.mIs2b, mState.mInHeap, and mState.mLength = 0 with mAllBits;
+ mAllBits = 0;
+}
+
+nsTextFragment& nsTextFragment::operator=(const nsTextFragment& aOther) {
+ ReleaseText();
+
+ if (aOther.mState.mLength) {
+ if (!aOther.mState.mInHeap) {
+ MOZ_ASSERT(!aOther.mState.mIs2b);
+ m1b = aOther.m1b;
+ } else if (aOther.mState.mIs2b) {
+ m2b = aOther.m2b;
+ NS_ADDREF(m2b);
+ } else {
+ m1b = static_cast<char*>(malloc(aOther.mState.mLength));
+ if (m1b) {
+ memcpy(const_cast<char*>(m1b), aOther.m1b, aOther.mState.mLength);
+ } else {
+ // allocate a buffer for a single REPLACEMENT CHARACTER
+ m2b = nsStringBuffer::Alloc(sizeof(char16_t) * 2).take();
+ if (!m2b) {
+ MOZ_CRASH("OOM!");
+ }
+ char16_t* data = static_cast<char16_t*>(m2b->Data());
+ data[0] = 0xFFFD; // REPLACEMENT CHARACTER
+ data[1] = char16_t(0);
+ mState.mIs2b = true;
+ mState.mInHeap = true;
+ mState.mLength = 1;
+ return *this;
+ }
+ }
+
+ mAllBits = aOther.mAllBits;
+ }
+
+ return *this;
+}
+
+static inline int32_t FirstNon8BitUnvectorized(const char16_t* str,
+ const char16_t* end) {
+ using p = Non8BitParameters<sizeof(size_t)>;
+ const size_t mask = p::mask();
+ const uint32_t alignMask = p::alignMask();
+ const uint32_t numUnicharsPerWord = p::numUnicharsPerWord();
+ const int32_t len = end - str;
+ int32_t i = 0;
+
+ // Align ourselves to a word boundary.
+ int32_t alignLen = std::min(
+ len, int32_t(((-NS_PTR_TO_INT32(str)) & alignMask) / sizeof(char16_t)));
+ for (; i < alignLen; i++) {
+ if (str[i] > 255) return i;
+ }
+
+ // Check one word at a time.
+ const int32_t wordWalkEnd =
+ ((len - i) / numUnicharsPerWord) * numUnicharsPerWord;
+ for (; i < wordWalkEnd; i += numUnicharsPerWord) {
+ const size_t word = *reinterpret_cast<const size_t*>(str + i);
+ if (word & mask) return i;
+ }
+
+ // Take care of the remainder one character at a time.
+ for (; i < len; i++) {
+ if (str[i] > 255) return i;
+ }
+
+ return -1;
+}
+
+#if defined(MOZILLA_MAY_SUPPORT_SSE2)
+# include "nsTextFragmentGenericFwd.h"
+#endif
+
+#ifdef __powerpc__
+namespace mozilla {
+namespace VMX {
+int32_t FirstNon8Bit(const char16_t* str, const char16_t* end);
+} // namespace VMX
+} // namespace mozilla
+#endif
+
+/*
+ * This function returns -1 if all characters in str are 8 bit characters.
+ * Otherwise, it returns a value less than or equal to the index of the first
+ * non-8bit character in str. For example, if first non-8bit character is at
+ * position 25, it may return 25, or for example 24, or 16. But it guarantees
+ * there is no non-8bit character before returned value.
+ */
+static inline int32_t FirstNon8Bit(const char16_t* str, const char16_t* end) {
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+ if (mozilla::supports_sse2()) {
+ return mozilla::FirstNon8Bit<xsimd::sse2>(str, end);
+ }
+#elif defined(__powerpc__)
+ if (mozilla::supports_vmx()) {
+ return mozilla::VMX::FirstNon8Bit(str, end);
+ }
+#endif
+
+ return FirstNon8BitUnvectorized(str, end);
+}
+
+bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
+ bool aUpdateBidi, bool aForce2b) {
+ if (MOZ_UNLIKELY(aLength > NS_MAX_TEXT_FRAGMENT_LENGTH)) {
+ return false;
+ }
+
+ if (aForce2b && mState.mIs2b && !m2b->IsReadonly()) {
+ uint32_t storageSize = m2b->StorageSize();
+ uint32_t neededSize = aLength * sizeof(char16_t);
+ if (!neededSize) {
+ if (storageSize < AutoStringDefaultStorageSize) {
+ // If we're storing small enough nsStringBuffer, let's preserve it.
+
+ static_cast<char16_t*>(m2b->Data())[0] = char16_t(0);
+ mState.mLength = 0;
+ mState.mIsBidi = false;
+ return true;
+ }
+ } else if ((neededSize < storageSize) &&
+ ((storageSize / 2) <
+ (neededSize + AutoStringDefaultStorageSize))) {
+ // Don't try to reuse the existing nsStringBuffer, if it would have
+ // lots of unused space.
+
+ memcpy(m2b->Data(), aBuffer, neededSize);
+ static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
+ mState.mLength = aLength;
+ mState.mIsBidi = false;
+ if (aUpdateBidi) {
+ UpdateBidiFlag(aBuffer, aLength);
+ }
+ return true;
+ }
+ }
+
+ ReleaseText();
+
+ if (aLength == 0) {
+ return true;
+ }
+
+ char16_t firstChar = *aBuffer;
+ if (!aForce2b && aLength == 1 && firstChar < 256) {
+ m1b = sSingleCharSharedString + firstChar;
+ mState.mInHeap = false;
+ mState.mIs2b = false;
+ mState.mLength = 1;
+
+ return true;
+ }
+
+ const char16_t* ucp = aBuffer;
+ const char16_t* uend = aBuffer + aLength;
+
+ // Check if we can use a shared string
+ if (!aForce2b &&
+ aLength <= 1 + TEXTFRAG_WHITE_AFTER_NEWLINE + TEXTFRAG_MAX_NEWLINES &&
+ (firstChar == ' ' || firstChar == '\n' || firstChar == '\t')) {
+ if (firstChar == ' ') {
+ ++ucp;
+ }
+
+ const char16_t* start = ucp;
+ while (ucp < uend && *ucp == '\n') {
+ ++ucp;
+ }
+ const char16_t* endNewLine = ucp;
+
+ char16_t space = ucp < uend && *ucp == '\t' ? '\t' : ' ';
+ while (ucp < uend && *ucp == space) {
+ ++ucp;
+ }
+
+ if (ucp == uend && endNewLine - start <= TEXTFRAG_MAX_NEWLINES &&
+ ucp - endNewLine <= TEXTFRAG_WHITE_AFTER_NEWLINE) {
+ char** strings = space == ' ' ? sSpaceSharedString : sTabSharedString;
+ m1b = strings[endNewLine - start];
+
+ // If we didn't find a space in the beginning, skip it now.
+ if (firstChar != ' ') {
+ ++m1b;
+ }
+
+ mState.mInHeap = false;
+ mState.mIs2b = false;
+ mState.mLength = aLength;
+
+ return true;
+ }
+ }
+
+ // See if we need to store the data in ucs2 or not
+ int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(ucp, uend);
+
+ if (first16bit != -1) { // aBuffer contains no non-8bit character
+ // Use ucs2 storage because we have to
+ CheckedUint32 m2bSize = CheckedUint32(aLength) + 1;
+ if (!m2bSize.isValid()) {
+ return false;
+ }
+ m2bSize *= sizeof(char16_t);
+ if (!m2bSize.isValid()) {
+ return false;
+ }
+
+ m2b = nsStringBuffer::Alloc(m2bSize.value()).take();
+ if (!m2b) {
+ return false;
+ }
+ memcpy(m2b->Data(), aBuffer, aLength * sizeof(char16_t));
+ static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
+
+ mState.mIs2b = true;
+ if (aUpdateBidi) {
+ UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
+ }
+
+ } else {
+ // Use 1 byte storage because we can
+ char* buff = static_cast<char*>(malloc(aLength));
+ if (!buff) {
+ return false;
+ }
+
+ // Copy data
+ LossyConvertUtf16toLatin1(Span(aBuffer, aLength), Span(buff, aLength));
+ m1b = buff;
+ mState.mIs2b = false;
+ }
+
+ // Setup our fields
+ mState.mInHeap = true;
+ mState.mLength = aLength;
+
+ return true;
+}
+
+void nsTextFragment::CopyTo(char16_t* aDest, uint32_t aOffset,
+ uint32_t aCount) {
+ const CheckedUint32 endOffset = CheckedUint32(aOffset) + aCount;
+ if (!endOffset.isValid() || endOffset.value() > GetLength()) {
+ aCount = mState.mLength - aOffset;
+ }
+
+ if (aCount) {
+ if (mState.mIs2b) {
+ memcpy(aDest, Get2b() + aOffset, sizeof(char16_t) * aCount);
+ } else {
+ const char* cp = m1b + aOffset;
+ ConvertLatin1toUtf16(Span(cp, aCount), Span(aDest, aCount));
+ }
+ }
+}
+
+bool nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength,
+ bool aUpdateBidi, bool aForce2b) {
+ if (!aLength) {
+ return true;
+ }
+
+ // This is a common case because some callsites create a textnode
+ // with a value by creating the node and then calling AppendData.
+ if (mState.mLength == 0) {
+ return SetTo(aBuffer, aLength, aUpdateBidi, aForce2b);
+ }
+
+ // Should we optimize for aData.Length() == 0?
+
+ // FYI: Don't use CheckedInt in this method since here is very hot path
+ // in some performance tests.
+ if (NS_MAX_TEXT_FRAGMENT_LENGTH - mState.mLength < aLength) {
+ return false; // Would be overflown if we'd keep handling.
+ }
+
+ if (mState.mIs2b) {
+ size_t size = mState.mLength + aLength + 1;
+ if (SIZE_MAX / sizeof(char16_t) < size) {
+ return false; // Would be overflown if we'd keep handling.
+ }
+ size *= sizeof(char16_t);
+
+ // Already a 2-byte string so the result will be too
+ nsStringBuffer* buff = nullptr;
+ nsStringBuffer* bufferToRelease = nullptr;
+ if (m2b->IsReadonly()) {
+ buff = nsStringBuffer::Alloc(size).take();
+ if (!buff) {
+ return false;
+ }
+ bufferToRelease = m2b;
+ memcpy(static_cast<char16_t*>(buff->Data()), m2b->Data(),
+ mState.mLength * sizeof(char16_t));
+ } else {
+ buff = nsStringBuffer::Realloc(m2b, size);
+ if (!buff) {
+ return false;
+ }
+ }
+
+ char16_t* data = static_cast<char16_t*>(buff->Data());
+ memcpy(data + mState.mLength, aBuffer, aLength * sizeof(char16_t));
+ mState.mLength += aLength;
+ m2b = buff;
+ data[mState.mLength] = char16_t(0);
+
+ NS_IF_RELEASE(bufferToRelease);
+
+ if (aUpdateBidi) {
+ UpdateBidiFlag(aBuffer, aLength);
+ }
+
+ return true;
+ }
+
+ // Current string is a 1-byte string, check if the new data fits in one byte
+ // too.
+ int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(aBuffer, aBuffer + aLength);
+
+ if (first16bit != -1) { // aBuffer contains no non-8bit character
+ size_t size = mState.mLength + aLength + 1;
+ if (SIZE_MAX / sizeof(char16_t) < size) {
+ return false; // Would be overflown if we'd keep handling.
+ }
+ size *= sizeof(char16_t);
+
+ // The old data was 1-byte, but the new is not so we have to expand it
+ // all to 2-byte
+ nsStringBuffer* buff = nsStringBuffer::Alloc(size).take();
+ if (!buff) {
+ return false;
+ }
+
+ // Copy data into buff
+ char16_t* data = static_cast<char16_t*>(buff->Data());
+ ConvertLatin1toUtf16(Span(m1b, mState.mLength), Span(data, mState.mLength));
+
+ memcpy(data + mState.mLength, aBuffer, aLength * sizeof(char16_t));
+ mState.mLength += aLength;
+ mState.mIs2b = true;
+
+ if (mState.mInHeap) {
+ free(const_cast<char*>(m1b));
+ }
+ data[mState.mLength] = char16_t(0);
+ m2b = buff;
+
+ mState.mInHeap = true;
+
+ if (aUpdateBidi) {
+ UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
+ }
+
+ return true;
+ }
+
+ // The new and the old data is all 1-byte
+ size_t size = mState.mLength + aLength;
+ MOZ_ASSERT(sizeof(char) == 1);
+ char* buff;
+ if (mState.mInHeap) {
+ buff = static_cast<char*>(realloc(const_cast<char*>(m1b), size));
+ if (!buff) {
+ return false;
+ }
+ } else {
+ buff = static_cast<char*>(malloc(size));
+ if (!buff) {
+ return false;
+ }
+
+ memcpy(buff, m1b, mState.mLength);
+ mState.mInHeap = true;
+ }
+
+ // Copy aBuffer into buff.
+ LossyConvertUtf16toLatin1(Span(aBuffer, aLength),
+ Span(buff + mState.mLength, aLength));
+
+ m1b = buff;
+ mState.mLength += aLength;
+
+ return true;
+}
+
+/* virtual */
+size_t nsTextFragment::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ if (Is2b()) {
+ return m2b->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ if (mState.mInHeap) {
+ return aMallocSizeOf(m1b);
+ }
+
+ return 0;
+}
+
+// To save time we only do this when we really want to know, not during
+// every allocation
+void nsTextFragment::UpdateBidiFlag(const char16_t* aBuffer, uint32_t aLength) {
+ if (mState.mIs2b && !mState.mIsBidi) {
+ if (HasRTLChars(Span(aBuffer, aLength))) {
+ mState.mIsBidi = true;
+ }
+ }
+}
+
+bool nsTextFragment::TextEquals(const nsTextFragment& aOther) const {
+ if (!Is2b()) {
+ // We're 1-byte.
+ if (!aOther.Is2b()) {
+ nsDependentCSubstring ourStr(Get1b(), GetLength());
+ return ourStr.Equals(
+ nsDependentCSubstring(aOther.Get1b(), aOther.GetLength()));
+ }
+
+ // We're 1-byte, the other thing is 2-byte. Instead of implementing a
+ // separate codepath for this, just use our code below.
+ return aOther.TextEquals(*this);
+ }
+
+ nsDependentSubstring ourStr(Get2b(), GetLength());
+ if (aOther.Is2b()) {
+ return ourStr.Equals(
+ nsDependentSubstring(aOther.Get2b(), aOther.GetLength()));
+ }
+
+ // We can't use EqualsASCII here, because the other string might not
+ // actually be ASCII. Just roll our own compare; do it in the simple way.
+ // Bug 1532356 tracks not having to roll our own.
+ if (GetLength() != aOther.GetLength()) {
+ return false;
+ }
+
+ const char16_t* ourChars = Get2b();
+ const char* otherChars = aOther.Get1b();
+ for (uint32_t i = 0; i < GetLength(); ++i) {
+ if (ourChars[i] != static_cast<char16_t>(otherChars[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/dom/base/nsTextFragment.h b/dom/base/nsTextFragment.h
new file mode 100644
index 0000000000..5330815683
--- /dev/null
+++ b/dom/base/nsTextFragment.h
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class which represents a fragment of text (eg inside a text
+ * node); if only codepoints below 256 are used, the text is stored as
+ * a char*; otherwise the text is stored as a char16_t*
+ */
+
+#ifndef nsTextFragment_h___
+#define nsTextFragment_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "nsCharTraits.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "nsISupportsImpl.h"
+
+// XXX should this normalize the code to keep a \u0000 at the end?
+
+// XXX nsTextFragmentPool?
+
+/**
+ * A fragment of text. If mIs2b is 1 then the m2b pointer is valid
+ * otherwise the m1b pointer is valid. If m1b is used then each byte
+ * of data represents a single ucs2 character with the high byte being
+ * zero.
+ *
+ * This class does not have a virtual destructor therefore it is not
+ * meant to be subclassed.
+ */
+class nsTextFragment final {
+ public:
+ static nsresult Init();
+ static void Shutdown();
+
+ /**
+ * Default constructor. Initialize the fragment to be empty.
+ */
+ nsTextFragment() : m1b(nullptr), mAllBits(0) {
+ MOZ_COUNT_CTOR(nsTextFragment);
+ NS_ASSERTION(sizeof(FragmentBits) == 4, "Bad field packing!");
+ }
+
+ ~nsTextFragment();
+
+ /**
+ * Change the contents of this fragment to be a copy of the
+ * the argument fragment, or to "" if unable to allocate enough memory.
+ */
+ nsTextFragment& operator=(const nsTextFragment& aOther);
+
+ /**
+ * Return true if this fragment is represented by char16_t data
+ */
+ bool Is2b() const { return mState.mIs2b; }
+
+ /**
+ * Return true if this fragment contains Bidi text
+ * For performance reasons this flag is only set if explicitely requested (by
+ * setting the aUpdateBidi argument on SetTo or Append to true).
+ */
+ bool IsBidi() const { return mState.mIsBidi; }
+
+ /**
+ * Get a pointer to constant char16_t data.
+ */
+ const char16_t* Get2b() const {
+ MOZ_ASSERT(Is2b(), "not 2b text");
+ return static_cast<char16_t*>(m2b->Data());
+ }
+
+ /**
+ * Get a pointer to constant char data.
+ */
+ const char* Get1b() const {
+ NS_ASSERTION(!Is2b(), "not 1b text");
+ return (const char*)m1b;
+ }
+
+ /**
+ * Get the length of the fragment. The length is the number of logical
+ * characters, not the number of bytes to store the characters.
+ */
+ uint32_t GetLength() const { return mState.mLength; }
+
+#define NS_MAX_TEXT_FRAGMENT_LENGTH (static_cast<uint32_t>(0x1FFFFFFF))
+
+ bool CanGrowBy(size_t n) const {
+ return n < (1 << 29) && mState.mLength + n < (1 << 29);
+ }
+
+ /**
+ * Change the contents of this fragment to be a copy of the given
+ * buffer. If aUpdateBidi is true, contents of the fragment will be scanned,
+ * and mState.mIsBidi will be turned on if it includes any Bidi characters.
+ * If aForce2b is true, aBuffer will be stored as char16_t as is. Then,
+ * you can access the value faster but may waste memory if all characters
+ * are less than U+0100.
+ */
+ bool SetTo(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi,
+ bool aForce2b);
+
+ bool SetTo(const nsString& aString, bool aUpdateBidi, bool aForce2b) {
+ if (MOZ_UNLIKELY(aString.Length() > NS_MAX_TEXT_FRAGMENT_LENGTH)) {
+ return false;
+ }
+ ReleaseText();
+ if (aForce2b && !aUpdateBidi) {
+ nsStringBuffer* buffer = nsStringBuffer::FromString(aString);
+ if (buffer) {
+ NS_ADDREF(m2b = buffer);
+ mState.mInHeap = true;
+ mState.mIs2b = true;
+ mState.mLength = aString.Length();
+ return true;
+ }
+ }
+
+ return SetTo(aString.get(), aString.Length(), aUpdateBidi, aForce2b);
+ }
+
+ /**
+ * Append aData to the end of this fragment. If aUpdateBidi is true, contents
+ * of the fragment will be scanned, and mState.mIsBidi will be turned on if
+ * it includes any Bidi characters.
+ * If aForce2b is true, the string will be stored as char16_t as is. Then,
+ * you can access the value faster but may waste memory if all characters
+ * are less than U+0100.
+ */
+ bool Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi,
+ bool aForce2b);
+
+ /**
+ * Append the contents of this string fragment to aString
+ */
+ void AppendTo(nsAString& aString) const {
+ if (!AppendTo(aString, mozilla::fallible)) {
+ aString.AllocFailed(aString.Length() + GetLength());
+ }
+ }
+
+ /**
+ * Append the contents of this string fragment to aString
+ * @return false if an out of memory condition is detected, true otherwise
+ */
+ [[nodiscard]] bool AppendTo(nsAString& aString,
+ const mozilla::fallible_t& aFallible) const {
+ if (mState.mIs2b) {
+ if (aString.IsEmpty()) {
+ m2b->ToString(mState.mLength, aString);
+ return true;
+ }
+ bool ok = aString.Append(Get2b(), mState.mLength, aFallible);
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+ } else {
+ return AppendASCIItoUTF16(Substring(m1b, mState.mLength), aString,
+ aFallible);
+ }
+ }
+
+ /**
+ * Append a substring of the contents of this string fragment to aString.
+ * @param aOffset where to start the substring in this text fragment
+ * @param aLength the length of the substring
+ */
+ void AppendTo(nsAString& aString, uint32_t aOffset, uint32_t aLength) const {
+ if (!AppendTo(aString, aOffset, aLength, mozilla::fallible)) {
+ aString.AllocFailed(aString.Length() + aLength);
+ }
+ }
+
+ /**
+ * Append a substring of the contents of this string fragment to aString.
+ * @param aString the string in which to append
+ * @param aOffset where to start the substring in this text fragment
+ * @param aLength the length of the substring
+ * @return false if an out of memory condition is detected, true otherwise
+ */
+ [[nodiscard]] bool AppendTo(nsAString& aString, uint32_t aOffset,
+ uint32_t aLength,
+ const mozilla::fallible_t& aFallible) const {
+ if (mState.mIs2b) {
+ bool ok = aString.Append(Get2b() + aOffset, aLength, aFallible);
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+ } else {
+ return AppendASCIItoUTF16(Substring(m1b + aOffset, aLength), aString,
+ aFallible);
+ }
+ }
+
+ /**
+ * Make a copy of the fragments contents starting at offset for
+ * count characters. The offset and count will be adjusted to
+ * lie within the fragments data. The fragments data is converted if
+ * necessary.
+ */
+ void CopyTo(char16_t* aDest, uint32_t aOffset, uint32_t aCount);
+
+ /**
+ * Return the character in the text-fragment at the given
+ * index. This always returns a char16_t.
+ */
+ char16_t CharAt(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mState.mLength, "bad index");
+ return mState.mIs2b ? Get2b()[aIndex]
+ : static_cast<unsigned char>(m1b[aIndex]);
+ }
+
+ /**
+ * IsHighSurrogateFollowedByLowSurrogateAt() returns true if character at
+ * aIndex is high surrogate and it's followed by low surrogate.
+ */
+ inline bool IsHighSurrogateFollowedByLowSurrogateAt(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mState.mLength);
+ if (!mState.mIs2b || aIndex + 1 >= mState.mLength) {
+ return false;
+ }
+ return NS_IS_SURROGATE_PAIR(Get2b()[aIndex], Get2b()[aIndex + 1]);
+ }
+
+ /**
+ * IsLowSurrogateFollowingHighSurrogateAt() returns true if character at
+ * aIndex is low surrogate and it follows high surrogate.
+ */
+ inline bool IsLowSurrogateFollowingHighSurrogateAt(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mState.mLength);
+ if (!mState.mIs2b || !aIndex) {
+ return false;
+ }
+ return NS_IS_SURROGATE_PAIR(Get2b()[aIndex - 1], Get2b()[aIndex]);
+ }
+
+ /**
+ * ScalarValueAt() returns a Unicode scalar value at aIndex. If the character
+ * at aIndex is a high surrogate followed by low surrogate, returns character
+ * code for the pair. If the index is low surrogate, or a high surrogate but
+ * not in a pair, returns 0.
+ */
+ inline char32_t ScalarValueAt(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mState.mLength);
+ if (!mState.mIs2b) {
+ return static_cast<unsigned char>(m1b[aIndex]);
+ }
+ char16_t ch = Get2b()[aIndex];
+ if (!IS_SURROGATE(ch)) {
+ return ch;
+ }
+ if (aIndex + 1 < mState.mLength && NS_IS_HIGH_SURROGATE(ch)) {
+ char16_t nextCh = Get2b()[aIndex + 1];
+ if (NS_IS_LOW_SURROGATE(nextCh)) {
+ return SURROGATE_TO_UCS4(ch, nextCh);
+ }
+ }
+ return 0;
+ }
+
+ void SetBidi(bool aBidi) { mState.mIsBidi = aBidi; }
+
+ struct FragmentBits {
+ // uint32_t to ensure that the values are unsigned, because we
+ // want 0/1, not 0/-1!
+ // Making these bool causes Windows to not actually pack them,
+ // which causes crashes because we assume this structure is no more than
+ // 32 bits!
+ uint32_t mInHeap : 1;
+ uint32_t mIs2b : 1;
+ uint32_t mIsBidi : 1;
+ // Note that when you change the bits of mLength, you also need to change
+ // NS_MAX_TEXT_FRAGMENT_LENGTH.
+ uint32_t mLength : 29;
+ };
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * Check whether the text in this fragment is the same as the text in the
+ * other fragment.
+ */
+ [[nodiscard]] bool TextEquals(const nsTextFragment& aOther) const;
+
+ private:
+ void ReleaseText();
+
+ /**
+ * Scan the contents of the fragment and turn on mState.mIsBidi if it
+ * includes any Bidi characters.
+ */
+ void UpdateBidiFlag(const char16_t* aBuffer, uint32_t aLength);
+
+ union {
+ nsStringBuffer* m2b;
+ const char* m1b; // This is const since it can point to shared data
+ };
+
+ union {
+ uint32_t mAllBits;
+ FragmentBits mState;
+ };
+};
+
+#endif /* nsTextFragment_h___ */
diff --git a/dom/base/nsTextFragmentGeneric.h b/dom/base/nsTextFragmentGeneric.h
new file mode 100644
index 0000000000..407cfec0ea
--- /dev/null
+++ b/dom/base/nsTextFragmentGeneric.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextFragmentGeneric_h__
+#define nsTextFragmentGeneric_h__
+
+#include "nsTextFragmentGenericFwd.h"
+
+#include "nscore.h"
+#include "nsTextFragmentImpl.h"
+#include <algorithm>
+
+namespace mozilla {
+
+template <class Arch>
+int32_t FirstNon8Bit(const char16_t* str, const char16_t* end) {
+ const uint32_t numUnicharsPerVector = xsimd::batch<int16_t, Arch>::size;
+ using p = Non8BitParameters<sizeof(size_t)>;
+ const size_t mask = p::mask();
+ const uint32_t numUnicharsPerWord = p::numUnicharsPerWord();
+ const int32_t len = end - str;
+ int32_t i = 0;
+
+ // Align ourselves to the Arch boundary
+ int32_t alignLen = std::min(
+ len, int32_t(((-NS_PTR_TO_INT32(str)) & (Arch::alignment() - 1)) /
+ sizeof(char16_t)));
+ for (; i < alignLen; i++) {
+ if (str[i] > 255) return i;
+ }
+
+ // Check one batch at a time.
+ const int32_t vectWalkEnd =
+ ((len - i) / numUnicharsPerVector) * numUnicharsPerVector;
+ const uint16_t shortMask = 0xff00;
+ xsimd::batch<int16_t, Arch> vectmask(static_cast<int16_t>(shortMask));
+ for (; i < vectWalkEnd; i += numUnicharsPerVector) {
+ const auto vect = xsimd::batch<int16_t, Arch>::load_aligned(str + i);
+ if (xsimd::any((vect & vectmask) != 0)) return i;
+ }
+
+ // Check one word at a time.
+ const int32_t wordWalkEnd =
+ ((len - i) / numUnicharsPerWord) * numUnicharsPerWord;
+ for (; i < wordWalkEnd; i += numUnicharsPerWord) {
+ const size_t word = *reinterpret_cast<const size_t*>(str + i);
+ if (word & mask) return i;
+ }
+
+ // Take care of the remainder one character at a time.
+ for (; i < len; i++) {
+ if (str[i] > 255) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/nsTextFragmentGenericFwd.h b/dom/base/nsTextFragmentGenericFwd.h
new file mode 100644
index 0000000000..a61b85f202
--- /dev/null
+++ b/dom/base/nsTextFragmentGenericFwd.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextFragmentGenericFwd_h__
+#define nsTextFragmentGenericFwd_h__
+
+#include <cstdint>
+#include <xsimd/xsimd.hpp>
+
+namespace mozilla {
+
+template <class Arch>
+int32_t FirstNon8Bit(const char16_t* str, const char16_t* end);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/nsTextFragmentImpl.h b/dom/base/nsTextFragmentImpl.h
new file mode 100644
index 0000000000..0e22c6f44c
--- /dev/null
+++ b/dom/base/nsTextFragmentImpl.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextFragmentImpl_h__
+#define nsTextFragmentImpl_h__
+
+#include <stdint.h>
+
+template <size_t size>
+struct Non8BitParameters;
+template <>
+struct Non8BitParameters<4> {
+ static inline size_t mask() { return 0xff00ff00; }
+ static inline uint32_t alignMask() { return 0x3; }
+ static inline uint32_t numUnicharsPerWord() { return 2; }
+};
+
+template <>
+struct Non8BitParameters<8> {
+ static inline size_t mask() {
+ static const uint64_t maskAsUint64 = 0xff00ff00ff00ff00ULL;
+ // We have to explicitly cast this 64-bit value to a size_t, or else
+ // compilers for 32-bit platforms will warn about it being too large to fit
+ // in the size_t return type. (Fortunately, this code isn't actually
+ // invoked on 32-bit platforms -- they'll use the <4> specialization above.
+ // So it is, in fact, OK that this value is too large for a 32-bit size_t.)
+ return (size_t)maskAsUint64;
+ }
+ static inline uint32_t alignMask() { return 0x7; }
+ static inline uint32_t numUnicharsPerWord() { return 4; }
+};
+
+#endif
diff --git a/dom/base/nsTextFragmentSSE2.cpp b/dom/base/nsTextFragmentSSE2.cpp
new file mode 100644
index 0000000000..24697d192f
--- /dev/null
+++ b/dom/base/nsTextFragmentSSE2.cpp
@@ -0,0 +1,10 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextFragmentGeneric.h"
+
+namespace mozilla {
+template int32_t FirstNon8Bit<xsimd::sse2>(const char16_t*, const char16_t*);
+} // namespace mozilla
diff --git a/dom/base/nsTextFragmentVMX.cpp b/dom/base/nsTextFragmentVMX.cpp
new file mode 100644
index 0000000000..a071adaee7
--- /dev/null
+++ b/dom/base/nsTextFragmentVMX.cpp
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file should only be compiled if you're on Power ISA.
+
+#include "nscore.h"
+#include "nsAlgorithm.h"
+#include "nsTextFragmentImpl.h"
+#include <algorithm>
+#include <altivec.h>
+
+namespace mozilla {
+namespace VMX {
+
+int32_t FirstNon8Bit(const char16_t* str, const char16_t* end) {
+ const uint32_t numUnicharsPerVector = 8;
+ const uint32_t numCharsPerVector = 16;
+ // Paranoia. If this assertion is wrong, change the vector loop below.
+ MOZ_ASSERT((numCharsPerVector / numUnicharsPerVector) == sizeof(char16_t));
+
+ typedef Non8BitParameters<sizeof(size_t)> p;
+ const uint32_t alignMask = p::alignMask();
+ const size_t mask = p::mask();
+ const uint32_t numUnicharsPerWord = p::numUnicharsPerWord();
+
+ const uint32_t len = end - str;
+
+ // i shall count the index in unichars; i2 shall count the index in chars.
+ uint32_t i = 0;
+ uint32_t i2 = 0;
+
+ // Align ourselves to a 16-byte boundary, as required by VMX loads.
+ uint32_t alignLen = std::min(
+ len, uint32_t(((-NS_PTR_TO_UINT32(str)) & 0xf) / sizeof(char16_t)));
+
+ if ((len - alignLen) >= numUnicharsPerVector) {
+ for (; i < alignLen; i++) {
+ if (str[i] > 255) return i;
+ }
+
+ // Construct a vector of shorts.
+#if __LITTLE_ENDIAN__
+ const vector unsigned short gtcompare =
+ reinterpret_cast<vector unsigned short>(
+ vec_mergel(vec_splat_s8(-1), vec_splat_s8(0)));
+#else
+ const vector unsigned short gtcompare =
+ reinterpret_cast<vector unsigned short>(
+ vec_mergel(vec_splat_s8(0), vec_splat_s8(-1)));
+#endif
+ const uint32_t vectWalkEnd =
+ ((len - i) / numUnicharsPerVector) * numUnicharsPerVector;
+ i2 = i * sizeof(char16_t);
+
+ while (1) {
+ vector unsigned short vect;
+
+ // Check one VMX register (8 unichars) at a time. The vec_any_gt
+ // intrinsic does exactly what we want. This loop is manually unrolled;
+ // it yields notable performance improvements this way.
+#define CheckForASCII \
+ vect = vec_ld(i2, reinterpret_cast<const unsigned short*>(str)); \
+ if (vec_any_gt(vect, gtcompare)) return i; \
+ i += numUnicharsPerVector; \
+ if (!(i < vectWalkEnd)) break; \
+ i2 += numCharsPerVector;
+
+ CheckForASCII CheckForASCII
+
+#undef CheckForASCII
+ }
+ } else {
+ // Align ourselves to a word boundary.
+ alignLen = std::min(len, uint32_t(((-NS_PTR_TO_UINT32(str)) & alignMask) /
+ sizeof(char16_t)));
+ for (; i < alignLen; i++) {
+ if (str[i] > 255) return i;
+ }
+ }
+
+ // Check one word at a time.
+ const uint32_t wordWalkEnd =
+ ((len - i) / numUnicharsPerWord) * numUnicharsPerWord;
+ for (; i < wordWalkEnd; i += numUnicharsPerWord) {
+ const size_t word = *reinterpret_cast<const size_t*>(str + i);
+ if (word & mask) return i;
+ }
+
+ // Take care of the remainder one character at a time.
+ for (; i < len; i++) {
+ if (str[i] > 255) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+} // namespace VMX
+} // namespace mozilla
diff --git a/dom/base/nsTextNode.cpp b/dom/base/nsTextNode.cpp
new file mode 100644
index 0000000000..f21a332012
--- /dev/null
+++ b/dom/base/nsTextNode.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of DOM Core's Text node.
+ */
+
+#include "nsTextNode.h"
+#include "mozilla/dom/TextBinding.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsThreadUtils.h"
+#include "nsStubMutationObserver.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#ifdef MOZ_DOM_LIST
+# include "nsRange.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/**
+ * class used to implement attr() generated content
+ */
+class nsAttributeTextNode final : public nsTextNode,
+ public nsStubMutationObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAttributeTextNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ int32_t aNameSpaceID, nsAtom* aAttrName)
+ : nsTextNode(std::move(aNodeInfo)),
+ mGrandparent(nullptr),
+ mNameSpaceID(aNameSpaceID),
+ mAttrName(aAttrName) {
+ NS_ASSERTION(mNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
+ NS_ASSERTION(mAttrName, "Must have attr name");
+ }
+
+ virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ virtual void UnbindFromTree(bool aNullParent = true) override;
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ virtual already_AddRefed<CharacterData> CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override {
+ RefPtr<nsAttributeTextNode> it = new (aNodeInfo->NodeInfoManager())
+ nsAttributeTextNode(do_AddRef(aNodeInfo), mNameSpaceID, mAttrName);
+ if (aCloneText) {
+ it->mText = mText;
+ }
+
+ return it.forget();
+ }
+
+ // Public method for the event to run
+ void UpdateText() { UpdateText(true); }
+
+ private:
+ virtual ~nsAttributeTextNode() {
+ NS_ASSERTION(!mGrandparent, "We were not unbound!");
+ }
+
+ // Update our text to our parent's current attr value
+ void UpdateText(bool aNotify);
+
+ // This doesn't need to be a strong pointer because it's only non-null
+ // while we're bound to the document tree, and it points to an ancestor
+ // so the ancestor must be bound to the document tree the whole time
+ // and can't be deleted.
+ Element* mGrandparent;
+ // What attribute we're showing
+ int32_t mNameSpaceID;
+ RefPtr<nsAtom> mAttrName;
+};
+
+nsTextNode::~nsTextNode() = default;
+
+// Use the CC variant of this, even though this class does not define
+// a new CC participant, to make QIing to the CC interfaces faster.
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(nsTextNode, CharacterData)
+
+JSObject* nsTextNode::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Text_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<CharacterData> nsTextNode::CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const {
+ RefPtr<nsTextNode> it =
+ new (aNodeInfo->NodeInfoManager()) nsTextNode(do_AddRef(aNodeInfo));
+ if (aCloneText) {
+ it->mText = mText;
+ }
+
+ return it.forget();
+}
+
+nsresult nsTextNode::AppendTextForNormalize(const char16_t* aBuffer,
+ uint32_t aLength, bool aNotify,
+ nsIContent* aNextSibling) {
+ CharacterDataChangeInfo::Details details = {
+ CharacterDataChangeInfo::Details::eMerge, aNextSibling};
+ return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify,
+ &details);
+}
+
+nsresult nsTextNode::BindToTree(BindContext& aContext, nsINode& aParent) {
+ nsresult rv = CharacterData::BindToTree(aContext, aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetDirectionFromNewTextNode(this);
+
+ return NS_OK;
+}
+
+void nsTextNode::UnbindFromTree(bool aNullParent) {
+ ResetDirectionSetByTextNode(this);
+
+ CharacterData::UnbindFromTree(aNullParent);
+}
+
+#ifdef MOZ_DOM_LIST
+void nsTextNode::List(FILE* out, int32_t aIndent) const {
+ int32_t index;
+ for (index = aIndent; --index >= 0;) fputs(" ", out);
+
+ fprintf(out, "Text@%p", static_cast<const void*>(this));
+ fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
+ if (IsClosestCommonInclusiveAncestorForRangeInSelection()) {
+ const LinkedList<AbstractRange>* ranges =
+ GetExistingClosestCommonInclusiveAncestorRanges();
+ uint32_t count = ranges ? ranges->length() : 0;
+ fprintf(out, " ranges:%d", count);
+ }
+ fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
+ fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
+
+ nsAutoString tmp;
+ ToCString(tmp, 0, mText.GetLength());
+ fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
+
+ fputs(">\n", out);
+}
+
+void nsTextNode::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const {
+ if (aDumpAll) {
+ int32_t index;
+ for (index = aIndent; --index >= 0;) fputs(" ", out);
+
+ nsAutoString tmp;
+ ToCString(tmp, 0, mText.GetLength());
+
+ if (!tmp.EqualsLiteral("\\n")) {
+ fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
+ if (aIndent) fputs("\n", out);
+ }
+ }
+}
+#endif
+
+nsresult NS_NewAttributeContent(nsNodeInfoManager* aNodeInfoManager,
+ int32_t aNameSpaceID, nsAtom* aAttrName,
+ nsIContent** aResult) {
+ MOZ_ASSERT(aNodeInfoManager, "Missing nodeInfoManager");
+ MOZ_ASSERT(aAttrName, "Must have an attr name");
+ MOZ_ASSERT(aNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
+
+ *aResult = nullptr;
+
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfoManager->GetTextNodeInfo();
+
+ RefPtr<nsAttributeTextNode> textNode = new (aNodeInfoManager)
+ nsAttributeTextNode(ni.forget(), aNameSpaceID, aAttrName);
+ textNode.forget(aResult);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode, nsTextNode,
+ nsIMutationObserver)
+
+nsresult nsAttributeTextNode::BindToTree(BindContext& aContext,
+ nsINode& aParent) {
+ MOZ_ASSERT(aParent.IsContent() && aParent.GetParent(),
+ "This node can't be a child of the document or of "
+ "the document root");
+
+ nsresult rv = nsTextNode::BindToTree(aContext, aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!mGrandparent, "We were already bound!");
+ mGrandparent = aParent.GetParent()->AsElement();
+ mGrandparent->AddMutationObserver(this);
+
+ // Note that there is no need to notify here, since we have no
+ // frame yet at this point.
+ UpdateText(false);
+
+ return NS_OK;
+}
+
+void nsAttributeTextNode::UnbindFromTree(bool aNullParent) {
+ // UnbindFromTree can be called anytime so we have to be safe.
+ if (mGrandparent) {
+ // aNullParent might not be true here, but we want to remove the
+ // mutation observer anyway since we only need it while we're
+ // in the document.
+ mGrandparent->RemoveMutationObserver(this);
+ mGrandparent = nullptr;
+ }
+ nsTextNode::UnbindFromTree(aNullParent);
+}
+
+void nsAttributeTextNode::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (aNameSpaceID == mNameSpaceID && aAttribute == mAttrName &&
+ aElement == mGrandparent) {
+ // Since UpdateText notifies, do it when it's safe to run script. Note
+ // that if we get unbound while the event is up that's ok -- we'll just
+ // have no grandparent when it fires, and will do nothing.
+ void (nsAttributeTextNode::*update)() = &nsAttributeTextNode::UpdateText;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(
+ "nsAttributeTextNode::AttributeChanged", this, update));
+ }
+}
+
+void nsAttributeTextNode::NodeWillBeDestroyed(nsINode* aNode) {
+ NS_ASSERTION(aNode == static_cast<nsINode*>(mGrandparent), "Wrong node!");
+ mGrandparent = nullptr;
+}
+
+void nsAttributeTextNode::UpdateText(bool aNotify) {
+ if (mGrandparent) {
+ nsAutoString attrValue;
+ mGrandparent->GetAttr(mNameSpaceID, mAttrName, attrValue);
+ SetText(attrValue, aNotify);
+ }
+}
diff --git a/dom/base/nsTextNode.h b/dom/base/nsTextNode.h
new file mode 100644
index 0000000000..23d7aff8a8
--- /dev/null
+++ b/dom/base/nsTextNode.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextNode_h
+#define nsTextNode_h
+
+/*
+ * Implementation of DOM Core's Text node.
+ */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Text.h"
+#include "nsDebug.h"
+
+class nsNodeInfoManager;
+
+/**
+ * Class used to implement DOM text nodes
+ */
+class nsTextNode : public mozilla::dom::Text {
+ private:
+ void Init() {
+ MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE, "Bad NodeType in aNodeInfo");
+ }
+
+ public:
+ explicit nsTextNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : mozilla::dom::Text(std::move(aNodeInfo)) {
+ Init();
+ }
+
+ explicit nsTextNode(nsNodeInfoManager* aNodeInfoManager)
+ : mozilla::dom::Text(aNodeInfoManager->GetTextNodeInfo()) {
+ Init();
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsINode
+ already_AddRefed<CharacterData> CloneDataNode(
+ mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override;
+
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ void UnbindFromTree(bool aNullParent = true) override;
+
+ nsresult AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify, nsIContent* aNextSibling);
+
+#ifdef MOZ_DOM_LIST
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const override;
+#endif
+
+ protected:
+ virtual ~nsTextNode();
+
+ virtual JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+#endif // nsTextNode_h
diff --git a/dom/base/nsTraversal.cpp b/dom/base/nsTraversal.cpp
new file mode 100644
index 0000000000..33582a5e8d
--- /dev/null
+++ b/dom/base/nsTraversal.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTraversal.h"
+
+#include "nsError.h"
+#include "nsINode.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+
+#include "nsGkAtoms.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsTraversal::nsTraversal(nsINode* aRoot, uint32_t aWhatToShow,
+ NodeFilter* aFilter)
+ : mRoot(aRoot),
+ mWhatToShow(aWhatToShow),
+ mFilter(aFilter),
+ mInAcceptNode(false) {
+ NS_ASSERTION(aRoot, "invalid root in call to nsTraversal constructor");
+}
+
+nsTraversal::~nsTraversal() { /* destructor code */
+}
+
+/*
+ * Tests if and how a node should be filtered. Uses mWhatToShow and
+ * mFilter to test the node.
+ * @param aNode Node to test
+ * @param aResult Whether we succeeded
+ * @returns Filtervalue. See NodeFilter.webidl
+ */
+int16_t nsTraversal::TestNode(nsINode* aNode, mozilla::ErrorResult& aResult,
+ nsCOMPtr<nsINode>* aUnskippedNode) {
+ if (mInAcceptNode) {
+ aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return 0;
+ }
+
+ uint16_t nodeType = aNode->NodeType();
+
+ if (nodeType <= 12 && !((1 << (nodeType - 1)) & mWhatToShow)) {
+ return NodeFilter_Binding::FILTER_SKIP;
+ }
+
+ if (aUnskippedNode) {
+ *aUnskippedNode = aNode;
+ }
+
+ if (!mFilter) {
+ // No filter, just accept
+ return NodeFilter_Binding::FILTER_ACCEPT;
+ }
+
+ AutoRestore<bool> inAcceptNode(mInAcceptNode);
+ mInAcceptNode = true;
+ // No need to pass in an execution reason, since the generated default,
+ // "NodeFilter.acceptNode", is pretty much exactly what we'd say anyway.
+ return mFilter->AcceptNode(*aNode, aResult, nullptr,
+ CallbackObject::eRethrowExceptions);
+}
diff --git a/dom/base/nsTraversal.h b/dom/base/nsTraversal.h
new file mode 100644
index 0000000000..801e4a291c
--- /dev/null
+++ b/dom/base/nsTraversal.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of common traversal methods for TreeWalker and NodeIterator.
+ */
+
+#ifndef nsTraversal_h___
+#define nsTraversal_h___
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+
+class nsINode;
+
+namespace mozilla {
+class ErrorResult;
+}
+
+class nsTraversal {
+ public:
+ nsTraversal(nsINode* aRoot, uint32_t aWhatToShow,
+ mozilla::dom::NodeFilter* aFilter);
+ virtual ~nsTraversal();
+
+ protected:
+ nsCOMPtr<nsINode> mRoot;
+ uint32_t mWhatToShow;
+ RefPtr<mozilla::dom::NodeFilter> mFilter;
+ bool mInAcceptNode;
+
+ /*
+ * Tests if and how a node should be filtered. Uses mWhatToShow and
+ * mFilter to test the node.
+ * @param aNode Node to test
+ * @param aResult Whether we succeeded
+ * @param aUnskippedNode If non-null is passed, set to aNode if node isn't
+ * filtered out by mWhatToShow.
+ * @returns Filtervalue. See NodeFilter.webidl
+ */
+ int16_t TestNode(nsINode* aNode, mozilla::ErrorResult& aResult,
+ nsCOMPtr<nsINode>* aUnskippedNode = nullptr);
+};
+
+#endif
diff --git a/dom/base/nsTreeSanitizer.cpp b/dom/base/nsTreeSanitizer.cpp
new file mode 100644
index 0000000000..9f9e256792
--- /dev/null
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -0,0 +1,2603 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTreeSanitizer.h"
+
+#include "mozilla/Algorithm.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BindingStyleRule.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/css/Rule.h"
+#include "mozilla/dom/CSSRuleList.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/SanitizerBinding.h"
+#include "mozilla/dom/ShadowIncludingTreeIterator.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsAtom.h"
+#include "nsCSSPropertyID.h"
+#include "nsHashtablesFwd.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsUnicharInputStream.h"
+#include "nsAttrName.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIParserUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsQueryObject.h"
+
+#include <iterator>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//
+// Thanks to Mark Pilgrim and Sam Ruby for the initial whitelist
+//
+const nsStaticAtom* const kElementsHTML[] = {
+ // clang-format off
+ nsGkAtoms::a,
+ nsGkAtoms::abbr,
+ nsGkAtoms::acronym,
+ nsGkAtoms::address,
+ nsGkAtoms::area,
+ nsGkAtoms::article,
+ nsGkAtoms::aside,
+ nsGkAtoms::audio,
+ nsGkAtoms::b,
+ nsGkAtoms::bdi,
+ nsGkAtoms::bdo,
+ nsGkAtoms::big,
+ nsGkAtoms::blockquote,
+ // body checked specially
+ nsGkAtoms::br,
+ nsGkAtoms::button,
+ nsGkAtoms::canvas,
+ nsGkAtoms::caption,
+ nsGkAtoms::center,
+ nsGkAtoms::cite,
+ nsGkAtoms::code,
+ nsGkAtoms::col,
+ nsGkAtoms::colgroup,
+ nsGkAtoms::data,
+ nsGkAtoms::datalist,
+ nsGkAtoms::dd,
+ nsGkAtoms::del,
+ nsGkAtoms::details,
+ nsGkAtoms::dfn,
+ nsGkAtoms::dialog,
+ nsGkAtoms::dir,
+ nsGkAtoms::div,
+ nsGkAtoms::dl,
+ nsGkAtoms::dt,
+ nsGkAtoms::em,
+ nsGkAtoms::fieldset,
+ nsGkAtoms::figcaption,
+ nsGkAtoms::figure,
+ nsGkAtoms::font,
+ nsGkAtoms::footer,
+ nsGkAtoms::form,
+ nsGkAtoms::h1,
+ nsGkAtoms::h2,
+ nsGkAtoms::h3,
+ nsGkAtoms::h4,
+ nsGkAtoms::h5,
+ nsGkAtoms::h6,
+ // head checked specially
+ nsGkAtoms::header,
+ nsGkAtoms::hgroup,
+ nsGkAtoms::hr,
+ // html checked specially
+ nsGkAtoms::i,
+ nsGkAtoms::img,
+ nsGkAtoms::input,
+ nsGkAtoms::ins,
+ nsGkAtoms::kbd,
+ nsGkAtoms::keygen,
+ nsGkAtoms::label,
+ nsGkAtoms::legend,
+ nsGkAtoms::li,
+ nsGkAtoms::link,
+ nsGkAtoms::listing,
+ nsGkAtoms::main,
+ nsGkAtoms::map,
+ nsGkAtoms::mark,
+ nsGkAtoms::menu,
+ nsGkAtoms::meta,
+ nsGkAtoms::meter,
+ nsGkAtoms::nav,
+ nsGkAtoms::nobr,
+ nsGkAtoms::noscript,
+ nsGkAtoms::ol,
+ nsGkAtoms::optgroup,
+ nsGkAtoms::option,
+ nsGkAtoms::output,
+ nsGkAtoms::p,
+ nsGkAtoms::picture,
+ nsGkAtoms::pre,
+ nsGkAtoms::progress,
+ nsGkAtoms::q,
+ nsGkAtoms::rb,
+ nsGkAtoms::rp,
+ nsGkAtoms::rt,
+ nsGkAtoms::rtc,
+ nsGkAtoms::ruby,
+ nsGkAtoms::s,
+ nsGkAtoms::samp,
+ nsGkAtoms::section,
+ nsGkAtoms::select,
+ nsGkAtoms::small,
+ nsGkAtoms::source,
+ nsGkAtoms::span,
+ nsGkAtoms::strike,
+ nsGkAtoms::strong,
+ nsGkAtoms::sub,
+ nsGkAtoms::summary,
+ nsGkAtoms::sup,
+ // style checked specially
+ nsGkAtoms::table,
+ nsGkAtoms::tbody,
+ nsGkAtoms::td,
+ // template checked and traversed specially
+ nsGkAtoms::textarea,
+ nsGkAtoms::tfoot,
+ nsGkAtoms::th,
+ nsGkAtoms::thead,
+ nsGkAtoms::time,
+ // title checked specially
+ nsGkAtoms::tr,
+ nsGkAtoms::track,
+ nsGkAtoms::tt,
+ nsGkAtoms::u,
+ nsGkAtoms::ul,
+ nsGkAtoms::var,
+ nsGkAtoms::video,
+ nsGkAtoms::wbr,
+ nullptr
+ // clang-format on
+};
+
+const nsStaticAtom* const kAttributesHTML[] = {
+ // clang-format off
+ nsGkAtoms::abbr,
+ nsGkAtoms::accept,
+ nsGkAtoms::acceptcharset,
+ nsGkAtoms::accesskey,
+ nsGkAtoms::action,
+ nsGkAtoms::alt,
+ nsGkAtoms::as,
+ nsGkAtoms::autocomplete,
+ nsGkAtoms::autofocus,
+ nsGkAtoms::autoplay,
+ nsGkAtoms::axis,
+ nsGkAtoms::_char,
+ nsGkAtoms::charoff,
+ nsGkAtoms::charset,
+ nsGkAtoms::checked,
+ nsGkAtoms::cite,
+ nsGkAtoms::_class,
+ nsGkAtoms::cols,
+ nsGkAtoms::colspan,
+ nsGkAtoms::content,
+ nsGkAtoms::contenteditable,
+ nsGkAtoms::contextmenu,
+ nsGkAtoms::controls,
+ nsGkAtoms::coords,
+ nsGkAtoms::crossorigin,
+ nsGkAtoms::datetime,
+ nsGkAtoms::dir,
+ nsGkAtoms::disabled,
+ nsGkAtoms::draggable,
+ nsGkAtoms::enctype,
+ nsGkAtoms::face,
+ nsGkAtoms::_for,
+ nsGkAtoms::frame,
+ nsGkAtoms::headers,
+ nsGkAtoms::height,
+ nsGkAtoms::hidden,
+ nsGkAtoms::high,
+ nsGkAtoms::href,
+ nsGkAtoms::hreflang,
+ nsGkAtoms::icon,
+ nsGkAtoms::id,
+ nsGkAtoms::integrity,
+ nsGkAtoms::ismap,
+ nsGkAtoms::itemid,
+ nsGkAtoms::itemprop,
+ nsGkAtoms::itemref,
+ nsGkAtoms::itemscope,
+ nsGkAtoms::itemtype,
+ nsGkAtoms::kind,
+ nsGkAtoms::label,
+ nsGkAtoms::lang,
+ nsGkAtoms::list_,
+ nsGkAtoms::longdesc,
+ nsGkAtoms::loop,
+ nsGkAtoms::low,
+ nsGkAtoms::max,
+ nsGkAtoms::maxlength,
+ nsGkAtoms::media,
+ nsGkAtoms::method,
+ nsGkAtoms::min,
+ nsGkAtoms::minlength,
+ nsGkAtoms::multiple,
+ nsGkAtoms::muted,
+ nsGkAtoms::name,
+ nsGkAtoms::nohref,
+ nsGkAtoms::novalidate,
+ nsGkAtoms::nowrap,
+ nsGkAtoms::open,
+ nsGkAtoms::optimum,
+ nsGkAtoms::pattern,
+ nsGkAtoms::placeholder,
+ nsGkAtoms::playbackrate,
+ nsGkAtoms::poster,
+ nsGkAtoms::preload,
+ nsGkAtoms::prompt,
+ nsGkAtoms::pubdate,
+ nsGkAtoms::radiogroup,
+ nsGkAtoms::readonly,
+ nsGkAtoms::rel,
+ nsGkAtoms::required,
+ nsGkAtoms::rev,
+ nsGkAtoms::reversed,
+ nsGkAtoms::role,
+ nsGkAtoms::rows,
+ nsGkAtoms::rowspan,
+ nsGkAtoms::rules,
+ nsGkAtoms::scoped,
+ nsGkAtoms::scope,
+ nsGkAtoms::selected,
+ nsGkAtoms::shape,
+ nsGkAtoms::span,
+ nsGkAtoms::spellcheck,
+ nsGkAtoms::src,
+ nsGkAtoms::srclang,
+ nsGkAtoms::start,
+ nsGkAtoms::summary,
+ nsGkAtoms::tabindex,
+ nsGkAtoms::target,
+ nsGkAtoms::title,
+ nsGkAtoms::type,
+ nsGkAtoms::usemap,
+ nsGkAtoms::value,
+ nsGkAtoms::width,
+ nsGkAtoms::wrap,
+ nullptr
+ // clang-format on
+};
+
+const nsStaticAtom* const kPresAttributesHTML[] = {
+ // clang-format off
+ nsGkAtoms::align,
+ nsGkAtoms::background,
+ nsGkAtoms::bgcolor,
+ nsGkAtoms::border,
+ nsGkAtoms::cellpadding,
+ nsGkAtoms::cellspacing,
+ nsGkAtoms::color,
+ nsGkAtoms::compact,
+ nsGkAtoms::clear,
+ nsGkAtoms::hspace,
+ nsGkAtoms::noshade,
+ nsGkAtoms::pointSize,
+ nsGkAtoms::size,
+ nsGkAtoms::valign,
+ nsGkAtoms::vspace,
+ nullptr
+ // clang-format on
+};
+
+// List of HTML attributes with URLs that the
+// browser will fetch. Should be kept in sync with
+// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
+const nsStaticAtom* const kURLAttributesHTML[] = {
+ // clang-format off
+ nsGkAtoms::action,
+ nsGkAtoms::href,
+ nsGkAtoms::src,
+ nsGkAtoms::longdesc,
+ nsGkAtoms::cite,
+ nsGkAtoms::background,
+ nsGkAtoms::formaction,
+ nsGkAtoms::data,
+ nsGkAtoms::ping,
+ nsGkAtoms::poster,
+ nullptr
+ // clang-format on
+};
+
+const nsStaticAtom* const kElementsSVG[] = {
+ nsGkAtoms::a, // a
+ nsGkAtoms::circle, // circle
+ nsGkAtoms::clipPath, // clipPath
+ nsGkAtoms::colorProfile, // color-profile
+ nsGkAtoms::cursor, // cursor
+ nsGkAtoms::defs, // defs
+ nsGkAtoms::desc, // desc
+ nsGkAtoms::ellipse, // ellipse
+ nsGkAtoms::elevation, // elevation
+ nsGkAtoms::erode, // erode
+ nsGkAtoms::ex, // ex
+ nsGkAtoms::exact, // exact
+ nsGkAtoms::exponent, // exponent
+ nsGkAtoms::feBlend, // feBlend
+ nsGkAtoms::feColorMatrix, // feColorMatrix
+ nsGkAtoms::feComponentTransfer, // feComponentTransfer
+ nsGkAtoms::feComposite, // feComposite
+ nsGkAtoms::feConvolveMatrix, // feConvolveMatrix
+ nsGkAtoms::feDiffuseLighting, // feDiffuseLighting
+ nsGkAtoms::feDisplacementMap, // feDisplacementMap
+ nsGkAtoms::feDistantLight, // feDistantLight
+ nsGkAtoms::feDropShadow, // feDropShadow
+ nsGkAtoms::feFlood, // feFlood
+ nsGkAtoms::feFuncA, // feFuncA
+ nsGkAtoms::feFuncB, // feFuncB
+ nsGkAtoms::feFuncG, // feFuncG
+ nsGkAtoms::feFuncR, // feFuncR
+ nsGkAtoms::feGaussianBlur, // feGaussianBlur
+ nsGkAtoms::feImage, // feImage
+ nsGkAtoms::feMerge, // feMerge
+ nsGkAtoms::feMergeNode, // feMergeNode
+ nsGkAtoms::feMorphology, // feMorphology
+ nsGkAtoms::feOffset, // feOffset
+ nsGkAtoms::fePointLight, // fePointLight
+ nsGkAtoms::feSpecularLighting, // feSpecularLighting
+ nsGkAtoms::feSpotLight, // feSpotLight
+ nsGkAtoms::feTile, // feTile
+ nsGkAtoms::feTurbulence, // feTurbulence
+ nsGkAtoms::filter, // filter
+ nsGkAtoms::font, // font
+ nsGkAtoms::font_face, // font-face
+ nsGkAtoms::font_face_format, // font-face-format
+ nsGkAtoms::font_face_name, // font-face-name
+ nsGkAtoms::font_face_src, // font-face-src
+ nsGkAtoms::font_face_uri, // font-face-uri
+ nsGkAtoms::foreignObject, // foreignObject
+ nsGkAtoms::g, // g
+ // glyph
+ nsGkAtoms::glyphRef, // glyphRef
+ // hkern
+ nsGkAtoms::image, // image
+ nsGkAtoms::line, // line
+ nsGkAtoms::linearGradient, // linearGradient
+ nsGkAtoms::marker, // marker
+ nsGkAtoms::mask, // mask
+ nsGkAtoms::metadata, // metadata
+ nsGkAtoms::missingGlyph, // missingGlyph
+ nsGkAtoms::mpath, // mpath
+ nsGkAtoms::path, // path
+ nsGkAtoms::pattern, // pattern
+ nsGkAtoms::polygon, // polygon
+ nsGkAtoms::polyline, // polyline
+ nsGkAtoms::radialGradient, // radialGradient
+ nsGkAtoms::rect, // rect
+ nsGkAtoms::stop, // stop
+ nsGkAtoms::svg, // svg
+ nsGkAtoms::svgSwitch, // switch
+ nsGkAtoms::symbol, // symbol
+ nsGkAtoms::text, // text
+ nsGkAtoms::textPath, // textPath
+ nsGkAtoms::title, // title
+ nsGkAtoms::tref, // tref
+ nsGkAtoms::tspan, // tspan
+ nsGkAtoms::use, // use
+ nsGkAtoms::view, // view
+ // vkern
+ nullptr};
+
+constexpr const nsStaticAtom* const kAttributesSVG[] = {
+ // accent-height
+ nsGkAtoms::accumulate, // accumulate
+ nsGkAtoms::additive, // additive
+ nsGkAtoms::alignment_baseline, // alignment-baseline
+ // alphabetic
+ nsGkAtoms::amplitude, // amplitude
+ // arabic-form
+ // ascent
+ nsGkAtoms::attributeName, // attributeName
+ nsGkAtoms::attributeType, // attributeType
+ nsGkAtoms::azimuth, // azimuth
+ nsGkAtoms::baseFrequency, // baseFrequency
+ nsGkAtoms::baseline_shift, // baseline-shift
+ // baseProfile
+ // bbox
+ nsGkAtoms::begin, // begin
+ nsGkAtoms::bias, // bias
+ nsGkAtoms::by, // by
+ nsGkAtoms::calcMode, // calcMode
+ // cap-height
+ nsGkAtoms::_class, // class
+ nsGkAtoms::clip_path, // clip-path
+ nsGkAtoms::clip_rule, // clip-rule
+ nsGkAtoms::clipPathUnits, // clipPathUnits
+ nsGkAtoms::color, // color
+ nsGkAtoms::colorInterpolation, // color-interpolation
+ nsGkAtoms::colorInterpolationFilters, // color-interpolation-filters
+ nsGkAtoms::cursor, // cursor
+ nsGkAtoms::cx, // cx
+ nsGkAtoms::cy, // cy
+ nsGkAtoms::d, // d
+ // descent
+ nsGkAtoms::diffuseConstant, // diffuseConstant
+ nsGkAtoms::direction, // direction
+ nsGkAtoms::display, // display
+ nsGkAtoms::divisor, // divisor
+ nsGkAtoms::dominant_baseline, // dominant-baseline
+ nsGkAtoms::dur, // dur
+ nsGkAtoms::dx, // dx
+ nsGkAtoms::dy, // dy
+ nsGkAtoms::edgeMode, // edgeMode
+ nsGkAtoms::elevation, // elevation
+ // enable-background
+ nsGkAtoms::end, // end
+ nsGkAtoms::fill, // fill
+ nsGkAtoms::fill_opacity, // fill-opacity
+ nsGkAtoms::fill_rule, // fill-rule
+ nsGkAtoms::filter, // filter
+ nsGkAtoms::filterUnits, // filterUnits
+ nsGkAtoms::flood_color, // flood-color
+ nsGkAtoms::flood_opacity, // flood-opacity
+ // XXX focusable
+ nsGkAtoms::font, // font
+ nsGkAtoms::font_family, // font-family
+ nsGkAtoms::font_size, // font-size
+ nsGkAtoms::font_size_adjust, // font-size-adjust
+ nsGkAtoms::font_stretch, // font-stretch
+ nsGkAtoms::font_style, // font-style
+ nsGkAtoms::font_variant, // font-variant
+ nsGkAtoms::fontWeight, // font-weight
+ nsGkAtoms::format, // format
+ nsGkAtoms::from, // from
+ nsGkAtoms::fx, // fx
+ nsGkAtoms::fy, // fy
+ // g1
+ // g2
+ // glyph-name
+ // glyphRef
+ // glyph-orientation-horizontal
+ // glyph-orientation-vertical
+ nsGkAtoms::gradientTransform, // gradientTransform
+ nsGkAtoms::gradientUnits, // gradientUnits
+ nsGkAtoms::height, // height
+ nsGkAtoms::href,
+ // horiz-adv-x
+ // horiz-origin-x
+ // horiz-origin-y
+ nsGkAtoms::id, // id
+ // ideographic
+ nsGkAtoms::image_rendering, // image-rendering
+ nsGkAtoms::in, // in
+ nsGkAtoms::in2, // in2
+ nsGkAtoms::intercept, // intercept
+ // k
+ nsGkAtoms::k1, // k1
+ nsGkAtoms::k2, // k2
+ nsGkAtoms::k3, // k3
+ nsGkAtoms::k4, // k4
+ // kerning
+ nsGkAtoms::kernelMatrix, // kernelMatrix
+ nsGkAtoms::kernelUnitLength, // kernelUnitLength
+ nsGkAtoms::keyPoints, // keyPoints
+ nsGkAtoms::keySplines, // keySplines
+ nsGkAtoms::keyTimes, // keyTimes
+ nsGkAtoms::lang, // lang
+ // lengthAdjust
+ nsGkAtoms::letter_spacing, // letter-spacing
+ nsGkAtoms::lighting_color, // lighting-color
+ nsGkAtoms::limitingConeAngle, // limitingConeAngle
+ // local
+ nsGkAtoms::marker, // marker
+ nsGkAtoms::marker_end, // marker-end
+ nsGkAtoms::marker_mid, // marker-mid
+ nsGkAtoms::marker_start, // marker-start
+ nsGkAtoms::markerHeight, // markerHeight
+ nsGkAtoms::markerUnits, // markerUnits
+ nsGkAtoms::markerWidth, // markerWidth
+ nsGkAtoms::mask, // mask
+ nsGkAtoms::maskContentUnits, // maskContentUnits
+ nsGkAtoms::maskUnits, // maskUnits
+ // mathematical
+ nsGkAtoms::max, // max
+ nsGkAtoms::media, // media
+ nsGkAtoms::method, // method
+ nsGkAtoms::min, // min
+ nsGkAtoms::mode, // mode
+ nsGkAtoms::name, // name
+ nsGkAtoms::numOctaves, // numOctaves
+ nsGkAtoms::offset, // offset
+ nsGkAtoms::opacity, // opacity
+ nsGkAtoms::_operator, // operator
+ nsGkAtoms::order, // order
+ nsGkAtoms::orient, // orient
+ nsGkAtoms::orientation, // orientation
+ // origin
+ // overline-position
+ // overline-thickness
+ nsGkAtoms::overflow, // overflow
+ // panose-1
+ nsGkAtoms::path, // path
+ nsGkAtoms::pathLength, // pathLength
+ nsGkAtoms::patternContentUnits, // patternContentUnits
+ nsGkAtoms::patternTransform, // patternTransform
+ nsGkAtoms::patternUnits, // patternUnits
+ nsGkAtoms::pointer_events, // pointer-events XXX is this safe?
+ nsGkAtoms::points, // points
+ nsGkAtoms::pointsAtX, // pointsAtX
+ nsGkAtoms::pointsAtY, // pointsAtY
+ nsGkAtoms::pointsAtZ, // pointsAtZ
+ nsGkAtoms::preserveAlpha, // preserveAlpha
+ nsGkAtoms::preserveAspectRatio, // preserveAspectRatio
+ nsGkAtoms::primitiveUnits, // primitiveUnits
+ nsGkAtoms::r, // r
+ nsGkAtoms::radius, // radius
+ nsGkAtoms::refX, // refX
+ nsGkAtoms::refY, // refY
+ nsGkAtoms::repeatCount, // repeatCount
+ nsGkAtoms::repeatDur, // repeatDur
+ nsGkAtoms::requiredExtensions, // requiredExtensions
+ nsGkAtoms::requiredFeatures, // requiredFeatures
+ nsGkAtoms::restart, // restart
+ nsGkAtoms::result, // result
+ nsGkAtoms::rotate, // rotate
+ nsGkAtoms::rx, // rx
+ nsGkAtoms::ry, // ry
+ nsGkAtoms::scale, // scale
+ nsGkAtoms::seed, // seed
+ nsGkAtoms::shape_rendering, // shape-rendering
+ nsGkAtoms::slope, // slope
+ nsGkAtoms::spacing, // spacing
+ nsGkAtoms::specularConstant, // specularConstant
+ nsGkAtoms::specularExponent, // specularExponent
+ nsGkAtoms::spreadMethod, // spreadMethod
+ nsGkAtoms::startOffset, // startOffset
+ nsGkAtoms::stdDeviation, // stdDeviation
+ // stemh
+ // stemv
+ nsGkAtoms::stitchTiles, // stitchTiles
+ nsGkAtoms::stop_color, // stop-color
+ nsGkAtoms::stop_opacity, // stop-opacity
+ // strikethrough-position
+ // strikethrough-thickness
+ nsGkAtoms::string, // string
+ nsGkAtoms::stroke, // stroke
+ nsGkAtoms::stroke_dasharray, // stroke-dasharray
+ nsGkAtoms::stroke_dashoffset, // stroke-dashoffset
+ nsGkAtoms::stroke_linecap, // stroke-linecap
+ nsGkAtoms::stroke_linejoin, // stroke-linejoin
+ nsGkAtoms::stroke_miterlimit, // stroke-miterlimit
+ nsGkAtoms::stroke_opacity, // stroke-opacity
+ nsGkAtoms::stroke_width, // stroke-width
+ nsGkAtoms::surfaceScale, // surfaceScale
+ nsGkAtoms::systemLanguage, // systemLanguage
+ nsGkAtoms::tableValues, // tableValues
+ nsGkAtoms::target, // target
+ nsGkAtoms::targetX, // targetX
+ nsGkAtoms::targetY, // targetY
+ nsGkAtoms::text_anchor, // text-anchor
+ nsGkAtoms::text_decoration, // text-decoration
+ // textLength
+ nsGkAtoms::text_rendering, // text-rendering
+ nsGkAtoms::title, // title
+ nsGkAtoms::to, // to
+ nsGkAtoms::transform, // transform
+ nsGkAtoms::transform_origin, // transform-origin
+ nsGkAtoms::type, // type
+ // u1
+ // u2
+ // underline-position
+ // underline-thickness
+ // unicode
+ nsGkAtoms::unicode_bidi, // unicode-bidi
+ // unicode-range
+ // units-per-em
+ // v-alphabetic
+ // v-hanging
+ // v-ideographic
+ // v-mathematical
+ nsGkAtoms::values, // values
+ nsGkAtoms::vector_effect, // vector-effect
+ // vert-adv-y
+ // vert-origin-x
+ // vert-origin-y
+ nsGkAtoms::viewBox, // viewBox
+ nsGkAtoms::viewTarget, // viewTarget
+ nsGkAtoms::visibility, // visibility
+ nsGkAtoms::width, // width
+ // widths
+ nsGkAtoms::word_spacing, // word-spacing
+ nsGkAtoms::writing_mode, // writing-mode
+ nsGkAtoms::x, // x
+ // x-height
+ nsGkAtoms::x1, // x1
+ nsGkAtoms::x2, // x2
+ nsGkAtoms::xChannelSelector, // xChannelSelector
+ nsGkAtoms::y, // y
+ nsGkAtoms::y1, // y1
+ nsGkAtoms::y2, // y2
+ nsGkAtoms::yChannelSelector, // yChannelSelector
+ nsGkAtoms::z, // z
+ nsGkAtoms::zoomAndPan, // zoomAndPan
+ nullptr};
+
+constexpr const nsStaticAtom* const kURLAttributesSVG[] = {nsGkAtoms::href,
+ nullptr};
+
+static_assert(AllOf(std::begin(kURLAttributesSVG), std::end(kURLAttributesSVG),
+ [](auto aURLAttributeSVG) {
+ return AnyOf(std::begin(kAttributesSVG),
+ std::end(kAttributesSVG),
+ [&](auto aAttributeSVG) {
+ return aAttributeSVG == aURLAttributeSVG;
+ });
+ }));
+
+const nsStaticAtom* const kElementsMathML[] = {
+ nsGkAtoms::abs_, // abs
+ nsGkAtoms::_and, // and
+ nsGkAtoms::annotation_, // annotation
+ nsGkAtoms::annotation_xml_, // annotation-xml
+ nsGkAtoms::apply_, // apply
+ nsGkAtoms::approx_, // approx
+ nsGkAtoms::arccos_, // arccos
+ nsGkAtoms::arccosh_, // arccosh
+ nsGkAtoms::arccot_, // arccot
+ nsGkAtoms::arccoth_, // arccoth
+ nsGkAtoms::arccsc_, // arccsc
+ nsGkAtoms::arccsch_, // arccsch
+ nsGkAtoms::arcsec_, // arcsec
+ nsGkAtoms::arcsech_, // arcsech
+ nsGkAtoms::arcsin_, // arcsin
+ nsGkAtoms::arcsinh_, // arcsinh
+ nsGkAtoms::arctan_, // arctan
+ nsGkAtoms::arctanh_, // arctanh
+ nsGkAtoms::arg_, // arg
+ nsGkAtoms::bind_, // bind
+ nsGkAtoms::bvar_, // bvar
+ nsGkAtoms::card_, // card
+ nsGkAtoms::cartesianproduct_, // cartesianproduct
+ nsGkAtoms::cbytes_, // cbytes
+ nsGkAtoms::ceiling, // ceiling
+ nsGkAtoms::cerror_, // cerror
+ nsGkAtoms::ci_, // ci
+ nsGkAtoms::cn_, // cn
+ nsGkAtoms::codomain_, // codomain
+ nsGkAtoms::complexes_, // complexes
+ nsGkAtoms::compose_, // compose
+ nsGkAtoms::condition_, // condition
+ nsGkAtoms::conjugate_, // conjugate
+ nsGkAtoms::cos_, // cos
+ nsGkAtoms::cosh_, // cosh
+ nsGkAtoms::cot_, // cot
+ nsGkAtoms::coth_, // coth
+ nsGkAtoms::cs_, // cs
+ nsGkAtoms::csc_, // csc
+ nsGkAtoms::csch_, // csch
+ nsGkAtoms::csymbol_, // csymbol
+ nsGkAtoms::curl_, // curl
+ nsGkAtoms::declare, // declare
+ nsGkAtoms::degree_, // degree
+ nsGkAtoms::determinant_, // determinant
+ nsGkAtoms::diff_, // diff
+ nsGkAtoms::divergence_, // divergence
+ nsGkAtoms::divide_, // divide
+ nsGkAtoms::domain_, // domain
+ nsGkAtoms::domainofapplication_, // domainofapplication
+ nsGkAtoms::el, // el
+ nsGkAtoms::emptyset_, // emptyset
+ nsGkAtoms::eq_, // eq
+ nsGkAtoms::equivalent_, // equivalent
+ nsGkAtoms::eulergamma_, // eulergamma
+ nsGkAtoms::exists_, // exists
+ nsGkAtoms::exp_, // exp
+ nsGkAtoms::exponentiale_, // exponentiale
+ nsGkAtoms::factorial_, // factorial
+ nsGkAtoms::factorof_, // factorof
+ nsGkAtoms::_false, // false
+ nsGkAtoms::floor, // floor
+ nsGkAtoms::fn_, // fn
+ nsGkAtoms::forall_, // forall
+ nsGkAtoms::gcd_, // gcd
+ nsGkAtoms::geq_, // geq
+ nsGkAtoms::grad, // grad
+ nsGkAtoms::gt_, // gt
+ nsGkAtoms::ident_, // ident
+ nsGkAtoms::image, // image
+ nsGkAtoms::imaginary_, // imaginary
+ nsGkAtoms::imaginaryi_, // imaginaryi
+ nsGkAtoms::implies_, // implies
+ nsGkAtoms::in, // in
+ nsGkAtoms::infinity, // infinity
+ nsGkAtoms::int_, // int
+ nsGkAtoms::integers_, // integers
+ nsGkAtoms::intersect_, // intersect
+ nsGkAtoms::interval_, // interval
+ nsGkAtoms::inverse_, // inverse
+ nsGkAtoms::lambda_, // lambda
+ nsGkAtoms::laplacian_, // laplacian
+ nsGkAtoms::lcm_, // lcm
+ nsGkAtoms::leq_, // leq
+ nsGkAtoms::limit_, // limit
+ nsGkAtoms::list_, // list
+ nsGkAtoms::ln_, // ln
+ nsGkAtoms::log_, // log
+ nsGkAtoms::logbase_, // logbase
+ nsGkAtoms::lowlimit_, // lowlimit
+ nsGkAtoms::lt_, // lt
+ nsGkAtoms::maction_, // maction
+ nsGkAtoms::maligngroup_, // maligngroup
+ nsGkAtoms::malignmark_, // malignmark
+ nsGkAtoms::math, // math
+ nsGkAtoms::matrix, // matrix
+ nsGkAtoms::matrixrow_, // matrixrow
+ nsGkAtoms::max, // max
+ nsGkAtoms::mean_, // mean
+ nsGkAtoms::median_, // median
+ nsGkAtoms::menclose_, // menclose
+ nsGkAtoms::merror_, // merror
+ nsGkAtoms::mfrac_, // mfrac
+ nsGkAtoms::mglyph_, // mglyph
+ nsGkAtoms::mi_, // mi
+ nsGkAtoms::min, // min
+ nsGkAtoms::minus_, // minus
+ nsGkAtoms::mlabeledtr_, // mlabeledtr
+ nsGkAtoms::mlongdiv_, // mlongdiv
+ nsGkAtoms::mmultiscripts_, // mmultiscripts
+ nsGkAtoms::mn_, // mn
+ nsGkAtoms::mo_, // mo
+ nsGkAtoms::mode, // mode
+ nsGkAtoms::moment_, // moment
+ nsGkAtoms::momentabout_, // momentabout
+ nsGkAtoms::mover_, // mover
+ nsGkAtoms::mpadded_, // mpadded
+ nsGkAtoms::mphantom_, // mphantom
+ nsGkAtoms::mprescripts_, // mprescripts
+ nsGkAtoms::mroot_, // mroot
+ nsGkAtoms::mrow_, // mrow
+ nsGkAtoms::ms_, // ms
+ nsGkAtoms::mscarries_, // mscarries
+ nsGkAtoms::mscarry_, // mscarry
+ nsGkAtoms::msgroup_, // msgroup
+ nsGkAtoms::msline_, // msline
+ nsGkAtoms::mspace_, // mspace
+ nsGkAtoms::msqrt_, // msqrt
+ nsGkAtoms::msrow_, // msrow
+ nsGkAtoms::mstack_, // mstack
+ nsGkAtoms::mstyle_, // mstyle
+ nsGkAtoms::msub_, // msub
+ nsGkAtoms::msubsup_, // msubsup
+ nsGkAtoms::msup_, // msup
+ nsGkAtoms::mtable_, // mtable
+ nsGkAtoms::mtd_, // mtd
+ nsGkAtoms::mtext_, // mtext
+ nsGkAtoms::mtr_, // mtr
+ nsGkAtoms::munder_, // munder
+ nsGkAtoms::munderover_, // munderover
+ nsGkAtoms::naturalnumbers_, // naturalnumbers
+ nsGkAtoms::neq_, // neq
+ nsGkAtoms::none, // none
+ nsGkAtoms::_not, // not
+ nsGkAtoms::notanumber_, // notanumber
+ nsGkAtoms::note_, // note
+ nsGkAtoms::notin_, // notin
+ nsGkAtoms::notprsubset_, // notprsubset
+ nsGkAtoms::notsubset_, // notsubset
+ nsGkAtoms::_or, // or
+ nsGkAtoms::otherwise, // otherwise
+ nsGkAtoms::outerproduct_, // outerproduct
+ nsGkAtoms::partialdiff_, // partialdiff
+ nsGkAtoms::pi_, // pi
+ nsGkAtoms::piece_, // piece
+ nsGkAtoms::piecewise_, // piecewise
+ nsGkAtoms::plus_, // plus
+ nsGkAtoms::power_, // power
+ nsGkAtoms::primes_, // primes
+ nsGkAtoms::product_, // product
+ nsGkAtoms::prsubset_, // prsubset
+ nsGkAtoms::quotient_, // quotient
+ nsGkAtoms::rationals_, // rationals
+ nsGkAtoms::real_, // real
+ nsGkAtoms::reals_, // reals
+ nsGkAtoms::reln_, // reln
+ nsGkAtoms::rem, // rem
+ nsGkAtoms::root_, // root
+ nsGkAtoms::scalarproduct_, // scalarproduct
+ nsGkAtoms::sdev_, // sdev
+ nsGkAtoms::sec_, // sec
+ nsGkAtoms::sech_, // sech
+ nsGkAtoms::selector_, // selector
+ nsGkAtoms::semantics_, // semantics
+ nsGkAtoms::sep_, // sep
+ nsGkAtoms::set, // set
+ nsGkAtoms::setdiff_, // setdiff
+ nsGkAtoms::share_, // share
+ nsGkAtoms::sin_, // sin
+ nsGkAtoms::sinh_, // sinh
+ nsGkAtoms::subset_, // subset
+ nsGkAtoms::sum, // sum
+ nsGkAtoms::tan_, // tan
+ nsGkAtoms::tanh_, // tanh
+ nsGkAtoms::tendsto_, // tendsto
+ nsGkAtoms::times_, // times
+ nsGkAtoms::transpose_, // transpose
+ nsGkAtoms::_true, // true
+ nsGkAtoms::union_, // union
+ nsGkAtoms::uplimit_, // uplimit
+ nsGkAtoms::variance_, // variance
+ nsGkAtoms::vector_, // vector
+ nsGkAtoms::vectorproduct_, // vectorproduct
+ nsGkAtoms::xor_, // xor
+ nullptr};
+
+const nsStaticAtom* const kAttributesMathML[] = {
+ nsGkAtoms::accent_, // accent
+ nsGkAtoms::accentunder_, // accentunder
+ nsGkAtoms::actiontype_, // actiontype
+ nsGkAtoms::align, // align
+ nsGkAtoms::alignmentscope_, // alignmentscope
+ nsGkAtoms::alt, // alt
+ nsGkAtoms::altimg_, // altimg
+ nsGkAtoms::altimg_height_, // altimg-height
+ nsGkAtoms::altimg_valign_, // altimg-valign
+ nsGkAtoms::altimg_width_, // altimg-width
+ nsGkAtoms::background, // background
+ nsGkAtoms::base, // base
+ nsGkAtoms::bevelled_, // bevelled
+ nsGkAtoms::cd_, // cd
+ nsGkAtoms::cdgroup_, // cdgroup
+ nsGkAtoms::charalign_, // charalign
+ nsGkAtoms::close, // close
+ nsGkAtoms::closure_, // closure
+ nsGkAtoms::color, // color
+ nsGkAtoms::columnalign_, // columnalign
+ nsGkAtoms::columnalignment_, // columnalignment
+ nsGkAtoms::columnlines_, // columnlines
+ nsGkAtoms::columnspacing_, // columnspacing
+ nsGkAtoms::columnspan_, // columnspan
+ nsGkAtoms::columnwidth_, // columnwidth
+ nsGkAtoms::crossout_, // crossout
+ nsGkAtoms::decimalpoint_, // decimalpoint
+ nsGkAtoms::definitionURL_, // definitionURL
+ nsGkAtoms::denomalign_, // denomalign
+ nsGkAtoms::depth_, // depth
+ nsGkAtoms::dir, // dir
+ nsGkAtoms::display, // display
+ nsGkAtoms::displaystyle_, // displaystyle
+ nsGkAtoms::edge_, // edge
+ nsGkAtoms::encoding, // encoding
+ nsGkAtoms::equalcolumns_, // equalcolumns
+ nsGkAtoms::equalrows_, // equalrows
+ nsGkAtoms::fence_, // fence
+ nsGkAtoms::fontfamily_, // fontfamily
+ nsGkAtoms::fontsize_, // fontsize
+ nsGkAtoms::fontstyle_, // fontstyle
+ nsGkAtoms::fontweight_, // fontweight
+ nsGkAtoms::form, // form
+ nsGkAtoms::frame, // frame
+ nsGkAtoms::framespacing_, // framespacing
+ nsGkAtoms::groupalign_, // groupalign
+ nsGkAtoms::height, // height
+ nsGkAtoms::href, // href
+ nsGkAtoms::id, // id
+ nsGkAtoms::indentalign_, // indentalign
+ nsGkAtoms::indentalignfirst_, // indentalignfirst
+ nsGkAtoms::indentalignlast_, // indentalignlast
+ nsGkAtoms::indentshift_, // indentshift
+ nsGkAtoms::indentshiftfirst_, // indentshiftfirst
+ nsGkAtoms::indenttarget_, // indenttarget
+ nsGkAtoms::index, // index
+ nsGkAtoms::integer, // integer
+ nsGkAtoms::largeop_, // largeop
+ nsGkAtoms::length, // length
+ nsGkAtoms::linebreak_, // linebreak
+ nsGkAtoms::linebreakmultchar_, // linebreakmultchar
+ nsGkAtoms::linebreakstyle_, // linebreakstyle
+ nsGkAtoms::linethickness_, // linethickness
+ nsGkAtoms::location_, // location
+ nsGkAtoms::longdivstyle_, // longdivstyle
+ nsGkAtoms::lquote_, // lquote
+ nsGkAtoms::lspace_, // lspace
+ nsGkAtoms::ltr, // ltr
+ nsGkAtoms::mathbackground_, // mathbackground
+ nsGkAtoms::mathcolor_, // mathcolor
+ nsGkAtoms::mathsize_, // mathsize
+ nsGkAtoms::mathvariant_, // mathvariant
+ nsGkAtoms::maxsize_, // maxsize
+ nsGkAtoms::minlabelspacing_, // minlabelspacing
+ nsGkAtoms::minsize_, // minsize
+ nsGkAtoms::movablelimits_, // movablelimits
+ nsGkAtoms::msgroup_, // msgroup
+ nsGkAtoms::name, // name
+ nsGkAtoms::newline, // newline
+ nsGkAtoms::notation_, // notation
+ nsGkAtoms::numalign_, // numalign
+ nsGkAtoms::number, // number
+ nsGkAtoms::open, // open
+ nsGkAtoms::order, // order
+ nsGkAtoms::other, // other
+ nsGkAtoms::overflow, // overflow
+ nsGkAtoms::position, // position
+ nsGkAtoms::role, // role
+ nsGkAtoms::rowalign_, // rowalign
+ nsGkAtoms::rowlines_, // rowlines
+ nsGkAtoms::rowspacing_, // rowspacing
+ nsGkAtoms::rowspan, // rowspan
+ nsGkAtoms::rquote_, // rquote
+ nsGkAtoms::rspace_, // rspace
+ nsGkAtoms::schemaLocation_, // schemaLocation
+ nsGkAtoms::scriptlevel_, // scriptlevel
+ nsGkAtoms::scriptminsize_, // scriptminsize
+ nsGkAtoms::scriptsize_, // scriptsize
+ nsGkAtoms::scriptsizemultiplier_, // scriptsizemultiplier
+ nsGkAtoms::selection_, // selection
+ nsGkAtoms::separator_, // separator
+ nsGkAtoms::separators_, // separators
+ nsGkAtoms::shift_, // shift
+ nsGkAtoms::side_, // side
+ nsGkAtoms::src, // src
+ nsGkAtoms::stackalign_, // stackalign
+ nsGkAtoms::stretchy_, // stretchy
+ nsGkAtoms::subscriptshift_, // subscriptshift
+ nsGkAtoms::superscriptshift_, // superscriptshift
+ nsGkAtoms::symmetric_, // symmetric
+ nsGkAtoms::type, // type
+ nsGkAtoms::voffset_, // voffset
+ nsGkAtoms::width, // width
+ nsGkAtoms::xref_, // xref
+ nullptr};
+
+const nsStaticAtom* const kURLAttributesMathML[] = {
+ // clang-format off
+ nsGkAtoms::href,
+ nsGkAtoms::src,
+ nsGkAtoms::cdgroup_,
+ nsGkAtoms::altimg_,
+ nsGkAtoms::definitionURL_,
+ nullptr
+ // clang-format on
+};
+
+// https://wicg.github.io/sanitizer-api/#baseline-attribute-allow-list
+constexpr const nsStaticAtom* const kBaselineAttributeAllowlist[] = {
+ // clang-format off
+ nsGkAtoms::abbr,
+ nsGkAtoms::accept,
+ nsGkAtoms::acceptcharset,
+ nsGkAtoms::charset,
+ nsGkAtoms::accesskey,
+ nsGkAtoms::action,
+ nsGkAtoms::align,
+ nsGkAtoms::alink,
+ nsGkAtoms::allow,
+ nsGkAtoms::allowfullscreen,
+ // nsGkAtoms::allowpaymentrequest,
+ nsGkAtoms::alt,
+ nsGkAtoms::anchor,
+ nsGkAtoms::archive,
+ nsGkAtoms::as,
+ nsGkAtoms::async,
+ nsGkAtoms::autocapitalize,
+ nsGkAtoms::autocomplete,
+ // nsGkAtoms::autocorrect,
+ nsGkAtoms::autofocus,
+ // nsGkAtoms::autopictureinpicture,
+ nsGkAtoms::autoplay,
+ nsGkAtoms::axis,
+ nsGkAtoms::background,
+ nsGkAtoms::behavior,
+ nsGkAtoms::bgcolor,
+ nsGkAtoms::border,
+ nsGkAtoms::bordercolor,
+ nsGkAtoms::capture,
+ nsGkAtoms::cellpadding,
+ nsGkAtoms::cellspacing,
+ // nsGkAtoms::challenge,
+ nsGkAtoms::_char,
+ nsGkAtoms::charoff,
+ nsGkAtoms::charset,
+ nsGkAtoms::checked,
+ nsGkAtoms::cite,
+ nsGkAtoms::_class,
+ nsGkAtoms::classid,
+ nsGkAtoms::clear,
+ nsGkAtoms::code,
+ nsGkAtoms::codebase,
+ nsGkAtoms::codetype,
+ nsGkAtoms::color,
+ nsGkAtoms::cols,
+ nsGkAtoms::colspan,
+ nsGkAtoms::compact,
+ nsGkAtoms::content,
+ nsGkAtoms::contenteditable,
+ nsGkAtoms::controls,
+ // nsGkAtoms::controlslist,
+ // nsGkAtoms::conversiondestination,
+ nsGkAtoms::coords,
+ nsGkAtoms::crossorigin,
+ nsGkAtoms::csp,
+ nsGkAtoms::data,
+ nsGkAtoms::datetime,
+ nsGkAtoms::declare,
+ nsGkAtoms::decoding,
+ nsGkAtoms::_default,
+ nsGkAtoms::defer,
+ nsGkAtoms::dir,
+ nsGkAtoms::direction,
+ // nsGkAtoms::dirname,
+ nsGkAtoms::disabled,
+ // nsGkAtoms::disablepictureinpicture,
+ // nsGkAtoms::disableremoteplayback,
+ // nsGkAtoms::disallowdocumentaccess,
+ nsGkAtoms::download,
+ nsGkAtoms::draggable,
+ // nsGkAtoms::elementtiming,
+ nsGkAtoms::enctype,
+ nsGkAtoms::end,
+ nsGkAtoms::enterkeyhint,
+ nsGkAtoms::event,
+ nsGkAtoms::exportparts,
+ nsGkAtoms::face,
+ nsGkAtoms::_for,
+ nsGkAtoms::form,
+ nsGkAtoms::formaction,
+ nsGkAtoms::formenctype,
+ nsGkAtoms::formmethod,
+ nsGkAtoms::formnovalidate,
+ nsGkAtoms::formtarget,
+ nsGkAtoms::frame,
+ nsGkAtoms::frameborder,
+ nsGkAtoms::headers,
+ nsGkAtoms::height,
+ nsGkAtoms::hidden,
+ nsGkAtoms::high,
+ nsGkAtoms::href,
+ nsGkAtoms::hreflang,
+ // nsGkAtoms::hreftranslate,
+ nsGkAtoms::hspace,
+ nsGkAtoms::http,
+ // nsGkAtoms::equiv,
+ nsGkAtoms::id,
+ nsGkAtoms::imagesizes,
+ nsGkAtoms::imagesrcset,
+ // nsGkAtoms::importance,
+ // nsGkAtoms::impressiondata,
+ // nsGkAtoms::impressionexpiry,
+ // nsGkAtoms::incremental,
+ nsGkAtoms::inert,
+ nsGkAtoms::inputmode,
+ nsGkAtoms::integrity,
+ // nsGkAtoms::invisible,
+ nsGkAtoms::is,
+ nsGkAtoms::ismap,
+ // nsGkAtoms::keytype,
+ nsGkAtoms::kind,
+ nsGkAtoms::label,
+ nsGkAtoms::lang,
+ nsGkAtoms::language,
+ // nsGkAtoms::latencyhint,
+ nsGkAtoms::leftmargin,
+ nsGkAtoms::link,
+ // nsGkAtoms::list,
+ nsGkAtoms::loading,
+ nsGkAtoms::longdesc,
+ nsGkAtoms::loop,
+ nsGkAtoms::low,
+ nsGkAtoms::lowsrc,
+ nsGkAtoms::manifest,
+ nsGkAtoms::marginheight,
+ nsGkAtoms::marginwidth,
+ nsGkAtoms::max,
+ nsGkAtoms::maxlength,
+ // nsGkAtoms::mayscript,
+ nsGkAtoms::media,
+ nsGkAtoms::method,
+ nsGkAtoms::min,
+ nsGkAtoms::minlength,
+ nsGkAtoms::multiple,
+ nsGkAtoms::muted,
+ nsGkAtoms::name,
+ nsGkAtoms::nohref,
+ nsGkAtoms::nomodule,
+ nsGkAtoms::nonce,
+ nsGkAtoms::noresize,
+ nsGkAtoms::noshade,
+ nsGkAtoms::novalidate,
+ nsGkAtoms::nowrap,
+ nsGkAtoms::object,
+ nsGkAtoms::open,
+ nsGkAtoms::optimum,
+ nsGkAtoms::part,
+ nsGkAtoms::pattern,
+ nsGkAtoms::ping,
+ nsGkAtoms::placeholder,
+ // nsGkAtoms::playsinline,
+ // nsGkAtoms::policy,
+ nsGkAtoms::poster,
+ nsGkAtoms::preload,
+ // nsGkAtoms::pseudo,
+ nsGkAtoms::readonly,
+ nsGkAtoms::referrerpolicy,
+ nsGkAtoms::rel,
+ // nsGkAtoms::reportingorigin,
+ nsGkAtoms::required,
+ nsGkAtoms::resources,
+ nsGkAtoms::rev,
+ nsGkAtoms::reversed,
+ nsGkAtoms::role,
+ nsGkAtoms::rows,
+ nsGkAtoms::rowspan,
+ nsGkAtoms::rules,
+ nsGkAtoms::sandbox,
+ nsGkAtoms::scheme,
+ nsGkAtoms::scope,
+ // nsGkAtoms::scopes,
+ nsGkAtoms::scrollamount,
+ nsGkAtoms::scrolldelay,
+ nsGkAtoms::scrolling,
+ nsGkAtoms::select,
+ nsGkAtoms::selected,
+ // nsGkAtoms::shadowroot,
+ // nsGkAtoms::shadowrootdelegatesfocus,
+ nsGkAtoms::shape,
+ nsGkAtoms::size,
+ nsGkAtoms::sizes,
+ nsGkAtoms::slot,
+ nsGkAtoms::span,
+ nsGkAtoms::spellcheck,
+ nsGkAtoms::src,
+ nsGkAtoms::srcdoc,
+ nsGkAtoms::srclang,
+ nsGkAtoms::srcset,
+ nsGkAtoms::standby,
+ nsGkAtoms::start,
+ nsGkAtoms::step,
+ nsGkAtoms::style,
+ nsGkAtoms::summary,
+ nsGkAtoms::tabindex,
+ nsGkAtoms::target,
+ nsGkAtoms::text,
+ nsGkAtoms::title,
+ nsGkAtoms::topmargin,
+ nsGkAtoms::translate,
+ nsGkAtoms::truespeed,
+ // nsGkAtoms::trusttoken,
+ nsGkAtoms::type,
+ nsGkAtoms::usemap,
+ nsGkAtoms::valign,
+ nsGkAtoms::value,
+ nsGkAtoms::valuetype,
+ nsGkAtoms::version,
+ // nsGkAtoms::virtualkeyboardpolicy,
+ nsGkAtoms::vlink,
+ nsGkAtoms::vspace,
+ nsGkAtoms::webkitdirectory,
+ nsGkAtoms::width,
+ nsGkAtoms::wrap,
+ // clang-format on
+};
+
+// https://wicg.github.io/sanitizer-api/#baseline-elements
+constexpr const nsStaticAtom* const kBaselineElementAllowlist[] = {
+ nsGkAtoms::a, nsGkAtoms::abbr, nsGkAtoms::acronym,
+ nsGkAtoms::address, nsGkAtoms::area, nsGkAtoms::article,
+ nsGkAtoms::aside, nsGkAtoms::audio, nsGkAtoms::b,
+ nsGkAtoms::basefont, nsGkAtoms::bdi, nsGkAtoms::bdo,
+ nsGkAtoms::bgsound, nsGkAtoms::big, nsGkAtoms::blockquote,
+ nsGkAtoms::body, nsGkAtoms::br, nsGkAtoms::button,
+ nsGkAtoms::canvas, nsGkAtoms::caption, nsGkAtoms::center,
+ nsGkAtoms::cite, nsGkAtoms::code, nsGkAtoms::col,
+ nsGkAtoms::colgroup, nsGkAtoms::command, nsGkAtoms::data,
+ nsGkAtoms::datalist, nsGkAtoms::dd, nsGkAtoms::del,
+ nsGkAtoms::details, nsGkAtoms::dfn, nsGkAtoms::dialog,
+ nsGkAtoms::dir, nsGkAtoms::div, nsGkAtoms::dl,
+ nsGkAtoms::dt, nsGkAtoms::em, nsGkAtoms::fieldset,
+ nsGkAtoms::figcaption, nsGkAtoms::figure, nsGkAtoms::font,
+ nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1,
+ nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4,
+ nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::head,
+ nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr,
+ nsGkAtoms::html, nsGkAtoms::i, nsGkAtoms::image,
+ nsGkAtoms::img, nsGkAtoms::input, nsGkAtoms::ins,
+ nsGkAtoms::kbd, nsGkAtoms::keygen, nsGkAtoms::label,
+ nsGkAtoms::layer, nsGkAtoms::legend, nsGkAtoms::li,
+ nsGkAtoms::link, nsGkAtoms::listing, nsGkAtoms::main,
+ nsGkAtoms::map, nsGkAtoms::mark, nsGkAtoms::marquee,
+ nsGkAtoms::menu, nsGkAtoms::meta, nsGkAtoms::meter,
+ nsGkAtoms::nav, nsGkAtoms::nobr, nsGkAtoms::ol,
+ nsGkAtoms::optgroup, nsGkAtoms::option, nsGkAtoms::output,
+ nsGkAtoms::p, nsGkAtoms::picture, nsGkAtoms::plaintext,
+ nsGkAtoms::popup, nsGkAtoms::portal, nsGkAtoms::pre,
+ nsGkAtoms::progress, nsGkAtoms::q, nsGkAtoms::rb,
+ nsGkAtoms::rp, nsGkAtoms::rt, nsGkAtoms::rtc,
+ nsGkAtoms::ruby, nsGkAtoms::s, nsGkAtoms::samp,
+ nsGkAtoms::section, nsGkAtoms::select, nsGkAtoms::selectmenu,
+ nsGkAtoms::slot, nsGkAtoms::small, nsGkAtoms::source,
+ nsGkAtoms::span, nsGkAtoms::strike, nsGkAtoms::strong,
+ nsGkAtoms::style, nsGkAtoms::sub, nsGkAtoms::summary,
+ nsGkAtoms::sup, nsGkAtoms::table, nsGkAtoms::tbody,
+ nsGkAtoms::td, nsGkAtoms::_template, nsGkAtoms::textarea,
+ nsGkAtoms::tfoot, nsGkAtoms::th, nsGkAtoms::thead,
+ nsGkAtoms::time, nsGkAtoms::title, nsGkAtoms::tr,
+ nsGkAtoms::track, nsGkAtoms::tt, nsGkAtoms::u,
+ nsGkAtoms::ul, nsGkAtoms::var, nsGkAtoms::video,
+ nsGkAtoms::wbr, nsGkAtoms::xmp,
+};
+
+// https://wicg.github.io/sanitizer-api/#default-configuration
+// default configuration's attribute allow list.
+// Note: Currently all listed attributes are allowed for every element
+// (e.g. they use "*").
+// Compared to kBaselineAttributeAllowlist only deprecated allowpaymentrequest
+// attribute is missing.
+constexpr const nsStaticAtom* const kDefaultConfigurationAttributeAllowlist[] =
+ {
+ nsGkAtoms::abbr,
+ nsGkAtoms::accept,
+ nsGkAtoms::acceptcharset,
+ nsGkAtoms::charset,
+ nsGkAtoms::accesskey,
+ nsGkAtoms::action,
+ nsGkAtoms::align,
+ nsGkAtoms::alink,
+ nsGkAtoms::allow,
+ nsGkAtoms::allowfullscreen,
+ nsGkAtoms::alt,
+ nsGkAtoms::anchor,
+ nsGkAtoms::archive,
+ nsGkAtoms::as,
+ nsGkAtoms::async,
+ nsGkAtoms::autocapitalize,
+ nsGkAtoms::autocomplete,
+ // nsGkAtoms::autocorrect,
+ nsGkAtoms::autofocus,
+ // nsGkAtoms::autopictureinpicture,
+ nsGkAtoms::autoplay,
+ nsGkAtoms::axis,
+ nsGkAtoms::background,
+ nsGkAtoms::behavior,
+ nsGkAtoms::bgcolor,
+ nsGkAtoms::border,
+ nsGkAtoms::bordercolor,
+ nsGkAtoms::capture,
+ nsGkAtoms::cellpadding,
+ nsGkAtoms::cellspacing,
+ // nsGkAtoms::challenge,
+ nsGkAtoms::_char,
+ nsGkAtoms::charoff,
+ nsGkAtoms::charset,
+ nsGkAtoms::checked,
+ nsGkAtoms::cite,
+ nsGkAtoms::_class,
+ nsGkAtoms::classid,
+ nsGkAtoms::clear,
+ nsGkAtoms::code,
+ nsGkAtoms::codebase,
+ nsGkAtoms::codetype,
+ nsGkAtoms::color,
+ nsGkAtoms::cols,
+ nsGkAtoms::colspan,
+ nsGkAtoms::compact,
+ nsGkAtoms::content,
+ nsGkAtoms::contenteditable,
+ nsGkAtoms::controls,
+ // nsGkAtoms::controlslist,
+ // nsGkAtoms::conversiondestination,
+ nsGkAtoms::coords,
+ nsGkAtoms::crossorigin,
+ nsGkAtoms::csp,
+ nsGkAtoms::data,
+ nsGkAtoms::datetime,
+ nsGkAtoms::declare,
+ nsGkAtoms::decoding,
+ nsGkAtoms::_default,
+ nsGkAtoms::defer,
+ nsGkAtoms::dir,
+ nsGkAtoms::direction,
+ // nsGkAtoms::dirname,
+ nsGkAtoms::disabled,
+ // nsGkAtoms::disablepictureinpicture,
+ // nsGkAtoms::disableremoteplayback,
+ // nsGkAtoms::disallowdocumentaccess,
+ nsGkAtoms::download,
+ nsGkAtoms::draggable,
+ // nsGkAtoms::elementtiming,
+ nsGkAtoms::enctype,
+ nsGkAtoms::end,
+ nsGkAtoms::enterkeyhint,
+ nsGkAtoms::event,
+ nsGkAtoms::exportparts,
+ nsGkAtoms::face,
+ nsGkAtoms::_for,
+ nsGkAtoms::form,
+ nsGkAtoms::formaction,
+ nsGkAtoms::formenctype,
+ nsGkAtoms::formmethod,
+ nsGkAtoms::formnovalidate,
+ nsGkAtoms::formtarget,
+ nsGkAtoms::frame,
+ nsGkAtoms::frameborder,
+ nsGkAtoms::headers,
+ nsGkAtoms::height,
+ nsGkAtoms::hidden,
+ nsGkAtoms::high,
+ nsGkAtoms::href,
+ nsGkAtoms::hreflang,
+ // nsGkAtoms::hreftranslate,
+ nsGkAtoms::hspace,
+ nsGkAtoms::http,
+ // nsGkAtoms::equiv,
+ nsGkAtoms::id,
+ nsGkAtoms::imagesizes,
+ nsGkAtoms::imagesrcset,
+ // nsGkAtoms::importance,
+ // nsGkAtoms::impressiondata,
+ // nsGkAtoms::impressionexpiry,
+ // nsGkAtoms::incremental,
+ nsGkAtoms::inert,
+ nsGkAtoms::inputmode,
+ nsGkAtoms::integrity,
+ // nsGkAtoms::invisible,
+ nsGkAtoms::is,
+ nsGkAtoms::ismap,
+ // nsGkAtoms::keytype,
+ nsGkAtoms::kind,
+ nsGkAtoms::label,
+ nsGkAtoms::lang,
+ nsGkAtoms::language,
+ // nsGkAtoms::latencyhint,
+ nsGkAtoms::leftmargin,
+ nsGkAtoms::link,
+ // nsGkAtoms::list,
+ nsGkAtoms::loading,
+ nsGkAtoms::longdesc,
+ nsGkAtoms::loop,
+ nsGkAtoms::low,
+ nsGkAtoms::lowsrc,
+ nsGkAtoms::manifest,
+ nsGkAtoms::marginheight,
+ nsGkAtoms::marginwidth,
+ nsGkAtoms::max,
+ nsGkAtoms::maxlength,
+ // nsGkAtoms::mayscript,
+ nsGkAtoms::media,
+ nsGkAtoms::method,
+ nsGkAtoms::min,
+ nsGkAtoms::minlength,
+ nsGkAtoms::multiple,
+ nsGkAtoms::muted,
+ nsGkAtoms::name,
+ nsGkAtoms::nohref,
+ nsGkAtoms::nomodule,
+ nsGkAtoms::nonce,
+ nsGkAtoms::noresize,
+ nsGkAtoms::noshade,
+ nsGkAtoms::novalidate,
+ nsGkAtoms::nowrap,
+ nsGkAtoms::object,
+ nsGkAtoms::open,
+ nsGkAtoms::optimum,
+ nsGkAtoms::part,
+ nsGkAtoms::pattern,
+ nsGkAtoms::ping,
+ nsGkAtoms::placeholder,
+ // nsGkAtoms::playsinline,
+ // nsGkAtoms::policy,
+ nsGkAtoms::poster,
+ nsGkAtoms::preload,
+ // nsGkAtoms::pseudo,
+ nsGkAtoms::readonly,
+ nsGkAtoms::referrerpolicy,
+ nsGkAtoms::rel,
+ // nsGkAtoms::reportingorigin,
+ nsGkAtoms::required,
+ nsGkAtoms::resources,
+ nsGkAtoms::rev,
+ nsGkAtoms::reversed,
+ nsGkAtoms::role,
+ nsGkAtoms::rows,
+ nsGkAtoms::rowspan,
+ nsGkAtoms::rules,
+ nsGkAtoms::sandbox,
+ nsGkAtoms::scheme,
+ nsGkAtoms::scope,
+ // nsGkAtoms::scopes,
+ nsGkAtoms::scrollamount,
+ nsGkAtoms::scrolldelay,
+ nsGkAtoms::scrolling,
+ nsGkAtoms::select,
+ nsGkAtoms::selected,
+ // nsGkAtoms::shadowroot,
+ // nsGkAtoms::shadowrootdelegatesfocus,
+ nsGkAtoms::shape,
+ nsGkAtoms::size,
+ nsGkAtoms::sizes,
+ nsGkAtoms::slot,
+ nsGkAtoms::span,
+ nsGkAtoms::spellcheck,
+ nsGkAtoms::src,
+ nsGkAtoms::srcdoc,
+ nsGkAtoms::srclang,
+ nsGkAtoms::srcset,
+ nsGkAtoms::standby,
+ nsGkAtoms::start,
+ nsGkAtoms::step,
+ nsGkAtoms::style,
+ nsGkAtoms::summary,
+ nsGkAtoms::tabindex,
+ nsGkAtoms::target,
+ nsGkAtoms::text,
+ nsGkAtoms::title,
+ nsGkAtoms::topmargin,
+ nsGkAtoms::translate,
+ nsGkAtoms::truespeed,
+ // nsGkAtoms::trusttoken,
+ nsGkAtoms::type,
+ nsGkAtoms::usemap,
+ nsGkAtoms::valign,
+ nsGkAtoms::value,
+ nsGkAtoms::valuetype,
+ nsGkAtoms::version,
+ // nsGkAtoms::virtualkeyboardpolicy,
+ nsGkAtoms::vlink,
+ nsGkAtoms::vspace,
+ nsGkAtoms::webkitdirectory,
+ nsGkAtoms::width,
+ nsGkAtoms::wrap,
+};
+
+// https://wicg.github.io/sanitizer-api/#default-configuration
+// default configuration's element allow list.
+constexpr const nsStaticAtom* const kDefaultConfigurationElementAllowlist[] = {
+ nsGkAtoms::a, nsGkAtoms::abbr, nsGkAtoms::acronym,
+ nsGkAtoms::address, nsGkAtoms::area, nsGkAtoms::article,
+ nsGkAtoms::aside, nsGkAtoms::audio, nsGkAtoms::b,
+ nsGkAtoms::bdi, nsGkAtoms::bdo, nsGkAtoms::bgsound,
+ nsGkAtoms::big, nsGkAtoms::blockquote, nsGkAtoms::body,
+ nsGkAtoms::br, nsGkAtoms::button, nsGkAtoms::canvas,
+ nsGkAtoms::caption, nsGkAtoms::center, nsGkAtoms::cite,
+ nsGkAtoms::code, nsGkAtoms::col, nsGkAtoms::colgroup,
+ nsGkAtoms::datalist, nsGkAtoms::dd, nsGkAtoms::del,
+ nsGkAtoms::details, nsGkAtoms::dfn, nsGkAtoms::dialog,
+ nsGkAtoms::dir, nsGkAtoms::div, nsGkAtoms::dl,
+ nsGkAtoms::dt, nsGkAtoms::em, nsGkAtoms::fieldset,
+ nsGkAtoms::figcaption, nsGkAtoms::figure, nsGkAtoms::font,
+ nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1,
+ nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4,
+ nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::head,
+ nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr,
+ nsGkAtoms::html, nsGkAtoms::i, nsGkAtoms::img,
+ nsGkAtoms::input, nsGkAtoms::ins, nsGkAtoms::kbd,
+ nsGkAtoms::keygen, nsGkAtoms::label, nsGkAtoms::layer,
+ nsGkAtoms::legend, nsGkAtoms::li, nsGkAtoms::link,
+ nsGkAtoms::listing, nsGkAtoms::main, nsGkAtoms::map,
+ nsGkAtoms::mark, nsGkAtoms::marquee, nsGkAtoms::menu,
+ nsGkAtoms::meta, nsGkAtoms::meter, nsGkAtoms::nav,
+ nsGkAtoms::nobr, nsGkAtoms::ol, nsGkAtoms::optgroup,
+ nsGkAtoms::option, nsGkAtoms::output, nsGkAtoms::p,
+ nsGkAtoms::picture, nsGkAtoms::popup, nsGkAtoms::pre,
+ nsGkAtoms::progress, nsGkAtoms::q, nsGkAtoms::rb,
+ nsGkAtoms::rp, nsGkAtoms::rt, nsGkAtoms::rtc,
+ nsGkAtoms::ruby, nsGkAtoms::s, nsGkAtoms::samp,
+ nsGkAtoms::section, nsGkAtoms::select, nsGkAtoms::selectmenu,
+ nsGkAtoms::small, nsGkAtoms::source, nsGkAtoms::span,
+ nsGkAtoms::strike, nsGkAtoms::strong, nsGkAtoms::style,
+ nsGkAtoms::sub, nsGkAtoms::summary, nsGkAtoms::sup,
+ nsGkAtoms::table, nsGkAtoms::tbody, nsGkAtoms::td,
+ nsGkAtoms::tfoot, nsGkAtoms::th, nsGkAtoms::thead,
+ nsGkAtoms::time, nsGkAtoms::tr, nsGkAtoms::track,
+ nsGkAtoms::tt, nsGkAtoms::u, nsGkAtoms::ul,
+ nsGkAtoms::var, nsGkAtoms::video, nsGkAtoms::wbr,
+};
+
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsHTML = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesHTML = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sPresAttributesHTML = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsSVG = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesSVG = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsMathML = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesMathML = nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sBaselineAttributeAllowlist =
+ nullptr;
+nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sBaselineElementAllowlist =
+ nullptr;
+nsTreeSanitizer::AtomsTable*
+ nsTreeSanitizer::sDefaultConfigurationAttributeAllowlist = nullptr;
+nsTreeSanitizer::AtomsTable*
+ nsTreeSanitizer::sDefaultConfigurationElementAllowlist = nullptr;
+nsIPrincipal* nsTreeSanitizer::sNullPrincipal = nullptr;
+
+nsTreeSanitizer::nsTreeSanitizer(uint32_t aFlags)
+ : mAllowStyles(aFlags & nsIParserUtils::SanitizerAllowStyle),
+ mAllowComments(aFlags & nsIParserUtils::SanitizerAllowComments),
+ mDropNonCSSPresentation(aFlags &
+ nsIParserUtils::SanitizerDropNonCSSPresentation),
+ mDropForms(aFlags & nsIParserUtils::SanitizerDropForms),
+ mCidEmbedsOnly(aFlags & nsIParserUtils::SanitizerCidEmbedsOnly),
+ mDropMedia(aFlags & nsIParserUtils::SanitizerDropMedia),
+ mFullDocument(false),
+ mLogRemovals(aFlags & nsIParserUtils::SanitizerLogRemovals) {
+ if (mCidEmbedsOnly) {
+ // Sanitizing styles for external references is not supported.
+ mAllowStyles = false;
+ }
+
+ if (!sElementsHTML) {
+ // Initialize lazily to avoid having to initialize at all if the user
+ // doesn't paste HTML or load feeds.
+ InitializeStatics();
+ }
+}
+
+bool nsTreeSanitizer::MustFlatten(int32_t aNamespace, nsAtom* aLocal) {
+ if (mIsForSanitizerAPI) {
+ return MustFlattenForSanitizerAPI(aNamespace, aLocal);
+ }
+
+ if (aNamespace == kNameSpaceID_XHTML) {
+ if (mDropNonCSSPresentation &&
+ (nsGkAtoms::font == aLocal || nsGkAtoms::center == aLocal)) {
+ return true;
+ }
+ if (mDropForms &&
+ (nsGkAtoms::form == aLocal || nsGkAtoms::input == aLocal ||
+ nsGkAtoms::option == aLocal || nsGkAtoms::optgroup == aLocal)) {
+ return true;
+ }
+ if (mFullDocument &&
+ (nsGkAtoms::title == aLocal || nsGkAtoms::html == aLocal ||
+ nsGkAtoms::head == aLocal || nsGkAtoms::body == aLocal)) {
+ return false;
+ }
+ if (nsGkAtoms::_template == aLocal) {
+ return false;
+ }
+ return !sElementsHTML->Contains(aLocal);
+ }
+ if (aNamespace == kNameSpaceID_SVG) {
+ if (mCidEmbedsOnly || mDropMedia) {
+ // Sanitizing CSS-based URL references inside SVG presentational
+ // attributes is not supported, so flattening for cid: embed case.
+ return true;
+ }
+ return !sElementsSVG->Contains(aLocal);
+ }
+ if (aNamespace == kNameSpaceID_MathML) {
+ return !sElementsMathML->Contains(aLocal);
+ }
+ return true;
+}
+
+bool nsTreeSanitizer::MustFlattenForSanitizerAPI(int32_t aNamespace,
+ nsAtom* aLocal) {
+ // This implements everything in
+ // https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-element that
+ // is supposed to be blocked.
+
+ // Step 6. If element matches any name in config["blockElements"]: Return
+ // block.
+ if (mBlockElements &&
+ MatchesElementName(*mBlockElements, aNamespace, aLocal)) {
+ return true;
+ }
+
+ // Step 7. Let allow list be null.
+ // Step 8. If "allowElements" exists in config:
+ // Step 8.1. Then : Set allow list to config["allowElements"].
+ if (mAllowElements) {
+ // Step 9. If element does not match any name in allow list:
+ // Return block.
+ if (!MatchesElementName(*mAllowElements, aNamespace, aLocal)) {
+ return true;
+ }
+ } else {
+ // Step 8.2. Otherwise: Set allow list to the default configuration's
+ // element allow list.
+
+ // Step 9. If element does not match any name in allow list:
+ // Return block.
+
+ // The default configuration only contains HTML elements, so we can
+ // reject everything else.
+ if (aNamespace != kNameSpaceID_XHTML ||
+ !sDefaultConfigurationElementAllowlist->Contains(aLocal)) {
+ return true;
+ }
+ }
+
+ // Step 10. Return keep.
+ return false;
+}
+
+bool nsTreeSanitizer::IsURL(const nsStaticAtom* const* aURLs,
+ nsAtom* aLocalName) {
+ const nsStaticAtom* atom;
+ while ((atom = *aURLs)) {
+ if (atom == aLocalName) {
+ return true;
+ }
+ ++aURLs;
+ }
+ return false;
+}
+
+bool nsTreeSanitizer::MustPrune(int32_t aNamespace, nsAtom* aLocal,
+ mozilla::dom::Element* aElement) {
+ if (mIsForSanitizerAPI) {
+ return MustPruneForSanitizerAPI(aNamespace, aLocal, aElement);
+ }
+
+ // To avoid attacks where a MathML script becomes something that gets
+ // serialized in a way that it parses back as an HTML script, let's just
+ // drop elements with the local name 'script' regardless of namespace.
+ if (nsGkAtoms::script == aLocal) {
+ return true;
+ }
+ if (aNamespace == kNameSpaceID_XHTML) {
+ if (nsGkAtoms::title == aLocal && !mFullDocument) {
+ // emulate the quirks of the old parser
+ return true;
+ }
+ if (mDropForms &&
+ (nsGkAtoms::select == aLocal || nsGkAtoms::button == aLocal ||
+ nsGkAtoms::datalist == aLocal)) {
+ return true;
+ }
+ if (mDropMedia &&
+ (nsGkAtoms::img == aLocal || nsGkAtoms::video == aLocal ||
+ nsGkAtoms::audio == aLocal || nsGkAtoms::source == aLocal)) {
+ return true;
+ }
+ if (nsGkAtoms::meta == aLocal &&
+ (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::charset) ||
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv))) {
+ // Throw away charset declarations even if they also have microdata
+ // which they can't validly have.
+ return true;
+ }
+ if (((!mFullDocument && nsGkAtoms::meta == aLocal) ||
+ nsGkAtoms::link == aLocal) &&
+ !(aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::itemprop) ||
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::itemscope))) {
+ // emulate old behavior for non-Microdata <meta> and <link> presumably
+ // in <head>. <meta> and <link> are whitelisted in order to avoid
+ // corrupting Microdata when they appear in <body>. Note that
+ // SanitizeAttributes() will remove the rel attribute from <link> and
+ // the name attribute from <meta>.
+ return true;
+ }
+ }
+ if (mAllowStyles) {
+ return nsGkAtoms::style == aLocal && !(aNamespace == kNameSpaceID_XHTML ||
+ aNamespace == kNameSpaceID_SVG);
+ }
+ if (nsGkAtoms::style == aLocal) {
+ return true;
+ }
+ return false;
+}
+
+enum class ElementKind {
+ Regular,
+ Custom,
+ Unknown,
+};
+
+// https://wicg.github.io/sanitizer-api/#element-kind
+static ElementKind GetElementKind(int32_t aNamespace, nsAtom* aLocal,
+ Element* aElement) {
+ // XXX(bug 1782926) The spec for this is known to be wrong.
+ // https://github.com/WICG/sanitizer-api/issues/147
+
+ // custom, if element’s local name is a valid custom element name,
+ // XXX shouldn't this happen after unknown.
+ if (nsContentUtils::IsCustomElementName(aLocal, kNameSpaceID_XHTML)) {
+ return ElementKind::Custom;
+ }
+
+ // unknown, if element is not in the [HTML] namespace
+ // XXX this doesn't really make sense to me
+ // https://github.com/WICG/sanitizer-api/issues/167
+ if (aNamespace != kNameSpaceID_XHTML) {
+ return ElementKind::Unknown;
+ }
+
+ // or if element’s local name denotes an unknown element
+ // — that is, if the element interface the [HTML] specification assigns to it
+ // would be HTMLUnknownElement,
+ if (nsCOMPtr<HTMLUnknownElement> el = do_QueryInterface(aElement)) {
+ return ElementKind::Unknown;
+ }
+
+ // regular, otherwise.
+ return ElementKind::Regular;
+}
+
+bool nsTreeSanitizer::MustPruneForSanitizerAPI(int32_t aNamespace,
+ nsAtom* aLocal,
+ Element* aElement) {
+ // This implements everything in
+ // https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-element that
+ // is supposed to be dropped.
+
+ // Step 1. Let kind be element’s element kind.
+ ElementKind kind = GetElementKind(aNamespace, aLocal, aElement);
+
+ switch (kind) {
+ case ElementKind::Regular:
+ // Step 2. If kind is regular and element does not match any name in the
+ // baseline element allow list: Return drop.
+ if (!sBaselineElementAllowlist->Contains(aLocal)) {
+ return true;
+ }
+ break;
+
+ case ElementKind::Custom:
+ // Step 3. If kind is custom and if config["allowCustomElements"] does not
+ // exist or if config["allowCustomElements"] is false: Return drop.
+ if (!mAllowCustomElements) {
+ return true;
+ }
+ break;
+
+ case ElementKind::Unknown:
+ // Step 4. If kind is unknown and if config["allowUnknownMarkup"] does not
+ // exist or it config["allowUnknownMarkup"] is false: Return drop.
+ if (!mAllowUnknownMarkup) {
+ return true;
+ }
+ break;
+ }
+
+ // Step 5. If element matches any name in config["dropElements"]: Return drop.
+ if (mDropElements && MatchesElementName(*mDropElements, aNamespace, aLocal)) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Parses a style sheet and reserializes it with unsafe styles removed.
+ *
+ * @param aOriginal the original style sheet source
+ * @param aSanitized the reserialization without dangerous CSS.
+ * @param aDocument the document the style sheet belongs to
+ * @param aBaseURI the base URI to use
+ * @param aSanitizationKind the kind of style sanitization to use.
+ */
+static void SanitizeStyleSheet(const nsAString& aOriginal,
+ nsAString& aSanitized, Document* aDocument,
+ nsIURI* aBaseURI,
+ StyleSanitizationKind aSanitizationKind) {
+ aSanitized.Truncate();
+
+ NS_ConvertUTF16toUTF8 style(aOriginal);
+ nsIReferrerInfo* referrer =
+ aDocument->ReferrerInfoForInternalCSSAndSVGResources();
+ auto extraData =
+ MakeRefPtr<URLExtraData>(aBaseURI, referrer, aDocument->NodePrincipal());
+ RefPtr<StyleStylesheetContents> contents =
+ Servo_StyleSheet_FromUTF8Bytes(
+ /* loader = */ nullptr,
+ /* stylesheet = */ nullptr,
+ /* load_data = */ nullptr, &style,
+ css::SheetParsingMode::eAuthorSheetFeatures, extraData.get(),
+ /* line_number_offset = */ 0, aDocument->GetCompatibilityMode(),
+ /* reusable_sheets = */ nullptr,
+ /* use_counters = */ nullptr, StyleAllowImportRules::Yes,
+ aSanitizationKind, &aSanitized)
+ .Consume();
+}
+
+bool nsTreeSanitizer::SanitizeInlineStyle(
+ Element* aElement, StyleSanitizationKind aSanitizationKind) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aElement->IsHTMLElement(nsGkAtoms::style) ||
+ aElement->IsSVGElement(nsGkAtoms::style));
+
+ nsAutoString styleText;
+ nsContentUtils::GetNodeTextContent(aElement, false, styleText);
+
+ nsAutoString sanitizedStyle;
+ SanitizeStyleSheet(styleText, sanitizedStyle, aElement->OwnerDoc(),
+ aElement->GetBaseURI(), StyleSanitizationKind::Standard);
+ RemoveAllAttributesFromDescendants(aElement);
+ nsContentUtils::SetNodeTextContent(aElement, sanitizedStyle, true);
+
+ return sanitizedStyle.Length() != styleText.Length();
+}
+
+void nsTreeSanitizer::RemoveConditionalCSSFromSubtree(nsINode* aRoot) {
+ AutoTArray<RefPtr<nsINode>, 10> nodesToSanitize;
+ for (nsINode* node : ShadowIncludingTreeIterator(*aRoot)) {
+ if (node->IsHTMLElement(nsGkAtoms::style) ||
+ node->IsSVGElement(nsGkAtoms::style)) {
+ nodesToSanitize.AppendElement(node);
+ }
+ }
+ for (nsINode* node : nodesToSanitize) {
+ SanitizeInlineStyle(node->AsElement(),
+ StyleSanitizationKind::NoConditionalRules);
+ }
+}
+
+template <size_t Len>
+static bool UTF16StringStartsWith(const char16_t* aStr, uint32_t aLength,
+ const char16_t (&aNeedle)[Len]) {
+ MOZ_ASSERT(aNeedle[Len - 1] == '\0',
+ "needle should be a UTF-16 encoded string literal");
+
+ if (aLength < Len - 1) {
+ return false;
+ }
+ for (size_t i = 0; i < Len - 1; i++) {
+ if (aStr[i] != aNeedle[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void nsTreeSanitizer::SanitizeAttributes(mozilla::dom::Element* aElement,
+ AllowedAttributes aAllowed) {
+ int32_t ac = (int)aElement->GetAttrCount();
+
+ for (int32_t i = ac - 1; i >= 0; --i) {
+ const nsAttrName* attrName = aElement->GetAttrNameAt(i);
+ int32_t attrNs = attrName->NamespaceID();
+ RefPtr<nsAtom> attrLocal = attrName->LocalName();
+
+ if (mIsForSanitizerAPI) {
+ if (MustDropAttribute(aElement, attrNs, attrLocal) ||
+ MustDropFunkyAttribute(aElement, attrNs, attrLocal)) {
+ aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false);
+ if (mLogRemovals) {
+ LogMessage("Removed unsafe attribute.", aElement->OwnerDoc(),
+ aElement, attrLocal);
+ }
+
+ // in case the attribute removal shuffled the attribute order, start
+ // the loop again.
+ --ac;
+ i = ac; // i will be decremented immediately thanks to the for loop
+ }
+ continue;
+ }
+
+ if (kNameSpaceID_None == attrNs) {
+ if (aAllowed.mStyle && nsGkAtoms::style == attrLocal) {
+ continue;
+ }
+ if (aAllowed.mDangerousSrc && nsGkAtoms::src == attrLocal) {
+ continue;
+ }
+ if (IsURL(aAllowed.mURLs, attrLocal)) {
+ bool fragmentOnly = aElement->IsSVGElement(nsGkAtoms::use);
+ if (SanitizeURL(aElement, attrNs, attrLocal, fragmentOnly)) {
+ // in case the attribute removal shuffled the attribute order, start
+ // the loop again.
+ --ac;
+ i = ac; // i will be decremented immediately thanks to the for loop
+ continue;
+ }
+ // else fall through to see if there's another reason to drop this
+ // attribute (in particular if the attribute is background="" on an
+ // HTML element)
+ }
+ if (!mDropNonCSSPresentation &&
+ (aAllowed.mNames == sAttributesHTML) && // element is HTML
+ sPresAttributesHTML->Contains(attrLocal)) {
+ continue;
+ }
+ if (aAllowed.mNames->Contains(attrLocal) &&
+ !((attrLocal == nsGkAtoms::rel &&
+ aElement->IsHTMLElement(nsGkAtoms::link)) ||
+ (!mFullDocument && attrLocal == nsGkAtoms::name &&
+ aElement->IsHTMLElement(nsGkAtoms::meta)))) {
+ // name="" and rel="" are whitelisted, but treat them as blacklisted
+ // for <meta name> (fragment case) and <link rel> (all cases) to avoid
+ // document-wide metadata or styling overrides with non-conforming
+ // <meta name itemprop> or
+ // <link rel itemprop>
+ continue;
+ }
+ const char16_t* localStr = attrLocal->GetUTF16String();
+ uint32_t localLen = attrLocal->GetLength();
+ // Allow underscore to cater to the MCE editor library.
+ // Allow data-* on SVG and MathML, too, as a forward-compat measure.
+ // Allow aria-* on all for simplicity.
+ if (UTF16StringStartsWith(localStr, localLen, u"_") ||
+ UTF16StringStartsWith(localStr, localLen, u"data-") ||
+ UTF16StringStartsWith(localStr, localLen, u"aria-")) {
+ continue;
+ }
+ // else not allowed
+ } else if (kNameSpaceID_XML == attrNs) {
+ if (nsGkAtoms::lang == attrLocal || nsGkAtoms::space == attrLocal) {
+ continue;
+ }
+ // else not allowed
+ } else if (aAllowed.mXLink && kNameSpaceID_XLink == attrNs) {
+ if (nsGkAtoms::href == attrLocal) {
+ bool fragmentOnly = aElement->IsSVGElement(nsGkAtoms::use);
+ if (SanitizeURL(aElement, attrNs, attrLocal, fragmentOnly)) {
+ // in case the attribute removal shuffled the attribute order, start
+ // the loop again.
+ --ac;
+ i = ac; // i will be decremented immediately thanks to the for loop
+ }
+ continue;
+ }
+ if (nsGkAtoms::type == attrLocal || nsGkAtoms::title == attrLocal ||
+ nsGkAtoms::show == attrLocal || nsGkAtoms::actuate == attrLocal) {
+ continue;
+ }
+ // else not allowed
+ }
+ aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false);
+ if (mLogRemovals) {
+ LogMessage("Removed unsafe attribute.", aElement->OwnerDoc(), aElement,
+ attrLocal);
+ }
+ // in case the attribute removal shuffled the attribute order, start the
+ // loop again.
+ --ac;
+ i = ac; // i will be decremented immediately thanks to the for loop
+ }
+
+ // If we've got HTML audio or video, add the controls attribute, because
+ // otherwise the content is unplayable with scripts removed.
+ if (aElement->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) {
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::controls, u""_ns, false);
+ }
+}
+
+// https://wicg.github.io/sanitizer-api/#element-matches-an-element-name
+bool nsTreeSanitizer::MatchesElementName(ElementNameSet& aNames,
+ int32_t aNamespace,
+ nsAtom* aLocalName) {
+ return aNames.Contains(ElementName(aNamespace, aLocalName));
+}
+
+// https://wicg.github.io/sanitizer-api/#attribute-match-list
+bool nsTreeSanitizer::MatchesAttributeMatchList(
+ AttributesToElementsMap& aMatchList, Element& aElement,
+ int32_t aAttrNamespace, nsAtom* aAttrLocalName) {
+ // Step 1. If attribute’s local name does not match the attribute match list
+ // list’s key and if the key is not "*": Return false.
+ ElementNameSet* names;
+ if (auto lookup =
+ aMatchList.Lookup(AttributeName(aAttrNamespace, aAttrLocalName))) {
+ names = lookup->get();
+ } else {
+ return false;
+ }
+
+ // Step 2. Let element be the attribute’s Element.
+ // Step 3. Let element name be element’s local name.
+ // Step 4. If element is a in either the SVG or MathML namespaces (i.e., it’s
+ // a foreign element), then prefix element name with the appropriate namespace
+ // designator plus a whitespace character.
+
+ // TODO: This is spec text is going to change.
+ int32_t namespaceID = aElement.NodeInfo()->NamespaceID();
+ RefPtr<nsAtom> nameAtom = aElement.NodeInfo()->NameAtom();
+
+ // Step 5. If list’s value does not contain element name and value is not
+ // ["*"]: Return false.
+ // Step 6. Return true.
+
+ // nullptr means star (*), i.e. any element.
+ if (!names) {
+ return true;
+ }
+ return MatchesElementName(*names, namespaceID, nameAtom);
+}
+
+// https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-attribute
+bool nsTreeSanitizer::MustDropAttribute(Element* aElement,
+ int32_t aAttrNamespace,
+ nsAtom* aAttrLocalName) {
+ // Step 1. Let kind be attribute’s attribute kind.
+ // Step 2. If kind is unknown and if config["allowUnknownMarkup"] does not
+ // exist or it config["allowUnknownMarkup"] is false: Return drop.
+ //
+ // TODO: Not clear how to determine if something is an "unknown" attribute.
+ // https://github.com/WICG/sanitizer-api/issues/147 should probably define
+ // an explicit list.
+
+ // Step 3. If kind is regular and attribute’s local name does not match any
+ // name in the baseline attribute allow list: Return drop.
+ if (!sBaselineAttributeAllowlist->Contains(aAttrLocalName)) {
+ return true;
+ }
+
+ // Step 4. If attribute matches any attribute match list in config’s attribute
+ // drop list: Return drop.
+ if (mDropAttributes &&
+ MatchesAttributeMatchList(*mDropAttributes, *aElement, aAttrNamespace,
+ aAttrLocalName)) {
+ return true;
+ }
+
+ // Step 5. If attribute allow list exists in config:
+ if (mAllowAttributes) {
+ // Step 5.1. Then let allow list be |config|["allowAttributes"].
+ // Step 6. If attribute does not match any attribute match list in allow
+ // list: Return drop.
+ if (!MatchesAttributeMatchList(*mAllowAttributes, *aElement, aAttrNamespace,
+ aAttrLocalName)) {
+ return true;
+ }
+ } else {
+ // Step 5.2. Otherwise: Let allow list be the default configuration's
+ // attribute allow list.
+ // Step 6. If attribute does not match any attribute
+ // match list in allow list: Return drop.
+ if (!sDefaultConfigurationAttributeAllowlist->Contains(aAttrLocalName)) {
+ return true;
+ }
+ }
+
+ // Step 7. Return keep.
+ return false;
+}
+
+// https://wicg.github.io/sanitizer-api/#handle-funky-elements
+bool nsTreeSanitizer::MustDropFunkyAttribute(Element* aElement,
+ int32_t aAttrNamespace,
+ nsAtom* aAttrLocalName) {
+ // Step 1. If element’s element interface is HTMLTemplateElement:
+ // Note: This step is implemented in the main loop of SanitizeChildren.
+
+ // Step 2. If element’s element interface has a HTMLHyperlinkElementUtils
+ // mixin, and if element’s protocol property is "javascript:":
+ // TODO(https://github.com/WICG/sanitizer-api/issues/168)
+ if (aAttrLocalName == nsGkAtoms::href) {
+ if (nsCOMPtr<Link> link = do_QueryInterface(aElement)) {
+ nsCOMPtr<nsIURI> uri = link->GetURI();
+ if (uri && uri->SchemeIs("javascript")) {
+ // Step 2.1. Remove the `href` attribute from element.
+ return true;
+ }
+ }
+ }
+
+ // Step 3. if element’s element interface is HTMLFormElement, and if element’s
+ // action attribute is a URL with "javascript:" protocol:
+ if (auto* form = HTMLFormElement::FromNode(aElement)) {
+ if (aAttrNamespace == kNameSpaceID_None &&
+ aAttrLocalName == nsGkAtoms::action) {
+ nsCOMPtr<nsIURI> uri;
+ form->GetURIAttr(aAttrLocalName, nullptr, getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("javascript")) {
+ // Step 3.1 Remove the `action` attribute from element.
+ return true;
+ }
+ }
+ }
+
+ // Step 4. if element’s element interface is HTMLInputElement or
+ // HTMLButtonElement, and if element’s formaction attribute is a [URL] with
+ // javascript: protocol
+ if (aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::button) &&
+ aAttrNamespace == kNameSpaceID_None &&
+ aAttrLocalName == nsGkAtoms::formaction) {
+ // XXX nsGenericHTMLFormControlElementWithState::GetFormAction falls back to
+ // the document URI.
+ nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(aElement);
+ nsCOMPtr<nsIURI> uri;
+ el->GetURIAttr(aAttrLocalName, nullptr, getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("javascript")) {
+ // Step 4.1 Remove the `formaction` attribute from element.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsTreeSanitizer::SanitizeURL(mozilla::dom::Element* aElement,
+ int32_t aNamespace, nsAtom* aLocalName,
+ bool aFragmentsOnly) {
+ nsAutoString value;
+ aElement->GetAttr(aNamespace, aLocalName, value);
+
+ // Get value and remove mandatory quotes
+ static const char* kWhitespace = "\n\r\t\b";
+ const nsAString& v = nsContentUtils::TrimCharsInSet(kWhitespace, value);
+ // Fragment-only url cannot be harmful.
+ if (!v.IsEmpty() && v.First() == u'#') {
+ return false;
+ }
+ // if we allow only same-document fragment URLs, stop and remove here
+ if (aFragmentsOnly) {
+ aElement->UnsetAttr(aNamespace, aLocalName, false);
+ if (mLogRemovals) {
+ LogMessage("Removed unsafe URI from element attribute.",
+ aElement->OwnerDoc(), aElement, aLocalName);
+ }
+ return true;
+ }
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ uint32_t flags = nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL;
+
+ nsCOMPtr<nsIURI> attrURI;
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(attrURI), v, nullptr, aElement->GetBaseURI());
+ if (NS_SUCCEEDED(rv)) {
+ if (mCidEmbedsOnly && kNameSpaceID_None == aNamespace) {
+ if (nsGkAtoms::src == aLocalName || nsGkAtoms::background == aLocalName) {
+ // comm-central uses a hack that makes nsIURIs created with cid: specs
+ // actually have an about:blank spec. Therefore, nsIURI facilities are
+ // useless for cid: when comm-central code is participating.
+ if (!(v.Length() > 4 && (v[0] == 'c' || v[0] == 'C') &&
+ (v[1] == 'i' || v[1] == 'I') && (v[2] == 'd' || v[2] == 'D') &&
+ v[3] == ':')) {
+ rv = NS_ERROR_FAILURE;
+ }
+ } else if (nsGkAtoms::cdgroup_ == aLocalName ||
+ nsGkAtoms::altimg_ == aLocalName ||
+ nsGkAtoms::definitionURL_ == aLocalName) {
+ // Gecko doesn't fetch these now and shouldn't in the future, but
+ // in case someone goofs with these in the future, let's drop them.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = secMan->CheckLoadURIWithPrincipal(sNullPrincipal, attrURI, flags,
+ 0);
+ }
+ } else {
+ rv = secMan->CheckLoadURIWithPrincipal(sNullPrincipal, attrURI, flags, 0);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ aElement->UnsetAttr(aNamespace, aLocalName, false);
+ if (mLogRemovals) {
+ LogMessage("Removed unsafe URI from element attribute.",
+ aElement->OwnerDoc(), aElement, aLocalName);
+ }
+ return true;
+ }
+ return false;
+}
+
+void nsTreeSanitizer::Sanitize(DocumentFragment* aFragment) {
+ // If you want to relax these preconditions, be sure to check the code in
+ // here that notifies / does not notify or that fires mutation events if
+ // in tree.
+ MOZ_ASSERT(!aFragment->IsInUncomposedDoc(), "The fragment is in doc?");
+
+ mFullDocument = false;
+ SanitizeChildren(aFragment);
+}
+
+void nsTreeSanitizer::Sanitize(Document* aDocument) {
+ // If you want to relax these preconditions, be sure to check the code in
+ // here that notifies / does not notify or that fires mutation events if
+ // in tree.
+#ifdef DEBUG
+ MOZ_ASSERT(!aDocument->GetContainer(), "The document is in a shell.");
+ RefPtr<mozilla::dom::Element> root = aDocument->GetRootElement();
+ MOZ_ASSERT(root->IsHTMLElement(nsGkAtoms::html), "Not HTML root.");
+#endif
+
+ mFullDocument = true;
+ SanitizeChildren(aDocument);
+}
+
+void nsTreeSanitizer::SanitizeChildren(nsINode* aRoot) {
+ nsIContent* node = aRoot->GetFirstChild();
+ while (node) {
+ if (node->IsElement()) {
+ mozilla::dom::Element* elt = node->AsElement();
+ mozilla::dom::NodeInfo* nodeInfo = node->NodeInfo();
+ nsAtom* localName = nodeInfo->NameAtom();
+ int32_t ns = nodeInfo->NamespaceID();
+
+ if (MustPrune(ns, localName, elt)) {
+ if (mLogRemovals) {
+ LogMessage("Removing unsafe node.", elt->OwnerDoc(), elt);
+ }
+ RemoveAllAttributes(elt);
+ nsIContent* descendant = node;
+ while ((descendant = descendant->GetNextNode(node))) {
+ if (descendant->IsElement()) {
+ RemoveAllAttributes(descendant->AsElement());
+ }
+ }
+ nsIContent* next = node->GetNextNonChildNode(aRoot);
+ node->RemoveFromParent();
+ node = next;
+ continue;
+ }
+ if (auto* templateEl = HTMLTemplateElement::FromNode(elt)) {
+ // traverse into the DocFragment content attribute of template elements
+ bool wasFullDocument = mFullDocument;
+ mFullDocument = false;
+ RefPtr<DocumentFragment> frag = templateEl->Content();
+ SanitizeChildren(frag);
+ mFullDocument = wasFullDocument;
+ }
+ if (!mIsForSanitizerAPI && nsGkAtoms::style == localName) {
+ // If styles aren't allowed, style elements got pruned above. Even
+ // if styles are allowed, non-HTML, non-SVG style elements got pruned
+ // above.
+ NS_ASSERTION(ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG,
+ "Should have only HTML or SVG here!");
+ if (SanitizeInlineStyle(elt, StyleSanitizationKind::Standard) &&
+ mLogRemovals) {
+ LogMessage("Removed some rules and/or properties from stylesheet.",
+ aRoot->OwnerDoc());
+ }
+
+ AllowedAttributes allowed;
+ allowed.mStyle = mAllowStyles;
+ if (ns == kNameSpaceID_XHTML) {
+ allowed.mNames = sAttributesHTML;
+ allowed.mURLs = kURLAttributesHTML;
+ } else {
+ allowed.mNames = sAttributesSVG;
+ allowed.mURLs = kURLAttributesSVG;
+ allowed.mXLink = true;
+ }
+ SanitizeAttributes(elt, allowed);
+ node = node->GetNextNonChildNode(aRoot);
+ continue;
+ }
+ if (MustFlatten(ns, localName)) {
+ if (mLogRemovals) {
+ LogMessage("Flattening unsafe node (descendants are preserved).",
+ elt->OwnerDoc(), elt);
+ }
+ RemoveAllAttributes(elt);
+ nsCOMPtr<nsIContent> next = node->GetNextNode(aRoot);
+ nsCOMPtr<nsIContent> parent = node->GetParent();
+ nsCOMPtr<nsIContent> child; // Must keep the child alive during move
+ ErrorResult rv;
+ while ((child = node->GetFirstChild())) {
+ nsCOMPtr<nsINode> refNode = node;
+ parent->InsertBefore(*child, refNode, rv);
+ if (rv.Failed()) {
+ break;
+ }
+ }
+ node->RemoveFromParent();
+ node = next;
+ continue;
+ }
+ NS_ASSERTION(ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG ||
+ ns == kNameSpaceID_MathML,
+ "Should have only HTML, MathML or SVG here!");
+ AllowedAttributes allowed;
+ if (ns == kNameSpaceID_XHTML) {
+ allowed.mNames = sAttributesHTML;
+ allowed.mURLs = kURLAttributesHTML;
+ allowed.mStyle = mAllowStyles;
+ allowed.mDangerousSrc = nsGkAtoms::img == localName && !mCidEmbedsOnly;
+ SanitizeAttributes(elt, allowed);
+ } else if (ns == kNameSpaceID_SVG) {
+ allowed.mNames = sAttributesSVG;
+ allowed.mURLs = kURLAttributesSVG;
+ allowed.mXLink = true;
+ allowed.mStyle = mAllowStyles;
+ SanitizeAttributes(elt, allowed);
+ } else {
+ allowed.mNames = sAttributesMathML;
+ allowed.mURLs = kURLAttributesMathML;
+ allowed.mXLink = true;
+ SanitizeAttributes(elt, allowed);
+ }
+ node = node->GetNextNode(aRoot);
+ continue;
+ }
+ NS_ASSERTION(!node->GetFirstChild(), "How come non-element node had kids?");
+ nsIContent* next = node->GetNextNonChildNode(aRoot);
+ if (!mAllowComments && node->IsComment()) {
+ node->RemoveFromParent();
+ }
+ node = next;
+ }
+}
+
+void nsTreeSanitizer::RemoveAllAttributes(Element* aElement) {
+ const nsAttrName* attrName;
+ while ((attrName = aElement->GetAttrNameAt(0))) {
+ int32_t attrNs = attrName->NamespaceID();
+ RefPtr<nsAtom> attrLocal = attrName->LocalName();
+ aElement->UnsetAttr(attrNs, attrLocal, false);
+ }
+}
+
+void nsTreeSanitizer::RemoveAllAttributesFromDescendants(
+ mozilla::dom::Element* aElement) {
+ nsIContent* node = aElement->GetFirstChild();
+ while (node) {
+ if (node->IsElement()) {
+ mozilla::dom::Element* elt = node->AsElement();
+ RemoveAllAttributes(elt);
+ }
+ node = node->GetNextNode(aElement);
+ }
+}
+
+void nsTreeSanitizer::LogMessage(const char* aMessage, Document* aDoc,
+ Element* aElement, nsAtom* aAttr) {
+ if (mLogRemovals) {
+ nsAutoString msg;
+ msg.Assign(NS_ConvertASCIItoUTF16(aMessage));
+ if (aElement) {
+ msg.Append(u" Element: "_ns + aElement->LocalName() + u"."_ns);
+ }
+ if (aAttr) {
+ msg.Append(u" Attribute: "_ns + nsDependentAtomString(aAttr) + u"."_ns);
+ }
+
+ if (mInnerWindowID) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ msg, nsIScriptError::warningFlag, "DOM"_ns, mInnerWindowID);
+ } else {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ msg, nsIScriptError::warningFlag, "DOM"_ns, aDoc);
+ }
+ }
+}
+
+void nsTreeSanitizer::InitializeStatics() {
+ MOZ_ASSERT(!sElementsHTML, "Initializing a second time.");
+
+ sElementsHTML = new AtomsTable(ArrayLength(kElementsHTML));
+ for (uint32_t i = 0; kElementsHTML[i]; i++) {
+ sElementsHTML->Insert(kElementsHTML[i]);
+ }
+
+ sAttributesHTML = new AtomsTable(ArrayLength(kAttributesHTML));
+ for (uint32_t i = 0; kAttributesHTML[i]; i++) {
+ sAttributesHTML->Insert(kAttributesHTML[i]);
+ }
+
+ sPresAttributesHTML = new AtomsTable(ArrayLength(kPresAttributesHTML));
+ for (uint32_t i = 0; kPresAttributesHTML[i]; i++) {
+ sPresAttributesHTML->Insert(kPresAttributesHTML[i]);
+ }
+
+ sElementsSVG = new AtomsTable(ArrayLength(kElementsSVG));
+ for (uint32_t i = 0; kElementsSVG[i]; i++) {
+ sElementsSVG->Insert(kElementsSVG[i]);
+ }
+
+ sAttributesSVG = new AtomsTable(ArrayLength(kAttributesSVG));
+ for (uint32_t i = 0; kAttributesSVG[i]; i++) {
+ sAttributesSVG->Insert(kAttributesSVG[i]);
+ }
+
+ sElementsMathML = new AtomsTable(ArrayLength(kElementsMathML));
+ for (uint32_t i = 0; kElementsMathML[i]; i++) {
+ sElementsMathML->Insert(kElementsMathML[i]);
+ }
+
+ sAttributesMathML = new AtomsTable(ArrayLength(kAttributesMathML));
+ for (uint32_t i = 0; kAttributesMathML[i]; i++) {
+ sAttributesMathML->Insert(kAttributesMathML[i]);
+ }
+
+ sBaselineAttributeAllowlist =
+ new AtomsTable(ArrayLength(kBaselineAttributeAllowlist));
+ for (const auto* atom : kBaselineAttributeAllowlist) {
+ sBaselineAttributeAllowlist->Insert(atom);
+ }
+
+ sBaselineElementAllowlist =
+ new AtomsTable(ArrayLength(kBaselineElementAllowlist));
+ for (const auto* atom : kBaselineElementAllowlist) {
+ sBaselineElementAllowlist->Insert(atom);
+ }
+
+ sDefaultConfigurationAttributeAllowlist =
+ new AtomsTable(ArrayLength(kDefaultConfigurationAttributeAllowlist));
+ for (const auto* atom : kDefaultConfigurationAttributeAllowlist) {
+ sDefaultConfigurationAttributeAllowlist->Insert(atom);
+ }
+
+ sDefaultConfigurationElementAllowlist =
+ new AtomsTable(ArrayLength(kDefaultConfigurationElementAllowlist));
+ for (const auto* atom : kDefaultConfigurationElementAllowlist) {
+ sDefaultConfigurationElementAllowlist->Insert(atom);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ principal.forget(&sNullPrincipal);
+}
+
+void nsTreeSanitizer::ReleaseStatics() {
+ delete sElementsHTML;
+ sElementsHTML = nullptr;
+
+ delete sAttributesHTML;
+ sAttributesHTML = nullptr;
+
+ delete sPresAttributesHTML;
+ sPresAttributesHTML = nullptr;
+
+ delete sElementsSVG;
+ sElementsSVG = nullptr;
+
+ delete sAttributesSVG;
+ sAttributesSVG = nullptr;
+
+ delete sElementsMathML;
+ sElementsMathML = nullptr;
+
+ delete sAttributesMathML;
+ sAttributesMathML = nullptr;
+
+ delete sBaselineAttributeAllowlist;
+ sBaselineAttributeAllowlist = nullptr;
+
+ delete sBaselineElementAllowlist;
+ sBaselineElementAllowlist = nullptr;
+
+ delete sDefaultConfigurationAttributeAllowlist;
+ sDefaultConfigurationAttributeAllowlist = nullptr;
+
+ delete sDefaultConfigurationElementAllowlist;
+ sDefaultConfigurationElementAllowlist = nullptr;
+
+ NS_IF_RELEASE(sNullPrincipal);
+}
+
+static int32_t ConvertNamespaceString(const nsAString& aNamespace,
+ bool aForAttribute,
+ mozilla::ErrorResult& aRv) {
+ int32_t namespaceID = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ aNamespace, /* aInChromeDoc */ false);
+ if (namespaceID == kNameSpaceID_XHTML || namespaceID == kNameSpaceID_MathML ||
+ namespaceID == kNameSpaceID_SVG) {
+ return namespaceID;
+ }
+ if (aForAttribute && (namespaceID == kNameSpaceID_XMLNS ||
+ namespaceID == kNameSpaceID_XLink)) {
+ return namespaceID;
+ }
+
+ aRv.ThrowTypeError("Invalid namespace: \""_ns +
+ NS_ConvertUTF16toUTF8(aNamespace) + "\"."_ns);
+ return kNameSpaceID_Unknown;
+}
+
+UniquePtr<nsTreeSanitizer::ElementNameSet> nsTreeSanitizer::ConvertElements(
+ const nsTArray<OwningStringOrSanitizerElementNamespace>& aElements,
+ mozilla::ErrorResult& aRv) {
+ auto set = MakeUnique<ElementNameSet>(aElements.Length());
+ for (const auto& entry : aElements) {
+ if (entry.IsString()) {
+ RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(entry.GetAsString());
+ // The default namespace for elements is HTML.
+ ElementName elemName(kNameSpaceID_XHTML, std::move(nameAtom));
+ set->Insert(elemName);
+ } else {
+ const auto& elemNamespace = entry.GetAsSanitizerElementNamespace();
+
+ int32_t namespaceID =
+ ConvertNamespaceString(elemNamespace.mNamespace, false, aRv);
+ if (aRv.Failed()) {
+ return {};
+ }
+
+ RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elemNamespace.mName);
+ ElementName elemName(namespaceID, std::move(nameAtom));
+ set->Insert(elemName);
+ }
+ }
+
+ return set;
+}
+
+UniquePtr<nsTreeSanitizer::ElementNameSet> nsTreeSanitizer::ConvertElements(
+ const OwningStarOrStringOrSanitizerElementNamespaceSequence& aElements,
+ mozilla::ErrorResult& aRv) {
+ if (aElements.IsStar()) {
+ return nullptr;
+ }
+ return ConvertElements(
+ aElements.GetAsStringOrSanitizerElementNamespaceSequence(), aRv);
+}
+
+UniquePtr<nsTreeSanitizer::AttributesToElementsMap>
+nsTreeSanitizer::ConvertAttributes(
+ const nsTArray<SanitizerAttribute>& aAttributes, ErrorResult& aRv) {
+ auto map = MakeUnique<AttributesToElementsMap>();
+
+ for (const auto& entry : aAttributes) {
+ // The default namespace for attributes is the "null" namespace.
+ int32_t namespaceID = kNameSpaceID_None;
+ if (!entry.mNamespace.IsVoid()) {
+ namespaceID = ConvertNamespaceString(entry.mNamespace, true, aRv);
+ if (aRv.Failed()) {
+ return {};
+ }
+ }
+ RefPtr<nsAtom> attrAtom = NS_AtomizeMainThread(entry.mName);
+ AttributeName attrName(namespaceID, std::move(attrAtom));
+
+ UniquePtr<ElementNameSet> elements = ConvertElements(entry.mElements, aRv);
+ if (aRv.Failed()) {
+ return {};
+ }
+ map->InsertOrUpdate(attrName, std::move(elements));
+ }
+
+ return map;
+}
+
+void nsTreeSanitizer::WithWebSanitizerOptions(
+ nsIGlobalObject* aGlobal, const mozilla::dom::SanitizerConfig& aOptions,
+ ErrorResult& aRv) {
+ if (StaticPrefs::dom_security_sanitizer_logging()) {
+ mLogRemovals = true;
+ if (nsPIDOMWindowInner* win = aGlobal->AsInnerWindow()) {
+ mInnerWindowID = win->WindowID();
+ }
+ }
+
+ mIsForSanitizerAPI = true;
+
+ if (aOptions.mAllowComments.WasPassed()) {
+ mAllowComments = aOptions.mAllowComments.Value();
+ }
+ if (aOptions.mAllowCustomElements.WasPassed()) {
+ mAllowCustomElements = aOptions.mAllowCustomElements.Value();
+ }
+ if (aOptions.mAllowUnknownMarkup.WasPassed()) {
+ mAllowUnknownMarkup = aOptions.mAllowUnknownMarkup.Value();
+ }
+
+ if (aOptions.mAllowElements.WasPassed()) {
+ mAllowElements = ConvertElements(aOptions.mAllowElements.Value(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ if (aOptions.mBlockElements.WasPassed()) {
+ mBlockElements = ConvertElements(aOptions.mBlockElements.Value(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ if (aOptions.mDropElements.WasPassed()) {
+ mDropElements = ConvertElements(aOptions.mDropElements.Value(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ if (aOptions.mAllowAttributes.WasPassed()) {
+ mAllowAttributes =
+ ConvertAttributes(aOptions.mAllowAttributes.Value(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ if (aOptions.mDropAttributes.WasPassed()) {
+ mDropAttributes = ConvertAttributes(aOptions.mDropAttributes.Value(), aRv);
+ }
+}
diff --git a/dom/base/nsTreeSanitizer.h b/dom/base/nsTreeSanitizer.h
new file mode 100644
index 0000000000..ed1c49c60c
--- /dev/null
+++ b/dom/base/nsTreeSanitizer.h
@@ -0,0 +1,408 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeSanitizer_h_
+#define nsTreeSanitizer_h_
+
+#include "nsAtom.h"
+#include "nsHashKeys.h"
+#include "nsHashtablesFwd.h"
+#include "nsIPrincipal.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/SanitizerBinding.h"
+
+class nsIContent;
+class nsIGlobalObject;
+class nsINode;
+
+namespace mozilla {
+class DeclarationBlock;
+class ErrorResult;
+enum class StyleSanitizationKind : uint8_t;
+} // namespace mozilla
+
+namespace mozilla::dom {
+class DocumentFragment;
+class Element;
+class OwningStringOrSanitizerElementNameNamespace;
+struct SanitizerAttribute;
+} // namespace mozilla::dom
+
+/**
+ * See the documentation of nsIParserUtils::sanitize for documentation
+ * about the default behavior and the configuration options of this sanitizer.
+ */
+class nsTreeSanitizer {
+ public:
+ /**
+ * The constructor.
+ *
+ * @param aFlags Flags from nsIParserUtils
+ */
+ explicit nsTreeSanitizer(uint32_t aFlags = 0);
+
+ static void InitializeStatics();
+ static void ReleaseStatics();
+
+ /**
+ * Sanitizes a disconnected DOM fragment freshly obtained from a parser.
+ * The fragment must have just come from a parser so that it can't have
+ * mutation event listeners set on it.
+ */
+ void Sanitize(mozilla::dom::DocumentFragment* aFragment);
+
+ /**
+ * Sanitizes a disconnected (not in a docshell) document freshly obtained
+ * from a parser. The document must not be embedded in a docshell and must
+ * not have had a chance to get mutation event listeners attached to it.
+ * The root element must be <html>.
+ */
+ void Sanitize(mozilla::dom::Document* aDocument);
+
+ /**
+ * Provides additional options for usage from the Web Sanitizer API
+ * which allows modifying the allow-list from above
+ */
+ void WithWebSanitizerOptions(nsIGlobalObject* aGlobal,
+ const mozilla::dom::SanitizerConfig& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Removes conditional CSS from this subtree.
+ */
+ static void RemoveConditionalCSSFromSubtree(nsINode* aRoot);
+
+ private:
+ /**
+ * Whether <style> and style="" are allowed.
+ */
+ bool mAllowStyles;
+
+ /**
+ * Whether comment nodes are allowed.
+ */
+ bool mAllowComments;
+
+ /**
+ * Whether HTML <font>, <center>, bgcolor="", etc., are dropped.
+ */
+ bool mDropNonCSSPresentation;
+
+ /**
+ * Whether to remove forms and form controls (excluding fieldset/legend).
+ */
+ bool mDropForms;
+
+ /**
+ * Whether only cid: embeds are allowed.
+ */
+ bool mCidEmbedsOnly;
+
+ /**
+ * Whether to drop <img>, <video>, <audio> and <svg>.
+ */
+ bool mDropMedia;
+
+ /**
+ * Whether we are sanitizing a full document (as opposed to a fragment).
+ */
+ bool mFullDocument;
+
+ /**
+ * Whether we should notify to the console for anything that's stripped.
+ */
+ bool mLogRemovals;
+
+ // WindowID used for logging removals.
+ uint64_t mInnerWindowID = 0;
+
+ /**
+ * We have various tables of static atoms for elements and attributes.
+ */
+ class AtomsTable : public nsTHashSet<const nsStaticAtom*> {
+ public:
+ explicit AtomsTable(uint32_t aLength)
+ : nsTHashSet<const nsStaticAtom*>(aLength) {}
+
+ bool Contains(nsAtom* aAtom) {
+ // Because this table only contains static atoms, if aAtom isn't
+ // static we can immediately fail.
+ return aAtom->IsStatic() && GetEntry(aAtom->AsStatic());
+ }
+ };
+
+ // The name of an element combined with its namespace.
+ class NamespaceAtom : public PLDHashEntryHdr {
+ public:
+ using KeyType = const NamespaceAtom&;
+ using KeyTypePointer = const NamespaceAtom*;
+
+ explicit NamespaceAtom(KeyTypePointer aKey)
+ : mNamespaceID(aKey->mNamespaceID), mLocalName(aKey->mLocalName) {}
+ NamespaceAtom(int32_t aNamespaceID, RefPtr<nsAtom> aLocalName)
+ : mNamespaceID(aNamespaceID), mLocalName(std::move(aLocalName)) {}
+ NamespaceAtom(NamespaceAtom&&) = default;
+ ~NamespaceAtom() = default;
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return mNamespaceID == aKey->mNamespaceID &&
+ mLocalName == aKey->mLocalName;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ if (!aKey) {
+ return 0;
+ }
+
+ return mozilla::HashGeneric(aKey->mNamespaceID, aKey->mLocalName.get());
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ int32_t mNamespaceID = kNameSpaceID_None;
+ RefPtr<nsAtom> mLocalName;
+ };
+
+ using ElementName = NamespaceAtom;
+ using AttributeName = NamespaceAtom;
+
+ using ElementNameSet = nsTHashSet<ElementName>;
+ // nullptr value (ElementNameSet) means all elements (*).
+ using AttributesToElementsMap =
+ nsTHashMap<AttributeName, mozilla::UniquePtr<ElementNameSet>>;
+
+ void SanitizeChildren(nsINode* aRoot);
+
+ /**
+ * Queries if an element must be replaced with its children.
+ * @param aNamespace the namespace of the element the question is about
+ * @param aLocal the local name of the element the question is about
+ * @return true if the element must be replaced with its children and
+ * false if the element is to be kept
+ */
+ bool MustFlatten(int32_t aNamespace, nsAtom* aLocal);
+ bool MustFlattenForSanitizerAPI(int32_t aNamespace, nsAtom* aLocal);
+
+ /**
+ * Queries if an element including its children must be removed.
+ * @param aNamespace the namespace of the element the question is about
+ * @param aLocal the local name of the element the question is about
+ * @param aElement the element node itself for inspecting attributes
+ * @return true if the element and its children must be removed and
+ * false if the element is to be kept
+ */
+ bool MustPrune(int32_t aNamespace, nsAtom* aLocal,
+ mozilla::dom::Element* aElement);
+ bool MustPruneForSanitizerAPI(int32_t aNamespace, nsAtom* aLocal,
+ mozilla::dom::Element* aElement);
+
+ /**
+ * Checks if a given local name (for an attribute) is on the given list
+ * of URL attribute names.
+ * @param aURLs the list of URL attribute names
+ * @param aLocalName the name to search on the list
+ * @return true if aLocalName is on the aURLs list and false otherwise
+ */
+ bool IsURL(const nsStaticAtom* const* aURLs, nsAtom* aLocalName);
+
+ /**
+ * Struct for what attributes and their values are allowed.
+ */
+ struct AllowedAttributes {
+ // The whitelist of permitted local names to use.
+ AtomsTable* mNames = nullptr;
+ // The local names of URL-valued attributes for URL checking.
+ const nsStaticAtom* const* mURLs = nullptr;
+ // Whether XLink attributes are allowed.
+ bool mXLink = false;
+ // Whether the style attribute is allowed.
+ bool mStyle = false;
+ // Whether to leave the value of the src attribute unsanitized.
+ bool mDangerousSrc = false;
+ };
+
+ /**
+ * Removes dangerous attributes from the element. If the style attribute
+ * is allowed, its value is sanitized. The values of URL attributes are
+ * sanitized, except src isn't sanitized when it is allowed to remain
+ * potentially dangerous.
+ *
+ * @param aElement the element whose attributes should be sanitized
+ * @param aAllowed options for sanitizing attributes
+ */
+ void SanitizeAttributes(mozilla::dom::Element* aElement,
+ AllowedAttributes aAllowed);
+ // Currently only used for the Sanitizer API.
+ bool MustDropAttribute(mozilla::dom::Element* aElement,
+ int32_t aAttrNamespace, nsAtom* aAttrLocalName);
+ bool MustDropFunkyAttribute(mozilla::dom::Element* aElement,
+ int32_t aAttrNamespace, nsAtom* aAttrLocalName);
+
+ /**
+ * Remove the named URL attribute from the element if the URL fails a
+ * security check.
+ *
+ * @param aElement the element whose attribute to possibly modify
+ * @param aNamespace the namespace of the URL attribute
+ * @param aLocalName the local name of the URL attribute
+ * @param aFragmentsOnly allows same-document references only
+ * @return true if the attribute was removed and false otherwise
+ */
+ bool SanitizeURL(mozilla::dom::Element* aElement, int32_t aNamespace,
+ nsAtom* aLocalName, bool aFragmentsOnly = false);
+
+ /**
+ * Checks a style rule for the presence of the 'binding' CSS property and
+ * removes that property from the rule.
+ *
+ * @param aDeclaration The style declaration to check
+ * @return true if the rule was modified and false otherwise
+ */
+ bool SanitizeStyleDeclaration(mozilla::DeclarationBlock* aDeclaration);
+
+ /**
+ * Sanitizes an inline style element (an HTML or SVG <style>).
+ *
+ * Returns whether the style has changed.
+ */
+ static bool SanitizeInlineStyle(mozilla::dom::Element*,
+ mozilla::StyleSanitizationKind);
+
+ /**
+ * Removes all attributes from an element node.
+ */
+ static void RemoveAllAttributes(mozilla::dom::Element* aElement);
+
+ /**
+ * Removes all attributes from the descendants of an element but not from
+ * the element itself.
+ */
+ static void RemoveAllAttributesFromDescendants(mozilla::dom::Element*);
+
+ static bool MatchesElementName(ElementNameSet& aNames, int32_t aNamespace,
+ nsAtom* aLocalName);
+ static bool MatchesAttributeMatchList(AttributesToElementsMap& aMatchList,
+ mozilla::dom::Element& aElement,
+ int32_t aAttrNamespace,
+ nsAtom* aAttrLocalName);
+
+ static mozilla::UniquePtr<ElementNameSet> ConvertElements(
+ const nsTArray<mozilla::dom::OwningStringOrSanitizerElementNamespace>&
+ aElements,
+ mozilla::ErrorResult& aRv);
+
+ static mozilla::UniquePtr<ElementNameSet> ConvertElements(
+ const mozilla::dom::OwningStarOrStringOrSanitizerElementNamespaceSequence&
+ aElements,
+ mozilla::ErrorResult& aRv);
+
+ static mozilla::UniquePtr<AttributesToElementsMap> ConvertAttributes(
+ const nsTArray<mozilla::dom::SanitizerAttribute>& aAttributes,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Log a Console Service message to indicate we removed something.
+ * If you pass an element and/or attribute, their information will
+ * be appended to the message.
+ *
+ * @param aMessage the basic message to log.
+ * @param aDocument the base document we're modifying
+ * (used for the error message)
+ * @param aElement optional, the element being removed or modified.
+ * @param aAttribute optional, the attribute being removed or modified.
+ */
+ void LogMessage(const char* aMessage, mozilla::dom::Document* aDoc,
+ mozilla::dom::Element* aElement = nullptr,
+ nsAtom* aAttr = nullptr);
+
+ /**
+ * The whitelist of HTML elements.
+ */
+ static AtomsTable* sElementsHTML;
+
+ /**
+ * The whitelist of non-presentational HTML attributes.
+ */
+ static AtomsTable* sAttributesHTML;
+
+ /**
+ * The whitelist of presentational HTML attributes.
+ */
+ static AtomsTable* sPresAttributesHTML;
+
+ /**
+ * The whitelist of SVG elements.
+ */
+ static AtomsTable* sElementsSVG;
+
+ /**
+ * The whitelist of SVG attributes.
+ */
+ static AtomsTable* sAttributesSVG;
+
+ /**
+ * The whitelist of SVG elements.
+ */
+ static AtomsTable* sElementsMathML;
+
+ /**
+ * The whitelist of MathML attributes.
+ */
+ static AtomsTable* sAttributesMathML;
+
+ /**
+ * The built-in baseline attribute allow list used by the Sanitizer API.
+ */
+ static AtomsTable* sBaselineAttributeAllowlist;
+
+ /**
+ * The built-in baseline element allow list used by the Sanitizer API.
+ */
+ static AtomsTable* sBaselineElementAllowlist;
+
+ /**
+ * The default configuration's attribute allow list used by the Sanitizer API.
+ */
+ static AtomsTable* sDefaultConfigurationAttributeAllowlist;
+
+ /**
+ * The default configuration's element allow list used by the Sanitizer API.
+ */
+ static AtomsTable* sDefaultConfigurationElementAllowlist;
+
+ /**
+ * Reusable null principal for URL checks.
+ */
+ static nsIPrincipal* sNullPrincipal;
+
+ // === Variables used to implement HTML Sanitizer API. ==
+
+ // This nsTreeSanitizer instance should behave like the Sanitizer API.
+ bool mIsForSanitizerAPI = false;
+
+ bool mAllowCustomElements = false;
+ bool mAllowUnknownMarkup = false;
+
+ // An allow-list of elements to keep.
+ mozilla::UniquePtr<ElementNameSet> mAllowElements;
+
+ // A deny-list of elements to block. (aka flatten)
+ mozilla::UniquePtr<ElementNameSet> mBlockElements;
+
+ // A deny-list of elements to drop. (aka prune)
+ mozilla::UniquePtr<ElementNameSet> mDropElements;
+
+ // An allow-list of attributes to keep.
+ mozilla::UniquePtr<AttributesToElementsMap> mAllowAttributes;
+
+ // A deny-list of attributes to drop.
+ mozilla::UniquePtr<AttributesToElementsMap> mDropAttributes;
+};
+
+#endif // nsTreeSanitizer_h_
diff --git a/dom/base/nsViewportInfo.cpp b/dom/base/nsViewportInfo.cpp
new file mode 100644
index 0000000000..67ac728330
--- /dev/null
+++ b/dom/base/nsViewportInfo.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewportInfo.h"
+#include "mozilla/Assertions.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+void nsViewportInfo::ConstrainViewportValues() {
+ // Non-positive zoom factors can produce NaN or negative viewport sizes,
+ // so we better be sure our constraints will produce positive zoom factors.
+ MOZ_ASSERT(mMinZoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+ MOZ_ASSERT(mMaxZoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+
+ if (mDefaultZoom > mMaxZoom) {
+ mDefaultZoomValid = false;
+ mDefaultZoom = mMaxZoom;
+ }
+ if (mDefaultZoom < mMinZoom) {
+ mDefaultZoomValid = false;
+ mDefaultZoom = mMinZoom;
+ }
+}
+
+static const float& MinOrMax(const float& aA, const float& aB,
+ const float& (*aMinOrMax)(const float&,
+ const float&)) {
+ MOZ_ASSERT(aA != nsViewportInfo::kExtendToZoom &&
+ aA != nsViewportInfo::kDeviceSize &&
+ aB != nsViewportInfo::kExtendToZoom &&
+ aB != nsViewportInfo::kDeviceSize);
+ if (aA == nsViewportInfo::kAuto) {
+ return aB;
+ }
+ if (aB == nsViewportInfo::kAuto) {
+ return aA;
+ }
+ return aMinOrMax(aA, aB);
+}
+
+// static
+const float& nsViewportInfo::Min(const float& aA, const float& aB) {
+ return MinOrMax(aA, aB, std::min);
+}
+
+// static
+const float& nsViewportInfo::Max(const float& aA, const float& aB) {
+ return MinOrMax(aA, aB, std::max);
+}
diff --git a/dom/base/nsViewportInfo.h b/dom/base/nsViewportInfo.h
new file mode 100644
index 0000000000..dff49545c8
--- /dev/null
+++ b/dom/base/nsViewportInfo.h
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsViewportInfo_h___
+#define nsViewportInfo_h___
+
+#include <algorithm>
+#include <stdint.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "Units.h"
+
+namespace mozilla::dom {
+enum class ViewportFitType : uint8_t {
+ Auto,
+ Contain,
+ Cover,
+};
+} // namespace mozilla::dom
+
+/**
+ * Default values for the nsViewportInfo class.
+ */
+static const mozilla::CSSIntSize kViewportMinSize(200, 40);
+static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);
+
+inline mozilla::LayoutDeviceToScreenScale ViewportMinScale() {
+ return mozilla::LayoutDeviceToScreenScale(
+ std::max(mozilla::StaticPrefs::apz_min_zoom(), 0.1f));
+}
+
+inline mozilla::LayoutDeviceToScreenScale ViewportMaxScale() {
+ return mozilla::LayoutDeviceToScreenScale(
+ std::min(mozilla::StaticPrefs::apz_max_zoom(), 100.0f));
+}
+
+/**
+ * Information retrieved from the <meta name="viewport"> tag. See
+ * Document::GetViewportInfo for more information on this functionality.
+ */
+class MOZ_STACK_CLASS nsViewportInfo {
+ public:
+ enum class AutoSizeFlag {
+ AutoSize,
+ FixedSize,
+ };
+ enum class AutoScaleFlag {
+ AutoScale,
+ FixedScale,
+ };
+ enum class ZoomFlag {
+ AllowZoom,
+ DisallowZoom,
+ };
+ enum class ZoomBehaviour {
+ Mobile,
+ Desktop, // disallows zooming out past default zoom
+ };
+ nsViewportInfo(const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSToScreenScale& aDefaultZoom,
+ ZoomFlag aZoomFlag, ZoomBehaviour aBehaviour)
+ : mDefaultZoom(aDefaultZoom),
+ mViewportFit(mozilla::dom::ViewportFitType::Auto),
+ mDefaultZoomValid(true),
+ mAutoSize(true),
+ mAllowZoom(aZoomFlag == ZoomFlag::AllowZoom) {
+ mSize = mozilla::ScreenSize(aDisplaySize) / mDefaultZoom;
+ mozilla::CSSToLayoutDeviceScale pixelRatio(1.0f);
+ if (aBehaviour == ZoomBehaviour::Desktop) {
+ mMinZoom = aDefaultZoom;
+ } else {
+ mMinZoom = pixelRatio * ViewportMinScale();
+ }
+ mMaxZoom = pixelRatio * ViewportMaxScale();
+ ConstrainViewportValues();
+ }
+
+ nsViewportInfo(const mozilla::CSSToScreenScale& aDefaultZoom,
+ const mozilla::CSSToScreenScale& aMinZoom,
+ const mozilla::CSSToScreenScale& aMaxZoom,
+ const mozilla::CSSSize& aSize, AutoSizeFlag aAutoSizeFlag,
+ AutoScaleFlag aAutoScaleFlag, ZoomFlag aZoomFlag,
+ mozilla::dom::ViewportFitType aViewportFit)
+ : mDefaultZoom(aDefaultZoom),
+ mMinZoom(aMinZoom),
+ mMaxZoom(aMaxZoom),
+ mSize(aSize),
+ mViewportFit(aViewportFit),
+ mDefaultZoomValid(aAutoScaleFlag != AutoScaleFlag::AutoScale),
+ mAutoSize(aAutoSizeFlag == AutoSizeFlag::AutoSize),
+ mAllowZoom(aZoomFlag == ZoomFlag::AllowZoom) {
+ ConstrainViewportValues();
+ }
+
+ bool IsDefaultZoomValid() const { return mDefaultZoomValid; }
+ mozilla::CSSToScreenScale GetDefaultZoom() const { return mDefaultZoom; }
+ mozilla::CSSToScreenScale GetMinZoom() const { return mMinZoom; }
+ mozilla::CSSToScreenScale GetMaxZoom() const { return mMaxZoom; }
+
+ mozilla::CSSSize GetSize() const { return mSize; }
+
+ bool IsAutoSizeEnabled() const { return mAutoSize; }
+ bool IsZoomAllowed() const { return mAllowZoom; }
+
+ mozilla::dom::ViewportFitType GetViewportFit() const { return mViewportFit; }
+
+ static constexpr float kAuto = -1.0f;
+ static constexpr float kExtendToZoom = -2.0f;
+ static constexpr float kDeviceSize =
+ -3.0f; // for device-width or device-height
+
+ // MIN/MAX computations where one of the arguments is auto resolve to the
+ // other argument. For instance, MIN(0.25, auto) = 0.25, and
+ // MAX(5, auto) = 5.
+ // https://drafts.csswg.org/css-device-adapt/#constraining-defs
+ static const float& Max(const float& aA, const float& aB);
+ static const float& Min(const float& aA, const float& aB);
+
+ private:
+ /**
+ * Constrain the viewport calculations from the
+ * Document::GetViewportInfo() function in order to always return
+ * sane minimum/maximum values.
+ */
+ void ConstrainViewportValues();
+
+ // Default zoom indicates the level at which the display is 'zoomed in'
+ // initially for the user, upon loading of the page.
+ mozilla::CSSToScreenScale mDefaultZoom;
+
+ // The minimum zoom level permitted by the page.
+ mozilla::CSSToScreenScale mMinZoom;
+
+ // The maximum zoom level permitted by the page.
+ mozilla::CSSToScreenScale mMaxZoom;
+
+ // The size of the viewport, specified by the <meta name="viewport"> tag.
+ mozilla::CSSSize mSize;
+
+ // The value of the viewport-fit.
+ mozilla::dom::ViewportFitType mViewportFit;
+
+ // If the default zoom was specified and was between the min and max
+ // zoom values.
+ // FIXME: Bug 1504362 - Unify this and mDefaultZoom into
+ // Maybe<CSSToScreenScale>.
+ bool mDefaultZoomValid;
+
+ // Whether or not we should automatically size the viewport to the device's
+ // width. This is true if the document has been optimized for mobile, and
+ // the width property of a specified <meta name="viewport"> tag is either
+ // not specified, or is set to the special value 'device-width'.
+ bool mAutoSize;
+
+ // Whether or not the user can zoom in and out on the page. Default is true.
+ bool mAllowZoom;
+};
+
+#endif
diff --git a/dom/base/nsWindowMemoryReporter.cpp b/dom/base/nsWindowMemoryReporter.cpp
new file mode 100644
index 0000000000..5b9ceedc44
--- /dev/null
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -0,0 +1,936 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowMemoryReporter.h"
+#include "nsWindowSizes.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "XPCJSMemoryReporter.h"
+#include "js/MemoryMetrics.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULPrototypeCache.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
+
+/**
+ * Don't trigger a ghost window check when a DOM window is detached if we've
+ * run it this recently.
+ */
+const int32_t kTimeBetweenChecks = 45; /* seconds */
+
+nsWindowMemoryReporter::nsWindowMemoryReporter()
+ : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
+ mCycleCollectorIsRunning(false),
+ mCheckTimerWaitingForCCEnd(false),
+ mGhostWindowCount(0) {}
+
+nsWindowMemoryReporter::~nsWindowMemoryReporter() { KillCheckTimer(); }
+
+NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
+ nsISupportsWeakReference)
+
+static nsresult AddNonJSSizeOfWindowAndItsDescendents(
+ nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
+ // Measure the window.
+ SizeOfState state(moz_malloc_size_of);
+ nsWindowSizes windowSizes(state);
+ aWindow->AddSizeOfIncludingThis(windowSizes);
+
+ // Measure the inner window, if there is one.
+ nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
+ if (inner) {
+ inner->AddSizeOfIncludingThis(windowSizes);
+ }
+
+ windowSizes.addToTabSizes(aSizes);
+
+ BrowsingContext* bc = aWindow->GetBrowsingContext();
+ if (!bc) {
+ return NS_OK;
+ }
+
+ // Measure this window's descendents.
+ for (const auto& frame : bc->Children()) {
+ if (auto* childWin = nsGlobalWindowOuter::Cast(frame->GetDOMWindow())) {
+ MOZ_TRY(AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes));
+ }
+ }
+ return NS_OK;
+}
+
+static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize,
+ size_t* aStyleSize, size_t* aOtherSize) {
+ nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
+
+ nsTabSizes sizes;
+ nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aDomSize = sizes.mDom;
+ *aStyleSize = sizes.mStyle;
+ *aOtherSize = sizes.mOther;
+ return NS_OK;
+}
+
+/* static */
+void nsWindowMemoryReporter::Init() {
+ MOZ_ASSERT(!sWindowReporter);
+ sWindowReporter = new nsWindowMemoryReporter();
+ ClearOnShutdown(&sWindowReporter);
+ RegisterStrongMemoryReporter(sWindowReporter);
+ RegisterNonJSSizeOfTab(NonJSSizeOfTab);
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
+ /* weakRef = */ true);
+ os->AddObserver(sWindowReporter, "cycle-collector-begin",
+ /* weakRef = */ true);
+ os->AddObserver(sWindowReporter, "cycle-collector-end",
+ /* weakRef = */ true);
+ }
+
+ RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount);
+}
+
+/* static */
+nsWindowMemoryReporter* nsWindowMemoryReporter::Get() {
+ return sWindowReporter;
+}
+
+static nsCString GetWindowURISpec(nsGlobalWindowInner* aWindow) {
+ NS_ENSURE_TRUE(aWindow, ""_ns);
+
+ nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
+ if (doc) {
+ nsCOMPtr<nsIURI> uri;
+ uri = doc->GetDocumentURI();
+ return uri->GetSpecOrDefault();
+ }
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
+ do_QueryObject(aWindow);
+ NS_ENSURE_TRUE(scriptObjPrincipal, ""_ns);
+
+ // GetPrincipal() will print a warning if the window does not have an outer
+ // window, so check here for an outer window first. This code is
+ // functionally correct if we leave out the GetOuterWindow() check, but we
+ // end up printing a lot of warnings during debug mochitests.
+ if (!aWindow->GetOuterWindow()) {
+ return ""_ns;
+ }
+ nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
+ if (!principal) {
+ return ""_ns;
+ }
+ nsCString spec;
+ principal->GetAsciiSpec(spec);
+ return spec;
+}
+
+static void AppendWindowURI(nsGlobalWindowInner* aWindow, nsACString& aStr,
+ bool aAnonymize) {
+ nsCString spec = GetWindowURISpec(aWindow);
+
+ if (spec.IsEmpty()) {
+ // If we're unable to find a URI, we're dealing with a chrome window with
+ // no document in it (or somesuch), so we call this a "system window".
+ aStr += "[system]"_ns;
+ return;
+ }
+ if (aAnonymize && !aWindow->IsChromeWindow()) {
+ aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID());
+ return;
+ }
+ // A hack: replace forward slashes with '\\' so they aren't
+ // treated as path separators. Users of the reporters
+ // (such as about:memory) have to undo this change.
+ spec.ReplaceChar('/', '\\');
+ aStr += spec;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf)
+
+// The key is the window ID.
+using WindowPaths = nsTHashMap<nsUint64HashKey, nsCString>;
+
+static void ReportAmount(const nsCString& aBasePath, const char* aPathTail,
+ size_t aAmount, const nsCString& aDescription,
+ uint32_t aKind, uint32_t aUnits,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData) {
+ if (aAmount == 0) {
+ return;
+ }
+
+ nsAutoCString path(aBasePath);
+ path += aPathTail;
+
+ aHandleReport->Callback(""_ns, path, aKind, aUnits, aAmount, aDescription,
+ aData);
+}
+
+static void ReportSize(const nsCString& aBasePath, const char* aPathTail,
+ size_t aAmount, const nsCString& aDescription,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData) {
+ ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
+ nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+ aHandleReport, aData);
+}
+
+static void ReportCount(const nsCString& aBasePath, const char* aPathTail,
+ size_t aAmount, const nsCString& aDescription,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData) {
+ ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
+ nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
+ aHandleReport, aData);
+}
+
+static void ReportDOMSize(const nsCString& aBasePath,
+ nsDOMSizes& aTotalDOMSizes,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, nsDOMSizes aDOMSizes) {
+#define REPORT_DOM_SIZE(_windowPath, _pathTail, _field, _desc) \
+ ReportSize(_windowPath, _pathTail, aDOMSizes._field, \
+ nsLiteralCString(_desc), aHandleReport, aData); \
+ aTotalDOMSizes._field += aDOMSizes._field;
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/element-nodes", mDOMElementNodesSize,
+ "Memory used by the element nodes in a window's DOM.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/text-nodes", mDOMTextNodesSize,
+ "Memory used by the text nodes in a window's DOM.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/cdata-nodes", mDOMCDATANodesSize,
+ "Memory used by the CDATA nodes in a window's DOM.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/comment-nodes", mDOMCommentNodesSize,
+ "Memory used by the comment nodes in a window's DOM.");
+
+ REPORT_DOM_SIZE(
+ aBasePath, "/dom/event-targets", mDOMEventTargetsSize,
+ "Memory used by the event targets table in a window's DOM, and "
+ "the objects it points to, which include XHRs.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/performance/user-entries",
+ mDOMPerformanceUserEntries,
+ "Memory used for performance user entries.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/performance/resource-entries",
+ mDOMPerformanceResourceEntries,
+ "Memory used for performance resource entries.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/media-query-lists", mDOMMediaQueryLists,
+ "Memory used by MediaQueryList objects for the window's "
+ "document.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/resize-observers",
+ mDOMResizeObserverControllerSize,
+ "Memory used for resize observers.");
+
+ REPORT_DOM_SIZE(aBasePath, "/dom/other", mDOMOtherSize,
+ "Memory used by a window's DOM that isn't measured by the "
+ "other 'dom/' numbers.");
+#undef REPORT_DOM_SIZE
+}
+
+static void CollectWindowReports(nsGlobalWindowInner* aWindow,
+ nsWindowSizes* aWindowTotalSizes,
+ nsTHashSet<uint64_t>* aGhostWindowIDs,
+ WindowPaths* aWindowPaths,
+ WindowPaths* aTopWindowPaths,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ nsAutoCString windowPath("explicit/");
+
+ // Avoid calling aWindow->GetInProcessTop() if there's no outer window. It
+ // will work just fine, but will spew a lot of warnings.
+ nsGlobalWindowOuter* top = nullptr;
+ if (aWindow->GetOuterWindow()) {
+ // Our window should have a null top iff it has a null docshell.
+ MOZ_ASSERT(!!aWindow->GetInProcessTopInternal() ==
+ !!aWindow->GetDocShell());
+ top = aWindow->GetInProcessTopInternal();
+ }
+
+ windowPath += "window-objects/"_ns;
+
+ if (top) {
+ windowPath += "top("_ns;
+ AppendWindowURI(top->GetCurrentInnerWindowInternal(), windowPath,
+ aAnonymize);
+ windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID());
+
+ aTopWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath);
+
+ windowPath += aWindow->IsFrozen() ? "/cached/"_ns : "/active/"_ns;
+ } else {
+ if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
+ windowPath += "top(none)/ghost/"_ns;
+ } else {
+ windowPath += "top(none)/detached/"_ns;
+ }
+ }
+
+ windowPath += "window("_ns;
+ AppendWindowURI(aWindow, windowPath, aAnonymize);
+ windowPath += ")"_ns;
+
+ // Use |windowPath|, but replace "explicit/" with "event-counts/".
+ nsCString censusWindowPath(windowPath);
+ censusWindowPath.ReplaceLiteral(0, strlen("explicit"), "event-counts");
+
+ // Remember the path for later.
+ aWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath);
+
+// Report the size from windowSizes and add to the appropriate total in
+// aWindowTotalSizes.
+#define REPORT_SIZE(_pathTail, _field, _desc) \
+ ReportSize(windowPath, _pathTail, windowSizes._field, \
+ nsLiteralCString(_desc), aHandleReport, aData); \
+ aWindowTotalSizes->_field += windowSizes._field;
+
+// Report the size, which is a sum of other sizes, and so doesn't require
+// updating aWindowTotalSizes.
+#define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
+ ReportSize(windowPath, _pathTail, _amount, nsLiteralCString(_desc), \
+ aHandleReport, aData);
+
+// Like REPORT_SIZE, but for a count.
+#define REPORT_COUNT(_pathTail, _field, _desc) \
+ ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
+ nsLiteralCString(_desc), aHandleReport, aData); \
+ aWindowTotalSizes->_field += windowSizes._field;
+
+ // This SizeOfState contains the SeenPtrs used for all memory reporting of
+ // this window.
+ SizeOfState state(WindowsMallocSizeOf);
+ nsWindowSizes windowSizes(state);
+ aWindow->AddSizeOfIncludingThis(windowSizes);
+
+ ReportDOMSize(windowPath, aWindowTotalSizes->mDOMSizes, aHandleReport, aData,
+ windowSizes.mDOMSizes);
+
+ nsCString dataDocumentPath(windowPath);
+ dataDocumentPath += "/data-documents";
+ nsWindowSizes dataDocumentSizes(state);
+ aWindow->CollectDOMSizesForDataDocuments(dataDocumentSizes);
+ ReportDOMSize(dataDocumentPath, aWindowTotalSizes->mDOMSizes, aHandleReport,
+ aData, dataDocumentSizes.mDOMSizes);
+
+ REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize,
+ "Memory used by document style sheets within a window.");
+
+ REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations,
+ "Memory used by mapped declarations of SVG elements");
+
+ REPORT_SIZE("/layout/shadow-dom/style-sheets",
+ mLayoutShadowDomStyleSheetsSize,
+ "Memory used by Shadow DOM style sheets within a window.");
+
+ // TODO(emilio): We might want to split this up between invalidation map /
+ // element-and-pseudos / revalidation too just like the style set.
+ REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles,
+ "Memory used by Shadow DOM computed rule data within a window.");
+
+ REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize,
+ "Memory used by layout's PresShell, along with any structures "
+ "allocated in its arena and not measured elsewhere, "
+ "within a window.");
+
+ REPORT_SIZE("/layout/display-list", mLayoutRetainedDisplayListSize,
+ "Memory used by the retained display list data, "
+ "along with any structures allocated in its arena and not "
+ "measured elsewhere, within a window.");
+
+ REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
+ mLayoutStyleSetsStylistRuleTree,
+ "Memory used by rule trees within style sets within a window.");
+
+ REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
+ mLayoutStyleSetsStylistElementAndPseudosMaps,
+ "Memory used by element and pseudos maps within style "
+ "sets within a window.");
+
+ REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
+ mLayoutStyleSetsStylistInvalidationMap,
+ "Memory used by invalidation maps within style sets "
+ "within a window.");
+
+ REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
+ mLayoutStyleSetsStylistRevalidationSelectors,
+ "Memory used by selectors for cache revalidation within "
+ "style sets within a window.");
+
+ REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther,
+ "Memory used by other Stylist data within style sets "
+ "within a window.");
+
+ REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther,
+ "Memory used by other parts of style sets within a window.");
+
+ REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects,
+ "Memory used for ElementData objects, but not the things "
+ "hanging off them.");
+
+ REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize,
+ "Memory used for text-runs (glyph layout) in the PresShell's "
+ "frame tree, within a window.");
+
+ REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize,
+ "Memory used for the PresContext in the PresShell's frame "
+ "within a window.");
+
+ REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize,
+ "Memory used for frame properties attached to frames "
+ "within a window.");
+
+ REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom,
+ "Memory used by ComputedValues objects accessible from DOM "
+ "elements.");
+
+ REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom,
+ "Memory used by ComputedValues objects not accessible from DOM "
+ "elements.");
+
+ REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited,
+ "Memory used by ComputedValues objects used for visited styles.");
+
+ REPORT_SIZE("/property-tables", mPropertyTablesSize,
+ "Memory used for the property tables within a window.");
+
+ REPORT_SIZE("/bindings", mBindingsSize,
+ "Memory used by bindings within a window.");
+
+ REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount,
+ "Number of non-node event targets in the event targets table "
+ "in a window's DOM, such as XHRs.");
+
+ REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount,
+ "Number of event listeners in a window, including event "
+ "listeners on nodes and other event targets.");
+
+ // There are many different kinds of frames, but it is very likely
+ // that only a few matter. Implement a cutoff so we don't bloat
+ // about:memory with many uninteresting entries.
+ const size_t ARENA_SUNDRIES_THRESHOLD =
+ js::MemoryReportingSundriesThreshold();
+
+ size_t presArenaSundriesSize = 0;
+#define ARENA_OBJECT(name_, sundries_size_, prefix_) \
+ { \
+ size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_); \
+ if (size < ARENA_SUNDRIES_THRESHOLD) { \
+ sundries_size_ += size; \
+ } else { \
+ REPORT_SUM_SIZE(prefix_ #name_, size, \
+ "Memory used by objects of type " #name_ \
+ " within a window."); \
+ } \
+ aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += size; \
+ }
+#define PRES_ARENA_OBJECT(name_) \
+ ARENA_OBJECT(name_, presArenaSundriesSize, "/layout/pres-arena/")
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT
+
+ if (presArenaSundriesSize > 0) {
+ REPORT_SUM_SIZE(
+ "/layout/pres-arena/sundries", presArenaSundriesSize,
+ "The sum of all memory used by objects in the arena which were too "
+ "small to be shown individually.");
+ }
+
+ size_t displayListArenaSundriesSize = 0;
+#define DISPLAY_LIST_ARENA_OBJECT(name_) \
+ ARENA_OBJECT(name_, displayListArenaSundriesSize, \
+ "/layout/display-list-arena/")
+#include "nsDisplayListArenaTypes.h"
+#undef DISPLAY_LIST_ARENA_OBJECT
+
+ if (displayListArenaSundriesSize > 0) {
+ REPORT_SUM_SIZE(
+ "/layout/display-list-arena/sundries", displayListArenaSundriesSize,
+ "The sum of all memory used by objects in the DL arena which were too "
+ "small to be shown individually.");
+ }
+
+#undef ARENA_OBJECT
+
+ // There are many different kinds of style structs, but it is likely that
+ // only a few matter. Implement a cutoff so we don't bloat about:memory with
+ // many uninteresting entries.
+ const size_t STYLE_SUNDRIES_THRESHOLD =
+ js::MemoryReportingSundriesThreshold();
+
+ size_t styleSundriesSize = 0;
+#define STYLE_STRUCT(name_) \
+ { \
+ size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
+ if (size < STYLE_SUNDRIES_THRESHOLD) { \
+ styleSundriesSize += size; \
+ } else { \
+ REPORT_SUM_SIZE("/layout/style-structs/" #name_, size, \
+ "Memory used by the " #name_ \
+ " style structs within a window."); \
+ } \
+ aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ if (styleSundriesSize > 0) {
+ REPORT_SUM_SIZE(
+ "/layout/style-structs/sundries", styleSundriesSize,
+ "The sum of all memory used by style structs which were too "
+ "small to be shown individually.");
+ }
+
+#undef REPORT_SIZE
+#undef REPORT_SUM_SIZE
+#undef REPORT_COUNT
+}
+
+using WindowArray = nsTArray<RefPtr<nsGlobalWindowInner>>;
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
+ nsGlobalWindowInner::GetWindowsTable();
+ NS_ENSURE_TRUE(windowsById, NS_OK);
+
+ // Hold on to every window in memory so that window objects can't be
+ // destroyed while we're calling the memory reporter callback.
+ const auto windows = ToTArray<WindowArray>(windowsById->Values());
+
+ // Get the IDs of all the "ghost" windows, and call
+ // aHandleReport->Callback() for each one.
+ nsTHashSet<uint64_t> ghostWindows;
+ CheckForGhostWindows(&ghostWindows);
+
+ for (const auto& key : ghostWindows) {
+ nsGlobalWindowInner* window = windowsById->Get(key);
+ if (!window) {
+ NS_WARNING("Could not look up window?");
+ continue;
+ }
+
+ nsAutoCString path;
+ path.AppendLiteral("ghost-windows/");
+ AppendWindowURI(window, path, aAnonymize);
+
+ aHandleReport->Callback(
+ /* process = */ ""_ns, path, nsIMemoryReporter::KIND_OTHER,
+ nsIMemoryReporter::UNITS_COUNT,
+ /* amount = */ 1,
+ /* description = */ "A ghost window."_ns, aData);
+ }
+
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
+"The number of ghost windows present (the number of nodes underneath "
+"explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost "
+"window is not shown in any tab, is not in a browsing context group with any "
+"non-detached windows, and has met these criteria for at least "
+"memory.ghost_window_timeout_seconds, or has survived a round of "
+"about:memory's minimize memory usage button.\n\n"
+"Ghost windows can happen legitimately, but they are often indicative of "
+"leaks in the browser or add-ons.");
+ // clang-format on
+
+ WindowPaths windowPaths;
+ WindowPaths topWindowPaths;
+
+ // Collect window memory usage.
+ SizeOfState fakeState(nullptr); // this won't be used
+ nsWindowSizes windowTotalSizes(fakeState);
+ for (uint32_t i = 0; i < windows.Length(); i++) {
+ CollectWindowReports(windows[i], &windowTotalSizes, &ghostWindows,
+ &windowPaths, &topWindowPaths, aHandleReport, aData,
+ aAnonymize);
+ }
+
+ // Report JS memory usage. We do this from here because the JS memory
+ // reporter needs to be passed |windowPaths|.
+ xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths, aHandleReport,
+ aData, aAnonymize);
+
+ nsXULPrototypeCache::CollectMemoryReports(aHandleReport, aData);
+
+#define REPORT(_path, _amount, _desc) \
+ aHandleReport->Callback(""_ns, nsLiteralCString(_path), KIND_OTHER, \
+ UNITS_BYTES, _amount, nsLiteralCString(_desc), \
+ aData);
+
+ REPORT("window-objects/dom/element-nodes",
+ windowTotalSizes.mDOMSizes.mDOMElementNodesSize,
+ "This is the sum of all windows' 'dom/element-nodes' numbers.");
+
+ REPORT("window-objects/dom/text-nodes",
+ windowTotalSizes.mDOMSizes.mDOMTextNodesSize,
+ "This is the sum of all windows' 'dom/text-nodes' numbers.");
+
+ REPORT("window-objects/dom/cdata-nodes",
+ windowTotalSizes.mDOMSizes.mDOMCDATANodesSize,
+ "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
+
+ REPORT("window-objects/dom/comment-nodes",
+ windowTotalSizes.mDOMSizes.mDOMCommentNodesSize,
+ "This is the sum of all windows' 'dom/comment-nodes' numbers.");
+
+ REPORT("window-objects/dom/event-targets",
+ windowTotalSizes.mDOMSizes.mDOMEventTargetsSize,
+ "This is the sum of all windows' 'dom/event-targets' numbers.");
+
+ REPORT("window-objects/dom/performance",
+ windowTotalSizes.mDOMSizes.mDOMPerformanceUserEntries +
+ windowTotalSizes.mDOMSizes.mDOMPerformanceResourceEntries,
+ "This is the sum of all windows' 'dom/performance/' numbers.");
+
+ REPORT("window-objects/dom/other", windowTotalSizes.mDOMSizes.mDOMOtherSize,
+ "This is the sum of all windows' 'dom/other' numbers.");
+
+ REPORT("window-objects/layout/style-sheets",
+ windowTotalSizes.mLayoutStyleSheetsSize,
+ "This is the sum of all windows' 'layout/style-sheets' numbers.");
+
+ REPORT("window-objects/layout/pres-shell",
+ windowTotalSizes.mLayoutPresShellSize,
+ "This is the sum of all windows' 'layout/arenas' numbers.");
+
+ REPORT("window-objects/layout/style-sets",
+ windowTotalSizes.mLayoutStyleSetsStylistRuleTree +
+ windowTotalSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +
+ windowTotalSizes.mLayoutStyleSetsStylistInvalidationMap +
+ windowTotalSizes.mLayoutStyleSetsStylistRevalidationSelectors +
+ windowTotalSizes.mLayoutStyleSetsStylistOther +
+ windowTotalSizes.mLayoutStyleSetsOther,
+ "This is the sum of all windows' 'layout/style-sets/' numbers.");
+
+ REPORT("window-objects/layout/element-data-objects",
+ windowTotalSizes.mLayoutElementDataObjects,
+ "This is the sum of all windows' 'layout/element-data-objects' "
+ "numbers.");
+
+ REPORT("window-objects/layout/text-runs",
+ windowTotalSizes.mLayoutTextRunsSize,
+ "This is the sum of all windows' 'layout/text-runs' numbers.");
+
+ REPORT("window-objects/layout/pres-contexts",
+ windowTotalSizes.mLayoutPresContextSize,
+ "This is the sum of all windows' 'layout/pres-contexts' numbers.");
+
+ REPORT("window-objects/layout/frame-properties",
+ windowTotalSizes.mLayoutFramePropertiesSize,
+ "This is the sum of all windows' 'layout/frame-properties' numbers.");
+
+ REPORT("window-objects/layout/computed-values",
+ windowTotalSizes.mLayoutComputedValuesDom +
+ windowTotalSizes.mLayoutComputedValuesNonDom +
+ windowTotalSizes.mLayoutComputedValuesVisited,
+ "This is the sum of all windows' 'layout/computed-values/' numbers.");
+
+ REPORT("window-objects/property-tables", windowTotalSizes.mPropertyTablesSize,
+ "This is the sum of all windows' 'property-tables' numbers.");
+
+ size_t presArenaTotal = 0;
+#define PRES_ARENA_OBJECT(name_) \
+ presArenaTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT
+
+ REPORT("window-objects/layout/pres-arena", presArenaTotal,
+ "Memory used for the pres arena within windows. "
+ "This is the sum of all windows' 'layout/pres-arena/' numbers.");
+
+ size_t displayListArenaTotal = 0;
+#define DISPLAY_LIST_ARENA_OBJECT(name_) \
+ displayListArenaTotal += \
+ windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
+#include "nsDisplayListArenaTypes.h"
+#undef DISPLAY_LIST_ARENA_OBJECT
+
+ REPORT("window-objects/layout/display-list-arena", displayListArenaTotal,
+ "Memory used for the display list arena within windows. This is the "
+ "sum of all windows' 'layout/display-list-arena/' numbers.");
+
+ size_t styleTotal = 0;
+#define STYLE_STRUCT(name_) \
+ styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ REPORT("window-objects/layout/style-structs", styleTotal,
+ "Memory used for style structs within windows. This is the sum of "
+ "all windows' 'layout/style-structs/' numbers.");
+
+#undef REPORT
+
+ return NS_OK;
+}
+
+uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
+ return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
+}
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "after-minimize-memory-usage")) {
+ ObserveAfterMinimizeMemoryUsage();
+ } else if (!strcmp(aTopic, "cycle-collector-begin")) {
+ if (mCheckTimer) {
+ mCheckTimerWaitingForCCEnd = true;
+ KillCheckTimer();
+ }
+ mCycleCollectorIsRunning = true;
+ } else if (!strcmp(aTopic, "cycle-collector-end")) {
+ mCycleCollectorIsRunning = false;
+ if (mCheckTimerWaitingForCCEnd) {
+ mCheckTimerWaitingForCCEnd = false;
+ AsyncCheckForGhostWindows();
+ }
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ return NS_OK;
+}
+
+void nsWindowMemoryReporter::ObserveDOMWindowDetached(
+ nsGlobalWindowInner* aWindow) {
+ nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
+ if (!weakWindow) {
+ NS_WARNING("Couldn't take weak reference to a window?");
+ return;
+ }
+
+ mDetachedWindows.InsertOrUpdate(weakWindow, TimeStamp());
+
+ AsyncCheckForGhostWindows();
+}
+
+// static
+void nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData) {
+ if (sWindowReporter) {
+ MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning);
+ sWindowReporter->CheckForGhostWindows();
+ }
+}
+
+void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
+ if (mCheckTimer) {
+ return;
+ }
+
+ if (mCycleCollectorIsRunning) {
+ mCheckTimerWaitingForCCEnd = true;
+ return;
+ }
+
+ // If more than kTimeBetweenChecks seconds have elapsed since the last check,
+ // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
+ // since the last check. Reducing the delay by the time since the last check
+ // prevents the timer from being completely starved if it is repeatedly killed
+ // and restarted.
+ int32_t timeSinceLastCheck =
+ (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
+ int32_t timerDelay =
+ (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) *
+ PR_MSEC_PER_SEC;
+
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mCheckTimer), CheckTimerFired, nullptr, timerDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
+}
+
+void nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() {
+ // Someone claims they've done enough GC/CCs so that all eligible windows
+ // have been free'd. So we deem that any windows which satisfy ghost
+ // criteria (1) and (2) now satisfy criterion (3) as well.
+ //
+ // To effect this change, we'll backdate some of our timestamps.
+
+ TimeStamp minTimeStamp =
+ TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout());
+
+ for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
+ TimeStamp& timeStamp = iter.Data();
+ if (!timeStamp.IsNull() && timeStamp > minTimeStamp) {
+ timeStamp = minTimeStamp;
+ }
+ }
+}
+
+/**
+ * Iterate over mDetachedWindows and update it to reflect the current state of
+ * the world. In particular:
+ *
+ * - Remove weak refs to windows which no longer exist.
+ *
+ * - Remove references to windows which are no longer detached.
+ *
+ * - Reset the timestamp on detached windows which share a domain with a
+ * non-detached window (they no longer meet ghost criterion (2)).
+ *
+ * - If a window now meets ghost criterion (2) but didn't before, set its
+ * timestamp to now.
+ *
+ * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
+ * all ghost windows we found.
+ */
+void nsWindowMemoryReporter::CheckForGhostWindows(
+ nsTHashSet<uint64_t>* aOutGhostIDs /* = nullptr */) {
+ nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
+ nsGlobalWindowInner::GetWindowsTable();
+ if (!windowsById) {
+ NS_WARNING("GetWindowsTable returned null");
+ return;
+ }
+
+ mLastCheckForGhostWindows = TimeStamp::NowLoRes();
+ KillCheckTimer();
+
+ nsTHashSet<BrowsingContextGroup*> nonDetachedBrowsingContextGroups;
+
+ // Populate nonDetachedBrowsingContextGroups.
+ for (const auto& entry : *windowsById) {
+ // Null outer window implies null top, but calling GetInProcessTop() when
+ // there's no outer window causes us to spew debug warnings.
+ nsGlobalWindowInner* window = entry.GetWeak();
+ if (!window->GetOuterWindow() || !window->GetInProcessTopInternal() ||
+ !window->GetBrowsingContextGroup()) {
+ // This window is detached, so we don't care about its browsing
+ // context group.
+ continue;
+ }
+
+ nonDetachedBrowsingContextGroups.Insert(window->GetBrowsingContextGroup());
+ }
+
+ // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
+ // if it's not null.
+ uint32_t ghostTimeout = GetGhostTimeout();
+ TimeStamp now = mLastCheckForGhostWindows;
+ mGhostWindowCount = 0;
+ for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
+ nsWeakPtr weakKey = do_QueryInterface(iter.Key());
+ nsCOMPtr<mozIDOMWindow> iwindow = do_QueryReferent(weakKey);
+ if (!iwindow) {
+ // The window object has been destroyed. Stop tracking its weak ref in
+ // our hashtable.
+ iter.Remove();
+ continue;
+ }
+
+ nsPIDOMWindowInner* window = nsPIDOMWindowInner::From(iwindow);
+
+ // Avoid calling GetInProcessTop() if we have no outer window. Nothing
+ // will break if we do, but it will spew debug output, which can cause our
+ // test logs to overflow.
+ nsCOMPtr<nsPIDOMWindowOuter> top;
+ if (window->GetOuterWindow()) {
+ top = window->GetOuterWindow()->GetInProcessTop();
+ }
+
+ if (top) {
+ // The window is no longer detached, so we no longer want to track it.
+ iter.Remove();
+ continue;
+ }
+
+ TimeStamp& timeStamp = iter.Data();
+ BrowsingContextGroup* browsingContextGroup =
+ window->GetBrowsingContextGroup();
+ if (browsingContextGroup &&
+ nonDetachedBrowsingContextGroups.Contains(browsingContextGroup)) {
+ // This window is in the same browsing context group as a non-detached
+ // window, so reset its clock.
+ timeStamp = TimeStamp();
+ } else {
+ // This window is not in the same browsing context group as a non-detached
+ // window, so it meets ghost criterion (2).
+ if (timeStamp.IsNull()) {
+ // This may become a ghost window later; start its clock.
+ timeStamp = now;
+ } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
+ // This definitely is a ghost window, so add it to aOutGhostIDs, if
+ // that is not null.
+ mGhostWindowCount++;
+ if (aOutGhostIDs && window) {
+ aOutGhostIDs->Insert(window->WindowID());
+ }
+ }
+ }
+ }
+
+ Telemetry::ScalarSetMaximum(
+ Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS, mGhostWindowCount);
+}
+
+/* static */
+int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
+ return sWindowReporter->mGhostWindowCount;
+}
+
+void nsWindowMemoryReporter::KillCheckTimer() {
+ if (mCheckTimer) {
+ mCheckTimer->Cancel();
+ mCheckTimer = nullptr;
+ }
+}
+
+#ifdef DEBUG
+/* static */
+void nsWindowMemoryReporter::UnlinkGhostWindows() {
+ if (!sWindowReporter) {
+ return;
+ }
+
+ nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
+ nsGlobalWindowInner::GetWindowsTable();
+ if (!windowsById) {
+ return;
+ }
+
+ // Hold on to every window in memory so that window objects can't be
+ // destroyed while we're calling the UnlinkGhostWindows callback.
+ const auto windows = ToTArray<WindowArray>(windowsById->Values());
+
+ // Get the IDs of all the "ghost" windows, and unlink them all.
+ nsTHashSet<uint64_t> ghostWindows;
+ sWindowReporter->CheckForGhostWindows(&ghostWindows);
+ for (const auto& key : ghostWindows) {
+ nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
+ nsGlobalWindowInner::GetWindowsTable();
+ if (!windowsById) {
+ continue;
+ }
+
+ RefPtr<nsGlobalWindowInner> window = windowsById->Get(key);
+ if (window) {
+ window->RiskyUnlink();
+ }
+ }
+}
+#endif
diff --git a/dom/base/nsWindowMemoryReporter.h b/dom/base/nsWindowMemoryReporter.h
new file mode 100644
index 0000000000..ed2b296f26
--- /dev/null
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowMemoryReporter_h__
+#define nsWindowMemoryReporter_h__
+
+#include "nsGlobalWindow.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/TimeStamp.h"
+
+/**
+ * nsWindowMemoryReporter is responsible for the 'explicit/window-objects'
+ * memory reporter.
+ *
+ * We classify DOM window objects into one of three categories:
+ *
+ * - "active" windows, which are displayed in a tab (as the top-level window
+ * or an iframe),
+ *
+ * - "cached" windows, which are in the fastback cache (aka the bfcache), and
+ *
+ * - "detached" windows, which have a null docshell. A window becomes
+ * detached when its <iframe> or tab containing the window is destroyed --
+ * i.e., when the window is no longer active or cached.
+ *
+ * Additionally, we classify a subset of detached windows as "ghost" windows.
+ * Although ghost windows can happen legitimately (a page can hold a reference
+ * to a cross-domain window and then close its container), the presence of
+ * ghost windows is often indicative of a memory leak.
+ *
+ * A window is a ghost if it meets the following three criteria:
+ *
+ * 1) The window is detached.
+ *
+ * 2) There exist no non-detached windows with the same base domain as
+ * the window's principal. (For example, the base domain of
+ * "wiki.mozilla.co.uk" is "mozilla.co.uk".) This criterion makes us less
+ * likely to flag a legitimately held-alive detached window as a ghost.
+ *
+ * 3) The window has met criteria (1) and (2) above for at least
+ * memory.ghost_window_timeout_seconds. This criterion is in place so we
+ * don't immediately declare a window a ghost before the GC/CC has had a
+ * chance to run.
+ *
+ * nsWindowMemoryReporter observes window detachment and uses mDetachedWindows
+ * to remember when a window first met criteria (1) and (2). When we generate
+ * a memory report, we use this accounting to determine which windows are
+ * ghosts.
+ *
+ *
+ * We use the following memory reporter path for active and cached windows:
+ *
+ * explicit/window-objects/top(<top-outer-uri>, id=<top-outer-id>)/
+ * <category>/window(<window-uri>)/...
+ *
+ * For detached and ghost windows, we use
+ *
+ * explicit/window-objects/top(none)/<category>/window(<window-uri>)/...
+ *
+ * Where
+ *
+ * - <category> is "active", "cached", "detached", or "ghost", as described
+ * above.
+ *
+ * - <top-outer-id> is the window id of the top outer window (i.e. the tab, or
+ * the top level chrome window). Exposing this ensures that each tab gets
+ * its own sub-tree, even if multiple tabs are showing the same URI.
+ *
+ * - <top-uri> is the URI of the top window. Excepting special windows (such
+ * as browser.xhtml or hiddenWindow.html) it's what the address bar shows for
+ * the tab.
+ *
+ */
+class nsWindowMemoryReporter final : public nsIMemoryReporter,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ NS_DECL_NSIOBSERVER
+
+ static void Init();
+
+#ifdef DEBUG
+ /**
+ * Unlink all known ghost windows, to enable investigating what caused them
+ * to become ghost windows in the first place.
+ */
+ static void UnlinkGhostWindows();
+#endif
+
+ static nsWindowMemoryReporter* Get();
+ void ObserveDOMWindowDetached(nsGlobalWindowInner* aWindow);
+
+ static int64_t GhostWindowsDistinguishedAmount();
+
+ private:
+ ~nsWindowMemoryReporter();
+
+ // Protect ctor, use Init() instead.
+ nsWindowMemoryReporter();
+
+ /**
+ * Get the number of seconds for which a window must satisfy ghost criteria
+ * (1) and (2) before we deem that it satisfies criterion (3).
+ */
+ uint32_t GetGhostTimeout();
+
+ void ObserveAfterMinimizeMemoryUsage();
+
+ /**
+ * Iterate over all weak window pointers in mDetachedWindows and update our
+ * accounting of which windows meet ghost criterion (2).
+ *
+ * This method also cleans up mDetachedWindows, removing entries for windows
+ * which have been destroyed or are no longer detached.
+ *
+ * If aOutGhostIDs is non-null, we populate it with the Window IDs of the
+ * ghost windows.
+ *
+ * This is called asynchronously after we observe a DOM window being detached
+ * from its docshell, and also right before we generate a memory report.
+ */
+ void CheckForGhostWindows(nsTHashSet<uint64_t>* aOutGhostIDs = nullptr);
+
+ /**
+ * Eventually do a check for ghost windows, if we haven't done one recently
+ * and we aren't already planning to do one soon.
+ */
+ void AsyncCheckForGhostWindows();
+
+ /**
+ * Kill the check timer, if it exists.
+ */
+ void KillCheckTimer();
+
+ static void CheckTimerFired(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * Maps a weak reference to a detached window (nsIWeakReference) to the time
+ * when we observed that the window met ghost criterion (2) above.
+ *
+ * If the window has not yet met criterion (2) it maps to the null timestamp.
+ *
+ * (Although windows are not added to this table until they're detached, it's
+ * possible for a detached window to become non-detached, and we won't
+ * remove it from the table until CheckForGhostWindows runs.)
+ */
+ nsTHashMap<nsISupportsHashKey, mozilla::TimeStamp> mDetachedWindows;
+
+ /**
+ * Track the last time we ran CheckForGhostWindows(), to avoid running it
+ * too often after a DOM window is detached.
+ */
+ mozilla::TimeStamp mLastCheckForGhostWindows;
+
+ nsCOMPtr<nsITimer> mCheckTimer;
+
+ bool mCycleCollectorIsRunning;
+
+ bool mCheckTimerWaitingForCCEnd;
+
+ int64_t mGhostWindowCount;
+};
+
+#endif // nsWindowMemoryReporter_h__
diff --git a/dom/base/nsWindowRoot.cpp b/dom/base/nsWindowRoot.cpp
new file mode 100644
index 0000000000..9cc04bbbf1
--- /dev/null
+++ b/dom/base/nsWindowRoot.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/dom/WindowRootBinding.h"
+#include "nsCOMPtr.h"
+#include "nsWindowRoot.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsLayoutCID.h"
+#include "nsContentCID.h"
+#include "nsString.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsFrameLoader.h"
+#include "nsQueryActor.h"
+#include "nsGlobalWindow.h"
+#include "nsFocusManager.h"
+#include "nsIContent.h"
+#include "nsIControllers.h"
+#include "nsIController.h"
+#include "nsQueryObject.h"
+#include "xpcpublic.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "nsXULElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsWindowRoot::nsWindowRoot(nsPIDOMWindowOuter* aWindow) { mWindow = aWindow; }
+
+nsWindowRoot::~nsWindowRoot() {
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ }
+
+ JSActorService::UnregisterChromeEventTarget(this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsWindowRoot)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsWindowRoot)
+ JSActorService::UnregisterChromeEventTarget(tmp);
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsWindowRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowRoot)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsPIWindowRoot)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsWindowRoot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsWindowRoot)
+
+bool nsWindowRoot::DispatchEvent(Event& aEvent, CallerType aCallerType,
+ ErrorResult& aRv) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(
+ static_cast<EventTarget*>(this), nullptr, &aEvent, nullptr, &status);
+ bool retval = !aEvent.DefaultPrevented(aCallerType);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ return retval;
+}
+
+bool nsWindowRoot::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
+ return false;
+}
+
+EventListenerManager* nsWindowRoot::GetOrCreateListenerManager() {
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager* nsWindowRoot::GetExistingListenerManager() const {
+ return mListenerManager;
+}
+
+void nsWindowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ aVisitor.mCanHandle = true;
+ aVisitor.mForceContentDispatch = true; // FIXME! Bug 329119
+ // To keep mWindow alive
+ aVisitor.mItemData = static_cast<nsISupports*>(mWindow);
+ aVisitor.SetParentTarget(mParent, false);
+}
+
+nsresult nsWindowRoot::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ return NS_OK;
+}
+
+nsPIDOMWindowOuter* nsWindowRoot::GetOwnerGlobalForBindingsInternal() {
+ return mWindow;
+}
+
+nsIGlobalObject* nsWindowRoot::GetOwnerGlobal() const {
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(mWindow->GetCurrentInnerWindow());
+ // We're still holding a ref to it, so returning the raw pointer is ok...
+ return global;
+}
+
+nsPIDOMWindowOuter* nsWindowRoot::GetWindow() { return mWindow; }
+
+nsresult nsWindowRoot::GetControllers(bool aForVisibleWindow,
+ nsIControllers** aResult) {
+ *aResult = nullptr;
+
+ // XXX: we should fix this so there's a generic interface that
+ // describes controllers, so this code would have no special
+ // knowledge of what object might have controllers.
+
+ nsFocusManager::SearchRange searchRange =
+ aForVisibleWindow ? nsFocusManager::eIncludeVisibleDescendants
+ : nsFocusManager::eIncludeAllDescendants;
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ mWindow, searchRange, getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ RefPtr<nsXULElement> xulElement = nsXULElement::FromNode(focusedContent);
+ if (xulElement) {
+ ErrorResult rv;
+ *aResult = xulElement->GetControllers(rv);
+ NS_IF_ADDREF(*aResult);
+ return rv.StealNSResult();
+ }
+
+ HTMLTextAreaElement* htmlTextArea =
+ HTMLTextAreaElement::FromNode(focusedContent);
+ if (htmlTextArea) return htmlTextArea->GetControllers(aResult);
+
+ HTMLInputElement* htmlInputElement =
+ HTMLInputElement::FromNode(focusedContent);
+ if (htmlInputElement) return htmlInputElement->GetControllers(aResult);
+
+ if (focusedContent->IsEditable() && focusedWindow)
+ return focusedWindow->GetControllers(aResult);
+ } else {
+ return focusedWindow->GetControllers(aResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWindowRoot::GetControllerForCommand(const char* aCommand,
+ bool aForVisibleWindow,
+ nsIController** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ // If this is the parent process, check if a child browsing context from
+ // another process is focused, and ask if it has a controller actor that
+ // supports the command.
+ if (XRE_IsParentProcess()) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Unfortunately, messages updating the active/focus state in the focus
+ // manager don't happen fast enough in the case when switching focus between
+ // processes when clicking on a chrome UI element while a child tab is
+ // focused, so we need to check whether the focus manager thinks a child
+ // frame is focused as well.
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ mWindow, nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(focusedContent);
+ if (loaderOwner) {
+ // Only check browsing contexts if a remote frame is focused. If chrome is
+ // focused, just check the controllers directly below.
+ RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader();
+ if (frameLoader && frameLoader->IsRemoteFrame()) {
+ // GetActiveBrowsingContextInChrome actually returns the top-level
+ // browsing context if the focus is in a child process tab, or null if
+ // the focus is in chrome.
+ BrowsingContext* focusedBC =
+ fm->GetActiveBrowsingContextInChrome()
+ ? fm->GetFocusedBrowsingContextInChrome()
+ : nullptr;
+ if (focusedBC) {
+ // At this point, it is known that a child process is focused, so ask
+ // its Controllers actor if the command is supported.
+ nsCOMPtr<nsIController> controller = do_QueryActor(
+ "Controllers", focusedBC->Canonical()->GetCurrentWindowGlobal());
+ if (controller) {
+ bool supported;
+ controller->SupportsCommand(aCommand, &supported);
+ if (supported) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ {
+ nsCOMPtr<nsIControllers> controllers;
+ GetControllers(aForVisibleWindow, getter_AddRefs(controllers));
+ if (controllers) {
+ nsCOMPtr<nsIController> controller;
+ controllers->GetControllerForCommand(aCommand,
+ getter_AddRefs(controller));
+ if (controller) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsFocusManager::SearchRange searchRange =
+ aForVisibleWindow ? nsFocusManager::eIncludeVisibleDescendants
+ : nsFocusManager::eIncludeAllDescendants;
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(mWindow, searchRange,
+ getter_AddRefs(focusedWindow));
+ while (focusedWindow) {
+ nsCOMPtr<nsIControllers> controllers;
+ focusedWindow->GetControllers(getter_AddRefs(controllers));
+ if (controllers) {
+ nsCOMPtr<nsIController> controller;
+ controllers->GetControllerForCommand(aCommand,
+ getter_AddRefs(controller));
+ if (controller) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+
+ // XXXndeakin P3 is this casting safe?
+ nsGlobalWindowOuter* win = nsGlobalWindowOuter::Cast(focusedWindow);
+ focusedWindow = win->GetPrivateParent();
+ }
+
+ return NS_OK;
+}
+
+void nsWindowRoot::GetEnabledDisabledCommandsForControllers(
+ nsIControllers* aControllers, nsTHashSet<nsCString>& aCommandsHandled,
+ nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands) {
+ uint32_t controllerCount;
+ aControllers->GetControllerCount(&controllerCount);
+ for (uint32_t c = 0; c < controllerCount; c++) {
+ nsCOMPtr<nsIController> controller;
+ aControllers->GetControllerAt(c, getter_AddRefs(controller));
+
+ nsCOMPtr<nsICommandController> commandController(
+ do_QueryInterface(controller));
+ if (commandController) {
+ // All of our default command controllers have 20-60 commands. Let's just
+ // leave enough space here for all of them so we probably don't need to
+ // heap-allocate.
+ AutoTArray<nsCString, 64> commands;
+ if (NS_SUCCEEDED(commandController->GetSupportedCommands(commands))) {
+ for (auto& commandStr : commands) {
+ // Use a hash to determine which commands have already been handled by
+ // earlier controllers, as the earlier controller's result should get
+ // priority.
+ if (aCommandsHandled.EnsureInserted(commandStr)) {
+ // We inserted a new entry into aCommandsHandled.
+ bool enabled = false;
+ controller->IsCommandEnabled(commandStr.get(), &enabled);
+
+ if (enabled) {
+ aEnabledCommands.AppendElement(commandStr);
+ } else {
+ aDisabledCommands.AppendElement(commandStr);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void nsWindowRoot::GetEnabledDisabledCommands(
+ nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands) {
+ nsTHashSet<nsCString> commandsHandled;
+
+ nsCOMPtr<nsIControllers> controllers;
+ GetControllers(false, getter_AddRefs(controllers));
+ if (controllers) {
+ GetEnabledDisabledCommandsForControllers(
+ controllers, commandsHandled, aEnabledCommands, aDisabledCommands);
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(mWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ while (focusedWindow) {
+ focusedWindow->GetControllers(getter_AddRefs(controllers));
+ if (controllers) {
+ GetEnabledDisabledCommandsForControllers(
+ controllers, commandsHandled, aEnabledCommands, aDisabledCommands);
+ }
+
+ nsGlobalWindowOuter* win = nsGlobalWindowOuter::Cast(focusedWindow);
+ focusedWindow = win->GetPrivateParent();
+ }
+}
+
+already_AddRefed<nsINode> nsWindowRoot::GetPopupNode() {
+ nsCOMPtr<nsINode> popupNode = do_QueryReferent(mPopupNode);
+ return popupNode.forget();
+}
+
+void nsWindowRoot::SetPopupNode(nsINode* aNode) {
+ mPopupNode = do_GetWeakReference(aNode);
+}
+
+nsIGlobalObject* nsWindowRoot::GetParentObject() {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* nsWindowRoot::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::WindowRoot_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void nsWindowRoot::AddBrowser(nsIRemoteTab* aBrowser) {
+ nsWeakPtr weakBrowser = do_GetWeakReference(aBrowser);
+ mWeakBrowsers.Insert(weakBrowser);
+}
+
+void nsWindowRoot::RemoveBrowser(nsIRemoteTab* aBrowser) {
+ nsWeakPtr weakBrowser = do_GetWeakReference(aBrowser);
+ mWeakBrowsers.Remove(weakBrowser);
+}
+
+void nsWindowRoot::EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) {
+ // Collect strong references to all browsers in a separate array in
+ // case aEnumFunc alters mWeakBrowsers.
+ nsTArray<nsCOMPtr<nsIRemoteTab>> remoteTabs;
+ for (const auto& key : mWeakBrowsers) {
+ nsCOMPtr<nsIRemoteTab> remoteTab(do_QueryReferent(key));
+ if (remoteTab) {
+ remoteTabs.AppendElement(remoteTab);
+ }
+ }
+
+ for (uint32_t i = 0; i < remoteTabs.Length(); ++i) {
+ aEnumFunc(remoteTabs[i], aArg);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<EventTarget> NS_NewWindowRoot(nsPIDOMWindowOuter* aWindow) {
+ nsCOMPtr<EventTarget> result = new nsWindowRoot(aWindow);
+
+ RefPtr<JSActorService> wasvc = JSActorService::GetSingleton();
+ wasvc->RegisterChromeEventTarget(result);
+
+ return result.forget();
+}
diff --git a/dom/base/nsWindowRoot.h b/dom/base/nsWindowRoot.h
new file mode 100644
index 0000000000..7fb56acd18
--- /dev/null
+++ b/dom/base/nsWindowRoot.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowRoot_h__
+#define nsWindowRoot_h__
+
+class nsIGlobalObject;
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsPIWindowRoot.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashSet.h"
+#include "nsHashKeys.h"
+
+class nsWindowRoot final : public nsPIWindowRoot {
+ public:
+ explicit nsWindowRoot(nsPIDOMWindowOuter* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ mozilla::EventListenerManager* GetExistingListenerManager() const override;
+ mozilla::EventListenerManager* GetOrCreateListenerManager() override;
+
+ bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool DispatchEvent(
+ mozilla::dom::Event& aEvent, mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aRv) override;
+
+ void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
+
+ nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
+
+ // nsPIWindowRoot
+
+ nsPIDOMWindowOuter* GetWindow() override;
+
+ nsresult GetControllers(bool aForVisibleWindow,
+ nsIControllers** aResult) override;
+ nsresult GetControllerForCommand(const char* aCommand, bool aForVisibleWindow,
+ nsIController** _retval) override;
+
+ void GetEnabledDisabledCommands(
+ nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands) override;
+
+ already_AddRefed<nsINode> GetPopupNode() override;
+ void SetPopupNode(nsINode* aNode) override;
+
+ void SetParentTarget(mozilla::dom::EventTarget* aTarget) override {
+ mParent = aTarget;
+ }
+ mozilla::dom::EventTarget* GetParentTarget() override { return mParent; }
+ nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
+ nsIGlobalObject* GetOwnerGlobal() const override;
+
+ nsIGlobalObject* GetParentObject();
+
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsWindowRoot)
+
+ void AddBrowser(nsIRemoteTab* aBrowser) override;
+ void RemoveBrowser(nsIRemoteTab* aBrowser) override;
+ void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) override;
+
+ protected:
+ virtual ~nsWindowRoot();
+
+ void GetEnabledDisabledCommandsForControllers(
+ nsIControllers* aControllers, nsTHashSet<nsCString>& aCommandsHandled,
+ nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands);
+
+ // Members
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ // We own the manager, which owns event listeners attached to us.
+ RefPtr<mozilla::EventListenerManager> mListenerManager; // [Strong]
+ nsWeakPtr mPopupNode;
+
+ nsCOMPtr<mozilla::dom::EventTarget> mParent;
+
+ // The BrowserParents that are currently registered with this top-level
+ // window.
+ using WeakBrowserTable = nsTHashSet<RefPtr<nsIWeakReference>>;
+ WeakBrowserTable mWeakBrowsers;
+};
+
+extern already_AddRefed<mozilla::dom::EventTarget> NS_NewWindowRoot(
+ nsPIDOMWindowOuter* aWindow);
+
+#endif
diff --git a/dom/base/nsWindowSizes.h b/dom/base/nsWindowSizes.h
new file mode 100644
index 0000000000..1f8633ac7e
--- /dev/null
+++ b/dom/base/nsWindowSizes.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowSizes_h
+#define nsWindowSizes_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SizeOfState.h"
+
+class nsTabSizes {
+ public:
+ enum Kind {
+ DOM, // DOM stuff.
+ Style, // Style stuff.
+ Other // Everything else.
+ };
+
+ nsTabSizes() : mDom(0), mStyle(0), mOther(0) {}
+
+ void add(Kind kind, size_t n) {
+ switch (kind) {
+ case DOM:
+ mDom += n;
+ break;
+ case Style:
+ mStyle += n;
+ break;
+ case Other:
+ mOther += n;
+ break;
+ default:
+ MOZ_CRASH("bad nsTabSizes kind");
+ }
+ }
+
+ size_t mDom;
+ size_t mStyle;
+ size_t mOther;
+};
+
+#define ZERO_SIZE(kind, mSize) mSize(0),
+#define ADD_TO_TAB_SIZES(kind, mSize) aSizes->add(nsTabSizes::kind, mSize);
+#define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize;
+#define DECL_SIZE(kind, mSize) size_t mSize;
+
+#define NS_STYLE_SIZES_FIELD(name_) mStyle##name_
+
+struct nsStyleSizes {
+ nsStyleSizes()
+ :
+#define STYLE_STRUCT(name_) NS_STYLE_SIZES_FIELD(name_)(0),
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ dummy() {
+ }
+
+ void addToTabSizes(nsTabSizes* aSizes) const {
+#define STYLE_STRUCT(name_) \
+ aSizes->add(nsTabSizes::Style, NS_STYLE_SIZES_FIELD(name_));
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ }
+
+ size_t getTotalSize() const {
+ size_t total = 0;
+
+#define STYLE_STRUCT(name_) total += NS_STYLE_SIZES_FIELD(name_);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ return total;
+ }
+
+#define STYLE_STRUCT(name_) size_t NS_STYLE_SIZES_FIELD(name_);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ // Present just to absorb the trailing comma in the constructor.
+ int dummy;
+};
+
+#define NS_ARENA_SIZES_FIELD(classname) mArena##classname
+
+struct nsArenaSizes {
+ nsArenaSizes()
+ :
+#define PRES_ARENA_OBJECT(name_) NS_ARENA_SIZES_FIELD(name_)(0),
+#define DISPLAY_LIST_ARENA_OBJECT(name_) PRES_ARENA_OBJECT(name_)
+#include "nsPresArenaObjectList.h"
+#include "nsDisplayListArenaTypes.h"
+#undef PRES_ARENA_OBJECT
+#undef DISPLAY_LIST_ARENA_OBJECT
+ dummy() {
+ }
+
+ void addToTabSizes(nsTabSizes* aSizes) const {
+#define PRES_ARENA_OBJECT(name_) \
+ aSizes->add(nsTabSizes::Other, NS_ARENA_SIZES_FIELD(name_));
+#define DISPLAY_LIST_ARENA_OBJECT(name_) PRES_ARENA_OBJECT(name_)
+#include "nsPresArenaObjectList.h"
+#include "nsDisplayListArenaTypes.h"
+#undef PRES_ARENA_OBJECT
+#undef DISPLAY_LIST_ARENA_OBJECT
+ }
+
+ size_t getTotalSize() const {
+ size_t total = 0;
+
+#define PRES_ARENA_OBJECT(name_) total += NS_ARENA_SIZES_FIELD(name_);
+#define DISPLAY_LIST_ARENA_OBJECT(name_) PRES_ARENA_OBJECT(name_)
+#include "nsPresArenaObjectList.h"
+#include "nsDisplayListArenaTypes.h"
+#undef PRES_ARENA_OBJECT
+#undef DISPLAY_LIST_ARENA_OBJECT
+
+ return total;
+ }
+
+#define PRES_ARENA_OBJECT(name_) size_t NS_ARENA_SIZES_FIELD(name_);
+#define DISPLAY_LIST_ARENA_OBJECT(name_) PRES_ARENA_OBJECT(name_)
+#include "nsPresArenaObjectList.h"
+#include "nsDisplayListArenaTypes.h"
+#undef PRES_ARENA_OBJECT
+#undef DISPLAY_LIST_ARENA_OBJECT
+
+ // Present just to absorb the trailing comma in the constructor.
+ int dummy;
+};
+
+struct nsDOMSizes {
+#define FOR_EACH_SIZE(MACRO) \
+ MACRO(DOM, mDOMElementNodesSize) \
+ MACRO(DOM, mDOMTextNodesSize) \
+ MACRO(DOM, mDOMCDATANodesSize) \
+ MACRO(DOM, mDOMCommentNodesSize) \
+ MACRO(DOM, mDOMEventTargetsSize) \
+ MACRO(DOM, mDOMMediaQueryLists) \
+ MACRO(DOM, mDOMPerformanceEventEntries) \
+ MACRO(DOM, mDOMPerformanceUserEntries) \
+ MACRO(DOM, mDOMPerformanceResourceEntries) \
+ MACRO(DOM, mDOMResizeObserverControllerSize)
+
+ nsDOMSizes() : FOR_EACH_SIZE(ZERO_SIZE) mDOMOtherSize(0) {}
+
+ void addToTabSizes(nsTabSizes* aSizes) const {
+ FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+ aSizes->add(nsTabSizes::DOM, mDOMOtherSize);
+ }
+
+ size_t getTotalSize() const {
+ size_t total = 0;
+ FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE)
+ total += mDOMOtherSize;
+ return total;
+ }
+
+ FOR_EACH_SIZE(DECL_SIZE)
+
+ size_t mDOMOtherSize;
+#undef FOR_EACH_SIZE
+};
+
+class nsWindowSizes {
+#define FOR_EACH_SIZE(MACRO) \
+ MACRO(Style, mLayoutStyleSheetsSize) \
+ MACRO(Style, mLayoutShadowDomStyleSheetsSize) \
+ MACRO(Style, mLayoutShadowDomAuthorStyles) \
+ MACRO(Other, mLayoutPresShellSize) \
+ MACRO(Other, mLayoutRetainedDisplayListSize) \
+ MACRO(Style, mLayoutStyleSetsStylistRuleTree) \
+ MACRO(Style, mLayoutStyleSetsStylistElementAndPseudosMaps) \
+ MACRO(Style, mLayoutStyleSetsStylistInvalidationMap) \
+ MACRO(Style, mLayoutStyleSetsStylistRevalidationSelectors) \
+ MACRO(Style, mLayoutStyleSetsStylistOther) \
+ MACRO(Style, mLayoutStyleSetsOther) \
+ MACRO(Style, mLayoutElementDataObjects) \
+ MACRO(Other, mLayoutTextRunsSize) \
+ MACRO(Other, mLayoutPresContextSize) \
+ MACRO(Other, mLayoutFramePropertiesSize) \
+ MACRO(Style, mLayoutComputedValuesDom) \
+ MACRO(Style, mLayoutComputedValuesNonDom) \
+ MACRO(Style, mLayoutComputedValuesVisited) \
+ MACRO(Style, mLayoutSvgMappedDeclarations) \
+ MACRO(Other, mPropertyTablesSize) \
+ MACRO(Other, mBindingsSize)
+
+ public:
+ explicit nsWindowSizes(mozilla::SizeOfState& aState)
+ : FOR_EACH_SIZE(ZERO_SIZE) mDOMEventTargetsCount(0),
+ mDOMEventListenersCount(0),
+ mArenaSizes(),
+ mStyleSizes(),
+ mState(aState) {}
+
+ void addToTabSizes(nsTabSizes* aSizes) const {
+ FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+ mDOMSizes.addToTabSizes(aSizes);
+ mArenaSizes.addToTabSizes(aSizes);
+ mStyleSizes.addToTabSizes(aSizes);
+ }
+
+ size_t getTotalSize() const {
+ size_t total = 0;
+
+ FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE)
+ total += mDOMSizes.getTotalSize();
+ total += mArenaSizes.getTotalSize();
+ total += mStyleSizes.getTotalSize();
+
+ return total;
+ }
+
+ FOR_EACH_SIZE(DECL_SIZE);
+
+ uint32_t mDOMEventTargetsCount;
+ uint32_t mDOMEventListenersCount;
+
+ nsDOMSizes mDOMSizes;
+
+ nsArenaSizes mArenaSizes;
+
+ nsStyleSizes mStyleSizes;
+
+ mozilla::SizeOfState& mState;
+
+#undef FOR_EACH_SIZE
+};
+
+#undef ZERO_SIZE
+#undef ADD_TO_TAB_SIZES
+#undef ADD_TO_TOTAL_SIZE
+#undef DECL_SIZE
+
+#endif // nsWindowSizes_h
diff --git a/dom/base/nsWrapperCache.cpp b/dom/base/nsWrapperCache.cpp
new file mode 100644
index 0000000000..317e580c6c
--- /dev/null
+++ b/dom/base/nsWrapperCache.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWrapperCacheInlines.h"
+
+#include "jsfriendapi.h"
+#include "js/Class.h"
+#include "js/Proxy.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsCycleCollector.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#ifdef DEBUG
+/* static */
+bool nsWrapperCache::HasJSObjectMovedOp(JSObject* aWrapper) {
+ return js::HasObjectMovedOp(aWrapper);
+}
+#endif
+
+void nsWrapperCache::HoldJSObjects(void* aScriptObjectHolder,
+ nsScriptObjectTracer* aTracer,
+ JS::Zone* aWrapperZone) {
+ cyclecollector::HoldJSObjectsImpl(aScriptObjectHolder, aTracer, aWrapperZone);
+ if (mWrapper && !JS::ObjectIsTenured(mWrapper)) {
+ JS::HeapObjectPostWriteBarrier(&mWrapper, nullptr, mWrapper);
+ }
+}
+
+static inline bool IsNurseryWrapper(JSObject* aWrapper) {
+ return aWrapper && !JS::ObjectIsTenured(aWrapper);
+}
+
+void nsWrapperCache::SetWrapperJSObject(JSObject* aNewWrapper) {
+ JSObject* oldWrapper = mWrapper;
+ mWrapper = aNewWrapper;
+ UnsetWrapperFlags(kWrapperFlagsMask);
+
+ if (IsNurseryWrapper(aNewWrapper) && !IsNurseryWrapper(oldWrapper)) {
+ CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
+ }
+}
+
+void nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder) {
+ // If the behavior here changes in a substantive way, you may need
+ // to update css::Rule::UnlinkDeclarationWrapper as well.
+ if (PreservingWrapper()) {
+ SetPreservingWrapper(false);
+ cyclecollector::DropJSObjectsImpl(aScriptObjectHolder);
+ JS::HeapObjectPostWriteBarrier(&mWrapper, mWrapper, nullptr);
+ }
+}
+
+#ifdef DEBUG
+
+void nsWrapperCache::AssertUpdatedWrapperZone(const JSObject* aNewObject,
+ const JSObject* aOldObject) {
+ MOZ_ASSERT(js::GetObjectZoneFromAnyThread(aNewObject) ==
+ js::GetObjectZoneFromAnyThread(aOldObject));
+}
+
+class DebugWrapperTraversalCallback
+ : public nsCycleCollectionTraversalCallback {
+ public:
+ explicit DebugWrapperTraversalCallback(JSObject* aWrapper)
+ : mFound(false), mWrapper(JS::GCCellPtr(aWrapper)) {
+ mFlags = WANT_ALL_TRACES;
+ }
+
+ NS_IMETHOD_(void)
+ DescribeRefCountedNode(nsrefcnt aRefCount, const char* aObjName) override {}
+ NS_IMETHOD_(void)
+ DescribeGCedNode(bool aIsMarked, const char* aObjName,
+ uint64_t aCompartmentAddress) override {}
+
+ NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aChild) override {
+ if (aChild == mWrapper) {
+ mFound = true;
+ }
+ }
+ NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override {}
+ NS_IMETHOD_(void)
+ NoteNativeChild(void* aChild,
+ nsCycleCollectionParticipant* aHelper) override {}
+
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aKey, nsISupports* aVal,
+ nsCycleCollectionParticipant* aValParticipant) override {}
+
+ NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override {}
+
+ bool mFound;
+
+ private:
+ JS::GCCellPtr mWrapper;
+};
+
+static void DebugWrapperTraceCallback(JS::GCCellPtr aPtr, const char* aName,
+ void* aClosure) {
+ DebugWrapperTraversalCallback* callback =
+ static_cast<DebugWrapperTraversalCallback*>(aClosure);
+ if (aPtr.is<JSObject>()) {
+ callback->NoteJSChild(aPtr);
+ }
+}
+
+void nsWrapperCache::CheckCCWrapperTraversal(void* aScriptObjectHolder,
+ nsScriptObjectTracer* aTracer) {
+ JSObject* wrapper = GetWrapperPreserveColor();
+ if (!wrapper) {
+ return;
+ }
+
+ // Temporarily make this a preserving wrapper so that TraceWrapper() traces
+ // it.
+ bool wasPreservingWrapper = PreservingWrapper();
+ SetPreservingWrapper(true);
+
+ DebugWrapperTraversalCallback callback(wrapper);
+
+ // The CC traversal machinery cannot trigger GC; however, the analysis cannot
+ // see through the COM layer, so we use a suppression to help it.
+ JS::AutoSuppressGCAnalysis suppress;
+
+ aTracer->TraverseNativeAndJS(aScriptObjectHolder, callback);
+ MOZ_ASSERT(callback.mFound,
+ "Cycle collection participant didn't traverse to preserved "
+ "wrapper! This will probably crash.");
+
+ callback.mFound = false;
+ aTracer->Trace(aScriptObjectHolder,
+ TraceCallbackFunc(DebugWrapperTraceCallback), &callback);
+ MOZ_ASSERT(callback.mFound,
+ "Cycle collection participant didn't trace preserved wrapper! "
+ "This will probably crash.");
+
+ SetPreservingWrapper(wasPreservingWrapper);
+}
+
+#endif // DEBUG
diff --git a/dom/base/nsWrapperCache.h b/dom/base/nsWrapperCache.h
new file mode 100644
index 0000000000..d7232470d9
--- /dev/null
+++ b/dom/base/nsWrapperCache.h
@@ -0,0 +1,646 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWrapperCache_h___
+#define nsWrapperCache_h___
+
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/RustCell.h"
+#include "js/HeapAPI.h"
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include <type_traits>
+
+namespace mozilla::dom {
+class ContentProcessMessageManager;
+class InProcessBrowserChildMessageManager;
+class BrowserChildMessageManager;
+} // namespace mozilla::dom
+class SandboxPrivate;
+class nsWindowRoot;
+
+#define NS_WRAPPERCACHE_IID \
+ { \
+ 0x6f3179a1, 0x36f7, 0x4a5c, { \
+ 0x8c, 0xf1, 0xad, 0xc8, 0x7c, 0xde, 0x3e, 0x87 \
+ } \
+ }
+
+// There are two sets of flags used by DOM nodes. One comes from reusing the
+// remaining bits of the inherited nsWrapperCache flags (mFlags), and another is
+// exclusive to nsINode (mBoolFlags).
+//
+// Both sets of flags are 32 bits. On 64-bit platforms, this can cause two
+// wasted 32-bit fields due to alignment requirements. Some compilers are
+// smart enough to coalesce the fields if we make mBoolFlags the first member
+// of nsINode, but others (such as MSVC) are not.
+//
+// So we just store mBoolFlags directly on nsWrapperCache on 64-bit platforms.
+// This may waste space for some other nsWrapperCache-derived objects that have
+// a 32-bit field as their first member, but those objects are unlikely to be as
+// numerous or performance-critical as DOM nodes.
+#ifdef HAVE_64BIT_BUILD
+static_assert(sizeof(void*) == 8, "These architectures should be 64-bit");
+# define BOOL_FLAGS_ON_WRAPPER_CACHE
+#else
+static_assert(sizeof(void*) == 4, "Only support 32-bit and 64-bit");
+#endif
+
+/**
+ * Class to store the wrapper for an object. This can only be used with objects
+ * that only have one non-security wrapper at a time (for an XPCWrappedNative
+ * this is usually ensured by setting an explicit parent in the PreCreate hook
+ * for the class).
+ *
+ * An instance of nsWrapperCache can be gotten from an object that implements
+ * a wrapper cache by calling QueryInterface on it. Note that this breaks XPCOM
+ * rules a bit (this object doesn't derive from nsISupports).
+ *
+ * The cache can store objects other than wrappers. We allow wrappers to use a
+ * separate JSObject to store their state (mostly expandos). If the wrapper is
+ * collected and we want to preserve this state we actually store the state
+ * object in the cache.
+ *
+ * The cache can store 3 types of objects: a DOM binding object (regular JS
+ * object or proxy), an nsOuterWindowProxy or an XPCWrappedNative wrapper.
+ *
+ * The finalizer for the wrapper clears the cache.
+ *
+ * A compacting GC can move the wrapper object. Pointers to moved objects are
+ * usually found and updated by tracing the heap, however non-preserved wrappers
+ * are weak references and are not traced, so another approach is
+ * necessary. Instead a class hook (objectMovedOp) is provided that is called
+ * when an object is moved and is responsible for ensuring pointers are
+ * updated. It does this by calling UpdateWrapper() on the wrapper
+ * cache. SetWrapper() asserts that the hook is implemented for any wrapper set.
+ *
+ * A number of the methods are implemented in nsWrapperCacheInlines.h because we
+ * have to include some JS headers that don't play nicely with the rest of the
+ * codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
+ */
+
+class JS_HAZ_ROOTED nsWrapperCache {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_WRAPPERCACHE_IID)
+
+ nsWrapperCache() = default;
+ ~nsWrapperCache() {
+ // Preserved wrappers should never end up getting cleared, but this can
+ // happen during shutdown when a leaked wrapper object is finalized, causing
+ // its wrapper to be cleared.
+ MOZ_ASSERT(!PreservingWrapper() || js::RuntimeIsBeingDestroyed(),
+ "Destroying cache with a preserved wrapper!");
+ }
+
+ /**
+ * Get the cached wrapper.
+ *
+ * This getter clears the gray bit before handing out the JSObject which means
+ * that the object is guaranteed to be kept alive past the next CC.
+ */
+ JSObject* GetWrapper() const;
+
+ /**
+ * Get the cached wrapper.
+ *
+ * This getter does not change the color of the JSObject meaning that the
+ * object returned is not guaranteed to be kept alive past the next CC.
+ *
+ * This should only be called if you are certain that the return value won't
+ * be passed into a JSAPI function and that it won't be stored without being
+ * rooted (or otherwise signaling the stored value to the CC).
+ */
+ JSObject* GetWrapperPreserveColor() const;
+
+ /**
+ * Get the cached wrapper.
+ *
+ * This getter does not check whether the wrapper is dead and in the process
+ * of being finalized.
+ *
+ * This should only be called if you really need to see the raw contents of
+ * this cache, for example as part of finalization. Don't store the result
+ * anywhere or pass it into JSAPI functions that may cause the value to
+ * escape.
+ */
+ JSObject* GetWrapperMaybeDead() const { return mWrapper; }
+
+#ifdef DEBUG
+ private:
+ static bool HasJSObjectMovedOp(JSObject* aWrapper);
+
+ static void AssertUpdatedWrapperZone(const JSObject* aNewObject,
+ const JSObject* aOldObject);
+
+ public:
+#endif
+
+ void SetWrapper(JSObject* aWrapper) {
+ MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
+ MOZ_ASSERT(aWrapper, "Use ClearWrapper!");
+ MOZ_ASSERT(HasJSObjectMovedOp(aWrapper),
+ "Object has not provided the hook to update the wrapper if it "
+ "is moved");
+
+ SetWrapperJSObject(aWrapper);
+ }
+
+ /**
+ * Clear the cache.
+ */
+ void ClearWrapper() {
+ // Preserved wrappers should never end up getting cleared, but this can
+ // happen during shutdown when a leaked wrapper object is finalized, causing
+ // its wrapper to be cleared.
+ MOZ_ASSERT(!PreservingWrapper() || js::RuntimeIsBeingDestroyed(),
+ "Clearing a preserved wrapper!");
+ SetWrapperJSObject(nullptr);
+ }
+
+ /**
+ * Clear the cache if it still contains a specific wrapper object. This should
+ * be called from the finalizer for the wrapper.
+ */
+ void ClearWrapper(JSObject* obj) {
+ if (obj == mWrapper) {
+ ClearWrapper();
+ }
+ }
+
+ /**
+ * Update the wrapper when the object moves between globals.
+ */
+ template <typename T>
+ void UpdateWrapperForNewGlobal(T* aScriptObjectHolder, JSObject* aNewWrapper);
+
+ /**
+ * Update the wrapper if the object it contains is moved.
+ *
+ * This method must be called from the objectMovedOp class extension hook for
+ * any wrapper cached object.
+ */
+ void UpdateWrapper(JSObject* aNewObject, const JSObject* aOldObject) {
+#ifdef DEBUG
+ AssertUpdatedWrapperZone(aNewObject, aOldObject);
+#endif
+ if (mWrapper) {
+ MOZ_ASSERT(mWrapper == aOldObject);
+ mWrapper = aNewObject;
+ }
+ }
+
+ bool PreservingWrapper() const {
+ return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
+ }
+
+ /**
+ * Wrap the object corresponding to this wrapper cache. If non-null is
+ * returned, the object has already been stored in the wrapper cache.
+ */
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) = 0;
+
+ /**
+ * Returns true if the object has a wrapper that is known live from the point
+ * of view of cycle collection.
+ */
+ bool HasKnownLiveWrapper() const;
+
+ /**
+ * Returns true if the object has a known-live wrapper (from the CC point of
+ * view) and all the GC things it is keeping alive are already known-live from
+ * CC's point of view.
+ */
+ bool HasKnownLiveWrapperAndDoesNotNeedTracing(nsISupports* aThis);
+
+ bool HasNothingToTrace(nsISupports* aThis);
+
+ /**
+ * Mark our wrapper, if any, as live as far as the CC is concerned.
+ */
+ void MarkWrapperLive();
+
+ // Only meant to be called by code that preserves a wrapper.
+ void SetPreservingWrapper(bool aPreserve) {
+ if (aPreserve) {
+ SetWrapperFlags(WRAPPER_BIT_PRESERVED);
+ } else {
+ UnsetWrapperFlags(WRAPPER_BIT_PRESERVED);
+ }
+ }
+
+ void TraceWrapper(const TraceCallbacks& aCallbacks, void* aClosure) {
+ if (PreservingWrapper() && mWrapper) {
+ aCallbacks.Trace(this, "Preserved wrapper", aClosure);
+ }
+ }
+
+ /*
+ * The following methods for getting and manipulating flags allow the unused
+ * bits of mFlags to be used by derived classes.
+ */
+
+ using FlagsType = uint32_t;
+
+ FlagsType GetFlags() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mozilla::IsInServoTraversal());
+ return mFlags.Get() & ~kWrapperFlagsMask;
+ }
+
+ // This can be called from stylo threads too, so it needs to be atomic, as
+ // this value may be mutated from multiple threads during servo traversal from
+ // rust.
+ bool HasFlag(FlagsType aFlag) const {
+ MOZ_ASSERT((aFlag & kWrapperFlagsMask) == 0, "Bad flag mask");
+ return __atomic_load_n(mFlags.AsPtr(), __ATOMIC_RELAXED) & aFlag;
+ }
+
+ // Identical to HasFlag, but more explicit about its handling of multiple
+ // flags. This can be called from stylo threads too.
+ bool HasAnyOfFlags(FlagsType aFlags) const { return HasFlag(aFlags); }
+
+ // This can also be called from stylo, in the sequential part of the
+ // traversal, though it's probably not worth differentiating them for the
+ // purposes of assertions.
+ bool HasAllFlags(FlagsType aFlags) const {
+ MOZ_ASSERT((aFlags & kWrapperFlagsMask) == 0, "Bad flag mask");
+ return (__atomic_load_n(mFlags.AsPtr(), __ATOMIC_RELAXED) & aFlags) ==
+ aFlags;
+ }
+
+ void SetFlags(FlagsType aFlagsToSet) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT((aFlagsToSet & kWrapperFlagsMask) == 0, "Bad flag mask");
+ mFlags.Set(mFlags.Get() | aFlagsToSet);
+ }
+
+ void UnsetFlags(FlagsType aFlagsToUnset) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT((aFlagsToUnset & kWrapperFlagsMask) == 0, "Bad flag mask");
+ mFlags.Set(mFlags.Get() & ~aFlagsToUnset);
+ }
+
+ void PreserveWrapper(nsISupports* aScriptObjectHolder) {
+ if (PreservingWrapper()) {
+ return;
+ }
+
+ nsISupports* ccISupports;
+ aScriptObjectHolder->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
+ reinterpret_cast<void**>(&ccISupports));
+ MOZ_ASSERT(ccISupports);
+
+ nsXPCOMCycleCollectionParticipant* participant;
+ CallQueryInterface(ccISupports, &participant);
+ PreserveWrapper(ccISupports, participant);
+ }
+
+ void PreserveWrapper(void* aScriptObjectHolder,
+ nsScriptObjectTracer* aTracer) {
+ if (PreservingWrapper()) {
+ return;
+ }
+
+ JSObject* wrapper = GetWrapper(); // Read barrier for incremental GC.
+ HoldJSObjects(aScriptObjectHolder, aTracer, JS::GetObjectZone(wrapper));
+ SetPreservingWrapper(true);
+#ifdef DEBUG
+ // Make sure the cycle collector will be able to traverse to the wrapper.
+ CheckCCWrapperTraversal(aScriptObjectHolder, aTracer);
+#endif
+ }
+
+ void ReleaseWrapper(void* aScriptObjectHolder);
+
+ void TraceWrapper(JSTracer* aTrc, const char* name) {
+ if (mWrapper) {
+ js::UnsafeTraceManuallyBarrieredEdge(aTrc, &mWrapper, name);
+ }
+ }
+
+ protected:
+ void PoisonWrapper() {
+ if (mWrapper) {
+ // Set the pointer to a value that will cause a crash if it is
+ // dereferenced.
+ mWrapper = reinterpret_cast<JSObject*>(1);
+ }
+ }
+
+ private:
+ void SetWrapperJSObject(JSObject* aWrapper);
+
+ // We'd like to assert that these aren't used from servo threads, but we don't
+ // have a great way to do that because:
+ // * We can't just assert that they get used on the main thread, because
+ // these are used from workers.
+ // * We can't just assert that they aren't used when IsInServoTraversal(),
+ // because the traversal has a sequential, main-thread-only phase, where we
+ // run animations that can fiddle with JS promises.
+ FlagsType GetWrapperFlags() const { return mFlags.Get() & kWrapperFlagsMask; }
+
+ bool HasWrapperFlag(FlagsType aFlag) const {
+ MOZ_ASSERT((aFlag & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
+ return !!(mFlags.Get() & aFlag);
+ }
+
+ void SetWrapperFlags(FlagsType aFlagsToSet) {
+ MOZ_ASSERT((aFlagsToSet & ~kWrapperFlagsMask) == 0,
+ "Bad wrapper flag bits");
+ mFlags.Set(mFlags.Get() | aFlagsToSet);
+ }
+
+ void UnsetWrapperFlags(FlagsType aFlagsToUnset) {
+ MOZ_ASSERT((aFlagsToUnset & ~kWrapperFlagsMask) == 0,
+ "Bad wrapper flag bits");
+ mFlags.Set(mFlags.Get() & ~aFlagsToUnset);
+ }
+
+ void HoldJSObjects(void* aScriptObjectHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone);
+
+#ifdef DEBUG
+ public:
+ void CheckCCWrapperTraversal(void* aScriptObjectHolder,
+ nsScriptObjectTracer* aTracer);
+
+ private:
+#endif // DEBUG
+
+ /**
+ * If this bit is set then we're preserving the wrapper, which in effect ties
+ * the lifetime of the JS object stored in the cache to the lifetime of the
+ * native object. We rely on the cycle collector to break the cycle that this
+ * causes between the native object and the JS object, so it is important that
+ * any native object that supports preserving of its wrapper
+ * traces/traverses/unlinks the cached JS object (see
+ * NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER and
+ * NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER).
+ */
+ enum { WRAPPER_BIT_PRESERVED = 1 << 0 };
+
+ enum { kWrapperFlagsMask = WRAPPER_BIT_PRESERVED };
+
+ JSObject* mWrapper = nullptr;
+
+ // Rust code needs to read and write some flags atomically, but we don't want
+ // to make the wrapper flags atomic whole-sale because main-thread code would
+ // become more expensive (loads wouldn't change, but flag setting /
+ // unsetting could become slower enough to be noticeable). Making this an
+ // Atomic whole-sale needs more measuring.
+ //
+ // In order to not mess with aliasing rules the type should not be frozen, so
+ // we use a RustCell, which contains an UnsafeCell internally. See also the
+ // handling of ServoData (though that's a bit different).
+ mozilla::RustCell<FlagsType> mFlags{0};
+
+ protected:
+#ifdef BOOL_FLAGS_ON_WRAPPER_CACHE
+ uint32_t mBoolFlags = 0;
+#endif
+};
+
+enum { WRAPPER_CACHE_FLAGS_BITS_USED = 1 };
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsWrapperCache, NS_WRAPPERCACHE_IID)
+
+#define NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
+ if (aIID.Equals(NS_GET_IID(nsWrapperCache))) { \
+ *aInstancePtr = static_cast<nsWrapperCache*>(this); \
+ return NS_OK; \
+ }
+
+#define NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY \
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY \
+ else
+
+// Cycle collector macros for wrapper caches.
+//
+// The NS_DECL_*WRAPPERCACHE_* macros make it easier to mark classes as holding
+// just a single pointer to a JS value. That information is then used for
+// certain GC optimizations.
+
+#define NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(_class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsXPCOMCycleCollectionParticipant(aFlags | \
+ FlagMaybeSingleZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(void) \
+ TraceWrapper(void* aPtr, const TraceCallbacks& aCb, void* aClosure) final; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(_class, _class)
+
+#define NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_INHERITED(_class, \
+ _base_class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags) \
+ : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)( \
+ aFlags | FlagMaybeSingleZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(void) \
+ TraceWrapper(void* aPtr, const TraceCallbacks& aCb, void* aClosure) final; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_AMBIGUOUS( \
+ _class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags) \
+ : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip | \
+ FlagMaybeSingleZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(void) \
+ TraceWrapper(void* aPtr, const TraceCallbacks& aCb, void* aClosure) final; \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_AMBIGUOUS(_class, \
+ _class)
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_INHERITED( \
+ _class, _base_class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)( \
+ aFlags | FlagMightSkip | FlagMaybeSingleZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(void) \
+ TraceWrapper(void* aPtr, const TraceCallbacks& aCb, void* aClosure) final; \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(_class) \
+ void DeleteCycleCollectable(void) { delete this; } \
+ class NS_CYCLE_COLLECTION_INNERCLASS : public nsScriptObjectTracer { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsScriptObjectTracer(aFlags | FlagMaybeSingleZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(void) \
+ TraceWrapper(void* aPtr, const TraceCallbacks& aCb, void* aClosure) final; \
+ static constexpr nsScriptObjectTracer* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ } \
+ }; \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
+ tmp->TraceWrapper(aCallbacks, aClosure);
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ tmp->ReleaseWrapper(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ static_assert(std::is_base_of<nsWrapperCache, _class>::value, \
+ "Class should inherit nsWrapperCache"); \
+ NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
+ TraceWrapper(p, aCallbacks, aClosure); \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END \
+ void NS_CYCLE_COLLECTION_CLASSNAME(_class)::TraceWrapper( \
+ void* p, const TraceCallbacks& aCallbacks, void* aClosure) { \
+ _class* tmp = DowncastCCParticipant<_class>(p); \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(_class) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(_class, ...) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK(_class, ...) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(_class, ...) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// This is used for wrapper cached classes that inherit from cycle
+// collected non-wrapper cached classes.
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(_class, _base, ...) \
+ static_assert(!std::is_base_of<nsWrapperCache, _base>::value, \
+ "Base class should not inherit nsWrapperCache"); \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base) \
+ /* Assert somewhere, in this case in the traverse method, that the */ \
+ /* parent isn't a single zone holder*/ \
+ MOZ_ASSERT(!_base::NS_CYCLE_COLLECTION_INNERNAME.IsSingleZoneJSHolder()); \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// if NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS is used to implement
+// a wrappercache class, one needs to use
+// NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS and its variants in the class
+// declaration.
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS( \
+ class_, native_members_, js_members_) \
+ static_assert(std::is_base_of<nsWrapperCache, class_>::value, \
+ "Class should inherit nsWrapperCache"); \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \
+ using ::ImplCycleCollectionUnlink; \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \
+ MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#endif /* nsWrapperCache_h___ */
diff --git a/dom/base/nsWrapperCacheInlines.h b/dom/base/nsWrapperCacheInlines.h
new file mode 100644
index 0000000000..efd0e9a7f5
--- /dev/null
+++ b/dom/base/nsWrapperCacheInlines.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWrapperCacheInline_h___
+#define nsWrapperCacheInline_h___
+
+#include "nsWrapperCache.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+
+inline JSObject* nsWrapperCache::GetWrapperPreserveColor() const {
+ JSObject* obj = GetWrapperMaybeDead();
+ if (obj && js::gc::EdgeNeedsSweepUnbarriered(&obj)) {
+ // The object has been found to be dead and is in the process of being
+ // finalized, so don't let the caller see it.
+ // Don't clear the cache though: this happens when a new wrapper is created
+ // for this native or when the wrapper is finalized.
+ return nullptr;
+ }
+ MOZ_ASSERT(obj == mWrapper);
+ return obj;
+}
+
+inline JSObject* nsWrapperCache::GetWrapper() const {
+ JSObject* obj = GetWrapperPreserveColor();
+ if (obj) {
+ JS::ExposeObjectToActiveJS(obj);
+ }
+ return obj;
+}
+
+inline bool nsWrapperCache::HasKnownLiveWrapper() const {
+ // If we have a wrapper and it's not gray in the GC-marking sense, that means
+ // that we can't be cycle-collected. That's because the wrapper is being kept
+ // alive by the JS engine (and not just due to being traced from some
+ // cycle-collectable thing), and the wrapper holds us alive, so we know we're
+ // not collectable.
+ JSObject* o = GetWrapperPreserveColor();
+ return o && !JS::ObjectIsMarkedGray(o);
+}
+
+static void SearchGray(JS::GCCellPtr aGCThing, const char* aName,
+ void* aClosure) {
+ bool* hasGrayObjects = static_cast<bool*>(aClosure);
+ if (!*hasGrayObjects && aGCThing && JS::GCThingIsMarkedGray(aGCThing)) {
+ *hasGrayObjects = true;
+ }
+}
+
+inline bool nsWrapperCache::HasNothingToTrace(nsISupports* aThis) {
+ nsXPCOMCycleCollectionParticipant* participant = nullptr;
+ CallQueryInterface(aThis, &participant);
+ bool hasGrayObjects = false;
+ participant->Trace(aThis, TraceCallbackFunc(SearchGray), &hasGrayObjects);
+ return !hasGrayObjects;
+}
+
+inline bool nsWrapperCache::HasKnownLiveWrapperAndDoesNotNeedTracing(
+ nsISupports* aThis) {
+ return HasKnownLiveWrapper() && HasNothingToTrace(aThis);
+}
+
+inline void nsWrapperCache::MarkWrapperLive() {
+ // Just call GetWrapper and ignore the return value. It will do the
+ // gray-unmarking for us.
+ GetWrapper();
+}
+
+template <typename T>
+inline void nsWrapperCache::UpdateWrapperForNewGlobal(T* aScriptObjectHolder,
+ JSObject* aNewWrapper) {
+ // If the new wrapper is in a different zone we must ensure the
+ // DropJSObjects/HoldJSObjects are called to move the holder to the new zone.
+
+ bool preserving = PreservingWrapper();
+ bool zoneChanged =
+ preserving && (JS::GetObjectZone(GetWrapperPreserveColor()) !=
+ JS::GetObjectZone(aNewWrapper));
+
+ if (zoneChanged) {
+ ReleaseWrapper(aScriptObjectHolder);
+ } else if (preserving) {
+ SetPreservingWrapper(false);
+ }
+
+ SetWrapper(aNewWrapper);
+
+ if (zoneChanged) {
+ PreserveWrapper(aScriptObjectHolder);
+ } else if (preserving) {
+ SetPreservingWrapper(true);
+ }
+}
+
+#endif /* nsWrapperCache_h___ */
diff --git a/dom/base/rust/Cargo.toml b/dom/base/rust/Cargo.toml
new file mode 100644
index 0000000000..38828c3f0f
--- /dev/null
+++ b/dom/base/rust/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "dom"
+version = "0.1.0"
+edition = "2021"
+authors = [
+ "Emilio Cobos Ãlvarez <emilio@crisal.io>",
+]
+license = "MPL-2.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+bitflags = "2"
diff --git a/dom/base/rust/cbindgen.toml b/dom/base/rust/cbindgen.toml
new file mode 100644
index 0000000000..a34420e112
--- /dev/null
+++ b/dom/base/rust/cbindgen.toml
@@ -0,0 +1,40 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RustTypes_h
+#error "Don't include this file directly, include mozilla/dom/RustTypes.h instead"
+#endif
+"""
+include_guard = "mozilla_dom_GeneratedElementDocumentState_h"
+include_version = true
+language = "C++"
+namespaces = ["mozilla", "dom"]
+includes = ["mozilla/Assertions.h"]
+
+[enum]
+derive_helper_methods = true
+derive_const_casts = true
+cast_assert_name = "MOZ_DIAGNOSTIC_ASSERT"
+
+[struct]
+associated_constants_in_body = true
+derive_eq = true
+derive_neq = true
+
+[macro_expansion]
+bitflags = true
+
+[export]
+include = [
+ "ElementState",
+ "DocumentState",
+]
+
+[export.body]
+"ElementState" = """
+ STATE_HELPERS(ElementState)
+"""
+"DocumentState" = """
+ STATE_HELPERS(DocumentState)
+"""
diff --git a/dom/base/rust/lib.rs b/dom/base/rust/lib.rs
new file mode 100644
index 0000000000..388c1320df
--- /dev/null
+++ b/dom/base/rust/lib.rs
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! DOM types to be shared between Rust and C++.
+
+use bitflags::bitflags;
+
+bitflags! {
+ /// Event-based element states.
+ #[repr(C)]
+ #[derive(Clone, Copy, Eq, PartialEq)]
+ pub struct ElementState: u64 {
+ /// The mouse is down on this element.
+ /// <https://html.spec.whatwg.org/multipage/#selector-active>
+ /// FIXME(#7333): set/unset this when appropriate
+ const ACTIVE = 1 << 0;
+ /// This element has focus.
+ /// <https://html.spec.whatwg.org/multipage/#selector-focus>
+ const FOCUS = 1 << 1;
+ /// The mouse is hovering over this element.
+ /// <https://html.spec.whatwg.org/multipage/#selector-hover>
+ const HOVER = 1 << 2;
+ /// Content is enabled (and can be disabled).
+ /// <http://www.whatwg.org/html/#selector-enabled>
+ const ENABLED = 1 << 3;
+ /// Content is disabled.
+ /// <http://www.whatwg.org/html/#selector-disabled>
+ const DISABLED = 1 << 4;
+ /// Content is checked.
+ /// <https://html.spec.whatwg.org/multipage/#selector-checked>
+ const CHECKED = 1 << 5;
+ /// <https://html.spec.whatwg.org/multipage/#selector-indeterminate>
+ const INDETERMINATE = 1 << 6;
+ /// <https://html.spec.whatwg.org/multipage/#selector-placeholder-shown>
+ const PLACEHOLDER_SHOWN = 1 << 7;
+ /// <https://html.spec.whatwg.org/multipage/#selector-target>
+ const URLTARGET = 1 << 8;
+ /// <https://fullscreen.spec.whatwg.org/#%3Afullscreen-pseudo-class>
+ const FULLSCREEN = 1 << 9;
+ /// <https://html.spec.whatwg.org/multipage/#selector-valid>
+ const VALID = 1 << 10;
+ /// <https://html.spec.whatwg.org/multipage/#selector-invalid>
+ const INVALID = 1 << 11;
+ /// <https://drafts.csswg.org/selectors-4/#user-valid-pseudo>
+ const USER_VALID = 1 << 12;
+ /// <https://drafts.csswg.org/selectors-4/#user-invalid-pseudo>
+ const USER_INVALID = 1 << 13;
+ /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken
+ const BROKEN = 1 << 14;
+ /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading
+ const LOADING = 1 << 15;
+ /// <https://html.spec.whatwg.org/multipage/#selector-required>
+ const REQUIRED = 1 << 16;
+ /// <https://html.spec.whatwg.org/multipage/#selector-optional>
+ /// We use an underscore to workaround a silly windows.h define.
+ const OPTIONAL_ = 1 << 17;
+ /// <https://html.spec.whatwg.org/multipage/#selector-defined>
+ const DEFINED = 1 << 18;
+ /// <https://html.spec.whatwg.org/multipage/#selector-visited>
+ const VISITED = 1 << 19;
+ /// <https://html.spec.whatwg.org/multipage/#selector-link>
+ const UNVISITED = 1 << 20;
+ /// <https://drafts.csswg.org/selectors-4/#the-any-link-pseudo>
+ const VISITED_OR_UNVISITED = Self::VISITED.bits | Self::UNVISITED.bits;
+ /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-drag-over
+ const DRAGOVER = 1 << 21;
+ /// <https://html.spec.whatwg.org/multipage/#selector-in-range>
+ const INRANGE = 1 << 22;
+ /// <https://html.spec.whatwg.org/multipage/#selector-out-of-range>
+ const OUTOFRANGE = 1 << 23;
+ /// <https://html.spec.whatwg.org/multipage/#selector-read-only>
+ const READONLY = 1 << 24;
+ /// <https://html.spec.whatwg.org/multipage/#selector-read-write>
+ const READWRITE = 1 << 25;
+ /// <https://html.spec.whatwg.org/multipage/#selector-default>
+ const DEFAULT = 1 << 26;
+ /// Non-standard & undocumented.
+ const OPTIMUM = 1 << 28;
+ /// Non-standard & undocumented.
+ const SUB_OPTIMUM = 1 << 29;
+ /// Non-standard & undocumented.
+ const SUB_SUB_OPTIMUM = 1 << 30;
+ /// Non-standard & undocumented.
+ const INCREMENT_SCRIPT_LEVEL = 1u64 << 31;
+ /// <https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo>
+ const FOCUSRING = 1u64 << 32;
+ /// <https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo>
+ const FOCUS_WITHIN = 1u64 << 33;
+ /// :dir matching; the states are used for dynamic change detection.
+ /// State that elements that match :dir(ltr) are in.
+ const LTR = 1u64 << 34;
+ /// State that elements that match :dir(rtl) are in.
+ const RTL = 1u64 << 35;
+ /// State that HTML elements that have a "dir" attr are in.
+ const HAS_DIR_ATTR = 1u64 << 36;
+ /// State that HTML elements with dir="ltr" (or something
+ /// case-insensitively equal to "ltr") are in.
+ const HAS_DIR_ATTR_LTR = 1u64 << 37;
+ /// State that HTML elements with dir="rtl" (or something
+ /// case-insensitively equal to "rtl") are in.
+ const HAS_DIR_ATTR_RTL = 1u64 << 38;
+ /// State that HTML <bdi> elements without a valid-valued "dir" attr or
+ /// any HTML elements (including <bdi>) with dir="auto" (or something
+ /// case-insensitively equal to "auto") are in.
+ const HAS_DIR_ATTR_LIKE_AUTO = 1u64 << 39;
+ /// Non-standard & undocumented.
+ const AUTOFILL = 1u64 << 40;
+ /// Non-standard & undocumented.
+ const AUTOFILL_PREVIEW = 1u64 << 41;
+ /// State for modal elements:
+ /// <https://drafts.csswg.org/selectors-4/#modal-state>
+ const MODAL = 1u64 << 42;
+ /// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
+ const INERT = 1u64 << 43;
+ /// State for the topmost modal element in top layer
+ const TOPMOST_MODAL = 1u64 << 44;
+ /// Initially used for the devtools highlighter, but now somehow only
+ /// used for the devtools accessibility inspector.
+ const DEVTOOLS_HIGHLIGHTED = 1u64 << 45;
+ /// Used for the devtools style editor. Probably should go away.
+ const STYLEEDITOR_TRANSITIONING = 1u64 << 46;
+ /// For :-moz-value-empty (to show widgets like the reveal password
+ /// button or the clear button).
+ const VALUE_EMPTY = 1u64 << 47;
+ /// For :-moz-revealed.
+ const REVEALED = 1u64 << 48;
+ /// https://html.spec.whatwg.org/#selector-popover-open
+ /// Match element's popover visibility state of showing
+ const POPOVER_OPEN = 1u64 << 49;
+
+ /// Some convenience unions.
+ const DIR_STATES = Self::LTR.bits | Self::RTL.bits;
+
+ const DIR_ATTR_STATES = Self::HAS_DIR_ATTR.bits |
+ Self::HAS_DIR_ATTR_LTR.bits |
+ Self::HAS_DIR_ATTR_RTL.bits |
+ Self::HAS_DIR_ATTR_LIKE_AUTO.bits;
+
+ const DISABLED_STATES = Self::DISABLED.bits | Self::ENABLED.bits;
+
+ const REQUIRED_STATES = Self::REQUIRED.bits | Self::OPTIONAL_.bits;
+
+ /// Event states that can be added and removed through
+ /// Element::{Add,Remove}ManuallyManagedStates.
+ ///
+ /// Take care when manually managing state bits. You are responsible
+ /// for setting or clearing the bit when an Element is added or removed
+ /// from a document (e.g. in BindToTree and UnbindFromTree), if that is
+ /// an appropriate thing to do for your state bit.
+ const MANUALLY_MANAGED_STATES = Self::AUTOFILL.bits | Self::AUTOFILL_PREVIEW.bits;
+
+ /// Event states that are managed externally to an element (by the
+ /// EventStateManager, or by other code). As opposed to those in
+ /// INTRINSIC_STATES, which are are computed by the element itself
+ /// and returned from Element::IntrinsicState.
+ const EXTERNALLY_MANAGED_STATES =
+ Self::MANUALLY_MANAGED_STATES.bits |
+ Self::DIR_ATTR_STATES.bits |
+ Self::DISABLED_STATES.bits |
+ Self::REQUIRED_STATES.bits |
+ Self::ACTIVE.bits |
+ Self::DEFINED.bits |
+ Self::DRAGOVER.bits |
+ Self::FOCUS.bits |
+ Self::FOCUSRING.bits |
+ Self::FOCUS_WITHIN.bits |
+ Self::FULLSCREEN.bits |
+ Self::POPOVER_OPEN.bits |
+ Self::HOVER.bits |
+ Self::URLTARGET.bits |
+ Self::MODAL.bits |
+ Self::INERT.bits |
+ Self::TOPMOST_MODAL.bits |
+ Self::REVEALED.bits |
+ Self::VALUE_EMPTY.bits;
+
+ const INTRINSIC_STATES = !Self::EXTERNALLY_MANAGED_STATES.bits;
+ }
+}
+
+bitflags! {
+ /// Event-based document states.
+ #[repr(C)]
+ #[derive(Clone, Copy, Eq, PartialEq)]
+ pub struct DocumentState: u64 {
+ /// Window activation status
+ const WINDOW_INACTIVE = 1 << 0;
+ /// RTL locale: specific to the XUL localedir attribute
+ const RTL_LOCALE = 1 << 1;
+ /// LTR locale: specific to the XUL localedir attribute
+ const LTR_LOCALE = 1 << 2;
+ /// LWTheme status
+ const LWTHEME = 1 << 3;
+
+ const ALL_LOCALEDIR_BITS = Self::LTR_LOCALE.bits | Self::RTL_LOCALE.bits;
+ }
+}
diff --git a/dom/base/test/.eslintrc.js b/dom/base/test/.eslintrc.js
new file mode 100644
index 0000000000..43ad70dd5e
--- /dev/null
+++ b/dom/base/test/.eslintrc.js
@@ -0,0 +1,12 @@
+"use strict";
+
+module.exports = {
+ overrides: [
+ {
+ files: ["file_module_js_cache.js", "file_script_module_*.js"],
+ parserOptions: {
+ sourceType: "module",
+ },
+ },
+ ],
+};
diff --git a/dom/base/test/345339_iframe.html b/dom/base/test/345339_iframe.html
new file mode 100644
index 0000000000..f4b2ecd13b
--- /dev/null
+++ b/dom/base/test/345339_iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <title>Form Elements</title>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+ </head>
+ <body>
+ <p>
+ <select id="select">
+ <option value="Mozilla">Mozilla</option>
+ <option value="Firefox">Firefox</option>
+ </select>
+ <form name="radioform" id="radioform">
+ <input type="radio" id="radio1" name="answer" value="Yes"
+ checked="checked" />
+ <input type="radio" id="radio2" name="answer" value="No" />
+ </form>
+
+ <input type="password" id="password" />
+
+ <input type="hidden" id="hidden" />
+
+ <input id="passwordToggle" />
+
+ <input type="file" id="file" />
+ </p>
+ </body>
+</html>
diff --git a/dom/base/test/Ahem.ttf b/dom/base/test/Ahem.ttf
new file mode 100644
index 0000000000..ac81cb0316
--- /dev/null
+++ b/dom/base/test/Ahem.ttf
Binary files differ
diff --git a/dom/base/test/FAIL.html b/dom/base/test/FAIL.html
new file mode 100644
index 0000000000..94e1707e85
--- /dev/null
+++ b/dom/base/test/FAIL.html
@@ -0,0 +1 @@
+FAIL
diff --git a/dom/base/test/PASS.html b/dom/base/test/PASS.html
new file mode 100644
index 0000000000..7ef22e9a43
--- /dev/null
+++ b/dom/base/test/PASS.html
@@ -0,0 +1 @@
+PASS
diff --git a/dom/base/test/accesscontrol.resource b/dom/base/test/accesscontrol.resource
new file mode 100644
index 0000000000..aca66f6f8d
--- /dev/null
+++ b/dom/base/test/accesscontrol.resource
@@ -0,0 +1,7 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+event: message
+data: 1
+
+
diff --git a/dom/base/test/accesscontrol.resource^headers^ b/dom/base/test/accesscontrol.resource^headers^
new file mode 100644
index 0000000000..75f1f88972
--- /dev/null
+++ b/dom/base/test/accesscontrol.resource^headers^
@@ -0,0 +1,5 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
+Access-Control-Allow-Credentials: true
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/audio.ogg b/dom/base/test/audio.ogg
new file mode 100644
index 0000000000..bed764fbf1
--- /dev/null
+++ b/dom/base/test/audio.ogg
Binary files differ
diff --git a/dom/base/test/badContentType.eventsource b/dom/base/test/badContentType.eventsource
new file mode 100644
index 0000000000..c9d0739e18
--- /dev/null
+++ b/dom/base/test/badContentType.eventsource
@@ -0,0 +1,5 @@
+retry:500
+event: message
+data: 1
+
+
diff --git a/dom/base/test/badContentType.eventsource^headers^ b/dom/base/test/badContentType.eventsource^headers^
new file mode 100644
index 0000000000..a1f9e38d90
--- /dev/null
+++ b/dom/base/test/badContentType.eventsource^headers^
@@ -0,0 +1 @@
+Content-Type: text/plain
diff --git a/dom/base/test/badHTTPResponseCode.eventsource b/dom/base/test/badHTTPResponseCode.eventsource
new file mode 100644
index 0000000000..c9d0739e18
--- /dev/null
+++ b/dom/base/test/badHTTPResponseCode.eventsource
@@ -0,0 +1,5 @@
+retry:500
+event: message
+data: 1
+
+
diff --git a/dom/base/test/badHTTPResponseCode.eventsource^headers^ b/dom/base/test/badHTTPResponseCode.eventsource^headers^
new file mode 100644
index 0000000000..545a9a201c
--- /dev/null
+++ b/dom/base/test/badHTTPResponseCode.eventsource^headers^
@@ -0,0 +1,2 @@
+HTTP 404 Not Found
+Content-Type: text/event-stream
diff --git a/dom/base/test/badMessageEvent.eventsource b/dom/base/test/badMessageEvent.eventsource
new file mode 100644
index 0000000000..0c635f0b57
--- /dev/null
+++ b/dom/base/test/badMessageEvent.eventsource
@@ -0,0 +1,4 @@
+retry:500
+event: message
+
+
diff --git a/dom/base/test/badMessageEvent.eventsource^headers^ b/dom/base/test/badMessageEvent.eventsource^headers^
new file mode 100644
index 0000000000..9bb8badcad
--- /dev/null
+++ b/dom/base/test/badMessageEvent.eventsource^headers^
@@ -0,0 +1 @@
+Content-Type: text/event-stream
diff --git a/dom/base/test/badMessageEvent2.eventsource b/dom/base/test/badMessageEvent2.eventsource
new file mode 100644
index 0000000000..ad6fa694f4
--- /dev/null
+++ b/dom/base/test/badMessageEvent2.eventsource
@@ -0,0 +1,5 @@
+retry:500
+data: ok
+
+id: invalid-id
+data: not-ok
diff --git a/dom/base/test/badMessageEvent2.eventsource^headers^ b/dom/base/test/badMessageEvent2.eventsource^headers^
new file mode 100644
index 0000000000..9bb8badcad
--- /dev/null
+++ b/dom/base/test/badMessageEvent2.eventsource^headers^
@@ -0,0 +1 @@
+Content-Type: text/event-stream
diff --git a/dom/base/test/browser.ini b/dom/base/test/browser.ini
new file mode 100644
index 0000000000..0386d433b5
--- /dev/null
+++ b/dom/base/test/browser.ini
@@ -0,0 +1,112 @@
+[DEFAULT]
+head = head.js
+support-files =
+ audio.ogg
+ dummy.html
+ empty.html
+ file_audioLoop.html
+ file_audioLoopInIframe.html
+ file_blocking_image.html
+ file_bug902350.html
+ file_bug902350_frame.html
+ file_bug1011748_redirect.sjs
+ file_bug1011748_OK.sjs
+ file_bug1303838.html
+ file_bug1303838_target.html
+ file_bug1303838_target_foo.html
+ file_bug1303838_target_bar.html
+ file_bug1303838_target_baz.html
+ file_bug1303838_target_ifoo.html
+ file_bug1303838_target_ibar.html
+ file_bug1303838_target_ibaz.html
+ file_bug1303838_with_iframe.html
+ file_messagemanager_unload.html
+ file_use_counter_bfcache.html
+ file_use_counter_bfcache_helper.html
+ file_use_counter_outer.html
+ file_use_counter_outer_display_none.html
+ file_use_counter_style.html
+ file_use_counter_svg_getElementById.svg
+ file_use_counter_svg_currentScale.svg
+ file_use_counter_svg_fill_pattern_definition.svg
+ file_use_counter_svg_fill_pattern.svg
+ file_use_counter_svg_fill_pattern_internal.svg
+ file_use_counter_svg_fill_pattern_data.svg
+ file_webaudio_startstop.html
+ !/image/test/mochitest/shaver.png
+
+
+[browser_blocking_image.js]
+[browser_bug902350.js]
+tags = mcb
+[browser_bug1011748.js]
+[browser_bug1058164.js]
+[browser_force_process_selector.js]
+skip-if =
+ verify
+ (os == 'win' && os_version == '10.0' && bits == 64 && asan)
+ (os == "linux" && bits == 64 && os_version == "18.04" && asan) # this only makes sense with e10s-multi , Bug 1651357
+[browser_messagemanager_loadprocessscript.js]
+[browser_aboutnewtab_process_selection.js]
+skip-if =
+ os == 'linux' && bits == 64 #Bug 1618098
+ os == 'mac' && fission # Bug 1618098
+[browser_messagemanager_targetframeloader.js]
+[browser_messagemanager_unload.js]
+skip-if = fission # Fails with Fission, and we're unlikely to spend time to fix it. (bug 1587490)
+[browser_pagehide_on_tab_close.js]
+skip-if = true # this tests non-e10s behavior.
+[browser_promiseDocumentFlushed.js]
+[browser_state_notifications.js]
+skip-if = true # Bug 1271028
+[browser_use_counters.js]
+skip-if = verify
+[browser_timeout_throttling_with_audio_playback.js]
+skip-if = (os == "win" && processor == "aarch64") # aarch64 due to bug 1536566
+[browser_bug1303838.js]
+skip-if =
+ os == "mac" && os_version == "10.15" && !debug # Bug 1703712
+[browser_bug1691214.js]
+skip-if =
+ os == 'win' # Bug 1692963
+ os == "mac" # Bug 1692963
+ os == 'linux' # Bug 1775696
+
+support-files =
+ file_bug1691214.html
+ file_bug1700871.html
+[browser_inputStream_structuredClone.js]
+[browser_multiple_popups.js]
+skip-if =
+ (os == 'win' && !debug)
+ (os == "mac" && !debug) # Bug 1505235, Bug 1661132 (osx)
+ socketprocess_networking
+support-files = browser_multiple_popups.html
+[browser_bug1554070.js]
+support-files =
+ file_bug1554070_1.html
+ file_bug1554070_2.html
+[browser_chromeutils_getalldomprocesses.js]
+[browser_chromeutils_isdomobject.js]
+[browser_outline_refocus.js]
+[browser_bug1703472.js]
+support-files =
+ file_bug1703472.html
+[browser_data_documents_aboutmemory.js]
+[browser_form_validity_popup_submit.js]
+[browser_refresh_content.js]
+support-files =
+ green.png
+ red.png
+ file_browser_refresh_content.html
+ file_browser_refresh_expired_resource.sjs
+ file_browser_refresh_non_cacheable.sjs
+ file_browser_refresh_image.sjs
+ file_browser_refresh_iframe.sjs
+[browser_page_load_event_telemetry.js]
+[browser_xml_toggle.js]
+[browser_user_input_handling_delay.js]
+[browser_user_input_handling_delay_bfcache.js]
+[browser_user_input_handling_delay_aboutblank.js]
+[browser_user_input_handling_delay_invisible_iframe.js]
+[browser_user_input_handling_delay_reload_ticks.js]
diff --git a/dom/base/test/browser_aboutnewtab_process_selection.js b/dom/base/test/browser_aboutnewtab_process_selection.js
new file mode 100644
index 0000000000..101f408483
--- /dev/null
+++ b/dom/base/test/browser_aboutnewtab_process_selection.js
@@ -0,0 +1,137 @@
+const TEST_URL = "http://www.example.com/browser/dom/base/test/dummy.html";
+const TEST_URL_2 = "http://example.org/browser/dom/base/test/dummy.html";
+const PRELOADED_STATE = "preloaded";
+
+var ppmm = Services.ppmm;
+
+add_task(async function () {
+ // We want to count processes in this test, so let's disable the pre-allocated process manager.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processPrelaunch.enabled", false],
+ ["dom.ipc.processCount", 10],
+ ["dom.ipc.processCount.webIsolated", 10],
+ ["dom.ipc.keepProcessesAlive.web", 10],
+ ],
+ });
+});
+
+add_task(async function () {
+ // This test is only relevant in e10s.
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ ppmm.releaseCachedProcesses();
+
+ // Wait for the preloaded browser to load.
+ await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
+
+ // Store the number of processes.
+ let expectedChildCount = ppmm.childCount;
+
+ // Open 3 tabs using the preloaded browser.
+ let tabs = [];
+ for (let i = 0; i < 3; i++) {
+ BrowserOpenTab();
+ tabs.unshift(gBrowser.selectedTab);
+ await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
+
+ // Check that the process count did not change.
+ is(
+ ppmm.childCount,
+ expectedChildCount,
+ "Preloaded browser should not create a new content process."
+ );
+ }
+
+ // Navigate to a content page from the parent side.
+ //
+ // We should create a new content process.
+ expectedChildCount += 1;
+ BrowserTestUtils.loadURIString(tabs[0].linkedBrowser, TEST_URL);
+ await BrowserTestUtils.browserLoaded(tabs[0].linkedBrowser, false, TEST_URL);
+ is(
+ ppmm.childCount,
+ expectedChildCount,
+ "Navigating away from the preloaded browser (parent side) should create a new content process."
+ );
+
+ // Navigate to the same content page from the child side.
+ //
+ // We should create a new content process.
+ expectedChildCount += 1;
+ await BrowserTestUtils.switchTab(gBrowser, tabs[1]);
+ await SpecialPowers.spawn(tabs[1].linkedBrowser, [TEST_URL], url => {
+ content.location.href = url;
+ });
+ await BrowserTestUtils.browserLoaded(tabs[1].linkedBrowser, false, TEST_URL);
+ is(
+ ppmm.childCount,
+ expectedChildCount,
+ "Navigating away from the preloaded browser (child side, same-origin) should create a new content process."
+ );
+
+ // Navigate to a new content page from the child side.
+ //
+ // We should create a new content process.
+ expectedChildCount += 1;
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+ await ContentTask.spawn(tabs[2].linkedBrowser, TEST_URL_2, url => {
+ content.location.href = url;
+ });
+ await BrowserTestUtils.browserLoaded(
+ tabs[2].linkedBrowser,
+ false,
+ TEST_URL_2
+ );
+ is(
+ ppmm.childCount,
+ expectedChildCount,
+ "Navigating away from the preloaded browser (child side, cross-origin) should create a new content process."
+ );
+
+ for (let tab of tabs) {
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ // Make sure the preload browser does not keep any of the new processes alive.
+ NewTabPagePreloading.removePreloadedBrowser(window);
+
+ // Since we kept alive all the processes, we can shut down the ones that do
+ // not host any tabs reliably.
+ ppmm.releaseCachedProcesses();
+});
+
+add_task(async function preloaded_state_attribute() {
+ // Wait for a preloaded browser to exist, use it, and then create another one
+ await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
+ let preloadedTabState =
+ gBrowser.preloadedBrowser.getAttribute("preloadedState");
+ is(
+ preloadedTabState,
+ PRELOADED_STATE,
+ "Sanity check that the first preloaded browser has the correct attribute"
+ );
+
+ BrowserOpenTab();
+ await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
+
+ // Now check that the tabs have the correct browser attributes set
+ is(
+ gBrowser.selectedBrowser.hasAttribute("preloadedState"),
+ false,
+ "The opened tab consumed the preloaded browser and removed the attribute"
+ );
+
+ preloadedTabState = gBrowser.preloadedBrowser.getAttribute("preloadedState");
+ is(
+ preloadedTabState,
+ PRELOADED_STATE,
+ "The preloaded browser has the correct attribute"
+ );
+
+ // Remove tabs and preloaded browsers
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ NewTabPagePreloading.removePreloadedBrowser(window);
+});
diff --git a/dom/base/test/browser_blocking_image.js b/dom/base/test/browser_blocking_image.js
new file mode 100644
index 0000000000..5749937f07
--- /dev/null
+++ b/dom/base/test/browser_blocking_image.js
@@ -0,0 +1,183 @@
+const TEST_URI =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "file_blocking_image.html";
+
+/**
+ * Loading an image from https:// should work.
+ */
+add_task(async function load_image_from_https_test() {
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.selectedTab = tab;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ function imgListener(img) {
+ return new Promise((resolve, reject) => {
+ img.addEventListener("load", () => resolve());
+ img.addEventListener("error", () => reject());
+ });
+ }
+
+ let img = content.document.createElement("img");
+ img.src = "https://example.com/tests/image/test/mochitest/shaver.png";
+ content.document.body.appendChild(img);
+
+ try {
+ await imgListener(img);
+ Assert.ok(true);
+ } catch (e) {
+ Assert.ok(false);
+ }
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Loading an image from http:// should be rejected.
+ */
+add_task(async function load_image_from_http_test() {
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.selectedTab = tab;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ function imgListener(img) {
+ return new Promise((resolve, reject) => {
+ img.addEventListener("load", () => reject());
+ img.addEventListener("error", () => resolve());
+ });
+ }
+
+ let img = content.document.createElement("img");
+ img.src = "http://example.com/tests/image/test/mochitest/shaver.png";
+ content.document.body.appendChild(img);
+
+ try {
+ await imgListener(img);
+ Assert.ok(true);
+ } catch (e) {
+ Assert.ok(false);
+ }
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Loading an image from http:// immediately after loading from https://
+ * The load from https:// should be replaced.
+ */
+add_task(async function load_https_and_http_test() {
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Clear image cache, otherwise in non-e10s mode the image might be cached by
+ // previous test, and make the image from https is loaded immediately.
+ let imgTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
+ let imageCache = imgTools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(false); // false=content
+
+ gBrowser.selectedTab = tab;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ function imgListener(img) {
+ return new Promise((resolve, reject) => {
+ img.addEventListener("load", () => reject());
+ img.addEventListener("error", () => resolve());
+ });
+ }
+
+ let img = content.document.createElement("img");
+ img.src = "https://example.com/tests/image/test/mochitest/shaver.png";
+ img.src = "http://example.com/tests/image/test/mochitest/shaver.png";
+
+ content.document.body.appendChild(img);
+
+ try {
+ await imgListener(img);
+ Assert.ok(true);
+ } catch (e) {
+ Assert.ok(false);
+ }
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Loading an image from https.
+ * Then after we have size information of the image, we immediately change the
+ * location to a http:// site (hence should be blocked by CSP).
+ */
+add_task(async function block_pending_request_test() {
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.selectedTab = tab;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let wrapper = {
+ _resolve: null,
+ _sizeAvail: false,
+
+ sizeAvailable(request) {
+ // In non-e10s mode the image may have already been cached, so sometimes
+ // sizeAvailable() is called earlier then waitUntilSizeAvailable().
+ if (this._resolve) {
+ this._resolve();
+ } else {
+ this._sizeAvail = true;
+ }
+ },
+
+ waitUntilSizeAvailable() {
+ return new Promise(resolve => {
+ this._resolve = resolve;
+ if (this._sizeAvail) {
+ resolve();
+ }
+ });
+ },
+
+ QueryInterface(aIID) {
+ if (aIID.equals(Ci.imgIScriptedNotificationObserver)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+ };
+
+ let observer = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .createScriptedObserver(wrapper);
+
+ let img = content.document.createElement("img");
+ img.src = "https://example.com/tests/image/test/mochitest/shaver.png";
+
+ let req = img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ img.addObserver(observer);
+
+ content.document.body.appendChild(img);
+
+ info("Wait until Size Available");
+ await wrapper.waitUntilSizeAvailable();
+ info("Size Available now!");
+ img.removeObserver(observer);
+
+ // Now we change to load from http:// site, which will be blocked.
+ img.src = "http://example.com/tests/image/test/mochitest/shaver.png";
+
+ Assert.equal(
+ img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST),
+ req,
+ "CURRENT_REQUEST shouldn't be replaced."
+ );
+ });
+
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/base/test/browser_bug1011748.js b/dom/base/test/browser_bug1011748.js
new file mode 100644
index 0000000000..566e1347e9
--- /dev/null
+++ b/dom/base/test/browser_bug1011748.js
@@ -0,0 +1,31 @@
+const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
+
+add_task(async function () {
+ var statusTexts = [];
+ var xhr = new XMLHttpRequest();
+ var observer = {
+ observe(aSubject, aTopic, aData) {
+ try {
+ var channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ channel.getResponseHeader("Location");
+ } catch (e) {
+ return;
+ }
+ statusTexts.push(xhr.statusText);
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response");
+ await new Promise(resolve => {
+ xhr.addEventListener("load", function () {
+ statusTexts.push(this.statusText);
+ is(statusTexts[0], "", "Empty statusText value for HTTP 302");
+ is(statusTexts[1], "OK", "OK statusText value for the redirect.");
+ resolve();
+ });
+ xhr.open("GET", gHttpTestRoot + "file_bug1011748_redirect.sjs", true);
+ xhr.send();
+ });
+
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+});
diff --git a/dom/base/test/browser_bug1058164.js b/dom/base/test/browser_bug1058164.js
new file mode 100644
index 0000000000..0bb1098ef2
--- /dev/null
+++ b/dom/base/test/browser_bug1058164.js
@@ -0,0 +1,238 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+SpecialPowers.pushPrefEnv({
+ set: [["security.allow_eval_with_system_principal", true]],
+});
+
+const PAGE =
+ "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+let gListenerId = 0;
+
+/**
+ * Adds a content event listener on the given browser element. NOTE: this test
+ * is checking the behavior of pageshow and pagehide as seen by frame scripts,
+ * so it is specifically implemented using the message message manager.
+ * Similar to BrowserTestUtils.waitForContentEvent, but the listener will fire
+ * until it is removed. A callable object is returned that, when called, removes
+ * the event listener. Note that this function works even if the browser's
+ * frameloader is swapped.
+ *
+ * @param {xul:browser} browser
+ * The browser element to listen for events in.
+ * @param {string} eventName
+ * Name of the event to listen to.
+ * @param {function} listener
+ * Function to call in parent process when event fires.
+ * Not passed any arguments.
+ * @param {function} checkFn [optional]
+ * Called with the Event object as argument, should return true if the
+ * event is the expected one, or false if it should be ignored and
+ * listening should continue. If not specified, the first event with
+ * the specified name resolves the returned promise. This is called
+ * within the content process and can have no closure environment.
+ *
+ * @returns function
+ * If called, the return value will remove the event listener.
+ */
+function addContentEventListenerWithMessageManager(
+ browser,
+ eventName,
+ listener,
+ checkFn
+) {
+ let id = gListenerId++;
+ let checkFnSource = checkFn
+ ? encodeURIComponent(escape(checkFn.toSource()))
+ : "";
+
+ // To correctly handle frameloader swaps, we load a frame script
+ // into all tabs but ignore messages from the ones not related to
+ // |browser|.
+
+ /* eslint-disable no-eval */
+ function frameScript(innerId, innerEventName, innerCheckFnSource) {
+ let innerCheckFn;
+ if (innerCheckFnSource) {
+ innerCheckFn = eval(`(() => (${unescape(innerCheckFnSource)}))()`);
+ }
+
+ function innerListener(event) {
+ if (innerCheckFn && !innerCheckFn(event)) {
+ return;
+ }
+ sendAsyncMessage("ContentEventListener:Run", innerId);
+ }
+ function removeListener(msg) {
+ if (msg.data == innerId) {
+ removeMessageListener("ContentEventListener:Remove", removeListener);
+ removeEventListener(innerEventName, innerListener);
+ }
+ }
+ addMessageListener("ContentEventListener:Remove", removeListener);
+ addEventListener(innerEventName, innerListener);
+ }
+ /* eslint-enable no-eval */
+
+ let frameScriptSource = `data:,(${frameScript.toString()})(${id}, "${eventName}",
+ "${checkFnSource}")`;
+
+ let mm = Services.mm;
+
+ function runListener(msg) {
+ if (msg.data == id && msg.target == browser) {
+ listener();
+ }
+ }
+ mm.addMessageListener("ContentEventListener:Run", runListener);
+
+ let needCleanup = true;
+
+ let unregisterFunction = function () {
+ if (!needCleanup) {
+ return;
+ }
+ needCleanup = false;
+ mm.removeMessageListener("ContentEventListener:Run", runListener);
+ mm.broadcastAsyncMessage("ContentEventListener:Remove", id);
+ mm.removeDelayedFrameScript(frameScriptSource);
+ };
+
+ function cleanupObserver(subject, topic, data) {
+ if (subject == browser.messageManager) {
+ unregisterFunction();
+ }
+ }
+
+ mm.loadFrameScript(frameScriptSource, true);
+
+ return unregisterFunction;
+}
+
+/**
+ * Returns a Promise that resolves when it sees a pageshow and
+ * pagehide events in a particular order, where each event must
+ * have the persisted property set to true. Will cause a test
+ * failure to be logged if it sees an event out of order.
+ *
+ * @param browser (<xul:browser>)
+ * The browser to expect the events from.
+ * @param expectedOrder (array)
+ * An array of strings describing what order the pageshow
+ * and pagehide events should be in.
+ * Example:
+ * ["pageshow", "pagehide", "pagehide", "pageshow"]
+ * @returns Promise
+ */
+function prepareForVisibilityEvents(browser, expectedOrder) {
+ return new Promise(resolve => {
+ let order = [];
+
+ let rmvHide, rmvShow;
+
+ let checkSatisfied = () => {
+ if (order.length < expectedOrder.length) {
+ // We're still waiting...
+ return;
+ } else {
+ rmvHide();
+ rmvShow();
+
+ for (let i = 0; i < expectedOrder.length; ++i) {
+ is(order[i], expectedOrder[i], "Got expected event");
+ }
+ resolve();
+ }
+ };
+
+ let eventListener = type => {
+ order.push(type);
+ checkSatisfied();
+ };
+
+ let checkFn = e => e.persisted;
+
+ rmvHide = addContentEventListenerWithMessageManager(
+ browser,
+ "pagehide",
+ () => eventListener("pagehide"),
+ checkFn
+ );
+ rmvShow = addContentEventListenerWithMessageManager(
+ browser,
+ "pageshow",
+ () => eventListener("pageshow"),
+ checkFn
+ );
+ });
+}
+
+/**
+ * Tests that frame scripts get pageshow / pagehide events when
+ * swapping browser frameloaders (which occurs when moving a tab
+ * into a different window).
+ */
+add_task(async function test_swap_frameloader_pagevisibility_events() {
+ // Disable window occlusion. Bug 1733955
+ if (navigator.platform.indexOf("Win") == 0) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["widget.windows.window_occlusion_tracking.enabled", false]],
+ });
+ }
+
+ // Load a new tab that we'll tear out...
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE);
+ gBrowser.selectedTab = tab;
+ let firstBrowser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(firstBrowser);
+
+ // Swap the browser out to a new window
+ let newWindow = gBrowser.replaceTabWithWindow(tab);
+
+ // We have to wait for the window to load so we can get the selected browser
+ // to listen to.
+ await BrowserTestUtils.waitForEvent(newWindow, "DOMContentLoaded");
+ let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
+
+ // Wait for the expected pagehide and pageshow events on the initial browser
+ await prepareForVisibilityEvents(newWindowBrowser, ["pagehide", "pageshow"]);
+
+ // Now let's send the browser back to the original window
+
+ // First, create a new, empty browser tab to replace the window with
+ let newTab = BrowserTestUtils.addTab(gBrowser);
+ gBrowser.selectedTab = newTab;
+ let emptyBrowser = newTab.linkedBrowser;
+
+ // Wait for that initial doc to be visible because if its pageshow hasn't
+ // happened we don't confuse it with the other expected events.
+ await ContentTask.spawn(emptyBrowser, null, async () => {
+ if (content.document.visibilityState === "hidden") {
+ info("waiting for hidden emptyBrowser to be visible");
+ await ContentTaskUtils.waitForEvent(
+ content.document,
+ "visibilitychange",
+ {}
+ );
+ }
+ });
+
+ info("emptyBrowser is shown now.");
+
+ // The empty tab we just added show now fire a pagehide as its replaced,
+ // and a pageshow once the swap is finished.
+ let emptyBrowserPromise = prepareForVisibilityEvents(emptyBrowser, [
+ "pagehide",
+ "pageshow",
+ ]);
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, newWindow.gBrowser.selectedTab);
+
+ await emptyBrowserPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/dom/base/test/browser_bug1303838.js b/dom/base/test/browser_bug1303838.js
new file mode 100644
index 0000000000..a4d5ee3b26
--- /dev/null
+++ b/dom/base/test/browser_bug1303838.js
@@ -0,0 +1,368 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 1303838.
+ * Load a tab with some links, emulate link clicks and check if the
+ * browser would switch to the existing target tab opened by previous
+ * link click if loadDivertedInBackground is set to true.
+ */
+
+"use strict";
+
+const BASE_URL = "http://mochi.test:8888/browser/dom/base/test/";
+
+add_task(async function () {
+ // On Linux, in our test automation, the mouse cursor floats over
+ // the first tab, which causes it to be warmed up when tab warming
+ // is enabled. The TabSwitchDone event doesn't fire until the warmed
+ // tab is evicted, which is after a few seconds. That means that
+ // this test ends up taking longer than we'd like, since its waiting
+ // for the TabSwitchDone event between tab switches.
+ //
+ // So now we make sure that warmed tabs are evicted very shortly
+ // after warming to avoid the test running too long.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.remote.warmup.unloadDelayMs", 50]],
+ });
+ await testLinkClick(false, false);
+ await testLinkClick(false, true);
+ await testLinkClick(true, false);
+ await testLinkClick(true, true);
+});
+
+async function waitForTestReady(loadDivertedInBackground, tab) {
+ if (!loadDivertedInBackground) {
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ } else {
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+}
+
+async function testLinkClick(withFrame, loadDivertedInBackground) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.loadDivertedInBackground", loadDivertedInBackground]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ BASE_URL +
+ (withFrame ? "file_bug1303838_with_iframe.html" : "file_bug1303838.html")
+ );
+ is(gBrowser.tabs.length, 2, "check tabs.length");
+ is(gBrowser.selectedTab, tab, "check selectedTab");
+
+ info(
+ "Test normal links with loadDivertedInBackground=" +
+ loadDivertedInBackground +
+ ", withFrame=" +
+ withFrame
+ );
+
+ let testTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ await clickLink(withFrame, "#link-1", tab.linkedBrowser);
+ let testTab = await testTabPromise;
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#link-2",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#link-3",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#link-4",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ // Location APIs shouldn't steal focus.
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#link-5",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ /* awaitTabSwitch = */ false
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(gBrowser.selectedTab, tab, "check selectedTab");
+
+ await waitForTestReady(/* diverted = */ true, tab);
+ await clickLink(
+ withFrame,
+ "#link-6",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ /* awaitTabSwitch = */ false
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(gBrowser.selectedTab, tab, "check selectedTab");
+
+ await waitForTestReady(/* diverted = */ true, tab);
+ let loaded = BrowserTestUtils.browserLoaded(
+ testTab.linkedBrowser,
+ true,
+ "data:text/html;charset=utf-8,testFrame"
+ );
+ await clickLink(
+ withFrame,
+ "#link-7",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground,
+ false
+ );
+ await loaded;
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ info(
+ "Test anchor links with loadDivertedInBackground=" +
+ loadDivertedInBackground +
+ ", withFrame=" +
+ withFrame
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#anchor-link-1",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#anchor-link-2",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#anchor-link-3",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ info(
+ "Test iframe links with loadDivertedInBackground=" +
+ loadDivertedInBackground +
+ ", withFrame=" +
+ withFrame
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#frame-link-1",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground,
+ true
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#frame-link-2",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground,
+ true
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ await waitForTestReady(loadDivertedInBackground, tab);
+ await clickLink(
+ withFrame,
+ "#frame-link-3",
+ tab.linkedBrowser,
+ testTab.linkedBrowser,
+ !loadDivertedInBackground,
+ true
+ );
+ is(gBrowser.tabs.length, 3, "check tabs.length");
+ is(
+ gBrowser.selectedTab,
+ loadDivertedInBackground ? tab : testTab,
+ "check selectedTab"
+ );
+
+ BrowserTestUtils.removeTab(testTab);
+ BrowserTestUtils.removeTab(tab);
+}
+
+function clickLink(
+ isFrame,
+ linkId,
+ browser,
+ testBrowser,
+ awaitTabSwitch = false,
+ targetsFrame = false,
+ locationChangeNum = 1
+) {
+ let promises = [];
+ if (awaitTabSwitch) {
+ promises.push(waitForTabSwitch(gBrowser));
+ }
+ if (testBrowser) {
+ promises.push(
+ waitForLocationChange(targetsFrame, testBrowser, locationChangeNum)
+ );
+ }
+
+ info("BC children: " + browser.browsingContext.children.length);
+ promises.push(
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ linkId,
+ {},
+ isFrame ? browser.browsingContext.children[0] : browser
+ )
+ );
+ return Promise.all(promises);
+}
+
+function waitForTabSwitch(tabbrowser) {
+ info("Waiting for TabSwitch");
+ return new Promise(resolve => {
+ tabbrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ info("TabSwitch done");
+ tabbrowser.removeEventListener("TabSwitchDone", onSwitch);
+ resolve(tabbrowser.selectedTab);
+ });
+ });
+}
+
+let locationChangeListener;
+function waitForLocationChange(isFrame, browser, locationChangeNum) {
+ if (isFrame) {
+ return waitForFrameLocationChange(browser, locationChangeNum);
+ }
+
+ info("Waiting for " + locationChangeNum + " LocationChange");
+ return new Promise(resolve => {
+ let seen = 0;
+ locationChangeListener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ info("LocationChange: " + aLocation.spec);
+ if (++seen == locationChangeNum) {
+ browser.removeProgressListener(this);
+ resolve();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ browser.addProgressListener(locationChangeListener);
+ });
+}
+
+function waitForFrameLocationChange(browser, locationChangeNum) {
+ info("Waiting for " + locationChangeNum + " LocationChange in subframe");
+ return SpecialPowers.spawn(browser, [locationChangeNum], async changeNum => {
+ let seen = 0;
+ let webprogress = content.docShell.QueryInterface(Ci.nsIWebProgress);
+ await new Promise(resolve => {
+ locationChangeListener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ info("LocationChange: " + aLocation.spec);
+ if (++seen == changeNum) {
+ resolve();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ webprogress.addProgressListener(
+ locationChangeListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+ });
+ webprogress.removeProgressListener(locationChangeListener);
+ });
+}
diff --git a/dom/base/test/browser_bug1554070.js b/dom/base/test/browser_bug1554070.js
new file mode 100644
index 0000000000..a5efc17e19
--- /dev/null
+++ b/dom/base/test/browser_bug1554070.js
@@ -0,0 +1,49 @@
+"use strict";
+
+const HTTPS_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+const URL0 = HTTPS_TEST_ROOT + "file_bug1554070_1.html";
+const URL1 = HTTPS_TEST_ROOT + "file_bug1554070_2.html";
+const URL2 = "https://example.org/";
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ waitForLoad: true,
+ });
+
+ let browser = tab.linkedBrowser;
+
+ function click() {
+ return SpecialPowers.spawn(browser, [], () => {
+ let anchor = content.document.querySelector("a");
+ anchor.click();
+ });
+ }
+
+ // Load file_bug1554070_1.html.
+ BrowserTestUtils.loadURIString(browser, URL0);
+ await BrowserTestUtils.browserLoaded(browser, false, URL0);
+ is(gBrowser.currentURI.spec, URL0, "loaded file_bug1554070_1.html");
+
+ // Click the link in file_bug1554070_1.html. It should open
+ // file_bug1554070_2.html in the current tab.
+ await click();
+ await BrowserTestUtils.browserLoaded(browser, false, URL1);
+ is(gBrowser.currentURI.spec, URL1, "loaded file_bug1554070_2.html");
+
+ // Click the link in file_bug1554070_2.html. It should open example.org in
+ // a new tab.
+ await Promise.all([
+ click(),
+ BrowserTestUtils.waitForNewTab(gBrowser, URL2, true),
+ ]);
+ is(gBrowser.tabs.length, 3, "got new tab");
+ is(gBrowser.currentURI.spec, URL2, "loaded example.org");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/base/test/browser_bug1691214.js b/dom/base/test/browser_bug1691214.js
new file mode 100644
index 0000000000..5f40df2ea3
--- /dev/null
+++ b/dom/base/test/browser_bug1691214.js
@@ -0,0 +1,122 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const BASE_URL = "http://mochi.test:8888/browser/dom/base/test/";
+
+add_task(async function bug1691214() {
+ await BrowserTestUtils.withNewTab(
+ BASE_URL + "file_bug1691214.html",
+ async function (browser) {
+ let win = await newFocusedWindow(function () {
+ return BrowserTestUtils.synthesizeMouseAtCenter("#link-1", {}, browser);
+ });
+ is(Services.focus.focusedWindow, win, "New window should be focused");
+
+ info("re-focusing the original window");
+
+ {
+ let focusBack = BrowserTestUtils.waitForEvent(window, "focus", true);
+ window.focus();
+ await focusBack;
+ is(Services.focus.focusedWindow, window, "should focus back");
+ }
+
+ info("Clicking on the second link.");
+
+ {
+ let focus = BrowserTestUtils.waitForEvent(win, "focus", true);
+ await BrowserTestUtils.synthesizeMouseAtCenter("#link-2", {}, browser);
+ info("Waiting for re-focus of the opened window.");
+ await focus;
+ }
+
+ is(
+ Services.focus.focusedWindow,
+ win,
+ "Existing window should've been targeted and focused"
+ );
+
+ win.close();
+ }
+ );
+});
+
+// The tab has a form infinitely submitting to an iframe, and that shouldn't
+// switch focus back. For that, we open a window from the tab, make sure it's
+// focused, and then wait for three submissions (but since we need to listen to
+// trusted events from chrome code, we use the iframe load event instead).
+add_task(async function bug1700871() {
+ await BrowserTestUtils.withNewTab(
+ BASE_URL + "file_bug1700871.html",
+ async function (browser) {
+ let win = await newFocusedWindow(function () {
+ return BrowserTestUtils.synthesizeMouseAtCenter("#link-1", {}, browser);
+ });
+
+ is(Services.focus.focusedWindow, win, "New window should be focused");
+
+ info("waiting for three submit events and ensuring focus hasn't moved");
+
+ await SpecialPowers.spawn(browser, [], function () {
+ let pending = 3;
+ return new Promise(resolve => {
+ content.document
+ .querySelector("iframe")
+ .addEventListener("load", function (e) {
+ info("Got load on the frame: " + pending);
+ if (!--pending) {
+ resolve();
+ }
+ });
+ });
+ });
+
+ is(Services.focus.focusedWindow, win, "Focus shouldn't have moved");
+ win.close();
+ }
+ );
+});
+
+add_task(async function bug1793829() {
+ await BrowserTestUtils.withNewTab(
+ BASE_URL + "file_bug1691214.html",
+ async function (browser) {
+ let win = await newFocusedWindow(function () {
+ return BrowserTestUtils.synthesizeMouseAtCenter("#link-1", {}, browser);
+ });
+ is(Services.focus.focusedWindow, win, "New window should be focused");
+
+ info("re-focusing the original window");
+
+ {
+ let focusBack = BrowserTestUtils.waitForEvent(window, "focus", true);
+ window.focus();
+ await focusBack;
+ is(Services.focus.focusedWindow, window, "should focus back");
+ }
+
+ info("Trying to steal focus.");
+
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.clearUserGestureActivation();
+ content.document.getElementById("link-2").click();
+ });
+
+ // We need to test that nothing happened, which is hard without an
+ // arbitrary timeout.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(c => setTimeout(c, 2000));
+
+ is(
+ Services.focus.focusedWindow,
+ window,
+ "Shouldn't steal focus without user gesture"
+ );
+
+ win.close();
+ }
+ );
+});
diff --git a/dom/base/test/browser_bug1703472.js b/dom/base/test/browser_bug1703472.js
new file mode 100644
index 0000000000..ba4d10a63f
--- /dev/null
+++ b/dom/base/test/browser_bug1703472.js
@@ -0,0 +1,68 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const BASE_URL = "http://mochi.test:8888/browser/dom/base/test/";
+
+add_task(async function bug1703472() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.block_multiple_popups", true],
+ ["dom.disable_open_during_load", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ BASE_URL + "file_bug1703472.html",
+ async function (browser) {
+ info("Opening popup");
+ let win = await newFocusedWindow(function () {
+ return BrowserTestUtils.synthesizeMouseAtCenter(
+ "#openWindow",
+ {},
+ browser
+ );
+ });
+
+ info("re-focusing the original window");
+ {
+ let focusBack = BrowserTestUtils.waitForEvent(window, "focus", true);
+ window.focus();
+ await focusBack;
+ is(Services.focus.focusedWindow, window, "should focus back");
+ }
+
+ // The click to do window.open() should've consumed the user interaction,
+ // and an artificial .click() shouldn't count as a user interaction, so the
+ // page shouldn't be allowed to focus it again without user interaction.
+ info("Trying to steal focus without interaction");
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.querySelector("#focusWindow").click();
+ });
+
+ // We need to wait for something _not_ happening, so we need to use an arbitrary setTimeout.
+ await new Promise(resolve => {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(resolve, 500);
+ });
+
+ is(Services.focus.focusedWindow, window, "should still be focused");
+
+ info("Trying to move focus with user interaction");
+ {
+ let focus = BrowserTestUtils.waitForEvent(win, "focus", true);
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#focusWindow",
+ {},
+ browser
+ );
+ await focus;
+ is(Services.focus.focusedWindow, win, "should focus back");
+ }
+
+ win.close();
+ }
+ );
+});
diff --git a/dom/base/test/browser_bug902350.js b/dom/base/test/browser_bug902350.js
new file mode 100644
index 0000000000..18b17c5953
--- /dev/null
+++ b/dom/base/test/browser_bug902350.js
@@ -0,0 +1,56 @@
+/*
+ * Mixed Content Block frame navigates for target="_top" - Test for Bug 902350
+ */
+
+add_task(async function mixed_content_block_for_target_top_test() {
+ const PREF_ACTIVE = "security.mixed_content.block_active_content";
+ const httpsTestRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ );
+
+ await SpecialPowers.pushPrefEnv({ set: [[PREF_ACTIVE, true]] });
+
+ let newTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ waitForLoad: true,
+ });
+ let testBrowser = newTab.linkedBrowser;
+
+ var url = httpsTestRoot + "file_bug902350.html";
+ var frameUrl = httpsTestRoot + "file_bug902350_frame.html";
+ let loadPromise = BrowserTestUtils.browserLoaded(testBrowser, false, url);
+ let frameLoadPromise = BrowserTestUtils.browserLoaded(
+ testBrowser,
+ true,
+ frameUrl
+ );
+ BrowserTestUtils.loadURIString(testBrowser, url);
+ await loadPromise;
+ await frameLoadPromise;
+
+ // Find the iframe and click the link in it.
+ let insecureUrl = "http://example.com/";
+ let insecureLoadPromise = BrowserTestUtils.browserLoaded(
+ testBrowser,
+ false,
+ insecureUrl
+ );
+ SpecialPowers.spawn(testBrowser, [], function () {
+ var frame = content.document.getElementById("testing_frame");
+ var topTarget = frame.contentWindow.document.getElementById("topTarget");
+ topTarget.click();
+ });
+
+ // Navigating to insecure domain through target='_top' should succeed.
+ await insecureLoadPromise;
+
+ // The link click should not invoke the Mixed Content Blocker.
+ let { gIdentityHandler } = testBrowser.ownerGlobal;
+ ok(
+ !gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "Mixed Content Doorhanger did not appear when trying to navigate top"
+ );
+
+ BrowserTestUtils.removeTab(newTab);
+});
diff --git a/dom/base/test/browser_chromeutils_getalldomprocesses.js b/dom/base/test/browser_chromeutils_getalldomprocesses.js
new file mode 100644
index 0000000000..aef5bcf12b
--- /dev/null
+++ b/dom/base/test/browser_chromeutils_getalldomprocesses.js
@@ -0,0 +1,62 @@
+add_task(async function testParentProcess() {
+ const [parentProcess] = ChromeUtils.getAllDOMProcesses();
+ // browser.xhtml is in the parent process, so its domProcess is the parent process one
+ is(
+ parentProcess,
+ window.browsingContext.currentWindowGlobal.domProcess,
+ "The first element is the parent process"
+ );
+ is(
+ parentProcess.osPid,
+ Services.appinfo.processID,
+ "We got the right OS Pid"
+ );
+ is(parentProcess.childID, 0, "Parent process has childID set to 0");
+});
+
+add_task(async function testContentProcesses() {
+ info("Open a tab in a content process");
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:blank");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ info("Sanity checks against all returned elements of getAllDOMProcesses");
+ const processes = ChromeUtils.getAllDOMProcesses();
+ const allPids = [],
+ allChildIDs = [];
+ for (const process of processes) {
+ ok(
+ process instanceof Ci.nsIDOMProcessParent,
+ `Each element of the array is a nsIDOMProcessParent (${process})`
+ );
+ ok(process.osPid > 0, `OS Pid looks correct ${process.osPid}`);
+ if (process == processes[0]) {
+ is(
+ process.childID,
+ 0,
+ `Child ID is 0 for the parent process, which is the first element of the returned array`
+ );
+ is(
+ process.remoteType,
+ null,
+ "The main process's remote type should be NOT_REMOTE"
+ );
+ } else {
+ ok(process.childID > 0, `Child ID looks also correct ${process.childID}`);
+ ok(process.remoteType, "Should have a remote type");
+ }
+
+ ok(
+ !allPids.includes(process.osPid),
+ "We only get one nsIDOMProcessParent per OS process == each osPid is different"
+ );
+ allPids.push(process.osPid);
+ ok(!allChildIDs.includes(process.childID), "Each childID is different");
+ allChildIDs.push(process.childID);
+ }
+
+ info("Search for the nsIDOMProcessParent for the opened tab");
+ const { currentWindowGlobal } = gBrowser.selectedBrowser.browsingContext;
+ const tabProcess = currentWindowGlobal.domProcess;
+ ok(processes.includes(tabProcess), "Found the tab process in the list");
+ is(tabProcess.osPid, currentWindowGlobal.osPid, "We got the right OS Pid");
+});
diff --git a/dom/base/test/browser_chromeutils_isdomobject.js b/dom/base/test/browser_chromeutils_isdomobject.js
new file mode 100644
index 0000000000..14a5d69898
--- /dev/null
+++ b/dom/base/test/browser_chromeutils_isdomobject.js
@@ -0,0 +1,115 @@
+"use strict";
+
+add_task(async function invalidArgument() {
+ const args = [undefined, null, 42, "foo"];
+
+ for (const argument of args) {
+ let hadException = false;
+ try {
+ ChromeUtils.isDOMObject(argument);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+ }
+});
+
+add_task(async function NoUnwrap() {
+ const args = [
+ window.document,
+ window.document.childNodes,
+ new DocumentFragment(),
+ new Response(),
+ new URL("https://example.com"),
+ ];
+
+ for (const argument of args) {
+ ok(
+ ChromeUtils.isDOMObject(argument, false),
+ `${ChromeUtils.getClassName(
+ argument
+ )} to be a DOM object with unwrap=false`
+ );
+ }
+
+ ok(
+ !ChromeUtils.isDOMObject(window, false),
+ `${ChromeUtils.getClassName(
+ window
+ )} not to be a DOM object with unwrap=false`
+ );
+});
+
+add_task(async function DOMObjects() {
+ const args = [
+ window,
+ window.document,
+ window.document.childNodes,
+ new DocumentFragment(),
+ new Response(),
+ new URL("https://example.com"),
+ ];
+
+ for (const argument of args) {
+ ok(
+ ChromeUtils.isDOMObject(argument),
+ `${ChromeUtils.getClassName(argument)} to be a DOM object`
+ );
+ }
+});
+
+add_task(async function nonDOMObjects() {
+ const args = [new Object(), {}, []];
+
+ for (const argument of args) {
+ ok(
+ !ChromeUtils.isDOMObject(argument),
+ `${ChromeUtils.getClassName(argument)} not to be a DOM object`
+ );
+ }
+});
+
+add_task(async function DOMObjects_contentProcess() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,<div>`,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], async () => {
+ const args = [
+ content,
+ content.document,
+ content.document.querySelector("div"),
+ content.document.childNodes,
+ new content.DocumentFragment(),
+ new content.URL("https://example.com"),
+ ];
+
+ for (const argument of args) {
+ ok(
+ ChromeUtils.isDOMObject(argument),
+ `${ChromeUtils.getClassName(
+ argument
+ )} in content to be a DOM object`
+ );
+ }
+
+ ok(
+ !ChromeUtils.isDOMObject({}),
+ `${ChromeUtils.getClassName({})} in content not to be a DOM object`
+ );
+
+ // unwrap=false
+ for (const argument of args) {
+ ok(
+ !ChromeUtils.isDOMObject(argument, false),
+ `${ChromeUtils.getClassName(
+ argument
+ )} in content not to be a DOM object with unwrap=false`
+ );
+ }
+ });
+ }
+ );
+});
diff --git a/dom/base/test/browser_data_documents_aboutmemory.js b/dom/base/test/browser_data_documents_aboutmemory.js
new file mode 100644
index 0000000000..5c534a83b6
--- /dev/null
+++ b/dom/base/test/browser_data_documents_aboutmemory.js
@@ -0,0 +1,20 @@
+add_task(async function () {
+ const doc = new DOMParser().parseFromString("<p>dadada</p>", "text/html");
+
+ let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
+ Ci.nsIMemoryReporterManager
+ );
+
+ let amount = 0;
+ const handleReport = (aProcess, aPath, aKind, aUnits, aAmount) => {
+ const regex = new RegExp(".*/window-objects/.*/data-documents/.*");
+ if (regex.test(aPath)) {
+ amount += aAmount;
+ }
+ };
+
+ await new Promise(r =>
+ mgr.getReports(handleReport, null, r, null, /* anonymized = */ false)
+ );
+ ok(amount > 0, "Got data documents amount");
+});
diff --git a/dom/base/test/browser_force_process_selector.js b/dom/base/test/browser_force_process_selector.js
new file mode 100644
index 0000000000..15157b240d
--- /dev/null
+++ b/dom/base/test/browser_force_process_selector.js
@@ -0,0 +1,38 @@
+"use strict";
+
+const CONTENT_CREATED = "ipc:content-created";
+
+// Make sure that BTU.withNewTab({ ..., forceNewProcess: true }) loads
+// new tabs in their own process.
+async function spawnNewAndTest(recur, pids) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank", forceNewProcess: true },
+ async function (browser) {
+ // Make sure our new browser is in its own process.
+ let newPid = browser.frameLoader.remoteTab.osPid;
+ ok(!pids.has(newPid), "new tab is in its own process: " + recur);
+ pids.add(newPid);
+
+ if (recur) {
+ await spawnNewAndTest(recur - 1, pids);
+ } else {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ function (lastBrowser) {
+ let lastPid = lastBrowser.frameLoader.remoteTab.osPid;
+ ok(pids.has(lastPid), "final tab cannot be in its own process");
+ }
+ );
+ }
+ }
+ );
+}
+
+add_task(async function test() {
+ let curPid = gBrowser.selectedBrowser.frameLoader.remoteTab.osPid;
+ let maxCount = Services.prefs.getIntPref("dom.ipc.processCount");
+
+ // Use at least one more tab than max processes or at least 5 to make this
+ // test interesting.
+ await spawnNewAndTest(Math.max(maxCount + 1, 5), new Set([curPid]));
+});
diff --git a/dom/base/test/browser_form_validity_popup_submit.js b/dom/base/test/browser_form_validity_popup_submit.js
new file mode 100644
index 0000000000..2cefbf9d98
--- /dev/null
+++ b/dom/base/test/browser_form_validity_popup_submit.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+async function promiseValidityPopupShown() {
+ await BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ /* capture = */ false,
+ event => event.target.id == "invalid-form-popup"
+ );
+ return document.getElementById("invalid-form-popup");
+}
+
+const HTML = `
+ <form action="">
+ <input name="text" type="text" placeholder="type here" autofocus required>
+ <input id="submit" type="submit">
+ </form>
+`;
+
+add_task(async function bug_1790128() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,${encodeURI(HTML)}`,
+ },
+ async function (aBrowser) {
+ let promisePopupShown = promiseValidityPopupShown();
+ await BrowserTestUtils.synthesizeMouseAtCenter("#submit", {}, aBrowser);
+ let popup = await promisePopupShown;
+ is(popup.state, "open", "Should be open");
+
+ let promisePopupHidden = BrowserTestUtils.waitForEvent(
+ popup,
+ "popuphidden"
+ );
+ promisePopupShown = promiseValidityPopupShown();
+
+ // This is written in a rather odd style because depending on whether
+ // panel animations are enabled we might be able to show the popup again
+ // after the first click or not. The point is that after one click the
+ // popup should've hid at least once, and after two the popup should've
+ // open again at least once.
+ await BrowserTestUtils.synthesizeMouseAtCenter("#submit", {}, aBrowser);
+ await promisePopupHidden;
+ await BrowserTestUtils.synthesizeMouseAtCenter("#submit", {}, aBrowser);
+ await promisePopupShown;
+ ok(true, "Should've shown the popup again");
+ }
+ );
+});
diff --git a/dom/base/test/browser_inputStream_structuredClone.js b/dom/base/test/browser_inputStream_structuredClone.js
new file mode 100644
index 0000000000..a022563ac0
--- /dev/null
+++ b/dom/base/test/browser_inputStream_structuredClone.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URIs = [
+ "about:about",
+ "http://example.com/browser/dom/base/test/empty.html",
+];
+
+async function runTest(input, url) {
+ let tab = BrowserTestUtils.addTab(gBrowser, url);
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData(input, input.length);
+
+ let data = {
+ inputStream: stream,
+ };
+
+ is(
+ data.inputStream.available(),
+ input.length,
+ "The length of the inputStream matches: " + input.length
+ );
+
+ // FIXME: SpecialPowers.spawn currently crashes when trying to return
+ // values containing input streams.
+ /* eslint-disable no-shadow */
+ let dataBack = await ContentTask.spawn(browser, data, function (data) {
+ let dataBack = {
+ inputStream: data.inputStream,
+ check: true,
+ };
+
+ if (content.location.href.startsWith("about:")) {
+ dataBack.check =
+ data.inputStream instanceof
+ content.Components.interfaces.nsIInputStream;
+ }
+
+ return dataBack;
+ });
+ /* eslint-enable no-shadow */
+
+ ok(dataBack.check, "The inputStream is a nsIInputStream also on content.");
+ ok(
+ data.inputStream instanceof Ci.nsIInputStream,
+ "The original object was an inputStream"
+ );
+ ok(
+ dataBack.inputStream instanceof Ci.nsIInputStream,
+ "We have an inputStream back from the content."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test() {
+ let a = "a";
+ for (let i = 0; i < 25; ++i) {
+ a += a;
+ }
+
+ for (let i = 0; i < URIs.length; ++i) {
+ await runTest("Hello world", URIs[i]);
+ await runTest(a, URIs[i]);
+ }
+});
diff --git a/dom/base/test/browser_messagemanager_loadprocessscript.js b/dom/base/test/browser_messagemanager_loadprocessscript.js
new file mode 100644
index 0000000000..16686dd566
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_loadprocessscript.js
@@ -0,0 +1,193 @@
+function getBaseNumberOfProcesses() {
+ // We should have three processes for this test, the parent process and two
+ // content processes for the tabs craeted by this test.
+ let processCount = 3;
+
+ // If we run WebExtensions out-of-process (see bug 1190679), there might be
+ // additional processes for those, so let's add these to the base count to
+ // not have system WebExtensions cause test failures.
+ for (let i = 0; i < Services.ppmm.childCount; i++) {
+ if (
+ Services.ppmm.getChildAt(i).remoteType === E10SUtils.EXTENSION_REMOTE_TYPE
+ ) {
+ processCount += 1;
+ }
+ }
+
+ return processCount;
+}
+
+function checkBaseProcessCount(description) {
+ const baseProcessCount = getBaseNumberOfProcesses();
+ const { childCount } = Services.ppmm;
+ // With preloaded activity-stream, process count is a bit undeterministic, so
+ // allow for some variation
+ const extraCount = baseProcessCount + 1;
+ ok(
+ childCount === baseProcessCount || childCount === extraCount,
+ `${description} (${baseProcessCount} or ${extraCount})`
+ );
+}
+
+function processScript() {
+ /* eslint-env mozilla/process-script */
+ if (Services.cpmm !== this) {
+ dump("Test failed: wrong global object\n");
+ return;
+ }
+
+ this.cpmm = Services.cpmm;
+
+ addMessageListener("ProcessTest:Reply", function listener(msg) {
+ removeMessageListener("ProcessTest:Reply", listener);
+ sendAsyncMessage("ProcessTest:Finished");
+ });
+ sendSyncMessage("ProcessTest:Loaded");
+}
+var processScriptURL = "data:,(" + processScript.toString() + ").call(this)";
+
+function initTestScript() {
+ /* eslint-env mozilla/process-script */
+ let init = initialProcessData;
+ if (init.test123 != "hello") {
+ dump("Initial data incorrect\n");
+ return;
+ }
+
+ sendAsyncMessage("ProcessTest:InitGood", init.test456.get("hi"));
+}
+var initTestScriptURL = "data:,(" + initTestScript.toString() + ")()";
+
+var checkProcess = async function (mm) {
+ let { target } = await promiseMessage(mm, "ProcessTest:Loaded");
+ target.sendAsyncMessage("ProcessTest:Reply");
+ await promiseMessage(target, "ProcessTest:Finished");
+ ok(true, "Saw process finished");
+};
+
+function promiseMessage(messageManager, message) {
+ return new Promise(resolve => {
+ let listener = msg => {
+ messageManager.removeMessageListener(message, listener);
+ resolve(msg);
+ };
+
+ messageManager.addMessageListener(message, listener);
+ });
+}
+
+add_task(async function () {
+ // We want to count processes in this test, so let's disable the pre-allocated process manager.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processPrelaunch.enabled", false]],
+ });
+});
+
+add_task(async function () {
+ // This test is only relevant in e10s.
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ Services.ppmm.releaseCachedProcesses();
+
+ await SpecialPowers.pushPrefEnv({ set: [["dom.ipc.processCount", 5]] });
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.keepProcessesAlive.web", 5]],
+ });
+
+ let tabs = [];
+ for (let i = 0; i < 3; i++) {
+ tabs[i] = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ }
+
+ for (let i = 0; i < 3; i++) {
+ // FIXME: This should wait for the tab removal gets reflected to the
+ // process count (bug 1446726).
+ let sessionStorePromise = BrowserTestUtils.waitForSessionStoreUpdate(
+ tabs[i]
+ );
+ BrowserTestUtils.removeTab(tabs[i]);
+ await sessionStorePromise;
+ }
+
+ Services.ppmm.releaseCachedProcesses();
+ checkBaseProcessCount(
+ "Should get back to the base number of processes at this point"
+ );
+});
+
+// Test that loading a process script loads in all existing processes
+add_task(async function () {
+ let checks = [];
+ for (let i = 0; i < Services.ppmm.childCount; i++) {
+ checks.push(checkProcess(Services.ppmm.getChildAt(i)));
+ }
+
+ Services.ppmm.loadProcessScript(processScriptURL, false);
+ await Promise.all(checks);
+});
+
+// Test that loading a process script loads in new processes
+add_task(async function () {
+ // This test is only relevant in e10s
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ checkBaseProcessCount(
+ "Should still be at the base number of processes at this point"
+ );
+
+ // Load something in the main process
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:mozilla");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let init = Services.ppmm.initialProcessData;
+ init.test123 = "hello";
+ init.test456 = new Map();
+ init.test456.set("hi", "bye");
+
+ // With no remote frames left we should be down to one process.
+ // However, stuff like remote thumbnails can cause a content
+ // process to exist nonetheless. This should be rare, though,
+ // so the test is useful most of the time.
+ if (Services.ppmm.childCount == 2) {
+ let mainMM = Services.ppmm.getChildAt(0);
+
+ let check = checkProcess(Services.ppmm);
+ Services.ppmm.loadProcessScript(processScriptURL, true);
+
+ // The main process should respond
+ await check;
+
+ check = checkProcess(Services.ppmm);
+ // Reset the default browser to start a new child process
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, {
+ remoteType: E10SUtils.DEFAULT_REMOTE_TYPE,
+ });
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:blank");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ checkBaseProcessCount(
+ "Should be back to the base number of processes at this point"
+ );
+
+ // The new process should have responded
+ await check;
+
+ Services.ppmm.removeDelayedProcessScript(processScriptURL);
+
+ let childMM;
+ childMM = Services.ppmm.getChildAt(2);
+
+ childMM.loadProcessScript(initTestScriptURL, false);
+ let msg = await promiseMessage(childMM, "ProcessTest:InitGood");
+ is(msg.data, "bye", "initial process data was correct");
+ } else {
+ info("Unable to finish test entirely");
+ }
+});
diff --git a/dom/base/test/browser_messagemanager_targetframeloader.js b/dom/base/test/browser_messagemanager_targetframeloader.js
new file mode 100644
index 0000000000..62c8482add
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_targetframeloader.js
@@ -0,0 +1,36 @@
+function frameScript() {
+ sendSyncMessage("Test:Message");
+ sendAsyncMessage("Test:Message");
+ sendAsyncMessage("Test:Done");
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ var newTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ gBrowser.selectedTab = newTab;
+
+ let browser = newTab.linkedBrowser;
+ let frameLoader = browser.frameLoader;
+ ok(frameLoader !== null, "frameLoader looks okay");
+
+ browser.messageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")()",
+ false
+ );
+
+ browser.messageManager.addMessageListener("Test:Message", msg => {
+ ok(msg.target === browser, "<browser> is correct");
+ ok(msg.targetFrameLoader === frameLoader, "frameLoader is correct");
+ ok(
+ browser.frameLoader === msg.targetFrameLoader,
+ "browser frameloader is correct"
+ );
+ });
+
+ browser.messageManager.addMessageListener("Test:Done", () => {
+ info("Finished");
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
diff --git a/dom/base/test/browser_messagemanager_unload.js b/dom/base/test/browser_messagemanager_unload.js
new file mode 100644
index 0000000000..de8be528b3
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_unload.js
@@ -0,0 +1,131 @@
+function frameScript() {
+ function eventHandler(e) {
+ if (!docShell) {
+ sendAsyncMessage("Test:Fail", "docShell is null");
+ }
+
+ sendAsyncMessage("Test:Event", [
+ e.type,
+ e.target === content.document,
+ e.eventPhase,
+ ]);
+ }
+
+ let outerID = content.docShell.outerWindowID;
+ function onOuterWindowDestroyed(subject, topic, data) {
+ if (docShell) {
+ sendAsyncMessage("Test:Fail", "docShell is non-null");
+ }
+
+ let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ sendAsyncMessage("Test:Event", ["outer-window-destroyed", id == outerID]);
+ if (id == outerID) {
+ Services.obs.removeObserver(
+ onOuterWindowDestroyed,
+ "outer-window-destroyed"
+ );
+ }
+ }
+
+ let url =
+ "https://example.com/browser/dom/base/test/file_messagemanager_unload.html";
+
+ content.location = url;
+ addEventListener(
+ "load",
+ e => {
+ if (e.target.location != url) {
+ return;
+ }
+
+ addEventListener("unload", eventHandler, false);
+ addEventListener("unload", eventHandler, true);
+ addEventListener("pagehide", eventHandler, false);
+ addEventListener("pagehide", eventHandler, true);
+ Services.obs.addObserver(
+ onOuterWindowDestroyed,
+ "outer-window-destroyed"
+ );
+
+ sendAsyncMessage("Test:Ready");
+ },
+ true
+ );
+}
+
+const EXPECTED = [
+ // Unload events on the BrowserChildGlobal. These come first so that the
+ // docshell is available.
+ ["unload", false, 2],
+ ["unload", false, 2],
+
+ // pagehide and unload events for the top-level page.
+ ["pagehide", true, 1],
+ ["pagehide", true, 3],
+ ["unload", true, 1],
+
+ // pagehide and unload events for the iframe.
+ ["pagehide", false, 1],
+ ["pagehide", false, 3],
+ ["unload", false, 1],
+
+ // outer-window-destroyed for both pages.
+ ["outer-window-destroyed", false],
+ ["outer-window-destroyed", true],
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ var newTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ gBrowser.selectedTab = newTab;
+
+ let browser = newTab.linkedBrowser;
+ let frameLoader = browser.frameLoader;
+ ok(frameLoader !== null, "frameLoader looks okay");
+
+ browser.messageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")()",
+ false
+ );
+
+ browser.messageManager.addMessageListener(
+ "Test:Fail",
+ msg => {
+ ok(false, msg.data);
+ },
+ true
+ );
+
+ let index = 0;
+ browser.messageManager.addMessageListener(
+ "Test:Event",
+ msg => {
+ ok(msg.target === browser, "<browser> is correct");
+ ok(msg.targetFrameLoader === frameLoader, "frameLoader is correct");
+ ok(
+ browser.frameLoader === null,
+ "browser frameloader null during teardown"
+ );
+
+ info(JSON.stringify(msg.data));
+
+ is(
+ JSON.stringify(msg.data),
+ JSON.stringify(EXPECTED[index]),
+ "results match"
+ );
+ index++;
+
+ if (index == EXPECTED.length) {
+ finish();
+ }
+ },
+ true
+ );
+
+ browser.messageManager.addMessageListener("Test:Ready", () => {
+ info("Got ready message");
+ gBrowser.removeCurrentTab();
+ });
+}
diff --git a/dom/base/test/browser_multiple_popups.html b/dom/base/test/browser_multiple_popups.html
new file mode 100644
index 0000000000..6e0b85ea06
--- /dev/null
+++ b/dom/base/test/browser_multiple_popups.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <button onclick="openPopups();" id="openPopups">open popups</button>
+ <button onclick="openNestedPopups();" id="openNestedPopups">open tested popups</button>
+ <button onclick="openPopupAndClick();" id="openPopupAndClick">open popups and click</button>
+ <button onclick="closeAllWindows();" id="closeAllWindows">close all windows</button>
+ <button onclick="openPopupInFrame();" id="openPopupInFrame">open popup in frame</button>
+ <input type="text" id="input" />
+ <script>
+let windows = [];
+
+function openPopups() {
+ windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
+ windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
+}
+
+function openNestedPopups() {
+ var w = window.open('empty.html', '_blank', 'width=100,height=100');
+ windows.push(w);
+ windows.push(w.open('empty.html', '_blank', 'width=100,height=100'));
+}
+
+var recursion = false;
+function openPopupAndClick() {
+ windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
+ if (!recursion) {
+ recursion = true;
+ document.getElementById("openPopupAndClick").click();
+ }
+}
+
+function openPopupInFrame() {
+ let iframe = document.createElement("iframe");
+ iframe.style.display = "none";
+ document.body.appendChild(iframe);
+ windows.push(iframe.contentWindow.open('empty.html', '_blank', 'width=100,height=100'));
+ iframe.remove();
+}
+
+function closeAllWindows() {
+ windows.forEach(w => {
+ try {
+ w.close();
+ } catch(e) {}
+ });
+}
+
+if (location.search.includes("openPopups")) {
+ let id = setInterval(() => {
+ if (document.getElementById('start')) {
+ clearInterval(id);
+ openPopups();
+ }
+ }, 500);
+}
+
+document.getElementById("input").onmouseup = _ => {
+ openPopups();
+}
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/browser_multiple_popups.js b/dom/base/test/browser_multiple_popups.js
new file mode 100644
index 0000000000..c50c246644
--- /dev/null
+++ b/dom/base/test/browser_multiple_popups.js
@@ -0,0 +1,296 @@
+/**
+ * In this test, we check that the content can't open more than one popup at a
+ * time (depending on "dom.allow_mulitple_popups" preference value).
+ */
+
+const TEST_DOMAIN = "https://example.net";
+const TEST_PATH = "/browser/dom/base/test/";
+const CHROME_DOMAIN = "chrome://mochitests/content";
+
+requestLongerTimeout(2);
+
+function promisePopups(count) {
+ let windows = [];
+ return new Promise(resolve => {
+ if (count == 0) {
+ resolve([]);
+ return;
+ }
+
+ let windowObserver = function (aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+ windows.push(aSubject);
+ if (--count == 0) {
+ Services.ww.unregisterNotification(windowObserver);
+ SimpleTest.executeSoon(() => resolve(windows));
+ }
+ };
+ Services.ww.registerNotification(windowObserver);
+ });
+}
+
+async function withTestPage(popupCount, optionsOrCallback, callback) {
+ let options = optionsOrCallback;
+ if (!callback) {
+ callback = optionsOrCallback;
+ options = {};
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.block_multiple_popups", true],
+ ["dom.disable_open_during_load", true],
+ ],
+ });
+
+ let domain = options.chrome ? CHROME_DOMAIN : TEST_DOMAIN;
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ domain + TEST_PATH + "browser_multiple_popups.html" + (options.query || "")
+ );
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let obs = promisePopups(popupCount);
+
+ await callback(browser);
+
+ let windows = await obs;
+ ok(true, `We had ${popupCount} windows.`);
+ for (let win of windows) {
+ if (win.document.readyState !== "complete") {
+ await BrowserTestUtils.waitForEvent(win, "load");
+ }
+ await BrowserTestUtils.closeWindow(win);
+ }
+ BrowserTestUtils.removeTab(tab);
+}
+
+function promisePopupsBlocked(browser, expectedCount) {
+ return SpecialPowers.spawn(browser, [expectedCount], count => {
+ return new content.Promise(resolve => {
+ content.addEventListener("DOMPopupBlocked", function cb() {
+ if (--count == 0) {
+ content.removeEventListener("DOMPopupBlocked", cb);
+ ok(true, "The popup has been blocked");
+ resolve();
+ }
+ });
+ });
+ });
+}
+
+function startOpeningTwoPopups(browser) {
+ return SpecialPowers.spawn(browser.browsingContext, [], () => {
+ let p = content.document.createElement("p");
+ p.setAttribute("id", "start");
+ content.document.body.appendChild(p);
+ });
+}
+
+add_task(async _ => {
+ info("All opened if the pref is off");
+ await withTestPage(2, async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.block_multiple_popups", false],
+ ["dom.disable_open_during_load", true],
+ ],
+ });
+
+ await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
+ });
+});
+
+add_task(async _ => {
+ info("2 window.open()s in a click event allowed because whitelisted domain.");
+
+ const uri = Services.io.newURI(TEST_DOMAIN);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+
+ Services.perms.addFromPrincipal(
+ principal,
+ "popup",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await withTestPage(2, async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
+ });
+
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => {
+ Assert.equal(value, 0);
+ resolve();
+ }
+ );
+ });
+});
+
+add_task(async _ => {
+ info(
+ "2 window.open()s in a mouseup event allowed because whitelisted domain."
+ );
+
+ const uri = Services.io.newURI(TEST_DOMAIN);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+
+ Services.perms.addFromPrincipal(
+ principal,
+ "popup",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await withTestPage(2, async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter("#input", {}, browser);
+ });
+
+ await new Promise(aResolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => {
+ Assert.equal(value, 0);
+ aResolve();
+ }
+ );
+ });
+});
+
+add_task(async _ => {
+ info(
+ "2 window.open()s in a single click event: only the first one is allowed."
+ );
+
+ await withTestPage(1, async function (browser) {
+ let p = promisePopupsBlocked(browser, 1);
+ await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
+ await p;
+ });
+});
+
+add_task(async _ => {
+ info(
+ "2 window.open()s in a single mouseup event: only the first one is allowed."
+ );
+
+ await withTestPage(1, async function (browser) {
+ let p = promisePopupsBlocked(browser, 1);
+ await BrowserTestUtils.synthesizeMouseAtCenter("#input", {}, browser);
+ await p;
+ });
+});
+
+add_task(async _ => {
+ info("2 window.open()s by non-event code: no windows allowed.");
+
+ await withTestPage(0, { query: "?openPopups" }, async function (browser) {
+ let p = promisePopupsBlocked(browser, 2);
+ await startOpeningTwoPopups(browser);
+ await p;
+ });
+});
+
+add_task(async _ => {
+ info("2 window.open()s by non-event code allowed by permission");
+ const uri = Services.io.newURI(TEST_DOMAIN);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+
+ Services.perms.addFromPrincipal(
+ principal,
+ "popup",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await withTestPage(2, { query: "?openPopups" }, async function (browser) {
+ await startOpeningTwoPopups(browser);
+ });
+
+ await new Promise(aResolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => {
+ Assert.equal(value, 0);
+ aResolve();
+ }
+ );
+ });
+});
+
+add_task(async _ => {
+ info(
+ "1 window.open() executing another window.open(): only the first one is allowed."
+ );
+
+ await withTestPage(1, async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#openNestedPopups",
+ {},
+ browser
+ );
+ });
+});
+
+add_task(async _ => {
+ info("window.open() and .click() on the element opening the window.");
+
+ await withTestPage(1, async function (browser) {
+ let p = promisePopupsBlocked(browser, 1);
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#openPopupAndClick",
+ {},
+ browser
+ );
+
+ await p;
+ });
+});
+
+add_task(async _ => {
+ info("All opened from chrome.");
+ await withTestPage(2, { chrome: true }, async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
+ });
+});
+
+add_task(async function test_bug_1685056() {
+ info(
+ "window.open() from a blank iframe window during an event dispatched at the parent page: window should be allowed"
+ );
+
+ await withTestPage(1, async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#openPopupInFrame",
+ {},
+ browser
+ );
+ });
+});
+
+add_task(async function test_bug_1689853() {
+ info("window.open() from a js bookmark (LOAD_FLAGS_ALLOW_POPUPS)");
+ await withTestPage(1, async function (browser) {
+ const URI =
+ "javascript:void(window.open('empty.html', '_blank', 'width=100,height=100'));";
+ window.openTrustedLinkIn(URI, "current", {
+ allowPopups: true,
+ inBackground: false,
+ allowInheritPrincipal: true,
+ });
+ });
+});
diff --git a/dom/base/test/browser_outline_refocus.js b/dom/base/test/browser_outline_refocus.js
new file mode 100644
index 0000000000..066c25de63
--- /dev/null
+++ b/dom/base/test/browser_outline_refocus.js
@@ -0,0 +1,65 @@
+const URL = `data:text/html,<a target="_blank" href="http://example.com">Click me</a>`;
+
+async function test_browser_outline_refocus(
+ aMessage,
+ aShouldFocusBeVisible,
+ aOpenTabCallback
+) {
+ await BrowserTestUtils.withNewTab(URL, async function (browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+
+ await aOpenTabCallback(browser);
+
+ info("waiting for new tab");
+ let newTab = await newTabPromise;
+
+ is(gBrowser.selectedTab, newTab, "Should've switched to the new tab");
+
+ info("switching back");
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ info("checking focus");
+ let [wasFocused, wasFocusVisible] = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ let link = content.document.querySelector("a");
+ return [link.matches(":focus"), link.matches(":focus-visible")];
+ }
+ );
+
+ ok(wasFocused, "Link should be refocused");
+ is(wasFocusVisible, aShouldFocusBeVisible, aMessage);
+
+ info("closing tab");
+ await BrowserTestUtils.removeTab(newTab);
+ });
+}
+
+add_task(async function browser_outline_refocus_mouse() {
+ await test_browser_outline_refocus(
+ "Link shouldn't show outlines since it was originally focused by mouse",
+ false,
+ function (aBrowser) {
+ info("clicking on link");
+ return BrowserTestUtils.synthesizeMouseAtCenter("a", {}, aBrowser);
+ }
+ );
+});
+
+add_task(async function browser_outline_refocus_key() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["accessibility.tabfocus", 7]],
+ });
+
+ await test_browser_outline_refocus(
+ "Link should show outlines since it was originally focused by keyboard",
+ true,
+ function (aBrowser) {
+ info("Navigating via keyboard");
+ EventUtils.sendKey("tab");
+ EventUtils.sendKey("return");
+ }
+ );
+});
diff --git a/dom/base/test/browser_page_load_event_telemetry.js b/dom/base/test/browser_page_load_event_telemetry.js
new file mode 100644
index 0000000000..839e333d61
--- /dev/null
+++ b/dom/base/test/browser_page_load_event_telemetry.js
@@ -0,0 +1,46 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS;
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ waitForLoad: true,
+ });
+
+ let browser = tab.linkedBrowser;
+
+ // Reset event counts.
+ Services.telemetry.clearEvents();
+ TelemetryTestUtils.assertNumberOfEvents(0);
+
+ // Add checks for pageload ping and pageload event
+ let pingSubmitted = false;
+ GleanPings.pageload.testBeforeNextSubmit(reason => {
+ pingSubmitted = true;
+ Assert.equal(reason, "threshold");
+ let record = Glean.perf.pageLoad.testGetValue();
+ Assert.greaterOrEqual(
+ record.length,
+ 30,
+ "Should have at least 30 page load events"
+ );
+ });
+
+ // Perform page load 30 times to trigger the ping being sent
+ for (let i = 0; i < 30; i++) {
+ BrowserTestUtils.loadURIString(browser, "https://example.com");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ await BrowserTestUtils.waitForCondition(
+ () => pingSubmitted,
+ "Page load ping should have been submitted."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/base/test/browser_pagehide_on_tab_close.js b/dom/base/test/browser_pagehide_on_tab_close.js
new file mode 100644
index 0000000000..4db890aa57
--- /dev/null
+++ b/dom/base/test/browser_pagehide_on_tab_close.js
@@ -0,0 +1,21 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = BrowserTestUtils.addTab(gBrowser);
+ gBrowser.selectedTab = tab;
+
+ tab.linkedBrowser.addEventListener(
+ "load",
+ function () {
+ tab.linkedBrowser.addEventListener("pagehide", function () {
+ ok(true, "got page hide event");
+ finish();
+ });
+
+ executeSoon(() => {
+ gBrowser.removeTab(tab);
+ });
+ },
+ { capture: true, once: true }
+ );
+}
diff --git a/dom/base/test/browser_promiseDocumentFlushed.js b/dom/base/test/browser_promiseDocumentFlushed.js
new file mode 100644
index 0000000000..1df8f7af55
--- /dev/null
+++ b/dom/base/test/browser_promiseDocumentFlushed.js
@@ -0,0 +1,292 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Dirties style and layout on the current browser window.
+ *
+ * @param {Number} Optional factor by which to modify the DOM. Useful for
+ * when multiple calls to dirtyTheDOM may occur, and you need them
+ * to dirty the DOM differently from one another. If you only need
+ * to dirty the DOM once, this can be omitted.
+ */
+function dirtyStyleAndLayout(factor = 1) {
+ gNavToolbox.style.padding = factor + "px";
+}
+
+/**
+ * Dirties style of the current browser window, but NOT layout.
+ */
+function dirtyStyle() {
+ gNavToolbox.style.color = "red";
+}
+
+const gWindowUtils = window.windowUtils;
+
+/**
+ * Asserts that no style or layout flushes are required by the
+ * current window.
+ */
+function assertNoFlushesRequired() {
+ Assert.ok(
+ !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ "No style flushes are required."
+ );
+ Assert.ok(
+ !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
+ "No layout flushes are required."
+ );
+}
+
+/**
+ * Asserts that the DOM has been dirtied, and so style and layout flushes
+ * are required.
+ */
+function assertFlushesRequired() {
+ Assert.ok(
+ gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ "Style flush required."
+ );
+ Assert.ok(
+ gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
+ "Layout flush required."
+ );
+}
+
+/**
+ * Removes style changes from dirtyTheDOM() from the browser window,
+ * and resolves once the refresh driver ticks.
+ */
+async function cleanTheDOM() {
+ gNavToolbox.style.padding = "";
+ gNavToolbox.style.color = "";
+ await window.promiseDocumentFlushed(() => {});
+}
+
+add_setup(async function () {
+ registerCleanupFunction(cleanTheDOM);
+});
+
+/**
+ * Tests that if the DOM is dirty, that promiseDocumentFlushed will
+ * resolve once layout and style have been flushed.
+ */
+add_task(async function test_basic() {
+ info("Dirtying style / layout");
+ dirtyStyleAndLayout();
+ assertFlushesRequired();
+
+ info("Waiting");
+ await window.promiseDocumentFlushed(() => {});
+ assertNoFlushesRequired();
+
+ info("Dirtying style");
+ dirtyStyle();
+ assertFlushesRequired();
+
+ info("Waiting");
+ await window.promiseDocumentFlushed(() => {});
+ assertNoFlushesRequired();
+
+ // The DOM should be clean already, but we'll do this anyway to isolate
+ // failures from other tests.
+ info("Cleaning up");
+ await cleanTheDOM();
+});
+
+/**
+ * Test that values returned by the callback passed to promiseDocumentFlushed
+ * get passed down through the Promise resolution.
+ */
+add_task(async function test_can_get_results_from_callback() {
+ const NEW_PADDING = "2px";
+
+ gNavToolbox.style.padding = NEW_PADDING;
+
+ assertFlushesRequired();
+
+ let paddings = await window.promiseDocumentFlushed(() => {
+ let style = window.getComputedStyle(gNavToolbox);
+ return {
+ left: style.paddingLeft,
+ right: style.paddingRight,
+ top: style.paddingTop,
+ bottom: style.paddingBottom,
+ };
+ });
+
+ for (let prop in paddings) {
+ Assert.equal(paddings[prop], NEW_PADDING, "Got expected padding");
+ }
+
+ await cleanTheDOM();
+
+ gNavToolbox.style.padding = NEW_PADDING;
+
+ assertFlushesRequired();
+
+ let rect = await window.promiseDocumentFlushed(() => {
+ let observer = {
+ reflow() {
+ Assert.ok(false, "A reflow should not have occurred.");
+ },
+ reflowInterruptible() {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIReflowObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ let docShell = window.docShell;
+ docShell.addWeakReflowObserver(observer);
+
+ let toolboxRect = gNavToolbox.getBoundingClientRect();
+
+ docShell.removeWeakReflowObserver(observer);
+ return toolboxRect;
+ });
+
+ // The actual values of this rect aren't super important for
+ // the purposes of this test - we just want to know that a valid
+ // rect was returned, so checking for properties being greater than
+ // 0 is sufficient.
+ for (let property of ["width", "height"]) {
+ Assert.ok(
+ rect[property] > 0,
+ `Rect property ${property} > 0 (${rect[property]})`
+ );
+ }
+
+ await cleanTheDOM();
+});
+
+/**
+ * Test that if promiseDocumentFlushed is requested on a window
+ * that closes before it gets a chance to do a refresh driver
+ * tick, the promiseDocumentFlushed Promise is still resolved, and
+ * the callback is still called.
+ */
+add_task(async function test_resolved_in_window_close() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await win.promiseDocumentFlushed(() => {});
+
+ // Use advanceTimeAndRefresh to pause paints in the window:
+ let utils = win.windowUtils;
+ utils.advanceTimeAndRefresh(0);
+
+ win.gNavToolbox.style.padding = "5px";
+
+ const EXPECTED = 1234;
+ let promise = win.promiseDocumentFlushed(() => {
+ // Despite the window not painting before closing, this
+ // callback should be fired when the window gets torn
+ // down and should stil be able to return a result.
+ return EXPECTED;
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ Assert.equal(await promise, EXPECTED);
+});
+
+/**
+ * Test that re-entering promiseDocumentFlushed is not possible
+ * from within a promiseDocumentFlushed callback. Doing so will
+ * result in the outer Promise rejecting with InvalidStateError.
+ */
+add_task(async function test_reentrancy() {
+ dirtyStyleAndLayout();
+ assertFlushesRequired();
+
+ let promise = window.promiseDocumentFlushed(() => {
+ return window.promiseDocumentFlushed(() => {
+ Assert.ok(false, "Should never run this.");
+ });
+ });
+
+ await Assert.rejects(promise, ex => ex.name == "InvalidStateError");
+});
+
+/**
+ * Tests the expected execution order of a series of promiseDocumentFlushed
+ * calls, their callbacks, and the resolutions of their Promises.
+ *
+ * When multiple promiseDocumentFlushed callbacks are queued, the callbacks
+ * should always been run first before any of the Promises are resolved.
+ *
+ * The callbacks should run in the order that they were queued in via
+ * promiseDocumentFlushed. The Promise resolutions should similarly run
+ * in the order that promiseDocumentFlushed was called in.
+ */
+add_task(async function test_execution_order() {
+ let result = [];
+
+ dirtyStyleAndLayout(1);
+ let promise1 = window
+ .promiseDocumentFlushed(() => {
+ result.push(0);
+ })
+ .then(() => {
+ result.push(2);
+ });
+
+ let promise2 = window
+ .promiseDocumentFlushed(() => {
+ result.push(1);
+ })
+ .then(() => {
+ result.push(3);
+ });
+
+ await Promise.all([promise1, promise2]);
+
+ Assert.equal(result.length, 4, "Should have run all callbacks and Promises.");
+
+ let promise3 = window
+ .promiseDocumentFlushed(() => {
+ result.push(4);
+ })
+ .then(() => {
+ result.push(6);
+ });
+
+ let promise4 = window
+ .promiseDocumentFlushed(() => {
+ result.push(5);
+ })
+ .then(() => {
+ result.push(7);
+ });
+
+ await Promise.all([promise3, promise4]);
+
+ Assert.equal(result.length, 8, "Should have run all callbacks and Promises.");
+
+ for (let i = 0; i < result.length; ++i) {
+ Assert.equal(
+ result[i],
+ i,
+ "Callbacks and Promises should have run in the expected order."
+ );
+ }
+
+ await cleanTheDOM();
+});
+
+/**
+ * Tests that modifying the DOM within a promiseDocumentFlushed callback
+ * will result in the Promise being rejected.
+ */
+add_task(async function test_reject_on_modification() {
+ dirtyStyleAndLayout(1);
+ assertFlushesRequired();
+
+ let promise = window.promiseDocumentFlushed(() => {
+ dirtyStyleAndLayout(2);
+ });
+
+ await Assert.rejects(promise, /NoModificationAllowedError/);
+
+ await cleanTheDOM();
+});
diff --git a/dom/base/test/browser_refresh_content.js b/dom/base/test/browser_refresh_content.js
new file mode 100644
index 0000000000..0ff61db423
--- /dev/null
+++ b/dom/base/test/browser_refresh_content.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Ensures resources can still be fetched without validation
+ */
+
+const CONTENT_URL =
+ "http://www.example.com/browser/dom/base/test/file_browser_refresh_content.html";
+
+async function run_test_browser_refresh(forceRevalidate) {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: CONTENT_URL,
+ waitForLoad: true,
+ /* Ensures each run is started with a fresh state */
+ forceNewProcess: true,
+ });
+
+ let originalAttributes = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ let result = content.document.getElementById("result");
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ result.getAttribute("imageDataURL") &&
+ result.getAttribute("iframeContent") &&
+ result.getAttribute("expiredResourceCacheControl")
+ );
+ });
+ return {
+ imageDataURL: result.getAttribute("imageDataURL"),
+ iframeContent: result.getAttribute("iframeContent"),
+ expiredResourceCacheControl: result.getAttribute(
+ "expiredResourceCacheControl"
+ ),
+ };
+ }
+ );
+
+ let imageDataURL = originalAttributes.imageDataURL;
+ let expiredResourceCacheControl =
+ originalAttributes.expiredResourceCacheControl;
+
+ is(
+ originalAttributes.iframeContent,
+ "first load",
+ "Iframe is loaded with the initial content"
+ );
+
+ tab.linkedBrowser.reload();
+
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let attributes = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ let result = content.document.getElementById("result");
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ result.getAttribute("imageDataURL") &&
+ result.getAttribute("expiredResourceCacheControl") &&
+ result.getAttribute("nonCacheableResourceCompleted")
+ );
+ });
+
+ return {
+ iframeContent: result.getAttribute("iframeContent"),
+ imageDataURL: result.getAttribute("imageDataURL"),
+ expiredResourceCacheControl: result.getAttribute(
+ "expiredResourceCacheControl"
+ ),
+ nonCacheableResourceCompleted: result.getAttribute(
+ "nonCacheableResourceCompleted"
+ ),
+ };
+ }
+ );
+
+ is(
+ attributes.iframeContent,
+ "second load",
+ "Iframe should always be revalidated"
+ );
+
+ if (!forceRevalidate) {
+ ok(attributes.imageDataURL === imageDataURL, "Image should use cache");
+ } else {
+ ok(attributes.imageDataURL !== imageDataURL, "Image should be revalidated");
+ }
+
+ if (!forceRevalidate) {
+ ok(
+ attributes.expiredResourceCacheControl === expiredResourceCacheControl,
+ "max-age shouldn't be changed after reload because it didn't revalidate"
+ );
+ } else {
+ ok(
+ attributes.expiredResourceCacheControl !== expiredResourceCacheControl,
+ "max-age should be changed after reload because it got revalidated"
+ );
+ }
+
+ is(
+ attributes.nonCacheableResourceCompleted,
+ "true",
+ "Non cacheable resource should still be loaded"
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_browser_refresh() {
+ Services.prefs.setBoolPref(
+ "browser.soft_reload.only_force_validate_top_level_document",
+ true
+ );
+ await run_test_browser_refresh(false);
+ Services.prefs.setBoolPref(
+ "browser.soft_reload.only_force_validate_top_level_document",
+ false
+ );
+ await run_test_browser_refresh(true);
+});
diff --git a/dom/base/test/browser_state_notifications.js b/dom/base/test/browser_state_notifications.js
new file mode 100644
index 0000000000..e2b600465b
--- /dev/null
+++ b/dom/base/test/browser_state_notifications.js
@@ -0,0 +1,193 @@
+/* globals Components: true, Promise: true, gBrowser: true, Test: true,
+ info: true, is: true, window: true, waitForExplicitFinish: true,
+ finish: true, ok: true*/
+
+"use strict";
+
+const { addObserver, removeObserver } = Cc[
+ "@mozilla.org/observer-service;1"
+].getService(Ci.nsIObserverService);
+const { openWindow } = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(
+ Ci.nsIWindowWatcher
+);
+
+const Test = routine => () => {
+ waitForExplicitFinish();
+ routine().then(finish, error => {
+ ok(false, error);
+ finish();
+ });
+};
+
+// Returns promise for the observer notification subject for
+// the given topic. If `receive("foo")` is called `n` times
+// nth promise is resolved on an `nth` "foo" notification.
+const receive = (topic, p, syncCallback) => {
+ return new Promise((resolve, reject) => {
+ const { queue } = receive;
+ const timeout = () => {
+ queue.splice(queue.indexOf(resolve) - 1, 2);
+ reject(new Error("Timeout"));
+ };
+
+ const observer = {
+ observe: subject => {
+ // Browser loads bunch of other documents that we don't care
+ // about so we let allow filtering notifications via `p` function.
+ if (p && !p(subject)) {
+ return;
+ }
+ // If observer is a first one with a given `topic`
+ // in a queue resolve promise and take it off the queue
+ // otherwise keep waiting.
+ const index = queue.indexOf(topic);
+ if (queue.indexOf(resolve) === index + 1) {
+ removeObserver(observer, topic);
+ clearTimeout(id, reject);
+ queue.splice(index, 2);
+ // Some tests need to be executed synchronously when the event is fired.
+ if (syncCallback) {
+ syncCallback(subject);
+ }
+ resolve(subject);
+ }
+ },
+ };
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ const id = setTimeout(timeout, 90000);
+ addObserver(observer, topic, false);
+ queue.push(topic, resolve);
+ });
+};
+receive.queue = [];
+
+const openTab = uri =>
+ (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, uri));
+
+// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+const isData = document => document.URL.startsWith("data:");
+
+const uri1 = "data:text/html;charset=utf-8,<h1>1</h1>";
+// For whatever reason going back on load event doesn't work so timeout it is :(
+const uri2 =
+ "data:text/html;charset=utf-8,<h1>2</h1><script>setTimeout(SpecialPowers.wrap(window).back,100)</script>";
+const uri3 = "data:text/html;charset=utf-8,<h1>3</h1>";
+
+const uri4 = "chrome://browser/content/license.html";
+
+const test = Test(async function () {
+ let documentInteractive = receive(
+ "content-document-interactive",
+ isData,
+ d => {
+ // This test is executed synchronously when the event is received.
+ is(d.readyState, "interactive", "document is interactive");
+ is(d.URL, uri1, "document.URL matches tab url");
+ }
+ );
+ let documentLoaded = receive("content-document-loaded", isData);
+ let pageShown = receive("content-page-shown", isData);
+
+ info("open: uri#1");
+ const tab1 = openTab(uri1);
+ const browser1 = gBrowser.getBrowserForTab(tab1);
+
+ let interactiveDocument1 = await documentInteractive;
+
+ let loadedDocument1 = await documentLoaded;
+ is(loadedDocument1.readyState, "complete", "document is loaded");
+ is(interactiveDocument1, loadedDocument1, "interactive document is loaded");
+
+ let shownPage = await pageShown;
+ is(interactiveDocument1, shownPage, "loaded document is shown");
+
+ // Wait until history entry is created before loading new uri.
+ await receive("sessionstore-state-write-complete");
+
+ info("load uri#2");
+
+ documentInteractive = receive("content-document-interactive", isData, d => {
+ // This test is executed synchronously when the event is received.
+ is(d.readyState, "interactive", "document is interactive");
+ is(d.URL, uri2, "document.URL matches URL loaded");
+ });
+ documentLoaded = receive("content-document-loaded", isData);
+ pageShown = receive("content-page-shown", isData);
+ let pageHidden = receive("content-page-hidden", isData);
+
+ BrowserTestUtils.loadURIString(browser1, uri2);
+
+ let hiddenPage = await pageHidden;
+ is(interactiveDocument1, hiddenPage, "loaded document is hidden");
+
+ let interactiveDocument2 = await documentInteractive;
+
+ let loadedDocument2 = await documentLoaded;
+ is(loadedDocument2.readyState, "complete", "document is loaded");
+ is(interactiveDocument2, loadedDocument2, "interactive document is loaded");
+
+ shownPage = await pageShown;
+ is(interactiveDocument2, shownPage, "loaded document is shown");
+
+ info("go back to uri#1");
+
+ documentInteractive = receive("content-document-interactive", isData, d => {
+ // This test is executed synchronously when the event is received.
+ is(d.readyState, "interactive", "document is interactive");
+ is(d.URL, uri3, "document.URL matches URL loaded");
+ });
+ documentLoaded = receive("content-document-loaded", isData);
+ pageShown = receive("content-page-shown", isData);
+ pageHidden = receive("content-page-hidden", isData);
+
+ hiddenPage = await pageHidden;
+ is(interactiveDocument2, hiddenPage, "new document is hidden");
+
+ shownPage = await pageShown;
+ is(interactiveDocument1, shownPage, "previous document is shown");
+
+ info("load uri#3");
+
+ BrowserTestUtils.loadURIString(browser1, uri3);
+
+ pageShown = receive("content-page-shown", isData);
+
+ let interactiveDocument3 = await documentInteractive;
+
+ let loadedDocument3 = await documentLoaded;
+ is(loadedDocument3.readyState, "complete", "document is loaded");
+ is(interactiveDocument3, loadedDocument3, "interactive document is loaded");
+
+ shownPage = await pageShown;
+ is(interactiveDocument3, shownPage, "previous document is shown");
+
+ gBrowser.removeTab(tab1);
+
+ info("load chrome uri");
+
+ const tab2 = openTab(uri4);
+ documentInteractive = receive("chrome-document-interactive", null, d => {
+ // This test is executed synchronously when the event is received.
+ is(d.readyState, "interactive", "document is interactive");
+ is(d.URL, uri4, "document.URL matches URL loaded");
+ });
+ documentLoaded = receive("chrome-document-loaded");
+ pageShown = receive("chrome-page-shown");
+
+ const interactiveDocument4 = await documentInteractive;
+
+ let loadedDocument4 = await documentLoaded;
+ is(loadedDocument4.readyState, "complete", "document is loaded");
+ is(interactiveDocument4, loadedDocument4, "interactive document is loaded");
+
+ shownPage = await pageShown;
+ is(interactiveDocument4, shownPage, "loaded chrome document is shown");
+
+ pageHidden = receive("chrome-page-hidden");
+ gBrowser.removeTab(tab2);
+
+ hiddenPage = await pageHidden;
+ is(interactiveDocument4, hiddenPage, "chrome document hidden");
+});
diff --git a/dom/base/test/browser_timeout_throttling_with_audio_playback.js b/dom/base/test/browser_timeout_throttling_with_audio_playback.js
new file mode 100644
index 0000000000..89be64f656
--- /dev/null
+++ b/dom/base/test/browser_timeout_throttling_with_audio_playback.js
@@ -0,0 +1,73 @@
+// The tab closing code leaves an uncaught rejection. This test has been
+// whitelisted until the issue is fixed.
+if (!gMultiProcessBrowser) {
+ const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+ );
+ PromiseTestUtils.expectUncaughtRejection(/is no longer, usable/);
+}
+
+const kBaseURI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
+var testURLs = [
+ "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
+ "http://mochi.test:8888/browser/dom/base/test/file_audioLoopInIframe.html",
+ "http://mochi.test:8888/browser/dom/base/test/file_webaudio_startstop.html",
+];
+
+// We want to ensure that while audio is being played back, a background tab is
+// treated the same as a foreground tab as far as timeout throttling is concerned.
+// So we use a 100,000 second minimum timeout value for background tabs. This
+// means that in case the test fails, it will time out in practice, but just for
+// sanity the test condition ensures that the observed timeout delay falls in
+// this range.
+const kMinTimeoutBackground = 100 * 1000 * 1000;
+
+const kDelay = 10;
+
+async function runTest(url) {
+ let currentTab = gBrowser.selectedTab;
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kBaseURI);
+ let newBrowser = gBrowser.getBrowserForTab(newTab);
+
+ // Wait for the UI to indicate that audio is being played back.
+ let promise = BrowserTestUtils.waitForAttribute(
+ "soundplaying",
+ newTab,
+ "true"
+ );
+ BrowserTestUtils.loadURIString(newBrowser, url);
+ await promise;
+
+ // Put the tab in the background.
+ await BrowserTestUtils.switchTab(gBrowser, currentTab);
+
+ let timeout = await SpecialPowers.spawn(
+ newBrowser,
+ [kDelay],
+ function (delay) {
+ return new Promise(resolve => {
+ let before = new Date();
+ content.window.setTimeout(function () {
+ let after = new Date();
+ resolve(after - before);
+ }, delay);
+ });
+ }
+ );
+ ok(timeout <= kMinTimeoutBackground, `Got the correct timeout (${timeout})`);
+
+ // All done.
+ BrowserTestUtils.removeTab(newTab);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.min_background_timeout_value", kMinTimeoutBackground]],
+ });
+});
+
+add_task(async function test() {
+ for (var url of testURLs) {
+ await runTest(url);
+ }
+});
diff --git a/dom/base/test/browser_use_counters.js b/dom/base/test/browser_use_counters.js
new file mode 100644
index 0000000000..b18d6aa06c
--- /dev/null
+++ b/dom/base/test/browser_use_counters.js
@@ -0,0 +1,348 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+requestLongerTimeout(2);
+
+const gHttpTestRoot = "https://example.com/browser/dom/base/test/";
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+var gOldContentCanRecord = false;
+var gOldParentCanRecord = false;
+add_task(async function test_initialize() {
+ let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ gOldParentCanRecord = Telemetry.canRecordExtended;
+ Telemetry.canRecordExtended = true;
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Because canRecordExtended is a per-process variable, we need to make sure
+ // that all of the pages load in the same content process. Limit the number
+ // of content processes to at most 1 (or 0 if e10s is off entirely).
+ ["dom.ipc.processCount", 1],
+ ["layout.css.use-counters.enabled", true],
+ ["layout.css.use-counters-unimplemented.enabled", true],
+ ],
+ });
+
+ gOldContentCanRecord = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ function () {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ let old = telemetry.canRecordExtended;
+ telemetry.canRecordExtended = true;
+ return old;
+ }
+ );
+ info("canRecord for content: " + gOldContentCanRecord);
+});
+
+add_task(async function () {
+ const TESTS = [
+ // Check that use counters are incremented by SVGs loaded directly in iframes.
+ {
+ type: "iframe",
+ filename: "file_use_counter_svg_getElementById.svg",
+ counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }],
+ },
+ {
+ type: "iframe",
+ filename: "file_use_counter_svg_currentScale.svg",
+ counters: [
+ { name: "SVGSVGELEMENT_CURRENTSCALE_getter" },
+ { name: "SVGSVGELEMENT_CURRENTSCALE_setter" },
+ ],
+ },
+
+ {
+ type: "iframe",
+ filename: "file_use_counter_style.html",
+ counters: [
+ // Check for longhands.
+ { name: "CSS_PROPERTY_BackgroundImage" },
+ // Check for shorthands.
+ { name: "CSS_PROPERTY_Padding" },
+ // Check for aliases.
+ { name: "CSS_PROPERTY_MozTransform" },
+ // Check for counted unknown properties.
+ { name: "CSS_PROPERTY_WebkitPaddingStart" },
+ ],
+ },
+
+ // Check that even loads from the imglib cache update use counters. The
+ // images should still be there, because we just loaded them in the last
+ // set of tests. But we won't get updated counts for the document
+ // counters, because we won't be re-parsing the SVG documents.
+ {
+ type: "iframe",
+ filename: "file_use_counter_svg_getElementById.svg",
+ counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }],
+ check_documents: false,
+ },
+ {
+ type: "iframe",
+ filename: "file_use_counter_svg_currentScale.svg",
+ counters: [
+ { name: "SVGSVGELEMENT_CURRENTSCALE_getter" },
+ { name: "SVGSVGELEMENT_CURRENTSCALE_setter" },
+ ],
+ check_documents: false,
+ },
+
+ // Check that use counters are incremented by SVGs loaded as images.
+ // Note that SVG images are not permitted to execute script, so we can only
+ // check for properties here.
+ {
+ type: "img",
+ filename: "file_use_counter_svg_getElementById.svg",
+ counters: [{ name: "CSS_PROPERTY_Fill" }],
+ },
+ {
+ type: "img",
+ filename: "file_use_counter_svg_currentScale.svg",
+ counters: [{ name: "CSS_PROPERTY_Fill" }],
+ },
+
+ // Check that use counters are incremented by directly loading SVGs
+ // that reference patterns defined in another SVG file.
+ {
+ type: "direct",
+ filename: "file_use_counter_svg_fill_pattern.svg",
+ counters: [{ name: "CSS_PROPERTY_FillOpacity", xfail: true }],
+ },
+
+ // Check that use counters are incremented by directly loading SVGs
+ // that reference patterns defined in the same file or in data: URLs.
+ {
+ type: "direct",
+ filename: "file_use_counter_svg_fill_pattern_internal.svg",
+ counters: [{ name: "CSS_PROPERTY_FillOpacity" }],
+ },
+
+ // Check that use counters are incremented in a display:none iframe.
+ {
+ type: "undisplayed-iframe",
+ filename: "file_use_counter_svg_currentScale.svg",
+ counters: [{ name: "SVGSVGELEMENT_CURRENTSCALE_getter" }],
+ },
+
+ // Check that a document that comes out of the bfcache reports any new use
+ // counters recorded on it.
+ {
+ type: "direct",
+ filename: "file_use_counter_bfcache.html",
+ waitForExplicitFinish: true,
+ counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }],
+ },
+
+ // // data: URLs don't correctly propagate to their referring document yet.
+ // {
+ // type: "direct",
+ // filename: "file_use_counter_svg_fill_pattern_data.svg",
+ // counters: [
+ // { name: "PROPERTY_FILL_OPACITY" },
+ // ],
+ // },
+ ];
+
+ for (let test of TESTS) {
+ let file = test.filename;
+ info(`checking ${file} (${test.type})`);
+
+ let newTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ gBrowser.selectedTab = newTab;
+ newTab.linkedBrowser.stop();
+
+ // Hold on to the current values of the telemetry histograms we're
+ // interested in.
+ let before = await grabHistogramsFromContent(
+ test.counters.map(c => c.name)
+ );
+
+ // Load the test file in the new tab, either directly or via
+ // file_use_counter_outer{,_display_none}.html, depending on the test type.
+ let url, targetElement;
+ switch (test.type) {
+ case "iframe":
+ url = gHttpTestRoot + "file_use_counter_outer.html";
+ targetElement = "content";
+ break;
+ case "undisplayed-iframe":
+ url = gHttpTestRoot + "file_use_counter_outer_display_none.html";
+ targetElement = "content";
+ break;
+ case "img":
+ url = gHttpTestRoot + "file_use_counter_outer.html";
+ targetElement = "display";
+ break;
+ case "direct":
+ url = gHttpTestRoot + file;
+ targetElement = null;
+ break;
+ default:
+ throw `unexpected type ${test.type}`;
+ }
+
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ if (test.waitForExplicitFinish) {
+ if (test.type != "direct") {
+ throw new Error(
+ `cannot use waitForExplicitFinish with test type ${test.type}`
+ );
+ }
+
+ // Wait until the tab changes its hash to indicate it has finished.
+ await BrowserTestUtils.waitForLocationChange(gBrowser, url + "#finished");
+ }
+
+ if (targetElement) {
+ // Inject our desired file into the target element of the newly-loaded page.
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ file, targetElement }],
+ function (opts) {
+ let target = content.document.getElementById(opts.targetElement);
+ target.src = opts.file;
+
+ return new Promise(resolve => {
+ let listener = event => {
+ event.target.removeEventListener("load", listener, true);
+ resolve();
+ };
+ target.addEventListener("load", listener, true);
+ });
+ }
+ );
+ }
+
+ // Tear down the page.
+ let tabClosed = BrowserTestUtils.waitForTabClosing(newTab);
+ gBrowser.removeTab(newTab);
+ await tabClosed;
+
+ // Grab histograms again.
+ let after = await grabHistogramsFromContent(
+ test.counters.map(c => c.name),
+ before.sentinel
+ );
+
+ // Compare before and after.
+ for (let counter of test.counters) {
+ let name = counter.name;
+ let value = counter.value ?? 1;
+ if (!counter.xfail) {
+ is(
+ after.page[name],
+ before.page[name] + value,
+ `page counts for ${name} after are correct`
+ );
+ is(
+ after.document[name],
+ before.document[name] + value,
+ `document counts for ${name} after are correct`
+ );
+ }
+ }
+
+ if (test.check_documents ?? true) {
+ ok(
+ after.toplevel_docs >= before.toplevel_docs + 1,
+ "top level destroyed document counts are correct"
+ );
+ // 2 documents for "img" tests: one for the outer html page containing the
+ // <img> element, and one for the SVG image itself.
+ ok(
+ after.docs >= before.docs + (test.type == "img" ? 2 : 1),
+ "destroyed document counts are correct"
+ );
+ }
+ }
+});
+
+add_task(async function () {
+ let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ Telemetry.canRecordExtended = gOldParentCanRecord;
+
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ oldCanRecord: gOldContentCanRecord }],
+ async function (arg) {
+ await new Promise(resolve => {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ telemetry.canRecordExtended = arg.oldCanRecord;
+ resolve();
+ });
+ }
+ );
+});
+
+async function grabHistogramsFromContent(names, prev_sentinel = null) {
+ // We don't have any way to get a notification when telemetry from the
+ // document that was just closed has been reported. So instead, we
+ // repeatedly poll for telemetry until we see that a specific use counter
+ // histogram (CSS_PROPERTY_MarkerMid, the "sentinel") that likely is not
+ // used by any other document that's open has been incremented.
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ let gatheredHistograms;
+ return BrowserTestUtils.waitForCondition(
+ function () {
+ // Document use counters are reported in the content process (when e10s
+ // is enabled), and page use counters are reported in the parent process.
+ let snapshots = telemetry.getSnapshotForHistograms("main", false);
+ let checkGet = probe => {
+ // When e10s is disabled, all histograms are reported in the parent
+ // process. Otherwise, all page use counters are reported in the parent
+ // and document use counters are reported in the content process.
+ let process =
+ !Services.appinfo.browserTabsRemoteAutostart ||
+ probe.endsWith("_PAGE") ||
+ probe == "TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED"
+ ? "parent"
+ : "content";
+ return snapshots[process][probe] ? snapshots[process][probe].sum : 0;
+ };
+ let page = Object.fromEntries(
+ names.map(name => [name, checkGet(`USE_COUNTER2_${name}_PAGE`)])
+ );
+ let document = Object.fromEntries(
+ names.map(name => [name, checkGet(`USE_COUNTER2_${name}_DOCUMENT`)])
+ );
+ gatheredHistograms = {
+ page,
+ document,
+ docs: checkGet("CONTENT_DOCUMENTS_DESTROYED"),
+ toplevel_docs: checkGet("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED"),
+ sentinel: {
+ doc: checkGet("USE_COUNTER2_CSS_PROPERTY_MarkerMid_DOCUMENT"),
+ page: checkGet("USE_COUNTER2_CSS_PROPERTY_MarkerMid_PAGE"),
+ },
+ };
+ let sentinelChanged =
+ !prev_sentinel ||
+ (prev_sentinel.doc != gatheredHistograms.sentinel.doc &&
+ prev_sentinel.page != gatheredHistograms.sentinel.page);
+ return sentinelChanged;
+ },
+ "grabHistogramsFromContent",
+ 100,
+ Infinity
+ ).then(
+ () => gatheredHistograms,
+ function (msg) {
+ throw msg;
+ }
+ );
+}
diff --git a/dom/base/test/browser_user_input_handling_delay.js b/dom/base/test/browser_user_input_handling_delay.js
new file mode 100644
index 0000000000..ccd3369c34
--- /dev/null
+++ b/dom/base/test/browser_user_input_handling_delay.js
@@ -0,0 +1,82 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function test_user_input_handling_delay_helper(prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: prefs,
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `data:text/html,<body></body>`
+ );
+
+ let canHandleInput = false;
+ let mouseDownPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "mousedown"
+ ).then(function () {
+ Assert.ok(
+ canHandleInput,
+ "This promise should be resolved after the 5 seconds mark has passed"
+ );
+ });
+
+ for (let i = 0; i < 10; ++i) {
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 10,
+ 10,
+ { type: "mousedown" },
+ tab.linkedBrowser
+ );
+ }
+ // Wait for roughly 5 seconds to give chances for the
+ // above mousedown event to be handled.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ for (let i = 0; i < 330; ++i) {
+ await new Promise(r => {
+ content.requestAnimationFrame(r);
+ });
+ }
+ });
+
+ // If any user input events were handled in the above 5 seconds
+ // the mouseDownPromise would be resolved with canHandleInput = false,
+ // so that the test would fail.
+ canHandleInput = true;
+
+ // Ensure the events can be handled eventually
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 10,
+ 10,
+ { type: "mousedown" },
+ tab.linkedBrowser
+ );
+
+ await mouseDownPromise;
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_MinRAF() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 100],
+ ["dom.input_events.security.minTimeElapsedInMS", 0],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_helper(prefs);
+});
+
+add_task(async function test_MinElapsedTime() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 0],
+ ["dom.input_events.security.minTimeElapsedInMS", 5000],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_helper(prefs);
+});
diff --git a/dom/base/test/browser_user_input_handling_delay_aboutblank.js b/dom/base/test/browser_user_input_handling_delay_aboutblank.js
new file mode 100644
index 0000000000..c49a9bf7ed
--- /dev/null
+++ b/dom/base/test/browser_user_input_handling_delay_aboutblank.js
@@ -0,0 +1,58 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function test_user_input_handling_delay_aboutblank_helper(prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: prefs,
+ });
+
+ let newTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:blank");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ // Open about:blank
+ content.window.open();
+ });
+
+ const tab = await newTabOpened;
+
+ let mouseDownPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "mousedown"
+ ).then(function () {
+ Assert.ok(true, "about:blank can handle user input events anytime");
+ });
+
+ // Now gBrowser.selectedBrowser is the newly opened about:blank
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 10,
+ 10,
+ { type: "mousedown" },
+ tab.linkedBrowser
+ );
+
+ await mouseDownPromise;
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_MinRAF_aboutblank() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 100],
+ ["dom.input_events.security.minTimeElapsedInMS", 0],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_aboutblank_helper(prefs);
+});
+
+add_task(async function test_MinElapsedTime_aboutblank() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 0],
+ ["dom.input_events.security.minTimeElapsedInMS", 5000],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_aboutblank_helper(prefs);
+});
diff --git a/dom/base/test/browser_user_input_handling_delay_bfcache.js b/dom/base/test/browser_user_input_handling_delay_bfcache.js
new file mode 100644
index 0000000000..826f4340c5
--- /dev/null
+++ b/dom/base/test/browser_user_input_handling_delay_bfcache.js
@@ -0,0 +1,107 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function test_user_input_handling_delay_BFCache_helper(prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: prefs,
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `data:text/html,<body></body>`,
+ true
+ );
+
+ let switchAwayPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "about:blank"
+ );
+ // Navigate away to make the page enters BFCache
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.location = "about:blank";
+ });
+ await switchAwayPromise;
+
+ // Navigate back to restore the page from BFCache
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow",
+ true
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.history.back();
+ });
+
+ await pageShownPromise;
+
+ let canHandleInput = false;
+ let mouseDownPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "mousedown"
+ ).then(function () {
+ Assert.ok(
+ canHandleInput,
+ "This promise should be resolved after the 5 seconds mark has passed"
+ );
+ });
+ // Ensure the events are discarded initially
+ for (let i = 0; i < 10; ++i) {
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 10,
+ 10,
+ { type: "mousedown" },
+ tab.linkedBrowser
+ );
+ }
+
+ // Wait for roughly 5 seconds to give chances for the
+ // above mousedown event to be handled.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ for (let i = 0; i < 330; ++i) {
+ await new Promise(r => {
+ content.requestAnimationFrame(r);
+ });
+ }
+ });
+
+ // If any user input events were handled in the above 5 seconds
+ // the mouseDownPromise would be resolved with canHandleInput = false,
+ // so that the test would fail.
+ canHandleInput = true;
+
+ // Ensure the events can be handled eventually
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 10,
+ 10,
+ { type: "mousedown" },
+ tab.linkedBrowser
+ );
+
+ await mouseDownPromise;
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_MinRAF_BFCache() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 100],
+ ["dom.input_events.security.minTimeElapsedInMS", 0],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_BFCache_helper(prefs);
+});
+
+add_task(async function test_MinElapsedTime_BFCache() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 0],
+ ["dom.input_events.security.minTimeElapsedInMS", 5000],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_BFCache_helper(prefs);
+});
diff --git a/dom/base/test/browser_user_input_handling_delay_invisible_iframe.js b/dom/base/test/browser_user_input_handling_delay_invisible_iframe.js
new file mode 100644
index 0000000000..8227a09a08
--- /dev/null
+++ b/dom/base/test/browser_user_input_handling_delay_invisible_iframe.js
@@ -0,0 +1,78 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function test_user_input_handling_delay_helper(prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: prefs,
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `
+ data:text/html,
+ <body>
+ <iframe width="1" height="1" style="position:absolute; top:-9999px; left: -9999px; border-style: none" src="https://example.org/dom/base/test/empty.html"></iframe>
+ <input />
+ </body>`
+ );
+
+ await BrowserTestUtils.reloadTab(tab);
+
+ let iframeFocused = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ let iframe = content.document.querySelector("iframe");
+ await ContentTaskUtils.waitForCondition(function () {
+ return content.document.activeElement == iframe;
+ });
+ }
+ );
+
+ // Now the focus moves to the cross origin iframe
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.document.querySelector("iframe").focus();
+ });
+
+ await iframeFocused;
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 1000));
+
+ const inputGetFocused = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ await ContentTaskUtils.waitForEvent(
+ content.document.querySelector("input"),
+ "focus"
+ );
+ }
+ ).then(function () {
+ Assert.ok(
+ true,
+ "Invisible OOP iframe shouldn't prevent user input event handling"
+ );
+ });
+
+ let iframeBC = tab.linkedBrowser.browsingContext.children[0];
+ // Next tab key should move the focus from the iframe to link
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, iframeBC);
+
+ await inputGetFocused;
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_InvisibleIframe() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 3],
+ ["dom.input_events.security.minTimeElapsedInMS", 0],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_helper(prefs);
+});
diff --git a/dom/base/test/browser_user_input_handling_delay_reload_ticks.js b/dom/base/test/browser_user_input_handling_delay_reload_ticks.js
new file mode 100644
index 0000000000..8afc7f16bb
--- /dev/null
+++ b/dom/base/test/browser_user_input_handling_delay_reload_ticks.js
@@ -0,0 +1,54 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function test_user_input_handling_delay_helper(prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: prefs,
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `data:text/html,<body></body>`
+ );
+
+ await BrowserTestUtils.reloadTab(tab);
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 5000));
+
+ const userInputHappend = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ await ContentTaskUtils.waitForEvent(content, "keydown");
+ }
+ ).then(function () {
+ Assert.ok(
+ true,
+ "User input event should be able to work after 5 seconds of an reload"
+ );
+ });
+
+ // In the buggy build, the following tab key doesn't work
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, tab.linkedBrowser);
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, tab.linkedBrowser);
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, tab.linkedBrowser);
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, tab.linkedBrowser);
+
+ await userInputHappend;
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_MinTick() {
+ const prefs = [
+ ["dom.input_events.security.minNumTicks", 10],
+ ["dom.input_events.security.minTimeElapsedInMS", 0],
+ ["dom.input_events.security.isUserInputHandlingDelayTest", true],
+ ];
+
+ await test_user_input_handling_delay_helper(prefs);
+});
diff --git a/dom/base/test/browser_xml_toggle.js b/dom/base/test/browser_xml_toggle.js
new file mode 100644
index 0000000000..477db038b1
--- /dev/null
+++ b/dom/base/test/browser_xml_toggle.js
@@ -0,0 +1,24 @@
+const URL = `data:text/xml,
+<?xml version="1.0" encoding="UTF-8"?>
+<note>
+ <to>Tove</to>
+ <from>Jani</from>
+ <heading>Reminder</heading>
+ <body>Don't forget me this weekend!</body>
+</note>
+`;
+
+add_task(async function xml_pretty_print_toggle() {
+ await BrowserTestUtils.withNewTab(URL, async function (browser) {
+ await SpecialPowers.spawn(browser, [], () => {
+ let summary =
+ content.document.documentElement.openOrClosedShadowRoot.querySelector(
+ "summary"
+ );
+ let details = summary.parentNode;
+ ok(details.open, "Should be open");
+ summary.click();
+ ok(!details.open, "Should be closed");
+ });
+ });
+});
diff --git a/dom/base/test/bug1576154.sjs b/dom/base/test/bug1576154.sjs
new file mode 100644
index 0000000000..d18151a8b4
--- /dev/null
+++ b/dom/base/test/bug1576154.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 500, "Internal Server Error");
+ response.setHeader("Content-Type", "image/svg+xml", false);
+
+ let body =
+ "<svg xmlns='http://www.w3.org/2000/svg' width='70' height='0'></svg>";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/dom/base/test/bug1739957.sjs b/dom/base/test/bug1739957.sjs
new file mode 100644
index 0000000000..38f5e72040
--- /dev/null
+++ b/dom/base/test/bug1739957.sjs
@@ -0,0 +1,10 @@
+function handleRequest(request, response) {
+ if (request.queryString == "loaded") {
+ response.write(getState("loaded") || "false");
+ return;
+ }
+
+ setState("loaded", "true");
+ response.setHeader("Content-Type", "image/svg+xml", false);
+ response.write(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`);
+}
diff --git a/dom/base/test/bug282547.sjs b/dom/base/test/bug282547.sjs
new file mode 100644
index 0000000000..a57a176038
--- /dev/null
+++ b/dom/base/test/bug282547.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 401, "Unauthorized");
+
+ response.setHeader("WWW-Authenticate", 'basic realm="restricted"', false);
+
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+}
diff --git a/dom/base/test/bug298064-subframe.html b/dom/base/test/bug298064-subframe.html
new file mode 100644
index 0000000000..af497f5905
--- /dev/null
+++ b/dom/base/test/bug298064-subframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script>
+ function test_func() {
+ var bar = new Option();
+ parent.is(bar.ownerDocument, document,
+ "Unexpected document for our new option");
+ bar = new Image();
+ parent.is(bar.ownerDocument, document,
+ "Unexpected document for our new image");
+ bar = new parent.Option();
+ parent.is(bar.ownerDocument, parent.document,
+ "Unexpected document for parent new option");
+ bar = new parent.Image();
+ parent.is(bar.ownerDocument, parent.document,
+ "Unexpected document for parent new image");
+ parent.isnot(parent.document, document, "Documents should be different");
+ }
+ </script>
+ </head>
+<html>
+
+
diff --git a/dom/base/test/bug313646.txt b/dom/base/test/bug313646.txt
new file mode 100644
index 0000000000..150f5ea6d1
--- /dev/null
+++ b/dom/base/test/bug313646.txt
@@ -0,0 +1 @@
+Nothing to see here. Just need to request this file via XHR.
diff --git a/dom/base/test/bug382113_object.html b/dom/base/test/bug382113_object.html
new file mode 100644
index 0000000000..935f00afd1
--- /dev/null
+++ b/dom/base/test/bug382113_object.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<title></title>
+<body onload="parent.childGotOnload = true;">
+ <p>A Document in an &lt;object&gt;</p>
+</body>
+
diff --git a/dom/base/test/bug403852_fileOpener.js b/dom/base/test/bug403852_fileOpener.js
new file mode 100644
index 0000000000..b35fa8fbcf
--- /dev/null
+++ b/dom/base/test/bug403852_fileOpener.js
@@ -0,0 +1,24 @@
+/* eslint-env mozilla/chrome-script */
+
+Cu.importGlobalProperties(["File"]);
+
+var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+testFile.append("prefs.js");
+
+addMessageListener("file.open", function () {
+ File.createFromNsIFile(testFile).then(function (file) {
+ File.createFromNsIFile(testFile, { lastModified: 123 }).then(function (
+ fileWithDate
+ ) {
+ sendAsyncMessage("file.opened", {
+ file,
+ mtime: testFile.lastModifiedTime,
+ fileWithDate,
+ fileDate: 123,
+ });
+ });
+ });
+});
diff --git a/dom/base/test/bug419132.html b/dom/base/test/bug419132.html
new file mode 100644
index 0000000000..ab2934c2d4
--- /dev/null
+++ b/dom/base/test/bug419132.html
@@ -0,0 +1,22 @@
+<html><head>
+</head><body>
+<span>
+<span id="a" tabindex="1">
+<span>
+
+<select>
+<script>
+document.getElementById('a').focus();
+</script>
+</select>
+
+<style>
+#a:focus { float:right;}
+body *:first-child {-moz-binding:url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%20inheritstyle%3D%22false%22%3E%0A%3Ccontent%3E%0A%3Cchildren/%3E%0A%3Cinput%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%20style%3D%22display%3A%20table%3B%20overflow%3A%20hidden%3B%22%20/%3E%0A%3C/content%3E%0A%3C/binding%3E%0A%3C/bindings%3E);
+</style>
+
+</span>
+</span>
+</span>
+</body>
+</html>
diff --git a/dom/base/test/bug426308-redirect.sjs b/dom/base/test/bug426308-redirect.sjs
new file mode 100644
index 0000000000..331f31d96a
--- /dev/null
+++ b/dom/base/test/bug426308-redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 302, "Found");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/dom/base/test/bug435425.sjs b/dom/base/test/bug435425.sjs
new file mode 100644
index 0000000000..0e3ef38419
--- /dev/null
+++ b/dom/base/test/bug435425.sjs
@@ -0,0 +1,25 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/dom/base/test/bug435425_redirect.sjs b/dom/base/test/bug435425_redirect.sjs
new file mode 100644
index 0000000000..14fec62331
--- /dev/null
+++ b/dom/base/test/bug435425_redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 302, "Moved");
+ response.setHeader("Location", "http://nosuchdomain.localhost", false);
+}
diff --git a/dom/base/test/bug444322.js b/dom/base/test/bug444322.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/bug444322.js
diff --git a/dom/base/test/bug444322.txt b/dom/base/test/bug444322.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/bug444322.txt
diff --git a/dom/base/test/bug444546.sjs b/dom/base/test/bug444546.sjs
new file mode 100644
index 0000000000..5861eeb25c
--- /dev/null
+++ b/dom/base/test/bug444546.sjs
@@ -0,0 +1,21 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+}
diff --git a/dom/base/test/bug455629-helper.svg b/dom/base/test/bug455629-helper.svg
new file mode 100644
index 0000000000..38098585ed
--- /dev/null
+++ b/dom/base/test/bug455629-helper.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <g transform="scale(0.5)">
+ <foreignObject id="f" width="100" height="100"/>
+ </g>
+</svg>
diff --git a/dom/base/test/bug457746.sjs b/dom/base/test/bug457746.sjs
new file mode 100644
index 0000000000..caa4433cde
--- /dev/null
+++ b/dom/base/test/bug457746.sjs
@@ -0,0 +1,10 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain; charset=ISO-8859-1", false);
+ const body = [0xc1];
+ var bos = Components.classes[
+ "@mozilla.org/binaryoutputstream;1"
+ ].createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ bos.writeByteArray(body);
+}
diff --git a/dom/base/test/bug461735-post-redirect.js b/dom/base/test/bug461735-post-redirect.js
new file mode 100644
index 0000000000..950948e4c9
--- /dev/null
+++ b/dom/base/test/bug461735-post-redirect.js
@@ -0,0 +1,3 @@
+var a = 0;
+var b = 0;
+c();
diff --git a/dom/base/test/bug461735-redirect1.sjs b/dom/base/test/bug461735-redirect1.sjs
new file mode 100644
index 0000000000..cf00e8b8c4
--- /dev/null
+++ b/dom/base/test/bug461735-redirect1.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://example.com/tests/dom/base/test/bug461735-post-redirect.js",
+ false
+ );
+}
diff --git a/dom/base/test/bug461735-redirect2.sjs b/dom/base/test/bug461735-redirect2.sjs
new file mode 100644
index 0000000000..416882003c
--- /dev/null
+++ b/dom/base/test/bug461735-redirect2.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://mochi.test:8888/tests/dom/base/test/bug461735-post-redirect.js",
+ false
+ );
+}
diff --git a/dom/base/test/bug466080.sjs b/dom/base/test/bug466080.sjs
new file mode 100644
index 0000000000..df3687000b
--- /dev/null
+++ b/dom/base/test/bug466080.sjs
@@ -0,0 +1,14 @@
+function handleRequest(request, response) {
+ var body = "loaded";
+ var origin = "localhost";
+ try {
+ var origin = request.getHeader("Origin");
+ } catch (e) {}
+
+ response.setHeader("Access-Control-Allow-Origin", origin, false);
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ response.setHeader("Access-Control-Allow-Methods", "XMETHOD", false);
+ response.setHeader("Connection", "Keep-alive", false);
+
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/dom/base/test/bug466409-empty.css b/dom/base/test/bug466409-empty.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/bug466409-empty.css
diff --git a/dom/base/test/bug466409-page.html b/dom/base/test/bug466409-page.html
new file mode 100644
index 0000000000..69ce7c2272
--- /dev/null
+++ b/dom/base/test/bug466409-page.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="bug466409-empty.css">
+ <title>Bug</title>
+ <SCRIPT LANGUAGE="JavaScript">
+ document.write("Hello, world!");
+ </SCRIPT>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/base/test/bug475156.sjs b/dom/base/test/bug475156.sjs
new file mode 100644
index 0000000000..9c54b69f65
--- /dev/null
+++ b/dom/base/test/bug475156.sjs
@@ -0,0 +1,23 @@
+function handleRequest(request, response) {
+ if (request.queryString == "") {
+ var etag = request.hasHeader("If-Match")
+ ? request.getHeader("If-Match")
+ : null;
+ if (!etag || etag == getState("etag")) {
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("ETag", getState("etag"));
+ response.setHeader("Cache-control", "max-age=36000");
+ response.write(getState("etag"));
+ } else if (etag) {
+ response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
+ }
+ } else {
+ var etag = request.queryString.match(/^etag=(.*)$/);
+ if (etag) {
+ setState("etag", etag[1]);
+ }
+
+ response.setStatusLine(request.httpVersion, 204, "No content");
+ }
+}
diff --git a/dom/base/test/bug482935.sjs b/dom/base/test/bug482935.sjs
new file mode 100644
index 0000000000..b480c40e41
--- /dev/null
+++ b/dom/base/test/bug482935.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+ var body = "initial";
+
+ try {
+ body = request.getHeader("X-Request");
+ } catch (e) {
+ body = "request.getHeader() failed! Exception: " + e;
+ }
+
+ response.setHeader("Cache-Control", "max-age=3600");
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/dom/base/test/bug540854.sjs b/dom/base/test/bug540854.sjs
new file mode 100644
index 0000000000..5861eeb25c
--- /dev/null
+++ b/dom/base/test/bug540854.sjs
@@ -0,0 +1,21 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+}
diff --git a/dom/base/test/bug578096LoadChromeScript.js b/dom/base/test/bug578096LoadChromeScript.js
new file mode 100644
index 0000000000..443fc34253
--- /dev/null
+++ b/dom/base/test/bug578096LoadChromeScript.js
@@ -0,0 +1,20 @@
+/* eslint-env mozilla/chrome-script */
+
+var file;
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("file.create", function (message) {
+ file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ file.append("foo.txt");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ File.createFromNsIFile(file).then(function (domFile) {
+ sendAsyncMessage("file.created", domFile);
+ });
+});
+
+addMessageListener("file.remove", function (message) {
+ file.remove(false);
+ sendAsyncMessage("file.removed", {});
+});
diff --git a/dom/base/test/bug638112-response.txt b/dom/base/test/bug638112-response.txt
new file mode 100644
index 0000000000..9ce788da79
--- /dev/null
+++ b/dom/base/test/bug638112-response.txt
Binary files differ
diff --git a/dom/base/test/bug638112.sjs b/dom/base/test/bug638112.sjs
new file mode 100644
index 0000000000..3fb6fb90f0
--- /dev/null
+++ b/dom/base/test/bug638112.sjs
@@ -0,0 +1,24 @@
+function getInputStream(path) {
+ var file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Components.interfaces.nsIFileInputStream);
+ var split = path.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ return fis;
+}
+
+function handleRequest(request, response) {
+ var inputStream = getInputStream(
+ "tests/dom/base/test/bug638112-response.txt"
+ );
+ response.seizePower();
+ response.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+ response.finish();
+ inputStream.close();
+}
diff --git a/dom/base/test/bug696301-script-1.js b/dom/base/test/bug696301-script-1.js
new file mode 100644
index 0000000000..98577364df
--- /dev/null
+++ b/dom/base/test/bug696301-script-1.js
@@ -0,0 +1,3 @@
+var a = 0;
+var global = "ran";
+c();
diff --git a/dom/base/test/bug696301-script-1.js^headers^ b/dom/base/test/bug696301-script-1.js^headers^
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/dom/base/test/bug696301-script-1.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/dom/base/test/bug696301-script-2.js b/dom/base/test/bug696301-script-2.js
new file mode 100644
index 0000000000..98577364df
--- /dev/null
+++ b/dom/base/test/bug696301-script-2.js
@@ -0,0 +1,3 @@
+var a = 0;
+var global = "ran";
+c();
diff --git a/dom/base/test/bug704320.sjs b/dom/base/test/bug704320.sjs
new file mode 100644
index 0000000000..f63b5d3a7e
--- /dev/null
+++ b/dom/base/test/bug704320.sjs
@@ -0,0 +1,396 @@
+var BASE_URL = "example.com/tests/dom/base/test/bug704320.sjs";
+
+function createTestUrl(schemeFrom, schemeTo, policy, action, type) {
+ return (
+ schemeTo +
+ "://" +
+ BASE_URL +
+ "?" +
+ "action=" +
+ action +
+ "&" +
+ "scheme=" +
+ schemeFrom +
+ "-to-" +
+ schemeTo +
+ "&" +
+ "policy=" +
+ policy +
+ "&" +
+ "type=" +
+ type
+ );
+}
+
+function create2ndLevelIframeUrl(schemeFrom, schemeTo, policy, type) {
+ return (
+ schemeFrom +
+ "://" +
+ BASE_URL +
+ "?" +
+ "action=create-2nd-level-iframe&" +
+ "scheme-from=" +
+ schemeFrom +
+ "&" +
+ "scheme-to=" +
+ schemeTo +
+ "&" +
+ "policy=" +
+ policy +
+ "&" +
+ "type=" +
+ type
+ );
+}
+
+// Creates the following test cases for the specified scheme and referrer
+// policy combination:
+// <link>
+// @import
+// font-face
+// bg-url
+// <script>
+// <img>
+// <iframe>
+// <audio>
+// <video>
+// <object type="bogus">
+// <object type="image/svg+xml">
+// <a>
+// <a ping>
+// <form>
+// window.location
+// window.open
+// XMLHttpRequest
+// EventSource
+// TODO: XSLT?
+//
+// This returns a page that loads all of the above resources and contains a
+// script that clicks a link after all resources are (hopefully)
+// loaded. The click triggers a redirection to file_bug704320_redirect.html,
+// which in turn notifies the main window that it's time to check the test
+// results.
+function createTest(schemeFrom, schemeTo, policy, optionalEarlierPolicy) {
+ var _createTestUrl = createTestUrl.bind(
+ null,
+ schemeFrom,
+ schemeTo,
+ policy,
+ "test"
+ );
+
+ var _create2ndLevelIframeUrl = create2ndLevelIframeUrl.bind(
+ null,
+ schemeFrom,
+ schemeTo,
+ policy
+ );
+
+ var metaReferrerPolicyString = "";
+ if (optionalEarlierPolicy && optionalEarlierPolicy != "") {
+ metaReferrerPolicyString +=
+ '<meta name="referrer" content="' + optionalEarlierPolicy + '">\n';
+ }
+ metaReferrerPolicyString += '<meta name="referrer" content="' + policy + '">';
+
+ return (
+ "<!DOCTYPE HTML>\n\
+ <html>\n\
+ <head>\n\
+ " +
+ metaReferrerPolicyString +
+ '\n\
+ <link rel="stylesheet" type="text/css" href="' +
+ _createTestUrl("stylesheet") +
+ '">\n\
+ <style type="text/css">\n\
+ @import "' +
+ _createTestUrl("import-css") +
+ '";\n\
+ @font-face {\n\
+ font-family: "Fake Serif Bold";\n\
+ src: url("' +
+ _createTestUrl("font-face") +
+ '");\n\
+ }\n\
+ body {\n\
+ font-family: "Fake Serif Bold", serif;\n\
+ background: url("' +
+ _createTestUrl("bg-url") +
+ '");\n\
+ }\n\
+ </style>\n\
+ </head>\n\
+ <body>\n\
+ <script src="' +
+ _createTestUrl("script") +
+ '"></script>\n\
+ <img src="' +
+ _createTestUrl("img") +
+ '"></img>\n\
+ <iframe src="' +
+ _createTestUrl("iframe") +
+ '"></iframe>\n\
+ <audio src="' +
+ _createTestUrl("audio") +
+ '"></audio>\n\
+ <video src="' +
+ _createTestUrl("video") +
+ '"></video>\n\
+ <object type="bogus" data="' +
+ _createTestUrl("object") +
+ '"></object>\n\
+ <object type="image/svg+xml" data="' +
+ _createTestUrl("object-svg") +
+ '"></object>\n\
+ <a id="link" href="' +
+ _createTestUrl("link") +
+ '" ping="' +
+ _createTestUrl("link-ping") +
+ '"></a>\n\
+ <iframe src="' +
+ _create2ndLevelIframeUrl("form") +
+ '"></iframe>\n\
+ <iframe src="' +
+ _create2ndLevelIframeUrl("window.location") +
+ '"></iframe>\n\
+ <script>\n\
+ var _testFinished = 0\n\
+ (function() {\n\
+ var x = new XMLHttpRequest();\n\
+ x.open("GET", "' +
+ _createTestUrl("xmlhttprequest") +
+ '");\n\
+ x.send();\n\
+ })();\n\
+ (function() {\n\
+ var eventSource = new EventSource("' +
+ _createTestUrl("eventsource") +
+ '");\n\
+ })();' +
+ // LOAD EVENT (most of the tests)
+ // fires when the resources for the page are loaded
+ 'var _isLoaded = false;\n\
+ window.addEventListener("load", function() {\n\
+ this._isLoaded = true;\n\
+ this.checkForFinish();\n\
+ }.bind(window), false);' +
+ // WINDOW.OPEN test
+ // listen for incoming status from window.open, close the window
+ // and check if we're done.
+ 'var _openedWindowLoaded = false;\n\
+ window.addEventListener("message", function(message) {\n\
+ if (message.data == "window.open") {\n\
+ this._openedWindowLoaded = true;\n\
+ this.win.close();\n\
+ this.checkForFinish();\n\
+ }\n\
+ }.bind(window), false);\n\
+ var win = window.open("' +
+ _createTestUrl("window.open") +
+ '", "");' +
+ // called by the two things that must complete: window.open page
+ // and the window load event. When both are complete, this
+ // "finishes" the iframe subtest by clicking the link.
+ // _testFinished avoids calling this function twice (which may happen)
+ 'function checkForFinish() {\n\
+ if (window._isLoaded && window._openedWindowLoaded && !window._testFinished) {\n\
+ window._testFinished = 1;\n\
+ document.getElementById("link").click();\n\
+ }\n\
+ }\n\
+ </script>\n\
+ </body>\n\
+ </html>'
+ );
+}
+
+function createIframedFormTest(schemeFrom, schemeTo, policy) {
+ var actionUrl = schemeTo + "://" + BASE_URL;
+
+ return (
+ '<!DOCTYPE HTML>\n\
+ <html>\n\
+ <head>\n\
+ <meta name="referrer" content="' +
+ policy +
+ '">\n\
+ </head>\n\
+ <body>\n\
+ <form id="form" action="' +
+ actionUrl +
+ '">\n\
+ <input type="hidden" name="action" value="test">\n\
+ <input type="hidden" name="scheme" value="' +
+ schemeFrom +
+ "-to-" +
+ schemeTo +
+ '">\n\
+ <input type="hidden" name="policy" value="' +
+ policy +
+ '">\n\
+ <input type="hidden" name="type" value="form">\n\
+ </form>\n\
+ <script>\n\
+ document.getElementById("form").submit();\n\
+ </script>\n\
+ </body>\n\
+ </html>'
+ );
+}
+
+function createIframedWindowLocationTest(schemeFrom, schemeTo, policy) {
+ var url = createTestUrl(
+ schemeFrom,
+ schemeTo,
+ policy,
+ "test",
+ "window.location"
+ );
+
+ return (
+ '<!DOCTYPE HTML>\n\
+ <html>\n\
+ <head>\n\
+ <meta name="referrer" content="' +
+ policy +
+ '">\n\
+ </head>\n\
+ <body>\n\
+ <script>\n\
+ window.location = "' +
+ url +
+ '";\n\
+ </script>\n\
+ </body>\n\
+ </html>'
+ );
+}
+
+function createPolicyTest(policy, optionalEarlierPolicy) {
+ var metaReferrerPolicyString = "";
+ if (optionalEarlierPolicy && optionalEarlierPolicy != "") {
+ metaReferrerPolicyString +=
+ '<meta name="referrer" content="' + optionalEarlierPolicy + '">\n';
+ }
+ metaReferrerPolicyString += '<meta name="referrer" content="' + policy + '">';
+
+ return (
+ "<!DOCTYPE HTML>\n\
+ <html>\n\
+ <head>\n\
+ " +
+ metaReferrerPolicyString +
+ '\n\
+ <script type="text/javascript" src="/tests/dom/base/test/file_bug704320_preload_common.js"></script>\n\
+ </head>\n\
+ <body>\n\
+ <img src="/tests/dom/base/test/bug704320_counter.sjs?type=img"\n\
+ onload="incrementLoad2(\'img\', 2);">\n\
+ <img src="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=img"\n\
+ onload="incrementLoad2(\'img\', 2);">\n\
+ </body>\n\
+ </html>'
+ );
+}
+
+function handleRequest(request, response) {
+ var sharedKey = "bug704320.sjs";
+ var params = request.queryString.split("&");
+ var action = params[0].split("=")[1];
+
+ if (action === "create-1st-level-iframe") {
+ // ?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=origin
+ var schemeFrom = params[1].split("=")[1];
+ var schemeTo = params[2].split("=")[1];
+ var policy = params[3].split("=")[1];
+ var optionalEarlierPolicy = "";
+ if (params[4]) {
+ optionalEarlierPolicy = params[4].split("=")[1];
+ }
+
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(
+ createTest(schemeFrom, schemeTo, policy, optionalEarlierPolicy)
+ );
+ } else if (action === "create-2nd-level-iframe") {
+ // ?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=origin&type=form"
+ var schemeFrom = params[1].split("=")[1];
+ var schemeTo = params[2].split("=")[1];
+ var policy = params[3].split("=")[1];
+ var type = params[4].split("=")[1];
+
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if (type === "form") {
+ response.write(createIframedFormTest(schemeFrom, schemeTo, policy));
+ } else if (type === "window.location") {
+ response.write(
+ createIframedWindowLocationTest(schemeFrom, schemeTo, policy)
+ );
+ }
+ } else if (action === "test") {
+ // ?action=test&scheme=http-to-https&policy=origin&type=img
+ var scheme = params[1].split("=")[1];
+ var policy = params[2].split("=")[1];
+ var type = params[3].split("=")[1];
+ var result = getSharedState(sharedKey);
+
+ if (result === "") {
+ result = {};
+ } else {
+ result = JSON.parse(result);
+ }
+
+ if (!result[type]) {
+ result[type] = {};
+ }
+
+ if (!result[type][scheme]) {
+ result[type][scheme] = {};
+ }
+
+ if (request.hasHeader("Referer")) {
+ result[type][scheme][policy] = request.getHeader("Referer");
+ } else {
+ result[type][scheme][policy] = "";
+ }
+
+ setSharedState(sharedKey, JSON.stringify(result));
+
+ if (type === "link") {
+ var loc =
+ "https://example.com/tests/dom/base/test/file_bug704320_redirect.html";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ }
+
+ if (type === "window.open") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(
+ "<html><body><script>" +
+ 'window.opener.postMessage("window.open", "*");' +
+ "</script></body></html>"
+ );
+ }
+ } else if (action === "get-test-results") {
+ // ?action=get-result
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState(sharedKey));
+ } else if (action === "generate-policy-test") {
+ // ?action=generate-policy-test&policy=b64-encoded-string
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ var policy = unescape(params[1].split("=")[1]);
+ var optionalEarlierPolicy = "";
+ if (params[2]) {
+ optionalEarlierPolicy = params[2].split("=")[1];
+ }
+
+ response.write(createPolicyTest(policy, optionalEarlierPolicy));
+ }
+}
diff --git a/dom/base/test/bug704320_counter.sjs b/dom/base/test/bug704320_counter.sjs
new file mode 100644
index 0000000000..061bd751d9
--- /dev/null
+++ b/dom/base/test/bug704320_counter.sjs
@@ -0,0 +1,96 @@
+// Handle counting loads for bug 704320.
+
+const SHARED_KEY = "bug704320_counter";
+const DEFAULT_STATE = {
+ css: { count: 0, referrers: [] },
+ img: { count: 0, referrers: [] },
+ js: { count: 0, referrers: [] },
+};
+const TYPE_MAP = {
+ css: "text/css",
+ js: "application/javascript",
+ img: "image/png",
+ html: "text/html",
+};
+
+// Writes an image to the response
+function WriteOutImage(response) {
+ var file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.nsIFile);
+
+ file.append("tests");
+ file.append("image");
+ file.append("test");
+ file.append("mochitest");
+ file.append("blue.png");
+
+ var fileStream = Components.classes[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Components.interfaces.nsIFileInputStream);
+ fileStream.init(file, 1, 0, false);
+ response.bodyOutputStream.writeFrom(fileStream, fileStream.available());
+}
+
+function handleRequest(request, response) {
+ var query = {};
+ request.queryString.split("&").forEach(function (val) {
+ var [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ var referrerLevel = "none";
+ if (request.hasHeader("Referer")) {
+ let referrer = request.getHeader("Referer");
+ if (referrer.indexOf("bug704320") > 0) {
+ referrerLevel = "full";
+ } else if (referrer == "http://mochi.test:8888/") {
+ referrerLevel = "origin";
+ }
+ }
+
+ var state = getSharedState(SHARED_KEY);
+ if (state === "") {
+ state = DEFAULT_STATE;
+ } else {
+ state = JSON.parse(state);
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ //avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if ("reset" in query) {
+ //reset server state
+ setSharedState(SHARED_KEY, JSON.stringify(DEFAULT_STATE));
+ //serve any CSS that we want to use.
+ response.write("");
+ return;
+ }
+
+ if ("results" in query) {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write(JSON.stringify(state));
+ return;
+ }
+
+ if ("type" in query) {
+ state[query.type].count++;
+ response.setHeader("Content-Type", TYPE_MAP[query.type], false);
+ if (state[query.type].referrers.indexOf(referrerLevel) < 0) {
+ state[query.type].referrers.push(referrerLevel);
+ }
+
+ if (query.type == "img") {
+ WriteOutImage(response);
+ }
+ }
+
+ if ("content" in query) {
+ response.write(unescape(query.content));
+ }
+
+ setSharedState(SHARED_KEY, JSON.stringify(state));
+ return;
+}
diff --git a/dom/base/test/bug819051.sjs b/dom/base/test/bug819051.sjs
new file mode 100644
index 0000000000..b880d4efe3
--- /dev/null
+++ b/dom/base/test/bug819051.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.setHeader(
+ "X-appended-result",
+ request.getHeader("X-appended-to-this")
+ );
+ response.setHeader("X-Accept-Result", request.getHeader("Accept"));
+ response.write("");
+}
diff --git a/dom/base/test/chrome.ini b/dom/base/test/chrome.ini
new file mode 100644
index 0000000000..08302adc2a
--- /dev/null
+++ b/dom/base/test/chrome.ini
@@ -0,0 +1,42 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ file_empty.html
+ file_blocking_image.html
+ file_bug945152.jar
+ file_bug945152_worker.js
+ file_bug1008126_worker.js
+ file_inline_script.html
+ file_inline_script.xhtml
+ file_external_script.html
+ file_external_script.xhtml
+ file_script.js
+ file_serializer_noscript.html
+ referrer_helper.js
+ referrer_testserver.sjs
+ !/image/test/mochitest/shaver.png
+
+[test_anonymousContent_xul_window.xhtml]
+[test_blockParsing.html]
+[test_blocking_image.html]
+[test_bug419527.xhtml]
+[test_bug564863-2.xhtml]
+[test_bug945152.html]
+[test_bug1008126.html]
+[test_bug1016960.html]
+[test_bug1120222.html]
+[test_anchor_target_blank_referrer.html]
+[test_domrequesthelper.xhtml]
+[test_fragment_sanitization.xhtml]
+[test_getLastOverWindowPointerLocationInCSSPixels.html]
+support-files = !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_messagemanager_send_principal.html]
+[test_navigator_resolve_identity_xrays.xhtml]
+support-files = file_navigator_resolve_identity_xrays.xhtml
+[test_sandboxed_blob_uri.html]
+[test_sendQueryContentAndSelectionSetEvent.html]
+[test_sendSelectionSetEvent_with_same_range.html]
+[test_urgent_start.html]
+skip-if = (os == "win" && debug) || (os == "mac" && debug) #leaks Bug 1571583
+[test_sanitize_xhr.html]
+[test_serializer_noscript.html]
diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js
new file mode 100644
index 0000000000..7c39df0c13
--- /dev/null
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -0,0 +1,88 @@
+/* globals chromeWindow */
+// The main test function.
+var test = function (isContent) {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ set: [["security.allow_eval_with_system_principal", true]],
+ });
+
+ if (!isContent) {
+ let { ww } = SpecialPowers.Services;
+ window.chromeWindow = ww.activeWindow;
+ }
+
+ // The pairs of values expected to be the same when
+ // fingerprinting resistance is enabled.
+ let pairs = [
+ ["screenX", 0],
+ ["screenY", 0],
+ ["mozInnerScreenX", 0],
+ ["mozInnerScreenY", 0],
+ ["screen.pixelDepth", 24],
+ ["screen.colorDepth", 24],
+ ["screen.availWidth", "innerWidth"],
+ ["screen.availHeight", "innerHeight"],
+ ["screen.left", 0],
+ ["screen.top", 0],
+ ["screen.availLeft", 0],
+ ["screen.availTop", 0],
+ ["screen.width", "innerWidth"],
+ ["screen.height", "innerHeight"],
+ ["screen.orientation.type", "'landscape-primary'"],
+ ["screen.orientation.angle", 0],
+ ["screen.mozOrientation", "'landscape-primary'"],
+ ["devicePixelRatio", 1],
+ ];
+
+ // checkPair: tests if members of pair [a, b] are equal when evaluated.
+ let checkPair = function (a, b) {
+ // eslint-disable-next-line no-eval
+ is(eval(a), eval(b), a + " should be equal to " + b);
+ };
+
+ // Returns generator object that iterates through pref values.
+ let prefVals = (function* () {
+ yield false;
+ yield true;
+ })();
+
+ // The main test function, runs until all pref values are exhausted.
+ let nextTest = function () {
+ let { value: prefValue, done } = prefVals.next();
+ if (done) {
+ SimpleTest.finish();
+ return;
+ }
+ SpecialPowers.pushPrefEnv(
+ { set: [["privacy.resistFingerprinting", prefValue]] },
+ function () {
+ // We will be resisting fingerprinting if the pref is enabled,
+ // and we are in a content script (not chrome).
+ let resisting = prefValue && isContent;
+ // Check each of the pairs.
+ pairs.map(function ([item, onVal]) {
+ if (resisting) {
+ checkPair("window." + item, onVal);
+ } else if (!isContent && !item.startsWith("moz")) {
+ checkPair("window." + item, "chromeWindow." + item);
+ }
+ });
+ if (!isContent && !resisting) {
+ // Hard to predict these values, but we can enforce constraints:
+ ok(
+ window.mozInnerScreenX >= chromeWindow.mozInnerScreenX,
+ "mozInnerScreenX"
+ );
+ ok(
+ window.mozInnerScreenY >= chromeWindow.mozInnerScreenY,
+ "mozInnerScreenY"
+ );
+ }
+ nextTest();
+ }
+ );
+ };
+
+ nextTest();
+};
diff --git a/dom/base/test/chrome/bug421622-referer.sjs b/dom/base/test/chrome/bug421622-referer.sjs
new file mode 100644
index 0000000000..14cab00de4
--- /dev/null
+++ b/dom/base/test/chrome/bug421622-referer.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var referer = request.hasHeader("Referer")
+ ? request.getHeader("Referer")
+ : "";
+ response.write("Referer: " + referer);
+}
diff --git a/dom/base/test/chrome/bug884693.sjs b/dom/base/test/chrome/bug884693.sjs
new file mode 100644
index 0000000000..f2650753f2
--- /dev/null
+++ b/dom/base/test/chrome/bug884693.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ let [status, statusText, encodedBody] = request.queryString.split("&");
+ let body = decodeURIComponent(encodedBody);
+ response.setStatusLine(request.httpVersion, status, statusText);
+ response.setHeader("Content-Type", "text/xml", false);
+ response.setHeader("Content-Length", "" + body.length, false);
+ response.write(body);
+}
diff --git a/dom/base/test/chrome/chrome.ini b/dom/base/test/chrome/chrome.ini
new file mode 100644
index 0000000000..5dde748a67
--- /dev/null
+++ b/dom/base/test/chrome/chrome.ini
@@ -0,0 +1,84 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ bug418986-1.js
+ clonedoc/**
+ file_bug549682.xhtml
+ file_bug616841.xhtml
+ file_bug816340.xhtml
+ file_bug990812-1.xhtml
+ file_bug990812-2.xhtml
+ file_bug990812-3.xhtml
+ file_bug990812-4.xhtml
+ file_bug990812-5.xhtml
+ file_bug1139964.xhtml
+ file_bug1209621.xhtml
+ fileconstructor_file.png
+ frame_custom_element_content.html
+ custom_element_ep.js
+ window_nsITextInputProcessor.xhtml
+ title_window.xhtml
+ window_swapFrameLoaders.xhtml
+prefs =
+ gfx.font_rendering.fallback.async=false
+
+[test_bug120684.xhtml]
+[test_bug206691.xhtml]
+[test_bug289714.xhtml]
+[test_bug339494.xhtml]
+[test_bug357450.xhtml]
+support-files = ../file_bug357450.js
+[test_bug380418.html]
+[test_bug380418.html^headers^]
+[test_bug383430.html]
+[test_bug418986-1.xhtml]
+[test_bug421622.xhtml]
+[test_bug429785.xhtml]
+[test_bug430050.xhtml]
+[test_bug467123.xhtml]
+[test_bug473284.xhtml]
+[test_bug549682.xhtml]
+skip-if = verify
+[test_bug571390.xhtml]
+[test_bug1098074_throw_from_ReceiveMessage.xhtml]
+[test_bug616841.xhtml]
+[test_bug635835.xhtml]
+[test_bug682305.html]
+[test_bug683852.xhtml]
+[test_bug752226-3.xhtml]
+[test_bug752226-4.xhtml]
+[test_bug765993.html]
+[test_bug780199.xhtml]
+[test_bug780529.xhtml]
+[test_bug800386.xhtml]
+[test_bug816340.xhtml]
+[test_bug884693.xhtml]
+[test_bug914381.html]
+[test_bug990812.xhtml]
+[test_bug1063837.xhtml]
+[test_bug1139964.xhtml]
+[test_bug1209621.xhtml]
+[test_bug1346936.html]
+[test_chromeOuterWindowID.xhtml]
+support-files =
+ window_chromeOuterWindowID.xhtml
+[test_getElementsWithGrid.html]
+[test_custom_element_content.xhtml]
+[test_custom_element_ep.xhtml]
+[test_document-element-inserted.xhtml]
+support-files =
+ file_document-element-inserted.xhtml
+ file_document-element-inserted-inner.xhtml
+[test_domparsing.xhtml]
+[test_fileconstructor.xhtml]
+[test_input_value_set_preserve_undo.xhtml]
+[test_nsITextInputProcessor.xhtml]
+[test_permission_hasValidTransientUserActivation.xhtml]
+support-files = ../dummy.html
+[test_range_getClientRectsAndTexts.html]
+[test_title.xhtml]
+support-files = file_title.xhtml
+[test_windowroot.xhtml]
+[test_swapFrameLoaders.xhtml]
+skip-if = os == 'mac' # bug 1674413
+[test_bug1339722.html]
diff --git a/dom/base/test/chrome/clonedoc/chrome.manifest b/dom/base/test/chrome/clonedoc/chrome.manifest
new file mode 100644
index 0000000000..5d7e720416
--- /dev/null
+++ b/dom/base/test/chrome/clonedoc/chrome.manifest
@@ -0,0 +1 @@
+content clonedoc content/
diff --git a/dom/base/test/chrome/clonedoc/content/doc.xml b/dom/base/test/chrome/clonedoc/content/doc.xml
new file mode 100644
index 0000000000..fdd7e7c6e0
--- /dev/null
+++ b/dom/base/test/chrome/clonedoc/content/doc.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<something>
+ <somethinglese/>
+</something>
diff --git a/dom/base/test/chrome/custom_element_ep.js b/dom/base/test/chrome/custom_element_ep.js
new file mode 100644
index 0000000000..d933ecbbab
--- /dev/null
+++ b/dom/base/test/chrome/custom_element_ep.js
@@ -0,0 +1,14 @@
+/* globals finishTest */
+class XFoo extends HTMLElement {
+ constructor() {
+ super();
+ this.magicNumber = 42;
+ }
+
+ connectedCallback() {
+ finishTest(this.magicNumber === 42);
+ }
+}
+customElements.define("x-foo", XFoo);
+
+document.firstChild.appendChild(document.createElement("x-foo"));
diff --git a/dom/base/test/chrome/file_bug1139964.xhtml b/dom/base/test/chrome/file_bug1139964.xhtml
new file mode 100644
index 0000000000..8bf7f27e0b
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1139964.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1139964
+-->
+<window title="Mozilla Bug 1139964"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run()">
+ <label value="Mozilla Bug 1139964"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage */
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
+
+ function ok(cond, msg) {
+ window.arguments[0].ok(cond, msg);
+ }
+
+ var msgName = "TEST:Global_has_Promise";
+
+ function mmScriptForPromiseTest() {
+ /* eslint-env mozilla/process-script */
+ sendAsyncMessage("TEST:Global_has_Promise",
+ {
+ hasPromise: ("Promise" in this),
+ hasTextEncoder: ("TextEncoder" in this),
+ hasWindow: ("Window" in this),
+ });
+ }
+
+ function processListener(m) {
+ ppm.removeMessageListener(msgName, processListener);
+ ok(m.data.hasPromise, "ProcessGlobal should have Promise object in the global scope!");
+ ok(m.data.hasTextEncoder, "ProcessGlobal should have TextEncoder object in the global scope!");
+ ok(m.data.hasWindow, "ProcessGlobal should have Window object in the global scope!");
+
+ messageManager.addMessageListener(msgName, tabListener)
+ messageManager.loadFrameScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true);
+ }
+
+ function tabListener(m) {
+ messageManager.removeMessageListener(msgName, tabListener);
+ ok(m.data.hasPromise, "BrowserChildGlobal should have Promise object in the global scope!");
+ ok(m.data.hasTextEncoder, "BrowserChildGlobal should have TextEncoder object in the global scope!");
+ ok(m.data.hasWindow, "BrowserChildGlobal should have Window object in the global scope!");
+
+ window.arguments[0].setTimeout(function() { this.done(); }, 0);
+ window.close();
+ }
+
+ function run() {
+ ppm.addMessageListener(msgName, processListener)
+ ppm.loadProcessScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true);
+ }
+
+ ]]></script>
+ <browser type="content" src="about:blank" id="ifr"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug1209621.xhtml b/dom/base/test/chrome/file_bug1209621.xhtml
new file mode 100644
index 0000000000..3ba58975bd
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1209621.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1209621
+-->
+<window title="Mozilla Bug 1209621"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run()">
+ <label value="Mozilla Bug 1209621"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ function ok(cond, msg) {
+ window.arguments[0].ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ window.arguments[0].is(actual, expected, msg);
+ }
+
+ function run() {
+ var docshell = window.docShell;
+ ok(docshell, "Active window should have a DocShell");
+ var treeOwner = docshell.treeOwner;
+ ok(treeOwner, "Active docshell should have a TreeOwner!");
+
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has primary=true.");
+ is(treeOwner.primaryRemoteTab, null,
+ "There shouldn't be primaryRemoteTab because no remote browser has primary=true.");
+ is(treeOwner.primaryContentBrowsingContext, null,
+ "There shouldn't be primaryContentBrowsingContext because no browser has primary=true.");
+
+ var ip = document.getElementById("inprocess");
+ var remote = document.getElementById("remote");
+ var remote2 = document.getElementById("remote2");
+
+ ip.setAttribute("primary", "true");
+ ok(ip.docShell, "non-remote browser should have a DocShell.");
+ is(treeOwner.primaryContentShell, ip.docShell,
+ "primary browser should be the primaryContentShell.");
+ is(treeOwner.primaryRemoteTab, null,
+ "There shouldn't be primaryRemoteTab because no remote browser has primary=true.");
+ is(treeOwner.primaryContentBrowsingContext, ip.browsingContext,
+ "primary browsing context should be the primaryContentBrowsingContext.");
+
+ ip.removeAttribute("primary");
+ remote.setAttribute("primary", "true");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has primary=true.");
+ var tp = remote.frameLoader.remoteTab;
+ ok(tp, "Remote browsers should have a remoteTab.");
+ is(treeOwner.primaryRemoteTab, tp,
+ "primary remote browser should be the primaryRemoteTab.");
+ is(treeOwner.primaryContentBrowsingContext, remote.browsingContext,
+ "primary remote browser should be the primaryContentBrowsingContext.");
+
+ remote.removeAttribute("primary");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has primary=true.");
+ is(treeOwner.primaryRemoteTab, null,
+ "There shouldn't be primaryRemoteTab because no remote browser has primary=true.");
+ is(treeOwner.primaryContentBrowsingContext, null,
+ "There shouldn't be primaryContentBrowsingContext because no browser has primary=true.");
+
+ remote2.setAttribute("primary", "true");
+ var tp2 = remote2.frameLoader.remoteTab;
+ ok(tp2, "Remote browsers should have a remoteTab.");
+ is(treeOwner.primaryRemoteTab, tp2,
+ "primary remote browser should be the primaryRemoteTab.");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has primary=true.");
+ is(treeOwner.primaryContentBrowsingContext, remote2.browsingContext,
+ "primary remote browser should be the primaryContentBrowsingContext.");
+
+ window.arguments[0].setTimeout(function() { this.done(); }, 0);
+ window.close();
+ }
+
+ ]]></script>
+ <browser type="content" src="about:blank" id="inprocess"/>
+ <browser type="content" remote="true" src="about:blank" id="remote"/>
+ <browser type="content" remote="true" src="about:blank" id="remote2"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug549682.xhtml b/dom/base/test/chrome/file_bug549682.xhtml
new file mode 100644
index 0000000000..02919386c4
--- /dev/null
+++ b/dom/base/test/chrome/file_bug549682.xhtml
@@ -0,0 +1,214 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549682
+-->
+<window title="Mozilla Bug 549682"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run()">
+ <label value="Mozilla Bug 549682"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage */
+ var didRunAsync = false;
+ var didRunLocal = false;
+
+ var global = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
+ var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
+
+ function ok(cond, msg) {
+ window.arguments[0].ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ window.arguments[0].is(actual, expected, msg);
+ }
+
+ var asyncPPML = false;
+ function ppmASL(m) {
+ asyncPPML = true;
+ }
+ var syncPPML = false;
+ function ppmSL(m) {
+ syncPPML = true;
+ }
+ ppm.addMessageListener("processmessageAsync", ppmASL);
+ ppm.addMessageListener("processmessageSync", ppmSL);
+
+ cpm.sendAsyncMessage("processmessageAsync", "");
+ cpm.sendSyncMessage("processmessageSync", "");
+
+ var asyncCPML = false;
+ function cpmASL(m) {
+ asyncCPML = true;
+ }
+ cpm.addMessageListener("childprocessmessage", cpmASL);
+ ppm.broadcastAsyncMessage("childprocessmessage", "");
+
+ function checkPMMMessages() {
+ ok(asyncPPML, "should have handled async message");
+ ok(syncPPML, "should have handled sync message");
+ ok(asyncCPML, "should have handled async message");
+ ppm.removeMessageListener("processmessageAsync", ppmASL);
+ ppm.removeMessageListener("processmessageSync", ppmSL);
+ cpm.removeMessageListener("childprocessmessage", cpmASL);
+ }
+
+ var globalListenerCallCount = 0;
+ function globalListener(m) {
+ ++globalListenerCallCount;
+ if (m.name == "sync") {
+ global.removeMessageListener("async", globalListener);
+ global.removeMessageListener("sync", globalListener);
+ global.removeMessageListener("global-sync", globalListener);
+ // Note, the result depends on what other windows are open.
+ ok(globalListenerCallCount >= 4,
+ "Global listener should have been called at least 4 times!");
+ ok(didRunLocal, "Local message received.");
+ }
+ }
+
+ function asyncL(m) {
+ didRunAsync = true;
+ is(m.name, "async", "Wrong message!");
+ is(m.json.data, 1234, "Wrong data!");
+ }
+
+ function syncL(m) {
+ is(m.name, "sync", "Wrong message!");
+ is(m.json.data, 1234, "Wrong data!");
+ ok(didRunAsync, "Should have run async!");
+ }
+
+ function localL(m) {
+ is(m.name, "lasync", "Wrong message!");
+ is(m.json.data, 2345, "Wrong data!");
+ didRunLocal = true;
+ }
+
+ var weakMessageReceived = false;
+ var weakListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
+
+ receiveMessage: function(msg) {
+ if (weakMessageReceived) {
+ ok(false, 'Weak listener fired twice.');
+ return;
+ }
+
+ ok(true, 'Weak listener fired once.');
+ weakMessageReceived = true;
+ document.getElementById('ifr').messageManager
+ .removeWeakMessageListener('weak', weakListener);
+ }
+ };
+
+ var weakListener2 = {
+ QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
+
+ receiveMessage: function(msg) {
+ ok(false, 'Should not have received a message.');
+ }
+ };
+
+ function weakDoneListener() {
+ ok(weakMessageReceived, 'Got "weak" message.');
+ finish();
+ }
+
+ function finish() {
+ window.arguments[0].setTimeout(function() { this.done(); }, 0);
+ var i = document.getElementById("ifr");
+ i.remove(); // This is a crash test!
+ window.close();
+ }
+
+ function loadScript() {
+ // Async should be processed first!
+ messageManager.loadFrameScript("data:,\
+ sendAsyncMessage('async', { data: 1234 });\
+ sendSyncMessage('sync', { data: 1234 });\
+ sendAsyncMessage('weak', {});\
+ sendAsyncMessage('weak', {});\
+ sendAsyncMessage('weakdone', {});", false);
+ }
+
+ function run() {
+ var localmm = document.getElementById('ifr').messageManager;
+
+ var docShell = document.getElementById('ifr').contentWindow.docShell;
+ ok(docShell, "Should have docshell");
+ var cfmm = docShell.messageManager;
+ ok(cfmm, "Should have content messageManager");
+
+ var didGetSyncMessage = false;
+ function syncContinueTestFn() {
+ didGetSyncMessage = true;
+ }
+ localmm.addMessageListener("syncContinueTest", syncContinueTestFn);
+ cfmm.sendSyncMessage("syncContinueTest", {});
+ localmm.removeMessageListener("syncContinueTest", syncContinueTestFn);
+ ok(didGetSyncMessage, "Should have got sync message!");
+
+ localmm.addMessageListener("lasync", localL);
+ localmm.loadFrameScript("data:,sendAsyncMessage('lasync', { data: 2345 })", false);
+
+ messageManager.addMessageListener("async", asyncL);
+ messageManager.addMessageListener("sync", syncL);
+ global.addMessageListener("async", globalListener);
+ global.addMessageListener("sync", globalListener);
+ global.addMessageListener("global-sync", globalListener);
+ global.loadFrameScript("data:,sendSyncMessage('global-sync', { data: 1234 });", true);
+ var toBeRemovedScript = "data:,sendAsyncMessage('toberemoved', { data: 2345 })";
+ var c = 0;
+ messageManager.addMessageListener("toberemoved", function() {
+ ++c;
+ is(c, 1, "Should be called only once!");
+ });
+ // This loads the script in the existing <browser>
+ messageManager.loadFrameScript(toBeRemovedScript, true);
+ // But it won't be loaded in the dynamically created <browser>
+ messageManager.removeDelayedFrameScript(toBeRemovedScript);
+
+ var oldValue = globalListenerCallCount;
+ var b = document.createXULElement("browser");
+ b.setAttribute("type", "content");
+ document.documentElement.appendChild(b);
+ is(globalListenerCallCount, oldValue + 1,
+ "Wrong message count");
+
+ localmm.addWeakMessageListener('weak', weakListener);
+ localmm.addMessageListener('weakdone', weakDoneListener);
+
+ // Add weakListener2 as a weak message listener, then force weakListener2
+ // to be gc'ed. weakListener2 shouldn't be run.
+ var weakRef = Cu.getWeakReference(weakListener2);
+ localmm.addWeakMessageListener('weak', weakListener2);
+ weakListener2 = null;
+
+ // Force a gc/cc in a loop until weakRef's referent has gone away.
+ function waitForWeakRefToDie() {
+ if (weakRef.get()) {
+ var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+ mgr.minimizeMemoryUsage(waitForWeakRefToDie);
+
+ // Print a message so that if the test hangs in a minimizeMemoryUsage
+ // loop, we'll be able to see it in the log.
+ ok(true, "waitForWeakRefToDie spinning...");
+ return;
+ }
+
+ setTimeout(checkPMMMessages, 0);
+ setTimeout(loadScript, 0);
+ }
+
+ waitForWeakRefToDie();
+ }
+
+ ]]></script>
+ <browser type="content" src="about:blank" id="ifr"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug616841.xhtml b/dom/base/test/chrome/file_bug616841.xhtml
new file mode 100644
index 0000000000..a5e5d9a427
--- /dev/null
+++ b/dom/base/test/chrome/file_bug616841.xhtml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=616841
+-->
+<window title="Mozilla Bug 616841"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start()">
+ <label value="Mozilla Bug 616841"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage */
+ const FRAME_SCRIPT =
+"data:,addMessageListener(\n"+
+" 'cmp',\n"+
+" function (m) {\n"+
+" sendAsyncMessage('cmp', { i: m.json.i,\n"+
+" cmp: m.json.a.localeCompare(m.json.b) });\n"+
+" });\n"+
+"sendAsyncMessage('contentReady');";
+
+ var toCompare = [ [ "C", "D" ],
+ [ "D", "C" ],
+ [ "\u010C", "D" ],
+ [ "D", "\u010C" ] ];
+ var nCmps = 0;
+
+ function recvContentReady(m) {
+ for (var i = 0; i < toCompare.length; ++i) {
+ var pair = toCompare[i];
+ messageManager.broadcastAsyncMessage("cmp",
+ { i: i, a: pair[0], b: pair[1] });
+ }
+ }
+
+ function recvCmp(m) {
+ var i = m.json.i, cmp = m.json.cmp;
+ var pair = toCompare[i];
+ window.arguments[0].is(pair[0].localeCompare(pair[1]), cmp, "localeCompare returned same result in frame script");
+
+ if (toCompare.length == ++nCmps) {
+ messageManager.removeMessageListener("cmp", recvCmp);
+ finish();
+ }
+ }
+
+ function start() {
+ messageManager.addMessageListener("contentReady", recvContentReady);
+ messageManager.addMessageListener("cmp", recvCmp);
+ messageManager.loadFrameScript(FRAME_SCRIPT, true);
+ }
+
+ function finish() {
+ window.arguments[0].setTimeout(function() { this.done(); }, 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug816340.xhtml b/dom/base/test/chrome/file_bug816340.xhtml
new file mode 100644
index 0000000000..2ee0ec3365
--- /dev/null
+++ b/dom/base/test/chrome/file_bug816340.xhtml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816340
+-->
+<window title="Mozilla Bug 816340"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 816340"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ function ok(val, msg) {
+ window.arguments[0].ok(val, msg);
+ }
+
+ var elems =
+ [
+ "input",
+ "textarea",
+ "select",
+ "fieldset",
+ "button",
+ ];
+
+ var chromeDidGetEvent = false;
+ function chromeListener() {
+ chromeDidGetEvent = true;
+ }
+
+ function testElement(el, disabled, contentShouldGetEvent) {
+ chromeDidGetEvent = false;
+ var b = document.getElementById("browser");
+ b.contentDocument.body.innerHTML = null;
+ var e = b.contentDocument.createElement(el);
+ if (disabled) {
+ e.setAttribute("disabled", "true");
+ }
+ b.contentDocument.body.appendChild(e);
+ var contentDidGetEvent = false;
+ b.contentDocument.body.addEventListener("foo",
+ function() { contentDidGetEvent = true }, true);
+
+ b.addEventListener("foo", chromeListener, true);
+ e.dispatchEvent(new Event("foo"));
+ b.removeEventListener("foo", chromeListener, true);
+ ok(contentDidGetEvent == contentShouldGetEvent, "content: " + el + (disabled ? " disabled" : ""));
+ ok(chromeDidGetEvent, "chrome: " + el + (disabled ? " disabled" : ""));
+ }
+
+ function start() {
+ // Test common element.
+ testElement("div", false, true);
+ testElement("div", true, true);
+
+ for (var i = 0; i < elems.length; ++i) {
+ testElement(elems[i], false, true);
+ testElement(elems[i], true, false);
+ }
+ ok(true, "done");
+ window.arguments[0].setTimeout(function() { this.done(); }, 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-1.xhtml b/dom/base/test/chrome/file_bug990812-1.xhtml
new file mode 100644
index 0000000000..8b8da3d136
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-1.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT_GLOBAL = "data:,sendSyncMessage('test', 'global')";
+ var FRAME_SCRIPT_WINDOW = "data:,sendSyncMessage('test', 'window')";
+ var FRAME_SCRIPT_GROUP = "data:,sendSyncMessage('test', 'group')";
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ window.arguments[0].is(val, exp, msg);
+ }
+
+ /**
+ * Ensures that delayed frame scripts are loaded in the expected order.
+ * Global frame scripts will be loaded before delayed frame scripts from
+ * window message managers. The latter will be loaded before group message
+ * manager frame scripts.
+ */
+ function start() {
+ globalMM.loadFrameScript(FRAME_SCRIPT_GLOBAL, true);
+ messageManager.loadFrameScript(FRAME_SCRIPT_WINDOW, true);
+ getGroupMessageManager("test").loadFrameScript(FRAME_SCRIPT_GROUP, true);
+
+ var order = ["global", "window", "group"];
+
+ messageManager.addMessageListener("test", function onMessage(msg) {
+ var next = order.shift();
+ window.arguments[0].is(msg.data, next, "received test:" + next);
+
+ if (!order.length) {
+ window.arguments[0].setTimeout(function() { this.next(); });
+ window.close();
+ }
+ });
+
+ var browser = document.createXULElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content");
+ document.documentElement.appendChild(browser);
+
+ globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
+ messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
+ getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
+ }
+
+ ]]></script>
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-2.xhtml b/dom/base/test/chrome/file_bug990812-2.xhtml
new file mode 100644
index 0000000000..e13b47f589
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-2.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT = "data:,sendAsyncMessage('test')";
+ var order = ["group", "window", "global"];
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ window.arguments[0].is(val, exp, msg);
+ }
+
+ function promiseMessage(type, mm) {
+ return new Promise(function (resolve) {
+ mm.addMessageListener("test", function onMessage() {
+ mm.removeMessageListener("test", onMessage);
+ is(type, order.shift(), "correct type " + type);
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * Tests that async messages sent by frame scripts bubble up as expected,
+ * passing the group, window, and global message managers in that order.
+ */
+ function start() {
+ var global = promiseMessage("global", globalMM);
+ var window = promiseMessage("window", messageManager);
+ var group = promiseMessage("group", getGroupMessageManager("test"));
+
+ var browser = document.querySelector("browser");
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
+
+ Promise.all([global, window, group]).then(function () {
+ self.arguments[0].setTimeout(function() { this.next(); });
+ self.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test" type="content" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-3.xhtml b/dom/base/test/chrome/file_bug990812-3.xhtml
new file mode 100644
index 0000000000..1f3e1d69f2
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-3.xhtml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT = "data:,addMessageListener('test', function (msg) {" +
+ "sendSyncMessage('test', msg.data)})";
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ window.arguments[0].is(val, exp, msg);
+ }
+
+ function promiseMessage(type, mm) {
+ var order = [type, "window", "global"];
+
+ return new Promise(function (resolve) {
+ mm.addMessageListener("test", function onMessage(msg) {
+ is(msg.data, order.shift(), "correct message " + msg.data);
+
+ if (!order.length) {
+ mm.removeMessageListener("test", onMessage);
+ resolve();
+ }
+ });
+ });
+ }
+
+ /**
+ * Ensures that broadcasting an async message does only reach descendants
+ * of a specific message manager and respects message manager groups.
+ */
+ function start() {
+ var mm1 = document.querySelector("browser").messageManager;
+ var promise1 = promiseMessage("group1", mm1);
+ mm1.loadFrameScript(FRAME_SCRIPT, true);
+
+ var mm2 = document.querySelector("browser + browser").messageManager;
+ var promise2 = promiseMessage("group2", mm2);
+ mm2.loadFrameScript(FRAME_SCRIPT, true);
+
+ getGroupMessageManager("test1").broadcastAsyncMessage("test", "group1");
+ getGroupMessageManager("test2").broadcastAsyncMessage("test", "group2");
+ messageManager.broadcastAsyncMessage("test", "window");
+ globalMM.broadcastAsyncMessage("test", "global");
+
+ Promise.all([promise1, promise2]).then(function () {
+ window.arguments[0].setTimeout(function() { this.next(); });
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-4.xhtml b/dom/base/test/chrome/file_bug990812-4.xhtml
new file mode 100644
index 0000000000..1c16ceb02c
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-4.xhtml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame2')})";
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ window.arguments[0].is(val, exp, msg);
+ }
+
+ function promiseMessage(type, mm) {
+ return new Promise(function (resolve) {
+ mm.addMessageListener("test", function onMessage(msg) {
+ mm.removeMessageListener("test", onMessage);
+ is(msg.data, type, "correct message " + type);
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * Tests that swapping docShells works as expected wrt to groups.
+ */
+ function start() {
+ var browser1 = document.querySelector("browser");
+ browser1.messageManager.loadFrameScript(FRAME_SCRIPT1, true);
+
+ var browser2 = document.querySelector("browser + browser");
+ browser2.messageManager.loadFrameScript(FRAME_SCRIPT2, true);
+
+ var promise1 = promiseMessage("frame2", getGroupMessageManager("test1"));
+ var promise2 = promiseMessage("frame1", getGroupMessageManager("test2"));
+
+ browser1.swapFrameLoaders(browser2);
+ messageManager.broadcastAsyncMessage("test");
+
+ Promise.all([promise1, promise2]).then(function () {
+ window.arguments[0].setTimeout(function() { this.next(); });
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-5.xhtml b/dom/base/test/chrome/file_bug990812-5.xhtml
new file mode 100644
index 0000000000..8c418492a1
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-5.xhtml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group2')})";
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ window.arguments[0].is(val, exp, msg);
+ }
+
+ function promiseTwoMessages(type, mm) {
+ var numLeft = 2;
+
+ return new Promise(function (resolve) {
+ mm.addMessageListener("test", function onMessage(msg) {
+ is(msg.data, type, "correct message " + type);
+
+ if (--numLeft == 0) {
+ mm.removeMessageListener("test", onMessage);
+ resolve();
+ }
+ });
+ });
+ }
+
+ /**
+ * This test ensures that having multiple message manager groups with
+ * multiple frame loaders in those works as expected. For a specific
+ * group message manager, frame scripts should only be loaded by its
+ * descendants and messages should only be received by and from those
+ * child message managers.
+ */
+ function start() {
+ var gmm1 = getGroupMessageManager("test1");
+ gmm1.loadFrameScript(FRAME_SCRIPT1, true);
+
+ var gmm2 = getGroupMessageManager("test2");
+ gmm2.loadFrameScript(FRAME_SCRIPT2, true);
+
+ var promise1 = promiseTwoMessages("group1", gmm1);
+ var promise2 = promiseTwoMessages("group2", gmm2);
+
+ messageManager.broadcastAsyncMessage("test");
+
+ Promise.all([promise1, promise2]).then(function () {
+ window.arguments[0].setTimeout(function() { this.next(); });
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+ <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+
+ <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812.xhtml b/dom/base/test/chrome/file_bug990812.xhtml
new file mode 100644
index 0000000000..02662d5749
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <label value="Mozilla Bug 990812"/>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* global messageManager, sendAsyncMessage, getGroupMessageManager */
+ var FRAME_SCRIPT_GLOBAL = "data:,sendSyncMessage('test', 'global')";
+ var FRAME_SCRIPT_WINDOW = "data:,sendSyncMessage('test', 'window')";
+ var FRAME_SCRIPT_GROUP = "data:,sendSyncMessage('test', 'group')";
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.is(val, exp, msg);
+ }
+
+ function start() {
+ globalMM.loadFrameScript(FRAME_SCRIPT_GLOBAL, true);
+ messageManager.loadFrameScript(FRAME_SCRIPT_WINDOW, true);
+ getGroupMessageManager("test").loadFrameScript(FRAME_SCRIPT_GROUP, true);
+
+ var order = ["global", "window", "group"];
+
+ messageManager.addMessageListener("test", function onMessage(msg) {
+ var next = order.shift();
+ opener.wrappedJSObject.is(msg.data, next, "received test:" + next);
+
+ if (!order.length) {
+ opener.setTimeout("next()");
+ window.close();
+ }
+ });
+
+ var browser = document.createXULElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content");
+ document.documentElement.appendChild(browser);
+
+ globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
+ messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
+ getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
+ }
+
+ ]]></script>
+
+</window>
diff --git a/dom/base/test/chrome/file_document-element-inserted-inner.xhtml b/dom/base/test/chrome/file_document-element-inserted-inner.xhtml
new file mode 100644
index 0000000000..2088e2789a
--- /dev/null
+++ b/dom/base/test/chrome/file_document-element-inserted-inner.xhtml
@@ -0,0 +1 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'></window> \ No newline at end of file
diff --git a/dom/base/test/chrome/file_document-element-inserted.xhtml b/dom/base/test/chrome/file_document-element-inserted.xhtml
new file mode 100644
index 0000000000..d67df13df7
--- /dev/null
+++ b/dom/base/test/chrome/file_document-element-inserted.xhtml
@@ -0,0 +1,3 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>
+ <iframe src='file_document-element-inserted-inner.xhtml'></iframe>
+</window> \ No newline at end of file
diff --git a/dom/base/test/chrome/file_title.xhtml b/dom/base/test/chrome/file_title.xhtml
new file mode 100644
index 0000000000..d1b04418aa
--- /dev/null
+++ b/dom/base/test/chrome/file_title.xhtml
@@ -0,0 +1 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>
diff --git a/dom/base/test/chrome/fileconstructor_file.png b/dom/base/test/chrome/fileconstructor_file.png
new file mode 100644
index 0000000000..51e8aaf38c
--- /dev/null
+++ b/dom/base/test/chrome/fileconstructor_file.png
Binary files differ
diff --git a/dom/base/test/chrome/frame_custom_element_content.html b/dom/base/test/chrome/frame_custom_element_content.html
new file mode 100644
index 0000000000..aa1d75863d
--- /dev/null
+++ b/dom/base/test/chrome/frame_custom_element_content.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<x-bar></x-bar>
+</body>
+</html>
diff --git a/dom/base/test/chrome/nochrome_bug1346936.html b/dom/base/test/chrome/nochrome_bug1346936.html
new file mode 100644
index 0000000000..158b20c884
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<html>
+</html>
diff --git a/dom/base/test/chrome/nochrome_bug1346936.js b/dom/base/test/chrome/nochrome_bug1346936.js
new file mode 100644
index 0000000000..a84113e1e1
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.js
@@ -0,0 +1,4 @@
+//# sourceMappingURL=bar.js.map
+
+// Define a single function to prevent script source from being gc'd
+function foo() {}
diff --git a/dom/base/test/chrome/nochrome_bug1346936.js^headers^ b/dom/base/test/chrome/nochrome_bug1346936.js^headers^
new file mode 100644
index 0000000000..812264590d
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug1346936.js^headers^
@@ -0,0 +1 @@
+SourceMap: foo.js.map
diff --git a/dom/base/test/chrome/nochrome_bug765993.html b/dom/base/test/chrome/nochrome_bug765993.html
new file mode 100644
index 0000000000..158b20c884
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug765993.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<html>
+</html>
diff --git a/dom/base/test/chrome/nochrome_bug765993.js b/dom/base/test/chrome/nochrome_bug765993.js
new file mode 100644
index 0000000000..a84113e1e1
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug765993.js
@@ -0,0 +1,4 @@
+//# sourceMappingURL=bar.js.map
+
+// Define a single function to prevent script source from being gc'd
+function foo() {}
diff --git a/dom/base/test/chrome/nochrome_bug765993.js^headers^ b/dom/base/test/chrome/nochrome_bug765993.js^headers^
new file mode 100644
index 0000000000..8efacff3c8
--- /dev/null
+++ b/dom/base/test/chrome/nochrome_bug765993.js^headers^
@@ -0,0 +1 @@
+X-SourceMap: foo.js.map
diff --git a/dom/base/test/chrome/test_bug1063837.xhtml b/dom/base/test/chrome/test_bug1063837.xhtml
new file mode 100644
index 0000000000..794cf1c72c
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1063837.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=206691
+-->
+<window title="Mozilla Bug 1063837"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1063837"
+ target="_blank">Mozilla Bug 1063837</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1063837 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", location, false);
+ xhr.onload = function() {
+ ok(xhr.responseXML, "We should have response content!");
+ var principal = xhr.responseXML.nodePrincipal;
+ ok(principal.schemeIs("moz-nullprincipal"), "The response document should have a null principal");
+ SimpleTest.finish();
+ }
+ xhr.send();
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml
new file mode 100644
index 0000000000..dbedb59abe
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098074
+-->
+<window title="Mozilla Bug 1098074"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 1098074 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.expectUncaughtException();
+
+ // Tell the test to expect exactly one console error with the given parameters,
+ // with SimpleTest.finish as a continuation function.
+ SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('acopia')}]);
+
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService();
+ globalMM.addMessageListener("flimfniffle", function onMessage(msg) {
+ globalMM.removeMessageListener("flimfniffle", onMessage);
+ is(msg.data, "teufeltor", "correct message");
+
+ // Cleanup the monitor after we throw.
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+
+ throw "acopia";
+ });
+
+ function start() {
+ globalMM.loadFrameScript("data:,sendAsyncMessage('flimfniffle', 'teufeltor')", true);
+ }
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098074"
+ target="_blank">Mozilla Bug 1098074</a>
+ </body>
+</window>
diff --git a/dom/base/test/chrome/test_bug1139964.xhtml b/dom/base/test/chrome/test_bug1139964.xhtml
new file mode 100644
index 0000000000..8b1b36fa64
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1139964.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1139964
+-->
+<window title="Mozilla Bug 1139964"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1139964"
+ target="_blank">Mozilla Bug 1139964</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1139964 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("file_bug1139964.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug120684.xhtml b/dom/base/test/chrome/test_bug120684.xhtml
new file mode 100644
index 0000000000..08e9b28cfe
--- /dev/null
+++ b/dom/base/test/chrome/test_bug120684.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=120684
+-->
+<window title="Mozilla Bug 120684"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=120684"
+ target="_blank">Mozilla Bug 120684</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 120684 **/
+
+ var list = new ChromeNodeList();
+ is(list.length, 0, "Length should be initially 0.");
+
+ ok(NodeList.isInstance(list), "ChromeNodeList object should be an instance of NodeList.");
+
+ try {
+ list.append(document);
+ ok(false, "should have throw!");
+ } catch(ex) {
+ ok(true, "ChromeNodeList supports only nsIContent objects for now.");
+ }
+
+ try {
+ list.remove(document);
+ ok(false, "should have throw!");
+ } catch(ex) {
+ ok(true, "ChromeNodeList supports only nsIContent objects for now.");
+ }
+ is(list.length, 0, "Length should be 0.");
+
+ list.append(document.documentElement);
+ is(list.length, 1, "Length should be 1.");
+ is(list[0], document.documentElement);
+ is(list[1], undefined);
+
+ // Removing element which isn't in the list shouldn't do anything.
+ list.remove(document.createXULElement("foo"));
+ is(list.length, 1, "Length should be 1.");
+ is(list[0], document.documentElement);
+
+ list.remove(document.documentElement);
+ is(list.length, 0, "Length should be 0.");
+ is(list[0], undefined);
+
+ var e1 = document.createXULElement("foo");
+ var e2 = document.createXULElement("foo");
+ var e3 = document.createXULElement("foo");
+
+ list.append(e1);
+ list.append(e2);
+ list.append(e3);
+
+ is(list[0], e1);
+ is(list[1], e2);
+ is(list[2], e3);
+ is(list.length, 3);
+
+ list.remove(e2);
+ is(list[0], e1);
+ is(list[1], e3);
+ is(list[2], undefined);
+ is(list.length, 2);
+
+ // A leak test.
+ list.expando = list;
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug1209621.xhtml b/dom/base/test/chrome/test_bug1209621.xhtml
new file mode 100644
index 0000000000..947606b638
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1209621.xhtml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1209621
+-->
+<window title="Mozilla Bug 1209621"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1209621"
+ target="_blank">Mozilla Bug 1209621</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 1209621 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("file_bug1209621.xhtml", "", "chrome,noopener", window);
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug1339722.html b/dom/base/test/chrome/test_bug1339722.html
new file mode 100644
index 0000000000..d8d95f1faa
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1339722.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+ <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1339722
+-->
+ <head>
+ <meta charset="utf-8" />
+ <title>Test for Bug 1339722</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin" />
+ <link
+ rel="stylesheet"
+ type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ />
+ <script type="application/javascript">
+ /**
+ * Test for Bug 1339722
+ * 1. Wait for "http-on-modify-request" or document-on-modify-request for the
+ * iframe load.
+ * 2. In the observer, access it's window proxy to trigger DOMWindowCreated.
+ * 3. In the event handler, delete the iframe so that the frameloader would be
+ * destroyed in the middle of ReallyStartLoading.
+ * 4. Verify that it doesn't crash.
+ **/
+
+ // This topic used to be http-on-useragent-request, but that got removed in
+ // bug 1513574. on-modify-request is called around the same time, and should
+ // behave similarly.
+ const TOPIC = "document-on-modify-request";
+ let win;
+ const observe = (subject, topic, data) => {
+ info("Got " + topic);
+ Services.obs.removeObserver(observe, TOPIC);
+
+ // Query window proxy so it triggers DOMWindowCreated.
+ let channel;
+ try {
+ // We need to QI nsIHttpChannel in order to load the interface's
+ // methods / attributes for later code that could assume we are dealing
+ // with a nsIHttpChannel.
+ channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ } catch (e) {
+ channel = subject.QueryInterface(Ci.nsIIdentChannel);
+ }
+ win = channel.notificationCallbacks.getInterface(Ci.mozIDOMWindowProxy);
+ };
+
+ Services.obs.addObserver(observe, TOPIC);
+
+ let docShell = SpecialPowers.wrap(window).docShell;
+ docShell.chromeEventHandler.addEventListener(
+ "DOMWindowCreated",
+ function handler(e) {
+ info("Got DOMWindowCreated");
+ let iframe = document.getElementById("testFrame");
+ is(e.target, iframe.contentDocument, "verify event target");
+
+ // Remove the iframe to cause frameloader destroy.
+ iframe.remove();
+ setTimeout($ => {
+ ok(!document.getElementById("testFrame"), "verify iframe removed");
+ SimpleTest.finish();
+ }, 0);
+ },
+ { once: true }
+ );
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </head>
+ <body>
+ <a
+ target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1339722"
+ >Mozilla Bug 1339722</a
+ >
+ <p id="display"></p>
+ <div id="content" style="display: none;"></div>
+ <pre id="test">
+ <div id="frameContainer">
+ <iframe id="testFrame" src="http://www.example.com"></iframe>
+ </div>
+</pre>
+ </body>
+</html>
diff --git a/dom/base/test/chrome/test_bug1346936.html b/dom/base/test/chrome/test_bug1346936.html
new file mode 100644
index 0000000000..2c61c65237
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1346936.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1346936
+-->
+<head>
+ <title>Test for Bug 1346936</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346936">Mozilla Bug 1346936</a>
+<style type="text/css">
+#link1 a { user-select:none; }
+</style>
+<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>
+<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1346936 **/
+
+const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.html";
+ iframe.onload = function() {
+ var script = iframe.contentWindow.document.createElement("script");
+ script.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js";
+ script.onload = function() {
+ var dbg = new Debugger(iframe.contentWindow);
+ ok(dbg, "Should be able to create debugger");
+
+ var scripts = dbg.findScripts({
+ url: "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js",
+ });
+ ok(scripts.length, "Should be able to find script");
+
+ is(scripts[0].source.sourceMapURL, "foo.js.map");
+ SimpleTest.finish();
+ };
+
+ iframe.contentWindow.document.body.appendChild(script);
+ };
+
+ document.body.appendChild(iframe);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug206691.xhtml b/dom/base/test/chrome/test_bug206691.xhtml
new file mode 100644
index 0000000000..16a27762ac
--- /dev/null
+++ b/dom/base/test/chrome/test_bug206691.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=206691
+-->
+<window title="Mozilla Bug 206691"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=206691"
+ target="_blank">Mozilla Bug 206691</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 206691 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", location, false);
+ xhr.send();
+ ok(xhr.responseText, "We should have response content!");
+ SimpleTest.finish();
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug289714.xhtml b/dom/base/test/chrome/test_bug289714.xhtml
new file mode 100644
index 0000000000..4b4cc6fb84
--- /dev/null
+++ b/dom/base/test/chrome/test_bug289714.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=289714
+-->
+<window title="Mozilla Bug 289714"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=289714"
+ target="_blank">Mozilla Bug 289714</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /** Test for Bug 289714 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ let xhr = new XMLHttpRequest();
+ xhr.responseType = "document";
+ xhr.open("GET", "data:text/xml,<xml");
+ ok(xhr.channel !== undefined, "System XHRs should be privileged");
+ xhr.onload = () => {
+ ok(xhr.responseXML !== null, "System XHRs should yield <parsererrors>");
+ SimpleTest.finish();
+ };
+ xhr.send();
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug339494.xhtml b/dom/base/test/chrome/test_bug339494.xhtml
new file mode 100644
index 0000000000..9922c41b66
--- /dev/null
+++ b/dom/base/test/chrome/test_bug339494.xhtml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=339494
+-->
+<window title="Mozilla Bug 339494"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=339494">Mozilla Bug 339494</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <xul:hbox id="d"/>
+ <xul:hbox id="s"/>
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 339494 **/
+
+ var d = document.getElementById("d");
+
+ d.setAttribute("hhh", "testvalue");
+
+ document.addEventListener("DOMAttrModified", removeItAgain, false);
+ d.removeAttribute("hhh");
+ document.removeEventListener("DOMAttrModified", removeItAgain, false);
+
+ function removeItAgain()
+ {
+ ok(!d.hasAttribute("hhh"), "Value check 1. There should be no value");
+ isnot(d.getAttribute("hhh"), "testvalue", "Value check 2");
+ document.removeEventListener("DOMAttrModified", removeItAgain, false);
+ d.removeAttribute("hhh");
+ ok(true, "Reachability. We shouldn't have crashed");
+ }
+
+ var s = document.getElementById("s");
+
+ s.setAttribute("ggg", "testvalue");
+
+ document.addEventListener("DOMAttrModified", compareVal, false);
+ s.setAttribute("ggg", "othervalue");
+ document.removeEventListener("DOMAttrModified", compareVal, false);
+
+ function compareVal()
+ {
+ ok(s.hasAttribute("ggg"), "Value check 3. There should be a value");
+ isnot(s.getAttribute("ggg"), "testvalue", "Value check 4");
+ is(s.getAttribute("ggg"), "othervalue", "Value check 5");
+ }
+
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_bug357450.xhtml b/dom/base/test/chrome/test_bug357450.xhtml
new file mode 100644
index 0000000000..7723364ecc
--- /dev/null
+++ b/dom/base/test/chrome/test_bug357450.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357450
+-->
+
+<window title="Mozilla Bug 357450"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- This file is shared with non-chrome tests -->
+ <script type="text/javascript" src="file_bug357450.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=357450"
+ target="_blank">Mozilla Bug 357450</a>
+
+<p id="display"></p>
+
+<div id="content" style="display: block">
+ <a class="classtest" name="nametest">hmm</a>
+ <b class="foo">hmm</b>
+ <b id="test1" class="test1">hmm</b>
+ <b id="test2" class="test2">hmm</b>
+ <b id="int-class" class="1">hmm</b>
+ <div id="example">
+ <p id="p1" class="aaa bbb"/>
+ <p id="p2" class="aaa ccc"/>
+ <p id="p3" class="bbb ccc"/>
+ </div>
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ height="100" width="100" style="float:left">
+
+ <path d="M38,38c0-12,24-15,23-2c0,9-16,13-16,23v7h11v-4c0-9,17-12,17-27c-2-22-45-22-45,3zM45,70h11v11h-11z" fill="#371"/>
+
+ <circle cx="50" cy="50" r="45" class="classtest"
+ fill="none" stroke="#371" stroke-width="10"/>
+
+</svg>
+
+<xul:label class="classtest"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ hmm
+</xul:label>
+
+
+</window>
diff --git a/dom/base/test/chrome/test_bug380418.html b/dom/base/test/chrome/test_bug380418.html
new file mode 100644
index 0000000000..eb3f8d3042
--- /dev/null
+++ b/dom/base/test/chrome/test_bug380418.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=380418 -->
+<head>
+ <title>Test for Bug 380418</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=380418">Mozilla Bug 380418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var xhrPath = "http://mochi.test:8888" +
+ window.location.pathname.substring("/content".length);
+
+ var request = new XMLHttpRequest();
+ request.open("GET", xhrPath, false);
+ request.send(null);
+
+ // Try reading headers in privileged context
+ is(request.getResponseHeader("Set-Cookie"), "test", "Reading Set-Cookie response header in privileged context");
+ is(request.getResponseHeader("Set-Cookie2"), "test2", "Reading Set-Cookie2 response header in privileged context");
+ is(request.getResponseHeader("X-Dummy"), "test", "Reading X-Dummy response header in privileged context");
+
+ ok(/\bSet-Cookie:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie in all response headers in privileged context");
+ ok(/\bSet-Cookie2:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie2 in all response headers in privileged context");
+ ok(/\bX-Dummy:/i.test(request.getAllResponseHeaders()), "Looking for X-Dummy in all response headers in privileged context");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug380418.html^headers^ b/dom/base/test/chrome/test_bug380418.html^headers^
new file mode 100644
index 0000000000..5f8d4969c0
--- /dev/null
+++ b/dom/base/test/chrome/test_bug380418.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: test
+Set-Cookie2: test2
+X-Dummy: test
+Cache-Control: max-age=0
diff --git a/dom/base/test/chrome/test_bug383430.html b/dom/base/test/chrome/test_bug383430.html
new file mode 100644
index 0000000000..ce526ef281
--- /dev/null
+++ b/dom/base/test/chrome/test_bug383430.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=383430
+-->
+<head>
+ <title>Test for Bug 383430</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=383430">Mozilla Bug 383430</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 383430 **/
+
+var req = new XMLHttpRequest();
+req.open("GET", window.location.href);
+req.send(null);
+
+ok(req.channel.loadGroup != null, "loadGroup is not null");
+
+req = new XMLHttpRequest();
+req.mozBackgroundRequest = true;
+req.open("GET", window.location.href);
+req.send(null);
+
+ok(req.channel.loadGroup == null, "loadGroup is null");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug418986-1.xhtml b/dom/base/test/chrome/test_bug418986-1.xhtml
new file mode 100644
index 0000000000..7d3add900a
--- /dev/null
+++ b/dom/base/test/chrome/test_bug418986-1.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1
+-->
+<window title="Mozilla Bug 418986 (Part 1)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1"
+ target="_blank">Mozilla Bug 418986 (Part 1)</a>
+
+ <script type="application/javascript" src="bug418986-1.js"></script>
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ window.onload = function() {
+ test(false);
+ };
+ ]]></script>
+ </body>
+</window>
diff --git a/dom/base/test/chrome/test_bug421622.xhtml b/dom/base/test/chrome/test_bug421622.xhtml
new file mode 100644
index 0000000000..236c42dd34
--- /dev/null
+++ b/dom/base/test/chrome/test_bug421622.xhtml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421622
+-->
+<window title="Mozilla Bug 421622"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=421622"
+ target="_blank">Mozilla Bug 421622</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 421622 **/
+ const SJS_URL = "http://mochi.test:8888/tests/dom/base/test/chrome/bug421622-referer.sjs";
+ const REFERER_URL = "http://www.mozilla.org/";
+
+ var req = new XMLHttpRequest();
+ req.open("GET", SJS_URL, false);
+ req.setRequestHeader("Referer", REFERER_URL);
+ req.send(null);
+
+ is(req.responseText,
+ "Referer: " + REFERER_URL,
+ "Referer header received by server does not match what was set");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug429785.xhtml b/dom/base/test/chrome/test_bug429785.xhtml
new file mode 100644
index 0000000000..f4f6b414b4
--- /dev/null
+++ b/dom/base/test/chrome/test_bug429785.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429785
+-->
+<window title="Mozilla Bug 429785"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=429785"
+ target="_blank">Mozilla Bug 429785</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /** Test for Bug 429785 **/
+ SimpleTest.waitForExplicitFinish();
+ var errorLogged = false;
+ const serv = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+ var listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
+ observe : function (msg) { errorLogged = true; }
+ };
+
+ function step2() {
+ is(errorLogged, false, "Should be no errors");
+
+ serv.logStringMessage("This is a test");
+
+ setTimeout(step3, 0);
+
+ }
+
+ function step3() {
+ is(errorLogged, true, "Should see errors when they happen");
+ serv.unregisterListener(listener);
+ SimpleTest.finish();
+ }
+
+ serv.registerListener(listener);
+
+ var p = new DOMParser();
+ p.parseFromString("<root/>", "application/xml");
+
+ // nsConsoleService notifies its listeners via async proxies, so we need
+ // to wait to see whether there was an error reported.
+ setTimeout(step2, 0);
+
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug430050.xhtml b/dom/base/test/chrome/test_bug430050.xhtml
new file mode 100644
index 0000000000..f6f005fae2
--- /dev/null
+++ b/dom/base/test/chrome/test_bug430050.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430050
+-->
+<window title="Mozilla Bug 430050"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=430050"
+ target="_blank">Mozilla Bug 430050</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 430050 **/
+
+ function endTest() {
+ ok(document.getElementById('b').contentDocument.documentElement.textContent ==
+ "succeeded", "Wrong URL loaded!");
+ SimpleTest.finish();
+ }
+
+ function startTest() {
+ document.documentElement.addEventListener('DOMAttrModified',
+ function(evt) {
+ if (evt.target == evt.currentTarget) {
+ document.getElementById('b').setAttribute("src",
+ "data:text/plain,failed");
+ const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ document.getElementById('b').loadURI(Services.io.newURI('data:text/plain,succeeded'), {
+ triggeringPrincipal: systemPrincipal
+ });
+ document.getElementById('b').addEventListener("load", endTest);
+ }
+ }, true);
+ document.documentElement.setAttribute("foo", "bar");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(startTest);
+
+ ]]></script>
+ <browser flex="1" id="b"/>
+</window>
diff --git a/dom/base/test/chrome/test_bug467123.xhtml b/dom/base/test/chrome/test_bug467123.xhtml
new file mode 100644
index 0000000000..222883badf
--- /dev/null
+++ b/dom/base/test/chrome/test_bug467123.xhtml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=467123
+-->
+<window title="Mozilla Bug 467123"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467123"
+ target="_blank">Mozilla Bug 467123</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /** Test for Bug 467123 **/
+ let url = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(document.location.href);
+ let file = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIChromeRegistry)
+ .convertChromeURL(url)
+ .QueryInterface(Ci.nsIFileURL)
+ .file.parent;
+ file.append("clonedoc");
+ Components.manager.addBootstrappedManifestLocation(file);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "chrome://clonedoc/content/doc.xml", false);
+ xhr.send();
+ ok(xhr.responseXML, "We should have response document!");
+ var e = null;
+ try {
+ var clone = xhr.responseXML.cloneNode(true);
+ } catch (ex) {
+ e = ex;
+ }
+ ok(!e, e);
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug473284.xhtml b/dom/base/test/chrome/test_bug473284.xhtml
new file mode 100644
index 0000000000..87c778a615
--- /dev/null
+++ b/dom/base/test/chrome/test_bug473284.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473284
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+onload="
+var result = '';
+try {
+ document.commandDispatcher.advanceFocusIntoSubtree({});
+ result += '1';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
+ result += '2';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.advanceFocusIntoSubtree(null);
+ result += '3';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedElement = {};
+ result += '4';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedElement = document.documentElement;
+ result += '5';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedElement = null;
+ result += '6';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedWindow = {};
+ result += 'a';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedWindow = null;
+ result += 'b';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.commandDispatcher.focusedWindow = window;
+ result += 'c';
+} catch (ex) {
+ result += '.';
+}
+
+is(result, '.23.56.bc', 'The correct assignments throw.');
+">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=473284"
+ target="_blank">Mozilla Bug 473284</a>
+ </body>
+</window>
diff --git a/dom/base/test/chrome/test_bug549682.xhtml b/dom/base/test/chrome/test_bug549682.xhtml
new file mode 100644
index 0000000000..0f1ecf646a
--- /dev/null
+++ b/dom/base/test/chrome/test_bug549682.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549682
+-->
+<window title="Mozilla Bug 549682"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=549682"
+ target="_blank">Mozilla Bug 549682</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 549682 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("file_bug549682.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug571390.xhtml b/dom/base/test/chrome/test_bug571390.xhtml
new file mode 100644
index 0000000000..ea1f357a5d
--- /dev/null
+++ b/dom/base/test/chrome/test_bug571390.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=571390
+-->
+<window title="Mozilla Bug 571390"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="foo bar">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=571390"
+ target="_blank">Mozilla Bug 571390</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 571390 **/
+
+ is(document.documentElement.classList.length, 2, "Should have 2 classes.");
+ ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class.");
+ ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class.");
+ ok(!document.documentElement.classList.contains("foobar"), "Shouldn't contain 'foobar' class.");
+
+ document.documentElement.classList.add("foobar");
+ is(document.documentElement.classList.length, 3, "Should have 3 classes.");
+ ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class.");
+ ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class.");
+ ok(document.documentElement.classList.contains("foobar"), "Should contain 'foobar' class.");
+
+ document.documentElement.classList.remove("foobar");
+ is(document.documentElement.classList.length, 2, "Should have 2 classes.");
+ ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class.");
+ ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class.");
+ ok(!document.documentElement.classList.contains("foobar"), "Shouldn't contain 'foobar' class.");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug616841.xhtml b/dom/base/test/chrome/test_bug616841.xhtml
new file mode 100644
index 0000000000..f5907f0b0b
--- /dev/null
+++ b/dom/base/test/chrome/test_bug616841.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=616841
+-->
+<window title="Mozilla Bug 616841"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=616841"
+ target="_blank">Mozilla Bug 616841</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("file_bug616841.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug635835.xhtml b/dom/base/test/chrome/test_bug635835.xhtml
new file mode 100644
index 0000000000..69bb3ae68b
--- /dev/null
+++ b/dom/base/test/chrome/test_bug635835.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635835
+-->
+<window title="Mozilla Bug 635835"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=635835"
+ target="_blank">Mozilla Bug 635835</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+SimpleTest.waitForExplicitFinish();
+const SHOW_ALL = NodeFilter.SHOW_ALL;
+
+addLoadEvent(function() {
+ var walker = document.createTreeWalker(document, SHOW_ALL, null);
+ try {
+ walker.currentNode = {};
+ walker.nextNode();
+ }
+ catch (e) {
+ // do nothing - this is a crash test
+ }
+ ok(true, "Crash test passed");
+ SimpleTest.finish();
+});
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug682305.html b/dom/base/test/chrome/test_bug682305.html
new file mode 100644
index 0000000000..d500dc91d5
--- /dev/null
+++ b/dom/base/test/chrome/test_bug682305.html
@@ -0,0 +1,150 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682305
+-->
+<head>
+ <title>XMLHttpRequest send and channel implemented in JS</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=682305">Mozilla Bug 682305</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * Register a custom nsIProtocolHandler service
+ * in order to be able to implement *and use* an
+ * nsIChannel component written in Javascript.
+ */
+
+const { ComponentUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/ComponentUtils.sys.mjs"
+);
+
+var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
+ .getService(Ci.nsIContentSecurityManager);
+
+var PROTOCOL_SCHEME = "jsproto";
+
+
+function CustomChannel(uri, loadInfo) {
+ this.URI = this.originalURI = uri;
+ this.loadInfo = loadInfo;
+}
+CustomChannel.prototype = {
+ URI: null,
+ originalURI: null,
+ loadInfo: null,
+ contentCharset: "utf-8",
+ contentLength: 0,
+ contentType: "text/plain",
+ owner: Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal),
+ securityInfo: null,
+ notificationCallbacks: null,
+ loadFlags: 0,
+ loadGroup: null,
+ name: null,
+ status: Cr.NS_OK,
+ asyncOpen(listener) {
+ // throws an error if security checks fail
+ var outListener = contentSecManager.performSecurityCheck(this, listener);
+ let stream = this.open();
+ try {
+ outListener.onStartRequest(this);
+ } catch (e) {}
+ try {
+ outListener.onDataAvailable(this, stream, 0, stream.available());
+ } catch (e) {}
+ try {
+ outListener.onStopRequest(this, Cr.NS_OK);
+ } catch (e) {}
+ },
+ open() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+
+ let data = "bar";
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+ return stream;
+ },
+ isPending() {
+ return false;
+ },
+ cancel() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ suspend() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ resume() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel", "nsIRequest"]),
+};
+
+
+function CustomProtocol() {}
+CustomProtocol.prototype = {
+ get scheme() {
+ return PROTOCOL_SCHEME;
+ },
+ allowPort: function allowPort() {
+ return false;
+ },
+ newChannel: function newChannel(URI, loadInfo) {
+ return new CustomChannel(URI, loadInfo);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference", "nsIProtocolHandler"]),
+};
+
+var gFactory = {
+ register() {
+ Services.io.registerProtocolHandler(
+ PROTOCOL_SCHEME,
+ new CustomProtocol(),
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+ -1
+ );
+
+ this.unregister = function() {
+ Services.io.unregisterProtocolHandler(PROTOCOL_SCHEME);
+ delete this.unregister;
+ };
+ },
+};
+
+// Register the custom procotol handler
+gFactory.register();
+
+// Then, checks if XHR works with it
+var xhr = new XMLHttpRequest();
+xhr.open("GET", PROTOCOL_SCHEME + ":foo", true);
+xhr.onload = function() {
+ is(xhr.responseText, "bar", "protocol doesn't work");
+ gFactory.unregister();
+ SimpleTest.finish();
+};
+try {
+ xhr.send(null);
+} catch (e) {
+ ok(false, e);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug683852.xhtml b/dom/base/test/chrome/test_bug683852.xhtml
new file mode 100644
index 0000000000..1f9e0d9472
--- /dev/null
+++ b/dom/base/test/chrome/test_bug683852.xhtml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=683852
+-->
+<window title="Mozilla Bug 683852"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gaktekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script>
+ <![CDATA[
+ customElements.define("custom-element", class extends XULElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+ });
+ ]]>
+ </script>
+ <html:template id="template"><xul:box anonid="anon">Anonymous</xul:box></html:template>
+ <custom-element id="custom-element"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=683852"
+ target="_blank" id="link">Mozilla Bug 683852</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 683852 **/
+ SimpleTest.waitForExplicitFinish();
+
+ const NS_HTML = "http://www.w3.org/1999/xhtml";
+
+ function startTest() {
+ is(document.contains(document), true, "Document should contain itself!");
+
+ let box = document.getElementById("custom-element");
+ is(document.contains(box), true, "Document should contain element in it!");
+ is(box.contains(box), true, "Element should contain itself.")
+ let anon = box.shadowRoot.querySelector("[anonid=anon]");
+ is(document.contains(anon), false, "Document should not contain anonymous element in it!");
+ is(box.contains(anon), false, "Element should not contain anonymous element in it!");
+ is(anon.contains(anon), true, "Anonymous element should contain itself.")
+ is(document.documentElement.contains(box), true, "Element should contain element in it!");
+ is(document.contains(document.createXULElement("foo")), false, "Document shouldn't contain element which is't in the document");
+ is(document.contains(document.createTextNode("foo")), false, "Document shouldn't contain text node which is't in the document");
+
+ var link = document.getElementById("link");
+ is(document.contains(link.firstChild), true,
+ "Document should contain a text node in it.");
+ is(link.contains(link.firstChild), true,
+ "Element should contain a text node in it.");
+ is(link.firstChild.contains(link), false, "text node shouldn't contain its parent.");
+
+ is(document.contains(null), false, "Document shouldn't contain null.");
+
+ var pi = document.createProcessingInstruction("adf", "asd");
+ is(pi.contains(document), false, "Processing instruction shouldn't contain document");
+ document.documentElement.appendChild(pi);
+ document.contains(pi, true, "Document should contain processing instruction");
+
+ var df = document.createRange().createContextualFragment(`<div xmlns="${NS_HTML}">foo</div>`);
+ is(df.contains(df.firstChild), true, "Document fragment should contain its child");
+ is(df.contains(df.firstChild.firstChild), true,
+ "Document fragment should contain its descendant");
+ is(df.contains(df), true, "Document fragment should contain itself.");
+
+ var d = document.implementation.createHTMLDocument("");
+ is(document.contains(d), false,
+ "Document shouldn't contain another document.");
+ is(document.contains(d.createElement("div")), false,
+ "Document shouldn't contain an element from another document.");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(startTest);
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug752226-3.xhtml b/dom/base/test/chrome/test_bug752226-3.xhtml
new file mode 100644
index 0000000000..747fb29c4e
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-3.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=752226
+-->
+<window title="Mozilla Bug 752226"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752226"
+ target="_blank">Mozilla Bug 752226</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 752226 **/
+ try {
+ new File(null);
+ } catch (e) {}
+ ok(true, "Didn't crash");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug752226-4.xhtml b/dom/base/test/chrome/test_bug752226-4.xhtml
new file mode 100644
index 0000000000..242e231a2e
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-4.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=752226
+-->
+<window title="Mozilla Bug 752226"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752226"
+ target="_blank">Mozilla Bug 752226</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 752226 **/
+ try {
+ new Cu.Sandbox("about:blank", null);
+ } catch (e) {}
+ ok(true, "Didn't crash");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug765993.html b/dom/base/test/chrome/test_bug765993.html
new file mode 100644
index 0000000000..3325c3713d
--- /dev/null
+++ b/dom/base/test/chrome/test_bug765993.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=765993
+-->
+<head>
+ <title>Test for Bug 765993</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=765993">Mozilla Bug 765993</a>
+<style type="text/css">
+#link1 a { user-select:none; }
+</style>
+<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>
+<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 765993 **/
+
+const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.html";
+ iframe.onload = function() {
+ var script = iframe.contentWindow.document.createElement("script");
+ script.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.js";
+ script.onload = function() {
+ var dbg = new Debugger(iframe.contentWindow);
+ ok(dbg, "Should be able to create debugger");
+
+ var scripts = dbg.findScripts({
+ url: "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.js",
+ });
+ ok(scripts.length, "Should be able to find script");
+
+ is(scripts[0].source.sourceMapURL, "foo.js.map");
+ SimpleTest.finish();
+ };
+
+ iframe.contentWindow.document.body.appendChild(script);
+ };
+
+ document.body.appendChild(iframe);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug780199.xhtml b/dom/base/test/chrome/test_bug780199.xhtml
new file mode 100644
index 0000000000..e27afb72fa
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780199.xhtml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780199
+-->
+<window title="Mozilla Bug 780199"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780199"
+ target="_blank">Mozilla Bug 780199</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 780199 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var b;
+
+ function callback(r) {
+ is(r[0].type, "attributes");
+ is(r[0].oldValue, b.getAttribute("src"));
+ setTimeout(continueTest, 500);
+ }
+
+ function continueTest() {
+ // Check that a new page wasn't loaded.
+ is(b.contentDocument.documentElement.textContent, "testvalue");
+ SimpleTest.finish();
+ }
+
+ function test() {
+ b = document.getElementById("b");
+ var m = new MutationObserver(callback);
+ m.observe(b, { attributes: true, attributeOldValue: true });
+ b.contentDocument.documentElement.textContent = "testvalue";
+ b.setAttribute("src", b.getAttribute("src"));
+ }
+
+ ]]>
+ </script>
+ <browser id="b" src="data:text/plain,initial"/>
+</window>
diff --git a/dom/base/test/chrome/test_bug780529.xhtml b/dom/base/test/chrome/test_bug780529.xhtml
new file mode 100644
index 0000000000..bf8b8b2981
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780529.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780529
+-->
+<window title="Mozilla Bug 780529"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780529"
+ target="_blank">Mozilla Bug 780529</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 780529 **/
+var req = new XMLHttpRequest();
+req.open("GET", "", true);
+// Have to call send() to get the XHR hooked up as the notification callbacks
+req.send();
+var callbacks = req.channel.notificationCallbacks;
+var sink = callbacks.getInterface(Ci.nsIChannelEventSink);
+ok(sink instanceof Ci.nsIChannelEventSink,
+ "Should be a channel event sink")
+ok("asyncOnChannelRedirect" in sink,
+ "Should have the right methods for an event sink");
+
+let sinkReq = sink.QueryInterface(Ci.nsIInterfaceRequestor);
+isnot(sinkReq, callbacks, "Sink should not be the XHR object");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug800386.xhtml b/dom/base/test/chrome/test_bug800386.xhtml
new file mode 100644
index 0000000000..681c9a9794
--- /dev/null
+++ b/dom/base/test/chrome/test_bug800386.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=800386
+-->
+<window title="Mozilla Bug 800386"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=800386"
+ target="_blank">Mozilla Bug 800386</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 800386 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var triedForwarding = false;
+ var forwardFailed = false;
+
+ var xhr = new XMLHttpRequest;
+ var xhr2 = new XMLHttpRequest;
+
+ var eventSink = xhr.getInterface(Ci.nsIProgressEventSink);
+ isnot(eventSink, null, "Should get event sink directly!");
+
+ // Now jump through some hoops to get us a getInterface call from C++
+
+ var requestor = {
+ getInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIProgressEventSink)) {
+ triedForwarding = true;
+ try {
+ return xhr2.getInterface(aIID);
+ } catch (e) {
+ forwardFailed = true;
+ }
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"])
+ };
+
+ // HTTP URI so that we get progress callbacks
+ xhr.open("GET", "http://mochi.test:8888/", false);
+ xhr.channel.notificationCallbacks = requestor;
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(triedForwarding,
+ "Should have had an attempt to treat us as a progress event sink");
+ ok(!forwardFailed,
+ "Should have been able to forward getInterface on to the XHR");
+ SimpleTest.finish();
+ }
+ }
+ xhr.send();
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug816340.xhtml b/dom/base/test/chrome/test_bug816340.xhtml
new file mode 100644
index 0000000000..66bd26660e
--- /dev/null
+++ b/dom/base/test/chrome/test_bug816340.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816340
+-->
+<window title="Mozilla Bug 816340"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816340"
+ target="_blank">Mozilla Bug 816340</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("file_bug816340.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug884693.xhtml b/dom/base/test/chrome/test_bug884693.xhtml
new file mode 100644
index 0000000000..db6eca88a8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug884693.xhtml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=884693
+-->
+<window title="Mozilla Bug 884693"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=884693"
+ target="_blank">Mozilla Bug 884693</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ const SERVER_URL = "http://mochi.test:8888/tests/dom/base/test/chrome/bug884693.sjs";
+ const INVALID_XML = "InvalidXML";
+ const XML_WITHOUT_ROOT = "<?xml version='1.0'?>";
+
+ let consoleService = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService)
+
+ function runTest(status, statusText, body, expectedResponse, expectedMessages)
+ {
+ return new Promise((resolve, reject) => {
+ consoleService.reset();
+
+ let xhr = new XMLHttpRequest();
+
+ xhr.onload = () => {
+ is(xhr.responseText, expectedResponse, "Correct responseText returned");
+
+ let messages = consoleService.getMessageArray() || [];
+ // broadcastlisteners can happen and cause false alarm.
+ messages = messages.filter(msg =>
+ !(msg instanceof Ci.nsIScriptError &&
+ msg.category.includes("chrome javascript") &&
+ msg.message.includes("Unknown event")));
+
+ if (messages.length) {
+ info(`Got console messages ${messages}`);
+ }
+ is(messages.length, expectedMessages.length, "Got expected message count");
+ messages = messages.map(m => m.message).join(",");
+ for(let message of expectedMessages) {
+ ok(messages.includes(message), "Got expected message: " + message);
+ }
+
+ resolve();
+ };
+
+ xhr.onerror = e => {
+ reject(e);
+ };
+
+ xhr.open("GET", `${SERVER_URL}?${status}&${statusText}&${body}`);
+ xhr.send();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest(201, "Created", "", "", []).
+ then(() => { return runTest(201, "Created", INVALID_XML, INVALID_XML, []); }).
+ then(() => { return runTest(202, "Accepted", "", "", []); }).
+ then(() => { return runTest(202, "Accepted", INVALID_XML, INVALID_XML, []); }).
+ then(() => { return runTest(204, "No Content", "", "", []); }).
+ then(() => { return runTest(204, "No Content", INVALID_XML, "", []); }).
+ then(() => { return runTest(205, "Reset Content", "", "", []); }).
+ then(() => { return runTest(205, "Reset Content", INVALID_XML, "", []); }).
+ then(() => { return runTest(304, "Not modified", "", "", []); }).
+ then(() => { return runTest(304, "Not modified", INVALID_XML, "", []); }).
+ then(() => { return runTest(200, "OK", "", "", []); }).
+ then(() => { return runTest(200, "OK", XML_WITHOUT_ROOT, XML_WITHOUT_ROOT, ["no root element found"]); }).
+ then(() => { return runTest(200, "OK", INVALID_XML, INVALID_XML, ["syntax error"]); }).
+ then(SimpleTest.finish);
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug914381.html b/dom/base/test/chrome/test_bug914381.html
new file mode 100644
index 0000000000..eb82ffd0f7
--- /dev/null
+++ b/dom/base/test/chrome/test_bug914381.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 914381</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=914381">Mozilla Bug 914381</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+function createFileWithData(fileData) {
+ var testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ testFile.append("testBug914381");
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ return testFile;
+}
+
+/** Test for Bug 914381. File's created in JS using an nsIFile should allow mozGetFullPathInternal calls to succeed **/
+var file = createFileWithData("Test bug 914381");
+
+SpecialPowers.pushPrefEnv({ set: [["dom.file.createInChild", true]]})
+.then(() => {
+ return File.createFromNsIFile(file);
+})
+.then(f => {
+ is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+ is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
+})
+.then(() => {
+ return File.createFromFileName(file.path);
+})
+.then(f => {
+ is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+ is(f.mozFullPath, "", "mozFullPath returns blank if created with a string");
+})
+.then(() => {
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug990812.xhtml b/dom/base/test/chrome/test_bug990812.xhtml
new file mode 100644
index 0000000000..410d6d2367
--- /dev/null
+++ b/dom/base/test/chrome/test_bug990812.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990812
+-->
+<window title="Mozilla Bug 990812"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=990812"
+ target="_blank">Mozilla Bug 990812</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ "file_bug990812-1.xhtml",
+ "file_bug990812-2.xhtml",
+ "file_bug990812-3.xhtml",
+ "file_bug990812-4.xhtml",
+ "file_bug990812-5.xhtml",
+ ];
+
+ function next() {
+ if (tests.length) {
+ var file = tests.shift();
+ info("-- running " + file);
+ window.openDialog(file, "_blank", "chrome,noopener", window);
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ addLoadEvent(next);
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_chromeOuterWindowID.xhtml b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml
new file mode 100644
index 0000000000..880cfdf8bb
--- /dev/null
+++ b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml
@@ -0,0 +1,137 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1474662
+Test that the chromeOuterWindowID on the MessageManager interface
+works, and that it properly updates when swapping frameloaders between
+windows.
+-->
+<window title="Mozilla Bug 1474662"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1474662"
+ target="_blank">Mozilla Bug 1474662</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {ContentTask} = ChromeUtils.import("resource://testing-common/ContentTask.jsm");
+
+ const BROWSER_DOC = "window_chromeOuterWindowID.xhtml";
+ const TEST_PAGE = "http://example.com";
+ const TEST_PAGE_2 = "http://example.com/browser";
+
+ function getOuterWindowID(win) {
+ return win.docShell.outerWindowID;
+ }
+
+ /**
+ * Takes two <xul:browser>'s that should be in the same document, and
+ * ensures that their frame script environments know the correct value
+ * of the host window's outerWindowID.
+ */
+ async function ensureExpectedChromeOuterWindowIDs(browser1, browser2) {
+ is(browser1.ownerDocument, browser2.ownerDocument,
+ "Both browsers should belong to the same document.");
+ let winID = getOuterWindowID(browser1.ownerGlobal);
+
+ let getChildRootOuterId = browser => {
+ try {
+ return docShell.browserChild?.chromeOuterWindowID;
+ } catch(ex) { }
+
+ // Not a remote tab
+ return content.top.windowRoot.ownerGlobal.docShell.outerWindowID;
+ };
+
+ let browser1ID = await SpecialPowers.spawn(browser1, [], getChildRootOuterId);
+ let browser2ID = await SpecialPowers.spawn(browser2, [], getChildRootOuterId);
+
+ is(browser1ID, winID,
+ "Browser 1 frame script environment should have the correct chromeOuterWindowID");
+ is(browser2ID, winID,
+ "Browser 1 frame script environment should have the correct chromeOuterWindowID");
+ }
+
+ /**
+ * Opens up a BROWSER_DOC test window, and points each browser to a particular
+ * page.
+ *
+ * @param num (Number)
+ * An identifier number for this window. Mainly used as a suffix for the
+ * returned values.
+ * @param page (String)
+ * A URL to load in each <xul:browser>
+ * @returns Promise
+ * The Promise resolves with an object with the following properties:
+ *
+ * win<num>: a reference to the opened window
+ * remote<num>: a reference to the remote browser in the window
+ * nonRemote<num>: a reference to the non-remote browser in the window
+ */
+ async function prepareWindow(num, page) {
+ let win = window.browsingContext.topChromeWindow.open(BROWSER_DOC, "bug1474662-" + num, "chrome,width=200,height=200");
+ await BrowserTestUtils.waitForEvent(win, "load");
+ let remote = win.document.getElementById("remote");
+ let nonRemote = win.document.getElementById("non-remote");
+
+ ok(remote && remote.isRemoteBrowser,
+ "Should have found a remote browser in test window " + num);
+ ok(nonRemote && !nonRemote.isRemoteBrowser,
+ "Should have found a non-remote browser in test window " + num);
+
+ BrowserTestUtils.loadURIString(remote, page);
+ await BrowserTestUtils.browserLoaded(remote);
+ BrowserTestUtils.loadURIString(nonRemote, page);
+ await BrowserTestUtils.browserLoaded(nonRemote);
+
+ let result = {};
+ result["win" + num] = win;
+ result["remote" + num] = remote;
+ result["nonRemote" + num] = nonRemote;
+ return result;
+ }
+
+ add_task(async () => {
+ let { win1, remote1, nonRemote1 } = await prepareWindow(1, TEST_PAGE);
+ let { win2, remote2, nonRemote2 } = await prepareWindow(2, TEST_PAGE_2);
+
+ let win1ID = getOuterWindowID(win1);
+ let win2ID = getOuterWindowID(win2);
+
+ // Quick sanity-test here - if something has gone horribly wrong
+ // and the windows have the same IDs, then the rest of this test
+ // is meaningless.
+ isnot(win1ID, win2ID,
+ "The windows should definitely have different IDs.");
+
+ await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1);
+ await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2);
+
+ // Swap the frameloaders! This doesn't swap the <browser> elements though,
+ // so what's expected is that the IDs should remain the same within
+ // the browsers despite the underlying content changing.
+ remote1.swapFrameLoaders(remote2);
+ nonRemote1.swapFrameLoaders(nonRemote2);
+
+ await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1);
+ await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2);
+
+ // Now swap them back.
+ remote1.swapFrameLoaders(remote2);
+ nonRemote1.swapFrameLoaders(nonRemote2);
+
+ await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1);
+ await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2);
+
+ await BrowserTestUtils.closeWindow(win1);
+ await BrowserTestUtils.closeWindow(win2);
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_custom_element_content.xhtml b/dom/base/test/chrome/test_custom_element_content.xhtml
new file mode 100644
index 0000000000..7778bc350e
--- /dev/null
+++ b/dom/base/test/chrome/test_custom_element_content.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1130028
+-->
+<window title="Mozilla Bug 1130028"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028"
+ target="_blank">Mozilla Bug 1130028</a>
+ <iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_custom_element_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1130028 **/
+ var connectedCallbackCount = 0;
+
+ function startTests() {
+ var frame = $("frame");
+
+ class XFoo extends frame.contentWindow.HTMLElement {};
+ frame.contentWindow.customElements.define("x-foo", XFoo);
+ var elem = new XFoo();
+ is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
+
+ class XBar extends frame.contentWindow.HTMLElement {
+ constructor() {
+ super();
+ this.magicNumber = 42;
+ }
+
+ connectedCallback() {
+ connectedCallbackCount++;
+ // Callback should be called only once by element created in content.
+ is(connectedCallbackCount, 1, "Connected callback called, should be called once in test.");
+ is(this.magicNumber, 42, "Callback should be able to see the custom prototype.");
+ }
+ };
+
+ frame.contentWindow.customElements.define("x-bar", XBar);
+ is(connectedCallbackCount, 1, "Connected callback should be called by element created in content.");
+
+ var element = frame.contentDocument.createElement("x-bar");
+ is(element.magicNumber, 42, "Should be able to see the custom prototype on created element.");
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_custom_element_ep.xhtml b/dom/base/test/chrome/test_custom_element_ep.xhtml
new file mode 100644
index 0000000000..28b4e876f5
--- /dev/null
+++ b/dom/base/test/chrome/test_custom_element_ep.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1130028
+-->
+<window title="Mozilla Bug 1130028"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028"
+ target="_blank">Mozilla Bug 1130028</a>
+ <iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_custom_element_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1130028 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function finishTest(canSeePrototype) {
+ ok(true, "connectedCallback called when reigsterElement was called with an extended principal.");
+ ok(canSeePrototype, "connectedCallback should be able to see custom prototype.");
+ SimpleTest.finish();
+ }
+
+ function startTests() {
+ var frame = $("frame");
+
+ // Create a sandbox with an extended principal then run a script that registers a custom element in the sandbox.
+ var sandbox = Cu.Sandbox([frame.contentWindow], { sandboxPrototype: frame.contentWindow });
+ sandbox.finishTest = finishTest;
+ Services.scriptloader.loadSubScript("chrome://mochitests/content/chrome/dom/base/test/chrome/custom_element_ep.js", sandbox);
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_document-element-inserted.xhtml b/dom/base/test/chrome/test_document-element-inserted.xhtml
new file mode 100644
index 0000000000..5fd35e364d
--- /dev/null
+++ b/dom/base/test/chrome/test_document-element-inserted.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1411707
+-->
+<window title="Mozilla Bug 1411707"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1411707"
+ target="_blank">Mozilla Bug 1411707</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ const OUTER_URL = "chrome://mochitests/content/chrome/dom/base/test/chrome/file_document-element-inserted.xhtml";
+ const INNER_URL = "chrome://mochitests/content/chrome/dom/base/test/chrome/file_document-element-inserted-inner.xhtml";
+
+ async function waitForEvent(url) {
+ return new Promise(resolve => {
+ SpecialPowers.addObserver(function inserted(document) {
+ is(document.documentURI, url, "Correct URL");
+ is(document.readyState, "loading", "Correct readyState");
+ SpecialPowers.removeObserver(inserted, "document-element-inserted");
+ resolve();
+ }, "document-element-inserted");
+ })
+ }
+
+ // Load a XUL document that also has an iframe to a subdocument, and
+ // expect both events to fire with the docs in the correct state.
+ async function testEvents() {
+ info(`Waiting for events after loading ${OUTER_URL}`);
+ let win = window.browsingContext.topChromeWindow.openDialog(OUTER_URL, "_blank", "chrome,dialog=no,all");
+ await waitForEvent(OUTER_URL);
+ await waitForEvent(INNER_URL);
+ win.close();
+ }
+
+ (async function() {
+ // Test the same document twice to make to make sure we are
+ // firing properly when loading the protype document.
+ await testEvents();
+ await testEvents();
+ SimpleTest.finish();
+ })();
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_domparsing.xhtml b/dom/base/test/chrome/test_domparsing.xhtml
new file mode 100644
index 0000000000..be04bdfc0d
--- /dev/null
+++ b/dom/base/test/chrome/test_domparsing.xhtml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="Test for the Mozilla extesion of the DOM Parsing and Serialization API"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816410"
+ target="_blank">Mozilla Bug 816410</a>
+ <xul:browser id="test1" type="content" src="data:text/html,content" />
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+"use strict";
+/** Test for Bug 816410 **/
+
+function throws(fn, type, message) {
+ try {
+ fn();
+ ok(false, message);
+ } catch (e) {
+ if (type) {
+ is(e.name, type, message);
+ } else {
+ ok(true, message);
+ }
+ }
+}
+
+add_task(function dom_parser_extra_args() {
+ // DOMParser constructor should not throw for extra arguments
+ new DOMParser(undefined);
+ new DOMParser(null);
+ new DOMParser(false);
+ new DOMParser(0);
+ new DOMParser("");
+ new DOMParser({});
+});
+
+add_task(function xml_serializer_extra_args() {
+ // XMLSerializer constructor should not throw for extra arguments
+ new XMLSerializer(undefined);
+ new XMLSerializer(null);
+ new XMLSerializer(false);
+ new XMLSerializer(0);
+ new XMLSerializer("");
+ new XMLSerializer({});
+});
+
+add_task(function chrome_window() {
+ runTest(window, true);
+});
+
+add_task(function content_window() {
+ runTest(document.getElementById("test1").contentWindow, false);
+});
+
+function runTest(win, expectSystem) {
+ let parser = new win.DOMParser();
+ let serializer = new win.XMLSerializer();
+ let principal = win.document.nodePrincipal;
+ is(principal.isSystemPrincipal, expectSystem,
+ `expected ${expectSystem ? "system" : "content"} principal`);
+
+ is(typeof parser.parseFromString, "function", "parseFromString should exist");
+ is(typeof parser.parseFromBuffer, "function", "parseFromBuffer should exist");
+ is(typeof parser.parseFromStream, "function", "parseFromStream should exist");
+ is(typeof parser.parseFromSafeString, "function", "parseFromSafeString should exist");
+
+ is(typeof serializer.serializeToString, "function", "serializeToString should exist");
+ is(typeof serializer.serializeToStream, "function", "serializeToStream should exist");
+
+ let tests = [
+ {input: "<html></html>", type: "text/html",
+ expected: '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>'},
+ {input: "<xml></xml>", type: "text/xml", expected: "<xml/>"},
+ {input: "<xml></xml>", type: "application/xml", expected: "<xml/>"},
+ {input: "<html></html>", type: "application/xhtml+xml", expected: "<html/>"},
+ {input: "<svg></svg>", type: "image/svg+xml", expected: "<svg/>"},
+ ];
+ for (let t of tests) {
+ const fromNormalString = parser.parseFromString(t.input, t.type);
+ if (principal.isSystemPrincipal) {
+ ok(fromNormalString.nodePrincipal.isNullPrincipal,
+ "system principal DOMParser produces a null principal document");
+ } else {
+ ok(fromNormalString.nodePrincipal === principal,
+ "content principal DOMParser produces a document with an object-identical principal");
+ }
+
+ const fromSafeString = parser.parseFromSafeString(t.input, t.type);
+ ok(fromSafeString.nodePrincipal === principal,
+ "DOMParser with parseFromSafeString always produces a document with an object-identical principal");
+
+ is(serializer.serializeToString(parser.parseFromString(t.input, t.type)), t.expected,
+ "parseFromString test for " + t.type);
+
+ for (let charset of [null, "UTF-8"]) {
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ let ostream = pipe.outputStream;
+ serializer.serializeToStream(parser.parseFromString(t.input, t.type), ostream, charset);
+ let istream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ istream.init(pipe.inputStream);
+ let data = istream.read(0xffffffff);
+ is(data, t.expected,
+ "serializeToStream test for " + t.type + ", charset=" + charset);
+ }
+
+ if (t.type === "text/html") {
+ // parseFromBuffer and parseFromStream don't support "text/html".
+ continue;
+ }
+
+ let array = Array.from(t.input, function(c) { return c.charCodeAt(c); });
+ let inputs = [
+ {array: array, name: "parseFromBuffer (array)"},
+ {array: new Uint8Array(array), name: "parseFromBuffer (Uint8Array)"},
+ ];
+ for (let input of inputs) {
+ let a = input.array;
+ is(serializer.serializeToString(parser.parseFromBuffer(a, t.type)), t.expected,
+ input.name + " test for " + t.type);
+ }
+
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ for (let charset of [null, "UTF-8"]) {
+ istream.setData(t.input, -1);
+ is(serializer.serializeToString(parser.parseFromStream(istream, charset, t.input.length, t.type)),
+ t.expected, "parseFromStream test for " + t.type + ", charset=" + charset);
+ }
+ }
+ throws(function() {
+ parser.parseFromString("<xml></xml>", "foo/bar");
+ }, "TypeError", "parseFromString should throw for the unknown type");
+}
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_fileconstructor.xhtml b/dom/base/test/chrome/test_fileconstructor.xhtml
new file mode 100644
index 0000000000..8884eb169b
--- /dev/null
+++ b/dom/base/test/chrome/test_fileconstructor.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607114.xul
+-->
+<window title="Mozilla Bug 607114"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=607114">
+ Mozilla Bug 607114</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 607114 **/
+
+// eslint-disable-next-line mozilla/no-addtask-setup
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({ set: [[ "dom.file.createInChild", true ]]});
+});
+
+add_task(async function test() {
+ var file = SpecialPowers.Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ // man I wish this were simpler ...
+ file.append("chrome");
+ file.append("dom");
+ file.append("base");
+ file.append("test");
+ file.append("chrome");
+ file.append("fileconstructor_file.png");
+
+ let domFile = await File.createFromFileName(file.path);
+
+ ok(File.isInstance(domFile), "File() should return a File");
+ is(domFile.type, "image/png", "File should be a PNG");
+ is(domFile.size, 95, "File has size 95 (and more importantly we can read it)");
+
+ domFile = await File.createFromNsIFile(file);
+ ok(File.isInstance(domFile), "File() should return a File for an nsIFile");
+ is(domFile.type, "image/png", "File should be a PNG");
+ is(domFile.size, 95, "File has size 95 (and more importantly we can read it)");
+
+ try {
+ await File.createFromFileName(
+ "i/sure/hope/this/does/not/exist/anywhere.txt"
+ );
+ ok(false, "Attempt to construct a non-existent file should fail.");
+ } catch (ex) {
+ is(
+ ex.result,
+ Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH,
+ "Constructing a non-existing file should fail with the correct error code"
+ );
+ }
+ let dir = SpecialPowers.Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ try {
+ await File.createFromNsIFile(dir);
+ ok(false, "Attempt to construct a file from a directory should fail.");
+ } catch (ex) {
+ is(
+ ex.result,
+ Cr.NS_ERROR_FILE_IS_DIRECTORY,
+ "Constructing a file from a directory should fail with the correct error code"
+ );
+ }
+})
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_getElementsWithGrid.html b/dom/base/test/chrome/test_getElementsWithGrid.html
new file mode 100644
index 0000000000..3554acb2e9
--- /dev/null
+++ b/dom/base/test/chrome/test_getElementsWithGrid.html
@@ -0,0 +1,121 @@
+<!doctype html>
+<html id="root" class="g">
+<head>
+<meta charset="utf-8">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+<style>
+.no-match {
+ display: block;
+}
+.g {
+ display: grid;
+}
+.s {
+ display: subgrid;
+}
+.gi {
+ display: inline-grid;
+}
+</style>
+
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function testTargetsAreInElements(targets, elements) {
+ let c = 0;
+ for (let target of targets) {
+ if (c >= elements.length) {
+ ok(false, "We shouldn't have more targets than elements found.");
+ break;
+ }
+ let element = elements[c];
+ let isMatching = (target.id == element.id);
+ let test_function = (target.todo ? todo : ok);
+
+ test_function(isMatching, "Should find " + target.message + ".");
+
+ // Only move to the next element in the elements if this one was a match.
+ // This handles the case of an unexpected element showing up, and prevents
+ // cascading errors in that case. If we've instead screwed up the target
+ // list, then we will get cascading errors.
+ if (isMatching) {
+ ++c;
+ }
+ }
+
+ // Make sure we don't have any extra elements after going through all the targets.
+ is(c, elements.length, "We shouldn't have more elements than we have targets.");
+}
+
+function runTests() {
+ // Part 1: Look for all the grid elements starting from the document root.
+ let elementsFromRoot = document.documentElement.getElementsWithGrid();
+
+ // Check that the expected elements were returned.
+ // Targets are provided in order we expect them to appear.
+ // Has to end in a non-todo element in order for testing logic to work.
+ let targetsFromRoot = [
+ {id: "root", message: "root with display:grid"},
+ {id: "a", message: "'plain' grid container with display:grid"},
+ {id: "b", message: "display:subgrid inside display:grid (to be fixed in Bug 1240834)", todo: true},
+ {id: "c", message: "'plain' grid container with display:inline-grid"},
+ {id: "d", message: "display:subgrid inside display:inline-grid (to be fixed in Bug 1240834)", todo: true},
+ {id: "e", message: "grid container with visibility:hidden"},
+ {id: "f", message: "grid container inside an element"},
+ {id: "g", message: "overflow:scroll grid container"},
+ {id: "h", message: "button as a grid container"},
+ {id: "i", message: "fieldset as a grid container"},
+ {id: "k1", message: "grid container containing a grid container"},
+ {id: "k2", message: "grid container inside a grid container"},
+ ];
+ is(elementsFromRoot.length, 10, "Found expected number of elements within document root.");
+ testTargetsAreInElements(targetsFromRoot, elementsFromRoot);
+
+
+ // Part 2: Look for all the grid elements starting from a non-root element.
+ let elementsFromNonRoot = document.getElementById("a_non_root_element").getElementsWithGrid();
+
+ let targetsFromNonRoot = [
+ {id: "f", message: "grid container inside an element (from non-root element)"},
+ ];
+ is(elementsFromNonRoot.length, 1, "Found expected number of elements from non-root element.");
+ testTargetsAreInElements(targetsFromNonRoot, elementsFromNonRoot);
+
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onLoad="runTests();">
+
+<div id="a" class="g">
+ <div class="no-match"></div>
+ <div id="b" class="s"></div>
+</div>
+
+<div class="no-match"></div>
+
+<div id="c" class="gi">
+ <div id="d" class="s"></div>
+</div>
+
+<div id="e" class="g" style="visibility:hidden"></div>
+
+<div id="a_non_root_element"><div id="f" class="g"></div></div>
+
+<div class="no-match"></div>
+
+<div id="g" style="overflow:scroll" class="g"></div>
+
+<button id="h" class="g"></button>
+
+<fieldset id="i" class="g"></fieldset>
+
+<div id="a_display_none_element" style="display:none"><div id="j" class="g"></div></div>
+
+<div id="k1" class="g"><div id="k2" class="g"></div></div>
+
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml b/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml
new file mode 100644
index 0000000000..80465202ab
--- /dev/null
+++ b/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="Bug 1676785"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<html:body>
+ <xul:hbox>
+ <html:input id="xul" />
+ </xul:hbox>
+ <html:div>
+ <html:input id="non-xul" />
+ </html:div>
+</html:body>
+<script class="testbody">
+SimpleTest.waitForExplicitFinish();
+
+function shouldPreserveHistory(input, preserve) {
+ input.focus();
+ input.value = "abc";
+ input.value = "def";
+ let ctrl = navigator.platform.indexOf("Mac") == 0 ? { metaKey: true } : { ctrlKey: true };
+ synthesizeKey("z", ctrl);
+ (preserve ? is : isnot)(input.value, "abc", `Expected ${input.id} to ${preserve ? "" : "not "}preserve undo history when setting .value`);
+}
+
+window.onload = function() {
+ shouldPreserveHistory(document.getElementById("xul"), true);
+ shouldPreserveHistory(document.getElementById("non-xul"), false);
+
+ SimpleTest.finish();
+}
+</script>
+</window>
diff --git a/dom/base/test/chrome/test_nsITextInputProcessor.xhtml b/dom/base/test/chrome/test_nsITextInputProcessor.xhtml
new file mode 100644
index 0000000000..3343c28560
--- /dev/null
+++ b/dom/base/test/chrome/test_nsITextInputProcessor.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing nsITextInputProcessor behavior"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("window_nsITextInputProcessor.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+</window>
diff --git a/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml b/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml
new file mode 100644
index 0000000000..06675a90ad
--- /dev/null
+++ b/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Tests that the hasValidTransientUserGestureActivation attribute on permission requests is set correctly.
+-->
+<window title="hasValidTransientUserGestureActivation test" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <iframe id="frame" src="https://example.com/chrome/dom/base/test/chrome/dummy.html" />
+ </body>
+
+ <script type="application/javascript">
+ <![CDATA[
+ const {Integration} = ChromeUtils.import("resource://gre/modules/Integration.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let frame = document.getElementById("frame");
+
+ function checkPermissionRequest(permission, hasValidTransientUserGestureActivation) {
+ return new Promise(function(resolve) {
+ let TestIntegration = (base) => ({
+ __proto__: base,
+ createPermissionPrompt(type, request) {
+ is(type, permission, `Has correct permission type ${permission}.`);
+ is(request.hasValidTransientUserGestureActivation, hasValidTransientUserGestureActivation,
+ "The hasValidTransientUserGestureActivation attribute is set correctly.");
+ Integration.contentPermission.unregister(TestIntegration);
+ resolve();
+ return { prompt() {} };
+ },
+ });
+ Integration.contentPermission.register(TestIntegration);
+ });
+ }
+
+ async function runTest() {
+ await SpecialPowers.setBoolPref("dom.webnotifications.allowcrossoriginiframe", true);
+
+ // Test programmatic request for persistent storage.
+ let request = checkPermissionRequest("persistent-storage", false);
+ navigator.storage.persist();
+ await request;
+
+ // Test user-initiated request for persistent storage.
+ request = checkPermissionRequest("persistent-storage", true);
+ document.notifyUserGestureActivation();
+ navigator.storage.persist();
+ await request;
+ content.document.clearUserGestureActivation();
+
+ // Test programmatic request for geolocation.
+ request = checkPermissionRequest("geolocation", false);
+ navigator.geolocation.getCurrentPosition(() => {});
+ await request;
+
+ // Test user-initiated request for geolocation.
+ request = checkPermissionRequest("geolocation", true);
+ document.notifyUserGestureActivation();
+ navigator.geolocation.getCurrentPosition(() => {});
+ await request;
+ document.clearUserGestureActivation();
+
+ // Notifications need to be tested in an HTTPS frame, because
+ // chrome:// URLs are whitelisted.
+ let frameWin = frame.contentWindow;
+
+ // Test programmatic request for notifications.
+ request = checkPermissionRequest("desktop-notification", false);
+ frameWin.Notification.requestPermission();
+ await request;
+
+ // Test user-initiated request for notifications.
+ request = checkPermissionRequest("desktop-notification", true);
+ frameWin.document.notifyUserGestureActivation();
+ frameWin.Notification.requestPermission();
+ await request;
+ frameWin.document.clearUserGestureActivation();
+
+ await SpecialPowers.clearUserPref("dom.webnotifications.allowcrossoriginiframe");
+ }
+
+ frame.addEventListener("load", function() {
+ runTest().then(() => SimpleTest.finish());
+ });
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_range_getClientRectsAndTexts.html b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html
new file mode 100644
index 0000000000..10eddaf522
--- /dev/null
+++ b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html
@@ -0,0 +1,74 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ let range = document.createRange();
+
+ let attempts = [
+ {startNode: "one", start: 0, endNode: "one", end: 0, textList: [], message: "Empty rect"},
+ {startNode: "one", start: 2, endNode: "one", end: 6, textList: ["l on"], message: "Single line"},
+ {startNode: "implicit", start: 6, endNode: "implicit", end: 12, textList: ["it bre"], message: "Implicit break"},
+ {startNode: "two.a", start: 1, endNode: "two.b", end: 2, textList: ["wo", "", "li"], message: "Two lines"},
+ {startNode: "embed.a", start: 7, endNode: "embed.b", end: 2, textList: ["th ", "simple nested", " ", "te"], message: "Simple nested"},
+ {startNode: "deep.a", start: 2, endNode: "deep.b", end: 2, textList: ["ne with ", "complex, more deeply nested", " ", "te"], message: "Complex nested"},
+ {startNode: "image.a", start: 7, endNode: "image.b", end: 2, textList: ["th inline ", "", " ", "im"], message: "Inline image"},
+ {startNode: "hyphen1", start: 0, endNode: "hyphen1", end: 3, textList: ["a\u00AD", "b"], message: "Shy hyphen (active)"},
+ {startNode: "hyphen2", start: 0, endNode: "hyphen2", end: 3, textList: ["c\u00ADd"], message: "Shy hyphen (inactive)"},
+ {startNode: "hyphen2", start: 0, endNode: "hyphen2", end: 2, textList: ["c\u00AD"], message: "Shy hyphen (inactive, trailing)"},
+ {startNode: "hyphen2", start: 1, endNode: "hyphen2", end: 3, textList: ["\u00ADd"], message: "Shy hyphen (inactive, leading)"},
+ {startNode: "uc", start: 0, endNode: "uc", end: 2, textList: ["EF"], message: "UC transform"},
+ {startNode: "pre", start: 0, endNode: "pre", end: 3, textList: ["g\n", "h"], message: "pre with break"},
+ ];
+
+ for (let attempt of attempts) {
+ range.setStart(document.getElementById(attempt.startNode).childNodes[0], attempt.start);
+ range.setEnd(document.getElementById(attempt.endNode).childNodes[0], attempt.end);
+
+ let result = range.getClientRectsAndTexts();
+
+ is(result.textList.length, attempt.textList.length, attempt.message + " range has expected number of texts.");
+ let i = 0;
+ for (let text of result.textList) {
+ is(text, attempt.textList[i], attempt.message + " matched text index " + i + ".");
+ i++;
+ }
+ }
+
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onLoad="runTests()">
+
+<div id="one">All on one line</div>
+
+<div id="implicit">Implicit
+break in one line</div>
+
+<div id="two.a">Two<br/
+><span id="two.b">lines</span></div>
+
+<div id="embed.a">Line with <span>simple nested</span> <span id="embed.b">text</span></div>
+
+<div id="deep.a">Line with <span>complex, <span>more <span>deeply <span>nested</span></span></span></span> <span id="deep.b">text</span></div>
+
+<div id="image.a">Line with inline <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" width="20" height="20"/> <span id="image.b">image</span></div>
+
+<div id="hyphen1" style="width:0">a&shy;b</div>
+
+<div id="hyphen2" style="width:100px">c&shy;d</div>
+
+<div id="uc" style="text-transform:uppercase">ef</div>
+
+<pre id="pre">g
+h</pre>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/chrome/test_swapFrameLoaders.xhtml b/dom/base/test/chrome/test_swapFrameLoaders.xhtml
new file mode 100644
index 0000000000..4ea11a1a62
--- /dev/null
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
+Test swapFrameLoaders with different frame types and remoteness
+-->
+<window title="Mozilla Bug 1242644"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644"
+ target="_blank">Mozilla Bug 1242644</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ window.openDialog("window_swapFrameLoaders.xhtml", "bug1242644",
+ "chrome,width=600,height=600,noopener", window);
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_title.xhtml b/dom/base/test/chrome/test_title.xhtml
new file mode 100644
index 0000000000..33b3fd0220
--- /dev/null
+++ b/dom/base/test/chrome/test_title.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=481777
+-->
+<window title="Mozilla Bug 481777"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=481777"
+ target="_blank">Mozilla Bug 481777</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 481777 **/
+
+ SimpleTest.waitForExplicitFinish();
+ window.openDialog("title_window.xhtml", "bug481777",
+ "chrome,width=100,height=100,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_windowroot.xhtml b/dom/base/test/chrome/test_windowroot.xhtml
new file mode 100644
index 0000000000..6f1c29095b
--- /dev/null
+++ b/dom/base/test/chrome/test_windowroot.xhtml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Test window.windowRoot"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ var root = window.windowRoot;
+ ok(WindowRoot.isInstance(root), "windowRoot should be a WindowRoot");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/title_window.xhtml b/dom/base/test/chrome/title_window.xhtml
new file mode 100644
index 0000000000..87a2769eb8
--- /dev/null
+++ b/dom/base/test/chrome/title_window.xhtml
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<window title="Mozilla Bug 481777 subwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()">
+
+ <iframe type="content" id="html1" src="data:text/html,&lt;html&gt;&lt;head&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="html2" src="data:text/html,&lt;html&gt;&lt;head&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;title&gt;Foo&lt;/title&gt;&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="html3" src="data:text/html,&lt;html&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml1" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;body&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/body&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml2" src="data:text/xml,&lt;title xmlns='http://www.w3.org/1999/xhtml'&gt;Test&lt;/title&gt;"/>
+ <iframe type="content" id="xhtml3" src="data:text/xml,&lt;title xmlns='http://www.w3.org/1999/xhtml'&gt;Te&lt;div&gt;bogus&lt;/div&gt;st&lt;/title&gt;"/>
+ <iframe type="content" id="xhtml4" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'/>"/>
+ <iframe type="content" id="xhtml5" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;head/>&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml6" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;head&gt;&lt;style/>&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe id="xul1" src="file_title.xhtml"/>
+ <iframe id="xul2" src="file_title.xhtml"/>
+ <iframe type="content" id="svg1" src="data:text/xml,&lt;svg xmlns='http://www.w3.org/2000/svg'&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/svg&gt;"/>
+ <iframe type="content" id="svg2" src="data:text/xml,&lt;svg xmlns='http://www.w3.org/2000/svg'&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/svg&gt;"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function testStatics() {
+ function testStatic(id, expect, description) {
+ is(document.getElementById(id).contentDocument.title, expect, description);
+ }
+
+ testStatic("html1", "Test", "HTML <title>");
+ testStatic("html2", "Test", "choose the first HTML <title>");
+ testStatic("html3", "", "No title");
+ testStatic("xhtml1", "Test", "XHTML <title> in body");
+ testStatic("xhtml2", "Test", "XHTML <title> as root element");
+ testStatic("xhtml3", "Test", "XHTML <title> containing an element");
+ testStatic("xul1", "Test", "XUL <window> title attribute");
+ testStatic("svg1", "Test", "SVG <title>");
+
+ // This one does nothing and won't fire an event
+ document.getElementById("xhtml4").contentDocument.title = "Hello";
+ is(document.getElementById("xhtml4").contentDocument.title, "", "Setting 'title' does nothing with no <head>");
+ }
+
+ function testDynamics() {
+ var inProgress = {};
+ var inProgressDoc = {};
+ var inProgressWin = {};
+ function testDynamic(id, expect, description, op, checkDOM) {
+ inProgress[description] = true;
+ inProgressDoc[description] = true;
+ inProgressWin[description] = true;
+ var frame = document.getElementById(id);
+
+ function listener(ev) {
+ inProgress[description] = false;
+ is(frame.contentDocument.title, expect, "'title': " + description);
+ is(frame.contentDocument, ev.target, "Unexpected target: " + description);
+ if (typeof(checkDOM) != "undefined") {
+ checkDOM(frame.contentDocument, "DOM: " + description);
+ }
+ }
+
+ function listener2(ev) {
+ inProgressDoc[description] = false;
+ }
+ function listener3(ev) {
+ inProgressWin[description] = false;
+ }
+ frame.addEventListener("DOMTitleChanged", listener, false);
+ frame.contentDocument.addEventListener("DOMTitleChanged", listener2, false);
+ frame.contentWindow.addEventListener("DOMTitleChanged", listener3, false);
+
+ op(frame.contentDocument);
+ }
+
+ var dynamicTests = [
+ [ "html1", "Hello", "Setting HTML <title> text contents",
+ function(doc){
+ var t = doc.getElementById("t"); t.textContent = "Hello";
+ } ],
+ [ "html2", "Foo", "Removing HTML <title>",
+ function(doc){
+ var t = doc.getElementById("t"); t.remove();
+ } ],
+ [ "html3", "Hello", "Appending HTML <title> element to root element",
+ function(doc){
+ var t = doc.createElement("title"); t.textContent = "Hello"; doc.documentElement.appendChild(t);
+ } ],
+ [ "xhtml3", "Hello", "Setting 'title' clears existing <title> contents",
+ function(doc){
+ doc.title = "Hello";
+ },
+ function(doc, desc) {
+ is(doc.documentElement.firstChild.data, "Hello", desc);
+ is(doc.documentElement.firstChild.nextSibling, null, desc);
+ } ],
+ [ "xhtml5", "Hello", "Setting 'title' works with a <head>",
+ function(doc){
+ doc.title = "Hello";
+ },
+ function(doc, desc) {
+ var head = doc.documentElement.firstChild;
+ var title = head.firstChild;
+ is(title.tagName.toLowerCase(), "title", desc);
+ is(title.firstChild.data, "Hello", desc);
+ is(title.firstChild.nextSibling, null, desc);
+ is(title.nextSibling, null, desc);
+ } ],
+ [ "xhtml6", "Hello", "Setting 'title' appends to <head>",
+ function(doc){
+ doc.title = "Hello";
+ },
+ function(doc, desc) {
+ var head = doc.documentElement.firstChild;
+ is(head.firstChild.tagName.toLowerCase(), "style", desc);
+ var title = head.firstChild.nextSibling;
+ is(title.tagName.toLowerCase(), "title", desc);
+ is(title.firstChild.data, "Hello", desc);
+ is(title.firstChild.nextSibling, null, desc);
+ is(title.nextSibling, null, desc);
+ } ],
+ [ "xul1", "Hello", "Setting XUL <window> title attribute",
+ function(doc){
+ doc.documentElement.setAttribute("title", "Hello");
+ } ],
+ [ "xul2", "Hello", "Setting 'title' in XUL",
+ function(doc){
+ doc.title = "Hello";
+ },
+ function(doc, desc) {
+ is(doc.documentElement.getAttribute("title"), "Hello", desc);
+ is(doc.documentElement.firstChild, null, desc);
+ } ],
+ [ "svg1", "Hello", "Setting SVG <title> text contents",
+ function(doc){
+ var t = doc.getElementById("t"); t.textContent = "Hello";
+ } ],
+ [ "svg2", "", "Removing SVG <title>",
+ function(doc){
+ var t = doc.getElementById("t"); t.remove();
+ } ] ];
+
+ var titleWindow = window;
+
+ function runIndividualTest(i) {
+ if (i == dynamicTests.length) {
+ // Closing the window will nuke the global properties, since this
+ // function is not really running on this window... or something
+ // like that. Thanks, executeSoon!
+ var tester = SimpleTest;
+ window.close();
+ tester.finish();
+ } else {
+ var parameters = dynamicTests[i];
+ var testElementId = parameters[0];
+ var testExpectedValue = parameters[1];
+ var testDescription = parameters[2];
+ var testOp = parameters[3];
+ var testCheckDOM = parameters[4];
+
+ function checkTest() {
+ ok(!inProgress[testDescription],
+ testDescription + ": DOMTitleChange not fired");
+ ok(inProgressDoc[testDescription],
+ testDescription + ": DOMTitleChange fired on content document");
+ ok(inProgressWin[testDescription],
+ testDescription + ": DOMTitleChange fired on content window");
+ // Run the next test in the context of the parent XUL window.
+ titleWindow.setTimeout(runIndividualTest, 0, i+1);
+ }
+ function spinEventLoopOp(doc) {
+ // Perform the test's operations.
+ testOp(doc);
+ // Spin the associated window's event loop to ensure we
+ // drain any asynchronous changes and fire associated
+ // events.
+ doc.defaultView.setTimeout(checkTest, 0);
+ }
+
+ testDynamic(testElementId, testExpectedValue, testDescription,
+ spinEventLoopOp, testCheckDOM);
+ }
+ }
+
+ window.setTimeout(runIndividualTest, 0, 0);
+ }
+
+ function runTests() {
+ testStatics();
+ testDynamics();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/window_chromeOuterWindowID.xhtml b/dom/base/test/chrome/window_chromeOuterWindowID.xhtml
new file mode 100644
index 0000000000..268409c1bf
--- /dev/null
+++ b/dom/base/test/chrome/window_chromeOuterWindowID.xhtml
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1474662
+Test that the chromeOuterWindowID on the MessageManager interface
+works, and that it properly updates when swapping frameloaders between
+windows.
+-->
+<window title="Mozilla Bug 1242644"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <browser id="non-remote" nodefaultsrc="true" type="content" flex="1"/>
+ <browser id="remote" remote="true" nodefaultsrc="true" type="content" flex="1"/>
+</window> \ No newline at end of file
diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xhtml b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml
new file mode 100644
index 0000000000..291a9e4324
--- /dev/null
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml
@@ -0,0 +1,4750 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing nsITextInputProcessor behavior"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onunload="onunload();">
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="display">
+<input id="input" type="text"/><input id="anotherInput" type="text"/><br/>
+<textarea></textarea>
+<iframe id="iframe" width="300" height="150"
+ src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
+<div contenteditable=""><br/></div>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var SimpleTest = window.arguments[0].SimpleTest;
+
+SimpleTest.waitForFocus(runTests, window);
+
+function getHTMLEditor(aWindow) {
+ return SpecialPowers.wrap(aWindow).docShell.editingSession?.getEditorForWindow(aWindow);
+}
+
+function ok(aCondition, aMessage)
+{
+ SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+ SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function info(aMessage) {
+ SimpleTest.info(aMessage);
+}
+
+function finish()
+{
+ window.close();
+}
+
+function onunload()
+{
+ SimpleTest.finish();
+}
+
+function checkInputEvent(aEvent, aCancelable, aIsComposing, aInputType, aData, aDescription) {
+ if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
+ throw new Error(`${aDescription}"${aEvent.type}" is not InputEvent`);
+ }
+ ok(InputEvent.isInstance(aEvent), `${aDescription}"${aEvent.type}" event should be dispatched with InputEvent interface`);
+ is(aEvent.cancelable, aCancelable, `${aDescription}"${aEvent.type}" event should ${aCancelable ? "be" : "not be"} cancelable`);
+ is(aEvent.bubbles, true, `${aDescription}"${aEvent.type}" event should always bubble`);
+ is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing of "${aEvent.type}" event should be ${aIsComposing}`);
+ is(aEvent.inputType, aInputType, `${aDescription}inputType of "${aEvent.type}" event should be "${aInputType}"`);
+ is(aEvent.data, aData, `${aDescription}data of "${aEvent.type}" event should be "${aData}"`);
+ is(aEvent.dataTransfer, null, `${aDescription}dataTransfer of "${aEvent.type}" event should be null`);
+ is(aEvent.getTargetRanges().length, 0, `${aDescription}getTargetRanges() of "${aEvent.type}" event should return empty array`);
+}
+
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+
+const iframe = document.getElementById("iframe");
+let childWindow = iframe.contentWindow;
+let textareaInFrame;
+const input = document.getElementById("input");
+const textarea = document.querySelector("textarea");
+const otherWindow = window.arguments[0];
+const otherDocument = otherWindow.document;
+const inputInChildWindow = otherDocument.getElementById("input");
+const contenteditable = document.querySelector("div[contenteditable]");
+
+const kLF = navigator.platform.startsWith("Win") ? "\r\n" : "\n";
+function getNativeText(aXPText)
+{
+ if (kLF == "\n") {
+ return aXPText;
+ }
+ return aXPText.replace(/\n/g, kLF);
+}
+
+function createTIP()
+{
+ return Cc["@mozilla.org/text-input-processor;1"].
+ createInstance(Ci.nsITextInputProcessor);
+}
+
+function runBeginInputTransactionMethodTests()
+{
+ var description = "runBeginInputTransactionMethodTests: ";
+ input.value = "";
+ input.focus();
+
+ var simpleCallback = function (aTIP, aNotification)
+ {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ return true;
+ };
+
+ var TIP1 = createTIP();
+ var TIP2 = createTIP();
+ isnot(TIP1, TIP2,
+ description + "TIP instances should be different");
+
+ // beginInputTransaction() and beginInputTransactionForTests() can take ownership if there is no composition.
+ ok(TIP1.beginInputTransaction(window, simpleCallback),
+ description + "TIP1.beginInputTransaction(window) should succeed because there is no composition");
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests(window) should succeed because there is no composition");
+ ok(TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2.beginInputTransaction(window) should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests(window) should succeed because there is no composition");
+
+ // Start composition with TIP1, then, other TIPs cannot take ownership during a composition.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ var composingStr = "foo";
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ ok(TIP1.flushPendingComposition(),
+ description + "TIP1.flushPendingComposition() should return true becuase it should be valid composition");
+ is(input.value, composingStr,
+ description + "The input element should have composing string");
+
+ // Composing nsITextInputProcessor instance shouldn't allow initialize it again.
+ try {
+ TIP1.beginInputTransaction(window, simpleCallback);
+ ok(false,
+ "TIP1.beginInputTransaction(window) should cause throwing an exception because it's composing with different purpose");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(window) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing for tests");
+ }
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow);
+ ok(false,
+ "TIP1.beginInputTransactionForTests(otherWindow) should cause throwing an exception because it's composing on different window");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing on this window");
+ }
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests(window) should succeed because TextEventDispatcher was initialized with same purpose");
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests(childWindow) should succeed because TextEventDispatcher was initialized with same purpose and is shared by window and childWindow");
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2.beginInputTransaction(window) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests(window) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransaction(childWindow, simpleCallback),
+ description + "TIP2.beginInputTransaction(childWindow) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransactionForTests(childWindow),
+ description + "TIP2.beginInputTransactionForTests(childWindow) should not succeed because there is composition synthesized by TIP1");
+ ok(TIP2.beginInputTransaction(otherWindow, simpleCallback),
+ description + "TIP2.beginInputTransaction(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+ ok(TIP2.beginInputTransactionForTests(otherWindow),
+ description + "TIP2.beginInputTransactionForTests(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+
+ // Let's confirm that the composing string is NOT committed by above tests.
+ TIP1.commitComposition();
+ is(input.value, composingStr,
+ description + "TIP1.commitString() without specifying commit string should commit current composition with the last composing string");
+ ok(!TIP1.hasComposition,
+ description + "TIP1.commitString() without specifying commit string should've end composition");
+
+ ok(TIP1.beginInputTransaction(window, simpleCallback),
+ description + "TIP1.beginInputTransaction() should succeed because there is no composition #2");
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition #2");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because the composition was already committed #2");
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keydown event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keypress event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keyup event handler during a call of TIP1.keyup();");
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
+ events = [];
+ TIP1.beginInputTransactionForTests(window);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests for tests from keydown event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from keypress event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from keyup event handler during a call of TIP1.keyup();");
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
+ }
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
+ }
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if startComposition() throws an exception after ownership is stolen.
+ input.value = "";
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ try {
+ TIP1.startComposition();
+ ok(false,
+ description + "TIP1.startComposition() should cause throwing an exception because TIP2 took the ownership");
+ TIP1.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.startComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if flushPendingComposition() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition()
+ ok(false,
+ description + "TIP1.flushPendingComposition() should cause throwing an exception because TIP2 took the ownership");
+ TIP1.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if commitCompositionWith("bar") throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ TIP1.commitCompositionWith("bar");
+ ok(false,
+ description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if keydown() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ var keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP1.keydown(keyF);
+ ok(false,
+ description + "TIP1.keydown(keyF) should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.keydown(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Let's check if keyup() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ var keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP1.keyup(keyF);
+ ok(false,
+ description + "TIP1.keyup(keyF) should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.keyup(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be omitted.
+ try {
+ TIP1.beginInputTransaction(window);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window) should be failed since aCallback is omitted");
+ } catch (e) {
+ ok(e.message.includes("Not enough arguments"),
+ description + "TIP1.beginInputTransaction(window) should cause throwing an exception including \"Not enough arguments\" since aCallback is omitted");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be undefined.
+ try {
+ TIP1.beginInputTransaction(window, undefined);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window, undefined) should be failed since aCallback is undefined");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP1.beginInputTransaction(window, undefined) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is undefined");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be null.
+ try {
+ TIP1.beginInputTransaction(window, null);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window, null) should be failed since aCallback is null");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP1.beginInputTransaction(window, null) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is null");
+ }
+}
+
+function runReleaseTests()
+{
+ var description = "runReleaseTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.value = "";
+ input.focus();
+
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ is(input.value, "foo",
+ description + "the input should have composition string");
+
+ // Release the TIP
+ TIP = null;
+ // Needs to run GC forcibly for testing this.
+ Cu.forceGC();
+
+ is(input.value, "",
+ description + "the input should be empty because the composition should be canceled");
+
+ TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed #2");
+}
+
+function runCompositionTests()
+{
+ var description = "runCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push({ "type": aEvent.type, "data": aEvent.data });
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ // nsITextInputProcessor.startComposition()
+ reset();
+ TIP.startComposition();
+ is(events.length, 1,
+ description + "startComposition() should cause only compositionstart");
+ is(events[0].type, "compositionstart",
+ description + "startComposition() should cause only compositionstart");
+ is(input.value, "",
+ description + "startComposition() shouldn't modify the focused editor");
+
+ // Setting composition string "foo" as a raw clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+ is(events[0].data, "foo",
+ description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+ is(input.value, "foo",
+ description + "modifying composition string should cause modifying the focused editor");
+
+ // Changing the raw clause to a selected clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 0,
+ description + "flushPendingComposition() changing only clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Separating the selected clause to two clauses
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 0,
+ description + "flushPendingComposition() separating a clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "separating composition clause shouldn't cause modifying the focused editor");
+
+ // Modifying the composition string
+ TIP.setPendingCompositionString("FOo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+ is(events[0].data, "FOo",
+ description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+ is(input.value, "FOo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Committing the composition string
+ reset();
+ TIP.commitComposition();
+ is(events.length, 1,
+ description + "commitComposition() should cause compositionend but shouldn't cause compositionupdate");
+ is(events[0].type, "compositionend",
+ description + "commitComposition() should cause compositionend");
+ is(events[0].data, "FOo",
+ description + "compositionend caused by commitComposition() should have the committed string in its data");
+ is(input.value, "FOo",
+ description + "commitComposition() shouldn't cause modifying the focused editor");
+
+ // Starting new composition without a call of startComposition()
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 2,
+ description + "flushPendingComposition() without a call of startComposition() should cause both compositionstart and compositionupdate");
+ is(events[0].type, "compositionstart",
+ description + "flushPendingComposition() without a call of startComposition() should cause compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "flushPendingComposition() without a call of startComposition() should cause compositionupdate after compositionstart");
+ is(events[1].data, "bar",
+ description + "compositionupdate caused by flushPendingComposition() without a call of startComposition() should have the composition string in its data");
+ is(input.value, "FOobar",
+ description + "new composition string should cause appending composition string to the focused editor");
+
+ // Canceling the composition
+ reset();
+ TIP.cancelComposition();
+ is(events.length, 2,
+ description + "cancelComposition() should cause both compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "cancelComposition() should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by cancelComposition() should have empty string in its data");
+ is(events[1].type, "compositionend",
+ description + "cancelComposition() should cause compositionend after compositionupdate");
+ is(events[1].data, "",
+ description + "compositionend caused by cancelComposition() should have empty string in its data");
+ is(input.value, "FOo",
+ description + "canceled composition string should be removed from the focused editor");
+
+ // Starting composition explicitly and canceling it
+ reset();
+ TIP.startComposition();
+ TIP.cancelComposition();
+ is(events.length, 2,
+ description + "canceling composition immediately after startComposition() should cause compositionstart and compositionend");
+ is(events[0].type, "compositionstart",
+ description + "canceling composition immediately after startComposition() should cause compositionstart first");
+ is(events[1].type, "compositionend",
+ description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+ is(events[1].data, "",
+ description + "compositionend caused by canceling composition should have empty string in its data");
+ is(input.value, "FOo",
+ description + "canceling composition shouldn't modify the focused editor");
+
+ // Create composition for next test.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobar",
+ description + "The focused editor should have new composition string \"bar\"");
+
+ // Allow to set empty composition string
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "making composition string empty should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "making composition string empty should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+ // Allow to insert new composition string without compositionend/compositionstart
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "modifying composition string from empty string should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "modifying composition string from empty string should cause compositionupdate");
+ is(events[0].data, "buzz",
+ description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+ is(input.value, "FOobuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with different string
+ reset();
+ TIP.commitCompositionWith("bar");
+ is(events.length, 2,
+ description + "committing with different string should cause compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "committing with different string should cause compositionupdate first");
+ is(events[0].data, "bar",
+ description + "compositionupdate caused by committing with different string should have the committing string in its data");
+ is(events[1].type, "compositionend",
+ description + "committing with different string should cause compositionend after compositionupdate");
+ is(events[1].data, "bar",
+ description + "compositionend caused by committing with different string should have the committing string in its data");
+ is(input.value, "FOobar",
+ description + "new committed string should be appended to the focused editor");
+
+ // Appending new composition string
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobarbuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with same string
+ reset();
+ TIP.commitCompositionWith("buzz");
+ is(events.length, 1,
+ description + "committing with same string should cause only compositionend");
+ is(events[0].type, "compositionend",
+ description + "committing with same string should cause compositionend");
+ is(events[0].data, "buzz",
+ description + "compositionend caused by committing with same string should have the committing string in its data");
+ is(input.value, "FOobarbuzz",
+ description + "new committed string should be appended to the focused editor");
+
+ // Inserting commit string directly
+ reset();
+ TIP.commitCompositionWith("boo!");
+ is(events.length, 3,
+ description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+ is(events[0].type, "compositionstart",
+ description + "committing text directly should cause compositionstart first");
+ is(events[1].type, "compositionupdate",
+ description + "committing text directly should cause compositionupdate after compositionstart");
+ is(events[1].data, "boo!",
+ description + "compositionupdate caused by committing text directly should have the committing text in its data");
+ is(events[2].type, "compositionend",
+ description + "committing text directly should cause compositionend after compositionupdate");
+ is(events[2].data, "boo!",
+ description + "compositionend caused by committing text directly should have the committing text in its data");
+ is(input.value, "FOobarbuzzboo!",
+ description + "committing text directly should append the committing text to the focused editor");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+}
+
+function runCompositionWithKeyEventTests()
+{
+ var description = "runCompositionWithKeyEventTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT });
+ var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE });
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ // nsITextInputProcessor.startComposition()
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ is(events.length, 2,
+ description + "startComposition(printableKeyEvent) should cause keydown and compositionstart");
+ is(events[0].type, "keydown",
+ description + "startComposition(printableKeyEvent) should cause keydown");
+ is(events[1].type, "compositionstart",
+ description + "startComposition(printableKeyEvent) should cause compositionstart");
+ is(input.value, "",
+ description + "startComposition(printableKeyEvent) shouldn't modify the focused editor");
+
+ // Setting composition string "foo" as a raw clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 1,
+ description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+ is(events[0].data, "foo",
+ description + "compositionupdate caused by flushPendingComposition(KeyupInternal) should have new composition string in its data");
+ is(input.value, "foo",
+ description + "modifying composition string should cause modifying the focused editor");
+
+ // Changing the raw clause to a selected clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 0,
+ description + "flushPendingComposition(convertKeyEvent) changing only clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Separating the selected clause to two clauses
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 0,
+ description + "flushPendingComposition(convertKeyEvent) separating a clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "separating composition clause shouldn't cause modifying the focused editor");
+
+ // Modifying the composition string
+ TIP.setPendingCompositionString("FOo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 1,
+ description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+ is(events[0].data, "FOo",
+ description + "compositionupdate caused by flushPendingComposition(convertKeyEvent) should have new composition string in its data");
+ is(input.value, "FOo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Committing the composition string
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 2,
+ description + "commitComposition(enterKeyEvent) should cause compositionend and keyup but shoudn't cause compositionupdate");
+ is(events[0].type, "compositionend",
+ description + "commitComposition(enterKeyEvent) should cause compositionend");
+ is(events[0].data, "FOo",
+ description + "compositionend caused by commitComposition(enterKeyEvent) should have the committed string in its data");
+ is(events[1].type, "keyup",
+ description + "commitComposition(enterKeyEvent) should cause keyup");
+ is(input.value, "FOo",
+ description + "commitComposition(enterKeyEvent) shouldn't cause modifying the focused editor");
+
+ // Starting new composition without a call of startComposition()
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause both compositionstart and compositionupdate");
+ is(events[0].type, "keydown",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause keydown");
+ is(events[1].type, "compositionstart",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionstart");
+ is(events[2].type, "compositionupdate",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionupdate after compositionstart");
+ is(events[2].data, "bar",
+ description + "compositionupdate caused by flushPendingComposition(printableKeyEvent) without a call of startComposition() should have the composition string in its data");
+ is(input.value, "FOobar",
+ description + "new composition string should cause appending composition string to the focused editor");
+
+ // Canceling the composition
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 3,
+ description + "cancelComposition(escKeyEvent) should cause both compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "cancelComposition(escKeyEvent) should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by cancelComposition(escKeyEvent) should have empty string in its data");
+ is(events[1].type, "compositionend",
+ description + "cancelComposition(escKeyEvent) should cause compositionend after compositionupdate");
+ is(events[1].data, "",
+ description + "compositionend caused by cancelComposition(escKeyEvent) should have empty string in its data");
+ is(events[2].type, "keyup",
+ description + "cancelComposition(escKeyEvent) should cause keyup after compositionend");
+ is(input.value, "FOo",
+ description + "canceled composition string should be removed from the focused editor");
+
+ // Starting composition explicitly and canceling it
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 4,
+ description + "canceling composition immediately after startComposition() should cause keydown, compositionstart, compositionend and keyup");
+ is(events[0].type, "keydown",
+ description + "canceling composition immediately after startComposition() should cause keydown first");
+ is(events[1].type, "compositionstart",
+ description + "canceling composition immediately after startComposition() should cause compositionstart after keydown");
+ is(events[2].type, "compositionend",
+ description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+ is(events[2].data, "",
+ description + "compositionend caused by canceling composition should have empty string in its data");
+ is(events[3].type, "keyup",
+ description + "canceling composition immediately after startComposition() should cause keyup after compositionend");
+ is(input.value, "FOo",
+ description + "canceling composition shouldn't modify the focused editor");
+
+ // Create composition for next test.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobar",
+ description + "The focused editor should have new composition string \"bar\"");
+
+ // Allow to set empty composition string
+ reset();
+ TIP.flushPendingComposition(backspaceKeyEvent);
+ is(events.length, 1,
+ description + "making composition string empty should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "making composition string empty should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+ // Allow to insert new composition string without compositionend/compositionstart
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 1,
+ description + "modifying composition string from empty string should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "modifying composition string from empty string should cause compositionupdate");
+ is(events[0].data, "buzz",
+ description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+ is(input.value, "FOobuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with different string
+ reset();
+ TIP.commitCompositionWith("bar", printableKeyEvent);
+ is(events.length, 3,
+ description + "committing with different string should cause compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "committing with different string should cause compositionupdate first");
+ is(events[0].data, "bar",
+ description + "compositionupdate caused by committing with different string should have the committing string in its data");
+ is(events[1].type, "compositionend",
+ description + "committing with different string should cause compositionend after compositionupdate");
+ is(events[1].data, "bar",
+ description + "compositionend caused by committing with different string should have the committing string in its data");
+ is(events[2].type, "keyup",
+ description + "committing with different string should cause keyup after compositionend");
+ is(input.value, "FOobar",
+ description + "new committed string should be appended to the focused editor");
+
+ // Appending new composition string
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobarbuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with same string
+ reset();
+ TIP.commitCompositionWith("buzz", enterKeyEvent);
+ is(events.length, 2,
+ description + "committing with same string should cause only compositionend");
+ is(events[0].type, "compositionend",
+ description + "committing with same string should cause compositionend");
+ is(events[0].data, "buzz",
+ description + "compositionend caused by committing with same string should have the committing string in its data");
+ is(events[1].type, "keyup",
+ description + "committing with same string should cause keyup after compositionend");
+ is(input.value, "FOobarbuzz",
+ description + "new committed string should be appended to the focused editor");
+
+ // Inserting commit string directly
+ reset();
+ TIP.commitCompositionWith("boo!", printableKeyEvent);
+ is(events.length, 5,
+ description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+ is(events[0].type, "keydown",
+ description + "committing text directly should cause keydown first");
+ is(events[1].type, "compositionstart",
+ description + "committing text directly should cause compositionstart after keydown");
+ is(events[2].type, "compositionupdate",
+ description + "committing text directly should cause compositionupdate after compositionstart");
+ is(events[2].data, "boo!",
+ description + "compositionupdate caused by committing text directly should have the committing text in its data");
+ is(events[3].type, "compositionend",
+ description + "committing text directly should cause compositionend after compositionupdate");
+ is(events[3].data, "boo!",
+ description + "compositionend caused by committing text directly should have the committing text in its data");
+ is(events[4].type, "keyup",
+ description + "committing text directly should cause keyup after compositionend");
+ is(input.value, "FOobarbuzzboo!",
+ description + "committing text directly should append the committing text to the focused editor");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+ // Even if "dom.keyboardevent.dispatch_during_composition" is true, keypress event shouldn't be fired during composition
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionstart",
+ description + "TIP.startComposition(printableKeyEvent) should cause compositionstart (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.startComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.flushPendingComposition(printableKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown, compositionupdate and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.commitComposition(enterKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 3,
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.cancelComposition(escKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.startComposition();
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 3,
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionend",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ var printableKeydownEvent = new KeyboardEvent("keydown", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+ var enterKeydownEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeydownEvent = new KeyboardEvent("keydown", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+ // TIP.startComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.startComposition(printableKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.startComposition(printableKeydownEvent) should cause keydown and compositionstart (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionstart",
+ description + "TIP.startComposition(printableKeydownEvent) should cause compositionstart (keyup event shouldn't be fired)");
+
+ // TIP.flushPendingComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown and compositionupdate (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause compositionupdate (keyup event shouldn't be fired)");
+
+ // TIP.commitComposition(enterKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.commitComposition(enterKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.commitComposition(enterKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+ // TIP.cancelComposition(escKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.startComposition();
+ reset();
+ TIP.cancelComposition(escKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionend",
+ description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runConsumingKeydownBeforeCompositionTests()
+{
+ var description = "runConsumingKeydownBeforeCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ if (aEvent.type == "keydown") {
+ aEvent.preventDefault();
+ }
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ // If keydown before compositionstart is consumed, composition shouldn't be started.
+ reset();
+ ok(!TIP.startComposition(printableKeyEvent),
+ description + "TIP.startComposition(printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.startComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.startComposition(printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.startComposition(printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.startComposition(printableKeyEvent) shouldn't cause inserting text");
+
+ // If keydown before compositionstart caused by flushPendingComposition(printableKeyEvent) is consumed, composition shouldn't be started.
+ reset();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ ok(!TIP.flushPendingComposition(printableKeyEvent),
+ description + "TIP.flushPendingComposition(printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause inserting text");
+
+ // If keydown before compositionstart is consumed, composition shouldn't be started.
+ reset();
+ ok(!TIP.commitCompositionWith("foo", printableKeyEvent),
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause inserting text");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+ // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled.
+ TIP.startComposition();
+ ok(TIP.hasComposition,
+ description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created");
+ reset();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ ok(TIP.flushPendingComposition(printableKeyEvent),
+ description + "TIP.flushPendingComposition(printableKeyEvent) should return true even if preceding keydown is consumed because there was a composition already");
+ is(events.length, 3,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate event after keydown");
+ is(events[2].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after compositionupdate");
+ ok(TIP.hasComposition,
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause canceling composition");
+ is(input.value, "foo",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause inserting text even if preceding keydown is consumed because there was a composition already");
+
+ // If composition is already started, TIP.commitComposition(enterKeyEvent) shouldn't be canceled.
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 3,
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeyEvent) should cause compositionend event after keydown");
+ is(events[2].type, "keyup",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keyup event after compositionend");
+ ok(!TIP.hasComposition,
+ description + "TIP.commitComposition(enterKeyEvent) should cause committing composition even if preceding keydown is consumed because there was a composition already");
+ is(input.value, "foo",
+ description + "TIP.commitComposition(enterKeyEvent) should commit composition even if preceding keydown is consumed because there was a composition already");
+
+ // cancelComposition() should work even if preceding keydown event is consumed.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ ok(TIP.hasComposition,
+ description + "Before TIP.cancelComposition(escKeyEvent), composition should've been created");
+ is(input.value, "foo",
+ description + "Before TIP.cancelComposition(escKeyEvent) should have composition string");
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 4,
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionupdate, compositionend and keyup events even if preceding keydown is consumed because there was a composition already");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionupdate event after keydown");
+ is(events[2].type, "compositionend",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate");
+ is(events[3].type, "keyup",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend");
+ ok(!TIP.hasComposition,
+ description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already");
+ is(input.value, "",
+ description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already");
+
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runKeyTests()
+{
+ var description = "runKeyTests(): ";
+ const kModifiers =
+ [ "Alt", "AltGraph", "CapsLock", "Control", "Fn", "FnLock", "Meta", "NumLock",
+ "ScrollLock", "Shift", "Symbol", "SymbolLock", "OS" ];
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+ var doPreventDefaults;
+
+ function reset()
+ {
+ events = [];
+ doPreventDefaults = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ if (doPreventDefaults.includes(aEvent.type)) {
+ aEvent.preventDefault();
+ }
+ }
+
+ function checkKeyAttrs(aMethodDescription, aEvent, aExpectedData)
+ {
+ var desc = description + aMethodDescription + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\": ";
+ var defaultValues = {
+ key: "Unidentified", code: "", keyCode: 0, charCode: 0,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, repeat: false, isComposing: false,
+ shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+ defaultPrevented: false
+ };
+ function expectedValue(aAttr)
+ {
+ return aExpectedData[aAttr] !== undefined ? aExpectedData[aAttr] : defaultValues[aAttr];
+ }
+ is(aEvent.type, aExpectedData.type,
+ desc + " should cause keydown event");
+ if (aEvent.type != aExpectedData.type) {
+ return;
+ }
+ is(aEvent.defaultPrevented, expectedValue("defaultPrevented"),
+ desc + ".defaultPrevented is wrong");
+ is(aEvent.key, expectedValue("key"),
+ desc + ".key is wrong");
+ is(aEvent.code, expectedValue("code"),
+ desc + ".code is wrong");
+ is(aEvent.location, expectedValue("location"),
+ desc + ".location is wrong");
+ is(aEvent.repeat, expectedValue("repeat"),
+ desc + ".repeat is wrong");
+ is(aEvent.isComposing, expectedValue("isComposing"),
+ desc + ".isComposing is wrong");
+ is(aEvent.keyCode, expectedValue("keyCode"),
+ desc + ".keyCode is wrong");
+ is(aEvent.charCode, expectedValue("charCode"),
+ desc + ".charCode is wrong");
+ is(aEvent.shiftKey, expectedValue("shiftKey"),
+ desc + ".shiftKey is wrong");
+ is(aEvent.ctrlKey, expectedValue("ctrlKey"),
+ desc + ".ctrlKey is wrong");
+ is(aEvent.altKey, expectedValue("altKey"),
+ desc + ".altKey is wrong");
+ is(aEvent.metaKey, expectedValue("metaKey"),
+ desc + ".metaKey is wrong");
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aExpectedData[kModifiers[i]] !== undefined ? aExpectedData[kModifiers[i]] : false,
+ desc + ".getModifierState(\"" + kModifiers[i] + "\") is wrong");
+ }
+ }
+
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+
+ // Printable key test:
+ // Emulates pressing 'a' key.
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+
+ reset();
+ var doDefaultKeydown = TIP.keydown(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because the keypress event should be consumed by the input element");
+ is(events.length, 2,
+ description + "TIP.keydown(keyA) should cause keydown and keypress event");
+ checkKeyAttrs("TIP.keydown(keyA)", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
+ checkKeyAttrs("TIP.keydown(keyA)", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value should be \"a\" which is inputted by TIP.keydown(keyA)");
+
+ // Emulates releasing 'a' key.
+ reset();
+ var doDefaultKeyup = TIP.keyup(keyA);
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 1,
+ description + "TIP.keyup(keyA) should cause keyup event");
+ checkKeyAttrs("TIP.keyup(keyA)", events[0],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+
+ // Non-printable key test:
+ // Emulates pressing Enter key.
+ var keyEnter = new KeyboardEvent("", { key: "Enter", code: "Enter" });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyEnter);
+
+ is(doDefaultKeydown, 0,
+ description + "TIP.keydown(keyEnter) should return 0");
+ is(events.length, 2,
+ description + "TIP.keydown(keyEnter) should cause keydown and keypress event");
+ checkKeyAttrs("TIP.keydown(keyEnter)", events[0],
+ { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ checkKeyAttrs("TIP.keydown(keyEnter)", events[1],
+ { type: "keypress", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+ // Emulates releasing Enter key.
+ reset();
+ doDefaultKeyup = TIP.keyup(keyEnter);
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyEnter) should return true");
+ is(events.length, 1,
+ description + "TIP.keyup(keyEnter) should cause keyup event");
+ checkKeyAttrs("TIP.keyup(keyEnter)", events[0],
+ { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+
+ // KEY_DEFAULT_PREVENTED should cause defaultPrevented = true and not cause keypress event
+ var keyB = new KeyboardEvent("", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED);
+ doDefaultKeyup = TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED);
+
+ is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
+ description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) should return 0x01 because it's marked as consumed at dispatching the event");
+ ok(!doDefaultKeyup,
+ description + "TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should return false because it's marked as consumed at dispatching the event");
+ is(events.length, 2,
+ description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should cause keydown and keyup event");
+ checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[0],
+ { type: "keydown", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[1],
+ { type: "keyup", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value shouldn't be modified by default prevented key events");
+
+ // Assume that KeyX causes inputting text "abc"
+ input.value = "";
+ var keyABC = new KeyboardEvent("", { key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyABC);
+ doDefaultKeyup = TIP.keyup(keyABC);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyABC) should return false because the keypress events should be consumed by the input element");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyABC) should return true");
+ is(events.length, 5,
+ description + "TIP.keydown(keyABC) and TIP.keyup(keyABC) should cause keydown, keypress, keypress, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[0],
+ { type: "keydown", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[1],
+ { type: "keypress", key: "abc".charAt(0), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[2],
+ { type: "keypress", key: "abc".charAt(1), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(1), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[3],
+ { type: "keypress", key: "abc".charAt(2), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(2), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[4],
+ { type: "keyup", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ is(input.value, "abc",
+ description + "input.value should be \"abc\"");
+
+ // If KEY_FORCE_PRINTABLE_KEY is specified, registered key names can be a printable key which inputs the specified value.
+ input.value = "";
+ var keyEnterPrintable = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
+ doDefaultKeyup = TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return 0x02 because the keypress events should be consumed by the input element");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return true");
+ is(events.length, 7,
+ description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should cause keydown, keypress, keypress, keypress, keypress, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[0],
+ { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[1],
+ { type: "keypress", key: "Enter".charAt(0), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[2],
+ { type: "keypress", key: "Enter".charAt(1), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(1), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[3],
+ { type: "keypress", key: "Enter".charAt(2), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(2), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[4],
+ { type: "keypress", key: "Enter".charAt(3), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(3), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[5],
+ { type: "keypress", key: "Enter".charAt(4), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(4), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[6],
+ { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false });
+ is(input.value, "Enter",
+ description + "input.value should be \"Enter\"");
+
+ // modifiers should be ignored.
+ var keyWithModifiers = new KeyboardEvent("", { key: "Escape", code: "Escape", shiftKey: true, ctrlKey: true, altKey: true, metaKey: true });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyWithModifiers);
+ doDefaultKeyup = TIP.keyup(keyWithModifiers);
+
+ is(doDefaultKeydown, 0,
+ description + "TIP.keydown(keyWithModifiers) should return 0");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyWithModifiers) should return true");
+ is(events.length, 3,
+ description + "TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers) should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[0],
+ { type: "keydown", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[1],
+ { type: "keypress", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[2],
+ { type: "keyup", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ is(input.value, "Enter",
+ description + "input.value should stay \"Enter\" which was inputted by TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)");
+
+ // Call preventDefault() at keydown
+ input.value = "";
+ reset();
+ doPreventDefaults = [ "keydown" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x01 because keydown event's preventDefault should be called");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 2,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown should cause keydown and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[1],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: false });
+ is(input.value, "",
+ description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keydown event is consumed");
+
+ // Call preventDefault() at keypress
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because keypress event's preventDefault should be called");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[2],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ is(input.value, "",
+ description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keypress event is consumed");
+
+ // Call preventDefault() at keyup
+ input.value = "";
+ reset();
+ doPreventDefaults = [ "keyup" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because the key event should be consumed by the input element");
+ ok(!doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return false because keyup event's preventDefault should be called");
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[2],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value should be \"a\" by TIP.keyup(keyA) even if the keyup event is consumed");
+
+ // key events during composition
+ try {
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ ok(TIP.startComposition(), "TIP.startComposition() should start composition");
+
+ input.value = "";
+ reset();
+ TIP.keydown(keyA);
+ is(events.length, 0,
+ description + "TIP.keydown(keyA) shouldn't cause key events during composition if it's disabled by the pref");
+ reset();
+ TIP.keyup(keyA);
+ is(events.length, 0,
+ description + "TIP.keyup(keyA) shouldn't cause key events during composition if it's disabled by the pref");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+ reset();
+ TIP.keydown(keyA);
+ is(events.length, 1,
+ description + "TIP.keydown(keyA) should cause keydown event even composition if it's enabled by the pref");
+ checkKeyAttrs("TIP.keydown(keyA) during composition", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
+ reset();
+ TIP.keyup(keyA);
+ is(events.length, 1,
+ description + "TIP.keyup(keyA) should cause keyup event even composition if it's enabled by the pref");
+ checkKeyAttrs("TIP.keyup(keyA) during composition", events[0],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
+
+ } finally {
+ TIP.cancelComposition();
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+ }
+
+ // Test .location computation
+ const kCodeToLocation = [
+ { code: "BracketLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "BracketRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Comma", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit0", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit1", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit2", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit3", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit4", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit5", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit6", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit7", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit8", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit9", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Equal", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Minus", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Period", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Slash", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "AltLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "AltRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "CapsLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ContextMenu", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ControlLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "ControlRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "OSLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "OSRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "ShiftLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "ShiftRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "Space", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Tab", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowDown", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowUp", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "NumLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Numpad0", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad1", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad2", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad3", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad4", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad5", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad6", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad7", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad8", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad9", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadBackspace", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadClearEntry", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadComma", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadDecimal", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadDivide", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadEnter", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadEqual", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryRecall", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryStore", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemorySubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMultiply", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadParenLeft", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadParenRight", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadSubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ ];
+ for (var i = 0; i < kCodeToLocation.length; i++) {
+ var keyEvent = new KeyboardEvent("", { code: kCodeToLocation[i].code });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ // If the location isn't initialized or initialized with 0, it should be computed from the code value.
+ TIP.keydown(keyEvent);
+ TIP.keyup(keyEvent);
+ var longDesc = description + "testing computation of .location of \"" + kCodeToLocation[i].code + "\", ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, kCodeToLocation[i].location,
+ longDesc + " type=\"" + events[j].type + "\", location value is wrong");
+ }
+ // However, if KEY_KEEP_KEY_LOCATION_STANDARD is specified, .location value should be kept as DOM_KEY_LOCATION_STANDARD (0).
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ var longDesc = description + "testing if .location is forcibly set to DOM_KEY_LOCATION_STANDARD, ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ longDesc + " type=\"" + events[j].type + "\", location value is not 0");
+ }
+ // If .location is initialized with non-zero value, the value shouldn't be computed again.
+ var keyEventWithLocation = new KeyboardEvent("", { code: kCodeToLocation[i].code, location: 0xFF });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEventWithLocation);
+ TIP.keyup(keyEventWithLocation);
+ longDesc = description + "testing if .location is not computed for \"" + kCodeToLocation[i].location + "\", ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, 0xFF,
+ longDesc + " type=\"" + events[j].type + "\", location shouldn't be computed if it's initialized with non-zero value");
+ }
+ }
+
+ // Test .keyCode value computation
+ const kKeyToKeyCode = [
+ { key: "Cancel", keyCode: KeyboardEvent.DOM_VK_CANCEL },
+ { key: "Help", keyCode: KeyboardEvent.DOM_VK_HELP },
+ { key: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE },
+ { key: "Tab", keyCode: KeyboardEvent.DOM_VK_TAB },
+ { key: "Clear", keyCode: KeyboardEvent.DOM_VK_CLEAR },
+ { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN },
+ { key: "Shift", keyCode: KeyboardEvent.DOM_VK_SHIFT, isModifier: true },
+ { key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL, isModifier: true },
+ { key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT, isModifier: true },
+ { key: "Pause", keyCode: KeyboardEvent.DOM_VK_PAUSE },
+ { key: "CapsLock", keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "Hiragana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "Katakana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "HiraganaKatakana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "KanaMode", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "HangulMode", keyCode: KeyboardEvent.DOM_VK_HANGUL },
+ { key: "Eisu", keyCode: KeyboardEvent.DOM_VK_EISU },
+ { key: "JunjaMode", keyCode: KeyboardEvent.DOM_VK_JUNJA },
+ { key: "FinalMode", keyCode: KeyboardEvent.DOM_VK_FINAL },
+ { key: "HanjaMode", keyCode: KeyboardEvent.DOM_VK_HANJA },
+ { key: "KanjiMode", keyCode: KeyboardEvent.DOM_VK_KANJI },
+ { key: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE },
+ { key: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT },
+ { key: "NonConvert", keyCode: KeyboardEvent.DOM_VK_NONCONVERT },
+ { key: "Accept", keyCode: KeyboardEvent.DOM_VK_ACCEPT },
+ { key: "ModeChange", keyCode: KeyboardEvent.DOM_VK_MODECHANGE },
+ { key: "PageUp", keyCode: KeyboardEvent.DOM_VK_PAGE_UP },
+ { key: "PageDown", keyCode: KeyboardEvent.DOM_VK_PAGE_DOWN },
+ { key: "End", keyCode: KeyboardEvent.DOM_VK_END },
+ { key: "Home", keyCode: KeyboardEvent.DOM_VK_HOME },
+ { key: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT },
+ { key: "ArrowUp", keyCode: KeyboardEvent.DOM_VK_UP },
+ { key: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT },
+ { key: "ArrowDown", keyCode: KeyboardEvent.DOM_VK_DOWN },
+ { key: "Select", keyCode: KeyboardEvent.DOM_VK_SELECT },
+ { key: "Print", keyCode: KeyboardEvent.DOM_VK_PRINT },
+ { key: "Execute", keyCode: KeyboardEvent.DOM_VK_EXECUTE },
+ { key: "PrintScreen", keyCode: KeyboardEvent.DOM_VK_PRINTSCREEN },
+ { key: "Insert", keyCode: KeyboardEvent.DOM_VK_INSERT },
+ { key: "Delete", keyCode: KeyboardEvent.DOM_VK_DELETE },
+ { key: "OS", keyCode: KeyboardEvent.DOM_VK_WIN, isModifier: true },
+ { key: "ContextMenu", keyCode: KeyboardEvent.DOM_VK_CONTEXT_MENU },
+ { key: "F1", keyCode: KeyboardEvent.DOM_VK_F1 },
+ { key: "F2", keyCode: KeyboardEvent.DOM_VK_F2 },
+ { key: "F3", keyCode: KeyboardEvent.DOM_VK_F3 },
+ { key: "F4", keyCode: KeyboardEvent.DOM_VK_F4 },
+ { key: "F5", keyCode: KeyboardEvent.DOM_VK_F5 },
+ { key: "F6", keyCode: KeyboardEvent.DOM_VK_F6 },
+ { key: "F7", keyCode: KeyboardEvent.DOM_VK_F7 },
+ { key: "F8", keyCode: KeyboardEvent.DOM_VK_F8 },
+ { key: "F9", keyCode: KeyboardEvent.DOM_VK_F9 },
+ { key: "F10", keyCode: KeyboardEvent.DOM_VK_F10 },
+ { key: "F11", keyCode: KeyboardEvent.DOM_VK_F11 },
+ { key: "F12", keyCode: KeyboardEvent.DOM_VK_F12 },
+ { key: "F13", keyCode: KeyboardEvent.DOM_VK_F13 },
+ { key: "F14", keyCode: KeyboardEvent.DOM_VK_F14 },
+ { key: "F15", keyCode: KeyboardEvent.DOM_VK_F15 },
+ { key: "F16", keyCode: KeyboardEvent.DOM_VK_F16 },
+ { key: "F17", keyCode: KeyboardEvent.DOM_VK_F17 },
+ { key: "F18", keyCode: KeyboardEvent.DOM_VK_F18 },
+ { key: "F19", keyCode: KeyboardEvent.DOM_VK_F19 },
+ { key: "F20", keyCode: KeyboardEvent.DOM_VK_F20 },
+ { key: "F21", keyCode: KeyboardEvent.DOM_VK_F21 },
+ { key: "F22", keyCode: KeyboardEvent.DOM_VK_F22 },
+ { key: "F23", keyCode: KeyboardEvent.DOM_VK_F23 },
+ { key: "F24", keyCode: KeyboardEvent.DOM_VK_F24 },
+ { key: "NumLock", keyCode: KeyboardEvent.DOM_VK_NUM_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "ScrollLock", keyCode: KeyboardEvent.DOM_VK_SCROLL_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "AudioVolumeMute", keyCode: KeyboardEvent.DOM_VK_VOLUME_MUTE },
+ { key: "AudioVolumeDown", keyCode: KeyboardEvent.DOM_VK_VOLUME_DOWN },
+ { key: "AudioVolumeUp", keyCode: KeyboardEvent.DOM_VK_VOLUME_UP },
+ { key: "Meta", keyCode: KeyboardEvent.DOM_VK_META, isModifier: true },
+ { key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALTGR, isModifier: true },
+ { key: "Attn", keyCode: KeyboardEvent.DOM_VK_ATTN },
+ { key: "CrSel", keyCode: KeyboardEvent.DOM_VK_CRSEL },
+ { key: "ExSel", keyCode: KeyboardEvent.DOM_VK_EXSEL },
+ { key: "EraseEof", keyCode: KeyboardEvent.DOM_VK_EREOF },
+ { key: "Play", keyCode: KeyboardEvent.DOM_VK_PLAY },
+ { key: "ZoomToggle", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "ZoomIn", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "ZoomOut", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "Unidentified", keyCode: 0 },
+ { key: "a", keyCode: 0, isPrintable: true },
+ { key: "A", keyCode: 0, isPrintable: true },
+ { key: " ", keyCode: 0, isPrintable: true },
+ { key: "", keyCode: 0, isPrintable: true },
+ ];
+
+ for (var i = 0; i < kKeyToKeyCode.length; i++) {
+ var keyEvent = new KeyboardEvent("", { key: kKeyToKeyCode[i].key });
+ var causeKeypress = !kKeyToKeyCode[i].isModifier;
+ var baseFlags = kKeyToKeyCode[i].isPrintable ? 0 : TIP.KEY_NON_PRINTABLE_KEY;
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ // If the keyCode isn't initialized or initialized with 0, it should be computed from the key value only when it's a printable key.
+ TIP.keydown(keyEvent, baseFlags);
+ TIP.keyup(keyEvent, baseFlags);
+ var longDesc = description + "testing computation of .keyCode of \"" + kKeyToKeyCode[i].key + "\", ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : kKeyToKeyCode[i].keyCode,
+ longDesc + " type=\"" + events[j].type + "\", keyCode value is wrong");
+ }
+ // However, if KEY_KEEP_KEYCODE_ZERO is specified, .keyCode value should be kept as 0.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
+ var longDesc = description + "testing if .keyCode is forcibly set to KEY_KEEP_KEYCODE_ZERO, ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, 0,
+ longDesc + " type=\"" + events[j].type + "\", keyCode value is not 0");
+ }
+ // If .keyCode is initialized with non-zero value, the value shouldn't be computed again.
+ var keyEventWithLocation = new KeyboardEvent("", { key: kKeyToKeyCode[i].key, keyCode: 0xFF });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEventWithLocation, baseFlags);
+ TIP.keyup(keyEventWithLocation, baseFlags);
+ longDesc = description + "testing if .keyCode is not computed for \"" + kKeyToKeyCode[i].key + "\", ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : 0xFF,
+ longDesc + " type=\"" + events[j].type + "\", keyCode shouldn't be computed if it's initialized with non-zero value");
+ }
+ // Unlock lockable modifier if the key is a lockable modifier key.
+ if (kKeyToKeyCode[i].isLockableModifier) {
+ TIP.keydown(keyEvent, baseFlags);
+ TIP.keyup(keyEvent, baseFlags);
+ }
+ }
+
+ // Modifier state tests
+ var sharedTIP = createTIP();
+ ok(sharedTIP.beginInputTransactionForTests(otherWindow),
+ description + "sharedTIP.beginInputTransactionForTests(otherWindow) should return true");
+ TIP.shareModifierStateOf(sharedTIP);
+ var independentTIP = createTIP();
+ const kModifierKeys = [
+ { key: "Alt", code: "AltLeft", isLockable: false },
+ { key: "Alt", code: "AltRight", isLockable: false },
+ { key: "AltGraph", code: "AltRight", isLockable: false },
+ { key: "CapsLock", code: "CapsLock", isLockable: true },
+ { key: "Control", code: "ControlLeft", isLockable: false },
+ { key: "Control", code: "ControlRight", isLockable: false },
+ { key: "Fn", code: "Fn", isLockable: false },
+ { key: "FnLock", code: "", isLockable: true },
+ { key: "Meta", code: "OSLeft", isLockable: false },
+ { key: "Meta", code: "OSRight", isLockable: false },
+ { key: "NumLock", code: "NumLock", isLockable: true },
+ { key: "ScrollLock", code: "ScrollLock", isLockable: true },
+ { key: "Shift", code: "ShiftLeft", isLockable: false },
+ { key: "Shift", code: "ShiftRight", isLockable: false },
+ { key: "Symbol", code: "", isLockable: false },
+ { key: "SymbolLock", code: "", isLockable: true },
+ { key: "OS", code: "OSLeft", isLockable: false },
+ { key: "OS", code: "OSRight", isLockable: false },
+ ];
+
+ function checkModifiers(aTestDesc, aEvent, aType, aKey, aCode, aModifiers)
+ {
+ var desc = description + aTestDesc + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\"";
+ is(aEvent.type, aType,
+ desc + ", .type value is wrong");
+ if (aEvent.type != aType) {
+ return;
+ }
+ is(aEvent.key, aKey,
+ desc + ", .key value is wrong");
+ is(aEvent.code, aCode,
+ desc + ", .code value is wrong");
+ is(aEvent.altKey, aModifiers.includes("Alt"),
+ desc + ", .altKey value is wrong");
+ is(aEvent.ctrlKey, aModifiers.includes("Control"),
+ desc + ", .ctrlKey value is wrong");
+ is(aEvent.metaKey, aModifiers.includes("Meta"),
+ desc + ", .metaKey value is wrong");
+ is(aEvent.shiftKey, aModifiers.includes("Shift"),
+ desc + ", .shiftKey value is wrong");
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+
+ function checkAllTIPModifiers(aTestDesc, aModifiers)
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(TIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ aTestDesc + ", TIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ is(sharedTIP.getModifierState(kModifiers[i]), TIP.getModifierState(kModifiers[i]),
+ aTestDesc + ", sharedTIP.getModifierState(\"" + kModifiers[i] + "\") returns different value from TIP");
+ is(independentTIP.getModifierState(kModifiers[i]), false,
+ aTestDesc + ", independentTIP.getModifierState(\"" + kModifiers[i] + "\") should return false");
+ }
+ }
+
+ // First, all modifiers must be false.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) should cause keydown, keypress and keyup");
+ checkModifiers("Before dispatching modifier key events", events[0], "keydown", "a", "KeyA", []);
+ checkModifiers("Before dispatching modifier key events", events[1], "keypress", "a", "KeyA", []);
+ checkModifiers("Before dispatching modifier key events", events[2], "keyup", "a", "KeyA", []);
+
+ // Test each modifier keydown/keyup causes activating/inactivating the modifier state.
+ for (var i = 0; i < kModifierKeys.length; i++) {
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ var modKey = new KeyboardEvent("", { key: kModifierKeys[i].key, code: kModifierKeys[i].code });
+ var testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") and a printable key";
+ if (!kModifierKeys[i].isLockable) {
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keyup", [ ]);
+ is(events.length, 5,
+ description + testDesc + " should cause 5 events");
+ checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[4], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+
+ // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ is(events.length, 6,
+ description + testDesc + " should cause 6 events");
+ checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]);
+ } else {
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keyup", [ kModifierKeys[i].key ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keydown", [ ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keyup", [ ]);
+ is(events.length, 7,
+ description + testDesc + " should cause 7 events");
+ checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[5], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+ checkModifiers(testDesc, events[6], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+
+ // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ is(events.length, 6,
+ description + testDesc + " should cause 6 events");
+ checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]);
+ }
+ }
+
+ // Modifier state should be inactivated only when all pressed modifiers are released
+ var shiftLeft = new KeyboardEvent("", { key: "Shift", code: "ShiftLeft" });
+ var shiftRight = new KeyboardEvent("", { key: "Shift", code: "ShiftRight" });
+ var shiftVirtual = new KeyboardEvent("", { key: "Shift", code: "" });
+ var altGrVirtual = new KeyboardEvent("", { key: "AltGraph", code: "" });
+ var ctrlVirtual = new KeyboardEvent("", { key: "Control", code: "" });
+
+ var testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftRight press -> ShiftLeft release -> ShiftRight release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ ]);
+
+ testDesc = "ShiftLeft press -> virtual Shift press -> virtual Shift release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "virtual Shift press -> ShiftRight press -> ShiftRight release -> virtual Shift release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftRight release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup again", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup again)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup again)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 14,
+ description + testDesc + " should cause 14 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[13], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftLeft press -> ShiftLeft release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown again", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup again", [ ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup again)", [ ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup again)", [ ]);
+
+ is(events.length, 13,
+ description + testDesc + " should cause 13 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+ checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ ]);
+
+ testDesc = "virtual Shift press -> virtual AltGraph press -> virtual AltGraph release -> virtual Shift release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
+ TIP.keyup(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-AltGraph keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-AltGraph keyup)", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[5], "keyup", "AltGraph", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]);
+
+ testDesc = "virtual Shift press -> virtual AltGraph press -> virtual Shift release -> virtual AltGr release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "AltGraph" ]);
+ TIP.keyup(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[9], "keyup", "AltGraph", "", [ ]);
+
+ // shareModifierStateOf(null) should cause resetting the modifier state
+ function checkTIPModifiers(aTestDesc, aTIP, aModifiers)
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aTIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ description + aTestDesc + ", aTIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+ TIP.keydown(shiftVirtual);
+ TIP.keydown(altGrVirtual);
+ sharedTIP.shareModifierStateOf(null);
+ checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) shouldn't cause TIP's modifiers reset", TIP, [ "Shift", "AltGraph" ]);
+ checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) should cause sharedTIP modifiers reset", sharedTIP, [ ]);
+
+ // sharedTIP.shareModifierStateOf(null) should be unlinked from TIP.
+ TIP.keydown(ctrlVirtual);
+ checkTIPModifiers("TIP.keydown(ctrlVirtual) should cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
+ checkTIPModifiers("TIP.keydown(ctrlVirtual) shouldn't cause sharedTIP modifiers set", sharedTIP, [ ]);
+
+ // beginInputTransactionForTests() shouldn't cause modifier state reset.
+ ok(TIP.beginInputTransactionForTests(otherWindow),
+ description + "TIP.beginInputTransactionForTests(otherWindow) should return true");
+ checkTIPModifiers("TIP.beginInputTransactionForTests(otherWindow) shouldn't cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
+ TIP.keyup(shiftLeft);
+ TIP.keyup(altGrVirtual);
+ TIP.keyup(ctrlVirtual);
+ checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ "Shift" ]);
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests(window) should return true");
+ checkTIPModifiers("TIP.beginInputTransactionForTests(window) shouldn't cause TIP's modifiers set", TIP, [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ ]);
+
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runErrorTests()
+{
+ var description = "runErrorTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.value = "";
+ input.focus();
+
+ // startComposition() should throw an exception if there is already a composition
+ TIP.startComposition();
+ try {
+ TIP.startComposition();
+ ok(false,
+ description + "startComposition() should fail if it was already called");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "startComposition() should cause NS_ERROR_FAILURE if there is already composition");
+ } finally {
+ TIP.cancelComposition();
+ }
+
+ // cancelComposition() should throw an exception if there is no composition
+ try {
+ TIP.cancelComposition();
+ ok(false,
+ description + "cancelComposition() should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "cancelComposition() should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // commitComposition() without commit string should throw an exception if there is no composition
+ try {
+ TIP.commitComposition();
+ ok(false,
+ description + "commitComposition() should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // commitCompositionWith("") should throw an exception if there is no composition
+ try {
+ TIP.commitCompositionWith("");
+ ok(false,
+ description + "commitCompositionWith(\"\") should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "commitCompositionWith(\"\") should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // Pending composition string should allow to flush without clause information (for compatibility)
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.flushPendingComposition();
+ ok(true,
+ description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(false,
+ description + "flushPendingComposition() shouldn't cause an exception even if appendClauseToPendingComposition() has never been called");
+ }
+
+ // Pending composition string must be filled by clause information
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if appendClauseToPendingComposition() doesn't fill all composition string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() doesn't fill all composition string");
+ }
+
+ // Pending composition string must not be shorter than appended clause length
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if appendClauseToPendingComposition() appends longer clause information");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() appends longer clause information");
+ }
+
+ // Pending composition must not have clause information with empty string
+ try {
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if there is a clause with empty string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if there is a clause with empty string");
+ }
+
+ // Appending a clause whose length is 0 should cause an exception
+ try {
+ TIP.appendClauseToPendingComposition(0, TIP.ATTR_RAW_CLAUSE);
+ ok(false,
+ description + "appendClauseToPendingComposition() should fail if the length is 0");
+ TIP.flushPendingComposition();
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the length is 0");
+ }
+
+ // Appending a clause whose attribute is invalid should cause an exception
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, 0);
+ ok(false,
+ description + "appendClauseToPendingComposition() should fail if the attribute is invalid");
+ TIP.flushPendingComposition();
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the attribute is invalid");
+ }
+
+ // Setting caret position outside of composition string should cause an exception
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(4);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if caret position is out of composition string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if caret position is out of composition string");
+ }
+
+ // Calling keydown() with a KeyboardEvent initialized with invalid code value should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP.keydown(keyInvalidCode);
+ ok(false,
+ description + "TIP.keydown(keyInvalidCode) should cause throwing an exception because its code value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP.keydown(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keyup() with a KeyboardEvent initialized with invalid code value should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP.keyup(keyInvalidCode);
+ ok(false,
+ description + "TIP.keyup(keyInvalidCode) should cause throwing an exception because its code value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP.keyup(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keydown(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
+ TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
+ ok(false,
+ description + "TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keyup(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
+ TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
+ ok(false,
+ description + "TIP.keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // KEY_KEEP_KEY_LOCATION_STANDARD flag should be used only when .location is not initialized with non-zero value.
+ try {
+ var keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ ok(false,
+ description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ ok(false,
+ description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value");
+ }
+
+ // KEY_KEEP_KEYCODE_ZERO flag should be used only when .keyCode is not initialized with non-zero value.
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
+ ok(false,
+ description + "keydown(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
+ ok(false,
+ description + "keyup(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keyup(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
+ }
+
+ // Specifying KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT with non-modifier key, it should cause an exception.
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "a", code: "ShiftLeft" });
+ TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ ok(false,
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", code: "ShiftLeft" });
+ TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ ok(false,
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
+ }
+
+ // The type of key events specified to composition methods should be "" or "keydown".
+ var kKeyEventTypes = [
+ { type: "keydown", valid: true },
+ { type: "keypress", valid: false },
+ { type: "keyup", valid: false },
+ { type: "", valid: true },
+ { type: "mousedown", valid: false },
+ { type: "foo", valid: false },
+ ];
+ for (var i = 0; i < kKeyEventTypes[i].length; i++) {
+ var keyEvent =
+ new KeyboardEvent(kKeyEventTypes[i].type, { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var testDescription = description + "type=\"" + kKeyEventTypes[i].type + "\", ";
+ try {
+ TIP.startComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.startComposition(keyEvent) should not accept the event type");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.startComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.startComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.flushPendingComposition(keyEvent) should not accept the event type");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.flushPendingComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.flushPendingComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.startComposition();
+ TIP.commitComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitComposition(keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.commitComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ TIP.cancelComposition();
+ }
+ try {
+ TIP.commitCompositionWith("foo", keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.startComposition();
+ TIP.cancelComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.cancelComposition(keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.cancelComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.cancelComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ TIP.cancelComposition();
+ }
+ input.value = "";
+ }
+}
+
+function runCommitCompositionTests()
+{
+ var description = "runCommitCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.focus();
+
+ // commitComposition() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ TIP.commitComposition();
+ is(input.value, "foo",
+ description + "commitComposition() should commit the composition with the last data");
+
+ // commitCompositionWith("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ TIP.commitCompositionWith("");
+ is(input.value, "",
+ description + "commitCompositionWith(\"\") should commit the composition with empty string");
+
+ function doCommit(aText)
+ {
+ TIP.commitCompositionWith(aText);
+ }
+
+ // doCommit() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit();
+ todo_is(input.value, "foo",
+ description + "doCommit() should commit the composition with the last data");
+
+ // doCommit("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit("");
+ is(input.value, "",
+ description + "doCommit(\"\") should commit the composition with empty string");
+
+ // doCommit(null) should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit(null);
+ is(input.value, "",
+ description + "doCommit(null) should commit the composition with empty string");
+
+ // doCommit(undefined) should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit(undefined);
+ todo_is(input.value, "foo",
+ description + "doCommit(undefined) should commit the composition with the last data");
+
+ function doCommitWithNullCheck(aText)
+ {
+ TIP.commitCompositionWith(aText ? aText : "");
+ }
+
+ // doCommitWithNullCheck() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck();
+ is(input.value, "",
+ description + "doCommitWithNullCheck() should commit the composition with empty string");
+
+ // doCommitWithNullCheck("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck("");
+ is(input.value, "",
+ description + "doCommitWithNullCheck(\"\") should commit the composition with empty string");
+
+ // doCommitWithNullCheck(null) should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck(null);
+ is(input.value, "",
+ description + "doCommitWithNullCheck(null) should commit the composition with empty string");
+
+ // doCommitWithNullCheck(undefined) should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck(undefined);
+ is(input.value, "",
+ description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
+}
+
+function runUnloadTests1()
+{
+ return new Promise(resolve => {
+ let description = "runUnloadTests1(): ";
+
+ let TIP1 = createTIP();
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests() should succeed");
+
+ let oldSrc = iframe.src;
+ let parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ childWindow = iframe.contentWindow;
+ textareaInFrame = null;
+ iframe.addEventListener("load", function () {
+ ok(true, description + "old iframe is restored");
+ // And also restore the iframe information with restored contents.
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ SimpleTest.executeSoon(resolve);
+ }, {capture: true, once: true});
+
+ // The composition should be committed internally. So, another TIP should
+ // be able to steal the rights to using TextEventDispatcher.
+ let TIP2 = createTIP();
+ ok(TIP2.beginInputTransactionForTests(parentWindow),
+ description + "TIP2.beginInputTransactionForTests() should succeed");
+
+ input.focus();
+ input.value = "";
+
+ TIP2.setPendingCompositionString("foo");
+ TIP2.appendClauseToPendingComposition(3, TIP2.ATTR_RAW_CLAUSE);
+ TIP2.setCaretInPendingComposition(3);
+ TIP2.flushPendingComposition();
+ is(input.value, "foo",
+ description + "the input in the parent document should have composition string");
+
+ TIP2.cancelComposition();
+
+ // Restore the old iframe content.
+ iframe.src = oldSrc;
+ }, {capture: true, once: true});
+
+ // Start composition in the iframe.
+ textareaInFrame.value = "";
+ textareaInFrame.focus();
+
+ TIP1.setPendingCompositionString("foo");
+ TIP1.appendClauseToPendingComposition(3, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.setCaretInPendingComposition(3);
+ TIP1.flushPendingComposition();
+ is(textareaInFrame.value, "foo",
+ description + "the textarea in the iframe should have composition string");
+
+ // Load different web page on the frame.
+ iframe.src = "data:text/html,<body>dummy page</body>";
+ });
+}
+
+function runUnloadTests2()
+{
+ return new Promise(resolve => {
+ let description = "runUnloadTests2(): ";
+
+ let TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(childWindow),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ let oldSrc = iframe.src;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ childWindow = iframe.contentWindow;
+ textareaInFrame = null;
+ iframe.addEventListener("load", function () {
+ ok(true, description + "old iframe is restored");
+ // And also restore the iframe information with restored contents.
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ SimpleTest.executeSoon(resolve);
+ }, {capture: true, once: true});
+
+ input.focus();
+ input.value = "";
+
+ // TIP should be still available in the same top level widget.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ if (input.value == "") {
+ // XXX TextInputProcessor or TextEventDispatcher may have a bug.
+ todo_is(input.value, "bar",
+ description + "the input in the parent document should have composition string");
+ } else {
+ is(input.value, "bar",
+ description + "the input in the parent document should have composition string");
+ }
+
+ TIP.cancelComposition();
+
+ // Restore the old iframe content.
+ iframe.src = oldSrc;
+ }, {capture: true, once: true});
+
+ // Start composition in the iframe.
+ textareaInFrame.value = "";
+ textareaInFrame.focus();
+
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ is(textareaInFrame.value, "foo",
+ description + "the textarea in the iframe should have composition string");
+
+ // Load different web page on the frame.
+ iframe.src = "data:text/html,<body>dummy page</body>";
+ });
+}
+
+async function runCallbackTests(aForTests)
+{
+ let description = "runCallbackTests(aForTests=" + aForTests + "): ";
+
+ input.value = "";
+ input.focus();
+ input.blur();
+
+ let TIP = createTIP();
+ let notifications = [];
+ let waitingNextNotification;
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP == TIP) {
+ notifications.push(aNotification);
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (waitingNextNotification) {
+ SimpleTest.executeSoon(waitingNextNotification);
+ waitingNextNotification = undefined;
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (let i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ description + "Unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ function waitUntilNotificationsReceived()
+ {
+ return new Promise(resolve => {
+ if (notifications.length) {
+ SimpleTest.executeSoon(resolve);
+ } else {
+ waitingNextNotification = resolve;
+ }
+ });
+ }
+
+ function checkPositionChangeNotification(aNotification, aDescription)
+ {
+ is(!aNotification || aNotification.type, "notify-position-change",
+ aDescription + " should cause position change notification");
+ }
+
+ function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(aNotification.type, "notify-selection-change",
+ aDescription + " should cause selection change notification");
+ if (aNotification.type != "notify-selection-change") {
+ return;
+ }
+ is(aNotification.hasRange, aExpected.hasRange !== false,
+ `${aDescription} should cause selection change notification whose hasRange is ${aExpected.hasRange}`);
+ if (aNotification.hasRange) {
+ is(aNotification.offset, aExpected.offset,
+ `${aDescription} should cause selection change notification whose offset is ${aExpected.offset}`);
+ is(aNotification.text, aExpected.text,
+ `${aDescription} should cause selection change notification whose text is "${aExpected.text}"`);
+ is(aNotification.length, aExpected.text.length,
+ `${aDescription} should cause selection change notification whose length is ${aExpected.text.length}`);
+ is(aNotification.reversed, aExpected.reversed || false,
+ `${aDescription} should cause selection change notification whose reversed is ${aExpected.reversed || false}`);
+ }
+ is(aNotification.collapsed, aExpected.hasRange === false || !aExpected.text.length,
+ `${aDescription} should cause selection change notification whose collapsed is ${aExpected.hasRange === false || !aExpected.text.length}`);
+ is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
+ `${aDescription} should cause selection change notification whose writingMode is ${aExpected.writingMode || "horizontal-tb"}`);
+ is(aNotification.causedByComposition, aExpected.causedByComposition || false,
+ `${aDescription} should cause selection change notification whose causedByComposition is ${aExpected.causedByComposition || false}`);
+ is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
+ `${aDescription} should cause selection change notification whose causedBySelectionEvent is ${aExpected.causedBySelectionEvent || false}`);
+ is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
+ `${aDescription} should cause cause selection change notification whose occurredDuringComposition is ${aExpected.occurredDuringComposition || false}`);
+ }
+
+ function checkTextChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(aNotification.type, "notify-text-change",
+ aDescription + " should cause text change notification");
+ if (aNotification.type != "notify-text-change") {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause text change notification whose offset is " + aExpected.offset);
+ is(aNotification.removedLength, aExpected.removedLength,
+ aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
+ is(aNotification.addedLength, aExpected.addedLength,
+ aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
+ is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
+ aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
+ is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
+ aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
+ is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
+ aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
+ }
+
+ if (aForTests) {
+ TIP.beginInputTransactionForTests(window, callback);
+ } else {
+ TIP.beginInputTransaction(window, callback);
+ }
+
+ notifications = [];
+ input.focus();
+ is(notifications.length, 1,
+ description + "input.focus() should cause a notification");
+ is(notifications[0].type, "notify-focus",
+ description + "input.focus() should cause \"notify-focus\"");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ input.blur();
+ is(notifications.length, 1,
+ description + "input.blur() should cause a notification");
+ is(notifications[0].type, "notify-blur",
+ description + "input.blur() should cause \"notify-focus\"");
+ dumpUnexpectedNotifications(1);
+
+ input.focus();
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(notifications.length, 3,
+ description + "creating composition string 'foo' should cause 3 notifications");
+ checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
+ { offset: 0, removedLength: 0, addedLength: 3,
+ causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+ checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
+ { offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
+ checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
+ dumpUnexpectedNotifications(3);
+
+ notifications = [];
+ synthesizeMouseAtCenter(input, {});
+ is(notifications.length, 3,
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
+ is(notifications[0].type, "request-to-commit",
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+ checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
+ { offset: 0, removedLength: 3, addedLength: 3,
+ causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+ checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
+ dumpUnexpectedNotifications(3);
+
+ input.focus();
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ // XXX On macOS, window.moveBy() doesn't cause notify-position-change.
+ // Investigate this later (although, we cannot notify position change to
+ // native IME on macOS).
+ // Wayland also does not support it.
+ var isWayland = Services.prefs.getBoolPref("widget.wayland.test-workarounds.enabled", false);
+ if (!kIsMac && !isWayland) {
+ window.moveBy(0, 10);
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ description + "window.moveBy(0, 10) should cause a notification");
+ checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ window.moveBy(10, 0);
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ description + "window.moveBy(10, 0) should cause a notification");
+ checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
+ dumpUnexpectedNotifications(1);
+ }
+
+ input.focus();
+ input.value = "abc"
+ notifications = [];
+ input.selectionStart = input.selectionEnd = 0;
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ let rightArrowKeyEvent =
+ new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowRight key press should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ let shiftKeyEvent =
+ new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
+ let leftArrowKeyEvent =
+ new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
+ TIP.keydown(shiftKeyEvent);
+ TIP.keydown(leftArrowKeyEvent);
+ TIP.keyup(leftArrowKeyEvent);
+ TIP.keyup(shiftKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowLeft key press with Shift should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
+ dumpUnexpectedNotifications(1);
+
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ notifications = [];
+ TIP.keydown(shiftKeyEvent);
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ TIP.keyup(shiftKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowRight key press with Shift should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ input.editor.selection.removeAllRanges();
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ `${description}Removing all selection ranges should cause a selection change notification`);
+ checkSelectionChangeNotification(
+ notifications[0],
+ `${description}Removing all selection ranges in editor`,
+ { hasRange: false }
+ );
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ let TIP2 = createTIP();
+ if (aForTests) {
+ TIP2.beginInputTransactionForTests(window, callback);
+ } else {
+ TIP2.beginInputTransaction(window, callback);
+ }
+ is(notifications.length, 1,
+ description + "Initializing another TIP should cause a notification");
+ is(notifications[0].type, "notify-end-input-transaction",
+ description + "Initializing another TIP should cause \"notify-detached\"");
+ dumpUnexpectedNotifications(1);
+}
+
+async function runFocusNotificationTestAfterDrop() {
+ const inputs = document.querySelectorAll("input[type=text]");
+ inputs[0].value = "abc";
+ inputs[1].value = "";
+
+ const TIP = createTIP();
+ let notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ notifications.push(aNotification.type);
+ break;
+ }
+ return true;
+ }
+
+ inputs[0].focus();
+ TIP.beginInputTransactionForTests(window, callback);
+ inputs[0].select();
+ try {
+ notifications = [];
+ await synthesizePlainDragAndDrop({
+ srcSelection: SpecialPowers.wrap(inputs[0]).editor.selection,
+ destElement: inputs[1],
+ });
+ } catch (ex) {
+ ok(false, `runFocusNotificationTestAfterDrop: unexpected error during DnD (${ex.message})`);
+ return;
+ }
+ is(
+ document.activeElement,
+ inputs[1],
+ "runFocusNotificationTestAfterDrop: Dropping to the second <input> should make it focused"
+ );
+ ok(
+ notifications.length > 1,
+ "runFocusNotificationTestAfterDrop: At least two notifications should be fired"
+ );
+ if (notifications.length) {
+ is(
+ notifications[notifications.length - 1],
+ "notify-focus",
+ "runFocusNotificationTestAfterDrop: focus notification should've been fired at last"
+ );
+ }
+}
+
+async function runQuerySelectionEventTestAtTextChangeNotification() {
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ contenteditable.focus();
+ // Ensure to send notify-focus from IMEContentObserver
+ await new Promise(
+ resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+ document.execCommand("selectall");
+ // Ensure to send notify-selection-change from IMEContentObserver
+ await new Promise(
+ resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+
+ const kTestName = "runQuerySelectionEventTestAtTextChangeNotification";
+ await new Promise(resolve => {
+ const TIP = createTIP();
+ TIP.beginInputTransactionForTests(window, (aTIP, aNotification) => {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-text-change":
+ const textContent = synthesizeQueryTextContent(0, 100);
+ if (textContent?.text.includes("abc")) {
+ break; // Different notification which we want to test, wait next one.
+ }
+ ok(
+ textContent?.succeeded,
+ `${kTestName}: query text content should succeed from notify-text-change handler`
+ );
+ const selectedText = synthesizeQuerySelectedText();
+ ok(
+ selectedText?.succeeded,
+ `${kTestName}: query selected text should succeed from notify-text-change handler`
+ );
+ if (textContent?.succeeded && selectedText?.succeeded) {
+ is(
+ selectedText.text,
+ textContent.text,
+ `${kTestName}: selected text should be same as all text`
+ );
+ }
+ resolve();
+ break;
+ }
+ return true;
+ });
+ // TODO: We want to do this while selection is batched but can flush
+ // pending notifications, however, I have no idea how to do it.
+ contenteditable.firstChild.remove();
+ });
+}
+
+async function runIMEStateUpdateTests() {
+ const TIP = createTIP();
+ let notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ notifications.push(aNotification.type);
+ break;
+ }
+ return true;
+ }
+
+ contenteditable.focus();
+ TIP.beginInputTransactionForTests(window, callback);
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flushing pending notifications if there is.
+
+ // run IMEStateManager::UpdateIMEState to disable IME
+ notifications = [];
+ const editor = getHTMLEditor(window);
+ editor.flags |= Ci.nsIEditor.eEditorReadonlyMask;
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flush pending notification even if handled asynchronously.
+ is(
+ notifications.length ? notifications[0] : undefined,
+ "notify-blur",
+ "runIMEStateUpdateTests: Making the HTMLEditor readonly should cause a blur notification"
+ );
+ is(
+ notifications.length,
+ 1,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should not cause any other notifications, but got ${
+ notifications.length > 1 ? notifications[1] : ""
+ } notification`
+ );
+ is(
+ SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
+ );
+
+ // run IMEStateManager::UpdateIMEState to enable IME
+ notifications = [];
+ editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask;
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flush pending notification even if handled asynchronously.
+ is(
+ notifications.length ? notifications[0] : undefined,
+ "notify-focus",
+ "runIMEStateUpdateTests: Making the HTMLEditor editable should cause a focus notification without blur notification"
+ );
+ is(
+ notifications.length,
+ 1,
+ `runIMEStateUpdateTests: Making the HTMLEditor editable should not cause any other notifications, but got ${
+ notifications.length > 1 ? notifications[1] : ""
+ } notification`
+ );
+ is(
+ SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
+ );
+}
+
+async function runTextNotificationChangesDuringNoFrame() {
+ const TIP = createTIP();
+ let onTextChange;
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-text-change":
+ if (onTextChange) {
+ onTextChange(aNotification);
+ }
+ break;
+ }
+ return true;
+ }
+
+ function promiseTextChangeNotification() {
+ return new Promise(resolve => onTextChange = resolve);
+ }
+
+ function waitForTick() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ }
+
+ const input = document.querySelector("input[type=text]");
+ input.focus();
+ TIP.beginInputTransactionForTests(window, callback);
+
+ await (async function test_text_change_notification_for_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Changed";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Changed".length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ await (async function test_text_change_notification_for_multiple_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multiple_value_set_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Changed";
+ input.value = "Again!";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Again!".length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ await (async function test_text_change_notification_for_value_set_and_typing_character_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_and_typing_character_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Change";
+ const dKey = new KeyboardEvent("", { code: "KeyD", key: "d", keyCode: KeyboardEvent.DOM_VK_D });
+ TIP.keydown(dKey);
+ TIP.keyup(dKey);
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Change".length,
+ `${description}: addedLength should be the length of the new (set) value`
+ );
+ })();
+
+ input.style.display = "";
+
+ textarea.focus();
+ TIP.beginInputTransaction(window, callback);
+
+ await (async function test_text_change_notification_for_multi_line_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multi_line_value_set_during_no_frame";
+ textarea.value = "Start\n2nd Line";
+ textarea.style.display = "inline";
+ textarea.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ textarea.style.display = "block";
+ textarea.value = "Changed\n2nd Line";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ getNativeText("Start\n2nd Line").length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ getNativeText("Changed\n2nd Line").length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ textarea.style.display = "";
+}
+
+async function runTests()
+{
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ runBeginInputTransactionMethodTests();
+ runReleaseTests();
+ runCompositionTests();
+ runCompositionWithKeyEventTests();
+ runConsumingKeydownBeforeCompositionTests();
+ runKeyTests();
+ runErrorTests();
+ runCommitCompositionTests();
+ await runCallbackTests(false);
+ await runCallbackTests(true);
+ await runTextNotificationChangesDuringNoFrame();
+ await runFocusNotificationTestAfterDrop();
+ await runUnloadTests1();
+ await runUnloadTests2();
+ await runQuerySelectionEventTestAtTextChangeNotification();
+ await runIMEStateUpdateTests();
+
+ finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/window_swapFrameLoaders.xhtml b/dom/base/test/chrome/window_swapFrameLoaders.xhtml
new file mode 100644
index 0000000000..0a42e975fc
--- /dev/null
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xhtml
@@ -0,0 +1,225 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
+Test swapFrameLoaders with different frame types and remoteness
+-->
+<window title="Mozilla Bug 1242644"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"><![CDATA[
+ ["SimpleTest", "SpecialPowers", "info", "is", "ok", "add_task"].forEach(key => {
+ window[key] = window.arguments[0][key];
+ })
+
+ const NS = {
+ xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ html: "http://www.w3.org/1999/xhtml",
+ }
+
+ const TAG = {
+ xul: "browser",
+ html: "iframe", // mozbrowser
+ }
+
+ const SCENARIOS = [
+ ["xul", "xul"],
+ ["xul", "html"],
+ ["html", "xul"],
+ ["html", "html"],
+ ["xul", "xul", { remote: true }],
+ ["xul", "html", { remote: true }],
+ ["html", "xul", { remote: true }],
+ ["html", "html", { remote: true }],
+ ["xul", "html", { userContextId: 2 }],
+ ["xul", "html", { userContextId: 2, remote: true }],
+ ];
+
+ const HEIGHTS = [
+ 200,
+ 400
+ ];
+
+ function frameScript() {
+ /* eslint-env mozilla/frame-script */
+ addEventListener("load", function onLoad() {
+ sendAsyncMessage("test:load");
+ }, true);
+ }
+
+ // Watch for loads in new frames
+ window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true);
+
+ function once(target, eventName, useCapture = false) {
+ info("Waiting for event: '" + eventName + "' on " + target + ".");
+
+ return new Promise(resolve => {
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addMessageListener", "removeMessageListener"],
+ ]) {
+ if ((add in target) && (remove in target)) {
+ target[add](eventName, function onEvent(...aArgs) {
+ info("Got event: '" + eventName + "' on " + target + ".");
+ target[remove](eventName, onEvent, useCapture);
+ resolve(aArgs);
+ }, useCapture);
+ break;
+ }
+ }
+ });
+ }
+
+ async function addFrame(type, options, height) {
+ let remote = options && options.remote;
+ let userContextId = options && options.userContextId;
+ let frame = document.createElementNS(NS[type], TAG[type]);
+ frame.setAttribute("remote", remote);
+ if (remote && type == "xul") {
+ frame.setAttribute("style", "-moz-binding: none;");
+ }
+ if (userContextId) {
+ frame.setAttribute("usercontextid", userContextId);
+ }
+ if (type == "html") {
+ frame.setAttribute("mozbrowser", "true");
+ frame.setAttribute("noisolation", "true");
+ frame.setAttribute("allowfullscreen", "true");
+ } else if (type == "xul") {
+ frame.setAttribute("type", "content");
+ }
+ let src = `data:text/html,<!doctype html>` +
+ `<body style="height:${height}px"/>`;
+ frame.setAttribute("src", src);
+ document.documentElement.appendChild(frame);
+ let mm = frame.frameLoader.messageManager;
+ await once(mm, "test:load");
+ return frame;
+ }
+
+ add_task(async function() {
+ for (let scenario of SCENARIOS) {
+ let [ typeA, typeB, options ] = scenario;
+ let heightA = HEIGHTS[0];
+ info(`Adding frame A, type ${typeA}, options ${JSON.stringify(options)}, height ${heightA}`);
+ let frameA = await addFrame(typeA, options, heightA);
+
+ let heightB = HEIGHTS[1];
+ info(`Adding frame B, type ${typeB}, options ${JSON.stringify(options)}, height ${heightB}`);
+ let frameB = await addFrame(typeB, options, heightB);
+
+ let frameScriptFactory = function(name) {
+ /* eslint-env mozilla/frame-script */
+ return `function() {
+ addMessageListener("ping", function() {
+ sendAsyncMessage("pong", "${name}");
+ });
+ addMessageListener("check-browser-api", function() {
+ let exists = "api" in this;
+ sendAsyncMessage("check-browser-api", {
+ exists,
+ running: exists && !this.api._shuttingDown,
+ });
+ });
+ addEventListener("pagehide", function({ inFrameSwap }) {
+ sendAsyncMessage("pagehide", inFrameSwap);
+ }, {mozSystemGroup: true});
+ }`;
+ }
+
+ // Load frame script into each frame
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false);
+ mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false);
+ }
+
+ // Ping before swap
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let inflightA = once(mmA, "pong");
+ let inflightB = once(mmB, "pong");
+
+ info("Ping message manager for frame A");
+ mmA.sendAsyncMessage("ping");
+ let [ { data: pongA } ] = await inflightA;
+ is(pongA, "A", "Frame A message manager gets reply A before swap");
+
+ info("Ping message manager for frame B");
+ mmB.sendAsyncMessage("ping");
+ let [ { data: pongB } ] = await inflightB;
+ is(pongB, "B", "Frame B message manager gets reply B before swap");
+ }
+
+ // Ping after swap using message managers acquired before
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let pagehideA = once(mmA, "pagehide");
+ let pagehideB = once(mmB, "pagehide");
+
+ info("swapFrameLoaders");
+ frameA.swapFrameLoaders(frameB);
+
+ let [ { data: inFrameSwapA } ] = await pagehideA;
+ ok(inFrameSwapA, "Frame A got pagehide with inFrameSwap: true");
+ let [ { data: inFrameSwapB } ] = await pagehideB;
+ ok(inFrameSwapB, "Frame B got pagehide with inFrameSwap: true");
+
+ let inflightA = once(mmA, "pong");
+ let inflightB = once(mmB, "pong");
+
+ info("Ping message manager for frame A");
+ mmA.sendAsyncMessage("ping");
+ let [ { data: pongA } ] = await inflightA;
+ is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap");
+
+ info("Ping message manager for frame B");
+ mmB.sendAsyncMessage("ping");
+ let [ { data: pongB } ] = await inflightB;
+ is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap");
+ }
+
+ // Check height after swap
+ {
+ if (frameA.getContentDimensions) {
+ let { height } = await frameA.getContentDimensions();
+ is(height, heightB, "Frame A's content height is 400px after swap");
+ }
+ if (frameB.getContentDimensions) {
+ let { height } = await frameB.getContentDimensions();
+ is(height, heightA, "Frame B's content height is 200px after swap");
+ }
+ }
+
+ // Ping after swap using message managers acquired after
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let inflightA = once(mmA, "pong");
+ let inflightB = once(mmB, "pong");
+
+ info("Ping message manager for frame A");
+ mmA.sendAsyncMessage("ping");
+ let [ { data: pongA } ] = await inflightA;
+ is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap");
+
+ info("Ping message manager for frame B");
+ mmB.sendAsyncMessage("ping");
+ let [ { data: pongB } ] = await inflightB;
+ is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
+ }
+
+ frameA.remove();
+ frameB.remove();
+ }
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/common_postMessages.js b/dom/base/test/common_postMessages.js
new file mode 100644
index 0000000000..e582adb41f
--- /dev/null
+++ b/dom/base/test/common_postMessages.js
@@ -0,0 +1,393 @@
+function getType(a) {
+ if (a === null || a === undefined) {
+ return "null";
+ }
+
+ if (Array.isArray(a)) {
+ return "array";
+ }
+
+ if (typeof a == "object") {
+ return "object";
+ }
+
+ if (
+ SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported() &&
+ a instanceof WebAssembly.Module
+ ) {
+ return "wasm";
+ }
+
+ return "primitive";
+}
+
+function compare(a, b) {
+ is(getType(a), getType(b), "Type matches");
+
+ var type = getType(a);
+ if (type == "array") {
+ is(a.length, b.length, "Array.length matches");
+ for (var i = 0; i < a.length; ++i) {
+ compare(a[i], b[i]);
+ }
+
+ return;
+ }
+
+ if (type == "object") {
+ ok(a !== b, "They should not match");
+
+ var aProps = [];
+ for (var p in a) {
+ aProps.push(p);
+ }
+
+ var bProps = [];
+ for (var p in b) {
+ bProps.push(p);
+ }
+
+ is(aProps.length, bProps.length, "Props match");
+ is(aProps.sort().toString(), bProps.sort().toString(), "Props names match");
+
+ for (var p in a) {
+ compare(a[p], b[p]);
+ }
+
+ return;
+ }
+
+ if (type == "wasm") {
+ var wasmA = new WebAssembly.Instance(a);
+ ok(wasmA instanceof WebAssembly.Instance, "got an instance");
+
+ var wasmB = new WebAssembly.Instance(b);
+ ok(wasmB instanceof WebAssembly.Instance, "got an instance");
+
+ ok(wasmA.exports.foo() === wasmB.exports.foo(), "Same result!");
+ ok(wasmB.exports.foo() === 42, "We want 42");
+ }
+
+ if (type != "null") {
+ is(a, b, "Same value");
+ }
+}
+
+var clonableObjects = [
+ { target: "all", data: "hello world" },
+ { target: "all", data: 123 },
+ { target: "all", data: null },
+ { target: "all", data: true },
+ { target: "all", data: new Date() },
+ { target: "all", data: [1, "test", true, new Date()] },
+ {
+ target: "all",
+ data: { a: true, b: null, c: new Date(), d: [true, false, {}] },
+ },
+ { target: "all", data: new Blob([123], { type: "plain/text" }) },
+ { target: "all", data: new ImageData(2, 2) },
+];
+
+function create_fileList() {
+ var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ var fileList = document.getElementById("fileList");
+ SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
+
+ // Just a simple test
+ var domFile = fileList.files[0];
+ is(domFile.name, "prefs.js", "fileName should be prefs.js");
+
+ clonableObjects.push({ target: "all", data: fileList.files });
+ script.destroy();
+ next();
+ }
+
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+}
+
+function create_directory() {
+ if (navigator.userAgent.toLowerCase().includes("Android")) {
+ next();
+ return;
+ }
+
+ var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ var fileList = document.getElementById("fileList");
+ SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+
+ SpecialPowers.wrap(fileList)
+ .getFilesAndDirectories()
+ .then(function (list) {
+ list = SpecialPowers.unwrap(list);
+ // Just a simple test
+ is(list.length, 1, "This list has 1 element");
+ ok(list[0] instanceof Directory, "We have a directory.");
+
+ clonableObjects.push({ target: "all", data: list[0] });
+ script.destroy();
+ next();
+ });
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open");
+}
+
+function create_wasmModule() {
+ info("Checking if we can play with WebAssembly...");
+
+ if (!SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported()) {
+ next();
+ return;
+ }
+
+ ok(WebAssembly, "WebAssembly object should exist");
+ ok(WebAssembly.compile, "WebAssembly.compile function should exist");
+
+ /*
+ js -e '
+ t = wasmTextToBinary(`
+ (module
+ (func $foo (result i32) (i32.const 42))
+ (export "foo" (func $foo))
+ )
+ `);
+ print(t)
+ '
+ */
+ // prettier-ignore
+ const fooModuleCode = new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,127,3,2,1,0,7,7,1,3,102,111,111,0,0,10,6,1,4,0,65,42,11,0,13,4,110,97,109,101,1,6,1,0,3,102,111,111]);
+
+ WebAssembly.compile(fooModuleCode).then(
+ m => {
+ ok(m instanceof WebAssembly.Module, "The WasmModule has been compiled.");
+ clonableObjects.push({ target: "sameProcess", data: m });
+ next();
+ },
+ () => {
+ ok(false, "The compilation of the wasmModule failed.");
+ }
+ );
+}
+
+function runTests(obj) {
+ ok(
+ "clonableObjectsEveryWhere" in obj &&
+ "clonableObjectsSameProcess" in obj &&
+ "transferableObjects" in obj &&
+ (obj.clonableObjectsEveryWhere ||
+ obj.clonableObjectsSameProcess ||
+ obj.transferableObjects),
+ "We must run some test!"
+ );
+
+ // cloning tests - everyWhere
+ new Promise(function (resolve, reject) {
+ var clonableObjectsId = 0;
+ function runClonableTest() {
+ if (clonableObjectsId >= clonableObjects.length) {
+ resolve();
+ return;
+ }
+
+ var object = clonableObjects[clonableObjectsId++];
+
+ if (object.target != "all") {
+ runClonableTest();
+ return;
+ }
+
+ obj
+ .send(object.data, [])
+ .catch(() => {
+ return { error: true };
+ })
+ .then(received => {
+ if (!obj.clonableObjectsEveryWhere) {
+ ok(received.error, "Error expected");
+ } else {
+ ok(!received.error, "Error not expected");
+ compare(received.data, object.data);
+ }
+ runClonableTest();
+ });
+ }
+
+ runClonableTest();
+ })
+
+ // clonable same process
+ .then(function () {
+ return new Promise(function (resolve, reject) {
+ var clonableObjectsId = 0;
+ function runClonableTest() {
+ if (clonableObjectsId >= clonableObjects.length) {
+ resolve();
+ return;
+ }
+
+ var object = clonableObjects[clonableObjectsId++];
+
+ if (object.target != "sameProcess") {
+ runClonableTest();
+ return;
+ }
+
+ obj
+ .send(object.data, [])
+ .catch(() => {
+ return { error: true };
+ })
+ .then(received => {
+ if (!obj.clonableObjectsSameProcess) {
+ ok(received.error, "Error expected");
+ } else {
+ ok(!received.error, "Error not expected");
+ compare(received.data, object.data);
+ }
+ runClonableTest();
+ });
+ }
+
+ runClonableTest();
+ });
+ })
+
+ // transfering tests
+ .then(function () {
+ if (!obj.transferableObjects) {
+ return;
+ }
+
+ // MessagePort
+ return new Promise(function (r, rr) {
+ var mc = new MessageChannel();
+ obj.send(42, [mc.port1]).then(function (received) {
+ is(received.ports.length, 1, "MessagePort has been transferred");
+ mc.port2.postMessage("hello world");
+ received.ports[0].onmessage = function (e) {
+ is(e.data, "hello world", "Ports are connected!");
+ r();
+ };
+ });
+ });
+ })
+
+ // no dup transfering
+ .then(function () {
+ if (!obj.transferableObjects) {
+ return;
+ }
+
+ // MessagePort
+ return new Promise(function (r, rr) {
+ var mc = new MessageChannel();
+ obj
+ .send(42, [mc.port1, mc.port1])
+ .then(
+ function (received) {
+ ok(false, "Duplicate ports should throw!");
+ },
+ function () {
+ ok(true, "Duplicate ports should throw!");
+ }
+ )
+ .then(r);
+ });
+ })
+
+ // maintaining order of transferred ports
+ .then(function () {
+ if (!obj.transferableObjects) {
+ return;
+ }
+
+ // MessagePort
+ return new Promise(function (r, rr) {
+ var mcs = [];
+ const NPORTS = 50;
+ for (let i = 0; i < NPORTS; i++) {
+ mcs.push(new MessageChannel());
+ }
+ obj
+ .send(
+ 42,
+ mcs.map(channel => channel.port1)
+ )
+ .then(function (received) {
+ is(
+ received.ports.length,
+ NPORTS,
+ `all ${NPORTS} ports transferred`
+ );
+ const promises = Array(NPORTS)
+ .fill()
+ .map(
+ (_, i) =>
+ new Promise(function (subr, subrr) {
+ mcs[i].port2.postMessage(i);
+ received.ports[i].onmessage = e => subr(e.data == i);
+ })
+ );
+ return Promise.all(promises);
+ })
+ .then(function (result) {
+ let in_order = 0;
+ for (const correct of result) {
+ if (correct) {
+ in_order++;
+ }
+ }
+ is(in_order, NPORTS, "All transferred ports are in order");
+ })
+ .then(r);
+ });
+ })
+
+ // non transfering tests
+ .then(function () {
+ if (obj.transferableObjects) {
+ return;
+ }
+
+ // MessagePort
+ return new Promise(function (r, rr) {
+ var mc = new MessageChannel();
+ obj
+ .send(42, [mc.port1])
+ .then(
+ function (received) {
+ ok(false, "This object should not support port transferring");
+ },
+ function () {
+ ok(true, "This object should not support port transferring");
+ }
+ )
+ .then(r);
+ });
+ })
+
+ // done.
+ .then(function () {
+ obj.finished();
+ });
+}
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+var tests = [create_fileList, create_directory, create_wasmModule];
diff --git a/dom/base/test/copypaste.js b/dom/base/test/copypaste.js
new file mode 100644
index 0000000000..01ec2da1b9
--- /dev/null
+++ b/dom/base/test/copypaste.js
@@ -0,0 +1,553 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function modifySelection(s) {
+ var g = window.getSelection();
+ var l = g.getRangeAt(0);
+ var d = document.createElement("p");
+ d.innerHTML = s;
+ d.appendChild(l.cloneContents());
+
+ var e = document.createElement("div");
+ document.body.appendChild(e);
+ e.appendChild(d);
+ var a = document.createRange();
+ a.selectNode(d);
+ g.removeAllRanges();
+ g.addRange(a);
+ window.setTimeout(function () {
+ e.remove();
+ g.removeAllRanges();
+ g.addRange(l);
+ }, 0);
+}
+
+function getLoadContext() {
+ var Ci = SpecialPowers.Ci;
+ return SpecialPowers.wrap(window).docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+async function testCopyPaste(isXHTML) {
+ var suppressUnicodeCheckIfHidden = !!isXHTML;
+ var suppressHTMLCheck = !!isXHTML;
+
+ var docShell = SpecialPowers.wrap(window).docShell;
+
+ var documentViewer = docShell.contentViewer.QueryInterface(
+ SpecialPowers.Ci.nsIContentViewerEdit
+ );
+
+ var clipboard = SpecialPowers.Services.clipboard;
+
+ var textarea = SpecialPowers.wrap(document.getElementById("input"));
+
+ async function copySelectionToClipboard(suppressUnicodeCheck) {
+ await SimpleTest.promiseClipboardChange(
+ () => true,
+ () => {
+ documentViewer.copySelection();
+ }
+ );
+ if (!suppressUnicodeCheck) {
+ ok(
+ clipboard.hasDataMatchingFlavors(["text/plain"], 1),
+ "check text/plain"
+ );
+ }
+ if (!suppressHTMLCheck) {
+ ok(clipboard.hasDataMatchingFlavors(["text/html"], 1), "check text/html");
+ }
+ }
+ function clear(node, suppressUnicodeCheck) {
+ textarea.blur();
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ }
+ async function copyToClipboard(node, suppressUnicodeCheck) {
+ clear();
+ var r = document.createRange();
+ r.selectNode(node);
+ window.getSelection().addRange(r);
+ await copySelectionToClipboard(suppressUnicodeCheck);
+ }
+ function addRange(startNode, startIndex, endNode, endIndex) {
+ var sel = window.getSelection();
+ var r = document.createRange();
+ r.setStart(startNode, startIndex);
+ r.setEnd(endNode, endIndex);
+ sel.addRange(r);
+ }
+ async function copyRangeToClipboard(
+ startNode,
+ startIndex,
+ endNode,
+ endIndex,
+ suppressUnicodeCheck
+ ) {
+ clear();
+ addRange(startNode, startIndex, endNode, endIndex);
+ await copySelectionToClipboard(suppressUnicodeCheck);
+ }
+ async function copyChildrenToClipboard(id) {
+ clear();
+ window.getSelection().selectAllChildren(document.getElementById(id));
+ await copySelectionToClipboard();
+ }
+ function getClipboardData(mime) {
+ var transferable = SpecialPowers.Cc[
+ "@mozilla.org/widget/transferable;1"
+ ].createInstance(SpecialPowers.Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = SpecialPowers.createBlankObject();
+ transferable.getTransferData(mime, data);
+ return data;
+ }
+ function testHtmlClipboardValue(mime, expected) {
+ // For Windows, navigator.platform returns "Win32".
+ var expectedValue = expected;
+ if (navigator.platform.includes("Win")) {
+ // Windows has extra content.
+ var expectedValue =
+ kTextHtmlPrefixClipboardDataWindows +
+ expected.replace(/\n/g, "\n") +
+ kTextHtmlSuffixClipboardDataWindows;
+ }
+ testClipboardValue(mime, expectedValue);
+ }
+ function testClipboardValue(mime, expected) {
+ if (suppressHTMLCheck && mime == "text/html") {
+ return null;
+ }
+ var data = SpecialPowers.wrap(getClipboardData(mime));
+ is(
+ data.value == null
+ ? data.value
+ : data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
+ expected,
+ mime + " value in the clipboard"
+ );
+ return data.value;
+ }
+ function testPasteText(expected) {
+ textarea.value = "";
+ textarea.focus();
+ textarea.editor.paste(1);
+ is(textarea.value, expected, "value of the textarea after the paste");
+ }
+ function testPasteHTML(id, expected) {
+ var contentEditable = $(id);
+ contentEditable.focus();
+ synthesizeKey("v", { accelKey: true });
+ is(contentEditable.innerHTML, expected, id + ".innerHtml after the paste");
+ }
+ function testSelectionToString(expected) {
+ is(
+ window.getSelection().toString().replace(/\r\n/g, "\n"),
+ expected,
+ "Selection.toString"
+ );
+ }
+ function testInnerHTML(id, expected) {
+ var value = document.getElementById(id).innerHTML;
+ is(value, expected, id + ".innerHTML");
+ }
+
+ await copyChildrenToClipboard("draggable");
+ testSelectionToString("This is a draggable bit of text.");
+ testClipboardValue("text/plain", "This is a draggable bit of text.");
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="draggable" title="title to have a long HTML line">This is a <em>draggable</em> bit of text.</div>'
+ );
+ testPasteText("This is a draggable bit of text.");
+
+ await copyChildrenToClipboard("alist");
+ testSelectionToString(" bla\n\n foo\n bar\n\n");
+ testClipboardValue("text/plain", " bla\n\n foo\n bar\n\n");
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="alist">\n bla\n <ul>\n <li>foo</li>\n \n <li>bar</li>\n </ul>\n </div>'
+ );
+ testPasteText(" bla\n\n foo\n bar\n\n");
+
+ await copyChildrenToClipboard("blist");
+ testSelectionToString(" mozilla\n\n foo\n bar\n\n");
+ testClipboardValue("text/plain", " mozilla\n\n foo\n bar\n\n");
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="blist">\n mozilla\n <ol>\n <li>foo</li>\n \n <li>bar</li>\n </ol>\n </div>'
+ );
+ testPasteText(" mozilla\n\n foo\n bar\n\n");
+
+ await copyChildrenToClipboard("clist");
+ testSelectionToString(" mzla\n\n foo\n bazzinga!\n bar\n\n");
+ testClipboardValue(
+ "text/plain",
+ " mzla\n\n foo\n bazzinga!\n bar\n\n"
+ );
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="clist">\n mzla\n <ul>\n <li>foo<ul>\n <li>bazzinga!</li>\n </ul></li>\n \n <li>bar</li>\n </ul>\n </div>'
+ );
+ testPasteText(" mzla\n\n foo\n bazzinga!\n bar\n\n");
+
+ await copyChildrenToClipboard("div4");
+ testSelectionToString(" Tt t t ");
+ testClipboardValue("text/plain", " Tt t t ");
+ if (isXHTML) {
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="div4">\n T<textarea xmlns="http://www.w3.org/1999/xhtml">t t t</textarea>\n</div>'
+ );
+ testInnerHTML(
+ "div4",
+ '\n T<textarea xmlns="http://www.w3.org/1999/xhtml">t t t</textarea>\n'
+ );
+ } else {
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="div4">\n T<textarea>t t t</textarea>\n</div>'
+ );
+ testInnerHTML("div4", "\n T<textarea>t t t</textarea>\n");
+ }
+ testPasteText(" Tt t t ");
+
+ await copyChildrenToClipboard("div5");
+ testSelectionToString(" T ");
+ testClipboardValue("text/plain", " T ");
+ if (isXHTML) {
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="div5">\n T<textarea xmlns="http://www.w3.org/1999/xhtml"> </textarea>\n</div>'
+ );
+ testInnerHTML(
+ "div5",
+ '\n T<textarea xmlns="http://www.w3.org/1999/xhtml"> </textarea>\n'
+ );
+ } else {
+ testHtmlClipboardValue(
+ "text/html",
+ '<div id="div5">\n T<textarea> </textarea>\n</div>'
+ );
+ testInnerHTML("div5", "\n T<textarea> </textarea>\n");
+ }
+ testPasteText(" T ");
+
+ await copyRangeToClipboard(
+ $("div6").childNodes[0],
+ 0,
+ $("div6").childNodes[1],
+ 1,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("");
+ // START Disabled due to bug 564688
+ if (false) {
+ testClipboardValue("text/plain", "");
+ testClipboardValue("text/html", "");
+ }
+ // END Disabled due to bug 564688
+ testInnerHTML("div6", "div6");
+
+ await copyRangeToClipboard(
+ $("div7").childNodes[0],
+ 0,
+ $("div7").childNodes[0],
+ 4,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("");
+ // START Disabled due to bug 564688
+ if (false) {
+ testClipboardValue("text/plain", "");
+ testClipboardValue("text/html", "");
+ }
+ // END Disabled due to bug 564688
+ testInnerHTML("div7", "div7");
+
+ await copyRangeToClipboard(
+ $("div8").childNodes[0],
+ 0,
+ $("div8").childNodes[0],
+ 4,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("");
+ // START Disabled due to bug 564688
+ if (false) {
+ testClipboardValue("text/plain", "");
+ testClipboardValue("text/html", "");
+ }
+ // END Disabled due to bug 564688
+ testInnerHTML("div8", "div8");
+
+ await copyRangeToClipboard(
+ $("div9").childNodes[0],
+ 0,
+ $("div9").childNodes[0],
+ 4,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("div9");
+ testClipboardValue("text/plain", "div9");
+ testHtmlClipboardValue("text/html", "div9");
+ testInnerHTML("div9", "div9");
+
+ await copyToClipboard($("div10"), suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+ testInnerHTML("div10", "div10");
+
+ await copyToClipboard($("div10").firstChild, suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+
+ await copyRangeToClipboard(
+ $("div10").childNodes[0],
+ 0,
+ $("div10").childNodes[0],
+ 1,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("");
+
+ await copyRangeToClipboard(
+ $("div10").childNodes[1],
+ 0,
+ $("div10").childNodes[1],
+ 1,
+ suppressUnicodeCheckIfHidden
+ );
+ testSelectionToString("");
+
+ if (!isXHTML) {
+ // ============ copy/paste multi-range selection (bug 1123505)
+ // with text start node
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var r = document.createRange();
+ var ul = $("ul1");
+ var parent = ul.parentNode;
+ r.setStart(parent, 0);
+ r.setEnd(parent.firstChild, 15);
+ sel.addRange(r); // <div>{Copy1then Paste]<ul id="ul1"><li>LI</li>\n</ul></div>
+
+ r = document.createRange();
+ r.setStart(ul, 1);
+ r.setEnd(parent, 2);
+ sel.addRange(r); // <div>Copy1then Paste<ul id="ul1"><li>LI{</li>\n</ul>}</div>
+ await copySelectionToClipboard(true);
+ testPasteHTML("contentEditable1", "Copy1then Paste"); // The <ul> should not appear because it has no <li>s
+
+ // with text end node
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var r = document.createRange();
+ var ul = $("ul2");
+ var parent = ul.parentNode;
+ r.setStart(parent, 0);
+ r.setEnd(ul, 1);
+ sel.addRange(r); // <div>{<ul id="ul2">\n}<li>LI</li></ul>Copy2then Paste</div>
+
+ r = document.createRange();
+ r.setStart(parent.childNodes[1], 0);
+ r.setEnd(parent, 2);
+ sel.addRange(r); // <div><ul id="ul2">\n<li>LI</li></ul>[Copy2then Paste}</div>
+ await copySelectionToClipboard(true);
+ testPasteHTML("contentEditable2", "Copy2then Paste"); // The <ul> should not appear because it has no <li>s
+
+ // with text end node and non-empty start
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var r = document.createRange();
+ var ul = $("ul3");
+ var parent = ul.parentNode;
+ r.setStart(parent, 0);
+ r.setEnd(ul, 1);
+ sel.addRange(r); // <div>{<ul id="ul3"><li>\n</li>}<li>LI</li></ul>Copy3then Paste</div>
+
+ r = document.createRange();
+ r.setStart(parent.childNodes[1], 0);
+ r.setEnd(parent, 2);
+ sel.addRange(r); // <div><ul id="ul3"><li>\n</li><li>LI</li></ul>[Copy3then Paste}</div>
+ await copySelectionToClipboard(true);
+ testPasteHTML(
+ "contentEditable3",
+ '<ul id="ul3"><li>\n<br></li></ul>Copy3then Paste' // The <ul> should appear because it has a <li>
+ );
+
+ // with elements of different depth
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var r = document.createRange();
+ var div1 = $("div1s");
+ var parent = div1.parentNode;
+ r.setStart(parent, 0);
+ r.setEnd(document.getElementById("div1se1"), 1); // after the "inner" DIV
+ sel.addRange(r);
+
+ r = document.createRange();
+ r.setStart(div1.childNodes[1], 0); // the start of "after"
+ r.setEnd(parent, 1);
+ sel.addRange(r);
+ await copySelectionToClipboard(true);
+ testPasteHTML(
+ "contentEditable4",
+ '<div id="div1s"><div id="div1se1">before</div></div><div id="div1s">after</div>'
+ );
+
+ // with elements of different depth, and a text node at the end
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var r = document.createRange();
+ var div1 = $("div2s");
+ var parent = div1.parentNode;
+ r.setStart(parent, 0);
+ r.setEnd(document.getElementById("div2se1"), 1); // after the "inner" DIV
+ sel.addRange(r);
+
+ r = document.createRange();
+ r.setStart(div1.childNodes[1], 0); // the start of "after"
+ r.setEnd(parent, 1);
+ sel.addRange(r);
+ await copySelectionToClipboard(true);
+ testPasteHTML(
+ "contentEditable5",
+ '<div id="div2s"><div id="div2se1">before</div></div><div id="div2s">after</div>'
+ );
+
+ // crash test for bug 1127835
+ var e1 = document.getElementById("1127835crash1");
+ var e2 = document.getElementById("1127835crash2");
+ var e3 = document.getElementById("1127835crash3");
+ var t1 = e1.childNodes[0];
+ var t3 = e3.childNodes[0];
+
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var r = document.createRange();
+ r.setStart(t1, 1);
+ r.setEnd(e2, 0);
+ sel.addRange(r); // <div>\n<span id="1127835crash1">1[</span><div id="1127835crash2">}<div>\n</div></div><a href="..." id="1127835crash3">3</a>\n</div>
+
+ r = document.createRange();
+ r.setStart(e2, 1);
+ r.setEnd(t3, 0);
+ sel.addRange(r); // <div>\n<span id="1127835crash1">1</span><div id="1127835crash2"><div>\n</div>{</div><a href="..." id="1127835crash3">]3</a>\n</div>
+ await copySelectionToClipboard(true);
+ testPasteHTML(
+ "contentEditable6",
+ '<span id="1127835crash1"></span><div id="1127835crash2"><div>\n</div></div><a href="http://www.mozilla.org/" id="1127835crash3"><br></a>'
+ ); // Don't strip the empty `<a href="...">` element because of avoiding any dataloss provided by the element
+ }
+
+ // ============ copy/paste test from/to a textarea
+
+ var val = "1\n 2\n 3";
+ textarea.value = val;
+ textarea.select();
+ await SimpleTest.promiseClipboardChange(textarea.value, () => {
+ textarea.editor.copy();
+ });
+ textarea.value = "";
+ textarea.editor.paste(1);
+ is(textarea.value, val);
+ textarea.value = "";
+
+ // ============ NOSCRIPT should not be copied
+
+ await copyChildrenToClipboard("div13");
+ testSelectionToString("__");
+ testClipboardValue("text/plain", "__");
+ testHtmlClipboardValue("text/html", '<div id="div13">__</div>');
+ testPasteText("__");
+
+ // ============ converting cell boundaries to tabs in tables
+
+ await copyToClipboard($("tr1"));
+ testClipboardValue("text/plain", "foo\tbar");
+
+ if (!isXHTML) {
+ // ============ spanning multiple rows
+
+ await copyRangeToClipboard($("tr2"), 0, $("tr3"), 0);
+ testClipboardValue("text/plain", "1\t2\n3\t4\n");
+ testHtmlClipboardValue(
+ "text/html",
+ '<table><tbody><tr id="tr2"><tr id="tr2"><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr><tr id="tr3"></tr></tr></tbody></table>'
+ );
+
+ // ============ spanning multiple rows in multi-range selection
+
+ clear();
+ addRange($("tr2"), 0, $("tr2"), 2);
+ addRange($("tr3"), 0, $("tr3"), 2);
+ await copySelectionToClipboard();
+ testClipboardValue("text/plain", "1\t2\n5\t6");
+ testHtmlClipboardValue(
+ "text/html",
+ '<table><tbody><tr id="tr2"><td>1</td><td>2</td></tr><tr id="tr3"><td>5</td><td>6</td></tr></tbody></table>'
+ );
+ }
+
+ // ============ manipulating Selection in oncopy
+
+ await copyRangeToClipboard(
+ $("div11").childNodes[0],
+ 0,
+ $("div11").childNodes[1],
+ 2
+ );
+ testClipboardValue("text/plain", "Xdiv11");
+ testHtmlClipboardValue("text/html", "<div><p>X<span>div</span>11</p></div>");
+
+ await new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+ testSelectionToString("div11");
+
+ await new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+ await copyRangeToClipboard(
+ $("div12").childNodes[0],
+ 0,
+ $("div12").childNodes[1],
+ 2
+ );
+
+ testClipboardValue("text/plain", "Xdiv12");
+ testHtmlClipboardValue("text/html", "<div><p>X<span>div</span>12</p></div>");
+ await new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+ testSelectionToString("div12");
+
+ await new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+
+ if (!isXHTML) {
+ // ============ copy from ruby
+
+ const ruby1 = $("ruby1");
+ const ruby1Container = ruby1.parentNode;
+
+ // Ruby annotation is included when selecting inside ruby.
+ await copyRangeToClipboard(ruby1, 0, ruby1, 6);
+ testClipboardValue("text/plain", "aabb(AABB)");
+
+ // Ruby annotation is ignored when selecting across ruby.
+ await copyRangeToClipboard(ruby1Container, 0, ruby1Container, 3);
+ testClipboardValue("text/plain", "XaabbY");
+
+ // ... unless converter.html2txt.always_include_ruby is set
+ await SpecialPowers.pushPrefEnv({
+ set: [["converter.html2txt.always_include_ruby", true]],
+ });
+ await copyRangeToClipboard(ruby1Container, 0, ruby1Container, 3);
+ testClipboardValue("text/plain", "Xaabb(AABB)Y");
+ await SpecialPowers.popPrefEnv();
+ }
+}
diff --git a/dom/base/test/delayedServerEvents.sjs b/dom/base/test/delayedServerEvents.sjs
new file mode 100644
index 0000000000..cfc1bca508
--- /dev/null
+++ b/dom/base/test/delayedServerEvents.sjs
@@ -0,0 +1,56 @@
+// this will take strings_to_send.length*500 ms = 5 sec
+
+var timer = null;
+var strings_to_send = [
+ "retry:999999999\ndata\r\n\nda",
+ "ta",
+ ":",
+ "de",
+ "layed1\n\n",
+ "",
+ "",
+ "data:delayed2\n\n",
+ "",
+ "",
+];
+var resp = null;
+
+function sendNextString() {
+ if (!strings_to_send.length) {
+ timer.cancel();
+ resp.finish();
+ timer = null;
+ resp = null;
+ return;
+ }
+
+ try {
+ resp.write(strings_to_send.shift());
+ } catch (e) {
+ timer.cancel();
+ timer = null;
+ resp = null;
+ }
+}
+
+function handleRequest(request, response) {
+ var bytes = strings_to_send.reduce((len, s) => len + s.length, 0);
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write(`Content-Length: ${bytes}\r\n`);
+ response.write("Content-Type: text/event-stream; charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache, must-revalidate\r\n");
+ response.write("\r\n");
+
+ resp = response;
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+ Components.interfaces.nsITimer
+ );
+ timer.initWithCallback(
+ sendNextString,
+ 500,
+ Components.interfaces.nsITimer.TYPE_REPEATING_SLACK
+ );
+}
diff --git a/dom/base/test/dummy.html b/dom/base/test/dummy.html
new file mode 100644
index 0000000000..1a87e28408
--- /dev/null
+++ b/dom/base/test/dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/dom/base/test/embed_bug455472.html b/dom/base/test/embed_bug455472.html
new file mode 100644
index 0000000000..d244ea9396
--- /dev/null
+++ b/dom/base/test/embed_bug455472.html
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' onload='parent.ran[2]=true'/>
diff --git a/dom/base/test/empty.html b/dom/base/test/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/empty.html
diff --git a/dom/base/test/eventsource.resource b/dom/base/test/eventsource.resource
new file mode 100644
index 0000000000..856e3b1aff
--- /dev/null
+++ b/dom/base/test/eventsource.resource
@@ -0,0 +1,22 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+retry:500
+data: 1
+unknow: unknow
+
+event: other_event_name
+retry:500
+data: 1
+unknow: unknow
+
+event: click
+retry:500
+
+event: blur
+retry:500
+
+event:keypress
+retry:500
+
+
diff --git a/dom/base/test/eventsource.resource^headers^ b/dom/base/test/eventsource.resource^headers^
new file mode 100644
index 0000000000..6a63b5341d
--- /dev/null
+++ b/dom/base/test/eventsource.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/eventsource_message.sjs b/dom/base/test/eventsource_message.sjs
new file mode 100644
index 0000000000..d52e2ee432
--- /dev/null
+++ b/dom/base/test/eventsource_message.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+ var match = request.queryString.match(/^status=(.*)$/);
+ var status = match ? +match[1] : 200;
+
+ if (status != 200) {
+ response.setStatusLine(request.httpVersion, status, "Err");
+ }
+ response.setHeader("Content-Type", "text/event-stream");
+ response.setHeader("Cache-Control", "no-cache");
+ response.write("data: msg 1\n");
+ response.write("id: 1\n\n");
+}
diff --git a/dom/base/test/eventsource_reconnect.sjs b/dom/base/test/eventsource_reconnect.sjs
new file mode 100644
index 0000000000..6627fefa9f
--- /dev/null
+++ b/dom/base/test/eventsource_reconnect.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ var name = "eventsource_reconnecting_" + request.queryString;
+ var reconnecting = getState(name);
+ var body = "";
+ if (!reconnecting) {
+ body = "retry: 2\n";
+ setState(name, "0");
+ } else if (reconnecting === "0") {
+ setState(name, "");
+ response.setStatusLine(request.httpVersion, 204, "No Content");
+ }
+
+ response.setHeader("Content-Type", "text/event-stream");
+ response.setHeader("Cache-Control", "no-cache");
+
+ body += "data: 1\n\n";
+ response.write(body);
+}
diff --git a/dom/base/test/eventsource_redirect.resource b/dom/base/test/eventsource_redirect.resource
new file mode 100644
index 0000000000..d073527bfb
--- /dev/null
+++ b/dom/base/test/eventsource_redirect.resource
@@ -0,0 +1,2 @@
+redirected
+
diff --git a/dom/base/test/eventsource_redirect.resource^headers^ b/dom/base/test/eventsource_redirect.resource^headers^
new file mode 100644
index 0000000000..eb79e2f814
--- /dev/null
+++ b/dom/base/test/eventsource_redirect.resource^headers^
@@ -0,0 +1,3 @@
+HTTP 301 Moved Permanently
+Location: eventsource_redirect_to.resource
+
diff --git a/dom/base/test/eventsource_redirect_to.resource b/dom/base/test/eventsource_redirect_to.resource
new file mode 100644
index 0000000000..1eb4081ac1
--- /dev/null
+++ b/dom/base/test/eventsource_redirect_to.resource
@@ -0,0 +1,4 @@
+retry:500
+data: 1
+
+
diff --git a/dom/base/test/eventsource_redirect_to.resource^headers^ b/dom/base/test/eventsource_redirect_to.resource^headers^
new file mode 100644
index 0000000000..6a63b5341d
--- /dev/null
+++ b/dom/base/test/eventsource_redirect_to.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/eventsource_worker.js b/dom/base/test/eventsource_worker.js
new file mode 100644
index 0000000000..863d52eec1
--- /dev/null
+++ b/dom/base/test/eventsource_worker.js
@@ -0,0 +1,6 @@
+const es = new EventSource(
+ "http://mochi.test:8888/tests/dom/base/test/eventsource_message.sjs"
+);
+es.onmessage = function () {
+ es.close();
+};
diff --git a/dom/base/test/fake_plugin.tst b/dom/base/test/fake_plugin.tst
new file mode 100644
index 0000000000..b3d41aed8d
--- /dev/null
+++ b/dom/base/test/fake_plugin.tst
@@ -0,0 +1 @@
+This is used in test_object.html to test loading by extension (.tst -> application/x-test).
diff --git a/dom/base/test/file1_setting_opener.html b/dom/base/test/file1_setting_opener.html
new file mode 100644
index 0000000000..6bc319bc9e
--- /dev/null
+++ b/dom/base/test/file1_setting_opener.html
@@ -0,0 +1 @@
+<script>opener.setTimeout(opener.basicOpenerTest, 0, this)</script>
diff --git a/dom/base/test/file2_setting_opener.html b/dom/base/test/file2_setting_opener.html
new file mode 100644
index 0000000000..b0f383ad23
--- /dev/null
+++ b/dom/base/test/file2_setting_opener.html
@@ -0,0 +1 @@
+<script>opener.setTimeout(opener.continueOpenerTest, 0, this);</script>
diff --git a/dom/base/test/file3_setting_opener.html b/dom/base/test/file3_setting_opener.html
new file mode 100644
index 0000000000..a40a0706f9
--- /dev/null
+++ b/dom/base/test/file3_setting_opener.html
@@ -0,0 +1 @@
+<script>opener.setTimeout(opener.continueOpenerTest2, 0, this);</script>
diff --git a/dom/base/test/file4_setting_opener.html b/dom/base/test/file4_setting_opener.html
new file mode 100644
index 0000000000..50b5b54a0e
--- /dev/null
+++ b/dom/base/test/file4_setting_opener.html
@@ -0,0 +1 @@
+Loaded
diff --git a/dom/base/test/file_audioLoop.html b/dom/base/test/file_audioLoop.html
new file mode 100644
index 0000000000..d680c9a58f
--- /dev/null
+++ b/dom/base/test/file_audioLoop.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<audio src="audio.ogg" autoplay="true" loop>
diff --git a/dom/base/test/file_audioLoopInIframe.html b/dom/base/test/file_audioLoopInIframe.html
new file mode 100644
index 0000000000..1131d140b3
--- /dev/null
+++ b/dom/base/test/file_audioLoopInIframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="file_audioLoop.html"></iframe>
diff --git a/dom/base/test/file_blocking_image.html b/dom/base/test/file_blocking_image.html
new file mode 100644
index 0000000000..8b66cb0c5b
--- /dev/null
+++ b/dom/base/test/file_blocking_image.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8" http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
+ <title>Image Blocking test </title>
+ </head>
+ <body>
+ </body>
+</html>
+
diff --git a/dom/base/test/file_browser_refresh_content.html b/dom/base/test/file_browser_refresh_content.html
new file mode 100644
index 0000000000..aa338efd3f
--- /dev/null
+++ b/dom/base/test/file_browser_refresh_content.html
@@ -0,0 +1,41 @@
+<html>
+ <body>
+ <img src="file_browser_refresh_image.sjs">
+ <iframe src="file_browser_refresh_iframe.sjs"></iframe>
+ <div id="result"></div>
+
+ <canvas id="canvas" width="100" height="100"> </canvas>
+ <script>
+ const image = document.querySelector("img");
+ const iframe = document.querySelector("iframe");
+ const result = document.getElementById("result");
+
+ iframe.addEventListener("load", function() {
+ result.setAttribute(
+ "iframeContent",
+ iframe.contentDocument.body.textContent
+ );
+ });
+
+ // Ensure images are loaded
+ image.addEventListener("load", function() {
+ var canvas = document.getElementById('canvas');
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(image, 0, 0);
+
+ result.setAttribute("imageDataURL", canvas.toDataURL());
+ });
+
+ // Ensure expired resources are still loaded
+ fetch('./file_browser_refresh_expired_resource.sjs').then((response) => {
+ let cacheControl = response.headers.get('Cache-Control');
+ result.setAttribute("expiredResourceCacheControl", cacheControl);
+ });
+
+ // Ensure non cacheable resources are still loaded
+ fetch('./file_browser_refresh_non_cacheable.sjs').then(() => {
+ result.setAttribute("nonCacheableResourceCompleted", true);
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/file_browser_refresh_expired_resource.sjs b/dom/base/test/file_browser_refresh_expired_resource.sjs
new file mode 100644
index 0000000000..ab5cdcf78d
--- /dev/null
+++ b/dom/base/test/file_browser_refresh_expired_resource.sjs
@@ -0,0 +1,13 @@
+"use strict";
+
+function handleRequest(request, response) {
+ if (getState("expired_resource") == "") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "max-age=1001");
+ setState("expired_resource", "ok");
+ } else {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "max-age=1003");
+ setState("expired_resource", "");
+ }
+}
diff --git a/dom/base/test/file_browser_refresh_iframe.sjs b/dom/base/test/file_browser_refresh_iframe.sjs
new file mode 100644
index 0000000000..0941a1160e
--- /dev/null
+++ b/dom/base/test/file_browser_refresh_iframe.sjs
@@ -0,0 +1,13 @@
+"use strict";
+
+function handleRequest(request, response) {
+ if (getState("iframe_resource") == "") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("first load");
+ setState("iframe_resource", "ok");
+ } else {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("second load");
+ setState("iframe_resource", "");
+ }
+}
diff --git a/dom/base/test/file_browser_refresh_image.sjs b/dom/base/test/file_browser_refresh_image.sjs
new file mode 100644
index 0000000000..17f11e87e9
--- /dev/null
+++ b/dom/base/test/file_browser_refresh_image.sjs
@@ -0,0 +1,38 @@
+"use strict";
+
+function handleRequest(request, response) {
+ const file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.nsIFile);
+
+ file.append("tests");
+ file.append("image");
+ file.append("test");
+ file.append("mochitest");
+
+ const redirectstate = "image_resource";
+ if (getState(redirectstate) == "") {
+ file.append("green.png");
+ setState(redirectstate, "green");
+ } else {
+ file.append("red.png");
+ setState(redirectstate, "");
+ }
+
+ response.setHeader("Cache-Control", "max-age=3600", false);
+
+ const fileStream = Components.classes[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Components.interfaces.nsIFileInputStream);
+
+ fileStream.init(file, 1, 0, false);
+ const binaryStream = Components.classes[
+ "@mozilla.org/binaryinputstream;1"
+ ].createInstance(Components.interfaces.nsIBinaryInputStream);
+ binaryStream.setInputStream(fileStream);
+
+ response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available());
+
+ binaryStream.close();
+ fileStream.close();
+}
diff --git a/dom/base/test/file_browser_refresh_non_cacheable.sjs b/dom/base/test/file_browser_refresh_non_cacheable.sjs
new file mode 100644
index 0000000000..9b5a39abed
--- /dev/null
+++ b/dom/base/test/file_browser_refresh_non_cacheable.sjs
@@ -0,0 +1,6 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-store");
+}
diff --git a/dom/base/test/file_bug1008126_worker.js b/dom/base/test/file_bug1008126_worker.js
new file mode 100644
index 0000000000..aaba278de5
--- /dev/null
+++ b/dom/base/test/file_bug1008126_worker.js
@@ -0,0 +1,151 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gEntry1 = "data_1.txt";
+var gEntry2 = "data_2.txt";
+var gEntry3 = "data_big.txt";
+var gPaddingChar = ".";
+var gPaddingSize = 10000;
+var gPadding = "";
+for (var i = 0; i < gPaddingSize; i++) {
+ gPadding += gPaddingChar;
+}
+var gData1 = "TEST_DATA_1:ABCDEFGHIJKLMNOPQRSTUVWXYZ" + gPadding;
+var gData2 = "TEST_DATA_2:1234567890" + gPadding;
+
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ postMessage({ type: "status", status: a === b, msg });
+}
+
+function checkData(xhr, data, mapped, cb) {
+ var ct = xhr.getResponseHeader("Content-Type");
+ if (mapped) {
+ ok(ct.includes("mem-mapped"), "Data is memory-mapped");
+ } else {
+ ok(!ct.includes("mem-mapped"), "Data is not memory-mapped");
+ }
+ ok(xhr.response, "Data is non-null");
+ var str = String.fromCharCode.apply(null, new Uint8Array(xhr.response));
+ ok(str == data, "Data is correct");
+ cb();
+}
+
+self.onmessage = function onmessage(event) {
+ var jar = event.data;
+
+ function makeJarURL(entry) {
+ return "jar:" + jar + "!/" + entry;
+ }
+
+ var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+
+ function reset_event_hander() {
+ xhr.onerror = function (e) {
+ ok(false, "Error: " + e.error + "\n");
+ };
+ xhr.onprogress = null;
+ xhr.onreadystatechange = null;
+ xhr.onload = null;
+ xhr.onloadend = null;
+ }
+
+ var readystatechangeCount = 0;
+ var loadCount = 0;
+ var loadendCount = 0;
+
+ function checkEventCount(cb) {
+ ok(
+ readystatechangeCount == 1 && loadCount == 1 && loadendCount == 1,
+ "Saw all expected events"
+ );
+ cb();
+ }
+
+ function test_multiple_events() {
+ ok(true, "Test multiple events");
+ xhr.abort();
+
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState == xhr.DONE) {
+ readystatechangeCount++;
+ checkData(xhr, gData2, false, function () {});
+ }
+ };
+ xhr.onload = function () {
+ loadCount++;
+ checkData(xhr, gData2, false, function () {});
+ };
+ xhr.onloadend = function () {
+ loadendCount++;
+ checkData(xhr, gData2, false, function () {});
+ };
+ xhr.open("GET", makeJarURL(gEntry2), false);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ checkEventCount(runTests);
+ }
+
+ function test_sync_xhr_data1() {
+ ok(true, "Test sync XHR with data1");
+ xhr.open("GET", makeJarURL(gEntry1), false);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ checkData(xhr, gData1, true, runTests);
+ }
+
+ function test_sync_xhr_data2() {
+ ok(true, "Test sync XHR with data2");
+ xhr.open("GET", makeJarURL(gEntry2), false);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ checkData(xhr, gData2, false, runTests);
+ }
+
+ function test_async_xhr_data1() {
+ ok(true, "Test async XHR with data1");
+ xhr.onload = function () {
+ checkData(xhr, gData1, true, runTests);
+ };
+ xhr.open("GET", makeJarURL(gEntry1), true);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ }
+
+ function test_async_xhr_data2() {
+ ok(true, "Test async XHR with data2");
+ xhr.onload = function () {
+ checkData(xhr, gData2, false, runTests);
+ };
+ xhr.open("GET", makeJarURL(gEntry2), true);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ }
+
+ var tests = [
+ test_multiple_events,
+ test_sync_xhr_data1,
+ test_sync_xhr_data2,
+ test_async_xhr_data1,
+ test_async_xhr_data2,
+ ];
+
+ function runTests() {
+ if (!tests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ reset_event_hander();
+
+ var test = tests.shift();
+ test();
+ }
+
+ runTests();
+};
diff --git a/dom/base/test/file_bug1011748_OK.sjs b/dom/base/test/file_bug1011748_OK.sjs
new file mode 100644
index 0000000000..71a0f5c3e8
--- /dev/null
+++ b/dom/base/test/file_bug1011748_OK.sjs
@@ -0,0 +1,4 @@
+// Function to indicate HTTP 200 OK after redirect from file_bug1011748_redirect.sjs
+function handleRequest(request, response) {
+ response.setStatusLine(null, 200, "OK");
+}
diff --git a/dom/base/test/file_bug1011748_redirect.sjs b/dom/base/test/file_bug1011748_redirect.sjs
new file mode 100644
index 0000000000..3dec893675
--- /dev/null
+++ b/dom/base/test/file_bug1011748_redirect.sjs
@@ -0,0 +1,5 @@
+// SJS handler to redirect the XMLHttpRequest object to file_bug1011748_OK.sjs
+function handleRequest(request, response) {
+ response.setStatusLine(null, 302, "Moved Temporarily");
+ response.setHeader("Location", "file_bug1011748_OK.sjs", false);
+}
diff --git a/dom/base/test/file_bug1091883_frame.html b/dom/base/test/file_bug1091883_frame.html
new file mode 100644
index 0000000000..a245175ae3
--- /dev/null
+++ b/dom/base/test/file_bug1091883_frame.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+<h2>Frame I am.</h2>
+<script>
+var subframeOrigin = location.hash.substr(1); // e.g., "http://example.com"
+var subframe = document.createElement("iframe");
+subframe.src = subframeOrigin +
+ "/tests/dom/base/test/file_bug1091883_subframe.html";
+document.body.appendChild(subframe);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1091883_subframe.html b/dom/base/test/file_bug1091883_subframe.html
new file mode 100644
index 0000000000..f282e1eeb9
--- /dev/null
+++ b/dom/base/test/file_bug1091883_subframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<h3>Subframe I am.</h3>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1091883_target.html b/dom/base/test/file_bug1091883_target.html
new file mode 100644
index 0000000000..2095595fbc
--- /dev/null
+++ b/dom/base/test/file_bug1091883_target.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<h3>Target I am.</h3>
+<script>
+var testRun = location.hash.substr(1);
+parent.parent.postMessage(document.referrer + " " + testRun,
+ "http://mochi.test:8888");
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1100912.html b/dom/base/test/file_bug1100912.html
new file mode 100644
index 0000000000..8fac15fae4
--- /dev/null
+++ b/dom/base/test/file_bug1100912.html
@@ -0,0 +1,150 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1100912
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1100912</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ td {
+ border-right: 1px solid black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100912">Mozilla Bug 1100912</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+ <!-- The table is here just to make the web page easier to read -->
+ <table>
+ <tr>
+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ </tr>
+
+ <tr><td>1.</td><td>
+ <div id="host1" dir="rtl"><span> 3 4 </span></div>
+ </td><td></td><td>rtl on host</td></tr>
+
+ <tr><td>2.</td><td>
+ <div id="host2" dir="rtl"><span> 3 4 </span></div>
+ </td><td></td><td>rtl on host, ltr slot's parent</td></tr>
+
+ <tr><td>3.</td><td>
+ <div id="host3" dir="rtl"><span> 3 4 </span></div>
+ </td><td></td><td>rtl on host, ltr on slot</td></tr>
+
+ <tr><td>4.</td><td>
+ <div id="host4" dir="auto"><span> 1 2 </span></div>
+ </td><td></td><td>auto host, rtl in shadow</td></tr>
+
+ <tr><td>5.</td><td>
+ <div id="host5" dir="auto"><span> &#1571;&#1582;&#1576;&#1575;&#1585; </span></div>
+ </td><td></td><td>auto host, rtl in host (in assigned node)</td></tr>
+
+ <tr><td>6.</td><td>
+ <div id="host6" dir="auto"><span> &#1571;&#1582;&#1576;&#1575;&#1585; </span></div>
+ </td><td></td><td>auto host, rtl in host, no assigned node</td></tr>
+
+ <tr><td>7.</td><td>
+ <div id="host7" dir="auto"><span> &#1571;&#1582;&#1576;&#1575;&#1585; </span></div>
+ </td><td></td><td>auto host, rtl in host, explicit ltr in shadow</td></tr>
+
+ <tr><td>8.</td><td>
+ <div id="host8" dir="auto"><span slot="second">&lrm;1 2 </span><span slot="first"> &#1571;&#1582;&#1576;&#1575;&#1585; </span></div>
+ </td><td></td><td>auto host, ltr in host, rtl in host, reverse order in slots</td></tr>
+
+ <tr><td>9.</td><td>
+ <div id="host9" dir="auto">&#1571;&#1582;&#1576;&#1575;&#1585;</div>
+ </td><td></td><td>auto host, rtl in host (in assigned text node)</td></tr>
+
+ <tr><td>10.</td><td>
+ <div id="host10" dir="auto"> 1 2</div>
+ </td><td></td><td>auto host, 1 2 in host (in assigned text node)</td></tr>
+
+</table>
+<script>
+
+function ltrExpected(element) {
+ opener.is(element.parentNode.querySelector(":dir(ltr)"), element,
+ "Should have got an ltr element.");
+}
+
+function rtlExpected(element) {
+ opener.is(element.parentNode.querySelector(":dir(rtl)"), element,
+ "Should have got an rtl element.");
+}
+
+const shadowRoot1 = host1.attachShadow({mode: 'closed'});
+shadowRoot1.innerHTML = '<div> 1 2 <span><slot></slot></span></div>';
+rtlExpected(host1);
+rtlExpected(host1.firstChild);
+rtlExpected(shadowRoot1.firstChild.lastChild); // span in the Shadow DOM
+
+const shadowRoot2 = host2.attachShadow({mode: 'closed'});
+shadowRoot2.innerHTML = '<div> 1 2 <span dir="ltr"><slot></slot></span></div>';
+rtlExpected(host2);
+ltrExpected(host2.firstChild);
+
+// This is weird case, and we have similar behavior as Blink. dir= on <slot>
+// doesn't affect to UI since slot has display: contents by default.
+const shadowRoot3 = host3.attachShadow({mode: 'closed'});
+shadowRoot3.innerHTML = '<div> 1 2 <span><slot dir="ltr"></slot></span></div>';
+rtlExpected(host3);
+
+const shadowRoot4 = host4.attachShadow({mode: 'closed'});
+shadowRoot4.innerHTML = '<div> &#1571;&#1582;&#1576;&#1575;&#1585; <span><slot></slot></span></div>';
+rtlExpected(host4);
+rtlExpected(host4.firstChild);
+rtlExpected(shadowRoot4.firstChild.lastChild);
+
+const shadowRoot5 = host5.attachShadow({mode: 'closed'});
+shadowRoot5.innerHTML = '<div> 1 2 <span><slot></slot></span></div>';
+rtlExpected(host5);
+rtlExpected(host5.firstChild);
+rtlExpected(shadowRoot5.firstChild.lastChild);
+
+// This case is different to Blink since it doesn't deal with nodes which aren't
+// in the flattened tree, so it doesn't detect rtl in child nodes, which
+// aren't assigned to any slot.
+const shadowRoot6 = host6.attachShadow({mode: 'closed'});
+shadowRoot6.innerHTML = '<div> 1 2 <span></span></div>';
+rtlExpected(host6);
+rtlExpected(host6.firstChild);
+rtlExpected(shadowRoot6.firstChild.lastChild);
+
+const shadowRoot7 = host7.attachShadow({mode: 'closed'});
+shadowRoot7.innerHTML = '<div> &lrm;1 2 <span><slot></slot></span></div>';
+ltrExpected(host7);
+ltrExpected(host7.firstChild);
+ltrExpected(shadowRoot7.firstChild.lastChild);
+
+const shadowRoot8 = host8.attachShadow({mode: 'closed'});
+shadowRoot8.innerHTML = '<div><slot name="first"></slot><slot name="second"></slot></div>';
+rtlExpected(host8);
+rtlExpected(host8.firstChild);
+rtlExpected(shadowRoot8.firstChild.firstChild);
+
+const shadowRoot9 = host9.attachShadow({mode: 'closed'});
+shadowRoot9.innerHTML = '<div> 1 2 <span><slot></slot></span></div>';
+rtlExpected(host9);
+rtlExpected(shadowRoot9.firstChild.lastChild);
+
+const shadowRoot10 = host10.attachShadow({mode: 'closed'});
+shadowRoot10.innerHTML = '<div> &#1571;&#1582;&#1576;&#1575;&#1585; <span><slot></slot></span></div>';
+rtlExpected(host10);
+rtlExpected(shadowRoot10.firstChild.lastChild);
+
+opener.didRunTests();
+window.close();
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_bug1198095.js b/dom/base/test/file_bug1198095.js
new file mode 100644
index 0000000000..2b8c018de7
--- /dev/null
+++ b/dom/base/test/file_bug1198095.js
@@ -0,0 +1,38 @@
+/* eslint-env mozilla/chrome-script */
+
+Cu.importGlobalProperties(["File"]);
+
+function createFileWithData(message) {
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIProperties
+ );
+ var testFile = dirSvc.get("ProfD", Ci.nsIFile);
+ testFile.append("fileAPItestfileBug1198095");
+
+ var outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ testFile,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666,
+ 0
+ );
+
+ outStream.write(message, message.length);
+ outStream.close();
+
+ return File.createFromNsIFile(testFile);
+}
+
+addMessageListener("file.open", function (message) {
+ createFileWithData(message).then(function (file) {
+ sendAsyncMessage("file.opened", file);
+ });
+});
+
+addMessageListener("file.modify", function (message) {
+ createFileWithData(message).then(function (file) {
+ sendAsyncMessage("file.modified", file);
+ });
+});
diff --git a/dom/base/test/file_bug1250148.sjs b/dom/base/test/file_bug1250148.sjs
new file mode 100644
index 0000000000..5f8037a08a
--- /dev/null
+++ b/dom/base/test/file_bug1250148.sjs
@@ -0,0 +1,73 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function utf8decode(s) {
+ return decodeURIComponent(escape(s));
+}
+
+function utf8encode(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function handleRequest(request, response) {
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+
+ var requestBody = "";
+ while ((bodyAvail = bodyStream.available()) > 0) {
+ requestBody += bodyStream.readBytes(bodyAvail);
+ }
+
+ var result = [];
+
+ if (request.method == "POST") {
+ var contentTypeParams = {};
+ request
+ .getHeader("Content-Type")
+ .split(/\s*\;\s*/)
+ .forEach(function (str) {
+ if (str.indexOf("=") >= 0) {
+ let [name, value] = str.split("=");
+ contentTypeParams[name] = value;
+ } else {
+ contentTypeParams[""] = str;
+ }
+ });
+
+ if (
+ contentTypeParams[""] == "multipart/form-data" &&
+ request.queryString == ""
+ ) {
+ requestBody
+ .split("--" + contentTypeParams.boundary)
+ .slice(1, -1)
+ .forEach(function (s) {
+ let headers = {};
+ let headerEnd = s.indexOf("\r\n\r\n");
+ s.substr(2, headerEnd - 2)
+ .split("\r\n")
+ .forEach(function (str) {
+ // We're assuming UTF8 for now
+ let [name, value] = str.split(": ");
+ headers[name] = utf8decode(value);
+ });
+
+ let body = s.substring(headerEnd + 4, s.length - 2);
+ if (
+ !headers["Content-Type"] ||
+ headers["Content-Type"] == "text/plain"
+ ) {
+ // We're assuming UTF8 for now
+ body = utf8decode(body);
+ }
+ result.push({ headers, body });
+ });
+ }
+ }
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write(utf8encode(JSON.stringify(result)));
+}
diff --git a/dom/base/test/file_bug1268962.sjs b/dom/base/test/file_bug1268962.sjs
new file mode 100644
index 0000000000..1aa06fadaa
--- /dev/null
+++ b/dom/base/test/file_bug1268962.sjs
@@ -0,0 +1,92 @@
+// Test server for bug 1268962
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+const HTTPStatus = new Map([
+ [100, "Continue"],
+ [101, "Switching Protocol"],
+ [200, "OK"],
+ [201, "Created"],
+ [202, "Accepted"],
+ [203, "Non-Authoritative Information"],
+ [204, "No Content"],
+ [205, "Reset Content"],
+ [206, "Partial Content"],
+ [300, "Multiple Choice"],
+ [301, "Moved Permanently"],
+ [302, "Found"],
+ [303, "See Other"],
+ [304, "Not Modified"],
+ [305, "Use Proxy"],
+ [306, "unused"],
+ [307, "Temporary Redirect"],
+ [308, "Permanent Redirect"],
+ [400, "Bad Request"],
+ [401, "Unauthorized"],
+ [402, "Payment Required"],
+ [403, "Forbidden"],
+ [404, "Not Found"],
+ [405, "Method Not Allowed"],
+ [406, "Not Acceptable"],
+ [407, "Proxy Authentication Required"],
+ [408, "Request Timeout"],
+ [409, "Conflict"],
+ [410, "Gone"],
+ [411, "Length Required"],
+ [412, "Precondition Failed"],
+ [413, "Request Entity Too Large"],
+ [414, "Request-URI Too Long"],
+ [415, "Unsupported Media Type"],
+ [416, "Requested Range Not Satisfiable"],
+ [417, "Expectation Failed"],
+ [500, "Internal Server Error"],
+ [501, "Not Implemented"],
+ [502, "Bad Gateway"],
+ [503, "Service Unavailable"],
+ [504, "Gateway Timeout"],
+ [505, "HTTP Version Not Supported"],
+]);
+
+const SAME_ORIGIN =
+ "http://mochi.test:8888/tests/dom/base/test/file_bug1268962.sjs";
+const CROSS_ORIGIN =
+ "http://example.com/tests/dom/base/test/file_bug1268962.sjs";
+
+function handleRequest(request, response) {
+ const queryMap = new URLSearchParams(request.queryString);
+
+ // Check redirection before everything else.
+ if (queryMap.has("redirect")) {
+ let redirect = queryMap.get("redirect");
+ let location;
+ if (redirect == "sameorigin") {
+ location = SAME_ORIGIN;
+ } else if (redirect == "crossorigin") {
+ location = CROSS_ORIGIN;
+ }
+
+ if (location) {
+ // Use HTTP 302 redirection.
+ response.setStatusLine("1.1", 302, HTTPStatus.get(302));
+
+ // Forward query strings except the redirect option.
+ queryMap.delete("redirect");
+ response.setHeader("Location", location + "?" + queryMap.toString());
+
+ return;
+ }
+ }
+
+ if (queryMap.has("statusCode")) {
+ let statusCode = parseInt(queryMap.get("statusCode"));
+ let statusText = HTTPStatus.get(statusCode);
+ response.setStatusLine("1.1", statusCode, statusText);
+ }
+ if (queryMap.has("cacheControl")) {
+ let cacheControl = queryMap.get("cacheControl");
+ response.setHeader("Cache-Control", cacheControl);
+ }
+ if (queryMap.has("allowOrigin")) {
+ let allowOrigin = queryMap.get("allowOrigin");
+ response.setHeader("Access-Control-Allow-Origin", allowOrigin);
+ }
+}
diff --git a/dom/base/test/file_bug1274806.html b/dom/base/test/file_bug1274806.html
new file mode 100644
index 0000000000..62bef3044b
--- /dev/null
+++ b/dom/base/test/file_bug1274806.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title></title>
+ <script>
+ window.onload = function() {
+ setTimeout(function() {
+ var rng = document.createRange();
+ rng.setStart(document.getElementsByTagName("p").item(0).firstChild, 100);
+ rng.setEndAfter(document.getElementsByTagName("p").item(0));
+ try {
+ rng.extractContents();
+ opener.ok(true, "extractContents should not throw when document in iframe is being modified.");
+ } catch(ex) {
+ opener.ok(false, "extractContents shouldn't have thrown: " + ex);
+ }
+
+ opener.setTimeout("SimpleTest.finish();", 0);
+ window.close();
+
+ }, 0);
+ };
+ </script>
+</head>
+<body>
+<p>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur elit nisi, convallis sed scelerisque sit amet, vestibulum eu odio. Pellentesque et quam et nibh sollicitudin rutrum. Fusce tristique hendrerit ligula, et euismod sapien facilisis quis. Donec tincidunt turpis tortor, in pharetra tellus euismod ac. Vestibulum consectetur nulla lacinia, consectetur mauris ac, tempus libero. Nam non dui id enim dapibus porta id sed lectus. Praesent at suscipit neque. Vestibulum tellus lorem, placerat et volutpat sed, elementum eget lacus. Sed interdum nisi et imperdiet varius. Sed non magna odio. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus velit risus, accumsan nec efficitur nec, semper sed arcu. Praesent consectetur lectus justo, fringilla imperdiet neque lobortis id. Donec efficitur pulvinar finibus.
+ <iframe src="data:text/html,<script>window.onunload = function() {document.removeChild(document.documentElement); }</script>" width="10" height="10"></iframe>
+</p>
+<p>test</p>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1303838.html b/dom/base/test/file_bug1303838.html
new file mode 100644
index 0000000000..d11444a449
--- /dev/null
+++ b/dom/base/test/file_bug1303838.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for tab switching on link clicks.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for tab switching on link clicks.</title>
+ <style>
+ a {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ overflow: hidden;
+ background-color: currentColor;
+ }
+ </style>
+</head>
+<body>
+ <a id="link-1" target="testTab" href="file_bug1303838_target_foo.html">Link 1</a>,
+ <a id="link-2" target="testTab" href="file_bug1303838_target_bar.html">Link 2</a>,
+ <a id="link-3" target="testTab" href="file_bug1303838_target_baz.html">Link 3</a>,
+ <a id="link-4" href="#" onclick="testTab = window.open('file_bug1303838_target_foo.html', 'testTab'); return false;">Link 4</a>,
+ <a id="link-5" href="#" onclick="testTab.location.href += '?1'; return false;">Link 5</a>,
+ <a id="link-6" href="#" onclick="testTab.location.href += '#1'; return false;">Link 6</a>,
+ <a id="link-7" target="testTab" href="file_bug1303838_target.html">Link 7</a>,
+ <a id="anchor-link-1" target="testTab" href="file_bug1303838_target.html#foo">Anchor Link 1</a>,
+ <a id="anchor-link-2" target="testTab" href="file_bug1303838_target.html#bar">Anchor Link 2</a>,
+ <a id="anchor-link-3" target="testTab" href="file_bug1303838_target.html#baz">Anchor Link 3</a>,
+ <a id="frame-link-1" target="testFrame" href="file_bug1303838_target_ifoo.html">Frame Link 1</a>,
+ <a id="frame-link-2" target="testFrame" href="file_bug1303838_target_ibar.html">Frame Link 2</a>,
+ <a id="frame-link-3" target="testFrame" href="file_bug1303838_target_ibaz.html">Frame Link 3</a>,
+</body>
+</html>
diff --git a/dom/base/test/file_bug1303838_target.html b/dom/base/test/file_bug1303838_target.html
new file mode 100644
index 0000000000..b1c8871f6b
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for tab switching on link clicks.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for tab switching on link clicks.</title>
+</head>
+<body onload="setTimeout(loadTestFrame, 0);">
+ <div id="foo">Foo</div>
+ <div id="bar">Bar</div>
+ <div id="baz">Baz</div>
+ <iframe name="testFrame"></iframe>
+ <script>
+ function loadTestFrame() {
+ window.open("data:text/html;charset=utf-8,testFrame", "testFrame");
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1303838_target_bar.html b/dom/base/test/file_bug1303838_target_bar.html
new file mode 100644
index 0000000000..2f1aa2bdef
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_bar.html
@@ -0,0 +1 @@
+<html><body>bar</body></html>
diff --git a/dom/base/test/file_bug1303838_target_baz.html b/dom/base/test/file_bug1303838_target_baz.html
new file mode 100644
index 0000000000..7b4e4c0ef3
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_baz.html
@@ -0,0 +1 @@
+<html><body>baz</body></html>
diff --git a/dom/base/test/file_bug1303838_target_foo.html b/dom/base/test/file_bug1303838_target_foo.html
new file mode 100644
index 0000000000..f0ab6775c0
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_foo.html
@@ -0,0 +1 @@
+<html><body>foo</body></html>
diff --git a/dom/base/test/file_bug1303838_target_ibar.html b/dom/base/test/file_bug1303838_target_ibar.html
new file mode 100644
index 0000000000..4832b38ce6
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_ibar.html
@@ -0,0 +1 @@
+<html><body>ibar</body></html>
diff --git a/dom/base/test/file_bug1303838_target_ibaz.html b/dom/base/test/file_bug1303838_target_ibaz.html
new file mode 100644
index 0000000000..243b786a5b
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_ibaz.html
@@ -0,0 +1 @@
+<html><body>ibaz</body></html>
diff --git a/dom/base/test/file_bug1303838_target_ifoo.html b/dom/base/test/file_bug1303838_target_ifoo.html
new file mode 100644
index 0000000000..b6c3a90e2e
--- /dev/null
+++ b/dom/base/test/file_bug1303838_target_ifoo.html
@@ -0,0 +1 @@
+<html><body>ifoo</body></html>
diff --git a/dom/base/test/file_bug1303838_with_iframe.html b/dom/base/test/file_bug1303838_with_iframe.html
new file mode 100644
index 0000000000..d949ad7383
--- /dev/null
+++ b/dom/base/test/file_bug1303838_with_iframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for tab switching on link clicks.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for tab switching on link clicks.</title>
+</head>
+<body>
+ <iframe id="frame" src="file_bug1303838.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1554070_1.html b/dom/base/test/file_bug1554070_1.html
new file mode 100644
index 0000000000..0294485293
--- /dev/null
+++ b/dom/base/test/file_bug1554070_1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1554070
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1554070</title>
+</head>
+<body>
+ <iframe name="foo"></iframe>
+ <a href="file_bug1554070_2.html">file_bug1554070_2.html</a>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1554070_2.html b/dom/base/test/file_bug1554070_2.html
new file mode 100644
index 0000000000..9287f535d4
--- /dev/null
+++ b/dom/base/test/file_bug1554070_2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1554070
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1554070</title>
+</head>
+<body>
+ <a href="https://example.org" target="foo">example.org</a>
+</body>
+</html>
diff --git a/dom/base/test/file_bug1639328.html b/dom/base/test/file_bug1639328.html
new file mode 100644
index 0000000000..97c96cf96c
--- /dev/null
+++ b/dom/base/test/file_bug1639328.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<script>
+onmessage = function(e) {
+ parent.postMessage({
+ throttledFrameRequests: SpecialPowers.DOMWindowUtils.effectivelyThrottlesFrameRequests,
+ }, e.origin);
+};
+</script>
diff --git a/dom/base/test/file_bug1691214.html b/dom/base/test/file_bug1691214.html
new file mode 100644
index 0000000000..e370166598
--- /dev/null
+++ b/dom/base/test/file_bug1691214.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<a id="link-1" href="?1" onclick="window.open(this.href, 'childWin', 'height=300,width=600'); return false;">Open in window.</a>
+<br>
+<a id="link-2" href="?2" onclick="window.open(this.href, 'childWin', 'height=300,width=600'); return false;">Open in window.</a>
diff --git a/dom/base/test/file_bug1700871.html b/dom/base/test/file_bug1700871.html
new file mode 100644
index 0000000000..3bc1808c66
--- /dev/null
+++ b/dom/base/test/file_bug1700871.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<a id="link-1" href="?1" onclick="window.open(this.href, 'childWin', 'height=300,width=600'); return false;">Open in window.</a>
+<form target="frame" method="get">
+ <input type=hidden name=counter value=0>
+ <iframe name="frame" href="file_bug1700871.html"></iframe>
+</form>
+<script>
+ (function submitForm() {
+ if (location.search) {
+ return; // Don't fork-bomb ourselves.
+ }
+
+ let counter = document.querySelector("input");
+ counter.value = ++counter.value;
+ document.querySelector("form").submit();
+ setTimeout(submitForm, 500);
+ }());
+</script>
diff --git a/dom/base/test/file_bug1703472.html b/dom/base/test/file_bug1703472.html
new file mode 100644
index 0000000000..9607663bc3
--- /dev/null
+++ b/dom/base/test/file_bug1703472.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<script>
+ let w = null;
+</script>
+<button id="openWindow" onclick="w = window.open('about:blank', '', 'width=10,height=10')">Open</button>
+<button id="focusWindow" onclick="w.focus()">Focus</button>
diff --git a/dom/base/test/file_bug28293.sjs b/dom/base/test/file_bug28293.sjs
new file mode 100644
index 0000000000..1b3e19402d
--- /dev/null
+++ b/dom/base/test/file_bug28293.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(decodeURIComponent(request.queryString));
+}
diff --git a/dom/base/test/file_bug326337.xml b/dom/base/test/file_bug326337.xml
new file mode 100644
index 0000000000..d328051e74
--- /dev/null
+++ b/dom/base/test/file_bug326337.xml
@@ -0,0 +1 @@
+<data root="yes"/>
diff --git a/dom/base/test/file_bug326337_inner.html b/dom/base/test/file_bug326337_inner.html
new file mode 100644
index 0000000000..01a1929ac8
--- /dev/null
+++ b/dom/base/test/file_bug326337_inner.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=326337
+-->
+<head>
+ <title>Inner file for Bug 326337</title>
+</head>
+<body>
+<script>
+
+document.domain = "example.com";
+
+runTest();
+
+var xhr;
+
+function runTest() {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_bug326337.xml", true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ check(xhr.responseXML.documentElement.getAttribute("root"));
+ SpecialPowers.spawn(parent, [], () => {
+ content.location.hash = "#done";
+ });
+ }
+ }
+ xhr.send(null);
+}
+
+function check(attr) {
+ if (attr != "yes") {
+ SpecialPowers.spawn(parent, [], () => {
+ content.location.hash = "#fail";
+ });
+ throw 1;
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/file_bug326337_outer.html b/dom/base/test/file_bug326337_outer.html
new file mode 100644
index 0000000000..9d63f72632
--- /dev/null
+++ b/dom/base/test/file_bug326337_outer.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe id="inner" src="http://test1.example.com/tests/dom/base/test/file_bug326337_inner.html"></iframe>
+<script>
+var t = setInterval(doCheck, 300);
+function doCheck() {
+ if (location.hash) {
+ clearInterval(t);
+ window.opener.finishTest(location.hash);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_bug357450.js b/dom/base/test/file_bug357450.js
new file mode 100644
index 0000000000..6d0d703e78
--- /dev/null
+++ b/dom/base/test/file_bug357450.js
@@ -0,0 +1,74 @@
+/** Test for Bug 357450 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function testGetElements(root, classtestCount) {
+ ok(root.getElementsByClassName, "getElementsByClassName exists");
+ is(
+ typeof root.getElementsByClassName,
+ "function",
+ "getElementsByClassName is a function"
+ );
+
+ var nodes = root.getElementsByClassName("f");
+
+ is(typeof nodes.item, "function");
+ is(typeof nodes.length, "number");
+ is(nodes.length, 0, "string with no matching class should get an empty list");
+
+ nodes = root.getElementsByClassName("foo");
+ ok(nodes, "should have nodelist object");
+
+ // HTML5 says ints are allowed in class names
+ // should match int class
+
+ nodes = root.getElementsByClassName("1");
+ is(nodes[0], $("int-class"), "match integer class name");
+ nodes = root.getElementsByClassName([1]);
+ is(nodes[0], $("int-class"), "match integer class name 2");
+ nodes = root.getElementsByClassName(["1 junk"]);
+
+ is(nodes.length, 0, "two classes, but no elements have both");
+
+ nodes = root.getElementsByClassName("test1");
+ is(nodes[0], $("test1"), "Id and class name turn up the same node");
+ nodes = root.getElementsByClassName("test1 test2");
+ is(nodes.length, 0, "two classes, but no elements have both");
+
+ // WHATWG examples
+ nodes = document.getElementById("example").getElementsByClassName("aaa");
+ is(nodes.length, 2, "returns 2 elements");
+
+ nodes = document.getElementById("example").getElementsByClassName("ccc bbb");
+ is(
+ nodes.length,
+ 1,
+ "only match elements that have all the classes specified in that array. tokenize string arg."
+ );
+ is(nodes[0], $("p3"), "matched tokenized string");
+
+ nodes = document.getElementById("example").getElementsByClassName("");
+ is(nodes.length, 0, "class name with empty string shouldn't return nodes");
+
+ nodes = root.getElementsByClassName({});
+ ok(nodes, "bogus arg shouldn't be null");
+ is(typeof nodes.item, "function");
+ is(typeof nodes.length, "number");
+ is(nodes.length, 0, "bogus arg should get an empty nodelist");
+}
+
+addLoadEvent(function () {
+ if (document.getElementsByName) {
+ var anchorNodes = document.getElementsByName("nametest");
+ is(anchorNodes.length, 1, "getElementsByName still works");
+ is(
+ anchorNodes[0].getAttribute("name"),
+ "nametest",
+ "getElementsByName still works"
+ );
+ }
+ testGetElements($("content"), 1);
+ testGetElements(document.documentElement, 3);
+ testGetElements(document, 3);
+});
+addLoadEvent(SimpleTest.finish);
diff --git a/dom/base/test/file_bug416317.xhtml b/dom/base/test/file_bug416317.xhtml
new file mode 100644
index 0000000000..e46c650cb1
--- /dev/null
+++ b/dom/base/test/file_bug416317.xhtml
@@ -0,0 +1,1468 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg"
+ xml:lang="en" lang="en" dir="ltr" id="html" class="unitTest" title=":root selector">
+<head>
+ <title>selectorTest</title>
+ <!-- (c) Disruptive Innovations 2008 -->
+ <style type="text/css">
+ /* TEST 0 : BASIC TESTS */
+ /* element type selector */
+ body { background-color: red; margin: 10px; padding: 10px; color: red; font-family: sans-serif }
+ div { background-color: red; color: red; }
+ div.header { background-color: #e0e0e0; color: black; padding: 10px; margin-bottom: 10px;}
+ /* class selector */
+ .unitTest { width: 10px; background-color: red; color: red; margin: 0px; margin-right: 2px; float: left; }
+ .test { margin-bottom: 2px; background-color: green; color: green; }
+ /* group of selectors */
+ .unitTest, .test { height: 10px; }
+
+ .UI > * { float: left }
+ .UI { clear: both; height: auto; padding-top: 6px;}
+ .tilda { clear: both; height: auto; padding-top: 6px;}
+ .plus { clear: both; height: auto; padding-top: 6px;}
+
+ h1, p { width: 500px; color: #000; }
+ a { color: #000; }
+ #results { background: #FFF; width: 600px; padding: 10px 40px; color: #000; font-size: 11px; line-height: 1.3em; }
+ #root, #root2, #root3 { display: none; }
+
+ /* init */
+ .blox16 { background-color: red; }
+ .blox17 { background-color: red; }
+ .lastChild > p { background-color: red; }
+ .firstOfType > p { background-color: red }
+ .lastOfType > p { background-color: red }
+ .empty > .isEmpty { color: red; }
+ html { background-color: red; }
+ </style>
+ <span type="text/test" id="test"><![CDATA[
+ /* :target selector */
+ .target :target { background-color: lime; }
+
+ /* test 1 : childhood selector */
+ html > body { background-color: green; }
+ .test > .blox1 { background-color: lime; }
+
+ /* test 2 : attribute existence selector */
+ /* attribute with a value */
+ .blox2[align] { background-color: lime; }
+ /* attribute with empty value */
+ .blox3[align] { background-color: lime; }
+ /* attribute with almost similar name */
+ .blox4, .blox5 { background-color: lime }
+ .blox4[align], .blox5[align] { background-color: red; }
+
+ /* test3 : attribute value selector */
+ .blox6[align="center"] { background-color: lime; }
+ .blox6[align="c"] { background-color: red; }
+ .blox6[align="centera"] { background-color: red; }
+ .blox6[foo="\e9"] { background-color: lime; }
+ .blox6[\_foo="\e9"] { background-color: lime; }
+
+ /* test 4 : [~=] */
+ .blox7[class~="foo"] { background-color: lime; }
+ .blox8, .blox9, .blox10 { background-color: lime; }
+ .blox8[class~=""] { background-color: red; }
+ .blox9[foo~=""] { background-color: red; }
+ .blox10[foo~="foo"] { background-color: red; }
+
+ /* test5 [^=] */
+ .attrStart > .t3 { background-color: lime; }
+ .attrStart > .t1[class^="unit"] { background-color: lime; }
+ .attrStart > .t2 { background-color: lime; }
+ .attrStart > .t2[class^="nit"] { background-color: red; }
+ .attrStart > .t3[align^=""] { background-color: red; }
+ .attrStart > .t4[foo^="\e9"] { background-color: lime; }
+
+ /* test6 [$=] */
+ .attrEnd > .t3 { background-color: lime; }
+ .attrEnd > .t1[class$="t1"] { background-color: lime; }
+ .attrEnd > .t2 { background-color: lime; }
+ .attrEnd > .t2[class$="unit"] { background-color: red; }
+ .attrEnd > .t3[align$=""] { background-color: red; }
+ .attrEnd > .t4[foo$="\e9"] { background-color: lime; }
+
+ /* test7 [*=] */
+ .attrMiddle > .t3 { background-color: lime; }
+ .attrMiddle > .t1[class*="t t"] { background-color: lime; }
+ .attrMiddle > .t2 { background-color: lime; }
+ .attrMiddle > .t2[class*="a"] { background-color: red; }
+ .attrMiddle > .t3[align*=""] { background-color: red; }
+ .attrMiddle > .t4[foo*="\e9"] { background-color: lime; }
+
+ /* :first-child tests */
+ .firstChild .unitTest:first-child { background-color: lime; }
+ .blox12:first-child { background-color: red; }
+ .blox13:first-child { background-color: red; }
+ .blox12, .blox13 { background-color: lime }
+
+ /* :root tests */
+ :root { background-color: green; }
+
+ /* :scope tests */
+ :scope { background-color: green; }
+
+ /* :nth-child(n) tests */
+ .nthchild1 > :nth-last-child(odd) { background-color: lime; }
+ .nthchild1 > :nth-child(odd) { background-color: lime; }
+
+ .nthchild2 > :nth-last-child(even) { background-color: lime; }
+ .nthchild2 > :nth-child(even) { background-color: lime; }
+
+ .nthchild3 > :nth-child(3n+2) { background-color: lime; }
+ .nthchild3 > :nth-last-child(3n+1) { background-color: lime; }
+ .nthchild3 > :nth-last-child(3n+3) { background-color: lime; }
+
+ .nthoftype1 > div:nth-of-type(odd) { background-color: lime; }
+ .nthoftype1 > div:nth-last-of-type(odd) { background-color: lime; }
+ .nthoftype1 > p { background-color: green; }
+
+ .nthoftype2 > div:nth-of-type(even) { background-color: lime; }
+ .nthoftype2 > div:nth-last-of-type(even) { background-color: lime; }
+ .nthoftype2 > p { background-color: green; }
+
+ .nthoftype3 > div:nth-of-type(3n+1) { background-color: lime; }
+ .nthoftype3 > div:nth-last-of-type(3n+1) { background-color: lime; }
+ .nthoftype3 > div:nth-last-of-type(3n+2) { background-color: lime; }
+ .nthoftype3 > p { background-color: green; }
+
+ /* :not() tests */
+ .blox14:not(span) { background-color: lime; }
+ .blox15:not([foo="blox14"]) { background-color: lime; }
+ .blox16:not(.blox15) { background-color: lime; }
+
+ /* :only-of-type tests */
+ .blox17:only-of-type { background-color: lime; }
+ .blox18:only-of-type { background-color: red; }
+ .blox18:not(:only-of-type) { background-color: lime; }
+
+ /* :last-child tests */
+ .lastChild > :last-child { background-color: lime }
+ .lastChild > :not(:last-child) { background-color: lime }
+
+ /* :first-of-type tests */
+ .firstOfType > *:first-of-type { background-color: lime; }
+ *.firstOfType > :not(:first-of-type) { background-color: lime; }
+
+ /* :last-of-type tests */
+ .lastOfType > *:last-of-type { background-color: lime; }
+ *.lastOfType > :not(:last-of-type) { background-color: lime; }
+
+ /* :only-child tests */
+ .onlyChild > *:not(:only-child) { background-color: lime; }
+ .onlyChild > .unitTest > *:only-child { background-color: lime; }
+
+ /* :only-of-type tests */
+ .onlyOfType *:only-of-type { background-color: lime; }
+ .onlyOfType *:not(:only-of-type) { background-color: lime; }
+
+ /* :empty tests */
+ .empty > *.isEmpty:empty { background-color: lime; color: lime; }
+ .empty > .isNotEmpty { background-color: blue; color: blue; }
+ .empty > .isNotEmpty:empty { background-color: red; color: red; }
+ .empty > .isNotEmpty:not(:empty) { background-color: lime; color: lime; }
+
+ /* :lang() tests */
+ .lang :lang(en) { background-color: lime; }
+ .lang :lang(fr) { background-color: lime; }
+ .lang .t1 { background-color: blue; }
+ .lang .t1:lang(es) { background-color: lime; }
+ .lang :lang(es-AR) { background-color: red; }
+
+ /* [|=] tests */
+ .attrLang .t1 { background-color: lime; }
+ .attrLang .t1[lang|="en"] { background-color: red; }
+ .attrLang [lang|="fr"] { background-color: lime; }
+ .attrLang .t2[lang|="en"] { background-color: lime; }
+ .attrLang .t3 { background-color: blue; }
+ .attrLang .t3[lang|="es"] { background-color: lime; }
+ .attrLang [lang|="es-AR"] { background-color: red; }
+
+ /* UI tests */
+ .UI .t1:enabled > .unitTest { background-color: lime; }
+ .UI .t2:disabled > .unitTest { background-color: lime; }
+ .UI .t3:checked + div { background-color: lime; }
+ .UI .t4:not(:checked) + div { background-color: lime; }
+
+ /* ~ combinator tests */
+ .tilda .t1 { background-color: white; }
+ .tilda .t1 ~ .unitTest { background-color: lime; }
+ .tilda .t1:hover ~ .unitTest { background-color: red; }
+
+ /* ~ combinator tests */
+ .plus .t1, .plus .t2 { background-color: white; }
+ .plus .t1 + .unitTest + .unitTest { background-color: lime; }
+ .plus .t1:hover + .unitTest + .unitTest { background-color: red; }
+ ]]></span>
+ <span type="text/test" id="error">
+ /* Tests from http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20060307/html/index.html */
+
+ div, { background: red; }
+ .5cm { background: red; }
+ [*=test] { background: red; }
+ [*|*=test] { background: red; }
+
+ div:subject { background: red; }
+ :canvas { background: red; }
+ :viewport { background: red; }
+ :window { background: red; }
+ :menu { background: red; }
+ :table { background: red; }
+ :select { background: red; }
+ ::canvas { background: red; }
+ ::viewport { background: red; }
+ ::window { background: red; }
+ ::menu { background: red; }
+ ::table { background: red; }
+ ::select { background: red; }
+
+ ..test { background: red; color: yellow; }
+ .foo..quux { background: red; color: yellow; }
+ .bar. { background: red; color: yellow; }
+ </span>
+ <script><![CDATA[
+ /* eslint-disable no-shadow */
+
+ window.onload = function() {
+ doTest();
+ }
+
+ function doTest(){
+ if ( !window.location.hash.includes("target") )
+ window.location.hash = "#target";
+
+ var root = document.getElementById("root");
+ var root2 = document.getElementById("root2");
+ var root3 = document.getElementById("root3");
+ var results = [];
+ var tests = 0, passed = 0;
+ var cache = {};
+
+ var css = document.getElementById("test").firstChild.nodeValue.split("\n");
+ for ( let i = 0; i < css.length; i++ ) {
+ css[i] = css[i].replace(/\/\*.*?\*\//g, "")
+ .replace(/^\s*|\s*$/g, "").split(/\s*{/);
+ }
+
+ var ecss = document.getElementById("error").firstChild.nodeValue.split("\n");
+ for ( let i = 0; i < ecss.length; i++ ) {
+ ecss[i] = ecss[i].replace(/\/\*.*?\*\//g, "")
+ .replace(/^\s*|\s*$/g, "").split(/\s*{/);
+ }
+
+ var namespaceCheck = {};
+
+ var badNamespace = [
+ {},
+ null,
+ undefined,
+ ];
+
+ interfaceCheck(root, "Element");
+ runTest( css, "Element", root, true );
+ check( "Inside Element", root, true, false );
+ cacheCheck( "Element", root );
+ check( "Outside Element", root2, passed === 0 ? "autofail" : false, false );
+ runTest( ecss, "SyntaxError: Element", root, false );
+ jqTests("Element", root3, "querySelectorAll");
+
+ var root4 = root2.cloneNode(true);
+ interfaceCheck(root4, "Disconnected Element");
+ runTest( css, "Disconnected Element", root4, true );
+ check( "Disconnected Element", root4, true, true );
+ cacheCheck( "Disconnected Element", root4 );
+ runTest( ecss, "SyntaxError: Disconnected Element", root4, false );
+ jqTests("Disconnected Element", root3.cloneNode(true), "querySelectorAll");
+ var newRoot = document.createElement("nosuchtag");
+ newRoot.appendChild(root3.cloneNode(true));
+ jqTests("Disconnected Element scoping", newRoot, "querySelectorAll");
+
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild( root2.cloneNode(true) );
+
+ interfaceCheck(fragment, "Fragment");
+ runTest( css, "Fragment", fragment, true );
+ check( "Fragment", fragment, true, true );
+ runTest( ecss, "SyntaxError: Fragment", fragment, false );
+ cacheCheck( "Fragment", fragment );
+
+ root.remove( );
+
+ interfaceCheck(document, "Document");
+ runTest( css, "Document", document, true );
+ check( "Document", document, true, false );
+ runTest( ecss, "SyntaxError: Document", document, false );
+ jqTests("Document", document, "querySelectorAll");
+ cacheCheck( "Document", document );
+
+ done();
+
+ function interfaceCheck(obj, type){
+ var q = typeof obj.querySelector === "function";
+ assert( q, type + " supports querySelector" );
+ var qa = typeof obj.querySelectorAll === "function";
+ assert( qa, type + " supports querySelectorAll" );
+ return q && qa;
+ }
+
+ function done(){
+ if (window.parent && window.parent.SimpleTest) {
+ window.parent.SimpleTest.finish();
+ } else {
+ var r = document.getElementById("results");
+ var li = document.createElement("li");
+ var b = document.createElement("b");
+ b.appendChild( document.createTextNode( ((passed / tests) * 100).toFixed(1) + "%" ) );
+ li.appendChild( b );
+ li.appendChild( document.createTextNode( ": " + passed + " passed, " + (tests - passed) + " failed" ) );
+ r.appendChild( li );
+
+ for ( let i = 0; i < results.length; i++ ) {
+ var li = document.createElement("li");
+ var span = document.createElement("span");
+ span.style.color = (results[i][0] === "FAIL" ? "red" : "green");
+ span.appendChild( document.createTextNode( results[i][0] ) );
+ li.appendChild( span );
+ li.appendChild( document.createTextNode( " " + results[i][1] ) );
+ r.appendChild( li );
+ }
+ }
+ }
+
+ function cacheCheck( type, root ) {
+ try {
+ var pre = root.querySelectorAll( "div" ), preLength = pre.length;
+
+ var div = document.createElement("div");
+ (root.body || root).appendChild( div );
+
+ var post = root.querySelectorAll( "div" ), postLength = post.length;
+
+ assert( pre.length == preLength, type + ": StaticNodeList" );
+ assert( post.length != pre.length, type + ": StaticNodeList" );
+ } catch(e) {
+ assert( false, type + ": StaticNodeList" );
+ assert( false, type + ": StaticNodeList" );
+ }
+
+ if ( div )
+ (root.body || root).removeChild( div );
+ }
+
+
+ function runTest( css, type, root, expect ) {
+ var pass = false;
+ try {
+ root.querySelectorAll("");
+ } catch(e){ pass = e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR; }
+ assert( pass, type + ".querySelectorAll Empty String" );
+
+ pass = false;
+ try {
+ pass = root.querySelectorAll(null).length === 0;
+ } catch(e){ pass = false; }
+ assert( pass, type + ".querySelectorAll null" );
+
+ pass = false;
+ try {
+ pass = root.querySelectorAll(undefined).length === 0;
+ } catch(e){ pass = false; }
+ assert( pass, type + ".querySelectorAll undefined" );
+
+ pass = false;
+ try {
+ if ( root.querySelectorAll )
+ root.querySelectorAll();
+ } catch(e){ pass = true; }
+ assert( pass, type + ".querySelectorAll no value" );
+
+ pass = false;
+ try {
+ root.querySelector("");
+ } catch(e){ pass = e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR; }
+ assert( pass, type + ".querySelector Empty String" );
+
+ pass = false;
+ try {
+ pass = root.querySelector(null) === null;
+ } catch(e){ pass = false; }
+ assert( pass, type + ".querySelector null" );
+
+ pass = false;
+ try {
+ pass = root.querySelector(undefined) === null;
+ } catch(e){ pass = false; }
+ assert( pass, type + ".querySelector undefined" );
+
+ pass = false;
+ try {
+ if ( root.querySelector )
+ root.querySelector();
+ } catch(e){ pass = true; }
+ assert( pass, type + ".querySelector no value" );
+
+ for ( let i = 0; i < css.length; i++ ) {
+ var test = css[i];
+ if ( test.length == 2 ) {
+ var query = test[0], color = test[1].match(/: ([^\s;]+)/)[1];
+
+ try {
+ var found = root.querySelectorAll(query);
+
+ for ( var f = 0; f < found.length; f++ ) {
+ found[f].style.backgroundColor = color;
+ }
+
+ var pass = color != "red" || found.length === 0;
+
+ assert(expect && pass, type + ".querySelectorAll: " + query);
+ } catch(e){
+ var pass = !expect && e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR || false;
+ assert(pass, type + ".querySelectorAll: " + query);
+ }
+
+ if ( expect ) {
+ var pass = false;
+
+ try {
+ var found2 = root.querySelectorAll( " \t\r\n " + query + " \t\r\n " );
+ pass = found2.length == found.length;
+ } catch(e) {}
+
+ assert(pass, type + ".querySelectorAll Whitespace Trim: " + query);
+ }
+
+ try {
+ var single = root.querySelector(query);
+
+ var pass = !found.length && single === null ||
+ found.length && found[0] == single;
+
+ assert(expect, type + ".querySelector: " + query);
+ } catch(e){
+ var pass = !expect && e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR || false;
+ assert(pass, type + ".querySelector: " + query);
+ }
+ }
+ }
+ }
+
+ function check( type, root, expect, fragment ){
+ var walker = document.createTreeWalker( root, NodeFilter.SHOW_ELEMENT, { acceptNode(){ return 1; } } );
+
+ while ( walker.nextNode() ) {
+ var div = walker.currentNode;
+ if ( (div.getAttribute("class") || "").toString().indexOf("unitTest") > -1 &&
+ (!fragment || div.getAttribute("id") !== "nofragment") ) {
+ // If we're display:none, we need to toggle that when doing computed
+ // style.
+ var needToggle =
+ (window.frameElement &&
+ window.frameElement.style.display == "none");
+ if (needToggle) {
+ if ((div.getAttribute("class") || "").toString().indexOf("skipWhenToggling") > -1) {
+ continue;
+ }
+ window.frameElement.style.display = "";
+ // make sure it kicks in immediately
+ document.body.offsetWidth;
+ }
+ var view = document.defaultView.getComputedStyle(div);
+ var bg = view.getPropertyValue("background-color") || div.style.backgroundColor;
+ if (needToggle) {
+ window.frameElement.style.display = "none";
+ // make sure it kicks in immediately
+ document.body.offsetWidth;
+ }
+
+ var pass = bg && !bg.includes("(255, 0, 0") && !bg.includes("#ff0000") && !bg.includes("red");
+ //var pass = bg && bg.indexOf("(255, 0, 0") == -1 && bg.indexOf("#ff0000") == -1;
+ assert(pass === expect, type + ": " + (div.title || div.parentNode.title));
+ }
+ }
+ }
+
+ function assert(pass, title) {
+ // Update |passed| no matter what: some tests depend on this
+ passed += (pass ? 1 : 0);
+
+ if (window.parent && window.parent.SimpleTest) {
+ window.parent.SimpleTest.ok(pass, title);
+ } else {
+ results.push([ (!pass ? "FAIL" : "PASS"), title ]);
+ tests++;
+ }
+ }
+
+ function jqTests(type, root, select) {
+
+ function query(q, resolver){
+ try {
+ return root[select](q, resolver);
+ } catch(e){
+ if ( e.message.indexOf("ERR") > -1 ||
+ (e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR) )
+ throw e;
+ }
+ }
+
+ var all = query("*");
+
+ function checkMatchesSelector(results, q) {
+ var key = +new Date + ":" + Math.random();
+
+ function report(item, shouldMatch) {
+ assert( item.matches(q) === shouldMatch,
+ item + (shouldMatch ? "does not match" : "matches")
+ + " selector '" + q + "'" );
+ }
+
+ for (var i = 0; i < results.length; i++) {
+ var item = results.item(i);
+ item[key] = true;
+ report( item, true );
+ }
+
+ for (var stride = 15, // reduce test spam
+ i = Math.round(Math.random() * stride);
+ i < all.length; i += stride)
+ {
+ var item = all.item(i),
+ shouldMatch = !!item[key];
+ report( item, shouldMatch );
+ }
+
+ for (var i = 0; i < results.length; i++)
+ delete results.item(i)[key];
+ }
+
+ function t( name, q, ids, restrict, ids2 ) {
+ var pass = true;
+
+ if ( restrict === false && root != document )
+ return;
+
+ var namespaced = /\|[^=]/.test( q );
+ var prepend = namespaced ? "xHTML|*#root3 " : "#root3 ";
+ q = (restrict === false || restrict === ":root" ||
+ restrict === ":scope" ? "" : prepend) +
+ q.replace(/,/g, ", " + prepend);
+ var nq = q.replace(/>/g, "&gt;").replace(/</g, "&lt;");
+
+ if ( namespaced ) {
+ for ( var i = 0; i < badNamespace.length; i++ ) {
+ var resolver = badNamespace[i], pass = false, results = null;
+
+ try {
+ results = query(q, resolver);
+ } catch(e) {
+ pass = (e.message === "bad ERROR" ||
+ (e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR));
+ }
+
+ assert( pass, type + ": " + name + " Bad Resolver #" + (i+1) + " (" + nq + ")" +
+ (pass ? "" : " Expected: " + extra(ids) + " Received: " + extra(results)) );
+ }
+ } else {
+ var pass = false;
+
+ try {
+ var results = query(q);
+ pass = hasPassed( results, ids );
+ } catch(e) {
+ pass = e.name == "SyntaxError" && e.code == DOMException.SYNTAX_ERR;
+ }
+
+ assert( pass, type + ": " + name + " (" + nq + ")" +
+ (pass ? "" : " Expected: " + extra(ids) + " Received: " + extra(results)) );
+
+ // For now, don't use checkMatchesSelector when
+ // restrict === ":scope" because we have no way to hand the
+ // right scope to it yet.
+ if (results && restrict !== ":scope")
+ checkMatchesSelector( results, q );
+ }
+
+ function hasPassed(results, ids){
+ var pass = (results && results.length == ids.length) || (!results && !ids);
+
+ if ( ids && results ) {
+ for ( var i = 0; ids && i < ids.length; i++ ) {
+ if ( ids[i] !== results[i].getAttribute("id") ) {
+ pass = false;
+ }
+ }
+ } else {
+ pass = false;
+ }
+
+ return pass;
+ }
+
+ function extra(results){
+ var extra = " [";
+ if ( results ) {
+ for ( var i = 0; i < results.length; i++ ) {
+ extra += (extra.length > 2 ? "," : "") + "'" + (results[i].id || results[i]) + "'";
+ }
+ }
+
+ extra += "]";
+ return extra;
+ }
+ }
+
+ t( "SVG", "*|svg", ["svg1","svg2","svg3"] );
+ t( "SVG", "svg|svg", ["svg2","svg3"] );
+ t( "SVG", "svg|svg *|circle", ["circle2","circle3"] );
+ t( "SVG", "svg|svg svg|circle", ["circle2","circle3"] );
+ t( "SVG", "xHTML|div *|svg", ["svg1","svg2","svg3"] );
+ t( "SVG", "div svg|svg", ["svg2","svg3"] );
+ t( "SVG", "xHTML|div svg|svg", ["svg2","svg3"] );
+ t( "SVG", "xHTML|div svg|svg *|circle", ["circle2","circle3"] );
+ t( "SVG", "xHTML|div svg *|circle", ["circle1","circle2","circle3"], true, ["circle1"] );
+ t( "SVG", "xHTML|div svg|svg svg|circle", ["circle2","circle3"] );
+
+ t( "Element Selector", "xHTML|p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Parent Element", "xHTML|div p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Parent Element", "xHTML|div xHTML|p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Parent Element", "*|div xHTML|p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Parent Element", "*|div *|p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Child", "xHTML|p > xHTML|a", ["simon1","google","groups","mark","yahoo","simon"] );
+ t( "Adjacent", "xHTML|a + xHTML|a", ["groups"] );
+ t( "Adjacent", "xHTML|a + a", ["groups"] );
+ t( "Nth-child", "xHTML|*#form xHTML|*#select1 xHTML|option:nth-child(3)", ["option1c"] );
+
+ assert( all && all.length > 30, type + ": Select all" );
+ var good = all && all.length;
+ for ( var i = 0; all && i < all.length; i++ )
+ if ( all[i].nodeType != 1 )
+ good = false;
+ assert( good, type + ": Select all elements, no comment nodes" );
+
+ if ( root == document ) {
+ t( ":root Selector", ":root", ["html"], false );
+ t( ":scope Selector", ":scope", ["html"], ":scope" );
+ } else {
+ t( ":root Selector", ":root", [], ":root" );
+ if (root.localName != "nosuchtag") {
+ t( ":scope Selector", ":scope > nosuchtag",
+ [ "outerbogustag" ], ":scope");
+ }
+ t( ":scope Selector", ":scope nosuchtag nosuchtag",
+ [ "innerbogustag" ], ":scope");
+
+ if ( !root.parentNode ) {
+ t( ":root All Selector", ":root *", [], ":root" );
+ }
+ }
+
+ if ( root.parentNode || root == document ) {
+ assert( query(":root *").length == query("*").length - (root == document ? 1 : 0), type + ": :root All Selector" );
+ }
+ assert( query(":scope *").length == query("*").length - (root == document ? 1 : 0), type + ": :scope All Selector" );
+
+ t( "Element Selector", "p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Element Selector", "body", ["body"], false );
+ t( "Element Selector", "html", ["html"], false );
+ t( "Parent Element", "div p", ["firstp","ap","sndp","en","sap","first"] );
+ var param = query("#object1 param");
+ assert( param && param.length == 2, type + ": Object/param as context" );
+
+ var l = query("#length");
+ assert( l && l.length, type + ': &lt;input name="length"&gt; cannot be found under IE' );
+ var lin = query("#lengthtest input");
+ assert( lin && lin.length, type + ': &lt;input name="length"&gt; cannot be found under IE' );
+
+ t( "Broken Selector", "[" );
+ t( "Broken Selector", "(" );
+ t( "Broken Selector", "{" );
+ t( "Broken Selector", "<" );
+ t( "Broken Selector", "()" );
+ t( "Broken Selector", "<>" );
+ t( "Broken Selector", "{}" );
+
+ t( "ID Selector", "#body", ["body"], false );
+ t( "ID Selector w/ Element", "body#body", ["body"], false );
+ t( "ID Selector w/ Element", "ul#first", [] );
+ t( "ID selector with existing ID descendant", "#firstp #simon1", ["simon1"] );
+ t( "ID selector with nonexistent descendant", "#firstp #foobar", [] );
+
+ t( "ID selector using UTF8", "#å°åŒ—TaÌibeÌŒi", ["å°åŒ—TaÌibeÌŒi"] );
+ t( "Multiple ID selectors using UTF8", "#å°åŒ—TaÌibeÌŒi, #å°åŒ—", ["å°åŒ—TaÌibeÌŒi","å°åŒ—"] );
+ t( "Descendant ID selector using UTF8", "div #å°åŒ—", ["å°åŒ—"] );
+ t( "Child ID selector using UTF8", "form > #å°åŒ—", ["å°åŒ—"] );
+
+ t( "Escaped ID", "#foo\\:bar", ["foo:bar"] );
+ t( "Escaped ID", "#test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Descendant escaped ID", "div #foo\\:bar", ["foo:bar"] );
+ t( "Descendant escaped ID", "div #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Child escaped ID", "form > #foo\\:bar", ["foo:bar"] );
+ t( "Child escaped ID", "form > #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+
+ t( "ID Selector, child ID present", "#form > #radio1", ["radio1"] ); // bug #267
+ t( "ID Selector, not an ancestor ID", "#form #first", [] );
+ t( "ID Selector, not a child ID", "#form > #option1a", [] );
+
+ t( "All Children of ID", "#foo > *", ["sndp", "en", "sap"] );
+ t( "All Children of ID with no children", "#firstUL > *", [] );
+
+ t( "ID selector with nonexistent ancestor", "#asdfasdf #foobar", [] ); // bug #986
+
+ //t( "body div#form", [], "ID selector within the context of another element" );
+
+ t( "Class Selector", ".blog", ["mark","simon"] );
+ t( "Class Selector", ".blog.link", ["simon"] );
+ t( "Class Selector w/ Element", "a.blog", ["mark","simon"] );
+ t( "Parent Class Selector", "p .blog", ["mark","simon"] );
+
+ t( "Class selector using UTF8", ".å°åŒ—TaÌibeÌŒi", ["utf8class1"] );
+ t( "Class selector using UTF8", ".å°åŒ—", ["utf8class1","utf8class2"] );
+ t( "Class selector using UTF8", ".å°åŒ—TaÌibeÌŒi.å°åŒ—", ["utf8class1"] );
+ t( "Class selector using UTF8", ".å°åŒ—TaÌibeÌŒi, .å°åŒ—", ["utf8class1","utf8class2"] );
+ t( "Descendant class selector using UTF8", "div .å°åŒ—TaÌibeÌŒi", ["utf8class1"] );
+ t( "Child class selector using UTF8", "form > .å°åŒ—TaÌibeÌŒi", ["utf8class1"] );
+
+ t( "Escaped Class", ".foo\\:bar", ["foo:bar"] );
+ t( "Escaped Class", ".test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Descendant scaped Class", "div .foo\\:bar", ["foo:bar"] );
+ t( "Descendant scaped Class", "div .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Child escaped Class", "form > .foo\\:bar", ["foo:bar"] );
+ t( "Child escaped Class", "form > .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+
+ t( "Comma Support", "a.blog, p", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Comma Support", "a.blog , p", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Comma Support", "a.blog ,p", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Comma Support", "a.blog,p", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+
+ t( "Outer Whitespace", " a.blog,p", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Outer Whitespace", "a.blog,p ", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Outer Whitespace", " p,a.blog", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+ t( "Outer Whitespace", "p,a.blog ", ['firstp','ap','mark','sndp','en','sap','simon','first'] );
+
+ t( "Child", "p > a", ["simon1","google","groups","mark","yahoo","simon"] );
+ t( "Child", "p> a", ["simon1","google","groups","mark","yahoo","simon"] );
+ t( "Child", "p >a", ["simon1","google","groups","mark","yahoo","simon"] );
+ t( "Child", "p>a", ["simon1","google","groups","mark","yahoo","simon"] );
+ t( "Child w/ Class", "p > a.blog", ["mark","simon"] );
+ t( "All Children", "code > *", ["anchor1","anchor2"] );
+ t( "All Grandchildren", "p > * > *", ["anchor1","anchor2"] );
+ t( "Adjacent", "a + a", ["groups"] );
+ t( "Adjacent", "a +a", ["groups"] );
+ t( "Adjacent", "a+ a", ["groups"] );
+ t( "Adjacent", "a+a", ["groups"] );
+ t( "Adjacent", "p + p", ["ap","en","sap"] );
+ t( "Comma, Child, and Adjacent", "a + a, code > a", ["groups","anchor1","anchor2"] );
+
+ t( "First Child", "p:first-child", ["firstp","sndp"] );
+ t( "Nth Child", "p:nth-child(1)", ["firstp","sndp"] );
+
+ t( "Last Child", "p:last-child", ["sap"] );
+ t( "Last Child", "a:last-child", ["simon1","anchor1","mark","yahoo","anchor2","simon"] );
+
+ t( "Nth-child", "#main form#form > *:nth-child(2)", ["text2"] );
+ t( "Nth-child", "#main form#form > :nth-child(2)", ["text2"] );
+
+ t( "Nth-child", "#form #select1 option:nth-child(3)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(0n+3)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(1n+0)", ["option1a", "option1b", "option1c", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(1n)", ["option1a", "option1b", "option1c", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(n)", ["option1a", "option1b", "option1c", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(even)", ["option1b", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(odd)", ["option1a", "option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(2n)", ["option1b", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(2n+1)", ["option1a", "option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n+1)", ["option1a", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n+2)", ["option1b"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n+3)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n-1)", ["option1b"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n-2)", ["option1a", "option1d"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n-3)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(3n+0)", ["option1c"] );
+ t( "Nth-child", "#form #select1 option:nth-child(-n+3)", ["option1a", "option1b", "option1c"] );
+
+ t( "Attribute Exists", "a[title]", ["google"] );
+ t( "Attribute Exists", "*[title]", ["google"] );
+ t( "Attribute Exists", "[title]", ["google"] );
+
+ t( "Attribute Equals", "a[rel='bookmark']", ["simon1"] );
+ t( "Attribute Equals", 'a[rel="bookmark"]', ["simon1"] );
+ t( "Attribute Equals", "a[rel=bookmark]", ["simon1"] );
+ t( "Multiple Attribute Equals", "#form input[type='hidden'],#form input[type='radio']", ['radio1','radio2','hidden1'] );
+ t( "Multiple Attribute Equals", "#form input[type=\"hidden\"],#form input[type='radio']", ['radio1','radio2','hidden1'] );
+ t( "Multiple Attribute Equals", "#form input[type=hidden],#form input[type=radio]", ['radio1','radio2','hidden1'] );
+
+ t( "Attribute selector using UTF8", "span[lang=中文]", ["å°åŒ—"] );
+
+ t( "Attribute Begins With", "a[href ^= 'http://www']", ["google","yahoo"] );
+ t( "Attribute Ends With", "a[href $= 'org/']", ["mark"] );
+ t( "Attribute Contains", "a[href *= 'google']", ["google","groups"] );
+
+ // t("Select options via [selected]", "#select1 option[selected]", ["option1a"] );
+ t("Select options via [selected]", "#select1 option[selected]", [] );
+ t("Select options via [selected]", "#select2 option[selected]", ["option2d"] );
+ t("Select options via [selected]", "#select3 option[selected]", ["option3b", "option3c"] );
+
+ t( "Grouped Form Elements", "input[name='foo[bar]']", ["hidden2"] );
+
+ t( ":not() Existing attribute", "#form select:not([multiple])", ["select1", "select2"]);
+ t( ":not() Equals attribute", "#form select:not([name=select1])", ["select2", "select3"]);
+ t( ":not() Equals quoted attribute", "#form select:not([name='select1'])", ["select2", "select3"]);
+
+ t( "First Child", "p:first-child", ["firstp","sndp"] );
+ t( "Last Child", "p:last-child", ["sap"] );
+ t( "Only Child", "a:only-child", ["simon1","anchor1","yahoo","anchor2"] );
+ t( "Empty", "ul:empty", ["firstUL"] );
+ //t( "Enabled UI Element", "#form input:enabled", ["text1","radio1","radio2","check1","check2","hidden2","name"] );
+ t( "Disabled UI Element", "#form input:disabled", ["text2"] );
+ t( "Checked UI Element", "#form input:checked", ["radio2","check1"] );
+ t( "Element Preceded By", "p ~ div", ["foo","fx-queue","fx-tests", "moretests"] );
+ t( "Not", "a.blog:not(.link)", ["mark"] );
+ }
+ };
+ ]]></script>
+</head>
+<body id="body" class="unitTest" title="childhood and element type selectors">
+<h1><a href="http://www.w3.org/TR/selectors-api/">Selectors API</a> Test Suite</h1>
+<p>Testrunner by <a href="http://ejohn.org/">John Resig</a>, tests by <a href="http://ejohn.org/">John Resig</a>, <a href="http://disruptive-innovations.com/zoo/css3tests/selectorTest.html">Disruptive Innovations</a>, <a href="http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20060307/html/index.html">W3C CSS Working Group</a>, <a href="http://jquery.com/test/">jQuery JavaScript Library</a>.</p>
+<div id="root">
+ <div class="header">
+ <h3>CSS 3 Selectors tests</h3>
+ <p>(c) <a href="http://www.disruptive-innovations.com">Disruptive Innovations</a> 2008<br/>
+ Last update: 2008-06-06</p>
+ </div>
+
+ <div class="test target">
+ <div class="unitTest skipWhenToggling" id="target" title=":target selector"></div>
+ </div>
+
+ <div class="test">
+ <div class="blox1 unitTest" title="childhood selector"></div>
+ </div>
+
+ <div class="test attributeExistence">
+ <div class="blox2 unitTest" align="center" title="attribute existence selector"></div>
+ <div class="blox3 unitTest" align="" title="attribute existence selector with empty string value"></div>
+ <div class="blox4 unitTest" valign="center" title="attribute existence selector with almost identical attribute"></div>
+ <div class="blox5 unitTest" alignv="center" title="attribute existence selector with almost identical attribute"></div>
+ </div>
+
+ <div class="test attributeValue">
+ <div class="blox6 unitTest" align="center" title="attribute value selector"></div>
+ <div class="blox6 unitTest" foo="&eacute;" title="attribute value selector with an entity in the attribute and an escaped value in the selector"></div>
+ <div class="blox6 unitTest" _foo="&eacute;" title="attribute value selector with an entity in the attribute, an escaped value in the selector, and a leading underscore in the attribute name"></div>
+ </div>
+
+ <div class="test attributeSpaceSeparatedValues">
+ <div class="blox7 foo unitTest" title="[~=] attribute selector"></div>
+ <div class="blox8 unitTest" title="[~=] attribute selector looking for empty string"></div>
+ <div class="blox9 unitTest" foo="" title="[~=] attribute selector looking for empty string in empty attribute"></div>
+ <div class="blox10 unitTest" foo="foobar" title="[~=] attribute selector looking for 'foo' in 'foobar'"></div>
+ </div>
+
+ <div class="test attrStart">
+ <div class="unitTest t1" title="[^=] attribute selector"></div>
+ <div class="unitTest t2" title="[^=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[^=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="&eacute;tagada" title="[^=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test attrEnd">
+ <div class="unitTest t1" title="[$=] attribute selector"></div>
+ <div class="unitTest t2" title="[$=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[$=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="tagada&eacute;" title="[$=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test attrMiddle">
+ <div class="unitTest t1" title="[*=] attribute selector"></div>
+ <div class="unitTest t2" title="[*=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[*=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="tagada&eacute;foo" title="[*=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test firstChild">
+ <div class="unitTest" title=":first-child selector"></div>
+ <div class="blox12 unitTest" title=":first-child selector should not match non first child"></div>
+ <div class="blox13 unitTest" title=":first-child selector should not match non first child"></div>
+ </div>
+
+ <div class="test not">
+ <div class="blox14 unitTest" title="negation pseudo-class with argument being an element type selector"></div>
+ <div class="blox15 unitTest" foo="blox15" title="negation pseudo-class with argument being an attribute selector"></div>
+ <div class="blox16 unitTest" foo="blox15" title="negation pseudo-class accepts only simple selectors for argument"></div>
+ </div>
+
+ <div class="test onlyOfType">
+ <div class="blox17 unitTest" title=":only-of-type selector"></div>
+ <p class="blox18 unitTest" title="negated :only-of-type selector"></p>
+ <p class="blox18 unitTest" title="negated :only-of-type selector"></p>
+ </div>
+
+ <div class="test nthchild1">
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ </div>
+ <div class="test nthchild2">
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ </div>
+ <div class="test nthchild3">
+ <div class="unitTest no" title=":nth-last-child(3n+3) selector"></div>
+ <div class="unitTest" title=":nth-child(3n+2) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+1) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+3) selector"></div>
+ <div class="unitTest" title=":nth-child(3n+2) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+1) selector"></div>
+ </div>
+
+ <div class="test nthoftype1">
+ <div class="unitTest" title=":nth-of-type(odd) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(odd) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-of-type(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-of-type(odd) selector"></div>
+ </div>
+ <div class="test nthoftype2">
+ <div class="unitTest" title=":nth-last-of-type(even) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-of-type(even) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(even) selector"></div>
+ <div class="unitTest" title=":nth-of-type(even) selector"></div>
+ </div>
+ <div class="test nthoftype3">
+ <div class="unitTest" title=":nth-of-type(3n+1) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+2) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+1) selector"></div>
+ <div class="unitTest" title=":nth-of-type(3n+1) selector"></div>
+ <p class="unitTest" title=":nth-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+2) selector"></div>
+ <div class="unitTest" title=":nth-last-of-type(3n+1) selector"></div>
+ </div>
+
+ <div class="test lastChild">
+ <p class="unitTest" title=":not(:last-child) selector"></p>
+ <div class="unitTest" title=":last-child selector"></div>&nbsp;
+ </div>
+
+ <div class="test firstOfType">
+ <p class="unitTest" title=":first-of-type selector"></p>
+ <div class="unitTest" title=":first-of-type selector"></div>
+ <p class="unitTest" title=":not(:first-of-type)"></p>
+ <div class="unitTest" title=":not(:first-of-type)"></div>
+ </div>
+
+ <div class="test lastOfType">
+ <p class="unitTest" title=":not(:last-of-type)"></p>
+ <div class="unitTest" title=":not(:last-of-type)"></div>
+ <p class="unitTest" title=":last-of-type selector"></p>
+ <div class="unitTest" title=":last-of-type selector"></div>
+ </div>
+
+ <div class="test onlyChild">
+ <div class="unitTest" title=":only-child where the element is NOT the only child"></div>
+ <div class="unitTest" title=":only-child where the element is the only child">
+ <div class="unitTest" title=":only-child where the element is the only child"></div>
+ </div>
+ </div>
+
+ <div class="test onlyOfType">
+ <p class="unitTest" title=":only-of-type"></p>
+ <div class="unitTest" title=":only-of-type">
+ <div class="unitTest" title=":only-of-type"></div>
+ </div>
+ <div class="unitTest" title=":not(only-of-type)"></div>
+ </div>
+
+ <div class="test empty">
+ <div class="unitTest isEmpty" title=":empty with empty element"></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains a whitespace"> </div>
+ <div class="unitTest isEmpty" title=":empty and element contains an SGML comment"><!-- foo --></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains a SPAN element"><span></span></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains an entity reference">&nbsp;</div>
+ </div>
+
+ <div class="test lang">
+ <div id="nofragment" class="unitTest" title=":lang() where language comes from the document"></div>
+ <div class="unitTest" lang="fr" title=":lang() where language comes from the element"></div>
+ <div class="unitTest" lang="en-US" title=":lang() where language comes from the element but is a dialect of the language queried"></div>
+ <div class="unitTest t1" lang="es" title=":lang() where language comes from the element but the language queried is a dialect of the element's one so it should not match"></div>
+ </div>
+
+ <div class="test attrLang">
+ <div class="unitTest t1" title="[|=] where language comes from the document"></div>
+ <div class="unitTest" lang="fr" title="[|=] where language comes from the element"></div>
+ <div class="unitTest t2" lang="en-US" title="[|=] where language comes from the element but is a dialect of the language queried"></div>
+ <div class="unitTest t3" lang="es" title="[|=] where language comes from the element but the language queried is a dialect of the element's one so it should not match"></div>
+ </div>
+
+ <div class="test UI">
+ <button name="submit" type="submit" value="submit" class="t1" title=":enabled pseudo-class"><div class="unitTest"></div></button>
+ <button name="submit" type="submit" value="submit" class="t2" disabled="true" title=":enabled pseudo-class"><div class="unitTest"></div></button>
+ </div>
+ <div class="test UI">
+ <input class="t3" type="checkbox" checked="true"/><div class="unitTest" title=":checked"></div>
+ the previous square should be green when the checkbox is checked and become red when you uncheck it
+ </div>
+ <div class="test UI">
+ <input class="t4" type="checkbox"/><div class="unitTest" title=":not(:checked)"></div>
+ the previous square should be green when the checkbox is NOT checked and become red when you check it
+ </div>
+
+ <div class="test tilda">
+ <div class="unitTest t1" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <span style="float:left">the three last squares should be green and become red when the pointer hovers over the white square</span>
+ </div>
+ <div class="test plus">
+ <div class="unitTest t1" title="+ combinator"></div>
+ <div class="unitTest t2" title="+ combinator"></div>
+ <div class="unitTest" title="+ combinator"></div>
+ <span style="float:left">the last square should be green and become red when the pointer hovers over the FIRST white square</span>
+ </div>
+</div>
+<div id="root2">
+ <div class="header">
+ <h3>CSS 3 Selectors tests</h3>
+ <p>(c) <a href="http://www.disruptive-innovations.com">Disruptive Innovations</a> 2008<br/>
+ Last update: 2008-06-06</p>
+ </div>
+
+ <div class="test">
+ <div class="blox1 unitTest" title="childhood selector"></div>
+ </div>
+
+ <div class="test attributeExistence">
+ <div class="blox2 unitTest" align="center" title="attribute existence selector"></div>
+ <div class="blox3 unitTest" align="" title="attribute existence selector with empty string value"></div>
+ <div class="blox4 unitTest" valign="center" title="attribute existence selector with almost identical attribute"></div>
+ <div class="blox5 unitTest" alignv="center" title="attribute existence selector with almost identical attribute"></div>
+ </div>
+
+ <div class="test attributeValue">
+ <div class="blox6 unitTest" align="center" title="attribute value selector"></div>
+ <div class="blox6 unitTest" foo="&eacute;" title="attribute value selector with an entity in the attribute and an escaped value in the selector"></div>
+ <div class="blox6 unitTest" _foo="&eacute;" title="attribute value selector with an entity in the attribute, an escaped value in the selector, and a leading underscore in the attribute name"></div>
+ </div>
+
+ <div class="test attributeSpaceSeparatedValues">
+ <div class="blox7 foo unitTest" title="[~=] attribute selector"></div>
+ <div class="blox8 unitTest" title="[~=] attribute selector looking for empty string"></div>
+ <div class="blox9 unitTest" foo="" title="[~=] attribute selector looking for empty string in empty attribute"></div>
+ <div class="blox10 unitTest" foo="foobar" title="[~=] attribute selector looking for 'foo' in 'foobar'"></div>
+ </div>
+
+ <div class="test attrStart">
+ <div class="unitTest t1" title="[^=] attribute selector"></div>
+ <div class="unitTest t2" title="[^=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[^=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="&eacute;tagada" title="[^=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test attrEnd">
+ <div class="unitTest t1" title="[$=] attribute selector"></div>
+ <div class="unitTest t2" title="[$=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[$=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="tagada&eacute;" title="[$=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test attrMiddle">
+ <div class="unitTest t1" title="[*=] attribute selector"></div>
+ <div class="unitTest t2" title="[*=] attribute selector"></div>
+ <div class="unitTest t3" align="center" title="[*=] attribute selector looking for empty string"></div>
+ <div class="unitTest t4" foo="tagada&eacute;foo" title="[*=] attribute selector looking for &eacute;"></div>
+ </div>
+
+ <div class="test firstChild">
+ <div class="unitTest" title=":first-child selector"></div>
+ <div class="blox12 unitTest" title=":first-child selector should not match non first child"></div>
+ <div class="blox13 unitTest" title=":first-child selector should not match non first child"></div>
+ </div>
+
+ <div class="test not">
+ <div class="blox14 unitTest" title="negation pseudo-class with argument being an element type selector"></div>
+ <div class="blox15 unitTest" foo="blox15" title="negation pseudo-class with argument being an attribute selector"></div>
+ <div class="blox16 unitTest" foo="blox15" title="negation pseudo-class accepts only simple selectors for argument"></div>
+ </div>
+
+ <div class="test onlyOfType">
+ <div class="blox17 unitTest" title=":only-of-type selector"></div>
+ <p class="blox18 unitTest" title="negated :only-of-type selector"></p>
+ <p class="blox18 unitTest" title="negated :only-of-type selector"></p>
+ </div>
+
+ <div class="test nthchild1">
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-child(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-child(odd) selector"></div>
+ </div>
+ <div class="test nthchild2">
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ <div class="unitTest" title=":nth-last-child(even) selector"></div>
+ <div class="unitTest" title=":nth-child(even) selector"></div>
+ </div>
+ <div class="test nthchild3">
+ <div class="unitTest no" title=":nth-last-child(3n+3) selector"></div>
+ <div class="unitTest" title=":nth-child(3n+2) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+1) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+3) selector"></div>
+ <div class="unitTest" title=":nth-child(3n+2) selector"></div>
+ <div class="unitTest no" title=":nth-last-child(3n+1) selector"></div>
+ </div>
+
+ <div class="test nthoftype1">
+ <div class="unitTest" title=":nth-of-type(odd) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(odd) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-of-type(odd) selector"></div>
+ <div class="unitTest" title=":nth-last-of-type(odd) selector"></div>
+ </div>
+ <div class="test nthoftype2">
+ <div class="unitTest" title=":nth-last-of-type(even) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-of-type(even) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(even) selector"></div>
+ <div class="unitTest" title=":nth-of-type(even) selector"></div>
+ </div>
+ <div class="test nthoftype3">
+ <div class="unitTest" title=":nth-of-type(3n+1) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+2) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+1) selector"></div>
+ <div class="unitTest" title=":nth-of-type(3n+1) selector"></div>
+ <p class="unitTest" title=":nth-of-* selector"></p>
+ <div class="unitTest" title=":nth-last-of-type(3n+2) selector"></div>
+ <div class="unitTest" title=":nth-last-of-type(3n+1) selector"></div>
+ </div>
+
+ <div class="test lastChild">
+ <p class="unitTest" title=":not(:last-child) selector"></p>
+ <div class="unitTest" title=":last-child selector"></div>&nbsp;
+ </div>
+
+ <div class="test firstOfType">
+ <p class="unitTest" title=":first-of-type selector"></p>
+ <div class="unitTest" title=":first-of-type selector"></div>
+ <p class="unitTest" title=":not(:first-of-type)"></p>
+ <div class="unitTest" title=":not(:first-of-type)"></div>
+ </div>
+
+ <div class="test lastOfType">
+ <p class="unitTest" title=":not(:last-of-type)"></p>
+ <div class="unitTest" title=":not(:last-of-type)"></div>
+ <p class="unitTest" title=":last-of-type selector"></p>
+ <div class="unitTest" title=":last-of-type selector"></div>
+ </div>
+
+ <div class="test onlyChild">
+ <div class="unitTest" title=":only-child where the element is NOT the only child"></div>
+ <div class="unitTest" title=":only-child where the element is the only child">
+ <div class="unitTest" title=":only-child where the element is the only child"></div>
+ </div>
+ </div>
+
+ <div class="test onlyOfType">
+ <p class="unitTest" title=":only-of-type"></p>
+ <div class="unitTest" title=":only-of-type">
+ <div class="unitTest" title=":only-of-type"></div>
+ </div>
+ <div class="unitTest" title=":not(only-of-type)"></div>
+ </div>
+
+ <div class="test empty">
+ <div class="unitTest isEmpty" title=":empty with empty element"></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains a whitespace"> </div>
+ <div class="unitTest isEmpty" title=":empty and element contains an SGML comment"><!-- foo --></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains a SPAN element"><span></span></div>
+ <div class="unitTest isNotEmpty" title=":empty but element contains an entity reference">&nbsp;</div>
+ </div>
+
+ <div class="test lang">
+ <div id="nofragment" class="unitTest" title=":lang() where language comes from the document"></div>
+ <div class="unitTest" lang="fr" title=":lang() where language comes from the element"></div>
+ <div class="unitTest" lang="en-US" title=":lang() where language comes from the element but is a dialect of the language queried"></div>
+ <div class="unitTest t1" lang="es" title=":lang() where language comes from the element but the language queried is a dialect of the element's one so it should not match"></div>
+ </div>
+
+ <div class="test attrLang">
+ <div class="unitTest t1" title="[|=] where language comes from the document"></div>
+ <div class="unitTest" lang="fr" title="[|=] where language comes from the element"></div>
+ <div class="unitTest t2" lang="en-US" title="[|=] where language comes from the element but is a dialect of the language queried"></div>
+ <div class="unitTest t3" lang="es" title="[|=] where language comes from the element but the language queried is a dialect of the element's one so it should not match"></div>
+ </div>
+
+ <div class="test UI">
+ <button name="submit" type="submit" value="submit" class="t1" title=":enabled pseudo-class"><div class="unitTest"></div></button>
+ <button name="submit" type="submit" value="submit" class="t2" disabled="true" title=":enabled pseudo-class"><div class="unitTest"></div></button>
+ </div>
+ <div class="test UI">
+ <input class="t3" type="checkbox" checked="true"/><div class="unitTest" title=":checked"></div>
+ the previous square should be green when the checkbox is checked and become red when you uncheck it
+ </div>
+ <div class="test UI">
+ <input class="t4" type="checkbox"/><div class="unitTest" title=":not(:checked)"></div>
+ the previous square should be green when the checkbox is NOT checked and become red when you check it
+ </div>
+
+ <div class="test tilda">
+ <div class="unitTest t1" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <div class="unitTest" title="~ combinator"></div>
+ <span style="float:left">the three last squares should be green and become red when the pointer hovers over the white square</span>
+ </div>
+ <div class="test plus">
+ <div class="unitTest t1" title="+ combinator"></div>
+ <div class="unitTest t2" title="+ combinator"></div>
+ <div class="unitTest" title="+ combinator"></div>
+ <span style="float:left">the last square should be green and become red when the pointer hovers over the FIRST white square</span>
+ </div>
+</div>
+<div id="root3">
+ <div id="svgs">
+ <!-- svg elements, but in the xhtml namespace -->
+ <svg width="12cm" height="4cm" viewBox="0 0 1200 400" version="1.1" id="svg1">
+ <desc id="desc1">Example circle01 - circle filled with red and stroked with blue</desc>
+ <rect id="rect1" x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2"/>
+ <circle id="circle1" cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10" />
+ </svg>
+ <!-- svg elements using svg: -->
+ <svg:svg width="12cm" height="4cm" viewBox="0 0 1200 400" version="1.1" id="svg2">
+ <svg:desc id="desc2">Example circle01 - circle filled with red and stroked with blue</svg:desc>
+ <svg:rect id="rect2" x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2"/>
+ <svg:circle id="circle2" cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10" />
+ </svg:svg>
+ <!-- svg using an inline xmlns -->
+ <svg width="12cm" height="4cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg3">
+ <desc id="desc3">Example circle01 - circle filled with red and stroked with blue</desc>
+ <rect id="rect3" x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2"/>
+ <circle id="circle3" cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10" />
+ </svg>
+ </div>
+
+ <h1 id="header">jQuery Test Suite</h1>
+ <h2 id="banner"></h2>
+ <h2 id="userAgent"></h2>
+
+ <!-- Test HTML -->
+ <div id="nothiddendiv" style="height:1px;background:white;">
+
+ <div id="nothiddendivchild"></div>
+ </div>
+ <!-- Test for scoping -->
+ <nosuchtag id="outerbogustag">
+ <nosuchtag id="innerbogustag"></nosuchtag>
+ </nosuchtag>
+ <!-- this iframe is outside the #main so it won't reload constantly wasting time, but it means the tests must be "safe" and clean up after themselves -->
+ <iframe id="loadediframe" name="loadediframe" style="display:none;" src="data/iframe.html"></iframe>
+ <dl id="dl" style="display:none;">
+ <div id="main" style="display: none;">
+ <p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> for more information.</p>
+
+ <p id="ap">
+ Here are some links in a normal paragraph: <a id="google" href="http://www.google.com/" title="Google!">Google</a>,
+ <a id="groups" href="http://groups.google.com/">Google Groups</a>.
+ This link has <code><a href="http://smin" id="anchor1">class="blog"</a></code>:
+ <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a>
+
+ </p>
+ <div id="foo">
+
+ <p id="sndp">Everything inside the red border is inside a div with <code>id="foo"</code>.</p>
+ <p lang="en" id="en">This is a normal link: <a id="yahoo" href="http://www.yahoo.com/" class="blogTest">Yahoo</a></p>
+ <p id="sap">This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: <a href="http://simon.incutio.com/" class="blog link" id="simon">Simon Willison's Weblog</a></p>
+
+ </div>
+
+ <p id="first">Try them out:</p>
+ <ul id="firstUL"></ul>
+ <ol id="empty"></ol>
+ <form id="form" action="formaction">
+ <input type="text" name="action" value="Test" id="text1" maxlength="30"/>
+ <input type="text" name="text2" value="Test" id="text2" disabled="disabled"/>
+ <input type="radio" name="radio1" id="radio1" value="on"/>
+
+ <input type="radio" name="radio2" id="radio2" checked="checked"/>
+
+ <input type="checkbox" name="check" id="check1" checked="checked"/>
+ <input type="checkbox" id="check2" value="on"/>
+
+ <input type="hidden" name="hidden" id="hidden1"/>
+ <input type="text" style="display:none;" name="foo[bar]" id="hidden2"/>
+
+ <input type="text" id="name" name="name" value="name" />
+
+ <button id="button" name="button">Button</button>
+
+ <textarea id="area1" maxlength="30">foobar</textarea>
+
+
+ <select name="select1" id="select1">
+ <option id="option1a" class="emptyopt" value="">Nothing</option>
+ <option id="option1b" value="1">1</option>
+ <option id="option1c" value="2">2</option>
+ <option id="option1d" value="3">3</option>
+ </select>
+ <select name="select2" id="select2">
+
+ <option id="option2a" class="emptyopt" value="">Nothing</option>
+ <option id="option2b" value="1">1</option>
+ <option id="option2c" value="2">2</option>
+ <option id="option2d" selected="selected" value="3">3</option>
+ </select>
+ <select name="select3" id="select3" multiple="multiple">
+ <option id="option3a" class="emptyopt" value="">Nothing</option>
+
+ <option id="option3b" selected="selected" value="1">1</option>
+ <option id="option3c" selected="selected" value="2">2</option>
+ <option id="option3d" value="3">3</option>
+ </select>
+
+ <object id="object1" codebase="stupid">
+ <param name="p1" value="x1" />
+ <param name="p2" value="x2" />
+
+ </object>
+
+ <span id="å°åŒ—TaÌibeÌŒi"></span>
+ <span id="å°åŒ—" lang="中文"></span>
+ <span id="utf8class1" class="å°åŒ—TaÌibeÌŒi å°åŒ—"></span>
+ <span id="utf8class2" class="å°åŒ—"></span>
+ <span id="foo:bar" class="foo:bar"></span>
+ <span id="test.foo[5]bar" class="test.foo[5]bar"></span>
+
+ <foo_bar id="foobar">test element</foo_bar>
+
+ </form>
+ <b id="floatTest">Float test.</b>
+ <iframe id="iframe" name="iframe"></iframe>
+ <form id="lengthtest">
+ <input type="text" id="length" name="test"/>
+ <input type="text" id="idTest" name="id"/>
+ </form>
+ <table id="table"></table>
+
+
+ <div id="fx-queue">
+ <div id="fadein" class='chain test'>fadeIn<div>fadeIn</div></div>
+ <div id="fadeout" class='chain test out'>fadeOut<div>fadeOut</div></div>
+
+ <div id="show" class='chain test'>show<div>show</div></div>
+ <div id="hide" class='chain test out'>hide<div>hide</div></div>
+
+
+ <div id="togglein" class='chain test'>togglein<div>togglein</div></div>
+ <div id="toggleout" class='chain test out'>toggleout<div>toggleout</div></div>
+
+
+ <div id="slideup" class='chain test'>slideUp<div>slideUp</div></div>
+ <div id="slidedown" class='chain test out'>slideDown<div>slideDown</div></div>
+
+ <div id="slidetogglein" class='chain test'>slideToggleIn<div>slideToggleIn</div></div>
+
+ <div id="slidetoggleout" class='chain test out'>slideToggleOut<div>slideToggleOut</div></div>
+ </div>
+
+ <div id="fx-tests"></div>
+
+ <form id="testForm" action="#" method="get">
+ <textarea name="T3" rows="2" cols="15">?
+Z</textarea>
+ <input type="hidden" name="H1" value="x" />
+ <input type="hidden" name="H2" />
+
+ <input name="PWD" type="password" value="" />
+ <input name="T1" type="text" />
+ <input name="T2" type="text" value="YES" readonly="readonly" />
+ <input type="checkbox" name="C1" value="1" />
+ <input type="checkbox" name="C2" />
+ <input type="radio" name="R1" value="1" />
+ <input type="radio" name="R1" value="2" />
+ <input type="text" name="My Name" value="me" />
+ <input type="reset" name="reset" value="NO" />
+
+ <select name="S1">
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ </select>
+ <select name="S2" multiple="multiple" size="3">
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ </select>
+ <select name="S3">
+ <option selected="selected">YES</option>
+ </select>
+ <select name="S4">
+
+ <option value="" selected="selected">NO</option>
+ </select>
+ <input type="submit" name="sub1" value="NO" />
+ <input type="submit" name="sub2" value="NO" />
+ <input type="image" name="sub3" value="NO" />
+ <button name="sub4" type="submit" value="NO">NO</button>
+ <input name="D1" type="text" value="NO" disabled="disabled" />
+ <input type="checkbox" checked="checked" disabled="disabled" name="D2" value="NO" />
+
+ <input type="radio" name="D3" value="NO" checked="checked" disabled="disabled" />
+ <select name="D4" disabled="disabled">
+ <option selected="selected" value="NO">NO</option>
+ </select>
+ </form>
+ <div id="moretests">
+ <form>
+ <div id="checkedtest" style="display:none;">
+
+ <input type="radio" name="checkedtestradios" checked="checked"/>
+ <input type="radio" name="checkedtestradios" value="on"/>
+ <input type="checkbox" name="checkedtestcheckboxes" checked="checked"/>
+ <input type="checkbox" name="checkedtestcheckboxes" />
+ </div>
+ </form>
+ <div id="nonnodes"><span>hi</span> there <!-- mon ami --></div>
+
+ <div id="t2037">
+ <div><div class="hidden">hidden</div></div>
+ </div>
+ </div>
+ </div>
+ </dl>
+
+ <ol id="tests"></ol>
+</div>
+<ol id="results"></ol>
+</body>
+</html>
diff --git a/dom/base/test/file_bug426646-1.html b/dom/base/test/file_bug426646-1.html
new file mode 100644
index 0000000000..4319311b08
--- /dev/null
+++ b/dom/base/test/file_bug426646-1.html
@@ -0,0 +1,37 @@
+<html><head>
+<title>Bug 426646, Using location.replace breaks iframe history</title>
+<script type="text/javascript">
+var BASE_URI = "http://mochi.test:8888/tests/dom/base/test/";
+var url1 = BASE_URI + "iframe1_bug426646.html";
+
+function soon(f) {
+ return function() { setTimeout(f, 0); };
+}
+
+function doe() {
+ document.body.innerHTML = "<iframe src='about:blank'></iframe>";
+ document.body.innerHTML += "<iframe src='about:blank'></iframe>";
+ window.frames[0].frameElement.onload = soon(doe2);
+ window.frames[0].location.replace(url1);
+}
+
+function doe2() {
+ window.frames[0].location = 'iframe2_bug426646.html';
+ window.frames[0].frameElement.onload = soon(doe3);
+}
+
+function doe3() {
+ window.frames[0].frameElement.onload = soon(doe4);
+ history.go(-1);
+}
+
+function doe4() {
+ opener.is(String(window.frames[0].location), url1, "History.go(-1) didn't work?");
+ opener.is(String(window.frames[1].location), "about:blank",
+ "History.go(-1) didn't work?");
+ close();
+}
+</script>
+</head>
+<body onload="doe();" onunload="opener.nextTest();">
+</body></html>
diff --git a/dom/base/test/file_bug426646-2.html b/dom/base/test/file_bug426646-2.html
new file mode 100644
index 0000000000..2da090b32f
--- /dev/null
+++ b/dom/base/test/file_bug426646-2.html
@@ -0,0 +1,65 @@
+<html><head>
+<title>Bug 426646, Using location.replace breaks iframe history</title>
+<script type="text/javascript">
+var BASE_URI = "http://mochi.test:8888/tests/dom/base/test/";
+var url1 = BASE_URI + "iframe1_bug426646.html";
+
+var win0 = null;
+
+function soon(f) {
+ return function() { setTimeout(f, 0); };
+}
+
+function doe() {
+ document.body.innerHTML = "<iframe src='about:blank'></iframe>";
+ document.body.innerHTML += "<iframe src='about:blank'></iframe>";
+ win0 = window.frames[0];
+ win0.frameElement.onload = soon(doe2);
+ win0.location.replace(url1);
+}
+
+function doe2() {
+ // Add some iframes/docshells. Session history should still work.
+ var ifr1 = document.createElement("iframe");
+ document.body.insertBefore(ifr1, document.body.firstChild);
+ ifr1.onload = soon(doe3);
+
+ var ifr2 = document.createElement("iframe");
+ document.body.insertBefore(ifr2, document.body.firstChild);
+ ifr2.onload = soon(doe3);
+
+ var ifr3 = document.createElement("iframe");
+ document.body.insertBefore(ifr3, document.body.firstChild);
+ ifr3.onload = soon(doe3);
+}
+
+var doe3_count = 0;
+function doe3() {
+ // Wait until all three iframes have loaded about:blank before navigating
+ // win0.
+ doe3_count++;
+ if (doe3_count < 3) {
+ return;
+ }
+ if (doe3_count > 3) {
+ ok(false, 'Unexpected ' + doe3_count + 'th call to doe3.');
+ return;
+ }
+
+ win0.frameElement.onload = soon(doe4);
+ win0.location = BASE_URI + 'iframe2_bug426646.html';
+}
+
+function doe4() {
+ win0.frameElement.onload = soon(doe5);
+ history.go(-1);
+}
+
+function doe5() {
+ opener.is(String(win0.location), url1, "History.go(-1) didn't work?");
+ close();
+}
+</script>
+</head>
+<body onload="setTimeout(doe, 0);" onunload="opener.nextTest();">
+</body></html>
diff --git a/dom/base/test/file_bug428847-1.xhtml b/dom/base/test/file_bug428847-1.xhtml
new file mode 100644
index 0000000000..b88701ece2
--- /dev/null
+++ b/dom/base/test/file_bug428847-1.xhtml
@@ -0,0 +1,4 @@
+<?xml-stylesheet type="text/xsl" href="http://example.com/whatever.xsl"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body id='body' onload="if (document.getElementById('body')) parent.iframe1Loaded = true;"/>
+</html>
diff --git a/dom/base/test/file_bug428847-2.xhtml b/dom/base/test/file_bug428847-2.xhtml
new file mode 100644
index 0000000000..75c60b7792
--- /dev/null
+++ b/dom/base/test/file_bug428847-2.xhtml
@@ -0,0 +1,4 @@
+<?xml-stylesheet type="text/xsl" href=":"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body id='body' onload="if (document.getElementById('body')) parent.iframeLoaded = true;"/>
+</html>
diff --git a/dom/base/test/file_bug498897.css b/dom/base/test/file_bug498897.css
new file mode 100644
index 0000000000..84beffdefc
--- /dev/null
+++ b/dom/base/test/file_bug498897.css
@@ -0,0 +1 @@
+body { background: orange; }
diff --git a/dom/base/test/file_bug498897.html b/dom/base/test/file_bug498897.html
new file mode 100644
index 0000000000..b0d36f4ca5
--- /dev/null
+++ b/dom/base/test/file_bug498897.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <title>Testcase for bug 498897</title>
+<script type="application/javascript" language="javascript">
+<!--
+function test()
+{
+ var hadException = false;
+ try {
+ document.createComment('a');
+ }
+ catch (e) {
+ hadException = true;
+ }
+ parent.ok(!hadException, "Shouldn't have got an exception!");
+ parent.testFinished();
+}
+//-->
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/dom/base/test/file_bug498897.html^headers^ b/dom/base/test/file_bug498897.html^headers^
new file mode 100644
index 0000000000..09b46ca4ee
--- /dev/null
+++ b/dom/base/test/file_bug498897.html^headers^
@@ -0,0 +1 @@
+Link: <file_bug498897.css>; rel=stylesheet
diff --git a/dom/base/test/file_bug503473-frame.sjs b/dom/base/test/file_bug503473-frame.sjs
new file mode 100644
index 0000000000..f642241091
--- /dev/null
+++ b/dom/base/test/file_bug503473-frame.sjs
@@ -0,0 +1,22 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.write(
+ "<!DOCTYPE html>" +
+ "<div></div>" +
+ "<script>" +
+ "function doWrite() {" +
+ ' document.write("<p></p>");' +
+ " parent.done();" +
+ " document.close();" +
+ "}" +
+ "setTimeout(doWrite, 0);" +
+ "</script>"
+ );
+
+ response.bodyOutputStream.flush();
+ // leave the stream open
+}
diff --git a/dom/base/test/file_bug503481.sjs b/dom/base/test/file_bug503481.sjs
new file mode 100644
index 0000000000..9aaeb31744
--- /dev/null
+++ b/dom/base/test/file_bug503481.sjs
@@ -0,0 +1,54 @@
+// 'timer' is global to avoid getting GCed which would cancel the timer
+var timer;
+const nsITimer = Components.interfaces.nsITimer;
+
+function attemptUnblock(s) {
+ try {
+ let blockedResponse = null;
+ getObjectState("bug503481_" + s, function (x) {
+ blockedResponse = x.wrappedJSObject.r;
+ });
+ blockedResponse.finish();
+ setObjectState("bug503481_" + s, null);
+ } catch (e) {
+ dump("unable to unblock " + s + "retrying in half a second\n");
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
+ timer.initWithCallback(
+ function () {
+ attemptUnblock(s);
+ },
+ 500,
+ nsITimer.TYPE_ONE_SHOT
+ );
+ }
+}
+
+function handleRequest(request, response) {
+ var query = {};
+ request.queryString.split("&").forEach(function (val) {
+ var [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ dump("processing:" + request.queryString + "\n");
+
+ if (query.unblock) {
+ attemptUnblock(query.unblock);
+ }
+
+ if (query.blockOn) {
+ response.processAsync();
+ x = {
+ r: response,
+ QueryInterface(iid) {
+ return this;
+ },
+ };
+ x.wrappedJSObject = x;
+ setObjectState("bug503481_" + query.blockOn, x);
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(query.body);
+}
diff --git a/dom/base/test/file_bug503481b_inner.html b/dom/base/test/file_bug503481b_inner.html
new file mode 100644
index 0000000000..6b34bc47b6
--- /dev/null
+++ b/dom/base/test/file_bug503481b_inner.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Async script that isn't preloaded -->
+<script async src="file_bug503481.sjs?blockOn=R&body=runFirst();"></script>
+<script>
+firstRan = false;
+secondRan = false;
+thirdRan = false;
+forthRan = false;
+function runFirst() {
+ firstRan = true;
+}
+function runThird() {
+ parent.is(forthRan, false, "forth should still be blocked");
+ unblock("T");
+ thirdRan = true;
+}
+function runForth() {
+ forthRan = true;
+}
+
+function done() {
+ parent.is(firstRan, true, "first should have run by onload");
+ parent.is(secondRan, true, "second should have run by onload");
+ parent.is(thirdRan, true, "third should have run by onload");
+ parent.is(forthRan, true, "forth should have run by onload");
+ parent.SimpleTest.finish();
+}
+
+var reqs = [];
+function unblock(s) {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_bug503481.sjs?unblock=" + s);
+ xhr.send();
+ reqs.push(xhr);
+}
+
+
+parent.is(firstRan, false, "First async script shouldn't have run");
+unblock("R");
+</script>
+
+<!-- test that inline async isn't actually async -->
+<script async>
+secondRan = true;
+</script>
+<script>
+parent.is(secondRan, true, "Second script shouldn't be async");
+</script>
+
+<!-- test that setting both defer and async treats the script as async -->
+<script defer async src="file_bug503481.sjs?blockOn=S&body=runThird();"></script>
+<script>
+parent.is(thirdRan, false, "third should not have run yet");
+unblock("S");
+</script>
+<script src="file_bug503481.sjs?blockOn=T&body=runForth();"></script>
+
+</head>
+
+<body onload="done()">
diff --git a/dom/base/test/file_bug518104.js b/dom/base/test/file_bug518104.js
new file mode 100644
index 0000000000..72a684522f
--- /dev/null
+++ b/dom/base/test/file_bug518104.js
@@ -0,0 +1,3 @@
+document.write("<p></p>");
+parent.done();
+document.close();
diff --git a/dom/base/test/file_bug541937.html b/dom/base/test/file_bug541937.html
new file mode 100644
index 0000000000..93056754f9
--- /dev/null
+++ b/dom/base/test/file_bug541937.html
@@ -0,0 +1,7 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test</title>
+ <link rel="Top" href=""> foo </link>
+
+</head><body>
+ <p>Hello world</p>
+</body></html> \ No newline at end of file
diff --git a/dom/base/test/file_bug541937.xhtml b/dom/base/test/file_bug541937.xhtml
new file mode 100644
index 0000000000..f4e5f3d320
--- /dev/null
+++ b/dom/base/test/file_bug541937.xhtml
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test</title>
+ <link rel="Top" href=""> foo </link>
+
+</head>
+<body>
+ <p>Hello world</p>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_bug557892.html b/dom/base/test/file_bug557892.html
new file mode 100644
index 0000000000..4b91ddafdd
--- /dev/null
+++ b/dom/base/test/file_bug557892.html
@@ -0,0 +1,25 @@
+<html><head>
+<title>Crash [@ nsGenericElement::SetAttr] with classList.toggle</title>
+<script>
+var classList;
+var interval;
+function run() {
+ classList = window.frames[0].document.documentElement.classList;
+ window.frames[0].location.reload();
+ interval = setInterval(function(aClassList) {aClassList.toggle('a'); forcegc();}, 10, classList);
+ // Let the interval run for awhile and close the window after 2 seconds.
+ setTimeout(function() { clearInterval(interval); window.opener.done(); window.close(); }, 2000);
+}
+
+function forcegc(){
+ SpecialPowers.forceGC();
+ SpecialPowers.gc();
+}
+
+ </script>
+ </head>
+ <body onload="run()">
+ <iframe></iframe>
+ </body>
+</html>
+
diff --git a/dom/base/test/file_bug562137.txt b/dom/base/test/file_bug562137.txt
new file mode 100644
index 0000000000..4a21b817e5
--- /dev/null
+++ b/dom/base/test/file_bug562137.txt
@@ -0,0 +1 @@
+I have nbsp
diff --git a/dom/base/test/file_bug590812-ref.xhtml b/dom/base/test/file_bug590812-ref.xhtml
new file mode 100644
index 0000000000..7309425fb4
--- /dev/null
+++ b/dom/base/test/file_bug590812-ref.xhtml
@@ -0,0 +1,3 @@
+<out><div id="top" xmlns="http://www.w3.org/1999/xhtml"><link href="chrome://global/content/xml/XMLPrettyPrint.css" type="text/css" rel="stylesheet"/><div id="header"><p>
+ This XML file does not appear to have any style information associated with it. The document tree is shown below.
+ </p></div><main id="tree" class="highlight"><div>&lt;<span class="start-tag">out</span>&gt;<span class="text">Here be sea hags</span>&lt;/<span class="end-tag">out</span>&gt;</div></main></div></out>
diff --git a/dom/base/test/file_bug590812.xml b/dom/base/test/file_bug590812.xml
new file mode 100644
index 0000000000..759d5066cf
--- /dev/null
+++ b/dom/base/test/file_bug590812.xml
@@ -0,0 +1 @@
+<out>Here be sea hags</out>
diff --git a/dom/base/test/file_bug590870.html b/dom/base/test/file_bug590870.html
new file mode 100644
index 0000000000..4432b01d3c
--- /dev/null
+++ b/dom/base/test/file_bug590870.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script>
+onload = function() {
+ var x = null;
+ try {
+ x = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "spacer");
+ } catch(ex) {}
+ window.parent.postMessage(x == null, "http://mochi.test:8888");
+}
+ </script>
+ </head>
+ <body>
+ Here be dragons!
+ </body>
+</html>
diff --git a/dom/base/test/file_bug601803a.html b/dom/base/test/file_bug601803a.html
new file mode 100644
index 0000000000..c39ff7219e
--- /dev/null
+++ b/dom/base/test/file_bug601803a.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 601803</title>
+<script type="application/javascript">
+function runTest() {
+ document.domain = "example.org";
+ var thrown = false;
+ try {
+ document.getElementById("frame").contentDocument.adoptNode(document.createTextNode("foo"));
+ }
+ catch (e) {
+ thrown = true;
+ }
+ parent.postMessage(thrown, "*");
+}
+</script>
+</head>
+<body>
+<iframe id="frame" src="http://test1.example.org/tests/dom/base/test/file_bug601803b.html" onload="runTest();"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_bug601803b.html b/dom/base/test/file_bug601803b.html
new file mode 100644
index 0000000000..0363654c06
--- /dev/null
+++ b/dom/base/test/file_bug601803b.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 601803</title>
+</head>
+<body>
+<script type="application/javascript">
+document.domain = "example.org";
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_bug604660-1.xml b/dom/base/test/file_bug604660-1.xml
new file mode 100644
index 0000000000..231b4357d6
--- /dev/null
+++ b/dom/base/test/file_bug604660-1.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="file_bug604660-2.xsl" ?>
+<placeholder/>
diff --git a/dom/base/test/file_bug604660-2.xsl b/dom/base/test/file_bug604660-2.xsl
new file mode 100644
index 0000000000..16611726ce
--- /dev/null
+++ b/dom/base/test/file_bug604660-2.xsl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:template match="/">
+ <html>
+ <head>
+ <title>XSLT script execution test</title>
+ </head>
+ <body>
+ <script defer="" src="data:text/javascript,parent.scriptRan(5);"></script>
+ <script>parent.scriptRan(1);</script>
+ <script async="" src="data:text/javascript,parent.asyncRan();"></script>
+ <script src="file_bug604660-3.js"></script>
+ <script>parent.scriptRan(3);</script>
+ <script src="file_bug604660-4.js"></script>
+ </body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
+
diff --git a/dom/base/test/file_bug604660-3.js b/dom/base/test/file_bug604660-3.js
new file mode 100644
index 0000000000..c11ae68f28
--- /dev/null
+++ b/dom/base/test/file_bug604660-3.js
@@ -0,0 +1 @@
+parent.scriptRan(2);
diff --git a/dom/base/test/file_bug604660-4.js b/dom/base/test/file_bug604660-4.js
new file mode 100644
index 0000000000..2bd5b43e5b
--- /dev/null
+++ b/dom/base/test/file_bug604660-4.js
@@ -0,0 +1 @@
+parent.scriptRan(4);
diff --git a/dom/base/test/file_bug604660-5.xml b/dom/base/test/file_bug604660-5.xml
new file mode 100644
index 0000000000..58c6382836
--- /dev/null
+++ b/dom/base/test/file_bug604660-5.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0"?>
+<placeholder/>
diff --git a/dom/base/test/file_bug604660-6.xsl b/dom/base/test/file_bug604660-6.xsl
new file mode 100644
index 0000000000..4a75777d90
--- /dev/null
+++ b/dom/base/test/file_bug604660-6.xsl
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:template match="/">
+ <html>
+ <script>xsltProcessorCreatedScriptRan();</script>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
+
diff --git a/dom/base/test/file_bug622088.sjs b/dom/base/test/file_bug622088.sjs
new file mode 100644
index 0000000000..87acdc3b8b
--- /dev/null
+++ b/dom/base/test/file_bug622088.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ // Echos the referrer back to the requester.
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(request.getHeader("Referer"));
+}
diff --git a/dom/base/test/file_bug622088_inner.html b/dom/base/test/file_bug622088_inner.html
new file mode 100644
index 0000000000..e89273d89b
--- /dev/null
+++ b/dom/base/test/file_bug622088_inner.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+function load() {
+ (window.opener || window.parent).innerLoaded(window);
+}
+
+function doXHR(req) {
+ // Do a sync XHR and return the XHR's referrer.
+ if (!req) {
+ req = new XMLHttpRequest();
+ }
+
+ // file_bug622088.sjs echos its referrer back to us. We need to refer to it
+ // using an absolute URI because we sometimes pass in |req| from a window
+ // which has a data: URI. In that case, a relative path would not get
+ // resolved properly!
+ //
+ // Resolve our relative URI to an absolute one by creating an anchor element
+ // and reading its href property.
+ var anchor = document.createElement('a');
+ anchor.href = 'file_bug622088.sjs';
+
+ dump('anchor.href=' + anchor.href + '\n');
+
+ req.open('GET', anchor.href, false);
+ req.send(null);
+ return req.responseText;
+}
+</script>
+</head>
+
+<body onload='load()'>
+<!--Inner frame target for test_bug622088_2.html. -->
+</body>
+
+</html>
diff --git a/dom/base/test/file_bug675121.sjs b/dom/base/test/file_bug675121.sjs
new file mode 100644
index 0000000000..29fef155df
--- /dev/null
+++ b/dom/base/test/file_bug675121.sjs
@@ -0,0 +1,19 @@
+var timer;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("Responded");
+ response.processAsync();
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+ Components.interfaces.nsITimer
+ );
+ timer.initWithCallback(
+ function () {
+ response.finish();
+ // 50ms certainly be enough for one refresh driver firing to happen!
+ },
+ 50,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/dom/base/test/file_bug687859-16.js b/dom/base/test/file_bug687859-16.js
new file mode 100644
index 0000000000..58f82cdda3
--- /dev/null
+++ b/dom/base/test/file_bug687859-16.js
Binary files differ
diff --git a/dom/base/test/file_bug687859-16.js^headers^ b/dom/base/test/file_bug687859-16.js^headers^
new file mode 100644
index 0000000000..e0d4fce5d0
--- /dev/null
+++ b/dom/base/test/file_bug687859-16.js^headers^
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=tis-620
diff --git a/dom/base/test/file_bug687859-bom.js b/dom/base/test/file_bug687859-bom.js
new file mode 100644
index 0000000000..7536004bb6
--- /dev/null
+++ b/dom/base/test/file_bug687859-bom.js
@@ -0,0 +1 @@
+var stringFromBomScript = "€";
diff --git a/dom/base/test/file_bug687859-bom.js^headers^ b/dom/base/test/file_bug687859-bom.js^headers^
new file mode 100644
index 0000000000..e0d4fce5d0
--- /dev/null
+++ b/dom/base/test/file_bug687859-bom.js^headers^
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=tis-620
diff --git a/dom/base/test/file_bug687859-charset.js b/dom/base/test/file_bug687859-charset.js
new file mode 100644
index 0000000000..e812a1e37f
--- /dev/null
+++ b/dom/base/test/file_bug687859-charset.js
@@ -0,0 +1 @@
+var stringFromCharsetScript = "¡"; \ No newline at end of file
diff --git a/dom/base/test/file_bug687859-http.js b/dom/base/test/file_bug687859-http.js
new file mode 100644
index 0000000000..1b1456d480
--- /dev/null
+++ b/dom/base/test/file_bug687859-http.js
@@ -0,0 +1 @@
+var stringFromHttpScript = "ä";
diff --git a/dom/base/test/file_bug687859-http.js^headers^ b/dom/base/test/file_bug687859-http.js^headers^
new file mode 100644
index 0000000000..e0d4fce5d0
--- /dev/null
+++ b/dom/base/test/file_bug687859-http.js^headers^
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=tis-620
diff --git a/dom/base/test/file_bug687859-inherit.js b/dom/base/test/file_bug687859-inherit.js
new file mode 100644
index 0000000000..b83f60b2fb
--- /dev/null
+++ b/dom/base/test/file_bug687859-inherit.js
@@ -0,0 +1 @@
+var stringFromInheritScript = "¡"; \ No newline at end of file
diff --git a/dom/base/test/file_bug692434.xml b/dom/base/test/file_bug692434.xml
new file mode 100644
index 0000000000..2d559c3aed
--- /dev/null
+++ b/dom/base/test/file_bug692434.xml
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="windows-1251"?><root>Þ</root>
diff --git a/dom/base/test/file_bug704320_preload_attr.html b/dom/base/test/file_bug704320_preload_attr.html
new file mode 100644
index 0000000000..e10f1727df
--- /dev/null
+++ b/dom/base/test/file_bug704320_preload_attr.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test whether the speculative parser should use the referrerpolicy attribute
+https://bugzilla.mozilla.org/show_bug.cgi?id=1399780
+-->
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="file_bug704320_preload_common.js"></script>
+ <script language="javascript" type="text/javascript">
+ // interfere doc.write(meta referrer) to the down side preloads
+ document.write("<meta name='referrer' content='unsafe-url'>");
+ </script>
+
+ <link rel="stylesheet"
+ href="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=css"
+ onload="incrementLoad2('link', 3);"
+ referrerpolicy="origin"/>
+
+ <img src="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=img"
+ onload="incrementLoad2('img', 3);"
+ referrerpolicy="origin"/>
+
+ <script src="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=js"
+ onload="incrementLoad2('script', 3);"
+ referrerpolicy="origin"></script>
+
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/file_bug704320_preload_common.js b/dom/base/test/file_bug704320_preload_common.js
new file mode 100644
index 0000000000..c7a409e297
--- /dev/null
+++ b/dom/base/test/file_bug704320_preload_common.js
@@ -0,0 +1,32 @@
+// Common code for the iframes used by bug704320_preload.
+
+var loadCount = 0;
+
+// Called by the various onload handlers to indicate that a resource has
+// been fully loaded. We require three loads to complete (img, script,
+// link) for this test.
+function incrementLoad(tag) {
+ loadCount++;
+ if (loadCount == 3) {
+ window.parent.postMessage("childLoadComplete", window.location.origin);
+ } else if (loadCount > 3) {
+ document.write("<h1>Too Many Load Events!</h1>");
+ window.parent.postMessage("childOverload", window.location.origin);
+ }
+}
+
+// This is same as incrementLoad, but the caller passes in the loadCount.
+function incrementLoad2(tag, expectedLoadCount) {
+ loadCount++;
+ if (loadCount == expectedLoadCount) {
+ window.parent.postMessage("childLoadComplete", window.location.origin);
+ } else if (loadCount > expectedLoadCount) {
+ document.write("<h1>Too Many Load Events!</h1>");
+ window.parent.postMessage("childOverload", window.location.origin);
+ }
+}
+
+// in case something fails to load, cause the test to fail.
+function postfail(msg) {
+ window.parent.postMessage("fail-" + msg, window.location.origin);
+}
diff --git a/dom/base/test/file_bug704320_preload_reuse.html b/dom/base/test/file_bug704320_preload_reuse.html
new file mode 100644
index 0000000000..5643e3d8d6
--- /dev/null
+++ b/dom/base/test/file_bug704320_preload_reuse.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This is a spot check for whether the speculative parser reuses style, script or image loads after the referrer policy has changed.
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+-->
+<head>
+ <meta charset="utf-8">
+ <meta name="referrer" content="origin">
+ <script type="text/javascript" src="file_bug704320_preload_common.js"></script>
+ <script language="javascript" type="text/javascript">
+ // mess with parser speculation -- the loads here MAY be reused because the
+ // referrer policy did not change.
+ document.write("<meta name='referrer' content='origin'>");
+ </script>
+
+ <script src="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=js"
+ onload="incrementLoad('script');"></script>
+
+ <link rel="stylesheet"
+ href="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=css"
+ onload="incrementLoad('link');"/>
+
+</head>
+<body>
+
+<img src="http://example.com/tests/dom/base/test/bug704320_counter.sjs?type=img"
+ onload="incrementLoad('img');"
+ onerror="postfail('image load caused an error in reuse test');"/>
+</body>
+</html>
diff --git a/dom/base/test/file_bug704320_redirect.html b/dom/base/test/file_bug704320_redirect.html
new file mode 100644
index 0000000000..09ed05d6fd
--- /dev/null
+++ b/dom/base/test/file_bug704320_redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ parent.postMessage('', 'http://mochi.test:8888');
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/file_bug707142_baseline.json b/dom/base/test/file_bug707142_baseline.json
new file mode 100644
index 0000000000..8c850a5fd2
--- /dev/null
+++ b/dom/base/test/file_bug707142_baseline.json
@@ -0,0 +1 @@
+{ "foo": "bar" }
diff --git a/dom/base/test/file_bug707142_bom.json b/dom/base/test/file_bug707142_bom.json
new file mode 100644
index 0000000000..e961357349
--- /dev/null
+++ b/dom/base/test/file_bug707142_bom.json
@@ -0,0 +1 @@
+{ "foo": "bar" }
diff --git a/dom/base/test/file_bug707142_utf-16.json b/dom/base/test/file_bug707142_utf-16.json
new file mode 100644
index 0000000000..eb28f6bb7e
--- /dev/null
+++ b/dom/base/test/file_bug707142_utf-16.json
Binary files differ
diff --git a/dom/base/test/file_bug708620-2.html b/dom/base/test/file_bug708620-2.html
new file mode 100644
index 0000000000..86899201df
--- /dev/null
+++ b/dom/base/test/file_bug708620-2.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Non-UTF form target</title>
+<body onload="parent.finish();">
diff --git a/dom/base/test/file_bug708620.html b/dom/base/test/file_bug708620.html
new file mode 100644
index 0000000000..a90e6aeeba
--- /dev/null
+++ b/dom/base/test/file_bug708620.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<meta charset=windows-1252>
+<title>Non-UTF form</title>
+<body onload="document.forms[0].submit();">
+<form action="file_bug708620-2.html">
+<input name=foo value=bar>
+</form>
diff --git a/dom/base/test/file_bug753278.html b/dom/base/test/file_bug753278.html
new file mode 100644
index 0000000000..922bc0c658
--- /dev/null
+++ b/dom/base/test/file_bug753278.html
@@ -0,0 +1 @@
+<meta charset="utf-8"><script>parent.pass();</script>
diff --git a/dom/base/test/file_bug769117.html b/dom/base/test/file_bug769117.html
new file mode 100644
index 0000000000..424f8dff1c
--- /dev/null
+++ b/dom/base/test/file_bug769117.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=769117
+ -->
+ <head>
+ </head>
+ <body>
+ <embed id="testembed"
+ src="https://mochitest.youtube.com/v/Xm5i5kbIXzc"
+ type="application/x-shockwave-flash"
+ allowscriptaccess="always"></embed>
+ <object id="testobject"
+ data="https://mochitest.youtube.com/v/Xm5i5kbIXzc"></embed>
+ </body>
+</html>
diff --git a/dom/base/test/file_bug782342.txt b/dom/base/test/file_bug782342.txt
new file mode 100644
index 0000000000..3b18e512db
--- /dev/null
+++ b/dom/base/test/file_bug782342.txt
@@ -0,0 +1 @@
+hello world
diff --git a/dom/base/test/file_bug787778.sjs b/dom/base/test/file_bug787778.sjs
new file mode 100644
index 0000000000..d22d67d9c9
--- /dev/null
+++ b/dom/base/test/file_bug787778.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("X-Frame-Options", "DENY", false);
+
+ response.finish();
+}
diff --git a/dom/base/test/file_bug869432.eventsource b/dom/base/test/file_bug869432.eventsource
new file mode 100644
index 0000000000..de92d4dd86
--- /dev/null
+++ b/dom/base/test/file_bug869432.eventsource
@@ -0,0 +1,4 @@
+retry:500
+data: data
+
+
diff --git a/dom/base/test/file_bug869432.eventsource^headers^ b/dom/base/test/file_bug869432.eventsource^headers^
new file mode 100644
index 0000000000..b8db77c582
--- /dev/null
+++ b/dom/base/test/file_bug869432.eventsource^headers^
@@ -0,0 +1,3 @@
+HTTP 304 NO CONTENT (CLOSE)
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate \ No newline at end of file
diff --git a/dom/base/test/file_bug902350.html b/dom/base/test/file_bug902350.html
new file mode 100644
index 0000000000..2101f80c7d
--- /dev/null
+++ b/dom/base/test/file_bug902350.html
@@ -0,0 +1,19 @@
+<DOCTYPE HTML>
+<html>
+<!--
+Test for Mixed Content Blocker target="_top" frame navigation
+https://bugzilla.mozilla.org/show_bug.cgi?id=902350
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 902350</title>
+</head>
+
+<body>
+ <div id="framediv">
+ <iframe src="https://example.com/browser/dom/base/test/file_bug902350_frame.html" id="testing_frame"></iframe>
+ </div>
+</body>
+</html>
+
+
diff --git a/dom/base/test/file_bug902350_frame.html b/dom/base/test/file_bug902350_frame.html
new file mode 100644
index 0000000000..183dabe255
--- /dev/null
+++ b/dom/base/test/file_bug902350_frame.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Opening link with _top target from an https iframe.
+https://bugzilla.mozilla.org/show_bug.cgi?id=902350
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<a href="http://example.com/" id="topTarget" target="_top">Go to http site</a>
+</body>
+</html>
diff --git a/dom/base/test/file_bug907892.html b/dom/base/test/file_bug907892.html
new file mode 100644
index 0000000000..9f5948661c
--- /dev/null
+++ b/dom/base/test/file_bug907892.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ var threw;
+ try {
+ document.domain = "example.org";
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ var sandboxed = (location.search == "?sandboxed");
+ parent.postMessage({ threw, sandboxed }, "*");
+</script>
diff --git a/dom/base/test/file_bug945152.jar b/dom/base/test/file_bug945152.jar
new file mode 100644
index 0000000000..eb732980d9
--- /dev/null
+++ b/dom/base/test/file_bug945152.jar
Binary files differ
diff --git a/dom/base/test/file_bug945152_worker.js b/dom/base/test/file_bug945152_worker.js
new file mode 100644
index 0000000000..9664045b6d
--- /dev/null
+++ b/dom/base/test/file_bug945152_worker.js
@@ -0,0 +1,99 @@
+var gData1 = "TEST_DATA_1:ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+var gData2 = "TEST_DATA_2:1234567890";
+var gPaddingChar = ".";
+var gPaddingSize = 10000;
+var gPadding = "";
+
+for (var i = 0; i < gPaddingSize; i++) {
+ gPadding += gPaddingChar;
+}
+
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ postMessage({ type: "status", status: a === b, msg });
+}
+
+function checkData(response, data_head, cb) {
+ ok(response, "Data is non-null");
+ var str = String.fromCharCode.apply(null, new Uint8Array(response));
+ ok(str.length == data_head.length + gPaddingSize, "Data size is correct");
+ ok(str.slice(0, data_head.length) == data_head, "Data head is correct");
+ ok(str.slice(data_head.length) == gPadding, "Data padding is correct");
+ cb();
+}
+
+self.onmessage = function onmessage(event) {
+ var jar = event.data;
+
+ function makeJarURL(entry) {
+ return "jar:" + jar + "!/" + entry;
+ }
+
+ function test_mapped_sync() {
+ var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+ xhr.open("GET", makeJarURL("data_1.txt"), false);
+ xhr.responseType = "arraybuffer";
+ xhr.send();
+ if (xhr.status) {
+ ok(xhr.status == 200, "Status is 200");
+ var ct = xhr.getResponseHeader("Content-Type");
+ ok(ct.includes("mem-mapped"), "Data is memory-mapped");
+ checkData(xhr.response, gData1, runTests);
+ }
+ }
+
+ function test_mapped_async() {
+ var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+ xhr.open("GET", makeJarURL("data_1.txt"));
+ xhr.responseType = "arraybuffer";
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState !== xhr.DONE) {
+ return;
+ }
+ if (xhr.status) {
+ ok(xhr.status == 200, "Status is 200");
+ var ct = xhr.getResponseHeader("Content-Type");
+ ok(ct.includes("mem-mapped"), "Data is memory-mapped");
+ checkData(xhr.response, gData1, runTests);
+ }
+ };
+ xhr.send();
+ }
+
+ // Make sure array buffer retrieved from compressed file in package is
+ // handled by memory allocation instead of memory mapping.
+ function test_non_mapped() {
+ var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+ xhr.open("GET", makeJarURL("data_2.txt"));
+ xhr.responseType = "arraybuffer";
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState !== xhr.DONE) {
+ return;
+ }
+ if (xhr.status) {
+ ok(xhr.status == 200, "Status is 200");
+ var ct = xhr.getResponseHeader("Content-Type");
+ ok(!ct.includes("mem-mapped"), "Data is not memory-mapped");
+ checkData(xhr.response, gData2, runTests);
+ }
+ };
+ xhr.send();
+ }
+
+ var tests = [test_mapped_sync, test_mapped_async, test_non_mapped];
+
+ function runTests() {
+ if (!tests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ runTests();
+};
diff --git a/dom/base/test/file_change_policy_redirect.html b/dom/base/test/file_change_policy_redirect.html
new file mode 100644
index 0000000000..e48386d97c
--- /dev/null
+++ b/dom/base/test/file_change_policy_redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ parent.postMessage('childLoadComplete', 'http://mochi.test:8888');
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/file_current_inner_window.html b/dom/base/test/file_current_inner_window.html
new file mode 100644
index 0000000000..8156e29000
--- /dev/null
+++ b/dom/base/test/file_current_inner_window.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+ <script>
+ function isCurrentWinnerWindow() {
+ // If we are the current inner window for our BrowsingContext, bare word
+ // access to `name` will return "". If we are not, it will throw.
+ // Note that this is *not* the same as accessing `window.name`, which
+ // will go through our WindowProxy, which will always forward it to the
+ // currently-active inner window.
+ try {
+ void name;
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/file_delazification_strategy.html b/dom/base/test/file_delazification_strategy.html
new file mode 100644
index 0000000000..a8f58c60f1
--- /dev/null
+++ b/dom/base/test/file_delazification_strategy.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag script to check delazification strategy</title>
+</head>
+<body>
+ <!-- <script id="watchme" src="file_delazification_strategy.js"></script> -->
+</body>
+</html>
diff --git a/dom/base/test/file_delazification_strategy.js b/dom/base/test/file_delazification_strategy.js
new file mode 100644
index 0000000000..5afcbeed53
--- /dev/null
+++ b/dom/base/test/file_delazification_strategy.js
@@ -0,0 +1,91 @@
+function baz() {}
+function bar() {}
+function foo() {
+ bar();
+}
+foo();
+
+// For testing, we require the script to be parsed off-htread. To schedule a
+// script off-thread, we require the script to be at least 5 KB. Thus, here is
+// one comment which is used to trick this heuristics:
+//
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWX0Oxoc;,.... ....,;coxO0XWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0kdlc;'.. ..';:ldk0XWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNKkoc,.. ..,cok0NWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:'. .':okKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKkl,. .,lkKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNOo;. .;oONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXkl'. 'lkXWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNOl' 'lONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMWKo, ,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMNk:. .:kNMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMXx, ,xXMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMWXd' 'dXWMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMXd' 'dXMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMNx' 'xNMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMWO; ;OWMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMKl. .lXMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMWk, ,kWMMMMMMMMMMMMM
+// MMMMMMMMMMMMXo. .lXMMMMMMMMMMMM
+// MMMMMMMMMMW0; ...... ;0WMMMMMMMMMM
+// MMMMMMMMMWk' ..,:loxxkOOkxdo:,. 'kWMMMMMMMMM
+// MMMMMMMMWx. .':lxO000000000000000ko;. .xWMMMMMMMM
+// MMMMMMMNd. .'cdk00000000000000000000000x;. ..',;ccloodddddddollc:;,'.. .dNMMMMMMM
+// MMMMMMNd. ,dO000000000000000000000000000Oo' ..;coxk00000000000000000000000Okdlc;'. .dNMMMMMM
+// MMMMMNd. .cO000000000000000000000000000000k; .;lxO0000000000000000000000000000000000Okoc,. .dWMMMMM
+// MMMMWx. .'cO0000000000000000000000000000Oc. .;ok0000K00000000000000000000000000000000000000ko, .kWMMMM
+// MMMMO' ,kK0000000000000000000000000000Oc. .:x000000000000000OkdoollcccllodxkO0000K00000000000k' 'OMMMM
+// MMMK; .'lO000000000000000000000000000000Ol'... 'd000000000000Odl;'.. ..',:ldkO00000000KO, ;KMMM
+// MMNo ,dkO0K00000000000000000000000000000000OOkxdoc;,,ck0000000000Oo;. .'ck000000KO; oNMM
+// MMk. .o0000000000000000000000000000000000000000000000000000000000d, .o000000K0: .OMM
+// MX: .o00000000000000000000000000000000000000000000000000000000Oc. c000000K0l. :XM
+// Wx. .c00000000000000000000000000000000000000000000000000000000x, :OK000000o. .xW
+// X: .:dxO00000000000000000000000000000000000000000000000000000Oo' ,k0000000d. :X
+// O. .:xOO0000000000000000000000000000000000000000000000000000Ol. .x000000Kd. .O
+// o ..'''''''',,:lx000000000000000000000000000000000000000000x, .lOOOOkkko. o
+// ; .:x00000000000000000000000000000000000000000O:. ......... ;
+// . 'd00000000000000000000000000000000000000000Ol. .
+// . ,k000000000000000000000000000000000000000000o. .
+// .o0000000000000000000000000000000000000000000d.
+// c00000000000000000000000000000000000000000000o.
+// ;OK0000000000000000000000000000000000000000000o.
+// ,kK00000000000000000000000000000000000000000000xoc:,'..
+// .x00000000000000000000000000000000000000000000000000Okxolc;,'..
+// .d000000000000000K000000000000000000000000000000000000000000OOxdl:,..
+// . .o00000000000000OO0000000000000000000000000000000000000000000000000Oxo:'. .
+// . .l0000000000000Oc:k0000000000000000000000000000000000000000000000000000Oxl,. .
+// ; c000000000000Kk, ,x000000000000000000000000000000xodkO00000000000000000000xc. ;
+// o ..''.. :000000000000Kx' ,k0000000000000000000000000000Oc. ..';codkO000000000000000k:. o
+// O. .,lxO00Okxl:,. :O000000000000d. ;k0000000000000000000000000000l. ..,cok0000000000000d' .O
+// X: ,d000000000000kdc;. :O000000000000l. .c0000000000000000000000000000o. .;oO00000000000k; :X
+// Wx. 'x00000000000000000Oxl;..:O00000000000O: .x000000000000000000000000000d. 'oO00000K0000k, .kW
+// MX: c0000000000000000000000Oxk00000000000Kk, cO00000000000000000000000000d. ;k0000000000d. :XM
+// MMO. :O000000000000000000000000000000000000x. cO00000000000000000000000000d. ,k000000000O: .OMM
+// MMNo .d000000000000000000000000000000000000l. .d000000000000000000000000000o. cO000000000o. .oNMM
+// MMMK; 'd00000000000000000000000000000000000c ,k000000000000000000000000000l. 'x000000000d. ;KMMM
+// MMMMO' .lO000000000000000000000000000000000kl;. .o00000000000000000000000000K0c .d000000000d. 'OMMMM
+// MMMMWx. ;x00000000000000000000000000000000000ko:lO000000000000000000000000000O; .x000000000d. .xWMMMM
+// MMMMMWd. .ck00000000000000000000000000000000000000000000000000000000000000000k, ;kK00000000l. .dWMMMMM
+// MMMMMMNd. .:x000000000000000000000000000000000000000000000000000000000000000d. .o000000000O; .dNMMMMMM
+// MMMMMMMNd. .;dO000000000000000000000000000000000000000000000000000000000000l. .o0000000000d. .dNMMMMMMM
+// MMMMMMMMWx. .,,'.. 'cx00000000000000000000000000000000000000000000000000000000KO: .;d0000000000x, .xWMMMMMMMM
+// MMMMMMMMMWk' 'd00Oxdl;'. .,lx000000000000000000000000000000000000000000000000000000k' .:dO0000000000x, 'kWMMMMMMMMM
+// MMMMMMMMMMW0; .:x000000kd:'. .,lk000000000000000000000000000000000000000000000000000d. ..,cdk0K000000000Oo. :0WMMMMMMMMMM
+// MMMMMMMMMMMMXo. .,ok000000Odc. ,x000000000000000000000000000000000000000000000000000d,...'',;:cldxO000000000000K0d;. .oXMMMMMMMMMMMM
+// MMMMMMMMMMMMMWk, .:x0000000Oo;.. .,oO000000000000000000000000000000000000000000000000000000OOOO00000000000000000000Od;. ,kWMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMXl. .;dO0000000Oxdoooxk0000000000OxxO0000000000000000000000000000000000000000000000000000000000000000Oxc' .lXMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMWO; 'lk00000000000000000000Oxc' ..:oxO0K000000000000000000000000000000000000000000000000000000Oko:'. ;OWMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMNx, .;ok00000000000000kdc'. ..;cdxO00000000000000000000000000000000000000000000Okxdoc;'. ,xNMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMXd' .,:ldxkkkkxdl:,. ..,:cldxkkkO000000000000Okkxdlc:;;;:::::;;;,,'... 'dXMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMXd' ...... ....'',,,,,,,,'.... 'dXWMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMXx,. ,xXMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMNk:. .:kNMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMWKo,. .,oKWMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNOl' 'lONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXkl'. .'lkXWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNOo;. .;oONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKkl;. .,lkKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:'. .':okKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNKkoc,.. ..,cok0NWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0kdlc;'.. ..';:ldk0XWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWX0Oxoc;,.... ....,;coxO0XNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
diff --git a/dom/base/test/file_domwindowutils_animation.html b/dom/base/test/file_domwindowutils_animation.html
new file mode 100644
index 0000000000..dfcc8fc05c
--- /dev/null
+++ b/dom/base/test/file_domwindowutils_animation.html
@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>DOMWindowUtils test with animation</title>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/dom/animation/test/testcommon.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+
+const SimpleTest = window.opener.SimpleTest;
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const next = window.opener.next;
+const is = window.opener.is;
+const ok = window.opener.ok;
+
+function addStyle(rules) {
+ const extraStyle = document.createElement("style");
+ document.head.appendChild(extraStyle);
+ rules.forEach(rule => {
+ extraStyle.sheet.insertRule(rule, extraStyle.sheet.cssRules.length);
+ });
+}
+
+function deleteStyle() {
+ document.head.querySelector("style").remove();
+}
+
+
+function test_getUnanimatedComputedStyle() {
+ [
+ {
+ property: "opacity",
+ keyframes: [1, 0],
+ expectedInitialStyle: "1",
+ expectedDuringTransitionStyle: "0",
+ isDiscrete: false,
+ },
+ {
+ property: "clear",
+ keyframes: ["left", "inline-end"],
+ expectedInitialStyle: "none",
+ expectedDuringTransitionStyle: "inline-end",
+ isDiscrete: true,
+ },
+ ].forEach(testcase => {
+ const { property, keyframes, expectedInitialStyle,
+ expectedDuringTransitionStyle, isDiscrete } = testcase;
+
+ [null, "unset", "initial", "inherit"].forEach(initialStyle => {
+ const scriptAnimation = target => {
+ return target.animate({ [property]: keyframes }, 1000);
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle, expectedInitialStyle,
+ scriptAnimation, "script animation");
+
+ const cssAnimationStyle = `@keyframes cssanimation {`
+ + ` from { ${property}: ${ keyframes[0] }; }`
+ + ` to { ${property}: ${ keyframes[1] }; } }`;
+ addStyle([cssAnimationStyle]);
+ const cssAnimation = target => {
+ target.style.animation = "cssanimation 1s";
+ return target.getAnimations()[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle, expectedInitialStyle,
+ cssAnimation, "CSS Animations");
+ deleteStyle();
+
+ // We don't support discrete animations for CSS Transitions yet.
+ // (bug 1320854)
+ if (!isDiscrete) {
+ const cssTransition = target => {
+ target.style[property] = keyframes[0];
+ target.style.transition =
+ `${ property } 1s`;
+ window.getComputedStyle(target)[property];
+ target.style[property] = keyframes[1];
+ return target.getAnimations()[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle,
+ expectedDuringTransitionStyle,
+ cssTransition, "CSS Transitions");
+ }
+
+ addStyle([cssAnimationStyle,
+ ".pseudo::before { content: '' }",
+ ".animation::before { animation: cssanimation 1s }"]);
+ const pseudoAnimation = target => {
+ target.classList.add("animation");
+ return target.getAnimations({ subtree: true })[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, "::before",
+ expectedInitialStyle, expectedInitialStyle,
+ pseudoAnimation, "Animation at pseudo");
+ deleteStyle();
+ });
+ });
+
+ const div = document.createElement("div");
+ document.body.appendChild(div);
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "background", utils.FLUSH_NONE),
+ "NS_ERROR_INVALID_ARG",
+ "Shorthand property should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "invalid", utils.FLUSH_NONE),
+ "NS_ERROR_INVALID_ARG",
+ "Invalid property should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(null, null, "opacity", utils.FLUSH_NONE),
+ "NS_ERROR_INVALID_ARG",
+ "Null element should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_LAYOUT),
+ "NS_ERROR_INVALID_ARG",
+ "FLUSH_LAYOUT option should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_DISPLAY),
+ "NS_ERROR_INVALID_ARG",
+ "FLUSH_DISPLAY option should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, "::before", "opacity", utils.FLUSH_NONE),
+ "NS_ERROR_FAILURE",
+ "Non-existent pseudo should throw");
+
+ // Flush styles since getUnanimatedComputedStyle flushes pending styles even
+ // with FLUSH_NONE option if the element hasn't yet styled.
+ getComputedStyle(div).opacity;
+
+ div.style.opacity = "0";
+ is(utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_NONE),
+ "1",
+ "getUnanimatedComputedStyle with FLUSH_NONE should not flush pending styles");
+
+ is(utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_STYLE),
+ "0",
+ "getUnanimatedComputedStyle with FLUSH_STYLE should flush pending styles");
+
+ div.remove();
+
+ test_needsFlushWithThrottledAnimations();
+}
+
+function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
+ expectedBeforeAnimation,
+ expectedDuringAnimation,
+ animate, animationType) {
+ const div = document.createElement("div");
+ document.body.appendChild(div);
+
+ if (initialStyle) {
+ div.style[property] = initialStyle;
+ }
+ if (pseudoType) {
+ div.classList.add("pseudo");
+ }
+
+ is(utils.getUnanimatedComputedStyle(div, pseudoType, property, utils.FLUSH_STYLE),
+ expectedBeforeAnimation,
+ `'${ property }' property with '${ initialStyle }' style `
+ + `should be '${ expectedBeforeAnimation }' `
+ + `before animating by ${ animationType }`);
+
+ const animation = animate(div);
+ animation.currentTime = 500;
+ is(utils.getUnanimatedComputedStyle(div, pseudoType, property, utils.FLUSH_STYLE),
+ expectedDuringAnimation,
+ `'${ property }' property with '${ initialStyle }' style `
+ + `should be '${ expectedDuringAnimation }' `
+ + `even while animating by ${ animationType }`);
+
+ div.remove();
+}
+
+function test_needsFlushWithThrottledAnimations() {
+ const div = document.createElement("div");
+ div.style = "width: 100px; height: 100px; background-color: blue;";
+ document.body.appendChild(div);
+
+ const animation = div.animate({ opacity: [ 0, 1 ] },
+ { duration: 100000, iterations: Infinity });
+ waitForAnimationReadyToRestyle(animation).then(() => {
+ ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
+ "Opacity animation should run on the compositor");
+
+ // FIXME! Bug 1442861: We should make sure needsFlush() returns true
+ // before flusing layout.
+ //ok(utils.needsFlush(SpecialPowers.Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ // "needsFlush should return true if there is an animation on the compositor");
+
+ // Flush layout.
+ document.documentElement.getBoundingClientRect();
+
+ ok(!utils.needsFlush(SpecialPowers.Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ "needsFlush should return false after flushing layout");
+
+ div.remove();
+ next();
+ window.close();
+ });
+}
+
+window.addEventListener("load", test_getUnanimatedComputedStyle);
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_domwindowutils_dynamic_toolbar.html b/dom/base/test/file_domwindowutils_dynamic_toolbar.html
new file mode 100644
index 0000000000..becb4bf08f
--- /dev/null
+++ b/dom/base/test/file_domwindowutils_dynamic_toolbar.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+ const ok = window.opener.ok;
+
+ SpecialPowers.getDOMWindowUtils(window).setDynamicToolbarMaxHeight(100);
+ ok(window.visualViewport.width > 0,
+ "Setting the dynamic toolbar max height shouldn't clobber the visual " +
+ "viewport size");
+ ok(window.visualViewport.height > 0,
+ "Setting the dynamic toolbar max height shouldn't clobber the visual " +
+ "viewport size");
+
+ window.opener.next();
+ window.close();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_empty.html b/dom/base/test/file_empty.html
new file mode 100644
index 0000000000..495c23ec8a
--- /dev/null
+++ b/dom/base/test/file_empty.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><body></body></html>
diff --git a/dom/base/test/file_explicit_user_agent.sjs b/dom/base/test/file_explicit_user_agent.sjs
new file mode 100644
index 0000000000..33b9a5c505
--- /dev/null
+++ b/dom/base/test/file_explicit_user_agent.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ if (request.hasHeader("User-Agent")) {
+ response.setHeader("Result-User-Agent", request.getHeader("User-Agent"));
+ }
+ response.write("");
+}
diff --git a/dom/base/test/file_external_script.html b/dom/base/test/file_external_script.html
new file mode 100644
index 0000000000..16f059d558
--- /dev/null
+++ b/dom/base/test/file_external_script.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <script src="file_script.js"></script>
+ <meta charset="utf-8">
+ <title></title>
+</head>
+<body>
+ <p>Hello Mochitest</p>
+</body>
+</html>
diff --git a/dom/base/test/file_external_script.xhtml b/dom/base/test/file_external_script.xhtml
new file mode 100644
index 0000000000..4327c499cb
--- /dev/null
+++ b/dom/base/test/file_external_script.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script src="file_script.js"/>
+ <title/>
+</head>
+<body>
+ <p>Hello Mochitest</p>
+</body>
+</html>
diff --git a/dom/base/test/file_focus_design_mode_inner.html b/dom/base/test/file_focus_design_mode_inner.html
new file mode 100644
index 0000000000..43e24652ab
--- /dev/null
+++ b/dom/base/test/file_focus_design_mode_inner.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>activeElement design-mode inner document</title>
+</head>
+<body>
+<h1>Inner</h1>
+<script>
+let innerlog = "innerlog:";
+
+document.designmode = "on";
+
+window.onmessage = function(e) {
+ if (e.data == "focus") {
+ document.documentElement.focus();
+ } else if (e.data == "getlog") {
+ innerlog += "activeElement:" + document.activeElement.tagName + ",";
+ parent.postMessage(innerlog, "*");
+ }
+};
+
+window.onfocus = function() {
+ innerlog += "windowfocus,";
+};
+
+window.onblur = function() {
+ innerlog += "windowblur,";
+};
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_focus_display_none_xorigin_iframe_inner.html b/dom/base/test/file_focus_display_none_xorigin_iframe_inner.html
new file mode 100644
index 0000000000..5831df882c
--- /dev/null
+++ b/dom/base/test/file_focus_display_none_xorigin_iframe_inner.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<h2>Inner</h2>
+<input></input><br>
+<script type="text/javascript">
+let input = document.querySelector("input");
+
+window.onmessage = function(e) {
+ info(`inner received message: ${e.data}`);
+ if (e.data === "focus") {
+ input.focus();
+ window.parent.postMessage("done", "*");
+ }
+};
+</script>
diff --git a/dom/base/test/file_focus_shadow_dom.html b/dom/base/test/file_focus_shadow_dom.html
new file mode 100644
index 0000000000..6fa9d1b88e
--- /dev/null
+++ b/dom/base/test/file_focus_shadow_dom.html
@@ -0,0 +1,999 @@
+<html>
+ <head>
+ <title>Test for Bug 1453693</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+
+ class TestNode extends HTMLElement {
+ constructor() {
+ super();
+ const styles = "<style>:focus{background-color:yellow;}</style>";
+ this.attachShadow({ mode: 'open' });
+ this.shadowRoot.innerHTML =
+ `${styles}<div tabindex='-1'>test node</div> <slot></slot>`;
+ }}
+
+ window.customElements.define('test-node', TestNode);
+
+ var lastFocusTarget;
+ function focusLogger(event) {
+ lastFocusTarget = event.target;
+ console.log(event.target + " under " + event.target.parentNode);
+ event.stopPropagation();
+ }
+
+ function testTabbingThroughShadowDOMWithTabIndexes() {
+ var anchor = document.createElement("a");
+ anchor.onfocus = focusLogger;
+ anchor.href = "#";
+ anchor.textContent = "in light DOM";
+ document.body.appendChild(anchor);
+
+ var host = document.createElement("div");
+ document.body.appendChild(host);
+
+ var sr = host.attachShadow({mode: "open"});
+ var shadowAnchor = anchor.cloneNode(false);
+ shadowAnchor.onfocus = focusLogger;
+ shadowAnchor.textContent = "in shadow DOM";
+ sr.appendChild(shadowAnchor);
+ var shadowInput = document.createElement("input");
+ shadowInput.onfocus = focusLogger;
+ shadowInput.tabIndex = 1;
+ sr.appendChild(shadowInput);
+
+ var shadowDate = document.createElement("input");
+ shadowDate.type = "date";
+ shadowDate.onfocus = focusLogger;
+ shadowDate.tabIndex = 1;
+ sr.appendChild(shadowDate);
+
+ var shadowIframe = document.createElement("iframe");
+ shadowIframe.tabIndex = 1;
+ sr.appendChild(shadowIframe);
+ shadowIframe.contentDocument.body.innerHTML = "<input>";
+
+ var input = document.createElement("input");
+ input.onfocus = focusLogger;
+ input.tabIndex = 1;
+ document.body.appendChild(input);
+
+ var input2 = document.createElement("input");
+ input2.onfocus = focusLogger;
+ document.body.appendChild(input2);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input, "Should have focused input element. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(shadowIframe.contentDocument.activeElement,
+ shadowIframe.contentDocument.documentElement,
+ "Should have focused document element in shadow iframe. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(shadowIframe.contentDocument.activeElement,
+ shadowIframe.contentDocument.body.firstChild,
+ "Should have focused input element in shadow iframe. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(shadowIframe.contentDocument.activeElement,
+ shadowIframe.contentDocument.body.firstChild,
+ "Should have focused input element in shadow iframe. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(shadowIframe.contentDocument.activeElement,
+ shadowIframe.contentDocument.documentElement,
+ "Should have focused document element in shadow iframe. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
+
+ document.body.innerHTML = null;
+ }
+
+ function testTabbingThroughSimpleShadowDOM() {
+ var anchor = document.createElement("a");
+ anchor.onfocus = focusLogger;
+ anchor.href = "#";
+ anchor.textContent = "in light DOM";
+ document.body.appendChild(anchor);
+ anchor.focus();
+
+ var host = document.createElement("div");
+ document.body.appendChild(host);
+
+ var sr = host.attachShadow({mode: "open"});
+ var shadowAnchor = anchor.cloneNode(false);
+ shadowAnchor.onfocus = focusLogger;
+ shadowAnchor.textContent = "in shadow DOM";
+ sr.appendChild(shadowAnchor);
+ var shadowInput = document.createElement("input");
+ shadowInput.onfocus = focusLogger;
+ sr.appendChild(shadowInput);
+
+ var hiddenShadowButton = document.createElement("button");
+ hiddenShadowButton.setAttribute("style", "display: none;");
+ sr.appendChild(hiddenShadowButton);
+
+ var input = document.createElement("input");
+ input.onfocus = focusLogger;
+ document.body.appendChild(input);
+
+ var input2 = document.createElement("input");
+ input2.onfocus = focusLogger;
+ document.body.appendChild(input2);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM.");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM.");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input, "Should have focused input element.");
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input2, "Should have focused input[2] element.");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input, "Should have focused input element. (2)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (2)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (2)");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (2)");
+
+ host.remove();
+ input.remove();
+ input2.remove();
+ }
+
+ function testTabbingThroughNestedShadowDOM() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)");
+
+ var host = document.createElement("div");
+ host.id = "host";
+ document.body.appendChild(host);
+
+ var sr0 = host.attachShadow({mode: "open"});
+ sr0.innerHTML = "<button id='button'>X</button><br id='br'><div id='h1'></div><div id='h2'></div>";
+ var button = sr0.getElementById("button");
+ button.onfocus = focusLogger;
+
+ var h1 = sr0.getElementById("h1");
+ var sr1 = h1.attachShadow({mode: "open"});
+ sr1.innerHTML = "h1 <input id='h11' placeholder='click me and press tab'><input id='h12' placeholder='and then tab again'>";
+ var input11 = sr1.getElementById("h11");
+ input11.onfocus = focusLogger;
+ var input12 = sr1.getElementById("h12");
+ input12.onfocus = focusLogger;
+
+ var h2 = sr0.getElementById("h2");
+ var sr2 = h2.attachShadow({mode: "open"});
+ sr2.innerHTML = "h2 <input id='h21'><input id='h22'>";
+ var input21 = sr2.getElementById("h21");
+ input21.onfocus = focusLogger;
+ var input22 = sr2.getElementById("h22");
+ input22.onfocus = focusLogger;
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input22, "[nested shadow] Should have focused input element. (4)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (5)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (8)");
+
+ // Back to beginning, outside of Shadow DOM.
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)");
+
+ host.remove();
+ }
+
+ function testTabbingThroughDisplayContentsHost() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)");
+
+ var host = document.createElement("div");
+ host.id = "host";
+ host.setAttribute("style", "display: contents; border: 1px solid black;");
+ document.body.appendChild(host);
+
+ var sr0 = host.attachShadow({mode: "open"});
+ sr0.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>";
+ var shadowInput1 = sr0.getElementById("shadowInput1");
+ shadowInput1.onfocus = focusLogger;
+ var shadowInput2 = sr0.getElementById("shadowInput2");
+ shadowInput2.onfocus = focusLogger;
+
+ var host1 = document.createElement("div");
+ host1.id = "host";
+ host1.tabIndex = 0;
+ host1.setAttribute("style", "display: contents; border: 1px solid black;");
+ document.body.appendChild(host1);
+
+ var sr1 = host1.attachShadow({mode: "open"});
+ sr1.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>";
+ var shadowInput3 = sr1.getElementById("shadowInput1");
+ shadowInput3.onfocus = focusLogger;
+ var shadowInput4 = sr1.getElementById("shadowInput2");
+ shadowInput4.onfocus = focusLogger;
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput4, "Should have focused input element. (4)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (5)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (7)");
+
+ // Back to beginning, outside of Shadow DOM.
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)");
+
+ host.remove();
+ host1.remove();
+ }
+
+ function testTabbingThroughLightDOMShadowDOMLightDOM() {
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ var host = document.createElement("span");
+ host.innerHTML = "\n";
+ host.id = "host";
+ document.body.appendChild(host);
+
+ var sr0 = host.attachShadow({mode: "open"});
+ sr0.innerHTML = document.getElementById("template").innerHTML;
+ var p1 = sr0.getElementById("p1");
+ p1.onfocus = focusLogger;
+ var p2 = sr0.getElementById("p2");
+ p2.onfocus = focusLogger;
+
+ var p = document.createElement("p");
+ p.innerHTML = " <a href='#p'>link 1</a> ";
+ var a = p.firstElementChild;
+ a.onfocus = focusLogger;
+ document.body.appendChild(p);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, p1, "Should have focused p1.");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, p2, "Should have focused p2.");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, a, "Should have focused a.");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, p2, "Should have focused p2.");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, p1, "Should have focused p1.");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ host.remove();
+ p.remove();
+ }
+
+ function testFocusableHost() {
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ var host = document.createElement("div");
+ host.id = "host";
+ host.tabIndex = 0;
+ host.onfocus = focusLogger;
+ document.body.appendChild(host);
+
+ var slotted = document.createElement("div");
+ slotted.tabIndex = 0;
+ slotted.onfocus = focusLogger;
+ host.appendChild(slotted);
+
+ var sr0 = host.attachShadow({mode: "open"});
+ sr0.appendChild(document.createElement("slot"));
+
+ var p = document.createElement("p");
+ p.innerHTML = " <a href='#p'>link 1</a> ";
+ var a = p.firstElementChild;
+ a.onfocus = focusLogger;
+ document.body.appendChild(p);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, host, "Should have focused host.");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, a, "Should have focused a.");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, host, "Should have focused host.");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ host.remove();
+ p.remove();
+ }
+
+ function testShiftTabbingThroughFocusableHost() {
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ var host = document.createElement("div");
+ host.id = "host";
+ host.tabIndex = 0;
+ host.onfocus = focusLogger;
+ document.body.appendChild(host);
+
+ var sr = host.attachShadow({mode: "open"});
+ var shadowButton = document.createElement("button");
+ shadowButton.innerText = "X";
+ shadowButton.onfocus = focusLogger;
+ sr.appendChild(shadowButton);
+
+ var shadowInput = document.createElement("input");
+ shadowInput.onfocus = focusLogger;
+ sr.appendChild(shadowInput);
+ sr.appendChild(document.createElement("br"));
+
+ var input = document.createElement("input");
+ input.onfocus = focusLogger;
+ document.body.appendChild(input);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, host, "Should have focused host element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ // focus is already on host
+ opener.is(sr.activeElement, null,
+ "Focus should have left button element in shadow DOM. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ host.remove();
+ input.remove();
+ }
+
+ function testTabbingThroughNestedSlot() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+ var host0 = document.createElement("div");
+ var sr0 = host0.attachShadow({mode: "open"});
+ sr0.innerHTML = "<slot></slot>";
+ document.body.appendChild(host0);
+
+ // focusable
+ var host00 = document.createElement("div");
+ var sr00 = host00.attachShadow({mode: "open"});
+ var div00 = document.createElement("div");
+ div00.tabIndex = 0;
+ div00.onfocus = focusLogger;
+ sr00.appendChild(div00);
+ host0.appendChild(host00);
+
+ // not focusable
+ var host01 = document.createElement("div");
+ var sr01 = host01.attachShadow({mode: "open"});
+ sr01.innerHTML = "<div></div>";
+ host0.appendChild(host01);
+
+ // focusable
+ var host02 = document.createElement("div");
+ var sr02 = host02.attachShadow({mode: "open"});
+ var div02 = document.createElement("div");
+ div02.tabIndex = 0;
+ div02.onfocus = focusLogger;
+ sr02.appendChild(div02);
+ host0.appendChild(host02);
+
+ var host1 = document.createElement("div");
+ var sr1 = host1.attachShadow({mode: "open"});
+ sr1.innerHTML = "<slot></slot>";
+ document.body.appendChild(host1);
+
+ var host10 = document.createElement("div");
+ var sr10 = host10.attachShadow({mode: "open"});
+ sr10.innerHTML = "<slot></slot>";
+ host1.appendChild(host10);
+
+ var input10 = document.createElement("input");
+ input10.onfocus = focusLogger;
+ host10.appendChild(input10);
+
+ var host11 = document.createElement("div");
+ var sr11 = host11.attachShadow({mode: "open"});
+ sr11.innerHTML = "<slot></slot>";
+ host1.appendChild(host11);
+
+ var input11 = document.createElement("input");
+ input11.onfocus = focusLogger;
+ host11.appendChild(input11);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ host0.remove();
+ host1.remove();
+ }
+
+ function testTabbingThroughSlotInLightDOM() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+ var input0 = document.createElement("input");
+ input0.onfocus = focusLogger;
+ document.body.appendChild(input0);
+
+ var slot1 = document.createElement("slot");
+ document.body.appendChild(slot1);
+
+ var input10 = document.createElement("input");
+ input10.onfocus = focusLogger;
+ slot1.appendChild(input10);
+
+ var input11 = document.createElement("input");
+ input11.onfocus = focusLogger;
+ slot1.appendChild(input11);
+
+ var input2 = document.createElement("input");
+ input2.onfocus = focusLogger;
+ document.body.appendChild(input2);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input0, "Should have focused input element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input2, "Should have focused input element. (4)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (5)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input0, "Should have focused input element. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ input0.remove();
+ slot1.remove();
+ input2.remove();
+ }
+
+ function testTabbingThroughFocusableSlotInLightDOM() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+ var slot0 = document.createElement("slot");
+ slot0.tabIndex = 0;
+ slot0.setAttribute("style", "display: inline;");
+ slot0.onfocus = focusLogger;
+ document.body.appendChild(slot0);
+
+ var slot00 = document.createElement("slot");
+ slot00.tabIndex = 0;
+ slot00.setAttribute("style", "display: inline;");
+ slot00.onfocus = focusLogger;
+ slot0.appendChild(slot00);
+
+ var input000 = document.createElement("input");
+ input000.onfocus = focusLogger;
+ slot00.appendChild(input000);
+
+ var input01 = document.createElement("input");
+ input01.onfocus = focusLogger;
+ slot0.appendChild(input01);
+
+ var input1 = document.createElement("input");
+ input1.onfocus = focusLogger;
+ document.body.appendChild(input1);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, slot00, "Should have focused slot element. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (4)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input1, "Should have focused input element. (5)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (6)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, slot00, "Should have focused slot element. (8)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, slot0, "Should have focused slot element. (9)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ slot0.remove();
+ input1.remove();
+ }
+
+ function testTabbingThroughScrollableShadowDOM() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+ var host0 = document.createElement("div");
+ host0.setAttribute("style", "height: 50px; overflow: auto;");
+ host0.onfocus = focusLogger;
+ document.body.appendChild(host0);
+
+ var sr0 = host0.attachShadow({mode: "open"});
+ sr0.innerHTML = `
+ <style>
+ div,slot {
+ height: 30px;
+ display: block;
+ overflow: auto;
+ }
+ input {
+ display: block;
+ }
+ </style>
+ `;
+
+ var input00 = document.createElement("input");
+ input00.setAttribute("style", "background-color: red;");
+ input00.onfocus = focusLogger;
+ sr0.appendChild(input00);
+
+ var container01 = document.createElement("div");
+ container01.onfocus = focusLogger;
+ sr0.appendChild(container01);
+
+ var input010 = document.createElement("input");
+ input010.onfocus = focusLogger;
+ container01.appendChild(input010);
+
+ var input011 = document.createElement("input");
+ input011.onfocus = focusLogger;
+ container01.appendChild(input011);
+
+ var slot02 = document.createElement("slot");
+ slot02.onfocus = focusLogger;
+ sr0.appendChild(slot02);
+
+ var input020 = document.createElement("input");
+ input020.setAttribute("style", "display: block;");
+ input020.onfocus = focusLogger;
+ host0.appendChild(input020);
+
+ var input021 = document.createElement("input");
+ input021.setAttribute("style", "display: block;");
+ input021.onfocus = focusLogger;
+ host0.appendChild(input021);
+
+ var input1 = document.createElement("input");
+ input1.onfocus = focusLogger;
+ document.body.appendChild(input1);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (4)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (5)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (6)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (7)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (8)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, input1, "Should have focused input element in light dom. (9)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (10)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (11)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (12)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (13)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (14)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (15)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (16)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ // focus is already on host
+ opener.is(sr0.activeElement, null,
+ "Focus should have left input element in shadow DOM. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ host0.remove();
+ input1.remove();
+ }
+
+ // Bug 1604140
+ function testTabbingThroughScrollableShadowHost() {
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ let aboveFirstHost = document.createElement("div");
+ aboveFirstHost.tabIndex = 0;
+ aboveFirstHost.onfocus = focusLogger;
+ document.body.appendChild(aboveFirstHost);
+
+ let firstHost = document.createElement("div");
+ firstHost.style = "overflow: scroll";
+ document.body.appendChild(firstHost);
+
+ let firstShadow = firstHost.attachShadow({mode: "open"});
+ let divInFirstShadow = document.createElement("div");
+ divInFirstShadow.tabIndex = 0;
+ divInFirstShadow.onfocus = focusLogger;
+ firstShadow.appendChild(divInFirstShadow);
+
+ let aboveSecondHost = document.createElement("div");
+ aboveSecondHost.tabIndex = 0;
+ aboveSecondHost.onfocus = focusLogger;
+ document.body.appendChild(aboveSecondHost);
+
+ let secondHost = document.createElement("div");
+ secondHost.style = "overflow: scroll";
+ secondHost.tabIndex = 0;
+ secondHost.onfocus = focusLogger;
+ document.body.appendChild(secondHost);
+
+ let secondShadow = secondHost.attachShadow({mode: "open"});
+ let divInSecondShadow = document.createElement("div");
+ divInSecondShadow.tabIndex = 0;
+ divInSecondShadow.onfocus = focusLogger;
+ secondShadow.appendChild(divInSecondShadow);
+
+ let belowSecondHost = document.createElement("div");
+ belowSecondHost.tabIndex = 0;
+ belowSecondHost.onfocus = focusLogger;
+ document.body.appendChild(belowSecondHost);
+
+ document.body.offsetLeft;
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (1)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (2)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (3)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, secondHost, "Should have focused second host element. (4)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (5)");
+
+ synthesizeKey("KEY_Tab");
+ opener.is(lastFocusTarget, belowSecondHost, "Should have focused div below second host. (6)");
+
+ // Backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (7)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ // focus is already on second host, so lastFocusTarget won't get updated.
+ opener.is(document.activeElement, secondHost, "Should have focused second host element. (8)");
+ opener.is(secondShadow.activeElement, null, "Focus should have left div in second shadow dom. (8)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (9)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (10)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (11)");
+
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild,
+ "body's first child should have focus.");
+
+ aboveFirstHost.remove();
+ firstHost.remove();
+ aboveSecondHost.remove();
+ secondHost.remove();
+ belowSecondHost.remove();
+ }
+
+ function testDeeplyNestedShadowTree() {
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+ var host1 = document.createElement("test-node");
+ var lastHost = host1;
+ for (var i = 0; i < 20; ++i) {
+ lastHost.appendChild(document.createElement("test-node"));
+ lastHost = lastHost.firstChild;
+ }
+
+ var input = document.createElement("input");
+ document.body.appendChild(host1);
+ document.body.appendChild(input);
+ document.body.offsetLeft;
+
+ // Test shadow tree which doesn't have anything tab-focusable.
+ host1.shadowRoot.querySelector("div").focus();
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement, input, "Should have focused input element.");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+ // Same test but with focusable elements in the tree...
+ var input2 = document.createElement("input");
+ var host2 = host1.firstChild;
+ var host3 = host2.firstChild;
+ host2.insertBefore(input2, host3);
+ var input3 = document.createElement("input");
+ lastHost.appendChild(input3);
+ document.body.offsetLeft;
+ host3.shadowRoot.querySelector("div").focus();
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement, input3, "Should have focused input3 element.");
+
+ // ...and backwards
+ host3.shadowRoot.querySelector("div").focus();
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ is(document.activeElement, input2, "Should have focused input2 element.");
+
+ // Remove elements added to body element.
+ host1.remove();
+ input.remove();
+
+ // Tests expect body.firstChild to have focus.
+ document.body.firstChild.focus();
+ }
+
+ // Bug 1558393
+ function testBackwardsTabbingWithSlotsWithoutFocusableContent() {
+ let first = document.createElement("div");
+ first.tabIndex = 0;
+ let host = document.createElement("div");
+ host.tabIndex = 0;
+ let second = document.createElement("div");
+ second.tabIndex = 0;
+ host.appendChild(document.createTextNode("foo"));
+ host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`;
+
+ document.body.appendChild(first);
+ document.body.appendChild(host);
+ document.body.appendChild(second);
+ document.body.offsetLeft;
+
+ first.focus();
+ opener.is(document.activeElement, first, "First light div should have focus");
+ synthesizeKey("KEY_Tab");
+ opener.is(document.activeElement, host, "Host should be focused");
+ synthesizeKey("KEY_Tab");
+ opener.is(document.activeElement, second, "Second light div should be focused");
+
+ // Now backwards
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, host, "Focus should return to host");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ opener.is(document.activeElement, first, "Focus should return to first light div");
+
+ second.remove();
+ host.remove();
+ first.remove();
+ }
+
+ function runTest() {
+
+ testTabbingThroughShadowDOMWithTabIndexes();
+ testTabbingThroughSimpleShadowDOM();
+ testTabbingThroughNestedShadowDOM();
+ testTabbingThroughDisplayContentsHost();
+ testTabbingThroughLightDOMShadowDOMLightDOM();
+ testFocusableHost();
+ testShiftTabbingThroughFocusableHost();
+ testTabbingThroughNestedSlot();
+ testTabbingThroughSlotInLightDOM();
+ testTabbingThroughFocusableSlotInLightDOM();
+ testTabbingThroughScrollableShadowDOM();
+ testTabbingThroughScrollableShadowHost();
+ testDeeplyNestedShadowTree();
+ testBackwardsTabbingWithSlotsWithoutFocusableContent();
+
+ opener.didRunTests();
+ window.close();
+ }
+
+ function init() {
+ SimpleTest.waitForFocus(runTest);
+ }
+ </script>
+ <style>
+ </style>
+ <template id="template">
+ <div style="overflow: hidden">
+ <p tabindex="0" id="p1">component</p>
+ <p tabindex="0" id="p2">/component</p>
+ </div>
+ </template>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/dom/base/test/file_general_document.html b/dom/base/test/file_general_document.html
new file mode 100644
index 0000000000..2539669de9
--- /dev/null
+++ b/dom/base/test/file_general_document.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>General document for testing</title>
+</head>
+<body>
+<p>Hello mochitest!</p>
+</body>
+</html>
diff --git a/dom/base/test/file_history_document_open.html b/dom/base/test/file_history_document_open.html
new file mode 100644
index 0000000000..b9f05f7c2c
--- /dev/null
+++ b/dom/base/test/file_history_document_open.html
@@ -0,0 +1 @@
+<script>function f() { history.length; } window.onload = function() { var func = window.f; document.open(); document.close(); parent.continueTest(func); }</script>
diff --git a/dom/base/test/file_htmlserializer_1.html b/dom/base/test/file_htmlserializer_1.html
new file mode 100644
index 0000000000..9576b5d7d6
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html><html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong> adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_bodyonly.html b/dom/base/test/file_htmlserializer_1_bodyonly.html
new file mode 100644
index 0000000000..848167c62a
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_bodyonly.html
@@ -0,0 +1,43 @@
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_format.html b/dom/base/test/file_htmlserializer_1_format.html
new file mode 100644
index 0000000000..09f80467e4
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_format.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+ </head>
+ <body>
+ <p>Hello world</p>
+ <p> Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+ adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis
+ ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent
+ taciti <span>sociosqu ad litora</span> torquent <a
+ href="file_htmlserializer_1_result1.html">per conubia</a>
+ nostra, per inceptos hymenaeos. </p>
+ <ul>
+ <li>Nam tellus massa,éàèçù</li>
+ <li> fringilla aliquam,</li>
+ <li> fermentum sit amet,</li>
+ <li>posuere ac,</li>
+ <li> est.</li>
+ </ul>
+ <div> Duis tristique egestas ligula. Mauris quis felis. </div>
+ <script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+ <ol>
+ <li>Fusce a ipsum</li>
+ <li> non lacus posuere aliquet.</li>
+ <li> Sed fermentum posuere nulla</li>
+ <li> Donec tempor.</li>
+ </ol>
+ Donec sollicitudin tortor
+ <!-- test on
+comments -->
+ <pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ ut gravida eros leo ut libero
+ <p></p>
+ <noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+ <p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus
+ aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+ </body>
+</html>
diff --git a/dom/base/test/file_htmlserializer_1_linebreak.html b/dom/base/test/file_htmlserializer_1_linebreak.html
new file mode 100644
index 0000000000..8194b8b415
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_linebreak.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_links.html b/dom/base/test/file_htmlserializer_1_links.html
new file mode 100644
index 0000000000..f0864c940c
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_links.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="http://mochi.test:8888/tests/dom/base/test/file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html>
diff --git a/dom/base/test/file_htmlserializer_1_nested_body.html b/dom/base/test/file_htmlserializer_1_nested_body.html
new file mode 100644
index 0000000000..94f67547e3
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_nested_body.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p><body><p>this is an other body element</p></body></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_no_body.html b/dom/base/test/file_htmlserializer_1_no_body.html
new file mode 100644
index 0000000000..9c749721b1
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_no_body.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_noflag.html b/dom/base/test/file_htmlserializer_1_noflag.html
new file mode 100644
index 0000000000..8194b8b415
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_noflag.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_noformatpre.html b/dom/base/test/file_htmlserializer_1_noformatpre.html
new file mode 100644
index 0000000000..aba95b62c8
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_noformatpre.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.
+
+ Cras quis
+
+ nisi at odio
+
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum,
+
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_raw.html b/dom/base/test/file_htmlserializer_1_raw.html
new file mode 100644
index 0000000000..c646f26963
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_raw.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong> adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_sibling_body.html b/dom/base/test/file_htmlserializer_1_sibling_body.html
new file mode 100644
index 0000000000..f533e6679a
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_sibling_body.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body><p>this is an other body element</p></body><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_sibling_body_only_body.html b/dom/base/test/file_htmlserializer_1_sibling_body_only_body.html
new file mode 100644
index 0000000000..97c1625156
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_sibling_body_only_body.html
@@ -0,0 +1,43 @@
+<body><p>this is an other body element</p></body><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_1_wrap.html b/dom/base/test/file_htmlserializer_1_wrap.html
new file mode 100644
index 0000000000..4552e9cba5
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_1_wrap.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="content-type"
+content="text/html; charset=UTF-8">
+ <title>Test for html serializer</title>
+
+</head><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu
+ ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per
+ conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script type="text/javascript">
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum
+posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br>
+ Cras quis<br>
+ nisi at odio<br>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros leo ut libero
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc & non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus
+aliquet lectus. Nunc vitae eros. Class aptent taciti</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_2.html b/dom/base/test/file_htmlserializer_2.html
new file mode 100644
index 0000000000..2156b1610c
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html><html><head><title>Test for html serializer with entities</title>
+</head><body>
+
+<p>The basic set is just &nbsp; &amp; &lt; &gt; &quot; for interoperability with older products that don't support &alpha; and friends.</p>
+
+<p>latin1 &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml; &copy; &ordf; &laquo; &not; &shy; &reg; &macr; &deg; &plusmn; &sup2; &sup3; &acute;
+&micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12; &frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring; &AElig;
+&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc; &Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times; &Oslash;
+&Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave; &aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave; &eacute; &ecirc;
+&euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute; &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc; &uuml; &yacute;
+&thorn; &yuml; </p>
+<p>symbols, math.. &fnof; &Alpha; &Beta; &Gamma; &Delta; &Epsilon; &Zeta; &Eta; &Theta; &Iota; &Kappa; &Lambda; &Mu; &Nu; &Xi; &Omicron; &Pi; &Rho; &Sigma; &Tau; &Upsilon;
+&Phi; &Chi; &Psi; &Omega; &alpha; &beta; &gamma; &delta; &epsilon; &zeta; &eta; &theta; &iota; &kappa; &lambda; &mu; &nu; &xi; &omicron; &pi; &rho; &sigmaf;
+&sigma; &tau; &upsilon; &phi; &chi; &psi; &omega; &thetasym; &upsih; &piv; &bull; &hellip; &prime; &Prime; &oline; &frasl; &weierp; &image; &real;
+&trade; &alefsym; &larr; &uarr; &rarr; &darr; &harr; &crarr; &lArr; &uArr; &rArr; &dArr; &hArr; &forall; &part; &exist; &empty; &nabla; &isin; &notin;
+&ni; &prod; &sum; &minus; &lowast; &radic; &prop; &infin; &ang; &and; &or; &cap; &cup; &int; &there4; &sim; &cong; &asymp; &ne; &equiv; &le; &ge;
+&sub; &sup; &nsub; &sube; &supe; &oplus; &otimes; &perp; &sdot; &lceil; &rceil; &lfloor; &rfloor; &loz; &spades; &clubs; &hearts; &diams;
+</p>
+<p> others
+&OElig; &oelig; &Scaron; &scaron; &Yuml; &circ; &tilde; &ensp; &emsp; &thinsp; &zwnj; &zwj; &lrm; &rlm;&ndash;&mdash; &lsquo; &rsquo;
+&sbquo;&ldquo; &rdquo; &bdquo; &dagger; &Dagger; &permil; &lsaquo; &rsaquo; &euro;
+</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_2_basic.html b/dom/base/test/file_htmlserializer_2_basic.html
new file mode 100644
index 0000000000..56ac95dfdd
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_2_basic.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset="><title>Test for html serializer with entities</title>
+</head><body>
+
+<p>The basic set is just &nbsp; &amp; &lt; &gt; " for interoperability with older products that don't support α and friends.</p>
+
+<p>latin1 ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ ° ± ² ³ ´
+µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À à Â Ã Ä Å Æ
+Ç È É Ê Ë Ì à Î à à Ñ Ò Ó Ô Õ Ö × Ø
+Ù Ú Û Ü à Þ ß à á â ã ä å æ ç è é ê
+ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý
+þ ÿ </p>
+<p>symbols, math.. ƒ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ ΠΞ Ο Π Ρ Σ Τ Υ
+Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο Ï€ Ï Ï‚
+σ τ υ φ χ ψ ω ϑ ϒ ϖ • … ′ ″ ‾ ℠℘ ℑ ℜ
+™ ℵ ↠↑ → ↓ ↔ ↵ ⇠⇑ ⇒ ⇓ ⇔ ∀ ∂ ∃ ∅ ∇ ∈ ∉
+∋ ∠∑ − ∗ √ ∠∞ ∠ ∧ ∨ ∩ ∪ ∫ ∴ ∼ ≅ ≈ ≠ ≡ ≤ ≥
+⊂ ⊃ ⊄ ⊆ ⊇ ⊕ ⊗ ⊥ ⋅ ⌈ ⌉ ⌊ ⌋ ◊ ♠ ♣ ♥ ♦
+</p>
+<p> others
+Å’ Å“ Å  Å¡ Ÿ ˆ Ëœ       ‌ †‎ â€â€“— ‘ ’
+‚“ †„ † ‡ ‰ ‹ › €
+</p></body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_ipv6.html b/dom/base/test/file_htmlserializer_ipv6.html
new file mode 100644
index 0000000000..298493e718
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_ipv6.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Testcase for IPv6 addresses</title>
+ <body>
+ <a href="http://[2001:4860:a003::68]/">Test</a>
+ </body></html> \ No newline at end of file
diff --git a/dom/base/test/file_htmlserializer_ipv6_out.html b/dom/base/test/file_htmlserializer_ipv6_out.html
new file mode 100644
index 0000000000..675a406d85
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_ipv6_out.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Testcase for IPv6 addresses</title>
+ </head><body>
+ <a href="http://[2001:4860:a003::68]/">Test</a>
+ </body></html> \ No newline at end of file
diff --git a/dom/base/test/file_inline_script.html b/dom/base/test/file_inline_script.html
new file mode 100644
index 0000000000..838c70f946
--- /dev/null
+++ b/dom/base/test/file_inline_script.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <script>window.scriptRan = true;</script>
+ <meta charset="utf-8">
+ <title></title>
+</head>
+<body>
+ <p>Hello Mochitest</p>
+</body>
+</html>
diff --git a/dom/base/test/file_inline_script.xhtml b/dom/base/test/file_inline_script.xhtml
new file mode 100644
index 0000000000..525daf29fe
--- /dev/null
+++ b/dom/base/test/file_inline_script.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script>window.scriptRan = true;</script>
+ <title/>
+</head>
+<body>
+ <p>Hello Mochitest</p>
+</body>
+</html>
diff --git a/dom/base/test/file_js_cache.html b/dom/base/test/file_js_cache.html
new file mode 100644
index 0000000000..6feb94d872
--- /dev/null
+++ b/dom/base/test/file_js_cache.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_js_cache.js b/dom/base/test/file_js_cache.js
new file mode 100644
index 0000000000..b9b966775c
--- /dev/null
+++ b/dom/base/test/file_js_cache.js
@@ -0,0 +1,5 @@
+function baz() {}
+function bar() {}
+function foo() { bar() }
+foo();
+
diff --git a/dom/base/test/file_js_cache_module.html b/dom/base/test/file_js_cache_module.html
new file mode 100644
index 0000000000..36d549c945
--- /dev/null
+++ b/dom/base/test/file_js_cache_module.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Load the script as a module</title>
+</head>
+<body>
+ <!-- crossorigin="use-credentials", because if we do an anonymous load that --
+ -- won't use the cache at all -->
+ <script id="watchme" src="file_js_cache.js" type="module"
+ crossorigin="use-credentials"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_js_cache_save_after_load.html b/dom/base/test/file_js_cache_save_after_load.html
new file mode 100644
index 0000000000..8a696c0026
--- /dev/null
+++ b/dom/base/test/file_js_cache_save_after_load.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Save the bytecode when all scripts are executed</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache_save_after_load.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_js_cache_save_after_load.js b/dom/base/test/file_js_cache_save_after_load.js
new file mode 100644
index 0000000000..7f5a20b524
--- /dev/null
+++ b/dom/base/test/file_js_cache_save_after_load.js
@@ -0,0 +1,15 @@
+function send_ping() {
+ window.dispatchEvent(new Event("ping"));
+}
+send_ping(); // ping (=1)
+
+window.addEventListener("load", function () {
+ send_ping(); // ping (=2)
+
+ // Append a script which should call |foo|, before the encoding of this script
+ // bytecode.
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.innerText = "send_ping();"; // ping (=3)
+ document.head.appendChild(script);
+});
diff --git a/dom/base/test/file_js_cache_syntax_error.html b/dom/base/test/file_js_cache_syntax_error.html
new file mode 100644
index 0000000000..cc4a9b2daa
--- /dev/null
+++ b/dom/base/test/file_js_cache_syntax_error.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Do not save bytecode on compilation errors</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache_syntax_error.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_js_cache_syntax_error.js b/dom/base/test/file_js_cache_syntax_error.js
new file mode 100644
index 0000000000..fcf587ae70
--- /dev/null
+++ b/dom/base/test/file_js_cache_syntax_error.js
@@ -0,0 +1 @@
+var // SyntaxError: missing variable name.
diff --git a/dom/base/test/file_js_cache_with_sri.html b/dom/base/test/file_js_cache_with_sri.html
new file mode 100644
index 0000000000..38ecb26984
--- /dev/null
+++ b/dom/base/test/file_js_cache_with_sri.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache.js"
+ integrity="sha384-8YSwN2ywq1SVThihWhj7uTVZ4UeIDwo3GgdPYnug+C+OS0oa6kH2IXBclwMaDJFb">
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_location_href_unknown_protocol.html b/dom/base/test/file_location_href_unknown_protocol.html
new file mode 100644
index 0000000000..10c994fbde
--- /dev/null
+++ b/dom/base/test/file_location_href_unknown_protocol.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<script>
+onbeforeunload = function() {
+ opener.onChildBeforeUnload();
+};
+onload = function() {
+ location.href = "this-protocol-is-unlikely-to-exist://foo";
+ setTimeout(function() {
+ opener.onChildLoadTimedOut();
+ }, 1000);
+};
+onunload = function() {
+ opener.onChildUnload();
+};
+</script>
diff --git a/dom/base/test/file_lock_orientation_with_pending_fullscreen.html b/dom/base/test/file_lock_orientation_with_pending_fullscreen.html
new file mode 100644
index 0000000000..07af0fc67d
--- /dev/null
+++ b/dom/base/test/file_lock_orientation_with_pending_fullscreen.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+const ok = window.parent.ok;
+const SpecialPowers = window.parent.SpecialPowers;
+
+async function runTest() {
+ SpecialPowers.wrap(document.documentElement).requestFullscreen();
+ const currentType = window.screen.orientation.type;
+ const lockPromise = window.screen.orientation.lock(currentType.startsWith("landscape") ? "portrait" : "landscape");
+ ok(true, "lock orientation doesn't throw error at this time");
+ // This document will be detached by "pending" message.
+ parent.postMessage("pending", "*");
+ await lockPromise;
+}
+</script>
+<body onload="runTest()">
+</body>
+</html>
diff --git a/dom/base/test/file_messagemanager_unload.html b/dom/base/test/file_messagemanager_unload.html
new file mode 100644
index 0000000000..f01e60bae2
--- /dev/null
+++ b/dom/base/test/file_messagemanager_unload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe id="frame" src="empty.html"></iframe>
+ </body>
+</html>
diff --git a/dom/base/test/file_module_js_cache.html b/dom/base/test/file_module_js_cache.html
new file mode 100644
index 0000000000..ecf14903d9
--- /dev/null
+++ b/dom/base/test/file_module_js_cache.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag module script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_module_js_cache.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_module_js_cache.js b/dom/base/test/file_module_js_cache.js
new file mode 100644
index 0000000000..1b08386c81
--- /dev/null
+++ b/dom/base/test/file_module_js_cache.js
@@ -0,0 +1,6 @@
+function baz() {}
+function bar() {}
+function foo() {
+ bar();
+}
+foo();
diff --git a/dom/base/test/file_module_js_cache_no_module.html b/dom/base/test/file_module_js_cache_no_module.html
new file mode 100644
index 0000000000..fd5e23fb37
--- /dev/null
+++ b/dom/base/test/file_module_js_cache_no_module.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Load the module script as a regular script</title>
+</head>
+<body>
+ <script id="watchme" src="file_module_js_cache.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_module_js_cache_with_sri.html b/dom/base/test/file_module_js_cache_with_sri.html
new file mode 100644
index 0000000000..60b2ca4f7a
--- /dev/null
+++ b/dom/base/test/file_module_js_cache_with_sri.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag module script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_module_js_cache.js"
+ integrity="sha384-1eNjxPXQdMh9pdr8ctqxBCIqiAnwYWzCCIKpcEIWp2MjECaRYx5Iw1TC3p/dx/uj">
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_mozfiledataurl_img.jpg b/dom/base/test/file_mozfiledataurl_img.jpg
new file mode 100644
index 0000000000..dcd99b9670
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_img.jpg
Binary files differ
diff --git a/dom/base/test/file_navigator_resolve_identity_xrays.xhtml b/dom/base/test/file_navigator_resolve_identity_xrays.xhtml
new file mode 100644
index 0000000000..bdce7c7b64
--- /dev/null
+++ b/dom/base/test/file_navigator_resolve_identity_xrays.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=985827
+-->
+<window title="Mozilla Bug 985827"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <iframe id="t"></iframe>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 985827 **/
+
+ addLoadEvent(function() {
+ var ok = parent.ok;
+ var is = parent.is;
+
+ var nav = document.getElementById("t").contentWindow.navigator;
+
+ ok(Cu.isXrayWrapper(nav), "Should have an Xray here");
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/file_receiveMessage.html b/dom/base/test/file_receiveMessage.html
new file mode 100644
index 0000000000..66f421270f
--- /dev/null
+++ b/dom/base/test/file_receiveMessage.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.onmessage = event => {
+ document.body.textContent = `${event.origin}|${event.data}`;
+ };
+ </script>
+</head>
+
+<body></body>
+</html>
+
diff --git a/dom/base/test/file_restrictedEventSource.sjs b/dom/base/test/file_restrictedEventSource.sjs
new file mode 100644
index 0000000000..b7ca11002a
--- /dev/null
+++ b/dom/base/test/file_restrictedEventSource.sjs
@@ -0,0 +1,69 @@
+function handleRequest(request, response) {
+ if (
+ (request.queryString == "test=user1_xhr" &&
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == "Basic dXNlciAxOnBhc3N3b3JkIDE=") ||
+ (request.queryString == "test=user1_evtsrc" &&
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == "Basic dXNlciAxOnBhc3N3b3JkIDE=")
+ ) {
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/event-stream", false);
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ "http://mochi.test:8888",
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
+ if (request.queryString == "test=user1_xhr") {
+ response.setHeader("Set-Cookie", "test=5c", false);
+ }
+ response.write("event: message\ndata: 1\n\n");
+ } else if (
+ (request.queryString == "test=user2_xhr" &&
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == "Basic dXNlciAyOnBhc3N3b3JkIDI=") ||
+ (request.queryString == "test=user2_evtsrc" &&
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == "Basic dXNlciAyOnBhc3N3b3JkIDI=" &&
+ request.hasHeader("Cookie") &&
+ request.getHeader("Cookie") == "test=5d")
+ ) {
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/event-stream", false);
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ "http://mochi.test:8888",
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
+ if (request.queryString == "test=user2_xhr") {
+ response.setHeader("Set-Cookie", "test=5d", false);
+ }
+ response.write("event: message\ndata: 1\n\n");
+ } else if (
+ request.queryString == "test=user1_xhr" ||
+ request.queryString == "test=user2_xhr"
+ ) {
+ response.setStatusLine(null, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'basic realm="restricted"', false);
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ "http://mochi.test:8888",
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ response.write("Unauthorized");
+ } else {
+ response.setStatusLine(null, 403, "Forbidden");
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ "http://mochi.test:8888",
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ response.write("Forbidden");
+ }
+}
diff --git a/dom/base/test/file_sandbox_and_document_uri.html b/dom/base/test/file_sandbox_and_document_uri.html
new file mode 100644
index 0000000000..ac23cd1fec
--- /dev/null
+++ b/dom/base/test/file_sandbox_and_document_uri.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/ChromeTask.js"></script>
+ <script>
+ function test() {
+ location.hash = "foobar"
+ if (!document.documentURI.includes("#foobar")) {
+ opener.postMessage("Wrong documentURI", "*");
+ window.close();
+ } else {
+ ChromeTask.spawn(null, () => {
+ return {
+ documentURI: actorParent.documentURI.spec,
+ principalURI: actorParent.documentPrincipal.URI.spec
+ };
+ }).then((uriAndPrincipal) => {
+ if (!uriAndPrincipal.documentURI.includes("#foobar")) {
+ opener.postMessage("Wrong documentURI in the parent process", "*");
+ } else if (!uriAndPrincipal.principalURI.includes("moz-nullprincipal")) {
+ opener.postMessage("Wrong document principal in the parent process", "*");
+ } else {
+ opener.postMessage("done", "*");
+ }
+ window.close();
+ });
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test)">
+ </body>
+</html>
diff --git a/dom/base/test/file_script.js b/dom/base/test/file_script.js
new file mode 100644
index 0000000000..3e15525fa6
--- /dev/null
+++ b/dom/base/test/file_script.js
@@ -0,0 +1 @@
+window.scriptRan = true;
diff --git a/dom/base/test/file_script_module_dynamic_and_element.html b/dom/base/test/file_script_module_dynamic_and_element.html
new file mode 100644
index 0000000000..dce909fcf9
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_element.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_dynamic_and_element.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_dynamic_and_element.js b/dom/base/test/file_script_module_dynamic_and_element.js
new file mode 100644
index 0000000000..4f5c707a4c
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_element.js
@@ -0,0 +1,19 @@
+const { f } = await import(
+ "./file_script_module_dynamic_and_element_imported_1.js"
+);
+import { g } from "./file_script_module_dynamic_and_element_imported_2.js";
+import { h } from "./file_script_module_dynamic_and_element_imported_3.js";
+f();
+g();
+h();
+
+let script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute(
+ "src",
+ "file_script_module_dynamic_and_element_imported_1.js"
+);
+document.body.appendChild(script);
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_dynamic_and_element_imported_1.js b/dom/base/test/file_script_module_dynamic_and_element_imported_1.js
new file mode 100644
index 0000000000..0be3913c65
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_element_imported_1.js
@@ -0,0 +1,6 @@
+import { g } from "./file_script_module_dynamic_and_element_imported_2.js";
+import { h } from "./file_script_module_dynamic_and_element_imported_3.js";
+g();
+h();
+
+export function f() {}
diff --git a/dom/base/test/file_script_module_dynamic_and_element_imported_2.js b/dom/base/test/file_script_module_dynamic_and_element_imported_2.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_element_imported_2.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_dynamic_and_element_imported_3.js b/dom/base/test/file_script_module_dynamic_and_element_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_element_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_script_module_dynamic_and_static.html b/dom/base/test/file_script_module_dynamic_and_static.html
new file mode 100644
index 0000000000..2a19772af1
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_static.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_dynamic_and_static.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_dynamic_and_static.js b/dom/base/test/file_script_module_dynamic_and_static.js
new file mode 100644
index 0000000000..3e2a50b7ab
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_static.js
@@ -0,0 +1,10 @@
+const { f } = await import(
+ "./file_script_module_dynamic_and_static_imported_1.js"
+);
+import { g } from "./file_script_module_dynamic_and_static_imported_2.js";
+import { h } from "./file_script_module_dynamic_and_static_imported_3.js";
+f();
+g();
+h();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_dynamic_and_static_imported_1.js b/dom/base/test/file_script_module_dynamic_and_static_imported_1.js
new file mode 100644
index 0000000000..671704ea17
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_static_imported_1.js
@@ -0,0 +1,4 @@
+import { h } from "./file_script_module_dynamic_and_static_imported_3.js";
+h();
+
+export function f() {}
diff --git a/dom/base/test/file_script_module_dynamic_and_static_imported_2.js b/dom/base/test/file_script_module_dynamic_and_static_imported_2.js
new file mode 100644
index 0000000000..e1a0050bce
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_static_imported_2.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_dynamic_and_static_imported_1.js";
+f();
+
+export function g() {}
diff --git a/dom/base/test/file_script_module_dynamic_and_static_imported_3.js b/dom/base/test/file_script_module_dynamic_and_static_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_and_static_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_script_module_dynamic_import.html b/dom/base/test/file_script_module_dynamic_import.html
new file mode 100644
index 0000000000..574e7f8411
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_import.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_dynamic_import.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_dynamic_import.js b/dom/base/test/file_script_module_dynamic_import.js
new file mode 100644
index 0000000000..c0ae8cf5d2
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_import.js
@@ -0,0 +1,4 @@
+const { f } = await import("./file_script_module_dynamic_import_imported.js");
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_dynamic_import_imported.js b/dom/base/test/file_script_module_dynamic_import_imported.js
new file mode 100644
index 0000000000..8b38a11158
--- /dev/null
+++ b/dom/base/test/file_script_module_dynamic_import_imported.js
@@ -0,0 +1 @@
+export function f() {}
diff --git a/dom/base/test/file_script_module_element_and_dynamic.html b/dom/base/test/file_script_module_element_and_dynamic.html
new file mode 100644
index 0000000000..178e76d797
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_dynamic.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_element_and_dynamic_imported_1.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_element_and_dynamic.js b/dom/base/test/file_script_module_element_and_dynamic.js
new file mode 100644
index 0000000000..6bd59930bb
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_dynamic.js
@@ -0,0 +1,10 @@
+const { f } = await import(
+ "./file_script_module_element_and_dynamic_imported_1.js"
+);
+import { g } from "./file_script_module_element_and_dynamic_imported_2.js";
+import { h } from "./file_script_module_element_and_dynamic_imported_3.js";
+f();
+g();
+h();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_element_and_dynamic_imported_1.js b/dom/base/test/file_script_module_element_and_dynamic_imported_1.js
new file mode 100644
index 0000000000..d56c222120
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_dynamic_imported_1.js
@@ -0,0 +1,12 @@
+import { g } from "./file_script_module_element_and_dynamic_imported_2.js";
+import { h } from "./file_script_module_element_and_dynamic_imported_3.js";
+g();
+h();
+
+export function f() {}
+
+let script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_element_and_dynamic.js");
+document.body.appendChild(script);
diff --git a/dom/base/test/file_script_module_element_and_dynamic_imported_2.js b/dom/base/test/file_script_module_element_and_dynamic_imported_2.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_dynamic_imported_2.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_element_and_dynamic_imported_3.js b/dom/base/test/file_script_module_element_and_dynamic_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_dynamic_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_script_module_element_and_import.html b/dom/base/test/file_script_module_element_and_import.html
new file mode 100644
index 0000000000..97d67f129e
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_import.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_element_and_import_imported_1.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_element_and_import.js b/dom/base/test/file_script_module_element_and_import.js
new file mode 100644
index 0000000000..18d0f57727
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_import.js
@@ -0,0 +1,8 @@
+import { f } from "./file_script_module_element_and_import_imported_1.js";
+import { g } from "./file_script_module_element_and_import_imported_2.js";
+import { h } from "./file_script_module_element_and_import_imported_3.js";
+f();
+g();
+h();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_element_and_import_imported_1.js b/dom/base/test/file_script_module_element_and_import_imported_1.js
new file mode 100644
index 0000000000..a257a40da9
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_import_imported_1.js
@@ -0,0 +1,12 @@
+import { g } from "./file_script_module_element_and_import_imported_2.js";
+import { h } from "./file_script_module_element_and_import_imported_3.js";
+g();
+h();
+
+export function f() {}
+
+let script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_element_and_import.js");
+document.body.appendChild(script);
diff --git a/dom/base/test/file_script_module_element_and_import_imported_2.js b/dom/base/test/file_script_module_element_and_import_imported_2.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_import_imported_2.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_element_and_import_imported_3.js b/dom/base/test/file_script_module_element_and_import_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_element_and_import_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_script_module_frames_dynamic.html b/dom/base/test/file_script_module_frames_dynamic.html
new file mode 100644
index 0000000000..9f07e094fa
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+ <script type="text/javascript">
+var loadFramePromiseResolve;
+var loadFramePromise = new Promise(r => {
+ loadFramePromiseResolve = r;
+});
+function onLoadFrameLoaded() {
+ loadFramePromiseResolve();
+}
+ </script>
+</head>
+<body>
+ <iframe id="save" name="save"
+ src="file_script_module_frames_dynamic_save.html"
+ width="50" height="50"></iframe>
+ <iframe id="load" name="load" onload="onLoadFrameLoaded()"
+ src="file_script_module_frames_dynamic_load.html"
+ width="50" height="50"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_dynamic_load.html b/dom/base/test/file_script_module_frames_dynamic_load.html
new file mode 100644
index 0000000000..eebb0fa36e
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic_load.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+function doLoad() {
+ const script = document.createElement("script");
+ script.id = "watchme";
+ script.setAttribute("type", "module");
+ script.setAttribute("src", "file_script_module_frames_dynamic_load.js");
+ document.body.appendChild(script);
+}
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_dynamic_load.js b/dom/base/test/file_script_module_frames_dynamic_load.js
new file mode 100644
index 0000000000..ba14638f8c
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic_load.js
@@ -0,0 +1,4 @@
+const { f } = await import("./file_script_module_frames_dynamic_shared.js");
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_frames_dynamic_save.html b/dom/base/test/file_script_module_frames_dynamic_save.html
new file mode 100644
index 0000000000..64d9e01482
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic_save.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+// Dynamically insert the script element so that the load doesn't happen
+// before the event relay handler is set up.
+const script = document.createElement("script");
+script.id = "watchme";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_frames_dynamic_save.js");
+document.body.appendChild(script);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_dynamic_save.js b/dom/base/test/file_script_module_frames_dynamic_save.js
new file mode 100644
index 0000000000..ba14638f8c
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic_save.js
@@ -0,0 +1,4 @@
+const { f } = await import("./file_script_module_frames_dynamic_shared.js");
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_frames_dynamic_shared.js b/dom/base/test/file_script_module_frames_dynamic_shared.js
new file mode 100644
index 0000000000..8b38a11158
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_dynamic_shared.js
@@ -0,0 +1 @@
+export function f() {}
diff --git a/dom/base/test/file_script_module_frames_element.html b/dom/base/test/file_script_module_frames_element.html
new file mode 100644
index 0000000000..aec8dfa5f0
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_element.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+ <script type="text/javascript">
+var loadFramePromiseResolve;
+var loadFramePromise = new Promise(r => {
+ loadFramePromiseResolve = r;
+});
+function onLoadFrameLoaded() {
+ loadFramePromiseResolve();
+}
+ </script>
+</head>
+<body>
+ <iframe id="save" name="save"
+ src="file_script_module_frames_element_save.html"
+ width="50" height="50"></iframe>
+ <iframe id="load" name="load" onload="onLoadFrameLoaded()"
+ src="file_script_module_frames_element_load.html"
+ width="50" height="50"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_element_load.html b/dom/base/test/file_script_module_frames_element_load.html
new file mode 100644
index 0000000000..940d9ad599
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_element_load.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+function doLoad() {
+ const script = document.createElement("script");
+ script.id = "watchme";
+ script.setAttribute("type", "module");
+ script.setAttribute("src", "file_script_module_frames_element_shared.js");
+ document.body.appendChild(script);
+}
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_element_save.html b/dom/base/test/file_script_module_frames_element_save.html
new file mode 100644
index 0000000000..b665416676
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_element_save.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+// Dynamically insert the script element so that the load doesn't happen
+// before the event relay handler is set up.
+const script = document.createElement("script");
+script.id = "watchme";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_frames_element_shared.js");
+document.body.appendChild(script);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_element_shared.js b/dom/base/test/file_script_module_frames_element_shared.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_element_shared.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_frames_import.html b/dom/base/test/file_script_module_frames_import.html
new file mode 100644
index 0000000000..a71d372724
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+ <script type="text/javascript">
+var loadFramePromiseResolve;
+var loadFramePromise = new Promise(r => {
+ loadFramePromiseResolve = r;
+});
+function onLoadFrameLoaded() {
+ loadFramePromiseResolve();
+}
+ </script>
+</head>
+<body>
+ <iframe id="save" name="save"
+ src="file_script_module_frames_import_save.html"
+ width="50" height="50"></iframe>
+ <iframe id="load" name="load" onload="onLoadFrameLoaded()"
+ src="file_script_module_frames_import_load.html"
+ width="50" height="50"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_import_load.html b/dom/base/test/file_script_module_frames_import_load.html
new file mode 100644
index 0000000000..d340e6c040
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import_load.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+function doLoad() {
+ const script = document.createElement("script");
+ script.id = "watchme";
+ script.setAttribute("type", "module");
+ script.setAttribute("src", "file_script_module_frames_import_load.js");
+ document.body.appendChild(script);
+}
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_import_load.js b/dom/base/test/file_script_module_frames_import_load.js
new file mode 100644
index 0000000000..3bae471015
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import_load.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_frames_import_shared.js";
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_frames_import_save.html b/dom/base/test/file_script_module_frames_import_save.html
new file mode 100644
index 0000000000..532745d444
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import_save.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode across iframe</title>
+</head>
+<body>
+ <script type="text/javascript" src="file_script_module_frames_relay.js"></script>
+ <script type="text/javascript">
+// Dynamically insert the script element so that the load doesn't happen
+// before the event relay handler is set up.
+const script = document.createElement("script");
+script.id = "watchme";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_frames_import_save.js");
+document.body.appendChild(script);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_frames_import_save.js b/dom/base/test/file_script_module_frames_import_save.js
new file mode 100644
index 0000000000..3bae471015
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import_save.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_frames_import_shared.js";
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_frames_import_shared.js b/dom/base/test/file_script_module_frames_import_shared.js
new file mode 100644
index 0000000000..8b38a11158
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_import_shared.js
@@ -0,0 +1 @@
+export function f() {}
diff --git a/dom/base/test/file_script_module_frames_relay.js b/dom/base/test/file_script_module_frames_relay.js
new file mode 100644
index 0000000000..141f202962
--- /dev/null
+++ b/dom/base/test/file_script_module_frames_relay.js
@@ -0,0 +1,22 @@
+function relay(event) {
+ if (event.type != "test_evaluated") {
+ if (!/^watchme/.test(event.target.id)) {
+ return;
+ }
+ }
+
+ const type = `${window.name}_${event.type}`;
+
+ window.parent.dispatchEvent(new window.parent.Event(type));
+}
+
+window.addEventListener("scriptloader_load_source", relay);
+window.addEventListener("scriptloader_load_bytecode", relay);
+window.addEventListener("scriptloader_execute", relay);
+window.addEventListener("scriptloader_evaluate_module", relay);
+window.addEventListener("scriptloader_encode", relay);
+window.addEventListener("scriptloader_no_encode", relay);
+window.addEventListener("scriptloader_bytecode_saved", relay);
+window.addEventListener("scriptloader_bytecode_failed", relay);
+window.addEventListener("scriptloader_fallback", relay);
+window.addEventListener("test_evaluated", relay);
diff --git a/dom/base/test/file_script_module_import.html b/dom/base/test/file_script_module_import.html
new file mode 100644
index 0000000000..f298586da7
--- /dev/null
+++ b/dom/base/test/file_script_module_import.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_import.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_import.js b/dom/base/test/file_script_module_import.js
new file mode 100644
index 0000000000..d0c92afc66
--- /dev/null
+++ b/dom/base/test/file_script_module_import.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_import_imported.js";
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_import_and_element.html b/dom/base/test/file_script_module_import_and_element.html
new file mode 100644
index 0000000000..de5754eab2
--- /dev/null
+++ b/dom/base/test/file_script_module_import_and_element.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_import_and_element.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_import_and_element.js b/dom/base/test/file_script_module_import_and_element.js
new file mode 100644
index 0000000000..685d0199b2
--- /dev/null
+++ b/dom/base/test/file_script_module_import_and_element.js
@@ -0,0 +1,17 @@
+import { f } from "./file_script_module_import_and_element_imported_1.js";
+import { g } from "./file_script_module_import_and_element_imported_2.js";
+import { h } from "./file_script_module_import_and_element_imported_3.js";
+f();
+g();
+h();
+
+let script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute(
+ "src",
+ "file_script_module_import_and_element_imported_1.js"
+);
+document.body.appendChild(script);
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_import_and_element_imported_1.js b/dom/base/test/file_script_module_import_and_element_imported_1.js
new file mode 100644
index 0000000000..4413bfbf85
--- /dev/null
+++ b/dom/base/test/file_script_module_import_and_element_imported_1.js
@@ -0,0 +1,6 @@
+import { g } from "./file_script_module_import_and_element_imported_2.js";
+import { h } from "./file_script_module_import_and_element_imported_3.js";
+g();
+h();
+
+export function f() {}
diff --git a/dom/base/test/file_script_module_import_and_element_imported_2.js b/dom/base/test/file_script_module_import_and_element_imported_2.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_import_and_element_imported_2.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_import_and_element_imported_3.js b/dom/base/test/file_script_module_import_and_element_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_import_and_element_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_script_module_import_imported.js b/dom/base/test/file_script_module_import_imported.js
new file mode 100644
index 0000000000..8b38a11158
--- /dev/null
+++ b/dom/base/test/file_script_module_import_imported.js
@@ -0,0 +1 @@
+export function f() {}
diff --git a/dom/base/test/file_script_module_import_multi.html b/dom/base/test/file_script_module_import_multi.html
new file mode 100644
index 0000000000..5636bd0f2d
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_import_multi.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_import_multi.js b/dom/base/test/file_script_module_import_multi.js
new file mode 100644
index 0000000000..54d7855d64
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi.js
@@ -0,0 +1,6 @@
+import { f } from "./file_script_module_import_multi_imported_once.js";
+import { g } from "./file_script_module_import_multi_imported_twice.js";
+f();
+g();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_import_multi_elems.html b/dom/base/test/file_script_module_import_multi_elems.html
new file mode 100644
index 0000000000..42edd06c52
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_import_multi_elems_1.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_import_multi_elems_1.js b/dom/base/test/file_script_module_import_multi_elems_1.js
new file mode 100644
index 0000000000..11648d3d1a
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_1.js
@@ -0,0 +1,14 @@
+import { f } from "./file_script_module_import_multi_elems_imported_once_1.js";
+import { h } from "./file_script_module_import_multi_elems_imported_twice.js";
+f();
+h();
+
+// Dynamically insert the element after loading all source, so that
+// the module import doesn't race.
+const script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute("src", "file_script_module_import_multi_elems_2.js");
+document.body.appendChild(script);
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_import_multi_elems_2.js b/dom/base/test/file_script_module_import_multi_elems_2.js
new file mode 100644
index 0000000000..062c263fc4
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_2.js
@@ -0,0 +1,6 @@
+import { g } from "./file_script_module_import_multi_elems_imported_once_2.js";
+import { h } from "./file_script_module_import_multi_elems_imported_twice.js";
+g();
+h();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_import_multi_elems_imported_once_1.js b/dom/base/test/file_script_module_import_multi_elems_imported_once_1.js
new file mode 100644
index 0000000000..8b38a11158
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_imported_once_1.js
@@ -0,0 +1 @@
+export function f() {}
diff --git a/dom/base/test/file_script_module_import_multi_elems_imported_once_2.js b/dom/base/test/file_script_module_import_multi_elems_imported_once_2.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_imported_once_2.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_import_multi_elems_imported_once_3.js b/dom/base/test/file_script_module_import_multi_elems_imported_once_3.js
new file mode 100644
index 0000000000..411595cafc
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_imported_once_3.js
@@ -0,0 +1 @@
+export function i() {}
diff --git a/dom/base/test/file_script_module_import_multi_elems_imported_twice.js b/dom/base/test/file_script_module_import_multi_elems_imported_twice.js
new file mode 100644
index 0000000000..fd0ddd25b4
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_elems_imported_twice.js
@@ -0,0 +1,3 @@
+import { i } from "./file_script_module_import_multi_elems_imported_once_3.js";
+i();
+export function h() {}
diff --git a/dom/base/test/file_script_module_import_multi_imported_once.js b/dom/base/test/file_script_module_import_multi_imported_once.js
new file mode 100644
index 0000000000..448c6641e5
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_imported_once.js
@@ -0,0 +1,4 @@
+import { g } from "./file_script_module_import_multi_imported_twice.js";
+g();
+
+export function f() {}
diff --git a/dom/base/test/file_script_module_import_multi_imported_twice.js b/dom/base/test/file_script_module_import_multi_imported_twice.js
new file mode 100644
index 0000000000..f75e8c9b21
--- /dev/null
+++ b/dom/base/test/file_script_module_import_multi_imported_twice.js
@@ -0,0 +1 @@
+export function g() {}
diff --git a/dom/base/test/file_script_module_single.html b/dom/base/test/file_script_module_single.html
new file mode 100644
index 0000000000..ec2c6f217c
--- /dev/null
+++ b/dom/base/test/file_script_module_single.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_single.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_single.js b/dom/base/test/file_script_module_single.js
new file mode 100644
index 0000000000..9a5745f516
--- /dev/null
+++ b/dom/base/test/file_script_module_single.js
@@ -0,0 +1,8 @@
+function baz() {}
+function bar() {}
+function foo() {
+ bar();
+}
+foo();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_basic.html b/dom/base/test/file_script_module_sri_basic.html
new file mode 100644
index 0000000000..743bcafbc7
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_basic.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_basic.js"
+ integrity="sha384-pZxhwO9umoHSuKzSRL6hi9YhRb70RfGdRnchu/zp5gbUCOC/x7NAWUPxxuv0DJoZ"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_basic.js b/dom/base/test/file_script_module_sri_basic.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_basic.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_basic_prep.html b/dom/base/test/file_script_module_sri_basic_prep.html
new file mode 100644
index 0000000000..743bcafbc7
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_basic_prep.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_basic.js"
+ integrity="sha384-pZxhwO9umoHSuKzSRL6hi9YhRb70RfGdRnchu/zp5gbUCOC/x7NAWUPxxuv0DJoZ"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem.html b/dom/base/test/file_script_module_sri_dynamic_elem.html
new file mode 100644
index 0000000000..a340f5f612
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_dynamic_elem.js"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_dynamic_elem_imported.js"
+ integrity="sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem.js b/dom/base/test/file_script_module_sri_dynamic_elem.js
new file mode 100644
index 0000000000..b68c9bb80c
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem.js
@@ -0,0 +1,4 @@
+const { f } = await import("./file_script_module_sri_dynamic_elem_imported.js");
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_imported.js b/dom/base/test/file_script_module_sri_dynamic_elem_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.html b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.html
new file mode 100644
index 0000000000..3f0a5f8c7b
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_dynamic_elem_nopreload.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.js b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.js
new file mode 100644
index 0000000000..5139b0c34d
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload.js
@@ -0,0 +1,20 @@
+const { f } = await import(
+ "./file_script_module_sri_dynamic_elem_nopreload_imported.js"
+);
+f();
+
+// Dynamically insert the script element in order to suppress preload.
+const script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute(
+ "src",
+ "file_script_module_sri_dynamic_elem_nopreload_imported.js"
+);
+script.setAttribute(
+ "integrity",
+ "sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"
+);
+document.body.appendChild(script);
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_imported.js b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_prep.html b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_prep.html
new file mode 100644
index 0000000000..da70674200
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_nopreload_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_dynamic_elem_nopreload_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_dynamic_elem_prep.html b/dom/base/test/file_script_module_sri_dynamic_elem_prep.html
new file mode 100644
index 0000000000..598e8519fa
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_dynamic_elem_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_dynamic_elem_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_dynamic.html b/dom/base/test/file_script_module_sri_elem_dynamic.html
new file mode 100644
index 0000000000..00741ca010
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_dynamic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_elem_dynamic_imported.js"
+ integrity="sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_elem_dynamic.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_dynamic.js b/dom/base/test/file_script_module_sri_elem_dynamic.js
new file mode 100644
index 0000000000..597038a066
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_dynamic.js
@@ -0,0 +1,4 @@
+const { f } = await import("./file_script_module_sri_elem_dynamic_imported.js");
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_dynamic_imported.js b/dom/base/test/file_script_module_sri_elem_dynamic_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_dynamic_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_dynamic_prep.html b/dom/base/test/file_script_module_sri_elem_dynamic_prep.html
new file mode 100644
index 0000000000..607fc0634f
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_dynamic_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_elem_dynamic_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_elem_1.html b/dom/base/test/file_script_module_sri_elem_elem_1.html
new file mode 100644
index 0000000000..38312d7245
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_elem_elem_1.js"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_elem_elem_1.js"
+ integrity="sha384-pZxhwO9umoHSuKzSRL6hi9YhRb70RfGdRnchu/zp5gbUCOC/x7NAWUPxxuv0DJoZ"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_elem_1.js b/dom/base/test/file_script_module_sri_elem_elem_1.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_1.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_elem_1_prep.html b/dom/base/test/file_script_module_sri_elem_elem_1_prep.html
new file mode 100644
index 0000000000..4617920deb
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_1_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_elem_elem_1.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_elem_2.html b/dom/base/test/file_script_module_sri_elem_elem_2.html
new file mode 100644
index 0000000000..b39d018d8e
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_elem_elem_2.js"
+ integrity="sha384-pZxhwO9umoHSuKzSRL6hi9YhRb70RfGdRnchu/zp5gbUCOC/x7NAWUPxxuv0DJoZ"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_elem_elem_2.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_elem_2.js b/dom/base/test/file_script_module_sri_elem_elem_2.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_2.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_elem_2_prep.html b/dom/base/test/file_script_module_sri_elem_elem_2_prep.html
new file mode 100644
index 0000000000..600b7607cb
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_elem_2_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_elem_elem_2.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_import.html b/dom/base/test/file_script_module_sri_elem_import.html
new file mode 100644
index 0000000000..22e2712f21
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_import.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_elem_import_imported.js"
+ integrity="sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_elem_import.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_elem_import.js b/dom/base/test/file_script_module_sri_elem_import.js
new file mode 100644
index 0000000000..5ecd8e4704
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_import.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_sri_elem_import_imported.js";
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_import_imported.js b/dom/base/test/file_script_module_sri_elem_import_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_import_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_elem_import_prep.html b/dom/base/test/file_script_module_sri_elem_import_prep.html
new file mode 100644
index 0000000000..3b23ab7f3f
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_elem_import_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_elem_import_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_fallback.html b/dom/base/test/file_script_module_sri_fallback.html
new file mode 100644
index 0000000000..5d54141816
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_fallback.js"
+ integrity="sha384-pZxhwO9umoHSuKzSRL6hi9YhRb70RfGdRnchu/zp5gbUCOC/x7NAWUPxxuv0DJoZ"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_fallback.js b/dom/base/test/file_script_module_sri_fallback.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_fallback_failure.html b/dom/base/test/file_script_module_sri_fallback_failure.html
new file mode 100644
index 0000000000..01eb0028f5
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback_failure.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_fallback_failure.js"
+ integrity="sha384-wronghash"></script>
+ <script type="text/javascript">
+// The above script isn't evaluated because of wrong integrity.
+window.dispatchEvent(new Event("test_evaluated"));
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_fallback_failure.js b/dom/base/test/file_script_module_sri_fallback_failure.js
new file mode 100644
index 0000000000..741b76cd09
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback_failure.js
@@ -0,0 +1 @@
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_fallback_failure_prep.html b/dom/base/test/file_script_module_sri_fallback_failure_prep.html
new file mode 100644
index 0000000000..a66b18dff0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback_failure_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_fallback_failure.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_fallback_prep.html b/dom/base/test/file_script_module_sri_fallback_prep.html
new file mode 100644
index 0000000000..dd1b6a0cc5
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_fallback_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_fallback.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_import_elem.html b/dom/base/test/file_script_module_sri_import_elem.html
new file mode 100644
index 0000000000..e37c05ac49
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme1" type="module" src="file_script_module_sri_import_elem.js"></script>
+ <script id="watchme2" type="module" src="file_script_module_sri_import_elem_imported.js"
+ integrity="sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_import_elem.js b/dom/base/test/file_script_module_sri_import_elem.js
new file mode 100644
index 0000000000..36c43f4d58
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem.js
@@ -0,0 +1,4 @@
+import { f } from "./file_script_module_sri_import_elem_imported.js";
+f();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_import_elem_imported.js b/dom/base/test/file_script_module_sri_import_elem_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_import_elem_nopreload.html b/dom/base/test/file_script_module_sri_import_elem_nopreload.html
new file mode 100644
index 0000000000..5321288e33
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_nopreload.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body onload="onLoad()">
+ <script id="watchme" type="module" src="file_script_module_sri_import_elem_nopreload.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_import_elem_nopreload.js b/dom/base/test/file_script_module_sri_import_elem_nopreload.js
new file mode 100644
index 0000000000..21606f7a8c
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_nopreload.js
@@ -0,0 +1,18 @@
+import { f } from "./file_script_module_sri_import_elem_nopreload_imported.js";
+f();
+
+// Dynamically insert the script element in order to suppress preload.
+const script = document.createElement("script");
+script.id = "watchme2";
+script.setAttribute("type", "module");
+script.setAttribute(
+ "src",
+ "file_script_module_sri_import_elem_nopreload_imported.js"
+);
+script.setAttribute(
+ "integrity",
+ "sha384-3XSIfAj4/GALfWzL3T89+t3eaLIY59g8IWz1qq59xKnEW3aGd4cz7XvdcYqoK2+J"
+);
+document.body.appendChild(script);
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_import_elem_nopreload_imported.js b/dom/base/test/file_script_module_sri_import_elem_nopreload_imported.js
new file mode 100644
index 0000000000..bf624148c0
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_nopreload_imported.js
@@ -0,0 +1,3 @@
+export function f() {}
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_sri_import_elem_nopreload_prep.html b/dom/base/test/file_script_module_sri_import_elem_nopreload_prep.html
new file mode 100644
index 0000000000..7fa9b011f8
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_nopreload_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_import_elem_nopreload_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_sri_import_elem_prep.html b/dom/base/test/file_script_module_sri_import_elem_prep.html
new file mode 100644
index 0000000000..5abdb1d16d
--- /dev/null
+++ b/dom/base/test/file_script_module_sri_import_elem_prep.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode fallback</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_sri_import_elem_imported.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_static_and_dynamic.html b/dom/base/test/file_script_module_static_and_dynamic.html
new file mode 100644
index 0000000000..ae614c85c1
--- /dev/null
+++ b/dom/base/test/file_script_module_static_and_dynamic.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test module script bytecode</title>
+</head>
+<body>
+ <script id="watchme" type="module" src="file_script_module_static_and_dynamic.js"></script>
+</body>
+</html>
diff --git a/dom/base/test/file_script_module_static_and_dynamic.js b/dom/base/test/file_script_module_static_and_dynamic.js
new file mode 100644
index 0000000000..60766106af
--- /dev/null
+++ b/dom/base/test/file_script_module_static_and_dynamic.js
@@ -0,0 +1,8 @@
+import { f } from "./file_script_module_static_and_dynamic_imported_1.js";
+import { g } from "./file_script_module_static_and_dynamic_imported_2.js";
+import { h } from "./file_script_module_static_and_dynamic_imported_3.js";
+f();
+g();
+h();
+
+window.dispatchEvent(new Event("test_evaluated"));
diff --git a/dom/base/test/file_script_module_static_and_dynamic_imported_1.js b/dom/base/test/file_script_module_static_and_dynamic_imported_1.js
new file mode 100644
index 0000000000..420667f36f
--- /dev/null
+++ b/dom/base/test/file_script_module_static_and_dynamic_imported_1.js
@@ -0,0 +1,4 @@
+import { h } from "./file_script_module_static_and_dynamic_imported_3.js";
+h();
+
+export function f() {}
diff --git a/dom/base/test/file_script_module_static_and_dynamic_imported_2.js b/dom/base/test/file_script_module_static_and_dynamic_imported_2.js
new file mode 100644
index 0000000000..c171417531
--- /dev/null
+++ b/dom/base/test/file_script_module_static_and_dynamic_imported_2.js
@@ -0,0 +1,6 @@
+const { f } = await import(
+ "./file_script_module_static_and_dynamic_imported_1.js"
+);
+f();
+
+export function g() {}
diff --git a/dom/base/test/file_script_module_static_and_dynamic_imported_3.js b/dom/base/test/file_script_module_static_and_dynamic_imported_3.js
new file mode 100644
index 0000000000..6b340b7588
--- /dev/null
+++ b/dom/base/test/file_script_module_static_and_dynamic_imported_3.js
@@ -0,0 +1 @@
+export function h() {}
diff --git a/dom/base/test/file_serializer_noscript.html b/dom/base/test/file_serializer_noscript.html
new file mode 100644
index 0000000000..440baf036c
--- /dev/null
+++ b/dom/base/test/file_serializer_noscript.html
@@ -0,0 +1 @@
+<body><noscript>&lt;/noscript&gt;<img></noscript></body>
diff --git a/dom/base/test/file_setname.html b/dom/base/test/file_setname.html
new file mode 100644
index 0000000000..0830feb53a
--- /dev/null
+++ b/dom/base/test/file_setname.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ window.name = location.search.substring(1);
+ </script>
+ </head>
+</html>
diff --git a/dom/base/test/file_settimeout_inner.html b/dom/base/test/file_settimeout_inner.html
new file mode 100644
index 0000000000..cfc66dfffb
--- /dev/null
+++ b/dom/base/test/file_settimeout_inner.html
@@ -0,0 +1 @@
+<script>window.onload = function runTest1() { document.open(); setTimeout('parent.test1Done();'); document.close(); }</script>
diff --git a/dom/base/test/file_suppressed_events_and_scrolling.html b/dom/base/test/file_suppressed_events_and_scrolling.html
new file mode 100644
index 0000000000..edf6793dfb
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_and_scrolling.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+ <body><iframe srcdoc="
+ <html>
+ <head>
+ <script>
+ onload = function() {
+ // Ensure the layout is up-to-date and painted.
+ requestAnimationFrame(function() {
+ setTimeout(run);
+ })
+ }
+
+ function run() {
+ parent.opener.postMessage('doscroll', '*');
+ window.onscroll = function() {
+ parent.opener.postMessage('didscroll', '*');
+ }
+ let xhr = new XMLHttpRequest();
+ xhr.open('GET', 'slow.sjs', false);
+ xhr.send();
+ parent.opener.postMessage('xhr_done', '*');
+ }
+ </script>
+ </head>
+ <body style='height: 3000px; border: 1px solid black;'>
+ </body>
+ </html>
+ "></iframe></body>
+</html>
diff --git a/dom/base/test/file_suppressed_events_inner.html b/dom/base/test/file_suppressed_events_inner.html
new file mode 100644
index 0000000000..44cc45254d
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_inner.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test event suppression</title>
+</head>
+<body>
+<div>Inner</div>
+<script type="application/javascript">
+
+window.onload = function() {
+ top.postMessage("ready", "*");
+};
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_suppressed_events_middle.html b/dom/base/test/file_suppressed_events_middle.html
new file mode 100644
index 0000000000..86b1f05374
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_middle.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test event suppression</title>
+</head>
+<body>
+<div>Middle</div>
+<iframe src="http://mochi.test:8888/tests/dom/base/test/file_suppressed_events_inner.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_suppressed_events_top.html b/dom/base/test/file_suppressed_events_top.html
new file mode 100644
index 0000000000..ca031abb70
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_top.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test event suppression</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div>Top</div>
+<script type="application/javascript">
+
+function waitForMessage(aMsg, aCallback) {
+ window.addEventListener("message", function handler(e) {
+ if (e.data != aMsg) {
+ return;
+ }
+
+ info(`received: ${e.data}`);
+ window.removeEventListener("message", handler);
+ if (aCallback) {
+ aCallback(e);
+ }
+ });
+}
+
+function waitForClickEvent(aElement, aWindow) {
+ return new Promise((aResolve) => {
+ aElement.addEventListener("click", aResolve, { once: true });
+ synthesizeMouseAtCenter(aElement, { type: "mousedown" }, aWindow);
+ synthesizeMouseAtCenter(aElement, { type: "mouseup" }, aWindow);
+ });
+}
+
+waitForMessage("ready", async function(e) {
+ await waitUntilApzStable();
+
+ let innerWin = e.source;
+ let innerDiv = innerWin.document.querySelector("div");
+
+ let eventCount = 0;
+ innerDiv.addEventListener("mousemove", function() {
+ eventCount++;
+ });
+
+ // Test that event handling is suppressed.
+ let utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.suppressEventHandling(true);
+ const TOTAL = 100;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+ utils.suppressEventHandling(false);
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.ok(eventCount < TOTAL, "event should be suspressed");
+
+ // Test that event handling is not suppressed.
+ eventCount = 0;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.is(eventCount, TOTAL, "event should not be suspressed");
+
+ opener.postMessage("done", "*");
+});
+
+</script>
+<iframe src="http://example.org/tests/dom/base/test/file_suppressed_events_middle.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_suppressed_events_top_modalstate.html b/dom/base/test/file_suppressed_events_top_modalstate.html
new file mode 100644
index 0000000000..e20938bb28
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_top_modalstate.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test event suppression</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div>Top</div>
+<script type="application/javascript">
+
+function waitForMessage(aMsg, aCallback) {
+ window.addEventListener("message", function handler(e) {
+ if (e.data != aMsg) {
+ return;
+ }
+
+ info(`received: ${e.data}`);
+ window.removeEventListener("message", handler);
+ if (aCallback) {
+ aCallback(e);
+ }
+ });
+}
+
+function waitForClickEvent(aElement, aWindow) {
+ return new Promise((aResolve) => {
+ aElement.addEventListener("click", aResolve, { once: true });
+ synthesizeMouseAtCenter(aElement, { type: "mousedown" }, aWindow);
+ synthesizeMouseAtCenter(aElement, { type: "mouseup" }, aWindow);
+ });
+}
+
+waitForMessage("ready", async function(e) {
+ await waitUntilApzStable();
+
+ let innerWin = e.source;
+ let innerDiv = innerWin.document.querySelector("div");
+
+ let eventCount = 0;
+ innerDiv.addEventListener("mousemove", function() {
+ eventCount++;
+ });
+
+ // Test that event handling is suppressed.
+ let utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.enterModalState();
+ const TOTAL = 100;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+ utils.leaveModalState();
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.ok(eventCount < TOTAL, "event should be suspressed");
+
+ // Test that event handling is not suppressed.
+ eventCount = 0;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.is(eventCount, TOTAL, "event should not be suspressed");
+
+ opener.postMessage("done", "*");
+});
+
+</script>
+<iframe src="http://example.org/tests/dom/base/test/file_suppressed_events_middle.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_suppressed_events_top_xhr.html b/dom/base/test/file_suppressed_events_top_xhr.html
new file mode 100644
index 0000000000..79299e291e
--- /dev/null
+++ b/dom/base/test/file_suppressed_events_top_xhr.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test event suppression</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div>Top</div>
+<script type="application/javascript">
+
+function waitForMessage(aMsg, aCallback) {
+ window.addEventListener("message", function handler(e) {
+ if (e.data != aMsg) {
+ return;
+ }
+
+ info(`received: ${e.data}`);
+ window.removeEventListener("message", handler);
+ if (aCallback) {
+ aCallback(e);
+ }
+ });
+}
+
+function waitForClickEvent(aElement, aWindow) {
+ return new Promise((aResolve) => {
+ aElement.addEventListener("click", aResolve, { once: true });
+ synthesizeMouseAtCenter(aElement, { type: "mousedown" }, aWindow);
+ synthesizeMouseAtCenter(aElement, { type: "mouseup" }, aWindow);
+ });
+}
+
+waitForMessage("ready", async function(e) {
+ await waitUntilApzStable();
+
+ let innerWin = e.source;
+ let innerDiv = innerWin.document.querySelector("div");
+
+ let eventCount = 0;
+ innerDiv.addEventListener("mousemove", function() {
+ eventCount++;
+ });
+
+ // Test that event handling is suppressed.
+ let xhr = new XMLHttpRequest();
+ opener.info("xhr open");
+ xhr.open('GET', 'slow.sjs', false);
+
+ const TOTAL = 100;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+ xhr.send();
+ opener.info(`xhr done`);
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.ok(eventCount < TOTAL, "event should be suspressed");
+
+ // Test that event handling is not suppressed.
+ eventCount = 0;
+ for (let i = 0; i < TOTAL; i++) {
+ synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
+ }
+
+ // Wait for click event to ensure we have received all mousemove events.
+ await waitForClickEvent(innerDiv, innerWin);
+ opener.info(`eventCount: ${eventCount}`);
+ opener.is(eventCount, TOTAL, "event should not be suspressed");
+
+ opener.postMessage("done", "*");
+});
+
+</script>
+<iframe src="http://example.org/tests/dom/base/test/file_suppressed_events_middle.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_timer_flood.html b/dom/base/test/file_timer_flood.html
new file mode 100644
index 0000000000..dc729d7e42
--- /dev/null
+++ b/dom/base/test/file_timer_flood.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+let count = 0;
+function cb() {
+ count += 1;
+ // Notify our parent that we are ready once the timer flood has
+ // warmed up.
+ if (count === 10000) {
+ window.parent.postMessage('STARTED', '*');
+ }
+ setTimeout(cb, 0);
+ setTimeout(cb, 0);
+}
+addEventListener('load', cb);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_title.xhtml b/dom/base/test/file_title.xhtml
new file mode 100644
index 0000000000..d1b04418aa
--- /dev/null
+++ b/dom/base/test/file_title.xhtml
@@ -0,0 +1 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>
diff --git a/dom/base/test/file_toScreenRect.html b/dom/base/test/file_toScreenRect.html
new file mode 100644
index 0000000000..990d9326f2
--- /dev/null
+++ b/dom/base/test/file_toScreenRect.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<style>
+html,body {
+ margin: 0;
+ padding: 0;
+}
+#target {
+ position: absolute;
+ top: 150px;
+ left: 150px;
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+}
+</style>
+<div id="target"></div>
+<script>
+const isfuzzy = opener.isfuzzy.bind(opener);
+const add_task = opener.add_task.bind(opener);
+const original_finish = opener.SimpleTest.finish;
+const SimpleTest = opener.SimpleTest;
+SimpleTest.finish = function finish() {
+ self.close();
+ original_finish();
+};
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 1.5]]});
+ SpecialPowers.setFullZoom(window, 2.0);
+
+ const rect = target.getBoundingClientRect();
+ const screenPixelsPerCSSPixel = window.devicePixelRatio;
+
+ let rectOnScreen =
+ SpecialPowers.DOMWindowUtils.toScreenRect(rect.x, rect.y, rect.width, rect.height);
+ isfuzzy(rectOnScreen.x,
+ rect.x * screenPixelsPerCSSPixel +
+ window.mozInnerScreenX * screenPixelsPerCSSPixel, 0.01, "x");
+ isfuzzy(rectOnScreen.y,
+ rect.y * screenPixelsPerCSSPixel +
+ window.mozInnerScreenY * screenPixelsPerCSSPixel, 0.01, "y");
+
+ isfuzzy(rectOnScreen.width, rect.width * screenPixelsPerCSSPixel, 0.01, "width");
+ isfuzzy(rectOnScreen.height, rect.height * screenPixelsPerCSSPixel, 0.01, "height");
+});
+</script>
diff --git a/dom/base/test/file_use_counter_bfcache.html b/dom/base/test/file_use_counter_bfcache.html
new file mode 100644
index 0000000000..6baf738945
--- /dev/null
+++ b/dom/base/test/file_use_counter_bfcache.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script>
+let goneBack = false;
+let w;
+
+onload = function() {
+ w = window.open("file_use_counter_bfcache_helper.html?a", "_blank");
+};
+
+onmessage = function() {
+ switch (event.data) {
+ case "a-doc-loaded":
+ if (!goneBack) {
+ w.postMessage("click-b-link", "*");
+ }
+ break;
+ case "b-doc-loaded":
+ if (!goneBack) {
+ goneBack = true;
+ w.postMessage("go-back", "*");
+ }
+ break;
+ case "a-doc-shown":
+ if (goneBack) {
+ w.postMessage("set-use-counter", "*");
+ }
+ break;
+ case "did-set-use-counter":
+ w.postMessage("click-c-link", "*");
+ break;
+ case "c-doc-loaded":
+ w.close();
+ // Signal that we've finished.
+ location.hash = "#finished";
+ break;
+ }
+};
+</script>
diff --git a/dom/base/test/file_use_counter_bfcache_helper.html b/dom/base/test/file_use_counter_bfcache_helper.html
new file mode 100644
index 0000000000..a5da790ae6
--- /dev/null
+++ b/dom/base/test/file_use_counter_bfcache_helper.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<a href="file_use_counter_bfcache_helper.html?b">b</a>
+<a href="file_use_counter_bfcache_helper.html?c">c</a>
+<svg></svg>
+<script>
+let which = location.search.substring(1);
+
+if (which == "c") {
+ // Set the sentinel use counter.
+ let s = document.createElement("style");
+ s.textContent = "g { marker-mid: none; }";
+ document.body.append(s);
+}
+
+onload = function() {
+ window.opener.postMessage(`${which}-doc-loaded`, "*");
+};
+
+onmessage = function() {
+ switch (event.data) {
+ case "click-b-link":
+ document.querySelectorAll("a")[0].click();
+ break;
+ case "click-c-link":
+ document.querySelectorAll("a")[1].click();
+ break;
+ case "go-back":
+ history.back();
+ break;
+ case "set-use-counter":
+ document.querySelector("svg").getElementById("x");
+ window.opener.postMessage("did-set-use-counter", "*");
+ break;
+ }
+};
+
+onpageshow = function() {
+ window.opener.postMessage(`${which}-doc-shown`, "*");
+};
+</script>
diff --git a/dom/base/test/file_use_counter_outer.html b/dom/base/test/file_use_counter_outer.html
new file mode 100644
index 0000000000..1a7eb90f7c
--- /dev/null
+++ b/dom/base/test/file_use_counter_outer.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=968923
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 968923</title>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
+<img id="display" />
+<iframe id="content">
+
+</iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_use_counter_outer_display_none.html b/dom/base/test/file_use_counter_outer_display_none.html
new file mode 100644
index 0000000000..22c4c4c8ab
--- /dev/null
+++ b/dom/base/test/file_use_counter_outer_display_none.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=968923
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 968923</title>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
+<iframe id="content" style="display: none">
+
+</iframe>
+</body>
+</html>
diff --git a/dom/base/test/file_use_counter_style.html b/dom/base/test/file_use_counter_style.html
new file mode 100644
index 0000000000..52880c0e40
--- /dev/null
+++ b/dom/base/test/file_use_counter_style.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<style>
+ div {
+ background-image: none;
+ padding: 10px;
+ -moz-transform: scale(10);
+ /* Just a property we're unlikely to implement */
+ -webkit-padding-start: 58.5;
+ /* Sentinel use counter to signal that telemetry for this document has been captured */
+ marker-mid: initial;
+ }
+</style>
+<!-- We currently count even if we don't match it, but well just in case we change that... -->
+<div></div>
diff --git a/dom/base/test/file_use_counter_svg_currentScale.svg b/dom/base/test/file_use_counter_svg_currentScale.svg
new file mode 100644
index 0000000000..cf4d64aba0
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_currentScale.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="4in" height="3in" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <desc>Test graphic for hitting currentScale
+ </desc>
+ <script type="text/javascript"> <![CDATA[
+ document.documentElement.currentScale = document.documentElement.currentScale;
+ ]]>
+ </script>
+ <image id="i1" x="200" y="200" width="100px" height="80px">
+ </image>
+ <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
+
+ <!-- Sentinel use counter to signal that telemetry for this document has been captured -->
+ <style>x { marker-mid: initial; }</style>
+</svg>
diff --git a/dom/base/test/file_use_counter_svg_fill_pattern.svg b/dom/base/test/file_use_counter_svg_fill_pattern.svg
new file mode 100644
index 0000000000..1cd84dd5e1
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
+ <!-- Outline the drawing area in blue -->
+ <rect fill="none" stroke="blue"
+ x="1" y="1" width="798" height="398"/>
+
+ <!-- The ellipse is filled using a triangle pattern paint server
+ and stroked with black -->
+ <ellipse fill="url(https://example.com/browser/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg#TrianglePattern)" stroke="black" stroke-width="5"
+ cx="400" cy="200" rx="350" ry="150" />
+
+ <!-- Sentinel use counter to signal that telemetry for this document has been captured -->
+ <style>x { marker-mid: initial; }</style>
+</svg>
diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_data.svg b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg
new file mode 100644
index 0000000000..9a180b2432
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
+ <!-- Outline the drawing area in blue -->
+ <rect fill="none" stroke="blue"
+ x="1" y="1" width="798" height="398"/>
+
+ <!-- The ellipse is filled using a triangle pattern paint server
+ and stroked with black -->
+ <ellipse fill="url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iIAogICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSI4Y20iIGhlaWdodD0iNGNtIiB2aWV3Qm94PSIwIDAgODAwIDQwMCIgdmVyc2lvbj0iMS4xIgogICAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlc2M+Qm9ycm93ZWQgZnJvbSBodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcvcHNlcnZlcnMuaHRtbDwvZGVzYz4KICA8ZGVmcz4KICAgIDxwYXR0ZXJuIGlkPSJUcmlhbmdsZVBhdHRlcm4iIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgICAgICAgICB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIKICAgICAgICAgICAgIHZpZXdCb3g9IjAgMCAxMCAxMCIgPgogICAgICA8cGF0aCBkPSJNIDAgMCBMIDcgMCBMIDMuNSA3IHoiIGZpbGw9InJlZCIgZmlsbC1vcGFjaXR5PSIwLjciIHN0cm9rZT0iYmx1ZSIgLz4KICAgIDwvcGF0dGVybj4gCiAgPC9kZWZzPgo8L3N2Zz4K#TrianglePattern)" stroke="black" stroke-width="5"
+ cx="400" cy="200" rx="350" ry="150" />
+</svg>
diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg
new file mode 100644
index 0000000000..a84292a63f
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
+ <defs>
+ <pattern id="TrianglePattern" patternUnits="userSpaceOnUse"
+ x="0" y="0" width="100" height="100"
+ viewBox="0 0 10 10" >
+ <path d="M 0 0 L 7 0 L 3.5 7 z" fill="red" fill-opacity="0.7" stroke="blue" />
+ </pattern>
+ </defs>
+</svg>
diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg
new file mode 100644
index 0000000000..47adafe38c
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <desc>Borrowed from http://www.w3.org/TR/SVG/pservers.html</desc>
+ <!-- Outline the drawing area in blue -->
+ <rect fill="none" stroke="blue"
+ x="1" y="1" width="798" height="398"/>
+
+ <defs>
+ <pattern id="TrianglePattern" patternUnits="userSpaceOnUse"
+ x="0" y="0" width="100" height="100"
+ viewBox="0 0 10 10" >
+ <path d="M 0 0 L 7 0 L 3.5 7 z" fill="red" fill-opacity="0.7" stroke="blue" />
+ </pattern>
+ </defs>
+
+ <!-- The ellipse is filled using a triangle pattern paint server
+ and stroked with black -->
+ <ellipse fill="url(#TrianglePattern)" stroke="black" stroke-width="5"
+ cx="400" cy="200" rx="350" ry="150" />
+
+ <!-- Sentinel use counter to signal that telemetry for this document has been captured -->
+ <style>x { marker-mid: initial; }</style>
+</svg>
diff --git a/dom/base/test/file_use_counter_svg_getElementById.svg b/dom/base/test/file_use_counter_svg_getElementById.svg
new file mode 100644
index 0000000000..7cd12ba315
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_getElementById.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="4in" height="3in" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <desc>Test graphic for hitting getElementById
+ </desc>
+ <image id="i1" x="200" y="200" width="100px" height="80px">
+ </image>
+ <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
+ <script type="text/javascript"> <![CDATA[
+ var image = document.documentElement.getElementById("i1");
+ image.addEventListener("load",
+ function() {
+ document.documentElement.removeAttribute("class");
+ },
+ false);
+ ]]>
+ </script>
+
+ <!-- Sentinel use counter to signal that telemetry for this document has been captured -->
+ <style>x { marker-mid: initial; }</style>
+</svg>
diff --git a/dom/base/test/file_viewport_metrics_on_landscape_content.html b/dom/base/test/file_viewport_metrics_on_landscape_content.html
new file mode 100644
index 0000000000..02b079aeac
--- /dev/null
+++ b/dom/base/test/file_viewport_metrics_on_landscape_content.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, minimum-scale=0.5">
+<style>
+html {
+ scrollbar-width: none; /* avoid scrollbar width is included in some metrics */
+}
+html, body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ overflow: none;
+}
+#twice-width {
+ width: 200%;
+ height: 100%;
+ position: absolute;
+}
+</style>
+<div id="twice-width"></div>
+<script>
+
+const is = opener.is.bind(opener);
+const todo_is = opener.todo_is.bind(opener);
+const add_task = opener.add_task.bind(opener);
+const original_finish = opener.SimpleTest.finish;
+const SimpleTest = opener.SimpleTest;
+
+SimpleTest.finish = function finish() {
+ self.close();
+ original_finish();
+}
+
+add_task(async () => {
+ // Explicitly set to 0.5x so that this test doesn't need to reply on our
+ // auto initial-scale calculation.
+ SpecialPowers.getDOMWindowUtils(window).setResolutionAndScaleTo(0.5);
+ is(window.visualViewport.scale, 0.5, "The content should be scaled by 0.5x");
+
+ // Now the visual viewport size is same as the layout viewport.
+ const layoutViewportHeight = window.visualViewport.height;
+
+ todo_is(window.innerHeight, layoutViewportHeight,
+ "window.innerHeight should reflect the layout viewport (Bug 1598487)");
+ is(document.documentElement.scrollHeight, layoutViewportHeight,
+ "The root element's scrollHeight should be the layout viewport height");
+ is(document.documentElement.getBoundingClientRect().height,
+ layoutViewportHeight / 2,
+ "The content height should be half of the layout viewport height");
+
+ // Set scale to 1.0x so that the visual viport size becomes 0.5x of the layout
+ // viewport.
+ SpecialPowers.getDOMWindowUtils(window).setResolutionAndScaleTo(1);
+ is(window.visualViewport.scale, 1, "The content should be scaled by 1.0x");
+ is(window.visualViewport.height, layoutViewportHeight / 2,
+ "Now the visual viewport height should be changed to half of the layout " +
+ "viewport height");
+ todo_is(window.innerHeight, layoutViewportHeight,
+ "window.innerHeight shouldn't be changed (Bug 1598487)");
+ is(document.documentElement.scrollHeight, layoutViewportHeight,
+ "The root element's scrollHeight shouldn't be changed");
+});
+</script>
+</html>
diff --git a/dom/base/test/file_viewport_scroll_quirks.html b/dom/base/test/file_viewport_scroll_quirks.html
new file mode 100644
index 0000000000..992b8a9968
--- /dev/null
+++ b/dom/base/test/file_viewport_scroll_quirks.html
@@ -0,0 +1 @@
+<html><body style='height:2000px; width:2000px;'>
diff --git a/dom/base/test/file_viewport_scroll_xml.xml b/dom/base/test/file_viewport_scroll_xml.xml
new file mode 100644
index 0000000000..1453e9b1ea
--- /dev/null
+++ b/dom/base/test/file_viewport_scroll_xml.xml
@@ -0,0 +1 @@
+<?xml-stylesheet href='data:text/css,html { display:block; height:2000px; width:2000px; }'?><foo><html></html></foo>
diff --git a/dom/base/test/file_webaudio_startstop.html b/dom/base/test/file_webaudio_startstop.html
new file mode 100644
index 0000000000..c0e4fafb01
--- /dev/null
+++ b/dom/base/test/file_webaudio_startstop.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+var runningPromise = new Promise(resolve => {
+ ac.onstatechange = event => {
+ if (ac.state == "running") {
+ resolve();
+ }
+ };
+});
+
+var osc = ac.createOscillator();
+osc.connect(ac.destination);
+osc.start(0);
+osc.stop(osc.context.currentTime + 2.0);
+
+var suspendPromise;
+function suspendAC() {
+ runningPromise.then(() => {
+ suspendPromise = ac.suspend();
+ });
+}
+
+var resumePromise;
+function resumeAC() {
+ suspendPromise.then(() => {
+ resumePromise = ac.resume();
+ });
+}
+
+function closeAC() {
+ resumePromise.then(() => {
+ ac.close();
+ });
+}
+</script>
diff --git a/dom/base/test/file_window_close.html b/dom/base/test/file_window_close.html
new file mode 100644
index 0000000000..5adec04ec4
--- /dev/null
+++ b/dom/base/test/file_window_close.html
@@ -0,0 +1,68 @@
+<html>
+ <head>
+ <script>
+ var b = new BroadcastChannel("windowclose");
+
+ function isInitialLoad() {
+ return location.search.substr(1) != "withhistory" || history.length == 1;
+ }
+
+ function run() {
+ if (location.search.substr(1) == "withhistory") {
+ // An entry for the initial load, pushState, iframe and the next page.
+ if (history.length == 4) {
+ // We're coming back from history.
+ function listener(m) {
+ if (m.message.includes("Scripts may not close windows that were not opened by script.")) {
+ SpecialPowers.postConsoleSentinel();
+ SpecialPowers.pushPrefEnv({ set: [["dom.allow_scripts_to_close_windows", true]]}).then(
+ function() {
+ window.onunload = function() {
+ b.postMessage('blocked');
+ b.close();
+ };
+ window.close();
+ });
+ }
+ }
+ SpecialPowers.registerConsoleListener(listener);
+ window.onunload = function() {
+ SpecialPowers.postConsoleSentinel();
+ b.postMessage('closed');
+ b.close();
+ };
+ window.close();
+ } else {
+ // Load a page which will call history.back()
+ location.href = "file_window_close_2.html";
+ }
+ } else {
+ onunload = function() {
+ b.postMessage('closed');
+ b.close();
+ };
+ window.close();
+ }
+ }
+
+ function init() {
+ if (isInitialLoad()) {
+ // Add some data to the session history.
+ history.pushState("foo", "foo");
+ var ifr = document.getElementsByTagName("iframe")[0];
+ ifr.onload = run;
+ ifr.src = "data:text/html,random data";
+ } else {
+ run();
+ }
+ }
+ window.onpageshow = () => {
+ setTimeout(init);
+ }
+
+ </script>
+ </head>
+ <body>
+ <iframe></iframe>
+ </body>
+</html>
diff --git a/dom/base/test/file_window_close_2.html b/dom/base/test/file_window_close_2.html
new file mode 100644
index 0000000000..c06ec90a8b
--- /dev/null
+++ b/dom/base/test/file_window_close_2.html
@@ -0,0 +1,4 @@
+<html>
+ <body onload="setTimeout('history.back();');">
+ </body>
+</html>
diff --git a/dom/base/test/file_window_focus_by_close_and_open.html b/dom/base/test/file_window_focus_by_close_and_open.html
new file mode 100644
index 0000000000..ab5ad72ae3
--- /dev/null
+++ b/dom/base/test/file_window_focus_by_close_and_open.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+SimpleTest.waitForFocus(() => {
+ finished();
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/file_x-frame-options_main.html b/dom/base/test/file_x-frame-options_main.html
new file mode 100644
index 0000000000..903f951e08
--- /dev/null
+++ b/dom/base/test/file_x-frame-options_main.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+<title>X-Frame-Options tests</title>
+<script type="text/javascript">
+// This frame loading means all subframes have either loaded or errored out. We
+// can now tell the test harness to check each subframe for the expected result.
+window.addEventListener('load', parent.testFramesLoaded);
+</script>
+</head>
+<body>
+
+<iframe id="control1" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=control1"></iframe><br>
+<iframe id="control2" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=control2"></iframe><br>
+<iframe id="deny" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=deny&xfo=deny"></iframe><br>
+<iframe id="sameorigin1" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin1&xfo=sameorigin"></iframe><br>
+<iframe id="sameorigin2" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin2&xfo=sameorigin"></iframe><br>
+<iframe id="sameorigin5" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin5&xfo=sameorigin2"></iframe><br>
+<iframe id="sameorigin6" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin6&xfo=sameorigin2"></iframe><br>
+<iframe id="sameorigin7" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin7&xfo=sameorigin3"></iframe><br>
+<iframe id="sameorigin8" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin8&xfo=sameorigin3"></iframe><br>
+<iframe id="mixedpolicy" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=mixedpolicy&xfo=mixedpolicy"></iframe><br>
+<iframe id="allow-from-allow" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=allow-from-allow&xfo=afa"></iframe><br>
+<iframe id="allow-from-deny" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=allow-from-deny&xfo=afd"></iframe><br>
+<iframe id="sameorigin-multipart" src="http://example.com/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin-multipart&xfo=sameorigin&multipart=1"></iframe><br>
+<iframe id="sameorigin-multipart2" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin-multipart2&xfo=sameorigin&multipart=1"></iframe><br>
+
+<!-- added for bug 836132 -->
+<script type="text/javascript">
+
+ function addFrame(test, xfo) {
+ var baseurl = "http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs";
+ var ifr = "<iframe id='" + test + "'" + "src='" + baseurl + "?testid=" + test + "&xfo=" + xfo + "' style='border:2px solid red;'></iframe>";
+ document.write(ifr);
+ }
+
+ addFrame("allow-from-allow-1", "afa1");
+ for (var i = 1; i<=14; i++)
+ addFrame("allow-from-deny-"+i, "afd"+i);
+
+</script>
+
+
+</body>
+</html>
diff --git a/dom/base/test/file_x-frame-options_page.sjs b/dom/base/test/file_x-frame-options_page.sjs
new file mode 100644
index 0000000000..13c9aa7cac
--- /dev/null
+++ b/dom/base/test/file_x-frame-options_page.sjs
@@ -0,0 +1,67 @@
+// SJS file for X-Frame-Options mochitests
+function handleRequest(request, response) {
+ var query = {};
+ var BOUNDARY = "BOUNDARYOMG3984";
+ request.queryString.split("&").forEach(function (val) {
+ var [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ if (query.multipart == "1") {
+ response.setHeader(
+ "Content-Type",
+ "multipart/x-mixed-replace;boundary=" + BOUNDARY,
+ false
+ );
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write("--" + BOUNDARY + "\r\n");
+ response.write("Content-Type: text/html\r\n\r\n");
+ } else {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ }
+
+ var testHeaders = {
+ deny: "DENY",
+ sameorigin: "SAMEORIGIN",
+ sameorigin2: "SAMEORIGIN, SAMEORIGIN",
+ sameorigin3: "SAMEORIGIN,SAMEORIGIN , SAMEORIGIN",
+ mixedpolicy: "DENY,SAMEORIGIN",
+
+ /* added for bug 836132 */
+ afa: "ALLOW-FROM http://mochi.test:8888/",
+ afd: "ALLOW-FROM http://example.com/",
+ afa1: "ALLOW-FROM http://mochi.test:8888",
+ afd1: "ALLOW-FROM:example.com",
+ afd2: "ALLOW-FROM: example.com",
+ afd3: "ALLOW-FROM example.com",
+ afd4: "ALLOW-FROM:http://example.com",
+ afd5: "ALLOW-FROM: http://example.com",
+ afd6: "ALLOW-FROM http://example.com",
+ afd7: "ALLOW-FROM:mochi.test:8888",
+ afd8: "ALLOW-FROM: mochi.test:8888",
+ afd9: "ALLOW-FROM:http://mochi.test:8888",
+ afd10: "ALLOW-FROM: http://mochi.test:8888",
+ afd11: "ALLOW-FROM mochi.test:8888",
+ afd12: "ALLOW-FROM",
+ afd13: "ALLOW-FROM ",
+ afd14: "ALLOW-FROM:",
+ };
+
+ if (testHeaders.hasOwnProperty(query.xfo)) {
+ response.setHeader("X-Frame-Options", testHeaders[query.xfo], false);
+ }
+
+ // from the test harness we'll be checking for the presence of this element
+ // to test if the page loaded
+ response.write('<h1 id="test">' + query.testid + "</h1>");
+
+ if (query.testid == "postmessage") {
+ response.write("<script>parent.opener.postMessage('ok', '*');</script>");
+ }
+
+ if (query.multipart == "1") {
+ response.write("\r\n--" + BOUNDARY + "\r\n");
+ }
+}
diff --git a/dom/base/test/file_xhtmlserializer_1.xhtml b/dom/base/test/file_xhtmlserializer_1.xhtml
new file mode 100644
index 0000000000..64271ce2ce
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong> adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/file_xhtmlserializer_1_bodyonly.xhtml b/dom/base/test/file_xhtmlserializer_1_bodyonly.xhtml
new file mode 100644
index 0000000000..fbefe91d6e
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_bodyonly.xhtml
@@ -0,0 +1,56 @@
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_format.xhtml b/dom/base/test/file_xhtmlserializer_1_format.xhtml
new file mode 100644
index 0000000000..d62cc367ba
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_format.xhtml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+ </head>
+ <body>
+ <p>Hello world</p>
+ <p> Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+ adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis
+ ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent
+ taciti <span>sociosqu ad litora</span> torquent <a
+ href="file_htmlserializer_1_result1.html">per conubia</a>
+ nostra, per inceptos hymenaeos. </p>
+ <ul>
+ <li>Nam tellus massa,éàèçù</li>
+ <li> fringilla aliquam,</li>
+ <li> fermentum sit amet,</li>
+ <li>posuere ac,</li>
+ <li> est.</li>
+ </ul>
+ <div> Duis tristique egestas ligula. Mauris quis felis. </div>
+ <script id="script" type="text/javascript"></script>
+ <script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+ <ol>
+ <li>Fusce a ipsum</li>
+ <li> non lacus posuere aliquet.</li>
+ <li> Sed fermentum posuere nulla</li>
+ <li> Donec tempor.</li>
+ </ol>
+ Donec sollicitudin tortor
+ <!-- test on
+comments -->
+ <pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ ut gravida eros <br />
+ leo ut libero
+ <!-- empty element: end tag should be generated for backward compatibility with HTML -->
+ <p></p>
+ <noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+ <p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus
+ aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+ <pre xmlns="http://mozilla.org/ns/foo">lacinia
+ <em>libero</em> ullamcorper laoreet.<br/> Cras quis<br/> nisi at
+ odio<br/> consectetuer molestie. Curabitur consectetuer urna a
+ sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in
+ faucibus orci luctus et ultrices posuere cubilia Curae; Sed
+ sollicitudin, nulla at pharetra rutrum,
+ <br/>
+ lacus risus pulvinar ante.
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/file_xhtmlserializer_1_linebreak.xhtml b/dom/base/test/file_xhtmlserializer_1_linebreak.xhtml
new file mode 100644
index 0000000000..a0aecdd2c6
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_linebreak.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_links.xhtml b/dom/base/test/file_xhtmlserializer_1_links.xhtml
new file mode 100644
index 0000000000..0c2814311b
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_links.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="http://mochi.test:8888/tests/dom/base/test/file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/file_xhtmlserializer_1_nested_body.xhtml b/dom/base/test/file_xhtmlserializer_1_nested_body.xhtml
new file mode 100644
index 0000000000..120f8e7dcb
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_nested_body.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+<body><p>this is an other body element</p></body></body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_no_body.xhtml b/dom/base/test/file_xhtmlserializer_1_no_body.xhtml
new file mode 100644
index 0000000000..6f5055bd57
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_no_body.xhtml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_noflag.xhtml b/dom/base/test/file_xhtmlserializer_1_noflag.xhtml
new file mode 100644
index 0000000000..a0aecdd2c6
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_noflag.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_noformatpre.xhtml b/dom/base/test/file_xhtmlserializer_1_noformatpre.xhtml
new file mode 100644
index 0000000000..a5eb6e9692
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_noformatpre.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.
+
+ Cras quis
+
+ nisi at odio
+
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum,
+
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_raw.xhtml b/dom/base/test/file_xhtmlserializer_1_raw.xhtml
new file mode 100644
index 0000000000..d13fce2ccd
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_raw.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong> adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_sibling_body.xhtml b/dom/base/test/file_xhtmlserializer_1_sibling_body.xhtml
new file mode 100644
index 0000000000..9ef4840e36
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_sibling_body.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body><p>this is an other body element</p></body><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_sibling_body_only_body.xhtml b/dom/base/test/file_xhtmlserializer_1_sibling_body_only_body.xhtml
new file mode 100644
index 0000000000..f9f92a0670
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_sibling_body_only_body.xhtml
@@ -0,0 +1,56 @@
+<body><p>this is an other body element</p></body><body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em> ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_1_wrap.xhtml b/dom/base/test/file_xhtmlserializer_1_wrap.xhtml
new file mode 100644
index 0000000000..6b156278ad
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_1_wrap.xhtml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>Test for html serializer</title>
+
+</head>
+<body>
+<p>Hello world</p> <p>
+
+ Lorem ipsum dolor sit amet, <strong>consectetuer</strong>
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti <span>sociosqu
+ ad
+ litora</span> torquent <a href="file_htmlserializer_1_result1.html">per
+ conubia</a>
+nostra, per inceptos hymenaeos. </p>
+
+
+<ul><li>Nam tellus massa,éàèçù</li><li>
+ fringilla
+aliquam,</li><li> fermentum sit amet,</li><li>posuere ac,</li><li> est.</li></ul>
+<div> Duis tristique egestas ligula. Mauris quis felis. </div>
+<script id="script" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+// a script which does nothing
+
+function nothing() {
+ var hey="hello";
+ var aLongLine="consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere";
+}
+var a=3, b=4, c=7;
+// here we test the non-serialization of xml character into javascript content
+var d = a < b && a > c;
+//]]>
+</script>
+
+<ol><li>Fusce
+ a ipsum</li><li> non lacus posuere aliquet.</li><li> Sed fermentum
+posuere nulla</li><li> Donec tempor.</li></ol>
+Donec sollicitudin tortor
+<!-- test on
+comments -->
+<pre>lacinia <em>libero</em> ullamcorper laoreet.<br />
+ Cras quis<br />
+ nisi at odio<br />
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at pharetra rutrum, <br />
+lacus risus pulvinar ante.
+</pre>
+ut gravida eros <br />leo ut libero
+<!-- empty element: end tag should be generated for backward compatibility with HTML -->
+<p></p>
+<noscript>
+<p>Curabitur consectetuer urna a sem. Nunc non urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci luctus</p></noscript>
+<p>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus
+aliquet lectus. Nunc vitae eros. Class aptent taciti</p>
+<pre xmlns="http://mozilla.org/ns/foo">lacinia <em>libero</em>
+ullamcorper laoreet.<br/>
+ Cras quis<br/>
+ nisi at odio<br/>
+ consectetuer molestie. Curabitur consectetuer urna a sem. Nunc non
+urna. Cras in massa. Vestibulum ante ipsum primis in faucibus orci
+luctus et ultrices posuere cubilia Curae; Sed sollicitudin, nulla at
+pharetra rutrum, <br/>
+lacus risus pulvinar ante.
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_2.xhtml b/dom/base/test/file_xhtmlserializer_2.xhtml
new file mode 100644
index 0000000000..2a9c55b95d
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_2.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for html serializer with entities</title>
+</head>
+<body>
+
+<p>The basic set is just "&nbsp;" &amp; &lt; &gt; &quot; for interoperability with older products that don't support &alpha; and friends.</p>
+
+<p>latin1 &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml; &copy; &ordf; &laquo; &not; &shy; &reg; &macr; &deg; &plusmn; &sup2; &sup3; &acute;
+&micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12; &frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring; &AElig;
+&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc; &Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times; &Oslash;
+&Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave; &aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave; &eacute; &ecirc;
+&euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute; &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc; &uuml; &yacute;
+&thorn; &yuml; </p>
+<p>symbols, math.. &fnof; &Alpha; &Beta; &Gamma; &Delta; &Epsilon; &Zeta; &Eta; &Theta; &Iota; &Kappa; &Lambda; &Mu; &Nu; &Xi; &Omicron; &Pi; &Rho; &Sigma; &Tau; &Upsilon;
+&Phi; &Chi; &Psi; &Omega; &alpha; &beta; &gamma; &delta; &epsilon; &zeta; &eta; &theta; &iota; &kappa; &lambda; &mu; &nu; &xi; &omicron; &pi; &rho; &sigmaf;
+&sigma; &tau; &upsilon; &phi; &chi; &psi; &omega; &thetasym; &upsih; &piv; &bull; &hellip; &prime; &Prime; &oline; &frasl; &weierp; &image; &real;
+&trade; &alefsym; &larr; &uarr; &rarr; &darr; &harr; &crarr; &lArr; &uArr; &rArr; &dArr; &hArr; &forall; &part; &exist; &empty; &nabla; &isin; &notin;
+&ni; &prod; &sum; &minus; &lowast; &radic; &prop; &infin; &ang; &and; &or; &cap; &cup; &int; &there4; &sim; &cong; &asymp; &ne; &equiv; &le; &ge;
+&sub; &sup; &nsub; &sube; &supe; &oplus; &otimes; &perp; &sdot; &lceil; &rceil; &lfloor; &rfloor; &lang; &rang; &loz; &spades; &clubs; &hearts; &diams;
+</p>
+<p> others
+&OElig; &oelig; &Scaron; &scaron; &Yuml; &circ; &tilde; &ensp; &emsp; &thinsp; &zwnj; &zwj; &lrm; &rlm;&ndash;&mdash; &lsquo; &rsquo;
+&sbquo;&ldquo; &rdquo; &bdquo; &dagger; &Dagger; &permil; &lsaquo; &rsaquo; &euro;
+</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_2_basic.xhtml b/dom/base/test/file_xhtmlserializer_2_basic.xhtml
new file mode 100644
index 0000000000..c35cc48cf8
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_2_basic.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=" />
+ <title>Test for html serializer with entities</title>
+</head>
+<body>
+
+<p>The basic set is just " " &amp; &lt; &gt; " for interoperability with older products that don't support α and friends.</p>
+
+<p>latin1 ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ ° ± ² ³ ´
+µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À à Â Ã Ä Å Æ
+Ç È É Ê Ë Ì à Î à à Ñ Ò Ó Ô Õ Ö × Ø
+Ù Ú Û Ü à Þ ß à á â ã ä å æ ç è é ê
+ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý
+þ ÿ </p>
+<p>symbols, math.. ƒ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ ΠΞ Ο Π Ρ Σ Τ Υ
+Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο Ï€ Ï Ï‚
+σ τ υ φ χ ψ ω ϑ ϒ ϖ • … ′ ″ ‾ ℠℘ ℑ ℜ
+™ ℵ ↠↑ → ↓ ↔ ↵ ⇠⇑ ⇒ ⇓ ⇔ ∀ ∂ ∃ ∅ ∇ ∈ ∉
+∋ ∠∑ − ∗ √ ∠∞ ∠ ∧ ∨ ∩ ∪ ∫ ∴ ∼ ≅ ≈ ≠ ≡ ≤ ≥
+⊂ ⊃ ⊄ ⊆ ⊇ ⊕ ⊗ ⊥ ⋅ ⌈ ⌉ ⌊ ⌋ ⟨ ⟩ ◊ ♠ ♣ ♥ ♦
+</p>
+<p> others
+Å’ Å“ Å  Å¡ Ÿ ˆ Ëœ       ‌ †‎ â€â€“— ‘ ’
+‚“ †„ † ‡ ‰ ‹ › €
+</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_2_enthtml.xhtml b/dom/base/test/file_xhtmlserializer_2_enthtml.xhtml
new file mode 100644
index 0000000000..0ba1c8421c
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_2_enthtml.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=" />
+ <title>Test for html serializer with entities</title>
+</head>
+<body>
+
+<p>The basic set is just &nbsp; &amp; &lt; &gt; " for interoperability with older products that don't support &alpha; and friends.</p>
+
+<p>latin1 &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml;
+&copy; &ordf; &laquo; &not; &shy; &reg; &macr; &deg; &plusmn; &sup2;
+&sup3; &acute;
+&micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12;
+&frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring;
+&AElig;
+&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc;
+&Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times;
+&Oslash;
+&Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave;
+&aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave;
+&eacute; &ecirc;
+&euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute;
+ &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc;
+&uuml; &yacute;
+&thorn; &yuml; </p>
+<p>symbols, math.. &fnof; &Alpha; &Beta; &Gamma; &Delta; &Epsilon;
+&Zeta; &Eta; &Theta; &Iota; &Kappa; &Lambda; &Mu; &Nu; &Xi; &Omicron;
+&Pi; &Rho; &Sigma; &Tau; &Upsilon;
+&Phi; &Chi; &Psi; &Omega; &alpha; &beta; &gamma; &delta; &epsilon;
+&zeta; &eta; &theta; &iota; &kappa; &lambda; &mu; &nu; &xi; &omicron;
+&pi; &rho; &sigmaf;
+&sigma; &tau; &upsilon; &phi; &chi; &psi; &omega; &thetasym; &upsih;
+&piv; &bull; &hellip; &prime; &Prime; &oline; &frasl; &weierp; &image;
+&real;
+&trade; &alefsym; &larr; &uarr; &rarr; &darr; &harr; &crarr; &lArr;
+&uArr; &rArr; &dArr; &hArr; &forall; &part; &exist; &empty; &nabla;
+&isin; &notin;
+&ni; &prod; &sum; &minus; &lowast; &radic; &prop; &infin; &ang; &and;
+&or; &cap; &cup; &int; &there4; &sim; &cong; &asymp; &ne; &equiv; &le;
+&ge;
+&sub; &sup; &nsub; &sube; &supe; &oplus; &otimes; &perp; &sdot; &lceil;
+&rceil; &lfloor; &rfloor; &lang; &rang; &loz; &spades; &clubs; &hearts;
+&diams;
+</p>
+<p> others
+&OElig; &oelig; &Scaron; &scaron; &Yuml; &circ; &tilde; &ensp; &emsp;
+&thinsp; &zwnj; &zwj; &lrm; &rlm;&ndash;&mdash; &lsquo; &rsquo;
+&sbquo;&ldquo; &rdquo; &bdquo; &dagger; &Dagger; &permil; &lsaquo;
+&rsaquo; &euro;
+</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_2_entw3c.xhtml b/dom/base/test/file_xhtmlserializer_2_entw3c.xhtml
new file mode 100644
index 0000000000..0ba1c8421c
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_2_entw3c.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=" />
+ <title>Test for html serializer with entities</title>
+</head>
+<body>
+
+<p>The basic set is just &nbsp; &amp; &lt; &gt; " for interoperability with older products that don't support &alpha; and friends.</p>
+
+<p>latin1 &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml;
+&copy; &ordf; &laquo; &not; &shy; &reg; &macr; &deg; &plusmn; &sup2;
+&sup3; &acute;
+&micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12;
+&frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring;
+&AElig;
+&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc;
+&Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times;
+&Oslash;
+&Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave;
+&aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave;
+&eacute; &ecirc;
+&euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute;
+ &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc;
+&uuml; &yacute;
+&thorn; &yuml; </p>
+<p>symbols, math.. &fnof; &Alpha; &Beta; &Gamma; &Delta; &Epsilon;
+&Zeta; &Eta; &Theta; &Iota; &Kappa; &Lambda; &Mu; &Nu; &Xi; &Omicron;
+&Pi; &Rho; &Sigma; &Tau; &Upsilon;
+&Phi; &Chi; &Psi; &Omega; &alpha; &beta; &gamma; &delta; &epsilon;
+&zeta; &eta; &theta; &iota; &kappa; &lambda; &mu; &nu; &xi; &omicron;
+&pi; &rho; &sigmaf;
+&sigma; &tau; &upsilon; &phi; &chi; &psi; &omega; &thetasym; &upsih;
+&piv; &bull; &hellip; &prime; &Prime; &oline; &frasl; &weierp; &image;
+&real;
+&trade; &alefsym; &larr; &uarr; &rarr; &darr; &harr; &crarr; &lArr;
+&uArr; &rArr; &dArr; &hArr; &forall; &part; &exist; &empty; &nabla;
+&isin; &notin;
+&ni; &prod; &sum; &minus; &lowast; &radic; &prop; &infin; &ang; &and;
+&or; &cap; &cup; &int; &there4; &sim; &cong; &asymp; &ne; &equiv; &le;
+&ge;
+&sub; &sup; &nsub; &sube; &supe; &oplus; &otimes; &perp; &sdot; &lceil;
+&rceil; &lfloor; &rfloor; &lang; &rang; &loz; &spades; &clubs; &hearts;
+&diams;
+</p>
+<p> others
+&OElig; &oelig; &Scaron; &scaron; &Yuml; &circ; &tilde; &ensp; &emsp;
+&thinsp; &zwnj; &zwj; &lrm; &rlm;&ndash;&mdash; &lsquo; &rsquo;
+&sbquo;&ldquo; &rdquo; &bdquo; &dagger; &Dagger; &permil; &lsaquo;
+&rsaquo; &euro;
+</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_xhtmlserializer_2_latin1.xhtml b/dom/base/test/file_xhtmlserializer_2_latin1.xhtml
new file mode 100644
index 0000000000..59b564ca77
--- /dev/null
+++ b/dom/base/test/file_xhtmlserializer_2_latin1.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=" />
+ <title>Test for html serializer with entities</title>
+</head>
+<body>
+
+<p>The basic set is just &nbsp; &amp; &lt; &gt; " for interoperability with older products that don't support α and friends.</p>
+
+<p>latin1 &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml;
+&copy; &ordf; &laquo; &not; &shy; &reg; &macr; &deg; &plusmn; &sup2;
+&sup3; &acute;
+&micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12;
+&frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring;
+&AElig;
+&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc;
+&Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times;
+&Oslash;
+&Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave;
+&aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave;
+&eacute; &ecirc;
+&euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute;
+ &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc;
+&uuml; &yacute;
+&thorn; &yuml; </p>
+<p>symbols, math.. ƒ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ ΠΞ Ο Π Ρ Σ Τ Υ
+Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο Ï€ Ï Ï‚
+σ τ υ φ χ ψ ω ϑ ϒ ϖ • … ′ ″ ‾ ℠℘ ℑ ℜ
+™ ℵ ↠↑ → ↓ ↔ ↵ ⇠⇑ ⇒ ⇓ ⇔ ∀ ∂ ∃ ∅ ∇ ∈ ∉
+∋ ∠∑ − ∗ √ ∠∞ ∠ ∧ ∨ ∩ ∪ ∫ ∴ ∼ ≅ ≈ ≠ ≡ ≤ ≥
+⊂ ⊃ ⊄ ⊆ ⊇ ⊕ ⊗ ⊥ ⋅ ⌈ ⌉ ⌊ ⌋ ⟨ ⟩ ◊ ♠ ♣ ♥ ♦
+</p>
+<p> others
+Å’ Å“ Å  Å¡ Ÿ ˆ Ëœ       ‌ †‎ â€â€“— ‘ ’
+‚“ †„ † ‡ ‰ ‹ › €
+</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/file_youtube_flash_embed.html b/dom/base/test/file_youtube_flash_embed.html
new file mode 100644
index 0000000000..0eb63477f4
--- /dev/null
+++ b/dom/base/test/file_youtube_flash_embed.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1240471
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1240471</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ let SimpleTest = {
+ finish() {
+ parent.postMessage(JSON.stringify({fn: "finish"}), "*");
+ }
+ };
+ ["ok", "is", "info"].forEach(fn => {
+ self[fn] = function (...args) {
+ parent.postMessage(JSON.stringify({fn, args}), "*");
+ }
+ });
+ "use strict";
+ function onLoad() {
+ let youtube_changed_url_query = "https://mochitest.youtube.com/embed/Xm5i5kbIXzc?start=10&end=20";
+
+ function testEmbed(embed, expected_url, expected_fullscreen) {
+ ok (!!embed, "Embed node exists");
+ // getSVGDocument will return HTMLDocument if the content is HTML
+ let doc = embed.getSVGDocument();
+ // doc must be unprivileged because privileged doc will always be
+ // allowed to use fullscreen.
+ is (doc.fullscreenEnabled, expected_fullscreen,
+ "fullscreen should be " + (expected_fullscreen ? "enabled" : "disabled"));
+ embed = SpecialPowers.wrap(embed);
+ is (embed.srcURI.spec, expected_url, "Should have src uri of " + expected_url);
+ }
+ info("Running youtube rewrite query test");
+ testEmbed(document.getElementById("testembed-correct"), youtube_changed_url_query, false);
+ testEmbed(document.getElementById("testembed-correct-fs"), youtube_changed_url_query, true);
+ testEmbed(document.getElementById("testembed-wrong"), youtube_changed_url_query, false);
+ testEmbed(document.getElementById("testembed-whywouldyouevendothat"), youtube_changed_url_query, true);
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="onLoad()">
+ <embed id="testembed-correct"
+ src="https://mochitest.youtube.com/v/Xm5i5kbIXzc?start=10&end=20"
+ type="application/x-shockwave-flash"
+ allowscriptaccess="always"></embed>
+ <embed id="testembed-correct-fs"
+ src="https://mochitest.youtube.com/v/Xm5i5kbIXzc?start=10&end=20"
+ type="application/x-shockwave-flash"
+ allowfullscreen
+ allowscriptaccess="always"></embed>
+ <embed id="testembed-wrong"
+ src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10&end=20"
+ type="application/x-shockwave-flash"
+ allowscriptaccess="always"></embed>
+ <embed id="testembed-whywouldyouevendothat"
+ src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10?end=20"
+ type="application/x-shockwave-flash"
+ allowfullscreen
+ allowscriptaccess="always"></embed>
+ </body>
+</html>
diff --git a/dom/base/test/fmm/browser.ini b/dom/base/test/fmm/browser.ini
new file mode 100644
index 0000000000..19f9b584e4
--- /dev/null
+++ b/dom/base/test/fmm/browser.ini
@@ -0,0 +1 @@
+[browser_frame_message_manager_cache.js]
diff --git a/dom/base/test/fmm/browser_frame_message_manager_cache.js b/dom/base/test/fmm/browser_frame_message_manager_cache.js
new file mode 100644
index 0000000000..fcedd6721a
--- /dev/null
+++ b/dom/base/test/fmm/browser_frame_message_manager_cache.js
@@ -0,0 +1,23 @@
+add_task(async function testCacheAfterInvalidate() {
+ // Load some page to make scripts cached.
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:addons"
+ );
+
+ // Discard ScriptPreloader cache.
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+
+ // Load some other page to use the cache in nsMessageManagerScriptExecutor
+ // cache.
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+
+ // Verify the browser doesn't crash.
+ ok(true);
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/dom/base/test/forRemoval.resource b/dom/base/test/forRemoval.resource
new file mode 100644
index 0000000000..09e882b3ba
--- /dev/null
+++ b/dom/base/test/forRemoval.resource
@@ -0,0 +1,24 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+retry:500
+event: message
+data: 1
+
+retry:500
+event: message
+data: 2
+
+retry:500
+event: message
+data: 3
+
+retry:500
+event: message
+data: 4
+
+retry:500
+event: message
+data: 5
+
+
diff --git a/dom/base/test/forRemoval.resource^headers^ b/dom/base/test/forRemoval.resource^headers^
new file mode 100644
index 0000000000..6a63b5341d
--- /dev/null
+++ b/dom/base/test/forRemoval.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/formReset.html b/dom/base/test/formReset.html
new file mode 100644
index 0000000000..faee1e5b4d
--- /dev/null
+++ b/dom/base/test/formReset.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <title>Form Elements</title>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+ </head>
+ <body>
+ <p>Check me! <input type="checkbox" id="checkbox1"><br>
+ Uncheck me! <input type="checkbox" checked id="checkbox2"><br>
+ <input type="text" size=30 value="Write something here" id="textinput"><br>
+ <textarea id="textarea">Write something here</textarea><br>
+ </p>
+ </body>
+</html>
diff --git a/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml
new file mode 100644
index 0000000000..93f00311e7
--- /dev/null
+++ b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test that "MozDOMFullscreen:*" events are dispatched to chrome on documents that use DOM fullscreen.
+
+ Test Description:
+
+ This chrome window has a browser. The browser's contentDocument (the "outer document")
+ in turn has an iframe (the "inner document").
+
+ We request fullscreen in the outer document, and check that MozDOMFullscreen:Entered and
+ MozDOMFullscreen:NewOrigin are dispatched to chrome, targeted at the outer document.
+
+ Then we request fullscreen in the inner document, and check that MozDOMFullscreen:NewOrigin
+ is dispatched to chrome, targeted at the inner document.
+
+ Then we cancel fullscreen in the inner document, and check that MozDOMFullscreen:NewOrigin is
+ dispatched again to chrome, targeted at the outer document.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="start();">
+
+<script src="chrome://mochikit/content/chrome-harness.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript"><![CDATA[
+
+function ok(condition, msg) {
+ window.arguments[0].ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ window.arguments[0].is(a, b, msg);
+}
+
+var gBrowser = null;
+var gOuterDoc = null;
+var gInnerDoc = null;
+
+var gReceivedFullscreenEnteredEvent = false;
+function firstEntry(event) {
+ if (event.type == "MozDOMFullscreen:NewOrigin") {
+ ok(false, "MozDOMFullscreen:NewOrigin shouldn't be triggered at first entry");
+ return;
+ }
+
+ if (event.type != "MozDOMFullscreen:Entered") {
+ ok(false, "Unknown event received");
+ return;
+ }
+
+ ok(gOuterDoc.fullscreenElement != null, "Outer doc should be in fullscreen");
+ is(event.target, gOuterDoc.body, "First MozDOMFullscreen:Entered should be targeted at outer body");
+ ok(!gReceivedFullscreenEnteredEvent, "MozDOMFullscreen:Entered shouldn't have been triggered twice");
+ gReceivedFullscreenEnteredEvent = true;
+ window.removeEventListener("MozDOMFullscreen:Entered", firstEntry);
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", firstEntry);
+
+ window.addEventListener("MozDOMFullscreen:NewOrigin", secondEntry);
+ gInnerDoc = gOuterDoc.getElementById("innerFrame").contentDocument;
+ gInnerDoc.defaultView.focus();
+ gInnerDoc.body.requestFullscreen();
+}
+
+function secondEntry(event) {
+ is(event.target, gInnerDoc, "Second MozDOMFullscreen:NewOrigin should be targeted at inner doc");
+ ok(gInnerDoc.fullscreenElement != null, "Inner doc should be in fullscreen");
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", secondEntry);
+ window.addEventListener("MozDOMFullscreen:NewOrigin", thirdEntry);
+ gInnerDoc.exitFullscreen();
+}
+
+function thirdEntry(event) {
+ is(event.target, gOuterDoc, "Third MozDOMFullscreen:NewOrigin should be targeted at outer doc");
+ ok(gOuterDoc.fullscreenElement != null, "Outer doc return to fullscreen after cancel fullscreen in inner doc");
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", thirdEntry);
+ window.removeEventListener("MozDOMFullscreen:Exited", earlyExit);
+ window.addEventListener("MozDOMFullscreen:Exited", lastExit);
+ gOuterDoc.exitFullscreen();
+}
+
+function earlyExit(event) {
+ ok(false, "MozDOMFullscreen:Exited should only be triggered after cancel all fullscreen");
+}
+
+function lastExit(event) {
+ is(event.target, gOuterDoc, "MozDOMFullscreen:Exited should be targeted at the last exited doc");
+ ok(gOuterDoc.fullscreenElement == null, "Fullscreen should have been fully exited");
+ window.arguments[0].done();
+}
+
+function start() {
+ SimpleTest.waitForFocus(
+ function() {
+ gBrowser = document.getElementById("browser");
+ gOuterDoc = gBrowser.contentDocument;
+ gBrowser.contentWindow.focus();
+ window.addEventListener("MozDOMFullscreen:Entered", firstEntry);
+ window.addEventListener("MozDOMFullscreen:NewOrigin", firstEntry);
+ gOuterDoc.body.requestFullscreen();
+ });
+}
+
+]]>
+</script>
+<browser type="content" id="browser" width="400" height="400" src="file_MozDomFullscreen.html"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/browser.ini b/dom/base/test/fullscreen/browser.ini
new file mode 100644
index 0000000000..c472e09b5c
--- /dev/null
+++ b/dom/base/test/fullscreen/browser.ini
@@ -0,0 +1,40 @@
+[DEFAULT]
+tags = fullscreen
+head = head.js
+support-files =
+ dummy_page.html
+ file_fullscreen-api-keys.html
+ file_fullscreen-iframe-inner.html
+ file_fullscreen-iframe-middle.html
+ file_fullscreen-iframe-top.html
+ file_fullscreen-newtab.html
+ fullscreen_helpers.js
+
+[browser_fullscreen-api-keys.js]
+[browser_fullscreen-document-mutation.js]
+[browser_fullscreen-document-mutation-navigation.js]
+[browser_fullscreen-document-mutation-race.js]
+[browser_fullscreen-contextmenu-esc.js]
+[browser_fullscreen-navigation.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_fullscreen-navigation-history.js]
+[browser_fullscreen-navigation-race.js]
+[browser_fullscreen-newtab.js]
+skip-if =
+ os == 'mac' # Bug 1494843
+ os == 'linux' && bits == 64 && os_version == '18.04' # Bug 1601460
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_fullscreen-tab-close.js]
+[browser_fullscreen-tab-close-race.js]
+skip-if = !fission # Bug 1750901
+[browser_fullscreen-bug-1798219.js]
+skip-if =
+ !fission
+ !nightly_build # Bug 1818608
+support-files =
+ file_fullscreen-bug-1798219.html
+ file_fullscreen-bug-1798219-2.html
+[browser_fullscreen-window-open-race.js]
+[browser_fullscreen-sizemode.js]
+[browser_fullscreen_exit_on_external_protocol.js]
diff --git a/dom/base/test/fullscreen/browser_fullscreen-api-keys.js b/dom/base/test/fullscreen/browser_fullscreen-api-keys.js
new file mode 100644
index 0000000000..1b1a07975e
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-api-keys.js
@@ -0,0 +1,218 @@
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+/** Test for Bug 545812 **/
+
+// List of key codes which should exit full-screen mode.
+const kKeyList = [
+ { key: "Escape", keyCode: "VK_ESCAPE", suppressed: true },
+ { key: "F11", keyCode: "VK_F11", suppressed: false },
+];
+
+function receiveExpectedKeyEvents(aBrowser, aKeyCode, aTrusted) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [aKeyCode, aTrusted],
+ (keyCode, trusted) => {
+ return new Promise(resolve => {
+ let events = trusted
+ ? ["keydown", "keyup"]
+ : ["keydown", "keypress", "keyup"];
+ if (trusted && keyCode == content.wrappedJSObject.KeyEvent.DOM_VK_F11) {
+ // trusted `F11` key shouldn't be fired because of reserved when it's
+ // a shortcut key for exiting from the full screen mode.
+ events.shift();
+ }
+ function listener(event) {
+ let expected = events.shift();
+ Assert.equal(
+ event.type,
+ expected,
+ `Should receive a ${expected} event`
+ );
+ Assert.equal(
+ event.keyCode,
+ keyCode,
+ `Should receive the event with key code ${keyCode}`
+ );
+ if (!events.length) {
+ content.document.removeEventListener("keydown", listener, true);
+ content.document.removeEventListener("keyup", listener, true);
+ content.document.removeEventListener("keypress", listener, true);
+ resolve();
+ }
+ }
+
+ content.document.addEventListener("keydown", listener, true);
+ content.document.addEventListener("keyup", listener, true);
+ content.document.addEventListener("keypress", listener, true);
+ });
+ }
+ );
+}
+
+const kPage =
+ "https://example.org/browser/" +
+ "dom/base/test/fullscreen/file_fullscreen-api-keys.html";
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+
+ let tab = BrowserTestUtils.addTab(gBrowser, kPage);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ registerCleanupFunction(() => gBrowser.removeTab(tab));
+ await waitForDocLoadComplete();
+
+ // Wait for the document being activated, so that
+ // fullscreen request won't be denied.
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus(),
+ "document is active"
+ );
+ });
+
+ // Register listener to capture unexpected events
+ let keyEventsCount = 0;
+ let fullScreenEventsCount = 0;
+ let removeFullScreenListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ () => fullScreenEventsCount++
+ );
+ let removeKeyDownListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keydown",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+ let removeKeyPressListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keypress",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+ let removeKeyUpListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keyup",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+
+ let expectedFullScreenEventsCount = 0;
+ let expectedKeyEventsCount = 0;
+
+ for (let { key, keyCode, suppressed } of kKeyList) {
+ let keyCodeValue = KeyEvent["DOM_" + keyCode];
+ info(`Test keycode ${key} (${keyCodeValue})`);
+
+ info("Enter fullscreen");
+ let state = new Promise(resolve => {
+ let removeFun = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ async () => {
+ removeFun();
+ resolve(
+ await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ })
+ );
+ }
+ );
+ });
+ // request fullscreen
+ SpecialPowers.spawn(browser, [], () => {
+ content.document.body.requestFullscreen();
+ });
+ ok(await state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement, "The chrome should also be in fullscreen");
+
+ is(
+ fullScreenEventsCount,
+ ++expectedFullScreenEventsCount,
+ "correct number of fullscreen events occurred"
+ );
+
+ info("Dispatch untrusted key events from content");
+ let promiseExpectedKeyEvents = receiveExpectedKeyEvents(
+ browser,
+ keyCodeValue,
+ false
+ );
+
+ SpecialPowers.spawn(browser, [keyCode], keyCodeChild => {
+ var evt = new content.CustomEvent("Test:DispatchKeyEvents", {
+ detail: Cu.cloneInto({ code: keyCodeChild }, content),
+ });
+ content.dispatchEvent(evt);
+ });
+ await promiseExpectedKeyEvents;
+
+ expectedKeyEventsCount += 3;
+ is(
+ keyEventsCount,
+ expectedKeyEventsCount,
+ "correct number of key events occurred"
+ );
+
+ info("Send trusted key events");
+
+ state = new Promise(resolve => {
+ let removeFun = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ async () => {
+ removeFun();
+ resolve(
+ await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ })
+ );
+ }
+ );
+ });
+
+ promiseExpectedKeyEvents = suppressed
+ ? Promise.resolve()
+ : receiveExpectedKeyEvents(browser, keyCodeValue, true);
+ await SpecialPowers.spawn(browser, [], () => {});
+
+ EventUtils.synthesizeKey("KEY_" + key);
+ await promiseExpectedKeyEvents;
+
+ ok(!(await state), "The content should have exited fullscreen");
+ ok(
+ !document.fullscreenElement,
+ "The chrome should also have exited fullscreen"
+ );
+
+ is(
+ fullScreenEventsCount,
+ ++expectedFullScreenEventsCount,
+ "correct number of fullscreen events occurred"
+ );
+ if (!suppressed) {
+ expectedKeyEventsCount += keyCode == "VK_F11" ? 1 : 3;
+ }
+ is(
+ keyEventsCount,
+ expectedKeyEventsCount,
+ "correct number of key events occurred"
+ );
+ }
+
+ removeFullScreenListener();
+ removeKeyDownListener();
+ removeKeyPressListener();
+ removeKeyUpListener();
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js b/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js
new file mode 100644
index 0000000000..2aef23b042
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Import helpers
+/* import-globals-from fullscreen_helpers.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error, https://bugzilla.mozilla.org/show_bug.cgi?id=1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function waitAndCheckFullscreenState(aWindow) {
+ // Wait fullscreen exit event if browser is still in fullscreen mode.
+ if (
+ aWindow.fullScreen ||
+ aWindow.document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("The widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(aWindow, false, true);
+ }
+ if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("The chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(aWindow, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!aWindow.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !aWindow.document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+}
+
+add_task(async () => {
+ const URL =
+ "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html";
+ // We need this dummy tab which load the same URL as test tab to keep the
+ // original content process alive after test page navigates away.
+ let dummyTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: URL,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.querySelector("button").click();
+ });
+
+ // Test requests fullscreen and performs navigation simultaneously,
+ // the fullscreen request might be rejected directly if navigation happens
+ // first, so there might be no reliable state that we can wait. So give
+ // some time for possible fullscreen transition instead and ensure window
+ // should end up exiting fullscreen.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+ await waitAndCheckFullscreenState(window);
+ }
+ );
+
+ let dummyTabClosed = BrowserTestUtils.waitForTabClosing(dummyTab);
+ BrowserTestUtils.removeTab(dummyTab);
+ await dummyTabClosed;
+});
+
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html",
+ },
+ async function (browser) {
+ // Open a new window to run the tests, the original window will keep the
+ // original content process alive after the test window navigates away.
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.querySelector("button").click();
+ });
+ let newWindow = await promiseWin;
+
+ await SpecialPowers.spawn(
+ newWindow.gBrowser.selectedTab.linkedBrowser,
+ [],
+ function () {
+ content.document.querySelector("button").click();
+ }
+ );
+
+ // Test requests fullscreen and performs navigation simultaneously,
+ // the fullscreen request might be rejected directly if navigation happens
+ // first, so there might be no reliable state that we can wait. So give
+ // some time for possible fullscreen transition instead and ensure window
+ // should end up exiting fullscreen.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+ await waitAndCheckFullscreenState(newWindow);
+
+ newWindow.close();
+ }
+ );
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js b/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js
new file mode 100644
index 0000000000..e89409a90f
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js
@@ -0,0 +1,128 @@
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "Caught an unexpected fullscreen change");
+}
+
+const kPage =
+ "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+
+function waitForDocActivated(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus()
+ );
+ });
+}
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: kPage,
+ waitForStateStop: true,
+ });
+ let browser = tab.linkedBrowser;
+
+ // As requestFullscreen checks the active state of the docshell,
+ // wait for the document to be activated, just to be sure that
+ // the fullscreen request won't be denied.
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus()
+ );
+ });
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu");
+
+ let state;
+ info("Enter DOM fullscreen");
+ let fullScreenChangedPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.requestFullscreen();
+ });
+
+ await fullScreenChangedPromise;
+ state = await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ });
+ ok(state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement, "The chrome should also be in fullscreen");
+
+ let removeContentEventListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ captureUnexpectedFullscreenChange
+ );
+
+ info("Open context menu");
+ is(contextMenu.state, "closed", "Should not have opened context menu");
+
+ let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+
+ EventUtils.synthesizeMouse(
+ browser,
+ screen.width / 2,
+ screen.height / 2,
+ { type: "contextmenu", button: 2 },
+ window
+ );
+ await popupShownPromise;
+ is(contextMenu.state, "open", "Should have opened context menu");
+
+ let popupHidePromise = promiseWaitForEvent(window, "popuphidden");
+
+ if (
+ !AppConstants.platform == "macosx" ||
+ !Services.prefs.getBoolPref("widget.macos.native-context-menus", false)
+ ) {
+ info("Send the first escape");
+ EventUtils.synthesizeKey("KEY_Escape");
+ } else {
+ // We cannot synthesize key events at native macOS menus.
+ // We also do not see key events that are processed by native macOS menus,
+ // so we cannot accidentally exit fullscreen when the user closes a native
+ // menu with Escape.
+ // Close the menu normally.
+ info("Close the context menu");
+ contextMenu.hidePopup();
+ }
+ await popupHidePromise;
+ is(contextMenu.state, "closed", "Should have closed context menu");
+
+ // Wait a small time to confirm that the first ESC key
+ // does not exit fullscreen.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ state = await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ });
+ ok(state, "The content should still be in fullscreen");
+ ok(document.fullscreenElement, "The chrome should still be in fullscreen");
+
+ removeContentEventListener();
+ info("Send the second escape");
+ let fullscreenExitPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "fullscreenchange"
+ );
+ EventUtils.synthesizeKey("KEY_Escape");
+ await fullscreenExitPromise;
+ ok(!document.fullscreenElement, "The chrome should have exited fullscreen");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js
new file mode 100644
index 0000000000..f6b5715f59
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false, true);
+ await testFun(browser);
+ await promiseFsState;
+
+ // This test triggers a fullscreen request during the fullscreen exit
+ // process, so it could be possible that the widget or the chrome
+ // document goes into fullscreen mode again, but they should end up
+ // leaving fullscreen mode again.
+ if (
+ window.fullScreen ||
+ document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(window, false, true);
+ }
+ if (document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(document, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function MutateAndNavigateFromRemoteDocument(
+ aBrowsingContext,
+ aElementId,
+ aURL
+) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId, aURL],
+ async function (id, url) {
+ let element = content.document.getElementById(id);
+ element.requestFullscreen();
+ content.document.body.appendChild(element);
+ content.location.href = url;
+ }
+ );
+}
+
+startTests(async browser => {
+ // toplevel
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext,
+ "div",
+ "about:blank"
+ );
+}, "document_mutation_navigation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // middle iframe
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div",
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_navigation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // innermost iframe
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div",
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js
new file mode 100644
index 0000000000..75ca199aaa
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(setupFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = Promise.all([
+ setupFun(browser),
+ waitForFullscreenState(document, false, true),
+ ]);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function RemoveElementFromRemoteDocument(aBrowsingContext, aElementId) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId],
+ async function (id) {
+ content.document.addEventListener(
+ "fullscreenchange",
+ function () {
+ content.document.getElementById(id).remove();
+ },
+ { once: true }
+ );
+ }
+ );
+}
+
+startTests(async browser => {
+ // toplevel
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ await RemoveElementFromRemoteDocument(browser.browsingContext, "div");
+ return promise;
+}, "document_mutation_toplevel");
+
+startTests(async browser => {
+ // middle iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div"
+ );
+ return promise;
+}, "document_mutation_middle_frame");
+
+startTests(async browser => {
+ // innermost iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ [browser.browsingContext.children[0].children[0], "inner"],
+ ]);
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div"
+ );
+ return promise;
+}, "document_mutation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js
new file mode 100644
index 0000000000..7cecdabb95
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false);
+ await testFun(browser);
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function RemoveElementFromRemoteDocument(aBrowsingContext, aElementId) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId],
+ async function (id) {
+ content.document.getElementById(id).remove();
+ }
+ );
+}
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // toplevel
+ await RemoveElementFromRemoteDocument(browser.browsingContext, "div");
+ await promiseRemoteFsState;
+}, "document_mutation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // middle iframe
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ [browser.browsingContext.children[0].children[0], "inner"],
+ ]);
+ // innermost iframe
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js
new file mode 100644
index 0000000000..08624ed327
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error, bug 1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+function preventBFCache(aBrowsingContext, aPrevent) {
+ return SpecialPowers.spawn(aBrowsingContext, [aPrevent], prevent => {
+ if (prevent) {
+ // Using a dummy onunload listener to disable the bfcache.
+ content.window.addEventListener("unload", () => {});
+ }
+ content.window.addEventListener(
+ "pagehide",
+ e => {
+ // XXX checking persisted property causes intermittent failures, so we
+ // dump the value instead, bug 1822263.
+ // is(e.persisted, !prevent, `Check BFCache state`);
+ info(`Check BFCache state: e.persisted is ${e.persisted}`);
+ },
+ { once: true }
+ );
+ });
+}
+
+[true, false].forEach(crossOrigin => {
+ [true, false].forEach(initialPagePreventsBFCache => {
+ [true, false].forEach(fullscreenPagePreventsBFCache => {
+ add_task(async function navigation_history() {
+ info(
+ `crossOrigin: ${crossOrigin}, initialPagePreventsBFCache: ${initialPagePreventsBFCache}, fullscreenPagePreventsBFCache: ${fullscreenPagePreventsBFCache}`
+ );
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/dom/base/test/fullscreen/dummy_page.html",
+ },
+ async function (browser) {
+ // Maybe prevent BFCache on initial page.
+ await preventBFCache(
+ browser.browsingContext,
+ initialPagePreventsBFCache
+ );
+
+ // Navigate to fullscreen page.
+ const url = crossOrigin
+ ? "https://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html"
+ : "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html";
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+
+ // Maybe prevent BFCache on fullscreen test page.
+ await preventBFCache(
+ browser.browsingContext,
+ fullscreenPagePreventsBFCache
+ );
+
+ // Trigger click event to enter fullscreen.
+ let promiseFsState = waitForFullscreenState(document, true);
+ SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ });
+ await promiseFsState;
+
+ // Navigate back to the previous page should exit fullscreen.
+ promiseFsState = waitForFullscreenState(document, false);
+ await SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.window.history.back();
+ });
+ await promiseFsState;
+ }
+ );
+ });
+ });
+ });
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js
new file mode 100644
index 0000000000..f9d1543a1a
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async function navigation() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <button id="button">Click here</button>
+ <script>
+ let button = document.getElementById("button");
+ button.addEventListener("click", function() {
+ button.requestFullscreen();
+ location.href = "about:blank";
+ });
+ </script>`,
+ },
+ async function (browser) {
+ BrowserTestUtils.synthesizeMouseAtCenter("#button", {}, browser);
+
+ // Give some time for fullscreen transition.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+
+ // Wait fullscreen exit event if browser is still in fullscreen mode.
+ if (
+ window.fullScreen ||
+ document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("The widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(window, false, true);
+ }
+ if (document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("The chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(window, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+});
+
+async function startTests(setupFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = Promise.all([
+ setupFun(browser),
+ waitForFullscreenState(document, false, true),
+ ]);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function NavigateRemoteDocument(aBrowsingContext, aURL) {
+ return SpecialPowers.spawn(aBrowsingContext, [aURL], async function (url) {
+ content.document.addEventListener(
+ "fullscreenchange",
+ function () {
+ content.location.href = url;
+ },
+ { once: true }
+ );
+ });
+}
+
+startTests(async browser => {
+ // toplevel
+ await NavigateRemoteDocument(browser.browsingContext, "about:blank");
+}, "navigation_toplevel");
+
+startTests(async browser => {
+ // middle iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0],
+ "about:blank"
+ );
+ return promise;
+}, "navigation_middle_frame");
+
+startTests(async browser => {
+ // innermost iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "about:blank"
+ );
+ return promise;
+}, "navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation.js b/dom/base/test/fullscreen/browser_fullscreen-navigation.js
new file mode 100644
index 0000000000..02387eb437
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async function navigation() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <button id="button">Click here</button>
+ <script>
+ let button = document.getElementById("button");
+ button.addEventListener("click", function() {
+ button.requestFullscreen();
+ });
+ </script>`,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event
+ BrowserTestUtils.synthesizeMouseAtCenter("#button", {}, browser);
+ await promiseFsState;
+
+ promiseFsState = waitForFullscreenState(document, false);
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.location.href = "about:blank";
+ });
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false);
+ await testFun(browser);
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function NavigateRemoteDocument(aBrowsingContext, aURL) {
+ return SpecialPowers.spawn(aBrowsingContext, [aURL], async function (url) {
+ content.location.href = url;
+ });
+}
+
+startTests(async browser => {
+ // toplevel
+ await NavigateRemoteDocument(browser.browsingContext, "about:blank");
+}, "navigation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // middle iframe
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0],
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "navigation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // innermost iframe
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-newtab.js b/dom/base/test/fullscreen/browser_fullscreen-newtab.js
new file mode 100644
index 0000000000..af714b1248
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-newtab.js
@@ -0,0 +1,91 @@
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+const kPage =
+ "https://example.org/browser/" +
+ "dom/base/test/fullscreen/file_fullscreen-newtab.html";
+
+function getSizeMode() {
+ return document.documentElement.getAttribute("sizemode");
+}
+
+async function runTest() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: kPage,
+ },
+ async function (browser) {
+ let promiseFsEvents = SpecialPowers.spawn(browser, [], function () {
+ return new Promise(resolve => {
+ let countFsChange = 0;
+ let countFsError = 0;
+ function checkAndResolve() {
+ if (countFsChange > 0 && countFsError > 0) {
+ Assert.ok(
+ false,
+ "Got both fullscreenchange and fullscreenerror events"
+ );
+ } else if (countFsChange > 2) {
+ Assert.ok(false, "Got too many fullscreenchange events");
+ } else if (countFsError > 1) {
+ Assert.ok(false, "Got too many fullscreenerror events");
+ } else if (countFsChange == 2 || countFsError == 1) {
+ resolve();
+ }
+ }
+
+ content.document.addEventListener("fullscreenchange", () => {
+ ++countFsChange;
+ checkAndResolve();
+ });
+ content.document.addEventListener("fullscreenerror", () => {
+ ++countFsError;
+ checkAndResolve();
+ });
+ });
+ });
+ let promiseNewTab = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:blank"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter("#link", {}, browser);
+ let [newtab] = await Promise.all([promiseNewTab, promiseFsEvents]);
+ await BrowserTestUtils.removeTab(newtab);
+
+ // Ensure the browser exits fullscreen state in reasonable time.
+ await Promise.race([
+ BrowserTestUtils.waitForCondition(() => getSizeMode() == "normal"),
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ new Promise(resolve => setTimeout(resolve, 2000)),
+ ]);
+
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.fullscreen,
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+}
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+ await runTest();
+});
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "200 200"],
+ ["full-screen-api.transition-duration.leave", "200 200"]
+ );
+ await runTest();
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-sizemode.js b/dom/base/test/fullscreen/browser_fullscreen-sizemode.js
new file mode 100644
index 0000000000..0aa79e5694
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-sizemode.js
@@ -0,0 +1,225 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const isMac = AppConstants.platform == "macosx";
+const isWin = AppConstants.platform == "win";
+
+async function waitForSizeMode(aWindow, aSizeMode) {
+ await BrowserTestUtils.waitForEvent(aWindow, "sizemodechange", false, () => {
+ return aWindow.windowState === aSizeMode;
+ });
+ const expectedHidden =
+ aSizeMode == aWindow.STATE_MINIMIZED || aWindow.isFullyOccluded;
+ if (aWindow.document.hidden != expectedHidden) {
+ await BrowserTestUtils.waitForEvent(aWindow, "visibilitychange");
+ }
+ is(
+ aWindow.document.hidden,
+ expectedHidden,
+ "Should be inactive if minimized or occluded"
+ );
+}
+
+async function checkSizeModeAndFullscreenState(
+ aWindow,
+ aSizeMode,
+ aFullscreen,
+ aFullscreenEventShouldHaveFired,
+ aStepFun
+) {
+ let promises = [];
+ if (aWindow.windowState != aSizeMode) {
+ promises.push(waitForSizeMode(aWindow, aSizeMode));
+ }
+ if (aFullscreenEventShouldHaveFired) {
+ promises.push(
+ BrowserTestUtils.waitForEvent(
+ aWindow,
+ aFullscreen ? "willenterfullscreen" : "willexitfullscreen"
+ )
+ );
+ promises.push(BrowserTestUtils.waitForEvent(aWindow, "fullscreen"));
+ }
+
+ // Add listener for unexpected event.
+ let unexpectedEventListener = aEvent => {
+ ok(false, `should not receive ${aEvent.type} event`);
+ };
+ if (aFullscreenEventShouldHaveFired) {
+ aWindow.addEventListener(
+ aFullscreen ? "willexitfullscreen" : "willenterfullscreen",
+ unexpectedEventListener
+ );
+ } else {
+ aWindow.addEventListener("willenterfullscreen", unexpectedEventListener);
+ aWindow.addEventListener("willexitfullscreen", unexpectedEventListener);
+ aWindow.addEventListener("fullscreen", unexpectedEventListener);
+ }
+
+ let eventPromise = Promise.all(promises);
+ aStepFun();
+ await eventPromise;
+
+ // Check SizeMode.
+ is(
+ aWindow.windowState,
+ aSizeMode,
+ "The new sizemode should have the expected value"
+ );
+ // Check Fullscreen state.
+ is(
+ aWindow.fullScreen,
+ aFullscreen,
+ `chrome window should ${aFullscreen ? "be" : "not be"} in fullscreen`
+ );
+ is(
+ aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ aFullscreen,
+ `chrome documentElement should ${
+ aFullscreen ? "have" : "not have"
+ } inFullscreen attribute`
+ );
+
+ // Remove listener for unexpected event.
+ if (aFullscreenEventShouldHaveFired) {
+ aWindow.removeEventListener(
+ aFullscreen ? "willexitfullscreen" : "willenterfullscreen",
+ unexpectedEventListener
+ );
+ } else {
+ aWindow.removeEventListener("willenterfullscreen", unexpectedEventListener);
+ aWindow.removeEventListener("willexitfullscreen", unexpectedEventListener);
+ aWindow.removeEventListener("fullscreen", unexpectedEventListener);
+ }
+}
+
+async function restoreWindowToNormal(aWindow) {
+ while (aWindow.windowState != aWindow.STATE_NORMAL) {
+ info(`Try to restore window with state ${aWindow.windowState} to normal`);
+ let eventPromise = BrowserTestUtils.waitForEvent(aWindow, "sizemodechange");
+ aWindow.restore();
+ await eventPromise;
+ info(`Window is now in state ${aWindow.windowState}`);
+ }
+}
+
+add_task(async function test_fullscreen_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_NORMAL,
+ false,
+ true,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// This test only enable on Windows because:
+// - Test gets intermittent timeout on macOS, see bug 1828848.
+// - Restoring a fullscreen window on GTK doesn't return it to the previous
+// sizemode, see bug 1828837.
+if (isWin) {
+ add_task(async function test_maximize_fullscreen_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Maximize window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MAXIMIZED,
+ false,
+ false,
+ () => {
+ win.maximize();
+ }
+ );
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MAXIMIZED,
+ false,
+ true,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ });
+}
+
+// Restoring a minimized window on macOS doesn't return it to the previous
+// sizemode, see bug 1828706.
+if (!isMac) {
+ add_task(async function test_fullscreen_minimize_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Minimize window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MINIMIZED,
+ true,
+ false,
+ () => {
+ win.minimize();
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ false,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ });
+}
diff --git a/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js
new file mode 100644
index 0000000000..f1b300b93a
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(setupFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenExit(document);
+ setupFun(browser);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+async function WaitRemoveDocumentAndCloseTab(aBrowser, aBrowsingContext) {
+ await SpecialPowers.spawn(aBrowsingContext, [], async function () {
+ return new Promise(resolve => {
+ content.document.addEventListener(
+ "fullscreenchange",
+ e => {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ });
+
+ // This should exit fullscreen
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ BrowserTestUtils.removeTab(tab);
+}
+
+startTests(async browser => {
+ // toplevel
+ WaitRemoveDocumentAndCloseTab(browser, browser.browsingContext);
+}, "tab_close_toplevel");
+
+startTests(browser => {
+ // middle iframe
+ WaitRemoveDocumentAndCloseTab(browser, browser.browsingContext.children[0]);
+}, "tab_close_middle_frame");
+
+startTests(async browser => {
+ // innermost iframe
+ WaitRemoveDocumentAndCloseTab(
+ browser,
+ browser.browsingContext.children[0].children[0]
+ );
+}, "tab_close_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-tab-close.js b/dom/base/test/fullscreen/browser_fullscreen-tab-close.js
new file mode 100644
index 0000000000..7d1772cd48
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-tab-close.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ let promiseFsExit = waitForFullscreenExit(document, false);
+ // This should exit fullscreen
+ let tab = gBrowser.getTabForBrowser(browser);
+ BrowserTestUtils.removeTab(tab);
+ await promiseFsExit;
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js b/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js
new file mode 100644
index 0000000000..4cf8a3d8c7
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error, bug 1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async () => {
+ const url =
+ "http://mochi.test:8888/browser/dom/base/test/fullscreen/dummy_page.html";
+ const name = "foo";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ info("open new window");
+ SpecialPowers.spawn(browser, [url, name], function (u, n) {
+ content.document.notifyUserGestureActivation();
+ content.window.open(u, n, "width=100,height=100");
+ });
+ let newWin = await BrowserTestUtils.waitForNewWindow({ url });
+ await SimpleTest.promiseFocus(newWin);
+
+ info("re-focusing main window");
+ await SimpleTest.promiseFocus(window);
+
+ info("open an existing window and request fullscreen");
+ await SpecialPowers.spawn(browser, [url, name], function (u, n) {
+ content.document.notifyUserGestureActivation();
+ content.window.open(u, n);
+ content.document.body.requestFullscreen();
+ });
+
+ // We call window.open() first than requestFullscreen() in a row on
+ // content page, but given that focus sync-up takes several IPC exchanges,
+ // so parent process ends up processing the requests in a reverse order,
+ // which should reject the fullscreen request and leave fullscreen.
+ await waitWidgetFullscreenEvent(window, false, true);
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+
+ await BrowserTestUtils.closeWindow(newWin);
+ }
+ );
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js b/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js
new file mode 100644
index 0000000000..6f525da541
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+
+const gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+const CONTENT = `data:text/html,
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <button>
+ <a href="mailto:test@example.com"></a>
+ </button>
+ </body>
+ </html>
+`;
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+function setupMailHandler() {
+ let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ let gOldMailHandlers = [];
+
+ // Remove extant web handlers because they have icons that
+ // we fetch from the web, which isn't allowed in tests.
+ let handlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ gOldMailHandlers.push(handler);
+ // If we get here, this is a web handler app. Remove it:
+ handlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+
+ let previousHandling = mailHandlerInfo.alwaysAskBeforeHandling;
+ mailHandlerInfo.alwaysAskBeforeHandling = true;
+
+ // Create a dummy web mail handler so we always know the mailto: protocol.
+ // Without this, the test fails on VMs without a default mailto: handler,
+ // because no dialog is ever shown, as we ignore subframe navigations to
+ // protocols that cannot be handled.
+ let dummy = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ dummy.name = "Handler 1";
+ dummy.uriTemplate = "https://example.com/first/%s";
+ mailHandlerInfo.possibleApplicationHandlers.appendElement(dummy);
+
+ gHandlerSvc.store(mailHandlerInfo);
+ registerCleanupFunction(() => {
+ // Re-add the original protocol handlers:
+ let mailHandlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ // See if this is a web handler. If it is, it'll throw, otherwise,
+ // we will remove it.
+ mailHandlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ mailHandlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+ for (let h of gOldMailHandlers) {
+ mailHandlers.appendElement(h);
+ }
+ mailHandlerInfo.alwaysAskBeforeHandling = previousHandling;
+ gHandlerSvc.store(mailHandlerInfo);
+ });
+}
+
+add_task(setupMailHandler);
+
+// Fullscreen is canceled during fullscreen transition
+add_task(async function OpenExternalProtocolOnPendingLaterFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ const leavelFullscreen = waitForFullscreenState(document, false, true);
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ const button = content.document.querySelector("button");
+
+ const clickDone = new Promise(r => {
+ button.addEventListener(
+ "click",
+ function () {
+ content.document.documentElement.requestFullscreen();
+ // When anchor.click() is called, the fullscreen request
+ // is probably still pending.
+ content.setTimeout(() => {
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ r();
+ }, 0);
+ },
+ { once: true }
+ );
+ });
+ button.click();
+ await clickDone;
+ }
+ );
+
+ await leavelFullscreen;
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
+
+// Fullscreen is canceled immediately.
+add_task(async function OpenExternalProtocolOnPendingFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ const button = content.document.querySelector("button");
+
+ const clickDone = new Promise(r => {
+ button.addEventListener(
+ "click",
+ function () {
+ content.document.documentElement
+ .requestFullscreen()
+ .then(() => {
+ ok(false, "Don't enter fullscreen");
+ })
+ .catch(() => {
+ ok(true, "Cancel entering fullscreen");
+ r();
+ });
+ // When anchor.click() is called, the fullscreen request
+ // is probably still pending.
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ },
+ { once: true }
+ );
+ });
+ button.click();
+ await clickDone;
+ }
+ );
+
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
+
+add_task(async function OpenExternalProtocolOnFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ const leavelFullscreen = waitForFullscreenState(document, false, true);
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ let button = content.document.querySelector("button");
+ button.addEventListener("click", function () {
+ content.document.documentElement.requestFullscreen();
+ });
+ button.click();
+
+ await new Promise(r => {
+ content.document.addEventListener("fullscreenchange", r);
+ });
+
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ }
+ );
+
+ await leavelFullscreen;
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
diff --git a/dom/base/test/fullscreen/chrome.ini b/dom/base/test/fullscreen/chrome.ini
new file mode 100644
index 0000000000..cf60c0f0b5
--- /dev/null
+++ b/dom/base/test/fullscreen/chrome.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+tags = fullscreen
+
+[test_fullscreen.xhtml]
+support-files =
+ file_MozDomFullscreen.html
+[test_MozDomFullscreen_event.xhtml]
+support-files =
+ fullscreen.xhtml
+ MozDomFullscreen_chrome.xhtml
diff --git a/dom/base/test/fullscreen/dummy_page.html b/dom/base/test/fullscreen/dummy_page.html
new file mode 100644
index 0000000000..fd238954c6
--- /dev/null
+++ b/dom/base/test/fullscreen/dummy_page.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Dummy test page</title>
+<meta charset="utf-8"/>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_MozDomFullscreen.html b/dom/base/test/fullscreen/file_MozDomFullscreen.html
new file mode 100644
index 0000000000..f954892706
--- /dev/null
+++ b/dom/base/test/fullscreen/file_MozDomFullscreen.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body style="background-color: blue;">
+<p>Outer doc</p>
+<iframe id="innerFrame" src="http://mochi.test:8888/"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api-keys.html b/dom/base/test/fullscreen/file_fullscreen-api-keys.html
new file mode 100644
index 0000000000..f526aa55ba
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api-keys.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+<script>
+window.addEventListener("Test:DispatchKeyEvents", aEvent => {
+ var keyCode = KeyEvent["DOM_" + aEvent.detail.code];
+
+ document.body.focus();
+ var evt = new KeyboardEvent("keydown", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+
+ evt = new KeyboardEvent("keypress", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+
+ evt = new KeyboardEvent("keyup", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api-race.html b/dom/base/test/fullscreen/file_fullscreen-api-race.html
new file mode 100644
index 0000000000..8310bc0a60
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api-race.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Helper file for test_fullscreen-api-race.html</title>
+</head>
+<body onload="window.opener.postMessage('ready', '*');">
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api.html b/dom/base/test/fullscreen/file_fullscreen-api.html
new file mode 100644
index 0000000000..645e6ece46
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api.html
@@ -0,0 +1,340 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM full-screen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<div id="fullscreen-element"></div>
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[fullscreen] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[fullscreen] " + msg);
+}
+
+/*
+<html>
+ <body onload='document.body.requestFullscreen();'>
+ <iframe id='inner-frame'></iframe>
+ </body>
+</html>
+*/
+var iframeContents = "<html><body onload='parent.SimpleTest.waitForFocus(function(){document.body.requestFullscreen();});'><iframe id='inner-frame'></iframe></body></html>";
+
+var iframe = null;
+var outOfDocElement = null;
+var inDocElement = null;
+var container = null;
+var button = null;
+
+
+function sendMouseClick(element) {
+ synthesizeMouseAtCenter(element, {});
+}
+
+function assertPromiseResolved(promise, msg) {
+ let { state, value } = SpecialPowers.PromiseDebugging.getState(promise);
+ is(state, "fulfilled", "Promise should have been resolved " + msg);
+ is(value, undefined, "Promise should be resolved with undefined " + msg);
+}
+
+function assertPromiseRejected(promise, msg) {
+ let { state, reason } = SpecialPowers.PromiseDebugging.getState(promise);
+ is(state, "rejected", "Promise should have been rejected " + msg);
+ // XXX Actually we should be testing "instanceof TypeError", but it
+ // doesn't work as expected currently. See bug 1412856.
+ is(reason.name, "TypeError",
+ "Promise should be rejected with TypeError " + msg);
+}
+
+const FULLSCREEN_ELEMENT = document.getElementById("fullscreen-element");
+let promise;
+
+function enter1(event) {
+ is(event.target, FULLSCREEN_ELEMENT,
+ "Event target should be the fullscreen element #1");
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, FULLSCREEN_ELEMENT,
+ "Full-screen element should be div element.");
+ ok(document.fullscreenElement.matches(":fullscreen"),
+ "FSE should match :fullscreen");
+ addFullscreenChangeContinuation("exit", exit1);
+ FULLSCREEN_ELEMENT.remove();
+ is(document.fullscreenElement, null,
+ "Full-screen element should be null after removing.");
+}
+
+function exit1(event) {
+ document.body.appendChild(FULLSCREEN_ELEMENT);
+ is(document.fullscreenElement, null,
+ "Full-screen element should still be null after re-adding former FSE.");
+ is(event.target, document, "Event target should be the document #2");
+ ok(!document.fullscreen, "Document should not be in fullscreen");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ iframe = document.createElement("iframe");
+ iframe.allowFullscreen = true;
+ addFullscreenChangeContinuation("enter", enter2);
+ document.body.appendChild(iframe);
+ iframe.srcdoc = iframeContents;
+}
+
+function enter2(event) {
+ is(event.target, iframe,
+ "Event target should be the fullscreen iframe #3");
+ is(document.fullscreenElement, iframe,
+ "Full-screen element should be iframe element.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body,
+ "Full-screen element in subframe should be body");
+
+ // The iframe's body is full-screen. Cancel full-screen in the subdocument to return
+ // the full-screen element to the previous full-screen element. This causes
+ // a fullscreenchange event.
+ addFullscreenChangeContinuation("exit", exit2);
+ promise = document.exitFullscreen();
+}
+
+function exit2(event) {
+ is(document.fullscreenElement, null,
+ "Full-screen element should have rolled back.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in subframe should be null");
+ assertPromiseResolved(promise, "in exit2");
+
+ addFullscreenChangeContinuation("enter", enter3);
+ promise = FULLSCREEN_ELEMENT.requestFullscreen();
+}
+
+function enter3(event) {
+ is(event.target, FULLSCREEN_ELEMENT,
+ "Event target should be the fullscreen element #3");
+ is(document.fullscreenElement, FULLSCREEN_ELEMENT,
+ "Full-screen element should be div.");
+ assertPromiseResolved(promise, "in enter3");
+
+ // Transplant the FSE into subdoc. Should exit full-screen.
+ addFullscreenChangeContinuation("exit", exit3);
+ var _innerFrame = iframe.contentDocument.getElementById("inner-frame");
+ _innerFrame.contentDocument.body.appendChild(FULLSCREEN_ELEMENT);
+ is(document.fullscreenElement, null,
+ "Full-screen element transplanted, should be null.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in outer frame should be null.");
+ is(_innerFrame.contentDocument.fullscreenElement, null,
+ "Full-screen element in inner frame should be null.");
+}
+
+function exit3(event) {
+ document.body.appendChild(FULLSCREEN_ELEMENT);
+ is(event.target, document, "Event target should be the document #3");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ document.body.removeChild(iframe);
+ iframe = null;
+
+ // Do a request out of document. It should be denied.
+ // Continue test in the following fullscreenerror handler.
+ outOfDocElement = document.createElement("div");
+ addFullscreenErrorContinuation(error1);
+ promise = outOfDocElement.requestFullscreen();
+}
+
+function error1(event) {
+ ok(!document.fullscreenElement,
+ "Requests for full-screen from not-in-doc elements should fail.");
+ assertPromiseRejected(promise, "in error1");
+ container = document.createElement("div");
+ inDocElement = document.createElement("div");
+ container.appendChild(inDocElement);
+ FULLSCREEN_ELEMENT.appendChild(container);
+
+ addFullscreenChangeContinuation("enter", enter4);
+ inDocElement.requestFullscreen();
+}
+
+function enter4(event) {
+ is(event.target, inDocElement,
+ "Event target should be the fullscreen element #4");
+ is(document.fullscreenElement, inDocElement, "FSE should be inDocElement.");
+
+ // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_1);
+ container.remove();
+ is(document.fullscreenElement, null,
+ "Should not have a full-screen element again.");
+}
+
+async function exit_to_arg_test_1(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (third time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_1);
+ var threw = false;
+ try {
+ await FULLSCREEN_ELEMENT.requestFullscreen(123);
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with bogus arg (123) shouldn't throw exception");
+}
+
+function enter_from_arg_test_1(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with bogus (ignored) argument (fourth time)");
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_2);
+ document.exitFullscreen();
+}
+
+async function exit_to_arg_test_2(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (fourth time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_2);
+ var threw = false;
+ try {
+ await FULLSCREEN_ELEMENT.requestFullscreen({ vrDisplay: null });
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with { vrDisplay: null } shouldn't throw exception");
+}
+
+function enter_from_arg_test_2(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with vrDisplay null argument (fifth time)");
+ addFullscreenChangeContinuation("exit", exit4);
+ document.exitFullscreen();
+}
+
+function exit4(event) {
+ ok(!document.fullscreenElement,
+ "Should be back in non-full-screen mode (fifth time)");
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.allow-trusted-requests-only", true]]}, function() {
+ addFullscreenErrorContinuation(error2);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function error2(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because calling context isn't trusted.");
+ button = document.createElement("button");
+ button.onclick = function() {
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ };
+ FULLSCREEN_ELEMENT.appendChild(button);
+ addFullscreenChangeContinuation("enter", enter5);
+ sendMouseClick(button);
+}
+
+function enter5(event) {
+ ok(document.fullscreenElement, "Moved to full-screen after mouse click");
+ addFullscreenChangeContinuation("exit", exit5);
+ document.exitFullscreen();
+}
+
+function exit5(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (last time).");
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.enabled", false]]}, function() {
+ is(document.fullscreenEnabled, false, "document.fullscreenEnabled should be false if full-screen-api.enabled is false");
+ addFullscreenErrorContinuation(error3);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function error3(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because pref is not enabled.");
+
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.enabled", true]]}, function() {
+ is(document.fullscreenEnabled, true, "document.fullscreenEnabled should be true if full-screen-api.enabled is true");
+ opener.nextTest();
+ });
+}
+
+function begin() {
+ testNamespaces(() => {
+ addFullscreenChangeContinuation("enter", enter1);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function testNamespaces(followupTestFn) {
+ let tests = [
+ {allowed: false, name: "element", ns: "http://www.w3.org/XML/1998/namespace"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1999/xlink"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/2000/svg"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: false, name: "mathml", ns: "unknown"},
+ {allowed: false, name: "svg", ns: "unknown"},
+ {allowed: true, name: "element", ns: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"},
+ {allowed: true, name: "element", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/2000/svg"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: true, name: "element"},
+ ];
+
+ function runNextNamespaceTest() {
+ let test = tests.shift();
+ if (!test) {
+ followupTestFn();
+ return;
+ }
+
+ let elem = test.ns ? document.createElementNS(test.ns, test.name) :
+ document.createElement(test.name);
+ document.body.appendChild(elem);
+
+ if (test.allowed) {
+ addFullscreenChangeContinuation("enter", () => {
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, elem,
+ `Element named '${test.name}' in this namespace should be allowed: ${test.ns}`);
+ addFullscreenChangeContinuation("exit", () => {
+ document.body.removeChild(elem);
+ runNextNamespaceTest();
+ });
+ document.exitFullscreen();
+ });
+ } else {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ `Element named '${test.name}' in this namespace should not be allowed: ${test.ns}`);
+ document.body.removeChild(elem);
+ runNextNamespaceTest();
+ });
+ }
+
+ SimpleTest.waitForFocus(() => elem.requestFullscreen());
+ }
+
+ runNextNamespaceTest();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-async.html b/dom/base/test/fullscreen/file_fullscreen-async.html
new file mode 100644
index 0000000000..e9b4147124
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-async.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Test for Bug 1129227</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<style>
+</style>
+<button>Async Request Fullscreen</button>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[async] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[async] " + msg);
+}
+
+function begin() {
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, startTest);
+}
+
+function startTest() {
+ let button = document.querySelector("button");
+ button.addEventListener("click", () => {
+ setTimeout(() => document.body.requestFullscreen(), 0);
+ });
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ addFullscreenErrorContinuation(() => {
+ ok(false, "Failed to enter fullscreen");
+ exitedFullscreen();
+ });
+ synthesizeMouseAtCenter(button, {});
+}
+
+function enteredFullscreen() {
+ is(document.fullscreenElement, document.body, "Entered fullscreen");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ SpecialPowers.popPrefEnv(finish);
+}
+
+function finish() {
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-backdrop.html b/dom/base/test/fullscreen/file_fullscreen-backdrop.html
new file mode 100644
index 0000000000..27be77a6d1
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-backdrop.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1064843</title>
+ <style id="style"></style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html {
+ overflow: hidden;
+ }
+ #placeholder {
+ height: 1000vh;
+ }
+ </style>
+</head>
+<body>
+<div id="fullscreen"></div>
+<div id="placeholder"></div>
+<script>
+
+const gStyle = document.getElementById("style");
+const gFullscreen = document.getElementById("fullscreen");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[backdrop] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[backdrop] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[backdrop] " + msg);
+}
+
+function info(msg) {
+ opener.info("[backdrop] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+const gFullscreenElementBackground = getComputedStyle(gFullscreen).background;
+
+function begin() {
+ info("The default background of window should be white");
+ assertWindowPureColor(window, "white");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function setBackdropStyle(style) {
+ gStyle.textContent = `#fullscreen::backdrop { ${style} }`;
+}
+
+function enterFullscreen() {
+ is(getComputedStyle(gFullscreen).background, gFullscreenElementBackground,
+ "Computed background of #fullscreen shouldn't be changed");
+
+ info("The default background of backdrop for fullscreen is black");
+ assertWindowPureColor(window, "black");
+
+ setBackdropStyle("background: green");
+ info("The background color of backdrop should be changed to green");
+ assertWindowPureColor(window, "green");
+
+ gFullscreen.style.background = "blue";
+ info("The blue fullscreen element should cover the backdrop");
+ assertWindowPureColor(window, "blue");
+
+ gFullscreen.style.background = "";
+ setBackdropStyle("display: none");
+ info("The white body should be shown when the backdrop is hidden");
+ assertWindowPureColor(window, "white");
+
+ setBackdropStyle("");
+ info("Content should return to black because we restore the backdrop");
+ assertWindowPureColor(window, "black");
+
+ gFullscreen.style.display = "none";
+ info("The backdrop should disappear with the fullscreen element");
+ assertWindowPureColor(window, "white");
+
+ gFullscreen.style.display = "";
+ setBackdropStyle("position: absolute");
+ info("Changing position shouldn't immediately affect the view");
+ assertWindowPureColor(window, "black");
+
+ window.scroll(0, screen.height);
+ info("Scrolled up the absolutely-positioned element");
+ assertWindowPureColor(window, "white");
+
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html
new file mode 100644
index 0000000000..61db80c228
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<button>Launch</button>
+<script>
+let button = document.querySelector("button");
+button.addEventListener("click", function(e) {
+ let newWindow = window.open("", "", "newWindow");
+ newWindow.document.write(`<!DOCTYPE HTML>
+ <button>click me!</button>
+ <script>
+ let button = document.querySelector("button");
+ button.addEventListener("click", function(e) {
+ document.documentElement.requestFullscreen();
+ setTimeout(() => {
+ while(true) {
+ // slowdown event loop
+ };
+ }, 1);
+ location.href = "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+ });
+ <\/script>`);
+});
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html
new file mode 100644
index 0000000000..7490f12936
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<button>click me!</button>
+<script>
+let button = document.querySelector("button");
+button.addEventListener("click", function(e) {
+ document.documentElement.requestFullscreen();
+ setTimeout(() => {
+ while(true) {
+ // slowdown event loop
+ };
+ }, 1);
+ location.href = "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+});
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-denied-inner.html b/dom/base/test/fullscreen/file_fullscreen-denied-inner.html
new file mode 100644
index 0000000000..6b5916b2e2
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-denied-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+ function handler(evt) {
+ document.removeEventListener("fullscreenchange", handler);
+ document.removeEventListener("fullscreenerror", handler);
+ parent.is(evt.type, "fullscreenerror", "Request from " +
+ `document inside ${parent.testTargetName} should be denied`);
+ parent.continueTest();
+ }
+ parent.ok(!document.fullscreenEnabled, "Fullscreen " +
+ `should not be enabled in ${parent.testTargetName}`);
+ document.addEventListener("fullscreenchange", handler);
+ document.addEventListener("fullscreenerror", handler);
+ document.documentElement.requestFullscreen();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-denied.html b/dom/base/test/fullscreen/file_fullscreen-denied.html
new file mode 100644
index 0000000000..db9a69e71a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-denied.html
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM fullscreen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[denied] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[denied] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-denied-inner.html";
+function setupForInnerTest(targetName, callback) {
+ window.testTargetName = targetName;
+ window.continueTest = () => {
+ delete window.testTargetName;
+ delete window.continueTest;
+ callback();
+ };
+}
+
+function begin() {
+ document.addEventListener("fullscreenchange", () => {
+ ok(false, "Should never receive " +
+ "a fullscreenchange event in the main window.");
+ });
+ SimpleTest.executeSoon(testIFrameWithoutAllowFullscreen);
+}
+
+function testIFrameWithoutAllowFullscreen() {
+ // Create an iframe without an allowfullscreen attribute, whose
+ // contents request fullscreen. The request should be denied, and
+ // we should not receive a fullscreenchange event in this document.
+ var iframe = document.createElement("iframe");
+ iframe.src = INNER_FILE;
+ // The iframe is same-origin so when we use feature policy otherwise we'd hit
+ // the "allowed" code-path (as intended). It is a bug that this test passes
+ // without the allow attribute.
+ iframe.allow = "fullscreen 'none'";
+ setupForInnerTest("an iframe without allowfullscreen", () => {
+ document.body.removeChild(iframe);
+ SimpleTest.executeSoon(testFrameElement);
+ });
+ document.body.appendChild(iframe);
+}
+
+function testFrameElement() {
+ var frameset = document.createElement("frameset");
+ var frame = document.createElement("frame");
+ frame.src = INNER_FILE;
+ frameset.appendChild(frame);
+ setupForInnerTest("a frame element", () => {
+ document.documentElement.removeChild(frameset);
+ SimpleTest.executeSoon(testObjectElement);
+ });
+ document.documentElement.appendChild(frameset);
+}
+
+function testObjectElement() {
+ var objectElem = document.createElement("object");
+ objectElem.data = INNER_FILE;
+ setupForInnerTest("an object element", () => {
+ document.body.removeChild(objectElem);
+ // In the following tests we want to test trust context requirement
+ // of fullscreen request, so temporary re-enable this pref.
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, testNonTrustContext);
+ });
+ document.body.appendChild(objectElem);
+}
+
+function testNonTrustContext() {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in non-trust context.");
+ SimpleTest.executeSoon(testLongRunningEventHandler);
+ });
+ document.documentElement.requestFullscreen();
+}
+
+function testLongRunningEventHandler() {
+ let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+ function longRunningHandler() {
+ window.removeEventListener("keypress", longRunningHandler);
+ // Busy loop until transient useractivation is timed out, so our request for
+ // fullscreen should be rejected.
+ var end = (new Date()).getTime() + timeout;
+ while ((new Date()).getTime() < end) {
+ ; // Wait...
+ }
+ document.documentElement.requestFullscreen();
+ }
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in long-running event handler.");
+ SimpleTest.executeSoon(testFullscreenMouseBtn);
+ });
+ window.addEventListener("keypress", longRunningHandler);
+ sendString("a");
+}
+
+function requestFullscreenMouseBtn(event, button) {
+ let clickEl = document.createElement("p");
+ clickEl.innerText = "Click Me";
+
+ function eventHandler(evt) {
+ document.body.requestFullscreen();
+ evt.target.removeEventListener(evt, this);
+ }
+
+ clickEl.addEventListener(event, eventHandler);
+ document.body.appendChild(clickEl);
+ synthesizeMouseAtCenter(clickEl, { button });
+}
+
+async function testFullscreenMouseBtn(event, button, next) {
+ await SpecialPowers.pushPrefEnv({
+ "set": [["full-screen-api.mouse-event-allow-left-button-only", true]]
+ });
+ let fsRequestEvents = ["mousedown", "mouseup", "pointerdown", "pointerup"];
+ let mouseButtons = [1, 2];
+
+ for (let i = 0; i < fsRequestEvents.length; i++) {
+ let evt = fsRequestEvents[i];
+ for (let j = 0; j < mouseButtons.length; j++) {
+ let mouseButton = mouseButtons[j];
+ await new Promise(resolve => {
+ addFullscreenErrorContinuation(resolve);
+ requestFullscreenMouseBtn(evt, mouseButton);
+ });
+ ok(!document.fullscreenElement, `Should not grant request on '${evt}' triggered by mouse button ${mouseButton}`);
+ }
+ }
+ // Restore the pref environment we changed before
+ // entering testNonTrustContext.
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ finish();
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html
new file mode 100644
index 0000000000..d7d8a90aaf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html
@@ -0,0 +1,58 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ body:not(:fullscreen) {
+ background-color: blue;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+var escKeyReceived = false;
+var escKeySent = false;
+
+function keyHandler(event) {
+ if (escKeyReceived == KeyboardEvent.DOM_VK_ESC) {
+ escKeyReceived = true;
+ }
+}
+
+window.addEventListener("keydown", keyHandler, true);
+window.addEventListener("keyup", keyHandler, true);
+window.addEventListener("keypress", keyHandler, true);
+
+function startTest() {
+ ok(!document.fullscreenElement, "Subdoc should not be in full-screen mode");
+ ok(parent.document.fullscreenElement, "Parent should be in full-screen mode");
+ escKeySent = true;
+ window.focus();
+ synthesizeKey("KEY_Escape");
+}
+
+</script>
+</pre>
+<p>Inner frame</p>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html
new file mode 100644
index 0000000000..f65f930b3f
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body:fullscreen, div:fullscreen {
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[esc-exit] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[esc-exit] " + msg);
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+function fullscreenchange1(event) {
+ is(document.fullscreenElement, document.body, "FSE should be doc");
+ addFullscreenChangeContinuation("exit", fullscreenchange2);
+ ok(!document.getElementById("subdoc").contentWindow.escKeySent, "Should not yet have sent ESC key press.");
+ document.getElementById("subdoc").contentWindow.startTest();
+}
+
+function fullscreenchange2(event) {
+ ok(document.getElementById("subdoc").contentWindow.escKeySent, "Should have sent ESC key press.");
+ ok(!document.getElementById("subdoc").contentWindow.escKeyReceived, "ESC key press to exit should not be delivered.");
+ ok(!document.fullscreenElement, "Should have left full-screen mode on ESC key press");
+ finish();
+}
+
+function begin() {
+ addFullscreenChangeContinuation("enter", fullscreenchange1);
+ document.body.requestFullscreen();
+}
+
+</script>
+
+<!-- This subframe conducts the test. -->
+<iframe id="subdoc" src="file_fullscreen-esc-exit-inner.html"></iframe>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-event-order.html b/dom/base/test/fullscreen/file_fullscreen-event-order.html
new file mode 100644
index 0000000000..72fb2c9b47
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-event-order.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<iframe src="empty.html" allowfullscreen></iframe>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[event-order] " + msg);
+}
+function is(a, b, msg) {
+ opener.is(a, b, "[event-order] " + msg);
+}
+
+let fullscreenEvents = [];
+let iframe, iframeDoc;
+
+function begin() {
+ iframe = document.querySelector("iframe");
+ iframeDoc = iframe.contentDocument;
+ document.addEventListener("fullscreenchange", evt => {
+ fullscreenEvents.push(evt);
+ });
+ iframeDoc.addEventListener("fullscreenchange", evt => {
+ fullscreenEvents.push(evt);
+ });
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ iframeDoc.body.requestFullscreen();
+}
+
+function assertFullscreenEvents(action) {
+ is(fullscreenEvents.length, 2,
+ "Two documents should have event dispatched for " + action);
+ is(fullscreenEvents[0].target, iframe,
+ "Root document should have the event dispatched first after " + action);
+ is(fullscreenEvents[1].target, iframeDoc.body,
+ "Inner document should have the event dispatched second after " + action);
+}
+
+function enterFullscreen() {
+ assertFullscreenEvents("requestFullscreen");
+ fullscreenEvents = [];
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ assertFullscreenEvents("exitFullscreen");
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html b/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html
new file mode 100644
index 0000000000..844684b054
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+ let isChrome = location.search.includes("chrome");
+
+ function handler(evt) {
+ document.removeEventListener("fullscreenchange", handler);
+ document.removeEventListener("fullscreenerror", handler);
+ const enabled = isChrome ? SpecialPowers.wrap(document).fullscreenEnabled
+ : document.fullscreenEnabled;
+ if (evt.type == "fullscreenchange") {
+ document.addEventListener("fullscreenchange", () => parent.continueTest(evt.type, enabled), {once: true});
+ document.exitFullscreen();
+ } else {
+ parent.continueTest(evt.type, enabled);
+ }
+ }
+ document.addEventListener("fullscreenchange", handler);
+ document.addEventListener("fullscreenerror", handler);
+ parent.opener.info("Requesting fullscreen");
+ if (isChrome) {
+ SpecialPowers.wrap(document.documentElement).requestFullscreen();
+ } else {
+ document.documentElement.requestFullscreen();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html b/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html
new file mode 100644
index 0000000000..c8b943c612
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FeaturePolicy + fullscreen</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+function ok(condition, msg) {
+ opener.ok(condition, "[featurePolicy] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[featurePolicy] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-featurePolicy-inner.html";
+
+function begin() {
+ nextTest();
+}
+
+var tests = [
+ ["fullscreen 'none'", "fullscreenerror"],
+ ["fullscreen", "fullscreenchange"],
+ ["fullscreen 'src'", "fullscreenchange"],
+ ["fullscreen 'self'", "fullscreenchange"],
+ ["fullscreen *", "fullscreenchange"],
+ ["fullscreen http://random.net", "fullscreenerror"],
+ [null, "fullscreenchange"],
+];
+
+async function nextTest() {
+ if (!tests.length) {
+ opener.nextTest();
+ return;
+ }
+
+ let [value, expectedEvent] = tests.shift();
+
+ for (const isChrome of [false, true]) {
+ opener.info(`Running ${value} ${isChrome ? "w/" : "wo/"} chrome privileges`);
+
+ // Create an iframe with an allowfullscreen and with an allow attribute.
+ // The request should be denied or allowed, based on the current test.
+ const iframe = document.createElement("iframe");
+ iframe.setAttribute("allowfullscreen", "true");
+ if (value) {
+ iframe.setAttribute("allow", value);
+ }
+ iframe.src = INNER_FILE + (isChrome ? "?chrome" : "");
+
+ const setupForInnerTest = targetName => {
+ window.testTargetName = targetName;
+ return new Promise(resolve => {
+ window.continueTest = (event, enabled) => {
+ delete window.testTargetName;
+ delete window.continueTest;
+ resolve({ event, enabled });
+ };
+ document.body.appendChild(iframe);
+ });
+ };
+
+ const { event, enabled } = await setupForInnerTest(
+ `an iframe+allowfullscreen+allow:${value}+isChrome:${isChrome}`
+ );
+
+ if (isChrome) {
+ is(event, "fullscreenchange", "Expected a fullscreenchange event");
+ ok(enabled, "Should be enabled in chrome");
+ } else {
+ is(event, expectedEvent, "Expected a " + expectedEvent + " event");
+ is(enabled, expectedEvent == "fullscreenchange", "Should be appropriately enabled");
+ }
+ iframe.remove();
+ }
+ SimpleTest.executeSoon(nextTest);
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-focus-inner.html b/dom/base/test/fullscreen/file_fullscreen-focus-inner.html
new file mode 100644
index 0000000000..73d39a9d83
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-focus-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Focus test - child window</title>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function enterFullscreen() {
+ addFullscreenErrorContinuation(() => { opener.enteredFullscreen(false); });
+
+ addFullscreenChangeContinuation("enter", () => {
+ opener.enteredFullscreen(true);
+ });
+
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-focus.html b/dom/base/test/fullscreen/file_fullscreen-focus.html
new file mode 100644
index 0000000000..be91025f45
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-focus.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+Test that a fullscreen request fails if the window is not focused.
+
+Open window1, open window2, focus window2, and then attempt to fullscreen
+window1 while it is not focused. The fullscreen attempt should be rejected
+because the window is not focused.
+
+-->
+<head>
+ <title>Test fullscreen request is blocked when window is not focused</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[focus] " + msg);
+}
+
+var window1, window2;
+
+function openWindow() {
+ var w = window.open("file_fullscreen-focus-inner.html", "",
+ "width=500,height=500");
+ return w;
+}
+
+function begin() {
+ window1 = openWindow();
+ window1.focus();
+
+ SimpleTest.waitForFocus(function(){
+ window2 = openWindow();
+ window2.focus();
+
+ SimpleTest.waitForFocus(function(){
+ // Now that window2 is focused, attempt to fullscreen window1.
+ // This should fail.
+ window1.enterFullscreen("one");
+ }, window2);
+
+ }, window1);
+}
+
+async function enteredFullscreen(enteredSuccessfully) {
+ ok(!enteredSuccessfully, "window1 did not enter fullscreen");
+
+ if (enteredSuccessfully) {
+ await window1.document.exitFullscreen()
+ }
+
+ window1.close();
+ window2.close();
+ opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-hidden.html b/dom/base/test/fullscreen/file_fullscreen-hidden.html
new file mode 100644
index 0000000000..bd8c8189c9
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-hidden.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=697636
+-->
+<head>
+ <title>Test for Bug 697636</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<iframe id="f" srcdoc="<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=697636">Mozilla Bug 697636</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 697636 **/
+
+var frameWin;
+var e1;
+
+function begin()
+{
+ var f = document.getElementById("f");
+ frameWin = f.contentWindow;
+ e1 = frameWin.document.documentElement;
+ f.srcdoc = "<body text=blue onload='parent.b2()'>2";
+}
+
+function b2()
+{
+ try {
+ e1.requestFullscreen();
+ } catch(e) {
+ opener.ok(false, "[hidden] Should not enter full-screen");
+ }
+ setTimeout(done, 0);
+}
+
+function done() {
+ opener.ok(!document.fullscreenElement, "[hidden] Should not have entered full-screen mode in hidden document.");
+ opener.ok(!e1.ownerDocument.fullscreenElement, "[hidden] Requesting owner should not have entered full-screen mode.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html b/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html
new file mode 100644
index 0000000000..4a614fdecf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html
@@ -0,0 +1,5 @@
+<html onclick="div.requestFullscreen()">
+<body>
+<div name="div" id="div" style="width: 100px; height: 100px; background: green;"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html b/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html
new file mode 100644
index 0000000000..b60dea43bf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html
@@ -0,0 +1,5 @@
+<div name="div" id="div" style="width: 100px; height: 100px; background: blue;">
+<iframe id="iframe" allowfullscreen="yes"
+ src="http://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html">
+</iframe>
+</div><br>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-top.html b/dom/base/test/fullscreen/file_fullscreen-iframe-top.html
new file mode 100644
index 0000000000..dddf4930c2
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-top.html
@@ -0,0 +1,5 @@
+<div name="div" id="div" style="width: 100px; height: 100px; background: red;">
+<iframe id="iframe" allowfullscreen="yes"
+ src="http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html">
+</iframe>
+</div><br>
diff --git a/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html b/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html
new file mode 100644
index 0000000000..02491c177e
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1268798</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+ opener.ok(condition, "[lenient-setters] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[lenient-setters] " + msg);
+}
+
+function info(msg) {
+ opener.info("[lenient-setters] " + msg);
+}
+
+let unattachedDiv = document.createElement("div");
+
+function begin() {
+ var originalValue = document.fullscreen;
+ try {
+ document.fullscreen = !document.fullscreen;
+ is(document.fullscreen, originalValue,
+ "fullscreen should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreen should not throw");
+ }
+
+ var originalElem = document.fullscreenElement;
+ try {
+ document.fullscreenElement = unattachedDiv;
+ document.fullscreenElement = [];
+ is(document.fullscreenElement, originalElem,
+ "fullscreenElement should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenElement should not throw");
+ }
+
+ var originalEnabled = document.fullscreenEnabled;
+ try {
+ document.fullscreenEnabled = !originalEnabled;
+ is(document.fullscreenEnabled, originalEnabled,
+ "fullscreenEnabled should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenEnabled should not throw");
+ }
+
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html b/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html
new file mode 100644
index 0000000000..cb5ca9b28e
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 724554</title>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+function begin(id) {
+ opener.addFullscreenErrorContinuation(function() {
+ opener.ok(false, "Fullscreen denied " + id);
+ }, document);
+ opener.addFullscreenChangeContinuation("enter",
+ function() {
+ opener.enteredFullscreen(id);
+ }, document);
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-multiple.html b/dom/base/test/fullscreen/file_fullscreen-multiple.html
new file mode 100644
index 0000000000..f9e35b5e78
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-multiple.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=724554
+
+Test that multiple windows can be fullscreen at the same time.
+
+Open one window, focus it and enter fullscreen, then open another, focus
+it and enter fullscreen, and check that both are still fullscreen.
+
+-->
+<head>
+ <title>Test for Bug 724554</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[multiple] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[multiple] " + msg);
+}
+
+var window1, window2;
+
+function openWindow(id) {
+ var w = window.open("file_fullscreen-multiple-inner.html", "", "width=500,height=500");
+ waitForLoadAndPaint(w, function() {
+ SimpleTest.waitForFocus(function() {
+ info(`Window ${id} is focused, starting test...`);
+ w.begin(id);
+ }, w);
+ w.focus();
+ });
+ return w;
+}
+
+function begin() {
+ window1 = openWindow("one");
+}
+
+function enteredFullscreen(id) {
+ if (id == "one") {
+ window2 = openWindow("two");
+ } else if (id == "two") {
+ ok(window1.document.fullscreenElement &&
+ window2.document.fullscreenElement,
+ "Both windows should be fullscreen concurrently");
+ window1.close();
+ window2.close();
+ opener.nextTest();
+ }
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-navigation.html b/dom/base/test/fullscreen/file_fullscreen-navigation.html
new file mode 100644
index 0000000000..9b68fedf9a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-navigation.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=685402
+-->
+<head>
+ <title>Test for Bug 685402</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="background-color: gray;">
+
+<iframe id="f" srcdoc="<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685402">Mozilla Bug 685402</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 685402 **/
+
+var frameWin;
+var e1;
+var prevEnabled;
+var prevTrusted;
+
+function begin()
+{
+ var f = document.getElementById("f");
+ frameWin = f.contentWindow;
+ e1 = frameWin.document.body;
+ document.addEventListener("fullscreenchange", function() {
+ opener.ok(document.fullscreenElement, "[navigation] Request should be granted");
+ f.srcdoc = "<body text=blue onload='parent.b2()'>2";
+ }, {once: true});
+
+ e1.requestFullscreen();
+}
+
+function b2()
+{
+ opener.ok(!document.fullscreenElement, "[navigation] Should have left full-screen due to navigation.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-nested.html b/dom/base/test/fullscreen/file_fullscreen-nested.html
new file mode 100644
index 0000000000..1629d8386c
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-nested.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1187801</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<iframe src="empty.html" allowfullscreen></iframe>
+<script type="text/javascript">
+
+/** Test for Bug 1187801 **/
+
+function info(msg) {
+ opener.info("[nested] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[nested] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[nested] " + msg);
+}
+
+var gInnerDoc;
+var gTestSteps;
+var gTestIndex = 0;
+
+function begin() {
+ var root = document.documentElement;
+ var iframe = document.querySelector("iframe");
+ var innerDoc = gInnerDoc = iframe.contentDocument;
+ var innerRoot = innerDoc.documentElement;
+
+ // The format of each test step is:
+ // [[action, target], [fsOuter, fsInner]] where:
+ // * "action" is "enter" or "exit" means whether we want to enter or
+ // fullscreen in this step. An action of "reset" means to force
+ // our count of enters to a certain value, to match how many exits
+ // we need to do to leave fullscreen. This is used when one exit
+ // can unwind more than one enter.
+ // * "target" is where we apply this action. For "enter" action, it
+ // is the element we want to call requestFullscreen() on, and for
+ // "exit", it is the document we want to call exitFullscreen() on.
+ // * "fsOuter" and "fsInner" are the expected fullscreen elements of
+ // the outer and inner document respectively after executing the
+ // action in this step. These are only checked after "enter" or
+ // "exit" actions.
+ gTestSteps = [
+ // innerRoot
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ null, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ null, null]],
+ // root, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ // iframe, innerRoot
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [["reset", 1], [ null, null]],
+ [[ "exit", document], [ null, null]],
+ // root, iframe, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [["reset", 1], [ null, null]],
+ [[ "exit", document], [ null, null]],
+ ];
+
+ nextStep();
+}
+
+function nextStep() {
+ if (gTestIndex == gTestSteps.length) {
+ opener.nextTest();
+ return;
+ }
+
+ var index = gTestIndex;
+ var [[action, target], [fsOuter, fsInner]] = gTestSteps[gTestIndex++];
+
+ function checkAndNext() {
+ is(document.fullscreenElement, fsOuter,
+ `Fullscreen element of outer doc should match after step ${index}`);
+ is(gInnerDoc.fullscreenElement, fsInner,
+ `Fullscreen element of inner doc should match after step ${index}`);
+ nextStep();
+ }
+
+ info(`Executing step ${index}: ${action} on ${target}...`);
+ if (action == "enter") {
+ // For "enter" action, the target is the element
+ var doc = target.ownerDocument;
+ addFullscreenChangeContinuation("enter", checkAndNext, doc);
+ target.requestFullscreen();
+ } else if (action == "exit") {
+ // For "exit" action, the target is the document
+ addFullscreenChangeContinuation("exit", checkAndNext, target);
+ target.exitFullscreen();
+ } else if (action == "reset") {
+ // For "reset" action, the target is the number to setFullscreenChangeEnters.
+ setFullscreenChangeEnters(target);
+ nextStep();
+ } else {
+ ok(false, `Unknown action ${action}`);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-newtab.html b/dom/base/test/fullscreen/file_fullscreen-newtab.html
new file mode 100644
index 0000000000..0eaf5dd546
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-newtab.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<a id="link" href="about:blank" target="_blank" rel="opener"
+ onclick="document.body.requestFullscreen()">Click here</a>
diff --git a/dom/base/test/fullscreen/file_fullscreen-prefixed.html b/dom/base/test/fullscreen/file_fullscreen-prefixed.html
new file mode 100644
index 0000000000..dfe1965365
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-prefixed.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 743198</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <div id="fullscreen"></div>
+<script>
+
+function ok(condition, msg) {
+ opener.ok(condition, "[prefixed] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[prefixed] " + msg);
+}
+
+function info(msg) {
+ opener.info("[prefixed] " + msg);
+}
+
+SimpleTest.requestFlakyTimeout(
+ "need to wait for a while to confirm no unexpected event is dispatched");
+
+let div = document.getElementById("fullscreen");
+let unattachedDiv = document.createElement('div');
+
+const NO_EVENT_HANDLER = 0;
+const PREFIXED_EVENT_ONLY = 1;
+const PREFIXED_AND_UNPREFIXED_EVENT = 2;
+
+class TestCase {
+ constructor(num, handlersOnWindow, handlersOnDocument) {
+ this.number = num;
+ this.handlersType = new Map([[window, handlersOnWindow],
+ [document, handlersOnDocument]]);
+ }
+
+ static checkState(inFullscreen, msg) {
+ var emptyOrNot = inFullscreen ? "" : "not ";
+ info(`Check fullscreen state ${msg}`);
+ is(document.mozFullScreen, inFullscreen,
+ `Should ${emptyOrNot}be in fullscreen`);
+ is(document.fullscreenElement, inFullscreen ? div : null,
+ `Fullscreen element should be ${inFullscreen ? "div" : "null"}`);
+ is(document.mozFullScreenElement, document.fullscreenElement,
+ "document.mozFullScreenElement should be identical to fullscreenElement");
+ is(div.matches(":fullscreen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :fullscreen pseudo class`);
+ is(div.matches(":-moz-full-screen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :-moz-full-screen pseudo class`);
+ }
+
+ changeListeners(action, eventType, handler) {
+ let method = `${action}EventListener`;
+ for (let [target, type] of this.handlersType.entries()) {
+ if (type == PREFIXED_EVENT_ONLY) {
+ target[method](`moz${eventType}`, handler);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ target[method](eventType, handler);
+ target[method](`moz${eventType}`, handler);
+ } else if (type != NO_EVENT_HANDLER) {
+ ok(false, `Unknown handlers type ${type}`);
+ }
+ }
+ }
+
+ doTest(actionCallback, eventType, inFullscreen, msg) {
+ return new Promise(resolve => {
+ let timeout = 0;
+ let expectEvent = new Map();
+ for (let [target] of this.handlersType) {
+ expectEvent.set(target, this.handlersType != NO_EVENT_HANDLER);
+ }
+ let handleEvent = evt => {
+ let target = evt.currentTarget;
+ let type = this.handlersType.get(target);
+ if (type == PREFIXED_EVENT_ONLY) {
+ is(evt.type, `moz${eventType}`,
+ `Should get prefixed event on ${target}`);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ is(evt.type, eventType,
+ `Should only get unprefixed event on ${target}`);
+ } else {
+ ok(false, `No event should be triggered on ${target}`);
+ }
+ // Ensure we receive each event exactly once.
+ if (expectEvent.get(target)) {
+ expectEvent.set(target, false);
+ } else {
+ ok(false, `Got an unexpected ${evt.type} event on ${target}`);
+ }
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ this.changeListeners("remove", eventType, handleEvent);
+ TestCase.checkState(inFullscreen,
+ `${msg} in test case ${this.number}`);
+ resolve();
+ });
+ }
+ };
+ this.changeListeners("add", eventType, handleEvent);
+ SimpleTest.waitForFocus(() => actionCallback());
+ });
+ }
+
+ test() {
+ return new Promise(resolve => {
+ Promise.resolve().then(() => {
+ return this.doTest(() => div.mozRequestFullScreen(),
+ "fullscreenchange", true, "after request");
+ }).then(() => {
+ return this.doTest(() => document.mozCancelFullScreen(),
+ "fullscreenchange", false, "after exit");
+ }).then(() => {
+ return this.doTest(() => unattachedDiv.mozRequestFullScreen(),
+ "fullscreenerror", false, "after failed request");
+ }).then(resolve);
+ });
+ }
+}
+
+let gTestcases = [
+ new TestCase(1, PREFIXED_EVENT_ONLY, NO_EVENT_HANDLER),
+ new TestCase(2, PREFIXED_AND_UNPREFIXED_EVENT, NO_EVENT_HANDLER),
+ new TestCase(3, NO_EVENT_HANDLER, PREFIXED_EVENT_ONLY),
+ new TestCase(4, PREFIXED_EVENT_ONLY, PREFIXED_EVENT_ONLY),
+ new TestCase(5, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_EVENT_ONLY),
+ new TestCase(6, NO_EVENT_HANDLER, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(7, PREFIXED_EVENT_ONLY, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(8, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_AND_UNPREFIXED_EVENT),
+ ];
+
+function begin() {
+ TestCase.checkState(false, "at the beginning");
+ runNextTestCase();
+}
+
+function runNextTestCase() {
+ let testcase = gTestcases.shift();
+ if (!testcase) {
+ opener.nextTest();
+ return;
+ }
+ testcase.test().then(runNextTestCase);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-resize.html b/dom/base/test/fullscreen/file_fullscreen-resize.html
new file mode 100644
index 0000000000..3050ba0d5d
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-resize.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1742421
+-->
+<head>
+ <title>Test for Bug 1742421</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_fullscreen-utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="background-color: gray;">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1742421">Mozilla Bug 1742421</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1742421 **/
+
+function begin()
+{
+ addFullscreenChangeContinuation("enter", () => {
+ opener.info("[resize] Entered fullscreen");
+ // Do not use addFullscreenChangeContinuation for fullscreen exit given that
+ // it expects the window will be restored to the original size.
+ document.addEventListener("fullscreenchange", () => {
+ opener.ok(!document.fullscreenElement, "[resize] Should have left full-screen due to resize");
+ opener.nextTest();
+ }, { once: true });
+ window.resizeBy(100,100);
+ });
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-rollback.html b/dom/base/test/fullscreen/file_fullscreen-rollback.html
new file mode 100644
index 0000000000..b1578b39cd
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-rollback.html
@@ -0,0 +1,140 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verifies that cancelFullScreen() rolls back to have the previous full-screen
+element full-screen.
+
+Tests:
+* Request full-screen in doc.
+* Request full-screen in doc on element not descended from full-screen element.
+* Cancel full-screen, FSE should rollback to previous FSE.
+* Request full-screen in subdoc.
+* Cancel full-screen in subdoc, doc should be full-screen.
+* Request full-screen in subdoc.
+* Removing FSE should fully-exit full-screen.
+
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<div id="fse">
+ <div id="fse-inner">
+ <iframe id="subdoc" allowfullscreen srcdoc="<html><body bgcolor='black'></body></html>"></iframe>
+ </div>
+</div>
+
+<div id="non-fse"></div>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[rollback] " + msg);
+ if (!condition) {
+ opener.finish();
+ }
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[rollback] " + msg);
+ if (a != b) {
+ opener.finish();
+ }
+}
+
+function enterFullscreen(element, callback) {
+ addFullscreenChangeContinuation("enter", callback);
+ element.focus();
+ element.requestFullscreen();
+}
+
+function revertFullscreen(doc, callback) {
+ ok(doc.fullscreenElement != null, "Should only exit fullscreen on a fullscreen doc");
+ addFullscreenChangeContinuation("exit", callback, doc);
+ doc.exitFullscreen();
+}
+
+function e(id) {
+ return document.getElementById(id);
+}
+
+function requestFullscreen(element) {
+ element.focus();
+ element.requestFullscreen();
+}
+
+function begin() {
+ enterFullscreen(e("fse"), change1);
+}
+
+function change1() {
+ is(document.fullscreenElement, e("fse"), "Body should be FSE");
+ // Request full-screen from element not descendent from current FSE.
+ enterFullscreen(e("non-fse"), change2);
+}
+
+function change2() {
+ is(document.fullscreenElement, e("non-fse"), "FSE should be e('non-fse')");
+ revertFullscreen(document, change3);
+}
+
+function change3() {
+ is(document.fullscreenElement, e("fse"), "FSE should rollback to FSE.");
+ var iframe = e("subdoc");
+ enterFullscreen(iframe.contentDocument.body, change4);
+}
+
+function change4() {
+ var iframe = e("subdoc");
+ is(document.fullscreenElement, iframe, "Subdoc container should be FSE.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body, "Subdoc body should be FSE in subdoc");
+ revertFullscreen(document, change5);
+}
+
+function change5() {
+ is(document.fullscreenElement, e("fse"), "FSE should rollback to FSE.");
+ revertFullscreen(document, change6);
+}
+
+function change6() {
+ is(document.fullscreenElement, null, "Should have left full-screen entirely");
+ enterFullscreen(e("fse"), change7);
+}
+
+function change7() {
+ is(document.fullscreenElement, e("fse"), "FSE should be e('fse')");
+ enterFullscreen(e("fse-inner"), change8);
+}
+
+function change8() {
+ var element = e('fse-inner');
+ is(document.fullscreenElement, element, "FSE should be e('fse-inner')");
+
+ // We're breaking out of two levels of fullscreen by removing the
+ // fullscreenElement. To make our helper functions work correctly,
+ // we set the fullscreenChangeEnters value to 1. This is a hack, but
+ // it is a hack that supports the expected behavior.
+ setFullscreenChangeEnters(1);
+ addFullscreenChangeContinuation("exit", change9);
+ info(`Removing FSE should exit fullscreen.`);
+ element.remove();
+}
+
+function change9() {
+ is(document.fullscreenElement, null, "Should have fully exited full-screen mode when removed FSE from doc");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-scrollbar.html b/dom/base/test/fullscreen/file_fullscreen-scrollbar.html
new file mode 100644
index 0000000000..05ab51431a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-scrollbar.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1201798</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html, body, #measure {
+ width: 100%; height: 100%;
+ margin: 0px; border: 0px;
+ }
+ div {
+ margin: 0px; border: 0px;
+ }
+ #ref-outer { width: 100px; height: 100px; overflow: scroll; }
+ #ref-inner { width: 100%; height: 100%; }
+ </style>
+</head>
+<body>
+<div id="measure"></div>
+<div style="height: 1000vh; width: 1000vw;"></div>
+<div id="ref-outer">
+ <div id="ref-inner"></div>
+</div>
+<div id="fullscreen"></div>
+<script type="text/javascript">
+
+/** Test for Bug 1201798 */
+
+var info = msg => opener.info("[scrollbar] " + msg);
+var ok = (cond, msg) => opener.ok(cond, "[scrollbar] " + msg);
+var is = (a, b, msg) => opener.is(a, b, "[scrollbar] " + msg);
+
+var gVerticalScrollbarWidth, gHorizontalScrollbarWidth;
+var gMeasureDiv = document.getElementById("measure");
+var gFullscreenDiv = document.getElementById("fullscreen");
+
+function getMeasureRect() {
+ return gMeasureDiv.getBoundingClientRect();
+}
+
+function triggerFrameReconstruction() {
+ info("Triggering a force frame reconstruction");
+ var docElem = document.documentElement;
+ var wm = window.getComputedStyle(docElem).writingMode;
+ if (wm == "horizontal-tb") {
+ docElem.style.writingMode = "vertical-rl";
+ } else {
+ docElem.style.writingMode = "horizontal-tb";
+ }
+ docElem.getBoundingClientRect();
+}
+
+function assertHasScrollbars(elem) {
+ var rect = getMeasureRect();
+ info(`screen.width: ${screen.width}, screen.height: ${screen.height}`);
+ info(`rect.width: ${rect.width}, rect.height: ${rect.height}`);
+ ok(rect.width <= screen.width - gVerticalScrollbarWidth,
+ `Should have width less than or equal to ${screen.width - gVerticalScrollbarWidth} indicating vertical scrollbar when ${elem} is in fullscreen`);
+ ok(rect.height <= screen.height - gHorizontalScrollbarWidth,
+ `Should have height less than or equal to ${screen.height - gHorizontalScrollbarWidth} indicating horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function assertHasNoScrollbars(elem) {
+ var rect = getMeasureRect();
+ info(`screen.width: ${screen.width}, screen.height: ${screen.height}`);
+ info(`rect.width: ${rect.width}, rect.height: ${rect.height}`);
+ is(rect.width, screen.width,
+ `Should not have vertical scrollbar when ${elem} is in fullscreen`);
+ is(rect.height, screen.height,
+ `Should not have horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function checkScrollbars(elem, shouldHaveScrollbars) {
+ is(document.fullscreenElement, elem,
+ "Should only check the current fullscreen element");
+ var assertFunc = shouldHaveScrollbars ?
+ assertHasScrollbars : assertHasNoScrollbars;
+ assertFunc(elem);
+ triggerFrameReconstruction();
+ assertFunc(elem);
+}
+
+function begin() {
+ // Check for the use of overlay scrollbars. We can only get an accurate
+ // answer to our media query if we are Chrome-privileged. Otherwise, the
+ // media query will never match.
+ let wrappedWindow = SpecialPowers.wrap(window);
+ if (wrappedWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
+ // If overlay scrollbar is enabled, the scrollbar is not measurable,
+ // so we skip this test in that case.
+ info("Skip this test because of overlay scrollbar");
+ opener.nextTest();
+ return;
+ }
+
+ const outerElement = document.getElementById("ref-outer");
+ var rectOuter = outerElement.getBoundingClientRect();
+ var rectInner = document.getElementById("ref-inner").getBoundingClientRect();
+ info(`rectOuter: ${rectOuter.width} x ${rectOuter.height}`);
+ info(`rectInner: ${rectInner.width} x ${rectInner.height}`);
+ gVerticalScrollbarWidth = rectOuter.width - rectInner.width;
+ gHorizontalScrollbarWidth = rectOuter.height - rectInner.height;
+ ok(gVerticalScrollbarWidth != 0, "Should have vertical scrollbar");
+ ok(gHorizontalScrollbarWidth != 0, "Should have horizontal scrollbar");
+ info(`gVerticalScrollbarWidth: ${gVerticalScrollbarWidth}`);
+ info(`gHorizontalScrollbarWidth: ${gHorizontalScrollbarWidth}`);
+
+ // Remove the display of outerElement to simplify layout when window goes
+ // to fullscreen.
+ outerElement.style.display = "none";
+
+ info("Entering fullscreen on root");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnRoot);
+ document.documentElement.requestFullscreen();
+}
+
+function enteredFullscreenOnRoot() {
+ checkScrollbars(document.documentElement, true);
+ info("Entering fullscreen on div");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnDiv);
+ gFullscreenDiv.requestFullscreen();
+}
+
+function enteredFullscreenOnDiv() {
+ checkScrollbars(gFullscreenDiv, false);
+ info("Exiting fullscreen on div");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnDiv);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnDiv() {
+ checkScrollbars(document.documentElement, true);
+ info("Exiting fullscreen on root");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnRoot);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnRoot() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-selector.html b/dom/base/test/fullscreen/file_fullscreen-selector.html
new file mode 100644
index 0000000000..64947b6cfd
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-selector.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1199522</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ div {
+ position: fixed;
+ top: 20px; height: 50px;
+ opacity: 0.3;
+ border: 5px solid black;
+ box-sizing: border-box;
+ }
+ #fullscreen0 {
+ left: 50px; width: 50px;
+ background: #ff0000;
+ border-color: #800000;
+ }
+ #fullscreen1 {
+ left: 100px; width: 50px;
+ background: #00ff00;
+ border-color: #008000;
+ }
+ #fullscreen2 {
+ left: 150px; width: 50px;
+ background: #0000ff;
+ border-color: #000080;
+ }
+ </style>
+</head>
+<body>
+<script type="application/javascript">
+
+/** Test for Bug 1199522 **/
+
+function info(msg) {
+ opener.info("[selector] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[selector] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[selector] " + msg);
+}
+
+function rectEquals(rect1, rect2) {
+ return rect1.x == rect2.x && rect1.y == rect2.y &&
+ rect1.width == rect2.width && rect1.height == rect2.height;
+}
+
+function getViewportRect() {
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
+}
+
+var fullscreenElems = [];
+
+function checkFullscreenState(elem, hasState, viewportRect) {
+ var id = elem.id;
+ var rect = elem.getBoundingClientRect();
+ if (hasState) {
+ ok(elem.matches(":fullscreen"),
+ `${id} should match selector ":fullscreen"`);
+ ok(rectEquals(rect, viewportRect),
+ `The bounding rect of ${id} should match the viewport`);
+ } else {
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ ok(rectEquals(rect, elem.initialRect),
+ `The bounding rect of ${id} should match its initial state`);
+ }
+}
+
+function checkFullscreenStates(states) {
+ var viewportRect = getViewportRect();
+ fullscreenElems.forEach((elem, index) => {
+ checkFullscreenState(elem, states[index], viewportRect);
+ });
+}
+
+function begin() {
+ fullscreenElems.push(document.getElementById('fullscreen0'));
+ fullscreenElems.push(document.getElementById('fullscreen1'));
+ fullscreenElems.push(document.getElementById('fullscreen2'));
+
+ var viewportRect = getViewportRect();
+ for (var elem of fullscreenElems) {
+ var rect = elem.getBoundingClientRect();
+ var id = elem.id;
+ elem.initialRect = rect;
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ ok(!rectEquals(elem.initialRect, viewportRect),
+ `The initial bounding rect of ${id} should not match the viewport`);
+ }
+
+ info("Entering fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("enter", enter0);
+ fullscreenElems[0].requestFullscreen();
+}
+
+function enter0() {
+ checkFullscreenStates([true, false, false]);
+ info("Entering fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("enter", enter1);
+ fullscreenElems[1].requestFullscreen();
+}
+
+function enter1() {
+ checkFullscreenStates([true, true, false]);
+ info("Entering fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("enter", enter2);
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enter2() {
+ checkFullscreenStates([true, true, true]);
+ info("Leaving fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("exit", exit2);
+ document.exitFullscreen();
+}
+
+function exit2() {
+ checkFullscreenStates([true, true, false]);
+ info("Leaving fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("exit", exit1);
+ document.exitFullscreen();
+}
+
+function exit1() {
+ checkFullscreenStates([true, false, false]);
+ info("Leaving fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("exit", exit0);
+ document.exitFullscreen();
+}
+
+function exit0() {
+ checkFullscreenStates([false, false, false]);
+
+ info("Entering fullscreen on all elements");
+ var count = 0;
+ function listener() {
+ if (++count == 3) {
+ document.removeEventListener("fullscreenchange", listener);
+ // We bypassed our fullscreenchangeenters count since we didn't
+ // do our requests with a addFullscreenChangeContinuation, so we
+ // fix up the expected value now that we're done with this part
+ // of the test.
+ setFullscreenChangeEnters(1);
+ enterAll();
+ }
+ }
+ document.addEventListener("fullscreenchange", listener);
+ fullscreenElems[0].requestFullscreen();
+ fullscreenElems[1].requestFullscreen();
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enterAll() {
+ checkFullscreenStates([true, true, true]);
+ info("Fully-exiting fullscreen");
+ addFullscreenChangeContinuation("exit", exitAll);
+ synthesizeKey("KEY_Escape");
+}
+
+function exitAll() {
+ checkFullscreenStates([false, false, false]);
+ opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="fullscreen0">
+ <div id="fullscreen1">
+ <div id="fullscreen2">
+ </div>
+ </div>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-shadowdom.html b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html
new file mode 100644
index 0000000000..348e08ae87
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1430305
+ Bug 1430305 - Implement ShadowRoot.fullscreenElement
+ -->
+ <head>
+ <title>Bug 1430305</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="/tests/SimpleTest/EventUtils.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305">
+ Mozilla Bug 1430305</a>
+
+ <div id="host"></div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ function begin() {
+ var host = document.getElementById("host");
+ var shadowRoot = host.attachShadow({mode: "open"});
+ shadowRoot.innerHTML = "<div>test</div>";
+ var elem = shadowRoot.firstChild;
+ var gotFullscreenEvent = false;
+
+ document.addEventListener("fullscreenchange", function (e) {
+ if (document.fullscreenElement === host) {
+ is(shadowRoot.fullscreenElement, elem,
+ "Expected element entered fullsceen");
+ gotFullscreenEvent = true;
+ document.exitFullscreen();
+ } else {
+ opener.ok(gotFullscreenEvent, "Entered fullscreen as expected");
+ is(shadowRoot.fullscreenElement, null,
+ "Shouldn't have fullscreenElement anymore.");
+ is(document.fullscreenElement, null,
+ "Shouldn't have fullscreenElement anymore.");
+ opener.nextTest();
+ }
+ });
+ elem.requestFullscreen();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-single.html b/dom/base/test/fullscreen/file_fullscreen-single.html
new file mode 100644
index 0000000000..2ebc58bdae
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-single.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+Open one window, focus it and enter fullscreen, then exit fullscreen.
+
+-->
+<head>
+ <title>Simple Fullscreen Enter and Exit Test</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<div id="fullscreen-div"><p>Fullscreen div</p></div>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[single] " + msg);
+}
+
+function is(value, expected, msg) {
+ opener.is(value, expected, "[single] " + msg);
+}
+
+function isnot(value, unexpected, msg) {
+ opener.isnot(value, unexpected, "[single] " + msg);
+}
+
+function info(msg) {
+ opener.info("[single] " + msg);
+}
+
+function windowResized() {
+ info(`Window resized to width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+}
+
+async function begin() {
+ window.addEventListener('resize', windowResized);
+
+ info(`Starting window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ let windowedWidth = window.innerWidth;
+ let windowedHeight = window.innerHeight;
+
+ info("Requesting fullscreen.");
+ let entryPromise = document.getElementById('fullscreen-div').requestFullscreen()
+ info("Fullscreen requested, waiting for promise to resolve.");
+
+ await entryPromise;
+
+ info("element.requestFullscreen() promise resolved.");
+ info(`Fullscreen window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ isnot(document.fullscreenElement, null, "document.fullscreenElement should exist.");
+ ok(window.fullScreen, "window.fullScreen");
+ isnot(windowedWidth, window.innerWidth, "window width should be changed.");
+ isnot(windowedHeight, window.innerHeight, "window height should be changed.");
+
+ info("Requesting fullscreen exit.");
+ let exitPromise = document.exitFullscreen()
+ info("Fullscreen exit requested, waiting for promise to resolve.");
+
+ await exitPromise;
+
+ info("document.exitFullscreen() promise resolved.");
+ info(`Restored window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ is(document.fullscreenElement, null, "document.fullscreenElement should be null.");
+ ok(!window.fullScreen, "window.fullScreen should be false.");
+ is(window.innerWidth, windowedWidth, "window width should be restored.");
+ is(window.innerHeight, windowedHeight, "window height should be restored.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html b/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html
new file mode 100644
index 0000000000..28b0235c87
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Test for Bug 1609180</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<style>
+</style>
+<button>Request Fullscreen on sub iframe</button>
+<iframe src="dummy_page.html" allowfullscreen></iframe>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[sub-iframe] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[sub-iframe] " + msg);
+}
+
+function begin() {
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, startTest);
+}
+
+let doc;
+function startTest() {
+ let button = document.querySelector("button");
+ doc = document.querySelector("iframe").contentDocument;
+ button.addEventListener("click", () => {
+ doc.documentElement.requestFullscreen();
+ });
+ addFullscreenChangeContinuation("enter", enteredFullscreen, doc);
+ addFullscreenErrorContinuation(() => {
+ ok(false, "Failed to enter fullscreen");
+ exitedFullscreen();
+ }, doc);
+ synthesizeMouseAtCenter(button, {});
+}
+
+function enteredFullscreen() {
+ is(doc.fullscreenElement, doc.documentElement, "Entered fullscreen");
+ addFullscreenChangeContinuation("exit", exitedFullscreen, doc);
+ doc.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ SpecialPowers.popPrefEnv(finish);
+}
+
+function finish() {
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-svg-element.html b/dom/base/test/fullscreen/file_fullscreen-svg-element.html
new file mode 100644
index 0000000000..1dfc78aa1c
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-svg-element.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=735031
+ Bug 735031 - Fullscreen API implementation assumes an HTML Element
+ -->
+ <head>
+ <title>Bug 735031</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="/tests/SimpleTest/EventUtils.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=73503">
+ Mozilla Bug 735031</a>
+
+ <svg id="svg-elem" width="100" height="100" viewbox="0 0 100 100">
+ <rect x="10" y="10" width="50" height="50"
+ fill="black" stroke="blue" stroke-width="2"/>
+ </svg>
+
+ <pre id="test">
+ <script type="application/javascript">
+ /*
+ * Test for Bug 735031
+ * Test locking non-html element.
+ */
+ function begin() {
+ var elem = document.getElementById("svg-elem")
+ , elemWasLocked = false;
+
+ document.addEventListener("fullscreenchange", function (e) {
+ if (document.fullscreenElement === elem) {
+ elemWasLocked = true;
+ document.exitFullscreen();
+ } else {
+ opener.ok(elemWasLocked, "Expected SVG elem to become locked.");
+ opener.nextTest();
+ }
+ });
+ elem.requestFullscreen();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-table.html b/dom/base/test/fullscreen/file_fullscreen-table.html
new file mode 100644
index 0000000000..39c602334a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-table.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1223561</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<table style="background-color: green"></table>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+ opener.ok(condition, "[table] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[table] " + msg);
+}
+
+function info(msg) {
+ opener.info("[table] " + msg);
+}
+
+const gTable = document.querySelector("table");
+
+function begin() {
+ info("The default background of window should be white");
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ assertWindowPureColor(window, "white");
+ gTable.requestFullscreen();
+}
+
+function enteredFullscreen() {
+ info("The table with green background should be in fullscreen");
+ assertWindowPureColor(window, "green");
+ gTable.style = "background: transparent";
+ info("When the table becames transparent, the black backdrop should appear");
+ assertWindowPureColor(window, "black");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-top-layer.html b/dom/base/test/fullscreen/file_fullscreen-top-layer.html
new file mode 100644
index 0000000000..9e95182b02
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-top-layer.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1126230</title>
+ <style>
+ #back {
+ position: fixed !important;
+ z-index: 2147483647 !important;
+ top: 0 !important; left: 0 !important;
+ right: 0 !important; bottom: 0 !important;
+ width: 100% !important; height: 100% !important;
+ }
+ #parent {
+ position: fixed;
+ z-index: -2147483748;
+ width: 0; height: 0;
+ overflow: hidden;
+ opacity: 0;
+ mask: url(#mask);
+ clip: rect(0, 0, 0, 0);
+ clip-path: url(#clipPath);
+ filter: opacity(0%);
+ will-change: transform;
+ perspective: 10px;
+ transform: scale(0);
+ }
+ /* The following styles are copied from ua.css to ensure that
+ * no other style change may trigger frame reconstruction */
+ :root {
+ overflow: hidden !important;
+ }
+ .two #fullscreen {
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ z-index: 2147483647 !important;
+ width: 100% !important;
+ height: 100% !important;
+ margin: 0 !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ box-sizing: border-box !important;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126230">Mozilla Bug 1126230</a>
+<div id="parent">
+ <div id="fullscreen" style="background-color: green"></div>
+</div>
+<div id="back" style="background-color: red"></div>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <clipPath id="clipPath"></clipPath>
+ <mask id="mask"></mask>
+ </defs>
+</svg>
+<script>
+const gParentProperties = [
+ "position", "zIndex", "overflow",
+ "opacity", "mask", "clip", "clipPath",
+ "filter", "willChange", "transform"
+];
+
+var gInitialVals = {};
+
+const gParent = document.getElementById("parent");
+const gFullscreen = document.getElementById("fullscreen");
+const gBack = document.getElementById("back");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[top-layer] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[top-layer] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[top-layer] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+
+var tests = ["one", "two"];
+
+function begin() {
+ // record initial computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ gInitialVals[prop] = style[prop];
+ }
+
+ nextTest();
+}
+
+function nextTest() {
+ document.body.className = tests.shift();
+ // trigger a reflow to ensure the state of frames before fullscreen
+ gFullscreen.getBoundingClientRect();
+
+ ok(!document.fullscreenElement, "Shouldn't be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "red");
+ // simulate click
+ window.addEventListener("click", firstClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function firstClick(evt) {
+ window.removeEventListener("click", firstClick);
+ is(evt.target, gBack, "Click target should be #back before fullscreen");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function enterFullscreen() {
+ ok(document.fullscreenElement, "Should now be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "green");
+ // check computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ is(style[prop], gInitialVals[prop],
+ `Computed style ${prop} of #parent should not be changed`);
+ }
+ // simulate click
+ window.addEventListener("click", secondClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function secondClick(evt) {
+ window.removeEventListener("click", secondClick);
+ is(evt.target, gFullscreen, "Click target should be #fullscreen now");
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ if (tests.length) {
+ nextTest();
+ } else {
+ opener.nextTest();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-utils.js b/dom/base/test/fullscreen/file_fullscreen-utils.js
new file mode 100644
index 0000000000..b4779da4de
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-utils.js
@@ -0,0 +1,87 @@
+// Keep track of how many fullscreenChange enters we've received, so that
+// we can balance them with the number of exits we receive. We reset this
+// to 0 when we load a test.
+var fullscreenChangeEnters = 0;
+
+addLoadEvent(function () {
+ info(`Resetting fullscreen enter count.`);
+ fullscreenChangeEnters = 0;
+});
+
+// This can be used to force a certain value for fullscreenChangeEnters
+// to handle unusual conditions -- such as exiting multiple levels of
+// fullscreen forcibly.
+function setFullscreenChangeEnters(enters) {
+ info(`Setting fullscreen enter count to ${enters}.`);
+ fullscreenChangeEnters = enters;
+}
+
+// Returns true if the window believes it is in fullscreen. This may be true even
+// before an asynchronous fullscreen transition is complete.
+function inFullscreenMode(win) {
+ return win.document.fullscreenElement;
+}
+
+// Adds a listener that will be called once a fullscreen transition
+// is complete. When type==='enter', callback is called when we've
+// received a fullscreenchange event, and the fullscreen transition is
+// complete. When type==='exit', callback is called when we've
+// received a fullscreenchange event and the window is out of
+// fullscreen. inDoc is the document which the listeners are added on,
+// if absent, the listeners are added to the current document.
+// the current document.
+function addFullscreenChangeContinuation(type, callback, inDoc) {
+ var doc = inDoc || document;
+ var topWin = doc.defaultView.top;
+ function checkCondition() {
+ if (type == "enter") {
+ fullscreenChangeEnters++;
+ return inFullscreenMode(topWin);
+ } else if (type == "exit") {
+ fullscreenChangeEnters--;
+ return fullscreenChangeEnters
+ ? inFullscreenMode(topWin)
+ : !inFullscreenMode(topWin);
+ }
+ throw new Error("'type' must be either 'enter', or 'exit'.");
+ }
+ function onFullscreenChange(event) {
+ doc.removeEventListener("fullscreenchange", onFullscreenChange);
+ ok(checkCondition(), `Should ${type} fullscreen.`);
+ // Delay invocation so other listeners have a chance to respond before
+ // we continue.
+ requestAnimationFrame(() => setTimeout(() => callback(event), 0), 0);
+ }
+ doc.addEventListener("fullscreenchange", onFullscreenChange);
+}
+
+// Calls |callback| when the next fullscreenerror is dispatched to inDoc||document.
+function addFullscreenErrorContinuation(callback, inDoc) {
+ let doc = inDoc || document;
+ let listener = function (event) {
+ doc.removeEventListener("fullscreenerror", listener);
+ // Delay invocation so other listeners have a chance to respond before
+ // we continue.
+ requestAnimationFrame(() => setTimeout(() => callback(event), 0), 0);
+ };
+ doc.addEventListener("fullscreenerror", listener);
+}
+
+// Waits until the window has both the load event and a MozAfterPaint called on
+// it, and then invokes the callback
+function waitForLoadAndPaint(win, callback) {
+ win.addEventListener(
+ "MozAfterPaint",
+ function () {
+ // The load event may have fired before the MozAfterPaint, in which case
+ // listening for it now will hang. Instead we check the readyState to see if
+ // it already fired, and if so, invoke the callback right away.
+ if (win.document.readyState == "complete") {
+ callback();
+ } else {
+ win.addEventListener("load", callback, { once: true });
+ }
+ },
+ { once: true }
+ );
+}
diff --git a/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html b/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html
new file mode 100644
index 0000000000..620bc5acf9
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1223561</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<div id="target" style="width: 100px; height: 100px; background-color: green;"></div>
+<script>
+"use strict";
+
+function begin() {
+ info("Setting full zoom to 30%");
+ SpecialPowers.setFullZoom(window, 0.3);
+
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ document.getElementById("target").requestFullscreen();
+}
+
+function enteredFullscreen() {
+ info("The element with green background should be in fullscreen");
+ assertWindowPureColor(window, "green");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html b/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html
new file mode 100644
index 0000000000..9938fdda6b
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta name=viewport content="width=980">
+<style>
+ #player {
+ background: green;
+ }
+ #overflow {
+ height: 500vh;
+ }
+</style>
+<div id="player"></div>
+<div id="overflow"></div>
diff --git a/dom/base/test/fullscreen/fullscreen.xhtml b/dom/base/test/fullscreen/fullscreen.xhtml
new file mode 100644
index 0000000000..2cc95642b6
--- /dev/null
+++ b/dom/base/test/fullscreen/fullscreen.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test for fullscreen sizemode in chrome
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ sizemode="fullscreen">
+
+<script>
+
+window.addEventListener("fullscreen", onFullScreen, true);
+
+function onFullScreen(event)
+{
+ window.arguments[0].done(window.fullScreen);
+}
+
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<button id="find-button" label="Find"/>
+<button id="cancel-button" label="Cancel"/>
+
+</body>
+</window>
diff --git a/dom/base/test/fullscreen/fullscreen_helpers.js b/dom/base/test/fullscreen/fullscreen_helpers.js
new file mode 100644
index 0000000000..6e78015cd8
--- /dev/null
+++ b/dom/base/test/fullscreen/fullscreen_helpers.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URLS = [
+ // all frames are in different process.
+ `data:text/html,
+ <div name="div" id="div" style="width: 100px; height: 100px; background: red;">
+ <iframe id="iframe" allowfullscreen="yes"
+ src="http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html"></iframe>
+ </div>`,
+ // toplevel and inner most iframe are in same process, and middle iframe is
+ // in a different process.
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ `http://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-top.html`,
+ // toplevel and middle iframe are in same process, and inner most iframe is
+ // in a different process.
+ `http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-top.html`,
+];
+
+function waitRemoteFullscreenExitEvents(aBrowsingContexts) {
+ let promises = [];
+ aBrowsingContexts.forEach(([aBrowsingContext, aName]) => {
+ promises.push(
+ SpecialPowers.spawn(aBrowsingContext, [aName], async name => {
+ return new Promise(resolve => {
+ let document = content.document;
+ document.addEventListener(
+ "fullscreenchange",
+ function changeHandler() {
+ if (document.fullscreenElement) {
+ return;
+ }
+
+ ok(true, `check remote DOM fullscreen event (${name})`);
+ document.removeEventListener("fullscreenchange", changeHandler);
+ resolve();
+ }
+ );
+ });
+ })
+ );
+ });
+ return Promise.all(promises);
+}
+
+function waitDOMFullscreenEvent(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return new Promise(resolve => {
+ function errorHandler() {
+ ok(false, "should not get fullscreenerror event");
+ aDocument.removeEventListener("fullscreenchange", changeHandler);
+ aDocument.removeEventListener("fullscreenerror", errorHandler);
+ resolve();
+ }
+
+ function changeHandler() {
+ if (aWaitUntil && aIsInFullscreen != !!aDocument.fullscreenElement) {
+ return;
+ }
+
+ is(
+ aIsInFullscreen,
+ !!aDocument.fullscreenElement,
+ "check DOM fullscreen (event)"
+ );
+ aDocument.removeEventListener("fullscreenchange", changeHandler);
+ aDocument.removeEventListener("fullscreenerror", errorHandler);
+ resolve();
+ }
+
+ aDocument.addEventListener("fullscreenchange", changeHandler);
+ aDocument.addEventListener("fullscreenerror", errorHandler);
+ });
+}
+
+function waitWidgetFullscreenEvent(
+ aWindow,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return BrowserTestUtils.waitForEvent(aWindow, "fullscreen", false, aEvent => {
+ if (
+ aWaitUntil &&
+ aIsInFullscreen !=
+ aWindow.document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ return false;
+ }
+
+ is(
+ aIsInFullscreen,
+ aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ "check widget fullscreen (event)"
+ );
+ return true;
+ });
+}
+
+function waitForFullScreenObserver(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return TestUtils.topicObserved("fullscreen-painted", (subject, data) => {
+ if (
+ aWaitUntil &&
+ aIsInFullscreen !=
+ aDocument.documentElement.hasAttribute("inDOMFullscreen")
+ ) {
+ return false;
+ }
+
+ is(
+ aIsInFullscreen,
+ aDocument.documentElement.hasAttribute("inDOMFullscreen"),
+ "check fullscreen (observer)"
+ );
+ return true;
+ });
+}
+
+function waitForFullscreenState(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return Promise.all([
+ waitWidgetFullscreenEvent(
+ aDocument.defaultView,
+ aIsInFullscreen,
+ aWaitUntil
+ ),
+ waitDOMFullscreenEvent(aDocument, aIsInFullscreen, aWaitUntil),
+ waitForFullScreenObserver(aDocument, aIsInFullscreen, aWaitUntil),
+ ]);
+}
+
+// Wait for fullscreenchange event for fullscreen exit. And wait for
+// fullscreen-painted observed conditionally.
+async function waitForFullscreenExit(aDocument) {
+ info(`waitForFullscreenExit`);
+ let promiseFsObserver = null;
+ let observer = function () {
+ if (aDocument.documentElement.hasAttribute("inDOMFullscreen")) {
+ info(`waitForFullscreenExit, fullscreen-painted, inDOMFullscreen`);
+ Services.obs.removeObserver(observer, "fullscreen-painted");
+ promiseFsObserver = waitForFullScreenObserver(aDocument, false);
+ }
+ };
+ Services.obs.addObserver(observer, "fullscreen-painted");
+
+ await waitDOMFullscreenEvent(aDocument, false, true);
+ // If there is a fullscreen-painted observer notified for inDOMFullscreen set,
+ // we expect to have a subsequent fullscreen-painted observer notified with
+ // inDOMFullscreen unset.
+ if (promiseFsObserver) {
+ info(`waitForFullscreenExit, promiseFsObserver`);
+ await promiseFsObserver;
+ return;
+ }
+
+ Services.obs.removeObserver(observer, "fullscreen-painted");
+ // If inDOMFullscreen is set we expect to have a subsequent fullscreen-painted
+ // observer notified with inDOMFullscreen unset.
+ if (aDocument.documentElement.hasAttribute("inDOMFullscreen")) {
+ info(`waitForFullscreenExit, inDOMFullscreen`);
+ await waitForFullScreenObserver(aDocument, false, true);
+ }
+}
diff --git a/dom/base/test/fullscreen/head.js b/dom/base/test/fullscreen/head.js
new file mode 100644
index 0000000000..1e3b435d0c
--- /dev/null
+++ b/dom/base/test/fullscreen/head.js
@@ -0,0 +1,65 @@
+function pushPrefs(...aPrefs) {
+ return SpecialPowers.pushPrefEnv({ set: aPrefs });
+}
+
+function promiseWaitForEvent(
+ object,
+ eventName,
+ capturing = false,
+ chrome = false
+) {
+ return new Promise(resolve => {
+ function listener(event) {
+ info("Saw " + eventName);
+ object.removeEventListener(eventName, listener, capturing, chrome);
+ resolve(event);
+ }
+
+ info("Waiting for " + eventName);
+ object.addEventListener(eventName, listener, capturing, chrome);
+ });
+}
+
+/**
+ * Waits for the next load to complete in any browser or the given browser.
+ * If a <tabbrowser> is given it waits for a load in any of its browsers.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser = gBrowser) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange(webProgress, req, flags, status) {
+ let docStop =
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+ Ci.nsIWebProgressListener.STATE_STOP;
+ info(
+ "Saw state " +
+ flags.toString(16) +
+ " and status " +
+ status.toString(16)
+ );
+ // When a load needs to be retargetted to a new process it is cancelled
+ // with NS_BINDING_ABORTED so ignore that case
+ if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
+ aBrowser.removeProgressListener(this);
+ waitForDocLoadComplete.listeners.delete(this);
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ info("Browser loaded " + chan.originalURI.spec);
+ resolve();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ aBrowser.addProgressListener(listener);
+ waitForDocLoadComplete.listeners.add(listener);
+ info("Waiting for browser load");
+ });
+}
+// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
+// they're not GC'ed before we saw the page load.
+waitForDocLoadComplete.listeners = new Set();
+registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
diff --git a/dom/base/test/fullscreen/mochitest.ini b/dom/base/test/fullscreen/mochitest.ini
new file mode 100644
index 0000000000..ee90f8960a
--- /dev/null
+++ b/dom/base/test/fullscreen/mochitest.ini
@@ -0,0 +1,51 @@
+[DEFAULT]
+tags = fullscreen
+support-files =
+ file_fullscreen-api-race.html
+ file_fullscreen-api.html
+ file_fullscreen-async.html
+ file_fullscreen-backdrop.html
+ file_fullscreen-denied-inner.html
+ file_fullscreen-denied.html
+ file_fullscreen-esc-exit-inner.html
+ file_fullscreen-esc-exit.html
+ file_fullscreen-event-order.html
+ file_fullscreen-featurePolicy.html
+ file_fullscreen-featurePolicy-inner.html
+ file_fullscreen-focus.html
+ file_fullscreen-focus-inner.html
+ file_fullscreen-hidden.html
+ file_fullscreen-lenient-setters.html
+ file_fullscreen_meta_viewport.html
+ file_fullscreen-multiple-inner.html
+ file_fullscreen-multiple.html
+ file_fullscreen-navigation.html
+ file_fullscreen-nested.html
+ file_fullscreen-prefixed.html
+ file_fullscreen-resize.html
+ file_fullscreen-rollback.html
+ file_fullscreen-scrollbar.html
+ file_fullscreen-selector.html
+ file_fullscreen-shadowdom.html
+ file_fullscreen-single.html
+ file_fullscreen-sub-iframe.html
+ file_fullscreen-svg-element.html
+ file_fullscreen-table.html
+ file_fullscreen-top-layer.html
+ file_fullscreen-utils.js
+ file_fullscreen-with-full-zoom.html
+
+[test_fullscreen-api-race.html]
+skip-if = toolkit == 'android' # same as test_fullscreen-api.html, 1356570
+ os == "mac" && debug
+[test_fullscreen-api-rapid-cycle.html]
+[test_fullscreen-api.html]
+allow_xul_xbl = true # XUL is used in file_fullscreen-api.html
+skip-if =
+ toolkit == 'android'
+ os == 'mac' # Bug 1579623, 1776996
+ http3
+[test_fullscreen_meta_viewport.html]
+[test_fullscreen_modal.html]
+skip-if =
+ http3
diff --git a/dom/base/test/fullscreen/moz.build b/dom/base/test/fullscreen/moz.build
new file mode 100644
index 0000000000..864fdc1e74
--- /dev/null
+++ b/dom/base/test/fullscreen/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "chrome.ini",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.ini",
+]
diff --git a/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml b/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml
new file mode 100644
index 0000000000..3041d851ac
--- /dev/null
+++ b/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test that "MozShowFullScreenWarning" is dispatched to chrome on restricted keypress.
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" width="400" height="400">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Ensure the full-screen api is enabled, and will be disabled on test exit.
+var gPrevEnabled = SpecialPowers.getBoolPref("full-screen-api.enabled");
+var gPrevTrusted = SpecialPowers.getBoolPref("full-screen-api.allow-trusted-requests-only");
+var newwindow;
+
+// Ensure "fullscreen" permissions are not present on the test URI.
+var uri = Services.io.newURI("http://mochi.test:8888");
+var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
+Services.perms.removeFromPrincipal(principal, "fullscreen");
+
+SpecialPowers.pushPrefEnv({"set": [
+ ['full-screen-api.enabled', true],
+ ['full-screen-api.allow-trusted-requests-only', false],
+ ['full-screen-api.transition-duration.enter', '0 0'],
+ ['full-screen-api.transition-duration.leave', '0 0']
+]}).then(setup);
+
+function setup() {
+ newwindow = window.browsingContext.topChromeWindow.openDialog(
+ "MozDomFullscreen_chrome.xhtml", "_blank","chrome,dialog=no,resizable=yes,width=400,height=400", window);
+}
+
+function done()
+{
+ newwindow.close();
+ SimpleTest.finish();
+}
+
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api-race.html b/dom/base/test/fullscreen/test_fullscreen-api-race.html
new file mode 100644
index 0000000000..3fe4cd3500
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api-race.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for race conditions of Fullscreen API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+
+function Deferred() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+}
+
+function checkIsChromeFullscreen(win, inFullscreen) {
+ return SimpleTest.promiseWaitForCondition(
+ () => win.fullScreen == inFullscreen,
+ "The window should exit fullscreen state");
+}
+
+SimpleTest.waitForExplicitFinish();
+// XXX This actually exposes a true race condition, but it could rarely
+// happen in real world, because it only happens when requestFullscreen
+// is called immediately after exiting fullscreen in certain condition,
+// and in real life, requestFullscreen can only be called inside a user
+// event handler. But we want to fix this race condition at some point,
+// via queuing all exiting request as well as entering request together
+// which we may eventually need to do for bug 1188256.
+SimpleTest.requestFlakyTimeout(
+ "Need to wait for potential fullscreen transition");
+addLoadEvent(function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ]
+ }, next);
+});
+
+const OPEN_WINDOW_FUNCS = [
+ function openNewTab() {
+ return new Promise(resolve => {
+ var win = window.open("about:blank");
+ win.addEventListener("load", () => {
+ resolve(win);
+ }, { once: true });
+ });
+ },
+ function openNewWindow() {
+ return new Promise(resolve => {
+ var win = window.open("about:blank", "", "width=300,height=200");
+ win.addEventListener("load", () => {
+ resolve(win);
+ }, { once: true });
+ });
+ }
+];
+
+const ACTION_FUNCS = [
+ function navigate(win) {
+ info("About to navigate to another page");
+ var promise = new Promise(resolve => {
+ window.addEventListener("message", () => {
+ SimpleTest.waitForFocus(() => {
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ resolve();
+ });
+ }, win);
+ }, { once: true });
+ });
+ win.location = "file_fullscreen-api-race.html";
+ return promise;
+ },
+ function closeWindow(win) {
+ info("About to close the window");
+ win.close();
+ return Promise.resolve();
+ },
+ function exitFullscreen(win) {
+ info("About to cancel fullscreen");
+ var deferred = new Deferred();
+ function listener() {
+ win.removeEventListener("fullscreenchange", listener);
+ ok(!win.document.fullscreenElement, "Should exit fullscreen");
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ deferred.resolve();
+ });
+ }
+ win.addEventListener("fullscreenchange", listener);
+ win.document.exitFullscreen();
+ return deferred.promise;
+ },
+ function exitAndClose(win) {
+ info("About to cancel fullscreen and close the window");
+ win.document.exitFullscreen();
+ win.close();
+ return Promise.resolve();
+ }
+];
+
+function* testGenerator() {
+ for (var openWinFunc of OPEN_WINDOW_FUNCS) {
+ for (var actionFunc of ACTION_FUNCS) {
+ info(`Testing ${openWinFunc.name}, ${actionFunc.name}`);
+ yield { openWinFunc, actionFunc };
+ }
+ }
+}
+
+function runTest(test) {
+ var winPromise = test.openWinFunc();
+ return winPromise.then((win) => {
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(() => resolve(win), win, true);
+ });
+ }).then((win) => {
+ return new Promise((resolve, reject) => {
+ var retried = false;
+ function listener(evt) {
+ if (!retried && evt.type == "fullscreenerror") {
+ todo(false, "Failed to enter fullscreen, but try again");
+ retried = true;
+ SimpleTest.waitForFocus(() => {
+ win.document.documentElement.requestFullscreen();
+ }, win, true);
+ return;
+ }
+ win.removeEventListener("fullscreenchange", listener);
+ win.removeEventListener("fullscreenerror", listener);
+ is(evt.type, "fullscreenchange", "Should get fullscreenchange");
+ ok(win.document.fullscreenElement, "Should have entered fullscreen");
+ ok(win.fullScreen, "The window should be in fullscreen");
+ test.actionFunc(win).then(() => resolve(win));
+ }
+ if (win.fullScreen) {
+ todo(false, "Should not open in fullscreen mode");
+ win.close();
+ reject();
+ return;
+ }
+ info("About to enter fullscreen");
+ win.addEventListener("fullscreenchange", listener);
+ win.addEventListener("fullscreenerror", listener);
+ win.document.documentElement.requestFullscreen();
+ });
+ }).then((win) => {
+ ok(win.closed, "The window should have been closed");
+ });
+}
+
+var tests = testGenerator();
+
+function next() {
+ var test = tests.next().value;
+ if (test) {
+ runTest(test).catch(() => {
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(resolve);
+ }).then(() => runTest(test));
+ }).catch(() => {
+ ok(false, "Fail to run test " +
+ `${test.openWinFunc.name}, ${test.actionFunc.name}`);
+ }).then(() => {
+ setTimeout(() => SimpleTest.waitForFocus(next), 1000);
+ });
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html b/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html
new file mode 100644
index 0000000000..36e622ad4c
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for rapid cycling of Fullscreen API requests</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+
+// There are two ways that web content should be able to reliably
+// request and respond to fullscreen:
+//
+// 1) Wait on the requestFullscreen() and exitFullscreen() promises.
+// 2) Respond to the "fullscreenchange" and "fullscreenerror" events
+// after calling requestFullscreen() or exitFullscreen().
+//
+// This test exercises both methods rapidly, while checking to see
+// if any expected signal is taking too long. If awaiting a promise
+// or waiting for an event takes longer than some number of seconds,
+// the test will fail instead of timing out. This is to help detect
+// vulnerabilities in the implementation which would slow down the
+// test harness waiting for the timeout.
+
+// How many enter-exit cycles we run for each method of detecting a
+// fullscreen transition.
+const CYCLE_COUNT = 3;
+
+// How long do we wait for one transition before considering it as
+// an error.
+const TOO_LONG_SECONDS = 3;
+
+SimpleTest.requestFlakyTimeout("We race against Promises to turn possible timeouts into errors.");
+
+function rejectAfterTooLong() {
+ return new Promise((resolve, reject) => {
+ const fail = () => {
+ reject(`timeout after ${TOO_LONG_SECONDS} seconds`);
+ }
+ setTimeout(fail, TOO_LONG_SECONDS * 1000);
+ });
+}
+
+add_setup(async () => {
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ // Keep the test structure simple.
+ ["full-screen-api.allow-trusted-requests-only", false],
+
+ // Make macOS fullscreen transitions asynchronous.
+ ["full-screen-api.macos-native-full-screen", true],
+
+ // Clarify that even no-duration async transitions are vulnerable.
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ]
+ });
+});
+
+add_task(ensureOutOfFullscreen);
+
+// It is an implementation detail that promises resolve first, and
+// then events are fired on a later event loop. For this reason,
+// it's very important that we do the rapidCycleAwaitEvents task
+// first, because we don't want to have any "stray" fullscreenchange
+// events in the pipeline when we start that task. Conversely,
+// there's really no way for the rapidCycleAwaitEvents to poison
+// the environment for the next task, which waits on promises.
+add_task(rapidCycleAwaitEvents);
+
+add_task(ensureOutOfFullscreen);
+
+add_task(rapidCycleAwaitPromises);
+
+add_task(() => { ok(true, "Completed test with one expected result."); });
+
+// This is a helper function to repeatedly invoke a Promise generator
+// until the Promise resolves, delaying by one event loop on each
+// attempt.
+async function repeatUntilSuccessful(f) {
+ let successful = false;
+ do {
+ try {
+ // Delay one event loop.
+ await new Promise(r => SimpleTest.executeSoon(r));
+ await f();
+ successful = true;
+ } catch (error) {
+ info(`repeatUntilSuccessful: error ${error}.`);
+ }
+ } while(!successful);
+}
+
+async function ensureOutOfFullscreen() {
+ // Repeatedly call exitFullscreen until we get out.
+ await repeatUntilSuccessful(async () => {
+ if (document.fullscreenElement) {
+ await document.exitFullscreen();
+ }
+ if (document.fullscreenElement) {
+ throw new Error("still in fullscreen");
+ }
+ });
+}
+
+async function rapidCycleAwaitEvents() {
+ const receiveOneFullscreenchange = () => {
+ return new Promise(resolve => {
+ document.addEventListener("fullscreenchange", resolve, { once: true });
+ });
+ };
+
+ let gotError = false;
+ for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
+ info(`Event cycle ${cycle} request fullscreen.`);
+ const enterPromise = receiveOneFullscreenchange();
+ document.documentElement.requestFullscreen();
+ await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Event cycle ${cycle} requestFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+
+ info(`Event cycle ${cycle} exit fullscreen.`);
+ const exitPromise = receiveOneFullscreenchange();
+ document.exitFullscreen();
+ await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Event cycle ${cycle} exitFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+ }
+}
+
+async function rapidCycleAwaitPromises() {
+ let gotError = false;
+ for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
+ info(`Promise cycle ${cycle} request fullscreen.`);
+ const enterPromise = document.documentElement.requestFullscreen();
+ await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Promise cycle ${cycle} requestFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+
+ info(`Promise cycle ${cycle} exit fullscreen.`);
+ const exitPromise = document.exitFullscreen();
+ await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Promise cycle ${cycle} exitFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api.html b/dom/base/test/fullscreen/test_fullscreen-api.html
new file mode 100644
index 0000000000..2a59d6eeb0
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api.html
@@ -0,0 +1,150 @@
+ <!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545812">Mozilla Bug 545812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Tests for Bug 545812 **/
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// Run the tests which go full-screen in new windows, as mochitests normally
+// run in an iframe, which by default will not have the allowfullscreen
+// attribute set, so full-screen won't work.
+var gTestWindows = [
+ { test: "file_fullscreen-single.html" },
+ { test: "file_fullscreen-multiple.html",
+ prefs: [["full-screen-api.exit-on.windowRaise", false],
+ ["full-screen-api.exit-on.windowOpen", false]] },
+ { test: "file_fullscreen-rollback.html" },
+ { test: "file_fullscreen-esc-exit.html" },
+ { test: "file_fullscreen-denied.html" },
+ { test: "file_fullscreen-api.html" },
+ { test: "file_fullscreen-hidden.html" },
+ { test: "file_fullscreen-focus.html" },
+ { test: "file_fullscreen-svg-element.html" },
+ { test: "file_fullscreen-navigation.html" },
+ { test: "file_fullscreen-scrollbar.html" },
+ { test: "file_fullscreen-selector.html" },
+ { test: "file_fullscreen-shadowdom.html" },
+ { test: "file_fullscreen-top-layer.html" },
+ { test: "file_fullscreen-backdrop.html" },
+ { test: "file_fullscreen-nested.html" },
+ { test: "file_fullscreen-prefixed.html" },
+ { test: "file_fullscreen-lenient-setters.html" },
+ { test: "file_fullscreen-table.html" },
+ { test: "file_fullscreen-event-order.html" },
+ { test: "file_fullscreen-featurePolicy.html",
+ prefs: [["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true]] },
+ { test: "file_fullscreen-async.html" },
+ { test: "file_fullscreen-sub-iframe.html" },
+ { test: "file_fullscreen-with-full-zoom.html" },
+ { test: "file_fullscreen-resize.html" },
+];
+
+var testWindow = null;
+var gTestIndex = 0;
+
+function finish() {
+ SimpleTest.finish();
+}
+
+function nextTest() {
+ if (testWindow) {
+ info("Waiting for focus to return to main window");
+ window.addEventListener("focus", function() {
+ info("main window focused, starting next test");
+ SimpleTest.executeSoon(runNextTest);
+ }, {once: true});
+ info("testWindow.close()");
+ testWindow.close();
+ } else {
+ SimpleTest.executeSoon(runNextTest);
+ }
+}
+
+function waitForEvent(eventTarget, eventName, checkFn, callback) {
+ eventTarget.addEventListener(eventName, function listener(event) {
+ if (checkFn && !checkFn(event)) {
+ return;
+ }
+ eventTarget.removeEventListener(eventName, listener);
+ callback();
+ });
+}
+
+function runNextTest() {
+ if (gTestIndex < gTestWindows.length) {
+ let test = gTestWindows[gTestIndex];
+ let promise = ("prefs" in test)
+ ? SpecialPowers.pushPrefEnv({"set": test.prefs})
+ : Promise.resolve();
+ promise.then(function() {
+ info(`Run test ${test.test}`);
+ testWindow = window.open(test.test, "", "width=500,height=500,scrollbars=yes");
+ // We'll wait for the window to load, then make sure our window is refocused
+ // before starting the test, which will get kicked off on "focus".
+ // This ensures that we're essentially back on the primary "desktop" on
+ // OS X Lion before we run the test.
+ waitForLoadAndPaint(testWindow, function() {
+ SimpleTest.waitForFocus(function() {
+ info("Were focused");
+ // For the platforms that support reporting occlusion state (e.g. Mac),
+ // we should wait until the docshell has been activated again,
+ // otherwise, the fullscreen request might be denied.
+ if (testWindow.document.hidden) {
+ info("Waiting for document to unhide");
+ waitForEvent(testWindow.document, "visibilitychange", (event) => {
+ return !testWindow.document.hidden;
+ }, testWindow.begin);
+ return;
+ }
+ testWindow.begin();
+ }, testWindow);
+ });
+ });
+ gTestIndex++;
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+try {
+ window.fullScreen = true;
+} catch (e) {
+}
+is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from content");
+// Ensure the full-screen api is enabled, and will be disabled on test exit.
+// Disable the requirement for trusted contexts only, so the tests are easier
+// to write
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ ]}, nextTest);
+});
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen.xhtml b/dom/base/test/fullscreen/test_fullscreen.xhtml
new file mode 100644
index 0000000000..e338bac523
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test for fullscreen sizemode in chrome
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ sizemode="fullscreen">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let newwindow = window.browsingContext.topChromeWindow.openDialog("fullscreen.xhtml", "_blank","chrome,resizable=yes", window);
+
+function done()
+{
+ // because we are cancelling the fullscreen event, it
+ // takes a bit for the fullScreen property to be set
+ setTimeout(function() { this.complete(); }, 0);
+}
+
+function complete()
+{
+ ok(newwindow.fullScreen, "window.fullScreen is true.");
+ newwindow.close();
+ SimpleTest.finish();
+}
+
+</script>
+
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html b/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html
new file mode 100644
index 0000000000..c2cd355c6b
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Test for Bug 545812</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.meta-viewport.enabled", true]]
+ });
+
+ let win = window.open("file_fullscreen_meta_viewport.html", "", "width=500,height=500,scrollbars=yes");
+ await SimpleTest.promiseFocus(win);
+
+ is(win.innerWidth, 980, "Meta viewport should be in effect");
+
+ let element = win.document.querySelector("#player");
+ await SpecialPowers.wrap(element).requestFullscreen();
+
+ ok(win.document.fullscreen, "Window should be in fullscreen");
+ is(win.document.fullscreenElement, element, "#player should be the fullscreen element");
+ is(win.innerWidth, screen.width, "Should be fullscreen (w)");
+ is(win.innerHeight, screen.height, "Should be fullscreen (h)");
+ is(element.clientWidth, win.innerWidth, "Element should fill the viewport vertically");
+ is(element.clientHeight, win.innerHeight, "Element should fill the viewport vertically");
+
+ SpecialPowers.wrap(win.document).exitFullscreen();
+ win.close();
+ SimpleTest.finish();
+}())
+</script>
diff --git a/dom/base/test/fullscreen/test_fullscreen_modal.html b/dom/base/test/fullscreen/test_fullscreen_modal.html
new file mode 100644
index 0000000000..94b115f93e
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen_modal.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<title>Test for bug 1771150</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ #fullscreen {
+ background-color: rgba(0, 255, 0, .5);
+ }
+ #fullscreen::backdrop {
+ background-color: transparent;
+ }
+ #fullscreen, #fullscreen::backdrop {
+ pointer-events: none;
+ }
+</style>
+<div id="fullscreen"></div>
+<button>Go fullscreen</button>
+<script>
+const button = document.querySelector("button");
+let clickCount = 0;
+let lastFullscreenPromise = null;
+let shouldEnterFullscreen = false;
+button.addEventListener("click", function(e) {
+ clickCount++;
+ if (shouldEnterFullscreen) {
+ lastFullscreenPromise = document.getElementById("fullscreen").requestFullscreen();
+ }
+});
+
+function clickButton(expectEvent) {
+ let lastClickCount = clickCount;
+ synthesizeMouseAtCenter(button, {});
+ (expectEvent ? isnot : is)(lastClickCount, clickCount, `Should've ${expectEvent ? "" : "not "}been able to click`);
+}
+
+function enterFullscreen() {
+ lastFullscreenPromise = null;
+ shouldEnterFullscreen = true;
+ clickButton(true);
+ shouldEnterFullscreen = false;
+ isnot(lastFullscreenPromise, null, "Should be transitioning to fullscreen");
+ return lastFullscreenPromise;
+}
+
+async function testFullscreenIsModal(modal) {
+ info("testing modal: " + modal);
+ is(document.fullscreenElement, null, "Shouldn't be in fullscreen");
+ await SpecialPowers.pushPrefEnv({ set: [["dom.fullscreen.modal", modal]] });
+ await enterFullscreen();
+
+ clickButton(/* expectEvent = */ !modal);
+
+ ok(document.fullscreenElement.matches(":fullscreen"), "Fullscreen element matches :fullscreen");
+ is(document.fullscreenElement.matches(":modal"), modal, "Fullscreen element matches :modal");
+
+ await document.exitFullscreen();
+ clickButton(/* expectEvent = */ true);
+}
+
+add_task(async function() {
+ await testFullscreenIsModal(true);
+});
+
+add_task(async function() {
+ await testFullscreenIsModal(false);
+});
+</script>
diff --git a/dom/base/test/green.png b/dom/base/test/green.png
new file mode 100644
index 0000000000..7df25f33bd
--- /dev/null
+++ b/dom/base/test/green.png
Binary files differ
diff --git a/dom/base/test/gtest/TestContentUtils.cpp b/dom/base/test/gtest/TestContentUtils.cpp
new file mode 100644
index 0000000000..d38cb1bdca
--- /dev/null
+++ b/dom/base/test/gtest/TestContentUtils.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "jsapi.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+
+using namespace mozilla::dom;
+
+struct IsURIInListMatch {
+ nsLiteralCString pattern;
+ bool firstMatch, secondMatch;
+};
+
+TEST(DOM_Base_ContentUtils, IsURIInList)
+{
+ nsCOMPtr<nsIURI> uri, subURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ "https://example.com/path/favicon.ico#"_ns);
+ ASSERT_TRUE(rv == NS_OK);
+
+ rv = NS_NewURI(getter_AddRefs(subURI),
+ "http://sub.example.com/favicon.ico?"_ns);
+ ASSERT_TRUE(rv == NS_OK);
+
+ static constexpr IsURIInListMatch patterns[] = {
+ {"bar.com,*.example.com,example.com,foo.com"_ns, true, true},
+ {"bar.com,example.com,*.example.com,foo.com"_ns, true, true},
+ {"*.example.com,example.com,foo.com"_ns, true, true},
+ {"example.com,*.example.com,foo.com"_ns, true, true},
+ {"*.example.com,example.com"_ns, true, true},
+ {"example.com,*.example.com"_ns, true, true},
+ {"*.example.com/,example.com/"_ns, true, true},
+ {"example.com/,*.example.com/"_ns, true, true},
+ {"*.example.com/pa,example.com/pa"_ns, false, false},
+ {"example.com/pa,*.example.com/pa"_ns, false, false},
+ {"*.example.com/pa/,example.com/pa/"_ns, false, false},
+ {"example.com/pa/,*.example.com/pa/"_ns, false, false},
+ {"*.example.com/path,example.com/path"_ns, false, false},
+ {"example.com/path,*.example.com/path"_ns, false, false},
+ {"*.example.com/path/,example.com/path/"_ns, true, false},
+ {"example.com/path/,*.example.com/path/"_ns, true, false},
+ {"*.example.com/favicon.ico"_ns, false, true},
+ {"example.com/path/favicon.ico"_ns, true, false},
+ {"*.example.com"_ns, false, true},
+ {"example.com"_ns, true, false},
+ {"foo.com"_ns, false, false},
+ {"*.foo.com"_ns, false, false},
+ };
+
+ for (auto& entry : patterns) {
+ bool result = nsContentUtils::IsURIInList(uri, entry.pattern);
+ ASSERT_EQ(result, entry.firstMatch) << "Matching " << entry.pattern;
+
+ result = nsContentUtils::IsURIInList(subURI, entry.pattern);
+ ASSERT_EQ(result, entry.secondMatch) << "Matching " << entry.pattern;
+ }
+}
+
+TEST(DOM_Base_ContentUtils,
+ StringifyJSON_EmptyValue_UndefinedIsNullStringLiteral)
+{
+ JS::Rooted<JSObject*> globalObject(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(globalObject));
+ JSContext* cx = jsAPI.cx();
+ nsAutoString serializedValue;
+
+ ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, JS::UndefinedHandleValue,
+ serializedValue,
+ UndefinedIsNullStringLiteral));
+ ASSERT_TRUE(serializedValue.EqualsLiteral("null"));
+}
+
+TEST(DOM_Base_ContentUtils, StringifyJSON_Object_UndefinedIsNullStringLiteral)
+{
+ JS::Rooted<JSObject*> globalObject(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(globalObject));
+ JSContext* cx = jsAPI.cx();
+ nsAutoString serializedValue;
+
+ JS::Rooted<JSObject*> jsObj(cx, JS_NewPlainObject(cx));
+ JS::Rooted<JSString*> valueStr(cx, JS_NewStringCopyZ(cx, "Hello World!"));
+ ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE));
+ JS::Rooted<JS::Value> jsValue(cx, JS::ObjectValue(*jsObj));
+
+ ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, jsValue, serializedValue,
+ UndefinedIsNullStringLiteral));
+
+ ASSERT_TRUE(serializedValue.EqualsLiteral("{\"key1\":\"Hello World!\"}"));
+}
+
+TEST(DOM_Base_ContentUtils, StringifyJSON_EmptyValue_UndefinedIsVoidString)
+{
+ JS::Rooted<JSObject*> globalObject(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(globalObject));
+ JSContext* cx = jsAPI.cx();
+ nsAutoString serializedValue;
+
+ ASSERT_TRUE(nsContentUtils::StringifyJSON(
+ cx, JS::UndefinedHandleValue, serializedValue, UndefinedIsVoidString));
+
+ ASSERT_TRUE(serializedValue.IsVoid());
+}
+
+TEST(DOM_Base_ContentUtils, StringifyJSON_Object_UndefinedIsVoidString)
+{
+ JS::Rooted<JSObject*> globalObject(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(globalObject));
+ JSContext* cx = jsAPI.cx();
+ nsAutoString serializedValue;
+
+ JS::Rooted<JSObject*> jsObj(cx, JS_NewPlainObject(cx));
+ JS::Rooted<JSString*> valueStr(cx, JS_NewStringCopyZ(cx, "Hello World!"));
+ ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE));
+ JS::Rooted<JS::Value> jsValue(cx, JS::ObjectValue(*jsObj));
+
+ ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, jsValue, serializedValue,
+ UndefinedIsVoidString));
+
+ ASSERT_TRUE(serializedValue.EqualsLiteral("{\"key1\":\"Hello World!\"}"));
+}
diff --git a/dom/base/test/gtest/TestMimeType.cpp b/dom/base/test/gtest/TestMimeType.cpp
new file mode 100644
index 0000000000..64a2b8f4bd
--- /dev/null
+++ b/dom/base/test/gtest/TestMimeType.cpp
@@ -0,0 +1,816 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "MimeType.h"
+#include "nsString.h"
+
+using mozilla::UniquePtr;
+
+TEST(MimeType, EmptyString)
+{
+ const auto in = u""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Empty string";
+}
+
+TEST(MimeType, JustWhitespace)
+{
+ const auto in = u" \t\r\n "_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Just whitespace";
+}
+
+TEST(MimeType, JustBackslash)
+{
+ const auto in = u"\\"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Just backslash";
+}
+
+TEST(MimeType, JustForwardslash)
+{
+ const auto in = u"/"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Just forward slash";
+}
+
+TEST(MimeType, MissingType1)
+{
+ const auto in = u"/bogus"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing type #1";
+}
+
+TEST(MimeType, MissingType2)
+{
+ const auto in = u" \r\n\t/bogus"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing type #2";
+}
+
+TEST(MimeType, MissingSubtype1)
+{
+ const auto in = u"bogus"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing subtype #1";
+}
+
+TEST(MimeType, MissingSubType2)
+{
+ const auto in = u"bogus/"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing subtype #2";
+}
+
+TEST(MimeType, MissingSubType3)
+{
+ const auto in = u"bogus;"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing subtype #3";
+}
+
+TEST(MimeType, MissingSubType4)
+{
+ const auto in = u"bogus; \r\n\t"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Missing subtype #3";
+}
+
+TEST(MimeType, ExtraForwardSlash)
+{
+ const auto in = u"bogus/bogus/;"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Extra forward slash";
+}
+
+TEST(MimeType, WhitespaceInType)
+{
+ const auto in = u"t\re\nx\tt /html"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Type with whitespace";
+}
+
+TEST(MimeType, WhitespaceInSubtype)
+{
+ const auto in = u"text/ h\rt\nm\tl"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Subtype with whitespace";
+}
+
+TEST(MimeType, NonAlphanumericMediaType1)
+{
+ const auto in = u"</>"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #1";
+}
+
+TEST(MimeType, NonAlphanumericMediaType2)
+{
+ const auto in = u"(/)"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #2";
+}
+
+TEST(MimeType, NonAlphanumericMediaType3)
+{
+ const auto in = u"{/}"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #3";
+}
+
+TEST(MimeType, NonAlphanumericMediaType4)
+{
+ const auto in = u"\"/\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #4";
+}
+
+TEST(MimeType, NonAlphanumericMediaType5)
+{
+ const auto in = u"\0/\0"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #5";
+}
+
+TEST(MimeType, NonAlphanumericMediaType6)
+{
+ const auto in = u"text/html(;doesnot=matter"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-alphanumeric media type #6";
+}
+
+TEST(MimeType, NonLatin1MediaType1)
+{
+ const auto in = u"ÿ/ÿ"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-latin1 media type #1";
+}
+
+TEST(MimeType, NonLatin1MediaType2)
+{
+ const auto in = u"\x0100/\x0100"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_FALSE(parsed)
+ << "Non-latin1 media type #2";
+}
+
+TEST(MimeType, MultipleParameters)
+{
+ const auto in = u"text/html;charset=gbk;no=1;charset_=gbk_;yes=2"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(u"text/html;charset=gbk;no=1;charset_=gbk_;yes=2"_ns))
+ << "Multiple parameters";
+}
+
+TEST(MimeType, DuplicateParameter1)
+{
+ const auto in = u"text/html;charset=gbk;charset=windows-1255"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(u"text/html;charset=gbk"_ns))
+ << "Duplicate parameter #1";
+}
+
+TEST(MimeType, DuplicateParameter2)
+{
+ const auto in = u"text/html;charset=();charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(u"text/html;charset=\"()\""_ns))
+ << "Duplicate parameter #2";
+}
+
+TEST(MimeType, CString)
+{
+ const auto in = "text/html;charset=();charset=GBK"_ns;
+ UniquePtr<CMimeType> parsed = CMimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsCString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals("text/html;charset=\"()\""_ns))
+ << "Duplicate parameter #2";
+}
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4819)
+#endif
+TEST(MimeType, NonAlphanumericParametersAreQuoted)
+{
+ const auto in = u"text/html;test=\x00FF\\;charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(u"text/html;test=\"\x00FF\\\\\";charset=gbk"_ns))
+ << "Non-alphanumeric parameters are quoted";
+}
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace1)
+{
+ const auto in = u"text/html;charset= g\\\"bk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" g\\\\\\\"bk\""))
+ << "Parameter is quoted if has leading whitespace #1";
+}
+
+TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace2)
+{
+ const auto in = u"text/html;charset= \"g\\bk\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" \\\"g\\\\bk\\\"\""))
+ << "Parameter is quoted if has leading whitespace #2";
+}
+
+TEST(MimeType, ParameterQuotedIfHasInternalWhitespace)
+{
+ const auto in = u"text/html;charset=g \\b\"k"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"g \\\\b\\\"k\""))
+ << "Parameter is quoted if has internal whitespace";
+}
+
+TEST(MimeType, ImproperlyQuotedParameter1)
+{
+ const auto in = u"x/x;test=\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x;test=\"\""))
+ << "Improperly-quoted parameter is handled properly #1";
+}
+
+TEST(MimeType, ImproperlyQuotedParameter2)
+{
+ const auto in = u"x/x;test=\"\\"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x;test=\"\\\\\""))
+ << "Improperly-quoted parameter is handled properly #2";
+}
+
+TEST(MimeType, NonLatin1ParameterIgnored)
+{
+ const auto in = u"x/x;test=\xFFFD;x=x"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x;x=x"))
+ << "Non latin-1 parameters are ignored";
+}
+
+TEST(MimeType, ParameterIgnoredIfWhitespaceInName1)
+{
+ const auto in = u"text/html;charset =gbk;charset=123"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=123"))
+ << "Parameter ignored if whitespace in name #1";
+}
+
+TEST(MimeType, ParameterIgnoredIfWhitespaceInName2)
+{
+ const auto in = u"text/html;cha rset =gbk;charset=123"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=123"))
+ << "Parameter ignored if whitespace in name #2";
+}
+
+TEST(MimeType, WhitespaceTrimmed)
+{
+ const auto in = u"\n\r\t text/plain\n\r\t ;\n\r\t charset=123\n\r\t "_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/plain;charset=123"))
+ << "Whitespace appropriately ignored";
+}
+
+TEST(MimeType, WhitespaceOnlyParameterIgnored)
+{
+ const auto in = u"x/x;x= \r\n\t"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x"))
+ << "Whitespace-only parameter is ignored";
+}
+
+TEST(MimeType, IncompleteParameterIgnored1)
+{
+ const auto in = u"x/x;test"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x"))
+ << "Incomplete parameter is ignored #1";
+}
+
+TEST(MimeType, IncompleteParameterIgnored2)
+{
+ const auto in = u"x/x;test="_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x"))
+ << "Incomplete parameter is ignored #2";
+}
+
+TEST(MimeType, IncompleteParameterIgnored3)
+{
+ const auto in = u"x/x;test= \r\n\t"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("x/x"))
+ << "Incomplete parameter is ignored #3";
+}
+
+TEST(MimeType, IncompleteParameterIgnored4)
+{
+ const auto in = u"text/html;test;charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Incomplete parameter is ignored #4";
+}
+
+TEST(MimeType, IncompleteParameterIgnored5)
+{
+ const auto in = u"text/html;test=;charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Incomplete parameter is ignored #5";
+}
+
+TEST(MimeType, EmptyParameterIgnored1)
+{
+ const auto in = u"text/html ; ; charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Empty parameter ignored #1";
+}
+
+TEST(MimeType, EmptyParameterIgnored2)
+{
+ const auto in = u"text/html;;;;charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Empty parameter ignored #2";
+}
+
+TEST(MimeType, InvalidParameterIgnored1)
+{
+ const auto in = u"text/html;';charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Invalid parameter ignored #1";
+}
+
+TEST(MimeType, InvalidParameterIgnored2)
+{
+ const auto in = u"text/html;\";charset=gbk;=123; =321"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Invalid parameter ignored #2";
+}
+
+TEST(MimeType, InvalidParameterIgnored3)
+{
+ const auto in = u"text/html;charset= \"\u007F;charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=GBK"))
+ << "Invalid parameter ignored #3";
+}
+
+TEST(MimeType, InvalidParameterIgnored4)
+{
+ const auto in = nsLiteralString(
+ u"text/html;charset=\"\u007F;charset=foo\";charset=GBK;charset=");
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=GBK"))
+ << "Invalid parameter ignored #4";
+}
+
+TEST(MimeType, SingleQuotes1)
+{
+ const auto in = u"text/html;charset='gbk'"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset='gbk'"))
+ << "Single quotes handled properly #1";
+}
+
+TEST(MimeType, SingleQuotes2)
+{
+ const auto in = u"text/html;charset='gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset='gbk"))
+ << "Single quotes handled properly #2";
+}
+
+TEST(MimeType, SingleQuotes3)
+{
+ const auto in = u"text/html;charset=gbk'"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk'"))
+ << "Single quotes handled properly #3";
+}
+
+TEST(MimeType, SingleQuotes4)
+{
+ const auto in = u"text/html;charset=';charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset='"))
+ << "Single quotes handled properly #4";
+}
+
+TEST(MimeType, SingleQuotes5)
+{
+ const auto in = u"text/html;charset=''';charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset='''"))
+ << "Single quotes handled properly #5";
+}
+
+TEST(MimeType, DoubleQuotes1)
+{
+ const auto in = u"text/html;charset=\"gbk\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Double quotes handled properly #1";
+}
+
+TEST(MimeType, DoubleQuotes2)
+{
+ const auto in = u"text/html;charset=\"gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Double quotes handled properly #2";
+}
+
+TEST(MimeType, DoubleQuotes3)
+{
+ const auto in = u"text/html;charset=gbk\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk\\\"\""))
+ << "Double quotes handled properly #3";
+}
+
+TEST(MimeType, DoubleQuotes4)
+{
+ const auto in = u"text/html;charset=\" gbk\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" gbk\""))
+ << "Double quotes handled properly #4";
+}
+
+TEST(MimeType, DoubleQuotes5)
+{
+ const auto in = u"text/html;charset=\"gbk \""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk \""))
+ << "Double quotes handled properly #5";
+}
+
+TEST(MimeType, DoubleQuotes6)
+{
+ const auto in = u"text/html;charset=\"\\ gbk\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" gbk\""))
+ << "Double quotes handled properly #6";
+}
+
+TEST(MimeType, DoubleQuotes7)
+{
+ const auto in = u"text/html;charset=\"\\g\\b\\k\""_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Double quotes handled properly #7";
+}
+
+TEST(MimeType, DoubleQuotes8)
+{
+ const auto in = u"text/html;charset=\"gbk\"x"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk"))
+ << "Double quotes handled properly #8";
+}
+
+TEST(MimeType, DoubleQuotes9)
+{
+ const auto in = u"text/html;charset=\"\";charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"\""))
+ << "Double quotes handled properly #9";
+}
+
+TEST(MimeType, DoubleQuotes10)
+{
+ const auto in = u"text/html;charset=\";charset=GBK"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\";charset=GBK\""))
+ << "Double quotes handled properly #10";
+}
+
+TEST(MimeType, UnexpectedCodePoints)
+{
+ const auto in = u"text/html;charset={gbk}"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"{gbk}\""))
+ << "Unexpected code points handled properly";
+}
+
+TEST(MimeType, LongTypesSubtypesAccepted)
+{
+ const auto in = nsLiteralString(
+ u"01234567890123456789012345678901234567890123456789012345678901234567890"
+ u"1"
+ "2345678901234567890123456789012345678901234567890123456789/"
+ "012345678901234567890123456789012345678901234567890123456789012345678901"
+ "2345678901234567890123456789012345678901234567890123456789");
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(in))
+ << "Long type/subtype accepted";
+}
+
+TEST(MimeType, LongParametersAccepted)
+{
+ const auto in = nsLiteralString(
+ u"text/"
+ "html;"
+ "012345678901234567890123456789012345678901234567890123456789012345678901"
+ "2345678901234567890123456789012345678901234567890123456789=x;charset="
+ "gbk");
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(in))
+ << "Long parameters accepted";
+}
+
+TEST(MimeType, AllValidCharactersAccepted1)
+{
+ const auto in = nsLiteralString(
+ u"x/x;x=\"\t "
+ u"!\\\"#$%&'()*+,-./"
+ u"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`"
+ u"abcdefghijklmnopqrstuvwxyz{|}~"
+ u"\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A"
+ u"\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095"
+ u"\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0"
+ u"\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB"
+ u"\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6"
+ u"\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1"
+ u"\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC"
+ u"\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7"
+ u"\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2"
+ u"\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED"
+ u"\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8"
+ u"\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\"");
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.Equals(in))
+ << "All valid characters accepted #1";
+}
+
+TEST(MimeType, CaseNormalization1)
+{
+ const auto in = u"TEXT/PLAIN;CHARSET=TEST"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/plain;charset=TEST"))
+ << "Case normalized properly #1";
+}
+
+TEST(MimeType, CaseNormalization2)
+{
+ const auto in = nsLiteralString(
+ u"!#$%&'*+-.^_`|~"
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/"
+ "!#$%&'*+-.^_`|~"
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-"
+ ".^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$"
+ "%&'*+-.^_`|~"
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral(
+ "!#$%&'*+-.^_`|~"
+ "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/"
+ "!#$%&'*+-.^_`|~"
+ "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-"
+ ".^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$"
+ "%&'*+-.^_`|~"
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"))
+ << "Case normalized properly #2";
+}
+
+TEST(MimeType, LegacyCommentSyntax1)
+{
+ const auto in = u"text/html;charset=gbk("_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk(\""))
+ << "Legacy comment syntax #1";
+}
+
+TEST(MimeType, LegacyCommentSyntax2)
+{
+ const auto in = u"text/html;x=(;charset=gbk"_ns;
+ UniquePtr<MimeType> parsed = MimeType::Parse(in);
+ ASSERT_TRUE(parsed)
+ << "Parsing succeeded";
+ nsAutoString out;
+ parsed->Serialize(out);
+ ASSERT_TRUE(out.EqualsLiteral("text/html;x=\"(\";charset=gbk"))
+ << "Legacy comment syntax #2";
+}
diff --git a/dom/base/test/gtest/TestParser.cpp b/dom/base/test/gtest/TestParser.cpp
new file mode 100644
index 0000000000..d9240cced7
--- /dev/null
+++ b/dom/base/test/gtest/TestParser.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/dom/DOMParser.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocumentEncoder.h"
+#include "mozilla/ErrorResult.h"
+
+// This is a test for mozilla::dom::DOMParser::CreateWithoutGlobal() which was
+// implemented for use in Thunderbird's MailNews module.
+
+// int main(int argc, char** argv)
+TEST(TestParser, TestParserMain)
+{
+ bool allTestsPassed = false;
+ constexpr auto htmlInput =
+ u"<html><head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=\">"
+ "</head><body>Hello <b>Thunderbird!</b></body></html>"_ns;
+
+ do {
+ // Parse the HTML source.
+ mozilla::IgnoredErrorResult rv2;
+ RefPtr<mozilla::dom::DOMParser> parser =
+ mozilla::dom::DOMParser::CreateWithoutGlobal(rv2);
+ if (rv2.Failed()) break;
+ nsCOMPtr<mozilla::dom::Document> document = parser->ParseFromString(
+ htmlInput, mozilla::dom::SupportedType::Text_html, rv2);
+ if (rv2.Failed()) break;
+
+ // Serialize it back to HTML source again.
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_createDocumentEncoder("text/html");
+ if (!encoder) break;
+ nsresult rv =
+ encoder->Init(document, u"text/html"_ns, nsIDocumentEncoder::OutputRaw);
+ if (NS_FAILED(rv)) break;
+ nsString parsed;
+ rv = encoder->EncodeToString(parsed);
+ if (NS_FAILED(rv)) break;
+
+ EXPECT_TRUE(parsed.Equals(htmlInput));
+ allTestsPassed = true;
+ } while (false);
+
+ EXPECT_TRUE(allTestsPassed);
+}
diff --git a/dom/base/test/gtest/TestPlainTextSerializer.cpp b/dom/base/test/gtest/TestPlainTextSerializer.cpp
new file mode 100644
index 0000000000..10a69fd318
--- /dev/null
+++ b/dom/base/test/gtest/TestPlainTextSerializer.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsIDocumentEncoder.h"
+#include "nsCRT.h"
+#include "nsIParserUtils.h"
+
+const uint32_t kDefaultWrapColumn = 72;
+
+void ConvertBufToPlainText(nsString& aConBuf, int aFlag, uint32_t aWrapColumn) {
+ nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
+ utils->ConvertToPlainText(aConBuf, aFlag, aWrapColumn, aConBuf);
+}
+
+// Test for ASCII with format=flowed; delsp=yes
+TEST(PlainTextSerializer, ASCIIWithFlowedDelSp)
+{
+ nsString test;
+ nsString result;
+
+ test.AssignLiteral(
+ "<html><body>"
+ "Firefox Firefox Firefox Firefox "
+ "Firefox Firefox Firefox Firefox "
+ "Firefox Firefox Firefox Firefox"
+ "</body></html>");
+
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputFormatFlowed |
+ nsIDocumentEncoder::OutputFormatDelSp,
+ kDefaultWrapColumn);
+
+ // create result case
+ result.AssignLiteral(
+ "Firefox Firefox Firefox Firefox "
+ "Firefox Firefox Firefox Firefox "
+ "Firefox \r\nFirefox Firefox Firefox\r\n");
+
+ ASSERT_TRUE(test.Equals(result))
+ << "Wrong HTML to ASCII text serialization with format=flowed; delsp=yes";
+}
+
+// Test for CJK with format=flowed; delsp=yes
+TEST(PlainTextSerializer, CJKWithFlowedDelSp)
+{
+ nsString test;
+ nsString result;
+
+ test.AssignLiteral("<html><body>");
+ for (uint32_t i = 0; i < 40; i++) {
+ // Insert Kanji (U+5341)
+ test.Append(0x5341);
+ }
+ test.AppendLiteral("</body></html>");
+
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputFormatFlowed |
+ nsIDocumentEncoder::OutputFormatDelSp,
+ kDefaultWrapColumn);
+
+ // create result case
+ for (uint32_t i = 0; i < 36; i++) {
+ result.Append(0x5341);
+ }
+ result.AppendLiteral(" \r\n");
+ for (uint32_t i = 0; i < 4; i++) {
+ result.Append(0x5341);
+ }
+ result.AppendLiteral("\r\n");
+
+ ASSERT_TRUE(test.Equals(result))
+ << "Wrong HTML to CJK text serialization with format=flowed; delsp=yes";
+}
+
+// Test for CJK with DisallowLineBreaking
+TEST(PlainTextSerializer, CJKWithDisallowLineBreaking)
+{
+ nsString test;
+ nsString result;
+
+ test.AssignLiteral("<html><body>");
+ for (uint32_t i = 0; i < 400; i++) {
+ // Insert Kanji (U+5341)
+ test.Append(0x5341);
+ }
+ test.AppendLiteral("</body></html>");
+
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputFormatFlowed |
+ nsIDocumentEncoder::OutputDisallowLineBreaking,
+ kDefaultWrapColumn);
+
+ // create result case
+ for (uint32_t i = 0; i < 400; i++) {
+ result.Append(0x5341);
+ }
+ result.AppendLiteral("\r\n");
+
+ ASSERT_TRUE(test.Equals(result))
+ << "Wrong HTML to CJK text serialization with OutputDisallowLineBreaking";
+}
+
+// Test for Latin with DisallowLineBreaking
+TEST(PlainTextSerializer, LatinWithDisallowLineBreaking)
+{
+ nsString test;
+ test.AssignLiteral("<html><body>");
+ for (uint32_t i = 0; i < 400; i++) {
+ // Insert á (Latin Small Letter a with Acute) (U+00E1)
+ test.Append(0x00E1);
+ }
+ test.AppendLiteral("</body></html>\r\n");
+
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputFormatFlowed |
+ nsIDocumentEncoder::OutputDisallowLineBreaking,
+ kDefaultWrapColumn);
+
+ // Create expect case.
+ nsString expect;
+ for (uint32_t i = 0; i < 400; i++) {
+ expect.Append(0x00E1);
+ }
+ expect.AppendLiteral(" \r\n\r\n");
+
+ ASSERT_TRUE(test.Equals(expect))
+ << "Wrong HTML to Latin text serialization with OutputDisallowLineBreaking";
+}
+
+// Test for ASCII with format=flowed; and quoted lines in preformatted span.
+TEST(PlainTextSerializer, PreformatFlowedQuotes)
+{
+ nsString test;
+ nsString result;
+
+ test.AssignLiteral(
+ "<html><body>"
+ "<span style=\"white-space: pre-wrap;\" _moz_quote=\"true\">"
+ "&gt; Firefox Firefox Firefox Firefox <br>"
+ "&gt; Firefox Firefox Firefox Firefox<br>"
+ "&gt;<br>"
+ "&gt;&gt; Firefox Firefox Firefox Firefox <br>"
+ "&gt;&gt; Firefox Firefox Firefox Firefox<br>"
+ "</span></body></html>");
+
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputFormatFlowed,
+ kDefaultWrapColumn);
+
+ // create result case
+ result.AssignLiteral(
+ "> Firefox Firefox Firefox Firefox \r\n"
+ "> Firefox Firefox Firefox Firefox\r\n"
+ ">\r\n"
+ ">> Firefox Firefox Firefox Firefox \r\n"
+ ">> Firefox Firefox Firefox Firefox\r\n");
+
+ ASSERT_TRUE(test.Equals(result))
+ << "Wrong HTML to ASCII text serialization "
+ "with format=flowed; and quoted "
+ "lines";
+}
+
+TEST(PlainTextSerializer, PrettyPrintedHtml)
+{
+ nsString test;
+ test.AppendLiteral("<html>" NS_LINEBREAK "<body>" NS_LINEBREAK
+ " first<br>" NS_LINEBREAK " second<br>" NS_LINEBREAK
+ "</body>" NS_LINEBREAK "</html>");
+
+ ConvertBufToPlainText(test, 0, kDefaultWrapColumn);
+ ASSERT_TRUE(test.EqualsLiteral("first" NS_LINEBREAK "second" NS_LINEBREAK))
+ << "Wrong prettyprinted html to text serialization";
+}
+
+TEST(PlainTextSerializer, PreElement)
+{
+ nsString test;
+ test.AppendLiteral("<html>" NS_LINEBREAK "<body>" NS_LINEBREAK
+ "<pre>" NS_LINEBREAK " first" NS_LINEBREAK
+ " second" NS_LINEBREAK "</pre>" NS_LINEBREAK
+ "</body>" NS_LINEBREAK "</html>");
+
+ ConvertBufToPlainText(test, 0, kDefaultWrapColumn);
+ ASSERT_TRUE(test.EqualsLiteral(" first" NS_LINEBREAK
+ " second" NS_LINEBREAK NS_LINEBREAK))
+ << "Wrong prettyprinted html to text serialization";
+}
+
+TEST(PlainTextSerializer, BlockElement)
+{
+ nsString test;
+ test.AppendLiteral("<html>" NS_LINEBREAK "<body>" NS_LINEBREAK
+ "<div>" NS_LINEBREAK " first" NS_LINEBREAK
+ "</div>" NS_LINEBREAK "<div>" NS_LINEBREAK
+ " second" NS_LINEBREAK "</div>" NS_LINEBREAK
+ "</body>" NS_LINEBREAK "</html>");
+
+ ConvertBufToPlainText(test, 0, kDefaultWrapColumn);
+ ASSERT_TRUE(test.EqualsLiteral("first" NS_LINEBREAK "second" NS_LINEBREAK))
+ << "Wrong prettyprinted html to text serialization";
+}
+
+TEST(PlainTextSerializer, PreWrapElementForThunderbird)
+{
+ // This test examines the magic pre-wrap setup that Thunderbird relies on.
+ nsString test;
+ test.AppendLiteral("<html>" NS_LINEBREAK
+ "<body style=\"white-space: pre-wrap;\">" NS_LINEBREAK
+ "<pre>" NS_LINEBREAK
+ " first line is too long" NS_LINEBREAK
+ " second line is even loooonger " NS_LINEBREAK
+ "</pre>" NS_LINEBREAK "</body>" NS_LINEBREAK "</html>");
+
+ const uint32_t wrapColumn = 10;
+ ConvertBufToPlainText(test, nsIDocumentEncoder::OutputWrap, wrapColumn);
+ // "\n\n first\nline is\ntoo long\n second\nline is\neven\nloooonger\n\n\n"
+ ASSERT_TRUE(test.EqualsLiteral(
+ NS_LINEBREAK NS_LINEBREAK
+ " first" NS_LINEBREAK "line is" NS_LINEBREAK "too long" NS_LINEBREAK
+ " second" NS_LINEBREAK "line is" NS_LINEBREAK "even" NS_LINEBREAK
+ "loooonger" NS_LINEBREAK NS_LINEBREAK NS_LINEBREAK))
+ << "Wrong prettyprinted html to text serialization";
+}
+
+TEST(PlainTextSerializer, Simple)
+{
+ nsString test;
+ test.AppendLiteral(
+ "<html><base>base</base><head><span>span</span></head>"
+ "<body>body</body></html>");
+ ConvertBufToPlainText(test, 0, kDefaultWrapColumn);
+ ASSERT_TRUE(test.EqualsLiteral("basespanbody"))
+ << "Wrong html to text serialization";
+}
+
+TEST(PlainTextSerializer, OneHundredAndOneOL)
+{
+ nsAutoString test;
+ test.AppendLiteral(
+ "<html>"
+ "<body>"
+ "<ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><"
+ "ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><"
+ "ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><"
+ "ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><"
+ "ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><"
+ "ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol><ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></ol></"
+ "ol></ol><li>X</li></ol>"
+ "</body>"
+ "</html>");
+
+ ConvertBufToPlainText(test, nsIDocumentEncoder::OutputFormatted,
+ kDefaultWrapColumn);
+
+ nsAutoString expected;
+ expected.AppendLiteral(" 1. X" NS_LINEBREAK);
+ ASSERT_EQ(test, expected);
+}
+
+TEST(PlainTextSerializer, BlockQuoteCite)
+{
+ nsAutoString test;
+ test.AppendLiteral(u"<blockquote type=cite>hello world</blockquote>");
+
+ const uint32_t wrapColumn = 10;
+ ConvertBufToPlainText(test,
+ nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputFormatFlowed |
+ nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak,
+ wrapColumn);
+
+ constexpr auto expect = NS_LITERAL_STRING_FROM_CSTRING(
+ "> hello \r\n"
+ "> world\r\n");
+
+ ASSERT_TRUE(test.Equals(expect))
+ << "Wrong blockquote cite to text serialization";
+}
diff --git a/dom/base/test/gtest/TestScheduler.cpp b/dom/base/test/gtest/TestScheduler.cpp
new file mode 100644
index 0000000000..00c578e556
--- /dev/null
+++ b/dom/base/test/gtest/TestScheduler.cpp
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/dom/CCGCScheduler.h"
+#include "mozilla/TimeStamp.h"
+
+// This is a test for mozilla::CCGCScheduler.
+
+using namespace mozilla;
+
+static TimeDuration kOneSecond = TimeDuration::FromSeconds(1);
+static TimeDuration kTenthSecond = TimeDuration::FromSeconds(0.1);
+static TimeDuration kFrameDuration = TimeDuration::FromSeconds(1.0 / 60.0);
+
+static mozilla::TimeStamp sNow = TimeStamp::Now();
+
+static mozilla::TimeStamp AdvanceTime(TimeDuration aDuration) {
+ sNow += aDuration;
+ return sNow;
+}
+
+static TimeStamp Now() { return sNow; }
+
+static uint32_t sSuspected = 0;
+
+static uint32_t SuspectedCCObjects() { return sSuspected; }
+static void SetNumSuspected(uint32_t n) { sSuspected = n; }
+static void SuspectMore(uint32_t n) { sSuspected += n; }
+
+using CCRunnerState = mozilla::CCGCScheduler::CCRunnerState;
+
+class TestGC {
+ protected:
+ CCGCScheduler& mScheduler;
+
+ public:
+ explicit TestGC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {}
+ void Run(int aNumSlices);
+};
+
+void TestGC::Run(int aNumSlices) {
+ // Make the purple buffer nearly empty so it is itself not an adequate reason
+ // for wanting a CC.
+ static_assert(3 < mozilla::kCCPurpleLimit);
+ SetNumSuspected(3);
+
+ // Running the GC should not influence whether a CC is currently seen as
+ // needed. But the first time we run GC, it will be false; later, we will
+ // have run a GC and set it to true.
+ CCReason neededCCAtStartOfGC =
+ mScheduler.IsCCNeeded(Now(), SuspectedCCObjects());
+
+ mScheduler.NoteGCBegin(JS::GCReason::API);
+
+ for (int slice = 0; slice < aNumSlices; slice++) {
+ EXPECT_TRUE(mScheduler.InIncrementalGC());
+ TimeStamp idleDeadline = Now() + kTenthSecond;
+ js::SliceBudget budget =
+ mScheduler.ComputeInterSliceGCBudget(idleDeadline, Now());
+ TimeDuration budgetDuration =
+ TimeDuration::FromMilliseconds(budget.timeBudget());
+ EXPECT_NEAR(budgetDuration.ToSeconds(), 0.1, 1.e-6);
+ // Pretend the GC took exactly the budget.
+ AdvanceTime(budgetDuration);
+
+ EXPECT_EQ(mScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ neededCCAtStartOfGC);
+
+ // Mutator runs for 1 second.
+ AdvanceTime(kOneSecond);
+ }
+
+ mScheduler.NoteGCEnd();
+ mScheduler.SetNeedsFullGC(false);
+}
+
+class TestCC {
+ protected:
+ CCGCScheduler& mScheduler;
+
+ public:
+ explicit TestCC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {}
+
+ void Run(int aNumSlices) {
+ Prepare();
+ MaybePokeCC();
+ TimerFires(aNumSlices);
+ EndCycleCollectionCallback();
+ KillCCRunner();
+ }
+
+ virtual void Prepare() = 0;
+ virtual void MaybePokeCC();
+ virtual void TimerFires(int aNumSlices);
+ virtual void RunSlices(int aNumSlices);
+ virtual void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
+ int aSliceNum, int aNumSlices) = 0;
+ virtual void ForgetSkippable();
+ virtual void EndCycleCollectionCallback();
+ virtual void KillCCRunner();
+};
+
+void TestCC::MaybePokeCC() {
+ // nsJSContext::MaybePokeCC
+
+ // In all tests so far, we will be running this just after a GC.
+ CCReason reason = mScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects());
+ EXPECT_EQ(reason, CCReason::GC_FINISHED);
+
+ mScheduler.InitCCRunnerStateMachine(CCRunnerState::ReducePurple, reason);
+ EXPECT_TRUE(mScheduler.IsEarlyForgetSkippable());
+}
+
+void TestCC::TimerFires(int aNumSlices) {
+ // Series of CCRunner timer fires.
+ CCRunnerStep step;
+
+ while (true) {
+ SuspectMore(1000);
+ TimeStamp idleDeadline = Now() + kOneSecond;
+ step =
+ mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ // Should first see a series of ForgetSkippable actions.
+ if (step.mAction != CCRunnerAction::ForgetSkippable ||
+ step.mParam.mRemoveChildless != KeepChildless) {
+ break;
+ }
+ EXPECT_EQ(step.mYield, Yield);
+ ForgetSkippable();
+ }
+
+ while (step.mYield == Continue) {
+ TimeStamp idleDeadline = Now() + kOneSecond;
+ step =
+ mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ }
+ EXPECT_EQ(step.mAction, CCRunnerAction::ForgetSkippable);
+ EXPECT_EQ(step.mParam.mRemoveChildless, RemoveChildless);
+ ForgetSkippable();
+
+ TimeStamp idleDeadline = Now() + kOneSecond;
+ step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CleanupContentUnbinder);
+ step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CleanupDeferred);
+
+ mScheduler.NoteCCBegin(CCReason::API, Now(), 0, sSuspected, 0);
+ RunSlices(aNumSlices);
+}
+
+void TestCC::ForgetSkippable() {
+ uint32_t suspectedBefore = sSuspected;
+ // ...ForgetSkippable would happen here...
+ js::SliceBudget budget =
+ mScheduler.ComputeForgetSkippableBudget(Now(), Now() + kTenthSecond);
+ EXPECT_NEAR(budget.timeBudget(), kTenthSecond.ToMilliseconds(), 1);
+ AdvanceTime(kTenthSecond);
+ mScheduler.NoteForgetSkippableComplete(Now(), suspectedBefore,
+ SuspectedCCObjects());
+}
+
+void TestCC::RunSlices(int aNumSlices) {
+ TimeStamp ccStartTime = Now();
+ TimeStamp prevSliceEnd = ccStartTime;
+ for (int ccslice = 0; ccslice < aNumSlices; ccslice++) {
+ RunSlice(ccStartTime, prevSliceEnd, ccslice, aNumSlices);
+ prevSliceEnd = Now();
+ }
+
+ SetNumSuspected(0);
+}
+
+void TestCC::EndCycleCollectionCallback() {
+ // nsJSContext::EndCycleCollectionCallback
+ CycleCollectorResults results;
+ results.mFreedGCed = 10;
+ results.mFreedJSZones = 2;
+ mScheduler.NoteCCEnd(results, Now(), TimeDuration());
+
+ // Because > 0 zones were freed.
+ EXPECT_TRUE(mScheduler.NeedsGCAfterCC());
+}
+
+void TestCC::KillCCRunner() {
+ // nsJSContext::KillCCRunner
+ mScheduler.KillCCRunner();
+}
+
+class TestIdleCC : public TestCC {
+ public:
+ explicit TestIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {}
+
+ void Prepare() override;
+ void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum,
+ int aNumSlices) override;
+};
+
+void TestIdleCC::Prepare() { EXPECT_TRUE(!mScheduler.InIncrementalGC()); }
+
+void TestIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
+ int aSliceNum, int aNumSlices) {
+ CCRunnerStep step;
+ TimeStamp idleDeadline = Now() + kTenthSecond;
+
+ // The scheduler should request a CycleCollect slice.
+ step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
+
+ // nsJSContext::RunCycleCollectorSlice
+
+ EXPECT_FALSE(mScheduler.InIncrementalGC());
+ bool preferShorter;
+ js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
+ idleDeadline, aCCStartTime, aPrevSliceEnd, Now(), &preferShorter);
+ // The scheduler will set the budget to our deadline (0.1sec in the future).
+ EXPECT_NEAR(budget.timeBudget(), kTenthSecond.ToMilliseconds(), 1);
+ EXPECT_FALSE(preferShorter);
+
+ AdvanceTime(kTenthSecond);
+}
+
+class TestNonIdleCC : public TestCC {
+ public:
+ explicit TestNonIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {}
+
+ void Prepare() override;
+ void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum,
+ int aNumSlices) override;
+};
+
+void TestNonIdleCC::Prepare() {
+ EXPECT_TRUE(!mScheduler.InIncrementalGC());
+
+ // Advance time by an hour to give time for a user event in the past.
+ AdvanceTime(TimeDuration::FromSeconds(3600));
+}
+
+void TestNonIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
+ int aSliceNum, int aNumSlices) {
+ CCRunnerStep step;
+ TimeStamp nullDeadline;
+
+ // The scheduler should tell us to run a slice of cycle collection.
+ step = mScheduler.AdvanceCCRunner(nullDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
+
+ // nsJSContext::RunCycleCollectorSlice
+
+ EXPECT_FALSE(mScheduler.InIncrementalGC());
+
+ bool preferShorter;
+ js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
+ nullDeadline, aCCStartTime, aPrevSliceEnd, Now(), &preferShorter);
+ if (aSliceNum == 0) {
+ // First slice of the CC, so always use the baseBudget which is
+ // kICCSliceBudget (3ms) for a non-idle slice.
+ EXPECT_NEAR(budget.timeBudget(), kICCSliceBudget.ToMilliseconds(), 0.1);
+ } else if (aSliceNum == 1) {
+ // Second slice still uses the baseBudget, since not much time has passed
+ // so none of the lengthening mechanisms have kicked in yet.
+ EXPECT_NEAR(budget.timeBudget(), kICCSliceBudget.ToMilliseconds(), 0.1);
+ } else if (aSliceNum == 2) {
+ // We're not overrunning kMaxICCDuration, so we don't go unlimited.
+ EXPECT_FALSE(budget.isUnlimited());
+ // This slice is delayed by twice the allowed amount. Slice time should be
+ // doubled.
+ EXPECT_NEAR(budget.timeBudget(), kICCSliceBudget.ToMilliseconds() * 2, 0.1);
+ } else {
+ // We're not overrunning kMaxICCDuration, so we don't go unlimited.
+ EXPECT_FALSE(budget.isUnlimited());
+
+ // These slices are not delayed, but enough time has passed that the
+ // dominating factor is now the linear ramp up to max slice time at the
+ // halfway point to kMaxICCDuration.
+ EXPECT_TRUE(budget.timeBudget() > kICCSliceBudget.ToMilliseconds());
+ EXPECT_TRUE(budget.timeBudget() <=
+ MainThreadIdlePeriod::GetLongIdlePeriod());
+ }
+ EXPECT_TRUE(preferShorter); // Non-idle prefers shorter slices
+
+ AdvanceTime(TimeDuration::FromMilliseconds(budget.timeBudget()));
+ if (aSliceNum == 1) {
+ // Delay the third slice (only).
+ AdvanceTime(kICCIntersliceDelay * 2);
+ }
+}
+
+// Do a GC then CC then GC.
+static bool BasicScenario(CCGCScheduler& aScheduler, TestGC* aTestGC,
+ TestCC* aTestCC) {
+ // Run a 10-slice incremental GC.
+ aTestGC->Run(10);
+
+ // After a GC, the scheduler should decide to do a full CC regardless of the
+ // number of purple buffer entries.
+ SetNumSuspected(3);
+ EXPECT_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ CCReason::GC_FINISHED);
+
+ // Now we should want to CC.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::GC_FINISHED);
+
+ // Do a 5-slice CC.
+ aTestCC->Run(5);
+
+ // Not enough suspected objects to deserve a CC.
+ EXPECT_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ SetNumSuspected(10000);
+
+ // We shouldn't want to CC again yet, it's too soon.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ AdvanceTime(mozilla::kCCDelay);
+
+ // *Now* it's time for another CC.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::MANY_SUSPECTED);
+
+ // Run a 3-slice incremental GC.
+ EXPECT_TRUE(!aScheduler.InIncrementalGC());
+ aTestGC->Run(3);
+
+ return true;
+}
+
+static CCGCScheduler scheduler;
+static TestGC gc(scheduler);
+static TestIdleCC ccIdle(scheduler);
+static TestNonIdleCC ccNonIdle(scheduler);
+
+TEST(TestScheduler, Idle)
+{
+ // Cannot CC until we GC once.
+ EXPECT_EQ(scheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+
+ EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccIdle));
+}
+
+TEST(TestScheduler, NonIdle)
+{ EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccNonIdle)); }
diff --git a/dom/base/test/gtest/TestXMLSerializerNoBreakLink.cpp b/dom/base/test/gtest/TestXMLSerializerNoBreakLink.cpp
new file mode 100644
index 0000000000..a63410303e
--- /dev/null
+++ b/dom/base/test/gtest/TestXMLSerializerNoBreakLink.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsCOMPtr.h"
+#include "nsIDocumentEncoder.h"
+#include "nsString.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DOMParser.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Test that serialising some DOM doesn't destroy links by word-wrapping long
+// href values containing spaces.
+TEST(TestXMLSerializerNoBreakLink, TestXMLSerializerNoBreakLinkMain)
+{
+ // Build up a stupidly-long URL with spaces. Default is to wrap at column
+ // 72, so we want to exceed that.
+ nsString longURL = u"http://www.example.com/link with spaces"_ns;
+ for (int i = 1; i < 125; ++i) {
+ longURL.Append(u' ');
+ longURL.Append(IntToTString<char16_t>(i));
+ }
+ nsString htmlInput =
+ u"<html><head>"
+ "<meta charset=\"utf-8\">"
+ "</head><body>Hello Thunderbird! <a href=\""_ns +
+ longURL + u"\">Link</a></body></html>"_ns;
+
+ // Parse HTML into a Document.
+ nsCOMPtr<Document> document;
+ {
+ IgnoredErrorResult rv;
+ RefPtr<DOMParser> parser = DOMParser::CreateWithoutGlobal(rv);
+ ASSERT_FALSE(rv.Failed());
+ document = parser->ParseFromString(htmlInput, SupportedType::Text_html, rv);
+ ASSERT_FALSE(rv.Failed());
+ }
+
+ // Serialize back in a variety of flavours and check the URL survives the
+ // round trip intact.
+ nsCString contentTypes[] = {"text/xml"_ns, "application/xml"_ns,
+ "application/xhtml+xml"_ns, "image/svg+xml"_ns,
+ "text/html"_ns};
+ for (auto const& contentType : contentTypes) {
+ uint32_t flagsToTest[] = {
+ nsIDocumentEncoder::OutputFormatted, nsIDocumentEncoder::OutputWrap,
+ nsIDocumentEncoder::OutputFormatted | nsIDocumentEncoder::OutputWrap};
+ for (uint32_t flags : flagsToTest) {
+ // Serialize doc back to HTML source again.
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_createDocumentEncoder(contentType.get());
+ ASSERT_TRUE(encoder);
+ nsresult rv =
+ encoder->Init(document, NS_ConvertASCIItoUTF16(contentType), flags);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ nsString parsed;
+ rv = encoder->EncodeToString(parsed);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // URL is intact?
+ EXPECT_TRUE(parsed.Find(longURL) != kNotFound);
+ }
+ }
+}
diff --git a/dom/base/test/gtest/TestXPathGenerator.cpp b/dom/base/test/gtest/TestXPathGenerator.cpp
new file mode 100644
index 0000000000..c9f4993179
--- /dev/null
+++ b/dom/base/test/gtest/TestXPathGenerator.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "XPathGenerator.h"
+#include "nsString.h"
+
+TEST(TestXPathGenerator, TestQuoteArgumentWithoutQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"testing");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"\'testing\'");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator, TestQuoteArgumentWithSingleQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"\'testing\'");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"\"\'testing\'\"");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator, TestQuoteArgumentWithDoubleQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"\"testing\"");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"\'\"testing\"\'");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator, TestQuoteArgumentWithSingleAndDoubleQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"\'testing\"");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"concat(\'\',\"\'\",\'testing\"\')");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+ printf("Result: %s\nExpected: %s\n", NS_ConvertUTF16toUTF8(result).get(),
+ NS_ConvertUTF16toUTF8(expectedResult).get());
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator,
+ TestQuoteArgumentWithDoubleQuoteAndASequenceOfSingleQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"\'\'\'\'testing\"");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"concat(\'\',\"\'\'\'\'\",\'testing\"\')");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+ printf("Result: %s\nExpected: %s\n", NS_ConvertUTF16toUTF8(result).get(),
+ NS_ConvertUTF16toUTF8(expectedResult).get());
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator,
+ TestQuoteArgumentWithDoubleQuoteAndTwoSequencesOfSingleQuote)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"\'\'\'\'testing\'\'\'\'\'\'\"");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(
+ u"concat(\'\',\"\'\'\'\'\",\'testing\',\"\'\'\'\'\'\'\",\'\"\')");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+ printf("Result: %s\nExpected: %s\n", NS_ConvertUTF16toUTF8(result).get(),
+ NS_ConvertUTF16toUTF8(expectedResult).get());
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator,
+ TestQuoteArgumentWithDoubleQuoteAndTwoSequencesOfSingleQuoteInMiddle)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"t\'\'\'\'estin\'\'\'\'\'\'\"g");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(
+ u"concat(\'t\',\"\'\'\'\'\",\'estin\',\"\'\'\'\'\'\'\",\'\"g\')");
+
+ nsAutoString result;
+ XPathGenerator::QuoteArgument(arg, result);
+ printf("Result: %s\nExpected: %s\n", NS_ConvertUTF16toUTF8(result).get(),
+ NS_ConvertUTF16toUTF8(expectedResult).get());
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator, TestEscapeNameWithNormalCharacters)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"testing");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"testing");
+
+ nsAutoString result;
+ XPathGenerator::EscapeName(arg, result);
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
+
+TEST(TestXPathGenerator, TestEscapeNameWithSpecialCharacters)
+{
+ nsAutoString arg;
+ arg.AssignLiteral(u"^testing!");
+
+ nsAutoString expectedResult;
+ expectedResult.AssignLiteral(u"*[local-name()=\'^testing!\']");
+
+ nsAutoString result;
+ XPathGenerator::EscapeName(arg, result);
+ printf("Result: %s\nExpected: %s\n", NS_ConvertUTF16toUTF8(result).get(),
+ NS_ConvertUTF16toUTF8(expectedResult).get());
+
+ ASSERT_TRUE(expectedResult.Equals(result));
+}
diff --git a/dom/base/test/gtest/moz.build b/dom/base/test/gtest/moz.build
new file mode 100644
index 0000000000..9a767eb2ff
--- /dev/null
+++ b/dom/base/test/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestContentUtils.cpp",
+ "TestMimeType.cpp",
+ "TestParser.cpp",
+ "TestPlainTextSerializer.cpp",
+ "TestScheduler.cpp",
+ "TestXMLSerializerNoBreakLink.cpp",
+ "TestXPathGenerator.cpp",
+]
+
+LOCAL_INCLUDES += ["/dom/base"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/base/test/head.js b/dom/base/test/head.js
new file mode 100644
index 0000000000..2b39f7a7b1
--- /dev/null
+++ b/dom/base/test/head.js
@@ -0,0 +1,15 @@
+async function newFocusedWindow(trigger) {
+ let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
+ let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
+
+ await trigger();
+
+ let win = await winPromise;
+ // New windows get focused after the first paint, see bug 1262946
+ await BrowserTestUtils.waitForContentEvent(
+ win.gBrowser.selectedBrowser,
+ "MozAfterPaint"
+ );
+ await delayedStartupPromise;
+ return win;
+}
diff --git a/dom/base/test/iframe1_bug1640766.html b/dom/base/test/iframe1_bug1640766.html
new file mode 100644
index 0000000000..51da4f22f0
--- /dev/null
+++ b/dom/base/test/iframe1_bug1640766.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Iframe 1 for Bug 1640766</title>
+</head>
+<body>
+<div>Iframe 1</div>
+<script type="application/javascript">
+if (parent == window) {
+ let iframe = document.createElement("iframe");
+ iframe.src = "http://example.org/tests/dom/base/test/iframe2_bug1640766.html";
+ document.body.appendChild(iframe);
+} else {
+ window.onload = function() {
+ top.opener.postMessage("ready", "*");
+ };
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/iframe1_bug426646.html b/dom/base/test/iframe1_bug426646.html
new file mode 100644
index 0000000000..533e77ad4c
--- /dev/null
+++ b/dom/base/test/iframe1_bug426646.html
@@ -0,0 +1 @@
+<html><meta charset="utf-8">1st page</html>
diff --git a/dom/base/test/iframe1_bug431701.html b/dom/base/test/iframe1_bug431701.html
new file mode 100644
index 0000000000..18ecdcb795
--- /dev/null
+++ b/dom/base/test/iframe1_bug431701.html
@@ -0,0 +1 @@
+<html></html>
diff --git a/dom/base/test/iframe2_bug1640766.html b/dom/base/test/iframe2_bug1640766.html
new file mode 100644
index 0000000000..6a5ca30796
--- /dev/null
+++ b/dom/base/test/iframe2_bug1640766.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Iframe 2 for Bug 1640766</title>
+</head>
+<body>
+<div>Iframe 2</div>
+<iframe src="http://mochi.test:8888/tests/dom/base/test/iframe1_bug1640766.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/iframe2_bug426646.html b/dom/base/test/iframe2_bug426646.html
new file mode 100644
index 0000000000..45e85c0f13
--- /dev/null
+++ b/dom/base/test/iframe2_bug426646.html
@@ -0,0 +1 @@
+<html><meta charset="utf-8">2nd page</html>
diff --git a/dom/base/test/iframe2_bug431701.html b/dom/base/test/iframe2_bug431701.html
new file mode 100644
index 0000000000..6c963c5455
--- /dev/null
+++ b/dom/base/test/iframe2_bug431701.html
@@ -0,0 +1 @@
+<html><meta charset="UTF-8"></html>
diff --git a/dom/base/test/iframe3_bug431701.html b/dom/base/test/iframe3_bug431701.html
new file mode 100644
index 0000000000..c0aac38766
--- /dev/null
+++ b/dom/base/test/iframe3_bug431701.html
@@ -0,0 +1 @@
+<html><meta charset="ISO-8859-1"></html>
diff --git a/dom/base/test/iframe4_bug431701.xml b/dom/base/test/iframe4_bug431701.xml
new file mode 100644
index 0000000000..18ecdcb795
--- /dev/null
+++ b/dom/base/test/iframe4_bug431701.xml
@@ -0,0 +1 @@
+<html></html>
diff --git a/dom/base/test/iframe5_bug431701.xml b/dom/base/test/iframe5_bug431701.xml
new file mode 100644
index 0000000000..d761751ee1
--- /dev/null
+++ b/dom/base/test/iframe5_bug431701.xml
@@ -0,0 +1 @@
+<?xml version='1.0'?><html></html>
diff --git a/dom/base/test/iframe6_bug431701.xml b/dom/base/test/iframe6_bug431701.xml
new file mode 100644
index 0000000000..17704b893a
--- /dev/null
+++ b/dom/base/test/iframe6_bug431701.xml
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='UTF-8'?><html></html>
diff --git a/dom/base/test/iframe7_bug431701.xml b/dom/base/test/iframe7_bug431701.xml
new file mode 100644
index 0000000000..73757924a7
--- /dev/null
+++ b/dom/base/test/iframe7_bug431701.xml
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='ISO-8859-1'?><html></html>
diff --git a/dom/base/test/iframe_bug962251.html b/dom/base/test/iframe_bug962251.html
new file mode 100644
index 0000000000..2fa3a50dcf
--- /dev/null
+++ b/dom/base/test/iframe_bug962251.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<input id="textinput" type="text"></input>
+<input id="textinput1" type="text"></input>
+</body>
+<script>
+ var SimpleTest = parent.SimpleTest;
+
+ var ok = SimpleTest.ok;
+ var info = SimpleTest.info;
+ var is = SimpleTest.is;
+ var finish = SimpleTest.finish.bind(SimpleTest);
+
+ var input = document.getElementById("textinput");
+ input.addEventListener("focus", function(aEvent) {
+ is(aEvent.target.id, "textinput", "Input should be focused.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null.");
+ parent.postMessage("runNextTest", "*");
+ }, {once: true});
+ input.focus();
+</script>
+</html> \ No newline at end of file
diff --git a/dom/base/test/iframe_bug976673.html b/dom/base/test/iframe_bug976673.html
new file mode 100644
index 0000000000..92c5bf76d0
--- /dev/null
+++ b/dom/base/test/iframe_bug976673.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 976673</title>
+</head>
+<body>
+ <input id="input" onfocus="event.target.value = event.type;"
+ onblur="event.target.value = event.type;">
+ <script>
+ var input = document.getElementById("input");
+ window.addEventListener("message", function (aEvent) {
+ switch (aEvent.data) {
+ case "init":
+ input.blur();
+ input.value = "";
+ input.focus();
+ // fall through
+ case "check":
+ aEvent.source.postMessage("input-value: " + input.value, "*");
+ break;
+ }
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/iframe_main_bug1022229.html b/dom/base/test/iframe_main_bug1022229.html
new file mode 100644
index 0000000000..c89a8c9a9d
--- /dev/null
+++ b/dom/base/test/iframe_main_bug1022229.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+ var SimpleTest = parent.SimpleTest;
+
+ var ok = SimpleTest.ok;
+ var info = SimpleTest.info;
+ var finish = SimpleTest.finish.bind(SimpleTest);
+
+ var gotTargetedMessage = false;
+ window.onmessage = function(evt) {
+ var message = evt.data;
+ info("Received message: " + message);
+ switch (message) {
+ case 'targeted':
+ gotTargetedMessage = true;
+ break;
+ case 'broadcast':
+ ok(gotTargetedMessage, "Should have received targeted message");
+ finish();
+ break;
+ default:
+ ok(false, "Unexpected message: " + message);
+ break;
+ }
+ }
+</script>
+</head>
+<body>
+<iframe src="iframe_sandbox_bug1022229.html" sandbox="allow-scripts"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/iframe_meta_refresh.sjs b/dom/base/test/iframe_meta_refresh.sjs
new file mode 100644
index 0000000000..0d881be72b
--- /dev/null
+++ b/dom/base/test/iframe_meta_refresh.sjs
@@ -0,0 +1,92 @@
+/*
+ * Test server for iframe refresh from meta http-equiv
+ */
+
+const SHARED_KEY = "iframe_meta_refresh";
+const DEFAULT_STATE = { count: 0, referrers: [] };
+const REFRESH_PAGE =
+ "http://example.com/tests/dom/base/test/iframe_meta_refresh.sjs?action=test";
+
+function createContent(refresh) {
+ let metaRefresh = "";
+ let scriptMessage = "";
+
+ if (refresh) {
+ metaRefresh = `<meta http-equiv="refresh" content="0;URL=${REFRESH_PAGE}">`;
+ } else {
+ scriptMessage = `
+ <script>
+ window.addEventListener("load", function() {
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");
+ }, false);
+ </script>`;
+ }
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ ${metaRefresh}
+ <title> Test referrer of meta http-equiv refresh</title>
+ </head>
+ <body>
+ ${scriptMessage}
+ </body>
+ </html>`;
+}
+
+function handleRequest(request, response) {
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let action = query.get("action");
+
+ var referrerLevel = "none";
+ if (request.hasHeader("Referer")) {
+ let referrer = request.getHeader("Referer");
+ if (referrer.indexOf("test_meta_refresh_referrer") > 0) {
+ referrerLevel = "full";
+ } else if (referrer == "http://mochi.test:8888/") {
+ referrerLevel = "origin";
+ }
+ }
+
+ var state = getSharedState(SHARED_KEY);
+ if (state === "") {
+ state = DEFAULT_STATE;
+ } else {
+ state = JSON.parse(state);
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ //avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if (action === "results") {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(JSON.stringify(state));
+ return;
+ }
+
+ if (action === "reset") {
+ //reset server state
+ setSharedState(SHARED_KEY, JSON.stringify(DEFAULT_STATE));
+ response.write("");
+ return;
+ }
+
+ if (action === "test") {
+ let load = query.get("load");
+ state.count++;
+ if (state.referrers.indexOf(referrerLevel) < 0) {
+ state.referrers.push(referrerLevel);
+ }
+
+ // Write frame content
+ response.write(createContent(load));
+ }
+
+ setSharedState(SHARED_KEY, JSON.stringify(state));
+ return;
+}
diff --git a/dom/base/test/iframe_postMessage_solidus.html b/dom/base/test/iframe_postMessage_solidus.html
new file mode 100644
index 0000000000..86f12367ec
--- /dev/null
+++ b/dom/base/test/iframe_postMessage_solidus.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ window.parent.postMessage(evt.data, '*');
+ }
+
+ </script>
+</body>
+</html>
+
+
diff --git a/dom/base/test/iframe_postMessages.html b/dom/base/test/iframe_postMessages.html
new file mode 100644
index 0000000000..0b07456eb8
--- /dev/null
+++ b/dom/base/test/iframe_postMessages.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+onmessage = function(e) {
+ parent.postMessage(e.data, '*', e.ports);
+}
+
+onmessageerror = function() {
+ parent.postMessage("error", '*');
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/iframe_sandbox_bug1022229.html b/dom/base/test/iframe_sandbox_bug1022229.html
new file mode 100644
index 0000000000..3d70e9d7af
--- /dev/null
+++ b/dom/base/test/iframe_sandbox_bug1022229.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+ // First send an origin-restricted message, and then send a non-restricted
+ // message to end the test promptly even in a failure mode.
+ parent.postMessage('targeted', 'http://mochi.test:8888');
+ setTimeout(function() { parent.postMessage('broadcast', '*'); }, 0);
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/iframe_shared_compartment2a.html b/dom/base/test/iframe_shared_compartment2a.html
new file mode 100644
index 0000000000..e2451367bb
--- /dev/null
+++ b/dom/base/test/iframe_shared_compartment2a.html
@@ -0,0 +1,2 @@
+<iframe src="http://mochi.test:8888/tests/dom/base/test/iframe_shared_compartment2b.html">
+</iframe>
diff --git a/dom/base/test/iframe_shared_compartment2b.html b/dom/base/test/iframe_shared_compartment2b.html
new file mode 100644
index 0000000000..64d57320f8
--- /dev/null
+++ b/dom/base/test/iframe_shared_compartment2b.html
@@ -0,0 +1,3 @@
+<script>
+window.onload = () => window.parent.parent.go(this);
+</script>
diff --git a/dom/base/test/intersectionobserver_cross_domain_iframe.html b/dom/base/test/intersectionobserver_cross_domain_iframe.html
new file mode 100644
index 0000000000..750ccaf6f2
--- /dev/null
+++ b/dom/base/test/intersectionobserver_cross_domain_iframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+#target5 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 20px;
+ height: 20px;
+ background: #f00;
+}
+</style>
+<body>
+<div id="target5"></div>
+<script>
+ var io = new IntersectionObserver(function (records) {
+ console.log(records[0].rootBounds, location.href);
+ window.parent.postMessage(records[0].rootBounds == null, 'http://mochi.test:8888');
+ }, {});
+ io.observe(document.getElementById("target5"));
+</script>
+</body>
+</html>
diff --git a/dom/base/test/intersectionobserver_iframe.html b/dom/base/test/intersectionobserver_iframe.html
new file mode 100644
index 0000000000..dae0da82c9
--- /dev/null
+++ b/dom/base/test/intersectionobserver_iframe.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+#target5 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 20px;
+ height: 20px;
+ background: #f00;
+}
+</style>
+<body>
+<div id="target5"></div>
+</body>
+</html>
diff --git a/dom/base/test/intersectionobserver_window.html b/dom/base/test/intersectionobserver_window.html
new file mode 100644
index 0000000000..826b5064eb
--- /dev/null
+++ b/dom/base/test/intersectionobserver_window.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+#target5 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 20px;
+ height: 20px;
+ background: #f00;
+}
+</style>
+<body>
+<div id="target"></div>
+<script>
+ var io = new IntersectionObserver(function(records) {
+ var viewportWidth =
+ document.documentElement.clientWidth || document.body.clientWidth;
+ var viewportHeight =
+ document.documentElement.clientHeight || document.body.clientHeight;
+ var result = records.length === 1 &&
+ records[0].rootBounds.top === 0 &&
+ records[0].rootBounds.left === 0 &&
+ records[0].rootBounds.right === viewportWidth &&
+ records[0].rootBounds.width === viewportWidth &&
+ records[0].rootBounds.bottom === viewportHeight &&
+ records[0].rootBounds.height === viewportHeight;
+ if (!result) {
+ result = [records.length,
+ records[0].isIntersecting,
+ records[0].rootBounds.top,
+ records[0].rootBounds.left,
+ records[0].rootBounds.right,
+ records[0].rootBounds.width,
+ records[0].rootBounds.bottom,
+ records[0].rootBounds.height,
+ viewportWidth,
+ viewportHeight].join(',');
+ }
+ window.opener.postMessage(result, '*');
+ });
+ io.observe(document.getElementById("target"));
+</script>
+</body>
+</html>
diff --git a/dom/base/test/invalid_accesscontrol.resource b/dom/base/test/invalid_accesscontrol.resource
new file mode 100644
index 0000000000..aca66f6f8d
--- /dev/null
+++ b/dom/base/test/invalid_accesscontrol.resource
@@ -0,0 +1,7 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+event: message
+data: 1
+
+
diff --git a/dom/base/test/invalid_accesscontrol.resource^headers^ b/dom/base/test/invalid_accesscontrol.resource^headers^
new file mode 100644
index 0000000000..d5bed552cb
--- /dev/null
+++ b/dom/base/test/invalid_accesscontrol.resource^headers^
@@ -0,0 +1,4 @@
+Access-Control-Allow-Origin: http://an.accesscrontrol.domain:80
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/jsmodules/.eslintrc.js b/dom/base/test/jsmodules/.eslintrc.js
new file mode 100644
index 0000000000..f811aa790b
--- /dev/null
+++ b/dom/base/test/jsmodules/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ parserOptions: {
+ sourceType: "module",
+ },
+};
diff --git a/dom/base/test/jsmodules/chrome.ini b/dom/base/test/jsmodules/chrome.ini
new file mode 100644
index 0000000000..2b75dab150
--- /dev/null
+++ b/dom/base/test/jsmodules/chrome.ini
@@ -0,0 +1,53 @@
+[DEFAULT]
+support-files =
+ module_setRan.js
+ module_testSyntax.js
+ module_badSyntax.js
+ module_simpleImport.js
+ module_simpleExport.js
+ module_badImport.js
+ module_simple1.js
+ module_simple2.js
+ module_simple3.js
+ module_cyclic1.js
+ module_cyclic2.js
+ module_cyclic3.js
+ module_multiImports.js
+ module_multiLargeImports.js
+ script_simple2.js
+ module_large1.js
+ module_large2.js
+ module_large3.js
+ module_extractIntroType.js
+ iframe_extractIntroType.html
+ module_missingImport.js
+
+[test_moduleScriptsRun.html]
+[test_moduleParsedAsModule.html]
+[test_scriptNotParsedAsModule.html]
+[test_typeAttrCaseInsensitive.html]
+[test_moduleNotFound.html]
+[test_import_meta_resolve.html]
+[test_importNotFound.html]
+[test_syntaxError.html]
+[test_syntaxErrorAsync.html]
+[test_syntaxErrorInline.html]
+[test_syntaxErrorInlineAsync.html]
+[test_simpleImport.html]
+[test_cyclicImport.html]
+[test_importResolveFailed.html]
+[test_multiTopLevelImports.html]
+[test_multiModuleImports.html]
+[test_multiAsyncImports.html]
+[test_scriptModuleOrder.html]
+[test_toplevelModuleMemoization.html]
+[test_importedModuleMemoization.html]
+[test_multiTopLevelLargeImports.html]
+[test_multiModuleLargeImports.html]
+[test_asyncInlineModules.html]
+[test_scriptInsertedModule.html]
+[test_linkErrorInCommon1.html]
+[test_linkErrorInCommon2.html]
+[test_topLevelIntroType.html]
+[test_importIntroType.html]
+[test_dynamicImportErrorMessage.html]
diff --git a/dom/base/test/jsmodules/iframe_extractIntroType.html b/dom/base/test/jsmodules/iframe_extractIntroType.html
new file mode 100644
index 0000000000..26c58aea6d
--- /dev/null
+++ b/dom/base/test/jsmodules/iframe_extractIntroType.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ // Hook up the debugger statement to extract the calling script's
+ // introductionType and set it in a property on the parent global.
+ const {addSandboxedDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+ addSandboxedDebuggerToGlobal(globalThis);
+ var dbg = new Debugger;
+ dbg.addDebuggee(parent);
+ dbg.onDebuggerStatement = function (frame) {
+ parent.introType = frame.script.source.introductionType;
+ }
+</script>
diff --git a/dom/base/test/jsmodules/importmaps/chrome.ini b/dom/base/test/jsmodules/importmaps/chrome.ini
new file mode 100644
index 0000000000..4fca7e97fa
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/chrome.ini
@@ -0,0 +1,29 @@
+[DEFAULT]
+support-files =
+ external_importMap.js
+ insert_a_base_element.js
+ module_simpleImportMap.js
+ module_simpleImportMap_dir.js
+ module_simpleImportMap_remap.js
+ module_simpleImportMap_remap_https.js
+ module_simpleExport.js
+ module_sortedImportMap.js
+ scope1/module_simpleExport.js
+ scope1/module_simpleImportMap.js
+ scope1/scope2/module_simpleExport.js
+ scope1/scope2/module_simpleImportMap.js
+prefs =
+ dom.importMaps.enabled=true
+
+[test_dynamic_import_reject_importMap.html]
+[test_externalImportMap.html]
+[test_import_meta_resolve_importMap.html]
+[test_inline_module_reject_importMap.html]
+[test_load_importMap_with_base.html]
+[test_load_importMap_with_base2.html]
+[test_module_script_reject_importMap.html]
+[test_parse_importMap_failed.html]
+[test_reject_multiple_importMaps.html]
+[test_simpleImportMap.html]
+[test_sortedImportMap.html]
+
diff --git a/dom/base/test/jsmodules/importmaps/external_importMap.js b/dom/base/test/jsmodules/importmaps/external_importMap.js
new file mode 100644
index 0000000000..e89d9f618f
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/external_importMap.js
@@ -0,0 +1,5 @@
+let imap = {
+ imports: {
+ foo: "./foo.js",
+ },
+};
diff --git a/dom/base/test/jsmodules/importmaps/insert_a_base_element.js b/dom/base/test/jsmodules/importmaps/insert_a_base_element.js
new file mode 100644
index 0000000000..435af97d1e
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/insert_a_base_element.js
@@ -0,0 +1,4 @@
+const el = document.createElement("base");
+el.href =
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/";
+document.currentScript.after(el);
diff --git a/dom/base/test/jsmodules/importmaps/module_simpleExport.js b/dom/base/test/jsmodules/importmaps/module_simpleExport.js
new file mode 100644
index 0000000000..9714d6d0ab
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_simpleExport.js
@@ -0,0 +1 @@
+export let x = 42;
diff --git a/dom/base/test/jsmodules/importmaps/module_simpleImportMap.js b/dom/base/test/jsmodules/importmaps/module_simpleImportMap.js
new file mode 100644
index 0000000000..153b84e6de
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_simpleImportMap.js
@@ -0,0 +1,2 @@
+import { x } from "simple";
+result = x;
diff --git a/dom/base/test/jsmodules/importmaps/module_simpleImportMap_dir.js b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_dir.js
new file mode 100644
index 0000000000..554cc6a7bd
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_dir.js
@@ -0,0 +1,2 @@
+import { x } from "dir/module_simpleExport.js";
+result_dir = x + 1;
diff --git a/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap.js b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap.js
new file mode 100644
index 0000000000..5ebaa30188
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap.js
@@ -0,0 +1,2 @@
+import { x } from "./module.js";
+result_remap = x + 2;
diff --git a/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap_https.js b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap_https.js
new file mode 100644
index 0000000000..c047fd28c3
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_simpleImportMap_remap_https.js
@@ -0,0 +1,2 @@
+import { x } from "https://example.com/module.js";
+result_remap_https = x + 3;
diff --git a/dom/base/test/jsmodules/importmaps/module_sortedImportMap.js b/dom/base/test/jsmodules/importmaps/module_sortedImportMap.js
new file mode 100644
index 0000000000..41b2903097
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/module_sortedImportMap.js
@@ -0,0 +1,4 @@
+import { x } from "scope1/scope2/module_simpleExport.js";
+import { x as y } from "scope1/scope2/scope3/scope4/module_simpleExport.js";
+sorted_result = x;
+sorted_result2 = y;
diff --git a/dom/base/test/jsmodules/importmaps/moz.build b/dom/base/test/jsmodules/importmaps/moz.build
new file mode 100644
index 0000000000..1a7d5281ea
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
diff --git a/dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js b/dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js
new file mode 100644
index 0000000000..e6b0ed1c0c
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js
@@ -0,0 +1 @@
+export let x = 84;
diff --git a/dom/base/test/jsmodules/importmaps/scope1/module_simpleImportMap.js b/dom/base/test/jsmodules/importmaps/scope1/module_simpleImportMap.js
new file mode 100644
index 0000000000..b1682e1900
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/scope1/module_simpleImportMap.js
@@ -0,0 +1,2 @@
+import { x } from "simple";
+result_scope1 = x;
diff --git a/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js b/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js
new file mode 100644
index 0000000000..ba2bbae16b
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js
@@ -0,0 +1 @@
+export let x = 126;
diff --git a/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleImportMap.js b/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleImportMap.js
new file mode 100644
index 0000000000..ecb38b7b21
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleImportMap.js
@@ -0,0 +1,2 @@
+import { x } from "simple";
+result_scope2 = x;
diff --git a/dom/base/test/jsmodules/importmaps/test_dynamic_import_reject_importMap.html b/dom/base/test/jsmodules/importmaps/test_dynamic_import_reject_importMap.html
new file mode 100644
index 0000000000..75471064f9
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_dynamic_import_reject_importMap.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test import map should be rejected.</title>
+</head>
+<body onload='testLoaded()'>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<!--There is a dynamic import before the import map tag, so the import map-->
+<!--cannot be accepted according to the spec.-->
+<!--And because the import map is rejected, so the module specifier-->
+<!--"./module_simpleExport.js" won't be remapped to-->
+<!--"./scope1/module_simpleExport.js".-->
+
+<script>
+ import("./module_simpleExport.js");
+</script>
+
+<script type="importmap" onerror='importMapError()'>
+{
+ "imports": {
+ "./module_simpleExport.js": "./scope1/module_simpleExport.js"
+ }
+}
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ let hasError = false;
+ function importMapError() {
+ hasError = true;
+ }
+
+ function testLoaded() {
+ import("./module_simpleExport.js").then((ns) => {
+ ok(ns.x == 42, 'Check simple imported value result: ' + ns.x);
+ ok(hasError, "onerror of the import map should be called.");
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_externalImportMap.html b/dom/base/test/jsmodules/importmaps/test_externalImportMap.html
new file mode 100644
index 0000000000..1345f61947
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_externalImportMap.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test an external import map</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+let gotMsg = false;
+let console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+let listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
+ observe(msg) {
+ info("console message:" + msg);
+ ok(msg.logLevel == Ci.nsIConsoleMessage.warn, "log level should be 'warn'.");
+ // The message will be localized, so we just test the strings won't be
+ // localized.
+ ok(msg.message.match(/<script type='importmap'>.*src/),
+ "The error message should contain \"<script type='importmap'>\"");
+ console.unregisterListener(this);
+ gotMsg = true;
+ }
+};
+console.registerListener(listener);
+</script>
+
+<!--Import maps spec doesn't clearly define the format of an external import map script.-->
+<script src="external_importMap.js" type="importmap" onload="scriptLoaded()" onerror="scriptError()"></script>
+
+<script>
+function testLoaded() {
+ SimpleTest.waitForExplicitFinish();
+ ok(gotMsg, "Should have got the console warning.");
+ SimpleTest.finish();
+}
+
+function scriptLoaded() {
+ ok(false, "Loading external import map script should have failed.");
+}
+
+function scriptError() {
+ ok(true, "Loading external import map script failed.");
+}
+</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/importmaps/test_import_meta_resolve_importMap.html b/dom/base/test/jsmodules/importmaps/test_import_meta_resolve_importMap.html
new file mode 100644
index 0000000000..df1bed2e56
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_import_meta_resolve_importMap.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test import.meta.resolve with import maps</title>
+</head>
+<body onload='testLoaded()'>
+
+<script type="importmap">
+{
+ "imports": {
+ "simple": "./module_simpleExport.js"
+ }
+}
+</script>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var hasThrown = false;
+ window.onerror = handleError;
+
+ function handleError(msg, url, line, col, error) {
+ ok(error instanceof TypeError, "Thrown error should be TypeError.");
+ hasThrown = true;
+ }
+</script>
+
+<script type="module">
+ ok(import.meta.resolve("simple") ==
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/module_simpleExport.js",
+ "calling import.meta.resolve with a specifier from import map.");
+ wasRun = true;
+</script>
+
+<script type="module">
+ // should throw a TypeError
+ import.meta.resolve("fail");
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(wasRun, "Check inline module has run.");
+ ok(hasThrown, "Check inline module has thrown.");
+ SimpleTest.finish();
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_inline_module_reject_importMap.html b/dom/base/test/jsmodules/importmaps/test_inline_module_reject_importMap.html
new file mode 100644
index 0000000000..2001cbcfb9
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_inline_module_reject_importMap.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test import map should be rejected.</title>
+</head>
+<body onload='testLoaded()'>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+let gotMsg = false;
+let console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+let listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
+ observe(msg) {
+ info("console message:" + msg);
+ ok(msg.logLevel == Ci.nsIConsoleMessage.warn, "log level should be 'warn'.");
+ console.unregisterListener(this);
+ gotMsg = true;
+ }
+};
+console.registerListener(listener);
+</script>
+
+<!--There is an inline module before the import map tag, so the import map-->
+<!--cannot be accepted according to the spec.-->
+<!--And because the import map is rejected, so the module specifier-->
+<!--"./module_simpleExport.js" won't be remapped to-->
+<!--"./scope1/module_simpleExport.js".-->
+
+<script type="module">
+</script>
+
+<script type="importmap" onerror='importMapError()'>
+{
+ "imports": {
+ "./module_simpleExport.js": "./scope1/module_simpleExport.js"
+ }
+}
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ let hasError = false;
+ function importMapError() {
+ hasError = true;
+ }
+
+ function testLoaded() {
+ import("./module_simpleExport.js").then((ns) => {
+ ok(ns.x == 42, 'Check simple imported value result: ' + ns.x);
+ ok(hasError, "onerror of the import map should be called.");
+ ok(gotMsg, "Should have got the console warning.");
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base.html b/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base.html
new file mode 100644
index 0000000000..3139a60d37
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test a simple import map with a base element</title>
+</head>
+<body onload='testLoaded()'>
+
+<!-- This will change the baseURL of the document.-->
+<base href="chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/">
+
+<!--
+With the <base> element, the correct "module_simpleExport.js" should be mapped
+to "scope1/module_simpleExport.js", instead of "./module_simpleExport.js".
+-->
+
+<script type="importmap">
+{
+ "imports": {
+ "simple": "./module_simpleExport.js"
+ }
+}
+</script>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="module">
+ import { x } from "simple";
+ result2 = x;
+</script>
+
+<script type="module" src="module_simpleImportMap.js"></script>
+
+<script>
+ var result_scope1, result2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(result_scope1 == 84, 'Check imported value result_scope1: ' + result_scope1);
+ ok(result2 == 84, 'Check imported value result2: ' + result2);
+
+ import("simple").then((ns) => {
+ ok(ns.x == 84, 'Check simple imported value result: ' + ns.x);
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base2.html b/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base2.html
new file mode 100644
index 0000000000..ed000512fd
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_load_importMap_with_base2.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test a simple import map with a script creates a base element</title>
+</head>
+<body onload='testLoaded()'>
+
+<!--This script will create a base element.-->
+<script src="insert_a_base_element.js"></script>
+
+<!--
+With the <base> element, the correct "module_simpleExport.js" should be mapped
+to "scope1/module_simpleExport.js", instead of "./module_simpleExport.js".
+-->
+
+<script type="importmap">
+{
+ "imports": {
+ "simple": "./module_simpleExport.js"
+ }
+}
+</script>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="module">
+ import { x } from "simple";
+ result2 = x;
+</script>
+
+<script type="module" src="module_simpleImportMap.js"></script>
+
+<script>
+ var result_scope1, result2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(result_scope1 == 84, 'Check imported value result_scope1: ' + result_scope1);
+ ok(result2 == 84, 'Check imported value result2: ' + result2);
+
+ import("simple").then((ns) => {
+ ok(ns.x == 84, 'Check simple imported value result: ' + ns.x);
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_module_script_reject_importMap.html b/dom/base/test/jsmodules/importmaps/test_module_script_reject_importMap.html
new file mode 100644
index 0000000000..bc73a60fc9
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_module_script_reject_importMap.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test import map should be rejected.</title>
+</head>
+<body onload='testLoaded()'>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<!--There is a module load before the import map tag, so the import map cannot-->
+<!--be accepted according to the spec.-->
+<!--And because the import map is rejected, so the module specifier-->
+<!--"./module_simpleExport.js" won't be remapped to-->
+<!--"./scope1/module_simpleExport.js".-->
+
+<script src="./module_simpleExport.js" type="module">
+</script>
+
+<script type="importmap" onerror='importMapError()'>
+{
+ "imports": {
+ "./module_simpleExport.js": "./scope1/module_simpleExport.js"
+ }
+}
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ let hasError = false;
+ function importMapError() {
+ hasError = true;
+ }
+
+ function testLoaded() {
+ import("./module_simpleExport.js").then((ns) => {
+ ok(ns.x == 42, 'Check simple imported value result: ' + ns.x);
+ ok(hasError, "onerror of the import map should be called.");
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_parse_importMap_failed.html b/dom/base/test/jsmodules/importmaps/test_parse_importMap_failed.html
new file mode 100644
index 0000000000..b304acd943
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_parse_importMap_failed.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test the error message when parsing import maps failed</title>
+</head>
+<body onload='testLoaded()'>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+let gotMsg = false;
+window.onerror = function(event, src, lineno, colno, error) {
+ info("error: " + error.message);
+ ok(error instanceof SyntaxError, "error should be SyntaxError.");
+ ok(error.message.match(/import map/),
+ "error.message should contain 'import map'");
+ gotMsg = true;
+};
+</script>
+
+<!--
+An import map with invalid JSON format. A SyntaxError will be thrown when parsing
+the import map.
+ -->
+<script type="importmap" onerror='importMapError()'>
+{
+ "imports": {{
+ "foo": "./foo.js"
+ }
+}
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(gotMsg, "Should have thrown a SyntaxError.");
+ SimpleTest.finish();
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_reject_multiple_importMaps.html b/dom/base/test/jsmodules/importmaps/test_reject_multiple_importMaps.html
new file mode 100644
index 0000000000..cc41163101
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_reject_multiple_importMaps.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test the 2nd import map should be rejected.</title>
+</head>
+<body onload='testLoaded()'>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+let gotMsg = false;
+let console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+let listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
+ observe(msg) {
+ info("console message:" + msg);
+ ok(msg.logLevel == Ci.nsIConsoleMessage.warn, "log level should be 'warn'.");
+ console.unregisterListener(this);
+ gotMsg = true;
+ }
+};
+console.registerListener(listener);
+</script>
+
+<script type="importmap" onerror='importMapError1()'>
+{
+ "imports": {
+ "./module_simpleExport.js": "./scope1/module_simpleExport.js"
+ }
+}
+</script>
+
+<!--The 2nd import map should be rejected.-->
+<script type="importmap" onerror='importMapError2()'>
+{
+ "imports": {
+ "./module_simpleExport.js": "./scope1/module_simpleExport.js"
+ }
+}
+</script>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ let hasError = false;
+ function importMapError1() {
+ ok(false, "The first import map should be accepted.");
+ }
+ function importMapError2() {
+ hasError = true;
+ }
+
+ function testLoaded() {
+ import("./module_simpleExport.js").then((ns) => {
+ ok(ns.x == 84, 'Check simple imported value result: ' + ns.x);
+ ok(hasError, "onerror of the import map should be called.");
+ ok(gotMsg, "Should have got the console warning.");
+ }).catch((e) => {
+ ok(false, "throws " + e);
+ }).then(() => {
+ SimpleTest.finish();
+ });
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/importmaps/test_simpleImportMap.html b/dom/base/test/jsmodules/importmaps/test_simpleImportMap.html
new file mode 100644
index 0000000000..6a46ff770e
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_simpleImportMap.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a simple import map</title>
+<script type="importmap">
+{
+ "imports": {
+ "simple": "./module_simpleExport.js",
+ "dir/": "/content/chrome/dom/base/test/jsmodules/importmaps/",
+ "./module.js": "/content/chrome/dom/base/test/jsmodules/importmaps/module_simpleExport.js",
+ "https://example.com/module.js": "./module_simpleExport.js"
+ },
+ "scopes": {
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/": {
+ "simple": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js"
+ },
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/": {
+ "simple": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js"
+ }
+ }
+}
+</script>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+ var result, result_dir, result_remap, result_remap_https;
+ var result_scope1, result_scope2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(result == 42, 'Check imported value result: ' + result);
+ ok(result_dir == 43, 'Check imported value result_dir: ' + result_dir);
+ ok(result_remap == 44, 'Check imported value result_remap: ' + result_remap);
+ ok(result_remap_https == 45,
+ 'Check imported value result_remap_https: ' + result_remap_https);
+ ok(result_scope1 == 84, 'Check imported value result_scope1: ' + result_scope1);
+ ok(result_scope2 == 126, 'Check imported value result_scope2: ' + result_scope2);
+
+ import("simple").then((ns) => {
+ ok(ns.x == 42, 'Check simple imported value result: ' + ns.x);
+ return import("dir/module_simpleExport.js");
+ }).then((ns) => {
+ ok(ns.x == 42, 'Check dir imported value result: ' + ns.x);
+ return import("./module.js");
+ }).then((ns) => {
+ ok(ns.x == 42, 'Check remap imported value result: ' + ns.x);
+ return import("https://example.com/module.js");
+ }).then((ns) => {
+ ok(ns.x == 42, 'Check remap https imported value result: ' + ns.x);
+ SimpleTest.finish();
+ });
+ }
+</script>
+<script type="module" src="module_simpleImportMap.js"></script>
+<script type="module" src="module_simpleImportMap_dir.js"></script>
+<script type="module" src="module_simpleImportMap_remap.js"></script>
+<script type="module" src="module_simpleImportMap_remap_https.js"></script>
+<script type="module" src="module_simpleImportMap_remap_https.js"></script>
+<script type="module" src="scope1/module_simpleImportMap.js"></script>
+<script type="module" src="scope1/scope2/module_simpleImportMap.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/importmaps/test_sortedImportMap.html b/dom/base/test/jsmodules/importmaps/test_sortedImportMap.html
new file mode 100644
index 0000000000..17e4049d1a
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_sortedImportMap.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a sorted import map</title>
+
+<!--
+According to Import maps spec, the entries in "imports" and "scopes" need to be
+sorted. Key with longest prefix should be chosen. So "a/b/" should take
+precendence over "a/".
+This test is verifying that requirement.
+
+In "imports" below, "scope1/scope2/" and "scope1/scope2/scope3/scope4/" should
+be chosen over "scope1" and "scope1/scope2/scop3/" respectively.
+Also "scope1/scope2/" is listed _after_ "scope1/ and
+"scope1/scope2/scope3/scope4" is listed _before_ "scope1/scope2/scope3/" to make
+sure the map is sorted.
+
+For "scopes" below, the "scope1/" is listed before "scope1/scope2/" in
+test_simpleImportMap.html, here we reverse the order, for example, we list
+"scope1/" after "scope1/scope2/" in this test.
+
+See:
+https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map, Step 3.
+https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes, Step 3.
+-->
+
+<script type="importmap">
+{
+ "imports": {
+ "scope1/": "/content/chrome/dom/base/test/jsmodules/importmaps/",
+ "scope1/scope2/": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/",
+ "scope1/scope2/scope3/scope4/": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/",
+ "scope1/scope2/scope3/": "/content/chrome/dom/base/test/jsmodules/importmaps/"
+ },
+ "scopes": {
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/": {
+ "simple": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/scope2/module_simpleExport.js"
+ },
+ "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/": {
+ "simple": "/content/chrome/dom/base/test/jsmodules/importmaps/scope1/module_simpleExport.js"
+ }
+ }
+}
+</script>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+ var sorted_result, sorted_result2;
+ var result_scope2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(sorted_result == 126, 'Check imported value sorted_result: ' + sorted_result);
+ ok(sorted_result2 == 126, 'Check imported value sorted_result: ' + sorted_result2);
+ ok(result_scope2 == 126, 'Check imported value result_scope2: ' + result_scope2);
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_sortedImportMap.js"></script>
+<script type="module" src="scope1/scope2/module_simpleImportMap.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/module_badImport.js b/dom/base/test/jsmodules/module_badImport.js
new file mode 100644
index 0000000000..ab18249156
--- /dev/null
+++ b/dom/base/test/jsmodules/module_badImport.js
@@ -0,0 +1 @@
+import "invalid specifier";
diff --git a/dom/base/test/jsmodules/module_badSyntax.js b/dom/base/test/jsmodules/module_badSyntax.js
new file mode 100644
index 0000000000..744158108c
--- /dev/null
+++ b/dom/base/test/jsmodules/module_badSyntax.js
@@ -0,0 +1,3 @@
+// Module with a syntax error.
+some invalid js syntax;
+wasRun = true;
diff --git a/dom/base/test/jsmodules/module_cyclic1.js b/dom/base/test/jsmodules/module_cyclic1.js
new file mode 100644
index 0000000000..480efee0ae
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic1.js
@@ -0,0 +1,8 @@
+import { func2 } from "./module_cyclic2.js";
+
+export function func1(x, y) {
+ if (x <= 0) {
+ return y;
+ }
+ return func2(x - 1, y + "1");
+}
diff --git a/dom/base/test/jsmodules/module_cyclic2.js b/dom/base/test/jsmodules/module_cyclic2.js
new file mode 100644
index 0000000000..5f17afbd0a
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic2.js
@@ -0,0 +1,8 @@
+import { func3 } from "./module_cyclic3.js";
+
+export function func2(x, y) {
+ if (x <= 0) {
+ return y;
+ }
+ return func3(x - 1, y + "2");
+}
diff --git a/dom/base/test/jsmodules/module_cyclic3.js b/dom/base/test/jsmodules/module_cyclic3.js
new file mode 100644
index 0000000000..4b4c1e4ff6
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic3.js
@@ -0,0 +1,8 @@
+import { func1 } from "./module_cyclic1.js";
+
+export function func3(x, y) {
+ if (x <= 0) {
+ return y;
+ }
+ return func1(x - 1, y + "3");
+}
diff --git a/dom/base/test/jsmodules/module_extractIntroType.js b/dom/base/test/jsmodules/module_extractIntroType.js
new file mode 100644
index 0000000000..b2e1f1c6cf
--- /dev/null
+++ b/dom/base/test/jsmodules/module_extractIntroType.js
@@ -0,0 +1,5 @@
+// Extract the introductionType for this module in conjunction with
+// iframe_extractIntroType.html.
+extractIntroType = function () {
+ debugger;
+};
diff --git a/dom/base/test/jsmodules/module_large1.js b/dom/base/test/jsmodules/module_large1.js
new file mode 100644
index 0000000000..d6933d298d
--- /dev/null
+++ b/dom/base/test/jsmodules/module_large1.js
@@ -0,0 +1,78 @@
+/*
+ * Scripts larger than 5KB may be compiled off main thread. This is such a
+ * script.
+ *
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ */
+
+results.push(1);
diff --git a/dom/base/test/jsmodules/module_large2.js b/dom/base/test/jsmodules/module_large2.js
new file mode 100644
index 0000000000..e1b6da4c91
--- /dev/null
+++ b/dom/base/test/jsmodules/module_large2.js
@@ -0,0 +1,78 @@
+/*
+ * Scripts larger than 5KB may be compiled off main thread. This is such a
+ * script.
+ *
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ */
+
+results.push(2);
diff --git a/dom/base/test/jsmodules/module_large3.js b/dom/base/test/jsmodules/module_large3.js
new file mode 100644
index 0000000000..c966a8eb20
--- /dev/null
+++ b/dom/base/test/jsmodules/module_large3.js
@@ -0,0 +1,78 @@
+/*
+ * Scripts larger than 5KB may be compiled off main thread. This is such a
+ * script.
+ *
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ * large large large large large large large large large large large large
+ */
+
+results.push(3);
diff --git a/dom/base/test/jsmodules/module_missingImport.js b/dom/base/test/jsmodules/module_missingImport.js
new file mode 100644
index 0000000000..28cf608de8
--- /dev/null
+++ b/dom/base/test/jsmodules/module_missingImport.js
@@ -0,0 +1 @@
+import { missing } from "./module_simple1.js";
diff --git a/dom/base/test/jsmodules/module_multiImports.js b/dom/base/test/jsmodules/module_multiImports.js
new file mode 100644
index 0000000000..0587ac5ca0
--- /dev/null
+++ b/dom/base/test/jsmodules/module_multiImports.js
@@ -0,0 +1,4 @@
+import "./module_simple1.js";
+import "./module_simple2.js";
+import "./module_simple3.js";
+results.push(4);
diff --git a/dom/base/test/jsmodules/module_multiLargeImports.js b/dom/base/test/jsmodules/module_multiLargeImports.js
new file mode 100644
index 0000000000..ddc5792d0d
--- /dev/null
+++ b/dom/base/test/jsmodules/module_multiLargeImports.js
@@ -0,0 +1,4 @@
+import "./module_large1.js";
+import "./module_large2.js";
+import "./module_large3.js";
+results.push(4);
diff --git a/dom/base/test/jsmodules/module_setRan.js b/dom/base/test/jsmodules/module_setRan.js
new file mode 100644
index 0000000000..4804382fdd
--- /dev/null
+++ b/dom/base/test/jsmodules/module_setRan.js
@@ -0,0 +1,2 @@
+// Set a global flag to indicate that this module was executed.
+moduleRan = true;
diff --git a/dom/base/test/jsmodules/module_simple1.js b/dom/base/test/jsmodules/module_simple1.js
new file mode 100644
index 0000000000..7594ac699e
--- /dev/null
+++ b/dom/base/test/jsmodules/module_simple1.js
@@ -0,0 +1 @@
+results.push(1);
diff --git a/dom/base/test/jsmodules/module_simple2.js b/dom/base/test/jsmodules/module_simple2.js
new file mode 100644
index 0000000000..f92a1c9d6e
--- /dev/null
+++ b/dom/base/test/jsmodules/module_simple2.js
@@ -0,0 +1 @@
+results.push(2);
diff --git a/dom/base/test/jsmodules/module_simple3.js b/dom/base/test/jsmodules/module_simple3.js
new file mode 100644
index 0000000000..71979926e6
--- /dev/null
+++ b/dom/base/test/jsmodules/module_simple3.js
@@ -0,0 +1 @@
+results.push(3);
diff --git a/dom/base/test/jsmodules/module_simpleExport.js b/dom/base/test/jsmodules/module_simpleExport.js
new file mode 100644
index 0000000000..9714d6d0ab
--- /dev/null
+++ b/dom/base/test/jsmodules/module_simpleExport.js
@@ -0,0 +1 @@
+export let x = 42;
diff --git a/dom/base/test/jsmodules/module_simpleImport.js b/dom/base/test/jsmodules/module_simpleImport.js
new file mode 100644
index 0000000000..9e459fef7a
--- /dev/null
+++ b/dom/base/test/jsmodules/module_simpleImport.js
@@ -0,0 +1,2 @@
+import { x } from "./module_simpleExport.js";
+result = x;
diff --git a/dom/base/test/jsmodules/module_testSyntax.js b/dom/base/test/jsmodules/module_testSyntax.js
new file mode 100644
index 0000000000..3d647ae0b4
--- /dev/null
+++ b/dom/base/test/jsmodules/module_testSyntax.js
@@ -0,0 +1,3 @@
+// Module that throws a syntax error if parsed as a script.
+export default 1;
+wasRun = true;
diff --git a/dom/base/test/jsmodules/moz.build b/dom/base/test/jsmodules/moz.build
new file mode 100644
index 0000000000..1a7d5281ea
--- /dev/null
+++ b/dom/base/test/jsmodules/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
diff --git a/dom/base/test/jsmodules/script_simple2.js b/dom/base/test/jsmodules/script_simple2.js
new file mode 100644
index 0000000000..f92a1c9d6e
--- /dev/null
+++ b/dom/base/test/jsmodules/script_simple2.js
@@ -0,0 +1 @@
+results.push(2);
diff --git a/dom/base/test/jsmodules/test_asyncInlineModules.html b/dom/base/test/jsmodules/test_asyncInlineModules.html
new file mode 100644
index 0000000000..188146e69c
--- /dev/null
+++ b/dom/base/test/jsmodules/test_asyncInlineModules.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test async inline modules</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results.sort(), [1, 2, 3]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" async>
+ results.push(1);
+</script>
+<script type="module" async>
+ import "./module_simple2.js";
+</script>
+<script type="module" async>
+ results.push(3);
+</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_cyclicImport.html b/dom/base/test/jsmodules/test_cyclicImport.html
new file mode 100644
index 0000000000..d316a140d4
--- /dev/null
+++ b/dom/base/test/jsmodules/test_cyclicImport.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test cyclic module imports</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var result;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ SimpleTest.finish();
+ }
+</script>
+<script type="module">
+ import { func1 } from "./module_cyclic1.js";
+ ok(func1(5, "") == "12312");
+</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_dynamicImportErrorMessage.html b/dom/base/test/jsmodules/test_dynamicImportErrorMessage.html
new file mode 100644
index 0000000000..f5552d5a6b
--- /dev/null
+++ b/dom/base/test/jsmodules/test_dynamicImportErrorMessage.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test the error message from import()</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ async function testLoaded() {
+ await import("./404.js").catch((error) => {
+ ok(error instanceof TypeError, "Should be a TypeError.");
+ ok(error.message.match(/404.js/), "Should have the filename.");
+ SimpleTest.finish();
+ });
+ }
+</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_importIntroType.html b/dom/base/test/jsmodules/test_importIntroType.html
new file mode 100644
index 0000000000..67c08bb5d7
--- /dev/null
+++ b/dom/base/test/jsmodules/test_importIntroType.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test introduction type of an imported module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var introType;
+ var extractIntroType;
+
+ info('start');
+ SimpleTest.waitForExplicitFinish();
+
+ function testIntroductionType() {
+ extractIntroType();
+ ok(introType == "importedModule", 'Check introduction type');
+ SimpleTest.finish();
+ }
+</script>
+<iframe src="iframe_extractIntroType.html"></iframe>
+<script type="module">
+import "./module_extractIntroType.js";
+</script>
+<body onload='testIntroductionType()'></body>
diff --git a/dom/base/test/jsmodules/test_importNotFound.html b/dom/base/test/jsmodules/test_importNotFound.html
new file mode 100644
index 0000000000..fc624b6b68
--- /dev/null
+++ b/dom/base/test/jsmodules/test_importNotFound.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test what happens when a module import is not found</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+
+ window.onerror = function(message, url, line, column, error) {
+ errorCount++;
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(eventCount == 1, 'Check that an error event was fired');
+ ok(errorCount == 0, 'Check that no error was reported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" onerror="eventCount++">
+import "./nonExistentModule.js";
+wasRun = true;
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_importResolveFailed.html b/dom/base/test/jsmodules/test_importResolveFailed.html
new file mode 100644
index 0000000000..559de77dcd
--- /dev/null
+++ b/dom/base/test/jsmodules/test_importResolveFailed.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test error thrown when an import cannot be resolved</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var hadTypeError = false;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ hadTypeError = error instanceof TypeError;
+ }
+
+ function testError() {
+ ok(hadTypeError, 'Check that a TypeError was thrown');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_badImport.js"></script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_import_meta_resolve.html b/dom/base/test/jsmodules/test_import_meta_resolve.html
new file mode 100644
index 0000000000..66eedc95bc
--- /dev/null
+++ b/dom/base/test/jsmodules/test_import_meta_resolve.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<head>
+ <meta charset=utf-8>
+ <title>Test import.meta.resolve</title>
+</head>
+<body onload='testLoaded()'>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function assertThrowsTypeError(fn, msg) {
+ let hasThrown = false;
+ try {
+ fn();
+ } catch (error) {
+ hasThrown = true;
+ ok(error instanceof TypeError, "Thrown error should be TypeError.");
+ }
+ ok(hasThrown, msg);
+ }
+
+ function testLoaded() {
+ SimpleTest.finish();
+ }
+</script>
+
+<script type="module">
+ is(typeof import.meta.resolve, "function", "resolve should be a function.");
+ is(import.meta.resolve.name, "resolve", "resolve.name should be 'resolve'.");
+ is(import.meta.resolve.length, 1, "resolve.length should be 1.");
+ is(Object.getPrototypeOf(import.meta.resolve), Function.prototype,
+ "prototype of resolve should be Function.prototype.");
+</script>
+
+<script type="module">
+ is(import.meta.resolve("http://example.com/"), "http://example.com/",
+ "resolve specifiers with absolute path.");
+</script>
+
+<script type="module">
+ is(import.meta.resolve("./x"), (new URL("./x", import.meta.url)).href,
+ "resolve specifiers with relative path.");
+</script>
+
+<script type="module">
+ assertThrowsTypeError(() => new import.meta.resolve("./x"),
+ "import.meta.resolve is not a constructor.");
+</script>
+
+<script type="module">
+ // Fails to resolve the specifier should throw a TypeError.
+ assertThrowsTypeError(() => import.meta.resolve("failed"),
+ "import.meta.resolve should throw if fails to resolve");
+</script>
+
+<script type="module">
+ for (const name of Reflect.ownKeys(import.meta)) {
+ const desc = Object.getOwnPropertyDescriptor(import.meta, name);
+ is(desc.writable, true, name + ".writable should be true.");
+ is(desc.enumerable, true, name + ".enumerable should be true.");
+ is(desc.configurable, true, name + ".configurable should be true.");
+ }
+</script>
+</body>
diff --git a/dom/base/test/jsmodules/test_importedModuleMemoization.html b/dom/base/test/jsmodules/test_importedModuleMemoization.html
new file mode 100644
index 0000000000..37cb26752d
--- /dev/null
+++ b/dom/base/test/jsmodules/test_importedModuleMemoization.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test imported modules are momoized and only loaded once</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3, 4]), 'Check modules only evaluated once');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_multiImports.js"></script>
+<script type="module" src="module_multiImports.js"></script>
+<script type="module" src="module_multiImports.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_linkErrorInCommon1.html b/dom/base/test/jsmodules/test_linkErrorInCommon1.html
new file mode 100644
index 0000000000..436493c75e
--- /dev/null
+++ b/dom/base/test/jsmodules/test_linkErrorInCommon1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test handling of a link error in a common module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var runCount = 0;
+ var hadSyntaxError = false;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ if (error instanceof SyntaxError) {
+ hadSyntaxError = true;
+ }
+ }
+
+ function testError() {
+ ok(runCount == 0, 'Check no modules were executed');
+ ok(hadSyntaxError, 'Check that an error was reported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module">
+ import { missing } from "./module_simple1.js";
+ runCount++;
+</script>
+<script type="module">
+ import { missing } from "./module_simple1.js";
+ runCount++;
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_linkErrorInCommon2.html b/dom/base/test/jsmodules/test_linkErrorInCommon2.html
new file mode 100644
index 0000000000..a882ec0992
--- /dev/null
+++ b/dom/base/test/jsmodules/test_linkErrorInCommon2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test handling of a link error in a common module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var runCount = 0;
+ var hadSyntaxError = false;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ if (error instanceof SyntaxError) {
+ hadSyntaxError = true;
+ }
+ }
+
+ function testError() {
+ ok(runCount == 0, 'Check no modules were executed');
+ ok(hadSyntaxError, 'Check that an error was reported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module">
+ import "./module_missingImport.js";
+ runCount++;
+</script>
+<script type="module">
+ import "./module_missingImport.js";
+ runCount++;
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_moduleNotFound.html b/dom/base/test/jsmodules/test_moduleNotFound.html
new file mode 100644
index 0000000000..f5e7c37364
--- /dev/null
+++ b/dom/base/test/jsmodules/test_moduleNotFound.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test what happens when a top-level module is not found</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+
+ window.onerror = function(message, url, line, column, error) {
+ errorCount++;
+ }
+
+ function testError() {
+ ok(eventCount == 1, 'Check that an error event was fired');
+ ok(errorCount == 0, 'Check that no error was reported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="./nonExistentModule.js" onerror="eventCount++">
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_moduleParsedAsModule.html b/dom/base/test/jsmodules/test_moduleParsedAsModule.html
new file mode 100644
index 0000000000..2cd4abbd57
--- /dev/null
+++ b/dom/base/test/jsmodules/test_moduleParsedAsModule.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test module script parsed as module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var hadSyntaxError = false;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ hadSyntaxError = error instanceof SyntaxError;
+ }
+
+ function testError() {
+ ok(wasRun, 'Check module was run');
+ ok(!hadSyntaxError, 'Check that no SyntaxError was thrown');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_testSyntax.js"></script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_moduleScriptsRun.html b/dom/base/test/jsmodules/test_moduleScriptsRun.html
new file mode 100644
index 0000000000..908f7ff46a
--- /dev/null
+++ b/dom/base/test/jsmodules/test_moduleScriptsRun.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test script elements with type="module" are run for chrome HTML</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var inlineModuleRan = false;
+ var moduleRan = false;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(moduleRan, 'Check module script was run');
+ ok(inlineModuleRan, 'Check inline module script was run');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_setRan.js"></script>
+<script type="module">inlineModuleRan = true;</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_multiAsyncImports.html b/dom/base/test/jsmodules/test_multiAsyncImports.html
new file mode 100644
index 0000000000..dc318851c8
--- /dev/null
+++ b/dom/base/test/jsmodules/test_multiAsyncImports.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a loading multiple modules with the async attribute from top level</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results.sort(), [1, 2, 3]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_simple1.js" async></script>
+<script type="module" src="module_simple2.js" async></script>
+<script type="module" src="module_simple3.js" async></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_multiModuleImports.html b/dom/base/test/jsmodules/test_multiModuleImports.html
new file mode 100644
index 0000000000..003f7e6754
--- /dev/null
+++ b/dom/base/test/jsmodules/test_multiModuleImports.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a importing multiple modules from a module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3, 4]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_multiImports.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_multiModuleLargeImports.html b/dom/base/test/jsmodules/test_multiModuleLargeImports.html
new file mode 100644
index 0000000000..f590bae449
--- /dev/null
+++ b/dom/base/test/jsmodules/test_multiModuleLargeImports.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test importing multiple large modules which may be compiled off main thread from a module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3, 4]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_multiLargeImports.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_multiTopLevelImports.html b/dom/base/test/jsmodules/test_multiTopLevelImports.html
new file mode 100644
index 0000000000..7cfe600959
--- /dev/null
+++ b/dom/base/test/jsmodules/test_multiTopLevelImports.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test importing multiple modules from top level</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_simple1.js"></script>
+<script type="module" src="module_simple2.js"></script>
+<script type="module" src="module_simple3.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_multiTopLevelLargeImports.html b/dom/base/test/jsmodules/test_multiTopLevelLargeImports.html
new file mode 100644
index 0000000000..8deee5479e
--- /dev/null
+++ b/dom/base/test/jsmodules/test_multiTopLevelLargeImports.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a importing large modules which may be compiled off main thread</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3]), 'Check modules imported');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_large1.js"></script>
+<script type="module" src="module_simple2.js"></script>
+<script type="module" src="module_large3.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_scriptInsertedModule.html b/dom/base/test/jsmodules/test_scriptInsertedModule.html
new file mode 100644
index 0000000000..3f00ab9684
--- /dev/null
+++ b/dom/base/test/jsmodules/test_scriptInsertedModule.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a script-inserted module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var result;
+
+ let script = document.createElement("script");
+ script.type = "module";
+ script.src = "./module_simpleImport.js";
+ document.head.appendChild(script);
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(result == 42);
+ SimpleTest.finish();
+ }
+</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_scriptModuleOrder.html b/dom/base/test/jsmodules/test_scriptModuleOrder.html
new file mode 100644
index 0000000000..bd8a4dcebe
--- /dev/null
+++ b/dom/base/test/jsmodules/test_scriptModuleOrder.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test execution order of deferred scripts and modules </title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1, 2, 3]), 'Check execution order');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_simple1.js"></script>
+<script defer src="script_simple2.js"></script>
+<script type="module" src="module_simple3.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_scriptNotParsedAsModule.html b/dom/base/test/jsmodules/test_scriptNotParsedAsModule.html
new file mode 100644
index 0000000000..ba09503503
--- /dev/null
+++ b/dom/base/test/jsmodules/test_scriptNotParsedAsModule.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test classic script not parsed as module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var hadSyntaxError = false;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ hadSyntaxError = error instanceof SyntaxError;
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(hadSyntaxError, 'Check that a SyntaxError was thrown');
+ SimpleTest.finish();
+ }
+</script>
+<script src="module_testSyntax.js"></script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_simpleImport.html b/dom/base/test/jsmodules/test_simpleImport.html
new file mode 100644
index 0000000000..3c1af3f5b6
--- /dev/null
+++ b/dom/base/test/jsmodules/test_simpleImport.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test a simple module import</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var result;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(result == 42, 'Check imported value');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_simpleImport.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_syntaxError.html b/dom/base/test/jsmodules/test_syntaxError.html
new file mode 100644
index 0000000000..180c5aa445
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxError.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing a module are reported</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_badSyntax.js" onerror="eventCount++"></script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_syntaxErrorAsync.html b/dom/base/test/jsmodules/test_syntaxErrorAsync.html
new file mode 100644
index 0000000000..64d8e6a21c
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorAsync.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an async module are reported</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_badSyntax.js" async onerror="eventCount++"></script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_syntaxErrorInline.html b/dom/base/test/jsmodules/test_syntaxErrorInline.html
new file mode 100644
index 0000000000..4960f5358a
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorInline.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an inline module are reported</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" onerror="eventCount++">
+// Module with a syntax error.
+some invalid js syntax;
+wasRun = true;
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html b/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
new file mode 100644
index 0000000000..39b8a7354d
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an inline async module are reported</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var wasRun = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ window.onerror = handleError;
+
+ function handleError(message, url, line, column, error) {
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
+ }
+
+ function testError() {
+ ok(!wasRun, 'Check script was not run');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" async onerror="eventCount++">
+// Module with a syntax error.
+some invalid js syntax;
+wasRun = true;
+</script>
+<body onload='testError()'></body>
diff --git a/dom/base/test/jsmodules/test_topLevelIntroType.html b/dom/base/test/jsmodules/test_topLevelIntroType.html
new file mode 100644
index 0000000000..2c9c91b5bd
--- /dev/null
+++ b/dom/base/test/jsmodules/test_topLevelIntroType.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test introduction type of a top-level module</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var introType;
+ var extractIntroType;
+
+ info('start');
+ SimpleTest.waitForExplicitFinish();
+
+ function testIntroductionType() {
+ extractIntroType();
+ ok(introType == "srcScript", 'Check introduction type');
+ SimpleTest.finish();
+ }
+</script>
+<iframe src="iframe_extractIntroType.html"></iframe>
+<script type="module" src="module_extractIntroType.js">
+</script>
+<body onload='testIntroductionType()'></body>
diff --git a/dom/base/test/jsmodules/test_toplevelModuleMemoization.html b/dom/base/test/jsmodules/test_toplevelModuleMemoization.html
new file mode 100644
index 0000000000..ed4dee6f8b
--- /dev/null
+++ b/dom/base/test/jsmodules/test_toplevelModuleMemoization.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test toplevel modules are momoized and only loaded once</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var results = [];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function arrayEquals(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ function testLoaded() {
+ ok(arrayEquals(results, [1]), 'Check execution order');
+ SimpleTest.finish();
+ }
+</script>
+<script type="module" src="module_simple1.js"></script>
+<script type="module" src="module_simple1.js"></script>
+<script type="module" src="module_simple1.js"></script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/test_typeAttrCaseInsensitive.html b/dom/base/test/jsmodules/test_typeAttrCaseInsensitive.html
new file mode 100644
index 0000000000..47ebc90dd6
--- /dev/null
+++ b/dom/base/test/jsmodules/test_typeAttrCaseInsensitive.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test script element's type attribute comparision is case-insensitive</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var inlineModuleRan = false;
+ var moduleRan = false;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(moduleRan, 'Check module script was run');
+ ok(inlineModuleRan, 'Check inline module script was run');
+ SimpleTest.finish();
+ }
+</script>
+<script type="MODULE" src="module_setRan.js"></script>
+<script type="mOdUlE">inlineModuleRan = true;</script>
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/meta_viewport/mochitest.ini b/dom/base/test/meta_viewport/mochitest.ini
new file mode 100644
index 0000000000..cfe63cc999
--- /dev/null
+++ b/dom/base/test/meta_viewport/mochitest.ini
@@ -0,0 +1,54 @@
+[DEFAULT]
+support-files =
+ viewport_helpers.js
+
+[test_meta_viewport0.html]
+[test_meta_viewport1.html]
+[test_meta_viewport2.html]
+[test_meta_viewport3.html]
+[test_meta_viewport4.html]
+[test_meta_viewport5.html]
+[test_meta_viewport6.html]
+[test_meta_viewport7.html]
+[test_meta_viewport8.html]
+[test_meta_viewport_auto_size_by_device_height.html]
+[test_meta_viewport_auto_size_by_device_width.html]
+[test_meta_viewport_auto_size_by_fixed_height_and_initial_scale_1.html]
+[test_meta_viewport_auto_size_by_fixed_width_and_device_height.html]
+[test_meta_viewport_auto_size_by_fixed_width_and_initial_scale_1.html]
+[test_meta_viewport_auto_size_by_initial_scale_0_5.html]
+[test_meta_viewport_auto_size_by_initial_scale_1.html]
+[test_meta_viewport_auto_size_by_invalid_width.html]
+[test_meta_viewport_auto_size_by_invalid_width_and_fixed_height.html]
+[test_meta_viewport_change_content_among_multiple.html]
+[test_meta_viewport_change_name.html]
+[test_meta_viewport_change_name_among_multiple.html]
+[test_meta_viewport_device_width.html]
+[test_meta_viewport_device_width_with_initial_scale_0_5.html]
+[test_meta_viewport_device_width_with_initial_scale_2.html]
+[test_meta_viewport_fit.html]
+[test_meta_viewport_fit_multiple.html]
+[test_meta_viewport_fixed_width_and_zero_display_width.html]
+[test_meta_viewport_initial_scale_0_5.html]
+[test_meta_viewport_initial_scale_2.html]
+[test_meta_viewport_insert_before_existing_tag.html]
+[test_meta_viewport_maximum_scale_0.html]
+[test_meta_viewport_maximum_scale_0_5.html]
+[test_meta_viewport_maximum_scale_2.html]
+[test_meta_viewport_multiple_tags.html]
+[test_meta_viewport_negative_width_and_negative_height.html]
+[test_meta_viewport_negative_width_and_no_height.html]
+[test_meta_viewport_negative_width_and_valid_height.html]
+[test_meta_viewport_valid_width_and_negative_height.html]
+[test_meta_viewport_valid_width_and_no_height.html]
+[test_meta_viewport_no_width_and_negative_height.html]
+[test_meta_viewport_no_width_and_valid_height.html]
+[test_meta_viewport_remove_node.html]
+[test_meta_viewport_remove_node_from_multiple.html]
+[test_meta_viewport_replace_content.html]
+[test_meta_viewport_tiny_display_size.html]
+[test_meta_viewport_initial_scale_with_trailing_characters.html]
+[test_meta_viewport_width_with_trailing_characters.html]
+[test_meta_viewport_empty_content_and_valid_content_tags.html]
+[test_meta_viewport_no_content_and_valid_content_tags.html]
+[test_meta_viewport_removing_content_attribute.html]
diff --git a/dom/base/test/meta_viewport/moz.build b/dom/base/test/meta_viewport/moz.build
new file mode 100644
index 0000000000..60b508c774
--- /dev/null
+++ b/dom/base/test/meta_viewport/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
diff --git a/dom/base/test/meta_viewport/test_meta_viewport0.html b/dom/base/test/meta_viewport/test_meta_viewport0.html
new file mode 100644
index 0000000000..8fef72c578
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport0.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>No &lt;meta name="viewport"&gt; tag</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ fuzzeq(info.defaultZoom, 0.25, "initial scale is clamped to the default mimumim scale");
+ fuzzeq(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width is the default width");
+ is(info.height, 588, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(490, 600);
+ is(info.width, 980, "width is still the default width");
+ is(info.height, 1200, "height is proportional to the new displayHeight");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 980, "width is still the default width");
+ is(info.height, 588, "height is still proportional to displayHeight");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport1.html b/dom/base/test/meta_viewport/test_meta_viewport1.html
new file mode 100644
index 0000000000..66dd02461e
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport1.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width, initial-scale=1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 100%");
+ is(info.width, 800, "width is the same as the displayWidth");
+ is(info.height, 480, "height is the same as the displayHeight");
+ is(info.autoSize, true, "width=device-width enables autoSize");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(900, 600);
+ is(info.width, 900, "changing the displayWidth changes the width");
+ is(info.height, 600, "changing the displayHeight changes the height");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ let info = getViewportInfo(900, 600);
+ is(info.defaultZoom, 1.5, "initial zoom is 150%");
+ is(info.width, 600, "width equals displayWidth/1.5");
+ is(info.height, 400, "height equals displayHeight/1.5");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport2.html b/dom/base/test/meta_viewport/test_meta_viewport2.html
new file mode 100644
index 0000000000..e0f45813be
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport2.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 100%");
+ is(info.width, 800, "width is the same as the displayWidth");
+ is(info.height, 480, "height is the same as the displayHeight");
+ is(info.autoSize, true, "width=device-width enables autoSize");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(900, 600);
+ is(info.width, 900, "changing the displayWidth changes the width");
+ is(info.height, 600, "changing the displayHeight changes the height");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ let info = getViewportInfo(900, 600);
+ is(info.defaultZoom, 1.5, "initial zoom is 150%");
+ is(info.width, 600, "width equals displayWidth/1.5");
+ is(info.height, 400, "height equals displayHeight/1.5");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport3.html b/dom/base/test/meta_viewport/test_meta_viewport3.html
new file mode 100644
index 0000000000..32464bd126
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport3.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=320">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=320</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 80);
+ is(info.defaultZoom, 2.5, "initial zoom fits the displayWidth");
+ is(info.width, 320, "width is set explicitly");
+ is(info.height, 40, "height is at the absolute minimum");
+ is(info.autoSize, false, "width=device-width enables autoSize");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(480, 800);
+ is(info.defaultZoom, 1.5, "initial zoom fits the new displayWidth");
+ is(info.width, 320, "explicit width is unchanged");
+ is(info.height, 533, "height changes proportional to displayHeight");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ // With an explicit width in CSS px, the scaleRatio has no effect.
+ let info = getViewportInfo(800, 80);
+ is(info.defaultZoom, 2.5, "initial zoom still fits the displayWidth");
+ is(info.width, 320, "width is still set explicitly");
+ is(info.height, 40, "height is still minimum height");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport4.html b/dom/base/test/meta_viewport/test_meta_viewport4.html
new file mode 100644
index 0000000000..bc7f7ada55
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport4.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale=1.0, user-scalable=no</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is set explicitly");
+ is(info.width, 800, "width fits the initial zoom level");
+ is(info.height, 480, "height fits the initial zoom level");
+ is(info.autoSize, true, "initial-scale=1 enables autoSize");
+ is(info.allowZoom, false, "zooming is explicitly disabled");
+
+ info = getViewportInfo(480, 800);
+ is(info.defaultZoom, 1, "initial zoom is still set explicitly");
+ is(info.width, 480, "width changes to match the displayWidth");
+ is(info.height, 800, "height changes to match the displayHeight");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1.5, "initial zoom is adjusted for device pixel ratio");
+ is(info.width, 533, "width fits the initial zoom");
+ is(info.height, 320, "height fits the initial zoom");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport5.html b/dom/base/test/meta_viewport/test_meta_viewport5.html
new file mode 100644
index 0000000000..938cc052ee
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport5.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="user-scalable=NO">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>user-scalable=NO</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, true, "user-scalable values are case-sensitive; 'NO' is not valid");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport6.html b/dom/base/test/meta_viewport/test_meta_viewport6.html
new file mode 100644
index 0000000000..e3ca1b72aa
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport6.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=2000, minimum-scale=0.75">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=2000, minimum-scale=0.75</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.minZoom, 0.75, "minumum scale is set explicitly");
+ is(info.defaultZoom, 0.75, "initial scale is bounded by the minimum scale");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 2000, "width is set explicitly");
+ is(info.height, 1200, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(2000, 1000);
+ is(info.minZoom, 0.75, "minumum scale is still set explicitly");
+ is(info.defaultZoom, 1, "initial scale fits the width");
+ is(info.width, 2000, "width is set explicitly");
+ is(info.height, 1000, "height is proportional to the new displayHeight");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
+
+ let info = getViewportInfo(800, 480);
+ is(info.minZoom, 1.125, "minumum scale is converted to device pixel scale");
+ is(info.defaultZoom, 1.125, "initial scale is bounded by the minimum scale");
+ is(info.maxZoom, 15, "maximum scale defaults to the absolute maximum");
+ is(info.width, 2000, "width is still set explicitly");
+ is(info.height, 1200, "height is still proportional to displayHeight");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport7.html b/dom/base/test/meta_viewport/test_meta_viewport7.html
new file mode 100644
index 0000000000..1f40c27109
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport7.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=320">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>Dynamic viewport updates</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ updateViewport("width=device-width");
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 100%");
+ is(info.width, 800, "width is the same as the displayWidth");
+ is(info.height, 480, "height is the same as the displayHeight");
+ is(info.autoSize, true, "width=device-width enables autoSize");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(900, 600);
+ is(info.width, 900, "changing the displayWidth changes the width");
+ is(info.height, 600, "changing the displayHeight changes the height");
+ });
+
+ add_task(async function test2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ updateViewport("width=320");
+ let info = getViewportInfo(800, 80);
+ is(info.defaultZoom, 2.5, "initial zoom fits the displayWidth");
+ is(info.width, 320, "width is set explicitly");
+ is(info.height, 40, "height is at the absolute minimum");
+ is(info.autoSize, false, "width=device-width enables autoSize");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ info = getViewportInfo(480, 800);
+ is(info.defaultZoom, 1.5, "initial zoom fits the new displayWidth");
+ is(info.width, 320, "explicit width is unchanged");
+ is(info.height, 533, "height changes proportional to displayHeight");
+ });
+
+ add_task(async function test3() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ updateViewport("user-scalable=no");
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, false, "zooming is explicitly disabled");
+ });
+
+ add_task(async function test4() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ updateViewport("user-scalable=yes");
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, true, "zooming is explicitly allowed");
+ });
+
+ function updateViewport(content) {
+ let meta = document.querySelector("meta[name=viewport]");
+ meta.content = content;
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport8.html b/dom/base/test/meta_viewport/test_meta_viewport8.html
new file mode 100644
index 0000000000..4c711c9cc6
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport8.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=0.01">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>minimum-scale=0.01</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.defaultZoom, 0.25, "initial scale is bounded by the minimum scale");
+ is(info.width, 3200, "width is scaled correctly by zoom level");
+ is(info.height, 1920, "height is scaled correctly by zoom level");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_height.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_height.html
new file mode 100644
index 0000000000..1b6c35a602
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_height.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>device-height enables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="height=device-height">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>height=device-height</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function device_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(info.autoSize, "device-height should enable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_width.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_width.html
new file mode 100644
index 0000000000..fb91f2e50c
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_device_width.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>device-width enables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function device_width() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(info.autoSize, "device-width should enable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_height_and_initial_scale_1.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_height_and_initial_scale_1.html
new file mode 100644
index 0000000000..83346f4924
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_height_and_initial_scale_1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale=1 with fixed height enable autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="height=400, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>height=400, initial-scale=1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function fixed_height_and_initial_scale_1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(info.autoSize,
+ "initial-scale=1 with fixed height should enable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_device_height.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_device_height.html
new file mode 100644
index 0000000000..e570f80644
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_device_height.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Fixed width and device-height disables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400,height=device-height">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=400, height=device-height</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function fixed_width_and_device_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(!info.autoSize,
+ "Fixed width should disable autoSize even if height is device-height");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_initial_scale_1.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_initial_scale_1.html
new file mode 100644
index 0000000000..4453b4c959
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_fixed_width_and_initial_scale_1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale=1 with fixed width disables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=400, initial-scale=1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function fixed_width_and_initial_scale_1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(!info.autoSize,
+ "initial-scale=1 with fixed width should disable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_0_5.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_0_5.html
new file mode 100644
index 0000000000..ff12b22454
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_0_5.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale!=1 without width disables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=0.5">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale!=1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function initial_scale_0_5() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(!info.autoSize,
+ "initial-scale!=1 without width should disable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_1.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_1.html
new file mode 100644
index 0000000000..e86742b8f2
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_initial_scale_1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale=1 without width enables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale=1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function initial_scale_1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(info.autoSize,
+ "initial-scale=1 without width should enable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width.html
new file mode 100644
index 0000000000..6c950185bc
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>invalid width enables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=-1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=-1</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function invalid_width() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(info.autoSize, "invalid width should enable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width_and_fixed_height.html b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width_and_fixed_height.html
new file mode 100644
index 0000000000..db68ecb6ec
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_auto_size_by_invalid_width_and_fixed_height.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>invalid width but with fixed height disables autoSize</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=-1,height=200">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=-1,height=200</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function invalid_width_and_fixed_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ ok(!info.autoSize,
+ "invalid width but with valid height should disable autoSize");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_change_content_among_multiple.html b/dom/base/test/meta_viewport/test_meta_viewport_change_content_among_multiple.html
new file mode 100644
index 0000000000..8ec88b8920
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_change_content_among_multiple.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>content attribute changes among multiple meta viewport tags</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta id="first" name="viewport" content="width=980">
+ <meta id="second" name="viewport" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function change_content_attribute() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // The second meta tag is the one we use.
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+
+ // Change the content of the first one.
+ // eslint-disable-next-line no-undef
+ first.setAttribute("content", "width=640");
+
+ // Now the first one is prior to the second one.
+ info = getViewportInfo(800, 480);
+ fuzzeq(info.defaultZoom, 800/640, "initial scale is calculated based on width");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 640, "width is the default width");
+ is(info.height, 480*640/800, "height is proportional to displayHeight");
+
+ // Cleat the second content.
+ // eslint-disable-next-line no-undef
+ second.setAttribute("content", "");
+
+ // The second one is prior to the first one, but the content is empty so
+ // that we should behave as if there is no meta viewport tag.
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 0.25, "initial scale is clamped to the default mimumim scale");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width should be 980");
+ is(info.height,588, "height is proportional to displayHeight");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_change_name.html b/dom/base/test/meta_viewport/test_meta_viewport_change_name.html
new file mode 100644
index 0000000000..84b9c321df
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_change_name.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>name attribute changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta id="viewport" name="initial-name" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function change_name_attribute() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // There is no valid <meta name="viewport"> so that viewport info values
+ // are the same as values in test_meta_viewport0.html.
+ is(info.defaultZoom, 0.25, "initial scale is clamped to the default mimumim scale");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width is the default width");
+ is(info.height, 588, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ // Now it's a valid viewport.
+ // eslint-disable-next-line no-undef
+ viewport.setAttribute("name", "viewport");
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+
+ // Now it's invalid again, but we retain the state.
+ // eslint-disable-next-line no-undef
+ viewport.setAttribute("name", "other");
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_change_name_among_multiple.html b/dom/base/test/meta_viewport/test_meta_viewport_change_name_among_multiple.html
new file mode 100644
index 0000000000..ca41577bbe
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_change_name_among_multiple.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>name attribute changes among multiple meta viewport tags</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=980">
+ <meta id="viewport" name="initial-name" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script>
+ "use strict";
+
+ add_task(async function change_name_attribute() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+ let viewport = document.getElementById("viewport");
+
+ let info = getViewportInfo(800, 480);
+ // The "width=980" content is a valid one.
+ fuzzeq(info.defaultZoom, 800/980, "initial scale is calculated based on width");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width is the default width");
+ is(info.height, 588, "height is proportional to displayHeight");
+
+ // Now the second meta tag is a valid viewport.
+ viewport.setAttribute("name", "viewport");
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+
+ // Now it's invalid again, but it's retained.
+ viewport.setAttribute("name", "other");
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_device_width.html b/dom/base/test/meta_viewport/test_meta_viewport_device_width.html
new file mode 100644
index 0000000000..0e1f2f1dac
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_device_width.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>device-width in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function device_width() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_0_5.html b/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_0_5.html
new file mode 100644
index 0000000000..fc3192c854
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_0_5.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>device-width with initial-scale=0.5 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width, initial-scale=0.5">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width, initial-scale=0.5</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function device_width_with_initial_scale_0_5() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 1600, "width should be the display width / initial-scale");
+ is(info.height, 960, "height should be the display height / initial-scale");
+ is(info.defaultZoom, 0.5, "initial-scale should be 0.5");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_2.html b/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_2.html
new file mode 100644
index 0000000000..e701266e60
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_device_width_with_initial_scale_2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>device-width with initial-scale=2 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width, initial-scale=2">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width, initial-scale=2</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function device_width_with_initial_scale_2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // In the case device is specified, max-width isn't 'extend-to-zoom', it's
+ // the display width, and if the initial-scale is greater than 1,
+ // min-width will be the same as max-width because extend-width will be
+ // less than the display width, thus the final width should be the display
+ // width.
+ is(info.width, 800, "width should be the display width");
+ is(info.height, 480, "height should be the display height");
+ is(info.defaultZoom, 2, "initial-scale should be 2");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_empty_content_and_valid_content_tags.html b/dom/base/test/meta_viewport/test_meta_viewport_empty_content_and_valid_content_tags.html
new file mode 100644
index 0000000000..6de2e08c40
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_empty_content_and_valid_content_tags.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>a valid meta viewport tag and empty content attribute viewport tag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400">
+ <meta name="viewport" content="">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function multiple_viewport_tags() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // The empty content attribute meta tag should be valid, which means
+ // it supersedes the first `width=400`.
+ is(info.width, 980, "width should be default, 980");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_fit.html b/dom/base/test/meta_viewport/test_meta_viewport_fit.html
new file mode 100644
index 0000000000..e86ad817d6
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_fit.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport viewport-fit test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="viewport-fit=cover">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>viewport-fit=cover</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let viewportFit = SpecialPowers.getDOMWindowUtils(window).getViewportFitInfo();
+ is(viewportFit, "cover", "viewport-fit is cover correctly");
+
+ let elements = document.getElementsByTagName("meta");
+ for (let meta of elements) {
+ if (meta.getAttribute("name") == "viewport") {
+ meta.setAttribute("content", "viewport-fit=contain");
+ }
+ }
+
+ viewportFit = SpecialPowers.getDOMWindowUtils(window).getViewportFitInfo();
+ is(viewportFit, "contain", "viewport-fit is contain correctly");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_fit_multiple.html b/dom/base/test/meta_viewport/test_meta_viewport_fit_multiple.html
new file mode 100644
index 0000000000..e204bb70f6
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_fit_multiple.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>multiple meta viewport viewport-fit test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="viewport-fit=cover">
+ <meta name="viewport" content="width=device-width">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>viewport-fit=cover</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let viewportFit = SpecialPowers.getDOMWindowUtils(window).getViewportFitInfo();
+ is(viewportFit, "auto", "No viewport-fit in last meta viewport. So auto is applied");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_fixed_width_and_zero_display_width.html b/dom/base/test/meta_viewport/test_meta_viewport_fixed_width_and_zero_display_width.html
new file mode 100644
index 0000000000..e649ad9fcd
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_fixed_width_and_zero_display_width.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Fixed meta viewport width, zero display width</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=100">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>Fixed meta viewport width, zero display width</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function valid_width() {
+ // We choose a 2.5 scaleRatio here to make clear that the later check
+ // of the viewport height is getting a scaled value.
+ await SpecialPowers.pushPrefEnv(scaleRatio(2.5));
+
+ // We request a zero-width viewport because that triggers a special
+ // codepath that sets the viewport height to the scaled display height.
+ let info = getViewportInfo(0, 500);
+ is(info.width, 100, "width should be 100");
+ is(info.height, 200, "height should be 200, since 500 / 2.5 = 200");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_0_5.html b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_0_5.html
new file mode 100644
index 0000000000..99fb5c1d36
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_0_5.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale=0.5 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=0.5">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale=0.5</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function initial_scale_0_5() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // 'min-width' is 'display width / initial-scale', thus the value will be
+ // bigger than the display width in the case where the initial-scale is
+ // less than 1, thus the final viewport width will be the 'min-width'.
+ // See 'Resolve initial width and height from min/max descriptors' section
+ // in the spec for more detail.
+ // height is calculated by the same rule.
+ // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
+ is(info.width, 1600, "width should be scaled by 1 / initial-scale");
+ is(info.height, 960, "height should be scaled by 1 / initial-scale");
+ is(info.defaultZoom, 0.5, "initial-scale should be 0.5");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_2.html b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_2.html
new file mode 100644
index 0000000000..22f0adc061
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale=2 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=2">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale=2</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function initial_scale_2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 400, "width should be scaled by 1 / initial-scale");
+ is(info.height, 240, "height should be scaled by 1 / initial-scale");
+ is(info.defaultZoom, 2, "initial-scale should be 2");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_with_trailing_characters.html b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_with_trailing_characters.html
new file mode 100644
index 0000000000..5939691cb5
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_initial_scale_with_trailing_characters.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>initial-scale with trailing characters in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="initial-scale=1.0/">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>initial-scale=1.0/</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function initial_scale_with_trailing_characters() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 800, "width should be scaled by 1 / initial-scale");
+ is(info.height, 480, "height should be scaled by 1 / initial-scale");
+ is(info.defaultZoom, 1, "initial-scale should be 1");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_insert_before_existing_tag.html b/dom/base/test/meta_viewport/test_meta_viewport_insert_before_existing_tag.html
new file mode 100644
index 0000000000..e2162c81f8
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_insert_before_existing_tag.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>interting a meta viewport tag before existing one</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=980">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function multiple_viewport_tags() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ const meta = document.createElement("meta");
+ meta.setAttribute("name", "viewport");
+ meta.setAttribute("content", "initial-scale=1,maximum-scale=1");
+ document.head.insertBefore(meta, document.querySelector("meta[name=viewport]"));
+
+ let info = getViewportInfo(800, 480);
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ is(info.maxZoom, 1, "maximum scale is 1");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0.html b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0.html
new file mode 100644
index 0000000000..893f3128d9
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=0">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width, minimum-scale=1, maximum-scale=0</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.maxZoom, 1, "maximum_scale should be clamped");
+ is(info.defaultZoom, 1, "initial scale should be 1");
+ is(info.minZoom, 1, "minimum scale should be 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0_5.html b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0_5.html
new file mode 100644
index 0000000000..7f17ef146e
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_0_5.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>maximum-scale=0.5 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="maximum-scale=0.5">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>maximum-scale=0.5</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function maximum_scale_0_5() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // In the case where the maximum-scale is less than 1 and there is no
+ // initial-scale or initial-scale is NOT greater than the maximum-scale,
+ // 'min-width' will be bigger than the display width.
+ is(info.width, 1600, "width should be scaled by 1 / maximum-scale");
+ is(info.height, 960, "height should be scaled by 1 / maximum-scale");
+ is(info.maxZoom, 0.5, "maximum-scale should be 0.5");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_2.html b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_2.html
new file mode 100644
index 0000000000..2fe3baf27d
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_maximum_scale_2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>maximum-scale=2 in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="maximum-scale=2">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>maximum-scale=2</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function maximum_scale_2() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 980, "without specified width, we get default width");
+ is(info.height, 588, "without specified width, height is scaled in aspect ratio to default width");
+ is(info.maxZoom, 2, "maximum-scale should be 2");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_multiple_tags.html b/dom/base/test/meta_viewport/test_meta_viewport_multiple_tags.html
new file mode 100644
index 0000000000..816dd62061
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_multiple_tags.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>multilple meta viewport tags</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=980">
+ <meta name="viewport" content="initial-scale=1,maximum-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function multiple_viewport_tags() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ is(info.maxZoom, 1, "maximum scale is 1");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_negative_height.html b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_negative_height.html
new file mode 100644
index 0000000000..e458cbc8dd
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_negative_height.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>negative width and height in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=-400, height=-240">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=-400, height=-240</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function negative_width_and_negative_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // negative width and height should be ignored, then display width and
+ // height are used for viewport.
+ is(info.width, 800, "width should be the display width");
+ is(info.height, 480, "height should be the display height");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_no_height.html b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_no_height.html
new file mode 100644
index 0000000000..2369f3b4f3
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_no_height.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>negative width in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=-400">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=-400</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function negative_width() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_valid_height.html b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_valid_height.html
new file mode 100644
index 0000000000..579712504d
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_negative_width_and_valid_height.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>negative width and valid height in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=-300,height=240">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=-400, height=240</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function negative_width_and_valid_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 980, "width should be default when specified width is invalid");
+ is(info.height, 240, "height should be 240");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_no_content_and_valid_content_tags.html b/dom/base/test/meta_viewport/test_meta_viewport_no_content_and_valid_content_tags.html
new file mode 100644
index 0000000000..f5a78d9eec
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_no_content_and_valid_content_tags.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>a valid meta viewport tag and no content attribute viewport tag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400">
+ <meta name="viewport">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function multiple_viewport_tags() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // The no content attribute meta tag should be ignored.
+ is(info.width, 400, "width should be 400");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_negative_height.html b/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_negative_height.html
new file mode 100644
index 0000000000..0e0d37812b
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_negative_height.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>negative height in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="height=-200">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>height=-200</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function negative_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 980, "without specified width, we get default width");
+ is(info.height, 588, "without specified width, and invalid specified height, " +
+ "height is scaled in aspect ratio to default width");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_valid_height.html b/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_valid_height.html
new file mode 100644
index 0000000000..a7be88ec40
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_no_width_and_valid_height.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>valid height in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="height=240">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>height=240</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function valid_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 980, "without specified width, we get default width");
+ is(info.height, 240, "height should be 240");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_remove_node.html b/dom/base/test/meta_viewport/test_meta_viewport_remove_node.html
new file mode 100644
index 0000000000..12104cdf50
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_remove_node.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>remove meta viewport node</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta id="viewport" name="viewport" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script>
+ "use strict";
+
+ add_task(async function remove_viewport_node() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+ let viewport = document.getElementById("viewport");
+
+ let info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+
+ // Now there is no <meta name="viewport">, but we still retain the info
+ // from the last one.
+ viewport.remove();
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_remove_node_from_multiple.html b/dom/base/test/meta_viewport/test_meta_viewport_remove_node_from_multiple.html
new file mode 100644
index 0000000000..b967967cc1
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_remove_node_from_multiple.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>remove a meta viewport node from multiple ones</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta id="willBeRemoved" name="viewport" content="width=980">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function remove_a_viewport_node() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+
+ // We use "width=980" for viewport stuff.
+ fuzzeq(info.defaultZoom, 800/980, "initial scale is calculated based on width");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width is the default width");
+ is(info.height, 588, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+
+ document.getElementById("willBeRemoved").remove();
+ info = getViewportInfo(800, 480);
+
+ // Now it was removed, but we remain the same.
+ fuzzeq(info.defaultZoom, 800/980, "initial scale is calculated based on width");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 980, "width is the default width");
+ is(info.height, 588, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_removing_content_attribute.html b/dom/base/test/meta_viewport/test_meta_viewport_removing_content_attribute.html
new file mode 100644
index 0000000000..784a503e9f
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_removing_content_attribute.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>removing content attribute</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400">
+ <meta name="viewport" content="width=800">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script>
+ "use strict";
+
+ add_task(async function multiple_viewport_tags() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ // The latter meta viewport tag is used.
+ is(info.width, 800, "width should be 800");
+
+ const secondMeta =
+ document.querySelector("meta[name=viewport][content='width=800']");
+ secondMeta.removeAttribute("content");
+
+ info = getViewportInfo(800, 480);
+ // The latter meta viewport tag is still used.
+ is(info.width, 800, "width should be 800");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_replace_content.html b/dom/base/test/meta_viewport/test_meta_viewport_replace_content.html
new file mode 100644
index 0000000000..202c62362b
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_replace_content.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>replace meta viewport content</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta id="viewport" name="viewport" content="width=device-width, initial-scale=1">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function replace_content() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ info = getViewportInfo(800, 480);
+ is(info.defaultZoom, 1, "initial zoom is 1");
+ is(info.width, 800, "width should be 800");
+ is(info.height, 480, "height should be 480");
+
+ // Now the content has only 'width=1080'.
+ // eslint-disable-next-line no-undef
+ viewport.setAttribute("content", "width=1080");
+ info = getViewportInfo(800, 480);
+
+ fuzzeq(info.defaultZoom, 800/1080, "initial scale is calculated by the given width");
+ is(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
+ is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
+ is(info.width, 1080, "width is the given width");
+ is(info.height, 480*1080/800, "height is proportional to displayHeight");
+ is(info.autoSize, false, "autoSize is disabled by default");
+ is(info.allowZoom, true, "zooming is enabled by default");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_tiny_display_size.html b/dom/base/test/meta_viewport/test_meta_viewport_tiny_display_size.html
new file mode 100644
index 0000000000..8e47fc677a
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_tiny_display_size.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=device-width</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function test1() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ // Check that the minimum viewport dimension of (200,40) are not enforced
+ // in cases where the display size itself has a smaller dimension.
+ let info = getViewportInfo(192, 32);
+ is(info.defaultZoom, 1, "initial zoom is 100%");
+ is(info.width, 192, "width is the same as the displayWidth");
+ is(info.height, 32, "height is the same as the displayHeight");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_negative_height.html b/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_negative_height.html
new file mode 100644
index 0000000000..10bce57d87
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_negative_height.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>valid width and negative height in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400, height=-200">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=400, height=-200</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function valid_width_and_negative_height() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 400, "width should be 400");
+ is(info.height, 240, "height should be 240 which is the result of the " +
+ "display height * viewport width / display width");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_no_height.html b/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_no_height.html
new file mode 100644
index 0000000000..b24ce680a7
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_valid_width_and_no_height.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>valid width in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=400</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function valid_width() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 400, "width should be 400");
+ is(info.height, 240, "height should be 240 which is the result of the " +
+ "display height * viewport width / display width");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/test_meta_viewport_width_with_trailing_characters.html b/dom/base/test/meta_viewport/test_meta_viewport_width_with_trailing_characters.html
new file mode 100644
index 0000000000..f3545d057b
--- /dev/null
+++ b/dom/base/test/meta_viewport/test_meta_viewport_width_with_trailing_characters.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>width with trailing characters in meta viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=400/">
+ <script src="viewport_helpers.js"></script>
+</head>
+<body>
+ <p>width=400/</p>
+ <script type="application/javascript">
+ "use strict";
+
+ add_task(async function width_with_trailing_characters() {
+ await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
+
+ let info = getViewportInfo(800, 480);
+ is(info.width, 400, "width should be 400");
+ is(info.height, 240, "height should be 240 which is the result of the " +
+ "display height * viewport width / display width");
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/meta_viewport/viewport_helpers.js b/dom/base/test/meta_viewport/viewport_helpers.js
new file mode 100644
index 0000000000..d4d346b5d0
--- /dev/null
+++ b/dom/base/test/meta_viewport/viewport_helpers.js
@@ -0,0 +1,44 @@
+function scaleRatio(scale) {
+ return {
+ set: [
+ ["layout.css.devPixelsPerPx", "" + scale],
+ ["dom.meta-viewport.enabled", true],
+ ],
+ };
+}
+
+function getViewportInfo(aDisplayWidth, aDisplayHeight) {
+ let defaultZoom = {},
+ allowZoom = {},
+ minZoom = {},
+ maxZoom = {},
+ width = {},
+ height = {},
+ autoSize = {};
+
+ let cwu = SpecialPowers.getDOMWindowUtils(window);
+ cwu.getViewportInfo(
+ aDisplayWidth,
+ aDisplayHeight,
+ defaultZoom,
+ allowZoom,
+ minZoom,
+ maxZoom,
+ width,
+ height,
+ autoSize
+ );
+ return {
+ defaultZoom: defaultZoom.value,
+ minZoom: minZoom.value,
+ maxZoom: maxZoom.value,
+ width: width.value,
+ height: height.value,
+ autoSize: autoSize.value,
+ allowZoom: allowZoom.value,
+ };
+}
+
+function fuzzeq(a, b, msg) {
+ ok(Math.abs(a - b) < 1e-6, msg);
+}
diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini
new file mode 100644
index 0000000000..c04ccc7ac6
--- /dev/null
+++ b/dom/base/test/mochitest.ini
@@ -0,0 +1,1111 @@
+[DEFAULT]
+tags = condprof
+prefs =
+ formhelper.autozoom.force-disable.test-only=true
+support-files =
+ audio.ogg
+ iframe_bug962251.html
+ iframe_bug976673.html
+ iframe_main_bug1022229.html
+ iframe_sandbox_bug1022229.html
+ file_empty.html
+ iframe_postMessage_solidus.html
+ file_setname.html
+ 345339_iframe.html
+ Ahem.ttf
+ accesscontrol.resource
+ accesscontrol.resource^headers^
+ badContentType.eventsource
+ badContentType.eventsource^headers^
+ badHTTPResponseCode.eventsource
+ badHTTPResponseCode.eventsource^headers^
+ badMessageEvent.eventsource
+ badMessageEvent.eventsource^headers^
+ badMessageEvent2.eventsource
+ badMessageEvent2.eventsource^headers^
+ bug282547.sjs
+ bug298064-subframe.html
+ bug313646.txt
+ bug382113_object.html
+ bug403852_fileOpener.js
+ bug419132.html
+ bug426308-redirect.sjs
+ bug435425.sjs
+ bug435425_redirect.sjs
+ bug444322.js
+ bug444322.txt
+ bug455629-helper.svg
+ bug457746.sjs
+ bug461735-post-redirect.js
+ bug461735-redirect1.sjs
+ bug461735-redirect2.sjs
+ bug466080.sjs
+ bug466409-empty.css
+ bug466409-page.html
+ bug475156.sjs
+ bug482935.sjs
+ bug540854.sjs
+ bug578096LoadChromeScript.js
+ bug638112-response.txt
+ bug638112.sjs
+ bug696301-script-1.js
+ bug696301-script-1.js^headers^
+ bug696301-script-2.js
+ bug704320.sjs
+ bug704320_counter.sjs
+ bug819051.sjs
+ bug1576154.sjs
+ chrome/bug418986-1.js
+ copypaste.js
+ delayedServerEvents.sjs
+ eventsource_message.sjs
+ eventsource_reconnect.sjs
+ eventsource.resource
+ eventsource.resource^headers^
+ eventsource_redirect.resource
+ eventsource_redirect.resource^headers^
+ eventsource_redirect_to.resource
+ eventsource_redirect_to.resource^headers^
+ eventsource_worker.js
+ file_bug1091883_frame.html
+ file_bug1091883_subframe.html
+ file_bug1091883_target.html
+ file_bug28293.sjs
+ file_bug326337.xml
+ file_bug326337_inner.html
+ file_bug326337_outer.html
+ file_bug416317.xhtml
+ file_bug426646-1.html
+ file_bug426646-2.html
+ file_bug428847-1.xhtml
+ file_bug428847-2.xhtml
+ file_bug498897.css
+ file_bug498897.html
+ file_bug498897.html^headers^
+ file_bug503481.sjs
+ file_bug503481b_inner.html
+ file_bug541937.html
+ file_bug541937.xhtml
+ file_bug557892.html
+ file_bug562137.txt
+ file_bug590812-ref.xhtml
+ file_bug590812.xml
+ file_bug590870.html
+ file_bug601803a.html
+ file_bug601803b.html
+ file_bug604660-1.xml
+ file_bug604660-2.xsl
+ file_bug604660-3.js
+ file_bug604660-4.js
+ file_bug604660-5.xml
+ file_bug604660-6.xsl
+ file_bug622088.sjs
+ file_bug622088_inner.html
+ file_bug675121.sjs
+ file_bug687859-16.js
+ file_bug687859-16.js^headers^
+ file_bug687859-bom.js
+ file_bug687859-bom.js^headers^
+ file_bug687859-charset.js
+ file_bug687859-http.js
+ file_bug687859-http.js^headers^
+ file_bug687859-inherit.js
+ file_bug692434.xml
+ file_bug704320_preload_attr.html
+ file_bug704320_preload_common.js
+ file_bug704320_preload_reuse.html
+ file_bug704320_redirect.html
+ file_bug707142_baseline.json
+ file_bug707142_bom.json
+ file_bug707142_utf-16.json
+ file_bug708620-2.html
+ file_bug708620.html
+ file_bug753278.html
+ file_bug769117.html
+ file_bug782342.txt
+ file_bug787778.sjs
+ file_bug869432.eventsource
+ file_bug869432.eventsource^headers^
+ file_bug907892.html
+ file_bug945152.jar
+ file_bug1274806.html
+ file_current_inner_window.html
+ file_domwindowutils_animation.html
+ file_domwindowutils_dynamic_toolbar.html
+ file_focus_shadow_dom.html
+ file_general_document.html
+ file_history_document_open.html
+ file_htmlserializer_1.html
+ file_htmlserializer_1_bodyonly.html
+ file_htmlserializer_1_format.html
+ file_htmlserializer_1_linebreak.html
+ file_htmlserializer_1_links.html
+ file_htmlserializer_1_nested_body.html
+ file_htmlserializer_1_no_body.html
+ file_htmlserializer_1_noflag.html
+ file_htmlserializer_1_noformatpre.html
+ file_htmlserializer_1_raw.html
+ file_htmlserializer_1_sibling_body.html
+ file_htmlserializer_1_sibling_body_only_body.html
+ file_htmlserializer_1_wrap.html
+ file_htmlserializer_2.html
+ file_htmlserializer_2_basic.html
+ file_htmlserializer_ipv6.html
+ file_htmlserializer_ipv6_out.html
+ file_lock_orientation_with_pending_fullscreen.html
+ file_mozfiledataurl_img.jpg
+ file_restrictedEventSource.sjs
+ file_settimeout_inner.html
+ file_timer_flood.html
+ file_viewport_scroll_quirks.html
+ file_viewport_scroll_xml.xml
+ file_window_close.html
+ file_window_close_2.html
+ file_x-frame-options_main.html
+ file_x-frame-options_page.sjs
+ file_xhtmlserializer_1.xhtml
+ file_xhtmlserializer_1_bodyonly.xhtml
+ file_xhtmlserializer_1_format.xhtml
+ file_xhtmlserializer_1_linebreak.xhtml
+ file_xhtmlserializer_1_links.xhtml
+ file_xhtmlserializer_1_nested_body.xhtml
+ file_xhtmlserializer_1_no_body.xhtml
+ file_xhtmlserializer_1_noflag.xhtml
+ file_xhtmlserializer_1_noformatpre.xhtml
+ file_xhtmlserializer_1_raw.xhtml
+ file_xhtmlserializer_1_sibling_body.xhtml
+ file_xhtmlserializer_1_sibling_body_only_body.xhtml
+ file_xhtmlserializer_1_wrap.xhtml
+ file_xhtmlserializer_2.xhtml
+ file_xhtmlserializer_2_basic.xhtml
+ file_xhtmlserializer_2_enthtml.xhtml
+ file_xhtmlserializer_2_entw3c.xhtml
+ file_xhtmlserializer_2_latin1.xhtml
+ file_youtube_flash_embed.html
+ forRemoval.resource
+ forRemoval.resource^headers^
+ formReset.html
+ invalid_accesscontrol.resource
+ invalid_accesscontrol.resource^headers^
+ script-1_bug597345.sjs
+ script-2_bug597345.js
+ script_bug602838.sjs
+ send_gzip_content.sjs
+ somedatas.resource
+ somedatas.resource^headers^
+ variable_style_sheet.sjs
+ w3element_traversal.svg
+ wholeTexty-helper.xml
+ referrerHelper.js
+ file_audioLoop.html
+ file_webaudio_startstop.html
+ referrer_helper.js
+ referrer_testserver.sjs
+ script_postmessages_fileList.js
+ common_postMessages.js
+ iframe_postMessages.html
+ worker_postMessages.js
+ test_anonymousContent_style_csp.html^headers^
+ file_explicit_user_agent.sjs
+ referrer_change_server.sjs
+ file_change_policy_redirect.html
+ file_bug1198095.js
+ file_bug1250148.sjs
+ file_bug1268962.sjs
+ iframe_meta_refresh.sjs
+ !/dom/security/test/cors/file_CrossSiteXHR_server.sjs
+ !/image/test/mochitest/blue.png
+ script_bug1238440.js
+ intersectionobserver_iframe.html
+ intersectionobserver_cross_domain_iframe.html
+ intersectionobserver_window.html
+ object_bug353334.html
+ embed_bug455472.html
+ object_bug455472.html
+ iframe1_bug431701.html
+ iframe2_bug431701.html
+ iframe3_bug431701.html
+ iframe4_bug431701.xml
+ iframe5_bug431701.xml
+ iframe6_bug431701.xml
+ iframe7_bug431701.xml
+ iframe1_bug426646.html
+ iframe2_bug426646.html
+ iframe_shared_compartment2a.html
+ iframe_shared_compartment2b.html
+ file1_setting_opener.html
+ file2_setting_opener.html
+ file3_setting_opener.html
+ file4_setting_opener.html
+ PASS.html
+ FAIL.html
+ !/dom/animation/test/testcommon.js
+ !/dom/events/test/event_leak_utils.js
+ ../../../toolkit/components/pdfjs/test/file_pdfjs_test.pdf
+ green.png
+ slow.sjs
+
+[test_anchor_area_referrer.html]
+skip-if =
+ http3
+[test_anchor_area_referrer_changing.html]
+skip-if =
+ http3
+[test_anchor_area_referrer_invalid.html]
+skip-if =
+ http3
+[test_anchor_area_referrer_rel.html]
+skip-if =
+ http3
+[test_anonymousContent_api.html]
+[test_anonymousContent_append_after_reflow.html]
+[test_anonymousContent_canvas.html]
+skip-if = headless # Bug 1405867
+[test_anonymousContent_insert.html]
+[test_anonymousContent_manipulate_content.html]
+[test_anonymousContent_set_style.html]
+[test_anonymousContent_style_csp.html]
+[test_appname_override.html]
+[test_async_setTimeout_stack.html]
+[test_async_setTimeout_stack_across_globals.html]
+[test_base.xhtml]
+skip-if =
+ http3
+[test_bug1433073.html]
+skip-if = (os == "android" || headless) # See
+[test_bug1730284.html]
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1632196 and
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1632438.
+[test_bug1739957.html]
+support-files =
+ bug1739957.sjs
+[test_bug1784187.html]
+[test_bug1799354.html]
+[test_bug5141.html]
+[test_bug28293.html]
+[test_bug28293.xhtml]
+[test_bug51034.html]
+[test_bug116083.html]
+skip-if = headless # fails in clipboard mode
+[test_bug166235.html]
+skip-if = headless # headless != clipboard
+[test_bug1639328.html]
+support-files = file_bug1639328.html
+[test_bug199959.html]
+[test_bug218236.html]
+[test_bug218277.html]
+[test_bug238409.html]
+[test_bug254337.html]
+[test_bug270145.xhtml]
+[test_bug276037-1.html]
+[test_bug276037-2.xhtml]
+[test_bug282547.html]
+[test_bug298064.html]
+[test_bug300992.html]
+[test_bug311681.xml]
+[test_bug313646.html]
+[test_bug320799.html]
+[test_bug322317.html]
+[test_bug326337.html]
+skip-if =
+ http3
+[test_bug331959.html]
+[test_bug333064.html]
+skip-if = headless # Headless: Bug 1405868
+[test_bug333198.html]
+[test_bug333673.html]
+[test_bug337631.html]
+[test_bug338541.xhtml]
+[test_bug338583.html]
+skip-if =
+ http3
+[test_bug338679.html]
+[test_bug339494.html]
+[test_bug339494.xhtml]
+[test_bug343596.html]
+[test_bug345339.html]
+skip-if =
+ http3
+[test_bug346485.html]
+[test_bug352728.html]
+[test_bug352728.xhtml]
+[test_bug353334.html]
+[test_bug355026.html]
+[test_bug357450.html]
+support-files = file_bug357450.js
+[test_bug357450.xhtml]
+[test_bug357450_svg.xhtml]
+[test_bug357509.html]
+[test_bug358660.html]
+[test_bug362391.xhtml]
+[test_bug364092.xhtml]
+[test_bug364413.xhtml]
+[test_bug366944.html]
+[test_bug366946.html]
+[test_bug367164.html]
+[test_bug368972.html]
+[test_bug371576-2.html]
+[test_bug371576-3.html]
+[test_bug371576-4.html]
+[test_bug371576-5.html]
+[test_bug372086.html]
+[test_bug372964-2.html]
+[test_bug372964.html]
+[test_bug373181.xhtml]
+[test_bug375314.html]
+[test_bug375314-2.html]
+skip-if =
+ xorigin # Hangs, [Exception... "Component returned failure code: 0xc1f30100 (NS_ERROR_FACTORY_EXISTS) [nsIComponentRegistrar.registerFactory]" nsresult: "0xc1f30100 (NS_ERROR_FACTORY_EXISTS)" location: "JS frame :: createChromeScript :: createChromeScript :: line 40" data: no]
+ http3
+[test_bug378969.html]
+[test_bug380418.html]
+support-files = test_bug380418.html^headers^
+[test_bug382113.html]
+[test_bug382871.html]
+[test_bug384003.xhtml]
+[test_bug390219.html]
+[test_bug390735.html]
+[test_bug392318.html]
+[test_bug392511.html]
+[test_bug393968.html]
+[test_bug395915.html]
+[test_bug397234.html]
+[test_bug398243.html]
+skip-if =
+ http3
+[test_bug401662.html]
+[test_bug402150.html]
+support-files = test_bug402150.html^headers^
+[test_bug403841.html]
+[test_bug403852.html]
+[test_bug403868.xml]
+[test_bug405182.html]
+[test_bug409380.html]
+[test_bug410229.html]
+[test_bug413974.html]
+[test_bug414190.html]
+[test_bug415860.html]
+[test_bug416317-1.html]
+[test_bug416317-2.html]
+[test_bug416383.html]
+[test_bug417255.html]
+[test_bug417384.html]
+[test_bug418214.html]
+[test_bug418986-1.html]
+[test_bug419132.html]
+[test_bug420609.xhtml]
+[test_bug420700.html]
+[test_bug421602.html]
+[test_bug422403-1.html]
+skip-if =
+ http3
+[test_bug422403-2.xhtml]
+[test_bug422537.html]
+skip-if =
+ http3
+[test_bug424212.html]
+[test_bug424359-1.html]
+skip-if =
+ http3
+[test_bug424359-2.html]
+[test_bug426308.html]
+skip-if =
+ http3
+[test_bug426646.html]
+skip-if =
+ http3
+[test_bug428847.html]
+skip-if =
+ http3
+[test_bug431082.html]
+[test_bug431701.html]
+[test_bug431833.html]
+[test_bug433533.html]
+[test_bug433662.html]
+[test_bug435425.html]
+skip-if =
+ http3
+[test_bug444322.html]
+[test_bug444546.html]
+disabled = Disabled for now. Mochitest is not reliable enough for these.
+support-files = bug444546.sjs
+[test_bug444722.html]
+[test_bug448993.html]
+[test_bug450160.html]
+[test_bug451376.html]
+[test_bug453521.html]
+[test_bug453736.html]
+[test_bug454325.html]
+[test_bug454326.html]
+[test_bug455472.html]
+[test_bug455629.html]
+skip-if =
+ http3
+[test_bug456262.html]
+[test_bug457746.html]
+[test_bug459424.html]
+[test_bug461555.html]
+[test_bug461735.html]
+skip-if =
+ http3
+[test_bug465767.html]
+[test_bug466080.html]
+skip-if =
+ http3
+[test_bug466409.html]
+[test_bug466751.xhtml]
+[test_bug469020.html]
+[test_bug469304.html]
+[test_bug473162-1.html]
+[test_bug473162-2.html]
+[test_bug475156.html]
+skip-if =
+ http3
+[test_bug482935.html]
+[test_bug484396.html]
+[test_bug493881.html]
+support-files = test_bug493881.js
+[test_bug498240.html]
+[test_bug498433.html]
+[test_bug498897.html]
+[test_bug499656.html]
+[test_bug499656.xhtml]
+[test_bug500937.html]
+[test_bug503473.html]
+disabled = Disabled due to making the harness time out
+support-files = file_bug503473-frame.sjs
+[test_bug503481.html]
+[test_bug503481b.html]
+[test_bug513194.html]
+[test_bug514487.html]
+[test_bug515401.html]
+skip-if =
+ http3
+[test_bug518104.html]
+support-files = file_bug518104.js
+[test_bug527896.html]
+[test_bug540854.html]
+[test_bug541937.html]
+[test_bug544642.html]
+[test_bug545644.html]
+[test_bug545644.xhtml]
+[test_bug548463.html]
+[test_bug553896.xhtml]
+[test_bug557892.html]
+[test_bug558726.html]
+[test_bug559526.html]
+[test_bug560780.html]
+[test_bug562137.html]
+[test_bug562169-1.html]
+[test_bug562169-2.html]
+[test_bug562652.html]
+[test_bug564047.html]
+[test_bug564863.xhtml]
+[test_bug567350.html]
+[test_bug574596.html]
+skip-if = toolkit == 'android'
+[test_bug578096.html]
+skip-if = (verify && (os == 'win'))
+[test_bug585978.html]
+[test_bug587931.html]
+[test_bug588990.html]
+[test_bug590812.html]
+skip-if =
+ (verify && !debug && (os == 'linux')) #bug 687032
+ http3
+[test_bug590870.html]
+skip-if =
+ http3
+[test_bug592366.html]
+[test_bug592829.html]
+[test_bug597345.html]
+[test_bug599295.html]
+skip-if =
+ http3
+[test_bug599588.html]
+[test_bug601803.html]
+skip-if =
+ http3
+[test_bug602838.html]
+[test_bug604592.html]
+[test_bug604660.html]
+[test_bug605982.html]
+[test_bug606729.html]
+[test_bug614058.html]
+[test_bug622088.html]
+[test_bug622117.html]
+support-files = !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_bug622246.html]
+support-files = !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_bug625722.html]
+[test_bug626262.html]
+[test_bug628938.html]
+[test_bug631615.html]
+[test_bug638112.html]
+skip-if =
+ http3
+[test_bug647518.html]
+[test_bug650001.html]
+[test_bug650776.html]
+[test_bug650784.html]
+[test_bug656283.html]
+[test_bug664916.html]
+[test_bug666604.html]
+[test_bug675121.html]
+[test_bug675166.html]
+[test_bug682463.html]
+[test_bug682554.html]
+[test_bug682592.html]
+[test_bug684671.html]
+[test_bug685798.html]
+[test_bug686449.xhtml]
+[test_bug687859.html]
+[test_bug690056.html]
+[test_bug692434.html]
+[test_bug693615.html]
+[test_bug693875.html]
+[test_bug694754.xhtml]
+[test_bug696301-1.html]
+skip-if =
+ http3
+[test_bug696301-2.html]
+skip-if =
+ http3
+[test_bug698381.html]
+[test_bug698384.html]
+[test_bug704063.html]
+[test_bug704320-1.html]
+skip-if =
+ http3
+[test_bug704320-2.html]
+skip-if =
+ http3
+[test_bug704320_policyset.html]
+skip-if =
+ http3
+[test_bug704320_policyset2.html]
+[test_bug704320_preload.html]
+skip-if =
+ http3
+[test_bug707142.html]
+[test_bug708620.html]
+[test_bug711047.html]
+[test_bug711180.html]
+[test_bug719533.html]
+[test_bug726364.html]
+[test_bug737087.html]
+[test_bug737565.html]
+[test_bug737612.html]
+[test_bug738108.html]
+[test_bug744830.html]
+[test_bug749367.html]
+[test_bug750096.html]
+[test_bug753278.html]
+[test_bug761120.html]
+[test_bug769117.html]
+skip-if =
+ fission && os == "android" # Bug 1827725
+[test_bug782342.html]
+[test_bug787778.html]
+[test_bug789315.html]
+[test_bug789856.html]
+[test_bug809003.html]
+[test_bug810494.html]
+[test_bug811701.html]
+[test_bug811701.xhtml]
+[test_bug813919.html]
+[test_bug814576.html]
+[test_bug819051.html]
+skip-if =
+ http3
+[test_bug820909.html]
+[test_bug864595.html]
+[test_bug868999.html]
+[test_bug869000.html]
+[test_bug869002.html]
+[test_bug869006.html]
+[test_bug876282.html]
+[test_bug891952.html]
+[test_bug894874.html]
+[test_bug895974.html]
+[test_bug907892.html]
+skip-if =
+ http3
+[test_bug913761.html]
+[test_bug922681.html]
+[test_bug927196.html]
+[test_bug962251.html]
+[test_bug976673.html]
+skip-if =
+ http3
+[test_bug982153.html]
+[test_bug999456.html]
+[test_bug1022229.html]
+skip-if =
+ http3
+[test_bug1025933.html]
+[test_bug1037687.html]
+support-files = test_bug1037687_subframe.html
+[test_bug1043106.html]
+[test_bug1057176.html]
+[test_bug1060938.html]
+[test_bug1064481.html]
+[test_bug1070015.html]
+[test_bug1075702.html]
+[test_bug1091883.html]
+skip-if =
+ http3
+[test_bug1100912.html]
+support-files = file_bug1100912.html
+[test_bug1101364.html]
+[test_bug1118689.html]
+[test_bug1126851.html]
+[test_bug1163743.html]
+skip-if =
+ http3
+[test_bug1165501.html]
+skip-if =
+ http3
+[test_bug1187157.html]
+[test_bug1198095.html]
+[test_bug1238440.html]
+[test_bug1250148.html]
+[test_bug1259588.html]
+[test_bug1268962.html]
+skip-if =
+ http3
+[test_bug1222633.html]
+skip-if =
+ http3
+[test_bug1667316.html]
+[test_bug1222633_link_update.html]
+skip-if =
+ http3
+[test_bug1274806.html]
+[test_bug1281963.html]
+[test_bug1295852.html]
+[test_bug1307730.html]
+[test_bug1308069.html]
+[test_bug1314032.html]
+[test_bug1318303.html]
+[test_bug1375050.html]
+[test_bug1381710.html]
+[test_bug1399605.html]
+[test_bug1404385.html]
+[test_bug1406102.html]
+[test_bug1421568.html]
+[test_bug1472427.html]
+[test_bug1499169.html]
+skip-if = toolkit == 'android' # Timeouts on android due to page closing issues with embedded pdf
+[test_bug1576154.html]
+[test_bug1632975.html]
+[test_bug1640766.html]
+support-files =
+ iframe1_bug1640766.html
+ iframe2_bug1640766.html
+skip-if =
+ http3
+[test_bug1648887.html]
+[test_caretPositionFromPoint.html]
+[test_change_policy.html]
+skip-if =
+ http3
+[test_clearTimeoutIntervalNoArg.html]
+[test_clipboard_nbsp.html]
+[test_constructor-assignment.html]
+[test_constructor.html]
+[test_content_iterator_post_order.html]
+[test_content_iterator_pre_order.html]
+[test_content_iterator_subtree.html]
+[test_copyimage.html]
+skip-if = toolkit == 'android'
+ headless #bug 904183
+[test_copypaste.html]
+skip-if = toolkit == 'android'
+ headless #bug 904183
+[test_copypaste.xhtml]
+skip-if = headless #bug 904183
+[test_copypaste_disabled.html]
+support-files = !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_createHTMLDocument.html]
+[test_data_uri.html]
+[test_document.all_iteration.html]
+[test_document.all_unqualified.html]
+[test_document_constructor.html]
+[test_document_importNode_document.html]
+[test_custom_element.html]
+[test_custom_element_reflector.html]
+[test_current_inner_window.html]
+skip-if =
+ http3
+[test_document_wireframe.html]
+skip-if =
+ !sessionHistoryInParent
+ http3
+[test_domparser_null_char.html]
+[test_domparsing.html]
+[test_domrequest.html]
+[test_domwindowutils.html]
+skip-if = toolkit == 'android' # Bug 1525959
+[test_element.matches.html]
+[test_element_closest.html]
+[test_elementTraversal.html]
+[test_embed_xorigin_document.html]
+skip-if =
+ http3
+[test_encodeToStringWithMaxLength.html]
+[test_encodeToStringWithRequiresReinitAfterOutput.html]
+[test_EventSource_redirects.html]
+skip-if =
+ http3
+[test_eventsource_event_listener_leaks.html]
+[test_eventsourceservice_basic.html]
+skip-if =
+ http3
+[test_eventsourceservice_reconnect_error.html]
+skip-if =
+ http3
+[test_eventsourceservice_status_error.html]
+[test_eventsourceservice_worker.html]
+skip-if =
+ http3
+[test_explicit_user_agent.html]
+[test_find.html]
+[test_find_nac.html]
+[test_find_bug1601118.html]
+[test_find_bug1654683.html]
+[test_focus_keyboard_event.html]
+skip-if =
+ http3
+[test_focus_shadow_dom_root.html]
+[test_focus_shadow_dom.html]
+[test_focus_scrollable_input.html]
+[test_focus_scrollable_fieldset.html]
+[test_focus_scroll_padding_tab.html]
+[test_focus_design_mode.html]
+support-files =
+ file_focus_design_mode_inner.html
+[test_focus_display_none_xorigin_iframe.html]
+support-files =
+ file_focus_display_none_xorigin_iframe_inner.html
+skip-if =
+ http3
+[test_getAttribute_after_createAttribute.html]
+[test_getElementById.html]
+[test_getTranslationNodes.html]
+[test_getTranslationNodes_limit.html]
+[test_gsp-qualified.html]
+[test_gsp-quirks.html]
+[test_gsp-standards.html]
+[test_history_document_open.html]
+[test_history_state_null.html]
+[test_html_colors_quirks.html]
+[test_html_colors_standards.html]
+[test_htmlcopyencoder.html]
+[test_htmlcopyencoder.xhtml]
+[test_iframe_event_listener_leaks.html]
+skip-if = (processor == 'aarch64' && os == 'win') # aarch64 due to 1530895
+[test_iframe_referrer.html]
+skip-if =
+ http3
+[test_iframe_referrer_changing.html]
+skip-if =
+ http3
+[test_iframe_referrer_invalid.html]
+skip-if =
+ http3
+[test_Image_constructor.html]
+[test_innersize_scrollport.html]
+[test_input_vsync_alignment_lower_than_normal.html]
+[test_input_vsync_alignment_input_while_vsync.html]
+[test_input_vsync_alignment_inner_event_loop.html]
+[test_integer_attr_with_leading_zero.html]
+[test_intersectionobservers.html]
+skip-if =
+ http3
+[test_link_prefetch.html]
+skip-if =
+ http3
+[test_link_preload.html]
+skip-if =
+ http3
+[test_link_stylesheet.html]
+skip-if =
+ http3
+[test_location_href_unknown_protocol.html]
+support-files = file_location_href_unknown_protocol.html
+[test_lock_orientation_after_fullscreen.html]
+skip-if = toolkit != 'android' # Only run on Android.
+[test_lock_orientation_with_pending_fullscreen.html]
+skip-if = os == 'mac' || os == 'linux' || headless
+[test_meta_refresh_referrer.html]
+skip-if =
+ http3
+[test_mozMatchesSelector.html]
+[test_mutationobservers.html]
+[test_named_frames.html]
+[test_navigator_cookieEnabled.html]
+[test_navigator_hardwareConcurrency.html]
+[test_navigator_language.html]
+[test_navigatorPrefOverride.html]
+[test_NodeIterator_basics_filters.xhtml]
+skip-if = xorigin # JavaScript error: http://mochi.test:8888/tests/SimpleTest/SimpleTest.js, line 76: DataCloneError: The object could not be cloned.
+[test_NodeIterator_mutations_1.xhtml]
+[test_NodeIterator_mutations_2.html]
+[test_NodeIterator_mutations_3.html]
+[test_nested_event_loop_spin_and_idle_tasks.html]
+[test_nodelist_holes.html]
+[test_open_null_features.html]
+[test_openDialogChromeOnly.html]
+tags = openwindow
+[test_pasting_svg_image.html]
+skip-if = headless # Bug 1669923.
+[test_pdf_print.html]
+skip-if = toolkit == 'android' # We don't ship pdf.js on Android
+[test_plugin_freezing.html]
+skip-if = (os == 'win' && processor == 'aarch64')
+reason = Plugins are not supported on Windows/AArch64
+[test_postMessage_solidus.html]
+skip-if =
+ http3
+[test_postMessages_window.html]
+skip-if =
+ http3
+[test_postMessages_workers.html]
+[test_postMessages_broadcastChannel.html]
+[test_postMessages_messagePort.html]
+[test_postMessage_originAttributes.html]
+support-files = file_receiveMessage.html
+skip-if = true # Uses mismatched OriginAttributes for iframe (bug 1616353)
+[test_processing_instruction_update_stylesheet.xhtml]
+[test_progress_events_for_gzip_data.html]
+skip-if = tsan # Bug 1621323
+[test_pushState_structuredclone.html]
+scheme = https
+[test_range_bounds.html]
+[test_reentrant_flush.html]
+[test_root_iframe.html]
+[test_sandbox_and_document_uri.html]
+support-files = file_sandbox_and_document_uri.html
+[test_screen_orientation.html]
+[test_script_loader_crossorigin_data_url.html]
+[test_script_loader_js_cache.html]
+skip-if = verify
+support-files =
+ file_js_cache.html
+ file_js_cache_with_sri.html
+ file_js_cache_module.html
+ file_js_cache.js
+ file_module_js_cache.html
+ file_module_js_cache_with_sri.html
+ file_module_js_cache_no_module.html
+ file_module_js_cache.js
+ file_js_cache_save_after_load.html
+ file_js_cache_save_after_load.js
+ file_js_cache_syntax_error.html
+ file_js_cache_syntax_error.js
+[test_script_loader_js_cache_module.html]
+skip-if = verify
+support-files =
+ file_script_module_single.html
+ file_script_module_single.js
+ file_script_module_import.html
+ file_script_module_import.js
+ file_script_module_import_imported.js
+ file_script_module_import_multi.html
+ file_script_module_import_multi.js
+ file_script_module_import_multi_imported_once.js
+ file_script_module_import_multi_imported_twice.js
+ file_script_module_import_multi_elems.html
+ file_script_module_import_multi_elems_1.js
+ file_script_module_import_multi_elems_2.js
+ file_script_module_import_multi_elems_imported_once_1.js
+ file_script_module_import_multi_elems_imported_once_2.js
+ file_script_module_import_multi_elems_imported_once_3.js
+ file_script_module_import_multi_elems_imported_twice.js
+ file_script_module_import_and_element.html
+ file_script_module_import_and_element.js
+ file_script_module_import_and_element_imported_1.js
+ file_script_module_import_and_element_imported_2.js
+ file_script_module_import_and_element_imported_3.js
+ file_script_module_element_and_import.html
+ file_script_module_element_and_import.js
+ file_script_module_element_and_import_imported_1.js
+ file_script_module_element_and_import_imported_2.js
+ file_script_module_element_and_import_imported_3.js
+ file_script_module_dynamic_import.html
+ file_script_module_dynamic_import.js
+ file_script_module_dynamic_import_imported.js
+ file_script_module_dynamic_and_element.html
+ file_script_module_dynamic_and_element.js
+ file_script_module_dynamic_and_element_imported_1.js
+ file_script_module_dynamic_and_element_imported_2.js
+ file_script_module_dynamic_and_element_imported_3.js
+ file_script_module_element_and_dynamic.html
+ file_script_module_element_and_dynamic.js
+ file_script_module_element_and_dynamic_imported_1.js
+ file_script_module_element_and_dynamic_imported_2.js
+ file_script_module_element_and_dynamic_imported_3.js
+ file_script_module_dynamic_and_static.html
+ file_script_module_dynamic_and_static.js
+ file_script_module_dynamic_and_static_imported_1.js
+ file_script_module_dynamic_and_static_imported_2.js
+ file_script_module_dynamic_and_static_imported_3.js
+ file_script_module_static_and_dynamic.html
+ file_script_module_static_and_dynamic.js
+ file_script_module_static_and_dynamic_imported_1.js
+ file_script_module_static_and_dynamic_imported_2.js
+ file_script_module_static_and_dynamic_imported_3.js
+[test_script_loader_js_cache_module_sri.html]
+skip-if = verify
+support-files =
+ file_script_module_sri_basic.html
+ file_script_module_sri_basic_prep.html
+ file_script_module_sri_basic.js
+ file_script_module_sri_fallback.html
+ file_script_module_sri_fallback_prep.html
+ file_script_module_sri_fallback.js
+ file_script_module_sri_fallback_failure.html
+ file_script_module_sri_fallback_failure_prep.html
+ file_script_module_sri_fallback_failure.js
+ file_script_module_sri_elem_elem_1.html
+ file_script_module_sri_elem_elem_1_prep.html
+ file_script_module_sri_elem_elem_1.js
+ file_script_module_sri_elem_elem_2.html
+ file_script_module_sri_elem_elem_2_prep.html
+ file_script_module_sri_elem_elem_2.js
+ file_script_module_sri_elem_import.html
+ file_script_module_sri_elem_import_prep.html
+ file_script_module_sri_elem_import.js
+ file_script_module_sri_elem_import_imported.js
+ file_script_module_sri_import_elem.html
+ file_script_module_sri_import_elem_prep.html
+ file_script_module_sri_import_elem.js
+ file_script_module_sri_import_elem_imported.js
+ file_script_module_sri_import_elem_nopreload.html
+ file_script_module_sri_import_elem_nopreload_prep.html
+ file_script_module_sri_import_elem_nopreload.js
+ file_script_module_sri_import_elem_nopreload_imported.js
+ file_script_module_sri_elem_dynamic.html
+ file_script_module_sri_elem_dynamic_prep.html
+ file_script_module_sri_elem_dynamic.js
+ file_script_module_sri_elem_dynamic_imported.js
+ file_script_module_sri_dynamic_elem.html
+ file_script_module_sri_dynamic_elem_prep.html
+ file_script_module_sri_dynamic_elem.js
+ file_script_module_sri_dynamic_elem_imported.js
+ file_script_module_sri_dynamic_elem_nopreload.html
+ file_script_module_sri_dynamic_elem_nopreload_prep.html
+ file_script_module_sri_dynamic_elem_nopreload.js
+ file_script_module_sri_dynamic_elem_nopreload_imported.js
+[test_script_loader_js_cache_frames.html]
+skip-if = verify
+support-files =
+ file_script_module_frames_relay.js
+ file_script_module_frames_element.html
+ file_script_module_frames_element_save.html
+ file_script_module_frames_element_load.html
+ file_script_module_frames_element_shared.js
+ file_script_module_frames_import.html
+ file_script_module_frames_import_save.html
+ file_script_module_frames_import_save.js
+ file_script_module_frames_import_load.html
+ file_script_module_frames_import_load.js
+ file_script_module_frames_import_shared.js
+ file_script_module_frames_dynamic.html
+ file_script_module_frames_dynamic_save.html
+ file_script_module_frames_dynamic_save.js
+ file_script_module_frames_dynamic_load.html
+ file_script_module_frames_dynamic_load.js
+ file_script_module_frames_dynamic_shared.js
+[test_delazification_strategy.html]
+skip-if = (verify || ccov)
+support-files =
+ file_delazification_strategy.html
+ file_delazification_strategy.js
+[test_setInterval_from_start.html]
+[test_setInterval_uncatchable_exception.html]
+skip-if = debug == false
+[test_settimeout_extra_arguments.html]
+[test_settimeout_inner.html]
+[test_setTimeoutWith0.html]
+[test_setting_opener.html]
+[test_shared_compartment1.html]
+skip-if =
+ http3
+[test_shared_compartment2.html]
+skip-if =
+ http3
+[test_structuredclone_backref.html]
+[test_structuredclone_error.html]
+[test_style_cssText.html]
+[test_suppressed_events_and_scrolling.html]
+support-files =
+ file_suppressed_events_and_scrolling.html
+[test_suppressed_events_nested_iframe.html]
+skip-if =
+ os == "android"
+ http3
+support-files =
+ file_suppressed_events_top_xhr.html
+ file_suppressed_events_top_modalstate.html
+ file_suppressed_events_top.html
+ file_suppressed_events_middle.html
+ file_suppressed_events_inner.html
+ !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_suppressed_microtasks.html]
+skip-if =
+ debug || asan || verify || toolkit == 'android' # The test needs to run reasonably fast.
+[test_text_wholeText.html]
+[test_textnode_normalize_in_selection.html]
+[test_textnode_split_in_selection.html]
+[test_timeout_clamp.html]
+[test_timer_flood.html]
+[test_title.html]
+support-files = file_title.xhtml
+[test_toScreenRect.html]
+support-files = file_toScreenRect.html
+[test_treewalker_nextsibling.xml]
+[test_user_select.html]
+skip-if = os == "android" # Bug 1791049
+[test_viewport_metrics_on_landscape_content.html]
+support-files =
+ file_viewport_metrics_on_landscape_content.html
+[test_viewport_scroll.html]
+[test_viewsource_forbidden_in_object.html]
+[test_w3element_traversal.html]
+[test_w3element_traversal.xhtml]
+[test_w3element_traversal_svg.html]
+[test_warning_for_blocked_cross_site_request.html]
+skip-if =
+ http3
+[test_window_close.html]
+[test_window_constructor.html]
+[test_window_content.html]
+[test_window_cross_origin_props.html]
+skip-if =
+ http3
+[test_window_define_nonconfigurable.html]
+[test_window_define_symbol.html]
+[test_window_element_enumeration.html]
+[test_window_enumeration.html]
+[test_window_extensible.html]
+[test_window_focus_by_close_and_open.html]
+support-files = file_window_focus_by_close_and_open.html
+[test_window_indexing.html]
+[test_window_keys.html]
+[test_window_named_frame_enumeration.html]
+skip-if =
+ http3
+[test_window_own_props.html]
+[test_window_proto.html]
+[test_writable-replaceable.html]
+[test_x-frame-options.html]
+skip-if =
+ toolkit == 'android' && debug
+ xorigin # JavaScript error: http://mochi.test:8888/tests/dom/base/test/test_x-frame-options.html, line 48: TypeError: can't access property "textContent", this.content.document.getElementById(...) is null, JavaScript error: resource://gre/modules/ProcessSelector.jsm, line 56: TypeError: can't access property "tabCount", process is null
+ http3
+[test_youtube_flash_embed.html]
+skip-if =
+ http3
+# Please keep alphabetical order.
diff --git a/dom/base/test/moz.build b/dom/base/test/moz.build
new file mode 100644
index 0000000000..37cc3ab239
--- /dev/null
+++ b/dom/base/test/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/xpcshell.ini",
+ "unit_ipc/xpcshell.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "chrome.ini",
+ "chrome/chrome.ini",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.ini",
+ "fmm/browser.ini",
+]
+
+TEST_DIRS += [
+ "fullscreen",
+ "gtest",
+ "jsmodules",
+ "jsmodules/importmaps",
+ "useractivation",
+ "meta_viewport",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.base.test.chrome += [
+ "chrome/bug421622-referer.sjs",
+ "chrome/bug884693.sjs",
+ "chrome/nochrome_bug1346936.html",
+ "chrome/nochrome_bug1346936.js",
+ "chrome/nochrome_bug1346936.js^headers^",
+ "chrome/nochrome_bug765993.html",
+ "chrome/nochrome_bug765993.js",
+ "chrome/nochrome_bug765993.js^headers^",
+]
diff --git a/dom/base/test/object_bug353334.html b/dom/base/test/object_bug353334.html
new file mode 100644
index 0000000000..8e73c916c6
--- /dev/null
+++ b/dom/base/test/object_bug353334.html
@@ -0,0 +1 @@
+<body>test</body>
diff --git a/dom/base/test/object_bug455472.html b/dom/base/test/object_bug455472.html
new file mode 100644
index 0000000000..b2f3ae4f44
--- /dev/null
+++ b/dom/base/test/object_bug455472.html
@@ -0,0 +1 @@
+<script>parent.ran[1]=true</script>
diff --git a/dom/base/test/red.png b/dom/base/test/red.png
new file mode 100644
index 0000000000..a6e195d59c
--- /dev/null
+++ b/dom/base/test/red.png
Binary files differ
diff --git a/dom/base/test/referrerHelper.js b/dom/base/test/referrerHelper.js
new file mode 100644
index 0000000000..da3097b849
--- /dev/null
+++ b/dom/base/test/referrerHelper.js
@@ -0,0 +1,343 @@
+/**
+ * Listen for notifications from the child.
+ * These are sent in case of error, or when the loads we await have completed.
+ */
+window.addEventListener("message", function (event) {
+ if (event.data == "childLoadComplete") {
+ // all loads happen, continue the test.
+ advance();
+ } else if (event.data == "childOverload") {
+ // too many loads happened in a test frame, abort.
+ ok(false, "Too many load handlers called in test.");
+ SimpleTest.finish();
+ } else if (event.data.indexOf("fail-") == 0) {
+ // something else failed in the test frame, abort.
+ ok(false, "Child failed the test with error " + event.data.substr(5));
+ SimpleTest.finish();
+ }
+});
+
+/**
+ * helper to perform an XHR.
+ */
+function doXHR(url, onSuccess, onFail) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ if (xhr.status == 200) {
+ onSuccess(xhr);
+ } else {
+ onFail(xhr);
+ }
+ };
+ xhr.open("GET", url, true);
+ xhr.send(null);
+}
+
+/**
+ * This triggers state-resetting on the counter server.
+ */
+function resetCounter() {
+ doXHR(
+ "/tests/dom/base/test/bug704320_counter.sjs?reset",
+ advance,
+ function (xhr) {
+ ok(false, "Need to be able to reset the request counter");
+ SimpleTest.finish();
+ }
+ );
+}
+
+/**
+ * Grabs the results via XHR and passes to checker.
+ */
+function checkIndividualResults(testname, expected) {
+ doXHR(
+ "/tests/dom/base/test/bug704320_counter.sjs?results",
+ function (xhr) {
+ var results = JSON.parse(xhr.responseText);
+ info(xhr.responseText);
+
+ ok(
+ "img" in results,
+ testname + " test: some image loads required in results object."
+ );
+ is(
+ results.img.count,
+ 2,
+ testname + " Test: Expected 2 loads for image requests."
+ );
+
+ expected.forEach(function (ref) {
+ ok(
+ results.img.referrers.includes(ref),
+ testname +
+ " Test: Expected " +
+ ref +
+ " referrer policy in test, results were " +
+ JSON.stringify(results.img.referrers) +
+ "."
+ );
+ });
+ advance();
+ },
+ function (xhr) {
+ ok(false, "Can't get results from the counter server.");
+ SimpleTest.finish();
+ }
+ );
+}
+
+/**
+ * Grabs the results via XHR and checks them
+ */
+function checkExpectedGlobalResults(testName) {
+ var url = "bug704320.sjs?action=get-test-results";
+ doXHR(
+ url,
+ function (xhr) {
+ var response = JSON.parse(xhr.response);
+
+ for (type in response) {
+ for (scheme in response[type]) {
+ for (policy in response[type][scheme]) {
+ var expectedResult =
+ EXPECTED_RESULTS[type] === undefined
+ ? EXPECTED_RESULTS.default[scheme][policy]
+ : EXPECTED_RESULTS[type][scheme][policy];
+ is(
+ response[type][scheme][policy],
+ expectedResult,
+ type + " " + scheme + " " + policy
+ );
+ }
+ }
+ }
+ advance(testName);
+ },
+ function (xhr) {
+ ok(false, "Can't get results from the counter server.");
+ SimpleTest.finish();
+ }
+ );
+}
+
+var EXPECTED_RESULTS = {
+ // From docshell/base/nsDocShell.cpp:
+ // "If the document containing the hyperlink being audited was not retrieved
+ // over an encrypted connection and its address does not have the same
+ // origin as "ping URL", send a referrer."
+ "link-ping": {
+ // Same-origin
+ "http-to-http": {
+ "no-referrer": "",
+ "unsafe-url": "",
+ origin: "",
+ "origin-when-cross-origin": "",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ "http-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url",
+ origin: "http://example.com/",
+ "origin-when-cross-origin": "http://example.com/",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade",
+ "same-origin": "",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin": "http://example.com/",
+ },
+ // Encrypted and not same-origin
+ "https-to-http": {
+ "no-referrer": "",
+ "unsafe-url": "",
+ origin: "",
+ "origin-when-cross-origin": "",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ // Encrypted
+ "https-to-https": {
+ "no-referrer": "",
+ "unsafe-url": "",
+ origin: "",
+ "origin-when-cross-origin": "",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ },
+ // form is tested in a 2nd level iframe.
+ form: {
+ "http-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=form",
+ origin: "http://example.com/",
+ "origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=form",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=form",
+ "same-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=same-origin&type=form",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=strict-origin-when-cross-origin&type=form",
+ },
+ "http-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=form",
+ origin: "http://example.com/",
+ "origin-when-cross-origin": "http://example.com/",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=form",
+ "same-origin": "",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin": "http://example.com/",
+ },
+ "https-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=form",
+ origin: "https://example.com/",
+ "origin-when-cross-origin": "https://example.com/",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ "https-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=form",
+ origin: "https://example.com/",
+ "origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=form",
+ "no-referrer-when-downgrade":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=form",
+ "same-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=same-origin&type=form",
+ "strict-origin": "https://example.com/",
+ "strict-origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=strict-origin-when-cross-origin&type=form",
+ },
+ },
+ // window.location is tested in a 2nd level iframe.
+ "window.location": {
+ "http-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=window.location",
+ origin: "http://example.com/",
+ "origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=window.location",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=window.location",
+ "same-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=same-origin&type=window.location",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=strict-origin-when-cross-origin&type=window.location",
+ },
+ "http-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=window.location",
+ origin: "http://example.com/",
+ "origin-when-cross-origin": "http://example.com/",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location",
+ "same-origin": "",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin": "http://example.com/",
+ },
+ "https-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=window.location",
+ origin: "https://example.com/",
+ "origin-when-cross-origin": "https://example.com/",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ "https-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=window.location",
+ origin: "https://example.com/",
+ "origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=window.location",
+ "no-referrer-when-downgrade":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location",
+ "same-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=same-origin&type=window.location",
+ "strict-origin": "https://example.com/",
+ "strict-origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=strict-origin-when-cross-origin&type=window.location",
+ },
+ },
+ default: {
+ "http-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url",
+ origin: "http://example.com/",
+ "origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade",
+ "same-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=same-origin",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=strict-origin-when-cross-origin",
+ },
+ "http-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url",
+ origin: "http://example.com/",
+ "origin-when-cross-origin": "http://example.com/",
+ "no-referrer-when-downgrade":
+ "http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade",
+ "same-origin": "",
+ "strict-origin": "http://example.com/",
+ "strict-origin-when-cross-origin": "http://example.com/",
+ },
+ "https-to-http": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url",
+ origin: "https://example.com/",
+ "origin-when-cross-origin": "https://example.com/",
+ "no-referrer-when-downgrade": "",
+ "same-origin": "",
+ "strict-origin": "",
+ "strict-origin-when-cross-origin": "",
+ },
+ "https-to-https": {
+ "no-referrer": "",
+ "unsafe-url":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url",
+ origin: "https://example.com/",
+ "origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin",
+ "no-referrer-when-downgrade":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade",
+ "same-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=same-origin",
+ "strict-origin": "https://example.com/",
+ "strict-origin-when-cross-origin":
+ "https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=strict-origin-when-cross-origin",
+ },
+ },
+};
diff --git a/dom/base/test/referrer_change_server.sjs b/dom/base/test/referrer_change_server.sjs
new file mode 100644
index 0000000000..d37cfb1e5f
--- /dev/null
+++ b/dom/base/test/referrer_change_server.sjs
@@ -0,0 +1,166 @@
+var BASE_URL = "example.com/tests/dom/base/test/referrer_change_server.sjs";
+
+function createTestUrl(aPolicy, aAction, aName) {
+ return (
+ "http://" +
+ BASE_URL +
+ "?" +
+ "action=" +
+ aAction +
+ "&" +
+ "policy=" +
+ aPolicy +
+ "&" +
+ "name=" +
+ aName +
+ "&" +
+ "type=link"
+ );
+}
+
+function createTest(aMetaPolicy, aReferrerPolicy, aName) {
+ return (
+ "<!DOCTYPE HTML>\n\
+ <html>" +
+ '<meta name="referrer" content="' +
+ aMetaPolicy +
+ '">' +
+ "<body>" +
+ '<a href="' +
+ createTestUrl(aReferrerPolicy, "test", aName + aReferrerPolicy) +
+ '" id="link">' +
+ aReferrerPolicy +
+ "</a>" +
+ "<script>" +
+ // LOAD EVENT (of the test)
+ // fires when the page is loaded, then click link
+ // first change meta referrer, then click link
+ 'window.addEventListener("load", function() {\n\
+ document.getElementsByName("referrer")[0].content = "' +
+ aReferrerPolicy +
+ '";\n\
+ document.getElementById("link").click();\n\
+ }.bind(window), false);' +
+ "</script>\n\
+ </body>\n\
+ </html>"
+ );
+}
+
+function createTest2(aMetaPolicy, aReferrerPolicy, aName) {
+ return (
+ "<!DOCTYPE HTML>\n\
+ <html>" +
+ '<meta name="referrer" content="' +
+ aMetaPolicy +
+ '">' +
+ "<body>" +
+ '<a href="' +
+ createTestUrl(aReferrerPolicy, "test", aName + aReferrerPolicy) +
+ '" id="link">' +
+ aReferrerPolicy +
+ "</a>" +
+ "<script>" +
+ // LOAD EVENT (of the test)
+ // fires when the page is loaded, then click link
+ // first change meta referrer, then click link
+ 'window.addEventListener("load", function() {\n\
+ document.getElementsByName("referrer")[0].setAttribute("content", "' +
+ aReferrerPolicy +
+ '");\n\
+ document.getElementById("link").click();\n\
+ }.bind(window), false);' +
+ "</script>\n\
+ </body>\n\
+ </html>"
+ );
+}
+
+function handleRequest(request, response) {
+ var sharedKey = "referrer_change_server.sjs";
+ var params = request.queryString.split("&");
+ var action = params[0].split("=")[1];
+
+ if (action === "resetState") {
+ var state = getSharedState(sharedKey);
+ state = {};
+ setSharedState(sharedKey, JSON.stringify(state));
+ response.write("");
+ return;
+ } else if (action === "test") {
+ // ?action=test&policy=origin&name=name
+ var policy = params[1].split("=")[1];
+ var name = params[2].split("=")[1];
+ var type = params[3].split("=")[1];
+ var result = getSharedState(sharedKey);
+
+ if (result === "") {
+ result = {};
+ } else {
+ result = JSON.parse(result);
+ }
+
+ if (!result.tests) {
+ result.tests = {};
+ }
+
+ var referrerLevel = "none";
+ var test = {};
+ if (request.hasHeader("Referer")) {
+ let referrer = request.getHeader("Referer");
+ if (referrer.indexOf("referrer_change_server") > 0) {
+ referrerLevel = "full";
+ } else if (referrer == "http://mochi.test:8888") {
+ referrerLevel = "origin";
+ }
+ test.referrer = request.getHeader("Referer");
+ } else {
+ test.referrer = "";
+ }
+ test.policy = referrerLevel;
+ test.expected = policy;
+
+ result.tests[name] = test;
+
+ setSharedState(sharedKey, JSON.stringify(result));
+
+ // forward link click to redirect URL to finish test
+ if (type === "link") {
+ var loc =
+ "https://example.com/tests/dom/base/test/file_change_policy_redirect.html";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ }
+
+ return;
+ } else if (action === "get-test-results") {
+ // ?action=get-result
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState(sharedKey));
+ return;
+ } else if (action === "generate-policy-test") {
+ // ?action=generate-policy-test&referrerPolicy=b64-encoded-string&name=name&newPolicy=b64-encoded-string
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ var referrerPolicy = unescape(params[1].split("=")[1]);
+ var name = unescape(params[2].split("=")[1]);
+ var newPolicy = params[3].split("=")[1];
+
+ response.write(createTest(referrerPolicy, newPolicy, name));
+ return;
+ } else if (action === "generate-policy-test2") {
+ // ?action=generate-policy-test2&referrerPolicy=b64-encoded-string&name=name&newPolicy=b64-encoded-string
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ var referrerPolicy = unescape(params[1].split("=")[1]);
+ var name = unescape(params[2].split("=")[1]);
+ var newPolicy = params[3].split("=")[1];
+
+ response.write(createTest2(referrerPolicy, newPolicy, name));
+ return;
+ } else {
+ response.write("I don't know action " + action);
+ return;
+ }
+}
diff --git a/dom/base/test/referrer_header.sjs b/dom/base/test/referrer_header.sjs
new file mode 100644
index 0000000000..29c324b8f6
--- /dev/null
+++ b/dom/base/test/referrer_header.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setHeader("Referrer-Policy", "same-origin");
+ response.write(
+ '<!DOCTYPE HTML><html><body>Loaded</body><script>parent.postMessage(document.referrer, "*");</script></html>'
+ );
+}
diff --git a/dom/base/test/referrer_helper.js b/dom/base/test/referrer_helper.js
new file mode 100644
index 0000000000..e2e3e32835
--- /dev/null
+++ b/dom/base/test/referrer_helper.js
@@ -0,0 +1,133 @@
+// This helper expects these globals to be defined.
+/* global PARAMS, SJS, testCases */
+
+/*
+ * common functionality for iframe, anchor, and area referrer attribute tests
+ */
+const GET_RESULT = SJS + "ACTION=get-test-results";
+const RESET_STATE = SJS + "ACTION=resetState";
+
+SimpleTest.waitForExplicitFinish();
+var advance = function () {
+ tests.next();
+};
+
+/**
+ * Listen for notifications from the child.
+ * These are sent in case of error, or when the loads we await have completed.
+ */
+window.addEventListener("message", function (event) {
+ if (event.data == "childLoadComplete") {
+ // all loads happen, continue the test.
+ advance();
+ }
+});
+
+/**
+ * helper to perform an XHR
+ * to do checkIndividualResults and resetState
+ */
+function doXHR(aUrl, onSuccess, onFail) {
+ // The server is at http[s]://example.com so we need cross-origin XHR.
+ var xhr = new XMLHttpRequest({ mozSystem: true });
+ xhr.responseType = "json";
+ xhr.onload = function () {
+ onSuccess(xhr);
+ };
+ xhr.onerror = function () {
+ onFail(xhr);
+ };
+ xhr.open("GET", "http" + aUrl, true);
+ xhr.send(null);
+}
+
+/**
+ * Grabs the results via XHR and passes to checker.
+ */
+function checkIndividualResults(aTestname, aExpectedReferrer, aName) {
+ var onload = xhr => {
+ var results = xhr.response;
+ info(JSON.stringify(xhr.response));
+ ok(aName in results, aName + " tests have to be performed.");
+ is(
+ results[aName].policy,
+ aExpectedReferrer,
+ aTestname +
+ " --- " +
+ results[aName].policy +
+ " (" +
+ results[aName].referrer +
+ ")"
+ );
+ advance();
+ };
+ var onerror = xhr => {
+ ok(false, "Can't get results from the counter server.");
+ SimpleTest.finish();
+ };
+ doXHR(GET_RESULT, onload, onerror);
+}
+
+function resetState() {
+ doXHR(RESET_STATE, advance, function (xhr) {
+ ok(false, "error in reset state");
+ SimpleTest.finish();
+ });
+}
+
+/**
+ * testing if referrer header is sent correctly
+ */
+var tests = (function* () {
+ yield SpecialPowers.pushPrefEnv(
+ { set: [["network.preload", true]] },
+ advance
+ );
+ yield SpecialPowers.pushPrefEnv(
+ { set: [["security.mixed_content.block_active_content", false]] },
+ advance
+ );
+ yield SpecialPowers.pushPrefEnv(
+ { set: [["network.http.referer.disallowRelaxingDefault", false]] },
+ advance
+ );
+ yield SpecialPowers.pushPermissions(
+ [{ type: "systemXHR", allow: true, context: document }],
+ advance
+ );
+
+ var iframe = document.getElementById("testframe");
+
+ for (var j = 0; j < testCases.length; j++) {
+ if (testCases[j].PREFS) {
+ yield SpecialPowers.pushPrefEnv({ set: testCases[j].PREFS }, advance);
+ }
+
+ var actions = testCases[j].ACTION;
+ var subTests = testCases[j].TESTS;
+ for (var k = 0; k < actions.length; k++) {
+ var actionString = actions[k];
+ for (var i = 0; i < subTests.length; i++) {
+ yield resetState();
+ var searchParams = new URLSearchParams();
+ searchParams.append("ACTION", actionString);
+ searchParams.append("NAME", subTests[i].NAME);
+ for (var l of PARAMS) {
+ if (subTests[i][l]) {
+ searchParams.append(l, subTests[i][l]);
+ }
+ }
+ var schemeFrom = subTests[i].SCHEME_FROM || "http";
+ yield (iframe.src = schemeFrom + SJS + searchParams.toString());
+ yield checkIndividualResults(
+ subTests[i].DESC,
+ subTests[i].RESULT,
+ subTests[i].NAME
+ );
+ }
+ }
+ }
+
+ // complete.
+ SimpleTest.finish();
+})();
diff --git a/dom/base/test/referrer_testserver.sjs b/dom/base/test/referrer_testserver.sjs
new file mode 100644
index 0000000000..bb7acbc3b2
--- /dev/null
+++ b/dom/base/test/referrer_testserver.sjs
@@ -0,0 +1,693 @@
+/*
+ * Test server for iframe, anchor, and area referrer attributes.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1175736
+ * Also server for further referrer tests such as redirecting tests
+ * bug 1174913, bug 1175736, bug 1184781
+ */
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+const SJS = "referrer_testserver.sjs?";
+const SJS_PATH = "/tests/dom/base/test/";
+const BASE_ORIGIN = "example.com";
+const BASE_URL = BASE_ORIGIN + SJS_PATH + SJS;
+const SHARED_KEY = SJS;
+const SAME_ORIGIN = "mochi.test:8888" + SJS_PATH + SJS;
+const CROSS_ORIGIN_URL = "test1.example.com" + SJS_PATH + SJS;
+
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+function createTestUrl(
+ aPolicy,
+ aAction,
+ aName,
+ aType,
+ aSchemeFrom,
+ aSchemeTo,
+ crossOrigin,
+ referrerPolicyHeader
+) {
+ var schemeTo = aSchemeTo || "http";
+ var schemeFrom = aSchemeFrom || "http";
+ var rpHeader = referrerPolicyHeader || "";
+ var url = schemeTo + "://";
+ url += crossOrigin ? CROSS_ORIGIN_URL : BASE_URL;
+ url +=
+ "ACTION=" +
+ aAction +
+ "&" +
+ "policy=" +
+ aPolicy +
+ "&" +
+ "NAME=" +
+ aName +
+ "&" +
+ "type=" +
+ aType +
+ "&" +
+ "RP_HEADER=" +
+ rpHeader +
+ "&" +
+ "SCHEME_FROM=" +
+ schemeFrom;
+ return url;
+}
+
+// test page using iframe referrer attribute
+// if aParams are set this creates a test where the iframe url is a redirect
+function createIframeTestPageUsingRefferer(
+ aMetaPolicy,
+ aAttributePolicy,
+ aNewAttributePolicy,
+ aName,
+ aParams,
+ aSchemeFrom,
+ aSchemeTo,
+ aChangingMethod
+) {
+ var metaString = "";
+ if (aMetaPolicy) {
+ metaString = `<meta name="referrer" content="${aMetaPolicy}">`;
+ }
+ var changeString = "";
+ if (aChangingMethod === "setAttribute") {
+ changeString = `document.getElementById("myframe").setAttribute("referrerpolicy", "${aNewAttributePolicy}")`;
+ } else if (aChangingMethod === "property") {
+ changeString = `document.getElementById("myframe").referrerPolicy = "${aNewAttributePolicy}"`;
+ }
+ var iFrameString = `<iframe src="" id="myframe" ${
+ aAttributePolicy ? ` referrerpolicy="${aAttributePolicy}"` : ""
+ }>iframe</iframe>`;
+ var iframeUrl = "";
+ if (aParams) {
+ aParams.delete("ACTION");
+ aParams.append("ACTION", "redirectIframe");
+ iframeUrl = "http://" + CROSS_ORIGIN_URL + aParams.toString();
+ } else {
+ iframeUrl = createTestUrl(
+ aAttributePolicy,
+ "test",
+ aName,
+ "iframe",
+ aSchemeFrom,
+ aSchemeTo
+ );
+ }
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ ${metaString}
+ </head>
+ <body>
+ ${iFrameString}
+ <script>
+ window.addEventListener("load", function() {
+ ${changeString}
+ document.getElementById("myframe").onload = function(){
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");
+ };
+ document.getElementById("myframe").src = "${iframeUrl}";
+ }.bind(window), false);
+ </script>
+ </body>
+ </html>`;
+}
+
+function buildAnchorString(
+ aMetaPolicy,
+ aReferrerPolicy,
+ aName,
+ aRelString,
+ aSchemeFrom,
+ aSchemeTo
+) {
+ if (aReferrerPolicy) {
+ return `<a href="${createTestUrl(
+ aReferrerPolicy,
+ "test",
+ aName,
+ "link",
+ aSchemeFrom,
+ aSchemeTo
+ )}" referrerpolicy="${aReferrerPolicy}" id="link" ${aRelString}>${aReferrerPolicy}</a>`;
+ }
+ return `<a href="${createTestUrl(
+ aMetaPolicy,
+ "test",
+ aName,
+ "link",
+ aSchemeFrom,
+ aSchemeTo
+ )}" id="link" ${aRelString}>link</a>`;
+}
+
+function buildAreaString(
+ aMetaPolicy,
+ aReferrerPolicy,
+ aName,
+ aRelString,
+ aSchemeFrom,
+ aSchemeTo
+) {
+ var result = `<img src="file_mozfiledataurl_img.jpg" alt="image" usemap="#imageMap">`;
+ result += `<map name="imageMap">`;
+ if (aReferrerPolicy) {
+ result += `<area shape="circle" coords="1,1,1" href="${createTestUrl(
+ aReferrerPolicy,
+ "test",
+ aName,
+ "link",
+ aSchemeFrom,
+ aSchemeTo
+ )}" alt="theArea" referrerpolicy="${aReferrerPolicy}" id="link" ${aRelString}>`;
+ } else {
+ result += `<area shape="circle" coords="1,1,1" href="${createTestUrl(
+ aMetaPolicy,
+ "test",
+ aName,
+ "link",
+ aSchemeFrom,
+ aSchemeTo
+ )}" alt="theArea" id="link" ${aRelString}>`;
+ }
+ result += `</map>`;
+
+ return result;
+}
+
+// test page using anchor or area referrer attribute
+function createAETestPageUsingRefferer(
+ aMetaPolicy,
+ aAttributePolicy,
+ aNewAttributePolicy,
+ aName,
+ aRel,
+ aStringBuilder,
+ aSchemeFrom,
+ aSchemeTo,
+ aChangingMethod
+) {
+ var metaString = "";
+ if (aMetaPolicy) {
+ metaString = `<head><meta name="referrer" content="${aMetaPolicy}"></head>`;
+ }
+ var changeString = "";
+ if (aChangingMethod === "setAttribute") {
+ changeString = `document.getElementById("link").setAttribute("referrerpolicy", "${aNewAttributePolicy}")`;
+ } else if (aChangingMethod === "property") {
+ changeString = `document.getElementById("link").referrerPolicy = "${aNewAttributePolicy}"`;
+ }
+ var relString = "";
+ if (aRel) {
+ relString = `rel="noreferrer"`;
+ }
+ var elementString = aStringBuilder(
+ aMetaPolicy,
+ aAttributePolicy,
+ aName,
+ relString,
+ aSchemeFrom,
+ aSchemeTo
+ );
+
+ return `<!DOCTYPE HTML>
+ <html>
+ ${metaString}
+ <body>
+ ${elementString}
+ <script>
+ window.addEventListener("load", function() {
+ ${changeString}
+ document.getElementById("link").click();
+ }.bind(window), false);
+ </script>
+ </body>
+ </html>`;
+}
+
+// test page using anchor target=_blank rel=noopener
+function createTargetBlankRefferer(
+ aMetaPolicy,
+ aName,
+ aSchemeFrom,
+ aSchemeTo,
+ aRpHeader
+) {
+ var metaString = "";
+ if (aMetaPolicy) {
+ metaString = `<head><meta name="referrer" content="${aMetaPolicy}"></head>`;
+ }
+ var elementString = `<a href="${createTestUrl(
+ aMetaPolicy,
+ "test",
+ aName,
+ "link",
+ aSchemeFrom,
+ aSchemeTo,
+ aRpHeader
+ )}" target=_blank rel="noopener" id="link">link</a>`;
+
+ return `<!DOCTYPE HTML>
+ <html>
+ ${metaString}
+ <body>
+ ${elementString}
+ <script>
+ window.addEventListener("load", function() {
+ let link = document.getElementById("link");
+ SpecialPowers.wrap(window).parent.postMessage("childLoadReady", "*");
+ link.click();
+ }.bind(window), false);
+ </script>
+ </body>
+ </html>`;
+}
+
+// creates test page with img that is a redirect
+function createRedirectImgTestCase(aParams, aAttributePolicy) {
+ var metaString = "";
+ if (aParams.has("META_POLICY")) {
+ metaString = `<meta name="referrer" content="${aParams.get(
+ "META_POLICY"
+ )}">`;
+ }
+ aParams.delete("ACTION");
+ aParams.append("ACTION", "redirectImg");
+ var imgUrl = "http://" + CROSS_ORIGIN_URL + aParams.toString();
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ ${metaString}
+ <title>Test referrer policies on redirect (img)</title>
+ </head>
+ <body>
+ <img id="testImg" src="${imgUrl}" ${
+ aAttributePolicy ? ` referrerpolicy="${aAttributePolicy}"` : ""
+ }>
+ <script>
+ window.addEventListener("load", function() {
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");
+ }.bind(window), false);
+ </script>
+ </body>
+ </html>`;
+}
+
+// test page using link referrer attribute
+function createLinkPageUsingRefferer(
+ aMetaPolicy,
+ aAttributePolicy,
+ aNewAttributePolicy,
+ aName,
+ aRel,
+ aStringBuilder,
+ aSchemeFrom,
+ aSchemeTo,
+ aTestType
+) {
+ var metaString = "";
+ if (aMetaPolicy) {
+ metaString = `<meta name="referrer" content="${aMetaPolicy}">`;
+ }
+
+ var changeString = "";
+ var policy = aAttributePolicy ? aAttributePolicy : aMetaPolicy;
+ var elementString = aStringBuilder(
+ policy,
+ aName,
+ aRel,
+ aSchemeFrom,
+ aSchemeTo,
+ aTestType
+ );
+
+ if (aTestType === "setAttribute") {
+ changeString = `var link = document.getElementById("test_link");
+ link.setAttribute("referrerpolicy", "${aNewAttributePolicy}");
+ link.href = "${createTestUrl(
+ policy,
+ "test",
+ aName,
+ "link_element_" + aRel,
+ aSchemeFrom,
+ aSchemeTo
+ )}";`;
+ } else if (aTestType === "property") {
+ changeString = `var link = document.getElementById("test_link");
+ link.referrerPolicy = "${aNewAttributePolicy}";
+ link.href = "${createTestUrl(
+ policy,
+ "test",
+ aName,
+ "link_element_" + aRel,
+ aSchemeFrom,
+ aSchemeTo
+ )}";`;
+ }
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ ${metaString}
+ </head>
+ <body>
+ ${elementString}
+ <script>
+ ${changeString}
+ </script>
+ </body>
+ </html>`;
+}
+
+function createFetchUserControlRPTestCase(
+ aName,
+ aSchemeFrom,
+ aSchemeTo,
+ crossOrigin
+) {
+ var srcUrl = createTestUrl(
+ "",
+ "test",
+ aName,
+ "fetch",
+ aSchemeFrom,
+ aSchemeTo,
+ crossOrigin
+ );
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test user control referrer policies</title>
+ </head>
+ <body>
+ <script>
+ fetch("${srcUrl}", {referrerPolicy: ""}).then(function (response) {
+ window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");
+ });
+ </script>
+ </body>
+ </html>`;
+}
+
+function buildLinkString(
+ aPolicy,
+ aName,
+ aRel,
+ aSchemeFrom,
+ aSchemeTo,
+ aTestType
+) {
+ var href = "";
+ var onChildComplete = `window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");`;
+ var policy = "";
+ var asString = "";
+ var relString = "";
+
+ if (aRel) {
+ relString = `rel="${aRel}"`;
+ }
+
+ if (aPolicy) {
+ policy = `referrerpolicy=${aPolicy}`;
+ }
+
+ if (aRel == "preload") {
+ asString = 'as="image"';
+ }
+
+ if (!aTestType) {
+ href = `href=${createTestUrl(
+ aPolicy,
+ "test",
+ aName,
+ "link_element_" + aRel,
+ aSchemeFrom,
+ aSchemeTo
+ )}`;
+ }
+
+ return `<link ${relString} ${href} ${policy} ${asString} id="test_link" onload='${onChildComplete}' onerror='${onChildComplete}'>`;
+}
+
+// eslint-disable-next-line complexity
+function handleRequest(request, response) {
+ var params = new URLSearchParams(request.queryString);
+ var action = params.get("ACTION");
+ var schemeFrom = params.get("SCHEME_FROM") || "http";
+ var schemeTo = params.get("SCHEME_TO") || "http";
+ var crossOrigin = params.get("CROSS_ORIGIN") || false;
+ var referrerPolicyHeader = params.get("RP_HEADER") || "";
+
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ if (referrerPolicyHeader) {
+ response.setHeader("Referrer-Policy", referrerPolicyHeader, false);
+ }
+
+ if (action === "resetState") {
+ setSharedState(SHARED_KEY, "{}");
+ response.write("");
+ return;
+ }
+ if (action === "get-test-results") {
+ // ?action=get-result
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState(SHARED_KEY));
+ return;
+ }
+ if (action === "redirect") {
+ response.write(
+ '<script>parent.postMessage("childLoadComplete", "http://mochi.test:8888");</script>'
+ );
+ return;
+ }
+ if (action === "redirectImg") {
+ params.delete("ACTION");
+ params.append("ACTION", "test");
+ params.append("type", "img");
+ // 302 found, 301 Moved Permanently, 303 See Other, 307 Temporary Redirect
+ response.setStatusLine("1.1", 302, "found");
+ response.setHeader(
+ "Location",
+ "http://" + CROSS_ORIGIN_URL + params.toString(),
+ false
+ );
+ return;
+ }
+ if (action === "redirectIframe") {
+ params.delete("ACTION");
+ params.append("ACTION", "test");
+ params.append("type", "iframe");
+ // 302 found, 301 Moved Permanently, 303 See Other, 307 Temporary Redirect
+ response.setStatusLine("1.1", 302, "found");
+ response.setHeader(
+ "Location",
+ "http://" + CROSS_ORIGIN_URL + params.toString(),
+ false
+ );
+ return;
+ }
+ if (action === "test") {
+ // ?action=test&policy=origin&name=name
+ var policy = params.get("policy");
+ var name = params.get("NAME");
+ var type = params.get("type");
+ var result = getSharedState(SHARED_KEY);
+
+ result = result ? JSON.parse(result) : {};
+
+ var referrerLevel = "none";
+ var test = {};
+ if (request.hasHeader("Referer")) {
+ var referrer = request.getHeader("Referer");
+ if (referrer.indexOf("referrer_testserver") > 0) {
+ referrerLevel = "full";
+ } else if (referrer.indexOf(schemeFrom + "://example.com") == 0) {
+ referrerLevel = "origin";
+ } else {
+ // this is never supposed to happen
+ referrerLevel = "other-origin";
+ }
+ test.referrer = referrer;
+ } else {
+ test.referrer = "";
+ }
+ test.policy = referrerLevel;
+ test.expected = policy;
+
+ result[name] = test;
+
+ setSharedState(SHARED_KEY, JSON.stringify(result));
+
+ if (type === "img" || type == "link_element_preload") {
+ // return image
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ return;
+ }
+ if (type === "iframe") {
+ // return iframe page
+ response.write("<html><body>I am the iframe</body></html>");
+ return;
+ }
+ if (type === "link") {
+ // forward link click to redirect URL to finish test
+ var loc = "http://" + BASE_URL + "ACTION=redirect";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ }
+ return;
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+
+ // parse test arguments and start test
+ var attributePolicy = params.get("ATTRIBUTE_POLICY") || "";
+ var newAttributePolicy = params.get("NEW_ATTRIBUTE_POLICY") || "";
+ var metaPolicy = params.get("META_POLICY") || "";
+ var rel = params.get("REL") || "";
+ var name = params.get("NAME");
+
+ // anchor & area
+ var _getPage = createAETestPageUsingRefferer.bind(
+ null,
+ metaPolicy,
+ attributePolicy,
+ newAttributePolicy,
+ name,
+ rel
+ );
+ var _getAnchorPage = _getPage.bind(
+ null,
+ buildAnchorString,
+ schemeFrom,
+ schemeTo
+ );
+ var _getAreaPage = _getPage.bind(null, buildAreaString, schemeFrom, schemeTo);
+
+ // aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aChangingMethod, aStringBuilder
+ if (action === "generate-anchor-policy-test") {
+ response.write(_getAnchorPage());
+ return;
+ }
+ if (action === "generate-anchor-changing-policy-test-set-attribute") {
+ response.write(_getAnchorPage("setAttribute"));
+ return;
+ }
+ if (action === "generate-anchor-changing-policy-test-property") {
+ response.write(_getAnchorPage("property"));
+ return;
+ }
+ if (action === "generate-area-policy-test") {
+ response.write(_getAreaPage());
+ return;
+ }
+ if (action === "generate-area-changing-policy-test-set-attribute") {
+ response.write(_getAreaPage("setAttribute"));
+ return;
+ }
+ if (action === "generate-area-changing-policy-test-property") {
+ response.write(_getAreaPage("property"));
+ return;
+ }
+ if (action === "generate-anchor-target-blank-policy-test") {
+ response.write(
+ createTargetBlankRefferer(
+ metaPolicy,
+ name,
+ schemeFrom,
+ schemeTo,
+ referrerPolicyHeader
+ )
+ );
+ return;
+ }
+
+ // iframe
+ _getPage = createIframeTestPageUsingRefferer.bind(
+ null,
+ metaPolicy,
+ attributePolicy,
+ newAttributePolicy,
+ name,
+ "",
+ schemeFrom,
+ schemeTo
+ );
+
+ // aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aChangingMethod
+ if (action === "generate-iframe-policy-test") {
+ response.write(_getPage());
+ return;
+ }
+ if (action === "generate-iframe-changing-policy-test-set-attribute") {
+ response.write(_getPage("setAttribute"));
+ return;
+ }
+ if (action === "generate-iframe-changing-policy-test-property") {
+ response.write(_getPage("property"));
+ return;
+ }
+
+ // redirect tests with img and iframe
+ if (action === "generate-img-redirect-policy-test") {
+ response.write(createRedirectImgTestCase(params, attributePolicy));
+ return;
+ }
+ if (action === "generate-iframe-redirect-policy-test") {
+ response.write(
+ createIframeTestPageUsingRefferer(
+ metaPolicy,
+ attributePolicy,
+ newAttributePolicy,
+ name,
+ params,
+ schemeFrom,
+ schemeTo
+ )
+ );
+ return;
+ }
+
+ var _getPage = createLinkPageUsingRefferer.bind(
+ null,
+ metaPolicy,
+ attributePolicy,
+ newAttributePolicy,
+ name,
+ rel
+ );
+ var _getLinkPage = _getPage.bind(null, buildLinkString, schemeFrom, schemeTo);
+
+ // link
+ if (action === "generate-link-policy-test") {
+ response.write(_getLinkPage());
+ return;
+ }
+ if (action === "generate-link-policy-test-set-attribute") {
+ response.write(_getLinkPage("setAttribute"));
+ return;
+ }
+ if (action === "generate-link-policy-test-property") {
+ response.write(_getLinkPage("property"));
+ return;
+ }
+
+ if (action === "generate-fetch-user-control-policy-test") {
+ response.write(
+ createFetchUserControlRPTestCase(name, schemeFrom, schemeTo, crossOrigin)
+ );
+ return;
+ }
+
+ response.write("I don't know action " + action);
+ return;
+}
diff --git a/dom/base/test/reftest/mixed-bmp-png.ico b/dom/base/test/reftest/mixed-bmp-png.ico
new file mode 100644
index 0000000000..32e2c4995c
--- /dev/null
+++ b/dom/base/test/reftest/mixed-bmp-png.ico
Binary files differ
diff --git a/dom/base/test/reftest/reftest.list b/dom/base/test/reftest/reftest.list
new file mode 100644
index 0000000000..8aa3f3b949
--- /dev/null
+++ b/dom/base/test/reftest/reftest.list
@@ -0,0 +1,7 @@
+== test_bug920877.html test_bug920877-ref.html
+HTTP == test_xmlPrettyPrint_csp.xml test_xmlPrettyPrint_csp-ref.xml
+# Ordinarily, reftests use a browser.viewport.desktopWidth of 800px, same as the
+# size of the reftest document. This test however needs something more representative
+# of a real mobile device, where the desktop viewport width doesn't match the
+# width of the device screen.
+test-pref(dom.meta-viewport.enabled,true) test-pref(browser.viewport.desktopWidth,1200) == test_bug1525662.txt test_bug1525662-ref.html
diff --git a/dom/base/test/reftest/test_bug1525662-ref.html b/dom/base/test/reftest/test_bug1525662-ref.html
new file mode 100644
index 0000000000..063daf7615
--- /dev/null
+++ b/dom/base/test/reftest/test_bug1525662-ref.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<meta name="viewport" content="width=device-width">
+</head>
+<body>
+<pre style="white-space: pre-wrap; word-wrap: break-word">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/reftest/test_bug1525662.txt b/dom/base/test/reftest/test_bug1525662.txt
new file mode 100644
index 0000000000..33fd1fd851
--- /dev/null
+++ b/dom/base/test/reftest/test_bug1525662.txt
@@ -0,0 +1,7 @@
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
diff --git a/dom/base/test/reftest/test_bug920877-ref.html b/dom/base/test/reftest/test_bug920877-ref.html
new file mode 100644
index 0000000000..1a593e5849
--- /dev/null
+++ b/dom/base/test/reftest/test_bug920877-ref.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+<script>
+var img = document.createElement("img");
+img.id = "img-ori";
+img.src = "mixed-bmp-png.ico";
+document.body.appendChild(img);
+
+img = document.createElement("img");
+img.id = "img-res32";
+img.src = "mixed-bmp-png.ico#-moz-resolution=32,32";
+document.body.appendChild(img);
+
+img = document.createElement("img");
+img.id = "img-res48";
+img.src = "mixed-bmp-png.ico#-moz-resolution=48,48";
+document.body.appendChild(img);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/reftest/test_bug920877.html b/dom/base/test/reftest/test_bug920877.html
new file mode 100644
index 0000000000..18bae4009e
--- /dev/null
+++ b/dom/base/test/reftest/test_bug920877.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<script>
+var dataURL = "data:image/vnd.microsoft.icon;base64,AAABAAQAMDAAAAEAIACoJQAARgAAACAgAAABACAAqBAAAO4lAAAYGAAAAQAgAIgJAACWNgAAEBAAAAEAIABoBAAAHkAAACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAEDQZgCA0GYAv9BmAN/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgDf0GYAv9BmAIDQZgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAEDQZgCf0GYA79BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA79BmAJ/QZgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgBA0GYAv9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAv9BmAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAINBmAK/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgCv0GYAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgBQ0GYA79BmAP/QZgD/0GYA/9BmAL/QZgC/0GYAv9BmAL/QZgC/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA79BmAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAI/QZgDv0GYAn9BmAFDQZgAQAAAAAAAAAAAAAAAAIB8jECAfI0AgHyNAIB8jINBmABDQZgBA0GYAgNBmAK/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgCPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAgNBmAGAAAAAAAAAAAAAAAAAAAAAAIB8jQCAfI58gHyPvIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPPIB8jjyAfIzAAAAAA0GYAUNBmAJ/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIxAgHyO/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPPIB8jYAAAAADQZgAQ0GYAcNBmAN/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAJ8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jMCAfI+8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI78gHyNAAAAAAAAAAADQZgBg0GYA39BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgCPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8j7yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jjwAAAAAAAAAAAAAAANBmAIDQZgDv0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOfIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyOAIB8jgCAfI4AgHyOAIB8jgCAfI4AgHyOAIB8jgCAfI4AgHyOAIB8jgCAfI4AgHyOAIB8jgCAfI0AAAAAAAAAAAAAAAADQZgAg0GYAv9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA79BmACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIyAgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyNgIB8jEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAHDQZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI48gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI68gHyNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyNgIB8jzyAfIzAAAAAAAAAAAAAAAADQZgAw0GYA39BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI78gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jvyAfIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jICAfI78gHyP/IB8j/yAfI+8gHyMwAAAAAAAAAAAAAAAA0GYAENBmAM/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgC/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI+8gHyMwAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyNQIB8j7yAfI/8gHyP/IB8j/yAfI/8gHyPvIB8jIAAAAAAAAAAAAAAAANBmABDQZgDP0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPfIB8jEAAAAAAAAAAAAAAAACAfI2AgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jzyAfIxAAAAAAAAAAAAAAAADQZgAQ0GYAz9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAnwAAAAAAAAAAIhvgEAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jQCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jzyAfI4AgHyNAIB8jgCAfI78gHyP/IB8jcAAAAAAAAAAAIB8jICAfI/8gHyPfIB8jcCAfI0AgHyNQIB8jjyAfI+8gHyP/IB8j/yAfI58AAAAAAAAAAAAAAAAAAAAA0GYAENBmAM/QZgD/0GYA/9BmAP/QZgD/0GYA7wAAAAAiG+BAIhvgQAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jMCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI+8gHyNAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOfIB8jvwAAAAAAAAAAIB8jcCAfI88gHyMQAAAAAAAAAAAAAAAAAAAAACAfIxAgHyOfIB8j/yAfI/8gHyNQAAAAAAAAAAAAAAAAAAAAANBmACDQZgDv0GYA/9BmAP/QZgD/0GYA/9BmAEAiG+CAIhvgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j7yAfIzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8j3wAAAAAAAAAAIB8jgCAfI0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jnyAfI/8gHyPvIB8jEAAAAAAAAAAAAAAAAAAAAADQZgBA0GYA/9BmAP/QZgD/0GYA/9BmAIAiG+C/IhvgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jjwAAAAAAAAAAIB8jYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI98gHyP/IB8jgAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAj9BmAP/QZgD/0GYA/9BmAL8iG+DfIhvgjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI78gHyP/IB8j/yAfI/8gHyPPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jEAAAAAAAAAAAIB8jEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI2AgHyP/IB8j7yAfIyAAAAAAAAAAAAAAAAAAAAAA0GYAENBmAN/QZgD/0GYA/9BmAN8iG+D/Ihvg3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI58gHyP/IB8j/yAfI/8gHyOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIxAgHyP/IB8j/yAfI4AAAAAAAAAAAAAAAAAAAAAAAAAAANBmAGDQZgD/0GYA/9BmAP8iG+D/Ihvg/yIb4CAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI2AgHyP/IB8j/yAfI/8gHyNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyP/IB8j/yAfI+8gHyMQAAAAAAAAAAAAAAAAAAAAAAAAAADQZgDf0GYA/9BmAP8iG+D/Ihvg/yIb4I8AAAAAAAAAAAAAAAAAAAAAAAAAACAfIxAgHyP/IB8j/yAfI/8gHyNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyP/IB8j/yAfI/8gHyNgAAAAAAAAAAAAAAAAAAAAAAAAAADQZgBg0GYA/9BmAP8iG+D/Ihvg/yIb4O8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOvIB8j/yAfI/8gHyNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyP/IB8j/yAfI/8gHyOvAAAAAAAAAAAAAAAAAAAAAAAAAADQZgAQ0GYA79BmAP8iG+D/Ihvg/yIb4P8iG+BwAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyNQIB8j/yAfI/8gHyOfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI0AgHyP/IB8j/yAfI/8gHyPvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAn9BmAP8iG+D/Ihvg/yIb4P8iG+DvIhvgEAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8j3yAfI/8gHyPvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI48gHyP/IB8j/yAfI/8gHyP/IB8jQAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAYNBmAP8iG+DfIhvg/yIb4P8iG+D/IhvgjwAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jYCAfI/8gHyP/IB8jYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI+8gHyP/IB8j/yAfI/8gHyP/IB8jcAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAINBmAN8iG+C/Ihvg/yIb4P8iG+D/Ihvg/yIb4EAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI98gHyP/IB8jvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jYCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAK8iG+CAIhvg/yIb4P8iG+D/Ihvg/yIb4N8iG+AQAAAAAAAAAAAAAAAAAAAAACAfI0AgHyP/IB8j/yAfI1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8j3yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmADAiG+BAIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+C/AAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOPIB8j/yAfI98gHyMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOAIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvg7yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgnwAAAAAAAAAAAAAAAAAAAAAgHyMQIB8jzyAfI/8gHyOfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIzAgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgnyIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4J8AAAAAAAAAAAAAAAAAAAAAIB8jMCAfI+8gHyP/IB8jYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jECAfI98gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgQCIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+CfAAAAAAAAAAAAAAAAAAAAACAfI1AgHyP/IB8j/yAfIzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jvyAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4L8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgnwAAAAAAAAAAAAAAAAAAAAAgHyNgIB8j/yAfI+8gHyMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOfIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4EAiG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4M8iG+AgAAAAAAAAAAAAAAAAIB8jYCAfI/8gHyPvIB8jMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI58gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+CvIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+DvIhvgUAAAAAAAAAAAAAAAACAfI1AgHyPvIB8j7yAfI1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8jnyAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyOfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+AgIhvg7yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4K8iG+AQAAAAAAAAAAAgHyMwIB8jzyAfI/8gHyNwAAAAAAAAAAAAAAAAAAAAACAfIxAgHyPPIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgUCIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+DvIhvgcAAAAAAAAAAAIB8jECAfI48gHyP/IB8jnyAfIxAAAAAAIB8jMCAfI98gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4I8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4N8iG+BgAAAAAAAAAAAgHyNAIB8jvyAfI88gHyOAIB8j7yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+CfIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg7yIb4IAiG+AgAAAAACAfI2AgHyO/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgnyIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgzyIb4HAiG+AgIB8jICAfI4AgHyO/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPPIB8jjyAfIyAAAAAAAAAAAAAAAAAiG+AwIhvgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4I8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg3yIb4K8iG+CAIhvgQCIb4DAgHyNAIB8jIAAAAAAAAAAAAAAAACIb4DAiG+BQIhvgjyIb4M8iG+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+BQIhvg7yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg7yIb4FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgICIb4K8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+CvIhvgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+BAIhvgvyIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgvyIb4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4EAiG+CfIhvg7yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg7yIb4J8iG+AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4EAiG+CAIhvgvyIb4N8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+DfIhvgvyIb4IAiG+BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AB//+nbP/8AAA//6ds//AAAA//p2z/wAAAA/+nbP+AAAAB/6ds/wcAAAD/p2z+eACAAH+nbP/gACAAP6ds/8AADAAfp2z/gAAHAA+nbP+AAAOAB6ds/wB//+AHp2z/AB/8cAOnbP8AB/A4A6ds/wAD4BwBp2z/AAHADgGnbL4AAYAPAadsPgD5jweAp2w/Afmfw8CnbD8D/b/j4KdsPwf9v+Hgp2w/B///4fCnbB8H///w+KdsHwf///D4p2wfh///8PinbA+H///g/KdsB8f//+B8p2wHw///4HynbAPj///AfqdsAeH//4B+p2wB8P//gH+nbIDw//8Af6dsgHh//gB/p2yAPD/+AH+nbMAeH/wAf6dswAcP+AB/p2zgA4fgAP+nbOAAw8AA/6ds8ABggAH/p2z4ABgAA/+nbPwAAgAH/6ds/gAAAA5/p2z/AAAA4P+nbP+AAAAB/6ds/8AAAAP/p2z/8AAAD/+nbP/8AAA//6ds//+AAf//p2woAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmADDQZgCA0GYAv9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAL/QZgCA0GYAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmAFDQZgDP0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAz9BmAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmACDQZgC/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAL/QZgAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgBQ0GYA39BmAJ/QZgBw0GYAQNBmAECFSA9weEMSgNBmAHDQZgCP0GYAz9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAO/QZgBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAINBmAGAAAAAAAAAAACAfI1AgHyO/IB8j/yAfI/8gHyP/IB8j/yAfI98gHyOPZjsVUNBmAHDQZgDP0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOfIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jryAfIyDQZgBA0GYAv9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jgCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j7yAfI2AAAAAA0GYAQNBmAN/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAO/QZgAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIyAgHyP/IB8j/yAfI/8gHyP/IB8jUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAENBmAI/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAL8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jcCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j3yAfI4AgHyMQAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMgIB8jvyAfI58AAAAAAAAAANBmAFDQZgDv0GYA/9BmAP/QZgD/0GYA/9BmAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOvIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI88gHyMQAAAAAAAAAAAAAAAAIB8jYCAfI+8gHyP/IB8j/yAfI58AAAAAAAAAANBmADDQZgDv0GYA/9BmAP/QZgD/0GYAzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI78gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI78AAAAAAAAAACAfI4AgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI3AAAAAAAAAAANBmADDQZgDv0GYA/9BmAP/QZgD/0GYAMCIb4EAAAAAAAAAAAAAAAAAAAAAAIB8jvyAfI/8gHyP/IB8j/yAfI/8gHyOAIB8jEAAAAAAgHyMwIB8j3yAfIzAAAAAAIB8j3yAfI0AAAAAAAAAAACAfI1AgHyPfIB8j/yAfIzAAAAAAAAAAANBmAEDQZgD/0GYA/9BmAP/QZgCAIhvgvwAAAAAAAAAAAAAAAAAAAAAgHyO/IB8j/yAfI/8gHyP/IB8jYAAAAAAAAAAAAAAAAAAAAAAgHyNAIB8jQAAAAAAgHyOAAAAAAAAAAAAAAAAAAAAAACAfIxAgHyPvIB8jzwAAAAAAAAAAAAAAANBmAIDQZgD/0GYA/9BmAL8iG+D/IhvgEAAAAAAAAAAAAAAAACAfI58gHyP/IB8j/yAfI58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMgAAAAACAfIyAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI2AgHyP/IB8jYAAAAAAAAAAAAAAAANBmAM/QZgD/0GYA/yIb4P8iG+BQAAAAAAAAAAAAAAAAIB8jcCAfI/8gHyP/IB8jUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jECAfI/8gHyPfAAAAAAAAAAAAAAAA0GYAQNBmAP/QZgD/Ihvg/yIb4K8AAAAAAAAAAAAAAAAgHyMgIB8j/yAfI/8gHyNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8j/yAfI/8gHyMwAAAAAAAAAAAAAAAA0GYAv9BmAP8iG+D/Ihvg/yIb4CAAAAAAAAAAAAAAAAAgHyPPIB8j/yAfI2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfIxAgHyP/IB8j/yAfI48AAAAAAAAAAAAAAADQZgBQ0GYA/yIb4P8iG+D/IhvgnwAAAAAAAAAAAAAAACAfI2AgHyP/IB8jnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jYCAfI/8gHyP/IB8jzwAAAAAAAAAAAAAAAAAAAADQZgD/Ihvg/yIb4P8iG+D/IhvgQAAAAAAAAAAAAAAAACAfI98gHyPvIB8jEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyO/IB8j/yAfI/8gHyP/AAAAAAAAAAAAAAAAAAAAANBmAL8iG+C/Ihvg/yIb4P8iG+DfIhvgEAAAAAAAAAAAIB8jUCAfI/8gHyOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jQCAfI/8gHyP/IB8j/yAfI/8gHyMQAAAAAAAAAAAAAAAA0GYAQCIb4IAiG+D/Ihvg/yIb4P8iG+CvAAAAAAAAAAAAAAAAIB8jryAfI/8gHyMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyPPIB8j/yAfI/8gHyP/IB8j/yAfI0AAAAAAAAAAAAAAAAAAAAAAIhvgMCIb4P8iG+D/Ihvg/yIb4P8iG+CfAAAAAAAAAAAgHyMQIB8j3yAfI88gHyMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jjyAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jIAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgzyIb4P8iG+D/Ihvg/yIb4P8iG+CfAAAAAAAAAAAgHyMwIB8j7yAfI68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI2AgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+BgIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+C/IhvgEAAAAAAgHyMwIB8j7yAfI58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyNgIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+C/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+DvIhvgQAAAAAAgHyMwIB8j3yAfI78gHyMQAAAAAAAAAAAAAAAAIB8jYCAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4CAiG+DvIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgryIb4CAgHyMQIB8jryAfI88gHyMwAAAAACAfI58gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8jzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4FAiG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4J8iG+AgIB8jUCAfI88gHyPPIB8j/yAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI98gHyMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4GAiG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+C/IhvgYCEeWXAgHyOvIB8j7yAfI/8gHyP/IB8j/yAfI98gHyOAIB8jEAAAAAAiG+AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4FAiG+DvIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4N8iG+CvIhvggCEcm68iG+BAIhvgYCIb4IAiG+CfIhvgzyIb4FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4CAiG+C/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4L8iG+AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+BgIhvgzyIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4M8iG+BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgMCIb4IAiG+C/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgvyIb4IAiG+AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/zAAAP/gAAB/wACAP4H/wD+APjAfgBwYH4AYDAeAiYYHg8vHA4fr44OH/+ODh//xwcf/4cHH/+Hg4//h4GP/wOBx/8DwMP+A+Bj/AfgEfgH8AhwB/AAIA/4AAAP/AAAF/4AAAf/AAAP/8AAP//wAP/KAAAABgAAAAwAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgBQ0GYAn9BmAN/QZgD/0GYA/9BmAP/QZgD/0GYA39BmAJ/QZgBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAQNBmAN/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA39BmAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgCP0GYAz9BmAI/QZgCAoFMKr6BTCq/QZgCv0GYA39BmAP/QZgD/0GYA/9BmAP/QZgD/0GYA/9BmAP/QZgCPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANBmADDQZgAgIB8jECAfI58gHyPvIB8j/yAfI/8gHyP/IB8jz0wxGoDQZgCA0GYA39BmAP/QZgD/0GYA/9BmAP/QZgD/0GYAnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8jzyAfI/8gHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPPIB8jMNBmAGDQZgDv0GYA/9BmAP/QZgD/0GYA/9BmAI8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOAIB8j/yAfI/8gHyO/IB8jUCAfI0AgHyNAIB8jQCAfI0AgHyNAIB8jMAAAAADQZgAQ0GYAr9BmAP/QZgD/0GYA/9BmAP/QZgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyPvIB8j/yAfI/8gHyP/IB8j/yAfI58gHyMQAAAAAAAAAAAAAAAAIB8jUCAfI98gHyNgAAAAANBmAGDQZgD/0GYA/9BmAP/QZgDfAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI/8gHyPPIB8jEAAAAAAgHyOPIB8j/yAfI/8gHyP/IB8jYAAAAADQZgBg0GYA/9BmAP/QZgD/0GYAUCIb4FAAAAAAAAAAACAfI0AgHyP/IB8j/yAfI/8gHyPfIB8jYCAfI0AgHyOvIB8jYCAfIzAgHyPPIB8jQCAfI1AgHyO/IB8j7yAfIyAAAAAA0GYAYNBmAP/QZgD/0GYAnyIb4J8AAAAAAAAAAAAAAAAgHyP/IB8j/yAfI98gHyMQAAAAAAAAAAAgHyMQIB8jcCAfI0AgHyMgAAAAAAAAAAAAAAAAIB8jryAfI78AAAAAAAAAANBmAI/QZgD/0GYA3yIb4O8AAAAAAAAAAAAAAAAgHyPvIB8j/yAfI2AAAAAAAAAAAAAAAAAAAAAAIB8jECAfIxAAAAAAAAAAAAAAAAAAAAAAIB8jMCAfI/8gHyNAAAAAANBmABDQZgDf0GYA/yIb4P8iG+BAAAAAAAAAAAAgHyOvIB8j/yAfI0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI/8gHyOfAAAAAAAAAADQZgBg0GYA/yIb4P8iG+CvAAAAAAAAAAAgHyNgIB8j/yAfI1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jICAfI/8gHyP/AAAAAAAAAADQZgAQ0GYA7yIb4P8iG+D/IhvgQAAAAAAAAAAAIB8j3yAfI58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jcCAfI/8gHyP/IB8jQAAAAAAAAAAA0GYAryIb4N8iG+D/Ihvg3yIb4BAAAAAAIB8jYCAfI/8gHyMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMQIB8j3yAfI/8gHyP/IB8jUAAAAAAAAAAA0GYAYCIb4J8iG+D/Ihvg/yIb4K8AAAAAAAAAACAfI78gHyO/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyOAIB8j/yAfI/8gHyP/IB8jgAAAAAAAAAAAAAAAACIb4FAiG+D/Ihvg/yIb4P8iG+CfAAAAACAfIxAgHyPfIB8jgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI1AgHyP/IB8j/yAfI/8gHyP/IB8jQAAAAAAAAAAAAAAAAAAAAAAiG+DfIhvg/yIb4P8iG+D/IhvgryIb4BAgHyMwIB8j7yAfI2AAAAAAAAAAAAAAAAAAAAAAIB8jMCAfI+8gHyP/IB8j/yAfI/8gHyP/IB8jMAAAAAAAAAAAAAAAAAAAAAAiG+BAIhvg/yIb4P8iG+D/Ihvg/yIb4N8iG+BAIB8jICAfI88gHyOPAAAAAAAAAAAgHyNgIB8j7yAfI/8gHyP/IB8j/yAfI/8gHyO/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgjyIb4P8iG+D/Ihvg/yIb4P8iG+D/IhvgryEcoTAgHyOPIB8jvyAfI4AgHyP/IB8j/yAfI/8gHyP/IB8j/yAfI+8gHyMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIb4J8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+C/IhyxgCAfNp8gHyPfIB8j/yAfI/8gHyP/IB8jvyAfIzAiG+AgIhvgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+CPIhvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+DfIhvgvyEcrK8iG+CAIhvgnyIb4M8iG+CPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvgQCIb4N8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg/yIb4P8iG+D/Ihvg3yIb4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiG+BQIhvgnyIb4N8iG+D/Ihvg/yIb4P8iG+D/Ihvg3yIb4J8iG+BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4Af0H4AB9B8AAPQeAAB0HwAANB8AEBQfAcQUHwCCBBYAAQQXDDmEFx54hBMf/MQTH/jEEZ/4ZBCP8GQQz/B0EEfgdBgDwHQYAYD0HAAA9B4AAHQfAAD0H4AB9B/gB/QSgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0GYAQNBmAKXQZgDm0GYA/9BmAP/QZgDm0GYApdBmAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQZgAQ0GYAjNBmAKWkVAieqFYHr9BmANPQZgD/0GYA/9BmAP/QZgD/0GYArNBmABAAAAAAAAAAAAAAAAAAAAAA0GYAGSAfIyIgHyO8IB8j/SAfI/8gHyPWUjMZirZbBIPQZgDq0GYA/9BmAP/QZgDN0GYAEAAAAAAAAAAAAAAAACAfIwQgHyPYIB8j/yAfI4wgHyOAIB8jgCAfI4AgHyNQ0GYADtBmAJXQZgD/0GYA/9BmAKwAAAAAAAAAAAAAAAAgHyNBIB8j/yAfI/8gHyP2IB8jjyAfIwUAAAAAIB8jWSAfI88gHyMj0GYAVdBmAPrQZgD/0GYAQCIb3xAAAAAAIB8jYSAfI/8gHyP9IB8jniAfI4AgHyNqIB8jTiAfI4MgHyOKIB8jyiAfIwnQZgBR0GYA/dBmAKUiG99gAAAAACAfI04gHyP/IB8jeAAAAAAAAAAAIB8jLCAfIyIAAAAAAAAAACAfI1EgHyOBAAAAANBmAIfQZgDmIhvfugAAAAAgHyMeIB8j/yAfIx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHyMCIB8j7yAfIwzQZgAL0GYA6iIb3/0iG98pAAAAACAfI8MgHyM5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB8jFyAfI/8gHyNRAAAAANBmAI4iG9/mIhvfvSIb3wIgHyNHIB8jmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAfI3ggHyP/IB8jgQAAAADQZgA1IhvfpSIb3/8iG9+OAAAAACAfI5ogHyM6AAAAAAAAAAAAAAAAAAAAACAfIyAgHyPxIB8j/yAfI44AAAAAAAAAACIb30AiG9//Ihvf/yIb348hHIEMIB8jpyAfIyUAAAAAAAAAACAfIxIgHyPRIB8j/yAfI/8gHyNsAAAAAAAAAAAAAAAAIhvfrCIb3/8iG9//IhvfxSEbsDIgHyOIIB8jPCAfIyAgHyPUIB8j/yAfI/8gHyPxIB8jFQAAAAAAAAAAAAAAACIb3xAiG9/NIhvf/yIb3/8iG9/7IhvfpSEdgYEgHjioIB8j+CAfI/8gHyPUIB8jPCIb3wkAAAAAAAAAAAAAAAAAAAAAIhvfECIb36wiG9//Ihvf/yIb3/8iG9//Ihvf8yIb38UhHLC1IhvfryIb34UiG98OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhvfQCIb36UiG9/mIhvf/yIb3/8iG9/mIhvfpSIb3z4AAAAAAAAAAAAAAAAAAAAA8A+sQcADrEHAAaxBwAGsQcCArEFAAKxBRmSsQUfgrEEn4qxBB+KsQRPDrEEBg6xBgAOsQYADrEHAA6xB8A+sQQ==";
+
+var data = atob(dataURL.substring( "data:image/vnd.microsoft.icon;base64,".length ) );
+var asArray = new Uint8Array(data.length);
+for( var i = 0, len = data.length; i < len; ++i ) {
+ asArray[i] = data.charCodeAt(i);
+}
+var blob = new Blob( [ asArray.buffer ], {type: 'image/vnd.microsoft.icon'});
+var url = URL.createObjectURL(blob);
+
+//create img
+var img = document.createElement("img");
+img.id = "img-ori";
+img.src = url;
+document.body.appendChild(img);
+
+img = document.createElement("img");
+img.id = "img-res32";
+img.src = url + '#-moz-resolution=32,32';
+document.body.appendChild(img);
+
+img = document.createElement("img");
+img.id = "img-res48";
+img.src = url + '#-moz-resolution=48,48';
+document.body.appendChild(img);
+
+window.URL.revokeObjectURL(url);
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/reftest/test_xmlPrettyPrint_csp-ref.xml b/dom/base/test/reftest/test_xmlPrettyPrint_csp-ref.xml
new file mode 100644
index 0000000000..7b3c180912
--- /dev/null
+++ b/dom/base/test/reftest/test_xmlPrettyPrint_csp-ref.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<xml>
+ This is an XML document
+</xml>
diff --git a/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml b/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml
new file mode 100644
index 0000000000..7b3c180912
--- /dev/null
+++ b/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<xml>
+ This is an XML document
+</xml>
diff --git a/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml^headers^ b/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml^headers^
new file mode 100644
index 0000000000..93d453bd3b
--- /dev/null
+++ b/dom/base/test/reftest/test_xmlPrettyPrint_csp.xml^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none';
diff --git a/dom/base/test/script-1_bug597345.sjs b/dom/base/test/script-1_bug597345.sjs
new file mode 100644
index 0000000000..00180c997f
--- /dev/null
+++ b/dom/base/test/script-1_bug597345.sjs
@@ -0,0 +1,22 @@
+// timer has to be alive so it can't be eaten by the GC.
+var timer;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/javascript", false);
+ // The "stray" open comment at the end of the write is important!
+ response.write(
+ "document.write(\"<script charset='utf-8' src='script-2_bug597345.js'></script><!--\")"
+ );
+ response.processAsync();
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+ Components.interfaces.nsITimer
+ );
+ timer.initWithCallback(
+ function () {
+ response.finish();
+ },
+ 200,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/dom/base/test/script-2_bug597345.js b/dom/base/test/script-2_bug597345.js
new file mode 100644
index 0000000000..b464373b55
--- /dev/null
+++ b/dom/base/test/script-2_bug597345.js
@@ -0,0 +1 @@
+document.write("Räksmörgås");
diff --git a/dom/base/test/script_bug1238440.js b/dom/base/test/script_bug1238440.js
new file mode 100644
index 0000000000..1fc8422940
--- /dev/null
+++ b/dom/base/test/script_bug1238440.js
@@ -0,0 +1,33 @@
+/* eslint-env mozilla/chrome-script */
+
+Cu.importGlobalProperties(["File"]);
+
+var tmpFile;
+
+function writeFile(text, answer) {
+ var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ stream.init(tmpFile, 0x02 | 0x08 | 0x10, 0o600, 0);
+ stream.write(text, text.length);
+ stream.close();
+
+ File.createFromNsIFile(tmpFile).then(function (file) {
+ sendAsyncMessage(answer, { file });
+ });
+}
+
+addMessageListener("file.open", function (e) {
+ tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmpFile.append("foo.txt");
+ tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ writeFile("hello world", "file.opened");
+});
+
+addMessageListener("file.change", function (e) {
+ writeFile("hello world---------------", "file.changed");
+});
diff --git a/dom/base/test/script_bug602838.sjs b/dom/base/test/script_bug602838.sjs
new file mode 100644
index 0000000000..e3e7e3fde5
--- /dev/null
+++ b/dom/base/test/script_bug602838.sjs
@@ -0,0 +1,43 @@
+function setOurState(data) {
+ x = {
+ data,
+ QueryInterface(iid) {
+ return this;
+ },
+ };
+ x.wrappedJSObject = x;
+ setObjectState("bug602838", x);
+}
+
+function getOurState() {
+ var data;
+ getObjectState("bug602838", function (x) {
+ // x can be null if no one has set any state yet
+ if (x) {
+ data = x.wrappedJSObject.data;
+ }
+ });
+ return data;
+}
+
+function handleRequest(request, response) {
+ if (request.queryString) {
+ let blockedResponse = getOurState();
+ if (typeof blockedResponse == "object") {
+ blockedResponse.finish();
+ setOurState(null);
+ } else {
+ setOurState("unblocked");
+ }
+ return;
+ }
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write(
+ "ok(asyncRan, 'Async script should have run first.'); firstRan = true;"
+ );
+ if (getOurState() != "unblocked") {
+ response.processAsync();
+ setOurState(response);
+ }
+}
diff --git a/dom/base/test/script_postmessages_fileList.js b/dom/base/test/script_postmessages_fileList.js
new file mode 100644
index 0000000000..0287885c8b
--- /dev/null
+++ b/dom/base/test/script_postmessages_fileList.js
@@ -0,0 +1,26 @@
+/* eslint-env mozilla/chrome-script */
+
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("file.open", function () {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("prefs.js");
+
+ File.createFromNsIFile(testFile).then(function (file) {
+ sendAsyncMessage("file.opened", { file });
+ });
+});
+
+addMessageListener("dir.open", function () {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+
+ sendAsyncMessage("dir.opened", {
+ dir: testFile.path,
+ });
+});
diff --git a/dom/base/test/send_gzip_content.sjs b/dom/base/test/send_gzip_content.sjs
new file mode 100644
index 0000000000..f78b6f8635
--- /dev/null
+++ b/dom/base/test/send_gzip_content.sjs
@@ -0,0 +1,46 @@
+function gzipCompressString(string, obs) {
+ let scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
+ Ci.nsIStreamLoader
+ );
+ listener.init(obs);
+ let converter = scs.asyncConvertData("uncompressed", "gzip", listener, null);
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stringStream.data = string;
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, stringStream, 0, string.length);
+ converter.onStopRequest(null, null, null);
+}
+
+function produceData() {
+ var chars =
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+";
+ var result = "";
+ for (var i = 0; i < 100000; ++i) {
+ result += chars;
+ }
+ return result;
+}
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ // Generate data
+ var strings_to_send = produceData();
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+
+ let observer = {
+ onStreamComplete(loader, context, status, length, result) {
+ buffer = String.fromCharCode.apply(this, result);
+ response.setHeader("Content-Length", "" + buffer.length, false);
+ response.write(buffer);
+ response.finish();
+ },
+ };
+ gzipCompressString(strings_to_send, observer);
+}
diff --git a/dom/base/test/slow.sjs b/dom/base/test/slow.sjs
new file mode 100644
index 0000000000..7139eb91f3
--- /dev/null
+++ b/dom/base/test/slow.sjs
@@ -0,0 +1,15 @@
+function handleRequest(request, response) {
+ response.processAsync();
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+ Components.interfaces.nsITimer
+ );
+ timer.init(
+ function () {
+ response.write("Here the content. But slowly.");
+ response.finish();
+ },
+ 5000,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/dom/base/test/somedatas.resource b/dom/base/test/somedatas.resource
new file mode 100644
index 0000000000..bc277681d2
--- /dev/null
+++ b/dom/base/test/somedatas.resource
@@ -0,0 +1,16 @@
+
+
+retry: 500
+
+data:123456789
+data: 123456789123456789
+data:123456789123456789123456789123456789
+data: 123456789123456789123456789123456789123456789123456789123456789123456789
+:some utf-8 characteres
+data:çãá"'@`~à Ḿyyyy
+
+:test if the character ":"(which is used for comments) isn't misunderstood
+data: :xxabcdefghij
+data:çãá"'@`~à Ḿyyyy : zz
+
+
diff --git a/dom/base/test/somedatas.resource^headers^ b/dom/base/test/somedatas.resource^headers^
new file mode 100644
index 0000000000..6a63b5341d
--- /dev/null
+++ b/dom/base/test/somedatas.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
diff --git a/dom/base/test/test_EventSource_redirects.html b/dom/base/test/test_EventSource_redirects.html
new file mode 100644
index 0000000000..aaf2ff2742
--- /dev/null
+++ b/dom/base/test/test_EventSource_redirects.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=716841
+-->
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Test for Bug 338583</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+<body bgColor=white>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=716841">Mozilla Bug 716841</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ function doTest(test_id) {
+ source = new EventSource("eventsource_redirect.resource");
+ ok(source.url == "http://mochi.test:8888/tests/dom/base/test/eventsource_redirect.resource", "Test failed.");
+ ok(source.readyState == 0 || source.readyState == 1, "Test failed.");
+
+ source.onopen = function (event) {
+ ok(true, "opened");
+ };
+
+ source.onmessage = function (event) {
+ ok(true, "event received");
+ source.close();
+ SimpleTest.finish();
+ };
+
+ source.onerror = function (event) {
+ ok(false, "received onError: " + event);
+ source.close();
+ SimpleTest.finish();
+ };
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_Image_constructor.html b/dom/base/test/test_Image_constructor.html
new file mode 100644
index 0000000000..4c4bb70e54
--- /dev/null
+++ b/dom/base/test/test_Image_constructor.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=862702
+-->
+<head>
+ <meta charset="utf-8">
+ <!-- Make sure our script runs before anything else -->
+ <script>
+ var img = new Image;
+ </script>
+ <title>Test for Bug 862702</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 862702 **/
+ is(Object.getPrototypeOf(img), HTMLImageElement.prototype,
+ "Wrong prototype object");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=862702">Mozilla Bug 862702</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_NodeIterator_basics_filters.xhtml b/dom/base/test/test_NodeIterator_basics_filters.xhtml
new file mode 100644
index 0000000000..47504eebbe
--- /dev/null
+++ b/dom/base/test/test_NodeIterator_basics_filters.xhtml
@@ -0,0 +1,178 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!-- NodeIterator basics and filters tests.
+ Originally written by Ian Hickson, Mochi-ified by Zack Weinberg.
+ This file based on 001.xml, 002.xml, and 010.xml from
+ http://hixie.ch/tests/adhoc/dom/traversal/node-iterator/
+ with some additional cases.
+ -->
+<head>
+ <title>DOM Traversal: NodeIterator: Basics and Filters</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+<!-- comment -->
+<?body processing instruction?>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+ function compare_arrays(e, f, label) {
+ var length = (e.length > f.length) ? e.length : f.length;
+ for (var i = 0; i < length; i += 1) {
+ if (e[i] > 0)
+ is(f[i], e[i], label + " - index " + i + ": ");
+ else
+ todo_is(f[i], -e[i], label + " - index " + i + ": ");
+ }
+ }
+
+ /** DOM Traversal: NodeIterator: Basics **/
+ // NOTE: If you change the document structure, you have to make sure
+ // the magic numbers in this array (and 'expected_f', below) match.
+ var expected = new Array(9, // document
+ 1, // html
+ 3, 8, // leading comment
+ 3, 1, // head
+ 3, 1, 3, // title
+ 3, 1, // first script tag
+ 3, 1, // stylesheet tag
+ 3, // close head
+ 3, 1, // body
+ 3, 1, // p#display
+ 3, 1, // div#content
+ 3, 8, // comment
+ 3, 7, // processing instruction
+ 3, // close div
+ 3, 1, // pre#test
+ 3, 1, 4, // script and CDATA block
+ -3, -3, -3); // close close close
+ // these aren't there
+ // not sure why
+ var found = new Array();
+
+ var iterator = document.createNodeIterator(document,
+ NodeFilter.SHOW_ALL,
+ null);
+ var node;
+
+ // forwards
+ while (node = iterator.nextNode())
+ found.push(node.nodeType);
+ compare_arrays(expected, found, 'basics forward');
+
+ // backwards
+ found.length = 0;
+ while (node = iterator.previousNode())
+ found.unshift(node.nodeType);
+ compare_arrays(expected, found, 'basics backward');
+
+ /** DOM Traversal: NodeIterator: Filters **/
+ function filter(n) {
+ if (n.nodeType == 3) {
+ return NodeFilter.FILTER_SKIP;
+ } else if (n.nodeName == 'body') {
+ return NodeFilter.FILTER_REJECT; // same as _SKIP
+ }
+ return 1; // FILTER_ACCEPT
+ }
+
+ // Same warning applies to this array as to 'expected'.
+ var expect_f = new Array(9, // document
+ 1, // html
+ 8, // leading comment
+ 1, // head
+ 1, // title
+ 1, // first script tag
+ 1, // stylesheet tag
+ // body skipped
+ 1, // p#display
+ 1, // div#content
+ 8, // comment
+ // processing instruction skipped
+ 1, // pre#test
+ 1, 4); // script and CDATA block
+
+ found.length = 0;
+ iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL,
+ filter);
+
+ // forwards
+ while (node = iterator.nextNode())
+ found.push(node.nodeType);
+ compare_arrays(expect_f, found, 'filtered forward');
+
+ // backwards
+ found.length = 0;
+ while (node = iterator.previousNode())
+ found.unshift(node.nodeType);
+ compare_arrays(expect_f, found, 'filtered backward');
+
+ function checkBadFilter(method, n) {
+ var iter =
+ document.createNodeIterator(document, NodeFilter.SHOW_ALL,
+ function() {
+ if (n < 0)
+ iter.detach();
+ return NodeFilter.FILTER_ACCEPT;
+ });
+ while (--n >= 0)
+ iter.nextNode();
+ try {
+ iter[method]();
+ ok(true, "Able to call " + method + " on a NodeIterator after calling no-op detach()");
+ } catch (x) { ok(false, x) }
+ }
+ checkBadFilter("nextNode", 2);
+ checkBadFilter("previousNode", 3);
+
+ (function() {
+ // Implementing the scenario outlined in
+ // http://bugzilla.mozilla.org/show_bug.cgi?id=552110#c4
+
+ var iter = (function(filt) {
+ var grandparent = document.createElement("div"),
+ parent = document.createElement("span");
+
+ grandparent.appendChild(parent);
+ parent.appendChild(document.createElement("img"));
+ parent.appendChild(document.createElement("p"));
+
+ return document.createNodeIterator(grandparent,
+ NodeFilter.SHOW_ALL,
+ filt);
+ })(function(n) {
+ if (n.nodeName != "img")
+ return NodeFilter.FILTER_ACCEPT;
+
+ iter.detach();
+
+ n.parentNode.remove();
+ // Drop any node references passed into this function.
+ for (var i = 0; i < arguments.length; ++i)
+ arguments[i] = null;
+ ok(!n, "arguments[0] = null should have nulled out n");
+
+ // Try to trigger GC.
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", location.href, false);
+ xhr.send();
+
+ return NodeFilter.FILTER_SKIP;
+ });
+
+ is(iter.nextNode().nodeName, "div",
+ "iter.nextNode() returned the wrong node");
+ is(iter.nextNode().nodeName, "span",
+ "iter.nextNode() returned the wrong node");
+ try {
+ var p = iter.nextNode();
+ ok(false, "iter.nextNode() should have thrown, but instead it returned <" + p.nodeName + ">");
+ } catch (x) { ok(true, x) }
+ })();
+
+]]></script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_NodeIterator_mutations_1.xhtml b/dom/base/test/test_NodeIterator_mutations_1.xhtml
new file mode 100644
index 0000000000..5f6dc2f861
--- /dev/null
+++ b/dom/base/test/test_NodeIterator_mutations_1.xhtml
@@ -0,0 +1,204 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!-- NodeIterator mutation tests.
+ Originally written by Ian Hickson, Mochi-ified by Zack Weinberg.
+ This file based on 00[3-9].xml from
+ http://hixie.ch/tests/adhoc/dom/traversal/node-iterator/
+ -->
+<head>
+ <title>DOM Traversal: NodeIterator: Mutations (1/x)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+<span id="X"></span><span id="Y"><span id="root1"><span id="A"><span id="B"><span id="C"><span id="D"><span id="E"></span></span></span></span></span></span></span>
+<span id="root2"><span id="F"><span id="FF"></span></span><span id="G"></span><span id="H"><span id="HH"></span></span></span>
+<span id="root3"><span id="I"><span id="II"></span></span><span id="J"></span><span id="K"><span id="KK"></span></span></span>
+<span id="root4"><span id="L"></span><span id="M"><span id="MM"></span></span><span id="N"></span></span>
+<span id="root5"><span id="O"></span><span id="P"><span id="PP"></span></span><span id="Q"></span></span>
+<span id="root6"><span id="R"></span><span id="S"><span id="SS"></span></span><span id="T"></span></span>
+<span id="root7"><span id="U"></span><span id="V"><span id="VV"></span></span><span id="W"></span></span>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+ /** Originally written by Ian Hickson. **/
+
+ function check(f, e, label) {
+ var eid = e.id;
+ var fid = f ? f.id : 'null';
+ is(f, e, label + ': expected ' + eid + ' have ' + fid);
+ }
+
+ var childid = 0;
+ function addChildTo(a) {
+ var x = document.createElementNS('http://www.w3.org/1999/xhtml', 'span');
+ x.id = 'X' + childid;
+ childid++;
+ ok(a, 'parent ' + (a?a.id:'undefined') + ' for child ' + x.id);
+ if (a)
+ a.appendChild(x);
+ return x;
+ }
+ function remove(a) {
+ var p = a.parentNode;
+ ok(a && p,
+ 'removing ' + ( a?(a.id?a.id:'(no id)'):'undefined' )
+ + ' with parent ' + ( p?(p.id?p.id:'(no id)'):'undefined' ));
+ if (a && p)
+ p.removeChild(a);
+ }
+
+ /** Removal of nodes that should have no effect **/
+ (function () {
+ var root = $('root1');
+ var A = $('A');
+ var B = $('B');
+ var C = $('C');
+ var D = $('D');
+ var E = $('E');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '1.0');
+
+ // 1. Remove a node unrelated to the reference node
+ remove($('X'));
+ check(iterator.nextNode(), A, '1.1');
+
+ // 2. Remove an ancestor of the root node
+ remove($('Y'));
+ check(iterator.nextNode(), B, '1.2');
+
+ // 3. Remove the root node itself
+ remove(root);
+ check(iterator.nextNode(), C, '1.3');
+
+ // 4. Remove a descendant of the reference node
+ remove(E);
+ check(iterator.nextNode(), D, '1.4');
+ })();
+
+ /** Removal of the reference node **/
+ (function () {
+ var root = $('root2');
+ var F = $('F');
+ var FF = $('FF');
+ var G = $('G');
+ var H = $('H');
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+
+ check(iterator.nextNode(), root, '2.0');
+ check(iterator.nextNode(), F, '2.1');
+ check(iterator.nextNode(), FF, '2.2');
+ check(iterator.nextNode(), G, '2.3');
+ remove(G);
+ check(iterator.previousNode(), FF, '2.4');
+ remove(FF);
+ check(iterator.nextNode(), H, '2.5');
+ })();
+
+ /** Removal of the reference node (deep check) **/
+ (function () {
+ var root = $('root3');
+ var I = $('I');
+ var II = $('II');
+ var J = $('J');
+ var K = $('K');
+ var KK = $('KK');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '3.0');
+ check(iterator.nextNode(), I, '3.1');
+ check(iterator.nextNode(), II, '3.2');
+ check(iterator.nextNode(), J, '3.3');
+ remove(J);
+ var X = addChildTo(II);
+ check(iterator.nextNode(), X, '3.4');
+ check(iterator.previousNode(), X, '3.5');
+ remove(X);
+ var Y = addChildTo(II);
+ check(iterator.previousNode(), Y, '3.6');
+ check(iterator.nextNode(), Y, '3.7');
+ check(iterator.nextNode(), K, '3.8');
+ check(iterator.nextNode(), KK, '3.9');
+ })();
+
+ /** Removal of an ancestor of the Reference Node (forwards) **/
+ (function () {
+ var root = $('root4');
+ var L = $('L');
+ var M = $('M');
+ var MM = $('MM');
+ var N = $('N');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '4.1');
+ check(iterator.nextNode(), L, '4.2');
+ check(iterator.nextNode(), M, '4.3');
+ check(iterator.nextNode(), MM, '4.4');
+ remove(M);
+ check(iterator.previousNode(), L, '4.5');
+ })();
+
+ /** Removal of an ancestor of the Reference Node (forwards) (deep check) **/
+ (function () {
+ var root = $('root5');
+ var O = $('O');
+ var P = $('P');
+ var PP = $('PP');
+ var Q = $('Q');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '5.1');
+ check(iterator.nextNode(), O, '5.2');
+ check(iterator.nextNode(), P, '5.3');
+ check(iterator.nextNode(), PP, '5.4');
+ remove(P);
+ var X = addChildTo(O);
+ check(iterator.nextNode(), X, '5.5');
+ })();
+
+ /** Removal of an ancestor of the Reference Node (backwards) **/
+ (function () {
+ var root = $('root6');
+ var R = $('R');
+ var S = $('S');
+ var SS = $('SS');
+ var T = $('T');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '6.1');
+ check(iterator.nextNode(), R, '6.2');
+ check(iterator.nextNode(), S, '6.3');
+ check(iterator.nextNode(), SS, '6.4');
+ check(iterator.previousNode(), SS, '6.5');
+ remove(S);
+ check(iterator.nextNode(), T, '6.6');
+ })();
+
+ /** Removal of an ancestor of the Reference Node (backwards) (deep check) **/
+ (function () {
+ var root = $('root7');
+ var U = $('U');
+ var V = $('V');
+ var VV = $('VV');
+ var W = $('W');
+
+ var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL,
+ null);
+ check(iterator.nextNode(), root, '7.1');
+ check(iterator.nextNode(), U, '7.2');
+ check(iterator.nextNode(), V, '7.3');
+ check(iterator.nextNode(), VV, '7.4');
+ check(iterator.previousNode(), VV, '7.5');
+ remove(V);
+ var X = addChildTo(U);
+ check(iterator.previousNode(), X, '7.6');
+ })();
+]]></script></pre></body></html>
diff --git a/dom/base/test/test_NodeIterator_mutations_2.html b/dom/base/test/test_NodeIterator_mutations_2.html
new file mode 100644
index 0000000000..47d7b8b5e4
--- /dev/null
+++ b/dom/base/test/test_NodeIterator_mutations_2.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!-- NodeIterator mutation tests, 2.
+ Originally part of WebKit, Mochi-ified by Zack Weinberg.
+ This file based on node-iterator-00[...].html from
+ http://svn.webkit.org/repository/webkit/trunk/LayoutTests/traversal/
+ -->
+<head>
+ <title>DOM Traversal: NodeIterator: Mutations (2/x)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function resetContent() {
+ var content = $('content');
+ content.innerHTML = ('<span id="A"><\/span><span id="B"><\/span>'
+ + '<span id="C"><\/span><span id="D"><\/span>'
+ + '<span id="E"><\/span><span id="F"><\/span>'
+ + '<span id="G"><\/span><span id="H"><\/span>'
+ + '<span id="I"><\/span>');
+ return content;
+ }
+
+ function makeSpan(id) {
+ var e = document.createElement('span');
+ e.id = id;
+ return e;
+ }
+
+ function testNodeFilter(n) {
+ if (n.tagName == 'SPAN')
+ return NodeFilter.FILTER_ACCEPT;
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ function checkseq(it, root, expect) {
+ var checkIt = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ var printedPointer = (it.referenceNode == undefined);
+ var string = '';
+ var node;
+ while ((node = checkIt.nextNode()) != null) {
+ if (!printedPointer && it.referenceNode == node) {
+ printedPointer = true;
+ var s = '[' + node.id + '] ';
+ if (it.pointerBeforeReferenceNode)
+ string += "* " + s;
+ else
+ string += s + "* ";
+ } else {
+ string += node.id + " ";
+ }
+ }
+ is(string.slice(0, -1), expect, "sequence check");
+ }
+
+ // first a basic sanity check [node-iterator-001]
+ (function(){
+ var root = resetContent();
+ var it = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+
+ checkseq(it, root, 'A B C D E F G H I');
+ it.nextNode();
+ checkseq(it, root, '[A] * B C D E F G H I');
+ it.previousNode();
+ checkseq(it, root, '* [A] B C D E F G H I');
+ it.previousNode();
+ checkseq(it, root, '* [A] B C D E F G H I');
+ })();
+
+ // Mutations that should not move the iterator [node-iterator-002]
+ (function(){
+ var root = resetContent();
+ var it = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+
+ for (var i = 0; i < 4; i++)
+ it.nextNode();
+ checkseq(it, root, 'A B C [D] * E F G H I');
+
+ root.removeChild($('E'));
+ checkseq(it, root, 'A B C [D] * F G H I');
+
+ var X = makeSpan('X');
+ root.insertBefore(X, $('F'));
+ checkseq(it, root, 'A B C [D] * X F G H I');
+
+ var I = $('I');
+ root.removeChild(I);
+ root.insertBefore(I, X);
+ checkseq(it, root, 'A B C [D] * I X F G H');
+ })();
+
+ // 002 complete
+
+ /* Template
+ (function(){
+ var root = resetContent();
+ var it = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+
+ })();
+ */
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_NodeIterator_mutations_3.html b/dom/base/test/test_NodeIterator_mutations_3.html
new file mode 100644
index 0000000000..3eb23ee3ce
--- /dev/null
+++ b/dom/base/test/test_NodeIterator_mutations_3.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>DOM Traversal: NodeIterator: Mutations (3/x)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span id=root><span id=B></span><span id=C></span><span id=D></span><span id=E><span id=E1><span id=E11></span></span></span></span>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function removeNode(n) {
+ n.remove();
+ }
+ var initInner = $('content').innerHTML;
+ var content = $('content');
+
+
+ function resetContent() {
+ content.innerHTML = initInner;
+ var checkIt = document.createNodeIterator(content, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ var node;
+ while ((node = checkIt.nextNode()) != null) {
+ if (node.id) {
+ window[node.id] = node;
+ }
+ }
+ }
+
+ function makeSpan(id) {
+ var e = document.createElement('span');
+ e.id = id;
+ return e;
+ }
+
+ function testNodeFilter(n) {
+ if (n.tagName == 'SPAN')
+ return NodeFilter.FILTER_ACCEPT;
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ function checkseq(it, root, expect) {
+ var checkIt = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ var printedPointer = (it.referenceNode == undefined);
+ var string = '';
+ var node;
+ while ((node = checkIt.nextNode()) != null) {
+ if (!printedPointer && it.referenceNode == node) {
+ printedPointer = true;
+ var s = '[' + node.id + '] ';
+ if (it.pointerBeforeReferenceNode)
+ string += "* " + s;
+ else
+ string += s + "* ";
+ } else {
+ string += node.id + " ";
+ }
+ }
+ is(string.slice(0, -1), expect, "sequence check");
+ }
+
+ resetContent();
+ var it = document.createNodeIterator(E, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ checkseq(it, root, "root B C D * [E] E1 E11");
+
+ removeNode(C);
+ checkseq(it, root, "root B D * [E] E1 E11");
+
+ it.nextNode();
+ removeNode(D);
+ checkseq(it, root, "root B [E] * E1 E11");
+
+ it.nextNode();
+ removeNode(B);
+ checkseq(it, root, "root E [E1] * E11");
+
+ it.nextNode();
+ checkseq(it, root, "root E E1 [E11] *");
+
+ it.nextNode();
+ checkseq(it, root, "root E E1 [E11] *");
+
+ it.previousNode();
+ it.previousNode();
+ it.previousNode();
+ it.previousNode();
+ it.previousNode();
+ checkseq(it, root, "root * [E] E1 E11");
+
+ resetContent();
+ it = document.createNodeIterator(E, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ checkseq(it, root, "root B C D * [E] E1 E11");
+
+ it.nextNode();
+ it.nextNode();
+ checkseq(it, root, "root B C D E [E1] * E11");
+
+ it.previousNode();
+ it.previousNode();
+ checkseq(it, root, "root B C D * [E] E1 E11");
+
+ removeNode(D);
+ removeNode(B);
+ checkseq(it, root, "root C * [E] E1 E11");
+
+ n = makeSpan('n');
+ root.insertBefore(n, E);
+ checkseq(it, root, "root C n * [E] E1 E11");
+
+ n2 = makeSpan('n2');
+ root.insertBefore(n2, C);
+ checkseq(it, root, "root n2 C n * [E] E1 E11");
+
+ resetContent();
+ it = document.createNodeIterator(E, NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+ checkseq(it, root, "root B C D * [E] E1 E11");
+
+ removeNode(root);
+ checkseq(it, root, "root B C D * [E] E1 E11");
+
+ removeNode(B);
+ checkseq(it, root, "root C D * [E] E1 E11");
+
+ removeNode(D);
+ checkseq(it, root, "root C * [E] E1 E11");
+
+ it.nextNode();
+ it.nextNode();
+ it.nextNode();
+ checkseq(it, root, "root C E E1 [E11] *");
+
+ removeNode(E1);
+ checkseq(it, root, "root C [E] *");
+
+ n = makeSpan('n');
+ root.insertBefore(n, E);
+ checkseq(it, root, "root C n [E] *");
+
+ n2 = makeSpan('n2');
+ E.appendChild(n2);
+ checkseq(it, root, "root C n [E] * n2");
+
+ it.nextNode();
+ checkseq(it, root, "root C n E [n2] *");
+
+ removeNode(E);
+ checkseq(it, root, "root C n");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_anchor_area_referrer.html b/dom/base/test/test_anchor_area_referrer.html
new file mode 100644
index 0000000000..7e6992b404
--- /dev/null
+++ b/dom/base/test/test_anchor_area_referrer.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1174913</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that anchor and area referrer attributes are honoured correctly
+ * anchor tag with referrer attribute (generate-anchor-policy-test)
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1174913
+ -->
+
+ <script type="application/javascript">
+
+ SimpleTest.requestLongerTimeout(2);
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-anchor-policy-test", "generate-area-policy-test"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (anchor) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ DESC: "origin (anchor) with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "no-referrer (anchor) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "same-origin with origin in meta",
+ RESULT: 'full'},
+ {NAME: 'no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "no-referrer in meta",
+ RESULT: 'none'},
+
+ // Test if element attr would override meta referr policy.
+
+ // 1. Downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta downgrade in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin-when-cross-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // 2. No downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta downgrade in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin-when-cross-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'strict-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ DESC: "strict-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ DESC: "same-origin with origin in meta",
+ RESULT: 'none'},
+
+ // End of element attr overriding test..
+
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-no-meta',
+ META_POLICY: '',
+ DESC: "origin (anchor) with no meta",
+ RESULT: 'origin'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_anchor_area_referrer_changing.html b/dom/base/test/test_anchor_area_referrer_changing.html
new file mode 100644
index 0000000000..e8d6c249fb
--- /dev/null
+++ b/dom/base/test/test_anchor_area_referrer_changing.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1174913</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that anchor and area referrer attributes are honoured correctly
+ This test is split due to errors on b2g
+ * testing setAttribute and .referrer (generate-anchor-changing-test)
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1174913
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL"];
+
+ const testCases = [
+ {ACTION: ["generate-anchor-changing-policy-test-set-attribute", "generate-area-changing-policy-test-set-attribute"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NEW_ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'no-referrer-unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "no-referrer (anchor, orginally unsafe-url) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "unsafe-url (anchor, orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-origin-with-no-referrer-in-meta-rel',
+ META_POLICY: 'no-referrer',
+ DESC: "unsafe-url (anchor, orginally origin) with no-referrer in meta and rel=noreferrer",
+ RESULT: 'none',
+ REL: 'noreferrer'}]},
+ {ACTION: ["generate-anchor-changing-policy-test-property", "generate-area-changing-policy-test-property"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (anchor, orginally no-referrer) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-no-referrer-with-origin-in-meta-rel',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (anchor, orginally no-referrer) with origin in meta and rel=noreferrer",
+ RESULT: 'none',
+ REL: 'noreferrer'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_anchor_area_referrer_invalid.html b/dom/base/test/test_anchor_area_referrer_invalid.html
new file mode 100644
index 0000000000..f5687b7180
--- /dev/null
+++ b/dom/base/test/test_anchor_area_referrer_invalid.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1174913</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that anchor and area referrer attributes are honoured correctly
+ * anchor tag with invalid referrer attributes
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1174913
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL", , "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-anchor-policy-test", "generate-area-policy-test"],
+ TESTS: [
+ // setting invalid refer values -> we expect either full referrer (default)
+ // or whatever is specified in the meta referrer policy
+
+ // Note that for those test cases which require cross-origin test, we use different
+ // scheme to result in cross-origin request.
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-no-meta',
+ META_POLICY: '',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ DESC: "origin-when-cross-origin (anchor) with no meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'default',
+ NAME: 'default-with-no-meta',
+ META_POLICY: '',
+ DESC: "default (anchor) with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'something',
+ NAME: 'something-with-no-meta',
+ META_POLICY: '',
+ DESC: "something (anchor) with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ DESC: "origin-when-cross-origin (anchor) with no-referrer in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ DESC: "origin-when-cross-origin (anchor) with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ DESC: "origin-when-cross-origin (anchor) with origin in meta",
+ RESULT: 'origin'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_anchor_area_referrer_rel.html b/dom/base/test/test_anchor_area_referrer_rel.html
new file mode 100644
index 0000000000..71cd4f6390
--- /dev/null
+++ b/dom/base/test/test_anchor_area_referrer_rel.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1174913</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that anchor and area referrer attributes are honoured correctly
+ * anchor tag with referrer attribute with rel=noreferrer
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1174913
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL"];
+
+ const testCases = [
+ {ACTION: ["generate-anchor-policy-test", "generate-area-policy-test"],
+ TESTS: [
+ // setting rel=noreferrer -> we expect no referrer
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-with-origin-in-meta-rel',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (anchor) with origin in meta and rel=noreferrer",
+ RESULT: 'none',
+ REL: 'noreferrer'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-unsafe-url-in-meta-rel',
+ META_POLICY: 'unsafe-url',
+ DESC: "origin (anchor) with unsafe-url in meta and rel=noreferrer",
+ RESULT: 'none',
+ REL: 'noreferrer'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-no-meta-rel',
+ META_POLICY: '',
+ DESC: "origin (anchor) with no meta and rel=noreferrer",
+ RESULT: 'none',
+ REL: 'noreferrer'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_anchor_target_blank_referrer.html b/dom/base/test/test_anchor_target_blank_referrer.html
new file mode 100644
index 0000000000..fbd1ff9207
--- /dev/null
+++ b/dom/base/test/test_anchor_target_blank_referrer.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor target=_blank rel=noopener referrer header for Bug 1502678</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that anchor referrer header are honoured correctly
+ * anchor tag with rel=noopener target=_blank
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1502678
+ -->
+
+ <script type="application/javascript">
+ // We are going to open new tabs with target=_blank and rel=noopener
+ // Listen a new tab is opened then close the new tab, otherwise we will lose
+ // focus for the next tests
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const gBrowser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
+ window.addEventListener("message", function(event) {
+ if (event.data == "childLoadReady") {
+ BrowserTestUtils.waitForNewTab(gBrowser, null,
+ true).then(function(aNewTab) {
+ BrowserTestUtils.removeTab(aNewTab);
+ advance();
+ });
+ }
+ });
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["RP_HEADER", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-anchor-target-blank-policy-test"],
+ TESTS: [
+ // Referrer policy is set in meta
+ {NAME: 'origin-in-meta-rel-noopener',
+ META_POLICY: 'origin',
+ DESC: "origin in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {NAME: 'unsafe-url-in-meta-rel-noopener',
+ META_POLICY: 'unsafe-url',
+ DESC: "unsafe-url in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'full'},
+ {NAME: 'no-referrer-in-meta-rel-noopener',
+ META_POLICY: 'no-referrer',
+ DESC: "no-referrer in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'strict-origin-in-meta-rel-noopener',
+ META_POLICY: 'strict-origin',
+ DESC: "strict-origin in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'strict-origin-when-cross-origin-in-meta-rel-noopener',
+ META_POLICY: 'strict-origin-when-cross-origin',
+ DESC: "strict-origin-when-cross-origin in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'same-origin-in-meta-rel-noopener',
+ META_POLICY: 'same-origin',
+ DESC: "same-origin in meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'no-meta-rel-noopener',
+ META_POLICY: '',
+ DESC: "no meta and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // Referrer policy is set in Referrer-Policy Header
+ {NAME: 'origin-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'origin',
+ DESC: "origin in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {NAME: 'unsafe-url-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'unsafe-url',
+ DESC: "unsafe-url in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'full'},
+ {NAME: 'no-referrer-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'no-referrer',
+ DESC: "no-referrer in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'strict-origin-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'strict-origin',
+ DESC: "strict-origin in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'strict-origin-when-cross-origin-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'strict-origin-when-cross-origin',
+ DESC: "strict-origin-when-cross-origin in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'same-origin-in-referrer-policy-header-rel-noopener',
+ RP_HEADER: 'same-origin',
+ DESC: "same-origin in Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {NAME: 'no-referrer-policy-header-rel-noopener',
+ RP_HEADER: '',
+ DESC: "no Referrer-Policy Header and rel=noopener",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'}
+
+ ]}
+ ];
+ </script>
+ <script type="application/javascript" src="referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_anonymousContent_api.html b/dom/base/test/test_anonymousContent_api.html
new file mode 100644
index 0000000000..2bc8d08e62
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_api.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020244 - Test the chrome-only AnonymousContent API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
+ <script type="application/javascript">
+
+ // Testing the presence of the chrome-only API
+ ok(!document.insertAnonymousContent,
+ "Content document shouldn't have access to insertAnonymousContent");
+ ok(!document.removeAnonymousContent,
+ "Content document shouldn't have access to removeAnonymousContent");
+
+ let chromeDocument = SpecialPowers.wrap(document);
+ ok(chromeDocument.insertAnonymousContent,
+ "Chrome document should have access to insertAnonymousContent");
+ ok(chromeDocument.removeAnonymousContent,
+ "Chrome document should have access to removeAnonymousContent");
+
+ // Testing invalid inputs
+ let invalidNodes = [null, undefined, false, 1, "string"];
+ for (let node of invalidNodes) {
+ let didThrow = false;
+ try {
+ chromeDocument.insertAnonymousContent(node);
+ } catch (e) {
+ didThrow = true;
+ }
+ ok(didThrow, "Passing an invalid node to insertAnonymousContent should throw");
+ }
+
+ // Testing the API of the returned object
+ let div = document.createElement("div");
+ div.textContent = "this is a test element";
+ let anonymousContent = chromeDocument.insertAnonymousContent(div);
+ ok(anonymousContent, "AnonymousContent object returned");
+
+ let members = ["getTextContentForElement", "setTextContentForElement",
+ "getAttributeForElement", "setAttributeForElement",
+ "removeAttributeForElement", "getCanvasContext",
+ "setAnimationForElement", "setStyle"];
+ for (let member of members) {
+ ok(member in anonymousContent, "AnonymousContent object defines " + member);
+ }
+ chromeDocument.removeAnonymousContent(anonymousContent);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_append_after_reflow.html b/dom/base/test/test_anonymousContent_append_after_reflow.html
new file mode 100644
index 0000000000..1e1e62d3d5
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_append_after_reflow.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1020244 -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020244 - Make sure anonymous content still works after a reflow (after the canvasframe has been reconstructed)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div>
+ <div id="test-element" style="color:red;">text content</div>
+</div>
+<script type="application/javascript">
+ info("Inserting anonymous content into the document frame");
+ let chromeDocument = SpecialPowers.wrap(document);
+ let testElement = document.querySelector("div");
+ let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
+
+ info("Modifying the style of an element in the anonymous content");
+ let style = anonymousContent.setAttributeForElement("test-element",
+ "style", "color:green;");
+
+ info("Toggling the display style on the document element to force reframing");
+ // Note that we force sync reflows to make sure the canvasframe is recreated
+ // synchronously.
+ document.documentElement.style.display = "none";
+ let forceFlush = document.documentElement.offsetHeight;
+ document.documentElement.style.display = "block";
+ forceFlush = document.documentElement.offsetHeight;
+
+ info("Checking that the anonymous content can be retrieved still");
+ style = anonymousContent.getAttributeForElement("test-element", "style");
+ is(style, "color:green;", "The anonymous content still exists after reflow");
+
+ info("Removing the anonymous content");
+ chromeDocument.removeAnonymousContent(anonymousContent);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_canvas.html b/dom/base/test/test_anonymousContent_canvas.html
new file mode 100644
index 0000000000..cca364a52d
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_canvas.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1212477
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1212477 - Needs a way to access to &lt;canvas&gt;'s context (2d, webgl) from Anonymous Content API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1212477">Mozilla Bug 1212477</a>
+ <div>
+ <div id="id" class="test">text content</div>
+ <canvas id="canvas2d"></canvas>
+ <canvas id="canvas-webgl"></canvas>
+ <canvas id="canvas-foo"></canvas>
+ </div>
+ <script type="application/javascript">
+ let chromeDocument = SpecialPowers.wrap(document);
+ let testElement = document.querySelector("div");
+
+ let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
+
+ is(anonymousContent.getCanvasContext("id", "2d"), null,
+ "Context is null for non-canvas elements");
+
+ let context2d = anonymousContent.getCanvasContext("canvas2d", "2d");
+
+ is(context2d.toString(), "[object CanvasRenderingContext2D]",
+ "2D Context is returned properly");
+
+ is(context2d.canvas, null,
+ "context's canvas property is null in anonymous content");
+
+ is (anonymousContent.getCanvasContext("canvas-foo", "foo"), null,
+ "Context is null for unknown context type");
+
+ SimpleTest.doesThrow(
+ () => anonymousContent.getCanvasContext("foo", "2d"),
+ "NS_ERROR_NOT_AVAILABLE",
+ "Get a context using unexisting id should throw"
+ );
+
+ const normalWebGL = document.createElement('canvas').getContext('webgl');
+ if (normalWebGL) {
+ let webgl = anonymousContent.getCanvasContext("canvas-webgl", "webgl");
+
+ is(webgl.toString(), "[object WebGLRenderingContext]",
+ "WebGL Context is returned properly");
+
+ is(webgl.canvas, null,
+ "WebGL context's canvas property is null in anonymous content");
+ }
+ chromeDocument.removeAnonymousContent(anonymousContent);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_insert.html b/dom/base/test/test_anonymousContent_insert.html
new file mode 100644
index 0000000000..81e03cb45b
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_insert.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020244 - Insert content using the AnonymousContent API, several times, and don't remove it</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
+<div>
+ <div id="id" class="test">text content</div>
+</div>
+ <script type="application/javascript">
+ const INSERTED_NB = 5;
+
+ // Insert the same content several times
+ let chromeDocument = SpecialPowers.wrap(document);
+ let testElement = document.querySelector("div");
+
+ let anonymousContents = [];
+ for (let i = 0; i < INSERTED_NB; i ++) {
+ let content = chromeDocument.insertAnonymousContent(testElement);
+ // Adding an expando pointing to the document to make sure this doesn't
+ // cause leaks.
+ content.dummyExpando = testElement.ownerDocument;
+ anonymousContents.push(content);
+ }
+
+ // Make sure that modifying one of the inserted elements does not modify the
+ // other ones.
+ anonymousContents[0].setAttributeForElement("id", "class", "updated");
+ for (let i = 1; i < INSERTED_NB; i ++) {
+ is(anonymousContents[i].getAttributeForElement("id", "class"),
+ "test",
+ "Element " + i + " didn't change when element 0 was changed");
+ }
+
+ // Do not remove inserted elements on purpose to test for potential leaks too.
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_manipulate_content.html b/dom/base/test/test_anonymousContent_manipulate_content.html
new file mode 100644
index 0000000000..eb4bcb6be3
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_manipulate_content.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020244 - Manipulate content created with the AnonymousContent API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
+<div>
+ <div id="test-element" class="test-class" test="test">text content</div>
+</div>
+ <script type="application/javascript">
+
+ // Insert content
+ let chromeDocument = SpecialPowers.wrap(document);
+ let testElement = document.querySelector("div");
+ let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
+
+ // Test getting/setting text content.
+ is(anonymousContent.getTextContentForElement("test-element"),
+ "text content", "Textcontent for the test element is correct");
+
+ anonymousContent.setTextContentForElement("test-element",
+ "updated text content");
+ is(anonymousContent.getTextContentForElement("test-element"),
+ "updated text content",
+ "Textcontent for the test element is correct after update");
+
+ // Test that modifying the original DOM element doesn't change the inserted
+ // element.
+ testElement.removeAttribute("test");
+ is(anonymousContent.getAttributeForElement("test-element", "test"),
+ "test",
+ "Removing attributes on the original DOM node does not change the inserted node");
+
+ testElement.setAttribute("test", "test-updated");
+ is(anonymousContent.getAttributeForElement("test-element", "test"),
+ "test",
+ "Setting attributes on the original DOM node does not change the inserted node");
+
+ // Test getting/setting/removing attributes on the inserted element and test
+ // that this doesn't change the original DOM element.
+ is(anonymousContent.getAttributeForElement("test-element", "class"),
+ "test-class", "Class attribute for the test element is correct");
+
+ anonymousContent.setAttributeForElement("test-element", "class",
+ "updated-test-class");
+ is(anonymousContent.getAttributeForElement("test-element", "class"),
+ "updated-test-class",
+ "Class attribute for the test element is correct after update");
+ ok(testElement.getAttribute("class") !== "updated-test-class",
+ "Class attribute change on the inserted node does not change the original DOM node");
+
+ anonymousContent.removeAttributeForElement("test-element", "class");
+ is(anonymousContent.getAttributeForElement("test-element", "class"), null,
+ "Class attribute for the test element was removed");
+
+ let anim = anonymousContent.setAnimationForElement("test-element", [
+ { transform: 'translateY(0px)' },
+ { transform: 'translateY(-300px)' }
+ ], 2000);
+ is(anim.playState, "running", "Animation should be running");
+ anim.cancel();
+ is(anim.playState, "idle", "Animation should have stopped immediately");
+
+ chromeDocument.removeAnonymousContent(anonymousContent);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_set_style.html b/dom/base/test/test_anonymousContent_set_style.html
new file mode 100644
index 0000000000..75587b3e0f
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_set_style.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1571650
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1571650 - Test AnonymousContent.setStyle() API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1571650">Mozilla Bug 1571650</a>
+ <script type="application/javascript">
+ const chromeDocument = SpecialPowers.wrap(document);
+
+ info("Set z-index to anonymous content");
+ const div = document.createElement("div");
+ div.setAttribute("class", "set-style-test");
+ const anonymousContent = chromeDocument.insertAnonymousContent(div);
+ anonymousContent.setStyle("z-index", 3);
+
+ info("Test the element which became to anonymous");
+ const mozCustomContentContainerEl =
+ [...SpecialPowers.InspectorUtils.getChildrenForNode(document.documentElement, true, false)]
+ .find(n => n.classList && n.classList.contains("moz-custom-content-container"));
+
+ const targetEl = mozCustomContentContainerEl.querySelector(".set-style-test");
+ ok(targetEl, "Element which became to anonymous is found");
+ is(targetEl.style.zIndex, "3", "z-index is correct");
+
+ chromeDocument.removeAnonymousContent(anonymousContent);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_style_csp.html b/dom/base/test/test_anonymousContent_style_csp.html
new file mode 100644
index 0000000000..0eca83404c
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_style_csp.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1020244 -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1185351 - Make sure that we don't enforce CSP on styles for AnonymousContent</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div>
+ <div id="test-element" style="color:red;">text content</div>
+</div>
+<script type="application/javascript">
+ let chromeDocument = SpecialPowers.wrap(document);
+ let testElement = document.querySelector("div");
+ let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
+
+ let style = anonymousContent.setAttributeForElement("test-element",
+ "style", "color:green;");
+
+ style = anonymousContent.getAttributeForElement("test-element", "style");
+ is(style, "color:green;", "The anonymous content exists with CSP");
+
+ chromeDocument.removeAnonymousContent(anonymousContent);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_anonymousContent_style_csp.html^headers^ b/dom/base/test/test_anonymousContent_style_csp.html^headers^
new file mode 100644
index 0000000000..b7b3c8a4f9
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_style_csp.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
diff --git a/dom/base/test/test_anonymousContent_xul_window.xhtml b/dom/base/test/test_anonymousContent_xul_window.xhtml
new file mode 100644
index 0000000000..b81f827075
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_xul_window.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
+Check that XUL windows don't make insertAnonymousContent crash.
+-->
+<window title="Anonymous content in a XUL window"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml"></body>
+ <box>This is a test box</box>
+
+ <script type="application/javascript">
+ // Insert content
+ let testElement = document.querySelector("box");
+ document.insertAnonymousContent(testElement);
+ ok(true,
+ "Inserting anonymous content in a XUL document did not crash")
+ </script>
+</window>
diff --git a/dom/base/test/test_appname_override.html b/dom/base/test/test_appname_override.html
new file mode 100644
index 0000000000..bf1162a900
--- /dev/null
+++ b/dom/base/test/test_appname_override.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=939445
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 939445 - general.appname.override</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=939445">Mozilla Bug 939445</a>
+ <script type="application/javascript">
+
+ function runTest() {
+ is(navigator.appName, "hello", "general.appname.override not working");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["general.appname.override", "hello"]]}, runTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_async_setTimeout_stack.html b/dom/base/test/test_async_setTimeout_stack.html
new file mode 100644
index 0000000000..773a923be3
--- /dev/null
+++ b/dom/base/test/test_async_setTimeout_stack.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1142577 - Async stacks for setTimeout</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
+ <pre id="stack"></pre>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Testing async stacks across setTimeout");
+
+ function getFunctionName(frame) {
+ return frame.slice(0, frame.indexOf("@"));
+ }
+
+ function a() { b() }
+ function b() { c() }
+ function c() { setTimeout(d, 1) }
+ function d() { e() }
+ function e() { f() }
+ function f() { setTimeout(g, 1) }
+ function g() { h() }
+ function h() { i() }
+ function i() {
+ var stackString = Error().stack;
+ document.getElementById("stack").textContent = stackString;
+
+ var frames = stackString
+ .split("\n")
+ .map(getFunctionName)
+ .filter(function (name) { return !!name; });
+
+ is(frames[0], "i");
+ is(frames[1], "h");
+ is(frames[2], "g");
+ is(frames[3], "setTimeout handler*SimpleTest_setTimeoutShim");
+ is(frames[4], "f");
+ is(frames[5], "e");
+ is(frames[6], "d");
+ is(frames[7], "setTimeout handler*SimpleTest_setTimeoutShim");
+ is(frames[8], "c");
+ is(frames[9], "b");
+ is(frames[10], "a");
+
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv(
+ {"set": [['javascript.options.asyncstack_capture_debuggee_only', false]]},
+ a);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_async_setTimeout_stack_across_globals.html b/dom/base/test/test_async_setTimeout_stack_across_globals.html
new file mode 100644
index 0000000000..358aa21abe
--- /dev/null
+++ b/dom/base/test/test_async_setTimeout_stack_across_globals.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1142577 - Async stacks for setTimeout</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
+ <pre id="stack"></pre>
+ <iframe id="iframe"></iframe>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var otherGlobal = document.getElementById("iframe").contentWindow;
+
+ function getFunctionName(frame) {
+ return frame.slice(0, frame.indexOf("@"));
+ }
+
+ function a() { b() }
+ function b() { c() }
+ function c() { otherGlobal.setTimeout(d, 1) }
+ function d() { e() }
+ function e() { f() }
+ function f() { otherGlobal.setTimeout(g, 1) }
+ function g() { h() }
+ function h() { i() }
+ function i() {
+ var stackString = Error().stack;
+ document.getElementById("stack").textContent = stackString;
+
+ var frames = stackString
+ .split("\n")
+ .map(getFunctionName)
+ .filter(function (name) { return !!name; });
+
+ is(frames[0], "i");
+ is(frames[1], "h");
+ is(frames[2], "g");
+ is(frames[3], "setTimeout handler*f");
+ is(frames[4], "e");
+ is(frames[5], "d");
+ is(frames[6], "setTimeout handler*c");
+ is(frames[7], "b");
+ is(frames[8], "a");
+
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv(
+ {"set": [['javascript.options.asyncstack_capture_debuggee_only', false]]},
+ a);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_base.xhtml b/dom/base/test/test_base.xhtml
new file mode 100644
index 0000000000..b61bc72f68
--- /dev/null
+++ b/dom/base/test/test_base.xhtml
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for base URIs</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <base href="/tests/dom/base/" />
+</head>
+<body>
+<div id="1" xml:base="supercalifragilisticexpialidocious"><p><p xml:base="hello/"><p xml:base="world"><span xml:base="#iamtheverymodelofamodernmajorgeneral">text</span></p></p></p></div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ is(document.baseURI, "http://mochi.test:8888/tests/dom/base/",
+ "document base");
+ is(document.body.baseURI, "http://mochi.test:8888/tests/dom/base/",
+ "body base");
+
+ var expected =
+ ["http://mochi.test:8888/tests/dom/base/",
+ "http://mochi.test:8888/tests/dom/base/",
+ "http://mochi.test:8888/tests/dom/base/",
+ "http://mochi.test:8888/tests/dom/base/",
+ "http://mochi.test:8888/tests/dom/base/",
+ "http://mochi.test:8888/tests/dom/base/",
+ ];
+ var node = document.getElementById("1");
+ while(node) {
+ is(node.baseURI, expected.shift(), "node base");
+ node = node.firstChild;
+ }
+ is(expected.length, 0, "found all expected nodes");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_blockParsing.html b/dom/base/test/test_blockParsing.html
new file mode 100644
index 0000000000..05f104ce62
--- /dev/null
+++ b/dom/base/test/test_blockParsing.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for document.blockParsing</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
+
+async function runTest(url, initialHTML, finalHTML) {
+ let iframe = document.createElement("iframe");
+ iframe.src = url;
+
+ let blockerPromise;
+ let promise = TestUtils.topicObserved("document-element-inserted", document => {
+ if (document !== iframe.contentDocument) {
+ return false;
+ }
+
+ blockerPromise = new Promise(resolve => {
+ setTimeout(resolve, 0);
+ }).then(() => {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }).then(() => {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ });
+
+ is(document.documentElement.outerHTML, initialHTML,
+ "Should have initial HTML during document-element-inserted");
+ is(document.defaultView.wrappedJSObject.scriptRan, undefined,
+ "Script node should not have run");
+
+ document.blockParsing(blockerPromise);
+
+ return true;
+ }).then(([document]) => {
+ return document;
+ });
+
+ document.body.appendChild(iframe);
+
+ // Wait for document-element-inserted to fire.
+ let doc = await promise;
+ let win = doc.defaultView.wrappedJSObject;
+ let root = doc.documentElement;
+
+ // At this point, if the parser was successfully blocked, we should still
+ // have the initial skeleton HTML for the page.
+ is(root.outerHTML, initialHTML, "Should have initial HTML after document-element-inserted returns");
+ is(win.scriptRan, undefined, "Script node should still not have run");
+
+ await blockerPromise;
+
+ // Just after the promise that's blocking the parser fires, we shouldn't have
+ // returned to the main event loop, so we should still have the initial HTML.
+ is(root.outerHTML, initialHTML, "Should still have initial HTML");
+ is(win.scriptRan, undefined, "Script node should still not have run");
+
+ await new Promise(resolve => win.addEventListener("DOMContentLoaded", resolve, {once: true}));
+
+ // Parsing should have resumed, and we should have finished loading the document.
+ is(root.outerHTML, finalHTML, "Should have final HTML");
+ is(win.scriptRan, true, "Script node should have run");
+
+ iframe.remove();
+}
+
+add_task(async function() {
+ await runTest("http://mochi.test:8888/chrome/dom/base/test/file_inline_script.html",
+ '<html lang="en"></html>',
+ '<html lang="en"><head>\n <script>window.scriptRan = true;<\/script>\n <meta charset="utf-8">\n <title></title>\n</head>\n<body>\n <p>Hello Mochitest</p>\n\n\n</body></html>');
+
+ await runTest("http://mochi.test:8888/chrome/dom/base/test/file_inline_script.xhtml",
+ '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>',
+ '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">\n<head>\n <script>window.scriptRan = true;<\/script>\n <title></title>\n</head>\n<body>\n <p>Hello Mochitest</p>\n</body>\n</html>');
+
+ await runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.html",
+ '<html lang="en"></html>',
+ '<html lang="en"><head>\n <script src="file_script.js"><\/script>\n <meta charset="utf-8">\n <title></title>\n</head>\n<body>\n <p>Hello Mochitest</p>\n\n\n</body></html>');
+
+ await runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.xhtml",
+ '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>',
+ '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">\n<head>\n <script src="file_script.js"><\/script>\n <title></title>\n</head>\n<body>\n <p>Hello Mochitest</p>\n</body>\n</html>');
+});
+
+add_task(async function test_cleanup() {
+ const TOPIC = "blocking-promise-destroyed";
+
+ const finalizationWitness = Cc["@mozilla.org/toolkit/finalizationwitness;1"]
+ .getService(Ci.nsIFinalizationWitnessService);
+
+ for (let url of ["http://mochi.test:8888/chrome/dom/base/test/file_inline_script.html",
+ "http://mochi.test:8888/chrome/dom/base/test/file_inline_script.xhtml"]) {
+ let iframe = document.createElement("iframe");
+ iframe.src = url;
+
+ // Create a promise that never resolves.
+ let blockerPromise = new Promise(() => {});
+
+ // Create a finalization witness so we can be sure that the promises
+ // have been collected before the end of the test.
+ let destroyedPromise = TestUtils.topicObserved(TOPIC);
+ let witness = finalizationWitness.make(TOPIC, url);
+ blockerPromise.witness = witness;
+
+ let insertedPromise = TestUtils.topicObserved("document-element-inserted", document => {
+ document.blockParsing(blockerPromise).witness = witness;
+
+ return true;
+ });
+
+ document.body.appendChild(iframe);
+ await insertedPromise;
+
+ // Clear the promise reference, destroy the document, and force GC/CC. This should
+ // trigger any potential leaks or cleanup issues.
+ blockerPromise = null;
+ witness = null;
+ iframe.remove();
+
+ Cu.forceGC();
+ Cu.forceCC();
+ Cu.forceGC();
+
+ // Make sure the blocker promise has been collected.
+ let [, data] = await destroyedPromise;
+ is(data, url, "Should have correct finalizer URL");
+ }
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_blocking_image.html b/dom/base/test/test_blocking_image.html
new file mode 100644
index 0000000000..64924b0dab
--- /dev/null
+++ b/dom/base/test/test_blocking_image.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1267075
+-->
+ <title>Test for Bug 1267075</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1267075">Mozilla Bug 1267075</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function onLoad() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = function () {
+ info("iframe loaded");
+ function imgListener(img) {
+ return new Promise((resolve, reject) => {
+ img.addEventListener("load", () => reject());
+ img.addEventListener("error", () => resolve());
+ });
+ }
+
+ var doc = iframe.contentDocument;
+ var img = doc.createElement("img");
+ var img2 = doc.createElement("img");
+ var img3 = doc.createElement("img");
+
+ // image from HTTP should be blocked.
+ img.src = "http://example.com/tests/image/test/mochitest/shaver.png";
+ doc.body.appendChild(img);
+
+ imgListener(img).then(() => {
+ ok(true, "img shouldn't be loaded");
+
+ // `iframe` is a content iframe, and thus not in the same docgroup with
+ // us, which are a chrome-privileged test.
+ //
+ // Ensure the frame is laid out so that this cross-origin
+ // getComputedStyle call is guaranteed to work after bug 1440537.
+ iframe.getBoundingClientRect();
+
+ ok(img2.matches(":-moz-broken"), "should match ':-moz-broken' selector");
+
+ img2.src = "https://test.invalid";
+ doc.body.appendChild(img2);
+ return imgListener(img2);
+ }).then(() => {
+ ok(true, "img2 shouldn't be loaded");
+ ok(img2.matches(":-moz-broken"), "should match ':-moz-broken' selector");
+
+ // Now prepare for the next test, deny image.
+ return new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [["permissions.default.image", 2]]}, resolve)
+ });
+ }).then(() => {
+ // Now image is denied, loading image will be rejected with REJECT_TYPE,
+ // which will be mapped to :-moz-broken too.
+ img3.src = "https://example.com/tests/image/test/mochitest/shaver.png";
+ doc.body.appendChild(img3);
+ return imgListener(img3);
+ }).then(() => {
+ ok(true, "img3 shouldn't be loaded");
+
+ ok(img3.matches(":-moz-broken"), "should match ':-moz-broken' selector");
+
+ SimpleTest.finish();
+ }).catch((e) => {
+ // Expected early return
+ if(e === true) {
+ SimpleTest.finish();
+ return;
+ }
+ ok(false, "throwing " + e);
+ });
+ };
+
+ // file_blocking_image.html contains meta tag for CSP, which will block images from
+ // http.
+ iframe.src = "http://mochi.test:8888/chrome/dom/base/test/file_blocking_image.html";
+ document.getElementById("content").appendChild(iframe);
+}
+</script>
+</pre>
+<p id="display"></p>
+<div id="content">
+</div>
+
+</body> </html>
diff --git a/dom/base/test/test_bug1008126.html b/dom/base/test/test_bug1008126.html
new file mode 100644
index 0000000000..414f2727ec
--- /dev/null
+++ b/dom/base/test/test_bug1008126.html
@@ -0,0 +1,62 @@
+<!--
+2 Any copyright is dedicated to the Public Domain.
+3 http://creativecommons.org/publicdomain/zero/1.0/
+4 -->
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1008126
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1008126</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1008126">Mozilla Bug 1008126</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+function translateChrome(uriStr) {
+ const { Cc, Ci } = SpecialPowers;
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
+ let uri = ios.newURI(uriStr, null, ios.newURI(document.baseURI));
+ return chromeReg.convertChromeURL(uri).spec;
+}
+
+function runTest() {
+ var worker = new Worker("file_bug1008126_worker.js");
+
+ worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.filename + ":" + event.lineno + ":" + event.colno + ": " + event.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(translateChrome("file_bug945152.jar"));
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.mapped_arraybuffer.enabled", true]]}, function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1016960.html b/dom/base/test/test_bug1016960.html
new file mode 100644
index 0000000000..d801a1dba5
--- /dev/null
+++ b/dom/base/test/test_bug1016960.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1016960
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1016960</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1016960 **/
+
+ var chromeWindow = window.browsingContext.topChromeWindow.open("chrome://mochitests/content/chrome/dom/base/test/file_empty.html", "1016960", "chrome");
+ ok(chromeWindow.isChromeWindow, "A chrome window should return true for .isChromeWindow.");
+ chromeWindow.close();
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1016960">Mozilla Bug 1016960</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1022229.html b/dom/base/test/test_bug1022229.html
new file mode 100644
index 0000000000..7663cc22d0
--- /dev/null
+++ b/dom/base/test/test_bug1022229.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1022229
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1022229</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for postMessage between sandboxed iframe and non-sandboxed window.
+ **/
+ SimpleTest.waitForExplicitFinish();
+ function go() {
+ var ifr = document.createElement('iframe');
+
+ ifr.setAttribute('src', 'iframe_main_bug1022229.html');
+ document.body.appendChild(ifr);
+ }
+
+ </script>
+</head>
+<body onload="go()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1022229">Mozilla Bug 1022229</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1025933.html b/dom/base/test/test_bug1025933.html
new file mode 100644
index 0000000000..e578ebef20
--- /dev/null
+++ b/dom/base/test/test_bug1025933.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1025933
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1025933</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1025933 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ var iframe = document.createElement('iframe');
+ iframe.srcdoc = '<div id="content"> <div id="host"></div </div>';
+
+ iframe.onload = function() {
+ var s = iframe.contentDocument.getElementById("host").attachShadow({mode: 'open'});
+ s.innerHTML = '<div style="width:100px;height:100px;background:red"></div>';
+ var el = s.firstElementChild;
+ is(el.clientWidth, 100);
+ is(el.clientHeight, 100);
+ SimpleTest.finish();
+ }
+
+ document.body.appendChild(iframe);
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1025933">Mozilla Bug 1025933</a>
+<p id="display"></p>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1037687.html b/dom/base/test/test_bug1037687.html
new file mode 100644
index 0000000000..62f886c837
--- /dev/null
+++ b/dom/base/test/test_bug1037687.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1037687
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1037687</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1037687">Mozilla Bug 1037687</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+ /** Test for Bug 1037687 **/
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ // This test loads in an iframe, to ensure that the element instance is
+ // loaded with the correct value of the preference.
+ let iframe = document.createElement("iframe");
+ iframe.src = "test_bug1037687_subframe.html";
+ document.body.appendChild(iframe);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1037687_subframe.html b/dom/base/test/test_bug1037687_subframe.html
new file mode 100644
index 0000000000..4258f772b6
--- /dev/null
+++ b/dom/base/test/test_bug1037687_subframe.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="application/javascript">
+ var SimpleTest = window.parent.SimpleTest;
+ var ok = window.parent.ok;
+ var is = window.parent.is;
+
+ var host;
+ var embed;
+ var object;
+ var iframe;
+ var resourceLoadCount = 0;
+
+ function resourceLoaded(event) {
+ ++resourceLoadCount;
+ ok(true, event.target + " got " + event.load);
+ if (resourceLoadCount == 3) {
+ SimpleTest.finish();
+ }
+ }
+
+ function createResource(sr, type) {
+ var el = document.createElement(type);
+ var attrName = type == "object" ? "data" : "src";
+ el.setAttribute(attrName, "file_mozfiledataurl_img.jpg");
+ el.onload = resourceLoaded;
+ var info = document.createElement("div");
+ info.textContent = type;
+ sr.appendChild(info);
+ sr.appendChild(el);
+ }
+
+ function test() {
+ host = document.getElementById("host");
+ let sr = host.attachShadow({mode: 'open'});
+ embed = createResource(sr, "embed");
+ object = createResource(sr, "object");
+ iframe = createResource(sr, "iframe");
+ }
+</script>
+</head>
+<body onload="test()">
+<div id="host"></div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1043106.html b/dom/base/test/test_bug1043106.html
new file mode 100644
index 0000000000..42d6cfff57
--- /dev/null
+++ b/dom/base/test/test_bug1043106.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1043106
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1043106</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1043106">Mozilla Bug 1043106</a>
+ <iframe id="iframe"></iframe>
+ <script type="application/javascript">
+
+var storage;
+
+window.addEventListener("storage", function (event) {
+ is(event.storageArea, storage, "The storageArea is correct");
+ storage.removeItem("a");
+ runTests();
+});
+
+var tests = [ { key: 'localStorage', storage: localStorage },
+ { key: 'sessionStorage', storage: sessionStorage } ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var t = tests.shift();
+ storage = t.storage;
+
+ var ifr = document.getElementById("iframe");
+ ifr.srcdoc = "<script>"+ t.key + ".setItem(\"a\",\"b\");</"+"script>";
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1057176.html b/dom/base/test/test_bug1057176.html
new file mode 100644
index 0000000000..e9e516ac1e
--- /dev/null
+++ b/dom/base/test/test_bug1057176.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1057176
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1057176</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1057176 **/
+ var doc = document.implementation.createDocument(null, null);
+ var elem = doc.createElementNS("http://www.w3.org/1999/xhtml", "input");
+ elem.pattern = "abc";
+ elem.value = "def";
+ ok(!elem.validity.valid, '"def" should not match the pattern "abc"');
+ elem.value = "abc";
+ ok(elem.validity.valid, '"abc" should match the pattern "abc"');
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1057176">Mozilla Bug 1057176</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1060938.html b/dom/base/test/test_bug1060938.html
new file mode 100644
index 0000000000..451ed0d6fb
--- /dev/null
+++ b/dom/base/test/test_bug1060938.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1060938
+-->
+ <head>
+ <meta charset="utf-8">
+ <title> Test for Bug 1060938 </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"> </script>
+ <script src="/tests/SimpleTest/EventUtils.js"> </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1060938"> Mozilla Bug 1060938 </a>
+ <p id="display"></p>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 1060938 **/
+ // test: Element.removeAttributeNode()
+
+ parent = document.getElementsByTagName("p")[0];
+ parent.setAttributeNS("www.test1.com", "ID", "Test1");
+ parent.setAttributeNS("www.test2.com", "Class", "Test2");
+ parent.setAttribute("id", "www.test3.com");
+ parent.className = "www.test4.com";
+
+ allAttributes = parent.attributes;
+
+ function removeAttr(iter){
+ var removed_attribute = allAttributes[0];
+ is(removed_attribute, parent.removeAttributeNode(removed_attribute),
+ "(" + iter + ")" + " Returned attribute and remove attribute should be same.");
+ }
+
+ removeAttr(1);
+ removeAttr(2);
+ removeAttr(3);
+ removeAttr(4);
+
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug1064481.html b/dom/base/test/test_bug1064481.html
new file mode 100644
index 0000000000..9ce32f4d49
--- /dev/null
+++ b/dom/base/test/test_bug1064481.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1064481
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1064481</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1064481">Mozilla Bug 1064481</a>
+ <iframe id="iframe"></iframe>
+ <script type="application/javascript">
+
+var a = new URLSearchParams();
+
+a.set('foobar', 'a\nb');
+is(a.toString(), 'foobar=a%0Ab', "Bug fixed");
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1070015.html b/dom/base/test/test_bug1070015.html
new file mode 100644
index 0000000000..5debff4dc0
--- /dev/null
+++ b/dom/base/test/test_bug1070015.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1070015
+-->
+<head>
+ <title>Test for Bug 1070015</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1070015">Mozilla Bug 1070015</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="attrTest1" testAttr="testValue1"></div>
+<div id="attrTest2" testAttr="testValue2"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1070015 **/
+
+function testRemoveAttribute() {
+ var attrTest1 = document.getElementById("attrTest1");
+ var attr1 = attrTest1.getAttributeNode("testAttr");
+
+ var attrTest2 = document.getElementById("attrTest2");
+ var attr2 = attrTest2.getAttributeNode("testAttr");
+
+ ok(attrTest1.hasAttribute("testAttr"), "First object should have attribute");
+ ok(attrTest2.hasAttribute("testAttr"), "Second object should have attribute");
+
+ try {
+ attrTest1.removeAttributeNode(attr2);
+ ok(false, "Should throw when removing from the different element");
+ } catch (e) {
+ ok(true, "Should throw when removing from the different element");
+ }
+
+ ok(attrTest1.hasAttribute("testAttr"), "Object should not remove attribute which not belongs to it");
+ ok(attrTest2.hasAttribute("testAttr"), "Object should not be changed");
+
+ attrTest1.removeAttributeNode(attr1);
+ ok(!attrTest1.hasAttribute("testAttr"), "Object should remove its attribute");
+}
+testRemoveAttribute();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1075702.html b/dom/base/test/test_bug1075702.html
new file mode 100644
index 0000000000..a3842a21a5
--- /dev/null
+++ b/dom/base/test/test_bug1075702.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1075702
+-->
+<head>
+ <meta charset="utf-8">
+ <title> Test for Bug 1075702 </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"> </script>
+ <script src="/tests/SimpleTest/EventUtils.js"> </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1075702"> Mozilla Bug 1075702 </a>
+<p id="display"></p>
+
+<pre id="test">
+<script type="application/javascript">
+
+ /** Test for Bug 1075702 **/
+ // test: Element.removeAttributeNode()
+
+ var a1 = document.createAttribute("aa");
+ a1.nodeValue = "lowercase";
+
+ var a2 = document.createAttributeNS("", "AA");
+ a2.nodeValue = "UPPERCASE";
+
+ document.documentElement.setAttributeNode(a1);
+ document.documentElement.setAttributeNode(a2);
+
+ is(document.documentElement.getAttributeNS("", "aa"), "lowercase", "Should be lowercase!");
+ is(document.documentElement.getAttributeNS("", "AA"), "UPPERCASE", "Should be UPPERCASE!");
+
+ var a3 = document.createAttribute("AA");
+ a3.nodeValue = "UPPERCASE AGAIN";
+ document.documentElement.setAttributeNode(a3);
+
+ is(document.documentElement.getAttributeNS("", "aa"), "UPPERCASE AGAIN",
+ "Should be UPPERCASE AGAIN!");
+ is(document.documentElement.getAttributeNS("", "AA"), "UPPERCASE", "Should be UPPERCASE!");
+
+ var removedNodeAccordingToEvent;
+
+ function mutationHandler(aEvent) {
+ removedNodeAccordingToEvent = aEvent.relatedNode;
+ }
+
+ var test1 = document.createElement("div");
+ test1.setAttribute("x", "y");
+ removedNodeAccordingToEvent = null;
+
+ function testremoveNamedItemNS() {
+ test1.addEventListener("DOMAttrModified", mutationHandler, true);
+ var removedNodeAccordingToRemoveNamedItemNS = test1.attributes.removeNamedItemNS(null, "x");
+ test1.removeEventListener("DOMAttrModified", mutationHandler, true);
+ is(removedNodeAccordingToEvent, removedNodeAccordingToRemoveNamedItemNS, "Node removed according to event is not same as node removed by removeNamedItemNS.");
+ }
+
+ testremoveNamedItemNS();
+
+ var test2 = document.createElement("div");
+ test2.setAttribute("x", "y");
+ removedNodeAccordingToEvent = null;
+
+ function testremoveNamedItem() {
+ test2.addEventListener("DOMAttrModified", mutationHandler, true);
+ var removedNodeAccordingToRemoveNamedItem = test2.attributes.removeNamedItem("x");
+ test2.removeEventListener("DOMAttrModified", mutationHandler, true);
+ is(removedNodeAccordingToEvent, removedNodeAccordingToRemoveNamedItem, "Node removed according to event is not same as node removed by removeNamedItem.");
+ }
+
+ testremoveNamedItem();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1091883.html b/dom/base/test/test_bug1091883.html
new file mode 100644
index 0000000000..fd2558d89c
--- /dev/null
+++ b/dom/base/test/test_bug1091883.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1091883
+-->
+<head>
+ <meta charset="utf-8">
+ <meta name="referrer" content="origin-when-cross-origin">
+ <title>Test for Bug 1091883</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1091883">Mozilla Bug 1091883</a></p>
+<h2>Results</h2>
+<pre id="results">Running...</pre>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var origins = [
+ "http://mochi.test:8888", "http://example.com", "http://example.org"];
+var numOrigins = origins.length;
+
+// For each combination of (frame, subframe, target) origins, this test
+// includes a "frame" that includes a "subframe"; and then this test
+// navigates this "subframe" to the "target". Both the referrer and
+// the triggering principal are this test, i.e., "http://mochi.test:8888".
+// Since the referrer policy is origin-when-cross-origin, we expect to have
+// a full referrer if and only if the target is also "http://mochi.test:8888";
+// in all other cases, the referrer needs to be the origin alone.
+var numTests = numOrigins * numOrigins * numOrigins;
+
+// Helpers to look up the approriate origins for a given test number.
+function getFrameOrigin(i) {
+ return origins[(i / (numOrigins * numOrigins)) | 0];
+}
+function getSubframeOrigin(i) {
+ return origins[((i / numOrigins) | 0) % 3];
+}
+function getTargetOrigin(i) {
+ return origins[i % 3];
+}
+
+// Create the frames, and tell them which subframes to load.
+for (let i = 0; i < numTests; i++) {
+ var frame = document.createElement("iframe");
+ frame.src = getFrameOrigin(i) +
+ "/tests/dom/base/test/file_bug1091883_frame.html#" +
+ getSubframeOrigin(i);
+ document.body.appendChild(frame);
+}
+
+// Navigate all subframes to the target.
+window.onload = function() {
+ for (let i = 0; i < numTests; i++) {
+ frames[i].frames[0].location = getTargetOrigin(i) +
+ "/tests/dom/base/test/file_bug1091883_target.html#" + i;
+ }
+};
+
+// Check referrer messages from the target.
+var results = {};
+function makeResultsKey(i) {
+ return i + ": " + getFrameOrigin(i) + " | " + getSubframeOrigin(i) + " -> " +
+ getTargetOrigin(i);
+}
+window.addEventListener("message", function(event) {
+ var out = event.data.split(" ");
+ var referrer = out[0];
+ var testRun = +out[1];
+ results[makeResultsKey(testRun)] = referrer;
+ if (event.origin == "http://mochi.test:8888") {
+ is(referrer.split("?")[0],
+ "http://mochi.test:8888/tests/dom/base/test/test_bug1091883.html",
+ "must be full referrer");
+ } else {
+ is(referrer, "http://mochi.test:8888/", "must be origin referrer");
+ }
+ if (Object.keys(results).length == numTests) {
+ document.getElementById("results").textContent =
+ JSON.stringify(results, null, 4);
+ SimpleTest.finish();
+ }
+});
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1100912.html b/dom/base/test/test_bug1100912.html
new file mode 100644
index 0000000000..95913e8cdd
--- /dev/null
+++ b/dom/base/test/test_bug1100912.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1100912
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1100912</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /** Test for Bug 1100912 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTests() {
+ win = window.open("file_bug1100912.html", "");
+ }
+
+ function didRunTests() {
+ setTimeout("SimpleTest.finish()");
+ }
+
+ </script>
+</head>
+<body onload="SimpleTest.waitForFocus(runTests)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100912">Mozilla Bug 1100912</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1101364.html b/dom/base/test/test_bug1101364.html
new file mode 100644
index 0000000000..f3794d3dd0
--- /dev/null
+++ b/dom/base/test/test_bug1101364.html
@@ -0,0 +1,93 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1101364
+-->
+<head>
+<title>Test for Bug 1101364</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #test1 {
+ user-select: none;
+ }
+
+ #testDiv, #test2 {
+ user-select: text;
+ }
+ </style>
+</head>
+<body id='body'>
+
+<iframe id="test1" srcdoc="<h1 id='test1' style='user-select:none'>Header</h1><div id='testDiv'>test1</div>"></iframe>
+<iframe id="test2" srcdoc="<div contenteditable id='test2'>AAA<span id='test2Inner'>BBB</span></div>"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ await (async () => {
+ const iframe = document.getElementById("test1");
+ iframe.focus();
+ const docShell = SpecialPowers.wrap(iframe.contentWindow).docShell;
+
+ docShell.doCommand("cmd_selectAll");
+ info(
+ "Waiting for getting screenshot of \"Select All\" without contenteditable..."
+ );
+ const withoutContenteditable = await snapshotWindow(iframe.contentWindow);
+
+ iframe.contentDocument
+ .getElementById("testDiv")
+ .setAttribute("contentEditable", true);
+ docShell.doCommand("cmd_selectAll");
+ info(
+ "Waiting for getting screenshot of \"Select All\" in contenteditable..."
+ );
+ const withContenteditable = await snapshotWindow(iframe.contentWindow);
+ const result =
+ compareSnapshots(withoutContenteditable, withContenteditable, true);
+ ok(
+ result[0],
+ `Select all should look identical\ngot: ${
+ result[2]
+ }\nexpected: ${result[1]}`
+ );
+ })();
+
+ await (async () => {
+ const iframe = document.getElementById("test2");
+ iframe.focus();
+ iframe.contentDocument.querySelector("div[contenteditable]").focus();
+ const docShell = SpecialPowers.wrap(iframe.contentWindow).docShell;
+ const test2Inner = iframe.contentDocument.getElementById("test2Inner");
+ test2Inner.style.MozUserSelect = "text";
+ docShell.doCommand("cmd_selectAll");
+ info(
+ "Waiting for getting screenshot of \"Select All\" in contenteditable (use-select: text)..."
+ );
+ const withoutUserSelect = await snapshotWindow(iframe.contentWindow);
+
+ test2Inner.style.MozUserSelect = "none";
+ docShell.doCommand("cmd_selectAll");
+ info(
+ "Waiting for getting screenshot of \"Select All\" in contenteditable (use-select: none)..."
+ );
+ const withUserSelect = await snapshotWindow(iframe.contentWindow);
+ const result = compareSnapshots(withoutUserSelect, withUserSelect, true);
+ ok(
+ result[0],
+ `Editable fields should ignore user select style\ngot: ${
+ result[2]
+ }\nexpected: ${result[1]}`
+ );
+ })();
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1118689.html b/dom/base/test/test_bug1118689.html
new file mode 100644
index 0000000000..e073b6fcfa
--- /dev/null
+++ b/dom/base/test/test_bug1118689.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1118689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1118689</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1118689 **/
+ SimpleTest.requestFlakyTimeout("Just need some random timeout.");
+
+ function test() {
+ // check that load event doesn't keep up being dispatched if
+ // window has been closed.
+ var win = window.open('data:text/html,<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP" onload="this.src = this.src">',
+ "", "height=100,width=100");
+ setTimeout(function() {
+ win.close();
+ SimpleTest.finish();
+ }, 2500);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1118689">Mozilla Bug 1118689</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1120222.html b/dom/base/test/test_bug1120222.html
new file mode 100644
index 0000000000..f4f1bf8faa
--- /dev/null
+++ b/dom/base/test/test_bug1120222.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1120222
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test document-title-changed observer</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ const obsSvc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+
+ const observer = {
+ observe: (aSubject, aTopic, aData) => {
+ if (aTopic === "document-title-changed") {
+ SimpleTest.ok(true, "document-title-changed was received.");
+ obsSvc.removeObserver(observer, "document-title-changed");
+ SimpleTest.finish();
+ }
+ },
+ };
+
+ obsSvc.addObserver(observer, "document-title-changed");
+ document.title = "Changed title";
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1126851.html b/dom/base/test/test_bug1126851.html
new file mode 100644
index 0000000000..117aabb530
--- /dev/null
+++ b/dom/base/test/test_bug1126851.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1126851
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1126851</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1126851 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ win = window.open("about:blank", "");
+ win.onload = function() {
+ win.onunload = function() {
+ ok(true, "got unload");
+ win.close();
+ SimpleTest.finish();
+ }
+ win.onresize = function() {
+ win.location.reload();
+ }
+ win.document.dispatchEvent(new win.Event("resize", { bubbles: true }));
+ }
+ }
+
+
+ </script>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126851">Mozilla Bug 1126851</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug116083.html b/dom/base/test/test_bug116083.html
new file mode 100644
index 0000000000..a1736d6cce
--- /dev/null
+++ b/dom/base/test/test_bug116083.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=116083
+-->
+<head>
+ <title>Test for Bug 116083</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=116083">Mozilla Bug 116083</a>
+<div id="content">
+<div style="white-space: pre">foo bar</div>
+<div style="white-space: pre-wrap">foo bar</div>
+<div style="white-space: pre-line">foo bar</div>
+<div style="white-space: -moz-pre-space">foo bar</div>
+<div style="white-space: pre" data-collapse-selection-to-child-and-extend>foo bar</div>
+<div style="white-space: pre-wrap" data-collapse-selection-to-child-and-extend>foo bar</div>
+<div style="white-space: pre-line" data-collapse-selection-to-child-and-extend>foo bar</div>
+<div style="white-space: -moz-pre-space" data-collapse-selection-to-child-and-extend>foo bar</div>
+<div data-result="bar baz"><span style="white-space: pre">bar </span>baz</div>
+<div data-result="bar baz"><span style="white-space: pre-wrap">bar </span>baz</div>
+<div data-result="bar baz"><span style="white-space: pre-line">bar </span>baz</div>
+<div data-result="bar baz"><span style="white-space: -moz-pre-space">bar </span>baz</div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre"><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre" contenteditable><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre-wrap"><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre-wrap" contenteditable><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre-line"><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: pre-line" contenteditable><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: -moz-pre-space"><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo &#10; bar&#10;&#10;!&#10;&#10;&#10;baz" style="white-space: -moz-pre-space" contenteditable><div>foo </div><div> bar</div><div><br></div><div>!</div><div><br><br></div><div>baz</div></div>
+<div data-result="foo&#10;bar&#10;baz&#10;qux"><div>foo<br></div><span>bar<br>baz<br>qux</span></div>
+<div data-result="foo&#10;bar&#10;baz&#10;qux" contenteditable><div>foo<br></div><span>bar<br>baz<br>qux</span></div>
+<div data-result="foo&#10;&#10;"><div>foo</div><span><br></span></div>
+<div data-result="foo&#10;&#10;" contenteditable><div>foo</div><span><br></span></div>
+<div data-result="foo&#10;&#10;bar"><div>foo</div><span><br></span><div>bar</div></div>
+<div data-result="foo&#10;&#10;bar" contenteditable><div>foo</div><span><br></span><div>bar</div></div>
+<div data-result="foo&#10;bar&#10;"><div>foo</div><span>bar<br></span></div>
+<div data-result="foo&#10;bar" contenteditable><div>foo</div><span>bar<br></span></div>
+<div data-result="foo&#10;bar&#10;baz"><div>foo</div><span>bar<br></span><div>baz</div></div>
+<div data-result="foo&#10;bar&#10;baz" contenteditable><div>foo</div><span>bar<br></span><div>baz</div></div>
+<div data-result="&#10;&#10;foo"><div><br><br><div>foo</div></div></div>
+<div data-result="&#10;&#10;foo" contenteditable><div><br><br><div>foo</div></div></div>
+<div data-result="foo&#10;bar"><div>foo<br>bar</div></div>
+<div data-result="foo&#10;bar" contenteditable><div>foo<br>bar</div></div>
+<div data-result="foo&#10;bar&#10;"><div>foo<br>bar<br></div></div>
+<div data-result="foo&#10;bar" contenteditable><div>foo<br>bar<br></div></div>
+<div data-result="&#10;foo bar&#10;">foo bar</div>
+</div>
+<script type="application/javascript">
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+function hasExpectedFlavors() {
+ var cb = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+ ok(cb.hasDataMatchingFlavors(["text/plain"], cb.kGlobalClipboard),
+ "The clipboard has text/plain");
+
+ ok(cb.hasDataMatchingFlavors(["text/html"], cb.kGlobalClipboard),
+ "The clipboard has text/html");
+
+ if (navigator.appVersion.includes("Win")) {
+ ok(cb.hasDataMatchingFlavors(["application/x-moz-nativehtml"], cb.kGlobalClipboard),
+ "The clipboard has application/x-moz-nativehtml");
+ }
+}
+
+function collapseSelectionToChildAndExtend(divElement) {
+ is(divElement.childNodes.length, 1, "Expected exactly one child node.");
+ var textChildNode = divElement.childNodes[0];
+ getSelection().collapse(textChildNode);
+ getSelection().extend(textChildNode, divElement.textContent.length);
+}
+
+function selectDependingOnAttributes(divElement) {
+ if (divElement.hasAttribute("data-collapse-selection-to-child-and-extend")) {
+ // Selecting text as follow comes closest to user behaviour.
+ collapseSelectionToChildAndExtend(divElement);
+ } else {
+ getSelection().selectAllChildren(divElement);
+ }
+}
+
+function nextTest() {
+ var div = document.querySelector("#content>div");
+ if (!div) {
+ SimpleTest.finish();
+ return;
+ }
+
+ selectDependingOnAttributes(div);
+
+ var expected = div.hasAttribute("data-result") ?
+ div.getAttribute("data-result") :
+ div.textContent;
+
+ SimpleTest.waitForClipboard(expected, function() {
+ synthesizeKey("C", {accelKey: true});
+ }, function() {
+ ok(true, div.getAttribute("style") + " passed");
+ hasExpectedFlavors();
+ div.remove();
+ nextTest();
+ }, function() {
+ ok(false, "failed to copy the expected content to the clipboard");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(nextTest);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1163743.html b/dom/base/test/test_bug1163743.html
new file mode 100644
index 0000000000..1965909284
--- /dev/null
+++ b/dom/base/test/test_bug1163743.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This checks if the origin-when-crossorigin policy works.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1163743
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 1163743</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * testing legacy support for origin-when-crossorigin (1163743)
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+ const sjs = "/tests/dom/base/test/bug704320.sjs?action=generate-policy-test";
+
+ // origin when crossorigin (trimming whitespace)
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape(' origin-when-crossorigin');
+ yield checkIndividualResults("origin-when-cross-origin", ["origin", "full"]);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug1165501.html b/dom/base/test/test_bug1165501.html
new file mode 100644
index 0000000000..d0f98a9b05
--- /dev/null
+++ b/dom/base/test/test_bug1165501.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Spot test to see if newer meta-referrer policy is used
+https://bugzilla.mozilla.org/show_bug.cgi?id=1165501
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 1165501</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * testing if policy is overwritten if there are two meta statements (1165501)
+ * XXX: would be nice to test this with CSP and meta as well
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+ const sjs = "/tests/dom/base/test/bug704320.sjs?action=generate-policy-test";
+
+ // setting first unsafe-url and then origin - origin shall prevail
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('origin')+ "&wrongPolicy=" + escape('unsafe-url');
+ yield checkIndividualResults("unsafe-url then origin", ["origin"]);
+
+ // setting first no-referrer and then default - default shall prevail
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('default')+ "&wrongPolicy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer then default", ["full"]);
+
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug1187157.html b/dom/base/test/test_bug1187157.html
new file mode 100644
index 0000000000..d96c11e635
--- /dev/null
+++ b/dom/base/test/test_bug1187157.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1187157
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1187157</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1187157">Mozilla Bug 1187157</a>
+<form id="a"><input name="b" type="file"/></form>
+
+<script type="text/javascript">
+ var obj = new FormData(document.getElementById('a')).get('b');
+ ok(obj instanceof File, "This should return a File");
+ is(obj.size, 0, "Size should be 0");
+ is(obj.name, "", "Name should be the empty string.");
+ is(
+ obj.type,
+ "application/octet-stream",
+ "Type should be application/octet-stream"
+ );
+
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1198095.html b/dom/base/test/test_bug1198095.html
new file mode 100644
index 0000000000..bcc005d897
--- /dev/null
+++ b/dom/base/test/test_bug1198095.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1198095
+-->
+ <title>Test for Bug 1198095</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198095">Mozilla Bug 1198095</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var fileData1 = '1234567890';
+var fileData2 = '43210';
+var r, firstBlob;
+
+var openerURL = SimpleTest.getTestFileURL("file_bug1198095.js");
+
+var opener = SpecialPowers.loadChromeScript(openerURL);
+opener.addMessageListener("file.opened", onFileOpened);
+opener.addMessageListener("file.modified", onFileModified);
+opener.sendAsyncMessage("file.open", fileData1);
+
+function onLoadEnd1(e) {
+ e.target.removeEventListener('loadend', onLoadEnd1);
+
+ is(e.target, r, "Target and r are ok");
+ is(e.target.readyState, FileReader.DONE, "The file has been read.");
+ ok(e.target.result instanceof ArrayBuffer, "The result is an ArrayBuffer");
+
+ var view = new Uint8Array(e.target.result);
+ is(view.length, fileData1.length, "File data length matches");
+ for (var i = 0; i < fileData1.length; ++i) {
+ is(String.fromCharCode(view[i]), fileData1[i], "Byte matches");
+ }
+
+ opener.sendAsyncMessage("file.modify", fileData2);
+}
+
+function onError1(e) {
+ ok(false, "This method should not be called - error1!");
+}
+
+function onError2(e) {
+ e.target.removeEventListener('error', onError2);
+ SimpleTest.finish();
+}
+
+function onFileOpened(blob) {
+ firstBlob = blob;
+ r = new FileReader();
+ r.addEventListener("loadend", onLoadEnd1);
+ r.addEventListener("error", onError1);
+ r.readAsArrayBuffer(firstBlob);
+}
+
+function onFileModified(blob) {
+ r.removeEventListener('error', onError1);
+ r.addEventListener("error", onError2);
+ r.readAsArrayBuffer(firstBlob);
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body> </html>
diff --git a/dom/base/test/test_bug1222633.html b/dom/base/test/test_bug1222633.html
new file mode 100644
index 0000000000..f2f83145de
--- /dev/null
+++ b/dom/base/test/test_bug1222633.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222633
+-->
+<head>
+ <title>Test for Bug 1222633</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1222633">Mozilla Bug 1222633</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1222633 **/
+
+function testPreloadEvent(url, crossorigin, expectLoad) {
+ return new Promise((resolve) => {
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "preload");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "fetch");
+ if (crossorigin) {
+ link.setAttribute("crossorigin", "");
+ }
+
+ link.addEventListener("load", () => {
+ ok(expectLoad, "not expecting load event for " + url);
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(!expectLoad, "not expecting error event for " + url);
+ link.remove();
+ resolve();
+ });
+ document.head.appendChild(link);
+ });
+}
+
+function testChangePrefetchToPreload(url) {
+ return new Promise((resolve) => {
+ var preloaded = false;
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "prefetch");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "fetch");
+
+ link.addEventListener("load", () => {
+ ok(preloaded, "this will happen only on a preload");
+ ok(true, "not expecting load event for " + url);
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(false, "not expecting error event for " + url);
+ link.remove();
+ resolve();
+ });
+ document.head.appendChild(link);
+ preloaded = true;
+ link.setAttribute("rel", "preload");
+ })
+};
+
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
+const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["network.preload", true]]})
+
+// test same origin
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, false))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, false))
+
+// test cross origin without CORS
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin by redirection without CORS
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin with CORS request but no CORS response
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", true, true))
+
+// test cross origin with CORS request and CORS response
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache&allowOrigin=*", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache&allowOrigin=*", true, false))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120&allowOrigin=*", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120&allowOrigin=*", true, false))
+.then(() => testChangePrefetchToPreload(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1222633_link_update.html b/dom/base/test/test_bug1222633_link_update.html
new file mode 100644
index 0000000000..07c6662085
--- /dev/null
+++ b/dom/base/test/test_bug1222633_link_update.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222633
+-->
+<head>
+ <title>Test for Bug 1222633</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1222633">Mozilla Bug 1222633</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1222633 **/
+
+// Test changing as attribute from an empty value to "foo" to "image". The empty value and "foo" will
+// not trigger any event and "image" should trigger an load event.
+function testPreloadEventAsAttributeChange(url) {
+ return new Promise((resolve) => {
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "preload");
+ link.setAttribute("href", url);
+
+ link.addEventListener("load", () => {
+ ok(link.as == "image", "Only image will trigger a load event");
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(false, "We should not get an error event.");
+ });
+
+ document.head.appendChild(link);
+ link.setAttribute("as", "foo");
+ link.setAttribute("as", "image");
+ });
+}
+
+function testPreloadEventAttributeChange(url, attr, invalidValue, validValue) {
+ return new Promise((resolve) => {
+ var count = 0;
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "preload");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "image");
+
+ link.setAttribute(attr, invalidValue);
+
+ let firedLoad = false;
+ link.addEventListener("load", () => {
+ ok(!firedLoad, "expecting first load event for " + url);
+ firedLoad = true;
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(false, "shouldn't fire error events");
+ });
+ document.head.appendChild(link);
+ SimpleTest.executeSoon(function() {
+ ok(!firedLoad, "Invalid value shouldn't fire load event");
+ link.setAttribute(attr, validValue);
+ })
+ });
+}
+
+function testPreloadEventSetCrossOrigin(url) {
+ return new Promise((resolve) => {
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "preload");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "fetch");
+ count = 0;
+
+ link.addEventListener("load", () => {
+ count++;
+ if ((count == 1) || (count == 3) || (count == 5)) {
+ ok(true, "expecting " + count + ". load event for " + url);
+ } else {
+ ok(false, "expecting " + count + ". event for " + url);
+ }
+ if ((count == 1) || (count == 3)) {
+ link.setAttribute("crossorigin", "");
+ } else {
+ link.remove();
+ resolve();
+ }
+ });
+
+ link.addEventListener("error", () => {
+ count++;
+ if ((count == 2) || (count == 4)) {
+ ok(true, "expecting " + count + ". error event for " + url);
+ } else {
+ ok(false, "expecting " + count + ". error event for " + url);
+ }
+ link.removeAttribute("crossorigin");
+ });
+ document.head.appendChild(link);
+ });
+}
+
+const IMAGE_PATH = window.location.pathname.replace(/[^/]+$/, "file_mozfiledataurl_img.jpg");
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+const SAME_ORIGIN = "http://mochi.test:8888";
+const CROSS_ORIGIN = "http://example.com";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["network.preload", true]]})
+
+// Test changing as parameter from a wrong to a correct one.
+.then(() => testPreloadEventAsAttributeChange(SAME_ORIGIN + IMAGE_PATH))
+// Test changing type parameter from a wrong to a correct one for given as parameter.
+.then(() => testPreloadEventAttributeChange(SAME_ORIGIN + IMAGE_PATH, "type", "text/vtt", "image/png"))
+// Test changing media parameter from a wrong to a correct one.
+.then(() => testPreloadEventAttributeChange(SAME_ORIGIN + IMAGE_PATH, "media", "foo", "all"))
+// Test changing crossorigin parameter.
+.then(() => testPreloadEventSetCrossOrigin(CROSS_ORIGIN + SJS_PATH + "?statusCode=404&cacheControl=max-age%3D120&allowOrigin=*"))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1238440.html b/dom/base/test/test_bug1238440.html
new file mode 100644
index 0000000000..528b337c00
--- /dev/null
+++ b/dom/base/test/test_bug1238440.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test - bug 1238440</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <input type="file" id="file" />
+ <script type="application/javascript">
+
+var script;
+
+function step_createScript() {
+ info("Creating script...");
+ var url = SimpleTest.getTestFileURL("script_bug1238440.js");
+ script = SpecialPowers.loadChromeScript(url);
+ next();
+}
+
+function step_destroyScript() {
+ info("Destroying script...");
+ script.destroy();
+ next();
+}
+
+
+function step_createFile() {
+ info("Creating file...");
+
+ function onOpened(message) {
+ var fileList = document.getElementById('file');
+ SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
+ ok(!!message.file, "File created and set");
+ next();
+ }
+
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+}
+
+function step_changeFile() {
+ info("Changing file...");
+ script.addMessageListener("file.changed", next);
+ script.sendAsyncMessage("file.change");
+}
+
+function step_fileReader(status) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ is(status, true, "onload called!");
+ next();
+ }
+
+ fr.onerror = function(e) {
+ e.preventDefault();
+ is(status, false, "onerror called!");
+ next();
+ }
+
+ fr.readAsArrayBuffer(document.getElementById("file").files[0]);
+}
+
+var steps = [
+ step_createScript,
+ step_createFile,
+ function() { step_fileReader(true); },
+ step_changeFile,
+ function() { step_fileReader(false); },
+ step_destroyScript,
+];
+
+function next() {
+ if (!steps.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var step = steps.shift();
+ step();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1250148.html b/dom/base/test/test_bug1250148.html
new file mode 100644
index 0000000000..b32124ded6
--- /dev/null
+++ b/dom/base/test/test_bug1250148.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1250148
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1250148 - FormData and HTML submission compatibility</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <form id="form" enctype="multipart/form-data"><input type="file" name="test" /></form>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var f = document.getElementById('form');
+var fd = new FormData(f);
+var get = fd.get("test");
+ok(get instanceof File, "We want a File object.");
+is(get.name, "", "We want a File with an empty name.");
+is(
+ get.type,
+ "application/octet-stream",
+ "We want a File with type application/octet-stream."
+);
+is(get.size, 0, "We want a File with an empty body.");
+
+var getAll = fd.getAll("test");
+ok(Array.isArray(getAll), "We want an array with 1 empty File.");
+is(getAll.length, 1, "We want an array with 1 empty File.");
+is(
+ getAll[0],
+ get,
+ "We want the File returned from getAll to be that returned from get."
+);
+
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "file_bug1250148.sjs", true);
+xhr.onload = function() {
+ var obj = JSON.parse(xhr.responseText);
+
+ ok(Array.isArray(obj), "XHR response is an array.");
+ is(obj.length, 1, "XHR response array contains 1 result.");
+
+ ok("headers" in obj[0], "XHR response has an header value");
+
+ ok("Content-Disposition" in obj[0].headers, "XHR response.headers array has a Content-Desposition value");
+ is(obj[0].headers["Content-Disposition"], "form-data; name=\"test\"; filename=\"\"", "Correct Content-Disposition");
+
+ ok("Content-Type" in obj[0].headers, "XHR response.headers array has a Content-Type value");
+ is(obj[0].headers["Content-Type"], "application/octet-stream", "Correct Content-Type");
+
+ ok("body" in obj[0], "XHR response has a body value");
+ is(obj[0].body, "", "Correct body value");
+
+ SimpleTest.finish();
+}
+xhr.send(fd);
+
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug1259588.html b/dom/base/test/test_bug1259588.html
new file mode 100644
index 0000000000..40a272f905
--- /dev/null
+++ b/dom/base/test/test_bug1259588.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for Bug 1259588</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws(new TypeError, function() {
+ new File("");
+ }, "new File(\"\") should throw TypeError exception");
+}, "Test new File(\"\") should throw exception");
+</script>
diff --git a/dom/base/test/test_bug1268962.html b/dom/base/test/test_bug1268962.html
new file mode 100644
index 0000000000..54f99f8456
--- /dev/null
+++ b/dom/base/test/test_bug1268962.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268962
+-->
+<head>
+ <title>Test for Bug 1268962</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268962">Mozilla Bug 1268962</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1268962 **/
+
+function testPrefetchEvent(url, crossorigin, expectLoad) {
+ return new Promise((resolve) => {
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "prefetch");
+ link.setAttribute("href", url);
+ if (crossorigin) {
+ link.setAttribute("crossorigin", "");
+ }
+
+ link.addEventListener("load", () => {
+ ok(expectLoad, "not expecting load event for " + url);
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(!expectLoad, "not expecting error event for " + url);
+ link.remove();
+ resolve();
+ });
+ document.head.appendChild(link);
+ });
+}
+
+function testCancelPrefetchNotCrash(url) {
+ var ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"].
+ getService(SpecialPowers.Ci.nsIIOService);
+ var prefetch = SpecialPowers.Cc["@mozilla.org/prefetch-service;1"].
+ getService(SpecialPowers.Ci.nsIPrefetchService);
+
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "prefetch");
+ link.setAttribute("href", url);
+ document.head.appendChild(link);
+
+ // Not actually verifying any value, just to ensure cancelPrefetchPreload
+ // won't cause crash.
+ prefetch.cancelPrefetchPreloadURI(ios.newURI(url), link);
+}
+
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
+const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["network.prefetch-next.aggressive", true]]})
+
+// test same origin
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, false))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, false))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, false))
+
+// test cross origin without CORS
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin by redirection without CORS
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPrefetchEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin with CORS request but no CORS response
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", true, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", true, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", true, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", true, true))
+
+// test cross origin with CORS request and CORS response
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache&allowOrigin=*", true, false))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache&allowOrigin=*", true, false))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120&allowOrigin=*", true, true))
+.then(() => testPrefetchEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120&allowOrigin=*", true, false))
+
+// test the crash issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1294159
+.then(() => testCancelPrefetchNotCrash(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120"))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1274806.html b/dom/base/test/test_bug1274806.html
new file mode 100644
index 0000000000..c8ccb0bc65
--- /dev/null
+++ b/dom/base/test/test_bug1274806.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1274806
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1274806</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ /** Test for Bug 1274806 **/
+ function test() {
+ window.testWindow = window.open("file_bug1274806.html", "", "");
+ };
+
+ </script>
+</head>
+<body onload="setTimeout(test, 0);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274806">Mozilla Bug 1274806</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1281963.html b/dom/base/test/test_bug1281963.html
new file mode 100644
index 0000000000..b2d48cc7ec
--- /dev/null
+++ b/dom/base/test/test_bug1281963.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/1281963
+-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test for Bug 1281963</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content"></div>
+
+<script class="testbody" type="application/javascript">
+
+let pushPref = function(key, value) {
+ return SpecialPowers.pushPrefEnv({"set": [[key, value]]});
+};
+
+let popPref = function() {
+ SpecialPowers.popPrefEnv();
+};
+
+// Run a test to see that we don't hide the hard-coded mimeTypes
+// or plugins when "privacy.resistFingerprinting" is active.
+// See bug 1756280.
+add_task(async function() {
+ for (let rfpEnabled of [true, false]) {
+ await pushPref("privacy.resistFingerprinting", rfpEnabled);
+ for (let pdfDisabled of [true, false]) {
+ await pushPref("pdfjs.disabled", pdfDisabled);
+ if (pdfDisabled && !rfpEnabled) {
+ is(navigator.mimeTypes.length, 0, "navigator.mimeTypes.length should be 0");
+ is(navigator.plugins.length, 0, "navigator.plugins.length should 0");
+ } else {
+ let exampleMimeType = navigator.mimeTypes[0];
+ let examplePlugin = navigator.plugins[0];
+
+ isnot(navigator.mimeTypes[exampleMimeType.type], undefined, "Should reveal mime type");
+ is(navigator.mimeTypes.length, 2, "navigator.mimeTypes.length should be 2");
+
+ isnot(navigator.plugins[examplePlugin.name], undefined, "Should reveal plugin");
+ is(navigator.plugins.length, 5, "navigator.plugins.length should be nonzero");
+ }
+ await popPref();
+ }
+ await popPref();
+ }
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1295852.html b/dom/base/test/test_bug1295852.html
new file mode 100644
index 0000000000..e8049f30a8
--- /dev/null
+++ b/dom/base/test/test_bug1295852.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Bug 1295852</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<body>
+<script>
+
+var names = [
+ "span", "_moz_generated_content_before", "_moz_generated_content_after"
+];
+
+if (SpecialPowers.getBoolPref("dom.animations-api.getAnimations.enabled")) {
+ names.forEach(name => {
+ var element = document.createElement(name);
+ element.animate({ "color": ["red", "blue"] }, { duration: 1000 });
+ is(element.getAnimations().length, 1);
+ });
+} else {
+ ok("Test requires Web Animations, which is disabled.");
+}
+
+</script>
diff --git a/dom/base/test/test_bug1307730.html b/dom/base/test/test_bug1307730.html
new file mode 100644
index 0000000000..e0cad741f9
--- /dev/null
+++ b/dom/base/test/test_bug1307730.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1307730
+-->
+<head>
+ <title>Test for Bug 1307730</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1307730">Mozilla Bug 1307730</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const Cu = SpecialPowers.Cu;
+
+function runTest() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "https://example.com", false);
+ try {
+ xhr.send();
+ } catch (e) {
+ return e.name;
+ }
+ return 'XHR succeeded';
+}
+
+function evalInSandbox(sandbox, func) {
+ return SpecialPowers.unwrap(Cu.evalInSandbox(`(${func.toString()})()`, sandbox));
+}
+
+let sandbox = Cu.Sandbox([window, "https://example.org"],
+ {wantGlobalProperties: ['XMLHttpRequest']});
+is(evalInSandbox(sandbox, runTest), 'NetworkError',
+ "Shouldn't be able to make a CORS request with an expanded principal");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1308069.html b/dom/base/test/test_bug1308069.html
new file mode 100644
index 0000000000..2775ff1b7e
--- /dev/null
+++ b/dom/base/test/test_bug1308069.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1308069
+-->
+<head>
+<title>Bug 1308069</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1308069">Mozilla Bug 1308069</a>
+<script class="testbody" type="text/javascript">
+
+function testClearPendingErrorEvent() {
+ return new Promise(function(aResolve, aReject) {
+ var hasErrorEvent = false;
+ var imgTarget = new Image();
+
+ var imgForChangingTargetSrc = new Image();
+ // Queue an error event for changing imgTarget's src.
+ imgForChangingTargetSrc.src = '';
+ imgForChangingTargetSrc.onerror = function() {
+ // This clears imgTarget's pending error event.
+ imgTarget.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><path d="M10,10L32,90L90,32z" fill="lightgreen"/></svg>';
+
+ // Queue an error event for checking and resolving promise.
+ var imgForCheckingAndResolvingPromise = new Image();
+ imgForCheckingAndResolvingPromise.src = '';
+ imgForCheckingAndResolvingPromise.onerror = function() {
+ ok(!hasErrorEvent,
+ 'Should not receive an error event since the pending error event ' +
+ 'should be cleared before it fired');
+ aResolve();
+ };
+ };
+
+ // Setting src to empty string queues an error event.
+ imgTarget.src = '';
+ imgTarget.onerror = function() {
+ hasErrorEvent = true;
+ };
+ });
+}
+
+function testReplacePendingErrorEvent() {
+ return new Promise(function(aResolve) {
+ var numOfErrorEvent = 0;
+ var imgTarget = new Image();
+
+ var imgForChangingTargetSrc = new Image();
+ // Queue an error event for changing imgTarget's src.
+ imgForChangingTargetSrc.src = '';
+ imgForChangingTargetSrc.onerror = function() {
+ // This clears pending error event and fires a new one.
+ imgTarget.src = '';
+
+ // Queue an error event for checking and resolving promise.
+ var imgForCheckingAndResolvingPromise = new Image();
+ imgForCheckingAndResolvingPromise.src = '';
+ imgForCheckingAndResolvingPromise.onerror = function() {
+ is(numOfErrorEvent, 1,
+ 'Should only receive one error event since the first pending error ' +
+ 'event should be cleared before it fired');
+ aResolve();
+ };
+ };
+
+ // Setting src to empty string queues an error event.
+ imgTarget.src = '';
+ imgTarget.onerror = function() {
+ numOfErrorEvent++;
+ };
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+Promise.resolve()
+.then(() => testClearPendingErrorEvent())
+.then(() => testReplacePendingErrorEvent())
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1314032.html b/dom/base/test/test_bug1314032.html
new file mode 100644
index 0000000000..693eba2f62
--- /dev/null
+++ b/dom/base/test/test_bug1314032.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1314032</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1314032">Mozilla Bug 1243846</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+ let win = window.open(URL.createObjectURL(new Blob([
+ '<meta charset="utf-8">' +
+ '<script>' +
+ 'let observer = new IntersectionObserver(([entry]) => {' +
+ 'document.body.textContent += entry.time' +
+ '});' +
+ 'observer.observe(document.documentElement);' +
+ '<\/script>'
+ ], {'type': 'text/html'})));
+
+ win.onload = function () {
+ win.close();
+ ok(true);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1318303.html b/dom/base/test/test_bug1318303.html
new file mode 100644
index 0000000000..86053b2d7e
--- /dev/null
+++ b/dom/base/test/test_bug1318303.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1318303
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1318303</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ var _createIterator = function (root) {
+ return document.createNodeIterator.call(root.ownerDocument || root,
+ root,
+ NodeFilter.SHOW_ELEMENT
+ | NodeFilter.SHOW_COMMENT
+ | NodeFilter.SHOW_TEXT,
+ function () {
+ return NodeFilter.FILTER_ACCEPT;
+ },
+ false
+ );
+ };
+
+ evil = "<body><object data=''><p></p></object></body>";
+ doc = new DOMParser().parseFromString(evil, 'text/html');
+ body = doc.getElementsByTagName("body")[0];
+ nodeIterator = _createIterator(body);
+ try {
+ while ((currentNode = nodeIterator.nextNode())) {
+ currentNode.removeAttribute("data");
+ }
+ ok(true, "Removing data attributes did not throw error");
+ } catch(err) {
+ ok(false, "Removing data attribute threw error!");
+ }
+ obj = doc.getElementsByTagName("object")[0];
+ const objLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+
+ obj instanceof objLC;
+ obj = SpecialPowers.wrap(obj);
+
+ ok(obj.displayedType == objLC.TYPE_NULL,
+ "object with no given MIME type should be NULL type");
+
+ </script>
+ </head>
+</html>
+
diff --git a/dom/base/test/test_bug1375050.html b/dom/base/test/test_bug1375050.html
new file mode 100644
index 0000000000..5f598d7469
--- /dev/null
+++ b/dom/base/test/test_bug1375050.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375050
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1375050</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ try { o1 = document.createElement('input'); } catch(e) { console.log(e); };
+ try { o2 = document.createElement('col'); } catch(e) { console.log(e); };
+ try { o4 = document.createRange(); } catch(e) { console.log(e); };
+ try { document.documentElement.appendChild(o1); } catch(e) { console.log(e); };
+ try { for (let p in o1) { let x = o1[p] }; } catch(e) { console.log(e); };
+ try { o4.selectNode(o1); } catch(e) { console.log(e); };
+ try { o6 = document.createComment(" x"); } catch(e) { console.log(e); }
+ try { o4.surroundContents(o6); } catch(e) { console.log(e); }
+ try { o7 = document.implementation.createDocument('', '', null).adoptNode(o1); } catch(e) { console.log(e);};
+ try { o2.appendChild(o1); } catch(e) { console.log(e); };
+ ok(true, "Didn't crash.");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375050">Mozilla Bug 1375050</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1381710.html b/dom/base/test/test_bug1381710.html
new file mode 100644
index 0000000000..97531d1426
--- /dev/null
+++ b/dom/base/test/test_bug1381710.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1381710
+-->
+<head>
+ <title>Test for Mozilla Bug 1381710</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1381710">Mozilla Bug 1381710</a>
+<div id="content">
+ <div id="nonedit"></div>
+ <div id="edit" contenteditable=true></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+function test(element)
+{
+ let selection = window.getSelection();
+ selection.removeAllRanges();
+ let range = document.createRange();
+
+ element.innerHTML = "<table><tr id=tr><td id=td>A</td><td>B</td><tr></table>";
+ let td = document.getElementById("td");
+ range.selectNode(td);
+ // Don't throw exception
+ selection.addRange(range);
+ is(selection.anchorNode, document.getElementById("tr"),
+ "anchor node should be <TR> element");
+ element.innerHTML = "";
+}
+
+test(document.getElementById("nonedit"));
+test(document.getElementById("edit"));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1399605.html b/dom/base/test/test_bug1399605.html
new file mode 100644
index 0000000000..a8e0c6453d
--- /dev/null
+++ b/dom/base/test/test_bug1399605.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1399605</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1399605">Mozilla Bug 1399605</a>
+<p id="display"></p>
+<div id="content">
+ <div id="target"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ let observer = new IntersectionObserver(function() {
+ ok(true, "we are still observing");
+ SimpleTest.finish();
+ observer.unobserve(document.getElementById('target'));
+ });
+ observer.observe(document.getElementById('target'));
+ observer.unobserve(document.getElementById('content'));
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1404385.html b/dom/base/test/test_bug1404385.html
new file mode 100644
index 0000000000..828a44918f
--- /dev/null
+++ b/dom/base/test/test_bug1404385.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1404385
+-->
+<head>
+ <title>Test for Bug 1404385</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1404385">Mozilla Bug 1404385</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1404385 **/
+is(atob("T0s="), "OK", "atob");
+is(atob(" T0s="), "OK", "atob initial space removal");
+is(atob("T0s= "), "OK", "atob final space removal");
+is(atob("T 0s="), "OK", "atob middle space removal");
+is(atob("dGV zdA=="), "test", "atob middle space removal 2");
+is(atob(""), "", "atob empty string");
+is(atob(" "), "", "atob all whitespace");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1406102.html b/dom/base/test/test_bug1406102.html
new file mode 100644
index 0000000000..a561f1e8b6
--- /dev/null
+++ b/dom/base/test/test_bug1406102.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1406102</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ html {
+ height: 100%;
+ }
+ body {
+ height: 2866px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406102">Mozilla Bug 1406102</a>
+<p id="display"></p>
+<div id="content">
+ <div id="target"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ let observer = new IntersectionObserver(function (changes) {
+ ok(changes[0].intersectionRatio >= 0,
+ 'intersectionRatio should be equal to or greater than zero');
+ if (changes[0].intersectionRatio > 0) {
+ SimpleTest.finish();
+ }
+ });
+ observer.observe(document.body);
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1421568.html b/dom/base/test/test_bug1421568.html
new file mode 100644
index 0000000000..f4864c81c1
--- /dev/null
+++ b/dom/base/test/test_bug1421568.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1421568
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1421568</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript"><!--
+
+ /** Test for Bug 1421568 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function init() {
+ document.getElementById("content").innerHTML =
+ "<iframe src='about:blank' onload='test(this)'></iframe>";
+ }
+
+ function test(iframe) {
+ var d = iframe.contentDocument;
+ d.body.innerHTML = "<div>";
+ var div = d.body.firstChild;
+ var sr = div.attachShadow({mode: "closed"});
+ is(sr.mode, "closed", "Shadow root should be closed.");
+ is(div.shadowRoot, null, "Closed shadow root shouldn't be exposed.");
+ is(div.openOrClosedShadowRoot, undefined,
+ "openOrClosedShadowRoot should be exposed to the privileged scripts only.");
+ ok("openOrClosedShadowRoot" in SpecialPowers.wrap(div),
+ "Should have openOrClosedShadowRoot in privileged wrapper.")
+ is(SpecialPowers.unwrap(SpecialPowers.wrap(div).openOrClosedShadowRoot), sr);
+ SimpleTest.finish();
+ }
+
+ //--></script>
+</head>
+<body onload="init()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1421568">Mozilla Bug 1421568</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1433073.html b/dom/base/test/test_bug1433073.html
new file mode 100644
index 0000000000..7fe91aa287
--- /dev/null
+++ b/dom/base/test/test_bug1433073.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test bug 1433073</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ function synthesizeAccelKeyAndClickAt(aElementId) {
+ const element = document.getElementById(aElementId);
+ synthesizeMouseAtCenter(element, { accelKey: true });
+ }
+
+ function synthesizeAccelKeyAndC() {
+ synthesizeKey("C", { accelKey: true });
+ }
+
+ async function runTest() {
+ synthesizeAccelKeyAndClickAt("x");
+ synthesizeAccelKeyAndClickAt("y");
+ synthesizeAccelKeyAndClickAt("u");
+ synthesizeAccelKeyAndClickAt("v");
+
+ {
+ // Assert content contains the table.
+ // TODO: the `<meta>` element is missing; which is a defect in the
+ // test-code, see https://bugzilla.mozilla.org/show_bug.cgi?id=1632183.
+ const expectedString = "\
+<table>\
+<tbody>\
+<tr>\
+<td id=\"x\">x</td>\
+<td id=\"y\">y</td>\
+</tr>\
+<tr>\
+<td id=\"u\">u</td>\
+<td id=\"v\">v</td>\
+</tr>\
+</tbody>\
+</table>";
+
+ const flavor = "text/html";
+ await SimpleTest.promiseClipboardChange(expectedString,
+ synthesizeAccelKeyAndC, flavor);
+ }
+
+ {
+ // The key point of this check is that the string doesn't contain a
+ // `<tr>`. It's possible that `<tbody>` could be removed, but it's
+ // unknown if other applications rely on it being included.
+ const expectedString = "\
+<html>\
+<body onload=\"onLoad()\">\
+<div id=\"content\">\
+<table><tbody></tbody></table></div></body></html>";
+ const flavor = "text/_moz_htmlcontext";
+ await SimpleTest.promiseClipboardChange(expectedString,
+ synthesizeAccelKeyAndC, flavor);
+ }
+
+ SimpleTest.finish();
+ }
+
+ function onLoad() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(runTest);
+ }
+ </script>
+</head>
+<body onLoad="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1433073">Mozilla Bug 1433073</a>
+<p id="display"></p>
+<div id="content">
+ <table>
+ <tbody>
+ <tr>
+ <td id="x">x</td>
+ <td id="y">y</td>
+ </tr>
+ <tr>
+ <td id="u">u</td>
+ <td id="v">v</td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1472427.html b/dom/base/test/test_bug1472427.html
new file mode 100644
index 0000000000..902b9cd05b
--- /dev/null
+++ b/dom/base/test/test_bug1472427.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1472427
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1472427</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1472427 **/
+
+ SimpleTest.waitForExplicitFinish();
+ var image100x100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAnUlEQVR42u3RAQ0AAAQAMLJqqCA1zP4Kz+qe4IwUIgQhQhAiBCFCECJEiBCECEGIEIQIQYgQhCBECEKEIEQIQoQgBCFCECIEIUIQIgQhCBGCECEIEYIQIQhBiBCECEGIEIQIQQhChCBECEKEIEQIQhAiBCFCECIEIUIQghAhCBGCECEIEYIQhAhBiBCECEGIEIQIESIEIUIQIgQh3y1XQhXMIlaKKwAAAABJRU5ErkJggg==";
+ var ifr;
+ var doc;
+ var img;
+ var host;
+ var root;
+ var map;
+ var area;
+
+ function initPage() {
+ ifr = document.createElement("iframe");
+ ifr.src = "about:blank";
+ document.body.appendChild(ifr);
+ ifr.onload = initIframe;
+ }
+
+ function initIframe() {
+ ifr.contentWindow.focus();
+ doc = ifr.contentDocument;
+ host = doc.createElement("div");
+ doc.body.appendChild(host);
+ root = host.attachShadow({mode: "open"});
+
+ img = document.createElement("img");
+ img.useMap = "#map"
+ img.src = image100x100;
+ img.onload = runTest;
+ root.appendChild(img);
+
+ map = doc.createElement("map");
+ map.name = "map";
+ root.appendChild(map);
+
+ area = doc.createElement("area");
+ area.shape = "rect";
+ area.href = "#area";
+ area.coords = "0,0,100,100";
+ map.appendChild(area);
+ }
+
+ function runTest() {
+ var gotClick = false;
+ var expectedTarget = area;
+ root.addEventListener("click",
+ function(e) {
+ gotClick = true;
+ is(e.target, expectedTarget,
+ expectedTarget.localName + " element should be the target for the click.");
+ e.preventDefault();
+ });
+ synthesizeMouse(img, 50, 50, {}, ifr.contentWindow);
+ ok(gotClick, "Should have got a click event.");
+
+ gotclick = false;
+ map.name = "wrongNameMap";
+ expectedTarget = img;
+ synthesizeMouse(img, 50, 50, {}, ifr.contentWindow);
+ ok(gotClick, "Should have got a click event.");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="initPage()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1472427">Mozilla Bug 1472427</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1499169.html b/dom/base/test/test_bug1499169.html
new file mode 100644
index 0000000000..7d5141797d
--- /dev/null
+++ b/dom/base/test/test_bug1499169.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1499139
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1499169</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ const OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+ let obj = document.getElementById("pdftest");
+
+ obj instanceof OBJLC;
+ obj = SpecialPowers.wrap(obj);
+
+ // Make sure we've set our type correctly even though the mime type isn't quite as expected.
+ ok(obj.displayedType == OBJLC.TYPE_DOCUMENT, "expected document type");
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1499169">Mozilla Bug 1499169</a>
+ <object id="pdftest" onload="test()" data="file_pdfjs_test.pdf" type="application/pdf;charset=UTF-8" width="90%" height="600"></object>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug1576154.html b/dom/base/test/test_bug1576154.html
new file mode 100644
index 0000000000..eca8db6275
--- /dev/null
+++ b/dom/base/test/test_bug1576154.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1576154
+-->
+<head>
+ <title>Test for Bug 1576154</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1576154">Mozilla Bug 1576154</a>
+<p id="display"></p>
+<!-- bug1576154.sjs returns an SVG image with HTTP error code 500. -->
+<img src="bug1576154.sjs">
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 657191 **/
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ let img = document.querySelector("img");
+ img.src = "green.png";
+ img.onload = function() {
+ // As long as this doesn't crash, this test passes.
+ ok(true, "test passed");
+ SimpleTest.finish();
+ };
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1632975.html b/dom/base/test/test_bug1632975.html
new file mode 100644
index 0000000000..8b54ca47a5
--- /dev/null
+++ b/dom/base/test/test_bug1632975.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1632975
+-->
+<head>
+ <title>Test for Bug 1632975</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function observeTest(mutationsList) {
+ for (let mutation of mutationsList) {
+ for (let node of mutation.addedNodes) {
+ if (node.nodeName.toLowerCase() == "script") {
+ node.setAttribute("type", "text/zpconsent")
+ }
+ }
+ }
+}
+
+const observer = new MutationObserver(observeTest);
+observer.observe(document.body, { childList: true, subtree: true });
+
+let script2Ran = false;
+let script3Ran = false;
+script4Ran = false;
+
+onload = () => {
+ ok(!script2Ran, "script2 should not have run");
+ ok(!script3Ran, "script3 should not have run");
+ ok(!script4Ran, "script4 should not have run");
+ SimpleTest.finish();
+}
+</script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1632975">Mozilla Bug 1632975</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script id="script2">
+ script2Ran = true;
+ </script>
+
+ <script id="script3" type="disabled">
+ script3Ran = true;
+ </script>
+
+ <script id="script4" src="data:text/javascript,script4Ran = true;"></script>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1639328.html b/dom/base/test/test_bug1639328.html
new file mode 100644
index 0000000000..f7d88de5a4
--- /dev/null
+++ b/dom/base/test/test_bug1639328.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for bug 1639328</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ /* To ensure that they're all in the viewport when displayed */
+ iframe {
+ width: 10px;
+ height: 10px;
+ }
+</style>
+<iframe id="http" src="https://example.com/tests/dom/base/test/file_bug1639328.html"></iframe>
+<iframe id="https" src="https://example.com/tests/dom/base/test/file_bug1639328.html"></iframe>
+<iframe id="same-origin" src="file_bug1639328.html"></iframe>
+<iframe id="display-none-http" style="display: none" src="https://example.com/tests/dom/base/test/file_bug1639328.html"></iframe>
+<iframe id="display-none-https" style="display: none" src="https://example.com/tests/dom/base/test/file_bug1639328.html"></iframe>
+<iframe id="display-none-same-origin" style="display: none" src="file_bug1639328.html"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function getOneMessage(frame) {
+ info(`querying ${frame.src} (${frame.id})`);
+ let resolve;
+ let promise = new Promise(r => { resolve = r; });
+ window.addEventListener("message", function(e) {
+ info("got " + JSON.stringify(e.data));
+ resolve(e.data);
+ }, { once: true });
+ frame.contentWindow.postMessage("ping", "*");
+ return promise;
+}
+
+async function ticks(n) {
+ for (let i = 0; i < n; ++i) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+}
+
+async function checkFrame(frame, shouldThrottle) {
+ let message = null;
+ do {
+ if (message) {
+ await ticks(2);
+ }
+ message = await getOneMessage(frame);
+ } while (message.throttledFrameRequests != shouldThrottle);
+ is(message.throttledFrameRequests, shouldThrottle, frame.id);
+}
+
+onload = async function() {
+ await SimpleTest.promiseFocus(window);
+ await ticks(2);
+ is(SpecialPowers.DOMWindowUtils.effectivelyThrottlesFrameRequests, false, "Should not be throttling main page");
+ for (let frame of document.querySelectorAll("iframe")) {
+ let shouldThrottle = frame.style.display == "none";
+ await checkFrame(frame, shouldThrottle);
+ info("Switching display of " + frame.id);
+ frame.style.display = shouldThrottle ? "" : "none";
+ await checkFrame(frame, !shouldThrottle);
+ info("And switching display back for " + frame.id);
+ frame.style.display = shouldThrottle ? "none" : "";
+ await checkFrame(frame, shouldThrottle);
+ }
+
+ SimpleTest.finish();
+};
+</script>
diff --git a/dom/base/test/test_bug1640766.html b/dom/base/test/test_bug1640766.html
new file mode 100644
index 0000000000..ea77c99e59
--- /dev/null
+++ b/dom/base/test/test_bug1640766.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1640766
+-->
+<head>
+ <title>Test for Bug 1640766</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1640766">Mozilla Bug 1640766</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1640766 **/
+
+function waitForMessage(aMsg) {
+ return new Promise((aResolve) => {
+ window.addEventListener("message", function handler(e) {
+ if (e.data != aMsg) {
+ return;
+ }
+
+ info(`receive: ${e.data}`);
+ window.removeEventListener("message", handler);
+ aResolve(e.source);
+ });
+ });
+}
+
+async function testSuspend(aWindow) {
+ let timerRan = false;
+ let timer = aWindow.setTimeout(function() {
+ timerRan = true;
+ }, 0);
+
+ return new Promise((aResolve) => {
+ setTimeout(function() {
+ ok(!timerRan, "timer should not run as the window is suspended");
+ clearTimeout(timer);
+ aResolve();
+ }, 0);
+ });
+}
+
+add_task(async function() {
+ let w = window.open("iframe1_bug1640766.html");
+ let inner = await waitForMessage("ready");
+
+ var utils = SpecialPowers.getDOMWindowUtils(w);
+ utils.enterModalState();
+
+ await testSuspend(w);
+ await testSuspend(inner);
+
+ utils.leaveModalState();
+ w.close();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1648887.html b/dom/base/test/test_bug1648887.html
new file mode 100644
index 0000000000..a185086137
--- /dev/null
+++ b/dom/base/test/test_bug1648887.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1648887
+-->
+<head>
+<title>Test for Bug 1648887</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1648887">Mozilla Bug 1648887</a>
+<p id="display"></p>
+<iframe srcdoc="<a id='a' href='http://w'>"></iframe>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1648887 **/
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.disable_open_during_load", false],
+ ],
+ });
+
+ let iframe = document.querySelector("iframe");
+ iframe.contentDocument.getElementById('a').click();
+ iframe.contentWindow.open(document.createElement('r').outerHTML,'','h').close();
+ ok(true, "Should not crash");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug166235.html b/dom/base/test/test_bug166235.html
new file mode 100644
index 0000000000..9743d725f6
--- /dev/null
+++ b/dom/base/test/test_bug166235.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=166235
+https://bugzilla.mozilla.org/show_bug.cgi?id=816298
+-->
+<head>
+ <title>Test for Bug 166235</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235 and Bug 816298</a>
+<p id="test0">This text should be copied.</p>
+<p id="test1">This text should<span style="user-select: none;"> NOT</span> be copied.</p>
+<p id="test2">This<span style="user-select: none;"><span style="user-select: text"> text should</span> NOT</span> be copied.</p>
+<p id="test3">This text should<span style="user-select: -moz-none;"> NOT</span> be copied.</p>
+<p id="test4">This<span style="user-select: -moz-none;"><span style="user-select: text"> text should</span> NOT</span> be copied.</p>
+<p id="test5">This<span style="user-select: all"> text should</span> be copied.</p>
+<div id="content" style="display: none">
+
+</div>
+<textarea id="input"></textarea>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+/** Test for Bug 166235 **/
+ var Cc = SpecialPowers.Cc;
+ var Ci = SpecialPowers.Ci;
+
+ var docShell = SpecialPowers.wrap(window).docShell;
+
+ var documentViewer = docShell.contentViewer
+ .QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit);
+
+ var clipboard = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(SpecialPowers.Ci.nsIClipboard);
+
+ var textarea = SpecialPowers.wrap(document.getElementById('input'));
+
+ function getLoadContext() {
+ return SpecialPowers.wrap(window).docShell
+ .QueryInterface(Ci.nsILoadContext);
+ }
+
+ function copyChildrenToClipboard(id) {
+ textarea.blur();
+ clipboard.emptyClipboard(1);
+ window.getSelection().selectAllChildren(document.getElementById(id));
+ documentViewer.copySelection();
+
+ is(clipboard.hasDataMatchingFlavors(["text/plain"], 1), true);
+ is(clipboard.hasDataMatchingFlavors(["text/html"], 1), true);
+ }
+ function getClipboardData(mime) {
+ var transferable = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(SpecialPowers.Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = SpecialPowers.createBlankObject();
+ transferable.getTransferData(mime, data) ;
+ return SpecialPowers.wrap(data);
+ }
+ function testHtmlClipboardValue(mime, expected, test) {
+ var expectedValue = expected;
+ // For Windows, navigator.platform returns "Win32".
+ if (navigator.platform.includes("Win")) {
+ expectedValue = kTextHtmlPrefixClipboardDataWindows + expected + kTextHtmlSuffixClipboardDataWindows;
+ }
+ testClipboardValue(mime, expectedValue, test);
+ }
+ function testClipboardValue(mime, expected, test) {
+ var data = getClipboardData(mime);
+ is (data.value == null ? data.value :
+ data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
+ expected,
+ mime + " value in the clipboard");
+ return data.value;
+ }
+ function testPasteText(expected, test) {
+ textarea.value="";
+ textarea.focus();
+ textarea.editor.paste(1);
+ is(textarea.value, expected, test + ": textarea paste");
+ }
+ function testInnerHTML(id, expected) {
+ var value = document.getElementById(id).innerHTML;
+ is(value, expected, id + ".innerHTML");
+ }
+
+// expected results for Selection.toString()
+var originalStrings = [
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.'
+];
+
+// expected results for clipboard text/html
+var clipboardHTML = [
+ '<p id=\"test0\">This text should be copied.</p>',
+ '<p id=\"test1\">This text should be copied.</p>',
+ '<p id=\"test2\">This<span style=\"user-select: text\"> text should</span> be copied.</p>',
+ '<p id=\"test3\">This text should be copied.</p>',
+ '<p id=\"test4\">This<span style=\"user-select: text\"> text should</span> be copied.</p>',
+ '<p id=\"test5\">This<span style=\"user-select: all\"> text should</span> be copied.</p>',
+];
+
+// expected results for clipboard text/plain
+var clipboardUnicode = [
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.'
+];
+
+// expected results for .innerHTML
+var innerHTMLStrings = [
+ 'This text should be copied.',
+ 'This text should<span style=\"user-select: none;\"> NOT</span> be copied.',
+ 'This<span style=\"user-select: none;\"><span style=\"user-select: text\"> text should</span> NOT</span> be copied.',
+ 'This text should<span style=\"user-select: -moz-none;\"> NOT</span> be copied.',
+ 'This<span style=\"user-select: -moz-none;\"><span style=\"user-select: text\"> text should</span> NOT</span> be copied.',
+ 'This<span style=\"user-select: all\"> text should</span> be copied.',
+];
+
+// expected results for pasting into a TEXTAREA
+var textareaStrings = [
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.',
+ 'This text should be copied.'
+];
+
+for (var i = 0; i < originalStrings.length; i++) {
+ var id = 'test' + i;
+ copyChildrenToClipboard(id);
+ is(window.getSelection().toString(), originalStrings[i], id + ' Selection.toString()');
+ testHtmlClipboardValue("text/html", clipboardHTML[i], id);
+ testClipboardValue("text/plain", clipboardUnicode[i], id);
+ testInnerHTML(id, innerHTMLStrings[i]);
+ testPasteText(textareaStrings[i], id + '.innerHTML');
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1667316.html b/dom/base/test/test_bug1667316.html
new file mode 100644
index 0000000000..2770d58dd4
--- /dev/null
+++ b/dom/base/test/test_bug1667316.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1667316
+-->
+<head>
+ <title>Test for Bug 1667316</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1667316">Mozilla Bug 1667316</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1667316 **/
+
+function testPreloadEvent(url, crossorigin, expectLoad) {
+ return new Promise((resolve) => {
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "preload");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "fetch");
+ if (crossorigin) {
+ link.setAttribute("crossorigin", "");
+ }
+
+ link.addEventListener("load", () => {
+ ok(expectLoad, "not expecting load event for " + url);
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(!expectLoad, "not expecting error event for " + url);
+ link.remove();
+ resolve();
+ });
+ document.head.appendChild(link);
+ });
+}
+
+function testChangePrefetchToPreload(url) {
+ return new Promise((resolve) => {
+ var preloaded = false;
+ var link = document.createElement("LINK");
+ link.setAttribute("rel", "prefetch");
+ link.setAttribute("href", url);
+ link.setAttribute("as", "fetch");
+
+ link.addEventListener("load", () => {
+ ok(preloaded, "this will happen only on a preload");
+ ok(true, "not expecting load event for " + url);
+ link.remove();
+ resolve();
+ });
+ link.addEventListener("error", () => {
+ ok(false, "not expecting error event for " + url);
+ link.remove();
+ resolve();
+ });
+ document.head.appendChild(link);
+ preloaded = true;
+ link.setAttribute("rel", "preload");
+ })
+};
+
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["network.preload", true]]})
+
+
+.then(async () => {
+ await SpecialPowers.spawnChrome([], () => {
+
+let window = this.browsingContext.currentWindowGlobal;
+window.ChannelEventSink = {
+ _classDescription: "WebRequest channel event sink",
+ _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"),
+ _contractID: "@mozilla.org/webrequest/channel-event-sink;1",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
+
+ init() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(
+ this._classID,
+ this._classDescription,
+ this._contractID,
+ this
+ );
+ },
+
+ register() {
+ Services.catMan.addCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ this._contractID,
+ false,
+ true
+ );
+ },
+
+ unregister() {
+ Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar)
+ .unregisterFactory(this._classID, window.ChannelEventSink);
+ Services.catMan.deleteCategoryEntry(
+ "net-channel-event-sinks",
+ this._contractID,
+ false
+ );
+ },
+
+ // nsIChannelEventSink implementation
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
+ // Abort the redirection.
+ redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+};
+
+window.ChannelEventSink.init();
+window.ChannelEventSink.register();
+})})
+
+// test cross origin by redirection without CORS
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=no-cache", false, false))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(async () => {
+ await SpecialPowers.spawnChrome([], () => {
+ let window = this.browsingContext.currentWindowGlobal;
+ window.ChannelEventSink.unregister();
+ delete window.ChannelEventSink;
+ })
+})
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1730284.html b/dom/base/test/test_bug1730284.html
new file mode 100644
index 0000000000..6a24f69b05
--- /dev/null
+++ b/dom/base/test/test_bug1730284.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for bug 1730284 (throttling of same-origin iframes)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ iframe {
+ width: 10px;
+ height: 10px;
+ }
+ .display-none {
+ display: none;
+ }
+ .vis-hidden {
+ visibility: hidden
+ }
+ .transparent {
+ opacity: 0;
+ }
+ .zero-size {
+ width: 0;
+ height: 0;
+ border: 0;
+ }
+ .offscreen {
+ position: absolute;
+ top: 500%;
+ }
+ .scroller {
+ height: 100px;
+ overflow: auto;
+ }
+ .scroller-padding {
+ height: 500px;
+ }
+</style>
+<iframe class="visible"></iframe>
+<iframe class="display-none" data-throttled-expected></iframe>
+<iframe class="vis-hidden"></iframe>
+<iframe class="transparent"></iframe>
+<iframe class="zero-size"></iframe>
+<div class="scroller">
+ <div class="scroller-padding"></div>
+ <iframe class="scrolled-out-of-view" data-throttled-expected></iframe>
+</div>
+<iframe class="offscreen" data-throttled-expected></iframe>
+<iframe class="offscreen zero-size" data-throttled-expected></iframe>
+<iframe class="offscreen vis-hidden" data-throttled-expected></iframe>
+<iframe class="offscreen transparent" data-throttled-expected></iframe>
+<script>
+async function assertThrottled(win, shouldThrottle, msg) {
+ if (isXOrigin) {
+ // In XOrigin mode we need to depend as well on the main process having
+ // painted the cross-origin iframe at least once for coordinates to be
+ // correct.
+ await SimpleTest.promiseWaitForCondition(() => {
+ return SpecialPowers.getDOMWindowUtils(win).effectivelyThrottlesFrameRequests == shouldThrottle;
+ }, msg);
+ }
+ is(SpecialPowers.getDOMWindowUtils(win).effectivelyThrottlesFrameRequests, shouldThrottle, msg);
+}
+
+add_task(async function() {
+ await SimpleTest.promiseFocus(window);
+ await assertThrottled(window, false, "Should not be throttling main page");
+ for (let frame of document.querySelectorAll("iframe")) {
+ let shouldThrottle = frame.getAttribute("data-throttled-expected") !== null;
+ await assertThrottled(frame.contentWindow, shouldThrottle, frame.className);
+ }
+});
+</script>
diff --git a/dom/base/test/test_bug1739957.html b/dom/base/test/test_bug1739957.html
new file mode 100644
index 0000000000..fdfe1e3861
--- /dev/null
+++ b/dom/base/test/test_bug1739957.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Importing a node should respect sandbox</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function imageHasLoaded() {
+ return fetch("bug1739957.sjs?loaded")
+ .then(response => response.text())
+ .then(loaded => Boolean(loaded));
+ }
+
+ async function test() {
+ let newDiv = document.createElement("div");
+ newDiv.innerHTML = `<img src="bug1739957.sjs" onload="parent.postMessage('handlerRuns', '*')">`;
+
+ let eventHandlerCalled = false;
+ window.addEventListener("message", () => { eventHandlerCalled = true; }, { once: true })
+
+ document.getElementById("frame").contentDocument.body.appendChild(newDiv);
+
+ await SimpleTest.promiseWaitForCondition(imageHasLoaded, "Wait for image to load");
+
+ ok(!eventHandlerCalled, "Event handlers on imported nodes shouldn't execute if sandbox doesn't allow script");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test();">
+<p id="display"></p>
+<iframe id="frame" sandbox="allow-same-origin" src="about:blank"></iframe>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1784187.html b/dom/base/test/test_bug1784187.html
new file mode 100644
index 0000000000..6f9f782e3e
--- /dev/null
+++ b/dom/base/test/test_bug1784187.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1784187
+-->
+<head>
+ <title>Test for Bug 1784187</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function observeTest(mutationsList) {
+ for (let mutation of mutationsList) {
+ for (let n of mutation.addedNodes) {
+ if (n.id === 'content') {
+ is(n.innerHTML.trim(), "<script><\/script>", "Comment should not have been observed.");
+ SimpleTest.finish();
+ }
+ }
+ }
+}
+
+var observer = new MutationObserver(observeTest);
+observer.observe(document.body, { childList: true, subtree: true });
+</script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1784187">Mozilla Bug 1784187</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script></script>
+ <!-- Test comment that shouldn't be observed -->
+</div>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug1799354.html b/dom/base/test/test_bug1799354.html
new file mode 100644
index 0000000000..ba2177d576
--- /dev/null
+++ b/dom/base/test/test_bug1799354.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test bug 1799354</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ add_task(t => {
+ let root = document.createElement("div");
+ root.innerHTML = "<div id='a'>text<div id='b'>text2</div></div>";
+ const a = root.firstChild;
+ const b = a.lastChild;
+ const txt = b.previousSibling;
+ const txt2 = b.firstChild;
+ let events = [];
+ function listener(event) {
+ events.push(event);
+ }
+ root.addEventListener("DOMNodeRemoved", listener);
+
+ // Test 1, replace current children with an element.
+ b.replaceChildren(txt);
+ is(events.length, 2, "Should have got two DOMNodeRemoved events");
+ // replaceChildren removes first all the child nodes of b and then when
+ // a new child is added to it, that child is removed from its original parent.
+ // https://dom.spec.whatwg.org/commit-snapshots/6b3f055f3891a63423bf235d46f38ffdb298c2e7/#concept-node-replace-all
+ is(events[0].target, txt2);
+ is(events[0].relatedNode, b);
+ is(events[1].target, txt);
+ is(events[1].relatedNode, a);
+
+ // Test 2, replace current children with a document fragment.
+ events = [];
+ const df = document.createDocumentFragment();
+ const dfChild1 = document.createElement("div");
+ df.appendChild(dfChild1);
+ const dfChild2 = document.createElement("div");
+ df.appendChild(dfChild2);
+ df.addEventListener("DOMNodeRemoved", listener);
+
+ b.replaceChildren(df);
+ is(events.length, 3, "Should have got three DOMNodeRemoved events");
+ is(events[0].target, txt);
+ is(events[0].relatedNode, b);
+ is(events[1].target, dfChild1);
+ is(events[1].relatedNode, df);
+ is(events[2].target, dfChild2);
+ is(events[2].relatedNode, df);
+
+ // Test 3, replace current children with multiple elements.
+ events = [];
+ const rootChild1 = document.createElement("div");
+ root.appendChild(rootChild1);
+ const rootChild2 = document.createElement("div");
+ root.appendChild(rootChild2);
+ // Note, if replaceChildren gets more than one parameter, it moves the nodes
+ // to a new internal document fragment and then removes the current children.
+ b.replaceChildren(rootChild1, rootChild2);
+ is(events.length, 4, "Should have got four DOMNodeRemoved events");
+ is(events[0].target, rootChild1);
+ is(events[0].relatedNode, root);
+ is(events[1].target, rootChild2);
+ is(events[1].relatedNode, root);
+ is(events[2].target, dfChild1);
+ is(events[2].relatedNode, b);
+ is(events[3].target, dfChild2);
+ is(events[3].relatedNode, b);
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug199959.html b/dom/base/test/test_bug199959.html
new file mode 100644
index 0000000000..e807646c10
--- /dev/null
+++ b/dom/base/test/test_bug199959.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199959
+-->
+<head>
+ <title>Test for Bug 199959</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=199959">Mozilla Bug 199959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="attrTest" testAttr="testValue"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 199959 **/
+
+// From ACID3
+var attrTest = document.getElementById("attrTest");
+var attr = attrTest.getAttributeNode("testAttr");
+ok(attr.specified, "Attribute isn't specified!");
+attrTest.removeAttributeNode(attr);
+ok(attr.specified, "Attribute isn't specified after removal!");
+
+// From bug 199959
+attr = document.createAttribute('foo');
+ok(attr.specified, "Attribute isn't specified!");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug218236.html b/dom/base/test/test_bug218236.html
new file mode 100644
index 0000000000..d691ad4418
--- /dev/null
+++ b/dom/base/test/test_bug218236.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=218236
+-->
+<head>
+ <title>Test for Bug 218236</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=218236">Mozilla Bug 218236</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 218236 **/
+
+SimpleTest.waitForExplicitFinish();
+
+/* Test data */
+
+var url_200 = window.location.href;
+var url_404 = url_200.replace(/[^/]+$/, "this_file_is_not_going_to_be_there.dummy");
+var url_connection_error = url_200.replace(/^(\w+:\/\/[^/]+?)(:\d+)?\//, "$1:9546/");
+
+// List of tests: name of the test, URL to be requested, expected sequence
+// of events and optionally a function to be called from readystatechange handler.
+// Numbers in the list of events are values of XMLHttpRequest.readyState
+// when readystatechange event is triggered.
+var tests = [
+ ["200 OK", url_200, [1, 2, 3, 4, "load"], null],
+ ["404 Not Found", url_404, [1, 2, 3, 4, "load"], null],
+ ["connection error", url_connection_error, [1, 4, "error"], null],
+ ["abort() call on readyState = 1", url_200, [1, 4], null, doAbort1],
+ ["abort() call on readyState = 2", url_200, [1, 2, 4], doAbort2],
+];
+
+var testName = null;
+var currentState = 0;
+var currentSequence = null;
+var expectedSequence = null;
+var currentCallback = null;
+var finalizeTimeoutID = null;
+
+var request = null;
+
+runNextTest();
+
+function doAbort1() {
+ if (request.readyState == 1)
+ request.abort();
+}
+function doAbort2() {
+ if (request.readyState == 2)
+ request.abort();
+}
+
+/* Utility functions */
+
+function runNextTest() {
+ if (tests.length) {
+ var test = tests.shift();
+
+ // Initialize state variables
+ testName = test[0]
+ currentState = 0;
+ currentSequence = [];
+ expectedSequence = test[2];
+ currentCallback = test[3];
+ postSendCallback = test[4];
+
+ // Prepare request object
+ request = new XMLHttpRequest();
+ request.onreadystatechange = onReadyStateChange;
+ request.open("GET", test[1]);
+ request.onload = onLoad;
+ request.onerror = onError;
+
+ // Start request
+ request.send(null);
+ if (postSendCallback)
+ postSendCallback();
+ }
+ else
+ SimpleTest.finish();
+}
+
+function finalizeTest() {
+ finalizeTimeoutID = null;
+ ok(compareArrays(expectedSequence, currentSequence), "event sequence for '" + testName + "' was " + currentSequence.join(", "));
+
+ runNextTest();
+}
+
+function onReadyStateChange() {
+ clearTimeout(finalizeTimeoutID);
+ finalizeTimeoutID = null;
+
+ currentState = request.readyState;
+ currentSequence.push(currentState);
+
+ if (currentState == 4) {
+ // Allow remaining event to fire but then we are finished with this test
+ // unless we get another onReadyStateChange in which case we'll cancel
+ // this timeout
+ finalizeTimeoutID = setTimeout(finalizeTest, 0);
+ }
+
+ if (currentCallback)
+ currentCallback();
+}
+
+function onLoad() {
+ currentSequence.push("load");
+}
+
+function onError() {
+ currentSequence.push("error");
+}
+
+function compareArrays(array1, array2) {
+ if (array1.length != array2.length)
+ return false;
+
+ for (var i = 0; i < array1.length; i++)
+ if (array1[i] != array2[i])
+ return false;
+
+ return true;
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug218277.html b/dom/base/test/test_bug218277.html
new file mode 100644
index 0000000000..bcad1dafd7
--- /dev/null
+++ b/dom/base/test/test_bug218277.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=218277
+-->
+<head>
+ <title>Test for Bug 218277</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=218277">Mozilla Bug 218277</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+ <input id="ctrl" name="ctrl" size="20" value="keep&nbsp;together" readonly />
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 218277 **/
+
+is(escape($('ctrl').value), "keep%A0together", "nbsp preserved in form submissions");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug238409.html b/dom/base/test/test_bug238409.html
new file mode 100644
index 0000000000..ac0b261548
--- /dev/null
+++ b/dom/base/test/test_bug238409.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=238409
+-->
+<head>
+ <title>Test for Bug 238409</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=238409">Mozilla Bug 238409</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <table id="table_spacing0" cellspacing="0">
+ <tr><td>cellspacing="0"</td></tr>
+ </table>
+
+ <table id="table_spacing2" cellspacing="2">
+ <tr><td>cellspacing="2"</td></tr>
+ </table>
+
+ <table id="table_spacingNone">
+ <tr><td>no cellspacing</td></tr>
+ </table>
+
+ <table id="table_spacingMalformed" cellspacing>
+ <tr><td>malformed cellspacing</td></tr>
+ </table>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 238409 **/
+
+ok(document.getElementById("table_spacing0").cellSpacing == "0", "parsing table with cellspacing='0'");
+ok(document.getElementById("table_spacing2").cellSpacing == "2", "parsing table with cellspacing='2'");
+ok(document.getElementById("table_spacingNone").cellSpacing == "", "parsing table without cellspacing");
+ok(document.getElementById("table_spacingMalformed").cellSpacing == "", "parsing table with malformed cellspacing");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug254337.html b/dom/base/test/test_bug254337.html
new file mode 100644
index 0000000000..1c68b18c14
--- /dev/null
+++ b/dom/base/test/test_bug254337.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=254337
+-->
+<head>
+ <title>Test for Bug 254337</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=254337">Mozilla Bug 254337</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 254337 **/
+
+var el = document.createElement("div");
+el.setAttribute("class", "foobar1");
+is(el.className, "foobar1", "Wrong className!");
+el.className += " foobar2 ";
+is(el.className, "foobar1 foobar2 ", "Appending to className didn't work!");
+el.className += "foobar3";
+is(el.className, "foobar1 foobar2 foobar3", "Appending to className didn't work!");
+
+var el = document.createElement("div");
+el.setAttribute("class", " foobar1 ");
+is(el.className, " foobar1 ", "Wrong className!");
+el.className += "foobar2";
+is(el.className, " foobar1 foobar2", "Appending to className didn't work!");
+el.setAttribute("class", " ");
+is(el.getAttribute("class"), " ", "class attribute didn't store the right value!");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug270145.xhtml b/dom/base/test/test_bug270145.xhtml
new file mode 100644
index 0000000000..ebf89ef5ca
--- /dev/null
+++ b/dom/base/test/test_bug270145.xhtml
@@ -0,0 +1,51 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=270145
+-->
+<head>
+ <title>Test the html copy encoder with XHTML </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=270145">Mozilla Bug 270145</a>
+<p id="display"></p>
+<div id="content" >
+<p id="foo"><![CDATA[a text to copy]]></p>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+//<![CDATA[
+function testHtmlCopyEncoder () {
+ var encoder = SpecialPowers.Cu.createHTMLCopyEncoder();
+ var out, expected;
+
+ var node = document.getElementById('draggable');
+
+ var select = window.getSelection();
+ select.removeAllRanges();
+
+ node = document.getElementById("foo").firstChild;
+ var range = document.createRange();
+ range.setStart(node, 0);
+ range.setEnd(node, "a text to copy".length);
+ select.addRange(range);
+ encoder.init(document, "text/html", 0);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = "a text to copy";
+ is(out, expected, "test xhtml copy");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlCopyEncoder);
+//]]>
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug276037-1.html b/dom/base/test/test_bug276037-1.html
new file mode 100644
index 0000000000..2216480337
--- /dev/null
+++ b/dom/base/test/test_bug276037-1.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=276037
+-->
+<head>
+ <title>Test for Bug 276037</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=276037">Mozilla Bug 276037</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 276037 **/
+function countElements (node, namespaceURI, localName) {
+ var count = 0;
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType == Node.ELEMENT_NODE && child.localName == localName &&
+ child.namespaceURI == namespaceURI) {
+ count++;
+ }
+ if (child.hasChildNodes()) {
+ count += countElements(child, namespaceURI, localName);
+ }
+ }
+ return count;
+}
+
+function checkElements(namespaceURI, localName) {
+ var elementsNS = document.getElementsByTagNameNS(namespaceURI, localName);
+ var elements = document.getElementsByTagName(localName);
+ var elementCount = countElements(document, namespaceURI, localName);
+ const gEBTN = 'document.getElementsByTagName(\'' + localName + '\').length: ' + elements.length;
+ const gEBTNNS = '; document.getElementsByTagNameNS(\'' + namespaceURI + '\', \'' + localName + '\').length: ' + elementsNS.length;
+
+ text1 = gEBTN + '; element count: ' + elementCount;
+ text2 = gEBTNNS + '; element count: ' + elementCount;
+
+ is(elements.length, elementCount, text1);
+ is(elementsNS.length, elementCount, text2);
+ is(global.gEBTN[namespaceURI][localName].length, elementCount, text1);
+ is(global.gEBTNNS[namespaceURI][localName].length, elementCount, text2);
+}
+
+const xhtmlNS = 'http://www.w3.org/1999/xhtml';
+
+function checkSpansAndScripts () {
+ checkElements(xhtmlNS, 'span');
+ checkElements(xhtmlNS, 'script');
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() { checkSpansAndScripts() });
+addLoadEvent(SimpleTest.finish);
+
+// Init our global lists
+var global = {};
+global.gEBTN = {};
+global.gEBTN[xhtmlNS] = {};
+global.gEBTNNS = {};
+global.gEBTNNS[xhtmlNS] = {};
+
+global.gEBTN[xhtmlNS].span = document.getElementsByTagName("span");
+global.gEBTNNS[xhtmlNS].span = document.getElementsByTagNameNS(xhtmlNS, "span");
+global.gEBTN[xhtmlNS].script = document.getElementsByTagName("script");
+global.gEBTNNS[xhtmlNS].script = document.getElementsByTagNameNS(xhtmlNS, "script");
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug276037-2.xhtml b/dom/base/test/test_bug276037-2.xhtml
new file mode 100644
index 0000000000..d0155a4168
--- /dev/null
+++ b/dom/base/test/test_bug276037-2.xhtml
@@ -0,0 +1,106 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=276037
+-->
+<head>
+ <title>Test for Bug 276037</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=276037">Mozilla Bug 276037</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 276037 **/
+function countElements (node, namespaceURI, tagName) {
+ var count = 0;
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType == Node.ELEMENT_NODE && child.tagName == tagName &&
+ child.namespaceURI == namespaceURI) {
+ count++;
+ }
+ if (child.hasChildNodes()) {
+ count += countElements(child, namespaceURI, tagName);
+ }
+ }
+ return count;
+}
+
+function checkElements(namespaceURI, tagName) {
+ var elementsNS = document.getElementsByTagNameNS(namespaceURI, tagName);
+ var elements = document.getElementsByTagName(tagName);
+ var elementCount = countElements(document, namespaceURI, tagName);
+ const gEBTN = 'document.getElementsByTagName(\'' + tagName + '\').length: ' + elements.length;
+ const gEBTNNS = '; document.getElementsByTagNameNS(\'' + namespaceURI + '\', \'' + tagName + '\').length: ' + elementsNS.length;
+
+ var text1 = gEBTN + '; element count: ' + elementCount;
+ var text2 = gEBTNNS + '; element count: ' + elementCount;
+
+ is(elements.length, elementCount, text1);
+ is(elementsNS.length, elementCount, text2);
+ is(global.gEBTN[namespaceURI][tagName].length, elementCount, text1);
+ is(global.gEBTNNS[namespaceURI][tagName].length, elementCount, text2);
+}
+
+const xhtmlNS = 'http://www.w3.org/1999/xhtml';
+
+function checkSpansAndScripts () {
+ checkElements(xhtmlNS, 'span');
+ checkElements(xhtmlNS, 'script');
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() { checkSpansAndScripts() });
+addLoadEvent(SimpleTest.finish);
+
+// Init our global lists
+var global = {};
+global.gEBTN = {};
+global.gEBTN[xhtmlNS] = {};
+global.gEBTNNS = {};
+global.gEBTNNS[xhtmlNS] = {};
+global.gEBTN[xhtmlNS].span = document.getElementsByTagName("span");
+global.gEBTNNS[xhtmlNS].span = document.getElementsByTagNameNS(xhtmlNS, "span");
+global.gEBTN[xhtmlNS].script = document.getElementsByTagName("script");
+global.gEBTNNS[xhtmlNS].script = document.getElementsByTagNameNS(xhtmlNS, "script");
+]]>
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+<p><span>Static text in span.</span></p>
+<script type="text/javascript">
+checkSpansAndScripts();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug282547.html b/dom/base/test/test_bug282547.html
new file mode 100644
index 0000000000..4c310a2724
--- /dev/null
+++ b/dom/base/test/test_bug282547.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=282547
+-->
+<head>
+ <title>Test for Bug 282547</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=282547">Mozilla Bug 282547</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<script class="testbody" type="text/javascript">
+
+function xhr_userpass_sync() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'bug282547.sjs', false, 'username', 'password');
+
+ xhr.send(null);
+ ok(xhr.status == 401, "Status 401");
+
+ runTests();
+}
+
+function xhr_userpass_async() {
+ xhr = new XMLHttpRequest();
+ xhr.open('GET', 'bug282547.sjs', true, 'username', 'password');
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.status == 401, "Status 401");
+ runTests();
+ }
+ }
+
+ xhr.send(null);
+}
+
+function xhr_auth_header_sync() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'bug282547.sjs', false);
+ xhr.setRequestHeader("Authorization", "42");
+
+ xhr.send(null);
+ ok(xhr.status == 401, "Status 401");
+
+ runTests();
+}
+
+function xhr_auth_header_async() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'bug282547.sjs', true);
+ xhr.setRequestHeader("Authorization", "42");
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.status == 401, "Status 401");
+ runTests();
+ }
+ }
+
+ xhr.send(null);
+}
+
+function xhr_crossorigin_sync() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'http://example.com/tests/dom/base/test/bug282547.sjs', true);
+ xhr.withCredentials = true;
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.status == 401, "Status 401");
+ runTests();
+ }
+ }
+
+ xhr.send(null);
+}
+
+var tests = [ xhr_userpass_sync,
+ xhr_userpass_async,
+ xhr_auth_header_sync,
+ xhr_auth_header_async,
+ /* Disabled: bug799540 xhr_crossorigin_sync */ ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTests();
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug28293.html b/dom/base/test/test_bug28293.html
new file mode 100644
index 0000000000..332c75e42b
--- /dev/null
+++ b/dom/base/test/test_bug28293.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=28293
+-->
+<head>
+ <title>Test for Bug 28293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+scriptInsertedExternalExecuted = false;
+res = 'A';
+
+SimpleTest.waitForExplicitFinish();
+onload = function () {
+
+ res+='2';
+
+ s = document.createElement('script');
+ s.textContent="res+='g';";
+ s.defer = true;
+ document.body.appendChild(s);
+
+ res+='3';
+
+ s = document.createElement('script');
+ s.textContent="res+='i';done()";
+ s.defer = true;
+ document.body.appendChild(s);
+
+ res+='4';
+}
+
+function done() {
+ is(res, "AacBCDEFGeHIJb1M2g3i", "scripts executed in the wrong order");
+ ok(scriptInsertedExternalExecuted, "Dynamic script did not block load");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=28293">Mozilla Bug 28293</a>
+
+<script defer>
+res += 'a';
+</script>
+<script defer src="data:text/plain,res+='b'"></script>
+<script defer>
+res += 'c';
+</script>
+<script>
+res += 'B';
+</script>
+<script>
+res += 'C';
+
+s = document.createElement('script');
+s.textContent="res+='D';";
+document.body.appendChild(s);
+
+res += 'E';
+</script>
+<script>
+res += 'F';
+document.addEventListener("DOMContentLoaded", function() {
+ res += '1'
+ s = document.createElement('script');
+ s.src="file_bug28293.sjs?res+='M';";
+ document.body.appendChild(s);
+});
+res += 'G';
+</script>
+<script defer>
+res += 'e';
+</script>
+<script src="file_bug28293.sjs?res+='H';"></script>
+<script>
+res += 'I';
+s = document.createElement('script');
+s.src="file_bug28293.sjs?scriptInsertedExternalExecuted=true;";
+document.body.appendChild(s);
+res += 'J';
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug28293.xhtml b/dom/base/test/test_bug28293.xhtml
new file mode 100644
index 0000000000..47d73a01b4
--- /dev/null
+++ b/dom/base/test/test_bug28293.xhtml
@@ -0,0 +1,87 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=28293
+-->
+<head>
+ <title>Test for Bug 28293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+scriptInsertedExternalExecuted = false;
+res = 'A';
+
+SimpleTest.waitForExplicitFinish();
+onload = function () {
+
+ res+='2';
+
+ s = document.createElement('script');
+ s.textContent="res+='g';";
+ s.defer = true;
+ document.body.appendChild(s);
+
+ res+='3';
+
+ s = document.createElement('script');
+ s.textContent="res+='i';done()";
+ s.defer = true;
+ document.body.appendChild(s);
+
+ res+='4';
+}
+
+function done() {
+ is(res, "AacBCDEFGeHIJb1M2g3i", "scripts executed in the wrong order");
+ ok(scriptInsertedExternalExecuted, "Dynamic script did not block load");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=28293">Mozilla Bug 28293</a>
+
+<script defer="defer">
+res += 'a';
+</script>
+<script defer="defer" src="data:text/plain,res+='b'"></script>
+<script defer="defer">
+res += 'c';
+</script>
+<script>
+res += 'B';
+</script>
+<script>
+res += 'C';
+
+s = document.createElement('script');
+s.textContent="res+='D';";
+document.body.appendChild(s);
+
+res += 'E';
+</script>
+<script>
+res += 'F';
+document.addEventListener("DOMContentLoaded", function() {
+ res += '1'
+ s = document.createElement('script');
+ s.src="file_bug28293.sjs?res+='M';";
+ document.body.appendChild(s);
+});
+res += 'G';
+</script>
+<script defer="defer">
+res += 'e';
+</script>
+<script src="file_bug28293.sjs?res+='H';"></script>
+<script>
+<![CDATA[
+res += 'I';
+s = document.createElement('script');
+s.src="file_bug28293.sjs?scriptInsertedExternalExecuted=true;";
+document.body.appendChild(s);
+res += 'J';
+]]>
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug298064.html b/dom/base/test/test_bug298064.html
new file mode 100644
index 0000000000..6fd21e5129
--- /dev/null
+++ b/dom/base/test/test_bug298064.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=298064
+-->
+<head>
+ <title>Test for Bug 298064</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=298064">Mozilla Bug 298064</a>
+<p id="display"><iframe src="bug298064-subframe.html"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 298064 **/
+SimpleTest.waitForExplicitFinish()
+addLoadEvent(function() {
+ window.frames[0].test_func();
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug300992.html b/dom/base/test/test_bug300992.html
new file mode 100644
index 0000000000..c9059bea85
--- /dev/null
+++ b/dom/base/test/test_bug300992.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=300992
+-->
+<head>
+ <title>Test for Bug 300992</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=300992">Mozilla Bug 300992</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 300992 **/
+SimpleTest.waitForExplicitFinish();
+
+var i = 0;
+var states = ['loading',
+ 'interactive1', 'interactive2',
+ 'complete1', 'complete2'];
+
+is(document.readyState, states[i++], 'initial readyState');
+document.onreadystatechange = function (event) {
+ is(document.readyState + '1', states[i++], 'readystatechange event "on" handler');
+};
+document.addEventListener('readystatechange', function(event) {
+ is(document.readyState + '2', states[i++], 'readystatechange event document listener');
+});
+window.addEventListener('readystatechange', function(event) {
+ ok(false, 'window listener', 'readystatechange event should not bubble to window');
+});
+addLoadEvent(function() {
+ is(i, states.length, 'readystatechange event count');
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug311681.xml b/dom/base/test/test_bug311681.xml
new file mode 100644
index 0000000000..23efcb4688
--- /dev/null
+++ b/dom/base/test/test_bug311681.xml
@@ -0,0 +1,103 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=311681
+-->
+<head>
+ <title>Test for Bug 311681</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=311681">Mozilla Bug 311681</a>
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ // Setup script
+ SimpleTest.waitForExplicitFinish();
+
+ // Make sure to trigger the hashtable case by asking for enough elements
+ // by ID.
+ for (var i = 0; i < 256; ++i) {
+ var x = document.getElementById(i);
+ }
+
+ // save off the document.getElementById function, since getting it as a
+ // property off the document it causes a content flush.
+ var fun = document.getElementById;
+
+ // Slot for our initial element with id "content"
+ var testNode;
+
+ function getCont() {
+ return fun.call(document, "content");
+ }
+
+ function testClone() {
+ // Test to make sure that if we have multiple nodes with the same ID in
+ // a document we don't forget about one of them when the other is
+ // removed.
+ var newParent = $("display");
+ var node = testNode.cloneNode(true);
+ isnot(node, testNode, "Clone should be a different node");
+
+ newParent.appendChild(node);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting orig node pre-flush 1");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting new node post-flush 1");
+
+ clear(newParent);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), testNode, "Should be getting orig node pre-flush 2");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), testNode, "Should be getting orig node post-flush 2");
+
+ node = testNode.cloneNode(true);
+ newParent.appendChild(node);
+ testNode.parentNode.removeChild(testNode);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting clone pre-flush");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting clone post-flush");
+
+ }
+
+ function clear(node) {
+ while (node.hasChildNodes()) {
+ node.removeChild(node.firstChild);
+ }
+ }
+
+ addLoadEvent(testClone);
+ addLoadEvent(SimpleTest.finish);
+]]>
+</script>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script class="testbody" type="text/javascript">
+ <![CDATA[
+ testNode = fun.call(document, "content");
+ // Needs incremental XML parser
+ isnot(testNode, null, "Should have node here");
+ ]]>
+ </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug313646.html b/dom/base/test/test_bug313646.html
new file mode 100644
index 0000000000..55bb760978
--- /dev/null
+++ b/dom/base/test/test_bug313646.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=313646
+-->
+<head>
+ <title>Test for Bug 313646</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=313646">Mozilla Bug 313646</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 313646 **/
+
+// dom/base/test/bug313646.txt
+
+SimpleTest.waitForExplicitFinish();
+
+var count1 = 0;
+var count2 = 0;
+var count3 = 0;
+var count4 = 0;
+var innerXHRDone = 0;
+var req = new XMLHttpRequest();
+req.onreadystatechange = function(evt) {
+ ++window["count" + evt.target.readyState];
+
+ // Do something a bit evil, start a new sync XHR in
+ // readyStateChange listener.
+ var innerXHR = new XMLHttpRequest();
+ innerXHR.onreadystatechange = function(e) {
+ if (e.target.readyState == 4) {
+ ++innerXHRDone;
+ }
+ }
+ innerXHR.open("GET","bug313646.txt", false);
+ innerXHR.send();
+}
+
+// make the synchronous request
+req.open("GET","bug313646.txt", false);
+req.send();
+
+ok(count1, "XMLHttpRequest wasn't in state 1");
+is(count2, 0, "XMLHttpRequest shouldn't have been in state 2");
+is(count3, 0, "XMLHttpRequest shouldn't have been in state 3");
+ok(count4, "XMLHttpRequest wasn't in state 4");
+is(innerXHRDone, 2, "There should have been 2 inner XHRs.");
+
+SimpleTest.finish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug320799.html b/dom/base/test/test_bug320799.html
new file mode 100644
index 0000000000..dd7f598a0f
--- /dev/null
+++ b/dom/base/test/test_bug320799.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=320799
+-->
+<head>
+ <title>Test for Bug 320799</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=320799">Mozilla Bug 320799</a>
+<p id="display">
+ <select id="s" style="width: 100px; box-sizing: border-box; border: 0">
+ <option>This is a test, it really is a test I tell you</option>
+ </select>
+ <select id="s2">
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ <option>x</option>
+ </select>
+ <select id="s3">
+ <option>x</option>
+ </select>
+ <select id="s4" style="width: 100px; box-sizing: border-box; border: 0; margin: 10px">
+ <option>This is a test, it really is a test I tell you</option>
+ </select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 320799 **/
+is($("s").scrollWidth, 100, "Scroll width should not include dropdown contents");
+is($("s2").clientWidth, $("s3").clientWidth,
+ "Client width should not depend on the dropdown's vertical scrollbar");
+
+is($("s4").scrollWidth, 100, "Scroll width should not include dropdown contents");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug322317.html b/dom/base/test/test_bug322317.html
new file mode 100644
index 0000000000..d4e7b6f853
--- /dev/null
+++ b/dom/base/test/test_bug322317.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=322317
+-->
+<head>
+ <title>Test for Bug 322317</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=322317">Mozilla Bug 322317</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 322317 **/
+/* Test that this does not throw exceptions */
+var http =new XMLHttpRequest();
+http.open("GET", window.location.href);
+http.send(null);
+http.open("GET", window.location.href);
+http.send(null);
+is(1, 1, "Got here, yay!");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug326337.html b/dom/base/test/test_bug326337.html
new file mode 100644
index 0000000000..fc789018cb
--- /dev/null
+++ b/dom/base/test/test_bug326337.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=326337
+-->
+<head>
+ <title>Test for Bug 326337</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=326337">Mozilla Bug 326337</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 326337 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var win = window.open("file_bug326337_outer.html", "", "width=10, height=10");
+
+function finishTest(str) {
+ win.close();
+ is(str, "#done", "failed to run");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug331959.html b/dom/base/test/test_bug331959.html
new file mode 100644
index 0000000000..c098c55d7b
--- /dev/null
+++ b/dom/base/test/test_bug331959.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=331959
+-->
+<head>
+ <title>Test for Bug 331959</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=331959">Mozilla Bug 331959</a>
+<p id="display">
+ <iframe id="link-in-link-mouse"></iframe>
+ <iframe id="link-in-link-keyboard"></iframe>
+ <iframe id="input-in-link-mouse"></iframe>
+ <iframe id="input-in-link-keyboard"></iframe>
+ <iframe id="button-in-link-mouse"></iframe>
+ <iframe id="button-in-link-keyboard"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 331959 **/
+SimpleTest.waitForExplicitFinish();
+
+const FAILURL = "FAIL.html";
+const PASSURL = "PASS.html";
+
+var currentTest = 0;
+var tests = [ testLinkInLinkMouse, testLinkInLinkKeyboard,
+ testInputInLinkMouse, testInputInLinkKeyboard,
+ testButtonInLinkMouse, testButtonInLinkKeyboard ];
+function doNextTest() {
+ if (currentTest == tests.length) {
+ SimpleTest.finish();
+ } else {
+ tests[currentTest++]();
+ }
+}
+
+function generateLinkInLink(id, desc) {
+ var doc = $(id).contentDocument;
+ var outerA = doc.createElement("a");
+ var innerA = doc.createElement("a");
+ outerA.id = "outer";
+ innerA.id = "inner";
+ innerA.href = PASSURL;
+ outerA.href = FAILURL;
+ innerA.appendChild(doc.createTextNode("Text"));
+ outerA.appendChild(innerA);
+ doc.body.appendChild(outerA);
+ $(id).onload = function() {
+ is(this.contentDocument.documentElement.innerText, "PASS", desc);
+ // Have to remove the iframe we used from the DOM, because the harness is
+ // stupid and doesn't have enough space for more than one iframe.
+ $(id).remove();
+ doNextTest();
+ };
+ return [innerA, $(id).contentWindow];
+}
+
+function testLinkInLinkMouse() {
+ var [innerA, testWin] =
+ generateLinkInLink("link-in-link-mouse",
+ "Clicking an inner link should load the inner link");
+ synthesizeMouseAtCenter(innerA, {}, testWin);
+}
+
+function testLinkInLinkKeyboard() {
+ var [innerA, testWin] =
+ generateLinkInLink("link-in-link-keyboard",
+ "Hitting enter on an inner link should load the inner link");
+ innerA.focus();
+ synthesizeKey("VK_RETURN", {}, testWin);
+}
+
+function generateInputInLink(id, desc) {
+ var doc = $(id).contentDocument;
+ doc.body.innerHTML =
+ "<form action='" + PASSURL + "'><a href='" + FAILURL +
+ "'><input type='submit' id='submit'>";
+ $(id).onload = function() {
+ is(this.contentDocument.documentElement.innerText, "PASS", desc);
+ // Have to remove the iframe we used from the DOM, because the harness is
+ // stupid and doesn't have enough space for more than one iframe.
+ $(id).remove();
+ doNextTest();
+ };
+ var input = doc.getElementById("submit");
+ doc.body.offsetWidth;
+ return [input, $(id).contentWindow];
+}
+
+function testInputInLinkMouse() {
+ var [input, testWin] =
+ generateInputInLink("input-in-link-mouse",
+ "Clicking an submit input inside an anchor should submit the form");
+ synthesizeMouseAtCenter(input, {}, testWin);
+}
+
+function testInputInLinkKeyboard() {
+ var [input, testWin] =
+ generateInputInLink("input-in-link-keyboard",
+ "Return on submit input inside an anchor should submit the form");
+ input.focus();
+ synthesizeKey("VK_RETURN", {}, testWin);
+}
+
+function generateButtonInLink(id, desc) {
+ var doc = $(id).contentDocument;
+ doc.body.innerHTML =
+ "<form action='" + PASSURL + "'><a href='" + FAILURL +
+ "'><button type='submit' id='submit'>Submit</button>";
+ $(id).onload = function() {
+ is(this.contentDocument.documentElement.innerText, "PASS", desc);
+ // Have to remove the iframe we used from the DOM, because the harness is
+ // stupid and doesn't have enough space for more than one iframe.
+ $(id).remove();
+ doNextTest();
+ };
+ var button = doc.getElementById("submit");
+ return [button, $(id).contentWindow];
+}
+
+function testButtonInLinkMouse() {
+ var [button, testWin] =
+ generateButtonInLink("button-in-link-mouse",
+ "Clicking an submit button inside an anchor should submit the form");
+ synthesizeMouseAtCenter(button, {}, testWin);
+}
+
+function testButtonInLinkKeyboard() {
+ var [button, testWin] =
+ generateButtonInLink("button-in-link-keyboard",
+ "Return on submit button inside an anchor should submit the form");
+ button.focus();
+ synthesizeKey("VK_RETURN", {}, testWin);
+}
+
+// We need focus to handle clicks properly
+SimpleTest.waitForFocus(doNextTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug333064.html b/dom/base/test/test_bug333064.html
new file mode 100644
index 0000000000..65cc893a42
--- /dev/null
+++ b/dom/base/test/test_bug333064.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=333064
+-->
+<head>
+ <title>Test for Bug 333064</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=333064">Mozilla Bug 333064</a>
+<p id="display"></p>
+
+<div id="display">
+</div>
+<div id="korean-text">안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안</div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 333064 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("korean-text");
+ var sel = window.getSelection();
+
+ // Select text node in div.
+ var r = document.createRange();
+ r.setStart(div, 0);
+ r.setEnd(div, 1);
+ sel.addRange(r);
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ // Make sure we got the HTML flavour we asked for and that our
+ // string is included without additional spaces.
+ return value.includes("korean-text") && value.includes("안".repeat(160));
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ SimpleTest.finish();
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug333198.html b/dom/base/test/test_bug333198.html
new file mode 100644
index 0000000000..e1c09000e8
--- /dev/null
+++ b/dom/base/test/test_bug333198.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=333198
+-->
+<head>
+ <title>Test for Bug 333198</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<!-- setTimeout so that the test starts after paint suppression ends -->
+<body onload="setTimeout(runTest,0);">
+<iframe id="ifr"></iframe><br>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=333198">Mozilla Bug 333198</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 333198 **/
+
+var focusTester;
+var focusTester2;
+var focusCount = 0;
+var eventCount = 0;
+function clickHandler() {
+ ++eventCount;
+}
+
+function suppressEvents(suppress) {
+ SpecialPowers.DOMWindowUtils.suppressEventHandling(suppress);
+}
+
+function sendEvents() {
+ windowUtils = SpecialPowers.getDOMWindowUtils(window);
+ windowUtils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
+ windowUtils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0);
+
+ iframeUtils = SpecialPowers.getDOMWindowUtils(document.getElementById("ifr").contentWindow);
+ iframeUtils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
+ iframeUtils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0);
+}
+
+function runTest() {
+ window.focus();
+ focusTester = document.getElementsByTagName("input")[0];
+ focusTester.blur();
+ window.addEventListener("click", clickHandler, true);
+ var ifr = document.getElementById("ifr")
+ ifr.contentWindow.addEventListener("click", clickHandler, true);
+ sendEvents();
+ is(eventCount, 2, "Wrong event count(1)");
+ suppressEvents(true);
+ sendEvents();
+ is(eventCount, 2, "Wrong event count(2)");
+ suppressEvents(false);
+ sendEvents();
+ is(eventCount, 4, "Wrong event count(2)");
+
+ is(focusCount, 0, "Wrong focus count (1)");
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", window.location, false);
+ xhr.onload = function() {
+ focusTester.focus();
+ is(focusCount, 1, "Wrong focus count (2)");
+ focusTester.blur();
+ }
+ xhr.send();
+
+ focusTester.focus();
+ is(focusCount, 2, "Wrong focus count (3)");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<input type="text" onfocus="++focusCount;">
+</body>
+</html>
diff --git a/dom/base/test/test_bug333673.html b/dom/base/test/test_bug333673.html
new file mode 100644
index 0000000000..2e8547b56c
--- /dev/null
+++ b/dom/base/test/test_bug333673.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=333673
+-->
+<head>
+ <title>Test for Bug 333673</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=333673">Mozilla Bug 333673</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 333673 **/
+
+is(document.implementation, document.implementation,
+ "document.implementation should be the same object all the time.");
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug337631.html b/dom/base/test/test_bug337631.html
new file mode 100644
index 0000000000..ad8d4e3d5a
--- /dev/null
+++ b/dom/base/test/test_bug337631.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=337631
+-->
+<head>
+ <title>Test for Bug 337631</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=337631">Mozilla Bug 337631</a>
+<p id="display"></p>
+<div id="content">
+
+<a href="foo" id="test4">foo</a>
+<input id="test1" value="test">
+<p id="test2">adsf<a href="#" id="test3">asdf</a><input id="test5"></p>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 337631 **/
+
+function getActiveElement()
+{
+ var rv;
+
+ var el = document.activeElement;
+ if (!el) {
+ rv = "none";
+ return rv;
+ }
+
+ if (el && el != document.documentElement) {
+ var nt;
+ try {
+ nt = el.nodeType;
+ } catch (e) {
+ rv = "[no permission]";
+ return rv;
+ }
+ if (!nt) {
+ rv = "[unknown]";
+ } else if (nt == 1) {
+ rv = el.tagName;
+ } else if (nt == 3) {
+ rv = "textnode"
+ } else {
+ rv = nt;
+ }
+
+ el = el.parentNode;
+ while (el && el != document.documentElement) {
+ rv += " in ";
+ try {
+ nt = el.nodeType;
+ } catch (e) {
+ rv += "[no permission]";
+ return rv;
+ }
+ if (!nt) {
+ rv += "[unknown]";
+ } else if (nt == 1) {
+ rv += el.tagName;
+ } else if (nt == 3) {
+ rv += "textnode"
+ } else {
+ rv += nt;
+ }
+
+ el = el.parentNode;
+ }
+ }
+
+ return rv;
+}
+
+$('test1').focus();
+is(getActiveElement(), "INPUT in DIV in BODY", "getActiveElement tests");
+
+$('test2').focus();
+is(getActiveElement(), "INPUT in DIV in BODY", "getActiveElement tests");
+
+$('test3').focus();
+is(getActiveElement(), "A in P in DIV in BODY", "getActiveElement tests");
+
+$('test4').focus();
+is(getActiveElement(), "A in DIV in BODY", "getActiveElement tests");
+
+$('test5').focus();
+is(getActiveElement(), "INPUT in P in DIV in BODY", "getActiveElement tests");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug338541.xhtml b/dom/base/test/test_bug338541.xhtml
new file mode 100644
index 0000000000..9a83332a13
--- /dev/null
+++ b/dom/base/test/test_bug338541.xhtml
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=338541
+-->
+<head>
+ <title>Test for Bug 338541</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338541">Mozilla Bug 338541</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 338541 **/
+function getName(aNode, f)
+{
+ return (aNode ? aNode.nodeName : "(null)");
+}
+
+function walkDOM()
+{
+ var walker = document.createTreeWalker($('content'), NodeFilter.SHOW_ELEMENT, null);
+ var output = "";
+ while (walker.nextNode())
+ {
+ output += getName(walker.currentNode) + "\n";
+ }
+ output += "Final currentNode: " + getName(walker.currentNode);
+ is(output, "foo\nbar\nhtml:b\nqux\nbaz\nFinal currentNode: baz","treewalker returns correct nodeName");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(walkDOM, ok);
+
+</script>
+</pre>
+<div id="content" style="display: none">
+ <foo xmlns="http://example.com">
+ <bar><html:b xmlns:html="http://www.w3.org/1999/xhtml"><qux/></html:b>
+ <baz/>
+ </bar>
+ </foo>
+</div>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug338583.html b/dom/base/test/test_bug338583.html
new file mode 100644
index 0000000000..a399dd490c
--- /dev/null
+++ b/dom/base/test/test_bug338583.html
@@ -0,0 +1,665 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=338583
+-->
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Test for Bug 338583</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+<body bgColor=white>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338583">Mozilla Bug 338583</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Tests for Bug 338583 **/
+
+// we test:
+// 1) the EventSource behaviour
+// 2) if the events are trusted
+// 3) possible invalid eventsources
+// 4) the close method when the object is just been used
+// 5) access-control
+// 6) the data parameter
+// 7) delayed server responses
+
+// --
+
+ var gTestsHaveFinished = [];
+
+ function setTestHasFinished(test_id)
+ {
+ if (gTestsHaveFinished[test_id]) {
+ return;
+ }
+
+ gTestsHaveFinished[test_id] = true;
+ for (var i=0; i < gTestsHaveFinished.length; ++i) {
+ if (!gTestsHaveFinished[i]) {
+ return;
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ function runAllTests() {
+ // these tests run asynchronously, and they will take 8000 ms
+ var all_tests = [
+ doTest1, doTest1_e, doTest1_f, doTest2, doTest3, doTest3_b, doTest3_c, doTest3_d,
+ doTest3_e, doTest3_f, doTest3_g, doTest3_h, doTest4, doTest4_b,
+ doTest5, doTest5_b, doTest5_c, doTest5_e, doTest6, doTest7
+ ];
+
+ for (let test_id=0; test_id < all_tests.length; ++test_id) {
+ gTestsHaveFinished[test_id] = false;
+ var fn = all_tests[test_id];
+ fn(test_id);
+ }
+
+ setTimeout(function() {
+ for (let test_id=0; test_id < all_tests.length; ++test_id) {
+ if (!gTestsHaveFinished[test_id]) {
+ ok(false, "Test " + test_id + " took too long");
+ setTestHasFinished(test_id);
+ }
+ }
+ }, 60000 * stress_factor); // all tests together are supposed to take less than 1 minute
+ }
+
+ function fn_onmessage(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits.fn_onmessage++;
+ }
+
+ function fn_event_listener_message(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits.fn_event_listener_message++;
+ }
+
+ function fn_other_event_name(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits.fn_other_event_name++;
+ }
+
+ var gEventSourceObj1 = null, gEventSourceObj1_e, gEventSourceObj1_f;
+ var gEventSourceObj2 = null;
+ var gEventSourceObj3_a = null, gEventSourceObj3_b = null,
+ gEventSourceObj3_c = null, gEventSourceObj3_d = null,
+ gEventSourceObj3_e = null, gEventSourceObj3_f = null,
+ gEventSourceObj3_g = null, gEventSourceObj3_h = null;
+ var gEventSourceObj4_a = null, gEventSourceObj4_b = null;
+ var gEventSourceObj5_a = null, gEventSourceObj5_b = null,
+ gEventSourceObj5_c = null, gEventSourceObj5_d = null,
+ gEventSourceObj5_e = null, gEventSourceObj5_f = null;
+ var gEventSourceObj6 = null;
+ var gEventSourceObj7 = null;
+ var stress_factor; // used in the setTimeouts in order to help
+ // the test when running in slow machines
+
+ function hasBeenHitFor1And2(obj, min) {
+ if (obj.hits.fn_onmessage < min ||
+ obj.hits.fn_event_listener_message < min ||
+ obj.hits.fn_other_event_name < min)
+ return false;
+ return true;
+ }
+
+// in order to test (1):
+// a) if the EventSource constructor parameter is equal to its url attribute
+// b) let its fn_onmessage, fn_event_listener_message, and fn_other_event_name functions listeners be hit four times each
+// c) the close method (we expect readyState == CLOSED)
+// d) the close method (we expect no message events anymore)
+// e) use the default for withCredentials when passing dictionary arguments that don't explicitly set it
+// f) if a 204 HTTP response closes (interrupts) connections. See bug 869432.
+
+ function doTest1(test_id) {
+ gEventSourceObj1 = new EventSource("eventsource.resource");
+ ok(gEventSourceObj1.url == "http://mochi.test:8888/tests/dom/base/test/eventsource.resource", "Test 1.a failed.");
+ ok(gEventSourceObj1.readyState == 0 || gEventSourceObj1.readyState == 1, "Test 1.a failed.");
+
+ doTest1_b(test_id);
+ }
+
+ function doTest1_b(test_id) {
+ gEventSourceObj1.hits = [];
+ gEventSourceObj1.hits.fn_onmessage = 0;
+ gEventSourceObj1.onmessage = fn_onmessage;
+ gEventSourceObj1.hits.fn_event_listener_message = 0;
+ gEventSourceObj1.addEventListener('message', fn_event_listener_message, true);
+ gEventSourceObj1.hits.fn_other_event_name = 0;
+ gEventSourceObj1.addEventListener('other_event_name', fn_other_event_name, true);
+
+ // the eventsources.res always use a retry of 0.5 second, so for four hits a timeout of 6 seconds is enough
+ setTimeout(function(){
+ bhits = hasBeenHitFor1And2(gEventSourceObj1, 4);
+ ok(bhits, "Test 1.b failed.");
+
+ doTest1_c(test_id);
+ }, parseInt(6000*stress_factor));
+ }
+
+ function doTest1_c(test_id) {
+ gEventSourceObj1.close();
+ ok(gEventSourceObj1.readyState == 2, "Test 1.c failed.");
+
+ doTest1_d(test_id);
+ }
+
+ function doTest1_d(test_id) {
+ gEventSourceObj1.hits.fn_onmessage = 0;
+ gEventSourceObj1.hits.fn_event_listener_message = 0;
+ gEventSourceObj1.hits.fn_other_event_name = 0;
+
+ setTimeout(function(){
+ bhits = hasBeenHitFor1And2(gEventSourceObj1, 1);
+ ok(!bhits, "Test 1.d failed.");
+ gEventSourceObj1.close();
+ setTestHasFinished(test_id);
+ }, parseInt(2000*stress_factor));
+ }
+
+ function doTest1_e(test_id) {
+ try {
+ for (var options of [null, undefined, {}]) {
+ gEventSourceObj1_e = new EventSource("eventsource.resource", options);
+ is(gEventSourceObj1_e.withCredentials, false, "withCredentials should default to false");
+ gEventSourceObj1_e.close();
+ }
+ } catch (e) {
+ ok(false, "Test 1.e failed");
+ }
+ setTestHasFinished(test_id);
+ }
+
+ function doTest1_f(test_id) {
+ var called_on_error = false;
+
+ gEventSourceObj1_f = new EventSource("file_bug869432.eventsource");
+ gEventSourceObj1_f.onopen = function(e) {
+ ok(false, "Test 1.f failed: onopen was called");
+ };
+ gEventSourceObj1_f.onmessage = function(e) {
+ ok(false, "Test 1.f failed: onmessage was called");
+ };
+ gEventSourceObj1_f.onerror = function(e) {
+ if (called_on_error) {
+ ok(false, "Test 1.f failed: onerror was called twice");
+ }
+ called_on_error = true;
+ ok(gEventSourceObj1_f.readyState == 2, "Test 1.f failed: onerror was called with readyState = " + gEventSourceObj1_f.readyState);
+ };
+
+ setTimeout(function() { // just to clean...
+ ok(called_on_error, "Test 1.f failed: onerror was not called");
+ setTestHasFinished(test_id);
+ }, parseInt(5000*stress_factor));
+ }
+
+// in order to test (2)
+// a) set a eventsource that give the dom events messages
+// b) expect trusted events
+
+ function doTest2(test_id) {
+ var func = function(e) {
+ ok(e.isTrusted, "Test 2 failed");
+ gEventSourceObj2.close();
+ };
+
+ gEventSourceObj2 = new EventSource("eventsource.resource");
+ gEventSourceObj2.onmessage = func;
+
+ setTimeout(function() { // just to clean...
+ gEventSourceObj2.close();
+ setTestHasFinished(test_id);
+ }, parseInt(5000*stress_factor));
+ }
+
+// in order to test (3)
+// a) XSite domain error test
+// b) protocol file:// test
+// c) protocol javascript: test
+// d) wrong Content-Type test
+// e) bad http response code test
+// f) message eventsource without a data test
+// g) DNS error
+// h) EventSource which last message doesn't end with an empty line. See bug 710546
+
+ function doTest3(test_id) {
+ gEventSourceObj3_a = new EventSource("http://example.org/tests/dom/base/test/eventsource.resource");
+
+ gEventSourceObj3_a.onmessage = fn_onmessage;
+ gEventSourceObj3_a.hits = [];
+ gEventSourceObj3_a.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_a.hits.fn_onmessage == 0, "Test 3.a failed");
+ gEventSourceObj3_a.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_b(test_id) {
+ // currently no support yet for local files for b2g/Android mochitest, see bug 838726
+ if (navigator.appVersion.includes("Android") || SpecialPowers.Services.appinfo.name == "B2G") {
+ setTestHasFinished(test_id);
+ return;
+ }
+
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+ xhr.send();
+ var basePath = xhr.responseText;
+
+ gEventSourceObj3_b = new EventSource("file://" + basePath + "eventsource.resource");
+
+ gEventSourceObj3_b.onmessage = fn_onmessage;
+ gEventSourceObj3_b.hits = [];
+ gEventSourceObj3_b.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_b.hits.fn_onmessage == 0, "Test 3.b failed");
+ gEventSourceObj3_b.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function jsEvtSource()
+ {
+ return "event: message\n" +
+ "data: 1\n\n";
+ }
+
+ function doTest3_c(test_id) {
+ gEventSourceObj3_c = new EventSource("javascript: return jsEvtSource()");
+
+ gEventSourceObj3_c.onmessage = fn_onmessage;
+ gEventSourceObj3_c.hits = [];
+ gEventSourceObj3_c.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_c.hits.fn_onmessage == 0, "Test 3.c failed");
+ gEventSourceObj3_c.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_d(test_id) {
+ gEventSourceObj3_d = new EventSource("badContentType.eventsource");
+
+ gEventSourceObj3_d.onmessage = fn_onmessage;
+ gEventSourceObj3_d.hits = [];
+ gEventSourceObj3_d.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_d.hits.fn_onmessage == 0, "Test 3.d failed");
+ gEventSourceObj3_d.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_e(test_id) {
+ gEventSourceObj3_e = new EventSource("badHTTPResponseCode.eventsource");
+
+ gEventSourceObj3_e.onmessage = fn_onmessage;
+ gEventSourceObj3_e.hits = [];
+ gEventSourceObj3_e.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_e.hits.fn_onmessage == 0, "Test 3.e failed");
+ gEventSourceObj3_e.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_f(test_id) {
+ gEventSourceObj3_f = new EventSource("badMessageEvent.eventsource");
+
+ gEventSourceObj3_f.onmessage = fn_onmessage;
+ gEventSourceObj3_f.hits = [];
+ gEventSourceObj3_f.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_f.hits.fn_onmessage == 0, "Test 3.f failed");
+ gEventSourceObj3_f.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function fnInvalidNCName() {
+ fnInvalidNCName.hits++;
+ }
+
+ function doTest3_g(test_id) {
+ gEventSourceObj3_g = new EventSource("http://hdfskjghsbg.jtiyoejowe.example.com");
+
+ gEventSourceObj3_g.onmessage = fn_onmessage;
+ gEventSourceObj3_g.hits = [];
+ gEventSourceObj3_g.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_g.hits.fn_onmessage == 0, "Test 3.g failed");
+ gEventSourceObj3_g.close();
+ setTestHasFinished(test_id);
+ }, parseInt(1500*stress_factor));
+ }
+
+ function fnMessageListenerTest3h(e) {
+ fnMessageListenerTest3h.msg_ok = (fnMessageListenerTest3h.msg_ok && e.data == "ok");
+ fnMessageListenerTest3h.id_ok = (fnMessageListenerTest3h.id_ok && e.lastEventId == "");
+ }
+
+ function doTest3_h(test_id) {
+ gEventSourceObj3_h = new EventSource("badMessageEvent2.eventsource");
+
+ gEventSourceObj3_h.addEventListener('message', fnMessageListenerTest3h, true);
+ fnMessageListenerTest3h.msg_ok = true;
+ fnMessageListenerTest3h.id_ok = true;
+
+ gEventSourceObj3_h.onmessage = fn_onmessage;
+ gEventSourceObj3_h.hits = [];
+ gEventSourceObj3_h.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3_h.hits.fn_onmessage > 1, "Test 3.h.1 failed");
+ if (gEventSourceObj3_h.hits.fn_onmessage > 1) {
+ ok(fnMessageListenerTest3h.msg_ok, "Test 3.h.2 failed");
+ ok(fnMessageListenerTest3h.id_ok, "Test 3.h.3 failed");
+ }
+ gEventSourceObj3_h.close();
+ setTestHasFinished(test_id);
+ }, parseInt(6000*stress_factor));
+ }
+
+// in order to test (4)
+// a) close the object when it is in use, which is being processed and that is expected
+// to dispatch more eventlisteners
+// b) remove an eventlistener in use
+
+ function fn_onmessage4_a(e)
+ {
+ if (e.data > gEventSourceObj4_a.lastData)
+ gEventSourceObj4_a.lastData = e.data;
+ if (e.data == 2)
+ gEventSourceObj4_a.close();
+ }
+
+ function fn_onmessage4_b(e)
+ {
+ if (e.data > gEventSourceObj4_b.lastData)
+ gEventSourceObj4_b.lastData = e.data;
+ if (e.data == 2)
+ gEventSourceObj4_b.removeEventListener('message', fn_onmessage4_b, true);
+ }
+
+ function doTest4(test_id) {
+ gEventSourceObj4_a = new EventSource("forRemoval.resource");
+ gEventSourceObj4_a.lastData = 0;
+ gEventSourceObj4_a.onmessage = fn_onmessage4_a;
+
+ setTimeout(function() {
+ ok(gEventSourceObj4_a.lastData == 2, "Test 4.a failed");
+ gEventSourceObj4_a.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest4_b(test_id)
+ {
+ gEventSourceObj4_b = new EventSource("forRemoval.resource");
+ gEventSourceObj4_b.lastData = 0;
+ gEventSourceObj4_b.addEventListener('message', fn_onmessage4_b, true);
+
+ setTimeout(function() {
+ ok(gEventSourceObj4_b.lastData == 2, "Test 4.b failed");
+ gEventSourceObj4_b.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ }
+
+// in order to test (5)
+// a) valid access-control xsite request
+// b) invalid access-control xsite request
+// c) valid access-control xsite request on a restricted page with credentials
+// d) valid access-control xsite request on a restricted page without credentials
+// e) valid access-control xsite request on a restricted page when the parameter withCredentials is a getter
+// f) valid access-control xsite request on a restricted page when the parameter withCredentials is missing
+
+ function doTest5(test_id)
+ {
+ gEventSourceObj5_a = new EventSource("http://example.org/tests/dom/base/test/accesscontrol.resource");
+
+ gEventSourceObj5_a.onmessage = fn_onmessage;
+ gEventSourceObj5_a.hits = [];
+ gEventSourceObj5_a.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_a.hits.fn_onmessage != 0, "Test 5.a failed");
+ gEventSourceObj5_a.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest5_b(test_id)
+ {
+ gEventSourceObj5_b = new EventSource("http://example.org/tests/dom/base/test/invalid_accesscontrol.resource");
+
+ gEventSourceObj5_b.onmessage = fn_onmessage;
+ gEventSourceObj5_b.hits = [];
+ gEventSourceObj5_b.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_b.hits.fn_onmessage == 0, "Test 5.b failed");
+ gEventSourceObj5_b.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest5_c(test_id)
+ {
+ // credentials using the auth cache
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ // also, test mixed mode UI
+ xhr.open("GET", "https://example.com/tests/dom/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
+ xhr.send();
+ xhr.onloadend = function() {
+ ok(xhr.status == 200, "Failed to set credentials in test 5.c");
+
+ gEventSourceObj5_c = new EventSource("https://example.com/tests/dom/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
+ { withCredentials: true } );
+ ok(gEventSourceObj5_c.withCredentials, "Wrong withCredentials in test 5.c");
+
+ gEventSourceObj5_c.onmessage = function(e) {
+ ok(e.origin == "https://example.com", "Wrong Origin in test 5.c");
+ fn_onmessage(e);
+ };
+ gEventSourceObj5_c.hits = [];
+ gEventSourceObj5_c.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_c.hits.fn_onmessage > 0, "Test 5.c failed");
+ gEventSourceObj5_c.close();
+ doTest5_d(test_id);
+ }, parseInt(3000*stress_factor));
+ };
+ }
+
+ function doTest5_d(test_id)
+ {
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open("GET", "https://example.com/tests/dom/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
+ xhr.send();
+ xhr.onloadend = function() {
+ ok(xhr.status == 200, "Failed to set credentials in test 5.d");
+
+ gEventSourceObj5_d = new EventSource("https://example.com/tests/dom/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc");
+ ok(!gEventSourceObj5_d.withCredentials, "Wrong withCredentials in test 5.d");
+
+ gEventSourceObj5_d.onmessage = function(e) {
+ ok(e.origin == "https://example.com", "Wrong Origin in test 5.d");
+ fn_onmessage(e);
+ };
+ gEventSourceObj5_d.hits = [];
+ gEventSourceObj5_d.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_d.hits.fn_onmessage == 0, "Test 5.d failed");
+ gEventSourceObj5_d.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ };
+ }
+
+ function doTest5_e(test_id)
+ {
+ // credentials using the auth cache
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open("GET", "http://example.org/tests/dom/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
+ xhr.send();
+ xhr.onloadend = function() {
+ ok(xhr.status == 200, "Failed to set credentials in test 5.e");
+
+ gEventSourceObj5_e = new EventSource("http://example.org/tests/dom/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
+ { get withCredentials() { return true; } } );
+ ok(gEventSourceObj5_e.withCredentials, "Wrong withCredentials in test 5.e");
+
+ gEventSourceObj5_e.onmessage = function(e) {
+ ok(e.origin == "http://example.org", "Wrong Origin in test 5.e");
+ fn_onmessage(e);
+ };
+ gEventSourceObj5_e.hits = [];
+ gEventSourceObj5_e.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_e.hits.fn_onmessage > 0, "Test 5.e failed");
+ gEventSourceObj5_e.close();
+ doTest5_f(test_id);
+ }, parseInt(5000*stress_factor));
+ };
+ }
+
+ function doTest5_f(test_id)
+ {
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open("GET", "http://example.org/tests/dom/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
+ xhr.send();
+ xhr.onloadend = function() {
+ ok(xhr.status == 200, "Failed to set credentials in test 5.f");
+
+ gEventSourceObj5_f = new EventSource("http://example.org/tests/dom/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc",
+ { });
+ ok(!gEventSourceObj5_f.withCredentials, "Wrong withCredentials in test 5.f");
+
+ gEventSourceObj5_f.onmessage = function(e) {
+ ok(e.origin == "http://example.org", "Wrong Origin in test 5.f");
+ fn_onmessage(e);
+ };
+ gEventSourceObj5_f.hits = [];
+ gEventSourceObj5_f.hits.fn_onmessage = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5_f.hits.fn_onmessage == 0, "Test 5.f failed");
+ gEventSourceObj5_f.close();
+ setTestHasFinished(test_id);
+ }, parseInt(3000*stress_factor));
+ };
+ }
+
+ function doTest6(test_id)
+ {
+ gEventSourceObj6 = new EventSource("somedatas.resource");
+ var fn_somedata = function(e) {
+ if (fn_somedata.expected == 0) {
+ ok(e.data == "123456789\n123456789123456789\n123456789123456789123456789123456789\n 123456789123456789123456789123456789123456789123456789123456789123456789\nçãá\"\'@`~à Ḿyyyy",
+ "Test 6.a failed");
+ } else if (fn_somedata.expected == 1) {
+ ok(e.data == " :xxabcdefghij\nçãá\"\'@`~à Ḿyyyy : zz",
+ "Test 6.b failed");
+ gEventSourceObj6.close();
+ } else {
+ ok(false, "Test 6 failed (unexpected message event)");
+ }
+ fn_somedata.expected++;
+ }
+ fn_somedata.expected = 0;
+ gEventSourceObj6.onmessage = fn_somedata;
+
+ setTimeout(function() {
+ gEventSourceObj6.close();
+ setTestHasFinished(test_id);
+ }, parseInt(2500*stress_factor));
+ }
+
+ function doTest7(test_id)
+ {
+ gEventSourceObj7 = new EventSource("delayedServerEvents.sjs");
+ gEventSourceObj7.msg_received = [];
+ gEventSourceObj7.onmessage = function(e)
+ {
+ e.target.msg_received.push(e.data);
+ }
+
+ setTimeout(function() {
+ gEventSourceObj7.close();
+
+ ok(gEventSourceObj7.msg_received[0] == "" &&
+ gEventSourceObj7.msg_received[1] == "delayed1" &&
+ gEventSourceObj7.msg_received[2] == "delayed2", "Test 7 failed");
+
+ document.getElementById('waitSpan').innerHTML = '';
+ setTestHasFinished(test_id);
+ }, parseInt(8000*stress_factor));
+ }
+
+ function doTest()
+ {
+ // Allow all cookies, then run the actual test
+ SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]},
+ function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}],
+ doTestCallback);
+ });
+ }
+
+ function doTestCallback()
+ {
+
+ // we get a good stress_factor by testing 10 setTimeouts and some float
+ // arithmetic taking my machine as stress_factor==1 (time=589)
+
+ var begin_time = (new Date()).getTime();
+
+ var f = function() {
+ for (var j=0; j<f.i; ++j)
+ eval("Math.log(Math.atan(Math.sqrt(Math.pow(3.1415, 13.1415))/0.0007))");
+ if (f.i < 10) {
+ f.i++;
+ setTimeout(f, 10 + 10*f.i);
+ } else {
+ stress_factor = ((new Date()).getTime()-begin_time)*1/589;
+ stress_factor *= 1.50; // also, a margin of 50%
+
+ runAllTests();
+ }
+ }
+ f.i = 0;
+
+ setTimeout(f, 10);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ addLoadEvent(doTest);
+
+</script>
+</pre>
+ <span id=waitSpan>Wait please...</span>
+</body>
+</html>
diff --git a/dom/base/test/test_bug338679.html b/dom/base/test/test_bug338679.html
new file mode 100644
index 0000000000..bcc214f349
--- /dev/null
+++ b/dom/base/test/test_bug338679.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=338679
+-->
+<head>
+<title>Bug 338679: correct reporting of newValue/prevValue in
+ DOMAttrModified events</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=338679">Bug
+ 338679: correct reporting of newValue/prevValue in
+ DOMAttrModified events</a>
+
+<div id="test" style="width:20em"></div>
+
+<script>
+var testDiv = document.getElementById("test");
+var e_new, e_prev = testDiv.getAttribute("style");
+var phase, recursive = false;
+
+/* driver */
+var tests = [ test_1, test_2, test_3 ];
+var i = 0;
+function nextTest() {
+ if (i < tests.length) {
+ phase = tests[i];
+ i++;
+ phase();
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+if (SpecialPowers.getBoolPref("dom.mutation-events.cssom.disabled")) {
+ ok(true, "DOMAttrModified event from CSSOM change is disabled");
+} else {
+ SimpleTest.waitForExplicitFinish();
+ testDiv.addEventListener("DOMAttrModified", attr_modified);
+ nextTest();
+}
+
+/* event handler */
+function attr_modified(ev) {
+ is(ev.newValue, e_new,
+ phase.name + (recursive ? " recursive" : "") + ": newValue");
+ is(ev.prevValue, e_prev,
+ phase.name + (recursive ? " recursive" : "") + ": prevValue");
+
+ e_prev = e_new;
+ if (!recursive) {
+ recursive = true;
+ e_new = "width: 0px;";
+ testDiv.style.width = "0";
+ } else {
+ recursive = false;
+ setTimeout(nextTest, 0);
+ }
+}
+
+/* tests */
+function test_1() {
+ e_new = "width: auto;";
+ testDiv.style.width = "auto";
+}
+
+function test_2() {
+ e_new = "width: 15%;";
+ testDiv.style.width = "15%";
+}
+
+function test_3() {
+ window.getComputedStyle(testDiv).width; // force style resolution
+ e_new = "width: inherit;";
+ testDiv.style.width = "inherit";
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug339494.html b/dom/base/test/test_bug339494.html
new file mode 100644
index 0000000000..dbf81f5e98
--- /dev/null
+++ b/dom/base/test/test_bug339494.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=339494
+-->
+<head>
+ <title>Test for Bug 339494</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=339494">Mozilla Bug 339494</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="d"></div>
+ <div id="s"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /** Test for Bug 339494 **/
+
+ var d = document.getElementById("d");
+
+ d.setAttribute("hhh", "testvalue");
+
+ document.addEventListener("DOMAttrModified", removeItAgain);
+ d.removeAttribute("hhh");
+ document.removeEventListener("DOMAttrModified", removeItAgain);
+
+ function removeItAgain()
+ {
+ ok(!d.hasAttribute("hhh"), "Value check 1, there should be no value");
+ isnot(d.getAttribute("hhh"), "testvalue", "Value check 2");
+ document.removeEventListener("DOMAttrModified", removeItAgain);
+ d.removeAttribute("hhh");
+ ok(true, "Reachability, we shouldn't have crashed");
+ }
+
+ var s = document.getElementById("s");
+
+ s.setAttribute("ggg", "testvalue");
+
+ document.addEventListener("DOMAttrModified", compareVal);
+ s.setAttribute("ggg", "othervalue");
+ document.removeEventListener("DOMAttrModified", compareVal);
+
+ function compareVal()
+ {
+ ok(s.hasAttribute("ggg"), "Value check 3, there should be a value");
+ isnot(s.getAttribute("ggg"), "testvalue", "Value check 4");
+ is(s.getAttribute("ggg"), "othervalue", "Value check 5");
+ }
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug339494.xhtml b/dom/base/test/test_bug339494.xhtml
new file mode 100644
index 0000000000..985d78b368
--- /dev/null
+++ b/dom/base/test/test_bug339494.xhtml
@@ -0,0 +1,58 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=339494
+-->
+<head>
+ <title>Test for Bug 339494</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=339494">Mozilla Bug 339494</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="d" />
+ <div id="s" />
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 339494 **/
+
+ var d = document.getElementById("d");
+
+ d.setAttribute("hhh", "testvalue");
+
+ document.addEventListener("DOMAttrModified", removeItAgain);
+ d.removeAttribute("hhh");
+ document.removeEventListener("DOMAttrModified", removeItAgain);
+
+ function removeItAgain()
+ {
+ ok(!d.hasAttribute("hhh"), "Value check 1, there should be no value");
+ isnot(d.getAttribute("hhh"), "testvalue", "Value check 2");
+ document.removeEventListener("DOMAttrModified", removeItAgain);
+ d.removeAttribute("hhh");
+ ok(true, "Reachability, We shouldn't have crashed");
+ }
+
+ var s = document.getElementById("s");
+
+ s.setAttribute("ggg", "testvalue");
+
+ document.addEventListener("DOMAttrModified", compareVal);
+ s.setAttribute("ggg", "othervalue");
+ document.removeEventListener("DOMAttrModified", compareVal);
+
+ function compareVal()
+ {
+ ok(s.hasAttribute("ggg"), "Value check 3, there should be a value");
+ isnot(s.getAttribute("ggg"), "testvalue", "Value check 4");
+ is(s.getAttribute("ggg"), "othervalue", "Value check 5");
+ }
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug343596.html b/dom/base/test/test_bug343596.html
new file mode 100644
index 0000000000..31a3e92820
--- /dev/null
+++ b/dom/base/test/test_bug343596.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=343596
+-->
+<head>
+ <title>Test for Bug 343596</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=343596">Mozilla Bug 343596</a>
+<p id="display"></p>
+<script id="foo"></script>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 343596 **/
+
+SimpleTest.waitForExplicitFinish();
+
+/** Cut error handling, because we're going to throw on purpose**/
+var errorHandler = window.onerror;
+window.onerror = null;
+
+
+try{
+ // Insert text into an empty script node that will cause a syntax error.
+ document.getElementById("foo").appendChild(document.createTextNode("("));
+}
+catch(ex){
+ // Note that this catch block does not execute.
+ ok(false, "this catch block should not execute");
+}
+
+setTimeout(function(){
+ok(true,"setTimeout still executes after bogus script insertion");
+window.error = errorHandler;
+SimpleTest.finish();}, 0);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug345339.html b/dom/base/test/test_bug345339.html
new file mode 100644
index 0000000000..12baea6b25
--- /dev/null
+++ b/dom/base/test/test_bug345339.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345339
+-->
+<head>
+ <title>Test for Bug 345339</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345339">Mozilla Bug 345339</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+ <iframe id="testframe"
+ src="http://mochi.test:8888/tests/dom/base/test/345339_iframe.html">
+ </iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 345339 **/
+SimpleTest.waitForExplicitFinish();
+
+const testData = "Test data\n";
+let file = new File([testData],
+ "345339_test.file",
+ { type: "text/plain" });
+
+function afterLoad() {
+ var iframeDoc = $("testframe").contentDocument;
+
+ /* change all the form controls */
+ iframeDoc.getElementById("select").selectedIndex = 1;
+ iframeDoc.getElementById("radio2").checked = true;
+ iframeDoc.getElementById("password").value = "123456";
+ iframeDoc.getElementById("hidden").value = "gecko";
+
+ // Toggle the one field to a password type then text type like password
+ // visibility toggles on the web do.
+ iframeDoc.getElementById("passwordToggle").type = "password";
+ iframeDoc.getElementById("passwordToggle").value = "abcdef";
+ iframeDoc.getElementById("passwordToggle").type = "";
+
+ SpecialPowers.wrap(iframeDoc).getElementById("file").mozSetFileArray([file]);
+
+ /* Reload the page */
+ $("testframe").setAttribute("onload", "afterReload()");
+ iframeDoc.location.reload();
+}
+
+addLoadEvent(afterLoad);
+
+function afterReload() {
+ var iframeDoc = $("testframe").contentDocument;
+
+ is(iframeDoc.getElementById("select").selectedIndex, 1,
+ "select element selected index preserved");
+ is(iframeDoc.getElementById("radio1").checked, false,
+ "radio button #1 value preserved");
+ is(iframeDoc.getElementById("radio2").checked, true,
+ "radio button #2 value preserved");
+ isnot(iframeDoc.getElementById("password").value, "123456",
+ "password field value forgotten");
+ is(iframeDoc.getElementById("hidden").value, "gecko",
+ "hidden field value preserved");
+ is(iframeDoc.getElementById("passwordToggle").value, "",
+ "former password field value not saved");
+
+ // The new file object isn't ===, but it's extensionally equal:
+ let newFile = iframeDoc.getElementById("file").files[0];
+ for (let prop of ["name", "lastModified", "size", "type"]) {
+ is(newFile[prop], file[prop],
+ "file field " + prop + " property preserved");
+ }
+ let reader = new FileReader();
+ reader.onloadend = function() {
+ SimpleTest.finish();
+ };
+ reader.onload = function() {
+ is(reader.result, testData,
+ "file field contents preserved")
+ };
+ reader.onerror = function() {
+ is(reader.error, null,
+ "FileReader error");
+ };
+ reader.readAsText(newFile);
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug346485.html b/dom/base/test/test_bug346485.html
new file mode 100644
index 0000000000..53f5587a5f
--- /dev/null
+++ b/dom/base/test/test_bug346485.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=346485
+-->
+<head>
+ <title>Test for Bug 346485</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=346485">Mozilla Bug 346485</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input id='a'>
+ <input id='b'>
+ <output id='o' for='a b'></output>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 346485 **/
+
+/**
+ * This test is testing DOMTokenList used by the output element.
+ */
+
+function checkHtmlFor(htmlFor, list, msg) {
+ var length = htmlFor.length;
+ is(length, list.length, htmlFor + ": incorrect htmlFor length (" + msg + ")");
+ for (var i = 0; i < length; ++i) {
+ is(htmlFor[i], list[i], htmlFor + ": wrong element at " + i + " (" + msg + ")");
+ }
+}
+
+var o = document.getElementById('o');
+
+is(String(o.htmlFor), 'a b',
+ "htmlFor IDL attribute should reflect for content attribute");
+
+is(o.htmlFor.value, 'a b',
+ "value should return the underlying string");
+
+is(o.htmlFor.length, 2, "Size should be '2'");
+
+ok(o.htmlFor.contains('a'), "It should contain 'a' token'");
+ok(!o.htmlFor.contains('c'), "It should not contain 'c' token");
+
+is(o.htmlFor.item(0), 'a', "First item is 'a' token'");
+is(o.htmlFor.item(42), null, "Out-of-range should return null");
+
+o.htmlFor.add('c');
+checkHtmlFor(o.htmlFor, ['a', 'b', 'c'], "'c' token should have been added");
+
+o.htmlFor.add('a');
+checkHtmlFor(o.htmlFor, ['a', 'b', 'c'], "Nothing should have changed");
+
+o.htmlFor.remove('a');
+checkHtmlFor(o.htmlFor, ['b', 'c'], "'a' token should have been removed");
+
+o.htmlFor.remove('d');
+checkHtmlFor(o.htmlFor, ['b', 'c'], "Nothing should have been removed");
+
+o.htmlFor.toggle('a');
+checkHtmlFor(o.htmlFor, ['b', 'c', 'a'], "'a' token should have been added");
+
+o.htmlFor.toggle('b');
+checkHtmlFor(o.htmlFor, ['c', 'a'], "Nothing should have changed");
+
+o.htmlFor.value = "foo bar";
+checkHtmlFor(o.htmlFor, ['foo', 'bar'], "The underlying string should have changed");
+ok(o.htmlFor.contains('foo'), "It should contain 'foo'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug352728.html b/dom/base/test/test_bug352728.html
new file mode 100644
index 0000000000..c5bc92da5c
--- /dev/null
+++ b/dom/base/test/test_bug352728.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=352728
+-->
+<head>
+ <title>Test for Bug 352728</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=352728">Mozilla Bug 352728</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 352728 **/
+
+function checkTypes(aNode, aNodeType, aTypeArray)
+{
+ for (var i = 0; i < aTypeArray.length; ++i) {
+ ok(aNode instanceof aTypeArray[i],
+ `${aNodeType} type test ${i}: ${aNodeType} should be a ${aTypeArray[i]}`);
+ }
+}
+
+function testCharacterData(aNode, aText)
+{
+ is(aNode.length, aText.length, "Text length should match");
+ is(aNode.data, aText, "Text content should match");
+ is(aNode.nodeValue, aText, "Check nodeValue");
+ is(aNode.localName, undefined, "Check localName")
+ is(aNode.namespaceURI, undefined, "Check namespaceURI");
+}
+
+function testComment(aText)
+{
+ try {
+ var comment = document.createComment(aText);
+ var types = [ Comment, CharacterData, Node ];
+ checkTypes(comment, "comment", types);
+
+ testCharacterData(comment, aText);
+ is(comment.nodeName, "#comment", "Check nodeName");
+ is(comment.nodeType, Node.COMMENT_NODE, "Check nodeType");
+ } catch (e) {
+ ok(0, "Correct functioning of comment stuff", "something broke: " + e);
+ }
+}
+
+function testCDATASection(aText, aShouldSucceed)
+{
+ try {
+ var cdataSection = document.createCDATASection(aText);
+ ok(0, "Invalid CDATA section creation",
+ "Shouldn't create CDATA sections in HTML");
+ } catch (e) {
+ is(e.name, "NotSupportedError", "Check exception");
+ is(e.code, DOMException.NOT_SUPPORTED_ERR, "Check exception code");
+ }
+}
+
+function testPI(aTarget, aData, aShouldSucceed, aReason)
+{
+ try {
+ var pi = document.createProcessingInstruction(aTarget, aData);
+ var types = [ ProcessingInstruction, Node ];
+ checkTypes(pi, "processing instruction", types);
+
+ is(pi.target, aTarget, "Check target");
+ is(pi.data, aData, "Check data");
+ is(pi.nodeName, aTarget, "Check nodeName");
+ is(pi.nodeValue, aData, "Check nodeValue");
+ is(pi.localName, undefined, "Check localName")
+ is(pi.namespaceURI, undefined, "Check namespaceURI");
+
+ is(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE, "Check nodeType");
+
+ if (!aShouldSucceed) {
+ ok(false, "Invalid processing instruction creation", aReason);
+ }
+ } catch (e) {
+ if (aShouldSucceed) {
+ ok(false, "Correct functioning of processing instruction stuff",
+ "something broke: " + e);
+ } else {
+ is(e.name, "InvalidCharacterError", "Check exception");
+ is(e.code, DOMException.INVALID_CHARACTER_ERR, "Check exception code");
+ }
+ }
+}
+
+testComment("Some text");
+testComment("Some text with a '-' in it");
+testComment("Some text with a '-' and a '-' and another '-'");
+testComment("Some text -- this should create a node!");
+testComment("<!-- This is an HTML comment -->");
+
+testCDATASection("Some text", true);
+testCDATASection("Some text with a '?' in it", true);
+testCDATASection("Some text with a '>' in it", true);
+testCDATASection("Some text with a '?' and a '>' in it", true);
+testCDATASection("Some text with a '? >' in it", true);
+testCDATASection("Some text -- ?> this should be ok", true);
+testCDATASection("Some text ]]&gt; this should not create a node!", false);
+
+testPI("foo", "bar", true);
+testPI("foo:bar", "baz", true);
+testPI("foo", "bar?", true);
+testPI("foo", "bar>", true);
+testPI("foo", "bar? >", true);
+testPI("<aaa", "bar", false, "Target should not contain '<'");
+testPI("aaa>", "bar", false, "Target should not contain '>'");
+testPI("aa?", "bar", false, "Target should not contain '?'");
+testPI("foo", "bar?>", false, "Data should not contain '?>'");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug352728.xhtml b/dom/base/test/test_bug352728.xhtml
new file mode 100644
index 0000000000..b868deb463
--- /dev/null
+++ b/dom/base/test/test_bug352728.xhtml
@@ -0,0 +1,188 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=352728
+-->
+<head>
+ <title>Test for Bug 352728</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=352728">Mozilla Bug 352728</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<!-- First make sure that a script consisting of multiple CDATA sections is
+ even supported -->
+<script class="testbody" type="text/javascript">
+ var cdataTest1 = false;
+ var cdataTest2 = false;
+ var cdataTest3 = false;
+</script>
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ cdataTest1 = true;
+]]>
+ cdataTest2 = true;
+<![CDATA[
+ cdataTest3 = true;
+]]>
+</script>
+
+<script class="testbody" type="text/javascript">
+ is(cdataTest1, true, "Check first CDATA section");
+ is(cdataTest2, true, "Check in between CDATA sections");
+ is(cdataTest3, true, "Check second CDATA section");
+</script>
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 352728 **/
+function checkTypes(aNode, aNodeType, aTypeArray)
+{
+ for (var i = 0; i < aTypeArray.length; ++i) {
+ ok(
+ aNode instanceof aTypeArray[i],
+ aNodeType + " type test " + i + " - " +
+ aNodeType + " should be a " + aTypeArray[i]
+ );
+ }
+}
+
+function checkInterfaces(aNode, aNodeType, aInterfaceArray)
+{
+ for (var i = 0; i < aInterfaceArray.length; ++i) {
+ ok(aNode instanceof SpecialPowers.Ci[aInterfaceArray[i]],
+ aNodeType + " interface test " + i + " - " +
+ aNodeType + " should be a " + aInterfaceArray[i]);
+ }
+}
+
+function testCharacterData(aNode, aText)
+{
+ is(aNode.length, aText.length, "Text length should match");
+ is(aNode.data, aText, "Text content should match");
+ is(aNode.nodeValue, aText, "Check nodeValue");
+ is(aNode.localName, undefined, "Check localName")
+ is(aNode.namespaceURI, undefined, "Check namespaceURI");
+}
+
+function testComment(aText)
+{
+ try {
+ var comment = document.createComment(aText);
+ var types = [ Comment, CharacterData, Node ];
+ checkTypes(comment, "comment", types);
+
+ var interfaces = [];
+ checkInterfaces(comment, "comment", interfaces);
+
+ testCharacterData(comment, aText);
+ is(comment.nodeName, "#comment", "Check nodeName");
+ is(comment.nodeType, Node.COMMENT_NODE, "Check nodeType");
+ } catch (e) {
+ ok(false, "Correct functioning of comment stuff - something broke: " + e);
+ }
+}
+
+function testCDATASection(aText, aShouldSucceed)
+{
+ try {
+ var cdataSection = document.createCDATASection(aText);
+ var types = [ CDATASection, CharacterData, Node ];
+ checkTypes(cdataSection, "CDATA section", types);
+
+ var interfaces = [];
+ checkInterfaces(cdataSection, "CDATA section", interfaces);
+
+ testCharacterData(cdataSection, aText);
+ is(cdataSection.nodeName, "#cdata-section", "Check nodeName");
+ is(cdataSection.nodeType, Node.CDATA_SECTION_NODE, "Check nodeType");
+
+ if (!aShouldSucceed) {
+ ok(0, "Invalid CDATA section creation - " +
+]]>
+ "Shouldn't create CDATA section with embedded \"]]&gt;\"");
+<![CDATA[
+ }
+ } catch (e) {
+ if (aShouldSucceed) {
+ ok(false,
+ "Correct functioning of CDATA section stuff - something broke: " + e);
+ } else {
+ is(e.name, "InvalidCharacterError", "Check exception");
+ is(e.code, DOMException.INVALID_CHARACTER_ERR, "Check exception code");
+ }
+ }
+}
+
+function testPI(aTarget, aData, aShouldSucceed, aReason)
+{
+ try {
+ var pi = document.createProcessingInstruction(aTarget, aData);
+ var types = [ ProcessingInstruction, Node ];
+ checkTypes(pi, "processing instruction", types);
+
+ var interfaces = [];
+ checkInterfaces(pi, "processing instruction", interfaces);
+
+ is(pi.target, aTarget, "Check target");
+ is(pi.data, aData, "Check data");
+ is(pi.nodeName, aTarget, "Check nodeName");
+ is(pi.nodeValue, aData, "Check nodeValue");
+ is(pi.localName, undefined, "Check localName")
+ is(pi.namespaceURI, undefined, "Check namespaceURI");
+
+ is(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE, "Check nodeType");
+
+ if (!aShouldSucceed) {
+ ok(false, "Invalid processing instruction creation - " + aReason);
+ }
+ } catch (e) {
+ if (aShouldSucceed) {
+ ok(false,
+ "Correct functioning of processing instruction stuff - " +
+ "something broke: " + e);
+ } else {
+ is(e.name, "InvalidCharacterError", "Check exception");
+ is(e.code, DOMException.INVALID_CHARACTER_ERR, "Check exception code");
+ }
+ }
+}
+
+testComment("Some text");
+testComment("Some text with a '-' in it");
+testComment("Some text with a '-' and a '-' and another '-'");
+testComment("Some text -- this should create a node!");
+testComment("<!-- This is an HTML comment -->");
+
+testCDATASection("Some text", true);
+testCDATASection("Some text with a '?' in it", true);
+testCDATASection("Some text with a '>' in it", true);
+testCDATASection("Some text with a '?' and a '>' in it", true);
+testCDATASection("Some text with a '? >' in it", true);
+testCDATASection("Some text -- ?> this should be ok", true);
+]]>
+testCDATASection("Some text ]]&gt; this should not create a node!", false);
+
+<![CDATA[
+
+testPI("foo", "bar", true);
+testPI("foo:bar", "baz", true);
+testPI("foo", "bar?", true);
+testPI("foo", "bar>", true);
+testPI("foo", "bar? >", true);
+testPI("<aaa", "bar", false, "Target should not contain '<'");
+testPI("aaa>", "bar", false, "Target should not contain '>'");
+testPI("aa?", "bar", false, "Target should not contain '?'");
+testPI("foo", "bar?>", false, "Data should not contain '?>'");
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug353334.html b/dom/base/test/test_bug353334.html
new file mode 100644
index 0000000000..4fa30828a2
--- /dev/null
+++ b/dom/base/test/test_bug353334.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=353334
+-->
+<head>
+ <title>Test for Bug 353334</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>var x = "PASS"</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=353334">Mozilla Bug 353334</a>
+<p id="display">
+<iframe id="one"></iframe>
+<object id="two" data="about:blank"></object>
+<iframe id="three" srcdoc="<body>test</body>"></iframe>
+<object id="four" data="object_bug353334.html"></object>
+<iframe id="five" src="javascript:parent.x"></iframe>
+<object id="six" data="javascript:x"></object>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 353334 **/
+SimpleTest.waitForExplicitFinish();
+
+function doPrincipalTest(id) {
+ var doc = SpecialPowers.wrap($(id).contentDocument);
+
+ isnot(doc.nodePrincipal, undefined, "Should have a principal");
+ isnot(doc.nodePrincipal, null, "Should have a non-null principal");
+ is(doc.nodePrincipal.origin, SpecialPowers.wrap(document).nodePrincipal.origin,
+ "Wrong principal for document in node with id='" + id + "'");
+}
+
+function doContentTest(id) {
+ is($(id).contentDocument.documentElement.textContent, "PASS",
+ "Script executed in wrong context in node with id='" + id + "'");
+}
+
+function checkPrincipal() {
+ ok(SpecialPowers.call_Instanceof(SpecialPowers.wrap(document).nodePrincipal, SpecialPowers.Ci.nsIPrincipal),
+ "Should be a principal");
+}
+
+addLoadEvent(function() {
+ checkPrincipal();
+
+ for (var i of [ "one", "two", "three", "four" ]) {
+ doPrincipalTest(i);
+ }
+
+ for (i of [ "five", "six" ]) {
+ doContentTest(i);
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug355026.html b/dom/base/test/test_bug355026.html
new file mode 100644
index 0000000000..e5ace96970
--- /dev/null
+++ b/dom/base/test/test_bug355026.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test from Adam Guthrie.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=355026
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=355026">Mozilla Bug 355026</a>
+<div style="display: none" id="lengthtest">
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+ var foo = document.createElement("div");
+ foo.appendChild(document.createElement("div"));
+ var children = foo.getElementsByTagName("div");
+
+ is(children.length, 1, "After appending a child div to a div, div.length should be 1.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug357450.html b/dom/base/test/test_bug357450.html
new file mode 100644
index 0000000000..006768fa89
--- /dev/null
+++ b/dom/base/test/test_bug357450.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357450
+-->
+
+<head>
+ <title>Test for Bug 357450</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_bug357450.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ </style>
+</head>
+
+<body>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=357450"
+ target="_blank" >Mozilla Bug 357450</a>
+
+<p id="display"></p>
+<span class="classtest">hmm</span>
+<span class="classtest">hmm</span>
+<div id="content" style="display: block">
+ <a name="nametest">hmm</a>
+ <b class="foo">hmm</b>
+ <b id="test1" class="test1">hmm</b>
+ <b id="test2" class="test2">hmm</b>
+ <b id="int-class" class="1">hmm</b>
+ <span class="classtest">hmm</span>
+ <div id="example">
+ <p id="p1" class="aaa bbb"/>
+ <p id="p2" class="aaa ccc"/>
+ <p id="p3" class="bbb ccc"/>
+ </div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug357450.xhtml b/dom/base/test/test_bug357450.xhtml
new file mode 100644
index 0000000000..dee66f1cd1
--- /dev/null
+++ b/dom/base/test/test_bug357450.xhtml
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357450
+-->
+
+<head>
+ <title>Test for Bug 357450</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_bug357450.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=357450"
+ target="_blank">Mozilla Bug 357450</a>
+
+<p id="display"></p>
+<span class="classtest">hmm</span>
+<span class="classtest">hmm</span>
+
+<div id="content" style="display: block">
+ <a name="nametest">hmm</a>
+ <b class="foo">hmm</b>
+ <b id="test1" class="test1">hmm</b>
+ <b id="test2" class="test2">hmm</b>
+ <b id="int-class" class="1">hmm</b>
+ <span class="classtest">hmm</span>
+
+ <div id="example">
+ <p id="p1" class="aaa bbb"/>
+ <p id="p2" class="aaa ccc"/>
+ <p id="p3" class="bbb ccc"/>
+ </div>
+
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug357450_svg.xhtml b/dom/base/test/test_bug357450_svg.xhtml
new file mode 100644
index 0000000000..a1d8a528a7
--- /dev/null
+++ b/dom/base/test/test_bug357450_svg.xhtml
@@ -0,0 +1,46 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357450
+-->
+
+<head>
+ <title>Test for Bug 357450</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_bug357450.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+
+<body>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=357450"
+ target="_blank">Mozilla Bug 357450</a>
+
+<p id="display"></p>
+<span class="classtest">hmm</span>
+<span class="classtest">hmm</span>
+<div id="content" style="display: block">
+ <a name="nametest">hmm</a>
+ <b class="foo">hmm</b>
+ <b id="test1" class="test1">hmm</b>
+ <b id="test2" class="test2">hmm</b>
+ <b id="int-class" class="1">hmm</b>
+ <svg xmlns="http://www.w3.org/2000/svg"
+ height="100" width="100" style="float:left">
+ <path d="M38,38c0-12,24-15,23-2c0,9-16,13-16,23v7h11v-4c0-9,17-12,17-27c-2-22-45-22-45,3zM45,70h11v11h-11z" fill="#371"/>
+
+ <circle cx="50" cy="50" r="45" class="classtest"
+ fill="none" stroke="#371" stroke-width="10"/>
+ </svg>
+
+ <div id="example">
+ <p id="p1" class="aaa bbb"/>
+ <p id="p2" class="aaa ccc"/>
+ <p id="p3" class="bbb ccc"/>
+ </div>
+
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug357509.html b/dom/base/test/test_bug357509.html
new file mode 100644
index 0000000000..d8b288c2fd
--- /dev/null
+++ b/dom/base/test/test_bug357509.html
@@ -0,0 +1,36 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357509
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+ function foo(elem) {
+ rng = document.createRange();
+ rng.setStartBefore(elem);
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=357509">Mozilla Bug 357509</a>
+<div style="display: none" id="rangetest">
+</div>
+<pre id="test">
+<script type="text/javascript">
+var passed = false;
+try {
+ foo(document.getElementById("rangetest"))
+ passed = true;
+}
+catch (e) {
+ passed = false;
+}
+
+<!-- The ok() function tests the first arg -->
+ok(passed, "Range.setStartBefore");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug358660.html b/dom/base/test/test_bug358660.html
new file mode 100644
index 0000000000..4788b1824b
--- /dev/null
+++ b/dom/base/test/test_bug358660.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=358660
+-->
+<head>
+ <title>Test for Bug 358660</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=358660">Mozilla Bug 358660</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe"
+ srcdoc="<html><body>Some text</body></html>"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 358660 **/
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ var doc = $("testframe").contentDocument;
+ var range = doc.createRange();
+ range.selectNode(doc.documentElement);
+ is(range.toString(), "Some text", "Check text");
+
+ SimpleTest.finish()
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug362391.xhtml b/dom/base/test/test_bug362391.xhtml
new file mode 100644
index 0000000000..a00b8e52a8
--- /dev/null
+++ b/dom/base/test/test_bug362391.xhtml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:foobar="http://www.foobar.com">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=362391
+-->
+<head>
+ <title>Test for Bug 362391</title>
+ <!-- XHTML needs explicit script elements -->
+ <script type="text/javascript" src="/MochiKit/Base.js"></script>
+ <script type="text/javascript" src="/MochiKit/Iter.js"></script>
+ <script type="text/javascript" src="/MochiKit/DOM.js"></script>
+ <script type="text/javascript" src="/MochiKit/Style.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=362391">Mozilla Bug 362391</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test1"/>
+<div id="test2"/>
+<div id="test3" attr="null"/>
+<div id="test4" foobar:attr="http://www.foobar.com"/>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 362391 **/
+
+ var currentTest = 0;
+ var expected = "";
+ function listener(evt) {
+ var r = document.getElementById("result");
+ ++currentTest;
+ ok(((evt.relatedNode.namespaceURI + "") == expected),
+ evt.relatedNode.namespaceURI + " == "+ expected);
+ }
+
+ document.addEventListener("DOMAttrModified", listener, true);
+
+ function test() {
+ expected = "null";
+ document.getElementById("test1")
+ .setAttribute("attr", "null");
+
+ expected = "http://www.foobar.com";
+ document.getElementById("test2")
+ .setAttributeNS("http://www.foobar.com", "attr", "http://www.foobar.com");
+
+ expected = "http://www.foobar.com";
+ document.getElementById("test3")
+ .setAttributeNS("http://www.foobar.com", "attr", "http://www.foobar.com");
+
+ expected = "null";
+ document.getElementById("test4")
+ .setAttribute("attr", "null");
+
+ expected = "http://www.foobar.com";
+ document.getElementById("test3")
+ .removeAttributeNS("http://www.foobar.com", "attr");
+
+ expected = "null";
+ document.getElementById("test4")
+ .removeAttribute("attr");
+ }
+
+ test();
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug364092.xhtml b/dom/base/test/test_bug364092.xhtml
new file mode 100644
index 0000000000..93ba2554ec
--- /dev/null
+++ b/dom/base/test/test_bug364092.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364092
+-->
+<head>
+ <title>Test for Bug 364092</title>
+ <!-- XHTML needs explicit script elements -->
+ <script type="text/javascript" src="/MochiKit/Base.js"></script>
+ <script type="text/javascript" src="/MochiKit/Iter.js"></script>
+ <script type="text/javascript" src="/MochiKit/DOM.js"></script>
+ <script type="text/javascript" src="/MochiKit/Style.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364092">Mozilla Bug 364092</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test1" foo="foo"/>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 364092 **/
+
+ var test1 = document.getElementById("test1");
+ var attrNode = test1.getAttributeNode("foo");
+ function mutationHandler(aEvent) {
+ ok(attrNode == aEvent.relatedNode);
+ ok(!test1.hasAttribute("foo"));
+ }
+
+ function runTest() {
+ test1.addEventListener("DOMAttrModified", mutationHandler, true);
+ test1.removeAttributeNode(attrNode);
+ test1.removeEventListener("DOMAttrModified", mutationHandler, true);
+ }
+
+ runTest();
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug364413.xhtml b/dom/base/test/test_bug364413.xhtml
new file mode 100644
index 0000000000..4e4b20ea8c
--- /dev/null
+++ b/dom/base/test/test_bug364413.xhtml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:foobar="http://www.foobar.com">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364413
+-->
+<head>
+ <title>Test for Bug 364413</title>
+ <!-- XHTML needs explicit script elements -->
+ <script type="text/javascript" src="/MochiKit/Base.js"></script>
+ <script type="text/javascript" src="/MochiKit/Iter.js"></script>
+ <script type="text/javascript" src="/MochiKit/DOM.js"></script>
+ <script type="text/javascript" src="/MochiKit/Style.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364413">Mozilla Bug 364413</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test1" foobar:foo="foo"/>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 364413 **/
+
+ var test1 = document.getElementById("test1");
+ var attrNode = test1.getAttributeNodeNS("http://www.foobar.com", "foo");
+ function mutationHandler(aEvent) {
+ ok(attrNode == aEvent.relatedNode);
+ ok(aEvent.target == attrNode.ownerElement);
+ }
+
+ function runTest() {
+ test1.removeAttributeNode(attrNode);
+ test1.addEventListener("DOMAttrModified", mutationHandler, true);
+ test1.setAttributeNodeNS(attrNode);
+ test1.removeEventListener("DOMAttrModified", mutationHandler, true);
+ }
+
+ runTest();
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug366944.html b/dom/base/test/test_bug366944.html
new file mode 100644
index 0000000000..8e4acd9e3f
--- /dev/null
+++ b/dom/base/test/test_bug366944.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=366944
+-->
+<title>Test for Bug 366944</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366944">Mozilla Bug 366944</a>
+<script>
+
+/** Test for Bug 366944 **/
+var testNodes = [document, document.doctype, document.createDocumentFragment()];
+for (var i = 0; i < testNodes.length; i++) {
+ var range = document.createRange();
+ // If a non-Text node is partially contained, we expect to throw for that
+ // first
+ range.setStart(document.head, 0);
+ range.setEnd(document.body, 0);
+ var threw = false;
+ var desc = " (surrounding a range with partially-contained Element "
+ + "with " + (i == 0 ? "document" : i == 1 ? "doctype" : "docfrag") + ")";
+ try {
+ range.surroundContents(testNodes[i]);
+ } catch(e) {
+ threw = true;
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "Must throw DOMException" + desc);
+ is(e.name, "InvalidStateError", "Must throw InvalidStateError" + desc);
+ }
+ ok(threw, "Must throw" + desc);
+
+ range.setStart(document.body, 0);
+ range.setEnd(document.body, 1);
+ threw = false;
+ desc = " (surrounding a regular range "
+ + "with " + (i == 0 ? "document" : i == 1 ? "doctype" : "docfrag") + ")";
+ try {
+ range.surroundContents(testNodes[i]);
+ } catch(e) {
+ threw = true;
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "Must throw DOMException" + desc);
+ is(e.name, "InvalidNodeTypeError",
+ "Must throw InvalidNodeTypeError" + desc);
+ }
+ ok(threw, "Must throw" + desc);
+}
+
+</script>
diff --git a/dom/base/test/test_bug366946.html b/dom/base/test/test_bug366946.html
new file mode 100644
index 0000000000..a8173828dc
--- /dev/null
+++ b/dom/base/test/test_bug366946.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=366946
+-->
+<head>
+ <title>Test for Bug 366946</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366946">Mozilla Bug 366946</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="1"></div>
+ <div id="2"></div>
+ <div id="3"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ /** Test for Bug 366946 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var doc1 = document;
+
+ // Set up a new document.
+ var doc2 = document.implementation.createDocument('', '', null);
+
+ // Copy some nodes into doc2
+ var node1 = doc2.importNode(doc1.getElementById('1'), false);
+ var node2 = doc2.importNode(doc1.getElementById('1'), false);
+ node1.appendChild(node2);
+ doc2.appendChild(node1);
+
+ // Create two ranges in doc1 to compare.
+ var range1 = doc1.createRange();
+ range1.setStart(doc1.getElementById('1'), 0);
+ range1.setEnd(doc1.getElementById('2'), 0);
+
+ var range2 = doc1.createRange();
+ range2.setStart(doc1.getElementById('2'), 0);
+ range2.setEnd(doc1.getElementById('3'), 0);
+
+ // Create a range in doc2.
+ var range3 = doc2.createRange();
+ range3.setStart(node1, 0);
+ range3.setEnd(node2, 0);
+
+ // Compare range1 and range2: Should return 1.
+ try {
+ var result1 = range2.compareBoundaryPoints(Range.START_TO_START, range1);
+ }
+ catch (ex) {
+ }
+ ok(result1 === 1, "range1 and range2 are compared correctly.");
+
+ // Compare range1 and range3: Should throw DOMException WRONG_DOCUMENT_ERR.
+ try {
+ var result2 = range3.compareBoundaryPoints(Range.START_TO_START, range1);
+ }
+ catch (ex) {
+ var error = ex.name;
+ var errorCode = ex.code;
+ }
+
+ ok(error == "WrongDocumentError",
+ "The WrongDocumentError exception thrown when comparing ranges from " +
+ "different documents ");
+ ok(errorCode == DOMException.WRONG_DOCUMENT_ERR,
+ "The exception thrown when comparing ranges from different documents " +
+ "has the code DOMException.WRONG_DOCUMENT_ERR");
+ ok(result2 === undefined, "range1 and range3 couldn't be compared as expected.");
+ SimpleTest.finish();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug367164.html b/dom/base/test/test_bug367164.html
new file mode 100644
index 0000000000..46dc4f779f
--- /dev/null
+++ b/dom/base/test/test_bug367164.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=367164
+-->
+<head>
+ <title>Test for Bug 367164</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=367164">Mozilla Bug 367164</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 367164 **/
+
+var span = document.createElement("span");
+
+var ins1 = false;
+var ins2 = false;
+var rem1 = false;
+var rem2 = false;
+
+span.addEventListener("DOMNodeInserted", function() { ins1 = true; }, true);
+span.addEventListener("DOMNodeInserted", function() { ins2 = true; });
+span.addEventListener("DOMNodeRemoved", function() { rem1 = true; }, true);
+span.addEventListener("DOMNodeRemoved", function() { rem2 = true; });
+
+$("content").appendChild(span);
+$("content").removeChild(span);
+
+is(ins1, true, "Capturing DOMNodeInserted listener");
+is(ins2, true, "Bubbling DOMNodeInserted listener");
+is(rem1, true, "Capturing DOMNodeRemoved listener");
+is(rem2, true, "Bubbling DOMNodeRemoved listener");
+
+</script>
+
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug368972.html b/dom/base/test/test_bug368972.html
new file mode 100644
index 0000000000..08a348b3cd
--- /dev/null
+++ b/dom/base/test/test_bug368972.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=368972
+-->
+<head>
+ <title>Test for Bug 368972</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style type="text/css">
+#embed11, #object11 {
+ width: 400px;
+ height: 400px;
+}
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=368972">Mozilla Bug 368972</a>
+<p id="display"></p>
+<div id="content">
+Embed without defined width/height:
+<embed id="embed1" type="bogus"><br>
+Embed width=0 height=0
+<embed id="embed2" type="bogus" width="0" height="0"><br>
+Embed width=100 height=100
+<embed id="embed3" type="bogus" width="100" height="100"><br>
+Embed height=100
+<embed id="embed4" type="bogus" height="100"><br>
+Embed width=100
+<embed id="embed5" type="bogus" width="100"><br>
+Embed width=100xxx height=100
+<embed id="embed6" type="bogus" width="100xxx" height="100"><br>
+Embed width=0100 height=100
+<embed id="embed7" type="bogus" width="0100" height="100"><br>
+Embed width= height=100
+<embed id="embed8" type="bogus" width="" height="100"><br>
+Embed width=100 height=100 style="width:400px"
+<embed id="embed9" type="bogus" width="100" height="100" style="width:400px;"><br>
+Embed height=100 style="width:400px"
+<embed id="embed10" type="bogus" height="100" style="width:400px;"><br>
+Embed height=100 (stylesheet width:400px height:400px)
+<embed id="embed11" type="bogus" height="100"><br>
+
+Object without defined width/height:
+<object id="object1" type="bogus">
+</object><br>
+Object width=0 height=0
+<object id="object2" type="bogus" width="0" height="0">
+</object><br>
+Object width=100 height=100
+<object id="object3" type="bogus" width="100" height="100">
+</object><br>
+Object height=100
+<object id="object4" type="bogus" height="100">
+</object><br>
+Object width=100
+<object id="object5" type="bogus" width="100">
+</object><br>
+Object width=100xxx height=100
+<object id="object6" type="bogus" width="100xxx" height="100">
+</object><br>
+Object width=0100 height=100
+<object id="object7" type="bogus" width="0100" height="100">
+</object><br>
+Object width= height=100
+<object id="object8" type="bogus" width="" height="100">
+</object><br>
+Object width=100 height=100 style="width:400px"
+<object id="object9" type="bogus" width="100" height="100" style="width:400px;">
+</object><br>
+Object height=100 style="width:400px"
+<object id="object10" type="bogus" height="100" style="width:400px;">
+</object><br>
+Object height=100 (stylesheet width:400px height:400px)
+<object id="object11" type="bogus" height="100">
+</object><br>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function check_size(id, width, height) {
+ var element = document.getElementById(id);
+ ok(element, "Plugin element " + id + " did not exist");
+ if (width != "auto") {
+ width = width + "px";
+ }
+ if (height != "auto") {
+ height = height + "px";
+ }
+ var style = window.getComputedStyle(element);
+ is(style.width, width, "Plugin element " + id + " had an incorrect width");
+ is(style.height, height, "Plugin element " + id + " had an incorrect height");
+}
+
+check_size("embed1", "auto", "auto");
+check_size("embed2", 0, 0);
+check_size("embed3", 100, 100);
+check_size("embed4", "auto", 100);
+check_size("embed5", 100, "auto");
+check_size("embed6", 100, 100);
+check_size("embed7", 100, 100);
+check_size("embed8", "auto", 100);
+check_size("embed9", 400, 100);
+check_size("embed10", 400, 100);
+check_size("embed11", 400, 400);
+
+check_size("object1", "auto", "auto");
+check_size("object2", 0, 0);
+check_size("object3", 100, 100);
+check_size("object4", "auto", 100);
+check_size("object5", 100, "auto");
+check_size("object6", 100, 100);
+check_size("object7", 100, 100);
+check_size("object8", "auto", 100);
+check_size("object9", 400, 100);
+check_size("object10", 400, 100);
+check_size("object11", 400, 400);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug371576-2.html b/dom/base/test/test_bug371576-2.html
new file mode 100644
index 0000000000..2a09118645
--- /dev/null
+++ b/dom/base/test/test_bug371576-2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371576
+-->
+<head id="head">
+ <title>Test for Bug 371576</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=371576">Mozilla Bug 371576</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var result = "A";
+ var scrElem= document.createElement('script');
+ scrElem.textContent = 'result+="B";$("content").innerHTML="--";result+="C";';
+ $("head").appendChild(scrElem);
+ result += "D";
+
+</script>
+<script>result += "E"</script>
+<script>result += "F"</script>
+<script>is(result, "ABCDEF", "Wrong order of execution");</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug371576-3.html b/dom/base/test/test_bug371576-3.html
new file mode 100644
index 0000000000..ec91f7361c
--- /dev/null
+++ b/dom/base/test/test_bug371576-3.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371576
+-->
+<head id="head">
+ <title>Test for Bug 371576</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=371576">Mozilla Bug 371576</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script src='data:text/javascript,
+ var result = "A";
+ var scrElem= document.createElement("script");
+ scrElem.textContent = "result+=\"B\";$(\"content\").innerHTML=\"--\";result+=\"C\";";
+ $("head").appendChild(scrElem);
+ result += "D";'></script>
+<script>result += "E"</script>
+<script>result += "F"</script>
+<script>is(result, "ABCDEF", "Wrong order of execution");</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug371576-4.html b/dom/base/test/test_bug371576-4.html
new file mode 100644
index 0000000000..8fbd81fe51
--- /dev/null
+++ b/dom/base/test/test_bug371576-4.html
@@ -0,0 +1,21 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371576
+-->
+<head>
+ <title>Test2 for Bug 371576</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script>
+
+var _bShouldbeThere = false;
+var oScript = document.createElement('script');
+oScript.innerHTML = '_bShouldbeThere = true';
+document.getElementsByTagName('head')[0].appendChild(oScript);
+ok(_bShouldbeThere,"_bShouldbeThere is true, script has executed on time");
+
+</script>
+
+</head>
+</html>
diff --git a/dom/base/test/test_bug371576-5.html b/dom/base/test/test_bug371576-5.html
new file mode 100644
index 0000000000..842fa9e3c3
--- /dev/null
+++ b/dom/base/test/test_bug371576-5.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371576
+-->
+<head id="head">
+ <title>Test for Bug 371576</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(
+ function doe(){
+ var x=document.createElement('script');
+ x.src='data:text/html,var scr=document.createElement("script");\
+ scr.innerHTML = "function doe3(){$(\'display\').innerHTML = \'You should see this text\';}";\
+ $("head").appendChild(scr);';
+ x.onload= function (){
+ doe3();
+ ok(true,"function doe3 is defined, and the body content has been replaced.");
+ is($("display").textContent, "You should see this text", "text set properly");
+ SimpleTest.finish();
+ };
+ $('head').appendChild(x);
+ }
+);
+</script>
+
+</head>
+<body>
+<p id="display">You shouldn't see this</p>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=371576">Mozilla Bug 371576</a>
+</body>
+</html>
diff --git a/dom/base/test/test_bug372086.html b/dom/base/test/test_bug372086.html
new file mode 100644
index 0000000000..e6ef6ee176
--- /dev/null
+++ b/dom/base/test/test_bug372086.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372086
+-->
+<head>
+ <title>Test for Bug 372086</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372086">Mozilla Bug 372086</a>
+<p id="display">
+ <div id="d" is="custom-div">abc</div>def
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372086 **/
+
+customElements.define("custom-div", class extends HTMLDivElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.innerHTML = `ghi<slot></slot>jkl`;
+ }
+}, {
+ extends: "div",
+});
+
+function runTest() {
+ let range = document.createRange();
+
+ var c = $("d").firstChild;
+ var a = $("d").nextSibling;
+
+ range.setStart(c, 1);
+ range.setEnd(c, 3);
+ is(range.startContainer, c, "Unexpected start container");
+ is(range.startOffset, 1, "Unexpected start offset");
+ is(range.endContainer, c, "Unexpected end container");
+ is(range.endOffset, 3, "Unexpected end offset");
+ is(range.toString(), "bc", "Unexpected range serialization");
+
+ let shadow = d.shadowRoot.firstChild;
+ // Should collapse the range, because can't determine order
+ range.setEnd(shadow, 2);
+ is(range.startContainer, shadow,
+ "Unexpected collapsed start container");
+ is(range.startOffset, 2, "Unexpected collapsed start offset");
+ is(range.endContainer, shadow,
+ "Unexpected collapsed end container");
+ is(range.endOffset, 2, "Unexpected collapsed end offset");
+ is(range.toString(), "", "Unexpected collapsed range serialization");
+
+ range.setEnd(a, 2);
+ range.setStart(a, 0);
+ is(range.startContainer, a, "Unexpected start container after");
+ is(range.startOffset, 0, "Unexpected start offset after");
+ is(range.endContainer, a, "Unexpected end container after");
+ is(range.endOffset, 2, "Unexpected end offset after");
+ is(range.toString(), "de", "Unexpected range serialization after");
+
+ shadow = d.shadowRoot.lastChild;
+ // Collapses because one endpoint is anonymous from point of view of
+ // the other.
+ range.setStart(shadow, 1);
+ is(range.startContainer, shadow,
+ "Unexpected newly collapsed start container");
+ is(range.startOffset, 1, "Unexpected newly collapsed start offset");
+ is(range.endContainer, shadow,
+ "Unexpected newly collapsed end container");
+ is(range.endOffset, 1, "Unexpected newly collapsed end offset");
+ is(range.toString(), "", "Unexpected collapsed range serialization");
+
+ range.setEnd(shadow, 3);
+ is(range.startContainer, shadow,
+ "Unexpected shadow start container");
+ is(range.startOffset, 1, "Unexpected shadow start offset");
+ is(range.endContainer, shadow,
+ "Unexpected shadow end container");
+ is(range.endOffset, 3, "Unexpected shadow end offset");
+ is(range.toString(), "kl", "Unexpected shadow range serialization");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+addLoadEvent(SimpleTest.finish)
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug372964-2.html b/dom/base/test/test_bug372964-2.html
new file mode 100644
index 0000000000..f106965d6f
--- /dev/null
+++ b/dom/base/test/test_bug372964-2.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372964
+-->
+<head>
+ <title>Test for Bug 372964</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372964">Mozilla Bug 372964</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372964 **/
+
+var eventCount = 0;
+
+function runTest() {
+ var ifr = document.getElementsByTagName("iframe")[0];
+ var xhr = new ifr.contentWindow.XMLHttpRequest();
+ xhr.addEventListener("foo", ifr.contentWindow.listener);
+ var event = ifr.contentDocument.createEvent("Events");
+ event.initEvent("foo", true, true);
+ xhr.dispatchEvent(event);
+ is(eventCount, 1, "Should have handled an event");
+ ifr.contentDocument.open();
+ ifr.contentDocument.close();
+ event = ifr.contentDocument.createEvent("Events");
+ event.initEvent("foo", true, true);
+ xhr.dispatchEvent(event);
+ is(eventCount, 2,
+ "Should have handled the event because open()/close() keep the active document");
+ ifr.onload = function() {
+ event = ifr.contentDocument.createEvent("Events");
+ event.initEvent("foo", true, true);
+ xhr.dispatchEvent(event);
+ is(eventCount, 2,
+ "Shouldn't have handled an event because the context has changed");
+ SimpleTest.finish();
+ };
+ ifr.contentWindow.location = "about:blank";
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+<iframe srcdoc="<script>function listener() { ++parent.eventCount; } </script>"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug372964.html b/dom/base/test/test_bug372964.html
new file mode 100644
index 0000000000..c8f53850bc
--- /dev/null
+++ b/dom/base/test/test_bug372964.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372964
+-->
+<head>
+ <title>Test for Bug 372964</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372964">Mozilla Bug 372964</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372964 **/
+
+var expectedEventType = "";
+var shouldBeTrusted = false;
+var eventHandlerCallCount = 0;
+
+function eventHandler(evt) {
+ ++eventHandlerCallCount;
+ is(evt.type, expectedEventType, "Wrong event type");
+ is(evt.isTrusted, shouldBeTrusted, "Wrong .isTrusted");
+}
+
+function test(trusted, type, removeAddedListener, removeSetListener, allowUntrusted) {
+ if (trusted) {
+ var x1 = SpecialPowers.wrap(new XMLHttpRequest());
+ } else {
+ x1 = new XMLHttpRequest();
+ }
+
+ var handlerCount = 0;
+ if (trusted || allowUntrusted || allowUntrusted == undefined) {
+ ++handlerCount;
+ }
+
+ if (allowUntrusted == undefined) {
+ // Test .addEventListener with 3 parameters.
+ x1.addEventListener(type, eventHandler);
+ } else {
+ // Test .addEventListener with 4 parameters.
+ x1.addEventListener(type, eventHandler, false, allowUntrusted);
+ }
+
+ if (("on" + type) in x1) {
+ ++handlerCount;
+ x1["on" + type] = eventHandler;
+ }
+
+ if (removeAddedListener) {
+ x1.removeEventListener(type, eventHandler);
+ if (trusted || allowUntrusted || allowUntrusted == undefined) {
+ --handlerCount;
+ }
+ }
+
+ if (removeSetListener) {
+ if (("on" + type) in x1) {
+ --handlerCount;
+ x1["on" + type] = null;
+ }
+ }
+
+ var e1 = document.createEvent("Events");
+ e1.initEvent(type, true, true);
+ expectedEventType = type;
+ shouldBeTrusted = trusted;
+ var ecc = eventHandlerCallCount;
+ x1.dispatchEvent(e1);
+ is(eventHandlerCallCount, ecc + handlerCount,
+ "Wrong number event handler calls. (1)");
+
+ e1 = document.createEvent("Events");
+ e1.initEvent(type, true, true);
+ expectedEventType = type;
+ // Set trusted since open() may cause events to be sent.
+ shouldBeTrusted = true;
+ x1.open("GET", window.location);
+ x1.abort(); // This should not remove event listeners.
+ ecc = eventHandlerCallCount;
+ shouldBeTrusted = trusted;
+ x1.dispatchEvent(e1);
+ is(eventHandlerCallCount, ecc + handlerCount,
+ "Wrong number event handler calls. (2)");
+
+ e1 = document.createEvent("Events");
+ e1.initEvent(type, true, true);
+ expectedEventType = type;
+ // Set trusted since open()/send() may cause events to be sent.
+ shouldBeTrusted = true;
+ x1.open("GET", window.location);
+ x1.send("");
+ x1.abort(); // This should not remove event listeners!
+ ecc = eventHandlerCallCount;
+ shouldBeTrusted = trusted;
+ x1.dispatchEvent(e1);
+ is(eventHandlerCallCount, ecc + handlerCount,
+ "Wrong number event handler calls. (3)");
+}
+
+var events =
+ ["load", "error", "progress", "readystatechange", "foo"];
+
+do {
+ var e = events.shift();
+ test(false, e, false, false);
+ test(false, e, false, true);
+ test(false, e, true, false);
+ test(false, e, true, true);
+ test(true, e, false, false);
+ test(true, e, false, true);
+ test(true, e, true, false);
+ test(true, e, true, true);
+
+ test(false, e, false, false, false);
+ test(false, e, false, false, true);
+ test(false, e, false, true, false);
+ test(false, e, false, true, true);
+ test(false, e, true, false, false);
+ test(false, e, true, false, true);
+ test(false, e, true, true, false);
+ test(false, e, true, true, true);
+ test(true, e, false, false, false);
+ test(true, e, false, false, true);
+ test(true, e, false, true, false);
+ test(true, e, false, true, true);
+ test(true, e, true, false, false);
+ test(true, e, true, false, true);
+ test(true, e, true, true, false);
+ test(true, e, true, true, true);
+} while(events.length);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug373181.xhtml b/dom/base/test/test_bug373181.xhtml
new file mode 100644
index 0000000000..ed046e96c3
--- /dev/null
+++ b/dom/base/test/test_bug373181.xhtml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Testcase for bug 373181</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/x-javascript">
+ function onLoadFired()
+ {
+ ok(true, "Body onload event should fire");
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </head>
+ <body onload="onLoadFired();"/>
+</html>
diff --git a/dom/base/test/test_bug375314-2.html b/dom/base/test/test_bug375314-2.html
new file mode 100644
index 0000000000..89fa2aad67
--- /dev/null
+++ b/dom/base/test/test_bug375314-2.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375314
+-->
+<head>
+ <title>Test for Bug 375314</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375314">Mozilla Bug 375314</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 375314 **/
+
+var lastContentType = -1;
+const testURL = window.location.href + "/this/is/the/test/url";
+
+function createChromeScript() {
+ /* eslint-env mozilla/chrome-script */
+ var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(
+ Ci.nsICategoryManager
+ );
+
+ const POLICYNAME = "@mozilla.org/testpolicy;1";
+ const POLICYID = Components.ID("{6cc95ef3-40e1-4d59-87f0-86f100373227}");
+
+ var policy = {
+ // nsISupports implementation
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIFactory",
+ "nsIContentPolicy",
+ ]),
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ if (contentLocation.asciiSpec === "http://mochi.test:8888/tests/dom/base/test/test_bug375314-2.html/this/is/the/test/url") {
+ sendAsyncMessage("loadBlocked", { policyType: loadInfo.externalContentPolicyType});
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ }
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+ };
+
+ // Register content policy
+ var componentManager = Components.manager.QueryInterface(
+ Ci.nsIComponentRegistrar
+ );
+
+ componentManager.registerFactory(
+ POLICYID,
+ "Test content policy",
+ POLICYNAME,
+ policy
+ );
+ categoryManager.addCategoryEntry(
+ "content-policy",
+ POLICYNAME,
+ POLICYNAME,
+ false,
+ true
+ );
+
+ addMessageListener("shutdown", _ => {
+ categoryManager.deleteCategoryEntry(
+ "content-policy",
+ POLICYNAME,
+ false
+ );
+ componentManager.unregisterFactory(POLICYID, policy);
+ });
+
+ // Adding a new category dispatches an event to update
+ // caches, so we need to also dispatch an event to make
+ // sure we don't start the load until after that happens.
+ Services.tm.dispatchToMainThread(() => {
+ sendAsyncMessage("setupComplete");
+ });
+}
+
+// Request creating functions
+
+function requestDocument() {
+ // GeckoView shows an error page for CSP errors, which breaks this test, so just skip in that case.
+ try {
+ if (!SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService(SpecialPowers.Ci.nsIAndroidBridge).isFennec) {
+ return false;
+ }
+ } catch (e){}
+
+ top.location.href = testURL;
+ return true;
+}
+
+function requestSubdocument() {
+ var content = $("content");
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", testURL);
+ content.appendChild(frame);
+}
+
+function requestObject() {
+ var content = $("content");
+
+ var object = document.createElement("embed");
+ object.setAttribute("src", testURL);
+ content.appendChild(object);
+}
+
+add_task(async function() {
+ let chromeScript = SpecialPowers.loadChromeScript(createChromeScript);
+ await chromeScript.promiseOneMessage("setupComplete");
+
+ if (requestDocument()) {
+ let result = await chromeScript.promiseOneMessage("loadBlocked");
+ is(result.policyType, SpecialPowers.Ci.nsIContentPolicy.TYPE_DOCUMENT, "Content policies triggered for TYPE_DOCUMENT");
+ }
+
+ requestSubdocument();
+ result = await chromeScript.promiseOneMessage("loadBlocked");
+ is(result.policyType, SpecialPowers.Ci.nsIContentPolicy.TYPE_SUBDOCUMENT, "Content policies triggered for TYPE_SUBDOCUMENT");
+
+ requestObject();
+ result = await chromeScript.promiseOneMessage("loadBlocked");
+ is(result.policyType, SpecialPowers.Ci.nsIContentPolicy.TYPE_OBJECT, "Content policies triggered for TYPE_OBJECT");
+
+ chromeScript.sendAsyncMessage("shutdown");
+ chromeScript.destroy();
+});
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug375314.html b/dom/base/test/test_bug375314.html
new file mode 100644
index 0000000000..c7c69a8576
--- /dev/null
+++ b/dom/base/test/test_bug375314.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375314
+-->
+<head>
+ <title>Test for Bug 375314</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375314">Mozilla Bug 375314</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 375314 **/
+
+var lastContentType = -1;
+const testURL = window.location.href + "/this/is/the/test/url";
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+var { AppConstants } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+// Content policy / factory implementation for the test
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
+var policyName = "@mozilla.org/testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ QueryInterface(iid) {
+
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ let contentType = loadInfo.externalContentPolicyType;
+ // Remember last content type seen for the test url
+ if (SpecialPowers.wrap(contentLocation).spec == testURL) {
+ lastContentType = contentType;
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ }
+
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+// Register content policy
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+
+componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
+
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
+
+// Try creating different request types
+var tests = ["SCRIPT", "IMAGE", "STYLESHEET", "XMLHTTPREQUEST"];
+var curTest = -1;
+var div;
+
+SimpleTest.waitForExplicitFinish();
+setTimeout(runNextTest, 0);
+
+function runNextTest() {
+
+ if (curTest >= 0) {
+ var type = "TYPE_" + tests[curTest];
+ is(lastContentType, Ci.nsIContentPolicy[type], "Content policies triggered for " + type);
+ }
+
+ curTest++;
+ if (curTest < tests.length) {
+ var method = "request_" + tests[curTest].toLowerCase();
+ try {
+ window[method]();
+ } catch(e) {}
+ setTimeout(runNextTest, 0);
+ }
+ else {
+ // Unregister content policy
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+
+ setTimeout(function() {
+ // Component must be unregistered delayed, otherwise other content
+ // policy will not be removed from the category correctly
+ componentManager.unregisterFactory(policyID, policy);
+ }, 0);
+
+ SimpleTest.finish();
+ }
+}
+
+// Request creating functions
+
+function request_script() {
+ var content = $("content");
+
+ var script = document.createElement("script");
+ script.setAttribute("type", "text/javascript")
+ script.setAttribute("src", testURL)
+ content.appendChild(script);
+}
+
+function request_image() {
+ var content = $("content");
+
+ var image = new Image();
+ image.src = testURL;
+}
+
+function request_stylesheet() {
+ var content = $("content");
+
+ var stylesheet = document.createElement("link");
+ stylesheet.setAttribute("rel", "stylesheet");
+ stylesheet.setAttribute("type", "text/css");
+ stylesheet.setAttribute("href", testURL);
+ content.appendChild(stylesheet);
+}
+
+function request_object() {
+ var content = $("content");
+
+ var object = document.createElement("embed");
+ object.setAttribute("src", testURL);
+ content.appendChild(object);
+}
+
+function request_xmlhttprequest() {
+ var request = new XMLHttpRequest();
+ request.open("GET", testURL, false);
+ request.send(null);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug378969.html b/dom/base/test/test_bug378969.html
new file mode 100644
index 0000000000..58d859d1b7
--- /dev/null
+++ b/dom/base/test/test_bug378969.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=378969
+-->
+<head>
+ <title>Test for Bug 378969</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=378969">Mozilla Bug 378969</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 378969 **/
+
+function do_test()
+{
+ var XHTMLDocString = '<html xmlns="http://www.w3.org/1999/xhtml">';
+ XHTMLDocString += '<body><input/>input</body></html>';
+
+ var doc = new DOMParser().parseFromString(XHTMLDocString, "application/xml");
+
+ var body = doc.getElementsByTagName("body")[0];
+ var filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
+ var walker = doc.createTreeWalker(body, filter, null);
+ walker.currentNode = body.firstChild;
+ walker.nextNode();
+
+ ok("A" == "A", "A is A");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug380418.html b/dom/base/test/test_bug380418.html
new file mode 100644
index 0000000000..45b6f21d99
--- /dev/null
+++ b/dom/base/test/test_bug380418.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=380418 -->
+<head>
+ <title>Test for Bug 380418</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=380418">Mozilla Bug 380418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var request = new XMLHttpRequest();
+ request.open("GET", window.location.href, false);
+ request.send(null);
+
+ // Try reading headers in unprivileged context
+ is(request.getResponseHeader("Set-Cookie"), null, "Reading Set-Cookie response header in unprivileged context");
+ is(request.getResponseHeader("Set-Cookie2"), null, "Reading Set-Cookie2 response header in unprivileged context");
+ is(request.getResponseHeader("X-Dummy"), "test", "Reading X-Dummy response header in unprivileged context");
+
+ ok(!/\bSet-Cookie:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie in all response headers in unprivileged context");
+ ok(!/\bSet-Cookie2:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie2 in all response headers in unprivileged context");
+ ok(/\bX-Dummy:/i.test(request.getAllResponseHeaders()), "Looking for X-Dummy in all response headers in unprivileged context");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug380418.html^headers^ b/dom/base/test/test_bug380418.html^headers^
new file mode 100644
index 0000000000..5f8d4969c0
--- /dev/null
+++ b/dom/base/test/test_bug380418.html^headers^
@@ -0,0 +1,4 @@
+Set-Cookie: test
+Set-Cookie2: test2
+X-Dummy: test
+Cache-Control: max-age=0
diff --git a/dom/base/test/test_bug382113.html b/dom/base/test/test_bug382113.html
new file mode 100644
index 0000000000..faf0deef25
--- /dev/null
+++ b/dom/base/test/test_bug382113.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=382113
+-->
+<head>
+ <title>Test for Bug 382113</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script class="testbody" type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+ var childGotOnload = false;
+ var objectGotOnload = false;
+
+ /** Test for Bug 100533 **/
+ function checkEvents() {
+ is(childGotOnload, true, "Child got load event");
+ is(objectGotOnload, true, "Object got load event");
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="checkEvents()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382113">Mozilla Bug 382113</a>
+<p id="display"></p>
+<div id="content">
+ <object type="text/html" data="bug382113_object.html"
+ onload="objectGotOnload = true;"></object>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug382871.html b/dom/base/test/test_bug382871.html
new file mode 100644
index 0000000000..1414954060
--- /dev/null
+++ b/dom/base/test/test_bug382871.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=382871
+-->
+<head>
+ <title>Test for Bug 382871</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382871">Mozilla Bug 382871</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 382871 **/
+
+function loadHandler(evt) {
+ ok("randomProperty" in evt.target);
+ ok("randomProperty" in evt.target.upload);
+ SimpleTest.finish();
+}
+
+function runTest() {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = loadHandler;
+ xhr.randomProperty = true;
+ xhr.upload.randomProperty = true;
+ xhr.open("GET", "test_bug382871.html");
+ xhr.send();
+ xhr = null;
+ SpecialPowers.gc();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug384003.xhtml b/dom/base/test/test_bug384003.xhtml
new file mode 100644
index 0000000000..6d1a4f5898
--- /dev/null
+++ b/dom/base/test/test_bug384003.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" attr="value">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384003
+-->
+<head>
+ <title>Test for Bug 384003</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384003">Mozilla Bug 384003</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test" xmlns:foo="http://www.foo.org"><foo:foo/>
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 384003 **/
+
+
+function resolverTest(expr, resolver, result, extype) {
+ if (!extype) {
+ extype = 0;
+ }
+ try {
+ is(document.evaluate(expr, document, resolver,
+ XPathResult.FIRST_ORDERED_NODE_TYPE, null).
+ singleNodeValue,
+ result,
+ "Wrong XPathResult");
+ if (extype) {
+ ok(false, "Should have got an exception!");
+ }
+ } catch(ex) {
+ is(ex.name, extype, "Wrong exception");
+ }
+}
+
+// Expression should return document element.
+// Document resolver
+resolverTest("*", document, document.documentElement);
+// Element resolver
+resolverTest("*", document.documentElement, document.documentElement);
+// Attribute resolver
+resolverTest("*", document.documentElement.getAttributeNode("attr"),
+ document.documentElement);
+// Text node resolver
+resolverTest("*", document.documentElement.firstChild,
+ document.documentElement);
+// Comment node resolver
+resolverTest("*", document.documentElement.firstChild.nextSibling,
+ document.documentElement);
+
+// Expression should return foo element, but because of the
+// resolver it may throw an exception.
+var foo = document.getElementById("test").firstChild;
+// Document resolver
+resolverTest("//foo:foo", document, foo, "NamespaceError");
+// Element resolver
+resolverTest("//foo:foo", document.documentElement, foo, "NamespaceError");
+// Attribute resolver
+resolverTest("//foo:foo", document.documentElement.getAttributeNode("attr"),
+ foo, "NamespaceError");
+// Text node resolver
+resolverTest("//foo:foo", document.documentElement.firstChild,
+ foo, "NamespaceError");
+// Comment node resolver
+resolverTest("//foo:foo", document.documentElement.firstChild.nextSibling,
+ foo, "NamespaceError");
+// Function resolver
+resolverTest("//foo:foo",
+ function(p) { return (p == "foo") ? "http://www.foo.org" : ""; },
+ foo);
+// Element resolver, which has definition for foo namespace
+resolverTest("//foo:foo", foo.parentNode, foo);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug390219.html b/dom/base/test/test_bug390219.html
new file mode 100644
index 0000000000..630b2bdd85
--- /dev/null
+++ b/dom/base/test/test_bug390219.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=390219
+-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test for Bug 390219</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 390219 **/
+
+SimpleTest.waitForExplicitFinish();
+xhr = new XMLHttpRequest();
+xhr.open("GET", "nonexistent_url", true);
+xhr.send(null);
+xhr.abort();
+xhr.open("GET", ".", true);
+xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ is(xhr.status, 200, "wrong status");
+ SimpleTest.finish();
+ }
+}
+xhr.send(null);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug390735.html b/dom/base/test/test_bug390735.html
new file mode 100644
index 0000000000..57c300b5eb
--- /dev/null
+++ b/dom/base/test/test_bug390735.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=390735
+-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test for Bug 390735</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 390735 **/
+
+var contents = document.getElementsByTagName("head")[0].innerHTML;
+var expectedFind = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">";
+
+ok(contents.indexOf(expectedFind) > -1, "The meta tag element was not found");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug392318.html b/dom/base/test/test_bug392318.html
new file mode 100644
index 0000000000..112468f894
--- /dev/null
+++ b/dom/base/test/test_bug392318.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=392318
+-->
+<head>
+ <title>Test for Bug 392318</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 392318 **/
+
+ SimpleTest.waitForExplicitFinish();
+ var testRan = false;
+
+ function runTest() {
+ isnot($("t").offsetWidth, 0, "Unexpected offsetWidth");
+ testRan = true;
+ }
+
+ document.addEventListener("DOMContentLoaded", runTest);
+
+ addLoadEvent(function() {
+ is(testRan, true, "Onload firing too early");
+ });
+
+ addLoadEvent(SimpleTest.finish);
+ </script>
+ <!-- IMPORTANT: This sheet must come after the test that sets up the
+ DOMContentLoaded handler and before the <body> -->
+ <link rel="stylesheet" type="text/css" href="data:text/css;%20charset=utf-8,%23t%20%7B%0Awidth%3A%20200px%3B%0Aborder%3A%201px%20solid%20black%3B%0Aheight%3A%20100px%3B%0A%7D">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=392318">Mozilla Bug 392318</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<div id="t"></div>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug392511.html b/dom/base/test/test_bug392511.html
new file mode 100644
index 0000000000..b97d83377f
--- /dev/null
+++ b/dom/base/test/test_bug392511.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=392511
+-->
+<head>
+ <title>Test for Bug 392511</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=392511">Mozilla Bug 392511</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t1"><span onclick="&quot;&amp;"></span></div>
+ <div id="t2"><span foo="&quot;&amp;"></span></div>
+ <div id="t3"><span onclick='&apos;&amp;'></span></div>
+ <div id="t4"><span foo='&apos;&amp;'></span></div>
+ <div id="t5"><span onclick='"&apos;&amp;'></span></div>
+ <div id="t6"><span foo='"&apos;&amp;'></span></div>
+ <div id="t7"><span onclick="'&quot;&amp;"></span></div>
+ <div id="t8"><span foo="'&quot;&amp;"></span></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 392511 **/
+
+var results = [
+ "\"&quot;&amp;\"",
+ "\"&quot;&amp;\"",
+ "\"'&amp;\"",
+ "\"'&amp;\"",
+ "\"&quot;'&amp;\"",
+ "\"&quot;'&amp;\"",
+ "\"'&quot;&amp;\"",
+ "\"'&quot;&amp;\""
+];
+
+for (var i = 1; i <= 8; ++i) {
+ var id = "t" + i;
+ var str = $(id).innerHTML;
+ var expect = "<span ";
+ expect += (i % 2) ? "onclick" : "foo";
+ expect += "=" + results[i-1] + "></span>";
+ is (str, expect, "Wrong string for test " + id);
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug393968.html b/dom/base/test/test_bug393968.html
new file mode 100644
index 0000000000..760aeaab55
--- /dev/null
+++ b/dom/base/test/test_bug393968.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=393968
+-->
+<head>
+ <title>Test for Bug 393968</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=393968">Mozilla Bug 393968</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["dom.xhr.standard_content_type_normalization", true]]},
+function() {
+
+/** Test for Bug 393968 **/
+var req = new XMLHttpRequest();
+req.open("POST", window.location.href);
+req.setRequestHeader("Content-Type", "text/plain; charset=us-ascii; boundary=01234567890");
+req.send("Some text");
+
+is(SpecialPowers.wrap(req).channel
+ .QueryInterface(SpecialPowers.Ci.nsIHttpChannel)
+ .getRequestHeader("Content-Type"),
+ "text/plain;charset=UTF-8;boundary=01234567890",
+ "Headers should match");
+
+SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug395915.html b/dom/base/test/test_bug395915.html
new file mode 100644
index 0000000000..711a6fd6a3
--- /dev/null
+++ b/dom/base/test/test_bug395915.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html class="A b">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395915
+-->
+<head>
+ <title>Test for Bug 395915</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395915">Mozilla Bug 395915</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 395915 **/
+is(document.getElementsByClassName("a").length, 0,
+ "Class names are case-sensitive");
+is(document.getElementsByClassName("A").length, 1,
+ "Have one node of class A");
+is(document.getElementsByClassName("A")[0], document.documentElement,
+ "Root is class A");
+
+is(document.getElementsByClassName("a b").length, 0,
+ "Class names are case-sensitive two");
+is(document.getElementsByClassName("A B").length, 0,
+ "Class names are case-sensitive three");
+is(document.getElementsByClassName("a B").length, 0,
+ "Class names are case-sensitive four");
+is(document.getElementsByClassName("A b").length, 1,
+ "Have one node of class 'A b'");
+is(document.getElementsByClassName("A b")[0], document.documentElement,
+ "Root is class 'A b'");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug397234.html b/dom/base/test/test_bug397234.html
new file mode 100644
index 0000000000..50686ee388
--- /dev/null
+++ b/dom/base/test/test_bug397234.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397234
+-->
+<head>
+ <title>Test for Bug 397234</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397234">Mozilla Bug 397234</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["dom.xhr.standard_content_type_normalization", true]]},
+function() {
+
+/** Test for Bug 397234 **/
+var req = new XMLHttpRequest();
+req.open("POST", window.location.href);
+// Capitalization of charet param is on purpose!
+req.setRequestHeader("Content-Type", "text/plain; charset='uTf-8'");
+req.send("Some text");
+
+is(SpecialPowers.wrap(req).channel
+ .QueryInterface(SpecialPowers.Ci.nsIHttpChannel)
+ .getRequestHeader("Content-Type"),
+ "text/plain;charset=UTF-8",
+ "Headers should match");
+
+SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug398243.html b/dom/base/test/test_bug398243.html
new file mode 100644
index 0000000000..11328f66ed
--- /dev/null
+++ b/dom/base/test/test_bug398243.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=398243
+-->
+<head>
+ <title>Test for Bug 398243</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398243">Mozilla Bug 398243</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+ <iframe id="testframe" src="http://mochi.test:8888/tests/dom/base/test/formReset.html"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 398243 **/
+const enteredText1 = "New value for text input";
+const enteredText2 = "New value for texarea";
+SimpleTest.waitForExplicitFinish();
+
+function afterLoad() {
+ var iframeDoc = $("testframe").contentDocument;
+ /* change all the form controls */
+ iframeDoc.getElementById("checkbox1").checked = true;
+ iframeDoc.getElementById("checkbox2").checked = false;
+ iframeDoc.getElementById("textinput").value = enteredText1;
+ iframeDoc.getElementById("textarea").value = enteredText2;
+
+ /* Reload the page */
+ $("testframe").setAttribute("onload", "afterReload()");
+ iframeDoc.location.reload();
+}
+
+addLoadEvent(afterLoad);
+
+function afterReload() {
+ var iframeDoc = $("testframe").contentDocument;
+ is(iframeDoc.getElementById("checkbox1").checked, true,
+ "checkbox #1 state preserved");
+ is(iframeDoc.getElementById("checkbox2").checked, false,
+ "checkbox #2 state preserved");
+ is(iframeDoc.getElementById("textinput").value, enteredText1,
+ "text preserved in <input>");
+ is(iframeDoc.getElementById("textarea").value, enteredText2,
+ "text preserved in <textarea>");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug401662.html b/dom/base/test/test_bug401662.html
new file mode 100644
index 0000000000..76b910ce74
--- /dev/null
+++ b/dom/base/test/test_bug401662.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401662
+-->
+<head>
+ <title>Test for Bug 401662</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=401662">Mozilla Bug 401662</a>
+<p id="display"><iframe id="testframe2"
+ srcdoc="<html><body>foo<style style='display:block'>bar{}</style></body></html>">
+ </iframe>
+</p>
+<div id="content" style="display: none">
+ <iframe id="testframe"
+ srcdoc="<html><body>foo<style>bar</style></body></html>">
+ </iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 401662 - don't serialize style elements in body into
+ plaintext**/
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ const Cu = SpecialPowers.Cu;
+
+ var encoder = Cu.createDocumentEncoder("text/html");
+ var doc = $("testframe").contentDocument;
+ encoder.init(doc, "text/plain", encoder.OutputBodyOnly);
+ encoder.setCharset("UTF-8");
+ var out = encoder.encodeToString();
+ is(out, "foo", "style content serialized in plaintext?");
+
+ var encoder = Cu.createDocumentEncoder("text/html");
+ var doc = $("testframe2").contentDocument;
+ encoder.init(doc, "text/plain", encoder.OutputBodyOnly);
+ encoder.setCharset("UTF-8");
+ var out = encoder.encodeToString();
+ is(out.replace(/\r\n/g, "\n"), "foo\nbar{}", "visible style content NOT serialized in plaintext?");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug402150.html b/dom/base/test/test_bug402150.html
new file mode 100644
index 0000000000..eb3bf84a53
--- /dev/null
+++ b/dom/base/test/test_bug402150.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402150
+-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test for Bug 402150</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 402150 **/
+ok(true, "The document loaded properly");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug402150.html^headers^ b/dom/base/test/test_bug402150.html^headers^
new file mode 100644
index 0000000000..453e7e1f37
--- /dev/null
+++ b/dom/base/test/test_bug402150.html^headers^
@@ -0,0 +1 @@
+Last-Modified: Fri, 2 Nov 19107 00:00:01 GMT
diff --git a/dom/base/test/test_bug403841.html b/dom/base/test/test_bug403841.html
new file mode 100644
index 0000000000..469b31730f
--- /dev/null
+++ b/dom/base/test/test_bug403841.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403841
+-->
+<head>
+ <title>Test for Bug 403841</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=403841">Mozilla Bug 403841</a>
+<span id="content">abc</p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 403841 - Crash in nsContentUtils::CreateContextualFragment when passed a non-element node as context node **/
+ var t = document.getElementById("content").firstChild;
+ var r = document.createRange();
+ r.setStart(t,0);
+ r.setEnd(t,3);
+ // make sure this doesn't crash
+ var f = r.createContextualFragment("<span>");
+ ok(f.firstChild instanceof HTMLSpanElement, "created fragment ok");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug403852.html b/dom/base/test/test_bug403852.html
new file mode 100644
index 0000000000..fec41e9cc4
--- /dev/null
+++ b/dom/base/test/test_bug403852.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403852
+-->
+ <title>Test for Bug 403852</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=403852">Mozilla Bug 403852</a>
+<p id="display">
+ <input id="fileList" type="file"></input>
+</p>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var script = '';
+SpecialPowers.pushPrefEnv({ "set":
+ [["privacy.reduceTimerPrecision", false]]},
+ function() {
+ var url = SimpleTest.getTestFileURL("bug403852_fileOpener.js");
+ script = SpecialPowers.loadChromeScript(url);
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+});
+
+function onOpened(message) {
+ var fileList = document.getElementById('fileList');
+ SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
+
+ // Make sure the file is accessible with indexed notation
+ var domFile = fileList.files[0];
+
+ is(domFile.name, "prefs.js", "fileName should be prefs.js");
+
+ ok("lastModified" in domFile, "lastModified must be present");
+
+ var d = new Date(message.mtime);
+ is(d.getTime(), (new Date(domFile.lastModified)).getTime(), "lastModified should be the same");
+
+ var x = new Date();
+
+ // In our implementation of File object, lastModified is unknown only for new objects.
+ // Using canvas or input[type=file] elements, we 'often' have a valid lastModified values.
+ // For canvas we use memory files and the lastModified is now().
+ var f = new File([new Blob(['test'], {type: 'text/plain'})], "test-name");
+
+ var y = new Date(f.lastModified);
+ var z = new Date();
+
+ ok((x.getTime() <= y.getTime()) && (y.getTime() <= z.getTime()), "lastModified of file which does not have last modified date should be current time");
+
+ script.destroy();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body> </html>
diff --git a/dom/base/test/test_bug403868.xml b/dom/base/test/test_bug403868.xml
new file mode 100644
index 0000000000..cb2ababff4
--- /dev/null
+++ b/dom/base/test/test_bug403868.xml
@@ -0,0 +1,85 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403868
+-->
+<head>
+ <title>Test for Bug 403868</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=403868">Mozilla Bug 403868</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 403868 **/
+function createSpan(id, insertionPoint) {
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ s.id = id;
+ $("content").insertBefore(s, insertionPoint);
+ return s;
+}
+
+var s1a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Only one span with id=test1 in the tree; should work!");
+
+var s2a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Appending span with id=test1 doesn't change which one comes first");
+
+var s3a = createSpan("test1", s2a);
+is(document.getElementById("test1"), s1a,
+ "Inserting span with id=test1 not at the beginning; doesn't matter");
+
+var s4a = createSpan("test1", s1a);
+is(document.getElementById("test1"), s4a,
+ "Inserting span with id=test1 at the beginning changes which one is first");
+
+s4a.parentNode.removeChild(s4a);
+is(document.getElementById("test1"), s1a,
+ "First-created span with id=test1 is first again");
+
+s1a.parentNode.removeChild(s1a);
+is(document.getElementById("test1"), s3a,
+ "Third-created span with id=test1 is first now");
+
+// Start the id hashtable
+for (var i = 0; i < 256; ++i) {
+ document.getElementById("no-such-id-in-the-document" + i);
+}
+
+var s1b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Only one span with id=test2 in the tree; should work!");
+
+var s2b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Appending span with id=test2 doesn't change which one comes first");
+
+var s3b = createSpan("test2", s2b);
+is(document.getElementById("test2"), s1b,
+ "Inserting span with id=test2 not at the beginning; doesn't matter");
+
+var s4b = createSpan("test2", s1b);
+is(document.getElementById("test2"), s4b,
+ "Inserting span with id=test2 at the beginning changes which one is first");
+
+s4b.parentNode.removeChild(s4b);
+is(document.getElementById("test2"), s1b,
+ "First-created span with id=test2 is first again");
+
+s1b.parentNode.removeChild(s1b);
+is(document.getElementById("test2"), s3b,
+ "Third-created span with id=test2 is first now");
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug405182.html b/dom/base/test/test_bug405182.html
new file mode 100644
index 0000000000..d48d003ad0
--- /dev/null
+++ b/dom/base/test/test_bug405182.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=405182
+-->
+<head>
+ <title>Test for Bug 405182</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405182">Mozilla Bug 405182</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 405182 **/
+
+function do_test()
+{
+ var dE = document.documentElement;
+
+ document.addEventListener("DOMNodeRemoved", newScript);
+
+ document.removeChild(dE);
+
+ function newScript()
+ {
+ var ns = document.createElementNS("http://www.w3.org/1999/xhtml", "script");
+ var nt = document.createTextNode("42;");
+ ns.appendChild(nt);
+ dE.appendChild(ns);
+ ok(true, "Test is successful if we get here without crashing");
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug409380.html b/dom/base/test/test_bug409380.html
new file mode 100644
index 0000000000..ddc796b223
--- /dev/null
+++ b/dom/base/test/test_bug409380.html
@@ -0,0 +1,378 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=409380
+-->
+<head>
+ <title>Test for Bug 409380</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=409380">Mozilla Bug 409380</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 409380 **/
+
+// eslint-disable-next-line complexity
+function runRangeTest()
+{
+ // Bug 336381
+ // This is a case which can't be supported (at least not at the moment)
+ // because DOM Range requires that when the start boundary point is text node,
+ // it must be splitted. But in this case boundary point doesn't have parent,
+ // so splitting doesn't work.
+ var zz = document.getElementById("connectedDiv").firstChild;
+ zz.remove();
+ var range = document.createRange();
+ var hadException = false;
+ try {
+ range.setStart(zz, 0);
+ range.setEnd(zz, 0);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException ,
+ "It should be possible to select text node even if the node is not in DOM.");
+ hadException = false;
+ try {
+ range.insertNode(document.createTextNode('5'));
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException,
+ "It shouldn't be possible to insert text node to a detached range.");
+
+ // Bug 409380
+ var element = document.createElement('div');
+ var elementContent = "This is the element content";
+ element.innerHTML = elementContent;
+ range = element.ownerDocument.createRange();
+ hadException = false;
+ try {
+ range.selectNodeContents(element);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException,
+ "It should be possible to select node contents of a detached element.");
+ ok(range.toString() == elementContent, "Wrong range selection");
+
+ // range.selectNode can't succeed because selectNode sets boundary points
+ // to be parentNode, which in this testcase is null.
+ element = document.createElement('div');
+ range = element.ownerDocument.createRange();
+ hadException = false;
+ try {
+ range.selectNode(element);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "It shouldn't be possible to select a detached element.");
+
+ // Testing contextual fragment.
+ range = element.ownerDocument.createRange();
+ var cf = null;
+ var testContent = "<span>foo</span><span>bar</span>";
+ try {
+ range.selectNodeContents(element);
+ cf = range.createContextualFragment(testContent);
+ element.appendChild(cf);
+ } catch (ex) {
+ }
+ ok(cf, "Creating contextual fragment didn't succeed!");
+ ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+ element = document.createElement('div');
+ element.textContent = "foobar";
+ range = element.ownerDocument.createRange();
+ try {
+ range.selectNodeContents(element);
+ element.firstChild.insertData(3, " ");
+ } catch (ex) {
+ }
+ ok(range.toString() == "foo bar");
+
+ // Testing contextual fragment, but inserting element to document
+ // after creating range.
+ element = document.createElement('div');
+ range = element.ownerDocument.createRange();
+ document.body.appendChild(element);
+ cf = null;
+ testContent = "<span>foo</span><span>bar</span>";
+ try {
+ range.selectNodeContents(element);
+ cf = range.createContextualFragment(testContent);
+ element.appendChild(cf);
+ } catch (ex) {
+ }
+ ok(cf, "Creating contextual fragment didn't succeed!");
+ ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+ // Testing contextual fragment, but inserting element to document
+ // before creating range.
+ element = document.createElement('div');
+ document.body.appendChild(element);
+ range = element.ownerDocument.createRange();
+ cf = null;
+ testContent = "<span>foo</span><span>bar</span>";
+ try {
+ range.selectNodeContents(element);
+ cf = range.createContextualFragment(testContent);
+ element.appendChild(cf);
+ } catch (ex) {
+ }
+ ok(cf, "Creating contextual fragment didn't succeed!");
+ ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+ element = document.createElement('div');
+ var range2 = element.ownerDocument.createRange();
+ hadException = false;
+ try {
+ range2.selectNodeContents(element);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException,
+ "It should be possible to select node contents of a detached element.");
+
+ // Now the boundary points of range are in DOM, but boundary points of
+ // range2 aren't.
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.START_TO_START, range2);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.START_TO_END, range2);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.END_TO_START, range2);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.END_TO_END, range2);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.START_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.START_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.END_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.END_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ // range3 will be in document
+ element = document.createElement('div');
+ document.body.appendChild(element);
+ range3 = element.ownerDocument.createRange();
+ hadException = false;
+ try {
+ range3.selectNodeContents(element);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException,
+ "It should be possible to select node contents of a detached element.");
+
+ hadException = false;
+ try {
+ range3.compareBoundaryPoints(range.START_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range3.compareBoundaryPoints(range.START_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range3.compareBoundaryPoints(range.END_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range3.compareBoundaryPoints(range.END_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ // range4 won't be in document
+ element = document.createElement('div');
+ var range4 = element.ownerDocument.createRange();
+ hadException = false;
+ try {
+ range4.selectNodeContents(element);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException,
+ "It should be possible to select node contents of a detached element.");
+
+ hadException = false;
+ try {
+ range4.compareBoundaryPoints(range.START_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.START_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range4.compareBoundaryPoints(range.END_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ hadException = false;
+ try {
+ range4.compareBoundaryPoints(range.END_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(hadException, "Should have got an exception!");
+
+ // Compare range to itself.
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.START_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.START_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.END_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range.compareBoundaryPoints(range.END_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ // Attach startContainer of range2 to document.
+ ok(range2.startContainer == range2.endContainer, "Wrong container?");
+ document.body.appendChild(range2.startContainer);
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.START_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.START_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.END_TO_START, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ hadException = false;
+ try {
+ range2.compareBoundaryPoints(range.END_TO_END, range);
+ } catch (ex) {
+ hadException = true;
+ }
+ ok(!hadException, "Shouldn't have got an exception!");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runRangeTest);
+
+</script>
+</pre>
+<div id="connectedDiv">zz</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug410229.html b/dom/base/test/test_bug410229.html
new file mode 100644
index 0000000000..9e3815b094
--- /dev/null
+++ b/dom/base/test/test_bug410229.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410229
+-->
+<head>
+ <title>Test for Bug 410229</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=410229">Mozilla Bug 410229</a>
+<p id="display"></p>
+
+<span id="s"><span id="inner">Hello</span>
+<div>My</div>
+Kitty</span>
+
+<br>
+<span id="s2">Hello<div>My</div><span id="inner2">Kitty</span></span>
+
+<br>
+<span id="s3"><div id="inner3block">My</div><span id="inner3">Kitty</span></span>
+
+<br>
+<span id="s4"><div id="inner4block">My</div></span>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var s = document.getElementById("s");
+var inner = document.getElementById("inner");
+var rects = s.getClientRects();
+is(s.getBoundingClientRect().top, inner.getBoundingClientRect().top,
+ "'"+s.id+"' "+"IB-split span should start where its first line starts");
+is(s.getClientRects().length, 3,
+ "'"+s.id+"' "+"IB-split span should have three CSS boxes");
+ok(rects[0].left < rects[0].right && rects[0].top < rects[0].bottom,
+ "'"+s.id+"' "+"IB-split span should have a non-zero width first rect");
+ok(rects[1].left < rects[1].right && rects[1].top < rects[1].bottom,
+ "'"+s.id+"' "+"IB-split span should have a non-zero width second rect");
+ok(rects[2].left < rects[2].right && rects[2].top < rects[2].bottom,
+ "'"+s.id+"' "+"IB-split span should have a non-zero width second rect");
+is(s.getBoundingClientRect().top, rects[0].top,
+ "'"+s.id+"' "+"IB-split span should start where its first rect starts");
+is(s.getBoundingClientRect().bottom, rects[2].bottom,
+ "'"+s.id+"' "+"IB-split span should end where its last rect ends");
+
+s = document.getElementById("s2");
+inner = document.getElementById("inner2");
+rects = s.getClientRects();
+is(s.getBoundingClientRect().bottom, inner.getBoundingClientRect().bottom,
+ "'"+s.id+"' "+"IB-split span should end where its last line ends");
+is(s.getClientRects().length, 3,
+ "'"+s.id+"' "+"IB-split span should have three CSS boxes");
+is(s.getBoundingClientRect().bottom, rects[2].bottom,
+ "'"+s.id+"' "+"IB-split span should end where its last rect ends");
+
+s = document.getElementById("s3");
+inner = document.getElementById("inner3");
+var block = document.getElementById("inner3block");
+rects = s.getClientRects();
+is(s.getBoundingClientRect().top, block.getBoundingClientRect().top,
+ "'"+s.id+"' "+"IB-split span should start where its first line starts");
+is(s.getBoundingClientRect().bottom, inner.getBoundingClientRect().bottom,
+ "'"+s.id+"' "+"IB-split span should end where its last line ends");
+is(s.getClientRects().length, 3,
+ "'"+s.id+"' "+"IB-split span should have three CSS boxes");
+is(rects[0].left, rects[0].right,
+ "'"+s.id+"' "+"IB-split span should have a zero width first rect");
+is(s.getBoundingClientRect().top, rects[1].top,
+ "'"+s.id+"' "+"IB-split span should start where its second rect starts");
+
+s = document.getElementById("s4");
+block = document.getElementById("inner4block");
+rects = s.getClientRects();
+is(s.getBoundingClientRect().top, block.getBoundingClientRect().top,
+ "'"+s.id+"' "+"IB-split span should start where its first line starts");
+is(s.getBoundingClientRect().bottom, block.getBoundingClientRect().bottom,
+ "'"+s.id+"' "+"IB-split span should end where its last line ends");
+is(s.getClientRects().length, 3,
+ "'"+s.id+"' "+"IB-split span should have three CSS boxes");
+is(rects[0].left, rects[0].right,
+ "'"+s.id+"' "+"IB-split span should have a zero width first rect");
+is(s.getBoundingClientRect().bottom, rects[1].bottom,
+ "'"+s.id+"' "+"IB-split span should end where its block rect ends");
+/*
+ok(rects[2].left == rects[2].right,
+ "'"+s.id+"' "+"IB-split span should have a zero width last rect");
+*/
+
+/*
+alert("'"+s.id+"' bounding rect:\n"+
+ ' left='+s.getBoundingClientRect().left+' right='+s.getBoundingClientRect().right+' top='+s.getBoundingClientRect().top+' bottom='+s.getBoundingClientRect().bottom + '\nclient rects:\n' +
+ ' left='+rects[0].left+' right='+rects[0].right+' top='+rects[0].top+' bottom='+rects[0].bottom + '\n' +
+ ' left='+rects[1].left+' right='+rects[1].right+' top='+rects[1].top+' bottom='+rects[1].bottom + '\n' +
+ ' left='+rects[2].left+' right='+rects[2].right+' top='+rects[2].top+' bottom='+rects[2].bottom + '\n');
+*/
+
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/base/test/test_bug413974.html b/dom/base/test/test_bug413974.html
new file mode 100644
index 0000000000..d1b1ca0cc1
--- /dev/null
+++ b/dom/base/test/test_bug413974.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413974
+-->
+<head>
+ <title>Test for Bug 413974</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=413974">Mozilla Bug 413974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 413974 **/
+var req = new XMLHttpRequest();
+req.open("POST", window.location.href);
+req.setRequestHeader("Content-Type", "text/plain; boundary=01234567890");
+req.send("Some text");
+
+is(SpecialPowers.wrap(req).channel
+ .QueryInterface(SpecialPowers.Ci.nsIHttpChannel)
+ .getRequestHeader("Content-Type"),
+ "text/plain; boundary=01234567890",
+ "Charset should come before boundary");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug414190.html b/dom/base/test/test_bug414190.html
new file mode 100644
index 0000000000..813d35ae7d
--- /dev/null
+++ b/dom/base/test/test_bug414190.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=414190
+-->
+<head>
+ <title>Test for Bug 414190</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=414190">Mozilla Bug 414190</a>
+<p id="display"></p>
+
+<table id="table" border="0" cellspacing="0" cellpadding="0"
+ style="width:100px; border:10px solid silver;">
+ <tr><td id="cell" style="height:100px;"></td></tr>
+ <caption id="caption" style="caption-side:bottom; width:50px; height:70px; background:yellow;"></caption>
+</table>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function isEqualRect(r1, r2, r1border, s) {
+ is(r1.left + r1border, r2.left, s + " (left)");
+ is(r1.right - r1border, r2.right, s + " (right)");
+ is(r1.top + r1border, r2.top, s + " (top)");
+ is(r1.bottom - r1border, r2.bottom, s + " (bottom)");
+}
+
+function runTest() {
+ var table = document.getElementById("table");
+ var cell = document.getElementById("cell");
+ var caption = document.getElementById("caption");
+ var tableRects = table.getClientRects();
+ var tableBoundingRect = table.getBoundingClientRect();
+ var cellBoundingRect = cell.getBoundingClientRect();
+ var captionBoundingRect = caption.getBoundingClientRect();
+
+ is(tableRects.length, 2, "Table should have rects for body and caption");
+ isEqualRect(tableRects[0], cellBoundingRect, 10,
+ "Table first rect should be cell rect");
+ isEqualRect(tableRects[1], captionBoundingRect, 0,
+ "Table second rect should be caption rect");
+ is(cellBoundingRect.right - cellBoundingRect.left, 80, "Cell incorrect width");
+ is(cellBoundingRect.bottom - cellBoundingRect.top, 100, "Cell incorrect height");
+ is(captionBoundingRect.right - captionBoundingRect.left, 50, "Caption incorrect width");
+ is(captionBoundingRect.bottom - captionBoundingRect.top, 70, "Caption incorrect height");
+ is(captionBoundingRect.top, cellBoundingRect.bottom + 10, "Discontiguous vertical geometry");
+
+ is(tableBoundingRect.top, cellBoundingRect.top - 10, "Table top error");
+ is(tableBoundingRect.left, cellBoundingRect.left - 10, "Table left error");
+ is(tableBoundingRect.bottom, captionBoundingRect.bottom, "Table bottom error");
+ is(tableBoundingRect.right, cellBoundingRect.right + 10, "Table right error");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/base/test/test_bug415860.html b/dom/base/test/test_bug415860.html
new file mode 100644
index 0000000000..1c00ce8843
--- /dev/null
+++ b/dom/base/test/test_bug415860.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=415860
+-->
+<head>
+ <title>Test for Bug 415860</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=415860">Mozilla Bug 415860</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="testdata"> </div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 415860 **/
+
+function tests() {
+ // #text node
+ n = document.getElementById('testdata').firstChild;
+ s = getSelection();
+
+ // Initial text..
+ n.textContent = "Hello!";
+
+ // select the second last character
+ r = document.createRange();
+ r.setStart(n, 4);
+ r.setEnd(n, 5);
+ s.addRange(r);
+
+ ok(s == "o", "Should have selected 'o'");
+ ok(r.toString() == "o", "Range should be 'o'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+
+ // Update the text
+ n.textContent = "Hello!";
+
+ ok(s == "", "Should have selected ''");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+
+ // select the last character
+ r = document.createRange();
+ r.setStart(n, 5);
+ r.setEnd(n, 6);
+ s.addRange(r);
+
+ ok(s == "!", "Should have selected '!'");
+ ok(r.toString() == "!", "Range should be '!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+
+ // Update the text
+ n.textContent = "Hello!";
+ ok(s == "", "Should have selected ''");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+
+ r = document.createRange();
+ r.setStart(n, 5);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "!", "Range should be '!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ r.setStart(n, 0);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "Hello!", "Range should be 'Hello!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.deleteData(0, 1);
+ ok(n.nodeValue == "ello!", "Node value should be 'ello!'");
+ ok(r.toString() == "ello!", "Range should be 'ello!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.deleteData(0, 4);
+ ok(n.nodeValue == "!", "Node value should be '!'");
+ ok(r.toString() == "!", "Range should be '!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 0);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "Hello!", "Range should be 'Hello!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.replaceData(0, 6, "hELLO?");
+ ok(n.nodeValue == "hELLO?", "Node value should be 'hELLO?'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 1);
+ r.setEnd(n, 3);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "el", "Range should be 'el'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.replaceData(2, 6, "END");
+ ok(n.nodeValue == "HeEND", "Node value should be 'HeEND!'");
+ ok(r.toString() == "e", "Range should be 'e'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 1);
+ r.setEnd(n, 5);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "ello", "Range should be 'ello'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.replaceData(2, 1, "MID");
+ ok(n.nodeValue == "HeMIDlo!", "Node value should be 'HeMIDlo!'");
+ ok(r.toString() == "eMIDlo", "Range should be 'eMIDlo'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 0);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "Hello!", "Range should be 'Hello!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "hELLO?...";
+ ok(n.nodeValue == "hELLO?...", "Node value should be 'hELLO?...'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 1);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "ello!", "Range should be 'ello!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 0);
+ r.setEnd(n, 5);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "Hello", "Range should be 'Hello'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "hELLO?...";
+ ok(n.nodeValue == "hELLO?...", "Node value should be 'hELLO?...'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 0);
+ r.setEnd(n, 5);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "Hello", "Range should be 'Hello'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "...";
+ ok(n.nodeValue == "...", "Node value should be '...'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 1);
+ r.setEnd(n, 5);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "ello", "Range should be 'ello'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "...";
+ ok(n.nodeValue == "...", "Node value should be '...'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "$";
+ r.setStart(n, 0);
+ r.setEnd(n, 1);
+ ok(n.nodeValue == "$", "Node value should be $'");
+ ok(r.toString() == "$", "Range should be '$'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "?";
+ ok(n.nodeValue == "?", "Node value should be '?'");
+ ok(r.toString() == "", "Range should be ''");
+ ok(r.collapsed == true, "Range should be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 3);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "lo!", "Range should be 'lo!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.replaceData(1, 4, "MID");
+ ok(n.nodeValue == "HMID!", "Node value should be 'HMID!'");
+ ok(r.toString() == "MID!", "Range should be 'MID!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r.setStart(n, 3);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", "Node value should be 'Hello!'");
+ ok(r.toString() == "lo!", "Range should be 'lo!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.replaceData(1, 2, "MID");
+ ok(n.nodeValue == "HMIDlo!", "Node value should be 'HMIDlo!'");
+ ok(r.toString() == "MIDlo!", "Range should be 'MIDlo!'");
+ ok(r.collapsed == false, "Range shouldn't be collapsed");
+
+ n.textContent = "Hello!";
+ r = document.createRange();
+ r.setStart(n, 6);
+ r.setEnd(n, 6);
+ ok(n.nodeValue == "Hello!", " Node value should be 'Hello!'");
+ ok(r.toString() == "", " Range should be ''");
+ ok(r.startOffset == 6, "Start offset should be 6");
+ ok(r.endOffset == 6, "End offset should be 6");
+
+ n.textContent = "Hello!";
+ ok(n.nodeValue == "Hello!", " Node value should be 'Hello!'");
+ ok(r.toString() == "", " Range should be ''");
+ ok(r.startOffset == 0, "Start offset should be 0");
+ ok(r.endOffset == 0, "End offset should be 0");
+}
+
+tests();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug416317-1.html b/dom/base/test/test_bug416317-1.html
new file mode 100644
index 0000000000..bc571ad4a2
--- /dev/null
+++ b/dom/base/test/test_bug416317-1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416317
+-->
+<head>
+ <title>Test for Bug 416317</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(3);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416317">Mozilla Bug 416317</a>
+<p id="display">
+ <iframe src="file_bug416317.xhtml#target"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 416317 **/
+// Subframe handles the test
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug416317-2.html b/dom/base/test/test_bug416317-2.html
new file mode 100644
index 0000000000..59d26fa70c
--- /dev/null
+++ b/dom/base/test/test_bug416317-2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416317
+-->
+<head>
+ <title>Test for Bug 416317</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416317">Mozilla Bug 416317</a>
+<p id="display">
+ <iframe style="display: none" src="file_bug416317.xhtml#target"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 416317 **/
+SimpleTest.requestLongerTimeout(3);
+// Subframe handles the test
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug416383.html b/dom/base/test/test_bug416383.html
new file mode 100644
index 0000000000..8228d277f7
--- /dev/null
+++ b/dom/base/test/test_bug416383.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416383
+-->
+<head>
+ <title>Test for Bug 416383</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416383">Mozilla Bug 416383</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test_parent"><div someattr="foo"></div></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 416383 **/
+
+function runTest() {
+ var testParent = document.getElementById("test_parent");
+ var attrs = testParent.firstChild.attributes;
+ ok(attrs != null, "Element should have attributes!");
+ var attr = testParent.firstChild.getAttributeNode("someattr");
+ ok(attr.value == "foo", "The value of the attribute should be 'foo'!");
+ testParent.firstChild.remove();
+ SpecialPowers.gc();
+ ok(true, "Browser should not crash!")
+
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug417255.html b/dom/base/test/test_bug417255.html
new file mode 100644
index 0000000000..0e7a137f0b
--- /dev/null
+++ b/dom/base/test/test_bug417255.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=417255
+-->
+<head>
+ <title>Test for Bug 417255</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ .spacer { display:inline-block; height:10px; }
+ </style>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=417255">Mozilla Bug 417255</a>
+<div id="display" style="width:800px"></div>
+
+<div><span id="s1" style="border:2px dotted red;"><span class="spacer" style="width:100px"></span>
+<div style="width:500px; height:100px; background:yellow;"></div>
+<span class="spacer" style="width:200px"></span></span></div>
+
+<div><span id="s2" style="border:2px dotted red;"><span class="spacer" style="width:100px"></span>
+<div style="width:150px; height:100px; background:yellow;"></div>
+<span class="spacer" style="width:200px"></span></span></div>
+
+<!-- test nested spans around the IB split -->
+<div><span id="s3" style="border:2px dotted red;"><span><span class="spacer" style="width:100px"></span>
+<div style="width:500px; height:100px; background:yellow;"></div>
+<span class="spacer" style="width:200px"></span></span></span></div>
+
+<div id="content" style="display: none">
+
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function getWidth(box) {
+ return box.right - box.left;
+}
+
+function doTest(id, boundsWidth, w1, w2, w3) {
+ var s = document.getElementById(id);
+ is(s.offsetWidth, boundsWidth, "bad offsetWidth");
+ is(getWidth(s.getBoundingClientRect()), boundsWidth, "bad getBoundingClientRect width");
+ is(getWidth(s.getClientRects()[0]), w1, "bad getClientRects width");
+ is(getWidth(s.getClientRects()[1]), w2, "bad getClientRects width");
+ is(getWidth(s.getClientRects()[2]), w3, "bad getClientRects width");
+}
+
+doTest("s1", 500, 102, 500, 202);
+doTest("s2", 202, 102, 150, 202);
+doTest("s3", 500, 102, 500, 202);
+
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/base/test/test_bug417384.html b/dom/base/test/test_bug417384.html
new file mode 100644
index 0000000000..b99d96f155
--- /dev/null
+++ b/dom/base/test/test_bug417384.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=417384
+-->
+<head>
+ <title>Test for Bug 417384</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=417384">Mozilla Bug 417384</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 417384 **/
+
+var expectedSerialization = "about:blank document";
+function testSerializer() {
+ var doc = document.getElementById('test_iframe').contentDocument;
+ doc.body.textContent = expectedSerialization;
+ var head1 = doc.createElement("head");
+ doc.body.appendChild(head1);
+ var span = doc.createElement("span");
+ head1.appendChild(span);
+ span.appendChild(doc.createTextNode("before inner head\n"));
+ span.appendChild(doc.createElement("head"));
+ span.appendChild(doc.createTextNode("\nafter inner head"));
+
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/html");
+ encoder.init(doc, "text/plain", 0);
+ encoder.setCharset("UTF-8");
+ var out = encoder.encodeToString();
+ ok(out == expectedSerialization, "Wrong serialization!");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testSerializer);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+<iframe id="test_iframe" src="about:blank"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug418214.html b/dom/base/test/test_bug418214.html
new file mode 100644
index 0000000000..0c747a873f
--- /dev/null
+++ b/dom/base/test/test_bug418214.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418214
+-->
+<head>
+ <title>Test for Bug 418214</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418214">Mozilla Bug 418214</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var str = '<root xmlns:html="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:math="http://www.w3.org/1998/Math/MathML"><html:div id="d" style="border:: invalid"/><svg:svg id="s" style="border:: invalid"/><math:math id="m" style="border:: invalid"/></root>';
+
+/** Test for Bug 418214 **/
+var doc = (new DOMParser()).parseFromString(str, "text/xml");
+var d = doc.getElementById("d");
+var s = doc.getElementById("s");
+var m = doc.getElementById("m");
+
+is(d.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on HTML in data documents");
+is(s.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on SVG in data documents");
+is(m.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on MathML in data documents");
+
+var d2 = d.cloneNode(true);
+var s2 = s.cloneNode(true);
+var m2 = m.cloneNode(true);
+
+is(d2.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on HTML on clone");
+is(s2.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on SVG on clone");
+is(m2.getAttribute("style"), "border:: invalid",
+ "Shouldn't be parsing style on MathML on clone");
+
+d2.style;
+s2.style;
+m2.style;
+
+is(d2.getAttribute("style"), "border:: invalid",
+ "Getting .style shouldn't affect style attribute on HTML");
+is(s2.getAttribute("style"), "border:: invalid",
+ "Getting .style shouldn't affect style attribute on SVG");
+is(m2.getAttribute("style"), "border:: invalid",
+ "Getting .style shouldn't affect style attribute on MathML");
+
+d2.style.color = "green";
+s2.style.color = "green";
+m2.style.color = "green";
+
+is(d2.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on HTML");
+is(s2.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on SVG");
+is(m2.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on MathML");
+
+d = document.adoptNode(d);
+s = document.adoptNode(s);
+m = document.adoptNode(m);
+
+is(d.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on HTML");
+is(s.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on SVG");
+is(m.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on MathML");
+
+$("display").appendChild(d);
+$("display").appendChild(s);
+$("display").appendChild(m);
+
+is(d.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on HTML");
+is(s.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on SVG");
+is(m.getAttribute("style"), "border:: invalid",
+ "Adopting should not parse style on MathML");
+
+d.style.color = "green";
+s.style.color = "green";
+m.style.color = "green";
+
+is(d.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on HTML");
+is(s.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on SVG");
+is(m.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on MathML");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug418986-1.html b/dom/base/test/test_bug418986-1.html
new file mode 100644
index 0000000000..bbae1feced
--- /dev/null
+++ b/dom/base/test/test_bug418986-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome/bug418986-1.js"></script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <script>
+ window.onload = function() {
+ test(true);
+ };
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug419132.html b/dom/base/test/test_bug419132.html
new file mode 100644
index 0000000000..c21e68afcf
--- /dev/null
+++ b/dom/base/test/test_bug419132.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=419132
+-->
+<head>
+ <title>Test for Bug 419132</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=419132">Mozilla Bug 419132</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<iframe id="i"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 419132 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var iframe = document.getElementById("i");
+ var loadCounts = 4;
+ iframe.addEventListener("load", function() {
+ if (--loadCounts == 0) {
+ ok(true, "This is a mochikit version of a crash test. To complete is to pass.");
+ SimpleTest.finish();
+ } else {
+ // Garbage collect after every other load
+ if ((loadCounts % 2) == 1) {
+ SpecialPowers.gc();
+ }
+ setTimeout(function() {
+ iframe.contentWindow.location.reload();
+ }, 0);
+ }
+ });
+ iframe.src = "bug419132.html";
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug419527.xhtml b/dom/base/test/test_bug419527.xhtml
new file mode 100644
index 0000000000..6b3644a0a0
--- /dev/null
+++ b/dom/base/test/test_bug419527.xhtml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=419527
+-->
+<head>
+ <title>Test for Bug 419527</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<template id="template"><span>Foo</span></template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=419527">Mozilla Bug 419527</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 419527 **/
+customElements.define("custom-element", class extends HTMLElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ const shadowRoot = this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+ connectedCallback() {
+ var win = window;
+ var span = this.shadowRoot.children[0];
+ win.ok(span.textContent == "Foo", "Right span.");
+ win.ok(span.localName == "span", "Wrong anon node!");
+ var range = document.createRange();
+ range.selectNode(span.firstChild);
+ win.ok(range.startContainer == span, "Wrong start container!");
+ win.ok(range.endContainer == span, "Wrong end container!");
+ var newSubTree = win.newSubTree;
+ newSubTree.appendChild(this);
+ range.setStart(newSubTree.firstChild, 0);
+ win.ok(range.startContainer == newSubTree.firstChild,
+ "Range should have been collapsed to newSubTree.firstChild!");
+ win.ok(range.endContainer == newSubTree.firstChild,
+ "Range should have been collapsed to newSubTree.firstChild!");
+ SimpleTest.finish();
+ }
+});
+
+var d;
+
+function runRangeTest() {
+ window.newSubTree = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ newSubTree.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "div"));
+
+ d = document.createElementNS("http://www.w3.org/1999/xhtml", "custom-element");
+ document.body.appendChild(d);
+}
+
+SimpleTest.waitForExplicitFinish();
+setTimeout(runRangeTest, 0);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug420609.xhtml b/dom/base/test/test_bug420609.xhtml
new file mode 100644
index 0000000000..64cbe3e320
--- /dev/null
+++ b/dom/base/test/test_bug420609.xhtml
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=420609
+-->
+<head>
+ <title>Test for Bug 420609</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=420609">Mozilla Bug 420609</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+&nbsp;&mdash;&sup1;&hellip;
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 420609 **/
+ var request = new XMLHttpRequest();
+ request.open("GET", window.location.href, false);
+ request.send(null);
+
+ ok(request.responseXML && request.responseXML.documentElement.tagName == "html", "XMLHttpRequest should load XHTML document with entities");
+ is(document.getElementById("content").textContent, request.responseXML.getElementById("content").textContent, "Entities should be expanded in the document loaded by XMLHttpRequest");
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug420700.html b/dom/base/test/test_bug420700.html
new file mode 100644
index 0000000000..cdab3fee8b
--- /dev/null
+++ b/dom/base/test/test_bug420700.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=420700
+-->
+<head>
+ <title>Test for Bug 420700</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=420700">Mozilla Bug 420700</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var r = document.createRange();
+ r.selectNode(document.documentElement);
+
+ var df = r.createContextualFragment("<p>BAD</p>");
+
+ var display = document.getElementById("display");
+ display.innerHTML = "<p>GOOD</p>";
+
+ var p = display.firstChild;
+
+ is(p.textContent, "GOOD", "createContextualFragment tests");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug421602.html b/dom/base/test/test_bug421602.html
new file mode 100644
index 0000000000..b283d81fed
--- /dev/null
+++ b/dom/base/test/test_bug421602.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421602
+-->
+<head>
+ <title>Test for Bug 421602</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=421602">Mozilla Bug 421602</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 421602 **/
+SimpleTest.waitForExplicitFinish();
+
+var img1loaded = false;
+var img1errored = false;
+
+// Our test image
+function loadTestImage() {
+ var img1 = new Image();
+ img1.onload = function() {
+ img1loaded = true;
+ finishTest();
+ }
+ img1.onerror = function() {
+ img1errored = true;
+ finishTest();
+ }
+ img1.src = window.location.href + "?image1=true";
+}
+loadTestImage();
+
+// Probably overkill to gc() more than once, but let's be safe
+SpecialPowers.gc(); SpecialPowers.gc(); SpecialPowers.gc();
+
+function finishTest() {
+ is(img1errored, true, "Image 1 should error");
+ is(img1loaded, false, "Image 1 should not load");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug422403-1.html b/dom/base/test/test_bug422403-1.html
new file mode 100644
index 0000000000..c3778cc075
--- /dev/null
+++ b/dom/base/test/test_bug422403-1.html
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for XHTML serializer</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422043">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<!-- IMPORTANT: This iframe needs to actually be displayed, so the serializer
+ sees the relevant styles for <pre> elements. -->
+<iframe id="testframe" src="file_xhtmlserializer_1.xhtml"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function loadFileContent(aFile, aCharset) {
+ //if(aAsIso == undefined) aAsIso = false;
+ if(aCharset == undefined)
+ aCharset = 'UTF-8';
+
+ var baseUri = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1']
+ .createInstance(SpecialPowers.Ci.nsIURIMutator)
+ .setSpec(window.location.href)
+ .finalize();
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var cis = SpecialPowers.Ci.nsIConverterInputStream;
+
+ var inputStream = SpecialPowers.Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(cis);
+ inputStream.init(chann.open(), aCharset, 1024, cis.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {}, content = '';
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
+
+
+function testHtmlSerializer_1 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("application/xhtml+xml");
+
+ var doc = SpecialPowers.wrap($("testframe")).contentDocument;
+ var out, expected;
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+ //------------ no flags
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_noflag.xhtml");
+ is(out, expected, "test no flags");
+
+ //------------- unsupported flags
+ // since the following flags are not supported, we should
+ // have a result like the one without flag
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputPreformatted);
+ out = encoder.encodeToString();
+ is(out, expected, "test OutputPreformatted");
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputFormatFlowed);
+ out = encoder.encodeToString();
+ is(out, expected, "test OutputFormatFlowed");
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputNoScriptContent);
+ out = encoder.encodeToString();
+ is(out, expected, "test OutputNoScriptContent");
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputNoFramesContent);
+ out = encoder.encodeToString();
+ is(out, expected, "test OutputNoFramesContent");
+
+
+ //------------ OutputWrap
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputWrap);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_wrap.xhtml");
+ is(out, expected, "test OutputWrap");
+
+ //------------ OutputFormatted
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputFormatted);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_format.xhtml");
+ is(out, expected, "test OutputFormatted");
+
+ //------------ OutputRaw
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputRaw);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_raw.xhtml");
+ is(out, expected, "test OutputRaw");
+
+ //------------ OutputBodyOnly
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputBodyOnly);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_bodyonly.xhtml");
+ is(out, expected, "test OutputBodyOnly");
+
+
+ //------------ OutputAbsoluteLinks
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputAbsoluteLinks);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_links.xhtml").trim('\n');
+ is(out, expected, "test OutputAbsoluteLinks");
+
+ //------------ OutputLFLineBreak
+ encoder.init(doc, "application/xhtml+xml",de.OutputLFLineBreak);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_linebreak.xhtml");
+ is(out, expected, "test OutputLFLineBreak");
+
+ //------------ OutputCRLineBreak
+ encoder.init(doc, "application/xhtml+xml",de.OutputCRLineBreak);
+ out = encoder.encodeToString();
+ expected = expected.replace(/\n/mg, "\r");
+ is(out, expected, "test OutputCRLineBreak");
+
+ //------------ OutputLFLineBreak + OutputCRLineBreak
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputCRLineBreak);
+ out = encoder.encodeToString();
+ expected = expected.replace(/\r/mg, "\r\n");
+ is(out, expected, "test OutputLFLineBreak + OutputCRLineBreak");
+
+ //------------ OutputNoFormattingInPre
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputNoFormattingInPre);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_noformatpre.xhtml");
+ is(out, expected, "test OutputNoFormattingInPre");
+
+ // ------------- nested body elements
+ var body2 = doc.createElement('body');
+ var p = doc.createElement('p');
+ p.appendChild(doc.createTextNode("this is an other body element"));
+ body2.appendChild(p);
+ var body = doc.getElementsByTagName('body')[0];
+ body.appendChild(body2);
+
+ is(doc.getElementsByTagName('body').length, 2); // to be sure we have two body elements
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_nested_body.xhtml");
+ is(out, expected, "test with two nested body elements");
+
+ // ------------- two body elements
+ body.parentNode.insertBefore(body2, body);
+
+ is(doc.getElementsByTagName('body').length, 2); // to be sure we have two body elements
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_sibling_body.xhtml");
+ is(out, expected, "test with two body elements");
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputBodyOnly);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_sibling_body_only_body.xhtml");
+ is(out, expected, "test with two body elements, and output body only");
+
+ // --------------- no body element
+ doc.documentElement.removeChild(body);
+ doc.documentElement.removeChild(body2);
+
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_1_no_body.xhtml");
+ is(out, expected, "test with no body element");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlSerializer_1);
+
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug422403-2.xhtml b/dom/base/test/test_bug422403-2.xhtml
new file mode 100644
index 0000000000..bff04c8f45
--- /dev/null
+++ b/dom/base/test/test_bug422403-2.xhtml
@@ -0,0 +1,296 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+-->
+<head>
+ <title>Test XHTML serializer with entities and selection</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422043">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe" src="file_xhtmlserializer_2.xhtml">
+ </iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+//<![CDATA[
+
+function loadFileContent(aFile, aCharset) {
+ //if(aAsIso == undefined) aAsIso = false;
+ if(aCharset == undefined)
+ aCharset = 'UTF-8';
+
+ var baseUri = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1']
+ .createInstance(SpecialPowers.Ci.nsIURIMutator)
+ .setSpec(window.location.href)
+ .finalize();
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var cis = SpecialPowers.Ci.nsIConverterInputStream;
+
+ var inputStream = SpecialPowers.Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(cis);
+ inputStream.init(chann.open(), aCharset, 1024, cis.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {}, content = '';
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
+
+
+function testHtmlSerializer_1 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("application/xhtml+xml");
+
+ var doc = $("testframe").contentDocument;
+ var out, expected;
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+ //------------ OutputEncodeW3CEntities
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputEncodeW3CEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_2_basic.xhtml");
+ is(out, expected, "test OutputEncodeW3CEntities");
+
+ //------------ OutputEncodeBasicEntities
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputEncodeBasicEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_2_basic.xhtml");
+ is(out, expected, "test OutputEncodeBasicEntities");
+
+ //------------ OutputEncodeLatin1Entities
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputEncodeLatin1Entities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_2_basic.xhtml");
+ is(out, expected, "test OutputEncodeLatin1Entities");
+
+ //------------ OutputEncodeHTMLEntities
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputEncodeHTMLEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_xhtmlserializer_2_basic.xhtml");
+ is(out, expected, "test OutputEncodeHTMLEntities");
+
+ // tests on the serialization of selections
+
+ var node = document.getElementById('draggable');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = 'This is a <em xmlns=\"http://www.w3.org/1999/xhtml\">draggable</em> bit of text.';
+ is(out, expected, "test selection");
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(null);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = 'This is a <em xmlns=\"http://www.w3.org/1999/xhtml\">draggable</em> bit of text.';
+ is(out, expected, "test container node");
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<div xmlns=\"http://www.w3.org/1999/xhtml\" id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>";
+ is(out, expected, "test node");
+
+ node = document.getElementById('aList');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '\n <li xmlns=\"http://www.w3.org/1999/xhtml\">Lorem ipsum dolor</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">sit amet, <strong>consectetuer</strong> </li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">adipiscing elit</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">aptent taciti</li>\n';
+ is(out, expected, "test list selection");
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(null);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = '\n <li xmlns=\"http://www.w3.org/1999/xhtml\">Lorem ipsum dolor</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">sit amet, <strong>consectetuer</strong> </li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">adipiscing elit</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li xmlns=\"http://www.w3.org/1999/xhtml\">aptent taciti</li>\n';
+ is(out, expected, "test list container node");
+
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<ol xmlns=\"http://www.w3.org/1999/xhtml\" id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>";
+ is(out, expected, "test list node");
+
+ var liList = node.getElementsByTagName("li");
+ var range = document.createRange();
+
+ // selection start at the first child of the ol, and end after the element ol
+ range.setStart(node, 1);
+ range.setEnd(node.parentNode, 2);
+ select.removeAllRanges();
+ select.addRange(range);
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol xmlns=\"http://www.w3.org/1999/xhtml\" id="aList"><li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the first child of the ol, and end after the element ol");
+
+ // selection start at the third child of the ol, and end after the element ol
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol xmlns=\"http://www.w3.org/1999/xhtml\" id="aList"><li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol");
+
+
+ // selection start at the third child of the ol, and end after the element ol + ol start at the value 5
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ node.setAttribute("start","5");
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol xmlns=\"http://www.w3.org/1999/xhtml\" id="aList" start="5"><li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol + ol start at the value 5");
+
+
+ // selection contains only some child of the ol
+ node.removeAttribute("start");
+ range.setStart(node, 3);
+ range.setEnd(node, 5);
+ encoder.init(document, "application/xhtml+xml", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li xmlns=\"http://www.w3.org/1999/xhtml\">sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol");
+
+
+ // test on short attributes
+
+ node = document.getElementById('shortattr1');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<input xmlns="http://www.w3.org/1999/xhtml" id="shortattr1" checked="checked" value="" disabled="disabled" ismap="ismap" readonly="readonly" foo:checked="" xmlns:foo="http://mozilla.org/ns/any" foo:disabled="" />';
+ is(out, expected, "test short attr #1");
+
+ node = document.getElementById('shortattr2');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<ol xmlns="http://www.w3.org/1999/xhtml" id="shortattr2" compact="compact"><li></li></ol>';
+ is(out, expected, "test short attr #2");
+
+ node = document.getElementById('shortattr3');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<object xmlns="http://www.w3.org/1999/xhtml" id="shortattr3" declare="declare"></object>';
+ is(out, expected, "test short attr #3");
+
+ node = document.getElementById('shortattr4');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<script xmlns="http://www.w3.org/1999/xhtml" id="shortattr4" defer="defer"></script>';
+ is(out, expected, "test short attr #4");
+
+ node = document.getElementById('shortattr5');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<select xmlns="http://www.w3.org/1999/xhtml" id="shortattr5" multiple="multiple"><option selected="selected">aaa</option></select>';
+ is(out, expected, "test short attr #5");
+
+ node = document.getElementById('shortattr6');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<hr xmlns="http://www.w3.org/1999/xhtml" id="shortattr6" noshade="noshade" />';
+ is(out, expected, "test short attr #6");
+
+ node = document.getElementById('shortattr7');
+ encoder.init(document, "application/xhtml+xml",de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div xmlns="http://www.w3.org/1999/xhtml" id="shortattr7"><foo:bar xmlns:foo="http://mozilla.org/ns/any" checked="" value="" disabled="" ismap="" readonly=""/></div>';
+ is(out, expected, "test short attr #7");
+
+ // test on _moz and -moz attr
+ node = document.getElementById('mozattr');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div id="mozattr" __moz_b="b"> lorem ipsum</div>';
+ is(out, expected, "test -moz/_moz attr");
+
+ node.setAttribute('_moz_c','barc');
+ node.setAttribute('_-moz_d','bard');
+ node.setAttribute('__moz_e','bare');
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div id="mozattr" __moz_b="b" _-moz_d="bard" __moz_e="bare"> lorem ipsum</div>';
+ is(out, expected, "test -moz/_moz attr #2");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlSerializer_1);
+//]]>
+</script>
+</pre>
+<div style="display: none">
+
+<div id="draggable" ondragstart="doDragStartSelection(event)">This is a <em>draggable</em> bit of text.</div>
+
+</div>
+<div style="display: none">
+
+<ol id="aList">
+ <li>Lorem ipsum dolor</li>
+ <li>sit amet, <strong>consectetuer</strong> </li>
+ <li>adipiscing elit</li>
+ <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>
+ <li>aptent taciti</li>
+</ol>
+
+<!-- test for some short attr -->
+<div id="shortattr" xmlns:foo="http://mozilla.org/ns/any">
+ <input id="shortattr1" checked="" value="" disabled="" ismap="" readonly="" foo:checked="" foo:disabled=""/>
+ <ol id="shortattr2" compact=""><li></li></ol>
+ <object id="shortattr3" declare="" />
+ <script id="shortattr4" defer="" />
+ <select id="shortattr5" multiple=""><option selected="">aaa</option></select>
+ <hr id="shortattr6" noshade=""/>
+ <div id="shortattr7"><foo:bar checked="" value="" disabled="" ismap="" readonly="" /></div>
+ <div id="mozattr" _moz_a="a" __moz_b="b"> lorem ipsum</div>
+</div>
+
+</div>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug422537.html b/dom/base/test/test_bug422537.html
new file mode 100644
index 0000000000..f8cd03f111
--- /dev/null
+++ b/dom/base/test/test_bug422537.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=422537
+-->
+<head>
+ <title>Test for bug 422537</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422537">Mozilla Bug 422537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 422537 **/
+var isupports_string = SpecialPowers.Cc["@mozilla.org/supports-string;1"]
+ .createInstance(SpecialPowers.Ci.nsISupportsString);
+isupports_string.data = "foo";
+
+const url = "http://mochi.test:8888";
+var body = [
+ document,
+ "foo",
+ isupports_string
+];
+
+for (var i of body) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", url, true);
+ if (i == isupports_string)
+ SpecialPowers.wrap(xhr).send(i);
+ else
+ xhr.send(i);
+ var chan = SpecialPowers.wrap(xhr).channel;
+ if (!SpecialPowers.call_Instanceof(chan, SpecialPowers.Ci.nsIUploadChannel))
+ throw "Must be an upload channel";
+ var stream = chan.uploadStream;
+ if (!stream || !SpecialPowers.call_Instanceof(stream, SpecialPowers.Ci.nsISeekableStream))
+ throw "Stream must be seekable";
+ // the following is a no-op, but should not throw an exception
+ stream.seek(SpecialPowers.Ci.nsISeekableStream.NS_SEEK_CUR, 0);
+}
+
+ok(true, "xhr is seekable");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug424212.html b/dom/base/test/test_bug424212.html
new file mode 100644
index 0000000000..4203794c01
--- /dev/null
+++ b/dom/base/test/test_bug424212.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=424212
+-->
+<head>
+ <title>Test for Bug 424212</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=424212">Mozilla Bug 424212</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 424212 **/
+var fired = false;
+var xhr = new XMLHttpRequest();
+xhr.open("GET", window.location.href + "?" + Math.random());
+xhr.send("");
+xhr.onreadystatechange = function () {
+ fired = true;
+ is(xhr.status, 0, "Unexpected status");
+}
+xhr.abort();
+is(fired, true, "No onreadystatechange handling?");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug424359-1.html b/dom/base/test/test_bug424359-1.html
new file mode 100644
index 0000000000..870b54438d
--- /dev/null
+++ b/dom/base/test/test_bug424359-1.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for HTML serializer</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=424359">Mozilla Bug </a>
+<p id="display"></p>
+<!-- IMPORTANT: This iframe needs to actually be displayed, so the serializer
+ sees the relevant styles for <pre> elements. -->
+<iframe id="testframe" src="file_htmlserializer_1.html"></iframe>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function loadFileContent(aFile, aCharset) {
+ //if(aAsIso == undefined) aAsIso = false;
+ if(aCharset == undefined)
+ aCharset = 'UTF-8';
+
+ var baseUri = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1']
+ .createInstance(SpecialPowers.Ci.nsIURIMutator)
+ .setSpec(window.location.href)
+ .finalize();
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var cis = SpecialPowers.Ci.nsIConverterInputStream;
+
+ var inputStream = SpecialPowers.Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(cis);
+ inputStream.init(chann.open(), aCharset, 1024, cis.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {}, content = '';
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
+
+function isRoughly(actual, expected, message) {
+ return is(actual.replace("<!DOCTYPE HTML", "<!DOCTYPE html"),
+ expected,
+ message);
+}
+
+function testHtmlSerializer_1 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/html");
+
+ var doc = $("testframe").contentDocument;
+ var out, expected;
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+ //------------ no flags
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_noflag.html");
+ isRoughly(out, expected, "test no flags");
+
+ //------------- unsupported flags
+ // since the following flags are not supported, we should
+ // have a result like the one without flag
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputPreformatted);
+ out = encoder.encodeToString();
+ isRoughly(out, expected, "test OutputPreformatted");
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputFormatFlowed);
+ out = encoder.encodeToString();
+ isRoughly(out, expected, "test OutputFormatFlowed");
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputNoScriptContent);
+ out = encoder.encodeToString();
+ isRoughly(out, expected, "test OutputNoScriptContent");
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputNoFramesContent);
+ out = encoder.encodeToString();
+ isRoughly(out, expected, "test OutputNoFramesContent");
+
+
+ //------------ OutputWrap
+ encoder.init(doc, "text/html", de.OutputLFLineBreak |de.OutputWrap);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_wrap.html");
+ isRoughly(out, expected, "test OutputWrap");
+
+ //------------ OutputFormatted
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputFormatted);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_format.html");
+ isRoughly(out, expected, "test OutputFormatted");
+
+ //------------ OutputRaw
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputRaw);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_raw.html");
+ isRoughly(out, expected, "test OutputRaw");
+
+ //------------ OutputBodyOnly
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputBodyOnly);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_bodyonly.html");
+ isRoughly(out, expected, "test OutputBodyOnly");
+
+
+
+ //------------ OutputAbsoluteLinks
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputAbsoluteLinks);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_links.html").trim('\n');
+ isRoughly(out, expected, "test OutputAbsoluteLinks");
+
+ //------------ OutputLFLineBreak
+ encoder.init(doc, "text/html",de.OutputLFLineBreak);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_linebreak.html");
+ isRoughly(out, expected, "test OutputLFLineBreak");
+
+ //------------ OutputCRLineBreak
+ encoder.init(doc, "text/html",de.OutputCRLineBreak);
+ out = encoder.encodeToString();
+ expected = expected.replace(/\n/mg, "\r");
+ isRoughly(out, expected, "test OutputCRLineBreak");
+
+ //------------ OutputLFLineBreak + OutputCRLineBreak
+ encoder.init(doc, "text/html",de.OutputLFLineBreak | de.OutputCRLineBreak);
+ out = encoder.encodeToString();
+ expected = expected.replace(/\r/mg, "\r\n");
+ isRoughly(out, expected, "test OutputLFLineBreak + OutputCRLineBreak");
+
+ //------------ OutputNoFormattingInPre
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputNoFormattingInPre);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_noformatpre.html");
+ isRoughly(out, expected, "test OutputNoFormattingInPre");
+
+ // ------------- nested body elements
+ var body2 = doc.createElement('body');
+ var p = doc.createElement('p');
+ p.appendChild(doc.createTextNode("this is an other body element"));
+ body2.appendChild(p);
+ var body = doc.getElementsByTagName('body')[0];
+ body.appendChild(body2);
+
+ is(doc.getElementsByTagName('body').length, 2); // to be sure we have two body elements
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_nested_body.html");
+ isRoughly(out, expected, "test with two nested body elements");
+
+ // ------------- two body elements
+ body.parentNode.insertBefore(body2, body);
+
+ is(doc.getElementsByTagName('body').length, 2); // to be sure we have two body elements
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_sibling_body.html");
+ isRoughly(out, expected, "test with two body elements");
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputBodyOnly);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_sibling_body_only_body.html");
+ isRoughly(out, expected, "test with two body elements, and output body only");
+
+ // --------------- no body element
+ doc.documentElement.removeChild(body);
+ doc.documentElement.removeChild(body2);
+
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_1_no_body.html");
+ isRoughly(out, expected, "test with no body element");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlSerializer_1);
+
+</script>
+</pre>
+<!--<h1>1</h1><h2>result</h2><textarea id="t1" cols="80" rows="20"></textarea>
+ <h2>expected</h2><textarea id="t1e" cols="80" rows="20"></textarea>-->
+
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug424359-2.html b/dom/base/test/test_bug424359-2.html
new file mode 100644
index 0000000000..b3ad1bfe5a
--- /dev/null
+++ b/dom/base/test/test_bug424359-2.html
@@ -0,0 +1,301 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test HTML serializer with entities</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=424359">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe" src="file_htmlserializer_2.html">
+ </iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function loadFileContent(aFile, aCharset) {
+ //if(aAsIso == undefined) aAsIso = false;
+ if(aCharset == undefined)
+ aCharset = 'UTF-8';
+
+ var baseUri = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1']
+ .createInstance(SpecialPowers.Ci.nsIURIMutator)
+ .setSpec(window.location.href)
+ .finalize();
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var cis = SpecialPowers.Ci.nsIConverterInputStream;
+
+ var inputStream = SpecialPowers.Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(cis);
+ inputStream.init(chann.open(), aCharset, 1024, cis.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {}, content = '';
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
+
+function isRoughly(actual, expected, message) {
+ return is(actual.replace("<!DOCTYPE HTML", "<!DOCTYPE html"),
+ expected,
+ message);
+}
+
+function testHtmlSerializer_1 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/html");
+
+ var doc = $("testframe").contentDocument;
+ var out, expected;
+
+ // in the following test, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+ //------------ OutputEncodeBasicEntities
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputEncodeBasicEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_2_basic.html");
+ isRoughly(out, expected, "test OutputEncodeBasicEntities");
+
+ // tests on the serialization of selections
+
+ var node = document.getElementById('draggable');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = 'This is a <em>draggable</em> bit of text.';
+ is(out, expected, "test selection");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(null);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = 'This is a <em>draggable</em> bit of text.';
+ is(out, expected, "test container node");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>";
+ is(out, expected, "test node");
+
+ node = document.getElementById('aList');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n';
+ is(out, expected, "test list selection");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(null);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = '\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n';
+ is(out, expected, "test list container node");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<ol id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>";
+ is(out, expected, "test list node");
+
+ var liList = node.getElementsByTagName("li");
+ var range = document.createRange();
+
+ // selection start at the first child of the ol, and end after the element ol
+ range.setStart(node, 1);
+ range.setEnd(node.parentNode, 2);
+ select.removeAllRanges();
+ select.addRange(range);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList"><li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the first child of the ol, and end after the element ol");
+
+ // selection start at the third child of the ol, and end after the element ol
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList"><li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol");
+
+
+ // selection start at the third child of the ol, and end after the element ol + ol start at the value 5
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList" start="5"><li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol + ol start at the value 5");
+
+
+ // selection contains only some child of the ol
+ node.removeAttribute("start");
+ range.setStart(node, 3);
+ range.setEnd(node, 5);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol");
+
+ // selection contains only some child of the ol + ol start at the value 5
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol + ol start at the value 5");
+
+ // selection contains only some child of the ol + a value is set on the first li
+ node.removeAttribute("start");
+ liList[0].setAttribute("value","8");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol + ol start at the value 5");
+
+
+
+ // test on short attributes
+ node = document.getElementById('shortattr1');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<input id="shortattr1" checked="checked" value="" disabled="disabled" ismap="ismap" readonly="readonly" foo="">';
+ is(out, expected, "test short attr #1");
+
+ node = document.getElementById('shortattr2');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<ol id="shortattr2" compact="compact"><li></li></ol>';
+ is(out, expected, "test short attr #2");
+
+ node = document.getElementById('shortattr3');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<object id="shortattr3" declare="declare"></object>';
+ is(out, expected, "test short attr #3");
+
+ node = document.getElementById('shortattr4');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<script id="shortattr4" defer="defer"></'+ 'script>';
+ is(out, expected, "test short attr #4");
+
+ node = document.getElementById('shortattr5');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<select id="shortattr5" multiple="multiple"><option selected="selected">aaa</option></select>';
+ is(out, expected, "test short attr #5");
+
+ node = document.getElementById('shortattr6');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<hr id="shortattr6" noshade="noshade">';
+ is(out, expected, "test short attr #6");
+
+ node = document.getElementById('shortattr7');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div id="shortattr7"><foo checked="" value="" disabled="" ismap="" readonly=""></foo></div>';
+ is(out, expected, "test short attr #7");
+
+ // test on _moz and -moz attr
+ node = document.getElementById('mozattr');
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div id="mozattr" __moz_b="b"> lorem ipsum</div>';
+ is(out, expected, "test -moz/_moz attr");
+
+ node.setAttribute('_moz_c','barc');
+ node.setAttribute('_-moz_d','bard');
+ node.setAttribute('__moz_e','bare');
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = '<div id="mozattr" __moz_b="b" _-moz_d="bard" __moz_e="bare"> lorem ipsum</div>';
+ is(out, expected, "test -moz/_moz attr #2");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlSerializer_1);
+
+</script>
+</pre>
+<div style="display: none">
+
+<div id="draggable" ondragstart="doDragStartSelection(event)">This is a <em>draggable</em> bit of text.</div>
+
+</div>
+<div style="display: none">
+
+<ol id="aList">
+ <li>Lorem ipsum dolor</li>
+ <li>sit amet, <strong>consectetuer</strong> </li>
+ <li>adipiscing elit</li>
+ <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>
+ <li>aptent taciti</li>
+</ol>
+
+
+<!-- test for some short attr -->
+<div id="shortattr">
+ <input id="shortattr1" checked="" value="" disabled="" ismap="" readonly="" foo="">
+ <ol id="shortattr2" compact=""><li></li></ol>
+ <object id="shortattr3" declare=""></object>
+ <script id="shortattr4" defer=""></script>
+ <select id="shortattr5" multiple=""><option selected="">aaa</option></select>
+ <hr noshade="" id="shortattr6">
+ <div id="shortattr7"><foo checked="" value="" disabled="" ismap="" readonly=""></div>
+ <div id="mozattr" _moz_a="a" __moz_b="b"> lorem ipsum</div>
+</div>
+
+
+
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug426308.html b/dom/base/test/test_bug426308.html
new file mode 100644
index 0000000000..6587a6f2f3
--- /dev/null
+++ b/dom/base/test/test_bug426308.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=426308
+-->
+<head>
+ <title>Test for Bug 426308</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=426308">Mozilla Bug 426308</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 426308 **/
+
+const SJS_URL = "http://example.org:80/tests/dom/base/test/bug426308-redirect.sjs";
+
+function startTest() {
+ var req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ req.open("GET", SJS_URL + "?" + window.location.href, false);
+ req.send(null);
+
+ is(req.status, 200, "Redirect did not happen");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug426646.html b/dom/base/test/test_bug426646.html
new file mode 100644
index 0000000000..d5a0a18c01
--- /dev/null
+++ b/dom/base/test/test_bug426646.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=426646
+-->
+<head>
+ <title>Test for Bug 426646</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=426646">Mozilla Bug 426646</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 426646 **/
+
+var testNumber = 0;
+var testCount = 2;
+
+function nextTest() {
+ if (++testNumber <= testCount) {
+ window.open("file_bug426646-" + testNumber + ".html", "", "width=100,height=100");
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(nextTest);
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug428847.html b/dom/base/test/test_bug428847.html
new file mode 100644
index 0000000000..e34eb183bf
--- /dev/null
+++ b/dom/base/test/test_bug428847.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428847
+-->
+<head>
+ <title>Test for Bug 428847</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runtests();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428847">Mozilla Bug 428847</a>
+<script class="testbody" type="text/javascript">
+var iframe1Loaded = false;
+var iframe2Loaded = false;
+
+function runtests()
+{
+ todo(iframe1Loaded, true,
+ "Iframe with cross-origin xslt stylesheet failed to load");
+ is(iframe2Loaded, false,
+ "Iframe with invalid xslt stylesheet URI didn't fail to load");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+<iframe src="file_bug428847-1.xhtml">
+<iframe src="file_bug428847-2.xhtml">
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug431082.html b/dom/base/test/test_bug431082.html
new file mode 100644
index 0000000000..aee6711f29
--- /dev/null
+++ b/dom/base/test/test_bug431082.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=431082
+-->
+<head>
+ <title>Test for Bug 431082</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=431082">Mozilla Bug 431082</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 431082 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() { ok(true, "browser should not crash."); });
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+<!--
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="a" observes="b">
+ <box id="b"/>
+</box>
+
+<box id="a" src="javascript:"/>
+<box id="b" src="javascript://"/>
+<editor observes="a"/>
+
+<script>
+function doe() {
+window.addEventListener('DOMAttrModified', function()
+{window.frameElement.remove();}, true);
+document.documentElement.appendChild(document.getElementsByTagName('box')[0]);
+}
+setTimeout(doe,0);
+</script>
+</window>
+-->
+<iframe src="data:application/xhtml+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%3Cbox%20%20id%3D%22a%22%20observes%3D%22b%22%3E%0A%20%20%3Cbox%20id%3D%22b%22/%3E%0A%3C/box%3E%0A%0A%3Cbox%20%20id%3D%22a%22%20%20src%3D%22javascript%3A%22/%3E%0A%3Cbox%20id%3D%22b%22%20src%3D%22javascript%3A//%22/%3E%0A%3Ceditor%20observes%3D%22a%22/%3E%0A%0A%3Cscript%3E%20%0Afunction%20doe%28%29%20%7B%0Awindow.addEventListener%28%27DOMAttrModified%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0Adocument.documentElement.appendChild%28document.getElementsByTagName%28%27box%27%29%5B0%5D%29%3B%0A%7D%0AsetTimeout%28doe%2C0%29%3B%0A%3C/script%3E%0A%3C/window%3E" style="width:1000px;height: 300px;"></iframe>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug431701.html b/dom/base/test/test_bug431701.html
new file mode 100644
index 0000000000..4009fc017a
--- /dev/null
+++ b/dom/base/test/test_bug431701.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=431701
+-->
+<head>
+ <meta charset="windows-1252">
+ <title>Test for Bug 431701</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=431701">Mozilla Bug 431701</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="one"></iframe>
+ <iframe id="two"></iframe>
+ <iframe id="three"></iframe>
+ <iframe id="four"></iframe>
+ <iframe id="five"></iframe>
+ <iframe id="six"></iframe>
+ <iframe id="seven"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 431701 **/
+SimpleTest.waitForExplicitFinish();
+
+var docSources = [
+ "iframe1_bug431701.html",
+ "iframe2_bug431701.html",
+ "iframe3_bug431701.html",
+ "iframe4_bug431701.xml",
+ "iframe5_bug431701.xml",
+ "iframe6_bug431701.xml",
+ "iframe7_bug431701.xml",
+];
+
+for (let i = 0; i < docSources.length; ++i) {
+ document.getElementsByTagName("iframe")[i].src = docSources[i];
+}
+
+function frameDoc(id) {
+ return function() { return $(id).contentDocument; };
+}
+
+function createDoc() {
+ return document.implementation.createDocument('', 'html', null);
+}
+
+function xhrDoc(idx) {
+ return function() {
+ // Defy same-origin restrictions!
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open("GET", docSources[idx], false);
+ xhr.send();
+ return xhr.responseXML;
+ };
+}
+
+// Each row has the document getter function, then the characterSet,
+// inputEncoding expected for that document.
+
+var tests = [
+ [ frameDoc("one"), "windows-1252" ],
+ [ frameDoc("two"), "UTF-8" ],
+ [ frameDoc("three"), "windows-1252" ],
+ [ frameDoc("four"), "UTF-8" ],
+ [ frameDoc("five"), "UTF-8" ],
+ [ frameDoc("six"), "UTF-8" ],
+ [ frameDoc("seven"), "windows-1252" ],
+ [ createDoc, "UTF-8" ],
+ [ xhrDoc(4), "UTF-8" ],
+ [ xhrDoc(5), "UTF-8" ],
+ [ xhrDoc(6), "windows-1252" ],
+];
+
+function doTest(idx) {
+ var [docGetter, expectedCharacterSet] = tests[idx];
+ var doc = docGetter();
+
+ // Have to be careful here to catch null vs ""
+ is(doc.characterSet, expectedCharacterSet, "Test " + idx + " characterSet");
+ is(doc.inputEncoding, expectedCharacterSet,
+ "Test " + idx + " inputEncoding");
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+
+function startTest() {
+ // sanity check
+ isnot("", null, "Shouldn't be equal!");
+
+ for (let i = 0; i < tests.length; ++i) {
+ doTest(i);
+ }
+
+ // Now check what xhr does
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", document.location.href);
+ xhr.send(createDoc());
+ is(SpecialPowers.wrap(xhr).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel)
+ .getRequestHeader("Content-Type"),
+ "application/xml;charset=UTF-8", "Testing correct type on the wire");
+ xhr.abort();
+
+ SimpleTest.finish();
+};
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug431833.html b/dom/base/test/test_bug431833.html
new file mode 100644
index 0000000000..131b7d75a8
--- /dev/null
+++ b/dom/base/test/test_bug431833.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=431833
+-->
+<head>
+ <title>Test for Bug 431833</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+ var loadsComplete = [];
+ function test(e) {
+ loadsComplete[e.target.id] = true;
+ }
+ window.addEventListener('DOMFrameContentLoaded',test,true);
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=431833">Mozilla Bug 431833</a>
+<p id="display">
+ <iframe id="f1" srcdoc="1"></iframe>
+ <iframe id="f2" srcdoc="2"></iframe>
+ <iframe id="f3" srcdoc="<iframe id='f4' src='data:text/html,3'></iframe>"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 431833 **/
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ function check(id) {
+ ok(loadsComplete[id], "DOMFrameContentLoaded didn't fire for " + id);
+ }
+ check("f1");
+ check("f2");
+ check("f3");
+ check("f4");
+ });
+
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug433533.html b/dom/base/test/test_bug433533.html
new file mode 100644
index 0000000000..12dd138109
--- /dev/null
+++ b/dom/base/test/test_bug433533.html
@@ -0,0 +1,300 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=433533
+-->
+<head>
+ <title>Test for Bug 433533</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=433533">Mozilla Bug 433533</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 433533 **/
+
+var input = document.createElement("input");
+input.setAttribute("type", "hidden");
+is(input.getAttribute("type"), "hidden", "Setting type attribute didn't work!");
+input.setAttribute("type", "hiDDen");
+is(input.getAttribute("type"), "hiDDen", "Type attribute didn't store the original value");
+is(input.type, "hidden", "Wrong input.type!");
+input.setAttribute("type", "HIDDEN");
+is(input.getAttribute("type"), "HIDDEN", "Type attribute didn't store the original value");
+is(input.type, "hidden", "Wrong input.type!");
+
+var td = document.createElement("td");
+td.setAttribute("scope", "rOW");
+is(td.getAttribute("scope"), "rOW", "Scope attribute didn't store the original value");
+td.setAttribute("scope", "row");
+is(td.getAttribute("scope"), "row", "Scope attribute didn't store the original value");
+td.setAttribute("colspan", "100k");
+is(td.getAttribute("colspan"), "100k", "Colspan attribute didn't store the original value");
+td.setAttribute("colspan", " 100 ");
+is(td.getAttribute("colspan"), " 100 ", "Colspan attribute didn't store the original value");
+td.setAttribute("colspan", "100");
+is(td.getAttribute("colspan"), "100", "Colspan attribute didn't store the original value");
+
+// Note, if colspan is negative, it is set to 1, because of backwards compatibility.
+// @see nsHTMLTableCellElement::ParseAttribute
+td.setAttribute("colspan", "-100k");
+is(td.getAttribute("colspan"), "-100k", "Colspan attribute didn't store the original value");
+td.setAttribute("colspan", " -100 ");
+is(td.getAttribute("colspan"), " -100 ", "Colspan attribute didn't store the original value");
+is(td.colSpan, 1, "Colspan reflection should be correct for ' -100 '");
+td.setAttribute("colspan", "-100");
+is(td.getAttribute("colspan"), "-100", "Colspan attribute didn't store the original value");
+is(td.colSpan, 1, "Colspan reflection should be correct for '-100'");
+
+
+td.setAttribute("colspan", "foobar");
+is(td.getAttribute("colspan"), "foobar", "Colspan attribute didn't store the original value");
+
+var iframe = document.createElement("iframe");
+iframe.setAttribute("marginwidth", "50%");
+is(iframe.getAttribute("marginwidth"), "50%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "50");
+is(iframe.getAttribute("marginwidth"), "50",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "0");
+is(iframe.getAttribute("marginwidth"), "0",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "0%");
+is(iframe.getAttribute("marginwidth"), "0%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "9999999999999999999999");
+is(iframe.getAttribute("marginwidth"), "9999999999999999999999",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "9999999999999999999999%");
+is(iframe.getAttribute("marginwidth"), "9999999999999999999999%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "-9999999999999999999999");
+is(iframe.getAttribute("marginwidth"), "-9999999999999999999999",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-9999999999999999999999%");
+is(iframe.getAttribute("marginwidth"), "-9999999999999999999999%",
+ "Marginwidth attribute didn't store the original value");
+
+
+// Test PRInt32 min/max value
+iframe.setAttribute("marginwidth", "2147483647");
+is(iframe.getAttribute("marginwidth"), "2147483647",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "2147483647%");
+is(iframe.getAttribute("marginwidth"), "2147483647%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "-2147483648");
+is(iframe.getAttribute("marginwidth"), "-2147483648",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-2147483648%");
+is(iframe.getAttribute("marginwidth"), "-2147483648%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "2147483646");
+is(iframe.getAttribute("marginwidth"), "2147483646",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "2147483647%");
+is(iframe.getAttribute("marginwidth"), "2147483647%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "-2147483647");
+is(iframe.getAttribute("marginwidth"), "-2147483647",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-2147483647%");
+is(iframe.getAttribute("marginwidth"), "-2147483647%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "2147483648");
+is(iframe.getAttribute("marginwidth"), "2147483648",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "2147483648%");
+is(iframe.getAttribute("marginwidth"), "2147483648%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "-2147483649");
+is(iframe.getAttribute("marginwidth"), "-2147483649",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-2147483649%");
+is(iframe.getAttribute("marginwidth"), "-2147483649%",
+ "Marginwidth attribute didn't store the original value");
+
+// some values 0 > x > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE
+iframe.setAttribute("marginwidth", "134217726");
+is(iframe.getAttribute("marginwidth"), "134217726",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217727");
+is(iframe.getAttribute("marginwidth"), "134217727",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217728");
+is(iframe.getAttribute("marginwidth"), "134217728",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217729");
+is(iframe.getAttribute("marginwidth"), "134217729",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "134217726%");
+is(iframe.getAttribute("marginwidth"), "134217726%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217727%");
+is(iframe.getAttribute("marginwidth"), "134217727%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217728%");
+is(iframe.getAttribute("marginwidth"), "134217728%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "134217729%");
+is(iframe.getAttribute("marginwidth"), "134217729%",
+ "Marginwidth attribute didn't store the original value");
+
+// some values 0 < x < NS_ATTRVALUE_INTEGERTYPE_MINVALUE
+iframe.setAttribute("marginwidth", "-134217727");
+is(iframe.getAttribute("marginwidth"), "-134217727",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217728");
+is(iframe.getAttribute("marginwidth"), "-134217728",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217729");
+is(iframe.getAttribute("marginwidth"), "-134217729",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217730");
+is(iframe.getAttribute("marginwidth"), "-134217730",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217727%");
+is(iframe.getAttribute("marginwidth"), "-134217727%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217728%");
+is(iframe.getAttribute("marginwidth"), "-134217728%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217729%");
+is(iframe.getAttribute("marginwidth"), "-134217729%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-134217730%");
+is(iframe.getAttribute("marginwidth"), "-134217730%",
+ "Marginwidth attribute didn't store the original value");
+
+iframe.setAttribute("marginwidth", "-0");
+is(iframe.getAttribute("marginwidth"), "-0",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-0%");
+is(iframe.getAttribute("marginwidth"), "-0%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", " 0 ");
+is(iframe.getAttribute("marginwidth"), " 0 ",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", " 0% ");
+is(iframe.getAttribute("marginwidth"), " 0% ",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-50%");
+is(iframe.getAttribute("marginwidth"), "-50%",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "-50");
+is(iframe.getAttribute("marginwidth"), "-50",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", " -50% ");
+is(iframe.getAttribute("marginwidth"), " -50% ",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", " -50 ");
+is(iframe.getAttribute("marginwidth"), " -50 ",
+ "Marginwidth attribute didn't store the original value");
+iframe.setAttribute("marginwidth", "foobar");
+is(iframe.getAttribute("marginwidth"), "foobar",
+ "Marginwidth attribute didn't store the original value");
+
+var bd = document.createElement("body");
+bd.setAttribute("bgcolor", "red");
+is(bd.getAttribute("bgcolor"), "red", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "red", ".bgColor didn't return the right value!");
+
+bd.setAttribute("bgcolor", " red ");
+is(bd.getAttribute("bgcolor"), " red ", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, " red ", ".bgColor didn't return the right value!");
+
+bd.setAttribute("bgcolor", "#ff0000");
+is(bd.getAttribute("bgcolor"), "#ff0000", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "#ff0000", ".bgColor didn't return the right value!");
+
+bd.setAttribute("bgcolor", "#f00");
+is(bd.getAttribute("bgcolor"), "#f00", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "#f00", ".bgColor didn't return the right value!");
+
+bd.setAttribute("bgcolor", " #ff0000 ");
+is(bd.getAttribute("bgcolor"), " #ff0000 ", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, " #ff0000 ", ".bgColor didn't return the right value!");
+
+bd.setAttribute("bgcolor", "nonsense(complete)");
+is(bd.getAttribute("bgcolor"), "nonsense(complete)", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "nonsense(complete)", ".bgColor didn't return the right value!");
+
+// same test again setting the prop
+bd.bgColor = "red";
+is(bd.getAttribute("bgcolor"), "red", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "red", ".bgColor didn't return the right value!");
+
+bd.bgColor = " red ";
+is(bd.getAttribute("bgcolor"), " red ", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, " red ", ".bgColor didn't return the right value!");
+
+bd.bgColor = "#ff0000";
+is(bd.getAttribute("bgcolor"), "#ff0000", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "#ff0000", ".bgColor didn't return the right value!");
+
+bd.bgColor = "#f00";
+is(bd.getAttribute("bgcolor"), "#f00", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "#f00", ".bgColor didn't return the right value!");
+
+bd.bgColor = " #ff0000 ";
+is(bd.getAttribute("bgcolor"), " #ff0000 ", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, " #ff0000 ", ".bgColor didn't return the right value!");
+
+bd.bgColor = "nonsense(complete)";
+is(bd.getAttribute("bgcolor"), "nonsense(complete)", "Bgcolor attribute didn't store the original value");
+is(bd.bgColor, "nonsense(complete)", ".bgColor didn't return the right value!");
+
+// equal color, unequal string
+var f1 = document.createElement("font");
+var f2 = document.createElement("font");
+var f3 = document.createElement("font");
+f1.color = "#f00";
+f2.color = "#ff0000";
+f3.color = "red";
+isnot(f1.color, f2.color, "#f00 and #ff0000 should not compare equal");
+isnot(f1.color, f3.color, "#f00 and red should not compare equal");
+isnot(f2.color, f3.color, "#ff0000 and red should not compare equal");
+
+isnot(f1.getAttribute("color"), f2.getAttribute("color"),
+ "#f00 and #ff0000 should not compare equal [attr]");
+isnot(f1.getAttribute("color"), f3.getAttribute("color"),
+ "#f00 and red should not compare equal [attr]");
+isnot(f2.getAttribute("color"), f3.getAttribute("color"),
+ "#ff0000 and red should not compare equal [attr]");
+
+var video = document.createElement("video");
+video.setAttribute("playbackrate", "1");
+is(video.getAttribute('playbackrate'), "1",
+ "Playbackrate attribute didn't store the original value");
+video.setAttribute("playbackrate", "1.5");
+is(video.getAttribute('playbackrate'), "1.5",
+ "Playbackrate attribute didn't store the original value");
+video.setAttribute("playbackrate", "999999999999999999");
+is(video.getAttribute('playbackrate'), "999999999999999999",
+ "Playbackrate attribute didn't store the original value");
+video.setAttribute("playbackrate", "-999999999999999999");
+is(video.getAttribute('playbackrate'), "-999999999999999999",
+ "Playbackrate attribute didn't store the original value");
+video.setAttribute("playbackrate", "foo");
+is(video.getAttribute('playbackrate'), "foo",
+ "Playbackrate attribute didn't store the original value");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug433662.html b/dom/base/test/test_bug433662.html
new file mode 100644
index 0000000000..ec30775a21
--- /dev/null
+++ b/dom/base/test/test_bug433662.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=433662
+-->
+<title>Test for Bug 433662</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=433662">Mozilla Bug 433662</a>
+<script>
+
+/** Test for Bug 433662 **/
+var range = document.createRange();
+range.setStart(document.body, 0);
+range.insertNode(document.createComment("abc"));
+is(document.body.firstChild.nodeType, Node.COMMENT_NODE,
+ "Comment must be inserted (start of node)");
+is(document.body.firstChild.nodeValue, "abc",
+ "Comment must have right contents (start of node)");
+is(range.endOffset, 1,
+ "insertNode() needs to include the newly-added node (start of node)");
+
+range.setStart(document.body, document.body.childNodes.length);
+range.insertNode(document.createComment("def"));
+is(document.body.lastChild.nodeType, Node.COMMENT_NODE,
+ "Comment must be inserted (end of node)");
+is(document.body.lastChild.nodeValue, "def",
+ "Comment must have right contents (end of node)");
+is(range.endOffset, document.body.childNodes.length,
+ "insertNode() needs to include the newly-added node (end of node)");
+
+</script>
diff --git a/dom/base/test/test_bug435425.html b/dom/base/test/test_bug435425.html
new file mode 100644
index 0000000000..beb97e0776
--- /dev/null
+++ b/dom/base/test/test_bug435425.html
@@ -0,0 +1,430 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435425
+-->
+<head>
+ <title>Test for Bug 435425</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435425">Mozilla Bug 435425</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 435425 **/
+
+var upload = null;
+var currentEvents = null;
+var expectedResponseText = null;
+var uploadTotal = 0;
+var currentProgress = -1;
+
+function logEvent(evt) {
+ var i = 0;
+ while ((currentEvents.length != i) &&
+ currentEvents[i].optional &&
+ ((currentEvents[i].type != evt.type) ||
+ !(evt.target instanceof currentEvents[i].target))) {
+ ++i;
+ }
+
+ if (evt.target instanceof XMLHttpRequestUpload) {
+ if (evt.type == "loadstart") {
+ uploadTotal = evt.total
+ } else {
+ if (evt.type == "progress") {
+ is(evt.lengthComputable, evt.total != 0, "event(" + evt.type + ").lengthComputable should be " + (evt.total != 0) + ".");
+ }
+ if (evt.total != uploadTotal && evt.total != 0) {
+ ok(false, "event(" + evt.type + ").total should not change during upload except to become 0 on error/abort/timeout.");
+ }
+ }
+ }
+
+ // There can be any number of repeated progress events, so special-case this.
+ if (evt.type == "progress") {
+ // Progress events can repeat, but their "loaded" value must increase.
+ if (currentProgress >= 0) {
+ ok(currentProgress < evt.loaded, "Progress should increase, got " +
+ evt.loaded + " after " + currentProgress);
+ currentProgress = evt.loaded;
+ return; // stay at the currentEvent, since we got progress instead of it.
+ }
+ // Starting a new progress event group.
+ currentProgress = evt.loaded;
+ } else {
+ // Reset the progress indicator on any other event type.
+ currentProgress = -1;
+ }
+
+ ok(i != currentEvents.length, "Extra or wrong event?");
+ is(evt.type, currentEvents[i].type, "Wrong event!")
+ ok(evt.target instanceof currentEvents[i].target,
+ "Wrong event target [" + evt.target + "," + evt.type + "]!");
+
+ // If we handled non-optional event, remove all optional events before the
+ // handled event and then the non-optional event from the list.
+ if (!currentEvents[i].optional) {
+ for (;i != -1; --i) {
+ currentEvents.shift();
+ }
+ }
+}
+
+function hasPendingNonOptionalEvent(ev) {
+ var i = 0;
+ while (i < currentEvents.length) {
+ if (!currentEvents[i].optional && currentEvents[i].type == ev)
+ return true;
+ ++i;
+ }
+ return false;
+}
+
+function maybeStop(evt) {
+ logEvent(evt);
+ if (!hasPendingNonOptionalEvent("loadend"))
+ nextTest();
+}
+
+function openXHR(xhr, method, url, privileged) {
+ if (privileged)
+ SpecialPowers.wrap(xhr).open(method, url);
+ else
+ xhr.open(method, url);
+}
+
+function start(obj) {
+ let xhr = new XMLHttpRequest();
+ upload = xhr.upload;
+ currentEvents = obj.expectedEvents;
+ expectedResponseText = obj.withUpload;
+ currentProgress = -1;
+ xhr.onload =
+ function(evt) {
+ if (expectedResponseText) {
+ is(evt.target.responseText, expectedResponseText, "Wrong responseText");
+ }
+ logEvent(evt);
+ }
+ xhr.onerror =
+ function(evt) {
+ logEvent(evt);
+ }
+ xhr.onabort =
+ function(evt) {
+ logEvent(evt);
+ }
+ xhr.onloadend =
+ function (evt) {
+ maybeStop(evt);
+ }
+ xhr.onloadstart =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.onprogress =
+ function (evt) {
+ logEvent(evt);
+ }
+
+ if ("upload" in xhr) {
+ xhr.upload.onloadstart =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.upload.onprogress =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.upload.onload =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.upload.onerror =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.upload.onabort =
+ function (evt) {
+ logEvent(evt);
+ }
+ xhr.upload.onloadend =
+ function (evt) {
+ maybeStop(evt);
+ }
+ }
+
+ try {
+ var methodIsGet = (obj.method == "GET");
+ var url;
+ var privileged = false;
+ if (obj.testRedirectError) {
+ url = "bug435425_redirect.sjs";
+ } else if (obj.testNetworkError) {
+ url = "http://nosuchdomain.localhost";
+ privileged = true;
+ } else {
+ url = "bug435425.sjs";
+ if (obj.withUpload && methodIsGet) {
+ url += "?" + obj.withUpload;
+ }
+ }
+ openXHR(xhr, obj.method, url, privileged);
+ xhr.send(!methodIsGet ? obj.withUpload : null);
+ if (obj.testAbort) {
+ xhr.abort();
+ }
+ } catch (ex) {
+ ok(false, ex);
+ }
+}
+
+var none = null;
+var small = "";
+var mid = "";
+var large = "";
+
+
+for (var smallLength = 0; smallLength < (0x00000000 + 2); ++smallLength) {
+ small += "A";
+}
+
+for (var midLength = 0; midLength < (0x00000FFF + 2); ++midLength) {
+ mid += "A";
+}
+
+for (var largeLength = 0; largeLength < (0x0000FFFF + 2); ++largeLength) {
+ large += "A";
+}
+
+const XHR = XMLHttpRequest;
+const UPLOAD = XMLHttpRequestUpload;
+
+var tests =
+ [
+ { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: false},
+ {target: UPLOAD, type: "load", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "abort", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: false},
+ {target: UPLOAD, type: "load", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "abort", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+
+ { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: false},
+ {target: UPLOAD, type: "load", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: false},
+ {target: XHR, type: "load", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "abort", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "progress", optional: true},
+ {target: XHR, type: "abort", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "progress", optional: true},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+ { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
+ expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "loadstart", optional: false},
+ {target: UPLOAD, type: "error", optional: false},
+ {target: UPLOAD, type: "loadend", optional: false},
+ {target: XHR, type: "error", optional: false},
+ {target: XHR, type: "loadend", optional: false}]},
+];
+
+function runTest() {
+ var test = tests.shift();
+ start(test);
+}
+
+function nextTest() {
+ if (tests.length) {
+ setTimeout("runTest()", 0);
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+ok("upload" in (new XMLHttpRequest()), "XMLHttpRequest.upload isn't supported!");
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug444322.html b/dom/base/test/test_bug444322.html
new file mode 100644
index 0000000000..e9254d1015
--- /dev/null
+++ b/dom/base/test/test_bug444322.html
@@ -0,0 +1,2588 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Firefox 3 Bug #444322 example</title>
+
+<script type="text/javascript" src="444322.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script type="text/javascript">
+function getFile(url) {
+ if (window.XMLHttpRequest) {
+ AJAX=new XMLHttpRequest();
+ } else {
+ AJAX=new ActiveXObject("Microsoft.XMLHTTP");
+ }
+ if (AJAX) {
+ AJAX.open("GET", url, false);
+ AJAX.send(null);
+ return AJAX.responseText;
+ } else {
+ return false;
+ }
+}
+var text = getFile("444322.txt");
+var x = 'scott';
+</script>
+ </head>
+<body>
+<p id="display"></p>
+<p>This page demonstrates the bug described in <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=444322#c24" target="_blank">Bugzilla #444322</a>.</p>
+<p>In the &lt;head&gt; section of the document, the global var x is set to "scott". In the &lt;body&gt;, a Javascript statement writes the value of x to the screen.</p>
+<p>The expected output is: <b>scott</b>.</p>
+<p>The actual output is: <b>
+ <script type="text/javascript">document.write(x); is(x, "scott", "x is correct")</script></b></p>
+<p>This shows that the Javascript in the &lt;head&gt; is not completing before the script in the &lt;body&gt; is executed.</p>
+<p>For this bug to occur, the following circumstances must be met:
+ <ol>
+ <li>An external Javascript file must be loaded on the page. It doesn't matter what code is in the file, and it can in fact be blank as test.js is in this example.
+ <li>A synchronous AJAX call must be made in the &lt;head&gt;. Again, the file being loaded can be empty. An alert() call will also work.
+ <li>The size of the .html (or whatever) file must be relatively large -- in our tests anything above 169,609 bytes did the trick (hence the large amount of text following this explanation).
+ </ol>
+<p>On some occasions we saw the issue <i>not</i> occur if the page was not cached or if it was refreshed, but it would then occur if the page was hit again from a link or by pressing enter in the location bar. This example seems to cause the bug whether the page is cached or not.</p>
+<br />
+<hr />
+<br />
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras ac velit sed tellus facilisis euismod. Proin vel nulla vel turpis tristique dignissim. Donec lacus ipsum, eleifend ut, volutpat a, ultrices adipiscing, arcu. Etiam ligula dolor, adipiscing ut, porta vitae, bibendum non, dolor. Mauris ligula. Sed placerat tincidunt elit. Vestibulum non libero. Curabitur cursus tortor id sem. Integer consectetuer auctor lacus. Proin nisl nisi, pulvinar eget, pharetra at, aliquam eu, velit. Morbi fringilla. Quisque faucibus, mauris posuere vulputate interdum, lectus libero sollicitudin tellus, sit amet ultrices enim purus ac mauris. Pellentesque sit amet mauris eu ante aliquet egestas. Mauris dapibus, velit consectetuer tristique luctus, enim augue pulvinar libero, fringilla dictum lectus felis eu ligula. In ac lorem.<br /><br />
+
+Integer laoreet. Ut ultricies arcu nec est. Aenean varius nisl ut odio. Nullam arcu. Vestibulum non pede. Proin vel est. Nam condimentum fermentum dui. Donec at arcu. Donec at libero adipiscing odio mattis dapibus. Suspendisse libero neque, faucibus sed, facilisis et, convallis sit amet, justo. Duis purus tortor, ornare ac, convallis ut, pretium et, tellus. Nam accumsan, ipsum eget accumsan mollis, sapien dolor adipiscing metus, id tincidunt ipsum metus sed nulla. Praesent hendrerit lectus eget tortor. Morbi id lectus et elit ultrices hendrerit. Cras gravida velit sed mauris. Proin lacinia tempus est. Sed sapien tortor, fringilla vel, elementum in, volutpat ac, ante. Vivamus eu tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Mauris in sem ac felis pretium placerat. Donec tempus cursus sem. Aliquam scelerisque porttitor sem. Curabitur consectetuer, pede vitae aliquam aliquet, sapien lacus vehicula neque, ut rhoncus nibh neque sed velit. In rhoncus, nulla eu dignissim egestas, diam nibh hendrerit mauris, condimentum laoreet sapien arcu quis mi. Sed euismod sem. Nulla non ligula sed lacus tempor molestie. Quisque varius. In hac habitasse platea dictumst. Sed felis ipsum, consequat et, blandit vitae, tincidunt id, quam. Nunc nunc. Duis gravida. In massa neque, cursus quis, rutrum sed, semper quis, erat. Donec enim. Suspendisse condimentum eros vel elit. Vestibulum adipiscing erat id lorem. Maecenas enim dui, cursus a, pulvinar ac, rutrum sed, sem. Suspendisse gravida ante vel lectus.<br /><br />
+
+Vestibulum molestie, ante at dignissim venenatis, pede urna dictum arcu, vel ullamcorper ligula eros eget metus. Pellentesque nec nisl. Morbi ut nibh. Aenean mauris. Mauris rutrum justo nec velit. Nunc condimentum tortor id augue. Quisque semper massa eget nibh. Maecenas ac odio pretium lorem tincidunt faucibus. Sed congue. Cras sit amet orci ut ligula cursus congue. Etiam laoreet lacus sit amet tortor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus accumsan. Ut gravida urna hendrerit leo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.<br /><br />
+
+Proin viverra augue in felis. Mauris sed neque. Proin libero. Donec elementum fermentum lacus. Nam et tortor eu purus porta interdum. Suspendisse eget erat et massa vehicula accumsan. Aliquam est. In sollicitudin sapien a tellus. Sed placerat tellus non velit. Nulla facilisi. Donec quam urna, tempus et, egestas ac, pretium ac, risus. Sed venenatis tempor dui. Nam condimentum, erat id fermentum pretium, ante ligula bibendum lorem, accumsan viverra dui augue a pede.<br /><br />
+
+Nulla est dui, mattis id, scelerisque eu, hendrerit ut, tellus. Aliquam rhoncus. Vivamus lacinia tortor id justo. Pellentesque id nisi eget sem luctus venenatis. Nunc dolor. Aliquam consectetuer metus ac odio. Sed congue. Vivamus risus eros, bibendum et, congue quis, hendrerit vel, purus. Curabitur sed massa ut augue feugiat imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec enim orci, convallis sit amet, semper sed, vehicula at, turpis.<br /><br />
+
+Nam quam mauris, iaculis eget, suscipit vel, laoreet eu, dui. Duis leo. Aenean sit amet nunc a metus fermentum ornare. In et est. Vestibulum vitae tortor. Nunc non risus. Nulla ullamcorper nulla nec eros. Ut mi neque, dapibus at, semper ut, faucibus faucibus, ligula. Suspendisse lectus lacus, consectetuer a, imperdiet id, eleifend quis, nibh. Vestibulum sit amet sem. Nam auctor feugiat augue. Nullam non nulla vitae mi ornare aliquet. In mollis. Pellentesque ac pede. Suspendisse placerat tellus pharetra augue. Sed massa magna, scelerisque non, lobortis ac, rhoncus in, purus. Vestibulum vitae quam vel est tristique fringilla. Fusce laoreet interdum mauris.<br /><br />
+
+Cras lectus elit, pharetra ut, iaculis vel, mattis consectetuer, orci. Duis ut justo vitae massa scelerisque accumsan. Morbi et ipsum nec dolor tincidunt gravida. Donec ac sem. Pellentesque dictum erat. Vestibulum lobortis lorem vel nisi. Suspendisse potenti. Cras fermentum est a sem. Nunc suscipit, velit ac vestibulum aliquet, nulla orci fringilla lectus, ut imperdiet odio nunc nec ligula. Integer nisl lacus, interdum sit amet, tempor vitae, ultricies id, elit. Nam augue turpis, adipiscing at, vestibulum ut, vehicula vitae, urna. In hac habitasse platea dictumst. Suspendisse arcu ligula, imperdiet eu, vestibulum non, posuere id, enim. Nullam ornare erat at orci. Quisque eget purus. Nunc ultrices felis aliquam ipsum. Proin gravida. Sed ipsum.<br /><br />
+
+Sed vehicula vehicula erat. Sed dui nulla, adipiscing a, ultricies sed, lacinia eget, justo. Sed nisi justo, gravida nec, placerat volutpat, sollicitudin eu, sapien. In orci. Nam convallis neque vitae eros. Curabitur mattis lectus eget tortor. Donec neque. Proin dui pede, ultrices hendrerit, volutpat nec, adipiscing ac, urna. Fusce aliquam condimentum felis. Etiam sodales varius risus. Nulla nisl diam, pharetra sit amet, vestibulum nec, tincidunt hendrerit, neque. Nullam nunc. Curabitur pellentesque, lacus at lobortis vehicula, erat lectus commodo enim, ut aliquet orci felis eget eros. Donec a augue sit amet lacus pharetra semper. Praesent porta dignissim nunc. Suspendisse ut leo at sapien convallis malesuada. Proin posuere interdum massa. Pellentesque mollis, dolor ut tincidunt euismod, nunc tellus adipiscing lectus, nec volutpat nulla nulla ac nulla.<br /><br />
+
+Curabitur eu ante. Pellentesque nulla diam, feugiat nec, suscipit eget, blandit vel, odio. Cras ullamcorper metus vel lectus. Fusce pulvinar. Etiam convallis adipiscing ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum felis. Donec euismod purus sit amet dui. Ut iaculis. Curabitur non ipsum. Mauris augue. Proin lacinia. Suspendisse potenti. Suspendisse feugiat sodales leo. Aliquam erat volutpat. Mauris fermentum nisi non nisi. Aliquam velit. Donec iaculis, justo sit amet tempus iaculis, sapien nulla congue orci, a elementum massa sem non velit.<br /><br />
+
+Etiam quis urna quis eros dapibus aliquam. Donec non risus. Curabitur pretium. Suspendisse fermentum ligula eu augue. Curabitur sollicitudin. Pellentesque porta mauris. In hac habitasse platea dictumst. Nullam cursus, arcu eu malesuada porta, magna lacus ultricies eros, sed lacinia erat justo vel nibh. Etiam ultricies placerat sem. Pellentesque nec erat. Etiam augue.<br /><br />
+
+Quisque odio. Nullam eu libero in augue convallis pellentesque. Duis placerat. Curabitur porta leo eu orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean justo. Nam vehicula. Vivamus ante elit, iaculis a, rhoncus vel, interdum et, libero. Fusce in sem scelerisque libero vehicula rhoncus. Sed vitae nibh. Curabitur molestie dictum magna. Quisque tristique purus vel ante. Fusce et erat. Phasellus erat. Quisque pellentesque. Integer velit lacus, pretium et, auctor vel, molestie malesuada, purus.<br /><br />
+
+Morbi purus enim, gravida vel, elementum et, aliquam in, ante. Nam eget massa. Donec quam diam, posuere at, volutpat sit amet, laoreet eu, tellus. Sed eu ipsum et nisi porta ullamcorper. In hac habitasse platea dictumst. Sed non dolor nec lorem hendrerit porta. Etiam sollicitudin ornare sapien. Pellentesque a mi. Mauris porttitor velit vel felis. Duis est. Donec sollicitudin. Cras vel justo adipiscing ligula bibendum pellentesque. Maecenas justo dolor, porttitor et, malesuada in, dictum sit amet, leo. Praesent molestie porta nibh. Aliquam facilisis.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eget nisl. Curabitur libero nibh, iaculis non, vehicula non, gravida sit amet, augue. Praesent feugiat massa id ligula. Fusce non orci. Suspendisse fringilla dictum est. Nulla condimentum, risus sit amet accumsan fringilla, eros orci tristique risus, a sagittis ligula dolor in metus. Nunc sem dolor, sodales ac, tempor nec, commodo a, sapien. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin viverra nulla placerat est. Suspendisse at lectus. Proin tristique, nulla vitae tincidunt elementum, nisi urna pellentesque dui, nec egestas urna lacus ac nibh.<br /><br />
+
+Morbi et nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur molestie. Cras mauris dui, fringilla ut, aliquam semper, condimentum vitae, tellus. Aliquam in nibh nec justo porta viverra. Duis consectetuer mi in nunc. Duis ac felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur eros tortor, ultricies id, accumsan ut, ultrices ac, nisi. Fusce elementum pede id nisi. Mauris venenatis nibh quis enim.<br /><br />
+
+Pellentesque sed odio a urna iaculis facilisis. Ut placerat faucibus nibh. Maecenas turpis pede, aliquet a, condimentum nec, dignissim et, nisl. Vivamus ac nulla. Nulla facilisi. Nam at lorem a ligula consequat semper. Nulla facilisi. Phasellus non nulla non diam faucibus blandit. Cras viverra, leo sit amet commodo dictum, enim ipsum scelerisque purus, ac malesuada ligula ligula ut metus. Ut vel nunc. Phasellus euismod ipsum et sem. Quisque luctus pretium arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras adipiscing viverra diam. Aenean sed ipsum id felis condimentum porta. Quisque eros dolor, fringilla ac, scelerisque ac, placerat eu, tortor.<br /><br />
+
+Quisque aliquet, mi vulputate dignissim molestie, orci diam aliquam nulla, commodo euismod neque arcu eu enim. Sed sed mi. Donec scelerisque tincidunt arcu. Nunc augue elit, cursus id, ultrices ut, lobortis id, nibh. Quisque nulla sem, scelerisque sed, convallis ut, aliquam a, purus. Proin nulla nunc, scelerisque in, aliquet vel, gravida eu, pede. Donec eget nunc eu tortor adipiscing imperdiet. Vestibulum eu quam id lacus hendrerit ornare. In hac habitasse platea dictumst. Sed molestie mollis mauris. Nunc porta.<br /><br />
+
+Maecenas condimentum ipsum eget elit. Donec sit amet mi. Nulla non neque in quam interdum lobortis. Suspendisse potenti. Cras orci dui, eleifend ut, ultrices at, volutpat at, orci. Mauris justo erat, pharetra vel, molestie a, varius ut, dolor. Etiam feugiat lacus id est. Vivamus lobortis, justo a cursus ultricies, turpis tellus tincidunt tortor, nec dignissim turpis nisl vel pede. Etiam eu turpis. Donec eget justo. Aenean gravida elit eget quam. Proin commodo, ligula sed mattis accumsan, risus erat pulvinar lorem, vitae consectetuer arcu magna in risus. Vivamus nulla. Suspendisse egestas nisl quis urna. Ut quis eros. Mauris ligula velit, aliquet non, venenatis et, rhoncus vitae, lectus. Nam ornare quam a augue.<br /><br />
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut eros magna, rhoncus et, condimentum in, scelerisque commodo, ipsum. Nulla hendrerit, est a varius ornare, velit pede condimentum magna, eu rhoncus odio diam at sem. Sed cursus euismod libero. Maecenas sit amet mauris. Sed egestas ante vitae metus. Nulla lacus. In arcu ante, ultrices quis, suscipit ac, sagittis eu, neque. Duis laoreet. Maecenas mi. Quisque urna metus, tincidunt tincidunt, imperdiet sit amet, molestie at, odio. Etiam ac felis. Praesent ligula. Phasellus tempus nibh. Pellentesque dapibus. Curabitur ultricies leo a est. Praesent sit amet arcu. Suspendisse fermentum lectus eget arcu. Ut est. Aenean sit amet nisl non urna suscipit ornare.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur ut lacus id ipsum condimentum pellentesque. Nunc suscipit. Maecenas sagittis eros at lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris porttitor mi in tellus. Proin vel sem ac est euismod iaculis. Aliquam hendrerit nisl vitae nibh. Sed mollis. Nulla facilisi. Vivamus faucibus quam.<br /><br />
+
+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut et turpis non arcu fringilla pellentesque. Nullam interdum facilisis felis. Mauris tincidunt. Donec luctus, lorem sed lacinia ornare, enim quam sagittis lacus, ut porttitor nulla nisi eget mi. Nullam sagittis. Sed dapibus, turpis non eleifend sodales, risus massa hendrerit neque, volutpat aliquam nulla tellus eu dui. Pellentesque iaculis fermentum mi. Nam cursus ligula et nibh. Fusce tortor nibh, bibendum vitae, pharetra et, volutpat sed, urna.<br /><br />
+
+Nulla facilisi. Nulla non ante. Etiam vel nunc. Cras luctus auctor nibh. Suspendisse varius arcu a risus. Duis interdum malesuada tortor. Sed vel mauris. Mauris sed lorem. Aliquam purus. Vivamus sit amet neque. Nulla ultrices, ante ac porttitor ultrices, enim dui varius ipsum, tincidunt malesuada felis turpis non turpis. Nullam egestas, massa venenatis dictum imperdiet, urna est rhoncus magna, a fermentum ligula dolor malesuada lacus. Proin ac dui.<br /><br />
+
+Donec vestibulum magna quis enim. Nam dui nisl, lacinia non, dapibus at, mollis et, elit. Aliquam tempor nulla vitae metus. Integer varius convallis massa. Ut tempus, sem vel commodo aliquam, tellus justo placerat magna, ac mollis ipsum nulla ornare arcu. Cras risus nibh, eleifend in, scelerisque id, consequat quis, erat. Maecenas venenatis augue id odio. Cras libero. Donec sed eros. Etiam varius odio id nunc. Nam euismod urna a tellus. In non sem. In aliquet. Morbi sodales magna eu enim. Cras tristique.<br /><br />
+
+Nam quis quam eu quam euismod tristique. Donec ac velit. Ut a velit. Suspendisse eu turpis. Integer eros leo, euismod eu, rutrum eget, imperdiet sed, risus. Donec congue sapien. Nulla venenatis magna ac quam. Fusce purus odio, pharetra eget, vulputate non, auctor quis, magna. Nulla facilisi. Ut sagittis, mauris at cursus aliquam, ipsum mi faucibus justo, vel pharetra metus felis ac dolor. Donec elementum arcu quis tellus. Quisque mattis sem eu metus. Duis tempor elit non sem. Cras ultrices risus.<br /><br />
+
+Integer pulvinar. Vestibulum blandit dolor et lacus. Aliquam varius arcu ac arcu ornare pharetra. Phasellus ut sapien nec neque posuere feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus pharetra semper justo. Sed ut elit. Aenean aliquet euismod quam. Mauris turpis justo, lacinia at, blandit sit amet, molestie tristique, sapien. Integer et urna sit amet lectus facilisis pulvinar.<br /><br />
+
+Phasellus vitae elit non odio pulvinar faucibus. Maecenas elementum. Fusce bibendum, odio eget rutrum aliquam, velit felis dapibus elit, in dapibus libero velit eget arcu. Sed dolor enim, porta eget, luctus in, cursus nec, erat. Duis pretium. Cras volutpat velit in dui. Fusce vitae enim. Nunc ornare dolor non purus. Maecenas pulvinar velit id mauris. Vestibulum in erat. Mauris in metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at dui nec ipsum suscipit ultricies.<br /><br />
+
+Integer tristique enim sed neque. Sed sapien sapien, suscipit at, bibendum sed, iaculis a, eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus sagittis accumsan lectus. Maecenas tincidunt semper erat. In vitae arcu. Ut vulputate tempus magna. Nam erat. Sed mi. Vestibulum mauris. Maecenas et sem. Cras risus justo, hendrerit ut, cursus nec, pretium in, ipsum. Sed molestie lectus sed arcu consectetuer vulputate. Nunc mollis posuere dolor. Morbi nec velit a libero pharetra facilisis. Cras tincidunt.<br /><br />
+
+Donec rutrum luctus augue. Donec mattis commodo erat. Aenean sodales. Duis convallis metus id massa. Integer eget lorem et lectus sodales cursus. Integer commodo, tellus ut consequat placerat, nibh nisi malesuada nulla, eu aliquet metus mauris id nulla. Sed sagittis. Nam in mauris. Quisque eget ipsum. Suspendisse enim arcu, semper vitae, tincidunt dapibus, malesuada ac, dolor. Donec adipiscing auctor sem. Phasellus vitae pede. Integer lectus risus, ultrices non, euismod sed, condimentum et, lacus. Suspendisse potenti. Praesent tristique iaculis nulla. Curabitur sagittis. Aliquam erat volutpat. Donec feugiat, lectus vel tincidunt blandit, nisi mauris venenatis mi, id vehicula quam ante eget massa. Suspendisse volutpat sodales lorem. Nunc leo odio, dictum a, rutrum ac, aliquam at, felis.<br /><br />
+
+Vestibulum blandit. Sed volutpat mi nec massa. Aliquam erat volutpat. Nam eu erat et turpis accumsan semper. Nam justo. Sed lacinia. Pellentesque dignissim diam. Suspendisse consectetuer mattis massa. Praesent feugiat orci a augue. Donec eget tellus. Vestibulum suscipit neque vel eros. Nunc libero. Sed scelerisque nunc et sapien. Nulla sit amet ante convallis felis dapibus consectetuer. Pellentesque iaculis erat eu purus viverra facilisis. Duis vestibulum, felis ac sollicitudin interdum, augue eros tincidunt felis, sit amet eleifend quam nibh eu sem.<br /><br />
+
+Duis fermentum, urna et commodo porta, nisl ante egestas ante, in varius sem lacus eget turpis. Nullam dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque bibendum velit quis lorem. Nulla facilisi. Nunc rutrum diam eu magna. Phasellus eleifend, tellus ut elementum fringilla, turpis neque ornare elit, et gravida nisi odio ac lacus. Integer non velit vitae pede laoreet molestie. Nullam in tellus at turpis interdum rhoncus. Donec ut purus vel elit lobortis rutrum. Nullam est leo, porta eu, facilisis et, tempus vel, massa. Vivamus eu purus. Sed volutpat consectetuer tortor. Aliquam nec lacus. Aliquam purus est, tempor in, auctor vel, ornare nec, diam. Duis ipsum erat, vestibulum lacinia, tincidunt eget, sodales nec, nibh. Maecenas convallis vulputate est. Quisque enim.<br /><br />
+
+Aenean ut dui. Sed eleifend ligula sit amet odio. Aliquam mi. Integer metus ante, commodo quis, ullamcorper non, euismod in, est. In hac habitasse platea dictumst. Donec pede erat, venenatis a, pretium et, placerat vitae, velit. Cras lectus diam, ultricies a, interdum quis, placerat at, diam. Nam aliquet orci in velit luctus ornare. Donec a diam quis mi iaculis aliquam. Suspendisse iaculis. Duis nec lorem. Sed vehicula massa et urna. Morbi lorem. Proin et eros eget tellus eleifend viverra. Sed suscipit.<br /><br />
+
+Ut id leo sed tortor porttitor tincidunt. In nec felis. Maecenas tempus nunc et tortor. Fusce arcu. Mauris at leo. Nunc ultricies augue a quam. Duis quis mi sed leo hendrerit hendrerit. Vestibulum eros. Nam felis. Donec felis. Suspendisse egestas dictum augue. Suspendisse nec ligula non ipsum fermentum tempus. In velit felis, lobortis nec, porttitor nec, rutrum at, leo. Donec porta eleifend felis. Nunc ullamcorper est porta purus porttitor volutpat. Sed pharetra ligula et nisi. Donec vehicula sodales eros. Cras ut sem tincidunt turpis dignissim sollicitudin. Aliquam quis tellus.<br /><br />
+
+Cras id arcu quis neque mollis laoreet. Nulla mattis. Pellentesque et lectus. Praesent id sem. Proin malesuada nunc quis est. Integer varius sodales purus. Ut tellus. Vestibulum sed sem rhoncus justo eleifend feugiat. Donec nisi massa, sagittis non, fringilla sed, adipiscing at, diam. Donec pellentesque orci facilisis nisi. Sed a leo id odio cursus dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu augue. Quisque consequat. Cras odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean a nunc ac magna viverra pharetra. Donec at dolor. Nulla aliquet venenatis magna.<br /><br />
+
+Vivamus tortor dolor, feugiat quis, aliquet eu, commodo at, felis. Vivamus venenatis nibh ac nunc. Pellentesque euismod. Duis arcu. Mauris nec metus vitae augue varius euismod. Mauris tellus. Quisque tristique euismod lacus. Proin eu quam vitae ipsum elementum tincidunt. Ut rutrum ultrices enim. Quisque fringilla pede quis ante pharetra fermentum. Nunc gravida. In augue massa, congue non, cursus quis, interdum vel, nisl. Pellentesque tempus, purus vel ornare semper, justo nibh consequat tortor, ac facilisis turpis nisi ut lorem. Duis sapien.<br /><br />
+
+In hac habitasse platea dictumst. Sed egestas rhoncus dolor. Proin turpis nibh, mollis a, egestas ut, faucibus aliquet, est. Sed quam justo, lobortis vel, lacinia sed, ultrices ultricies, massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi bibendum diam nec massa. Morbi auctor. Fusce condimentum volutpat ante. Proin sed arcu in dui sollicitudin imperdiet. Donec feugiat faucibus diam. In auctor. Nunc tincidunt pellentesque massa. Maecenas nulla. Nam sapien neque, pretium sed, pulvinar vel, posuere nec, nibh. Vivamus sagittis, nunc eu placerat fringilla, ligula velit bibendum urna, molestie commodo magna magna nec lacus. Aenean vitae quam.<br /><br />
+
+Aenean non dui. Aliquam rutrum tempor est. Cras fringilla lacus vitae sem. Suspendisse laoreet sodales magna. Curabitur posuere mi at magna. Ut fermentum, dui quis posuere sagittis, erat lorem feugiat nibh, a suscipit metus nulla vitae tellus. In eget velit. Nam iaculis dictum diam. Sed porttitor metus at nunc. Fusce quis pede adipiscing risus congue mollis. Ut dictum, mi at accumsan venenatis, orci nulla accumsan sem, ut aliquam felis libero quis quam. Nulla turpis diam, tempus ut, dictum sit amet, blandit sit amet, enim. Suspendisse potenti. Maecenas elit turpis, malesuada et, ultricies quis, congue et, enim. Maecenas ut sapien. Nullam hendrerit accumsan tellus.<br /><br />
+
+Proin cursus orci eu dolor. Suspendisse sem magna, porta ac, feugiat in, pretium sed, felis. Nulla elementum commodo massa. Suspendisse consectetuer nibh in urna. Sed sit amet augue. Nam id ligula. Phasellus ullamcorper tellus ut eros. Vivamus arcu lectus, molestie at, posuere non, suscipit semper, eros. Donec sed augue a tellus tincidunt vulputate. Nullam elementum ante quis augue. Cras mauris felis, elementum sit amet, iaculis vitae, ornare nec, nisl. Nam venenatis orci et leo. Nam scelerisque. Praesent tellus diam, vehicula et, volutpat at, sollicitudin aliquet, dui. Phasellus tellus velit, malesuada id, semper ac, venenatis sit amet, nibh. Duis convallis lorem a erat.<br /><br />
+
+Pellentesque tincidunt eleifend ligula. Aenean turpis justo, ornare in, mattis sit amet, eleifend ac, odio. Maecenas nec metus vitae velit eleifend pretium. Donec dui orci, tempus sed, malesuada tempor, dignissim et, ante. Fusce egestas, dolor mollis faucibus sollicitudin, nisl felis porta libero, eget varius justo libero id mauris. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi auctor gravida massa. Nullam sed elit. Nullam pulvinar. In dignissim cursus purus. Phasellus non ante sed turpis venenatis dignissim. Nam libero pede, laoreet sed, vulputate quis, egestas ac, risus.<br /><br />
+
+Vivamus sagittis facilisis libero. Pellentesque velit erat, ornare a, consectetuer et, congue sed, pede. Nullam justo massa, volutpat et, blandit ut, pharetra vel, leo. Aliquam rutrum. Vestibulum blandit varius ipsum. Nullam vel velit. In non lectus ut sem consectetuer luctus. Ut feugiat massa vel nibh. Sed vitae turpis vitae eros pharetra posuere. Sed non neque. Ut auctor odio a quam eleifend vestibulum. Maecenas tincidunt risus vel ipsum. Morbi euismod cursus turpis. Nam quis dolor non libero facilisis lacinia. Pellentesque ultrices. Aenean ullamcorper, purus at sollicitudin imperdiet, urna augue bibendum ligula, eget placerat diam mi a mauris. Donec porttitor varius augue.<br /><br />
+
+Pellentesque fringilla erat in ante. Pellentesque orci tellus, varius vitae, tempus sed, vehicula placerat, dolor. Praesent nisi diam, pharetra nec, laoreet tempor, elementum in, tortor. In accumsan. Fusce ut mi ac ligula venenatis sollicitudin. Donec vulputate mollis nisl. Aenean nec purus nec lorem dapibus semper. In at enim nec orci consequat imperdiet. Curabitur vestibulum sapien id tellus. Phasellus iaculis lectus. Ut non turpis. Donec vitae ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget erat eu massa laoreet vestibulum. Donec convallis erat eu ipsum. Donec libero massa, lacinia nec, mollis eu, tempus a, enim. Cras eu leo. Sed massa nunc, scelerisque sit amet, faucibus quis, blandit in, nunc. Aliquam posuere enim nec nibh. Duis quis velit tempor eros interdum interdum.<br /><br />
+
+Aenean tempus, leo at vehicula varius, lectus elit facilisis tellus, sit amet ornare metus metus ut metus. In eros. Aenean eget est. Curabitur egestas. Sed ut elit. Mauris iaculis accumsan ligula. Aliquam imperdiet, libero et iaculis semper, mi augue posuere velit, id pretium nunc justo nec enim. Mauris lobortis turpis sit amet lacus. Quisque eu risus eget nibh mollis tristique. Mauris vestibulum. Etiam dui massa, condimentum a, tempus et, convallis vulputate, ante. Curabitur porta ultricies tortor. Praesent rutrum volutpat ipsum. In accumsan vestibulum lacus.<br /><br />
+
+Ut in justo vel enim viverra euismod. Phasellus ultrices semper urna. Aenean mauris tellus, vulputate feugiat, cursus ac, dignissim vel, nulla. In hac habitasse platea dictumst. In euismod, risus et pellentesque vulputate, nibh est sollicitudin felis, in accumsan diam metus sit amet diam. Fusce turpis lectus, ultricies euismod, pellentesque non, ullamcorper in, nunc. Vestibulum accumsan, lorem nec malesuada adipiscing, ante augue pellentesque magna, quis laoreet neque eros vel sapien. Phasellus feugiat. Curabitur gravida mauris eget augue. Praesent bibendum. Aenean sit amet odio ut arcu pellentesque scelerisque. Donec luctus venenatis eros. Phasellus tempus enim nec tortor. Sed ac lorem. Proin ut dui. Aliquam ipsum.<br /><br />
+
+Nunc varius dui sit amet tellus tincidunt facilisis. Mauris molestie varius purus. Vivamus gravida, est sed auctor tincidunt, risus justo euismod tortor, sed rhoncus nunc ipsum ac turpis. Nullam lacus sapien, ornare nec, placerat quis, commodo sit amet, ante. Etiam non urna vitae ligula hendrerit pharetra. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer feugiat, mi non egestas suscipit, pede sapien mattis pede, et tristique dui risus posuere erat. Nulla nunc odio, consequat feugiat, fermentum in, dignissim id, dolor. Donec rutrum purus sit amet dolor. Vestibulum molestie. Nulla pulvinar, mi ac eleifend cursus, pede eros ultrices sapien, sed consequat est mi eget mauris. Sed nibh metus, luctus vitae, volutpat sit amet, consectetuer nec, neque. Mauris rutrum dapibus nunc. Ut iaculis turpis at mauris. In hac habitasse platea dictumst. Sed congue fringilla orci. Sed turpis pede, vehicula sed, imperdiet ac, porttitor vestibulum, metus. Nunc cursus. Nam turpis ipsum, ultricies nec, cursus sit amet, rhoncus molestie, mi.<br /><br />
+
+Suspendisse potenti. Donec massa ante, porttitor id, ornare vel, rutrum sed, mauris. Donec auctor risus non elit. Donec pretium congue elit. Aliquam varius. Aliquam eget lacus. Vestibulum sed mauris eu erat ornare adipiscing. Proin congue. Nulla facilisi. Sed orci libero, tincidunt id, mattis non, volutpat in, ligula. Fusce ut odio. Aliquam semper eleifend felis. Nunc orci. Nulla facilisi.<br /><br />
+
+Morbi dolor nisi, pulvinar ut, feugiat at, rutrum nec, lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc gravida, libero a pulvinar iaculis, mi nulla adipiscing quam, ut gravida quam nunc quis nibh. Mauris ac nunc ut sem aliquet rhoncus. Vivamus urna. Nulla semper. Vivamus laoreet. Sed scelerisque condimentum dui. Phasellus libero metus, iaculis vel, elementum vel, scelerisque mattis, dui. In hac habitasse platea dictumst. Pellentesque ac nisi. Integer gravida. Praesent pharetra sem vitae justo. Praesent tempor nulla eget metus. Cras at dui. Nulla ultrices sem. Nam facilisis lectus malesuada orci. Vestibulum porttitor, neque ut tristique aliquet, dui libero venenatis augue, ac convallis nibh sem ac orci. Suspendisse potenti. Mauris suscipit dapibus mauris.<br /><br />
+
+Morbi sapien. Integer leo erat, blandit at, convallis eget, luctus eu, arcu. Sed urna metus, dignissim pulvinar, viverra sed, lacinia at, mi. Mauris congue feugiat mi. Mauris libero urna, blandit non, fermentum non, semper eu, erat. Pellentesque nec lacus. Fusce ut elit. Fusce sagittis, magna vel luctus suscipit, est ligula imperdiet leo, vulputate porta nisl sem ut erat. Vestibulum arcu turpis, tincidunt in, faucibus quis, pharetra at, elit. Vivamus imperdiet magna sit amet neque. Vestibulum vel leo. Suspendisse semper congue magna. Donec eleifend rhoncus lacus. Morbi tellus nunc, hendrerit sit amet, fringilla at, commodo vitae, sem. Maecenas vestibulum eros sagittis ipsum. Curabitur elit felis, rutrum vel, viverra vitae, vulputate et, arcu.<br /><br />
+
+Quisque ac augue quis tellus dictum ornare. Quisque vitae orci eu mi cursus feugiat. Nulla facilisi. In dui magna, ultricies eget, tempor sed, malesuada in, sapien. Duis mi. In hac habitasse platea dictumst. Nunc ultricies. Nulla accumsan, libero sed ullamcorper porttitor, eros lectus aliquam erat, in aliquet massa metus ac purus. Pellentesque enim odio, facilisis sed, sagittis vitae, scelerisque id, arcu. Aenean ante lectus, cursus non, rutrum vel, faucibus porta, tortor. Nam vitae neque. Morbi non turpis. Etiam facilisis. Mauris et urna. Cras tempor. Nullam rutrum, nisl eu cursus tristique, velit tellus aliquam pede, quis interdum massa sem id lorem. Fusce posuere. Quisque in leo venenatis dui facilisis sodales. In orci augue, bibendum et, viverra id, vehicula vitae, augue.<br /><br />
+
+Etiam malesuada est vel dolor. Integer suscipit volutpat libero. Cras semper dui sit amet dui. Quisque imperdiet leo vitae felis. Morbi volutpat nisi. Vestibulum sit amet eros a odio pellentesque lobortis. Sed dapibus est quis ante. Ut lobortis. Maecenas ullamcorper libero vel lacus. Aenean ut turpis vel elit tempor congue. Morbi sed sem. Aliquam odio neque, cursus sit amet, sollicitudin eu, vestibulum ac, turpis. Donec sed mauris. Pellentesque ut enim a sem lobortis euismod. Ut tellus odio, dapibus sed, iaculis sed, congue vel, massa. Phasellus tempus orci non orci. Proin erat ante, ornare ac, ornare commodo, elementum sit amet, libero. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed aliquam ornare dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras quis ante et felis porta porttitor. Aliquam erat volutpat. Phasellus et lacus. Morbi augue augue, sollicitudin at, tristique eget, porta vel, neque. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris id velit a erat molestie venenatis. Cras pellentesque. Maecenas tincidunt sem ut odio. Proin at ante. Cras fringilla, erat ac varius dapibus, lacus dui posuere mauris, at interdum lacus nisi ac pede.<br /><br />
+
+Pellentesque tristique. Donec eleifend. Nam tempus, mauris eu fermentum scelerisque, mauris nisl gravida nisl, sit amet pulvinar magna neque eget sapien. Etiam eu diam eu enim commodo scelerisque. Suspendisse potenti. Sed vitae sapien. Proin vel dui in augue tempus facilisis. Aenean nibh erat, interdum id, dictum ac, pharetra ac, eros. Suspendisse magna. Sed aliquet tellus non leo. Curabitur velit. Suspendisse sapien leo, pretium a, vehicula vel, faucibus nec, mi. Maecenas tincidunt volutpat diam. Proin vitae quam at dui gravida placerat. Sed eu nulla. Integer iaculis massa ac lacus. Praesent auctor ultricies quam. Suspendisse ut sapien. Morbi varius quam vel nisl.<br /><br />
+
+Nulla facilisi. Donec lobortis urna at mi. Mauris vulputate, enim sed auctor molestie, nisi elit vehicula mi, ac sollicitudin felis turpis nec ante. Praesent rutrum, arcu non semper euismod, magna sapien rutrum elit, eu varius turpis erat at eros. Morbi porta malesuada massa. Etiam fermentum, urna non sagittis gravida, lacus ligula blandit massa, vel scelerisque nunc odio vitae turpis. Morbi et leo. Pellentesque a neque nec nibh rhoncus ultricies. Curabitur hendrerit velit non velit. Sed mattis lacus vel urna. Integer ultricies sem non elit consequat consequat.<br /><br />
+
+Fusce imperdiet. Etiam semper vulputate est. Aenean posuere, velit dapibus dapibus vehicula, magna ante laoreet mauris, quis tempus neque nunc quis ligula. Nulla fringilla dignissim leo. Nulla laoreet libero ut urna. Nunc bibendum lorem vitae diam. Duis mi. Phasellus vitae diam. Morbi quis urna. Pellentesque rutrum est et magna. Integer pharetra. Phasellus ante velit, consectetuer sed, volutpat auctor, varius eu, enim. Maecenas fringilla odio et dui. Quisque tempus, est non cursus lacinia, turpis massa consequat purus, a pellentesque sem felis sed augue.<br /><br />
+
+Pellentesque semper rhoncus odio. Ut at libero. Praesent molestie. Cras odio dui, vulputate quis, ultrices in, pellentesque et, ipsum. Nunc fermentum. Nulla et purus. Sed libero nisl, tempor a, posuere nec, accumsan ut, urna. Praesent facilisis lobortis lectus. Curabitur viverra feugiat turpis. Curabitur ante magna, vulputate sodales, hendrerit nec, interdum in, odio. Vivamus accumsan felis eleifend massa. Suspendisse pharetra.<br /><br />
+
+Suspendisse at odio ac lorem hendrerit luctus. In dolor nibh, sodales quis, consectetuer id, mattis ut, arcu. Nullam diam nisl, congue vel, bibendum sit amet, fringilla sed, tortor. Praesent mattis dui non nibh. Donec ipsum nulla, tempor in, pellentesque nec, auctor ut, risus. Praesent metus lacus, mattis vel, varius et, feugiat vitae, metus. Donec nec leo. Ut velit nibh, porta id, tempus in, mattis ac, lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse sed felis. Pellentesque luctus. Vivamus elit. In ultricies lacinia ipsum. Pellentesque venenatis ante eget tortor. Duis lacus. Nulla pretium libero nec nunc. Sed pede.<br /><br />
+
+Fusce ligula. Cras dui enim, tincidunt nec, ultricies sit amet, vehicula vitae, orci. Cras nec erat. Praesent non nulla. Proin commodo hendrerit quam. Aliquam sed massa ut elit venenatis hendrerit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis porttitor ante ac nisl fringilla sollicitudin. Quisque nec nibh et nunc gravida posuere. Sed eget libero ut eros faucibus ultricies. Vivamus gravida augue sit amet leo suscipit tempor. Maecenas elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum diam non elit. In porttitor consectetuer nisi. Praesent vitae nulla et eros porttitor ornare. Quisque eu purus quis pede suscipit elementum. Proin tempor tincidunt tortor. Etiam tellus.<br /><br />
+
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce cursus. Mauris non leo. Pellentesque ullamcorper justo in lectus. Cras ut purus eu enim consectetuer rhoncus. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In placerat pellentesque arcu. Sed volutpat, lectus sed ullamcorper adipiscing, nunc nisl molestie orci, ac commodo nunc metus congue urna. Aliquam tortor nunc, tristique eget, bibendum eu, pharetra id, massa. Nunc non tellus. Pellentesque venenatis. Nunc consequat, urna at pellentesque blandit, ante orci consectetuer metus, eu fringilla arcu turpis in lacus. Mauris rhoncus risus nec massa. Proin rhoncus facilisis ligula. Quisque imperdiet porta nisl.<br /><br />
+
+Sed quis risus eget sem consectetuer pretium. Maecenas et erat ac lorem fermentum fermentum. Nulla elementum. Fusce semper tincidunt ipsum. Donec interdum mauris ac nibh. Nulla eget purus. Donec convallis, lorem nec tincidunt viverra, quam purus malesuada orci, nec gravida purus risus vel diam. Nullam quis tortor. Fusce eget tellus. Sed cursus lorem. Etiam ornare rhoncus augue. Nullam auctor pede et lacus.<br /><br />
+
+Nullam pellentesque condimentum ligula. Donec ullamcorper, enim a fringilla elementum, ante lacus malesuada risus, vitae vulputate dolor tellus in nisl. Pellentesque diam eros, tempor pharetra, suscipit vel, tincidunt et, dui. Aenean augue tortor, semper aliquam, euismod eu, posuere id, ante. Aenean consequat. Ut turpis pede, auctor eget, mollis vitae, euismod id, leo. Phasellus urna. Sed rutrum, eros sed porta placerat, nisl mauris auctor lorem, quis ultricies metus sapien eget nulla. Phasellus quis nisi sed dolor fermentum dictum. Ut pretium pede quis sapien. In hac habitasse platea dictumst. Suspendisse volutpat, urna ac pretium malesuada, risus ligula dictum ligula, vel fringilla diam metus et nisl. Mauris at massa at ante venenatis vehicula. Proin rhoncus dui nec nibh. Sed vel felis ornare erat bibendum mattis.<br /><br />
+
+Nunc iaculis venenatis nisl. Mauris faucibus imperdiet nibh. Donec tristique mattis lectus. Aliquam erat volutpat. Donec et mauris a pede cursus lobortis. Pellentesque dictum malesuada augue. Pellentesque malesuada, lorem et fringilla posuere, libero nulla cursus pede, eget adipiscing nisl magna id diam. Morbi tortor. Ut et urna. Duis quis massa.<br /><br />
+
+Quisque eu eros ut tortor dapibus auctor. Pellentesque commodo tellus vitae tortor. Praesent accumsan auctor ligula. Vestibulum convallis molestie justo. Praesent et ipsum. Vestibulum neque quam, ornare eu, cursus at, sollicitudin eget, nulla. Fusce leo massa, euismod cursus, scelerisque nec, mollis nec, est. Morbi iaculis tempor risus. In hac habitasse platea dictumst. Pellentesque malesuada libero. Nulla facilisi. Nam ante. Maecenas tincidunt eros ut justo. Morbi sollicitudin pulvinar elit. Aenean nisl.<br /><br />
+
+Morbi dapibus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce at lectus. Cras vulputate. Duis velit sapien, vehicula ut, consectetuer nec, pretium in, metus. Praesent quis mi. Fusce rhoncus enim nec nibh. Suspendisse dictum nunc. Duis ut metus sed est tempor semper. Nullam elit dolor, facilisis nec, gravida in, dictum sed, sapien. In laoreet. Sed quis nulla id mi mollis congue. Ut ante massa, commodo nec, ornare quis, blandit at, elit. In hac habitasse platea dictumst. Mauris neque nisi, porta non, eleifend fringilla, scelerisque porta, urna. Proin euismod cursus erat. Etiam non lorem et ipsum volutpat posuere. Donec ante justo, fringilla at, fermentum quis, sagittis nec, ante.<br /><br />
+
+Proin lobortis. Sed a tellus ut nulla vestibulum gravida. Suspendisse potenti. Fusce at nisi. Ut risus. Duis velit. In hac habitasse platea dictumst. Suspendisse augue eros, condimentum sit amet, vulputate id, volutpat in, orci. Praesent tempor, pede eu fermentum pharetra, ante metus pretium velit, accumsan varius risus erat vitae enim. Nullam sapien dui, malesuada sit amet, hendrerit eget, viverra sed, augue. Vivamus vulputate justo sed metus. Proin lacus. Cras erat augue, adipiscing nec, semper fermentum, varius id, urna. Quisque mi nunc, fringilla ut, pellentesque in, commodo sit amet, urna. Nunc luctus.<br /><br />
+
+Maecenas elementum eros ac justo. Proin felis. Duis pretium sagittis nisi. Praesent ac nulla. Nullam dui tellus, vestibulum nec, condimentum nec, dapibus in, mauris. Duis felis. Mauris sodales, nibh at viverra eleifend, libero ante hendrerit enim, non congue massa dolor sit amet orci. Sed urna leo, ullamcorper a, luctus ut, tincidunt at, ipsum. Duis placerat ullamcorper sapien. Cras a quam eget libero venenatis viverra.<br /><br />
+
+Curabitur hendrerit blandit massa. Suspendisse ligula urna, aliquet sit amet, accumsan in, porttitor nec, dolor. Praesent sodales orci vitae diam. Ut convallis ipsum egestas ligula. Aenean feugiat pede sit amet nulla. Suspendisse enim ante, porta eu, imperdiet in, congue nec, enim. Phasellus vitae velit nec risus hendrerit pharetra. Suspendisse potenti. Donec rutrum ultricies diam. Nulla nisl. Etiam justo enim, bibendum sit amet, mollis ac, fringilla ut, nunc. Proin euismod pede vitae ligula. Proin ante eros, commodo eu, ultrices eu, sagittis sed, nisi. Nullam et leo. Fusce consectetuer orci eget odio. Maecenas accumsan posuere mauris. Maecenas adipiscing. Nulla ut tellus at ante tristique vulputate. Fusce erat.<br /><br />
+
+Donec quis orci non nulla consectetuer placerat. Aliquam tincidunt auctor lacus. Fusce nisi metus, mattis id, mollis eget, laoreet vel, dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean faucibus mollis nibh. Fusce dapibus leo. Ut lacinia elit in turpis. Vivamus sapien sem, imperdiet eu, facilisis ut, blandit quis, purus. Vivamus urna. Vivamus non nibh sed erat ultricies sodales. Maecenas molestie sem ac pede. Etiam consequat commodo sem.<br /><br />
+
+Sed vitae metus et lacus euismod malesuada. Maecenas bibendum nunc at dui. Maecenas luctus, turpis nec tincidunt convallis, arcu nisi gravida diam, vitae imperdiet mi nisl a odio. Integer vitae massa non magna ultrices ullamcorper. Morbi quis ligula in purus gravida ultrices. Nunc varius posuere diam. Mauris eget ante. Maecenas nunc. Quisque quam metus, vulputate a, tristique non, malesuada nec, pede. Sed interdum consectetuer urna. Vivamus tincidunt libero vitae nulla. Pellentesque lobortis eleifend magna. Duis vehicula gravida lorem. Vivamus tortor massa, varius ut, gravida euismod, posuere faucibus, eros. Etiam aliquam, quam sed pretium lacinia, nulla mi auctor ante, et porttitor lorem nulla vitae enim. Donec justo. Maecenas lorem. Pellentesque faucibus dapibus eros. Vivamus mollis leo id neque fringilla elementum. Phasellus convallis scelerisque ipsum.<br /><br />
+
+Sed sapien dui, pharetra a, fringilla interdum, vestibulum nec, tellus. Suspendisse ultrices diam quis lectus. Nulla neque felis, tincidunt vitae, suscipit at, gravida euismod, felis. Sed elementum eros eu nisl. Pellentesque imperdiet. Donec vehicula. Duis eu purus molestie diam volutpat lacinia. Phasellus ultricies pharetra pede. Suspendisse varius leo non dui. Aliquam erat volutpat. Nam nisi. Vivamus pellentesque congue eros. Integer risus. Nullam lectus. Sed vel sapien. Praesent blandit neque eu enim.<br /><br />
+
+Nunc dictum venenatis velit. Ut aliquam velit. In dapibus, nisl vel elementum auctor, erat metus elementum ligula, vel molestie lectus nulla nec nulla. Sed tempus elit eget diam. Suspendisse potenti. Mauris tempus ante eu magna. Suspendisse potenti. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut iaculis tristique pede. Cras vel mauris sed mi accumsan blandit. Nulla vel tortor. Etiam lacinia. Suspendisse eget lectus sed nisl sodales ultricies. Aliquam venenatis ante quis tortor. Fusce a lorem.<br /><br />
+
+Mauris euismod sagittis urna. Pellentesque pulvinar tellus id libero. Morbi id magna ut libero vehicula sodales. Nulla pretium. Sed mauris leo, scelerisque a, varius a, commodo convallis, erat. In tellus. Suspendisse velit felis, sodales non, lacinia quis, iaculis eu, tortor. Nunc pellentesque. In iaculis est a mi viverra tincidunt. Etiam eleifend mi a ante. Nullam et risus. Etiam tempor enim id nunc vehicula vestibulum. Pellentesque mollis, felis eu laoreet feugiat, tortor enim convallis eros, non mollis justo ipsum et sem. Cras egestas massa. Sed molestie, turpis ac imperdiet tristique, turpis arcu rhoncus elit, vitae imperdiet enim massa at lorem. Donec aliquam laoreet nunc.<br /><br />
+
+Cras arcu justo, fringilla sit amet, ultricies eu, condimentum gravida, urna. In erat. Vivamus rhoncus ipsum quis quam. In eu tortor et eros accumsan malesuada. Curabitur hendrerit quam et risus ultrices porta. Maecenas pulvinar turpis vel arcu. Cras erat. Mauris tempus. Nunc a risus id nulla ultricies ullamcorper. Aenean nec mi ut dolor elementum iaculis. Sed nisi. Donec nisl lectus, condimentum nec, commodo ac, imperdiet id, nibh. Morbi ante sapien, laoreet eget, auctor et, tempus nec, elit. Aliquam luctus est eu elit.<br /><br />
+
+Sed malesuada interdum purus. Aenean id odio sed dolor sagittis porttitor. Sed nec ipsum. Nullam porttitor nunc sit amet leo. Praesent condimentum sapien quis tortor. Sed dapibus ipsum id risus. Fusce dapibus fringilla nunc. Cras tristique mauris sit amet diam. Suspendisse molestie, nisl ut scelerisque pharetra, lorem leo rutrum quam, in vestibulum felis nulla vitae sapien. Integer elementum velit quis eros adipiscing scelerisque. Etiam ac purus sit amet est laoreet porttitor. Etiam gravida pretium felis. Aenean sapien. Sed est tellus, hendrerit pellentesque, imperdiet sed, tempus at, dolor. Integer feugiat lectus vulputate metus. Mauris at neque.<br /><br />
+
+Nunc nec orci in dui aliquet placerat. Aenean ultrices diam quis nisl. Phasellus tempor lorem sed orci. Nam mauris erat, congue ac, condimentum quis, accumsan eget, lectus. Cras vulputate pulvinar lorem. Proin non mi ac orci gravida gravida. Fusce urna. Proin suscipit dui ultrices justo. Nullam pretium nibh quis dui. Cras sem magna, lacinia sit amet, laoreet id, pretium sed, nisi. Suspendisse et pede bibendum erat porttitor blandit. Vestibulum condimentum nisi pellentesque eros.<br /><br />
+
+Proin tellus. Pellentesque eu nulla ut lorem dictum tincidunt. Duis non enim. Sed ornare magna dignissim lectus. Curabitur ligula. Maecenas varius. Pellentesque condimentum bibendum diam. Ut sed nibh ut libero consectetuer rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ligula. Etiam accumsan. Ut vestibulum auctor mi. In hac habitasse platea dictumst. Nunc mollis. Aenean velit metus, auctor ac, lacinia sit amet, facilisis sed, risus. Nam aliquam volutpat elit. Quisque quis diam sed felis mollis feugiat. Aliquam luctus. Donec nec magna.<br /><br />
+
+Morbi porttitor viverra tellus. Duis elit lectus, convallis nec, rutrum nec, accumsan vel, tellus. Duis venenatis nisl in velit. Donec mauris. Morbi a diam at felis molestie dignissim. Praesent consectetuer leo sed tortor. Aliquam volutpat, eros vitae facilisis rhoncus, nibh massa sagittis magna, sed placerat metus turpis a ligula. Proin at purus ut erat feugiat consequat. Nam ornare libero nec turpis. Aenean eget quam vitae diam convallis faucibus. Duis molestie turpis vel lacus semper convallis.<br /><br />
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed tempus turpis eget quam fringilla fermentum. Curabitur risus magna, congue vel, commodo sed, tempus sit amet, lacus. Curabitur quam nulla, bibendum in, hendrerit eu, ultricies vitae, ligula. Curabitur nisl. Mauris nulla justo, laoreet non, faucibus sit amet, vulputate sit amet, lectus. Maecenas pharetra ligula quis nisl. Suspendisse suscipit tortor ac eros. Ut non urna tincidunt sapien aliquet consectetuer. Etiam convallis. Proin tempor tellus et dui. Donec sit amet lorem. Etiam et eros id nisl fermentum varius. Aenean ultricies. Donec leo. Vivamus adipiscing tempus dolor.<br /><br />
+
+Vestibulum convallis venenatis quam. Quisque sed ante. Pellentesque auctor ipsum sed mi. Integer gravida. Sed molestie nisi tempus quam. In varius. Curabitur feugiat, erat sed imperdiet interdum, ante justo convallis diam, at condimentum nunc odio a nulla. Nam turpis enim, sodales at, cursus varius, volutpat sed, risus. Nunc malesuada suscipit dui. Donec tincidunt molestie diam. Phasellus mattis congue neque. Maecenas in lorem. Maecenas ultricies rhoncus arcu. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce varius purus in nibh.<br /><br />
+
+Curabitur lobortis mi fermentum nisi. Donec sed augue at nisl euismod interdum. Nam ultrices mi sit amet quam. Quisque luctus sem id lorem. Phasellus mattis neque id arcu. Aliquam pellentesque iaculis mi. Ut at libero ut felis iaculis dapibus. Proin mauris. Etiam vel orci nec magna vehicula lacinia. Vivamus eu nulla. Aenean vehicula, nunc ac cursus dictum, elit odio tempor mauris, ultrices porta ligula erat ac nibh. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac nibh placerat massa dapibus lobortis. Maecenas et mi. Etiam vel ipsum. Nam nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec laoreet massa egestas lectus. Maecenas dictum. Integer at purus.<br /><br />
+
+Phasellus nisi. Duis ullamcorper justo in est. Suspendisse potenti. Nunc velit est, ultricies eu, facilisis sed, condimentum ut, ligula. Phasellus a metus. Fusce ac elit adipiscing justo tincidunt varius. Quisque pede. Nulla porta ante eget nunc. Pellentesque viverra. Nunc eleifend. Nulla facilisi. Mauris molestie est a arcu. Pellentesque aliquam, sapien id tincidunt feugiat, lectus massa porttitor pede, id accumsan ipsum nisi vel ligula. Integer eu enim et dui suscipit varius.<br /><br />
+
+Aliquam a odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus rhoncus posuere nisi. Integer et tellus in odio ultrices euismod. Vestibulum facilisis placerat nisl. Fusce sed lectus. Aenean semper enim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec congue tortor accumsan erat. Pellentesque diam erat, congue congue, tristique ac, auctor a, sem. Donec feugiat leo id augue.<br /><br />
+
+Sed tempor est non augue. Suspendisse pretium fermentum erat. Quisque dapibus tellus vitae sapien. Vivamus feugiat libero non nunc. Phasellus ornare aliquet arcu. Sed faucibus. Phasellus hendrerit tortor vitae elit pellentesque malesuada. Donec eu tortor quis lacus fermentum congue. Pellentesque adipiscing risus at felis. Quisque est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales nisl non pede. Etiam ac libero. Morbi euismod libero faucibus velit dignissim tempor.<br /><br />
+
+Nam tempor, velit in condimentum bibendum, arcu elit adipiscing sapien, vitae adipiscing enim lectus nec tortor. Aliquam varius egestas arcu. Duis nisl libero, commodo egestas, ultrices sed, convallis non, lectus. Proin semper. Donec pretium. In bibendum luctus metus. Quisque et tortor. Sed ultricies. Sed libero. Phasellus vel lacus at eros pretium dignissim. Phasellus risus nisi, sodales at, ultricies eleifend, laoreet in, neque. Suspendisse accumsan gravida lectus. Donec sed nulla. Pellentesque lobortis lorem at felis. Morbi ultricies volutpat turpis.<br /><br />
+
+Donec nisl risus, vulputate ac, congue consequat, aliquam et, dolor. Proin accumsan lorem in augue. Donec non nisi. Ut blandit, ligula ut sagittis aliquam, felis dolor sodales odio, sit amet accumsan augue tortor vitae nulla. Nunc sit amet arcu id sapien mollis gravida. Etiam luctus dolor quis sem. Nullam in metus sed massa tincidunt porttitor. Phasellus odio nulla, ullamcorper quis, ornare non, pretium sed, dui. Quisque tincidunt. Proin diam sapien, imperdiet quis, scelerisque nec, scelerisque non, sapien. Nulla facilisi.<br /><br />
+
+Donec tempor dolor sit amet elit. Maecenas nec ipsum eu quam consectetuer sodales. Duis imperdiet ante ac tellus. Vestibulum blandit porta ipsum. Aenean purus justo, mollis eu, consectetuer vel, sodales sollicitudin, leo. Mauris sollicitudin ullamcorper odio. Maecenas metus turpis, fringilla nec, tincidunt sed, pellentesque ut, libero. Suspendisse bibendum. Phasellus risus nibh, luctus ac, sagittis in, sagittis a, libero. Etiam fringilla, dui tristique fringilla sollicitudin, urna odio malesuada sapien, ut dictum augue lorem ac eros. Phasellus lobortis neque eget ipsum. Proin neque ipsum, posuere in, semper eu, tempor eu, leo.<br /><br />
+
+Pellentesque ante nulla, sodales in, euismod vel, eleifend vitae, turpis. Sed nunc. Sed eleifend elit non ipsum. Quisque posuere sapien vel metus. Nullam euismod eleifend nunc. Vestibulum ligula urna, posuere sit amet, posuere ac, rutrum id, libero. Mauris lorem metus, porta at, elementum quis, tempor ut, nibh. Aenean porttitor magna quis odio. Praesent pulvinar varius leo. Maecenas vitae risus tristique mauris imperdiet congue. Duis ullamcorper venenatis velit. Phasellus eleifend. Maecenas nec velit. Aliquam eget turpis. Cras iaculis volutpat nulla. Donec luctus, diam eu varius convallis, diam justo venenatis erat, convallis mattis mauris mauris vitae lacus. Pellentesque id leo. Donec at massa vitae mi bibendum vehicula. Proin placerat.<br /><br />
+
+Mauris convallis dui sit amet enim. Nullam dui. Integer convallis. Nunc ac sapien. Curabitur et felis. Sed velit odio, porta vitae, malesuada sed, convallis sed, turpis. Praesent eget magna. Maecenas at risus. Curabitur dictum. Maecenas ligula lectus, viverra ut, pulvinar sed, pulvinar at, diam. Suspendisse bibendum urna id odio. Mauris adipiscing. Donec accumsan lorem nec nunc. Sed commodo.<br /><br />
+
+Nulla facilisi. Morbi eget mauris sit amet augue varius imperdiet. Donec viverra erat eget metus. Proin pharetra condimentum quam. Nunc vel odio. Mauris elementum augue nec metus. Vivamus quam. Donec nec quam. Integer lacus odio, placerat sed, tempus nec, bibendum in, justo. Nulla aliquam, neque vel sagittis adipiscing, lectus massa gravida quam, ut pulvinar tellus massa lobortis massa.<br /><br />
+
+Curabitur vitae nisi et lectus porttitor euismod. Morbi ultricies convallis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla lobortis faucibus nulla. Maecenas pharetra turpis commodo dolor. Donec dolor neque, consectetuer non, dignissim at, blandit ultricies, felis. Nunc quis justo. Maecenas faucibus. Sed eget purus. Aenean dui eros, luctus a, rhoncus non, vestibulum bibendum, pede. Proin imperdiet sollicitudin sem.<br /><br />
+
+Suspendisse nisi nisl, commodo a, aliquam ut, commodo bibendum, neque. Donec blandit. Mauris tortor. Proin lectus ipsum, venenatis non, auctor vel, interdum vel, lacus. Nullam tempor ipsum a enim. Duis elit elit, cursus vel, venenatis in, dignissim vitae, neque. Morbi erat. Proin rutrum hendrerit massa. Pellentesque ultrices, ligula eu molestie auctor, tellus elit rhoncus turpis, at aliquet ante pede id ipsum. Aenean vitae est. Aliquam non neque. Ut nunc. Nulla at elit eu nunc molestie suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent nec ipsum. Vivamus elit arcu, faucibus non, pulvinar vel, vehicula et, massa. Aenean non sapien vitae sapien porttitor pretium. Sed vestibulum, lorem id venenatis hendrerit, mi nunc gravida ligula, sed euismod justo felis sit amet dolor. Duis molestie, nunc mattis feugiat accumsan, nibh est posuere nibh, volutpat consectetuer risus eros eget lectus.<br /><br />
+
+Vivamus non mauris. Nullam ornare convallis magna. In congue feugiat velit. Proin tellus magna, congue eu, scelerisque ut, hendrerit ac, lorem. Suspendisse potenti. Sed rhoncus, nunc sed tempus venenatis, eros dolor ultricies felis, nec tincidunt pede sem eget orci. Sed consequat leo. In porta turpis eget nibh. Aliquam sem tortor, gravida eu, fermentum vulputate, vestibulum in, nibh. Vivamus volutpat eros ac eros posuere tristique. Nam posuere erat vitae eros. Suspendisse potenti. Integer mattis dolor ac purus. Donec est turpis, lacinia non, vulputate non, pellentesque eu, sem. Morbi mollis volutpat lacus. Donec vitae justo. Aliquam mattis lacus in ipsum cursus sollicitudin. Sed bibendum rutrum odio. Donec nulla justo, pulvinar et, faucibus eget, tempus non, quam.<br /><br />
+
+Morbi malesuada augue ac libero pellentesque faucibus. Donec egestas turpis ac nunc. Integer semper. Nam auctor justo ac enim. Curabitur diam elit, tristique ac, viverra eget, placerat ac, nisl. Aenean faucibus auctor diam. Nam sed augue. Duis posuere massa vel nulla. Integer diam sem, fermentum quis, dignissim eget, egestas quis, ante. Donec sit amet mauris. Mauris id mauris quis purus sagittis malesuada. Suspendisse in justo at ipsum pharetra pellentesque. In quis eros. Phasellus cursus, libero eu vulputate molestie, felis eros tempor dui, vel viverra nulla pede in dui. Nulla tortor massa, eleifend sed, dapibus et, mollis sollicitudin, diam. Integer vitae ipsum ac velit egestas dictum. Fusce sed neque ac erat sagittis elementum. Nulla convallis, sem id ullamcorper rhoncus, pede lorem imperdiet pede, eu pharetra nisi tortor ac felis.<br /><br />
+
+Donec fringilla. Mauris elit felis, tempor aliquam, fringilla quis, tempor et, ipsum. Mauris vestibulum, ante commodo tempus gravida, lectus sem volutpat magna, et blandit ante urna eget justo. Curabitur fermentum, ligula at interdum commodo, nibh nunc pharetra ante, eu dictum justo ligula sed tortor. Donec interdum eleifend sapien. Pellentesque pellentesque erat eu nunc. Vivamus vitae ligula sit amet mauris porta luctus. Nullam tortor. Aenean eget augue. Donec ipsum metus, pulvinar eget, consectetuer ac, luctus id, risus. Aliquam interdum eros molestie sapien. Vivamus et enim. Donec adipiscing cursus ante.<br /><br />
+
+Phasellus turpis elit, suscipit non, pellentesque nec, imperdiet eget, ante. Sed mauris nulla, tempus ut, fringilla id, tempus vitae, magna. Pellentesque luctus justo nec augue. Aliquam pharetra mollis magna. Nunc dui augue, sollicitudin ut, cursus eget, vestibulum eget, sem. Donec ac dolor eget enim sodales cursus. Morbi interdum. Cras vel eros non elit faucibus aliquet. Donec quis lectus ut libero sagittis lacinia. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ante. Nam odio. Mauris turpis. Ut dolor. Aenean suscipit tellus a turpis.<br /><br />
+
+Ut mollis dolor ut felis. Aliquam euismod odio in dui. Donec tincidunt. Etiam malesuada velit nec lacus. Etiam ullamcorper feugiat odio. Sed quis urna. Morbi vehicula erat at sapien. Suspendisse potenti. Ut auctor sem ac odio. Nulla nec nisi. Aliquam iaculis ipsum non elit. Cras feugiat egestas lacus. Aenean eget nulla ac nisl feugiat scelerisque. Aenean posuere iaculis tellus. Duis posuere suscipit magna. Duis sagittis, massa sit amet fringilla tempus, nulla mi lobortis leo, in blandit quam felis in quam.<br /><br />
+
+Donec orci. Pellentesque purus. Suspendisse diam erat, posuere quis, tempor vestibulum, cursus in, mi. In vehicula urna ut lacus. Etiam sollicitudin purus vitae metus. In eget nisi ac enim mattis dictum. Duis quis nunc. Fusce ac neque eu sem aliquam porttitor. Integer at nisl vitae odio dictum gravida. Quisque laoreet, neque ac ultrices accumsan, arcu nibh dignissim justo, eu eleifend nibh pede non purus. Duis molestie eros eget risus. Curabitur nibh. Nunc at ipsum ultricies urna posuere sollicitudin. Nullam placerat. Aliquam varius ipsum sed nisl. Fusce condimentum, mauris a ornare venenatis, lorem risus vulputate leo, ac pellentesque ligula ligula vel lacus. Quisque at diam. Proin gravida mauris sed tellus. Curabitur enim.<br /><br />
+
+Vivamus luctus, erat a varius pellentesque, augue purus porttitor eros, non sollicitudin purus lorem et dui. Nunc magna. Nam in pede. Donec molestie justo eu ligula feugiat vulputate. Nunc euismod. Donec accumsan, velit vitae aliquet suscipit, massa augue mattis magna, a interdum nisi eros sit amet lacus. Suspendisse euismod enim sed leo. Donec nunc. Suspendisse tristique arcu ac elit. Suspendisse blandit dui. Suspendisse potenti. Integer mollis, nisi vitae lobortis dignissim, mauris arcu sagittis est, quis sodales turpis est a lacus. Morbi lacinia eros a velit. Aenean blandit, ligula ut facilisis facilisis, neque lectus interdum magna, vel dictum tortor leo non nisl. Quisque enim. Donec ut turpis. Phasellus cursus. Aenean sem. Suspendisse potenti. Vestibulum neque.<br /><br />
+
+Cras pulvinar tempus justo. Nulla facilisi. Sed id lorem consequat quam suscipit tincidunt. Donec ac ante. Duis leo lacus, ultrices et, mattis et, fringilla sit amet, tellus. Donec tincidunt. Nam non ligula et leo pellentesque hendrerit. Integer auctor consequat est. Morbi id magna. Nam massa nunc, dignissim ut, tincidunt nec, aliquet ac, purus. Etiam accumsan. Phasellus mattis sem in nibh. Morbi faucibus ligula eget lectus. Mauris libero felis, accumsan et, tincidunt quis, suscipit et, elit. Ut luctus, turpis ut iaculis tincidunt, lorem metus tempus sem, a lacinia quam metus nec justo. Integer molestie sapien vitae leo. Suspendisse tristique. Curabitur elit ante, vulputate ac, euismod in, vehicula tincidunt, metus. Quisque ac risus. Nunc est libero, pulvinar ac, sodales at, scelerisque at, nibh.<br /><br />
+
+Praesent id lacus. Sed vitae metus. Mauris iaculis luctus tellus. Phasellus dictum nunc. In metus orci, pellentesque sit amet, dictum et, tincidunt aliquam, dolor. Nulla malesuada. Phasellus lacus. Suspendisse leo risus, tincidunt vitae, varius sed, scelerisque id, massa. Suspendisse id elit. In vel justo eu sem vulputate molestie. Maecenas rhoncus imperdiet augue. Sed est justo, mattis dictum, dapibus eu, rhoncus vel, velit. Aenean velit urna, congue quis, convallis ullamcorper, aliquam id, tortor. Morbi tempor.<br /><br />
+
+Nunc mollis pede vitae sem. Nulla facilisi. Etiam blandit, magna sed ornare laoreet, est leo mattis sem, id dignissim orci nunc at nisl. In vel leo. Aliquam porttitor mi ut libero. Nulla eu metus. Integer et mi vitae leo adipiscing molestie. Ut in lacus. Curabitur eu libero. Vivamus gravida pharetra lectus. Quisque rutrum ultrices lectus. Integer convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec nisi dolor, rhoncus et, tristique id, lacinia id, massa.<br /><br />
+
+Aenean elit. Praesent eleifend, lacus sed iaculis aliquet, nisl quam accumsan dui, eget adipiscing tellus lacus sit amet mauris. Maecenas iaculis, ligula sed pulvinar interdum, orci leo dignissim ante, id tempor magna enim nec metus. Cras ac nisl eu nisl auctor ullamcorper. Etiam malesuada ante nec diam. Quisque sed sem nec est lacinia tempor. Sed felis. Proin nec eros vitae odio blandit gravida. Proin ornare. Nunc eros. Nam enim. Nam lacinia. Quisque et odio sit amet turpis ultricies volutpat. Aenean varius. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras et mi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec consequat imperdiet lacus. Morbi lobortis pellentesque sem.<br /><br />
+
+Mauris id augue sed erat blandit rhoncus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur lectus velit, varius at, eleifend id, gravida nec, elit. Nulla facilisi. Vestibulum tempus turpis eget nulla. Cras nisl mi, iaculis vel, dapibus id, facilisis vitae, dolor. Praesent turpis. Vestibulum scelerisque, neque sed rhoncus tincidunt, tellus sem consectetuer quam, vel accumsan nisl ipsum ac diam. Nulla tellus massa, dapibus id, consequat vehicula, elementum ac, lorem. Vestibulum faucibus faucibus nisl. Quisque mauris enim, rutrum vestibulum, venenatis vel, venenatis nec, sapien. Quisque vel sem a nibh rutrum tincidunt. Praesent metus velit, pretium vel, ornare non, elementum ut, purus. Quisque mauris magna, scelerisque sed, condimentum dictum, auctor vitae, nisi. Mauris sed ligula. Proin purus diam, sollicitudin vel, rutrum nec, imperdiet sit amet, erat.<br /><br />
+
+Aliquam a metus ac ipsum sagittis luctus. Quisque quis nisl in odio euismod pretium. Vestibulum quis mi. Maecenas imperdiet, mauris sit amet viverra aliquet, ligula augue imperdiet orci, a mollis dolor nisl nec arcu. Morbi metus magna, fringilla sed, mollis porttitor, condimentum ut, risus. Phasellus eu sapien eu felis auctor congue. Ut aliquam nisi ac dui. Morbi id leo eget nisi ultricies lobortis. Donec auctor. Praesent vulputate. Morbi viverra. Sed elementum arcu eu nibh. Fusce non velit nec dui lobortis posuere. Suspendisse pretium, tortor at cursus laoreet, elit lorem vulputate ipsum, at elementum nisi nisi non nunc. Vestibulum aliquam massa vitae neque. Praesent eget arcu sit amet lacus euismod interdum.<br /><br />
+
+Duis lectus massa, luctus a, vulputate ut, consequat ut, enim. Proin nisi augue, consectetuer nec, bibendum eget, tempor nec, nulla. Suspendisse eu lorem. Praesent posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin lorem. Integer vehicula. Curabitur lorem turpis, pulvinar a, commodo ut, scelerisque ac, tortor. Morbi id enim non est consectetuer aliquam. Etiam gravida metus porta orci. Vestibulum velit elit, bibendum non, consequat sit amet, faucibus non, lorem. Integer mattis, turpis nec hendrerit lacinia, pede urna tincidunt dui, vel lobortis lorem lorem ac magna.<br /><br />
+
+Curabitur faucibus. Nam a urna in diam egestas luctus. Curabitur mi neque, tincidunt vel, iaculis id, iaculis in, quam. Praesent sodales bibendum orci. Nulla eu nunc ut purus eleifend aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce vulputate lorem sit amet enim. Nulla feugiat pretium sapien. Curabitur eget eros. Aenean sagittis sagittis dui. Praesent vel eros vitae dolor varius malesuada. Mauris suscipit lacus at erat. Mauris vestibulum. In et enim nec eros ultricies ultricies. Maecenas tempus lorem.<br /><br />
+
+Morbi metus elit, posuere id, rutrum et, porttitor a, mauris. Aliquam in orci in augue sodales venenatis. Ut ac purus. Fusce pharetra libero eget ligula. Praesent vel mi vitae nulla mollis dictum. Sed metus lorem, malesuada in, dictum ut, tincidunt a, dolor. Mauris rutrum sem fringilla massa adipiscing vestibulum. Cras viverra aliquet ligula. Aliquam quis leo. Nullam volutpat egestas odio. Nullam suscipit velit. Ut dapibus, ipsum ut dictum viverra, dui purus pharetra lectus, nec imperdiet ante metus sed dolor. Donec suscipit velit eu elit. Vestibulum eget lacus id tellus pharetra suscipit. Phasellus pede. Nulla posuere, odio ac congue placerat, arcu erat faucibus nisi, fringilla facilisis ligula quam a orci. Morbi mollis urna. Maecenas felis. Sed at arcu.<br /><br />
+
+Nam sit amet orci vel libero sollicitudin facilisis. Nunc fermentum pretium est. Donec dictum massa ut nibh. In gravida ullamcorper mauris. Cras sed odio. Praesent dolor metus, mattis a, vestibulum ac, iaculis in, purus. Proin egestas cursus arcu. Nullam feugiat, diam pulvinar convallis aliquet, odio felis facilisis urna, eu commodo leo risus eget dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In sodales tellus eu lectus. Mauris pulvinar.<br /><br />
+
+Etiam sed mi vitae felis pellentesque mattis. Nunc at metus et est porttitor pellentesque. Mauris ligula velit, faucibus eu, aliquet a, sodales sed, libero. Nulla vulputate. Duis enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi mattis mattis eros. Nullam id tellus ut arcu convallis dictum. Vestibulum tempus facilisis sem. Maecenas tempor.<br /><br />
+
+Pellentesque pellentesque vehicula lectus. Sed viverra adipiscing arcu. Proin auctor nisl id tellus. Nunc pulvinar viverra nibh. Aliquam posuere sapien non nunc. Maecenas tristique, tortor sed vulputate dictum, tortor elit consectetuer sapien, at malesuada nunc ligula mollis nisi. Curabitur nisi elit, scelerisque at, pharetra interdum, cursus sit amet, nisl. Mauris ornare, orci id convallis rutrum, purus justo laoreet ligula, at posuere sapien nisi quis dolor. Quisque viverra. Ut dapibus. Maecenas vulputate. Praesent bibendum metus vitae urna.<br /><br />
+
+Aliquam eget quam eleifend augue dictum pellentesque. Pellentesque diam neque, sodales vestibulum, imperdiet vitae, posuere at, ligula. In ac felis. Cras nisl. Pellentesque lobortis augue quis sapien. Maecenas suscipit tempor elit. Nulla pellentesque. Pellentesque lacus. Cras dignissim tortor et lectus. Donec cursus mauris eget nulla. Aenean facilisis facilisis pede. Nullam aliquet volutpat ante. Maecenas libero ante, fermentum id, hendrerit vehicula, ultrices ac, erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi laoreet egestas felis. Nunc varius nulla id mauris. Maecenas id massa.<br /><br />
+
+Praesent pellentesque libero et mi. Ut semper nulla eu elit. Vivamus nibh eros, vestibulum eget, luctus a, faucibus et, ante. Duis elementum dolor sed turpis. Aliquam elit. Etiam accumsan volutpat mauris. Integer interdum porta diam. Sed sed erat. Curabitur tristique. Praesent non nunc. Praesent quam est, tempus a, ornare vitae, pellentesque quis, orci. Vivamus id nulla. Nam pede lacus, placerat ut, sollicitudin ut, sodales id, augue. Nullam ultricies. Sed ac magna. Mauris eu arcu sit amet velit rutrum rutrum. Sed eu felis vitae ipsum sollicitudin gravida. Proin in arcu. Maecenas rhoncus, quam at facilisis fermentum, metus magna tempus metus, quis egestas turpis sem non tortor.<br /><br />
+
+Ut euismod. Suspendisse tincidunt tincidunt nulla. In dui. Praesent commodo nibh. Pellentesque suscipit interdum elit. Ut vitae enim pharetra erat cursus condimentum. Sed tristique lacus viverra ante. Cras ornare metus sit amet nisi. Morbi sed ligula. Mauris sit amet nulla et libero cursus laoreet. Integer et dui. Proin aliquam, sapien vel tempor semper, lorem elit scelerisque nunc, at malesuada mi lorem vel tortor. Curabitur in sem. Pellentesque cursus. Curabitur imperdiet sapien. Aliquam vehicula consequat quam.<br /><br />
+
+Aliquam erat volutpat. Donec lacinia porttitor mauris. Suspendisse porttitor. Integer ante. Ut et risus vitae lacus consectetuer porttitor. Curabitur posuere aliquam nulla. Pellentesque eleifend, mauris eu commodo tincidunt, ligula pede bibendum libero, ut aliquet nisi tellus at justo. Suspendisse quis lectus. Quisque iaculis dapibus libero. Fusce aliquet mattis risus.<br /><br />
+
+Suspendisse rutrum purus a nibh. Etiam in urna. Pellentesque viverra rhoncus neque. Mauris eu nunc. Integer a risus et est suscipit condimentum. Nulla lectus mi, vulputate vitae, euismod at, facilisis a, quam. Quisque convallis mauris et ante. Nunc aliquet egestas lorem. Integer sodales ante et velit. Curabitur malesuada. Suspendisse potenti. Mauris accumsan odio in nulla. Vestibulum luctus eleifend lacus. Aenean diam. Nullam nec metus. Curabitur id eros a elit pulvinar mattis. Donec dignissim consequat sapien. Praesent dictum, metus eget malesuada pellentesque, risus ante ultrices neque, eu gravida augue mi a pede. Morbi ac justo.<br /><br />
+
+Donec tempus consequat mauris. Sed felis lorem, lobortis et, sodales sit amet, adipiscing a, eros. Vestibulum vitae nunc non lectus porta bibendum. Curabitur nulla. Proin auctor nisl eget lacus. Donec dignissim venenatis nibh. Suspendisse ullamcorper tempus augue. Donec dictum sodales ipsum. Phasellus ac urna sit amet purus sagittis ullamcorper. Etiam orci.<br /><br />
+
+Phasellus facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse eros purus, auctor ac, auctor sed, placerat tincidunt, mi. Aliquam nibh est, congue sed, tempus vitae, pellentesque in, dui. Nullam mattis dapibus urna. Morbi at lorem. Praesent lobortis, sem et interdum suscipit, erat justo mattis nisl, vitae pulvinar quam leo in turpis. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam quis massa non sapien accumsan congue. Praesent adipiscing. Vivamus tempus aliquam nunc. Quisque id sem ac eros tincidunt mattis. Etiam magna augue, feugiat ut, pretium vitae, volutpat quis, turpis. Morbi leo. Ut tortor. Nunc non mi. Maecenas tincidunt massa eu ligula. Vestibulum at nibh.<br /><br />
+
+Nunc vestibulum. Curabitur at nunc ac nisl vulputate congue. Suspendisse scelerisque. Integer mi. In hac habitasse platea dictumst. Donec nulla. Sed sapien. Aenean ac purus. Duis elit erat, hendrerit at, adipiscing in, fermentum ut, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;<br /><br />
+
+Donec elit. Duis consequat purus vitae mauris. Mauris a tortor vel mi fringilla hendrerit. Curabitur mi. Aliquam arcu nibh, bibendum quis, bibendum sed, ultricies sit amet, ante. Morbi tincidunt, justo pellentesque feugiat rhoncus, est enim luctus pede, id congue metus odio eu mi. Fusce blandit nunc a lorem. Cras non risus. Nullam magna eros, elementum eu, mollis viverra metus.
+ </body>
+</html>
diff --git a/dom/base/test/test_bug444546.html b/dom/base/test/test_bug444546.html
new file mode 100644
index 0000000000..4fec1754e0
--- /dev/null
+++ b/dom/base/test/test_bug444546.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=444546
+-->
+<head>
+ <title>Test for Bug 444546</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .up {
+ height: 14px;
+ width: 1px;
+ background: blue;
+ font-size: 11px;
+ color: white;
+ }
+ .down {
+ height: 14px;
+ width: 1px;
+ background: blue;
+ font-size: 11px;
+ color: white;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=444546">Mozilla Bug 444546</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 444546 **/
+
+ var xhrCount = 5;
+ var xhrs = new Array();
+ var uploads = new Array();
+ var maxSize = 5000000;
+ var hugeString = new Array(maxSize + 1).join('a');
+
+ function updateProgress(evt) {
+ ++evt.target.pcounter;
+ var time = new Date().getTime();
+ // 350 - 200 = 150ms
+ if ((time - evt.target.prevTime) < 150) {
+ evt.target.log.parentNode.style.background = "red";
+ }
+ var diff = (time - evt.target.prevTime);
+ if (evt.target.min == -1 || evt.target.min > diff) {
+ evt.target.min = diff;
+ }
+ if (evt.target.max == -1 || evt.target.max < diff) {
+ evt.target.max = diff;
+ }
+
+ evt.target.log.textContent = diff + "ms";
+ evt.target.prevTime = time;
+ if (evt.lengthComputable) {
+ var fractionLoaded = (evt.loaded / evt.total);
+ if (fractionLoaded < 1) {
+ evt.target.log.style.width = (fractionLoaded * 400) + "px";
+ }
+ }
+ }
+
+ function loaded(evt) {
+ evt.target.log.style.width = "400px";
+ evt.target.log.style.background = "green";
+ if ("xhr" in evt.target) {
+ evt.target.xhr.prevTime = new Date().getTime();
+ evt.target.xhr.startTime = evt.target.xhr.prevTime;
+ }
+ var total = new Date().getTime() - evt.target.startTime;
+ evt.target.log.textContent = "total:" + total + "ms";
+ if (evt.target.pcounter) {
+ evt.target.log.textContent += " ," + evt.target.pcounter + "pe, avg:" +
+ parseInt((evt.target.prevTime - evt.target.startTime)/evt.target.pcounter) + "ms";
+ }
+ if (evt.target.min != -1) {
+ ok(evt.target.min >= 150, "Events fired too fast!");
+ evt.target.log.textContent += ", min:" + evt.target.min + "ms";
+ }
+ if (evt.target.max != -1) {
+ // Disabled for now.
+ //ok(evt.target.max <= 550, "Events didn't fire fast enough!");
+ evt.target.log.textContent += ", max:" + evt.target.max + "ms";
+ }
+ if ("upload" in evt.target) {
+ is(evt.total, maxSize * 10, "Wrong data!");
+ --xhrCount;
+ if (xhrCount == 0) {
+ // This is a hack. To get more progress events, server sends the data
+ // 10 times.
+ SimpleTest.finish();
+ } else {
+ setTimeout(start, 10);
+ }
+ } else {
+ is(evt.total, maxSize, "Wrong data!");
+ }
+ }
+
+ function start() {
+ var xhr = new XMLHttpRequest();
+ xhrs.push(xhr);
+ uploads.push(xhr.upload);
+ var container = document.createElement("tr");
+ var td1 = document.createElement("td");
+ container.appendChild(td1);
+ td1.textContent = xhrs.length + ".";
+ var td2 = document.createElement("td");
+ container.appendChild(td2);
+ var td3 = document.createElement("td");
+ container.appendChild(td3);
+ var uploadElement = document.createElement("div");
+ td2.appendChild(uploadElement);
+ uploadElement.className = "up";
+ var downloadElement = document.createElement("div");
+ td3.appendChild(downloadElement);
+ downloadElement.className = "down";
+ document.getElementById('tbody').appendChild(container);
+ xhr.log = downloadElement;
+ xhr.upload.log = uploadElement;
+ xhr.onprogress = updateProgress;
+ xhr.upload.onprogress = updateProgress;
+ xhr.onload = loaded;
+ xhr.upload.onload = loaded;
+ xhr.open("POST", "bug444546.sjs");
+ xhr.upload.prevTime = new Date().getTime();
+ xhr.upload.startTime = xhr.upload.prevTime;
+ xhr.upload.xhr = xhr;
+ xhr.pcounter = 0;
+ xhr.upload.pcounter = 0;
+ xhr.min = -1;
+ xhr.upload.min = -1;
+ xhr.max = -1;
+ xhr.upload.max = -1;
+ xhr.send(hugeString);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() { setTimeout(start, 10); });
+
+</script>
+</pre>
+ <table>
+ <tbody id="tbody">
+ <tr>
+ <td>XHR</td>
+ <td style="min-width: 410px;">upload</td>
+ <td style="min-width: 410px;">download</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
diff --git a/dom/base/test/test_bug444722.html b/dom/base/test/test_bug444722.html
new file mode 100644
index 0000000000..84a7873d18
--- /dev/null
+++ b/dom/base/test/test_bug444722.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=444722
+-->
+<head>
+ <title>Test for Bug 444722</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=444722">Mozilla Bug 444722</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 444722 **/
+var counter = 0;
+var testCount = 0;
+var xhrs = new Array();
+
+function loadHandler() {
+ ++counter;
+ ok(true, "load handler should have been called.");
+ if (counter == testCount) {
+ SimpleTest.finish();
+ }
+}
+
+function testXHR(method, hasData, data) {
+ ++testCount;
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, "file_XHR_pass1.xml");
+ xhr.onload = loadHandler;
+ try {
+ if (hasData) {
+ xhr.send(data);
+ } else {
+ xhr.send();
+ }
+ } catch(ex) {
+ --testCount;
+ ok(false, "Calling XMLHttpRequest.send failed: " + ex);
+ }
+ // Keep XHR alive.
+ xhrs.push(xhr);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+testXHR("GET", false, null);
+testXHR("GET", true, null);
+testXHR("GET", true, "some data");
+
+testXHR("POST", false, null);
+testXHR("POST", true, null);
+testXHR("POST", true, "some data");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug448993.html b/dom/base/test/test_bug448993.html
new file mode 100644
index 0000000000..e7915c4431
--- /dev/null
+++ b/dom/base/test/test_bug448993.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448993
+-->
+<head>
+ <title>Test for Bug 448993</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448993">Mozilla Bug 448993</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 448993 **/
+
+function runTest() {
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ var c = document.getElementById("c")
+ var r = document.createRange();
+ r.setStart(b, 0);
+ r.setEnd(a, 2);
+ c.appendChild(b);
+ r.extractContents();
+ var s = document.createRange();
+ s.setEnd(b, 0);
+ SpecialPowers.gc();
+ s.deleteContents();
+ ok(true, "test did not crash");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+<div id="a"><span id="b"></span><span id="c"></span></div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug450160.html b/dom/base/test/test_bug450160.html
new file mode 100644
index 0000000000..2b2f80e693
--- /dev/null
+++ b/dom/base/test/test_bug450160.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450160
+-->
+<head>
+ <title>Test for Bug 450160</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450160">Mozilla Bug 450160</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 450160 **/
+
+
+function testHTMLDocument() {
+ var doc = document.implementation.createHTMLDocument();
+ ok(!!doc.documentElement, "Document should have document element!");
+ ok(!!doc.body, "Should have .body!");
+ ok(doc instanceof HTMLDocument,
+ "Document should be an HTML document!");
+}
+
+function testSVGDocument() {
+ var docType1 =
+ document.implementation.createDocumentType("svg",
+ "-//W3C//DTD SVG 1.1//EN",
+ null);
+ ok(docType1, "No doctype?");
+ ok(docType1.ownerDocument, "docType should have ownerDocument!");
+ var doc1 = document.implementation.createDocument(null, null, docType1);
+ is(docType1.ownerDocument, doc1, "docType should have ownerDocument!");
+ ok(!doc1.documentElement, "Document shouldn't have document element!");
+ ok(!(doc1 instanceof HTMLDocument),
+ "Document shouldn't be an HTML document!");
+ ok(doc1 instanceof XMLDocument,
+ "Document should be an XML document!");
+
+ // SVG documents have .documentElement.
+ ok("documentElement" in doc1, "No .documentElement in document");
+
+ var docType2 =
+ document.implementation.createDocumentType("svg",
+ "-//W3C//DTD SVG 1.1//EN",
+ null);
+ var doc2 = document.implementation.createDocument("http://www.w3.org/2000/svg",
+ "svg", docType2);
+ ok(doc2.documentElement, "Document should have document element!");
+ is(doc2.documentElement.localName, "svg", "Wrong .documentElement!");
+}
+
+function testFooBarDocument() {
+ var docType1 =
+ document.implementation.createDocumentType("FooBar", "FooBar", null);
+ ok(docType1, "No doctype?");
+ ok(docType1.ownerDocument, "docType should have ownerDocument!");
+ var doc1 = document.implementation.createDocument(null, null, docType1);
+ is(docType1.ownerDocument, doc1, "docType should have ownerDocument!");
+ ok(!doc1.documentElement, "Document shouldn't have document element!");
+ ok(!(doc1 instanceof HTMLDocument),
+ "Document shouldn't be an HTML document!");
+
+ var docType2 =
+ document.implementation.createDocumentType("FooBar", "FooBar", null);
+ var doc2 = document.implementation.createDocument("FooBarNS",
+ "FooBar", docType2);
+ ok(doc2.documentElement, "Document should have document element!");
+ is(doc2.documentElement.namespaceURI, "FooBarNS", "Wrong namespaceURI!");
+ is(doc2.documentElement.localName, "FooBar", "Wrong localName!");
+}
+
+function testNullDocTypeDocument() {
+ var doc1 = document.implementation.createDocument(null, null, null);
+ ok(!doc1.documentElement, "Document shouldn't have document element!");
+ ok(!(doc1 instanceof HTMLDocument),
+ "Document shouldn't be an HTML document!");
+
+ var doc2 = document.implementation.createDocument("FooBarNS",
+ "FooBar", null);
+ ok(doc2.documentElement, "Document should have document element!");
+ is(doc2.documentElement.namespaceURI, "FooBarNS", "Wrong namespaceURI!");
+ is(doc2.documentElement.localName, "FooBar", "Wrong localName!");
+}
+
+testHTMLDocument();
+testSVGDocument();
+testFooBarDocument();
+testNullDocTypeDocument();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug451376.html b/dom/base/test/test_bug451376.html
new file mode 100644
index 0000000000..500f9bafee
--- /dev/null
+++ b/dom/base/test/test_bug451376.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=451376
+-->
+<head>
+ <title>Test for Bug 451376</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body onload="doTest()">
+ <a target="_blank"
+ title="IAccessibleText::attributes provides incorrect info after a mis-spelled word"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=451376">Mozilla Bug 451376</a>
+ <p id="display"></p>
+ <div id="content" style="display:none">
+ </div>
+ <pre id="test">
+
+ <div id="area"><button>btn1</button>text <button>btn2</button></div>
+
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function testRange(aRangeID,
+ aStartNode, aStartOffset,
+ aEndNode, aEndOffset,
+ aBeforeRangeNode, aBeforeRangeOffset,
+ aInRangeNode, aInRangeOffset,
+ aAfterRangeNode, aAfterRangeOffset)
+ {
+ var range = document.createRange();
+
+ range.setStart(aStartNode, aStartOffset);
+ range.setEnd(aEndNode, aEndOffset);
+
+ if (aBeforeRangeNode)
+ is(range.comparePoint(aBeforeRangeNode, aBeforeRangeOffset), -1,
+ "Wrong result for the point before the range '" + aRangeID + "'");
+ if (aInRangeNode)
+ is(range.comparePoint(aInRangeNode, aInRangeOffset), 0,
+ "Wrong result for the point inside the range '" + aRangeID + "'");
+ if (aAfterRangeNode)
+ is(range.comparePoint(aAfterRangeNode, aAfterRangeOffset), 1,
+ "Wrong result for the point after the range '" + aRangeID + "'");
+ // Comparare also start and end point
+ is(range.comparePoint(aStartNode, aStartOffset), 0,
+ "Wrong result for the start point '" + aRangeID + "'");
+ is(range.comparePoint(aEndNode, aEndOffset), 0,
+ "Wrong result for the end point '" + aRangeID + "'");
+ ok(range.isPointInRange(aStartNode, aStartOffset),
+ "Wrong result for the start point '" + aRangeID + "'");
+ ok(range.isPointInRange(aEndNode, aEndOffset),
+ "Wrong result for the end point '" + aRangeID + "'");
+ }
+
+ function doTest()
+ {
+ var area = document.getElementById("area");
+ var btn1 = area.firstChild;
+ var text = btn1.nextSibling;
+ var btn2 = area.lastChild;
+
+ testRange("range1", area, 0, area, 1,
+ null, 0,
+ area, 0,
+ area, 2);
+
+ testRange("range2", text, 2, text, 4,
+ text, 0,
+ text, 3,
+ text, 5);
+
+ testRange("range3", text, 4, area, 2,
+ text, 0,
+ text, 4,
+ area, 3);
+
+ SimpleTest.finish();
+ }
+ </script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug453521.html b/dom/base/test/test_bug453521.html
new file mode 100644
index 0000000000..4a93d7af9c
--- /dev/null
+++ b/dom/base/test/test_bug453521.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453521
+-->
+<head>
+ <title>Test for Bug 453521</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453521">Mozilla Bug 453521</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 453521 **/
+
+
+var r = document.createRange();
+r.setStart(document.documentElement, 0);
+r.setEnd(document.documentElement, 0);
+ok(r.extractContents() != null,
+ "range.extractContents() shouldn't return null.");
+is(r.extractContents().nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ "range.extractContents() should return a document fragment.");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug453736.html b/dom/base/test/test_bug453736.html
new file mode 100644
index 0000000000..8cb8ebd9b9
--- /dev/null
+++ b/dom/base/test/test_bug453736.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453736
+-->
+<head>
+ <title>Test for Bug 453736</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453736">Mozilla Bug 453736</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 453736 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ const scriptCreationFuncs = [
+ function() { return document.createElement("script"); },
+ function() { return document.createElementNS("http://www.w3.org/2000/svg", "script"); }
+ ];
+
+ const scriptContainers = ["div", "iframe", "noframes", "noembed"];
+ for (var i = 0; i < scriptContainers.length; ++i) {
+ for (var func of scriptCreationFuncs) {
+ var cont = scriptContainers[i];
+ var node = document.createElement(cont);
+ document.body.appendChild(node);
+ var s = func();
+ s.setAttribute("type", "application/javascript");
+ s.appendChild(document.createTextNode('window["'+cont+'ScriptRan"] = true'));
+
+ window[cont+"ScriptRan"] = false;
+ node.appendChild(s);
+ is(window[cont+"ScriptRan"], true,
+ "Script created with " + func +" should run when inserting in <"+cont+">");
+
+ window[cont+"ScriptRan"] = false;
+ document.body.appendChild(s);
+ is(window[cont+"ScriptRan"], false,
+ "Script created with " + func + " shouldn't run when moving out of <"+cont+">");
+
+ window[cont+"ScriptRan"] = false;
+ document.body.appendChild(s.cloneNode(true));
+ is(window[cont+"ScriptRan"], false,
+ "Clone of script inside <" + cont + "> created with " + func + " shouldn't run");
+ }
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug454325.html b/dom/base/test/test_bug454325.html
new file mode 100644
index 0000000000..96426ec82b
--- /dev/null
+++ b/dom/base/test/test_bug454325.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454325
+-->
+<head>
+ <title>Test for Bug 454325</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=454325">Mozilla Bug 454325</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 454325 **/
+
+function testDocument1() {
+ var doc = document.implementation.createDocument("", "", null);
+ var html = doc.createElement('html');
+ doc.appendChild(html);
+ var body = doc.createElement('body');
+ html.appendChild(body);
+ var h1 = doc.createElement('h1');
+ var t1 = doc.createTextNode('Hello ');
+ h1.appendChild(t1);
+ var em = doc.createElement('em');
+ var t2 = doc.createTextNode('Wonderful');
+ em.appendChild(t2);
+ h1.appendChild(em);
+ var t3 = doc.createTextNode(' Kitty');
+ h1.appendChild(t3);
+ body.appendChild(h1);
+ var p = doc.createElement('p');
+ var t4 = doc.createTextNode(' How are you?');
+ p.appendChild(t4);
+ body.appendChild(p);
+ var r = doc.createRange();
+ r.selectNodeContents(doc);
+ is(r.toString(), "Hello Wonderful Kitty How are you?",
+ "toString() on range selecting Document gave wrong output");
+ r.setStart(h1, 3);
+ r.setEnd(p, 0);
+ // <html><body><h1>Hello <em>Wonder ful<\em> Kitty<\h1><p>How are you?<\p><\body></html>
+ // ^ -----^
+ is(r.toString(), "", "toString() on range crossing text nodes gave wrong output");
+ var c1 = r.cloneContents();
+ is(c1.childNodes.length, 2, "Wrong child nodes");
+ try {
+ is(c1.childNodes[0].localName, "h1", "Wrong child node");
+ is(c1.childNodes[1].localName, "p", "Wrong child node");
+ } catch(ex) {
+ ok(!ex, ex);
+ }
+
+ r.setStart(t2, 6);
+ r.setEnd(p, 0);
+ // <html><body><h1>Hello <em>Wonder ful<\em> Kitty<\h1><p>How are you?<\p><\body></html>
+ // ^----------------------^
+ is(r.toString(), "ful Kitty", "toString() on range crossing text nodes gave wrong output");
+ var c2 = r.cloneContents();
+ is(c2.childNodes.length, 2, "Wrong child nodes");
+ try {
+ is(c1.childNodes[0].localName, "h1", "Wrong child node");
+ is(c1.childNodes[1].localName, "p", "Wrong child node");
+ } catch(ex) {
+ ok(!ex, ex);
+ }
+
+ var e1 = r.extractContents();
+ is(e1.childNodes.length, 2, "Wrong child nodes");
+ try {
+ is(e1.childNodes[0].localName, "h1", "Wrong child node");
+ is(e1.childNodes[1].localName, "p", "Wrong child node");
+ } catch(ex) {
+ ok(!ex, ex);
+ }
+}
+
+function testDocument2() {
+ var doc = document.implementation.createDocument("", "", null);
+ var html = doc.createElement('html');
+ doc.appendChild(html);
+ var head = doc.createElement('head');
+ html.appendChild(head);
+ var foohead = doc.createElement('foohead');
+ html.appendChild(foohead);
+ var body = doc.createElement('body');
+ html.appendChild(body);
+ var d1 = doc.createElement('div');
+ head.appendChild(d1);
+ var t1 = doc.createTextNode("|||");
+ d1.appendChild(t1);
+ var d2 = doc.createElement("div");
+ body.appendChild(d2);
+ var d3 = doc.createElement("div");
+ d2.appendChild(d3);
+ var d4 = doc.createElement("div");
+ d2.appendChild(d4);
+ var r = doc.createRange();
+ r.setStart(t1, 1);
+ r.setEnd(d2, 2);
+ is(r.toString(), "||", "Wrong range");
+ var c1 = r.cloneContents();
+ var e1 = r.extractContents();
+ ok(c1.isEqualNode(e1), "Wrong cloning or extracting!");
+}
+
+function testSurroundContents() {
+ var div = document.createElement('div');
+ document.body.appendChild(div);
+ div.innerHTML = '<div>hello</div>world';
+ var innerDiv = div.firstChild;
+ var hello = innerDiv.firstChild;
+ var range = document.createRange();
+ range.setStart(hello, 0);
+ range.setEnd(hello, 5);
+ range.surroundContents(document.createElement('code'));
+ is(innerDiv.childNodes.length, 3, "Wrong childNodes count");
+ is(innerDiv.childNodes[0].nodeName, "#text", "Wrong node name (1)");
+ is(innerDiv.childNodes[0].textContent, "", "Wrong textContent (1)");
+ is(innerDiv.childNodes[1].nodeName, "CODE", "Wrong node name (2)");
+ is(innerDiv.childNodes[1].textContent, "hello", "Wrong textContent (2)");
+ is(innerDiv.childNodes[2].nodeName, "#text", "Wrong node name (3)");
+ is(innerDiv.childNodes[2].textContent, "", "Wrong textContent (3)");
+}
+
+function runTest() {
+ testDocument1();
+ testDocument2();
+ testSurroundContents();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug454326.html b/dom/base/test/test_bug454326.html
new file mode 100644
index 0000000000..d477db4288
--- /dev/null
+++ b/dom/base/test/test_bug454326.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454326
+-->
+<head>
+ <title>Test for Bug 454326</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=454326">Mozilla Bug 454326</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="partial-text-selection">Hello Hello <div></div>World!</div>
+<div id="partial-element-selection"><div id="begin">Hello Hello </div><div></div><div id="end">World!</div></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 454326 **/
+
+ function reinitPartialTextSelection() {
+ var pts = document.getElementById("partial-text-selection");
+ pts.textContent = null;
+ pts.appendChild(document.createTextNode("Hello Hello "));
+ pts.appendChild(document.createElement("div"));
+ pts.appendChild(document.createTextNode("World!"));
+ }
+
+
+ function doTest() {
+ var pts = document.getElementById("partial-text-selection");
+ var ex = null;
+ try {
+ var r1 = document.createRange();
+ r1.setStart(pts.firstChild, 6);
+ r1.setEnd(pts.lastChild, 6);
+ is(r1.toString(), "Hello World!", "Wrong range!");
+ r1.surroundContents(document.createElement("div"));
+ is(r1.toString(), "Hello World!", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ }
+ is(ex, null, "Unexpected exception!");
+
+ reinitPartialTextSelection();
+ ex = null;
+ try {
+ var r2 = document.createRange();
+ r2.setStart(pts.firstChild, 6);
+ r2.setEnd(pts, 2);
+ is(r2.toString(), "Hello ", "Wrong range!");
+ r2.surroundContents(document.createElement("div"));
+ is(r2.toString(), "Hello ", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ }
+ is(ex, null, "Unexpected exception!");
+
+ reinitPartialTextSelection();
+ ex = null;
+ try {
+ var r3 = document.createRange();
+ r3.setStart(pts, 1);
+ r3.setEnd(pts.lastChild, 6);
+ is(r3.toString(), "World!", "Wrong range!");
+ r3.surroundContents(document.createElement("div"));
+ is(r3.toString(), "World!", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ }
+ is(ex, null, "Unexpected exception!");
+
+ reinitPartialTextSelection();
+ ex = null;
+ try {
+ var r3 = document.createRange();
+ r3.setStart(pts.firstChild, 6);
+ r3.setEnd(pts.firstChild.nextSibling, 0);
+ is(r3.toString(), "Hello ", "Wrong range!");
+ r3.surroundContents(document.createElement("div"));
+ is(r3.toString(), "Hello ", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ is(e.name, "InvalidStateError", "Didn't get InvalidStateError exception!");
+ is(Object.getPrototypeOf(e), DOMException.prototype, "Didn't get DOMException!");
+ is(e.code, 11, "Didn't get INVALID_STATE_ERR exception!");
+ }
+ ok(ex, "There should have been an exception!");
+
+ reinitPartialTextSelection();
+ ex = null;
+ try {
+ var r3 = document.createRange();
+ r3.setStart(pts.firstChild.nextSibling, 0);
+ r3.setEnd(pts.lastChild, 6);
+ is(r3.toString(), "World!", "Wrong range!");
+ r3.surroundContents(document.createElement("div"));
+ is(r3.toString(), "World!", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ is(e.name, "InvalidStateError", "Didn't get InvalidStateError exception!");
+ is(Object.getPrototypeOf(e), DOMException.prototype, "Didn't get DOMException!");
+ is(e.code, 11, "Didn't get INVALID_STATE_ERR exception!");
+ }
+ ok(ex, "There should have been an exception!");
+
+ ex = null;
+ try {
+ var pes = document.getElementById("partial-element-selection");
+ var r4 = document.createRange();
+ r4.setStart(pes.firstChild.firstChild, 6);
+ r4.setEnd(pes.lastChild.firstChild, 6);
+ is(r4.toString(), "Hello World!", "Wrong range!");
+ r4.surroundContents(document.createElement("div"));
+ is(r4.toString(), "Hello World!", "Wrong range!");
+ } catch(e) {
+ ex = e;
+ is(e.name, "InvalidStateError", "Didn't get InvalidStateError exception!");
+ is(Object.getPrototypeOf(e), DOMException.prototype, "Didn't get DOMException!");
+ is(e.code, 11, "Didn't get INVALID_STATE_ERR exception!");
+ }
+ ok(ex, "There should have been an exception!");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+ addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug455472.html b/dom/base/test/test_bug455472.html
new file mode 100644
index 0000000000..b99c8a3fae
--- /dev/null
+++ b/dom/base/test/test_bug455472.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=455472
+-->
+<head>
+ <title>Test for Bug 455472</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=455472">Mozilla Bug 455472</a>
+<p id="display"></p>
+<script>
+ var ran = [ false, false, false, false, false ];
+</script>
+<div id="content" style="display: none">
+ <iframe srcdoc="<script>parent.ran[0]=true</script>"></iframe>
+ <object id="o1" type="text/html" data="object_bug455472.html"></object>
+ <embed type="image/svg+xml" src="embed_bug455472.html">
+ <object type="text/html" data="data:application/octet-stream,<script>parent.ran[3]=true</script>"></object>
+ <embed type="image/svg+xml" src="data:application/octet-stream,<svg%20xmlns='http://www.w3.org/2000/svg'%20onload='parent.ran[4]=true'/>">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 455472 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var expected = [ true, true, true, false, false ];
+ is (expected.length, ran.length, "Length mismatch");
+ for (var i = 0; i < expected.length; ++i) {
+ is(ran[i], expected[i],
+ "Unexpected behavior in object " + i + " (0-based)");
+ }
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug455629.html b/dom/base/test/test_bug455629.html
new file mode 100644
index 0000000000..73d6a4c6f2
--- /dev/null
+++ b/dom/base/test/test_bug455629.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=455629
+-->
+<head>
+ <title>Test for Bug 455629</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=455629">Mozilla Bug 455629</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 455629 **/
+SimpleTest.waitForExplicitFinish();
+
+var done = 0;
+function doTest(name) {
+ var elem = $(name);
+ var doc = elem.getSVGDocument();
+ try {
+ doc.foopy = 42;
+ fail("Able to set cross origin property!");
+ } catch (e) {
+ ok(true, "unable to set non-allAccess property cross origin");
+ }
+
+ if (elem instanceof HTMLObjectElement) {
+ doc = elem.contentDocument;
+ try {
+ doc.foopy = 42;
+ fail("Able to set cross origin property!");
+ } catch (e) {
+ ok(true, "unable to set non-allAccess property cross origin");
+ }
+ }
+
+ if (++done == 2) {
+ SimpleTest.finish();
+ }
+}
+</script>
+
+<object id="obj"
+ type="image/svg+xml"
+ onload="doTest('obj')"
+ data="http://example.org/tests/dom/base/test/bug455629-helper.svg">
+</object>
+
+<embed id="emb"
+ type="image/svg+xml"
+ onload="doTest('emb')"
+ src="http://example.org/tests/dom/base/test/bug455629-helper.svg">
+</embed>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug456262.html b/dom/base/test/test_bug456262.html
new file mode 100644
index 0000000000..253499aaa5
--- /dev/null
+++ b/dom/base/test/test_bug456262.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=456262
+-->
+<head>
+ <title>Test for Bug 456262</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=456262">Mozilla Bug 456262</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 456262 **/
+
+function runTest() {
+ document.expando = document;
+ document.documentElement.expando = document;
+ document.documentElement.setAttribute("foo", "bar");
+ document.documentElement.getAttributeNode("foo").expando = document;
+ SpecialPowers.gc();
+ ok(document.expando, "Should have preserved the expando!");
+ ok(document.documentElement.expando, "Should have preserved the expando!");
+ ok(document.documentElement.getAttributeNode('foo').expando,
+ "Should have preserved the expando!");
+}
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug457746.html b/dom/base/test/test_bug457746.html
new file mode 100644
index 0000000000..807af1cac0
--- /dev/null
+++ b/dom/base/test/test_bug457746.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=457746
+-->
+<head>
+ <title>Test for Bug 457746</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=457746">Mozilla Bug 457746</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 457746 **/
+SimpleTest.waitForExplicitFinish();
+
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "bug457746.sjs");
+xhr.send("");
+xhr.abort();
+xhr.open("GET", "bug457746.sjs");
+xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ is(xhr.responseText, "\u00c1", "Broken encoding conversion?");
+ SimpleTest.finish();
+ }
+}
+xhr.send("");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug459424.html b/dom/base/test/test_bug459424.html
new file mode 100644
index 0000000000..70acdc838c
--- /dev/null
+++ b/dom/base/test/test_bug459424.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=459424
+-->
+<head>
+ <title>Test for Bug 459424</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=459424">Mozilla Bug 459424</a>
+<p id="display" style="mask: url(no-such-document-I-tell-you#x)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 459424 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ SpecialPowers.setFullZoom(window, 2);
+ is(true, true, "Gotta test something");
+ SpecialPowers.setFullZoom(window, 1);
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug461555.html b/dom/base/test/test_bug461555.html
new file mode 100644
index 0000000000..c56baf229a
--- /dev/null
+++ b/dom/base/test/test_bug461555.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=461555
+-->
+<head>
+ <title>Test for Bug 461555</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=461555">Mozilla Bug 461555</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function writeIt(n) {
+ document.write("<span>" + n + "</span>");
+}
+
+function done() {
+ nodes = document.getElementsByTagName('span');
+ is(nodes.length, 3, "wrong length");
+ for (i = 0; i < nodes.length; ++i) {
+ is(nodes[i].textContent, String(i + 1), "wrong order");
+ }
+ SimpleTest.finish();
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ done();
+});
+
+writeIt(1);
+
+</script>
+<script defer>
+writeIt(2);
+</script>
+<script>
+writeIt(3);
+</script>
diff --git a/dom/base/test/test_bug461735.html b/dom/base/test/test_bug461735.html
new file mode 100644
index 0000000000..e04c5cd72b
--- /dev/null
+++ b/dom/base/test/test_bug461735.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=461735
+-->
+<head>
+ <title>Test for Bug 461735</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=461735">Mozilla Bug 461735</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var errorFired = false;
+window.onerror = function(message, uri, line) {
+ is(message, "Script error.", "Should have empty error message");
+ is(uri,
+ "http://mochi.test:8888/tests/dom/base/test/bug461735-redirect1.sjs",
+ "Should have pre-redirect error location URI");
+ is(line, 0, "Shouldn't have a line here");
+ errorFired = true;
+}
+</script>
+<script src="bug461735-redirect1.sjs"></script>
+<script>
+ is(errorFired, true, "Should have error in redirected different origin script");
+ errorFired = false;
+</script>
+<script type="application/javascript">
+window.onerror = function(message, uri, line) {
+ is(message, "ReferenceError: c is not defined", "Should have correct error message");
+ is(uri,
+ "http://mochi.test:8888/tests/dom/base/test/bug461735-redirect2.sjs",
+ "Unexpected error location URI");
+ is(line, 3, "Should have a line here");
+ errorFired = true;
+}
+</script>
+<script src="bug461735-redirect2.sjs"></script>
+<script>
+ is(errorFired, true, "Should have error in same origin script");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug465767.html b/dom/base/test/test_bug465767.html
new file mode 100644
index 0000000000..f9e08bcda9
--- /dev/null
+++ b/dom/base/test/test_bug465767.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=465767
+-->
+<head>
+ <title>Test for Bug 465767</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465767">Mozilla Bug 465767</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 465767 **/
+
+function runTest() {
+ var node = document.createElement("div");
+ var e = null;
+ try {
+ document.implementation.createDocument("", "", null).adoptNode(node);
+ SpecialPowers.gc();
+ document.implementation.createDocument("", "", null).adoptNode(node);
+ } catch(ex) {
+ e = ex;
+ }
+ is(e, null, ".adoptNode didn't succeed!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug466080.html b/dom/base/test/test_bug466080.html
new file mode 100644
index 0000000000..ef57e1c831
--- /dev/null
+++ b/dom/base/test/test_bug466080.html
@@ -0,0 +1,150 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 466080</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id="frame1"
+ src="https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs">
+
+ This iframe should load the resource via the src-attribute from
+ a secure server which requires a client-cert. Doing this is
+ supposed to work, but further below in the test we try to load
+ the resource from the same url using a XHR, which should not work.
+
+ TODO : What if we change 'src' from JS? Would/should it load?
+
+</iframe>
+
+<script class="testbody" type="text/javascript">
+
+"use strict";
+
+onWindowLoad();
+
+let alltests = [
+
+// load resource from a relative url - this should work
+ { url:"bug466080.sjs",
+ status_check:"==200",
+ error:"XHR from relative URL"},
+
+// TODO - load the resource from a relative url via https..?
+
+// load a non-existing resource - should get "404 Not Found"
+ { url:"bug466080-does-not.exist",
+ status_check:"==404",
+ error:"XHR loading non-existing resource"},
+
+// load resource from cross-site non-secure server
+ { url:"http://test1.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR from cross-site plaintext server"},
+
+// load resource from cross-site secure server - should work since no credentials are needed
+ { url:"https://test1.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR from cross-site secure server"},
+
+// load resource from cross-site secure server - should work since the server just requests certs
+ { url:"https://requestclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR from cross-site secure server requesting certificate"},
+
+// load resource from cross-site secure server - should NOT work since the server requires cert
+// note that this is the url which is used in the iframe.src above
+ { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"!=200",
+ error:"XHR from cross-site secure server requiring certificate"},
+
+// repeat previous, - should NOT work
+ { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR w/ credentials from cross-site secure server requiring certificate",
+ withCredentials:"true"},
+
+// repeat previous, but with credentials - should work
+ { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR w/ credentials from cross-site secure server requiring certificate",
+ withCredentials:"true"},
+
+// repeat previous, withCredentials but using a weird method to force preflight
+// should NOT work since our preflight is anonymous and will fail with our simple server
+ { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"!=200",
+ error:"XHR PREFLIGHT from cross-site secure server requiring certificate",
+ withCredentials:"true",
+ method:"XMETHOD"},
+
+// repeat previous, withCredentials but using a weird method to force preflight
+// Set network.cors_preflight.allow_client_cert pref, that will allow cers on an
+// anonymous connection.
+// This should work since our preflight will work now.
+ { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
+ status_check:"==200",
+ error:"XHR PREFLIGHT from cross-site secure server requiring certificate",
+ withCredentials:"true",
+ method:"XMETHOD",
+ enableCertOnPreflight: true},
+
+ { cleanEnableCertOnPreflight: true},
+];
+
+async function onWindowLoad() {
+ // First, check that resource was loaded into the iframe
+ // This check in fact depends on bug #444165... :)
+ await new Promise(resolve => {
+ document.getElementById("frame1").onload = () => { resolve(); };
+ });
+
+ async function runTest(test) {
+ if (test.cleanEnableCertOnPreflight) {
+ await SpecialPowers.pushPrefEnv({"set": [["network.cors_preflight.allow_client_cert", false]]});
+ if (!alltests.length) {
+ SimpleTest.finish();
+ } else {
+ runTest(alltests.shift());
+ }
+ } else {
+ if (test.enableCertOnPreflight != null) {
+ await SpecialPowers.pushPrefEnv({"set": [["network.cors_preflight.allow_client_cert", true]]});
+ }
+ var xhr = new XMLHttpRequest();
+
+ var method = "GET";
+ if (test.method != null) { method = test.method; }
+ xhr.open(method, test.url);
+
+ xhr.withCredentials = test.withCredentials;
+
+ SpecialPowers.wrap(xhr).setRequestHeader("Connection", "Keep-Alive", false);
+
+ try {
+ xhr.send();
+ } catch(e) {
+ }
+
+ xhr.onloadend = function() {
+ var success = eval(xhr.status + test.status_check);
+ ok(success, test.error);
+
+ if (!alltests.length) {
+ SimpleTest.finish();
+ } else {
+ runTest(alltests.shift());
+ }
+ };
+ }
+ }
+
+ runTest(alltests.shift());
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug466409.html b/dom/base/test/test_bug466409.html
new file mode 100644
index 0000000000..b0255e24cf
--- /dev/null
+++ b/dom/base/test/test_bug466409.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=466409
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=466409">Mozilla Bug 466409</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 466409 **/
+
+
+SimpleTest.waitForExplicitFinish();
+var testframe = document.getElementById('testframe');
+
+testframe.onload = function () {
+ ok(true, "page loaded successfully");
+ SimpleTest.finish();
+};
+testframe.src = "bug466409-page.html";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug466751.xhtml b/dom/base/test/test_bug466751.xhtml
new file mode 100644
index 0000000000..b9756c0f9d
--- /dev/null
+++ b/dom/base/test/test_bug466751.xhtml
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=466751
+-->
+<head>
+ <title>Test for Bug 466751</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=466751">Mozilla Bug 466751</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="test" />
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+/** Test for Bug 466751 **/
+
+var el = $("test");
+var name, message;
+
+try {
+ el.innerHTML = '<div ">bla</div>';
+} catch (ex) {
+ name = ex.name;
+ message = ex.message;
+}
+
+const NS_ERROR_DOM_SYNTAX_ERR = 0x8053000C;
+ok(/An invalid or illegal string was specified/.test(message), "threw SyntaxError message");
+is(name, "SyntaxError", "threw SyntaxError");
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug469020.html b/dom/base/test/test_bug469020.html
new file mode 100644
index 0000000000..f7f7631c93
--- /dev/null
+++ b/dom/base/test/test_bug469020.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469020
+-->
+<head>
+ <title>Test for Bug 469020</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469020">Mozilla Bug 469020</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 469020 **/
+
+ var range = null;
+ var anchor = null;
+
+ function doRangeAnchor(elem, start, end) {
+ range = document.createRange();
+ range.setStart(elem.firstChild, start);
+ end = end < elem.lastChild.length ? end : elem.lastChild.length
+ range.setEnd(elem.lastChild, end);
+ anchor = document.createElement('a');
+ anchor.href = "javascript: void(0);";
+ range.surroundContents(anchor);
+ }
+
+ function undoRangeAnchor() {
+ var pnode = anchor.parentNode;
+ var range2 = document.createRange();
+ range2.selectNodeContents(anchor);
+ var contents = range2.extractContents();
+ pnode.replaceChild(contents,anchor);
+ }
+
+function serializeNode(node) {
+ var s;
+ var isElem = false;
+ if (node.nodeName == "#text") {
+ if (node.nodeValue) {
+ s = node.nodeValue
+ } else {
+ s = "<#empty>"
+ }
+ } else {
+ isElem = true;
+ s = "<" + node.nodeName + ">";
+ }
+ for (var j = 0; j < node.childNodes.length; ++j) {
+ s += serializeNode(node.childNodes[j]);
+ }
+ if (isElem) {
+ s += "</" + node.nodeName + ">";
+ }
+ return s;
+}
+
+function runTest(elementID, start, end, expected1, expected2, expected3) {
+ var e = document.getElementById(elementID);
+ doRangeAnchor(e, start, end);
+ is(serializeNode(e), expected1, "Wrong range behavior!");
+ document.getElementById('log').textContent += serializeNode(e) + "\n";
+ undoRangeAnchor();
+ is(serializeNode(e), expected2, "Wrong range behavior!");
+ document.getElementById('log').textContent += serializeNode(e) + "\n";
+ doRangeAnchor(e, start, end);
+ is(serializeNode(e), expected3, "Wrong range behavior!");
+ document.getElementById('log').textContent += serializeNode(e) + "\n";
+}
+
+function runTests() {
+ runTest("test1", 0, 3,
+ "<P><#empty><A>http://www.<SPAN>mozilla.</SPAN>org</A><#empty></P>",
+ "<P><#empty>http://www.<SPAN>mozilla.</SPAN>org<#empty></P>",
+ "<P><#empty><A><#empty>http://www.<SPAN>mozilla.</SPAN>org<#empty></A><#empty></P>");
+
+ runTest("test2", 1, 3,
+ "<P>h<A>ttp://www.<SPAN>mozilla.</SPAN>org</A><#empty></P>",
+ "<P>http://www.<SPAN>mozilla.</SPAN>org<#empty></P>",
+ "<P>h<A><#empty>ttp://www.<SPAN>mozilla.</SPAN>org<#empty></A><#empty></P>");
+
+ runTest("test3", 0, 2,
+ "<P><#empty><A>http://www.<SPAN>mozilla.</SPAN>or</A>g</P>",
+ "<P><#empty>http://www.<SPAN>mozilla.</SPAN>org</P>",
+ "<P><#empty><A><#empty>http://www.<SPAN>mozilla.</SPAN>org</A><#empty></P>");
+
+ runTest("test4", 1, 2,
+ "<P>h<A>ttp://www.<SPAN>mozilla.</SPAN>or</A>g</P>",
+ "<P>http://www.<SPAN>mozilla.</SPAN>org</P>",
+ "<P>h<A><#empty>ttp://www.<SPAN>mozilla.</SPAN>org</A><#empty></P>");
+
+ runTest("test5", 11, 0,
+ "<P>http://www.<A><#empty><SPAN>mozilla.</SPAN><#empty></A>org</P>",
+ "<P>http://www.<#empty><SPAN>mozilla.</SPAN><#empty>org</P>",
+ "<P>http://www.<A><#empty><#empty><SPAN>mozilla.</SPAN><#empty><#empty></A>org</P>");
+
+ runTest("test6", 10, 1,
+ "<P>http://www<A>.<SPAN>mozilla.</SPAN>o</A>rg</P>",
+ "<P>http://www.<SPAN>mozilla.</SPAN>org</P>",
+ "<P>http://www<A><#empty>.<SPAN>mozilla.</SPAN>or</A>g</P>");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+
+
+</script>
+</pre>
+<p id="test1">http://www.<span>mozilla.</span>org</p>
+<p id="test2">http://www.<span>mozilla.</span>org</p>
+<p id="test3">http://www.<span>mozilla.</span>org</p>
+<p id="test4">http://www.<span>mozilla.</span>org</p>
+<p id="test5">http://www.<span>mozilla.</span>org</p>
+<p id="test6">http://www.<span>mozilla.</span>org</p>
+<pre id="log">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug469304.html b/dom/base/test/test_bug469304.html
new file mode 100644
index 0000000000..c2c6532dd7
--- /dev/null
+++ b/dom/base/test/test_bug469304.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469304
+-->
+<head>
+ <title>Test for Bug 469304</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469304">Mozilla Bug 469304</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 469304 **/
+function testGetAttribute() {
+ var a1 = document.createAttributeNS("", "aa");
+ a1.nodeValue = "lowercase";
+ var a2 = document.createAttributeNS("", "AA");
+ a2.nodeValue = "UPPERCASE";
+ document.body.setAttributeNode(a1);
+ document.body.setAttributeNode(a2);
+ var log = document.getElementById("log");
+ is(document.body.getAttribute('aa'), "lowercase", "First attribute should have localname aa (1).");
+ is(document.body.getAttribute('AA'), "lowercase", "First attribute should have localname aa (2).");
+ is(document.body.getAttributeNS("", "aa"), "lowercase", "First attribute should have localName aa (3).");
+ is(document.body.getAttributeNS("", "AA"), "UPPERCASE", "Second attribute should have value UPPERCASE!");
+
+ var s = "";
+ for (var i = 0; i < document.body.attributes.length; ++i) {
+ s += document.body.attributes[i].nodeName + "=" +
+ document.body.attributes[i].nodeValue;
+ }
+ is(s, "aa=lowercaseAA=UPPERCASE", "Wrong attribute!");
+
+ is(document.body.getAttributeNode("aa"), document.body.getAttributeNode("AA"),
+ "Wrong node!");
+
+ document.body.getAttributeNodeNS("", "AA").nodeValue = "FOO";
+ is(document.body.getAttributeNS("", "AA"), "FOO", "Wrong value!");
+
+ document.body.removeAttributeNode(document.body.getAttributeNodeNS("", "aa"));
+ ok(!document.body.getAttributeNode("AA"), "Should not have attribute node! (1)");
+ ok(!document.body.getAttributeNode("aa"), "Should not have attribute node! (2)");
+
+ is(a2.nodeValue, "FOO", "Wrong value!");
+ a2.nodeValue = "UPPERCASE";
+ is(a2.nodeValue, "UPPERCASE", "Wrong value!");
+
+ document.body.setAttributeNode(a2);
+ is(document.body.getAttributeNS("", "AA"), "UPPERCASE", "Wrong value!");
+ ok(document.body.getAttributeNodeNS("", "AA"), "Should have attribute node!");
+ is(document.body.getAttributeNS("", "aa"), null, "No attribute has the localName aa.");
+ ok(!document.body.getAttributeNodeNS("", "aa"), "Should not have attribute node!");
+}
+testGetAttribute();
+
+// A bit modified testcases from WebKit.
+function testGetAttributeCaseInsensitive() {
+ var div = document.createElement('div');
+ div.setAttribute("mixedCaseAttrib", "x");
+ // Do original case lookup, and lowercase lookup.
+ return div.getAttribute("mixedcaseattrib");
+}
+is(testGetAttributeCaseInsensitive(), "x", "(1)");
+
+function testGetAttributeNodeMixedCase() {
+ var div = document.createElement('div');
+ var a = div.ownerDocument.createAttributeNS("", "mixedCaseAttrib");
+ a.nodeValue = "x";
+ div.setAttributeNode(a);
+ return div.getAttributeNS("", "mixedCaseAttrib");
+}
+is(testGetAttributeNodeMixedCase(), "x", "(2)");
+
+function testGetAttributeNodeLowerCase(div) {
+ var div = document.createElement('div');
+ var a = div.ownerDocument.createAttribute("lowercaseattrib");
+ a.nodeValue = "x";
+ div.setAttributeNode(a);
+ return div.getAttribute("lowerCaseAttrib");
+}
+is(testGetAttributeNodeLowerCase(), "x", "(3)");
+
+function testSetAttributeNodeKeepsRef(div) {
+ var div = document.createElement('div');
+ var a = div.ownerDocument.createAttribute("attrib_name");
+ a.nodeValue = "0";
+ div.setAttributeNode(a);
+ // Mutate the attribute node.
+ a.nodeValue = "1";
+ return div.getAttribute("attrib_name");
+}
+is(testSetAttributeNodeKeepsRef(), "1", "(4)");
+
+function testAttribNodeNameFoldsCase() {
+ var div = document.createElement('div');
+ var a = div.ownerDocument.createAttribute("A");
+ a.nodeValue = "x";
+ div.setAttributeNode(a);
+ var result = [ a.name, a.nodeName ];
+ return result.join(",");
+}
+is(testAttribNodeNameFoldsCase(), "a,a", "(5)");
+
+function testAttribNodeNameFoldsCaseGetNode() {
+ var body = document.body;
+ var a = body.ownerDocument.createAttribute("A");
+ a.nodeValue = "x";
+ body.setAttributeNode(a);
+ a = document.body.getAttributeNodeNS("", "a");
+ if (!a)
+ return "FAIL";
+ var result = [ a.name, a.nodeName ];
+ return result.join(",");
+}
+is(testAttribNodeNameFoldsCaseGetNode(), "a,a", "(6)");
+
+function testAttribNodeNameFoldsCaseGetNode2() {
+ var body = document.body;
+ var a = body.ownerDocument.createAttribute("B");
+ a.nodeValue = "x";
+ body.setAttributeNode(a);
+ a = document.body.getAttributeNodeNS("", "b");
+ if (!a)
+ return "FAIL";
+ // Now create node second time
+ a = body.ownerDocument.createAttribute("B");
+ a.nodeValue = "x";
+ body.setAttributeNode(a);
+ a = document.body.getAttributeNodeNS("", "b");
+ var result = [ a.name, a.nodeName ];
+ return result.join(",");
+}
+is(testAttribNodeNameFoldsCaseGetNode2(), "b,b", "(7)");
+
+function testAttribNodeNameGetMutate() {
+ var body = document.body;
+ var a = body.ownerDocument.createAttribute("c");
+ a.nodeValue = "0";
+ body.setAttributeNode(a);
+ a = document.body.getAttributeNode("c");
+ a.value = "1";
+ a = document.body.getAttributeNode("c");
+ return a.nodeValue;
+}
+is(testAttribNodeNameGetMutate(), "1", "(8)");
+
+var node = document.createElement("div");
+var attrib = document.createAttribute("myAttrib");
+attrib.nodeValue = "XXX";
+node.setAttributeNode(attrib);
+// Note, this is different to what WebKit does
+is((new XMLSerializer).serializeToString(node),
+ "<div xmlns=\"http://www.w3.org/1999/xhtml\" myattrib=\"XXX\"></div>", "(9)");
+is(node.getAttributeNode('myAttrib').name, "myattrib", "(10)");
+is(node.getAttributeNode('myattrib').name, "myattrib", "(11)");
+is(attrib.name, "myattrib", "(12)");
+
+var o = document.createElement("div");
+o.setAttribute("myAttrib","htmlattr");
+o.setAttributeNS("","myAttrib","123");
+is(o.getAttributeNodeNS("","myAttrib").nodeName, "myAttrib", "nodeName should be case-sensitive.");
+is(o.getAttributeNode("myAttrib").nodeName, "myattrib", "nodeName shouldn't be case-sensitive.");
+is(o.getAttributeNodeNS("","myAttrib").value, "123", "Should have got the case-sensitive attribute.");
+is(o.attributes.length, 2, "Should have two attributes.");
+o.setAttribute("myAttrib2", "htmlattr");
+o.setAttributeNS("", "myAttrib2", "123");
+is(o.attributes.length, 4, "Should have four attributes.");
+var an = o.attributes.removeNamedItem("myAttrib2");
+is(o.attributes.length, 3, "An attribute should have been removed.");
+is(an.value, "htmlattr", "The removed attribute should have been the case-insensitive attribute.");
+is(o.getAttribute("myAttrib2"), null, "Element shouldn't have case-insensitive attribute anymore.");
+var an2 = o.attributes.removeNamedItemNS("", "myAttrib2");
+is(an2.localName, "myAttrib2", "The removed attribute should be case-sensitive.");
+is(o.attributes.length, 2, "Element should have two attributes.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug473162-1.html b/dom/base/test/test_bug473162-1.html
new file mode 100644
index 0000000000..f9025158de
--- /dev/null
+++ b/dom/base/test/test_bug473162-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473162
+-->
+<head>
+ <title>Test for Bug 473162</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473162">Mozilla Bug 473162</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<span id="TEST" class="TEST"></span>
+<span id="test" class="test"></span>
+<span id="Test" class="Test"></span>
+<span id="tesT" class="tesT"></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 473162 **/
+var nodes = document.getElementsByClassName("test");
+is(nodes.length, 1, "Unexpected length");
+is(nodes[0], $("test"), "Unexpected first node");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug473162-2.html b/dom/base/test/test_bug473162-2.html
new file mode 100644
index 0000000000..976c830675
--- /dev/null
+++ b/dom/base/test/test_bug473162-2.html
@@ -0,0 +1,33 @@
+<!-- NOTE: THIS TEST MUST BE IN QUIRKS MODE -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473162
+-->
+<head>
+ <title>Test for Bug 473162</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473162">Mozilla Bug 473162</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<span id="TEST" class="TEST"></span>
+<span id="test" class="test"></span>
+<span id="Test" class="Test"></span>
+<span id="tesT" class="tesT"></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 473162 **/
+var nodes = document.getElementsByClassName("test");
+is(nodes.length, 4, "Unexpected length");
+is(nodes[0], $("TEST"), "Unexpected first node");
+is(nodes[1], $("test"), "Unexpected second node");
+is(nodes[2], $("Test"), "Unexpected third node");
+is(nodes[3], $("tesT"), "Unexpected fourth node");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug475156.html b/dom/base/test/test_bug475156.html
new file mode 100644
index 0000000000..49bdb2b287
--- /dev/null
+++ b/dom/base/test/test_bug475156.html
@@ -0,0 +1,301 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475156
+-->
+<head>
+ <title>Test for Bug 475156</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="drive(tests.shift());">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var path = "http://mochi.test:8888/tests/dom/base/test/";
+
+function fromCache(xhr)
+{
+ var ch = SpecialPowers.wrap(xhr).channel.QueryInterface(SpecialPowers.Ci.nsICacheInfoChannel);
+ return ch.isFromCache();
+}
+
+var tests = [
+ // First just init the file with an ETag
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a1");
+ },
+
+ loading(xhr)
+ {
+ },
+
+ done(xhr)
+ {
+ },
+ },
+
+ // Try to load the file the first time regularly, we have to get 200 OK
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We get a fresh version of the file");
+ is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
+ is(xhr.responseText, "a1", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the second time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
+ is(xhr.responseText, "a1", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the third time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
+ is(xhr.responseText, "a1", "We got the expected file body");
+ },
+ },
+
+ // Now modify the ETag
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a2");
+ },
+
+ loading(xhr)
+ {
+ },
+
+ done(xhr)
+ {
+ },
+ },
+
+ // Try to load the file, we have to get 200 OK with the new content
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We get a fresh version of the file");
+ is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
+ is(xhr.responseText, "a2", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the second time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
+ is(xhr.responseText, "a2", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the third time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
+ is(xhr.responseText, "a2", "We got the expected file body");
+ },
+ },
+
+ // Now modify the ETag ones more
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a3");
+ },
+
+ loading(xhr)
+ {
+ },
+
+ done(xhr)
+ {
+ },
+ },
+
+ // Try to load the file, we have to get 200 OK with the new content
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We get a fresh version of the file");
+ is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
+ is(xhr.responseText, "a3", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the second time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
+ is(xhr.responseText, "a3", "We got the expected file body");
+ },
+ },
+
+ // Try to load the file the third time regularly, we have to get 304 Not Modified
+ {
+ init(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done(xhr)
+ {
+ is(xhr.status, 200, "We got cached version");
+ is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
+ is(xhr.responseText, "a3", "We got the expected file body");
+ },
+ },
+
+ // Load one last time to reset the state variable in the .sjs file
+ {
+ init (xhr) {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading (xhr) {
+ },
+
+ done (xhr) {
+ },
+ },
+]
+
+
+function drive(test)
+{
+ SpecialPowers.pushPrefEnv({set: [["network.http.rcwn.enabled", false]]}, _=>{
+ var xhr = new XMLHttpRequest();
+ test.init(xhr);
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 3) {
+ test.loading(this);
+ }
+ if (this.readyState == 4) {
+ test.done(this);
+ if (!tests.length)
+ SimpleTest.finish();
+ else
+ drive(tests.shift());
+ }
+ }
+ xhr.send();
+ });
+}
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug482935.html b/dom/base/test/test_bug482935.html
new file mode 100644
index 0000000000..444c3aada7
--- /dev/null
+++ b/dom/base/test/test_bug482935.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 482935</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href=" /tests/SimpleTest/test.css" />
+</head>
+<body onload="onWindowLoad()">
+<script class="testbody" type="text/javascript">
+
+var url = "bug482935.sjs";
+
+function clearCache() {
+ if (SpecialPowers.isMainProcess()) {
+ SpecialPowers.Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(SpecialPowers.Ci.nsICacheStorageService).
+ clear();
+ }
+}
+
+// Tests that the response is cached if the request is cancelled
+// after it has reached state 4
+function testCancelInPhase4() {
+
+ clearCache();
+
+ // First request - should be loaded from server
+ var xhr = new XMLHttpRequest();
+ xhr.addEventListener("readystatechange", function() {
+ if (xhr.readyState < xhr.DONE) return;
+ is(xhr.readyState, xhr.DONE, "wrong readyState");
+ xhr.abort();
+ SimpleTest.executeSoon(function() {
+ // This request was cancelled, so the responseText should be empty string
+ is(xhr.responseText, "", "Expected empty response to cancelled request");
+
+ // Second request - should be found in cache
+ var xhr2 = new XMLHttpRequest();
+
+ xhr2.addEventListener("load", function() {
+ is(xhr2.responseText, "0", "Received fresh value for second request");
+ SimpleTest.finish();
+ });
+
+ xhr2.open("GET", url);
+ xhr2.setRequestHeader("X-Request", "1", false);
+
+ try { xhr2.send(); }
+ catch(e) {
+ is(xhr2.status, "200", "Exception!");
+ }
+ });
+ });
+
+ xhr.open("GET", url, true);
+ xhr.setRequestHeader("X-Request", "0", false);
+ try { xhr.send(); }
+ catch(e) {
+ is("Nothing", "Exception", "Boom: " + e);
+ }
+}
+
+function onWindowLoad() {
+ // Disable rcwn to make cache behavior deterministic.
+ SpecialPowers.pushPrefEnv({set: [["network.http.rcwn.enabled", false]]},
+ testCancelInPhase4);
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug484396.html b/dom/base/test/test_bug484396.html
new file mode 100644
index 0000000000..24c34b3b1e
--- /dev/null
+++ b/dom/base/test/test_bug484396.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=484396
+-->
+<head>
+ <title>Test for Bug 484396</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484396">Mozilla Bug 484396</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var msg = 'xhr.open() succeeds with empty url';
+var xhr = new XMLHttpRequest();
+
+try {
+ xhr.open('GET', '');
+ ok(true, msg);
+} catch (e) {
+ ok(false, msg);
+}
+
+xhr.onerror = function () {
+ ok(false, 'xhr.send() succeeds with empty url');
+}
+
+xhr.onreadystatechange = function () {
+ if (4 == xhr.readyState) {
+ is(xhr.status, 200, 'xhr.status is 200 OK');
+ ok(-1 < xhr.responseText.indexOf('Bug 484396'), 'xhr.responseText contains the calling page');
+
+ SimpleTest.finish();
+ }
+};
+
+xhr.send('');
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug493881.html b/dom/base/test/test_bug493881.html
new file mode 100644
index 0000000000..a1ae21daa5
--- /dev/null
+++ b/dom/base/test/test_bug493881.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=493881
+-->
+
+<head>
+ <title>Test for Bug 493881</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_bug493881.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ </style>
+</head>
+
+<body id="body">
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=493881"
+ target="_blank" >Mozilla Bug 493881</a>
+
+<p id="display"></p>
+
+<a id="nonvisitedlink" href="http://www.example.com/">Non-visited link</a>
+<a id="visitedlink" href="">Visited link</a>
+<p id="plaintext">some text</p>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug493881.js b/dom/base/test/test_bug493881.js
new file mode 100644
index 0000000000..faee306c68
--- /dev/null
+++ b/dom/base/test/test_bug493881.js
@@ -0,0 +1,100 @@
+/**
+ * Test for Bug 493881: Changes to legacy HTML color properties before the BODY is loaded
+ * should be ignored. Additionally, after BODY loads, setting any of these properties to undefined
+ * should cause them to be returned as the string "undefined".
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var legacyProps = [
+ "fgColor",
+ "bgColor",
+ "linkColor",
+ "vlinkColor",
+ "alinkColor",
+];
+var testColors = ["blue", "silver", "green", "orange", "red"];
+var rgbTestColors = [
+ "rgb(255, 0, 0)",
+ "rgb(192, 192, 192)",
+ "rgb(0, 128, 0)",
+ "rgb(255, 165, 0)",
+ "rgb(255, 0, 0)",
+];
+var idPropList = [
+ { id: "plaintext", prop: "color" },
+ { id: "body", prop: "background-color" },
+ { id: "nonvisitedlink", prop: "color" },
+ { id: "visitedlink", prop: "color" },
+];
+var initialValues = [];
+
+function setAndTestProperty(prop, color) {
+ var initial = document[prop];
+ document[prop] = color;
+ is(document[prop], initial, "document[" + prop + "] not ignored before body");
+ return initial;
+}
+
+/**
+ * Attempt to set legacy color properties before BODY exists, and verify that such
+ * attempts are ignored.
+ */
+for (let i = 0; i < legacyProps.length; i++) {
+ initialValues[i] = setAndTestProperty(legacyProps[i], testColors[i]);
+}
+
+/**
+ * After BODY loads, run some more tests.
+ */
+addLoadEvent(function () {
+ // Verify that the legacy color properties still have their original values.
+ for (let i = 0; i < legacyProps.length; i++) {
+ is(
+ document[legacyProps[i]],
+ initialValues[i],
+ "document[" + legacyProps[i] + "] altered after body load"
+ );
+ }
+
+ // Verify that legacy color properties applied before BODY are really ignored when rendering.
+ // Save current computed style colors for later use.
+ for (let i = 0; i < idPropList.length; i++) {
+ var style = window.getComputedStyle(
+ document.getElementById(idPropList[i].id)
+ );
+ var color = style.getPropertyValue(idPropList[i].prop);
+ idPropList[i].initialComputedColor = color;
+ isnot(color, rgbTestColors[i], "element rendered using before-body style");
+ }
+ // XXX: Can't get links to visually activate via script events, so can't verify
+ // that the alinkColor property was not applied.
+
+ // Verify that setting legacy color props to undefined after BODY loads will cause them
+ // to be read as the string "undefined".
+ for (let i = 0; i < legacyProps.length; i++) {
+ document[legacyProps[i]] = undefined;
+ is(
+ document[legacyProps[i]],
+ "undefined",
+ "Unexpected value of " + legacyProps[i] + " after setting to undefined"
+ );
+ }
+
+ // Verify that setting legacy color props to undefined led to result
+ // of parsing undefined as a color.
+ for (let i = 0; i < idPropList.length; i++) {
+ var style = window.getComputedStyle(
+ document.getElementById(idPropList[i].id)
+ );
+ var color = style.getPropertyValue(idPropList[i].prop);
+ is(
+ color,
+ "rgb(0, 239, 14)",
+ "element's style should get result of parsing undefined as a color"
+ );
+ }
+
+ // Mark the test as finished.
+ setTimeout(SimpleTest.finish, 0);
+});
diff --git a/dom/base/test/test_bug498240.html b/dom/base/test/test_bug498240.html
new file mode 100644
index 0000000000..4bb66659d1
--- /dev/null
+++ b/dom/base/test/test_bug498240.html
@@ -0,0 +1,254 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=498240
+-->
+<head>
+ <title>Test for Bug 498240</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ .container { border: 1px solid blue; display:block; }
+ b { color:blue; }
+ i { color:magenta; }
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=498240">Mozilla Bug 498240</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 498240 **/
+
+
+function create(s) {
+ var p = document.createElement('span');
+ p.innerHTML = s;
+ p.setAttribute("class","container");
+ document.body.appendChild(p);
+ return p;
+}
+function select(start, startOffset, end, endOffset) {
+ var sel = getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(start, startOffset);
+ range.setEnd(end, endOffset);
+ sel.addRange(range);
+}
+
+function insertClone(node) {
+ var sel = getSelection();
+ var range = sel.getRangeAt(0);
+ range.insertNode(node.cloneNode(true));
+}
+function insertCloneAtEnd(node) {
+ var sel = getSelection();
+ var range = sel.getRangeAt(0);
+ range.endContainer.insertBefore(node.cloneNode(true),range.endContainer.childNodes[range.endOffset]);
+}
+
+function check(start, startOffset, end, endOffset, s) {
+ var sel = getSelection();
+ var range = sel.getRangeAt(0);
+ is(range.startContainer, start, "wrong start node for range: '"+range.toString()+"'");
+ is(range.startOffset, startOffset, "wrong start offset for range: '"+range.toString()+"'");
+ is(range.endContainer, end, "wrong end node for range: '"+range.toString()+"'");
+ is(range.endOffset, endOffset, "wrong end offset for range: '"+range.toString()+"'");
+}
+
+function testInsertNode(node) {
+ var p;
+
+ p = create('a<b>bc</b>');
+ select(p.childNodes[0],0,p.childNodes[1],0);
+ insertClone(node);
+ check(p.childNodes[0],0,p.childNodes[3],0);
+
+ p = create('d<b>ef</b>');
+ select(p.childNodes[0],0,p.childNodes[1],1);
+ insertClone(node);
+ check(p.childNodes[0],0,p.childNodes[3],1);
+
+ p = create('g<b>h</b>');
+ select(p.childNodes[0],0,p.childNodes[0],0);
+ insertClone(node);
+ check(p.childNodes[0],0,p,2);
+
+ p = create('i<b>j</b>');
+ select(p.childNodes[0],1,p.childNodes[0],1);
+ insertClone(node);
+ check(p.childNodes[0],1,p,2);
+
+ p = create('k<b>l</b>');
+ select(p.childNodes[0],0,p.childNodes[1].childNodes[0],0);
+ insertClone(node);
+ check(p.childNodes[0],0,p.childNodes[3].childNodes[0],0);
+
+ p = create('m<b>no</b>');
+ select(p.childNodes[0],1,p.childNodes[1].childNodes[0],0);
+ insertClone(node);
+ check(p.childNodes[0],1,p.childNodes[3].childNodes[0],0);
+
+ p = create('p<b>qr</b>');
+ select(p.childNodes[0],1,p.childNodes[1].childNodes[0],1);
+ insertClone(node);
+ check(p.childNodes[0],1,p.childNodes[3].childNodes[0],1);
+
+ p = create('s<b>tu</b>');
+ select(p.childNodes[0],1,p.childNodes[1],0);
+ insertClone(node);
+ check(p.childNodes[0],1,p.childNodes[3],0);
+
+ p = create('<i>A</i><b>BC</b>');
+ select(p.childNodes[0],0,p.childNodes[1],0);
+ insertClone(node);
+ check(p.childNodes[0],0,p.childNodes[1],0);
+
+ p = create('<i>D</i><b>EF</b>');
+ select(p.childNodes[0],1,p.childNodes[1],1);
+ insertClone(node);
+ check(p.childNodes[0],1,p.childNodes[1],1);
+
+ p = create('<i></i><b>GH</b>');
+ select(p.childNodes[0],0,p.childNodes[1],0);
+ insertClone(node);
+ check(p.childNodes[0],0,p.childNodes[1],0);
+
+ p = create('<i>I</i><b>J</b>');
+ select(p,0,p.childNodes[1],0);
+ insertClone(node);
+ check(p,0,p.childNodes[2],0);
+
+ p = create('<i>K</i><b>L</b>');
+ select(p,0,p,2);
+ insertClone(node);
+ check(p,0,p,3);
+
+ p = create('<i>M</i><b>N</b>');
+ select(p,1,p,2);
+ insertClone(node);
+ check(p,1,p,3);
+
+ p = create('<i>O</i><b>P</b>');
+ select(p,1,p,1);
+ insertClone(node);
+ check(p,1,p,2);
+
+ p = create('<i>Q</i><b>R</b>');
+ select(p,2,p,2);
+ insertClone(node);
+ check(p,2,p,3);
+
+ p = create('<i>S</i><b>T</b>');
+ select(p,1,p,1);
+ insertCloneAtEnd(node);
+ check(p,1,p,1);
+
+ p = create('<i>U</i><b>V</b>');
+ select(p,2,p,2);
+ insertCloneAtEnd(node);
+ check(p,2,p,2);
+
+ p = create('<i>X</i><b>Y</b>');
+ select(p,0,p,1);
+ insertCloneAtEnd(node);
+ check(p,0,p,1);
+
+ p = create('<i>X</i><b><s>Y</s></b>');
+ select(p,0,p.childNodes[1],1);
+ insertCloneAtEnd(node);
+ check(p,0,p.childNodes[1],1);
+
+ p = create('<i>Z</i><b></b>');
+ select(p,0,p.childNodes[1],0);
+ insertCloneAtEnd(node);
+ check(p,0,p.childNodes[1],0);
+
+ p = create('<i>ZA</i><b><s>ZB</s><u>ZC</u></b>');
+ select(p,0,p.childNodes[1],1);
+ insertCloneAtEnd(node);
+ check(p,0,p.childNodes[1],1);
+}
+function testInvalidNodeType(node) {
+ try {
+ testInsertNode(node);
+ ok(false,"Expected an InvalidNodeTypeError");
+ } catch(e) {
+ is(e.name, "InvalidNodeTypeError", "Wrong exception, expected InvalidNodeTypeError");
+ ok(e instanceof DOMException, "Wrong type of exception: " + e);
+ is(e.code, DOMException.INVALID_NODE_TYPE_ERR, "Wrong exception code, expected INVALID_NODE_TYPE_ERR");
+ }
+}
+
+function runTest() {
+ testInsertNode(document.createTextNode('123'));
+
+ var i = document.createElement('SPAN')
+ i.innerHTML='456'
+ testInsertNode(i);
+
+ i = document.createDocumentFragment();
+ i.appendChild(document.createTextNode('789'));
+ testInsertNode(i);
+
+ /// DOM2 Traversal and Range Specification 2.13 "insertNode":
+ /// RangeException INVALID_NODE_TYPE_ERR: Raised if newNode is an Attr, Entity, Notation, or Document node.
+ // BUG: testInvalidNodeType(document.createAttribute('a'));
+ todo(false, "Test insertion of Entity node into range");
+ // TODO: testInvalidNodeType(document.createEntity());
+ todo(false, "Test insertion of Notation node into range");
+ // TODO: testInvalidNodeType(document.createNotation());
+ // BUG: testInvalidNodeType(document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null));
+
+ // Intentionally fails because of bug 418755.
+ todo(false, "test that Range::insertNode() throws WrongDocumentError when it should");
+ i = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null).createElement('html');
+ try {
+ testInsertNode(i);
+ todo(false,"Expected a WrongDocumentError");
+ } catch(e) {
+ is(e.name, "WrongDocumentError", "Wrong exception, expected WrongDocumentError");
+ ok(e instanceof DOMException, "Wrong type of exception: " + e);
+ is(e.code, DOMException.WRONG_DOCUMENT_ERR, "Wrong exception code, expected WRONG_DOCUMENT_ERR");
+ }
+
+ // Inserting an ancestor of the start container should throw HierarchyRequestError
+ todo(false, "test that Range::insertNode() throws HierarchyRequestError when it should");
+ var p = create('<b>IJK</b>');
+ select(p.childNodes[0],0,p.childNodes[0],1);
+ var sel = getSelection();
+ var range = sel.getRangeAt(0);
+ try {
+ range.insertNode(p);
+ ok(false,"Expected a HierarchyRequestError");
+ } catch(e) {
+ is(e.name, "HierarchyRequestError", "Wrong exception, expected HierarchyRequestError");
+ ok(e instanceof DOMException, "Wrong type of exception: " + e);
+ is(e.code, DOMException.HIERARCHY_REQUEST_ERR, "Wrong exception code, expected HIERARCHY_REQUEST_ERR");
+ }
+
+ // TODO: we should also have a test for:
+ /// "HierarchyRequestError: Raised if the container of the start of the Range is of a type
+ /// that does not allow children of the type of newNode"
+
+ todo(false, "InvalidStateError test goes here...");
+
+ var sel = getSelection();
+ sel.removeAllRanges();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug498433.html b/dom/base/test/test_bug498433.html
new file mode 100644
index 0000000000..31ff4a4823
--- /dev/null
+++ b/dom/base/test/test_bug498433.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for HTML serializer</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=498433">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe" src="file_htmlserializer_ipv6.html">
+ </iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function loadFileContent(aFile, aCharset) {
+ if (aCharset == undefined)
+ aCharset = 'UTF-8';
+
+ var baseUri = SpecialPowers.Cc['@mozilla.org/network/standard-url-mutator;1']
+ .createInstance(SpecialPowers.Ci.nsIURIMutator)
+ .setSpec(window.location.href)
+ .finalize();
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var cis = SpecialPowers.Ci.nsIConverterInputStream;
+
+ var inputStream = SpecialPowers.Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(cis);
+ inputStream.init(chann.open(), aCharset, 1024, cis.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {}, content = '';
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
+
+function isRoughly(actual, expected, message) {
+ return is(actual.replace("<!DOCTYPE HTML", "<!DOCTYPE html"),
+ expected,
+ message);
+}
+
+function testHtmlSerializer_1 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/html");
+
+ var doc = $("testframe").contentDocument;
+ var out, expected;
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+ //------------ no flags
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_ipv6_out.html");
+ isRoughly(out, expected, "test no flags");
+ //------------ OutputAbsoluteLinks
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputAbsoluteLinks);
+ encoder.setCharset("UTF-8");
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_ipv6_out.html");
+ isRoughly(out, expected, "test OutputAbsoluteLinks");
+ //------------ serializing a selection
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputAbsoluteLinks);
+ encoder.setNode(doc.links[0]);
+ out = encoder.encodeToString();
+ expected = "<a href=\"http://[2001:4860:a003::68]/\">Test</a>";
+ isRoughly(out, expected, "test selection");
+
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlSerializer_1);
+
+</script>
+</pre>
+
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug498897.html b/dom/base/test/test_bug498897.html
new file mode 100644
index 0000000000..848bf73698
--- /dev/null
+++ b/dom/base/test/test_bug498897.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=498897
+-->
+<head>
+ <title>Test for Bug 498897</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=498897">Mozilla Bug 498897</a>
+<p id="display"><iframe id="testframe"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 498897 **/
+
+var checkedLoad = false;
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+// Content policy / factory implementation for the test
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{65944d64-2390-422e-bea3-80d0af7f69ef}");
+var policyName = "@mozilla.org/498897_testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ var url = window.location.href.substr(0, window.location.href.indexOf('test_bug498897'));
+ let loadingPrincipal = loadInfo.loadingPrincipal;
+ if (loadingPrincipal) {
+ requestOrigin = loadingPrincipal.spec;
+ }
+ if (contentLocation.spec == url + "file_bug498897.css" &&
+ requestOrigin == url + "file_bug498897.html") {
+ checkedLoad = true;
+ }
+
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+componentManager.registerFactory(policyID, "Test content policy for bug 498897",
+ policyName, policy);
+
+var categoryManager =
+ Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName,
+ false, true);
+
+function testFinished()
+{
+ ok(checkedLoad, "Content policy didn't get called!");
+
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+
+ setTimeout(function() {
+ componentManager.unregisterFactory(policyID, policy);
+
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+document.getElementById("testframe").src = "file_bug498897.html";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug499656.html b/dom/base/test/test_bug499656.html
new file mode 100644
index 0000000000..bfc273ecf4
--- /dev/null
+++ b/dom/base/test/test_bug499656.html
@@ -0,0 +1,57 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499656
+-->
+<head>
+ <title>Test for Bug 499656</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499656">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 499655 **/
+
+div1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+div2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+div3 = document.createElementNS("test","test");
+div4 = document.createElementNS("test","TEst");
+div5 = document.createElement("test");
+div6 = document.createElement("TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(div1);
+content.appendChild(div2);
+content.appendChild(div3);
+content.appendChild(div4);
+content.appendChild(div5);
+content.appendChild(div6);
+
+list = document.getElementsByTagName('test');
+is(list.length, 4, "Number of elements found");
+ok(list[0] == div1, "First element didn't match");
+ok(list[1] == div3, "Third element didn't match");
+ok(list[2] == div5, "Fifth element didn't match");
+ok(list[3] == div6, "Sixth element didn't match");
+
+list = document.getElementsByTagName('TEst');
+is(list.length, 4, "Wrong number of elements found");
+ok(list[0] == div1, "First element didn't match");
+ok(list[1] == div4, "Fourth element didn't match");
+ok(list[2] == div5, "Fifth element didn't match");
+ok(list[3] == div6, "Sixth element didn't match");
+
+list = document.getElementsByTagNameNS('test', 'test');
+is(list.length, 1, "Wrong number of elements found");
+ok(list[0] == div3, "Third element didn't match");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug499656.xhtml b/dom/base/test/test_bug499656.xhtml
new file mode 100644
index 0000000000..926fe2b65d
--- /dev/null
+++ b/dom/base/test/test_bug499656.xhtml
@@ -0,0 +1,57 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499655
+-->
+<head>
+ <title>Test for Bug 499655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+div1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+div2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+div3 = document.createElementNS("test","test");
+div4 = document.createElementNS("test","TEst");
+div5 = document.createElement("test");
+div6 = document.createElement("TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(div1);
+content.appendChild(div2);
+content.appendChild(div3);
+content.appendChild(div4);
+content.appendChild(div5);
+content.appendChild(div6);
+
+
+list = document.getElementsByTagName('test');
+is(list.length, 3, "Number of elements found");
+ok(list[0] == div1, "First element didn't match");
+ok(list[1] == div3, "Third element didn't match");
+ok(list[2] == div5, "Fifth element didn't match");
+
+list = document.getElementsByTagName('TEst');
+is(list.length, 3, "Number of elements found");
+ok(list[0] == div2, "Second element didn't match");
+ok(list[1] == div4, "Fourth element didn't match");
+ok(list[2] == div6, "Sixth element didn't match");
+
+list = document.getElementsByTagNameNS('test', 'test');
+is(list.length, 1, "Wrong number of elements found");
+ok(list[0] == div3, "Third element didn't match");
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug500937.html b/dom/base/test/test_bug500937.html
new file mode 100644
index 0000000000..b3d254ae58
--- /dev/null
+++ b/dom/base/test/test_bug500937.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=500937
+-->
+<head>
+ <title>Test for Bug 500937</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500937">Mozilla Bug 500937</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id=iframe src="about:blank"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 500937 **/
+
+var d = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
+var h = d.documentElement;
+h.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "head"));
+var b = d.createElementNS("http://www.w3.org/1999/xhtml", "body");
+h.appendChild(b);
+
+b.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "div"));
+b.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "script"));
+b.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "br"));
+b.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "source"));
+b.appendChild(d.createElementNS("http://www.w3.org/1999/xhtml", "param"));
+b.appendChild(d.createTextNode("\u00A0"));
+
+is(new XMLSerializer().serializeToString(d),
+ '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div></div><script><\/script><br /><source /><param />\u00A0</body></html>',
+ "XML DOM input to XMLSerializer");
+
+d = document.getElementById('iframe').contentWindow.document;
+
+while(d.documentElement.previousSibling) {
+ d.removeChild(d.documentElement.previousSibling);
+}
+
+d.replaceChild(h, d.documentElement);
+
+is(new XMLSerializer().serializeToString(d),
+ '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div></div><script><\/script><br /><source /><param />\u00A0</body></html>',
+ "HTML DOM input to XMLSerializer");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug503473.html b/dom/base/test/test_bug503473.html
new file mode 100644
index 0000000000..586fb20529
--- /dev/null
+++ b/dom/base/test/test_bug503473.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503473
+-->
+<head>
+ <title>Test for Bug 503473</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=503473">Mozilla Bug 503473</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 503473 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+ var iframe = document.getElementById("iframe");
+ var divs = iframe.contentWindow.document.getElementsByTagName("div").length;
+ is(divs, 0, "Div wasn't blown away.")
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<div id="content" style="display: none">
+ <iframe id='iframe' src="file_bug503473-frame.sjs">
+ </iframe>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug503481.html b/dom/base/test/test_bug503481.html
new file mode 100644
index 0000000000..98db4893e2
--- /dev/null
+++ b/dom/base/test/test_bug503481.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503481
+-->
+<head>
+ <title>Test for Bug 503481</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="done();">
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503481"
+ target="_blank" >Mozilla Bug 503481</a>
+
+<p id="display"></p>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+function done() {
+ is(firstRan, true, "first has run");
+ is(secondRan, true, "second has run");
+ is(thirdRan, true, "third has run");
+ SimpleTest.finish();
+}
+var reqs = [];
+function unblock(s) {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_bug503481.sjs?unblock=" + s);
+ xhr.send();
+ reqs.push(xhr);
+}
+var firstRan = false, secondRan = false, thirdRan = false;
+function runFirst() { firstRan = true; }
+function runSecond() {
+ is(thirdRan, true, "should have run third already");
+ secondRan = true;
+}
+function runThird() {
+ is(secondRan, false, "shouldn't have unblocked second yet");
+ thirdRan = true;
+ unblock("B");
+}
+</script>
+<script id=firstScript async src="file_bug503481.sjs?blockOn=A&body=runFirst();"></script>
+<script id=firstScriptHelper>
+is(document.getElementById("firstScript").async, true,
+ "async set");
+is(document.getElementById("firstScriptHelper").async, false,
+ "async not set");
+document.getElementById("firstScript").async = false;
+is(document.getElementById("firstScript").async, false,
+ "async no longer set");
+is(document.getElementById("firstScript").hasAttribute("async"), false,
+ "async attribute no longer set");
+is(firstRan, false, "First async script shouldn't have run");
+unblock("A");
+</script>
+
+<script async src="file_bug503481.sjs?blockOn=B&body=runSecond();"></script>
+<script async src="file_bug503481.sjs?blockOn=C&body=runThird();"></script>
+<script>
+is(secondRan, false, "Second async script shouldn't have run");
+is(thirdRan, false, "Third async script shouldn't have run");
+unblock("C");
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug503481b.html b/dom/base/test/test_bug503481b.html
new file mode 100644
index 0000000000..e11fe36116
--- /dev/null
+++ b/dom/base/test/test_bug503481b.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503481
+-->
+<head>
+ <title>Test for Bug 503481</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503481"
+ target="_blank" >Mozilla Bug 503481</a>
+
+<iframe src="file_bug503481b_inner.html"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+// script in the iframe will call SimpleTest.finish()
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug51034.html b/dom/base/test/test_bug51034.html
new file mode 100644
index 0000000000..b81fb3cb65
--- /dev/null
+++ b/dom/base/test/test_bug51034.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=51034
+-->
+<head>
+ <title>Test for Bug 51034</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=51034">Mozilla Bug 51034</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="divElement">
+ <span>Subelement1</span>
+ <span>Subelement2</span>
+ <span>Subelement3</span>
+ </div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 51034 **/
+var x = document.getElementById('divElement');
+
+isOK = false;
+try {
+x.getElementsByTagName('span');
+isOK = true;
+} catch (ex) {
+ isOK = false;
+}
+
+ok(isOK, "getElementsByTagName() doesn't cause exception");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug513194.html b/dom/base/test/test_bug513194.html
new file mode 100644
index 0000000000..d32a164561
--- /dev/null
+++ b/dom/base/test/test_bug513194.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=513194
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 513194</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=631615"
+ >Mozilla Bug 513194</a>
+<script>
+// The use of document.write is deliberate. We are testing for the
+// HTML parser to call the CSS parser once and only once when it
+// encounters a new <style> element.
+SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+SimpleTest.runTestExpectingConsoleMessages(
+ function () { document.write("<style>qux { foo : bar; }<\/style>") },
+ [{ errorMessage: /Unknown property/ }]
+);
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug5141.html b/dom/base/test/test_bug5141.html
new file mode 100644
index 0000000000..2abafdcd10
--- /dev/null
+++ b/dom/base/test/test_bug5141.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=5141
+-->
+<head>
+ <title>Test for Bug 5141</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=5141">Mozilla Bug 5141</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 5141 **/
+var baz = document.createElement('img');
+ok(baz.parentNode === null, "Node.parentNode should be null for standalone nodes");
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug514487.html b/dom/base/test/test_bug514487.html
new file mode 100644
index 0000000000..40c3ea7fd4
--- /dev/null
+++ b/dom/base/test/test_bug514487.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html id="root">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514487
+-->
+<head id="child">
+ <title>Test for Bug 514487</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTests()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=514487">Mozilla Bug 514487</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 514487 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTests() {
+ // Test XML document cloning.
+ var d = (new DOMParser()).parseFromString(
+ "<html xmlns='http://www.w3.org/1999/xhtml' id='root'><foo id='child'/></html>",
+ "text/xml");
+ var cloneDoc = d.cloneNode(true);
+ ok(cloneDoc.getElementById("root"),
+ "XML document should have an element with ID 'root'");
+ ok(cloneDoc.getElementById("child"),
+ "XML document should have an element with ID 'child'");
+
+ // Test HTML cloning.
+ cloneDoc = document.cloneNode(true);
+ ok(cloneDoc.getElementById("root"),
+ "HTML document should have an element with ID 'root'");
+ ok(cloneDoc.getElementById("child"),
+ "HTML document should have an element with ID 'child'");
+ SimpleTest.finish();
+ }
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug515401.html b/dom/base/test/test_bug515401.html
new file mode 100644
index 0000000000..a9b0751d8f
--- /dev/null
+++ b/dom/base/test/test_bug515401.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=515401
+-->
+<head>
+ <title>Test for Bug 515401</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <base id=basehref href="base/">
+ <base id=basehref2>
+ <base id=basetarget target="def_target">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515401">Mozilla Bug 515401</a>
+<a id=a href="dest.html">Simple link</a>
+<a id=awtarget target="a_target">Link with target</a>
+
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var a = $('a');
+var awtarget = $('awtarget');
+var head = document.documentElement.firstElementChild;
+
+// Test targets
+is(a.target, "def_target", "using default target");
+is(awtarget.target, "a_target", "specified target");
+$('basetarget').setAttribute("target", "new_def_target");
+is(a.target, "new_def_target", "using new default target");
+is(awtarget.target, "a_target", "still specified target");
+$('basetarget').removeAttribute("target");
+is(a.target, "", "no target");
+is(awtarget.target, "a_target", "yet still specified target");
+newbasetarget = document.createElement('base');
+newbasetarget.target = "new_target_elem";
+head.appendChild(newbasetarget);
+is(a.target, "new_target_elem", "new target element");
+is(awtarget.target, "a_target", "yet still specified target");
+newbasetarget.target = "new_target_elem_value";
+is(a.target, "new_target_elem_value", "new target element attribute");
+is(awtarget.target, "a_target", "yet still specified target");
+newbasetarget.target = "";
+is(a.target, "", "new target element no attribute");
+is(awtarget.target, "a_target", "yet still specified target");
+
+
+// link hrefs
+var basehref = $('basehref');
+var basehref2 = $('basehref2');
+var pre = "http://mochi.test:8888/tests/dom/base/test/";
+function verifyBase(base, test) {
+ if (base == "") {
+ is(document.baseURI, document.URL, "document base when " + test);
+ is(a.href, pre + "dest.html", "link href when " + test);
+ }
+ else {
+ is(document.baseURI, pre + base, "document base when " + test);
+ is(a.href, pre + base + "dest.html", "link href when " + test);
+ }
+}
+
+
+// In document base
+verifyBase("base/", "from markup");
+
+// Modify existing <base>
+basehref.href = "base2/";
+verifyBase("base2/", "modify existing");
+is(basehref.href, pre + "base2/", "HTMLBaseElement.href resolves correctly");
+
+// Modify existing <base> to absolute url
+basehref.href = "http://www.example.com/foo/bar.html";
+is(document.baseURI, "http://www.example.com/foo/bar.html", "document base when setting absolute url");
+is(a.href, "http://www.example.com/foo/dest.html", "link href when setting absolute url");
+is(basehref.href, "http://www.example.com/foo/bar.html",
+ "HTMLBaseElement.href resolves correctly when setting absolute url");
+
+// Remove href on existing element
+basehref.removeAttribute("href");
+verifyBase("", "remove href on existing element");
+
+// Create href on existing element
+basehref.href = "base3/";
+verifyBase("base3/", "create href on existing element");
+
+// Fall back to second after remove attr
+basehref2.href = "base4/";
+verifyBase("base3/", "add href on second base");
+basehref.removeAttribute("href");
+verifyBase("base4/", "fall back to second after remove attr");
+
+// Set href on existing again
+basehref.href = "base5/";
+verifyBase("base5/", "set href on existing again");
+
+// Unset href on second
+basehref2.removeAttribute("href");
+verifyBase("base5/", "unset href on second");
+
+// Insert base with href before existing
+var basehref0 = document.createElement("base");
+basehref0.href = "base6/";
+verifyBase("base5/", "nothing modified");
+head.insertBefore(basehref0, head.firstChild);
+verifyBase("base6/", "insert base with href before existing");
+
+// Insert base as grandchild of head
+var basehref3 = document.createElement("base");
+basehref3.href = "base7/";
+var tmp = document.createElement("head");
+tmp.appendChild(basehref3);
+head.insertBefore(tmp, head.firstChild);
+verifyBase("base7/", "inserted base as grandchild of head at the beginning of head");
+is(basehref3.parentNode.parentNode, head, "base got inserted correctly");
+
+// Remove secondary bases
+var tmp, tmp2;
+for (tmp = head.firstChild; tmp = tmp.nextSibling; tmp) {
+ if (tmp.localName == "base" && tmp != basehref0) {
+ tmp2 = tmp.previousSibling;
+ head.removeChild(tmp);
+ tmp = tmp2;
+ }
+ verifyBase("base7/", "remove unneeded base");
+}
+
+// Remove head
+document.documentElement.removeChild(head);
+verifyBase("", "removed head");
+
+// Insert base in body
+document.body.insertBefore(basehref3, document.body.firstChild);
+verifyBase("base7/", "inserted base in body");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug518104.html b/dom/base/test/test_bug518104.html
new file mode 100644
index 0000000000..6d557225ef
--- /dev/null
+++ b/dom/base/test/test_bug518104.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=518104
+-->
+<head>
+ <title>Test for Bug 518104</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518104">Mozilla Bug 518104</a>
+<p id="display"></p>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 518104 **/
+// data: version at parser/htmlparser/tests/mochitest/test_bug1364399.html
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+ // document.write should have gotten ignored due to the
+ // ignore-destructive-writes counter. Then document.close should
+ // have gotten ignored due to the parser still being not-script-created.
+ var iframe = document.getElementById("iframe");
+ var divs = iframe.contentWindow.document.getElementsByTagName("div").length;
+ is(divs, 2, "<div>s are still there.")
+ var ps = iframe.contentWindow.document.getElementsByTagName("p").length;
+ is(ps, 0, "<p> did not get written.")
+ SimpleTest.finish();
+}
+
+</script>
+<div id="content" style="display: none">
+ <iframe id='iframe' srcdoc="<div></div><div></div><script defer src='file_bug518104.js'></script>">
+ </iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug527896.html b/dom/base/test/test_bug527896.html
new file mode 100644
index 0000000000..582d26d24d
--- /dev/null
+++ b/dom/base/test/test_bug527896.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=527896
+-->
+<head>
+ <title>Test for Bug 527896</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload='done();'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=527896">Mozilla Bug 527896</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 527896 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var docWrittenSrcExecuted = false;
+var scriptInsertedSrcExecuted = false;
+
+// the iframe test runs with the HTML5 parser
+
+var iframe = document.getElementsByTagName('iframe')[0];
+iframe.contentWindow.document.open();
+iframe.contentWindow.document.write("<!DOCTYPE html>");
+iframe.contentWindow.document.write("<body><script id =\"thescript\" src=\"data:text/javascript,parent.docWrittenSrcExecuted = true;\">");
+
+// now remove the src attribute before the end tag is parsed
+iframe.contentWindow.document.getElementById('thescript').removeAttribute('src');
+
+iframe.contentWindow.document.write("parent.ok(false, \"Content executed.\");");
+iframe.contentWindow.document.write("<\/script>");
+iframe.contentWindow.document.close();
+
+// the insertion test runs with the default HTML parser since it's in this document itself!
+
+var div = document.getElementById('content');
+var script = document.createElement('script');
+div.appendChild(script); // this shouldn't yet freeze the script node nor run it
+script.setAttribute("src", "data:text/javascript,scriptInsertedSrcExecuted = true;");
+
+todo(false, "Add SVG tests after bug 528442.");
+
+function done() {
+ ok(docWrittenSrcExecuted, "document.written src didn't execute");
+ ok(scriptInsertedSrcExecuted, "script-inserted src didn't execute");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug540854.html b/dom/base/test/test_bug540854.html
new file mode 100644
index 0000000000..3baa946abd
--- /dev/null
+++ b/dom/base/test/test_bug540854.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=540854
+-->
+<head>
+ <title>Test for Bug 540854</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=540854">Mozilla Bug 540854</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 540854 **/
+
+
+ var x;
+
+ function getUTF8() {
+ return unescape(encodeURIComponent('\0foo'));
+ }
+
+ function xload() {
+ is(this.responseText, getUTF8(), "Unexpected responseText!");
+ SimpleTest.finish();
+ }
+
+ function runTest() {
+ x = new XMLHttpRequest();
+ x.open("POST", "bug540854.sjs")
+ x.onload = xload;
+ x.send(getUTF8());
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug541937.html b/dom/base/test/test_bug541937.html
new file mode 100644
index 0000000000..277b2e8e3f
--- /dev/null
+++ b/dom/base/test/test_bug541937.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for XHTML serializer, bug 541937</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=541937">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe" src="file_bug541937.html">
+ </iframe>
+ <iframe id="testframe2" src="file_bug541937.xhtml">
+ </iframe>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testSerializer () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/html");
+
+ var parser = new DOMParser();
+ var serializer = new XMLSerializer();
+
+ // with content
+ var str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"><!-- child nodes --> \n<content xmlns=""/></link>\n</doc>';
+ var expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml"><!-- child nodes --> \n<content xmlns=""/></link>\n</doc>';
+
+ var doc = parser.parseFromString(str,"application/xml");
+ var result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element inside an xml document with children");
+
+ // with only whitespaces
+ str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"> \n </link>\n</doc>';
+ expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml"> \n </link>\n</doc>';
+
+ doc = parser.parseFromString(str,"application/xml");
+ result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element with only whitespaces as content, inside an xml document");
+
+ // with only one space as content
+ str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"> </link>\n</doc>';
+ expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml"> </link>\n</doc>';
+
+ doc = parser.parseFromString(str,"application/xml");
+ result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element with only one space as content, inside an xml document");
+
+ // let's remove the content
+ str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"> <!-- child nodes --> \ndeleted content<content xmlns=""/> </link>\n</doc>';
+ expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml" />\n</doc>';
+
+ doc = parser.parseFromString(str,"application/xml");
+ doc.documentElement.firstElementChild.textContent = '';
+ result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element on which we removed dynamically the content, inside an xml document");
+
+ // with no content but an ended tag
+ str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"></link>\n</doc>';
+ expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml" />\n</doc>';
+
+ doc = parser.parseFromString(str,"application/xml");
+ result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element with no content but with an ended tag, inside an xml document");
+
+ // with no content
+ str = '<?xml version="1.0"?><doc>\n<link xmlns="http://www.w3.org/1999/xhtml"/>\n</doc>';
+ expected = '<?xml version="1.0" encoding="UTF-8"?>\n<doc>\n<link xmlns="http://www.w3.org/1999/xhtml" />\n</doc>';
+
+ doc = parser.parseFromString(str,"application/xml");
+ result = serializer.serializeToString(doc);
+ result = result.replace(/\r\n/mg, "\n");
+ is(result, expected, "serialization of a link element with no content, inside an xml document");
+
+
+ doc = $("testframe").contentDocument;
+ encoder.init(doc, "text/html", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ result = encoder.encodeToString();
+ expected = '<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n <title>Test</title>\n';
+ expected += ' <link rel=\"Top\" href=\"\"> ';
+ expected += ' </head><body>foo \n\n\n <p>Hello world</p>\n</body></html>';
+ is(result, expected, "serialization of a link element with content, inside an html document");
+
+ doc = $("testframe2").contentDocument;
+ encoder.init(doc, "application/xhtml+xml", de.OutputLFLineBreak);
+ encoder.setCharset("UTF-8");
+ result = encoder.encodeToString();
+ expected = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n';
+ expected += '<html xmlns="http://www.w3.org/1999/xhtml">\n<head>\n <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n <title>Test</title>\n';
+ expected += ' <link rel="Top" href=""> foo </link>\n';
+ expected += '\n</head>\n<body>\n <p>Hello world</p>\n</body>\n</html>';
+ is(result, expected, "serialization of a link element with content, inside an xhtml document");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testSerializer);
+
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug544642.html b/dom/base/test/test_bug544642.html
new file mode 100644
index 0000000000..1b9e7447d2
--- /dev/null
+++ b/dom/base/test/test_bug544642.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 544642</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=544642"
+ target="_blank" >Mozilla Bug 544642</a>
+<p id="display"></p>
+<iframe id=iframe></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+var gen = runTest();
+
+addLoadEvent(function() { gen.next(); });
+
+function* runTest() {
+ var iframe = $('iframe');
+ iframe.onerror = function() { gen.next("error"); };
+ iframe.onload = function() { gen.next("load"); };
+
+ iframe.src = "data:text/plain,hello";
+ is((yield), "load", "plaintext data");
+
+ iframe.src = "file://foo/bar";
+ is((yield), "error", "file");
+
+ // We should do this test too, however it brings up a modal dialog which
+ // we can't dismiss.
+ //iframe.src = "http:////";
+ //is((yield), "error", "invalid http");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug545644.html b/dom/base/test/test_bug545644.html
new file mode 100644
index 0000000000..466684c778
--- /dev/null
+++ b/dom/base/test/test_bug545644.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for HTML serializer + innerHtml, bug 545644</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545644">Mozilla Bug </a>
+<p id="display"></p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testInner () {
+ var div = document.getElementById("test_inner");
+
+ try {
+ div.innerHTML = "some \u00A0 &nbsp; text";
+ div.innerHTML += "!";
+ ok(true, "innerHTML in html test ok");
+ } catch (e) {
+ ok(false, "innerHTML in html test failed, unwanted exception " + e);
+ }
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testInner);
+
+</script>
+</pre>
+<div id="test_inner"></div>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug545644.xhtml b/dom/base/test/test_bug545644.xhtml
new file mode 100644
index 0000000000..066ff31ec0
--- /dev/null
+++ b/dom/base/test/test_bug545644.xhtml
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+-->
+<head>
+ <title>Test for XHTML serializer + innerHtml, bug 545644</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545644">Mozilla Bug </a>
+<p id="display"></p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testInner () {
+ var div = document.getElementById("test_inner");
+
+ try {
+ div.innerHTML = "some \u00A0 &amp;nbsp; text";
+ ok(false, "innerHTML in xhtml test failed, no exception by the parser when giving an unexpected entity"+e);
+ } catch (e) {
+ ok (true, "innerHTML in xhtml test ok");
+ }
+
+ try {
+ div.innerHTML = "some \u00A0 text";
+ div.innerHTML += "!";
+ ok (true, "innerHTML in xhtml test ok");
+ } catch (e) {
+ ok(false, "innerHTML in xhtml test failed, unexpected exception "+e);
+ }
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testInner);
+
+</script>
+</pre>
+<div id="test_inner"></div>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug548463.html b/dom/base/test/test_bug548463.html
new file mode 100644
index 0000000000..3357e4e02e
--- /dev/null
+++ b/dom/base/test/test_bug548463.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548463
+-->
+<head>
+ <title>Test for Bug 548463</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548463">Mozilla Bug 548463</a>
+<p id="display"></p>
+<iframe id="otherDoc"></iframe>
+<div id="content" style="display: none">
+ <p id="elem1"></p>
+ <p id="elem2"></p>
+</div>
+<div id="otherContent" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 548463 **/
+
+var otherDoc = document.getElementById("otherDoc").contentDocument;
+var content = document.getElementById("content");
+var elem1 = document.getElementById("elem1");
+var elem2 = document.getElementById("elem2");
+
+function testAdoptFromDOMNodeRemoved(nodeToAppend, nodeRemoved, nodeToAdopt)
+{
+ function reparent(event)
+ {
+ if (event.target == nodeRemoved) {
+ nodeRemoved.removeEventListener("DOMNodeRemoved", arguments.callee);
+ otherDoc.adoptNode(nodeToAdopt);
+ }
+ }
+ nodeRemoved.addEventListener("DOMNodeRemoved", reparent);
+
+ var thrown = false;
+ try {
+ document.getElementById("otherContent").appendChild(nodeToAppend);
+ }
+ catch (e) {
+ thrown = true;
+ }
+
+ ok(!thrown, "adoptNode while appending should not throw");
+}
+
+var frag = document.createDocumentFragment();
+frag.appendChild(elem1);
+frag.appendChild(elem2);
+testAdoptFromDOMNodeRemoved(frag, elem1, elem2);
+
+content.appendChild(elem1);
+testAdoptFromDOMNodeRemoved(elem1, elem1, content);
+
+content.appendChild(elem1);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug553896.xhtml b/dom/base/test/test_bug553896.xhtml
new file mode 100644
index 0000000000..e385b6aa91
--- /dev/null
+++ b/dom/base/test/test_bug553896.xhtml
@@ -0,0 +1,69 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=553896
+-->
+<head>
+ <title>Test for Bug 553896</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=553896">Mozilla Bug 553896</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+/** Test for Bug 553896 **/
+try {
+ ok(!document.createElement("foo").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createAttribute("foo").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createTextNode("foo").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createCDATASection("foo").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createProcessingInstruction("foo", "bar").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createComment("foo").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.implementation.createDocumentType("html", "", "").isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+try {
+ ok(!document.createDocumentFragment().isEqualNode(null), "Node is equal to null?");
+} catch (e) {
+ ok(false, "Exception was raised.");
+}
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug557892.html b/dom/base/test/test_bug557892.html
new file mode 100644
index 0000000000..f4ff010704
--- /dev/null
+++ b/dom/base/test/test_bug557892.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557892
+-->
+<head>
+ <title>Test for Bug 557892</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557892">Mozilla Bug 557892</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557892 **/
+
+SimpleTest.waitForExplicitFinish();
+
+window.open("file_bug557892.html", "", "width=100,height=100");
+
+function done() {
+ ok(true, "Should not crash");
+ setTimeout("SimpleTest.finish();", 0);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug558726.html b/dom/base/test/test_bug558726.html
new file mode 100644
index 0000000000..7c3844b189
--- /dev/null
+++ b/dom/base/test/test_bug558726.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=558726
+-->
+<head>
+ <title>Test for Bug 558726</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=558726">Mozilla Bug 558726</a>
+<div>
+<div id="test_558726_1"><select name="selecttest"><option value="0">item 1</option></select>x<input type="button" name="test" value="test"></div>
+<div id="test_558726_2">y<input><i style="user-select: none;">z</i></div>
+</div>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 558726 **/
+
+function runTest() {
+ is(document.getElementById('test_558726_1').innerHTML, '<select name="selecttest"><option value="0">item 1</option></select>x<input type="button" name="test" value="test">','test_558726_1.innerHTML')
+ is(document.getElementById('test_558726_2').innerHTML, 'y<input><i style="user-select: none;">z</i>','test_558726_2.innerHTML')
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug559526.html b/dom/base/test/test_bug559526.html
new file mode 100644
index 0000000000..3d2b01948c
--- /dev/null
+++ b/dom/base/test/test_bug559526.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=559526
+-->
+<head>
+ <title>Test for Bug 559526</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=559526">Mozilla Bug 559526</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<div id="nodes" style="display:none">
+ <div id="child1"></div>
+ <div id="child2"></div>
+
+ <div id="child3"></div>
+ <div id="child4"></div>
+ <div id="child5"></div>
+ <div id="child6"></div>
+ <div id="child7"></div>
+ <div id="child8"></div>
+</div>
+<script type="application/javascript">
+
+/** Test for Bug 559526 **/
+
+var it;
+var recurse = false;
+var testCount = 0;
+function filter(node) {
+ if (node.id == "child3" && ! recurse) {
+ recurse = true;
+ var ex = null;
+ try {
+ var foo = it.nextNode();
+ } catch(e) {
+ ex = e;
+ }
+ ++testCount;
+ is(ex.name, "InvalidStateError", "Should have thrown an exception!");
+ is(ex.code, DOMException.INVALID_STATE_ERR, "Should have thrown an exception!");
+ recurse = false;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+}
+
+(function testNodeIterator() {
+
+ it = document.createNodeIterator(
+ document.getElementById("nodes"),
+ NodeFilter.SHOW_ELEMENT,
+ filter
+ );
+ while (it.nextNode());
+})();
+
+(function testTreeWalker() {
+ it = document.createTreeWalker(
+ document.getElementById("nodes"),
+ NodeFilter.SHOW_ELEMENT,
+ filter
+ );
+ while(it.nextNode());
+
+ it = document.createTreeWalker(
+ document.getElementById("nodes"),
+ NodeFilter.SHOW_ELEMENT,
+ filter
+ );
+ it.firstChild();
+ while(it.nextSibling());
+
+ it = document.createTreeWalker(
+ document.getElementById("nodes"),
+ NodeFilter.SHOW_ELEMENT,
+ filter
+ );
+ it.lastChild();
+ while(it.previousSibling());
+})();
+
+is(testCount, 4, "Should have tests 3 filter calls!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug560780.html b/dom/base/test/test_bug560780.html
new file mode 100644
index 0000000000..796eb72e75
--- /dev/null
+++ b/dom/base/test/test_bug560780.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=560780
+-->
+<head>
+ <title>Test for Bug 560780</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="text/javascript">
+function init() {
+ var elem = document.getElementById('body');
+ elem.addEventListener('mousedown', mousedown, true);
+}
+var seen_mousedown = 0;
+function mousedown(event) {
+ var doc = event.target.ownerDocument;
+ var win = doc.defaultView;
+ var elem = doc.getElementById('body');
+ var selection = win.getSelection();
+ if (selection.rangeCount>0) {
+ var ragne = selection.getRangeAt(0);
+ var rect = ragne.getBoundingClientRect();
+ var p = elem.parentNode.appendChild(doc.createElement('p'));
+ p.textContent = "width: " + (rect.right -rect.left);
+ }
+ ++seen_mousedown;
+}
+</script>
+
+</head>
+<body id="body">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=560780">Mozilla Bug 560780</a>
+<p id="display" style="margin:0;padding:0;border:0"><a id="testlink" href="#aaaaaaaaaaaaaaaaaaaaaa">abcdefghijklmnabcdefghijklmn</a></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ 1.Start Minefield with New Profile.
+ 2.Select texts by alt + mouse dragging horizontaly from 'd' in the link above to far right of window.
+ 3.Click on the selected text
+ 4.Click empty area of page
+ 5.Repeat STEP 2 to 4 till browser crashes. (at least 5 times)
+
+<script type="application/javascript">
+
+/** Test for Bug 560780 **/
+
+function selectByMouseThenClick(elm,startx,starty) {
+ // select some text
+ var ctrl = !!navigator.platform.indexOf("Linux");
+ var alt = true;
+ var x = startx;
+ synthesizeMouse(elm, x, starty, { type:"mousedown", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x += 100, starty, { type:"mousemove", ctrlKey:ctrl, altKey:alt });
+ synthesizeMouse(elm, x, starty, { type:"mouseup", ctrlKey:ctrl, altKey:alt });
+
+ // click on the selection
+ synthesizeMouse(elm, startx + 10, starty + 1, {});
+
+ // click empty area of the page
+ synthesizeMouse(document.getElementById('body'), 800, 800, {});
+}
+
+function runTest() {
+ var e = document.getElementById('testlink');
+ selectByMouseThenClick(e,110,5);
+ selectByMouseThenClick(e,90,5);
+ selectByMouseThenClick(e,70,5);
+ selectByMouseThenClick(e,50,5);
+ selectByMouseThenClick(e,30,5);
+ selectByMouseThenClick(e,10,5);
+ is(seen_mousedown, 12, "got the mousedown events");
+ SimpleTest.finish();
+}
+
+function doTest() {
+ init();
+ runTest();
+}
+
+SimpleTest.waitForFocus(doTest, window);
+SimpleTest.waitForExplicitFinish();
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug562137.html b/dom/base/test/test_bug562137.html
new file mode 100644
index 0000000000..300d6f32b5
--- /dev/null
+++ b/dom/base/test/test_bug562137.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562137
+-->
+<head>
+ <title>Test for Bug 562137</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=562137">Mozilla Bug 562137</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562137 **/
+var myStr = "I\ufffdhave\ufffdnbsp\n";
+
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "file_bug562137.txt", false);
+xhr.send();
+is(xhr.responseText, myStr);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug562169-1.html b/dom/base/test/test_bug562169-1.html
new file mode 100644
index 0000000000..28da1421e2
--- /dev/null
+++ b/dom/base/test/test_bug562169-1.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562169
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 562169</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=562169">Mozilla Bug 562169</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div dir="rtl" id="z"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562169 **/
+/** Test that adding an child to an element with dir="rtl" makes the
+ child have rtl directionality, and removing the child makes it
+ go back to ltr directionality **/
+
+function checkSelector(element, expectedDir, expectedChild)
+{
+ ok(element.querySelector(":dir("+expectedDir+")") == expectedChild,
+ "direction should be " + expectedDir);
+}
+
+var x = document.createElement("div");
+var y = document.createElement("div");
+x.appendChild(y);
+checkSelector(x, "ltr", y);
+$(z).appendChild(x);
+checkSelector(x, "rtl", y);
+$(z).removeChild(x);
+checkSelector(x, "ltr", y);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug562169-2.html b/dom/base/test/test_bug562169-2.html
new file mode 100644
index 0000000000..003ed5dc02
--- /dev/null
+++ b/dom/base/test/test_bug562169-2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562169
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 562169</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=562169">Mozilla Bug 562169</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562169 **/
+/** Test that a newly created element has ltr directionality **/
+
+ok(document.createElement("div").matches(":dir(ltr)"),
+ "Element should be ltr on creation");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug562652.html b/dom/base/test/test_bug562652.html
new file mode 100644
index 0000000000..e71317558d
--- /dev/null
+++ b/dom/base/test/test_bug562652.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562652
+-->
+<head>
+ <title>Test for Bug 562652</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=562652">Mozilla Bug 562652</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="testtarget">_</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562652 **/
+
+
+var testCount = 0;
+function createHTMLDoc() {
+ var d = document.implementation.createHTMLDocument();
+ d.body.setAttribute("id", "testtarget");
+ return d;
+}
+
+function test(d) {
+ var t = d.getElementById("testtarget");
+ d.addEventListener("DOMNodeInserted", function(e) { ++testCount; });
+ t.innerHTML = "_";
+}
+
+function runTests() {
+ test(document);
+ test(createHTMLDoc());
+ is(testCount, 2, "DOMNodeInserted should have fired 2 times!");
+ SimpleTest.finish();
+}
+
+addLoadEvent(runTests);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug564047.html b/dom/base/test/test_bug564047.html
new file mode 100644
index 0000000000..c3321204cd
--- /dev/null
+++ b/dom/base/test/test_bug564047.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=564047
+-->
+<head>
+ <title>Test for Bug 564047</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=564047">Mozilla Bug 564047</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 564047 **/
+try {
+ document.doctype.appendChild(document.createTextNode("test"));
+ ok(false, "Should have thrown an exception");
+} catch (e) {
+ is(e.name, "HierarchyRequestError");
+ ok(e instanceof DOMException, "Should be a DOMException");
+ is(e.code, DOMException.HIERARCHY_REQUEST_ERR);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug564863-2.xhtml b/dom/base/test/test_bug564863-2.xhtml
new file mode 100644
index 0000000000..3282ccb1f4
--- /dev/null
+++ b/dom/base/test/test_bug564863-2.xhtml
@@ -0,0 +1,159 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=564863
+-->
+<head>
+ <title>Test for Bug 564863</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+* {
+ color: rgb(0, 0, 0);
+}
+#xul_id {
+ color: rgb(30, 30, 30);
+}
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=564863">Mozilla Bug 564863</a>
+
+<!-- DOM to muck around with for tests -->
+<p id="root">
+<xul:button id="xul_id" />
+</p>
+
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+root = $('root');
+xul = root.children[0];
+
+var xul_cs = getComputedStyle(xul, "");
+
+function checkHasId(test) {
+ // Check computed style first to avoid flushes from hiding problems
+ checkHasIdNoGEBI(test);
+
+ is($("xul_id"), xul, "xul getElementById " + test);
+}
+
+function checkHasIdNoGEBI(test) {
+ const connected = test != "removed node";
+ is(xul_cs.color, connected ? "rgb(30, 30, 30)" : "", "xul color " + test);
+
+ is(xul.id, "xul_id", "xul id " + test);
+}
+
+function checkHasNoId(removed, test) {
+ // XXX This fails for some reason when this is run as a Mochitest chrome, but
+ // not when run as a Mochitest plain.
+ //is(xul_cs.color, "rgb(0, 0, 0)", "xul color " + test);
+
+ attrValue = removed ? null : "";
+
+ is(xul.id, "", "xul id " + test);
+
+ is(xul.getAttribute("id"), "", "xul getAttribute " + test);
+
+ is($("xul_id"), null, "xul getElementById " + test);
+}
+
+// Check that dynamic modifications of attribute work
+
+checkHasId("in markup");
+
+xul.id = "";
+
+checkHasNoId(false, "set to empty");
+
+xul.id = "xul_id";
+
+checkHasId("set using .id");
+
+xul.setAttribute("id", "");
+
+checkHasNoId(false, "setAttribute to empty");
+
+xul.id = "xul_id";
+
+checkHasId("set again using .id");
+
+xul.removeAttribute("id");
+
+checkHasNoId(true, "removed attribute");
+
+xul.setAttribute("id", "xul_id");
+
+checkHasId("set using setAttribute");
+
+t3 = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "button");
+t3.id = "xul_id";
+
+// Check that inserting elements before/after existing work
+
+function insertAfter(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing.nextSibling);
+}
+function insertBefore(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing);
+}
+function removeNode(child) {
+ child.remove();
+}
+
+insertAfter(t3, xul);
+
+checkHasId("inserted after");
+
+insertBefore(t3, xul);
+
+checkHasIdNoGEBI("inserted before");
+is($("xul_id"), t3, "xul getElementById inserted before");
+
+t3.removeAttribute("id");
+
+checkHasId("removed tx attribute");
+
+t3.setAttribute("id", "xul_id");
+
+checkHasIdNoGEBI("setAttribute before");
+is($("xul_id"), t3, "xul getElementById setAttribute before");
+
+removeNode(t3);
+
+checkHasId("removed temporaries");
+
+removeNode(xul);
+
+checkHasIdNoGEBI("removed node");
+
+// Re-add the id inside a mutation event on a XUL element
+is($("xul_id"), null, "no xul");
+xul = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "button");
+xul.id = "xul_id";
+root.appendChild(xul);
+is($("xul_id"), xul, "new xul is set up");
+mutateFired = false;
+xul.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, xul, "target is xul");
+ is(xul.getAttribute("id"), "", "xul no longer has id attr");
+ is(xul.id, "", "xul no longer has id");
+ xul.id = "other_xul_id";
+ mutateFired = true;
+}, {once: true});
+xul.removeAttribute("id");
+ok(mutateFired, "mutation event fired");
+is($("xul_id"), null, "xul_id was removed from table");
+is($("other_xul_id"), xul, "other_xul_id was added");
+removeNode(xul);
+is($("other_xul_id"), null, "other_xul_id was removed");
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug564863.xhtml b/dom/base/test/test_bug564863.xhtml
new file mode 100644
index 0000000000..18a934b1d5
--- /dev/null
+++ b/dom/base/test/test_bug564863.xhtml
@@ -0,0 +1,305 @@
+<!DOCTYPE html [
+<!ATTLIST ns:x id ID #REQUIRED>
+<!ATTLIST ns2:x id_2 ID #REQUIRED>
+]>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:ns="urn:namespace"
+ xmlns:ns2="urn:namespace">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=564863
+-->
+<head>
+ <title>Test for Bug 564863</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+* {
+ color: rgb(0, 0, 0);
+}
+#div_id {
+ color: rgb(10, 10, 10);
+}
+#a_id {
+ color: rgb(20, 20, 20);
+}
+#svg_id {
+ color: rgb(40, 40, 40);
+}
+#ns_id {
+ color: rgb(50, 50, 50);
+}
+#ns2_id {
+ color: rgb(60, 60, 60);
+}
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=564863">Mozilla Bug 564863</a>
+<!-- Elements to ensure we have nodeinfos with id-attribute set -->
+<div><ns:x id="ns-holder"/><ns2:x id_2="ns2-holder"/></div>
+
+<!-- DOM to muck around with for tests -->
+<p id="root">
+<div id="div_id" />
+<a id="a_id" />
+<svg:svg><svg:g id="svg_id" /></svg:svg>
+<ns:x id="ns_id" />
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+root = $('root');
+div = root.children[0];
+a = root.children[1];
+svg = root.children[2].firstChild;
+nsx = root.children[3];
+
+var div_cs = getComputedStyle(div, "");
+var a_cs = getComputedStyle(a, "");
+var svg_cs = getComputedStyle(svg, "");
+var nsx_cs = getComputedStyle(nsx, "");
+
+function checkHasId(test) {
+ // Check computed style first to avoid flushes from hiding problems
+ checkHasIdNoGEBI(test);
+
+ is($("div_id"), div, "div getElementById " + test);
+ is($("a_id"), a, "a getElementById " + test);
+ is($("svg_id"), svg, "svg getElementById " + test);
+ is($("ns_id"), nsx, "ns getElementById " + test);
+}
+
+function checkHasIdNoGEBI(test) {
+ const connected = test != "removed node";
+ is(div_cs.color, connected ? "rgb(10, 10, 10)" : "", "div color " + test);
+ is(a_cs.color, connected ? "rgb(20, 20, 20)" : "", "a color " + test);
+ is(svg_cs.color, connected ? "rgb(40, 40, 40)" : "", "svg color " + test);
+ is(nsx_cs.color, connected ? "rgb(50, 50, 50)" : "", "nsx color " + test);
+
+ is(div.id, "div_id", "div id " + test);
+ is(a.id, "a_id", "a id " + test);
+ is(svg.id, "svg_id", "svg id " + test);
+ is (nsx.getAttribute("id"), "ns_id", "ns id " + test);
+}
+
+function checkHasNoId(removed, test) {
+ is(div_cs.color, "rgb(0, 0, 0)", "div color " + test);
+ is(a_cs.color, "rgb(0, 0, 0)", "a color " + test);
+ is(svg_cs.color, "rgb(0, 0, 0)", "svg color " + test);
+ is(nsx_cs.color, "rgb(0, 0, 0)", "nsx color " + test);
+
+ attrValue = removed ? null : "";
+
+ is(div.id, "", "div id " + test);
+ is(a.id, "", "a id " + test);
+ is(svg.id, "", "svg id " + test);
+
+ is(div.getAttribute("id"), attrValue, "div getAttribute " + test);
+ is(a.getAttribute("id"), attrValue, "a getAttribute " + test);
+ is(svg.getAttribute("id"), attrValue, "svg getAttribute " + test);
+ is(nsx.getAttribute("id"), attrValue, "ns getAttribute " + test);
+
+ is($("div_id"), null, "div getElementById " + test);
+ is($("a_id"), null, "a getElementById " + test);
+ is($("svg_id"), null, "svg getElementById " + test);
+ is($("ns_id"), null, "ns getElementById " + test);
+}
+
+// Check that dynamic modifications of attribute work
+
+checkHasId("in markup");
+
+div.id = "";
+a.id = "";
+svg.id = "";
+nsx.setAttribute("id", "");
+
+checkHasNoId(false, "set to empty");
+
+div.id = "div_id";
+a.id = "a_id";
+svg.id = "svg_id";
+nsx.setAttribute("id", "ns_id");
+
+checkHasId("set using .id");
+
+div.setAttribute("id", "");
+a.setAttribute("id", "");
+svg.setAttribute("id", "");
+nsx.setAttribute("id", "");
+
+checkHasNoId(false, "setAttribute to empty");
+
+div.id = "div_id";
+a.id = "a_id";
+svg.id = "svg_id";
+nsx.setAttribute("id", "ns_id");
+
+checkHasId("set again using .id");
+
+div.removeAttribute("id");
+a.removeAttribute("id");
+svg.removeAttribute("id");
+nsx.removeAttribute("id");
+
+checkHasNoId(true, "removed attribute");
+
+div.setAttribute("id", "div_id");
+a.setAttribute("id", "a_id");
+svg.setAttribute("id", "svg_id");
+nsx.setAttribute("id", "ns_id");
+
+checkHasId("set using setAttribute");
+
+t1 = document.createElement("div");
+t1.id = "div_id";
+t2 = document.createElement("a");
+t2.id = "a_id";
+t4 = document.createElementNS("http://www.w3.org/2000/svg", "g");
+t4.id = "svg_id";
+t5 = document.createElementNS("urn:namespace", "ns:x");
+t5.setAttribute("id", "ns_id");
+
+// Check that inserting elements before/after existing work
+
+function insertAfter(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing.nextSibling);
+}
+function insertBefore(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing);
+}
+function removeNode(child) {
+ child.remove();
+}
+
+insertAfter(t1, div);
+insertAfter(t2, a);
+insertAfter(t4, svg);
+insertAfter(t5, nsx);
+
+checkHasId("inserted after");
+
+insertBefore(t1, div);
+insertBefore(t2, a);
+insertBefore(t4, svg);
+insertBefore(t5, nsx);
+
+checkHasIdNoGEBI("inserted before");
+is($("div_id"), t1, "div getElementById inserted before");
+is($("a_id"), t2, "a getElementById inserted before");
+is($("svg_id"), t4, "svg getElementById inserted before");
+is($("ns_id"), t5, "ns getElementById inserted before");
+
+t1.removeAttribute("id");
+t2.removeAttribute("id");
+t4.removeAttribute("id");
+t5.removeAttribute("id");
+
+checkHasId("removed tx attribute");
+
+t1.setAttribute("id", "div_id");
+t2.setAttribute("id", "a_id");
+t4.setAttribute("id", "svg_id");
+t5.setAttribute("id", "ns_id");
+
+checkHasIdNoGEBI("setAttribute before");
+is($("div_id"), t1, "div getElementById setAttribute before");
+is($("a_id"), t2, "a getElementById setAttribute before");
+is($("svg_id"), t4, "svg getElementById setAttribute before");
+is($("ns_id"), t5, "ns getElementById setAttribute before");
+
+removeNode(t1);
+removeNode(t2);
+removeNode(t4);
+removeNode(t5);
+
+checkHasId("removed temporaries");
+
+removeNode(div);
+removeNode(a);
+removeNode(svg);
+removeNode(nsx);
+
+checkHasIdNoGEBI("removed node");
+
+// Check that removing an element during UnsetAttr works
+is(div.id, "div_id", "div still has id set");
+var mutateFired = false;
+root.appendChild(div);
+div.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, div, "target is div");
+ is(div.id, "", "div no longer has id");
+ is(div.getAttribute("id"), null, "div no longer has id attr");
+ removeNode(div);
+ is(div.parentNode, null, "div was removed");
+ mutateFired = true;
+}, {once: true});
+div.removeAttribute("id");
+ok(mutateFired, "mutation event fired");
+
+// Check same for XML elements
+is(nsx.getAttribute("id"), "ns_id", "nsx still has id set");
+mutateFired = false;
+root.appendChild(nsx);
+nsx.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, nsx, "target is nsx");
+ is(nsx.getAttribute("id"), null, "nsx no longer has id attr");
+ removeNode(nsx);
+ is(nsx.parentNode, null, "nsx was removed");
+ mutateFired = true;
+}, {once: true});
+nsx.removeAttribute("id");
+ok(mutateFired, "mutation event fired");
+
+
+// Re-add the id inside a mutation event on a XML element
+is($("ns_id"), null, "no nsx");
+is($("ns2_id"), null, "no nsx");
+nsx = document.createElementNS("urn:namespace", "ns:x");
+nsx.setAttribute("id", "ns_id");
+root.appendChild(nsx);
+is($("ns_id"), nsx, "new nsx is set up");
+mutateFired = false;
+nsx.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, nsx, "target is nsx");
+ is(nsx.getAttribute("id"), null, "nsx no longer has id attr");
+ nsx.setAttribute("id", "other_id");
+ mutateFired = true;
+}, {once: true});
+nsx.removeAttribute("id");
+ok(mutateFired, "mutation event fired");
+is($("ns_id"), null, "ns_id was removed from table");
+is($("other_id"), nsx, "other_id was added");
+removeNode(nsx);
+is($("other_id"), null, "other_id was removed");
+
+// Re-add the id inside a mutation event on a HTML element
+is($("div_id"), null, "no div");
+div = document.createElement("div");
+div.id = "div_id";
+root.appendChild(div);
+is($("div_id"), div, "new div is set up");
+mutateFired = false;
+div.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, div, "target is div");
+ is(div.getAttribute("id"), null, "div no longer has id attr");
+ is(div.id, "", "div no longer has id");
+ div.id = "other_div_id";
+ mutateFired = true;
+}, {once: true});
+div.removeAttribute("id");
+ok(mutateFired, "mutation event fired");
+is($("div_id"), null, "div_id was removed from table");
+is($("other_div_id"), div, "other_div_id was added");
+removeNode(div);
+is($("other_div_id"), null, "other_div_id was removed");
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug567350.html b/dom/base/test/test_bug567350.html
new file mode 100644
index 0000000000..14a8687af9
--- /dev/null
+++ b/dom/base/test/test_bug567350.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567350
+-->
+<head>
+ <title>Test for Bug 567350</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=567350">Mozilla Bug 567350</a>
+<p id="display"><span style="color: blue;"></span></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 567350 **/
+is(getComputedStyle(document.getElementById("display").firstChild).color,
+ "rgb(0, 0, 255)");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug574596.html b/dom/base/test/test_bug574596.html
new file mode 100644
index 0000000000..d9f6832bb4
--- /dev/null
+++ b/dom/base/test/test_bug574596.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=574596
+-->
+<head>
+ <title>Test for Bug 574596</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=574596">Mozilla Bug 574596</a>
+<style type="text/css">
+#link1 a { user-select:none; }
+</style>
+<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>
+<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 574596 **/
+
+function ignoreFunc(actualData, expectedData) {
+ return true;
+}
+
+var dragLinkText = [[
+ { type:"text/x-moz-url", data:"", eqTest:ignoreFunc },
+ { type:"text/x-moz-url-data", data:"http://www.mozilla.org/" },
+ { type:"text/x-moz-url-desc", data:"link1" },
+ { type:"text/uri-list", data:"http://www.mozilla.org/" },
+ { type:"text/_moz_htmlcontext", data:"", eqTest:ignoreFunc },
+ { type:"text/_moz_htmlinfo", data:"", eqTest:ignoreFunc },
+ { type:"text/html", data:'<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>' },
+ { type:"text/plain", data:"http://www.mozilla.org/" }
+]];
+
+
+function dumpTransfer(dataTransfer,expect) {
+ dataTransfer = SpecialPowers.wrap(dataTransfer);
+ dtData = dataTransfer.mozItemCount + "items:\n";
+ for (var i = 0; i < dataTransfer.mozItemCount; i++) {
+ var dtTypes = dataTransfer.mozTypesAt(i);
+ for (var j = 0; j < dtTypes.length; j++) {
+ var actualData = dataTransfer.mozGetDataAt(dtTypes[j],i)
+ if (expect && expect[i] && expect[i][j]) {
+ if (expect[i][j].eqTest)
+ dtData += expect[i][j].eqTest(actualData,expect[i][j].data) ? "ok" : "fail";
+ else
+ dtData += (actualData == expect[i][j].data) ? "ok" : "fail";
+ }
+ dtData += "["+i+"]" + "["+j+"]: " + '"' + dtTypes[j] + '" "' + actualData + '"\n';
+ }
+ }
+ alert(dtData);
+}
+
+async function runTest() {
+ var result = await synthesizePlainDragAndCancel(
+ {
+ srcElement: $('link1').firstChild,
+ finalY: -10, // Avoid clicking the link
+ },
+ dragLinkText);
+ ok(result === true, "Drag user-select:none link (#link1)");
+ // if (result) dumpTransfer(result,dragLinkText);
+
+ dragLinkText[0][2].data = "link2";
+ dragLinkText[0][6].data = '<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>'
+ var result = await synthesizePlainDragAndCancel(
+ {
+ srcElement: $('link2').firstChild,
+ finalY: -10, // Avoid clicking the link
+ },
+ dragLinkText);
+ ok(result === true, "Drag link (#link2)");
+ // if (result) dumpTransfer(result,dragLinkText);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug578096.html b/dom/base/test/test_bug578096.html
new file mode 100644
index 0000000000..22df0757ca
--- /dev/null
+++ b/dom/base/test/test_bug578096.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=578096
+-->
+<head>
+ <title>Test for Bug 578096</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=578096">Mozilla Bug 578096</a>
+<p id="display"></p>
+<div id="content">
+ <input type="file" id="file" onchange="fireXHR()">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var url = SimpleTest.getTestFileURL("bug578096LoadChromeScript.js");
+var script = SpecialPowers.loadChromeScript(url);
+
+script.addMessageListener("file.created", function (message) {
+ SpecialPowers.wrap(document.getElementById('file')).mozSetFileArray([message]);
+
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function(event) {
+ if (xhr.readyState == 4) {
+ script.sendAsyncMessage("file.remove", {});
+ }
+ }
+
+ xhr.open('POST', window.location, true);
+ xhr.send(document.getElementById('file').files[0]);
+});
+
+script.addMessageListener("file.removed", function (message) {
+ ok(true, "We didn't throw! Yay!");
+ script.destroy();
+ SimpleTest.finish();
+});
+
+script.sendAsyncMessage("file.create", {});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug585978.html b/dom/base/test/test_bug585978.html
new file mode 100644
index 0000000000..e00cb8d84c
--- /dev/null
+++ b/dom/base/test/test_bug585978.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=585978
+-->
+<head>
+ <title>Test for Bug 585978</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=585978">Mozilla Bug 585978</a>
+
+<script type="application/javascript">
+
+/* Test that if we have a unicode character in the middle of an ascii string,
+ the unicode character survives translation into and out of a text node. */
+
+for (let i = 0; i < 128; i++) {
+ let node = document.createTextNode('');
+ let str = '';
+ for (let j = 0; j < i; j++) {
+ str += 'a';
+ }
+ str += '\uA0A9'
+ node.data = str;
+
+ for (let j = 0; j < 32; j++) {
+ is(node.data, str);
+
+ str += 'b';
+ node.appendData('b');
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug587931.html b/dom/base/test/test_bug587931.html
new file mode 100644
index 0000000000..0a4b84dec7
--- /dev/null
+++ b/dom/base/test/test_bug587931.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=587931
+-->
+<head>
+ <title>Test for Bug 587931</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=587931">Mozilla Bug 587931</a>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 587931 **/
+SimpleTest.waitForExplicitFinish();
+var afterCount = 0;
+var lastBeforeExecute = null;
+var expectedCurrentScriptInAfterScriptExecute = null;
+function verifyScript(n) {
+ var curr = document.currentScript;
+ is(curr, document.getElementById(n), "correct script (" + n + ")");
+ is(lastBeforeExecute, curr, "correct beforescript (" + n + ")");
+ document.addEventListener("afterscriptexecute", function(event) {
+ afterCount++;
+ lastBeforeExecute = null;
+ is(event.target, curr, "correct afterscript (" + n + ")");
+ is(document.currentScript, expectedCurrentScriptInAfterScriptExecute,
+ "document.currentScript in afterscriptexecute(" + n + ")");
+ document.removeEventListener("afterscriptexecute", arguments.callee);
+ });
+}
+document.onbeforescriptexecute = function(event) {
+ lastBeforeExecute = event.target;
+};
+
+window.addEventListener("load", function() {
+ is(afterCount, 4, "correct number of afterscriptexecute");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+<!-- Test parser inserted scripts -->
+<script id="parse-inline">
+verifyScript("parse-inline");
+</script>
+<script id="parse-ext" src="data:text/plain,verifyScript('parse-ext');"></script>
+
+<!-- Test DOM inserted scripts -->
+<script>
+var s = document.createElement("script");
+s.textContent = "verifyScript('dom-inline');";
+s.id = "dom-inline";
+expectedCurrentScriptInAfterScriptExecute = document.currentScript;
+document.body.appendChild(s);
+expectedCurrentScriptInAfterScriptExecute = null;
+
+s = document.createElement("script");
+s.src = "data:text/plain,verifyScript('dom-ext');";
+s.id = "dom-ext";
+document.body.appendChild(s);
+</script>
+
+<!-- Test cancel using beforescriptexecute -->
+<script onbeforescriptexecute="return false;"
+ onafterescriptexecute="window.firedAfterScriptExecuteForCancel = true;">
+ok(false, "should have been canceled");
+</script>
+<script>
+isnot(window.firedAfterScriptExecuteForCancel, true, "onafterscriptexecute executed");
+</script>
+
+<!-- Test cancel using beforescriptexecute for external -->
+<script onbeforescriptexecute="return false;"
+ onafterescriptexecute="window.extFiredAfterScriptExecuteForCancel = true;"
+ onload="window.extFiredLoadForCancel = true;"
+ src="data:text/plain,ok(false, 'should have been canceled');">
+</script>
+<script>
+isnot(window.extFiredAfterScriptExecuteForCancel, true, "onafterscriptexecute executed");
+is(extFiredLoadForCancel, true, "onload executed");
+</script>
+
+<!-- Test that all events fire -->
+<script onbeforescriptexecute="window.beforeDidExecute = true;"
+ onafterscriptexecute="window.afterDidExecute = true;"
+ onload="window.loadDidExecute = true"
+ onerror="window.errorDidExecute = true"
+ src="data:text/plain,
+is(window.beforeDidExecute, true, 'onbeforescriptexecute executed');
+isnot(window.afterDidExecute, true, 'onafterscriptexecute executed');
+isnot(window.loadDidExecute, true, 'onload executed');
+isnot(window.errorDidExecute, true, 'onerror executed');
+">
+</script>
+<script>
+is(afterDidExecute, true, "onafterscriptexecute executed");
+is(loadDidExecute, true, "onload executed");
+isnot(window.errorDidExecute, true, "onerror executed");
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug588990.html b/dom/base/test/test_bug588990.html
new file mode 100644
index 0000000000..ac7051270e
--- /dev/null
+++ b/dom/base/test/test_bug588990.html
@@ -0,0 +1,332 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588990
+-->
+<head>
+ <title>Test for Bug 588990</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=588990">Mozilla Bug 588990</a>
+<!-- DOM to muck around with for tests -->
+<p id="root">
+<img name="n1">
+<img name="n2">
+<img name="n2">
+<img name="n3">
+<img name="n3">
+<img name="n3">
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+root = $('root');
+i1_1 = root.children[0];
+i2_1 = root.children[1];
+i2_2 = root.children[2];
+i3_1 = root.children[3];
+i3_2 = root.children[4];
+i3_3 = root.children[5];
+
+function checkHasName(test) {
+ // Check name first to avoid flushes from hiding problems
+ checkHasNameNoDocProp(test);
+
+ is(document.n1, i1_1, "i1_1 doc.name " + test);
+ is(document.n2[0], i2_1, "i2_1 doc.name " + test);
+ is(document.n2[1], i2_2, "i2_2 doc.name " + test);
+ is(document.n2.length, 2, "doc.name.length " + test);
+ is(document.n3[0], i3_1, "i3_1 doc.name " + test);
+ is(document.n3[1], i3_2, "i3_2 doc.name " + test);
+ is(document.n3[2], i3_3, "i3_3 doc.name " + test);
+ is(document.n3.length, 3, "doc.name.length " + test);
+}
+
+function checkHasNameNoDocProp(test) {
+ is(i1_1.name, "n1", "i1_1 name " + test);
+ is(i2_1.name, "n2", "i2_1 name " + test);
+ is(i2_2.name, "n2", "i2_2 name " + test);
+ is(i3_1.name, "n3", "i3_1 name " + test);
+ is(i3_2.name, "n3", "i3_2 name " + test);
+ is(i3_3.name, "n3", "i3_3 name " + test);
+}
+
+function checkHasNoName(removed, test) {
+ is(i1_1.name, "", "i1_1 name " + test);
+ is(i2_1.name, "", "i2_1 name " + test);
+ is(i2_2.name, "", "i2_2 name " + test);
+ is(i3_1.name, "", "i3_1 name " + test);
+ is(i3_2.name, "", "i3_2 name " + test);
+ is(i3_3.name, "", "i3_3 name " + test);
+
+ var attrValue = removed ? null : "";
+ is(i1_1.getAttribute("name"), attrValue, "i1_1 getAttribute " + test);
+ is(i2_1.getAttribute("name"), attrValue, "i2_1 getAttribute " + test);
+ is(i2_2.getAttribute("name"), attrValue, "i2_2 getAttribute " + test);
+ is(i3_1.getAttribute("name"), attrValue, "i3_1 getAttribute " + test);
+ is(i3_2.getAttribute("name"), attrValue, "i3_2 getAttribute " + test);
+ is(i3_3.getAttribute("name"), attrValue, "i3_3 getAttribute " + test);
+
+ is(document.n1, undefined, "doc.n1 " + test);
+ is(document.n2, undefined, "doc.n2 " + test);
+ is(document.n3, undefined, "doc.n3 " + test);
+}
+
+// Check that dynamic modifications of attribute work
+
+checkHasName("in markup");
+
+i1_1.name = "";
+i2_1.name = "";
+i2_2.name = "";
+i3_1.name = "";
+i3_2.name = "";
+i3_3.name = "";
+
+checkHasNoName(false, "set to empty");
+
+i1_1.name = "n1";
+i2_1.name = "n2";
+i2_2.name = "n2";
+i3_1.name = "n3";
+i3_2.name = "n3";
+i3_3.name = "n3";
+
+checkHasName("set using .name");
+
+i1_1.setAttribute("name", "");
+i2_1.setAttribute("name", "");
+i2_2.setAttribute("name", "");
+i3_1.setAttribute("name", "");
+i3_2.setAttribute("name", "");
+i3_3.setAttribute("name", "");
+
+checkHasNoName(false, "setAttribute to empty");
+
+i1_1.name = "n1";
+i2_1.name = "n2";
+i2_2.name = "n2";
+i3_1.name = "n3";
+i3_2.name = "n3";
+i3_3.name = "n3";
+
+checkHasName("set again using .name");
+
+i1_1.removeAttribute("name");
+i2_1.removeAttribute("name");
+i2_2.removeAttribute("name");
+i3_1.removeAttribute("name");
+i3_2.removeAttribute("name");
+i3_3.removeAttribute("name");
+
+checkHasNoName(true, "removed attribute");
+
+i1_1.setAttribute("name", "n1");
+i2_1.setAttribute("name", "n2");
+i2_2.setAttribute("name", "n2");
+i3_1.setAttribute("name", "n3");
+i3_2.setAttribute("name", "n3");
+i3_3.setAttribute("name", "n3");
+
+checkHasName("set using setAttribute");
+
+t1 = document.createElement("img");
+t1.name = "n1";
+t2 = document.createElement("img");
+t2.name = "n2";
+t3 = document.createElement("img");
+t3.name = "n2";
+t4 = document.createElement("img");
+t4.name = "n3";
+t5 = document.createElement("img");
+t5.name = "n3";
+t6 = document.createElement("img");
+t6.name = "n3";
+
+// Check that inserting elements before/after existing work
+
+function insertAfter(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing.nextSibling);
+}
+function insertBefore(newChild, existing) {
+ existing.parentNode.insertBefore(newChild, existing);
+}
+function removeNode(child) {
+ child.remove();
+}
+
+insertAfter(t1, i1_1);
+insertAfter(t2, i2_1);
+insertAfter(t3, i2_2);
+insertAfter(t4, i3_1);
+insertAfter(t5, i3_2);
+insertAfter(t6, i3_3);
+
+checkHasNameNoDocProp("inserted after");
+is(document.n1[0], i1_1, "i1_1 doc.name inserted after");
+is(document.n1[1], t1, "t1 doc.name inserted after");
+is(document.n1.length, 2, "doc.name1.length inserted after");
+is(document.n2[0], i2_1, "i2_1 doc.name inserted after");
+todo_is(document.n2[1], t2, "This is where t2 should show up. The elements in here should be in order-in-document rather than order-of-insertion");
+is(document.n2[1], i2_2, "i2_2 doc.name inserted after");
+is(document.n2[2], t2, "t2 doc.name inserted after");
+is(document.n2[3], t3, "t3 doc.name inserted after");
+is(document.n2.length, 4, "doc.name2.length inserted after");
+is(document.n3[0], i3_1, "i3_1 doc.name inserted after");
+is(document.n3[1], i3_2, "i3_3 doc.name inserted after");
+is(document.n3[2], i3_3, "i3_2 doc.name inserted after");
+is(document.n3[3], t4, "t4 doc.name inserted after");
+is(document.n3[4], t5, "t5 doc.name inserted after");
+is(document.n3[5], t6, "t6 doc.name inserted after");
+is(document.n3.length, 6, "doc.name3.length inserted after");
+
+
+insertBefore(t1, i1_1);
+insertBefore(t2, i2_1);
+insertBefore(t3, i2_2);
+insertBefore(t4, i3_1);
+insertBefore(t5, i3_2);
+insertBefore(t6, i3_3);
+
+checkHasNameNoDocProp("inserted before");
+is(document.n1[0], i1_1, "i1_1 doc.name inserted before");
+is(document.n1[1], t1, "t1 doc.name inserted before");
+is(document.n1.length, 2, "doc.name1.length inserted before");
+is(document.n2[0], i2_1, "i2_1 doc.name inserted before");
+is(document.n2[1], i2_2, "i2_2 doc.name inserted before");
+is(document.n2[2], t2, "t2 doc.name inserted before");
+is(document.n2[3], t3, "t3 doc.name inserted before");
+is(document.n2.length, 4, "doc.name2.length inserted before");
+is(document.n3[0], i3_1, "i3_1 doc.name inserted before");
+is(document.n3[1], i3_2, "i3_3 doc.name inserted before");
+is(document.n3[2], i3_3, "i3_2 doc.name inserted before");
+is(document.n3[3], t4, "t4 doc.name inserted before");
+is(document.n3[4], t5, "t5 doc.name inserted before");
+is(document.n3[5], t6, "t6 doc.name inserted before");
+is(document.n3.length, 6, "doc.name3.length inserted before");
+
+t1.removeAttribute("name");
+t2.removeAttribute("name");
+t3.removeAttribute("name");
+t4.removeAttribute("name");
+t5.removeAttribute("name");
+t6.removeAttribute("name");
+
+checkHasName("removed tx attribute");
+
+t1.setAttribute("name", "n1");
+t2.setAttribute("name", "n2");
+t3.setAttribute("name", "n2");
+t4.setAttribute("name", "n3");
+t5.setAttribute("name", "n3");
+t6.setAttribute("name", "n3");
+
+checkHasNameNoDocProp("inserted before");
+is(document.n1[0], i1_1, "i1_1 doc.name inserted before");
+is(document.n1[1], t1, "t1 doc.name inserted before");
+is(document.n1.length, 2, "doc.name1.length inserted before");
+is(document.n2[0], i2_1, "i2_1 doc.name inserted before");
+is(document.n2[1], i2_2, "i2_2 doc.name inserted before");
+is(document.n2[2], t2, "t2 doc.name inserted before");
+is(document.n2[3], t3, "t3 doc.name inserted before");
+is(document.n2.length, 4, "doc.name2.length inserted before");
+is(document.n3[0], i3_1, "i3_1 doc.name inserted before");
+is(document.n3[1], i3_2, "i3_3 doc.name inserted before");
+is(document.n3[2], i3_3, "i3_2 doc.name inserted before");
+is(document.n3[3], t4, "t4 doc.name inserted before");
+is(document.n3[4], t5, "t5 doc.name inserted before");
+is(document.n3[5], t6, "t6 doc.name inserted before");
+is(document.n3.length, 6, "doc.name3.length inserted before");
+
+removeNode(t1);
+removeNode(t2);
+removeNode(t3);
+removeNode(t4);
+removeNode(t5);
+removeNode(t6);
+
+checkHasName("removed temporaries");
+
+removeNode(i1_1);
+removeNode(i2_1);
+removeNode(i2_2);
+removeNode(i3_1);
+removeNode(i3_2);
+removeNode(i3_3);
+
+checkHasNameNoDocProp("removed node");
+
+// Check that removing an element during UnsetAttr works
+is(i1_1.name, "n1", "i1_1 has name set");
+var mutateFired = false;
+root.appendChild(i1_1);
+i1_1.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, i1_1, "target is i1_1");
+ is(i1_1.name, "", "i1_1 no longer has name");
+ is(i1_1.getAttribute("name"), null, "i1_1 no longer has name attr");
+ removeNode(i1_1);
+ is(i1_1.parentNode, null, "i1_1 was removed");
+ mutateFired = true;
+}, {once: true});
+i1_1.removeAttribute("name");
+ok(mutateFired, "mutation event fired");
+SpecialPowers.gc();
+
+// Check that removing an element during SetAttr works
+i2_1.name = "";
+mutateFired = false;
+root.appendChild(i2_1);
+i2_1.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, i2_1, "target is i2_1");
+ is(i2_1.name, "n2", "i2_1 no longer has name");
+ is(i2_1.getAttribute("name"), "n2", "i2_1 no longer has name attr");
+ removeNode(i2_1);
+ is(i2_1.parentNode, null, "i2_1 was removed");
+ mutateFired = true;
+}, {once: true});
+i2_1.name = "n2";
+ok(mutateFired, "mutation event fired");
+SpecialPowers.gc();
+
+// Re-add the name inside a mutation event on a HTML element
+is(i2_2.name, "n2", "i2_2 has name set");
+root.appendChild(i2_2);
+mutateFired = false;
+root.appendChild(i2_2);
+i2_2.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, i2_2, "target is i2_2");
+ is(i2_2.name, "", "i2_2 no longer has name");
+ is(i2_2.getAttribute("name"), "", "i2_2 has empty name attr");
+ i2_2.name = "n2";
+ mutateFired = true;
+}, {once: true});
+i2_2.name = "";
+ok(mutateFired, "mutation event fired");
+is(document.n2, i2_2, "named was readded during mutation");
+removeNode(i2_2);
+SpecialPowers.gc();
+
+// Re-remove the name inside a mutation event on a HTML element
+i3_1.name = "";
+root.appendChild(i3_1);
+mutateFired = false;
+root.appendChild(i3_1);
+i3_1.addEventListener("DOMAttrModified", function(e) {
+ is(e.target, i3_1, "target is i3_1");
+ is(i3_1.name, "n3", "i3_1 no longer has name");
+ is(i3_1.getAttribute("name"), "n3", "i3_1 has empty name attr");
+ i3_1.removeAttribute("name");
+ mutateFired = true;
+}, {once: true});
+i3_1.name = "n3";
+ok(mutateFired, "mutation event fired");
+is(document.n3, undefined, "named was readded during mutation");
+removeNode(i3_1);
+SpecialPowers.gc();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug590812.html b/dom/base/test/test_bug590812.html
new file mode 100644
index 0000000000..d96da7b538
--- /dev/null
+++ b/dom/base/test/test_bug590812.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XML pretty printing, bug 590812</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590812">Mozilla Bug 590812</a>
+<p id="display"></p>
+<iframe id=iframe></iframe>
+<iframe src="file_bug590812-ref.xhtml"></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+add_task(async function start() {
+ var noxul = "https://sub1.test1.example.com:443";
+ var yesxul = "https://example.org:443"
+
+ await SpecialPowers.pushPermissions([
+ { type: "allowXULXBL", allow: false, context: noxul },
+ { type: "allowXULXBL", allow: true, context: yesxul }
+ ]);
+
+ var path = "/tests/dom/base/test/file_bug590812.xml";
+ var iframe = $('iframe');
+ iframe.src = noxul + path;
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+ let sNoXUL = await snapshotWindow(window.frames[0], false);
+
+ iframe.src = yesxul + path;
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+ let sWithXUL = await snapshotWindow(window.frames[0], false);
+
+ let sRef = await snapshotWindow(window.frames[1], false);
+
+ let res;
+ ok(compareSnapshots(sNoXUL, sRef, true)[0],
+ "noxul domain same as ref");
+ ok(compareSnapshots(sWithXUL, sRef, true)[0],
+ "xul supporting domain same as ref");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug590870.html b/dom/base/test/test_bug590870.html
new file mode 100644
index 0000000000..29269ee609
--- /dev/null
+++ b/dom/base/test/test_bug590870.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for creating XUL elements, bug 590870</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590870">Mozilla Bug 590870</a>
+<p id="display"></p>
+<iframe id=iframe></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+add_task(async function start() {
+ var noxul = "https://sub1.test1.example.com:443";
+ var yesxul = "https://example.org:443"
+
+ await SpecialPowers.pushPermissions([
+ { type: "allowXULXBL", allow: false, context: noxul },
+ { type: "allowXULXBL", allow: true, context: yesxul }
+ ]);
+
+ var path = "/tests/dom/base/test/file_bug590870.html";
+ var iframe = $('iframe');
+
+ iframe.src = noxul + path;
+ await new Promise(resolve => window.addEventListener("message", event => {
+ is(event.data, true, "shouldn't be able to create XUL elements");
+ resolve();
+ }, { once: true } ));
+
+ iframe.src = yesxul + path;
+ await new Promise(resolve => window.addEventListener("message", event => {
+ is(event.data, false, "should be able to create XUL elements");
+ resolve();
+ }, { once: true } ));
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug592366.html b/dom/base/test/test_bug592366.html
new file mode 100644
index 0000000000..1e217b5358
--- /dev/null
+++ b/dom/base/test/test_bug592366.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592366
+-->
+<head>
+ <title>Test for Bug 592366</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=592366">Mozilla Bug 592366</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe></iframe>
+<iframe></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 592366 **/
+
+var gExecuted = false;
+
+function hitEventLoop(times, next)
+{
+ if (times == 0) {
+ next();
+ return;
+ }
+
+ SimpleTest.executeSoon(function() {
+ hitEventLoop(times - 1, next);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var s = document.createElement("script");
+ s.src = "data:text/javascript,parent.gExecuted=true;";
+
+ var iframes = document.getElementsByTagName("iframe");
+
+ iframes[0].contentDocument.body.appendChild(s);
+ iframes[1].contentDocument.body.appendChild(s);
+
+ // It seems to work with 1 event loop hit locally but using 2 given that it
+ // was hsivonen advice.
+ hitEventLoop(2, function() {
+ ok(!gExecuted, "The scripts should not have been executed");
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug592829.html b/dom/base/test/test_bug592829.html
new file mode 100644
index 0000000000..cb078f8bce
--- /dev/null
+++ b/dom/base/test/test_bug592829.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592829
+-->
+<head>
+ <title>Test for Bug 592829</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=592829">Mozilla Bug 592829</a>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 592829 **/
+
+// NOTE! It's imperative that we don't call .init() here. Otherwise we're not
+// testing what happens if parsing fails.
+// If we ever change how DOMParser initilization works, just update this code
+// to create a DOMParser which is not allowed to parse XUL.
+
+var isXUL = true;
+var parser = SpecialPowers.getNoXULDOMParser();
+ok(parser, "Should get a parser!");
+
+try {
+ var x = parser
+ .parseFromString('<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>', "text/xml");
+ isXUL = x.documentElement.namespaceURI ==
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+}
+catch (ex) {
+ isXUL = false;
+}
+
+is(isXUL, false, "We didn't create XUL and we didn't crash!");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug597345.html b/dom/base/test/test_bug597345.html
new file mode 100644
index 0000000000..21cd73a29a
--- /dev/null
+++ b/dom/base/test/test_bug597345.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=597345
+-->
+<head>
+ <title>Test for Bug 597345</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=597345">Mozilla Bug 597345</a>
+<pre id="test">
+ <!-- We want no spaces inside span id="target". The 'stray' close comment at
+ the end of that span needs to be there! -->
+ <span id="target"><script
+ src="script-1_bug597345.sjs"></script><script
+ charset="ISO-5559-1" src="script-2_bug597345.js"></script>--></span>
+<script type="application/javascript">
+
+/** Test for Bug 597345 **/
+is($("target").textContent, "R\u00e4ksm\u00f6rg\u00e5s",
+ "script-2 should be decoded as UTF-8");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug599295.html b/dom/base/test/test_bug599295.html
new file mode 100644
index 0000000000..ba807a5b7e
--- /dev/null
+++ b/dom/base/test/test_bug599295.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599295
+-->
+<head>
+ <title>Test for Bug 599295</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599295">Mozilla Bug 599295</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 599295 **/
+
+/* Do not allow a response to a CONNECT method, used to establish an
+ SSL tunnel over an HTTP proxy, to contain a redirect */
+
+function runTest() {
+ /* Previously, necko would allow a 302 as part of a CONNECT response
+ if the LOAD_DOCUMENT_URI flag was set and the original document
+ URI had not yet been changed. */
+
+ SpecialPowers.loadChannelAndReturnStatus("https://redirproxy.example.com/test",
+ true)
+ .then(function({status, httpStatus}) {
+ /* testing here that the redirect was not followed. If it was followed
+ we would see a http status of 200 and status of NS_OK */
+
+ is(httpStatus, 302, "http status 302");
+ is(status, SpecialPowers.Cr.NS_ERROR_CONNECTION_REFUSED,
+ "raised refused");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug599588.html b/dom/base/test/test_bug599588.html
new file mode 100644
index 0000000000..043f48687a
--- /dev/null
+++ b/dom/base/test/test_bug599588.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599588
+-->
+<head>
+ <title>Test for Bug 599588</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599588">Mozilla Bug 599588</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 599588 **/
+
+var div = document.createElement("div");
+div.innerHTML = "\u003Cscript>ok(false, 'Scripts created by innerHTML should not run.');\u003C/script>";
+document.body.appendChild(div);
+
+var contextualFragmentRan = false;
+
+var range = document.createRange();
+range.selectNode(document.body);
+var fragment = range.createContextualFragment("\u003Cscript>contextualFragmentRan = true;\u003C/script>");
+document.body.appendChild(fragment);
+
+ok(contextualFragmentRan, "Script from createContextualFragment should have run.");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug601803.html b/dom/base/test/test_bug601803.html
new file mode 100644
index 0000000000..5f4e765ab3
--- /dev/null
+++ b/dom/base/test/test_bug601803.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=601803
+-->
+<head>
+ <title>Test for Bug 601803</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=601803">Mozilla Bug 601803</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="frame"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 601803 **/
+SimpleTest.waitForExplicitFinish();
+
+window.onmessage = function (event) {
+ is(event.data, false, "Shouldn't throw when adopting a node cross-compartment");
+ SimpleTest.finish();
+}
+
+document.getElementById("frame").src = "http://example.org/tests/dom/base/test/file_bug601803a.html";
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug602838.html b/dom/base/test/test_bug602838.html
new file mode 100644
index 0000000000..661a6410e8
--- /dev/null
+++ b/dom/base/test/test_bug602838.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602838
+-->
+<head>
+ <title>Test for Bug 602838</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602838">Mozilla Bug 602838</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script id=withasync async></script>
+<script id=withoutasync></script>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 602838 **/
+SimpleTest.waitForExplicitFinish();
+var firstRan = false;
+var asyncRan = false;
+
+var withoutasync = document.getElementById("withoutasync");
+ok(withoutasync.async, "When a script loses parser-insertedness, it should become async.");
+
+var withasync = document.getElementById("withasync");
+ok(withasync.async, "A script with the async content attribute should have the DOM attribute reporting true.");
+withasync.removeAttribute("async");
+ok(!withasync.async, "Should be able to remove asyncness from a script that had the async content attribute when losing parser-insertedness by removing the content attribute.");
+
+var s = document.createElement("script");
+ok(s.async, "Script-created scripts should default to .async=true");
+ok(!s.hasAttribute("async"), "Script-created scripts should not have the async content attribute by default.");
+s.removeAttribute("async");
+ok(s.async, "Removing a non-existing content-attribute should not have an effect on the forced async DOM property.");
+s.setAttribute("async", "");
+ok(s.async, "The async DOM property should still be true.");
+s.removeAttribute("async");
+ok(!s.async, "When a previously present async content attribute is removed, the DOM property should become false.");
+s.src = "script_bug602838.sjs";
+document.body.appendChild(s);
+
+s = document.createElement("script");
+s.src = "data:text/javascript,ok(firstRan, 'The first script should have run'); SimpleTest.finish();";
+s.async = false;
+ok(!s.async, "Setting the async DOM property to false should turned of forcing async to true.");
+document.body.appendChild(s);
+
+function unblock() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "script_bug602838.sjs?unblock");
+ xhr.send();
+}
+
+s = document.createElement("script");
+s.src = "data:text/javascript,ok(!firstRan, 'Non-async should not have run yet.'); asyncRan = true; unblock();";
+document.body.appendChild(s);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug604592.html b/dom/base/test/test_bug604592.html
new file mode 100644
index 0000000000..e92ee8acde
--- /dev/null
+++ b/dom/base/test/test_bug604592.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=604592
+-->
+<head>
+ <title>Test for Bug 604592</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=604592">Mozilla Bug 604592</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 604592 **/
+function testInStrictMode() {
+ "use strict";
+ try {
+ document.body.prefix = "foo";
+ ok(false, "Should not have reached this point");
+ } catch (e) {
+ ok(e instanceof TypeError, "Expected a TypeError");
+ }
+}
+
+testInStrictMode();
+document.body.prefix = "foo";
+ok(document.body.prefix === null,
+ "Expected strictly equal to null, got " + document.body.prefix);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug604660.html b/dom/base/test/test_bug604660.html
new file mode 100644
index 0000000000..dd5c8be0d9
--- /dev/null
+++ b/dom/base/test/test_bug604660.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=604660
+-->
+<head>
+ <title>Test for Bug 604660</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=604660">Mozilla Bug 604660</a>
+<script>
+SimpleTest.waitForExplicitFinish();
+var asyncState = false;
+var scriptState = 0;
+
+function scriptRan(num) {
+ ++scriptState;
+ is(scriptState, num, "Scripts ran in the wrong sequence.");
+}
+
+function asyncRan() {
+ asyncState = true;
+}
+
+</script>
+<p id="display"><iframe src="file_bug604660-1.xml" onload="iframeloaded()";></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var xlstProcessorState = false;
+
+function xsltProcessorCreatedScriptRan() {
+ xlstProcessorState = true;
+}
+
+function iframeloaded() {
+ ok(asyncState, "Async script should have run.");
+ is(scriptState, 5, "Five scripts should have run.");
+
+ var processor = new XSLTProcessor();
+
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ processor.importStylesheet(this.responseXML);
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ var doc = processor.transformToDocument(this.responseXML);
+ var target = document.getElementById("display");
+ target.appendChild(doc.documentElement.firstChild);
+ ok(!xlstProcessorState, "Scripts created by transformToDocument should not run.");
+
+ var fragment = processor.transformToFragment(this.responseXML, document);
+ target.appendChild(fragment.firstChild.firstChild);
+ ok(xlstProcessorState, "Scripts created by transformToFragment should run.");
+
+ SimpleTest.finish();
+ }
+ }
+ xhr.open("GET", "file_bug604660-5.xml");
+ xhr.send();
+ }
+ }
+ xhr.open("GET", "file_bug604660-6.xsl");
+ xhr.send();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug605982.html b/dom/base/test/test_bug605982.html
new file mode 100644
index 0000000000..abd9dc9c32
--- /dev/null
+++ b/dom/base/test/test_bug605982.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605982
+-->
+<head>
+ <title>Test for Bug 605982</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=605982">Mozilla Bug 605982</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605982 **/
+
+var elmt = document.createElement("div");
+
+var caught = false;
+try {
+ elmt.matches("!!");
+} catch(e) {
+ ok(e.name == "SyntaxError", "Error should be SyntaxError");
+ ok(e.code == DOMException.SYNTAX_ERR, "Error code should be SYNTAX_ERR");
+ caught = true;
+}
+ok(caught, "An exception should have been thrown");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug606729.html b/dom/base/test/test_bug606729.html
new file mode 100644
index 0000000000..1f9e54d8ec
--- /dev/null
+++ b/dom/base/test/test_bug606729.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=606729
+-->
+<head>
+ <title>Test for Bug 606729</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=606729">Mozilla Bug 606729</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+ SimpleTest.waitForExplicitFinish();
+ var events = 0;
+ var expectedEvents = 2;
+ function eventFired() {
+ ++events;
+ if (events == expectedEvents) {
+ SimpleTest.finish();
+ }
+ }
+</script>
+<script
+ src="data:"
+ onerror="ok(true, 'Script with src=data: should fire onerror.');
+ eventFired();"
+ onload="ok(false, 'Script with src=data: should not fire onload.');
+ eventFired();"
+>
+ok(false, "Script with src=data: should not run textContent.");
+</script>
+<script
+ src="bogus:"
+ onerror="ok(true, 'Script with src=bogus: should fire onerror.');
+ eventFired();"
+ onload="ok(false, 'Script with src=bogus: should not fire onload.');
+ eventFired();"
+>
+ok(false, "Script with src=bogus: should not run textContent.");
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_bug614058.html b/dom/base/test/test_bug614058.html
new file mode 100644
index 0000000000..61ca86005e
--- /dev/null
+++ b/dom/base/test/test_bug614058.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=614058
+-->
+<head>
+ <title>Test for Bug 614058</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=614058">Mozilla Bug 614058</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 614058 **/
+var f1 = document.createDocumentFragment();
+f2 = f1.cloneNode(true);
+f1.appendChild(document.createElement("foo"));
+is(f1.isEqualNode(f2), false, "Fragments have different kids!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug622088.html b/dom/base/test/test_bug622088.html
new file mode 100644
index 0000000000..bedb192bfc
--- /dev/null
+++ b/dom/base/test/test_bug622088.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 622088 - Test that XHR gives the referrer corresponding to the dynamic script context.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622088">Mozilla Bug 622088</a>
+<pre id="test">
+
+<iframe id='iframe' src='file_bug622088_inner.html'></iframe>
+
+<iframe id='dataWindow' srcdoc="<html><head>
+<script>function getXHRObject() { return new XMLHttpRequest(); }</script>
+</head><body onload='parent.dataWindowLoaded()'>Hello!!</body></html>"></iframe>
+
+<script class="testbody" type="application/javascript">
+
+// Bug 622088 - Test that XHR gives the referrer corresponding to the
+// dynamic script context.
+
+const kOriginalLocation = location.href;
+
+SimpleTest.waitForExplicitFinish();
+
+var innerFinishedLoading = false;
+function innerLoaded(inner) {
+ // Here, we're being called through inner's onload handler, so our referrer
+ // should be inner's URL.
+ var referrer = inner.doXHR();
+ is (referrer, String(inner.document.location), 'Expected inner frame location');
+
+ // Now change the location of the inner frame. This should be reflected in
+ // the XHR's referrer.
+ inner.history.pushState('', '', Math.random());
+ referrer = inner.doXHR();
+ is (referrer, String(inner.document.location), 'Expected inner frame location after pushstate');
+
+ innerFinishedLoading = true;
+}
+
+var dataWindowFinishedLoading = false;
+function dataWindowLoaded() {
+ dataWindowFinishedLoading = true;
+}
+
+function callXHR() {
+ if (innerFinishedLoading && dataWindowFinishedLoading) {
+ var inner = document.getElementById('iframe').contentWindow;
+ var referrer = inner.doXHR();
+ is (referrer, String(inner.document.location),
+ 'Expected inner frame location when called from outer frame.');
+
+ var referrer = inner.doXHR(new XMLHttpRequest());
+ is (referrer, String(document.location),
+ "Expected outer frame location when called with outer's XHR object.");
+
+ // Now do a request within the inner window using an XMLHttpRequest
+ // retrieved from a data: URI. The referrer should be this window, not the
+ // data: URI.
+ var dataWindow = document.getElementById('dataWindow').contentWindow;
+ var referrer = inner.doXHR(dataWindow.getXHRObject());
+ is (referrer, String(document.location),
+ "Expected outer frame location when called with data's XHR object.");
+
+ // Now do that test again, but after having changed the outer window's URI.
+ // This currently fails, due to basically bug 631949. It's not even clear
+ // what the right behavior is. So marking as a todo for now.
+ history.replaceState('', '', Math.random());
+
+ var referrer = inner.doXHR(dataWindow.getXHRObject());
+ todo_is (referrer, String(document.location),
+ "Expected outer frame location when called with data's XHR object " +
+ "after replaceState.");
+
+ // In case you're temped, you probably don't want to do history.pushState
+ // here and test again with the outer frame. Calling pushState on the
+ // outer frame messes up Mochitest in subtle ways.
+
+ history.replaceState('', '', kOriginalLocation);
+ SimpleTest.finish();
+ }
+ else {
+ // ugh.
+ setTimeout(callXHR, 0);
+ }
+}
+
+callXHR();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug622117.html b/dom/base/test/test_bug622117.html
new file mode 100644
index 0000000000..8e829afc6a
--- /dev/null
+++ b/dom/base/test/test_bug622117.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622117
+-->
+<head>
+ <title>Test for Bug 622117</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622117">Mozilla Bug 622117</a>
+<p id="display">
+ <iframe id="testframe"
+ srcdoc="<a href='PASS.html' onclick='throw 1'>Click me</a>">
+ </iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622117 **/
+SimpleTest.waitForExplicitFinish();
+
+const { testframe } = document.all;
+
+waitUntilApzStable().then(async () => {
+ testframe.onload = function() {
+ is(this.contentDocument.documentElement.innerText, "PASS", "Should have loaded link");
+ SimpleTest.finish();
+ };
+
+ is(testframe.contentDocument.readyState, "complete", "iframe is not loaded yet?");
+
+ var win = testframe.contentWindow;
+ await SimpleTest.promiseFocus(win);
+
+ var a = win.document.getElementsByTagName("a")[0];
+ synthesizeMouseAtCenter(a, {}, win);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug622246.html b/dom/base/test/test_bug622246.html
new file mode 100644
index 0000000000..a4349afb68
--- /dev/null
+++ b/dom/base/test/test_bug622246.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622246
+-->
+<head>
+ <title>Test for Bug 622246</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622246">Mozilla Bug 622246</a>
+<p id="display">
+ <iframe id="testframe"
+ srcdoc="<span onclick='this.parentNode.removeChild(this)'><a href='PASS.html'>Click me</a></span>">
+ </iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622246 **/
+SimpleTest.waitForExplicitFinish();
+
+const { testframe } = document.all;
+
+waitUntilApzStable().then(async () => {
+ testframe.onload = function() {
+ is(this.contentDocument.documentElement.innerText, "PASS", "Should have loaded link");
+ SimpleTest.finish();
+ };
+
+ is(testframe.contentDocument.readyState, "complete", "iframe is not loaded yet?");
+
+ var win = testframe.contentWindow;
+ await SimpleTest.promiseFocus(win);
+
+ var a = win.document.getElementsByTagName("a")[0];
+ synthesizeMouseAtCenter(a, {}, win);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug625722.html b/dom/base/test/test_bug625722.html
new file mode 100644
index 0000000000..502427134c
--- /dev/null
+++ b/dom/base/test/test_bug625722.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=625722
+-->
+<head>
+ <title>Test for Bug 625722</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625722">Mozilla Bug 625722</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span id=root><span id=A><span id=A1></span></span><span id=B></span></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 625722 **/
+
+function testNodeFilter(n) {
+ if (n.id == 'A' || n.id == 'A1')
+ return NodeFilter.FILTER_SKIP;
+ return NodeFilter.FILTER_ACCEPT;
+}
+
+tw = document.createTreeWalker(document.getElementById("root"),
+ NodeFilter.SHOW_ELEMENT,
+ testNodeFilter);
+
+node = tw.firstChild();
+is(node.id, 'B', "First accepted child of root not B");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug626262.html b/dom/base/test/test_bug626262.html
new file mode 100644
index 0000000000..04af972040
--- /dev/null
+++ b/dom/base/test/test_bug626262.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=626262
+-->
+<head>
+ <title>Test for Bug 626262</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=626262">Mozilla Bug 626262</a>
+<p id="display"><iframe id="f" srcdoc="1"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 626262 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var iframe = document.getElementById("f");
+ var frameDoc = iframe.contentDocument;
+ var parent = frameDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
+
+ function a()
+ {
+ window.removeEventListener("DOMNodeRemoved", arguments.callee);
+ document.adoptNode(parent);
+ }
+
+ var text = document.createTextNode(" ");
+ document.documentElement.appendChild(text);
+
+ var thrown = false;
+ try {
+ window.addEventListener("DOMNodeRemoved", a);
+ parent.appendChild(text);
+ }
+ catch (e) {
+ thrown = true;
+ }
+
+ ok(!thrown, "changing ownerDocument during adoptNode should not throw");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug628938.html b/dom/base/test/test_bug628938.html
new file mode 100644
index 0000000000..a4f069468b
--- /dev/null
+++ b/dom/base/test/test_bug628938.html
@@ -0,0 +1,239 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=628938
+-->
+<head>
+ <title>Test for Bug 628938</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=628938">Mozilla Bug 628938</a>
+<p id="display"></p>
+<foo id="content" style="display: none">
+ <div><div>foo</div></div>
+ <span> bar </span>
+ <div>tulip<span>bar</span></div>
+ <span></span>
+ <div>foo</div>
+ <span></span>
+ <div>bar</div>
+ <span></span>
+ <div>foobar</div>
+</foo>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 628938 **/
+
+var gResults = [];
+var gIdx = 0;
+
+function walkTree(walker)
+{
+ if(walker.firstChild()) {
+ do {
+ if (walker.currentNode.nodeType == Node.ELEMENT_NODE) {
+ ok(gResults[gIdx][0], "current node should be an element");
+ is(walker.currentNode.nodeName, gResults[gIdx][1],
+ "current node name should be " + gResults[gIdx][1]);
+ } else {
+ ok(!gResults[gIdx][0], "current node shouldn't be an element");
+ is(walker.currentNode.nodeValue, gResults[gIdx][1],
+ "current node value should be " + gResults[gIdx][1]);
+ }
+ gIdx++;
+ // Recursively walk the rest of the sub-tree.
+ walkTree(walker);
+ } while(walker.nextSibling());
+
+ // don't forget to return the treewalker to it's previous state
+ // before exiting the function
+ walker.parentNode();
+ }
+}
+
+function regularWalk()
+{
+ gResults = [
+ [ false, "\n " ],
+ [ true, "DIV" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ false, "\n " ],
+ [ true, "SPAN" ],
+ [ false, " bar " ],
+ [ false, "\n " ],
+ [ true, "DIV" ],
+ [ false, "tulip" ],
+ [ true, "SPAN" ],
+ [ false, "bar" ],
+ [ false, "\n " ],
+ [ true, "SPAN" ],
+ [ false, "\n " ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ false, "\n " ],
+ [ true, "SPAN" ],
+ [ false, "\n " ],
+ [ true, "DIV" ],
+ [ false, "bar" ],
+ [ false, "\n " ],
+ [ true, "SPAN" ],
+ [ false, "\n " ],
+ [ true, "DIV" ],
+ [ false, "foobar" ],
+ [ false, "\n" ],
+ ];
+ var walker = document.createTreeWalker(document.getElementById('content'),
+ NodeFilter.SHOW_ALL, null);
+
+ walkTree(walker);
+
+ gIdx = 0;
+}
+
+function noWhiteSpaceWalk()
+{
+ gResults = [
+ [ true, "DIV" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "SPAN" ],
+ [ false, " bar " ],
+ [ true, "DIV" ],
+ [ false, "tulip" ],
+ [ true, "SPAN" ],
+ [ false, "bar" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ false, "bar" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ false, "foobar" ],
+ ];
+ var walker = document.createTreeWalker(document.getElementById('content'),
+ NodeFilter.SHOW_ALL,
+ {
+ acceptNode(node) {
+ if (node.nodeType == Node.TEXT_NODE &&
+ !(/[^\t\n\r ]/.test(node.nodeValue)))
+ return NodeFilter.FILTER_REJECT;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ });
+
+ walkTree(walker);
+
+ gIdx = 0;
+}
+
+function onlyElementsWalk()
+{
+ gResults = [
+ [ true, "DIV" ],
+ [ true, "DIV" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ true, "SPAN" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ ];
+ var walker = document.createTreeWalker(document.getElementById('content'),
+ NodeFilter.SHOW_ELEMENT, null);
+
+ walkTree(walker);
+
+ gIdx = 0;
+}
+
+function onlyDivSubTreeWalk()
+{
+ gResults = [
+ [ true, "DIV" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "DIV" ],
+ [ false, "tulip" ],
+ [ true, "SPAN" ],
+ [ false, "bar" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "DIV" ],
+ [ false, "bar" ],
+ [ true, "DIV" ],
+ [ false, "foobar" ],
+ ];
+ var walker = document.createTreeWalker(document.getElementById('content'),
+ NodeFilter.SHOW_ALL,
+ {
+ acceptNode(node) {
+ if (node.nodeType == Node.TEXT_NODE &&
+ !(/[^\t\n\r ]/.test(node.nodeValue)))
+ return NodeFilter.FILTER_REJECT;
+
+ while (node) {
+ if (node.nodeName == "DIV")
+ return NodeFilter.FILTER_ACCEPT;
+ node = node.parentNode;
+ }
+ return NodeFilter.FILTER_SKIP;
+ }
+ });
+
+ walkTree(walker);
+
+ gIdx = 0;
+}
+
+function onlyDivDataWalk()
+{
+ gResults = [
+ [ true, "DIV" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "DIV" ],
+ [ false, "tulip" ],
+ [ true, "SPAN" ],
+ [ true, "DIV" ],
+ [ false, "foo" ],
+ [ true, "DIV" ],
+ [ false, "bar" ],
+ [ true, "DIV" ],
+ [ false, "foobar" ],
+ ];
+ var walker = document.createTreeWalker(document.getElementById('content'),
+ NodeFilter.SHOW_ALL,
+ {
+ acceptNode(node) {
+ if (node.nodeName == "DIV" ||
+ (node.parentNode &&
+ node.parentNode.nodeName == "DIV"))
+ return NodeFilter.FILTER_ACCEPT;
+ return NodeFilter.FILTER_SKIP;
+ }
+ });
+
+ walkTree(walker);
+
+ gIdx = 0;
+}
+
+regularWalk();
+noWhiteSpaceWalk();
+onlyElementsWalk();
+onlyDivSubTreeWalk();
+onlyDivDataWalk();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug631615.html b/dom/base/test/test_bug631615.html
new file mode 100644
index 0000000000..b78295eb34
--- /dev/null
+++ b/dom/base/test/test_bug631615.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=631615
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 631615</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=631615"
+ >Mozilla Bug 631615</a>
+<pre id="monitor"></pre>
+<script>
+function doTest() {
+ var monitor = document.getElementById("monitor");
+ var html = document.documentElement;
+ var results;
+
+ try {
+ results = "return: " + html.matches("[test!='']:sizzle") + "\n";
+ } catch (e) {
+ results = "throws: " + e + "\n";
+ }
+
+ monitor.appendChild(document.createTextNode(results));
+ is(results.slice(0, 6), "throws", "looking for an exception");
+}
+
+SimpleTest.runTestExpectingConsoleMessages(doTest, [{
+ forbid: true,
+ message: /An invalid or illegal string was specified/
+}]);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug638112.html b/dom/base/test/test_bug638112.html
new file mode 100644
index 0000000000..8bc645f077
--- /dev/null
+++ b/dom/base/test/test_bug638112.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=638112
+https://bugzilla.mozilla.org/show_bug.cgi?id=796850
+-->
+<head>
+ <title>Test for Bug 638112</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=638112">Mozilla Bug 638112</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=796850">Mozilla Bug 796850</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+/** Test for Bug 638112, revised for Bug 796850 **/
+
+/* Bug 796850 changed the type of statusText to ByteString. As a result it is
+ * now considered to be raw 8 bit encoded rather than UTF8.
+ */
+
+function run_test() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "bug638112.sjs", false);
+ req.send(null);
+ var statusText = req.statusText;
+
+ is(statusText, "Information Sans-Autorit\u00E9", "");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(run_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug647518.html b/dom/base/test/test_bug647518.html
new file mode 100644
index 0000000000..0dc88d9ee1
--- /dev/null
+++ b/dom/base/test/test_bug647518.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=647518
+-->
+<head>
+ <title>Test for Bug 647518</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=647518">Mozilla Bug 647518</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 647518 **/
+SimpleTest.waitForExplicitFinish();
+var counter = 3;
+
+var called = false;
+var handle1 = window.requestAnimationFrame(function() {
+ called = true;
+});
+ok(handle1 > 0, "Should get back a nonzero handle");
+
+function checker() {
+ --counter;
+ if (counter == 0) {
+ is(called, false, "Canceled callback should not have been called");
+ SimpleTest.finish();
+ } else {
+ window.requestAnimationFrame(checker);
+ }
+}
+window.requestAnimationFrame(checker);
+window.cancelAnimationFrame(handle1);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug650001.html b/dom/base/test/test_bug650001.html
new file mode 100644
index 0000000000..14cbb4ae41
--- /dev/null
+++ b/dom/base/test/test_bug650001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650001
+-->
+<head>
+ <title>Test for Bug 650001</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650001">Mozilla Bug 650001</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650001 **/
+
+var svg = '<svg><style xml:lang="en" xlink:href="foo" xmlns="bar" xmlns:link="qux">&lt;&gt;</style><script>&lt;&gt;<\/script></svg>';
+var div = document.getElementById("content");
+div.innerHTML = svg;
+is(div.innerHTML, svg, "Unexpected serialization.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug650776.html b/dom/base/test/test_bug650776.html
new file mode 100644
index 0000000000..163da5e4c0
--- /dev/null
+++ b/dom/base/test/test_bug650776.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 650776</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650776">Mozilla Bug 650776</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650776 **/
+
+var u = SpecialPowers.Ci.nsIParserUtils;
+var s = SpecialPowers.ParserUtils;
+
+// Basic sanity
+is(s.sanitize("foo", 0), "<html><head></head><body>foo</body></html>", "Wrong sanitizer result 1");
+// Scripts get removed
+is(s.sanitize("<script>\u003c/script>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 2");
+// Event handlers get removed
+is(s.sanitize("<a onclick='boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 3");
+// By default, styles are removed
+is(s.sanitize("<style>p { color: red; }</style><p style='background-color: blue;'></p>", 0), "<html><head></head><body><p></p></body></html>", "Wrong sanitizer result 4");
+// Can allow styles
+is(s.sanitize("<style>p { color: red; }</style><p style='background-color: blue;'></p>", u.SanitizerAllowStyle), '<html><head><style>p { color: red; }</style></head><body><p style="background-color: blue;"></p></body></html>', "Wrong sanitizer result 5");
+// -moz-binding used to get dropped, but no longer does.
+is(s.sanitize("<style>p { color: red; -moz-binding: url(foo); }</style><p style='background-color: blue; -moz-binding: url(foo);'></p>", u.SanitizerAllowStyle), '<html><head><style>p { color: red; -moz-binding: url(foo); }</style></head><body><p style="background-color: blue; -moz-binding: url(foo);"></p></body></html>', "Wrong sanitizer result 6");
+// Various cid: embeds only cases
+is(s.sanitize("<img src='foo.html'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><img></body></html>', "Wrong sanitizer result 7");
+is(s.sanitize("<img src='cid:foo'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><img src="cid:foo"></body></html>', "Wrong sanitizer result 8");
+is(s.sanitize("<img src='data:image/png,'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><img></body></html>', "Wrong sanitizer result 9");
+is(s.sanitize("<img src='http://mochi.test/'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><img></body></html>', "Wrong sanitizer result 10");
+is(s.sanitize("<a href='http://mochi.test/'></a>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><a href="http://mochi.test/"></a></body></html>', "Wrong sanitizer result 11");
+is(s.sanitize("<body background='http://mochi.test/'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body></body></html>', "Wrong sanitizer result 12");
+is(s.sanitize("<body background='cid:foo'>", u.SanitizerCidEmbedsOnly), '<html><head></head><body background="cid:foo"></body></html>', "Wrong sanitizer result 13");
+is(s.sanitize("<svg></svg>", u.SanitizerCidEmbedsOnly), '<html><head></head><body></body></html>', "Wrong sanitizer result 14");
+is(s.sanitize("<math definitionURL='cid:foo' altimg='cid:foo'></math>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><math></math></body></html>', "Wrong sanitizer result 14");
+is(s.sanitize("<video><source src='http://mochi.test/'></video>", u.SanitizerCidEmbedsOnly), '<html><head></head><body><video controls="controls"><source></video></body></html>', "Wrong sanitizer result 15");
+is(s.sanitize("<style></style>", u.SanitizerAllowStyle | u.SanitizerCidEmbedsOnly), '<html><head></head><body></body></html>', "Wrong sanitizer result 16");
+// Dangerous links
+is(s.sanitize("<a href='javascript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 17");
+is(s.sanitize("<a href='JavaScript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 18");
+is(s.sanitize("<a href=' javascript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 19");
+is(s.sanitize("<a href='\njavascript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 20");
+is(s.sanitize("<a href='\fjavascript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 21");
+is(s.sanitize("<a href='\u00A0javascript:boom()'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 22");
+is(s.sanitize("<a href='foo.html'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 23");
+// Comments
+is(s.sanitize("<!-- foo -->", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 24");
+is(s.sanitize("<!-- foo -->", u.SanitizerAllowComments), "<!-- foo -->\n<html><head></head><body></body></html>", "Wrong sanitizer result 25");
+// noscript
+is(s.sanitize("<body><noscript><p class=bar>foo</p></noscript>", 0), '<html><head></head><body><noscript><p class="bar">foo</p></noscript></body></html>', "Wrong sanitizer result 26");
+// dangerous elements
+is(s.sanitize("<iframe></iframe>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 27");
+is(s.sanitize("<object></object>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 28");
+is(s.sanitize("<embed>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 29");
+// presentationalism
+is(s.sanitize("<font></font>", 0), "<html><head></head><body><font></font></body></html>", "Wrong sanitizer result 30");
+is(s.sanitize("<center></center>", 0), "<html><head></head><body><center></center></body></html>", "Wrong sanitizer result 31");
+is(s.sanitize("<div align=center></div>", 0), '<html><head></head><body><div align="center"></div></body></html>', "Wrong sanitizer result 32");
+is(s.sanitize("<table><tr><td bgcolor=#FFFFFF>", 0), '<html><head></head><body><table><tbody><tr><td bgcolor="#FFFFFF"></td></tr></tbody></table></body></html>', "Wrong sanitizer result 33");
+is(s.sanitize("<font></font>", u.SanitizerDropNonCSSPresentation), "<html><head></head><body></body></html>", "Wrong sanitizer result 34");
+is(s.sanitize("<center></center>", u.SanitizerDropNonCSSPresentation), "<html><head></head><body></body></html>", "Wrong sanitizer result 35");
+is(s.sanitize("<div align=center></div>", u.SanitizerDropNonCSSPresentation), '<html><head></head><body><div></div></body></html>', "Wrong sanitizer result 36");
+is(s.sanitize("<table><tr><td bgcolor=#FFFFFF>", u.SanitizerDropNonCSSPresentation), '<html><head></head><body><table><tbody><tr><td></td></tr></tbody></table></body></html>', "Wrong sanitizer result 37");
+// metadata
+is(s.sanitize("<meta charset=utf-7>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 38");
+is(s.sanitize("<meta http-equiv=content-type content='text/html; charset=utf-7'>", 0), "<html><head></head><body></body></html>", "Wrong sanitizer result 39");
+is(s.sanitize("<meta itemprop=foo content=bar>", 0), '<html><head><meta itemprop="foo" content="bar"></head><body></body></html>', "Wrong sanitizer result 40");
+is(s.sanitize("<link rel=whatever href=http://mochi.test/ >", 0), '<html><head></head><body></body></html>', "Wrong sanitizer result 41");
+is(s.sanitize("<link itemprop=foo href=http://mochi.test/ >", 0), '<html><head><link itemprop="foo" href="http://mochi.test/"></head><body></body></html>', "Wrong sanitizer result 42");
+is(s.sanitize("<link rel=stylesheet itemprop=foo href=http://mochi.test/ >", 0), '<html><head><link itemprop="foo" href="http://mochi.test/"></head><body></body></html>', "Wrong sanitizer result 43");
+is(s.sanitize("<meta name=foo content=bar>", 0), '<html><head><meta name="foo" content="bar"></head><body></body></html>', "Wrong sanitizer result 44");
+// forms
+is(s.sanitize("<form></form>", 0), '<html><head></head><body><form></form></body></html>', "Wrong sanitizer result 45");
+is(s.sanitize("<fieldset><legend></legend></fieldset>", 0), '<html><head></head><body><fieldset><legend></legend></fieldset></body></html>', "Wrong sanitizer result 46");
+is(s.sanitize("<input>", 0), '<html><head></head><body><input></body></html>', "Wrong sanitizer result 47");
+is(s.sanitize("<button>foo</button>", 0), '<html><head></head><body><button>foo</button></body></html>', "Wrong sanitizer result 48");
+is(s.sanitize("<select><optgroup><option>foo</option></optgroup></select></button>", 0), '<html><head></head><body><select><optgroup><option>foo</option></optgroup></select></body></html>', "Wrong sanitizer result 49");
+is(s.sanitize("<form></form>", u.SanitizerDropForms), '<html><head></head><body></body></html>', "Wrong sanitizer result 50");
+is(s.sanitize("<fieldset><legend></legend></fieldset>", u.SanitizerDropForms), '<html><head></head><body><fieldset><legend></legend></fieldset></body></html>', "Wrong sanitizer result 51");
+is(s.sanitize("<input>", u.SanitizerDropForms), '<html><head></head><body></body></html>', "Wrong sanitizer result 52");
+is(s.sanitize("<button>foo</button>", u.SanitizerDropForms), '<html><head></head><body></body></html>', "Wrong sanitizer result 53");
+is(s.sanitize("<select><optgroup><option>foo</option></optgroup></select></button>", u.SanitizerDropForms), '<html><head></head><body></body></html>', "Wrong sanitizer result 54");
+// doctype
+is(s.sanitize("<!DOCTYPE html>", 0), '<!DOCTYPE html>\n<html><head></head><body></body></html>', "Wrong sanitizer result 55");
+// title
+is(s.sanitize("<title></title>", 0), '<html><head><title></title></head><body></body></html>', "Wrong sanitizer result 56");
+// Drop media
+is(s.sanitize("<img>", u.SanitizerDropMedia), '<html><head></head><body></body></html>', "Wrong sanitizer result 57");
+is(s.sanitize("<svg>foo</svg>", u.SanitizerDropMedia), '<html><head></head><body>foo</body></html>', "Wrong sanitizer result 58");
+is(s.sanitize("<video><source></video>", u.SanitizerDropMedia), '<html><head></head><body></body></html>', "Wrong sanitizer result 59");
+is(s.sanitize("<audio><source></audio>", u.SanitizerDropMedia), '<html><head></head><body></body></html>', "Wrong sanitizer result 60");
+// disallow 'formaction' attributes
+is(s.sanitize("<input formaction='http://mochi.test/'>", 0), '<html><head></head><body><input></body></html>', "Wrong sanitizer result 61");
+// disallow 'ping' attributes
+is(s.sanitize("<a ping='http://mochi.test/'></a>", 0), "<html><head></head><body><a></a></body></html>", "Wrong sanitizer result 62");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug650784.html b/dom/base/test/test_bug650784.html
new file mode 100644
index 0000000000..aacc342899
--- /dev/null
+++ b/dom/base/test/test_bug650784.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 650776</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650776">Mozilla Bug 650776</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650776 **/
+
+var c = SpecialPowers.Ci.nsIDocumentEncoder;
+var s = SpecialPowers.ParserUtils;
+
+is(s.convertToPlainText("foo", c.OutputLFLineBreak, 0), "foo", "Wrong conversion result 1");
+is(s.convertToPlainText("foo foo foo", c.OutputWrap | c.OutputLFLineBreak, 7), "foo foo\nfoo", "Wrong conversion result 2");
+is(s.convertToPlainText("<body><noscript>b<span>a</span>r</noscript>foo", c.OutputLFLineBreak, 0), "foo", "Wrong conversion result 3");
+is(s.convertToPlainText("<body><noscript>b<span>a</span>r</noscript>foo", c.OutputNoScriptContent, 0), "barfoo", "Wrong conversion result 4");
+is(s.convertToPlainText("foo\u00A0bar", c.OutputPersistNBSP | c.OutputLFLineBreak, 0), "foo\u00A0bar", "Wrong conversion result 5");
+is(s.convertToPlainText("foo\u00A0bar", c.OutputLFLineBreak, 0), "foo bar", "Wrong conversion result 6");
+is(s.convertToPlainText("<body><noframes>bar</noframes>foo", c.OutputLFLineBreak, 0), "foo", "Wrong conversion result 7");
+// OutputNoFramesContent doesn't actually work, because the flag gets overridden in all cases.
+is(s.convertToPlainText("<body><noframes>bar</noframes>foo", c.OutputNoFramesContent | c.OutputLFLineBreak, 0), "foo", "Wrong conversion result 8");
+is(s.convertToPlainText("<i>foo</i> <b>bar</b>", c.OutputFormatted | c.OutputLFLineBreak, 0), "/foo/ *bar*\n", "Wrong conversion result 9");
+is(s.convertToPlainText("<p>foo</p> <p>bar</p>", c.OutputLFLineBreak, 0), "foo\n\nbar", "Wrong conversion result 10");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug656283.html b/dom/base/test/test_bug656283.html
new file mode 100644
index 0000000000..2b1c792017
--- /dev/null
+++ b/dom/base/test/test_bug656283.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=656283
+-->
+<head>
+ <title>Test for Bug 656283</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=656283">Mozilla Bug 656283</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 656283 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var ifr;
+function test() {
+ var d = document.implementation.createHTMLDocument("");
+ is(d.activeElement, d.body, "Active element should be body by default! (1)");
+
+
+ ifr = document.getElementById("ifr");
+ ifr.onload = test2;
+ ifr.srcdoc = "1";
+}
+
+var firstDoc;
+function test2() {
+ firstDoc = ifr.contentDocument;
+ is(firstDoc.activeElement, firstDoc.body,
+ "Active element should be body by default! (2)");
+ ifr.onload = test3;
+ ifr.srcdoc = "<input>";
+}
+
+function test3() {
+ ifr.contentDocument.getElementsByTagName("input")[0].focus();
+ is(firstDoc.activeElement, firstDoc.body,
+ "Active element should be body by default! (3)");
+ ifr.remove();
+ is(firstDoc.activeElement, firstDoc.body,
+ "Active element should be body by default! (4)");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<iframe id="ifr"></irame>
+</body>
+</html>
diff --git a/dom/base/test/test_bug664916.html b/dom/base/test/test_bug664916.html
new file mode 100644
index 0000000000..7089ee5d81
--- /dev/null
+++ b/dom/base/test/test_bug664916.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664916
+-->
+<head>
+ <title>Test for Bug 664916</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=664916">Mozilla Bug 664916</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+
+/** Test for Bug 664916 **/
+var div = document.createElement("div");
+var textNode = document.createTextNode("x")
+var tagNameGetter = div.__lookupGetter__("tagName");
+
+var tagName = "";
+try {
+ tagName = tagNameGetter.call(textNode);
+ ok(false, "Should throw when calling tagname getter on text node");
+} catch(e) {
+ ok(true, "Should throw when calling tagname getter on text node");
+}
+is(tagName, "", "Should not have changed tagName yet");
+tagName = tagNameGetter.call(div);
+is(tagName, "DIV", "Should get the right tag name");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug666604.html b/dom/base/test/test_bug666604.html
new file mode 100644
index 0000000000..1099db8a79
--- /dev/null
+++ b/dom/base/test/test_bug666604.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666604
+-->
+<head>
+ <title>Test for Bug 666604</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666604">Mozilla Bug 666604</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<a href="javascript:activationListener()" id="testlink">test</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 666604 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function hitEventLoop(times, next)
+{
+ if (times == 0) {
+ next();
+ return;
+ }
+
+ SimpleTest.executeSoon(function() {
+ hitEventLoop(times - 1, next);
+ });
+}
+
+var activationListener;
+
+function dispatchClick(target, ctrl) {
+ var e = document.createEvent("MouseEvent");
+ e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0,
+ ctrl, false, false, false, 0, null);
+ target.dispatchEvent(e);
+}
+
+function dispatchReturn(target, ctrl) {
+ var e = new KeyboardEvent("keypress", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ ctrlKey: ctrl,
+ keyCode: 13,
+ charCode: 0,
+ });
+ target.dispatchEvent(e);
+}
+
+function dispatchDOMActivate(target) {
+ var e = document.createEvent("UIEvent");
+ e.initUIEvent("DOMActivate", true, true, window, 0);
+ target.dispatchEvent(e);
+}
+
+var testlink = document.getElementById("testlink");
+function test1() {
+ activationListener =
+ function() {
+ ok(true, "Untrusted click should activate a link");
+ test2();
+ }
+ dispatchClick(testlink, false);
+}
+
+function test2() {
+ activationListener =
+ function() {
+ ok(true, "Untrusted return keypress should activate a link");
+ test3();
+ }
+ dispatchReturn(testlink, false);
+}
+
+function test3() {
+ activationListener =
+ function() {
+ ok(false, "Untrusted click+ctrl should not activate a link");
+ test4();
+ }
+ dispatchClick(testlink, true);
+ hitEventLoop(10, test4);
+}
+
+function test4() {
+ activationListener =
+ function() {
+ ok(false, "Untrusted return keypress+ctrl should not activate a link");
+ test5();
+ }
+ dispatchReturn(testlink, true);
+ hitEventLoop(10, test5);
+}
+
+function test5() {
+ activationListener =
+ function() {
+ ok(true, "click() should activate a link");
+ test6();
+ }
+ testlink.click();
+}
+
+function test6() {
+ activationListener =
+ function() {
+ ok(true, "Untrusted DOMActivate should activate a link");
+ SpecialPowers.pushPrefEnv({"set":[["dom.disable_open_during_load", false]]}, test7);
+ }
+ dispatchDOMActivate(testlink);
+}
+
+function test7() {
+ testlink.href = "javascript:opener.activationListener(); window.close();";
+ testlink.target = "_blank";
+ testlink.rel = "opener";
+ activationListener =
+ function() {
+ ok(true, "Click() should activate a link");
+ SpecialPowers.pushPrefEnv({"set":[["dom.disable_open_during_load", true]]}, test8);
+ }
+ testlink.click();
+}
+
+function test8() {
+ testlink.href = "javascript:opener.activationListener(); window.close();";
+ testlink.target = "_blank";
+ testlink.rel = "opener";
+ activationListener =
+ function() {
+ ok(false, "Click() should not activate a link");
+ }
+ testlink.click();
+ SimpleTest.executeSoon(SimpleTest.finish);
+}
+addLoadEvent(test1);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug675121.html b/dom/base/test/test_bug675121.html
new file mode 100644
index 0000000000..d0d15b1f6e
--- /dev/null
+++ b/dom/base/test/test_bug675121.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=675121
+-->
+<head>
+ <title>Test for Bug 675121</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=675121">Mozilla Bug 675121</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 675121 **/
+var callbackFired = false;
+var xhrInProgress = false;
+function f() {
+ callbackFired = true;
+ if (!xhrInProgress) {
+ SimpleTest.finish();
+ }
+}
+
+window.requestAnimationFrame(f);
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "file_bug675121.sjs", false);
+xhrInProgress = true;
+xhr.send();
+xhrInProgress = false;
+is(xhr.responseText, "Responded", "Should have a response by now");
+is(callbackFired, false, "Callback should not fire during sync XHR");
+
+if (!callbackFired) {
+ SimpleTest.waitForExplicitFinish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug675166.html b/dom/base/test/test_bug675166.html
new file mode 100644
index 0000000000..9543eb4e1f
--- /dev/null
+++ b/dom/base/test/test_bug675166.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=675166
+-->
+<head>
+ <title>Test for Bug 675166</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=675166">Mozilla Bug 675166</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 675166 **/
+
+
+var dt = document.implementation.createDocumentType("html", null, null);
+isnot(dt.ownerDocument, null, "DocumentType should have ownerDocument");
+
+var d = document.implementation.createDocument(null, null, dt);
+is(dt.ownerDocument, d, "DocumentType shouldn't have null ownerDocument");
+
+try {
+ document.implementation.createDocument(null, null, dt);
+ ok(true, "Creating document using already bound document type shouldn't throw!");
+} catch(ex) {
+ ok(false, "Creating document using already bound document type shouldn't throw!");
+}
+
+var d2 = document.implementation.createDocument(null, null, null);
+var dt2 = document.implementation.createDocumentType("html", null, null);
+d2.appendChild(dt2);
+is(dt2.ownerDocument, d2, "DocumentType shouldn't have null ownerDocument");
+
+is(document.ownerDocument, null, "Document's ownerDocument should be null!");
+is(document.documentElement.ownerDocument, document,
+ "Element should have ownerDocument!")
+
+is(dt2.parentNode, d2, "parentNode should be document!");
+d2.removeChild(dt2);
+is(dt2.parentNode, null, "parentNode should be null!");
+
+d.adoptNode(dt2);
+d2.adoptNode(dt2);
+d2.appendChild(dt2);
+is(dt2.parentNode, d2, "parentNode should be document!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug682463.html b/dom/base/test/test_bug682463.html
new file mode 100644
index 0000000000..91b06b7407
--- /dev/null
+++ b/dom/base/test/test_bug682463.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682463
+-->
+<head>
+ <title>Test for Bug 682463</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=682463">Mozilla Bug 682463</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 682463 **/
+
+ function text(s) {
+ return document.createTextNode(s);
+ }
+ function div(l,r) {
+ var d = document.createElement("DIV");
+ if (l) d.appendChild(l);
+ if (r) d.appendChild(r);
+ return d;
+ }
+ function createRange(sn,so,en,eo) {
+ var r = document.createRange();
+ r.setStart(sn,so);
+ r.setEnd(en,eo);
+ return r;
+ }
+ function verifyRange(msg,r,sn,so,en,eo) {
+ ok(r.startContainer == sn && r.startOffset == so &&
+ r.endContainer == en && r.endOffset == eo, msg);
+ }
+ function showRange(r,msg) {
+ var s = "" + r.startContainer + ": " + r.startOffset;
+ s+= '\n';
+ s += "" + r.endContainer + ": " + r.endOffset;
+ alert(msg + ':\n' + s)
+ }
+
+ var tests = [
+ function() {
+ var t = text("foobar");
+ var r = createRange(t,2,t,t.length);
+ var t2 = t.splitText(1);
+ verifyRange("split before, no parent",r,t2,1,t2,5);
+ },
+ function() {
+ var t = text("foobar");
+ var r = createRange(t,0,t,t.length);
+ var t2 = t.splitText(3);
+ verifyRange("split middle, no parent",r,t,0,t,3);
+ },
+ function() {
+ var t = text("foobar");
+ var r = createRange(t,0,t,t.length);
+ var n = t.length;
+ var t2 = t.splitText(n);
+ verifyRange("split after, no parent",r,t,0,t,n);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ var r = createRange(t,0,t,t.length);
+ var t2 = t.splitText(3);
+ verifyRange("split middle, parent",r,t,0,t2,3);
+ parent.removeChild(t);
+ verifyRange("removed left, parent",r,parent,0,t2,3);
+ var t2b = t2.splitText(1);
+ verifyRange("split middle, parent, end",r,parent,0,t2b,2);
+ },
+ function() {
+ var t0 = text("x");
+ var t = text("foobar");
+ var parent = div(t0,t);
+ var r = createRange(t,0,t,t.length);
+ var t2 = t.splitText(3);
+ parent.removeChild(t);
+ verifyRange("removed left, text sibling",r,parent,1,t2,3);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ var r = createRange(t,2,t,t.length);
+ var t2 = t.splitText(1);
+ verifyRange("split before, parent",r,t2,1,t2,5);
+ parent.removeChild(t2);
+ verifyRange("removed right, parent",r,parent,1,parent,1);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ var r = createRange(t,0,t,t.length);
+ var n = t.length;
+ var t2 = t.splitText(n);
+ verifyRange("split after, parent",r,t,0,t,n);
+ r.setEnd(t2,0);
+ verifyRange("split after, parent, extend",r,t,0,t2,0);
+ t2.splitText(0);
+ verifyRange("split after, parent, extend, split end",r,t,0,t2,0);
+ t2.textContent = "baz";
+ t2.splitText(2);
+ verifyRange("split after, parent, extend, split after end",r,t,0,t2,0);
+ r.setEnd(t2,2);
+ var t2b = t2.splitText(1);
+ verifyRange("split after, parent, split end",r,t,0,t2b,1);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ document.body.appendChild(parent);
+ var r = createRange(t,0,t,t.length);
+ var t2 = t.splitText(3);
+ verifyRange("split middle, in document",r,t,0,t2,3);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ document.body.appendChild(parent);
+ var r = createRange(t,2,t,t.length);
+ var t2 = t.splitText(1);
+ verifyRange("split before, in document",r,t2,1,t2,5);
+ },
+ function() {
+ var t = text("foobar");
+ var parent = div(t);
+ document.body.appendChild(parent);
+ var r = createRange(t,0,t,t.length);
+ var n = t.length;
+ var t2 = t.splitText(n);
+ verifyRange("split after, in document",r,t,0,t,n);
+ }
+ ];
+
+ function runTests() {
+ var len = tests.length;
+ for (var i = 0; i < len; ++i) {
+ tests[i]();
+ }
+ SimpleTest.finish();
+ }
+
+addLoadEvent(runTests);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug682554.html b/dom/base/test/test_bug682554.html
new file mode 100644
index 0000000000..4ad9e61e5e
--- /dev/null
+++ b/dom/base/test/test_bug682554.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682554
+-->
+<head>
+ <title>Test for Bug 682554</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=682554">Mozilla Bug 682554</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 682554 **/
+is("onreadystatechange" in window, false,
+ "Window should not have an onreadystatechange");
+is("onreadystatechange" in document, true,
+ "Document should have an onreadystatechange");
+is("onreadystatechange" in document.createElement("script"), false,
+ "<script> element should not have an onreadystatechange");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug682592.html b/dom/base/test/test_bug682592.html
new file mode 100644
index 0000000000..c0271e2ee1
--- /dev/null
+++ b/dom/base/test/test_bug682592.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682592
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
+ <title>Test for bug 682592</title>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+<iframe id="iframe-ref"></iframe>
+<iframe id="iframe-test"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 682592 **/
+
+/*
+ We want to check that bidi is detected correctly. So, we have a reference
+ document where ltr is set explicitely with <bdo> element. Then, we compare
+ it with test document.
+
+ In mozilla, once bidi has been detected in a document, document always
+ consider it's in bidi mode. So, if one fragment enables bidi correctly, and
+ we create or update a fragment in the same document, that operation may not
+ enable bidi by itself, but it will not be detected. So, we need to have new
+ document for each test.
+
+ So, instead of many diferent reftests, this mochitest implements a
+ reftest-like. It creates reference text fragments in reference iframe, test
+ text fragments in test iframe, and compare the documents. Then, it reloads
+ test iframe. Reference iframe does not need to be reloaded between tests.
+ It's ok (and maybe, desired) to keep bidi always enabled in that document.
+*/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+if (navigator.platform.startsWith("Linux arm")) { /* bugs 982875, 999429 */
+ SimpleTest.expectAssertions(0, 4);
+}
+
+var page = `<!DOCTYPE html>
+<html><meta charset='UTF-8'><body><p id="content"></p></body></html>`;
+var refFrame = document.getElementById("iframe-ref")
+var testFrame = document.getElementById("iframe-test");
+
+refFrame.addEventListener("load", function() {
+ testFrame.addEventListener("load", async function() {
+ let {done} = tests.next();
+ if (!done) {
+ ok(compareSnapshots(await snapshotWindow(testFrame.contentWindow),
+ await snapshotWindow(refFrame.contentWindow), true)[0],
+ "bidi is not detected correctly");
+
+ testFrame.contentWindow.location.reload();
+ } else {
+ SimpleTest.finish();
+ }
+ });
+ testFrame.srcdoc = page;
+});
+refFrame.srcdoc = page;
+
+var rtl = "עִבְרִית";
+var non8bit = "ʃ";
+var is8bit = "a";
+
+// concats aStr aNumber of times
+function strMult(aStr, aNumber) {
+ if (aNumber === 0) {
+ return "";
+ }
+ return strMult(aStr, aNumber - 1) + aStr;
+}
+
+function* runTests () {
+ var ltr = "", prefix = null;
+ var refContainer = refFrame.contentDocument.getElementById('content');
+ var testContainer, textNode;
+ var i = 0;
+
+ // 8bit chars + bidi
+ for (i = 0; i <= 16; i++) {
+ ltr = strMult(is8bit, i);
+ refContainer.innerHTML = ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ testContainer.innerHTML = ltr + rtl;
+ yield undefined;
+ }
+
+ // non-8bit char + 8bit chars + bidi
+ for (i = 0; i <= 16; i++) {
+ ltr = non8bit + strMult(is8bit, i);
+ refContainer.innerHTML = ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ testContainer.innerHTML = ltr + rtl;
+ yield undefined;
+ }
+
+ // appendData
+ for (i = 0; i <= 16; i++) {
+ ltr = strMult(is8bit, i);
+ refContainer.innerHTML = ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode("");
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+
+ for (i = 0; i <= 16; i++) {
+ ltr = non8bit + strMult(is8bit, i);
+ refContainer.innerHTML = ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode("");
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+
+ // appendData with 8bit prefix
+ for (i = 0; i <= 16; i++) {
+ prefix = is8bit;
+ ltr = strMult(is8bit, i);
+ refContainer.innerHTML = prefix + ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode(prefix);
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+
+ for (i = 0; i <= 16; i++) {
+ prefix = is8bit;
+ ltr = non8bit + strMult(is8bit, i);
+ refContainer.innerHTML = prefix + ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode(prefix);
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+
+ // appendData with non-8bit prefix
+ for (i = 0; i <= 16; i++) {
+ prefix = non8bit;
+ ltr = strMult(is8bit, i);
+ refContainer.innerHTML = prefix + ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode(prefix);
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+
+ for (i = 0; i <= 16; i++) {
+ prefix = non8bit;
+ ltr = non8bit + strMult(is8bit, i);
+ refContainer.innerHTML = prefix + ltr + '<bdo dir="rtl">' + rtl + '</bdo>';
+ testContainer = testFrame.contentDocument.getElementById('content');
+ textNode = document.createTextNode(prefix);
+ testContainer.appendChild(textNode);
+ textNode.appendData(ltr + rtl);
+ yield undefined;
+ }
+};
+
+var tests = runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug684671.html b/dom/base/test/test_bug684671.html
new file mode 100644
index 0000000000..996585d99d
--- /dev/null
+++ b/dom/base/test/test_bug684671.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684671
+-->
+<head>
+ <title>Test for Bug 684671</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=684671">Mozilla Bug 684671</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 684671 **/
+function f() {}
+document.onreadystatechange = f;
+is(document.onreadystatechange, f,
+ "Document onreadystatechange should be settable");
+
+window.onreadystatechange = f;
+is(window.onreadystatechange, f,
+ "Window onreadystatechange should be settable");
+
+document.documentElement.onreadystatechange = f;
+is(document.documentElement.onreadystatechange, f,
+ "Element onreadystatechange should be settable");
+
+var canSetReadyStateChange;
+try {
+ XMLDocument.prototype.onreadystatechange = null;
+ canSetReadyStateChange = true;
+} catch (e) {
+ canSetReadyStateChange = false;
+}
+ok(canSetReadyStateChange, "This may break web pages");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug685798.html b/dom/base/test/test_bug685798.html
new file mode 100644
index 0000000000..25fb276ec1
--- /dev/null
+++ b/dom/base/test/test_bug685798.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=685798
+-->
+<head>
+ <title>Test for Bug 685798</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685798">Mozilla Bug 685798</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 685798 **/
+
+
+is(document.parentElement, null,
+ "Document shouldn't have parentElement.");
+is(document.documentElement.parentElement, null,
+ "DocumentElement shouldn't have parentElement.");
+is(document.documentElement.firstChild.parentElement, document.documentElement,
+ "DocumentElement's child should have DocumentElement as parent.");
+
+var df = document.createRange().createContextualFragment("<div>foo</div>");
+is(df.parentElement, null,
+ "DocumentFragment should be null.");
+is(df.firstChild.parentElement, null,
+ "DocumentFragment's child shouldn't have parentElement");
+is(df.firstChild.firstChild.parentElement, df.firstChild,
+ "Text node's parent should be element.");
+
+is(document.createTextNode("foo").parentElement, null,
+ "Text node shouldn't have parent element.");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug686449.xhtml b/dom/base/test/test_bug686449.xhtml
new file mode 100644
index 0000000000..4f639154da
--- /dev/null
+++ b/dom/base/test/test_bug686449.xhtml
@@ -0,0 +1,79 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=686449
+-->
+<head>
+ <title>Test for Bug 686449</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=686449">Mozilla Bug 686449</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="rangetest">abcd<div id="picontainer1"><?pi efgh?></div><div>ijkl</div><div id="picontainer2"><?pi mnop?></div>qrst</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 686449 **/
+
+var pi = document.createProcessingInstruction("t", "data");
+ok("target" in pi, "No target?");
+ok("data" in pi, "No data?");
+ok("length" in pi, "No length?");
+ok("substringData" in pi, "No substringData?");
+ok("appendData" in pi, "No appendData?");
+ok("insertData" in pi, "No insertData?");
+ok("deleteData" in pi, "No deleteData?");
+ok("replaceData" in pi, "No replaceData?");
+
+is(pi.substringData(0, pi.length), pi.data, "wrong data?");
+var olddata = pi.data;
+var appenddata = "foo"
+pi.appendData(appenddata);
+is(pi.data, olddata + appenddata, "appendData doesn't work?");
+pi.deleteData(olddata.length, appenddata.length);
+is(pi.data, olddata, "deleteData doesn't work?");
+pi.replaceData(0, 0, olddata);
+is(pi.data, olddata + olddata, "replaceData doesn't work?");
+pi.insertData(0, olddata);
+is(pi.data, olddata + olddata + olddata, "insertData doesn't work?");
+pi.data = olddata;
+is(pi.data, olddata, "setting data doesn't work?");
+
+var r = document.createRange();
+r.selectNodeContents(pi);
+is(r.startContainer, pi, "Wrong startContainer!");
+is(r.startOffset, 0, "Wrong startOffset!");
+is(r.endContainer, pi, "Wrong endContainer!");
+is(r.endOffset, pi.length, "Wrong endOffset!");
+
+var df = r.cloneContents();
+is(df.childNodes.length, 1, "Unexpected child nodes?");
+ok(df.firstChild.isEqualNode(pi), "Wrong cloning?");
+
+r.setStart(pi, 1);
+r.setEnd(pi, 3);
+df = r.cloneContents();
+is(df.childNodes.length, 1, "Unexpected child nodes?");
+ok(!df.firstChild.isEqualNode(pi), "Should clone to similar pi!");
+is(df.firstChild.data, "at", "Wrong data cloning?");
+
+r.selectNode(document.getElementById("rangetest"));
+is(r.toString(), document.getElementById("rangetest").textContent,
+ "Wrong range stringification!");
+ok(r.cloneContents().firstChild.firstChild.nextSibling.firstChild.
+ isEqualNode(document.getElementById("picontainer1").firstChild),
+ "Wrong pi cloning!");
+ok(r.cloneContents().firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.
+ isEqualNode(document.getElementById("picontainer2").firstChild),
+ "Wrong pi cloning!");
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug687859.html b/dom/base/test/test_bug687859.html
new file mode 100644
index 0000000000..455a14a9b2
--- /dev/null
+++ b/dom/base/test/test_bug687859.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=687859
+-->
+<head>
+ <meta charset="windows-1251">
+ <title>Test for Bug 687859</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=687859">Mozilla Bug 687859</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script charset="ISO-8859-2" src="file_bug687859-bom.js"></script>
+<script charset="ISO-8859-2" src="file_bug687859-16.js"></script>
+<script charset="ISO-8859-2" src="file_bug687859-http.js"></script>
+<script charset="ISO-8859-2" src="file_bug687859-charset.js"></script>
+<script src="file_bug687859-inherit.js"></script>
+<script type="application/javascript">
+is(stringFromBomScript, "\u20AC", "Bogus encoding for UTF-8 BOM case.");
+is(stringFrom16Script, "\u20AC", "Bogus encoding for UTF-16 BOM case.");
+is(stringFromHttpScript, "\u0E44", "Bogus encoding for HTTP case.");
+is(stringFromCharsetScript, "\u0104", "Bogus encoding for charset case.");
+is(stringFromInheritScript, "\u040E", "Bogus encoding for inherit case.");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug690056.html b/dom/base/test/test_bug690056.html
new file mode 100644
index 0000000000..cfe483bfe1
--- /dev/null
+++ b/dom/base/test/test_bug690056.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690056
+-->
+<head>
+ <title>Test for Bug 690056</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056">Mozilla Bug 690056</a>
+<p id="display">
+ <iframe id="x"></iframe>
+ <iframe style="display: none" id="y"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 690056 **/
+SimpleTest.waitForExplicitFinish();
+is(document.hidden, false, "Document should not be hidden during load");
+is(document.visibilityState, "visible",
+ "Document should be visible during load");
+
+addLoadEvent(function() {
+ var doc = document.implementation.createDocument("", "", null);
+ is(doc.hidden, true, "Data documents should be hidden");
+ is(doc.visibilityState, "hidden", "Data documents really should be hidden");
+
+ is(document.hidden, false, "Document should not be hidden onload");
+ is(document.visibilityState, "visible",
+ "Document should be visible onload");
+
+ is($("x").contentDocument.hidden, false,
+ "Subframe document should not be hidden onload");
+ is($("x").contentDocument.visibilityState, "visible",
+ "Subframe document should be visible onload");
+ is($("y").contentDocument.hidden, false,
+ "display:none subframe document should not be hidden onload");
+ is($("y").contentDocument.visibilityState, "visible",
+ "display:none subframe document should be visible onload");
+
+ SimpleTest.finish();
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug692434.html b/dom/base/test/test_bug692434.html
new file mode 100644
index 0000000000..eeb659ee3a
--- /dev/null
+++ b/dom/base/test/test_bug692434.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=692434
+-->
+<head>
+ <title>Test for Bug 692434</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload=runTest();>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=692434">Mozilla Bug 692434</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 692434 **/
+SimpleTest.waitForExplicitFinish();
+
+var xhr = new XMLHttpRequest();
+
+function runTest() {
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ ok(this.responseXML, "Should have gotten responseXML");
+ is(this.responseXML.characterSet, "windows-1251", "Wrong character encoding");
+ is(this.responseXML.documentElement.firstChild.data, "\u042E", "Decoded using the wrong encoding.");
+ is(this.responseText.indexOf("\u042E"), 51, "Bad responseText");
+ SimpleTest.finish();
+ }
+ }
+ xhr.open("GET", "file_bug692434.xml");
+ xhr.send();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug693615.html b/dom/base/test/test_bug693615.html
new file mode 100644
index 0000000000..2be7311d5e
--- /dev/null
+++ b/dom/base/test/test_bug693615.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=693615
+-->
+<head>
+ <title>Test for Bug 693615</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=693615">Mozilla Bug 693615</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 693615 **/
+/*
+The following code tests if calling the DOM method Node::lookupNamespaceURI
+directly (with quickstubs) and through XPCOM leads to the same result.
+*/
+
+var content = document.getElementById("content");
+
+// called directly (quickstubs)
+var defaultNS = content.lookupNamespaceURI(null);
+is(defaultNS, null, "direct access working");
+
+// called via XPCOM
+// deleting the method from the prototype forces the engine to go through XPCOM
+var proto = Object.getPrototypeOf(content);
+delete(proto.lookupNamespaceURI);
+var wrapperNS = content.lookupNamespaceURI(null);
+is(wrapperNS, null, "access through XPCOM working");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug693875.html b/dom/base/test/test_bug693875.html
new file mode 100644
index 0000000000..33c6648e0d
--- /dev/null
+++ b/dom/base/test/test_bug693875.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=693875
+-->
+<head>
+ <title>Test for Bug 693875</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=693875">Mozilla Bug 693875</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 693875 **/
+
+var dp = new DOMParser();
+var d = dp.parseFromString("<?xml version='1.0'?><svg xmlns='http://www.w3.org/2000/svg'></svg>",
+ "image/svg+xml");
+
+ok(d instanceof XMLDocument, "Should have created an XML document.");
+ok("documentElement" in d, "Should have created an XML document, which has .documentElement.");
+is(d.documentElement.localName, "svg", "Root element should be svg.");
+is(d.documentElement.namespaceURI, "http://www.w3.org/2000/svg",
+ "Root element should be in svg namespace.");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug694754.xhtml b/dom/base/test/test_bug694754.xhtml
new file mode 100644
index 0000000000..a31c0a910c
--- /dev/null
+++ b/dom/base/test/test_bug694754.xhtml
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:test="http://example.com/test">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=694754
+-->
+<head>
+ <title>Test for Bug 694754</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=694754">Mozilla Bug 694754</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 694754 **/
+/*
+The following code tests if calling the DOM methods Document::lookupNamespaceURI
+and Document::lookupPrefix directly (with quickstubs) and through XPCOM leads
+to the same result.
+
+This test makes use of the bug/feature that deleting a method from the
+prototype forces the engine to go through XPCOM.
+*/
+
+// Document::lookupPrefix called directly (quickstubs)
+var prefixDirect = document.lookupPrefix("http://example.com/test");
+is(prefixDirect, "test",
+ "calling Document::lookupPrefix through quickstubs works");
+
+// Document::lookupPrefix called via XPCOM
+var proto = Object.getPrototypeOf(document);
+delete(proto.lookupPrefix);
+var prefixThroughXPCOM = document.lookupPrefix("http://example.com/test");
+is(prefixThroughXPCOM, "test",
+ "calling Document::lookupPrefix through XPCOM works");
+
+
+
+// Document::lookupNamespaceURI called directly (quickstubs)
+var namespaceDirect = document.lookupNamespaceURI(null);
+is(namespaceDirect, "http://www.w3.org/1999/xhtml",
+ "calling Document::lookupNamespaceURI through quickstubs works");
+
+// Document::lookupNamespaceURI called via XPCOM
+delete(proto.lookupNamespaceURI);
+var namespaceThroughXPCOM = document.lookupNamespaceURI(null);
+is(namespaceThroughXPCOM, "http://www.w3.org/1999/xhtml",
+ "calling Document::lookupNamespaceURI through XPCOM works");
+
+// Document::isDefaultNamespace called directly (quickstubs)
+var isDefaultNamespaceDirect = document.isDefaultNamespace("http://www.w3.org/1999/xhtml");
+is(isDefaultNamespaceDirect, true,
+ "Default namespace correctly detected through quickstubs");
+
+// Document::isDefaultNamespace called via XPCOM
+delete(proto.isDefaultNamespace);
+var isDefaultNamespaceXPCOM = document.isDefaultNamespace("http://www.w3.org/1999/xhtml");
+is(isDefaultNamespaceXPCOM, true,
+ "Default namespace correctly detected through XPCOM");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug696301-1.html b/dom/base/test/test_bug696301-1.html
new file mode 100644
index 0000000000..b3202c85d6
--- /dev/null
+++ b/dom/base/test/test_bug696301-1.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696301
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 696301</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696301">Mozilla Bug 696301</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var errorFired = false;
+var global = "";
+window.onerror = function(message, uri, line) {
+ is(message, "Script error.", "Should have empty error message");
+ is(uri,
+ "http://example.com/tests/dom/base/test/bug696301-script-1.js",
+ "Should have correct script URI");
+ is(line, 0, "Shouldn't have a line here");
+ errorFired = true;
+}
+</script>
+<script src="http://example.com/tests/dom/base/test/bug696301-script-1.js"></script>
+<script>
+ is(errorFired, true, "Should have error in different origin script");
+ is(global, "ran", "Different origin script should have run");
+</script>
+
+<script type="application/javascript">
+errorFired = false;
+global = "";
+window.onerror = function(message, uri, line) {
+ is(message, "ReferenceError: c is not defined", "Should have correct error message");
+ is(uri,
+ "http://example.com/tests/dom/base/test/bug696301-script-1.js",
+ "Should also have correct script URI");
+ is(line, 3, "Should have a line here");
+ errorFired = true;
+}
+</script>
+<script src="http://example.com/tests/dom/base/test/bug696301-script-1.js"
+ crossorigin></script>
+<script>
+ is(errorFired, true, "Should have error in different origin script with CORS");
+ is(global, "ran", "Different origin script with CORS should have run");
+</script>
+
+<script type="application/javascript">
+errorFired = false;
+global = "";
+window.addEventListener("error", function(e) {
+ is(Object.getPrototypeOf(e), Event.prototype,
+ "Object prototype should be Event");
+ var externalScripts = document.querySelectorAll("script[src]");
+ is(e.target, externalScripts[externalScripts.length - 1],
+ "Event's target should be the right <script>");
+ errorFired = true;
+}, true);
+</script>
+<script src="http://example.com/tests/dom/base/test/bug696301-script-2.js"
+ crossorigin></script>
+<script>
+ is(errorFired, true,
+ "Should have error when different origin script fails CORS check");
+ is(global, "", "Different origin script that fails CORS should not have run");
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug696301-2.html b/dom/base/test/test_bug696301-2.html
new file mode 100644
index 0000000000..4204ae269d
--- /dev/null
+++ b/dom/base/test/test_bug696301-2.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696301
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 696301</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696301">Mozilla Bug 696301</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<!-- Test SVG script here -->
+<svg>
+<script type="application/javascript">
+var errorFired = false;
+var global = "";
+window.onerror = function(message, uri, line) {
+ is(message, "Script error.", "Should have empty error message");
+ is(uri,
+ "http://example.com/tests/dom/base/test/bug696301-script-1.js",
+ "Should have correct script URI");
+ is(line, 0, "Shouldn't have a line here");
+ errorFired = true;
+}
+</script>
+<script xlink:href="http://example.com/tests/dom/base/test/bug696301-script-1.js"></script>
+<script>
+ is(errorFired, true, "Should have error in different origin script");
+ is(global, "ran", "Different origin script should have run");
+</script>
+
+<script type="application/javascript">
+errorFired = false;
+global = "";
+window.onerror = function(message, uri, line) {
+ is(message, "ReferenceError: c is not defined", "Should have correct error message");
+ is(uri,
+ "http://example.com/tests/dom/base/test/bug696301-script-1.js",
+ "Should also have correct script URI");
+ is(line, 3, "Should have a line here");
+ errorFired = true;
+}
+</script>
+<script xlink:href="http://example.com/tests/dom/base/test/bug696301-script-1.js"
+ crossorigin></script>
+<script>
+ is(errorFired, true, "Should have error in different origin script with CORS");
+ is(global, "ran", "Different origin script with CORS should have run");
+</script>
+
+<script type="application/javascript">
+errorFired = false;
+global = "";
+window.addEventListener("error", function(e) {
+ is(Object.getPrototypeOf(e), Event.prototype,
+ "Object prototype should be Event");
+ var scripts = document.querySelectorAll("script");
+ is(e.target, scripts[scripts.length - 1],
+ "Event's target should be the right &lt;script>");
+ errorFired = true;
+}, true);
+</script>
+<script xlink:href="http://example.com/tests/dom/base/test/bug696301-script-2.js"
+ crossorigin></script>
+<script>
+ is(errorFired, true,
+ "Should have error when different origin script fails CORS check");
+ is(global, "", "Different origin script that fails CORS should not have run");
+</script>
+</svg>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug698381.html b/dom/base/test/test_bug698381.html
new file mode 100644
index 0000000000..6fe84a1da2
--- /dev/null
+++ b/dom/base/test/test_bug698381.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=698381
+ -->
+ <head>
+ <title>Test for Bug 698381</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"
+ type="text/javascript"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+ </head>
+ <body onload="runTests();">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=698381">
+ Mozilla Bug 698381</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <div id="noChildren" style="display: none"></div>
+ <div id="hasChildren" style="display: none">
+ <div id="childOne" style="display: none"></div>
+ </div>
+ <pre id="test">
+ <script type="text/javascript">
+ /*
+ Checks to see if default parameter handling is correct when 0
+ parameters are passed.
+
+ If none are passed, then Node.cloneNode should default aDeep
+ to true.
+ */
+ SimpleTest.waitForExplicitFinish();
+
+ var hasChildren = document.getElementById("hasChildren"),
+ noChildren = document.getElementById("noChildren"),
+ clonedNode;
+
+ function runTests() {
+
+ // Test Node.cloneNode when no arguments are given
+ clonedNode = hasChildren.cloneNode();
+ is(clonedNode.hasChildNodes(), false, "Node.cloneNode with false " +
+ "default on a node with children does not clone the child nodes.");
+
+ clonedNode = noChildren.cloneNode();
+ is(clonedNode.hasChildNodes(), false, "Node.cloneNode with false " +
+ "default on a node without children doesn't clone child nodes." );
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug698384.html b/dom/base/test/test_bug698384.html
new file mode 100644
index 0000000000..c2c4663b35
--- /dev/null
+++ b/dom/base/test/test_bug698384.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=698384
+ -->
+ <head>
+ <title>Test for Bug 698384</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"
+ type="text/javascript"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+ </head>
+ <body onload="runTests();">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=698384">
+ Mozilla Bug 698384</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <script type="text/javascript">
+ /*
+ Checks to see if default parameter handling is correct when 0, 1
+ or 2 parameters are passed.
+
+ If one is only passed, aFilter should default to null
+ If none are passed, aFilter should be null and aWhatToShow should
+ be NodeFilter.SHOW_ALL
+ */
+ SimpleTest.waitForExplicitFinish();
+
+ var content = $('content'),
+ ni;
+
+ content.innerHTML = ('<span id="A"><\/span><span id="B"><\/span>'
+ + '<span id="C"><\/span>');
+
+ function runTests() {
+
+ // Test NodeIterator when no optional arguments are given
+ ni = document.createNodeIterator(content);
+ is(ni.whatToShow, NodeFilter.SHOW_ALL, "whatToShow should be " +
+ "NodeFilter.SHOW_ALL when both " +
+ " optionals are not given");
+ is(ni.filter, null, "filter should be defaulted to null when both " +
+ " optionals are not given");
+
+ // Test NodeIterator when first optional is passed
+ ni = document.createNodeIterator(content, NodeFilter.SHOW_ELEMENT);
+ is(ni.filter, null, "filter should be defaulted to null when only " +
+ " first argument is passed");
+ is(ni.whatToShow, NodeFilter.SHOW_ELEMENT, "whatToShow should " +
+ "properly be set to NodeFilter.SHOW_ELEMENT when whatToShow is " +
+ "provided and filter is not");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug704063.html b/dom/base/test/test_bug704063.html
new file mode 100644
index 0000000000..13d26d0a38
--- /dev/null
+++ b/dom/base/test/test_bug704063.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug **/
+ SimpleTest.waitForExplicitFinish();
+ var firstRan = false;
+ var secondRan = false;
+ function second(time) {
+ is(firstRan, true, "We were called second");
+ secondRan = true;
+ ok(Math.abs(time - performance.now()) < 3600000,
+ "An hour really shouldn't have passed here");
+ ok(Math.abs(time - Date.now()) > 3600000,
+ "More than an hour should have passed since 1970");
+ }
+ function first(time) {
+ is(secondRan, false, "second() was called first");
+ firstRan = true;
+ ok(Math.abs(time - performance.now()) < 3600000,
+ "An hour really shouldn't have passed here either");
+ ok(Math.abs(time - Date.now()) > 3600000,
+ "More than an hour should have passed since 1970 here either");
+ }
+ function third() {
+ ok(firstRan, "We should be after the first call");
+ ok(secondRan, "We should be after the second call");
+ SimpleTest.finish();
+ }
+
+ window.onload = function() {
+ window.requestAnimationFrame(first);
+ window.requestAnimationFrame(second);
+ window.requestAnimationFrame(third);
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug704320-1.html b/dom/base/test/test_bug704320-1.html
new file mode 100644
index 0000000000..d20e6c0e1d
--- /dev/null
+++ b/dom/base/test/test_bug704320-1.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+This Test is split into two for Bug 1453396
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 704320-Part1</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+//generates URLs to test
+var generateURLArray = (function(from, to){
+ const baseURL = '://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=';
+ const schemeTo = '&scheme-to=';
+
+ return [
+ from + baseURL + from + schemeTo + to + '&policy=no-referrer-when-downgrade',
+ from + baseURL + from + schemeTo + to + '&policy=no-referrer',
+ from + baseURL + from + schemeTo + to + '&policy=unsafe-url',
+ from + baseURL + from + schemeTo + to + '&policy=origin',
+ from + baseURL + from + schemeTo + to + '&policy=origin-when-cross-origin',
+ from + baseURL + from + schemeTo + to + '&policy=same-origin',
+ from + baseURL + from + schemeTo + to + '&policy=strict-origin',
+ from + baseURL + from + schemeTo + to + '&policy=strict-origin-when-cross-origin',
+ ];
+});
+
+let testIframeUrls = [generateURLArray('http', 'http'),
+ generateURLArray('https', 'https')];
+
+SimpleTest.waitForExplicitFinish();
+let advance = function(testName) {
+ testsGenerator[testName].next();
+};
+
+let testNames = ['testframeone', 'testframetwo'];
+let isTestFinished = 0;
+
+function checkTestsCompleted() {
+ isTestFinished++;
+ if (isTestFinished == 2) {
+ SimpleTest.finish();
+ }
+}
+let testsGenerator = {};
+SimpleTest.requestLongerTimeout(4);
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It performs all tests in sequence using four iframes.
+ */
+function startTests(testName, iframeUrls) {
+ testsGenerator[testName] = (function*() {
+ var testframe = document.getElementById(testName);
+ testframe.onload = function() {
+ advance(testName);
+ }
+
+ // load the test frame from iframeUrls[url]
+ // it will call back into this function via postMessage when it finishes
+ // loading and continue beyond the yield.
+ for(url in iframeUrls) {
+ yield testframe.src = iframeUrls[url];
+ // run test and check result for loaded test URL
+ yield checkExpectedGlobalResults(testName);
+ }
+ checkTestsCompleted();
+ })();
+}
+
+for (i = 0; i < testIframeUrls.length; i++) {
+ startTests(testNames[i], testIframeUrls[i]);
+}
+
+</script>
+</head>
+
+<body onload="testsGenerator[testNames[0]].next();
+ testsGenerator[testNames[1]].next();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP/HTTPS to HTTPS/HTTP</a>
+<p id="display"></p>
+<pre id="content">
+</pre>
+ <iframe id="testframeone"></iframe>
+ <iframe id="testframetwo"></iframe>
+</body>
diff --git a/dom/base/test/test_bug704320-2.html b/dom/base/test/test_bug704320-2.html
new file mode 100644
index 0000000000..3c02e1d8d7
--- /dev/null
+++ b/dom/base/test/test_bug704320-2.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+This Test is split into two for Bug 1453396
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 704320-Part2</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+//generates URLs to test
+var generateURLArray = (function(from, to){
+ const baseURL = '://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=';
+ const schemeTo = '&scheme-to=';
+
+ return [
+ from + baseURL + from + schemeTo + to + '&policy=no-referrer-when-downgrade',
+ from + baseURL + from + schemeTo + to + '&policy=no-referrer',
+ from + baseURL + from + schemeTo + to + '&policy=unsafe-url',
+ from + baseURL + from + schemeTo + to + '&policy=origin',
+ from + baseURL + from + schemeTo + to + '&policy=origin-when-cross-origin',
+ from + baseURL + from + schemeTo + to + '&policy=same-origin',
+ from + baseURL + from + schemeTo + to + '&policy=strict-origin',
+ from + baseURL + from + schemeTo + to + '&policy=strict-origin-when-cross-origin',
+ ];
+});
+
+let testIframeUrls = [generateURLArray('http', 'https'),
+ generateURLArray('https', 'http')];
+
+SimpleTest.waitForExplicitFinish();
+let advance = function(testName) {
+ testsGenerator[testName].next();
+};
+
+let testNames = ['testframeone', 'testframetwo'];
+let isTestFinished = 0;
+
+function checkTestsCompleted() {
+ isTestFinished++;
+ if (isTestFinished == 2) {
+ SimpleTest.finish();
+ }
+}
+let testsGenerator = {};
+SimpleTest.requestLongerTimeout(4);
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It performs all tests in sequence using four iframes.
+ */
+function startTests(testName, iframeUrls) {
+ testsGenerator[testName] = (function*() {
+ var testframe = document.getElementById(testName);
+ testframe.onload = function() {
+ advance(testName);
+ }
+
+ // load the test frame from iframeUrls[url]
+ // it will call back into this function via postMessage when it finishes
+ // loading and continue beyond the yield.
+ for(url in iframeUrls) {
+ yield testframe.src = iframeUrls[url];
+ // run test and check result for loaded test URL
+ yield checkExpectedGlobalResults(testName);
+ }
+ checkTestsCompleted();
+ })();
+}
+
+for (i = 0; i < testIframeUrls.length; i++) {
+ startTests(testNames[i], testIframeUrls[i]);
+}
+
+</script>
+</head>
+
+<body onload="testsGenerator[testNames[0]].next();
+ testsGenerator[testNames[1]].next();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP/HTTPS to HTTPS/HTTP</a>
+<p id="display"></p>
+<pre id="content">
+</pre>
+ <iframe id="testframeone"></iframe>
+ <iframe id="testframetwo"></iframe>
+</body>
diff --git a/dom/base/test/test_bug704320_policyset.html b/dom/base/test/test_bug704320_policyset.html
new file mode 100644
index 0000000000..227c3b20a5
--- /dev/null
+++ b/dom/base/test/test_bug704320_policyset.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This checks if the right policies are applied from a given string (including whitespace, invalid policy strings, etc). It doesn't do a complete check for all load types; that's done in another test.
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 704320</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It resets the counter, then performs two tests in sequence using
+ * the same iframe.
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+ const sjs = "/tests/dom/base/test/bug704320.sjs?action=generate-policy-test";
+
+
+ // basic calibration check
+ // reset the counter
+ yield resetCounter();
+
+ // load the first test frame
+ // it will call back into this function via postMessage when it finishes loading.
+ // and continue beyond the yield.
+ yield iframe.src = sjs + "&policy=" + escape('default');
+
+ // check the first test (two images, no referrers)
+ yield checkIndividualResults("default", ["full"]);
+
+ // check invalid policy
+ // According to the spec section Determine token's Policy,if there is a policy
+ // token and it is not one of the expected tokens, Empty string should be the
+ // policy used.
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('invalid-policy');
+ yield checkIndividualResults("invalid", ["full"]);
+
+ // whitespace checks.
+ // according to the spec section 4.1, the content attribute's value
+ // is fed to the token policy algorithm after stripping leading and
+ // trailing whitespace.
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('default ');
+ yield checkIndividualResults("trailing whitespace", ["full"]);
+
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape(' origin\f');
+ yield checkIndividualResults("trailing form feed", ["origin"]);
+
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('\f origin');
+ yield checkIndividualResults("leading form feed", ["origin"]);
+
+ // origin when cross-origin (trimming whitespace)
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape(' origin-when-cross-origin');
+ yield checkIndividualResults("origin-when-cross-origin", ["origin", "full"]);
+
+ // according to the spec section 4.1:
+ // "If the meta element lacks a content attribute, or if that attribute’s
+ // value is the empty string, then abort these steps."
+ // This means empty or missing content attribute means to ignore the meta
+ // tag and use default policy.
+ // Whitespace here is space, tab, LF, FF and CR.
+ // http://www.w3.org/html/wg/drafts/html/CR/infrastructure.html#space-character
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape(' \t ');
+ yield checkIndividualResults("basic whitespace only policy", ["full"]);
+
+ // and double-check that no-referrer works.
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer", ["none"]);
+
+ // Case insensitive
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape('\f OrigIn');
+ yield checkIndividualResults("origin case insensitive", ["origin"]);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug704320_policyset2.html b/dom/base/test/test_bug704320_policyset2.html
new file mode 100644
index 0000000000..546107d594
--- /dev/null
+++ b/dom/base/test/test_bug704320_policyset2.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This checks if the right policies are applied from a given string (including whitespace, invalid policy strings, etc). It doesn't do a complete check for all load types; that's done in another test.
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 704320</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It resets the counter, then performs two tests in sequence using
+ * the same iframe.
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+ const sjs = "/tests/dom/base/test/bug704320.sjs?action=generate-policy-test";
+
+ yield resetCounter();
+ yield iframe.src = sjs + "&policy=" + escape(' \f\r\n\t ');
+ yield checkIndividualResults("whitespace only policy", ["full"]);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug704320_preload.html b/dom/base/test/test_bug704320_preload.html
new file mode 100644
index 0000000000..b4ae415278
--- /dev/null
+++ b/dom/base/test/test_bug704320_preload.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This is a spot check for whether the speculative parser reuses style, script or image loads after the referrer policy has changed.
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test preloads for Bug 704320</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="referrerHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It resets the counter, then performs two tests in sequence using
+ * the same iframe.
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+
+ // reset the counter
+ yield resetCounter();
+
+ // load the second test frame
+ // it will call back into this function via postMessage when it finishes loading.
+ // and continue beyond the yield.
+ yield iframe.src = 'file_bug704320_preload_reuse.html';
+
+ // check the second test
+ yield checkResults(finalizePreloadReuse);
+
+ // reset the counter
+ yield resetCounter();
+
+ // load the third test frame
+ // it will call back into this function via postMessage when it finishes loading.
+ // and continue beyond the yield.
+ yield iframe.src = 'file_bug704320_preload_attr.html';
+
+ // check the third test
+ yield checkResults(finalizePreloadReferrerPolicyAttr);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+// Helper functions below.
+
+/**
+ * This checks the second test: a test where preloads SHOULD be reused.
+ * We expect one request for each image, script, js request since
+ * the referrer policy does not change after speculative loads.
+ */
+function finalizePreloadReuse(results) {
+ var expected = {'css': {'count': 1, 'referrers': ['origin']},
+ 'img': {'count': 1, 'referrers': ['origin']},
+ 'js': {'count': 1, 'referrers': ['origin']}};
+
+ for (var x in expected) {
+ ok(x in results, "some " + x + " loads required in results object.");
+
+ is(results[x].count, expected[x].count,
+ "Expected " + expected[x].count + " loads for " + x + " requests.");
+
+ // 'origin' is required
+ ok(results[x].referrers.includes('origin'),
+ "One load for " + x + " should have had 'origin' referrer.");
+
+ // no other values should be in the referrers.
+ is(results[x].referrers.indexOf('none'), -1,
+ "No loads for " + x + " should have a missing referrer.");
+ is(results[x].referrers.indexOf('full'), -1,
+ "No loads for " + x + " should have an 'full' referrer.")
+ }
+
+ advance();
+}
+
+/**
+ * This checks the third test: a test where preload requests of image, style
+ * should use referrerpolicy attribute and we expect the preloads should not
+ * be reused
+ */
+function finalizePreloadReferrerPolicyAttr(results) {
+ var expected = {'css': {'count': 1, 'referrers': ['origin']},
+ 'img': {'count': 1, 'referrers': ['origin']},
+ 'js': {'count': 1, 'referrers': ['origin']}};
+
+ for (var x in expected) {
+ ok(x in results, "some " + x + " loads required in results object.");
+
+ is(results[x].count, expected[x].count,
+ "Expected " + expected[x].count + " loads for " + x + " requests.");
+
+ // 'origin' is required
+ ok(results[x].referrers.includes('origin'),
+ "One load for " + x + " should have had 'origin' referrer.");
+
+ // no other values should be in the referrers.
+ is(results[x].referrers.indexOf('none'), -1,
+ "No loads for " + x + " should have a missing referrer.");
+ is(results[x].referrers.indexOf('full'), -1,
+ "No loads for " + x + " should have an 'full' referrer.")
+ }
+
+ advance();
+}
+
+/**
+ * Grabs the results via XHR and passes to checker.
+ */
+function checkResults(checker) {
+ doXHR('/tests/dom/base/test/bug704320_counter.sjs?results',
+ function(xhr) {
+ checker(JSON.parse(xhr.responseText));
+ },
+ function(xhr) {
+ ok(false, "Can't get results from the counter server.");
+ });
+}
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug707142.html b/dom/base/test/test_bug707142.html
new file mode 100644
index 0000000000..5326c1b96b
--- /dev/null
+++ b/dom/base/test/test_bug707142.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=707142
+-->
+<head>
+ <title>Test for Bug 707142</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=707142">Mozilla Bug 707142</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 707142 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var xhr = new XMLHttpRequest();
+xhr.onload = function() {
+ is(xhr.response.foo, "bar", "Should have gotten bar on baseline");
+
+ xhr.onload = function() {
+ is(xhr.response.foo, "bar", "Should have gotten bar with BOM");
+
+ xhr.onload = function() {
+ is(xhr.response, null, "Should have gotten null response with UTF-16 JSON");
+
+ SimpleTest.finish();
+ };
+ xhr.open("GET", "file_bug707142_utf-16.json");
+ xhr.responseType = "json";
+ xhr.send();
+ };
+ xhr.open("GET", "file_bug707142_bom.json");
+ xhr.responseType = "json";
+ xhr.send();
+};
+xhr.open("GET", "file_bug707142_baseline.json");
+xhr.responseType = "json";
+xhr.send();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug708620.html b/dom/base/test/test_bug708620.html
new file mode 100644
index 0000000000..d6b5b0c6d8
--- /dev/null
+++ b/dom/base/test/test_bug708620.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=708620
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 708620</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=708620"
+ >Mozilla Bug 708620</a>
+<iframe></iframe>
+<script>
+/** Test for Bug 708620 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.monitorConsole(SimpleTest.finish, [
+ { errorMessage: "A form was submitted in the windows-1252 encoding "+
+ "which cannot encode all Unicode characters, so user "+
+ "input may get corrupted. To avoid this problem, the "+
+ "page should be changed so that the form is submitted "+
+ "in the UTF-8 encoding either by changing the encoding "+
+ "of the page itself to UTF-8 or by specifying "+
+ "accept-charset=utf-8 on the form element.",
+ isWarning: true }
+]);
+
+window.onload = function () {
+ document.getElementsByTagName("iframe")[0].src = "file_bug708620.html";
+}
+
+function finish() {
+ SimpleTest.endMonitorConsole();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug711047.html b/dom/base/test/test_bug711047.html
new file mode 100644
index 0000000000..2aa8de3060
--- /dev/null
+++ b/dom/base/test/test_bug711047.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=711047
+-->
+<title>Test for Bug 711047</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=711047">Mozilla Bug 711047</a>
+<div id="content">
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 711047 **/
+ok(!("RangeException" in window), "RangeException shouldn't exist");
+</script>
+</pre>
diff --git a/dom/base/test/test_bug711180.html b/dom/base/test/test_bug711180.html
new file mode 100644
index 0000000000..d80bc1b85b
--- /dev/null
+++ b/dom/base/test/test_bug711180.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=711180
+-->
+<head>
+ <title>Test for Bug 711180</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=711180">Mozilla Bug 711180</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 711180 **/
+is(atob("aQ=="), atob("\t a\rQ\n=\f="), "Base64 space removal");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug719533.html b/dom/base/test/test_bug719533.html
new file mode 100644
index 0000000000..4154b3bdb2
--- /dev/null
+++ b/dom/base/test/test_bug719533.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=719544
+-->
+<title>Test for Bug 719544</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719544">Mozilla Bug 719544</a>
+<script>
+
+/** Test for Bug 719544 **/
+var threw = false;
+var origLength = document.childNodes.length;
+try {
+ var range = document.createRange();
+ range.selectNodeContents(document);
+ range.extractContents();
+} catch(e) {
+ threw = true;
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "Must throw DOMException");
+ is(e.name, "HierarchyRequestError", "Must throw HierarchyRequestError");
+}
+ok(threw, "Must throw");
+is(document.childNodes.length, origLength, "Must preserve original children");
+
+</script>
diff --git a/dom/base/test/test_bug726364.html b/dom/base/test/test_bug726364.html
new file mode 100644
index 0000000000..29830a39f7
--- /dev/null
+++ b/dom/base/test/test_bug726364.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=726364
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 726364</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=726364">Mozilla Bug 726364</a>
+<p id="display"><div id="v">ABC</div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 726364 **/
+function boom()
+{
+ var v = document.getElementById("v");
+ var t = v.firstChild;
+ is(v.childNodes.length,1, "child count");
+
+ var f = function(event) {
+ window.removeEventListener("DOMCharacterDataModified", f, true);
+ is(v.childNodes[0],t, "first child is the same");
+ is(v.childNodes.length,2, "child count");
+ r.setEnd(v, 0);
+ SimpleTest.finish();
+ };
+ window.addEventListener("DOMCharacterDataModified", f, true);
+
+ var r = document.createRange();
+ r.setStart(t, 2);
+ t.splitText(1);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(boom);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug737087.html b/dom/base/test/test_bug737087.html
new file mode 100644
index 0000000000..008ad3bc03
--- /dev/null
+++ b/dom/base/test/test_bug737087.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=737087
+-->
+<title>Test for Bug 737087</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737087">Mozilla Bug 737087</a>
+<script>
+
+/** Test for Bug 737087 **/
+SimpleTest.waitForExplicitFinish();
+
+var bubbled = false;
+var capturedEvent = null;
+var inlineFiredEvent = null;
+
+addEventListener("error", function() { bubbled = true });
+addEventListener("error", function(e) {
+ capturedEvent = e;
+ is(typeof e, "object", "Error event must be object");
+ is(Object.getPrototypeOf(e), Event.prototype, "Error event must be Event");
+ is(e.bubbles, false, "e.bubbles must be false");
+ is(e.cancelable, false, "e.cancelable must be false");
+}, true);
+
+addLoadEvent(function() {
+ is(bubbled, false, "Error event must not bubble");
+ isnot(capturedEvent, null, "Error event must be captured");
+ isnot(inlineFiredEvent, null, "Inline error handler must fire");
+ is(capturedEvent, inlineFiredEvent,
+ "Same event must be handled by both handlers");
+ SimpleTest.finish();
+});
+</script>
+<script src=nonexistent
+ onerror="inlineFiredEvent = event"></script>
diff --git a/dom/base/test/test_bug737565.html b/dom/base/test/test_bug737565.html
new file mode 100644
index 0000000000..9a24a70b9d
--- /dev/null
+++ b/dom/base/test/test_bug737565.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=737565
+-->
+<title>Test for Bug 737565</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737565">Mozilla Bug 737565</a>
+<script>
+
+/** Test for Bug 737565 **/
+var offsets = [-1, 0, 1, 2, 3, 65536, 1 << 31];
+// This is supposed to be an unsigned long, so adding or subtracting 1 << 32
+// should have no effect
+var offsetOffsets = [0, -Math.pow(2, 32), Math.pow(2, 32)];
+
+for (var i = 0; i < offsets.length; i++) {
+ for (var j = 0; j < offsetOffsets.length; j++) {
+ var offset = offsets[i] + offsetOffsets[j];
+
+ // Doctype always needs to throw
+ var threw = false;
+ try {
+ var range = document.createRange();
+ range.comparePoint(document.doctype, offset);
+ } catch(e) {
+ threw = true;
+ is(e.name, "InvalidNodeTypeError",
+ "comparePoint(document.doctype, " + offset
+ + ") must throw InvalidNodeTypeError");
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "comparePoint(document.doctype, " + offset
+ + ") must throw DOMException");
+ is(e.code, DOMException.INVALID_NODE_TYPE_ERR,
+ "comparePoint(document.doctype, " + offset
+ + ") must throw INVALID_NODE_TYPE_ERR");
+ }
+ ok(threw, "comparePoint(document.doctype, " + offset + ") must throw");
+
+ threw = false;
+ // document.documentElement has two children, head and body
+ var expectedThrew = offsets[i] < 0 || offsets[i] > 2;
+ try {
+ var range = document.createRange();
+ range.comparePoint(document.documentElement, offset);
+ } catch(e) {
+ threw = true;
+ is(e.name, "IndexSizeError",
+ "comparePoint(document.documentElement, " + offset
+ + ") must throw IndexSizeError");
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "comparePoint(document.documentElement, " + offset
+ + ") must throw DOMException");
+ is(e.code, DOMException.INDEX_SIZE_ERR,
+ "comparePoint(document.documentElement, " + offset
+ + ") must throw INDEX_SIZE_ERR");
+ }
+ is(threw, expectedThrew,
+ "comparePoint(document.documentElement, " + offset
+ + ") must " + (expectedThrew ? "" : "not ") + "throw");
+ }
+}
+
+</script>
diff --git a/dom/base/test/test_bug737612.html b/dom/base/test/test_bug737612.html
new file mode 100644
index 0000000000..9a17ab06e6
--- /dev/null
+++ b/dom/base/test/test_bug737612.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=737612
+-->
+<title>Test for Bug 737612</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737612">Mozilla Bug 737612</a>
+<script>
+
+/** Test for Bug 737612 **/
+var text = document.createTextNode("abc");
+var range = document.createRange();
+range.setStart(text, 1);
+var threw = false;
+try {
+ range.insertNode(document.head);
+} catch(e) {
+ var threw = true;
+ is(e.name, "HierarchyRequestError",
+ "Must throw HierarchyRequestError");
+ is(Object.getPrototypeOf(e), DOMException.prototype,
+ "Must throw DOMException");
+ is(e.code, DOMException.HIERARCHY_REQUEST_ERR,
+ "Must throw HIERARCHY_REQUEST_ERR");
+}
+ok(threw, "Must throw when insertNode()ing into detached text node");
+
+</script>
diff --git a/dom/base/test/test_bug738108.html b/dom/base/test/test_bug738108.html
new file mode 100644
index 0000000000..1ec69d83b9
--- /dev/null
+++ b/dom/base/test/test_bug738108.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=738108
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 738108</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=738108">Mozilla Bug 738108</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="foo"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 738108 **/
+is(document.querySelector("#foo"), $("foo"),
+ "querySelector on document should find element by id")
+is($("content").querySelector("#foo"), $("foo"),
+ "querySelector on parent element should find element by id")
+is($("foo").querySelector("#foo"), null,
+ "querySelector on element should not find the element itself by id")
+
+is(document.querySelectorAll("#foo").length, 1,
+ "querySelectorAll on document should find element by id")
+is($("content").querySelectorAll("#foo").length, 1,
+ "querySelectorAll on parent element should find element by id")
+is($("foo").querySelectorAll("#foo").length, 0,
+ "querySelectorall on element should not find the element itself by id")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug744830.html b/dom/base/test/test_bug744830.html
new file mode 100644
index 0000000000..2f9b3dc3c5
--- /dev/null
+++ b/dom/base/test/test_bug744830.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+ var t = document.getElementById('testnodes');
+ is(t.innerHTML,
+ "<span>hi</span> there <!-- mon ami -->",
+ "comment nodes should be included");
+
+ var PI = document.createProcessingInstruction('foo', 'bar="1.0"');
+ t.appendChild(PI);
+ is(t.innerHTML, '<span>hi</span> there <!-- mon ami --><?foo bar="1.0">',
+ "pi nodes should be included");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElement("textarea"));
+ t.firstChild.appendChild(document.createTextNode("\nhello"));
+ // This is the old behavior. Spec requires something else.
+ is(t.innerHTML, "<textarea>\nhello</textarea>",
+ "No extra newlines should be inserted to the textarea!");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<svg>&lt;foo&gt;</svg>");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math:math"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<math>&lt;foo&gt;</math>");
+
+ // Prefix is serialized if element isn't HTML/SVG/MathML
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.example.org", "ex:example"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<ex:example>&lt;foo&gt;</ex:example>");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.example.org", "example"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<example>&lt;foo&gt;</example>");
+
+ t.firstChild.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:lang", "us-en");
+ is(t.innerHTML, '<example xml:lang="us-en">&lt;foo&gt;</example>');
+
+ t.firstChild.setAttributeNS("http://www.w3.org/1999/xlink", "href", "foo");
+ is(t.innerHTML, '<example xml:lang="us-en" xlink:href="foo">&lt;foo&gt;</example>');
+
+ t.firstChild.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://foo");
+ is(t.innerHTML, '<example xml:lang="us-en" xlink:href="foo" xmlns="http://foo">&lt;foo&gt;</example>');
+
+ t.firstChild.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:bar", "http://bar");
+ is(t.innerHTML, '<example xml:lang="us-en" xlink:href="foo" xmlns="http://foo" xmlns:bar="http://bar">&lt;foo&gt;</example>');
+
+ t.firstChild.setAttributeNS("http://www.helloworldns.org", "hello:world", "!");
+ is(t.innerHTML, '<example xml:lang="us-en" xlink:href="foo" xmlns="http://foo" xmlns:bar="http://bar" hello:world="!">&lt;foo&gt;</example>');
+
+ t.firstChild.setAttribute("foo", '-"&\xA0-');
+ is(t.innerHTML, '<example xml:lang="us-en" xlink:href="foo" xmlns="http://foo" xmlns:bar="http://bar" hello:world="!" foo="-&quot;&amp;&nbsp;-">&lt;foo&gt;</example>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElement("div"));
+ t.firstChild.appendChild(document.implementation
+ .createDocument(null, null, null)
+ .createCDATASection("foo"));
+ is(t.innerHTML, '<div>foo</div>');
+
+ t.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<div>1&amp;2&lt;3&gt;4&nbsp;</div>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElement("script"));
+ t.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<script>1&2<3>4\xA0\u003C/script>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElement("style"));
+ t.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<style>1&2<3>4\xA0\u003C/style>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElement("span"));
+ t.firstChild.setAttributeNS("ext", "attr", "foo");
+ t.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<span attr="foo">1&amp;2&lt;3&gt;4&nbsp;\u003C/span>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<svg><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></svg>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "style"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<svg><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></svg>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></math>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "style"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></math>');
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug749367.html b/dom/base/test/test_bug749367.html
new file mode 100644
index 0000000000..be017b71ba
--- /dev/null
+++ b/dom/base/test/test_bug749367.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=749367
+-->
+
+<head>
+ <title>Test for Bug 749367</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=749367">Mozilla Bug 749367</a>
+<div id="content" style="display: none"></div>
+ <pre id="test">
+
+ <script type="text/template">
+ ok(false, "Shouldn't execute");
+ </script>
+
+ <script type="text/javascript">
+ ok(true, "Should execute");
+ </script>
+
+ </pre>
+</body>
+
+</html>
diff --git a/dom/base/test/test_bug750096.html b/dom/base/test/test_bug750096.html
new file mode 100644
index 0000000000..adbff360a5
--- /dev/null
+++ b/dom/base/test/test_bug750096.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=750096
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 750096</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=750096">Mozilla Bug 750096</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 750096 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var u = SpecialPowers.Ci.nsIParserUtils;
+var s = SpecialPowers.ParserUtils;
+
+var elt = document.getElementById("content");
+
+var embed = s.parseFragment("<embed src=\'javascript:this.fail = true;\'>", 0, false, null, elt);
+var img = s.parseFragment("<img src=\'javascript:this.fail = true;\'>", 0, false, null, elt);
+var video = s.parseFragment("<video src=\'javascript:this.fail = true;\'></video>", 0, false, null, elt);
+var object = s.parseFragment("<object data=\'javascript:this.fail = true;\'></object>", 0, false, null, elt);
+var iframe = s.parseFragment("<iframe src=\'javascript:this.fail = true;\'></iframe>", 0, false, null, elt);
+
+setTimeout(function() {
+ ok(!window.fail, "Should not have failed.");
+ SimpleTest.finish();
+}, 0);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug753278.html b/dom/base/test/test_bug753278.html
new file mode 100644
index 0000000000..e65cd6c346
--- /dev/null
+++ b/dom/base/test/test_bug753278.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=753278
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 753278</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753278">Mozilla Bug 753278</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 753278 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var f = document.getElementsByTagName("iframe")[0];
+
+function runTest() {
+ f.contentDocument.open();
+ f.contentDocument.write('<script>window.location = "file_bug753278.html"; document.close(); document.open(); document.write("\\u003Cscript>parent.fail();\\u003C/script>"); document.close();\u003c/script>');
+ f.contentDocument.close();
+}
+
+function pass() {
+ ok(true, "window.location took precedence");
+ SimpleTest.finish();
+}
+
+function fail() {
+ ok(false, "window.location should have taken precedence");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug761120.html b/dom/base/test/test_bug761120.html
new file mode 100644
index 0000000000..f3709ef692
--- /dev/null
+++ b/dom/base/test/test_bug761120.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=761120
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 761120</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=761120">Mozilla Bug 761120</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 761120 **/
+var doc = document.implementation.createHTMLDocument("title");
+try {
+ doc.appendChild(doc.createTextNode("text"));
+ ok(false, "Didn't throw");
+} catch (e) {
+ is(e.name, "HierarchyRequestError");
+}
+
+var el = document.createElement("div");
+var text = document.createTextNode("text");
+el.appendChild(text);
+is(el.firstChild, text);
+
+var df = document.createDocumentFragment();
+text = document.createTextNode("text");
+df.appendChild(text);
+is(df.firstChild, text);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug769117.html b/dom/base/test/test_bug769117.html
new file mode 100644
index 0000000000..eb59ade174
--- /dev/null
+++ b/dom/base/test/test_bug769117.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=769117
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 769117</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ /** Test for Bug 769117 **/
+ "use strict";
+ function onLoad () {
+ SpecialPowers.pushPrefEnv({"set": [["plugins.rewrite_youtube_embeds", true]]}, function() {
+
+ let youtube_url = "https://mochitest.youtube.com/v/Xm5i5kbIXzc";
+ let youtube_changed_url = "https://mochitest.youtube.com/embed/Xm5i5kbIXzc";
+ let static_iframe = document.getElementById("staticiframe");
+
+ function testEmbed(embed) {
+ ok (embed, "Embed node exists");
+ embed = SpecialPowers.wrap(embed);
+ is (embed.srcURI.spec, youtube_changed_url, "Should have src uri of " + youtube_changed_url);
+ }
+
+ function testStatic() {
+ info("Running static embed youtube rewrite test");
+ iframe_doc = static_iframe.contentWindow.document;
+ testEmbed(iframe_doc.getElementById("testembed"));
+ testEmbed(iframe_doc.getElementById("testobject"));
+ SimpleTest.executeSoon(() => {
+ testEmbed(embed_dynamic);
+ SimpleTest.finish();
+ });
+ }
+
+ info("Running dynamic embed youtube rewrite test");
+ let embed_dynamic = document.createElement("embed");
+ embed_dynamic.src = "https://mochitest.youtube.com/v/Xm5i5kbIXzc";
+ embed_dynamic.type = "application/x-shockwave-flash";
+ document.body.appendChild(embed_dynamic);
+
+ static_iframe.onload = testStatic;
+ static_iframe.src = "file_bug769117.html"
+
+ });
+ }
+ </script>
+ </head>
+ <body onload="onLoad()">
+ <iframe id="staticiframe"></iframe>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug782342.html b/dom/base/test/test_bug782342.html
new file mode 100644
index 0000000000..9816ae173a
--- /dev/null
+++ b/dom/base/test/test_bug782342.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=782342
+-->
+<head>
+ <title>Test for bug 782342 - blob: protocol no Content-Length header</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782342">Mozilla Bug 782342 - blob: protocol no Content-Length header</a>
+<p id="display">
+ <input id="fileList" type="file"></input>
+</p>
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var url = "file_bug782342.txt";
+var xhr = new XMLHttpRequest();
+xhr.open("GET", url, true);
+xhr.responseType = "blob";
+
+function checkHeaders(xhrInner) {
+ var headers = xhrInner.getAllResponseHeaders();
+ dump(headers + "\n");
+ var p = headers.split("\n");
+
+ var contentType = false;
+ var contentLength = false;
+
+ for (var i = 0; i < p.length; ++i) {
+ var header = p[i].split(':')[0];
+ if (header.toLowerCase() == 'content-type')
+ contentType = true;
+ else if (header.toLowerCase() == 'content-length')
+ contentLength = true;
+ }
+
+ ok(contentLength == true, "Content-length is part of the headers!");
+ ok(contentType == true, "Content-type is part of the headers!");
+
+ var ct = xhrInner.getResponseHeader('content-type');
+ ok(ct.length, 'Get Response Header - content type: ' + ct);
+ var cl = xhrInner.getResponseHeader('content-length');
+ ok(cl.length, 'Get Response Header - content length: ' + cl);
+}
+
+xhr.addEventListener("load", function () {
+ ok(xhr.status === 200, "Status 200!");
+ if (xhr.status === 200) {
+ var blob = xhr.response;
+ ok(blob, "Blob is: " + blob);
+ var blobUrl = window.URL.createObjectURL(blob);
+ ok(blobUrl, "Blob URL is: " + blobUrl);
+ checkHeaders(xhr);
+
+ var xhrBlob = new XMLHttpRequest();
+ xhrBlob.open("GET", blobUrl, true);
+ xhrBlob.responseType = "blob";
+
+ xhrBlob.addEventListener("load", function () {
+ var blob2 = xhr.response;
+ ok(blob2, "Blob is: " + blob2);
+ var blob2Url = window.URL.createObjectURL(blob);
+ ok(blob2Url, "Blob URL is: " + blob2Url);
+ checkHeaders(xhrBlob);
+
+ ok(blob2.size == blob.size, "Blob sizes are: " + blob2.size + " - " + blob.size);
+ ok(blob2.type == blob.type, "Blob types are: " + blob2.type + " - " + blob.type);
+
+ SimpleTest.finish();
+ });
+ xhrBlob.send();
+ }
+});
+xhr.send();
+</script>
+</body>
+
+</html>
diff --git a/dom/base/test/test_bug787778.html b/dom/base/test/test_bug787778.html
new file mode 100644
index 0000000000..4322b4567b
--- /dev/null
+++ b/dom/base/test/test_bug787778.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=787778
+ -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 787778</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ window.document.x.src='a';
+ SimpleTest.executeSoon(function () {
+ ok(true, "Didn't crash!");
+ SimpleTest.finish();
+ });
+ }
+ </script>
+ </head>
+ <body>
+ <embed name="x" src="./file_bug787778.sjs">
+ </body>
+</html>
diff --git a/dom/base/test/test_bug789315.html b/dom/base/test/test_bug789315.html
new file mode 100644
index 0000000000..58516a8f45
--- /dev/null
+++ b/dom/base/test/test_bug789315.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789315
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 789315</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ (function() {
+ const observerConfig = {
+ childList: true,
+ };
+
+ var observer = new MutationObserver(onMutations);
+ observer.observe(document.head, observerConfig);
+
+ function onMutations(mutations) {
+ for (var i in mutations) {
+ var mutation = mutations[i];
+ for (var j in mutation.addedNodes) {
+ var addedNode = mutation.addedNodes[j];
+ addedNode.mutationObserverHasNotified = true;
+ }
+ }
+ }
+
+ })();
+ </script>
+
+ <link id="testnode" rel="localization" href="dummy"></link>
+
+ <script type="text/javascript">
+ var testNode = document.getElementById("testnode");
+ ok(testNode.mutationObserverHasNotified);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=789315">Mozilla Bug 789315</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug789856.html b/dom/base/test/test_bug789856.html
new file mode 100644
index 0000000000..3e62232bfc
--- /dev/null
+++ b/dom/base/test/test_bug789856.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789856
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 789856</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=789856">Mozilla Bug 789856</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 789856 **/
+SimpleTest.waitForExplicitFinish();
+
+var script = document.createElement("script");
+script.onload = function() {
+ ok(false, "This script should not load");
+ SimpleTest.finish();
+}
+script.onerror = function() {
+ ok(true, "This script should fail to load");
+ SimpleTest.finish();
+}
+// If neither one fires, the test fails, as it should
+
+// Use a URL the test is not allowed to load
+script.src = "file:///tmp/"
+document.body.appendChild(script);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug809003.html b/dom/base/test/test_bug809003.html
new file mode 100644
index 0000000000..9c29392ea5
--- /dev/null
+++ b/dom/base/test/test_bug809003.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=809003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 809003</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #testdiv:before {
+ content: url('data:image/gif,GIF89a%01%00%01%00%80%01%00%FF%00%00%FF%FF%FF!%F9%04%01%00%00%01%00%2C%00%00%00%00%01%00%01%00%00%02%02D%01%00%3B');
+ }
+ #testdiv:after {
+ content: url('non_existing_image.gif');
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=809003">Mozilla Bug 809003</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 809003 **/
+
+window.didGetLoad = false;
+window.didGetError = false;
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+setTimeout(function() {
+ is(window.didGetLoad, false, "Shouldn't have got load event!");
+ is(window.didGetError, false, "Shouldn't have got error event!");
+ SimpleTest.finish();
+}, 1000);
+
+</script>
+</pre>
+<div id="testdiv" onload="window.didGetLoad = true;" onerror="window.didGetError = true;"></div>
+</body>
+</html>
diff --git a/dom/base/test/test_bug810494.html b/dom/base/test/test_bug810494.html
new file mode 100644
index 0000000000..7750211326
--- /dev/null
+++ b/dom/base/test/test_bug810494.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=810494
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 810494</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=810494">Mozilla Bug 810494</a>
+<pre id="test">
+<script type="application/javascript">
+
+function test(tag, type) {
+ "use strict";
+ info("testing " + tag + " tag with type " + type);
+
+ const OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+ let obj = document.createElement(tag);
+ obj.type = type;
+ document.body.appendChild(obj);
+
+ obj instanceof OBJLC;
+ obj = SpecialPowers.wrap(obj);
+
+ // We expect this tag to simply go to alternate content, not get a
+ // pluginProblem binding or fire any events.
+ ok(obj.displayedType == OBJLC.TYPE_NULL, "expected null type");
+}
+
+// Test all non-plugin types these tags can load to make sure none of them
+// trigger plugin-specific fallbacks when loaded with no URI
+test("object", "text/html"); // Document
+test("object", "image/png"); // Image
+test("object", "image/svg+xml"); // SVG Document
+
+test("embed", "image/png"); // Image
+test("embed", "image/svg+xml"); // SVG Document
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug811701.html b/dom/base/test/test_bug811701.html
new file mode 100644
index 0000000000..993ad81475
--- /dev/null
+++ b/dom/base/test/test_bug811701.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=811701
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 811701</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=811701">Mozilla Bug 811701</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<math><mtext>test</mtext></math>
+<svg><polygon points="0,0 100,100 200,300"/></svg>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 811701 **/
+ var math = document.querySelector("math");
+ is(math.innerHTML, "<mtext>test</mtext>", "<math> should have innerHTML");
+ is(math.outerHTML, "<math><mtext>test</mtext></math>",
+ "<math> should have innerHTML");
+ math.innerHTML = "<mo>+</mo>";
+ is(math.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML",
+ "Should have the right namespace after setting innerHTML on <math>");
+
+ var polygon = document.querySelector("polygon");
+ is(polygon.parentNode.innerHTML,
+ '<polygon points="0,0 100,100 200,300"></polygon>',
+ "<svg> should have innerHTML");
+ is(polygon.parentNode.outerHTML,
+ '<svg><polygon points="0,0 100,100 200,300"></polygon></svg>',
+ "<svg> should have outerHTML");
+ is(polygon.outerHTML, '<polygon points="0,0 100,100 200,300"></polygon>',
+ "<polygon> should have outerHTML");
+
+ var svg = document.querySelector("svg");
+ svg.innerHTML = "<rect/>";
+ is(svg.firstChild.namespaceURI, "http://www.w3.org/2000/svg",
+ "Should have the right namespace after setting innerHTML on <svg>");
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug811701.xhtml b/dom/base/test/test_bug811701.xhtml
new file mode 100644
index 0000000000..8986bac3e7
--- /dev/null
+++ b/dom/base/test/test_bug811701.xhtml
@@ -0,0 +1,52 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=811701
+-->
+<head>
+ <title>Test for Bug 811701</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=811701">Mozilla Bug 811701</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<math xmlns="http://www.w3.org/1998/Math/MathML"><mtext>test</mtext></math>
+<svg xmlns="http://www.w3.org/2000/svg"><polygon points="0,0 100,100 200,300"/></svg>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+ <![CDATA[
+
+
+ /** Test for Bug 811701 **/
+ var math = document.querySelector("math");
+ is(math.innerHTML,
+ '<mtext xmlns="http://www.w3.org/1998/Math/MathML">test</mtext>',
+ "<math> should have innerHTML");
+ is(math.outerHTML,
+ '<math xmlns="http://www.w3.org/1998/Math/MathML"><mtext>test</mtext></math>',
+ "<math> should have innerHTML");
+ math.innerHTML = "<mo>+</mo>";
+ is(math.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML",
+ "Should have the right namespace after setting innerHTML on <math>");
+
+ var polygon = document.querySelector("polygon");
+ is(polygon.parentNode.innerHTML,
+ '<polygon xmlns="http://www.w3.org/2000/svg" points="0,0 100,100 200,300"/>',
+ "<svg> should have innerHTML");
+ is(polygon.parentNode.outerHTML,
+ '<svg xmlns="http://www.w3.org/2000/svg"><polygon points="0,0 100,100 200,300"/></svg>',
+ "<svg> should have outerHTML");
+ is(polygon.outerHTML, '<polygon xmlns="http://www.w3.org/2000/svg" points="0,0 100,100 200,300"/>',
+ "<polygon> should have outerHTML");
+
+ var svg = document.querySelector("svg");
+ svg.innerHTML = "<rect/>";
+ is(svg.firstChild.namespaceURI, "http://www.w3.org/2000/svg",
+ "Should have the right namespace after setting innerHTML on <math>");
+ ]]>
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug813919.html b/dom/base/test/test_bug813919.html
new file mode 100644
index 0000000000..f81fc9f410
--- /dev/null
+++ b/dom/base/test/test_bug813919.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=813919
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 813919</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=813919">Mozilla Bug 813919</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 813919 **/
+
+ function testDataNode(dataNode) {
+ var div = document.createElement("div");
+ div.appendChild(dataNode);
+ var span = document.createElement("span");
+ div.appendChild(span);
+ var r = document.createRange();
+ r.setStart(dataNode, 0);
+ r.setEnd(div, div.childNodes.length);
+ r.deleteContents();
+ ok(r.collapsed, "Range should be collapsed!");
+ is(r.startContainer, div, "startContainer should be div.");
+ is(r.startOffset, div.childNodes.length,
+ "Range should be collaped to the end of the div element.");
+ }
+
+ testDataNode(document.createProcessingInstruction("x", "x"));
+ testDataNode(document.createComment("x"));
+ testDataNode(document.createTextNode("x"));
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug814576.html b/dom/base/test/test_bug814576.html
new file mode 100644
index 0000000000..62d19e1c48
--- /dev/null
+++ b/dom/base/test/test_bug814576.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814576
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 814576</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814576">Mozilla Bug 814576</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 814576 **/
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "");
+xhr.send();
+var called = false;
+// Add an event listener that only listens for trusted events
+xhr.addEventListener("abort", function() { called = true; }, false, false);
+
+// Check that synthetic events don't trigger the listener
+var ev = document.createEvent("Events");
+ev.initEvent("abort", false, false);
+xhr.dispatchEvent(ev);
+is(called, false, "Should not call listener for untrusted events");
+
+// And now make sure we get our abort
+xhr.abort();
+is(called, true, "Should call listener when we abort");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug819051.html b/dom/base/test/test_bug819051.html
new file mode 100644
index 0000000000..2d6bc500f0
--- /dev/null
+++ b/dom/base/test/test_bug819051.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=819051
+-->
+<head>
+ <title>Test for Bug 819051</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test_headers_append();">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var url = "http://mochi.test:8888/tests/dom/base/test/bug819051.sjs";
+
+function test_headers_append()
+{
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.setRequestHeader("X-appended-to-this", "True");
+ xhr.setRequestHeader("X-appended-to-this", "False");
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ is(xhr.getResponseHeader("X-appended-result"), "True, False", "Headers should have been appended.");
+ test_headers_clear();
+ }
+ }
+
+ xhr.send();
+}
+
+function test_headers_clear()
+{
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.setRequestHeader("X-appended-to-this", "True");
+ xhr.setRequestHeader("Accept", "foo/bar");
+
+ xhr.open("GET", url);
+ xhr.setRequestHeader("X-appended-to-this", "True");
+ xhr.setRequestHeader("Accept", "bar/foo");
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ is(xhr.getResponseHeader("X-Accept-Result"), "bar/foo", "Set headers record should have been cleared by open.");
+ SimpleTest.finish();
+ }
+ }
+
+ xhr.send();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug820909.html b/dom/base/test/test_bug820909.html
new file mode 100644
index 0000000000..90ce908d34
--- /dev/null
+++ b/dom/base/test/test_bug820909.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=820909
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 820909</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=820909">Mozilla Bug 820909</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span dÄ°sabled="CAPS"></span>
+</div>
+<pre id="test">
+<script>
+ var bogusScriptRan = false;
+</script>
+<script type="applÄ°cation/javascript">
+ bogusScriptRan = true;
+</script>
+<script type="application/javascript">
+
+/** Test for Bug 820909 **/
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 1
+ok(!bogusScriptRan, "Script types should be ASCII case-insensitive");
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 2
+var input = document.createElement("input");
+input.type = "radÄ°o";
+is(input.type, "text", "Input types should be ASCII case-insensitive");
+
+// XXX Not sure how to test items 3, 4, 5
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 6
+is(document.querySelector("[dÄ°sabled='caps']"), null,
+ "Checking whether an attribute is case-sensitive for selector-matching " +
+ "purposes should be ASCII case-insensitive on the attr name");
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 7
+$("content").style.width = "0";
+$("content").style.width = "1Ä°n";
+is($("content").style.width, "0px",
+ "CSS unit names should be ASCII case-insensitive");
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 8
+$("content").style.setProperty("animation-name", "a");
+$("content").style.setProperty("-moz-anÄ°mation-name", "b");
+is($("content").style.animationName, "a",
+ "CSS property aliases should be ASCII case-insensitive");
+
+// XXXbz don't know how to test item 9
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 10
+$("content").innerHTML = "<table><input type='hÄ°dden'></table>";
+is($("content").querySelector("input").parentNode, $("content"),
+ "Inputs that aren't actually type='hidden' should not be allowed as " +
+ "table kids");
+
+// XXXbz add test for item 11?
+
+// XXXbz add test for item 12?
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=820909#c7 item 13
+$("content").style.setProperty("animation-name", "a");
+$("content").style.setProperty("anÄ°mation-name", "b");
+is($("content").style.animationName, "a",
+ "CSS property names should be ASCII case-insensitive");
+
+$("content").style.setProperty("display", "none");
+$("content").style.setProperty("display", "Ä°nline");
+is($("content").style.display, "none",
+ "CSS keywords should be ASCII case-insensitive");
+
+$("content").style.setProperty("color", "white");
+$("content").style.setProperty("color", "Ä°ndigo");
+is($("content").style.color, "white",
+ "CSS color names should be ASCII case-insensitive");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug864595.html b/dom/base/test/test_bug864595.html
new file mode 100644
index 0000000000..15d210fa92
--- /dev/null
+++ b/dom/base/test/test_bug864595.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=864595
+-->
+<head>
+ <title>Test for Bug 864595</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=864595">Mozilla Bug 864595</a>
+<div id='editable' style='display:inline-block;'>abcd </div>
+<script type="application/javascript">
+/** Test for Bug 864595 **/
+var range = document.createRange();
+var elt = document.getElementById('editable');
+var eltRect = elt.getBoundingClientRect();
+
+var txtNode = elt.childNodes[0];
+range.setStart(txtNode, 0);
+range.setEnd(txtNode, 5);
+var rect = range.getBoundingClientRect();
+ok(rect.left >= eltRect.left && rect.right <= eltRect.right, "rect.left >= eltRect.left && rect.right <= eltRect.right");
+
+/* Put caret in the space */
+var caretPosX = rect.right + 10;
+var caretPosY = (rect.top + rect.bottom ) / 2;
+var caretRect = document.caretPositionFromPoint(caretPosX, caretPosY).getClientRect();
+ok(caretRect.right >= rect.right, "caretRect.right >= rect.right");
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug868999.html b/dom/base/test/test_bug868999.html
new file mode 100644
index 0000000000..e0dec1229c
--- /dev/null
+++ b/dom/base/test/test_bug868999.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=868999
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 868999</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=868999">Mozilla Bug 869006</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 868999 **/
+
+var r = new Range();
+ok(r, "Range has been created");
+
+var doc = document.implementation.createDocument("", "", null);
+var h1 = doc.createElement('h1');
+doc.appendChild(h1);
+
+var t = doc.createTextNode('Hello world');
+h1.appendChild(t);
+
+r.selectNodeContents(doc);
+is(r.toString(), "Hello world", "new Range() works!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug869000.html b/dom/base/test/test_bug869000.html
new file mode 100644
index 0000000000..152c66de4f
--- /dev/null
+++ b/dom/base/test/test_bug869000.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=869000
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 869000</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=869000">Mozilla Bug 869006</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 869000 **/
+
+var c = new Text();
+ok(c, "Text has been created without content");
+is(c.data, "", "Text.data is ok");
+
+c = new Text('foo');
+ok(c, "Text has been created");
+is(c.data, "foo", "Text.data is ok");
+
+document.getElementById('display').appendChild(c);
+ok(true, "Text has been added to the document");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug869002.html b/dom/base/test/test_bug869002.html
new file mode 100644
index 0000000000..db9a63e754
--- /dev/null
+++ b/dom/base/test/test_bug869002.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=868999
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 868999</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=868999">Mozilla Bug 869002</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 868999 **/
+
+var d = new DocumentFragment();
+ok(d, "DocumentFragment has been created");
+
+document.appendChild(d);
+ok(true, "DocumentFragment has been added to the document");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug869006.html b/dom/base/test/test_bug869006.html
new file mode 100644
index 0000000000..78cca7b553
--- /dev/null
+++ b/dom/base/test/test_bug869006.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=869006
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 869006</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=869006">Mozilla Bug 869006</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 869006 **/
+
+var c = new Comment();
+ok(c, "Comment has been created without content");
+is(c.data, "", "Comment.data is ok");
+
+c = new Comment('foo');
+ok(c, "Comment has been created");
+is(c.data, "foo", "Comment.data is ok");
+
+document.appendChild(c);
+ok(true, "Comment has been added to the document");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug876282.html b/dom/base/test/test_bug876282.html
new file mode 100644
index 0000000000..0dc88d9ee1
--- /dev/null
+++ b/dom/base/test/test_bug876282.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=647518
+-->
+<head>
+ <title>Test for Bug 647518</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=647518">Mozilla Bug 647518</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 647518 **/
+SimpleTest.waitForExplicitFinish();
+var counter = 3;
+
+var called = false;
+var handle1 = window.requestAnimationFrame(function() {
+ called = true;
+});
+ok(handle1 > 0, "Should get back a nonzero handle");
+
+function checker() {
+ --counter;
+ if (counter == 0) {
+ is(called, false, "Canceled callback should not have been called");
+ SimpleTest.finish();
+ } else {
+ window.requestAnimationFrame(checker);
+ }
+}
+window.requestAnimationFrame(checker);
+window.cancelAnimationFrame(handle1);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug891952.html b/dom/base/test/test_bug891952.html
new file mode 100644
index 0000000000..4ec2e7a79b
--- /dev/null
+++ b/dom/base/test/test_bug891952.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=891952
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 891952</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 891952 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ var all = document.all;
+ is(all.content, $("content"), "Should find the content");
+ ok(!("" in all), "Should not have an empty string prop on document.all");
+ is(all[""], undefined, "Should not get empty string on document.all");
+ is(all.namedItem(""), null,
+ "namedItem for empty string should return null on document.all");
+
+ var divs = document.getElementsByTagName("div");
+ ok(!("" in divs), "Should not have an empty string prop on getElementsByTagName");
+ is(divs[""], undefined, "Should not get empty string on getElementsByTagName");
+ is(divs.namedItem(""), null,
+ "namedItem for empty string should return null on getElementsByTagName");
+ var forms = document.forms;
+ ok(!("" in forms), "Should not have an empty string prop on document.forms");
+ is(forms[""], undefined, "Should not get empty string on document.forms");
+ is(forms.namedItem(""), null,
+ "namedItem for empty string should return null on document.forms");
+
+ var form = $("form");
+ ok(!("" in form), "Should not have an empty string prop on form");
+ is(form[""], undefined, "Should not get empty string on form");
+
+ var formEls = $("form").elements;
+ ok(!("" in formEls), "Should not have an empty string prop on form.elements");
+ is(formEls[""], undefined, "Should not get empty string on form.elements");
+ is(formEls.namedItem(""), null,
+ "namedItem for empty string should return null on form.elements");
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=891952">Mozilla Bug 891952</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id=""></div>
+ <div name=""></div>
+ <form id="form" name="">
+ <input name="">
+ </form>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug894874.html b/dom/base/test/test_bug894874.html
new file mode 100644
index 0000000000..603738a9df
--- /dev/null
+++ b/dom/base/test/test_bug894874.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=894874
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 894874</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- IMPORTANT: Keep the sheets in this order! -->
+ <link rel="stylesheet" type="text/css" href="data:text/css,">
+ <link rel="stylesheet" type="text/css" title="one" href="data:text/css,">
+ <link rel="stylesheet" type="text/css" title="two" href="data:text/css,">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 894874 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(document.styleSheets[0].disabled, false,
+ "Sheet with no title should be enabled");
+ is(document.styleSheets[1].disabled, false,
+ "First preferred sheet should be enabled");
+ is(document.styleSheets[2].disabled, true,
+ "Second preferred sheet should be disabled");
+ is(document.selectedStyleSheetSet, "one", "Sheet one is enabled");
+ document.styleSheets[0].disabled = true;
+ document.styleSheets[2].disabled = false;
+ ok(document.selectedStyleSheetSet === null,
+ "Sheet one and sheet two are both enabled");
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=894874">Mozilla Bug 894874</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug895974.html b/dom/base/test/test_bug895974.html
new file mode 100644
index 0000000000..7077e2c996
--- /dev/null
+++ b/dom/base/test/test_bug895974.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=895974
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 895974</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 895974 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var frag = document.createDocumentFragment();
+ var span = document.createElement("span");
+ var div = document.createElement("div");
+ var text = document.createTextNode("help");
+ frag.appendChild(document.createTextNode("fail"));
+ frag.appendChild(span);
+ frag.appendChild(text);
+ frag.appendChild(div);
+ frag.appendChild(document.createTextNode("fail"));
+
+ is(text.nextElementSibling, div, "nextElementSibling should work on text");
+ is(text.previousElementSibling, span,
+ "previousElementSibling should work on text");
+
+ is(document.firstElementChild, document.documentElement,
+ "firstElementChild should work on document");
+ is(document.lastElementChild, document.documentElement,
+ "lastElementChild should work on document");
+ is(document.children.length, 1, "Document has one element kid");
+ is(document.children[0], document.documentElement,
+ "Document only element child is <html>");
+
+ is(frag.firstElementChild, span,
+ "firstElementChild should work on document fragment");
+ is(frag.lastElementChild, div,
+ "lastElementChild should work on document fragment");
+ is(frag.children.length, 2, "Document fragment has two element kids");
+ is(frag.children[0], span, "Document fragment first element child is span");
+ is(frag.children[1], div, "Document fragment second element child is div");
+
+ is(document.documentElement.firstElementChild, document.head,
+ "firstElementChild should work on element");
+ is(document.documentElement.lastElementChild, document.body,
+ "lastElementChild should work on element");
+ is(document.documentElement.children.length, 2, "<html> has two element kids");
+ is(document.documentElement.children[0], document.head,
+ "<html> first element child is head");
+ is(document.documentElement.children[1], document.body,
+ "<html> second element child is body");
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=895974">Mozilla Bug 895974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug907892.html b/dom/base/test/test_bug907892.html
new file mode 100644
index 0000000000..31537aaee7
--- /dev/null
+++ b/dom/base/test/test_bug907892.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=907892
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 907892</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 907892 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var expectedMessages = 2;
+ window.onmessage = function (ev) {
+ if (ev.data.sandboxed) {
+ ok(ev.data.threw,
+ "Should have thrown when setting document.domain in sandboxed iframe");
+ } else {
+ ok(!ev.data.threw,
+ "Should not have thrown when setting document.domain in iframe");
+ }
+
+ --expectedMessages;
+ if (expectedMessages == 0) {
+ SimpleTest.finish();
+ }
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=907892">Mozilla Bug 907892</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<!-- Set all the sandbox flags to "allow" to make sure we cover that case -->
+<iframe
+ sandbox="allow-same-origin allow-scripts allow-forms allow-top-navigation alllow-pointer-lock"
+ src="http://test1.example.org/tests/dom/base/test/file_bug907892.html?sandboxed">
+</iframe>
+<iframe
+ src="http://test1.example.org/tests/dom/base/test/file_bug907892.html?normal">
+</iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug913761.html b/dom/base/test/test_bug913761.html
new file mode 100644
index 0000000000..6f08e5a1d2
--- /dev/null
+++ b/dom/base/test/test_bug913761.html
@@ -0,0 +1,40 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=913761
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 913761 - basic support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=913761">Mozilla Bug 913761</a>
+<script type="application/javascript">
+
+ var transportChannel = new MessageChannel();
+ transportChannel.port1.onmessage = function (event) {
+ ok(true, 'Port Returned.');
+ var portToService = event.data.port;
+ portToService.onmessage = function () {
+ ok(true, "message received");
+ SimpleTest.finish();
+ };
+ portToService.postMessage('READY?');
+ }
+
+ var serviceChannel = new MessageChannel();
+ serviceChannel.port1.onmessage = function (event) {
+ if (event.data == 'READY?') {
+ this.postMessage('READY!');
+ }
+ }
+
+ transportChannel.port2.postMessage({ port: serviceChannel.port2}, [serviceChannel.port2]);
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug922681.html b/dom/base/test/test_bug922681.html
new file mode 100644
index 0000000000..16b91f70be
--- /dev/null
+++ b/dom/base/test/test_bug922681.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=922681
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 922681</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function testInnerHTMLParserInsertionMode() {
+
+ function testInnerHTML(el, input, output) {
+ el.innerHTML = input;
+ is(el.innerHTML, output, el.tagName.toLowerCase() + ': "' + input + '"');
+ }
+
+ var c;
+
+ c = document.createElement("html");
+ testInnerHTML(c, "", "<head></head><body></body>");
+ testInnerHTML(c, "xyz", "<head></head><body>xyz</body>");
+ testInnerHTML(c, "<input>", "<head></head><body><input></body>");
+
+ c = document.createElement("colgroup");
+ testInnerHTML(c, "abcdef", "");
+ testInnerHTML(c, "", "");
+ testInnerHTML(c, "\n", "\n");
+ testInnerHTML(c, "<col>", "<col>");
+
+ c = document.createElement("select");
+ testInnerHTML(c, "123", "123");
+ testInnerHTML(c, "<input>", "");
+ testInnerHTML(c, "\0", "");
+ testInnerHTML(c, "<col>", "");
+ testInnerHTML(c, "<option>", "<option></option>");
+
+ c = document.createElement("head");
+ testInnerHTML(c, "123", "123");
+ testInnerHTML(c, "\n", "\n");
+
+ c = document.createElement("frameset");
+ testInnerHTML(c, "456", "");
+ testInnerHTML(c, "\n", "\n");
+ testInnerHTML(c, "<input>", "");
+ testInnerHTML(c, "\0", "");
+
+ c = document.createElement("table");
+ testInnerHTML(c, "abc", "abc");
+ testInnerHTML(c, "<td>", "<tbody><tr><td></td></tr></tbody>");
+ testInnerHTML(c, "</body>", "");
+ testInnerHTML(c, "<input>", "<input>");
+
+ c = document.createElement("tr");
+ testInnerHTML(c, "xyz", "xyz");
+ testInnerHTML(c, "<td>", "<td></td>");
+ testInnerHTML(c, "</body>", "");
+ testInnerHTML(c, "<table>", "");
+
+ c = document.createElement("td");
+ testInnerHTML(c, "789", "789");
+ testInnerHTML(c, "\0", "");
+ testInnerHTML(c, "<td>", "");
+
+ c = document.createElement("th");
+ testInnerHTML(c, "789", "789");
+ testInnerHTML(c, "\0", "");
+ testInnerHTML(c, "</tr>", "");
+
+ c = document.createElement("caption");
+ testInnerHTML(c, "xyz", "xyz");
+ testInnerHTML(c, "\0", "");
+ testInnerHTML(c, "<td>", "");
+ testInnerHTML(c, "<dd>", "<dd></dd>");
+ testInnerHTML(c, "<body>", "");
+
+ function testTableBody(tag) {
+ var e = document.createElement(tag);
+ testInnerHTML(e, "abc", "abc");
+ testInnerHTML(e, "<td>", "<tr><td></td></tr>");
+ testInnerHTML(e, "</body>", "");
+ testInnerHTML(e, "<input>", "<input>");
+ }
+ testTableBody("thead");
+ testTableBody("tbody");
+ testTableBody("tfoot");
+
+ c = document.createElement("template");
+ testInnerHTML(c, "abc", "abc");
+ testInnerHTML(c, "<td>", "<td></td>");
+ testInnerHTML(c, "</template>", "");
+ testInnerHTML(c, "<input>", "<input>");
+
+ c = document.createElement("div");
+ testInnerHTML(c, "abc", "abc");
+ testInnerHTML(c, "<td>", "");
+ testInnerHTML(c, "</body>", "");
+ testInnerHTML(c, "<input>", "<input>");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testInnerHTMLParserInsertionMode);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=922681">Mozilla Bug 922681</a>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug927196.html b/dom/base/test/test_bug927196.html
new file mode 100644
index 0000000000..3e1c829c57
--- /dev/null
+++ b/dom/base/test/test_bug927196.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=426308
+-->
+<head>
+ <title>Test for Bug 426308</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=927196">Mozilla Bug 927196</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 927196 **/
+
+function startTest() {
+ req = new XMLHttpRequest({mozSystem: true});
+ is(req.mozAnon, true, "XMLHttpRequest should be mozAnon (3)");
+
+ req = new XMLHttpRequest({mozAnon: true});
+ is(req.mozAnon, true, "XMLHttpRequest should be mozAnon (4)");
+ is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem (4)");
+
+ req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(req.mozAnon, true, "XMLHttpRequest should be mozAnon (5)");
+ is(req.mozSystem, true, "XMLHttpRequest should be mozSystem (5)");
+
+ req = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ is(req.mozAnon, true, "XMLHttpRequest should be mozAnon (6)");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var req = new XMLHttpRequest({mozAnon: true});
+is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem");
+
+req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+is(req.mozAnon, true, "XMLHttpRequest should be mozAnon (2)");
+is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem (2)");
+
+addLoadEvent(function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug945152.html b/dom/base/test/test_bug945152.html
new file mode 100644
index 0000000000..9075db0746
--- /dev/null
+++ b/dom/base/test/test_bug945152.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=945152
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 945152</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=945152">Mozilla Bug 945152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+function translateChrome(uriStr) {
+ const { Cc, Ci } = SpecialPowers;
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
+ let uri = ios.newURI(uriStr, null, ios.newURI(document.baseURI));
+ return chromeReg.convertChromeURL(uri).spec;
+}
+
+function runTest() {
+ var worker = new Worker("file_bug945152_worker.js");
+
+ worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.filename + ":" + event.lineno + ":" + event.colno + ": " + event.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(translateChrome("file_bug945152.jar"));
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.mapped_arraybuffer.enabled", true]]}, function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug962251.html b/dom/base/test/test_bug962251.html
new file mode 100644
index 0000000000..fff381676c
--- /dev/null
+++ b/dom/base/test/test_bug962251.html
@@ -0,0 +1,244 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=962251
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 962251</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function Test_ElementsInTheSameDocument() {
+ var button1 = document.getElementById("button1");
+ var button2 = document.getElementById("button2");
+
+ button1.focus();
+ is(button1.id, document.activeElement.id, "How did we call focus on button1 and it did" +
+ " not become the active element?");
+
+ var getBlurEvent = false;
+ button1.addEventListener("blur", function(aEvent) {
+ is(aEvent.target.id, "button1", "Button1 should lose focus.");
+ ok(aEvent.relatedTarget, "The relatedTarget should not be null.");
+ is(aEvent.relatedTarget.id, "button2", "The relatedTarget should be button2.");
+ getBlurEvent = true;
+ }, {once: true});
+
+ button2.addEventListener("focus", function(aEvent) {
+ ok(getBlurEvent, "Must get blur event first.");
+ is(aEvent.target.id, "button2", "Button2 should be focused.");
+ ok(aEvent.relatedTarget, "The relatedTarget should not be null.");
+ is(aEvent.relatedTarget.id, "button1", "The relatedTarget should be button1.");
+ button2.blur();
+ }, {once: true});
+
+ button2.addEventListener("blur", function(aEvent) {
+ is(aEvent.target.id, "button2", "Button2 should lose focus.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null.");
+ runTests();
+ }, {once: true});
+
+ button2.focus();
+ }
+
+ function Test_ElementsInDifferentDocument() {
+ var button2 = document.getElementById("button2");
+ button2.focus();
+ button2.addEventListener("blur", function(aEvent) {
+ is(aEvent.target.id, "button2", "Button2 should lose focus.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null, since it's in another document.");
+ }, {once: true});
+
+ var iframe = document.createElement("iframe");
+ iframe.id = "iframe";
+ iframe.src = "iframe_bug962251.html";
+ window.addEventListener("message", function(aEvent) {
+ if (aEvent.data == "runNextTest") {
+ runTests();
+ }
+ }, {once: true});
+ document.getElementById("content").appendChild(iframe);
+ }
+
+ function Test_FocusEventOnWindow() {
+ var iframe1 = document.createElement("iframe");
+ iframe1.id = "iframe1";
+ iframe1.src = "about:blank";
+
+ document.getElementById("content").appendChild(iframe1);
+ document.getElementById("button2").focus();
+ var iframe = document.getElementById("iframe");
+ var expectedEventTarget = [iframe.contentDocument, iframe.contentWindow];
+ var expectedEventTarget1 = [iframe1.contentDocument, iframe1.contentWindow];
+ iframe.contentWindow.addEventListener("focus", function onFocus(aEvent) {
+ var eventTarget = expectedEventTarget.shift();
+ ok(aEvent.target === eventTarget, "Get expected focus event target.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ if (!expectedEventTarget.length) {
+ iframe.contentWindow.removeEventListener("focus", onFocus, true);
+ runTests();
+ }
+ }, true);
+ iframe1.contentWindow.addEventListener("focus", function onFocus(aEvent) {
+ var eventTarget = expectedEventTarget1.shift();
+ ok(aEvent.target === eventTarget, "Get expected focus event target.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ if (!expectedEventTarget1.length) {
+ iframe1.contentWindow.removeEventListener("focus", onFocus, true);
+ // Append items for blur event listener
+ expectedEventTarget1.push(iframe1.contentDocument);
+ expectedEventTarget1.push(iframe1.contentWindow);
+ iframe.contentWindow.focus();
+ }
+ }, true);
+ iframe1.contentWindow.addEventListener("blur", function onBlur(aEvent) {
+ var eventTarget = expectedEventTarget1.shift();
+ ok(aEvent.target === eventTarget, "Get expected blur event target.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ if (!expectedEventTarget1.length) {
+ iframe1.contentWindow.removeEventListener("blur", onBlur, true);
+ }
+ }, true);
+ iframe1.contentWindow.focus();
+ }
+
+ function Test_SetFocusInBlurEvent() {
+ var button1 = document.getElementById("button1");
+ var button2 = document.getElementById("button2");
+ var button3 = document.getElementById("button3");
+
+ button1.focus();
+ is(button1.id, document.activeElement.id, "document.activeElement.id is button1");
+
+ button1.addEventListener("blur", function(aEvent) {
+ info("button1 blur");
+ is(aEvent.relatedTarget.id, button2.id, "relatedTarget.id should be button2.");
+ button3.focus();
+ }, {once: true});
+ button1.addEventListener("focus", function(aEvent) {
+ info("button1 focus");
+ }, {once: true});
+
+ button2.addEventListener("blur", function(aEvent) {
+ info("button2 blur");
+ }, {once: true});
+ button2.addEventListener("focus", function(aEvent) {
+ info("button2 focus");
+ }, {once: true});
+
+ button3.addEventListener("blur", function(aEvent) {
+ info("button3 blur");
+ }, {once: true});
+ button3.addEventListener("focus", function(aEvent) {
+ info("button3 focus");
+ ok(aEvent.relatedTarget === null, "aEvent.relatedTarget is null.");
+ runTests();
+ }, {once: true});
+
+ button2.focus();
+ }
+
+ function Test_ListenFocusBlurEventOnWindow1() {
+ var button2 = document.getElementById("button2");
+ button2.focus();
+
+ var iframe = document.getElementById("iframe");
+ var input = iframe.contentDocument.getElementById("textinput");
+ var expectedEventTarget = [button2, document, window];
+ var expectedEventTarget1 = [iframe.contentDocument, iframe.contentWindow, input];
+ window.addEventListener("blur", function onBlur(aEvent) {
+ var item = expectedEventTarget.shift();
+ ok(aEvent.target === item, "Get an expected blur event.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ if (!expectedEventTarget.length) {
+ iframe.contentWindow.removeEventListener("blur", onBlur, true);
+ }
+ }, true);
+ iframe.contentWindow.addEventListener("focus", function onFocus(aEvent) {
+ var item = expectedEventTarget1.shift();
+ ok(aEvent.target === item, "Get an expected focus event.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ if (!expectedEventTarget1.length) {
+ iframe.contentWindow.removeEventListener("focus", onFocus, true);
+ runTests();
+ }
+ }, true);
+
+ input.focus();
+ }
+
+ function Test_ListenFocusBlurEventOnWindow2() {
+ var iframe = document.getElementById("iframe");
+ var input = iframe.contentDocument.getElementById("textinput");
+ var input1 = iframe.contentDocument.getElementById("textinput1");
+
+ ok(iframe.contentDocument.activeElement === input, "Current focused element should be input.");
+ iframe.contentWindow.addEventListener("focus", function(aEvent) {
+ ok(aEvent.target === input1, "Input1 is focused.");
+ ok(aEvent.relatedTarget === input, "relatedTarget should be input.");
+ runTests();
+ }, {capture: true, once: true});
+ iframe.contentWindow.addEventListener("blur", function(aEvent) {
+ ok(aEvent.target === input, "Input is not focused.");
+ ok(aEvent.relatedTarget === input1, "relatedTarget should be input1.");
+ }, {capture: true, once: true});
+
+ input1.focus();
+ }
+
+ function Test_ListenFocusBlurEventOnWindow3() {
+ var iframe = document.getElementById("iframe");
+ var input1 = iframe.contentDocument.getElementById("textinput1");
+
+ ok(iframe.contentDocument.activeElement === input1, "Current focused element should be input1.");
+ iframe.contentWindow.addEventListener("blur", function(aEvent) {
+ ok(aEvent.target === input1, "Input1 is not focused.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ runTests();
+ }, {capture: true, once: true});
+
+ input1.blur();
+ }
+
+ var tests = [
+ Test_ElementsInTheSameDocument,
+ Test_ElementsInDifferentDocument,
+ Test_FocusEventOnWindow,
+ Test_SetFocusInBlurEvent,
+ Test_ListenFocusBlurEventOnWindow1,
+ Test_ListenFocusBlurEventOnWindow2,
+ Test_ListenFocusBlurEventOnWindow3
+ ];
+
+ function runTests()
+ {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ window.setTimeout(function () {
+ test();
+ });
+ }
+
+ </script>
+</head>
+<body onload="runTests();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=962251">Mozilla Bug 962251</a>
+<p id="display"></p>
+<div id="content">
+ <button id="button1">1</button>
+ <button id="button2">2</button>
+ <button id="button3">3</button>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/test_bug976673.html b/dom/base/test/test_bug976673.html
new file mode 100644
index 0000000000..ff656ef221
--- /dev/null
+++ b/dom/base/test/test_bug976673.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=976673
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 976673</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=976673">Mozilla Bug 976673</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<input id="input" onfocus="event.target.value = event.type;"
+ onblur="event.target.value = event.type;">
+<button id="button">set focus</button>
+<iframe id="iframe" src="http://example.org:80/tests/dom/base/test/iframe_bug976673.html"></iframe>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+// In e10s mode, ContentCacheInChild tries to retrieve selected text and
+// caret position when IMEContentObserver notifies IME of focus. At this time,
+// we hit assertion in ContentIterator.
+SimpleTest.expectAssertions(0, 6);
+
+window.addEventListener("mousedown", function (aEvent) { aEvent.preventDefault(); });
+
+function testSetFocus(aEventType, aCallback)
+{
+ var description = "Setting focus from " + aEventType + " handler: ";
+
+ var iframe = document.getElementById("iframe");
+ iframe.contentWindow.focus();
+
+ window.addEventListener("message", function (aEvent) {
+ is(aEvent.data, "input-value: focus",
+ description + "<input> in the iframe should get focus");
+
+
+ var input = document.getElementById("input");
+ input.value = "";
+
+ var button = document.getElementById("button");
+
+ var movingFocus = false;
+ button.addEventListener(aEventType,
+ function (event) {
+ movingFocus = true;
+ input.focus();
+ event.preventDefault();
+ button.removeEventListener(aEventType, arguments.callee, true);
+ }, true);
+
+ synthesizeMouseAtCenter(button, {});
+
+ window.addEventListener("message", function (event) {
+ if (movingFocus) {
+ is(event.data, "input-value: blur",
+ description + "<input> in the iframe should get blur");
+ is(input.value, "focus",
+ description + "<input> in the parent should get focus");
+ } else {
+ is(event.data, "input-value: focus",
+ description + "<input> in the iframe should keep having focus");
+ }
+
+ setTimeout(aCallback, 0);
+ }, {once: true});
+
+ iframe.contentWindow.postMessage("check", "*");
+ }, {once: true});
+
+ iframe.contentWindow.postMessage("init", "*");
+}
+
+function runTests()
+{
+ testSetFocus("mousedown",
+ function () {
+ testSetFocus("mouseup",
+ function () {
+ testSetFocus("click",
+ function () {
+ testSetFocus("DoNothing", // testing wihout moving focus by script
+ function () {
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug982153.html b/dom/base/test/test_bug982153.html
new file mode 100644
index 0000000000..c684c122e6
--- /dev/null
+++ b/dom/base/test/test_bug982153.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=982153
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 982153</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+
+var sc = document.createElement("script");
+var error = null;
+sc.textContent = "try {\n reference_error; } catch(e) { error = e; }";
+document.documentElement.appendChild(sc);
+is(error.lineNumber, 2, "Error line number must be correct");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982153">Mozilla Bug 982153</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug999456.html b/dom/base/test/test_bug999456.html
new file mode 100644
index 0000000000..ee1b3b5087
--- /dev/null
+++ b/dom/base/test/test_bug999456.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=999456
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 999456</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 999456 **/
+
+ SimpleTest.waitForExplicitFinish();
+ addEventListener("load", function (e) {
+ is(e.cancelable, false, "Load events should not be cancelable");
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=999456">Mozilla Bug 999456</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_caretPositionFromPoint.html b/dom/base/test/test_caretPositionFromPoint.html
new file mode 100644
index 0000000000..5861ff685b
--- /dev/null
+++ b/dom/base/test/test_caretPositionFromPoint.html
@@ -0,0 +1,131 @@
+<!doctype html>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=654352
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <title>Test for Bug 654352</title>
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+ }
+
+ #a {
+ font-family: Ahem;
+ padding: 10px;
+ border: 8px solid black;
+ width: 450px;
+ }
+
+ #test5 {
+ height: 100px;
+ }
+
+ textarea, input {
+ -moz-appearance: none;
+ }
+ </style>
+<script>
+ function convertEmToPx(aEm) {
+ // Assumes our base font size is 16px = 12pt = 1.0em.
+ var pxPerEm = 16.0 / 1.0;
+ return pxPerEm * aEm;
+ }
+
+ function checkOffsetsFromPoint(aX, aY, aExpected, aElementName='no-name') {
+ var cp = document.caretPositionFromPoint(aX, aY);
+ if (!cp) {
+ ok(false, 'caretPositionFromPoint returned null for point: (' + aX + ', ' + aY + ')');
+ return;
+ }
+
+ ok(aExpected == cp.offset, 'expected offset at (' + aX + ', ' + aY + ') [' + aElementName + ']: ' + aExpected + ', got: ' + cp.offset);
+ }
+
+ function doTesting() {
+ var test1Element = document.getElementById("test1");
+ var test1Rect = test1Element.getBoundingClientRect();
+
+ // Check the first and last characters of the basic div.
+ checkOffsetsFromPoint(Math.round(test1Rect.left + 1), Math.round(test1Rect.top + 1), 0, 'test1');
+ checkOffsetsFromPoint(Math.round(test1Rect.left + test1Rect.width - 1), Math.round(test1Rect.top + 1), 13, 'test1');
+
+ // Check a middle character in the second line of the div.
+ // To do this, we calculate 7em in from the left of the bounding
+ // box, and convert this to PX. (Hence the reason we need the AHEM
+ // font).
+ var pixelsLeft = convertEmToPx(7);
+ var test2Element = document.getElementById("test2");
+ var test2Rect = test2Element.getBoundingClientRect();
+ checkOffsetsFromPoint(Math.round(test2Rect.left + pixelsLeft + 1), Math.round(test2Rect.top + 1), 7, 'test2');
+
+ // Check the first and last characters of the textarea.
+ var test3Element = document.getElementById('test3');
+ var test3Rect = test3Element.getBoundingClientRect();
+ checkOffsetsFromPoint(test3Rect.left + 5, test3Rect.top + 5, 0, 'test3');
+ checkOffsetsFromPoint(Math.round(test3Rect.left + test3Rect.width - 15), Math.round(test3Rect.top + 5), 3, 'test3');
+
+ // Check the first and last characters of the input.
+ var test4Element = document.getElementById('test4');
+ var test4Rect = test4Element.getBoundingClientRect();
+ checkOffsetsFromPoint(test4Rect.left + 5, test4Rect.top + 5, 0, 'test4');
+ checkOffsetsFromPoint(Math.round(test4Rect.left + test4Rect.width - 10), Math.round(test4Rect.top + 10), 6, 'test4');
+
+ // Check to make sure that x or y outside the viewport returns null.
+ var nullCp1 = document.caretPositionFromPoint(-10, 0);
+ ok(!nullCp1, "caret position with negative x should be null");
+ var nullCp2 = document.caretPositionFromPoint(0, -10);
+ ok(!nullCp2, "caret position with negative y should be null");
+ var nullCp3 = document.caretPositionFromPoint(9000, 0);
+ ok(!nullCp3, "caret position with x > viewport width should be null");
+ var nullCp4 = document.caretPositionFromPoint(0, 9000);
+ ok(!nullCp4, "caret position with x > viewport height should be null");
+
+ // Check a point within the bottom whitespace of the input.
+ var test5Element = document.getElementById('test5');
+ var test5Rect = test5Element.getBoundingClientRect();
+ var test5x = test5Rect.left + 5;
+ var test5y = test5Rect.bottom - 10;
+
+ todo(false, "test5Rect: (" + test5Rect.top + ", " + test5Rect.left + ", " + test5Rect.width + ", " + test5Rect.height + ")");
+ checkOffsetsFromPoint(test5x, test5y, 0, 'test5');
+
+ // Check the first and last characters of the numeric input.
+ var test6Element = document.getElementById("test6");
+ var test6Rect = test6Element.getBoundingClientRect();
+ checkOffsetsFromPoint(Math.round(test6Rect.left + 4),
+ Math.round(test6Rect.top + (test6Rect.height / 2)),
+ 0, "test6");
+ checkOffsetsFromPoint(Math.round(test6Rect.left + test6Rect.width - 36),
+ Math.round(test6Rect.top + (test6Rect.height / 2)),
+ 5, "test6");
+
+ // Check the first and last characters of the transformed div.
+ var test7Element = document.getElementById('test7');
+ var test7Rect = test7Element.getBoundingClientRect();
+ checkOffsetsFromPoint(Math.round(test7Rect.left + 1), Math.round(test7Rect.top + 1), 0, 'test7');
+ checkOffsetsFromPoint(Math.round(test7Rect.right - 1), Math.round(test7Rect.top + 1), 13, 'test7');
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body onload="doTesting();">
+<div id="a" contenteditable><span id="test1">abc, abc, abc</span><br>
+<span id="test2" style="color: blue;">abc, abc, abc</span><br>
+<textarea id="test3">abc</textarea><input id="test4" value="abcdef"><br><br>
+<marquee>marquee</marquee>
+<!-- Translate test7 while staying within confines of the test viewport -->
+<div id="test7" style="transform: translate(140px, -20px); display: inline-block;">abc, abc, abc</div>
+</div>
+<input id="test5" value="The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down a very deep well. Either the well was very deep, or she fell very slowly, for she had plenty of time as she went down to look about her and to wonder what was going to happen next. First, she tried to look down and make out what she was coming to, but it was too dark to see anything; then she looked at the sides of the well, and noticed that they were filled with cupboards and book-shelves; here and there she saw maps and pictures hung upon pegs. She took down a jar from one of the shelves as she passed; it was labelled `ORANGE MARMALADE', but to her great disappointment it was empty: she did not like to drop the jar for fear of killing somebody, so managed to put it into one of the cupboards as she fell past it." type="text">
+<input id="test6" type="number" style="width:150px; height:57px;" value="31415"><br>
+</body>
+</html>
diff --git a/dom/base/test/test_change_policy.html b/dom/base/test/test_change_policy.html
new file mode 100644
index 0000000000..536b7ed776
--- /dev/null
+++ b/dom/base/test/test_change_policy.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 1101288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<!--
+This checks if the right policies are applied from a given string when the policy is changed after the document has been loaded.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1101288
+-->
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * Listen for notifications from the child.
+ * These are sent in case of error, or when the loads we await have completed.
+ */
+window.addEventListener("message", function(event) {
+ if (event.data == "childLoadComplete") {
+ // all loads happen, continue the test.
+ advance();
+ }
+});
+
+/**
+ * helper to perform an XHR.
+ */
+function doXHR(aUrl, onSuccess, onFail) {
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = "json";
+ xhr.onload = function () {
+ onSuccess(xhr);
+ };
+ xhr.onerror = function () {
+ onFail(xhr);
+ };
+ xhr.open('GET', aUrl, true);
+ xhr.send(null);
+}
+
+/**
+ * Grabs the results via XHR and passes to checker.
+ */
+function checkIndividualResults(aTestname, aExpectedReferrer, aName) {
+ doXHR('/tests/dom/base/test/referrer_change_server.sjs?action=get-test-results',
+ function(xhr) {
+ var results = xhr.response;
+ info(JSON.stringify(xhr.response));
+
+ for (i in aName) {
+ ok(aName[i] in results.tests, aName[i] + " tests have to be performed.");
+ is(results.tests[aName[i]].policy, aExpectedReferrer[i], aTestname + ' --- ' + results.tests[aName[i]].policy + ' (' + results.tests[aName[i]].referrer + ')');
+ }
+ advance();
+ },
+ function(xhr) {
+ ok(false, "Can't get results from the counter server.");
+ SimpleTest.finish();
+ });
+}
+
+function resetState() {
+ doXHR('/tests/dom/base/test/referrer_change_server.sjs?action=resetState',
+ advance,
+ function(xhr) {
+ ok(false, "error in reset state");
+ SimpleTest.finish();
+ });
+}
+
+
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It resets the counter, then performs two tests in sequence using
+ * the same iframe.
+ */
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+ var sjs = "/tests/dom/base/test/referrer_change_server.sjs?action=generate-policy-test";
+
+ yield SpecialPowers.pushPrefEnv(
+ { set: [["network.http.referer.disallowCrossSiteRelaxingDefault", false]] },
+ advance
+ );
+
+ yield resetState();
+ var name = "no-referrer-unsafe-url";
+ yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name + "&newPolicy=" + escape('unsafe-url');
+ yield checkIndividualResults("unsafe-url (changed) with no-referrer first", ["full"], [name+'unsafe-url']);
+
+ yield resetState();
+ var name = "origin-no-referrer";
+ yield iframe.src = sjs + "&policy=" + escape('origin') + "&name=" + name + "&newPolicy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer (changed) with origin first", ["none"], [name+'no-referrer']);
+
+ yield resetState();
+ var name = "unsafe-url-no-referrer";
+ yield iframe.src = sjs + "&policy=" + escape('unsafe-url') + "&name=" + name + "&newPolicy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer (changed) with unsafe-url first", ["none"], [name+'no-referrer']);
+
+ sjs = "/tests/dom/base/test/referrer_change_server.sjs?action=generate-policy-test2";
+
+ yield resetState();
+ var name = "no-referrer-unsafe-url";
+ yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name + "&newPolicy=" + escape('unsafe-url');
+ yield checkIndividualResults("unsafe-url (changed) with no-referrer first", ["full"], [name+'unsafe-url']);
+
+ yield resetState();
+ var name = "origin-no-referrer";
+ yield iframe.src = sjs + "&policy=" + escape('origin') + "&name=" + name + "&newPolicy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer (changed) with origin first", ["none"], [name+'no-referrer']);
+
+ yield resetState();
+ var name = "unsafe-url-no-referrer";
+ yield iframe.src = sjs + "&policy=" + escape('unsafe-url') + "&name=" + name + "&newPolicy=" + escape('no-referrer');
+ yield checkIndividualResults("no-referrer (changed) with unsafe-url first", ["none"], [name+'no-referrer']);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_clearTimeoutIntervalNoArg.html b/dom/base/test/test_clearTimeoutIntervalNoArg.html
new file mode 100644
index 0000000000..e1d60022fb
--- /dev/null
+++ b/dom/base/test/test_clearTimeoutIntervalNoArg.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for clearTimeout/clearInterval with no arguments not throwing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ clearTimeout();
+}, "clearTimeout with no args should not throw ");
+test(function() {
+ clearInterval();
+}, "clearInterval with no args should not throw ");
+</script>
diff --git a/dom/base/test/test_clipboard_nbsp.html b/dom/base/test/test_clipboard_nbsp.html
new file mode 100644
index 0000000000..a40c7246f6
--- /dev/null
+++ b/dom/base/test/test_clipboard_nbsp.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=359303
+-->
+<head>
+ <meta charset="utf-8" />
+ <title>Test for copying non-breaking spaces to the clipboard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=359303">Mozilla Bug 359303</a>
+
+<p id="display"></p>
+
+<div id="content">
+<!-- In a plain-text editable control (such as a textarea or textinput), copying to clipboard should
+preserve non-breaking spaces. -->
+<input
+ id="input-with-non-breaking-spaces"
+ value="Input content: This town is 100&nbsp;km away / «&nbsp;Est-ce Paris&nbsp;?&nbsp;» / Consecutive non-breaking spaces: '&nbsp;&nbsp;'">
+<textarea id="textarea-with-non-breaking-spaces">
+Textarea content:
+- This town is 100&nbsp;km away.
+- «&nbsp;Est-ce Paris&nbsp;?&nbsp;»
+- Consecutive non-breaking spaces: "&nbsp;&nbsp;"
+</textarea>
+
+<!-- In a content-editable div, copying to clipboard should preserve non-breaking spaces.
+However, for compatibility with what other browsers currently do, the behavior of replacing non-breaking spaces by spaces is preserved for now.
+See https://bugzilla.mozilla.org/show_bug.cgi?id=359303#c145
+-->
+<div contenteditable="true" id="content-editable-with-non-breaking-spaces">
+Content-editable content:
+- This town is 100&nbsp;km away.
+- «&nbsp;Est-ce Paris&nbsp;?&nbsp;»
+- Consecutive non-breaking spaces: "&nbsp;&nbsp;"
+</div>
+
+<!-- In non-editable HTML nodes, like this paragraph, copying to clipboard should preserve non-breaking
+spaces.
+However, for compatibility with what other browsers currently do, the behavior of replacing non-breaking spaces by spaces is preserved for now.
+See https://bugzilla.mozilla.org/show_bug.cgi?id=359303#c145
+-->
+<p id="paragraph-with-non-breaking-spaces">
+Paragraph content:
+- This town is 100&nbsp;km away.
+- «&nbsp;Est-ce Paris&nbsp;?&nbsp;»
+- Consecutive non-breaking spaces: "&nbsp;&nbsp;"
+</p>
+</div>
+
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+// Helper: for the given element, select all its content, execute a "Copy" command,
+// and return the text put into the clipboard.
+async function clipboardTextForElementId(aDomId, aExpectedString) {
+ let textContainer = document.getElementById(aDomId);
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+
+ if (textContainer.select) {
+ // Select the entire text in the input or textarea
+ textContainer.select();
+ } else {
+ // Select text node in element.
+ let r = document.createRange();
+ r.setStart(textContainer, 0);
+ r.setEnd(textContainer, 1);
+ sel.addRange(r);
+ }
+
+ let copiedText = await SimpleTest.promiseClipboardChange(
+ function compare(aValue) {
+ return aValue.includes(aExpectedString);
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ "text/plain");
+ return copiedText;
+}
+
+/** Test for Bug 359303 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async function() {
+ let iValue = await clipboardTextForElementId("input-with-non-breaking-spaces", "Input");
+ ok(iValue.includes("100 km"), "NBSP between two characters should be preserved");
+ ok(iValue.includes("« "), "A single NBSP near a punctuation mark should be preserved");
+ ok(iValue.includes(" »"), "A single NBSP near a punctuation mark should be preserved");
+ ok(iValue.includes(" ? "), "NBSPs before *and* after a character should be preserved");
+ ok(iValue.includes("  "), "Consecutive NBSPs should be preserved");
+
+ let tValue = await clipboardTextForElementId("textarea-with-non-breaking-spaces", "Textarea");
+ ok(tValue.includes("100 km"), "NBSP between two characters should be preserved");
+ ok(tValue.includes("« "), "A single NBSP near a punctuation mark should be preserved");
+ ok(tValue.includes(" »"), "A single NBSP near a punctuation mark should be preserved");
+ ok(tValue.includes(" ? "), "NBSPs before *and* after a character should be preserved");
+ ok(tValue.includes("  "), "Consecutive NBSPs should be preserved");
+
+ let cValue = await clipboardTextForElementId("content-editable-with-non-breaking-spaces", "Content-editable");
+ ok(cValue.includes("100 km"), "NBSP should be replaced by spaces, until brower compatibility issues are sorted out");
+
+ let pValue = await clipboardTextForElementId("paragraph-with-non-breaking-spaces", "Paragraph");
+ ok(pValue.includes("100 km"), "NBSP should be replaced by spaces, until brower compatibility issues are sorted out");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_constructor-assignment.html b/dom/base/test/test_constructor-assignment.html
new file mode 100644
index 0000000000..376e25fda3
--- /dev/null
+++ b/dom/base/test/test_constructor-assignment.html
@@ -0,0 +1,61 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+function testConstructor(name)
+{
+ window[name] = 17; // resolve through assignment
+
+
+ var desc = Object.getOwnPropertyDescriptor(window, name);
+ ok(typeof desc === "object" && desc !== null, name + ": property must exist");
+
+ is(desc.value, 17, name + ": overwrite didn't work correctly");
+ is(desc.enumerable, false,
+ name + ": initial descriptor was non-enumerable, and [[Put]] changes " +
+ "the property value but not its enumerability");
+ is(desc.configurable, true,
+ name + ": initial descriptor was configurable, and [[Put]] changes the " +
+ "property value but not its configurability");
+ is(desc.writable, true,
+ name + ": initial descriptor was writable, and [[Put]] changes the " +
+ "property value but not its writability");
+}
+
+var ctors =
+ [
+ "HTMLElement",
+ "HTMLDivElement",
+ "HTMLSpanElement",
+ "HTMLParagraphElement",
+ "HTMLOptionElement",
+ "HTMLHtmlElement",
+ "Element",
+ "Node",
+ "Document",
+ "Image",
+ "Audio",
+ "HTMLAudioElement",
+ "HTMLVideoElement",
+ "Window",
+ "XMLHttpRequest",
+ "Navigator",
+ "WebSocket",
+ "Event",
+ "IDBKeyRange",
+ "CSSPageRule",
+ "SVGPatternElement",
+ ];
+
+ctors.forEach(testConstructor);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_constructor.html b/dom/base/test/test_constructor.html
new file mode 100644
index 0000000000..1a66d5b613
--- /dev/null
+++ b/dom/base/test/test_constructor.html
@@ -0,0 +1,61 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+function testConstructor(name)
+{
+ window[name]; // resolve not through assignment
+ window[name] = 17;
+
+ var desc = Object.getOwnPropertyDescriptor(window, name);
+ ok(typeof desc === "object" && desc !== null, name + ": property must exist");
+
+ is(desc.value, 17, name + ": overwrite didn't work correctly");
+ is(desc.enumerable, false,
+ name + ": initial descriptor was non-enumerable, and [[Put]] changes " +
+ "the property value but not its enumerability");
+ is(desc.configurable, true,
+ name + ": initial descriptor was configurable, and [[Put]] changes the " +
+ "property value but not its configurability");
+ is(desc.writable, true,
+ name + ": initial descriptor was writable, and [[Put]] changes the " +
+ "property value but not its writability");
+}
+
+var ctors =
+ [
+ "HTMLElement",
+ "HTMLDivElement",
+ "HTMLSpanElement",
+ "HTMLParagraphElement",
+ "HTMLOptionElement",
+ "HTMLHtmlElement",
+ "Element",
+ "Node",
+ "Document",
+ "Image",
+ "Audio",
+ "HTMLAudioElement",
+ "HTMLVideoElement",
+ "Window",
+ "XMLHttpRequest",
+ "Navigator",
+ "WebSocket",
+ "Event",
+ "IDBKeyRange",
+ "CSSPageRule",
+ "SVGPatternElement",
+ ];
+
+ctors.forEach(testConstructor);
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_content_iterator_post_order.html b/dom/base/test/test_content_iterator_post_order.html
new file mode 100644
index 0000000000..cdd6f748e2
--- /dev/null
+++ b/dom/base/test/test_content_iterator_post_order.html
@@ -0,0 +1,875 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for post-order content iterator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+function finish() {
+ // The SimpleTest may require usual elements in the template, but they shouldn't be during test.
+ // So, let's create them at end of the test.
+ document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
+ SimpleTest.finish();
+}
+
+function createContentIterator() {
+ return Cc["@mozilla.org/scriptable-content-iterator;1"]
+ .createInstance(Ci.nsIScriptableContentIterator);
+}
+
+function getNodeDescription(aNode) {
+ if (aNode === undefined) {
+ return "undefine";
+ }
+ if (aNode === null) {
+ return "null";
+ }
+ function getElementDescription(aElement) {
+ if (aElement.tagName === "BR") {
+ if (aElement.previousSibling) {
+ return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
+ }
+ return `<br> element in ${getElementDescription(aElement.parentElement)}`;
+ }
+ let hasHint = aElement == document.body;
+ let tag = `<${aElement.tagName.toLowerCase()}`;
+ if (aElement.getAttribute("id")) {
+ tag += ` id="${aElement.getAttribute("id")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("class")) {
+ tag += ` class="${aElement.getAttribute("class")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("type")) {
+ tag += ` type="${aElement.getAttribute("type")}"`;
+ }
+ if (aElement.getAttribute("name")) {
+ tag += ` name="${aElement.getAttribute("name")}"`;
+ }
+ if (aElement.getAttribute("value")) {
+ tag += ` value="${aElement.getAttribute("value")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("style")) {
+ tag += ` style="${aElement.getAttribute("style")}"`;
+ hasHint = true;
+ }
+ if (hasHint) {
+ return tag + ">";
+ }
+ return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
+ }
+ switch (aNode.nodeType) {
+ case aNode.TEXT_NODE:
+ return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
+ case aNode.COMMENT_NODE:
+ return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
+ case aNode.ELEMENT_NODE:
+ return getElementDescription(SpecialPowers.unwrap(aNode));
+ default:
+ return "unknown node";
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function () {
+ let iter = createContentIterator();
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with an empty element.
+ */
+ document.body.innerHTML = "<div></div>";
+ let description = "Initialized with empty <div> as root node:";
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body.firstChild);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
+ */
+ let range = document.createRange();
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with range including only empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild, 0);
+ description = "Initialized with range collapsed in empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ description = "Initialized with a position in empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ document.body.firstChild, 0, document.body.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with the text element.
+ */
+ document.body.innerHTML = "<div>some text.</div>";
+ description = "Initialized with a text node as root node:";
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body.firstChild.firstChild);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be the text node after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
+ */
+ range = document.createRange();
+ range.selectNode(document.body.firstChild.firstChild);
+ description = "Initialized with range including only text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first() and next() after initialized with positions which select the text node.
+ * XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> element after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling next() from first position`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() from second position (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next() from second position`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, 0);
+ description = "Initialized with range collapsed at start of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at start of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with a range selecting all text in a text node.
+ */
+ range = document.createRange();
+ range.setStart(document.body.firstChild.firstChild, 0);
+ range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range selecting all text in text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with positions selecting all text in a text node.
+ */
+ description = "Initialized with positions selecting all text in text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, 0,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic tests with complicated tree.
+ */
+ function check(aIter, aExpectedResult, aDescription) {
+ if (aExpectedResult.length) {
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
+
+ for (let expected of aExpectedResult) {
+ is(SpecialPowers.unwrap(aIter.currentNode), expected,
+ `${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
+ aIter.next();
+ }
+
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
+ } else {
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
+ }
+ }
+
+ document.body.innerHTML = "<p>" +
+ "Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
+ "</p>" +
+ "<p>" +
+ "Here is an &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
+ "and a &lt;textarea&gt; element: <textarea>text area's text node</textarea><br><br>\n" +
+ "<!-- and here is comment node -->" +
+ "</p>";
+
+ let expectedResult =
+ [document.body.firstChild.firstChild, // the first text node
+ document.body.firstChild.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.firstChild.nextSibling, // <b>
+ document.body.firstChild.firstChild.nextSibling.nextSibling, // text next to <b>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.firstChild, // text in <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild, // <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.nextSibling, // text next to <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling, // <i>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <span>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <span>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <span>
+ document.body.firstChild, // first <p>
+ document.body.firstChild.nextSibling.firstChild, // the first text node in second <p>
+ document.body.firstChild.nextSibling.firstChild.nextSibling, // <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling, // <br> next to <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling, // text next to <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <br>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // text next to <br>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // comment
+ document.body.firstChild.nextSibling, // second <p>
+ document.body]; // <body>
+
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body);
+ check(iter, expectedResult, "Initialized with the <body> as root element:");
+
+ /**
+ * Selects the <body> with a range.
+ */
+ range = document.createRange();
+ range.selectNode(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter, expectedResult, "Initialized with range selecting the <body>");
+
+ /**
+ * Selects the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter, expectedResult, "Initialized with positions selecting the <body>");
+
+ /**
+ * Selects all children in the <body> with a range.
+ */
+ expectedResult.pop(); // <body> shouldn't be listed up.
+ range = document.createRange();
+ range.selectNodeContents(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter, expectedResult, "Initialized with range selecting all children in the <body>");
+
+ /**
+ * Selects all children in the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter, expectedResult, "Initialized with positions selecting all children in the <body>");
+
+ /**
+ * range/positions around elements.
+ */
+ document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
+ range = document.createRange();
+
+ range.setStart(document.body.firstChild, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '[abc<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '[abc<b>de]f'");
+
+ range.setStart(document.body.firstChild, 2);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'ab[c<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'ab[c<b>de]f'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'abc[<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'abc[<b>de]f'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'abc{<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'abc{<b>de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def]</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def]</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling, 1);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def}</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def}</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.nextSibling], // <b>
+ "Initialized with range selecting '<b>{def</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.nextSibling], // <b>
+ "Initialized with positions selecting '<b>{def</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.nextSibling], // <b>
+ "Initialized with range selecting '<b>def[</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.nextSibling], // <b>
+ "Initialized with positions selecting '<b>def[</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <b>
+ "Initialized with range selecting '<b>def{</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <b>
+ "Initialized with positions selecting '<b>def{</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <b>
+ "Initialized with range selecting '<b>def{</b><i>}ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <b>
+ "Initialized with positions selecting '<b>def{</b><i>}ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with range selecting '<b>def{</b><i>]ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with positions selecting '<b>def{</b><i>]ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
+ document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with range selecting '<i>{ghi</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
+ document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with positions selecting '<i>{ghi</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
+ document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with range selecting '<i>ghi[</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
+ document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with positions selecting '<i>ghi[</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with range selecting '<i>ghi{</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with positions selecting '<i>ghi{</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling, // <i>
+ document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
+ "Initialized with range selecting '<i>ghi{</i>]jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling, // <i>
+ document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
+ "Initialized with positions selecting '<i>ghi{</i>]jkl'");
+
+ /**
+ * range/positions around <br> elements.
+ */
+ document.body.innerHTML = "abc<br>def";
+ range = document.createRange();
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <br>
+ document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc[<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <br>
+ document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc[<br>]def'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc{<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc{<br>]def'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with range selecting 'abc{<br>}def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with positions selecting 'abc{<br>}def'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild], // text before <br>
+ "Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild], // text before <br>
+ "Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
+
+ finish();
+});
+</script>
+</head>
+<body></body>
+</html>
diff --git a/dom/base/test/test_content_iterator_pre_order.html b/dom/base/test/test_content_iterator_pre_order.html
new file mode 100644
index 0000000000..4ad1832e9d
--- /dev/null
+++ b/dom/base/test/test_content_iterator_pre_order.html
@@ -0,0 +1,869 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for pre-order content iterator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+function finish() {
+ // The SimpleTest may require usual elements in the template, but they shouldn't be during test.
+ // So, let's create them at end of the test.
+ document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
+ SimpleTest.finish();
+}
+
+function createContentIterator() {
+ return Cc["@mozilla.org/scriptable-content-iterator;1"]
+ .createInstance(Ci.nsIScriptableContentIterator);
+}
+
+function getNodeDescription(aNode) {
+ if (aNode === undefined) {
+ return "undefine";
+ }
+ if (aNode === null) {
+ return "null";
+ }
+ function getElementDescription(aElement) {
+ if (aElement.tagName === "BR") {
+ if (aElement.previousSibling) {
+ return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
+ }
+ return `<br> element in ${getElementDescription(aElement.parentElement)}`;
+ }
+ let hasHint = aElement == document.body;
+ let tag = `<${aElement.tagName.toLowerCase()}`;
+ if (aElement.getAttribute("id")) {
+ tag += ` id="${aElement.getAttribute("id")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("class")) {
+ tag += ` class="${aElement.getAttribute("class")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("type")) {
+ tag += ` type="${aElement.getAttribute("type")}"`;
+ }
+ if (aElement.getAttribute("name")) {
+ tag += ` name="${aElement.getAttribute("name")}"`;
+ }
+ if (aElement.getAttribute("value")) {
+ tag += ` value="${aElement.getAttribute("value")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("style")) {
+ tag += ` style="${aElement.getAttribute("style")}"`;
+ hasHint = true;
+ }
+ if (hasHint) {
+ return tag + ">";
+ }
+ return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
+ }
+ switch (aNode.nodeType) {
+ case aNode.TEXT_NODE:
+ return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
+ case aNode.COMMENT_NODE:
+ return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
+ case aNode.ELEMENT_NODE:
+ return getElementDescription(SpecialPowers.unwrap(aNode));
+ default:
+ return "unknown node";
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function () {
+ let iter = createContentIterator();
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with an empty element.
+ */
+ document.body.innerHTML = "<div></div>";
+ let description = "Initialized with empty <div> as root node:";
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body.firstChild);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
+ */
+ let range = document.createRange();
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with range including only empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild, 0);
+ description = "Initialized with range collapsed in empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ description = "Initialized with a position in empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ document.body.firstChild, 0, document.body.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with the text element.
+ */
+ document.body.innerHTML = "<div>some text.</div>";
+ description = "Initialized with a text node as root node:";
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body.firstChild.firstChild);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be the text node after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
+ */
+ range = document.createRange();
+ range.selectNode(document.body.firstChild.firstChild);
+ description = "Initialized with range including only text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first() and next() after initialized with positions which select the text node.
+ * XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> element immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> element after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling next() from first position`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() from second position (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next() from second position`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, 0);
+ description = "Initialized with range collapsed at start of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at start of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ * XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with a range selecting all text in a text node.
+ */
+ range = document.createRange();
+ range.setStart(document.body.firstChild.firstChild, 0);
+ range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range selecting all text in text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with positions selecting all text in a text node.
+ */
+ description = "Initialized with positions selecting all text in text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ document.body.firstChild.firstChild, 0,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic tests with complicated tree.
+ */
+ function check(aIter, aExpectedResult, aDescription) {
+ if (aExpectedResult.length) {
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
+
+ for (let expected of aExpectedResult) {
+ is(SpecialPowers.unwrap(aIter.currentNode), expected,
+ `${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
+ aIter.next();
+ }
+
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
+ } else {
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
+ }
+ }
+
+ document.body.innerHTML = "<p>" +
+ "Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
+ "</p>" +
+ "<p>" +
+ "Here is an &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
+ "and a &lt;textarea&gt; element: <textarea>text area's text node</textarea><br><br>\n" +
+ "<!-- and here is comment node -->" +
+ "</p>";
+
+ let expectedResult =
+ [document.body, // <body>
+ document.body.firstChild, // first <p>
+ document.body.firstChild.firstChild, // the first text node
+ document.body.firstChild.firstChild.nextSibling, // <b>
+ document.body.firstChild.firstChild.nextSibling.firstChild, // text in <b>
+ document.body.firstChild.firstChild.nextSibling.nextSibling, // text next to <b>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling, // <i>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild, // <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.firstChild, // text in <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.nextSibling, // text next to <u>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <span>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <span>
+ document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <span>
+ document.body.firstChild.nextSibling, // second <p>
+ document.body.firstChild.nextSibling.firstChild, // the first text node in second <p>
+ document.body.firstChild.nextSibling.firstChild.nextSibling, // <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling, // <br> next to <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling, // text next to <input>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <textarea>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <br>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // text next to <br>
+ document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling] // comment
+
+ iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body);
+ check(iter, expectedResult, "Initialized with the <body> as root element");
+
+ /**
+ * Selects the <body> with a range.
+ */
+ range = document.createRange();
+ range.selectNode(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter, expectedResult, "Initialized with range selecting the <body>");
+
+ /**
+ * Selects the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter, expectedResult, "Initialized with positions selecting the <body>");
+
+ /**
+ * Selects all children in the <body> with a range.
+ */
+ expectedResult.shift(); // <body> shouldn't be listed up.
+ range = document.createRange();
+ range.selectNodeContents(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter, expectedResult, "Initialized with range selecting all children in the <body>");
+
+ /**
+ * Selects all children in the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter, expectedResult, "Initialized with positions selecting all children in the <body>");
+
+ /**
+ * range/positions around elements.
+ */
+ document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
+ range = document.createRange();
+
+ range.setStart(document.body.firstChild, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '[abc<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '[abc<b>de]f'");
+
+ range.setStart(document.body.firstChild, 2);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'ab[c<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'ab[c<b>de]f'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'abc[<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <b>
+ document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'abc[<b>de]f'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting 'abc{<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <b>
+ document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting 'abc{<b>de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def]</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def]</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling, 1);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def}</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def}</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>def[</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>def[</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter, [],
+ "Initialized with range selecting '<b>def{</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [],
+ "Initialized with positions selecting '<b>def{</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with range selecting '<b>def{</b><i>}ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling], // <i>
+ "Initialized with positions selecting '<b>def{</b><i>}ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling, // <i>
+ document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with range selecting '<b>def{</b><i>]ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling, // <i>
+ document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with positions selecting '<b>def{</b><i>]ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with range selecting '<i>{ghi</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with positions selecting '<i>{ghi</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with range selecting '<i>ghi[</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with positions selecting '<i>ghi[</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter, [],
+ "Initialized with range selecting '<i>ghi{</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [],
+ "Initialized with positions selecting '<i>ghi{</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
+ "Initialized with range selecting '<i>ghi{</i>]jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
+ "Initialized with positions selecting '<i>ghi{</i>]jkl'");
+
+ /**
+ * range/positions around <br> elements.
+ */
+ document.body.innerHTML = "abc<br>def";
+ range = document.createRange();
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // text before <br>
+ document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc[<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // text before <br>
+ document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc[<br>]def'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc{<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc{<br>]def'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling, // <br>
+ document.body.firstChild.nextSibling.nextSibling], // text after <br>
+ "Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with range selecting 'abc{<br>}def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with positions selecting 'abc{<br>}def'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild], // text before <br>
+ "Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild], // text before <br>
+ "Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
+
+ finish();
+});
+</script>
+</head>
+<body></body>
+</html>
diff --git a/dom/base/test/test_content_iterator_subtree.html b/dom/base/test/test_content_iterator_subtree.html
new file mode 100644
index 0000000000..c6b311b3f6
--- /dev/null
+++ b/dom/base/test/test_content_iterator_subtree.html
@@ -0,0 +1,690 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for content subtree iterator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+function finish() {
+ // The SimpleTest may require usual elements in the template, but they shouldn't be during test.
+ // So, let's create them at end of the test.
+ document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
+ SimpleTest.finish();
+}
+
+function createContentIterator() {
+ return Cc["@mozilla.org/scriptable-content-iterator;1"]
+ .createInstance(Ci.nsIScriptableContentIterator);
+}
+
+function getNodeDescription(aNode) {
+ if (aNode === undefined) {
+ return "undefine";
+ }
+ if (aNode === null) {
+ return "null";
+ }
+ function getElementDescription(aElement) {
+ if (aElement.tagName === "BR") {
+ if (aElement.previousSibling) {
+ return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
+ }
+ return `<br> element in ${getElementDescription(aElement.parentElement)}`;
+ }
+ let hasHint = aElement == document.body;
+ let tag = `<${aElement.tagName.toLowerCase()}`;
+ if (aElement.getAttribute("id")) {
+ tag += ` id="${aElement.getAttribute("id")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("class")) {
+ tag += ` class="${aElement.getAttribute("class")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("type")) {
+ tag += ` type="${aElement.getAttribute("type")}"`;
+ }
+ if (aElement.getAttribute("name")) {
+ tag += ` name="${aElement.getAttribute("name")}"`;
+ }
+ if (aElement.getAttribute("value")) {
+ tag += ` value="${aElement.getAttribute("value")}"`;
+ hasHint = true;
+ }
+ if (aElement.getAttribute("style")) {
+ tag += ` style="${aElement.getAttribute("style")}"`;
+ hasHint = true;
+ }
+ if (hasHint) {
+ return tag + ">";
+ }
+ return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
+ }
+ switch (aNode.nodeType) {
+ case aNode.TEXT_NODE:
+ return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
+ case aNode.COMMENT_NODE:
+ return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
+ case aNode.ELEMENT_NODE:
+ return getElementDescription(SpecialPowers.unwrap(aNode));
+ default:
+ return "unknown node";
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function () {
+ let iter = createContentIterator();
+
+ /**
+ * FYI: ContentSubtreeIterator does not support initWithRootNode() nor positionAt().
+ */
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
+ */
+ document.body.innerHTML = "<div></div>";
+ let range = document.createRange();
+ range.selectNode(document.body.firstChild);
+ let description = "Initialized with range including only empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild, 0);
+ description = "Initialized with range collapsed in empty <div>:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range in an empty element.
+ */
+ description = "Initialized with a position in empty <div>:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ document.body.firstChild, 0, document.body.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
+ */
+ document.body.innerHTML = "<div>some text.</div>";
+ range = document.createRange();
+ range.selectNode(document.body.firstChild.firstChild);
+ description = "Initialized with range including only text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.last();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
+
+ iter.prev();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
+ `${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next()`);
+
+ /**
+ * Basic behavior tests of first() and next() after initialized with positions which select the text node.
+ * XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
+ * from initWithRange().
+ */
+ range.selectNode(document.body.firstChild);
+ description = "Initialized with positions including only text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> element immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
+ `${description} currentNode should be the <div> element after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
+
+ iter.next();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true after calling next() from first position`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, 0);
+ description = "Initialized with range collapsed at start of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at start of a text node.
+ */
+ description = "Initialized with a position at start of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at end of a text node.
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ */
+ range = document.createRange();
+ range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ description = "Initialized with range collapsed at end of text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with collapsed range at middle of a text node.
+ */
+ description = "Initialized with a position at end of text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with a range selecting all text in a text node.
+ */
+ range = document.createRange();
+ range.setStart(document.body.firstChild.firstChild, 0);
+ range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ description = "Initialized with range selecting all text in text node:";
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Tests to initializing with positions selecting all text in a text node.
+ */
+ description = "Initialized with positions selecting all text in text node:";
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ document.body.firstChild.firstChild, 0,
+ document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
+
+ iter.first();
+ is(SpecialPowers.unwrap(iter.currentNode), null,
+ `${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
+ ok(iter.isDone, `${description} isDone should be true even after calling first()`);
+
+ /**
+ * Basic tests with complicated tree.
+ */
+ function check(aIter, aExpectedResult, aDescription) {
+ if (aExpectedResult.length) {
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
+ `${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
+
+ for (let expected of aExpectedResult) {
+ is(SpecialPowers.unwrap(aIter.currentNode), expected,
+ `${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
+ ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
+ aIter.next();
+ }
+
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
+ } else {
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
+
+ aIter.first();
+ is(SpecialPowers.unwrap(aIter.currentNode), null,
+ `${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
+ ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
+ }
+ }
+
+ document.body.innerHTML = "<p>" +
+ "Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
+ "</p>" +
+ "<p>" +
+ "Here is an &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
+ "and a &lt;textarea&gt; element: <textarea>text area's text node</textarea><br><br>\n" +
+ "<!-- and here is comment node -->" +
+ "</p>";
+
+ /**
+ * Selects the <body> with a range.
+ */
+ range = document.createRange();
+ range.selectNode(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [document.body], "Initialized with range selecting the <body>");
+
+ /**
+ * Selects the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter, [document.body], "Initialized with positions selecting the <body>");
+
+ /**
+ * Selects all children in the <body> with a range.
+ */
+ range = document.createRange();
+ range.selectNodeContents(document.body);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild, // first <p>
+ document.body.firstChild.nextSibling], // second <p>
+ "Initialized with range selecting all children in the <body>");
+
+ /**
+ * Selects all children in the <body> with positions.
+ */
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset, range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild, // first <p>
+ document.body.firstChild.nextSibling], // second <p>
+ "Initialized with positions selecting all children in the <body>");
+
+ /**
+ * range/positions around elements.
+ */
+ document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
+ range = document.createRange();
+
+ range.setStart(document.body.firstChild, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '[abc<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '[abc<b>de]f'");
+
+ range.setStart(document.body.firstChild, 2);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,[], "Initialized with range selecting 'ab[c<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting 'ab[c<b>de]f'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting 'abc[<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting 'abc[<b>de]f'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting 'abc{<b>de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting 'abc{<b>de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>{de]f'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>{de]f'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>{def]</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>{def]</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling, 1);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def}</b>'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def}</b>'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with range selecting '<b>{def</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.firstChild], // text in <b>
+ "Initialized with positions selecting '<b>{def</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>def[</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>def[</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>def{</b>}<i>ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>def{</b>}<i>ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>def{</b><i>}ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>def{</b><i>}ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<b>def{</b><i>]ghi'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<b>def{</b><i>]ghi'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with range selecting '<i>{ghi</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
+ "Initialized with positions selecting '<i>{ghi</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<i>ghi[</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<i>ghi[</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body, 3);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<i>ghi{</i>}jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<i>ghi{</i>}jkl'");
+
+ range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting '<i>ghi{</i>]jkl'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting '<i>ghi{</i>]jkl'");
+
+ /**
+ * range/positions around <br> elements.
+ */
+ document.body.innerHTML = "abc<br>def";
+ range = document.createRange();
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with range selecting 'abc[<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with positions selecting 'abc[<br>]def'");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with range selecting 'abc{<br>]def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with positions selecting 'abc{<br>]def'");
+
+ range.setStart(document.body.firstChild.nextSibling, 0);
+ range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
+
+ range.setStart(document.body, 1);
+ range.setEnd(document.body, 2);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with range selecting 'abc{<br>}def'");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter,
+ [document.body.firstChild.nextSibling], // <br>
+ "Initialized with positions selecting 'abc{<br>}def'");
+
+ range.setStart(document.body.firstChild, 3);
+ range.setEnd(document.body.firstChild.nextSibling, 0);
+ iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
+ check(iter, [], "Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
+ iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
+ range.startContainer, range.startOffset,
+ range.endContainer, range.endOffset);
+ check(iter, [], "Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
+
+ finish();
+});
+</script>
+</head>
+<body></body>
+</html>
diff --git a/dom/base/test/test_copyimage.html b/dom/base/test/test_copyimage.html
new file mode 100644
index 0000000000..0badf940e5
--- /dev/null
+++ b/dom/base/test/test_copyimage.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=518249
+https://bugzilla.mozilla.org/show_bug.cgi?id=952456
+-->
+<head>
+ <title>Test for copy image</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518249">Mozilla Bug 518249</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952456">Mozilla Bug 952456</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testCopyImage () {
+ var Ci = SpecialPowers.Ci;
+ var Cc = SpecialPowers.Cc;
+ var clipboard = SpecialPowers.Services.clipboard;
+
+ function getClipboardData(mime) {
+ var transferable = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(Ci.nsITransferable);
+ var loadingContext = SpecialPowers.wrap(window).docShell
+ .QueryInterface(Ci.nsILoadContext);
+ transferable.init(loadingContext);
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = SpecialPowers.createBlankObject();
+ transferable.getTransferData(mime, data);
+ return data;
+ }
+
+ function testClipboardValue(mime, expected) {
+ var data = SpecialPowers.wrap(getClipboardData(mime));
+ var str = data.value == null ? data.value :
+ data.value.QueryInterface(Ci.nsISupportsString).data;
+ is(str, expected, "clipboard has correct [" + mime + "] content")
+ }
+
+ //--------- Prepare data and copy it.
+
+ // Select the node.
+ var node = document.getElementById('logo');
+
+ // Set node and copy image.
+ var docShell = SpecialPowers.wrap(window).docShell;
+ var documentViewer = docShell.contentViewer
+ .QueryInterface(Ci.nsIContentViewerEdit);
+ documentViewer.setCommandNode(node);
+ documentViewer.copyImage(documentViewer.COPY_IMAGE_ALL);
+
+ //--------- Let's check the content of the clipboard now.
+
+ // Does the clipboard contain text/plain data ?
+ ok(clipboard.hasDataMatchingFlavors(["text/plain"], clipboard.kGlobalClipboard), "clipboard contains unicode text");
+ // Does the clipboard contain text/html data ?
+ ok(clipboard.hasDataMatchingFlavors(["text/html"], clipboard.kGlobalClipboard), "clipboard contains html text");
+ // Does the clipboard contain image data ?
+ ok(clipboard.hasDataMatchingFlavors(["image/png"], clipboard.kGlobalClipboard), "clipboard contains image");
+
+ // Is the text/plain data correct ?
+ testClipboardValue('text/plain', 'about:logo');
+ // Is the text/html data correct ?
+ var expected = '<img id="logo" src="about:logo">';
+ if (navigator.platform.includes("Win")) {
+ expected = kTextHtmlPrefixClipboardDataWindows + expected + kTextHtmlSuffixClipboardDataWindows;
+ }
+ testClipboardValue('text/html', expected);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testCopyImage);
+</script>
+</pre>
+<div>
+ <img id="logo" src="about:logo">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_copypaste.html b/dom/base/test/test_copypaste.html
new file mode 100644
index 0000000000..014ac7f9dc
--- /dev/null
+++ b/dom/base/test/test_copypaste.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for copy/paste</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="copypaste.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=524975">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(() => {
+ add_task(async function test_copyhtml() {
+ await testCopyPaste(false);
+ });
+});
+
+</script>
+</pre>
+<div>
+
+ <div id="draggable" title="title to have a long HTML line">This is a <em>draggable</em> bit of text.</div>
+ <textarea id="input" cols="40" rows="10"></textarea>
+
+ <div id="alist">
+ bla
+ <ul>
+ <li>foo</li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ul>
+ </div>
+
+ <div id="blist">
+ mozilla
+ <ol>
+ <li>foo</li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ol>
+ </div>
+
+ <div id="clist">
+ mzla
+ <ul>
+ <li>foo<ul>
+ <li>bazzinga!</li>
+ </ul></li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ul>
+ </div>
+
+<div id="div4">
+ T<textarea>t t t</textarea>
+</div>
+
+<div id="div5">
+ T<textarea> </textarea>
+</div>
+
+<div>Copy1then Paste<ul id="ul1"><li>LI</li>
+</ul></div>
+
+<div><ul id="ul2">
+<li>LI</li></ul>Copy2then Paste</div>
+
+<div><ul id="ul3"><li>
+<li>LI</li></ul>Copy3then Paste</div>
+
+<div><div id="div1s"><div id="div1se1">before<div>inner</div></div>after</div></div>
+<div><div id="div2s"><div id="div2se1">before<div>inner</div></div>after</div>
+</div>
+
+<div id="contentEditable1" contenteditable spellcheck="false"></div>
+<div id="contentEditable2" contenteditable spellcheck="false"></div>
+<div id="contentEditable3" contenteditable spellcheck="false"></div>
+<div id="contentEditable4" contenteditable spellcheck="false"></div>
+<div id="contentEditable5" contenteditable spellcheck="false"></div>
+
+<div>
+<span id="1127835crash1">1</span><div id="1127835crash2"><div>
+</div></div><a href="http://www.mozilla.org/" id="1127835crash3">3</a>
+</div>
+<div id="contentEditable6" contenteditable spellcheck="false"></div>
+
+<div id="div6" style="display:none"></div>
+<script>
+var x = $("div6")
+x.appendChild(document.createTextNode('di'))
+x.appendChild(document.createTextNode('v6'))
+</script>
+
+<div id="div7" style="display:none">div7</div>
+<div id="div8" style="visibility:hidden">div8</div>
+<div style="visibility:hidden"><div id="div9" style="visibility:visible">div9</div></div>
+<div style="visibility:hidden"><div><div><div id="div10"></div></div></div></div>
+<script>
+var x = $("div10")
+x.appendChild(document.createTextNode('div'))
+x.appendChild(document.createTextNode('10'))
+</script>
+
+<div id="div11" oncopy="modifySelection('X')"><span>div</span>11</div>
+<div id="div12" oncopy="modifySelection('X<b style=\'display:none\'>Y</b>')"><span>div</span>12</div>
+
+<div id="div13">_<noscript>FAIL</noscript>_</div>
+
+<table><tr id=tr1><td>foo</td><td>bar</td></tr></table>
+<table><tr id=tr2><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr><tr id=tr3><td>5</td><td>6</td></tr></table>
+
+<div>X<ruby id="ruby1"><rb>aa</rb><rb>bb</rb><rp>(</rp><rt>AA</rt><rt>BB</rt><rp>)</rp></ruby>Y</div>
+
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_copypaste.xhtml b/dom/base/test/test_copypaste.xhtml
new file mode 100644
index 0000000000..ebc1371560
--- /dev/null
+++ b/dom/base/test/test_copypaste.xhtml
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<!--
+This test is copied from test_copypaste.html, but it's XHTML instead of HTML.
+XHTML is encoded differently from HTML when copied; see bugs 888839 and 723163.
+This test is different from test_copypaste.html in two ways:
+
+ 1. The text/html clipboard flavor isn't tested, since nsCopySupport doesn't
+ produce it for XHTML.
+ 2. The text/plain flavor isn't tested when the selection is in hidden
+ elements, since nsCopySupport doesn't produce text/plain for hidden
+ elements, and unlike HTML, neither does it produce text/_moz_htmlcontext
+ and text/_moz_htmlinfo, which the clipboard converts to text/plain.
+-->
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for copy/paste with XHTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="copypaste.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=888839">Mozilla Bug 888839</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+function modifySelectionDiv12() {
+ modifySelection("X<b style='display:none'>Y</b>");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(() => {
+ add_task(async function test_copyhtml() {
+ await testCopyPaste(true);
+ });
+});
+
+]]>
+</script>
+</pre>
+<div>
+
+ <div id="draggable" title="title to have a long HTML line">This is a <em>draggable</em> bit of text.</div>
+ <textarea id="input" cols="40" rows="10"></textarea>
+
+ <div id="alist">
+ bla
+ <ul>
+ <li>foo</li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ul>
+ </div>
+
+ <div id="blist">
+ mozilla
+ <ol>
+ <li>foo</li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ol>
+ </div>
+
+ <div id="clist">
+ mzla
+ <ul>
+ <li>foo<ul>
+ <li>bazzinga!</li>
+ </ul></li>
+ <li style="display: none;">baz</li>
+ <li>bar</li>
+ </ul>
+ </div>
+
+<div id="div4">
+ T<textarea>t t t</textarea>
+</div>
+
+<div id="div5">
+ T<textarea> </textarea>
+</div>
+
+<div id="div6" style="display:none"></div>
+<script>
+var x = $("div6")
+x.appendChild(document.createTextNode('di'))
+x.appendChild(document.createTextNode('v6'))
+</script>
+
+<div id="div7" style="display:none">div7</div>
+<div id="div8" style="visibility:hidden">div8</div>
+<div style="visibility:hidden"><div id="div9" style="visibility:visible">div9</div></div>
+<div style="visibility:hidden"><div><div><div id="div10"></div></div></div></div>
+<script>
+var x = $("div10")
+x.appendChild(document.createTextNode('div'))
+x.appendChild(document.createTextNode('10'))
+</script>
+
+<div id="div11" oncopy="modifySelection('X')"><span>div</span>11</div>
+<div id="div12" oncopy="modifySelectionDiv12()"><span>div</span>12</div>
+
+<div id="div13">_<noscript>FAIL</noscript>_</div>
+
+<table><tr id="tr1"><td>foo</td><td>bar</td></tr></table>
+
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_copypaste_disabled.html b/dom/base/test/test_copypaste_disabled.html
new file mode 100644
index 0000000000..06dbdbc779
--- /dev/null
+++ b/dom/base/test/test_copypaste_disabled.html
@@ -0,0 +1,116 @@
+<!doctype html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<script src="copypaste.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<div id="content">
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+ }
+ body { font-family: Ahem; font-size: 20px; margin: 0; }
+ input, textarea {
+ font: inherit;
+ -moz-appearance: none;
+ padding: 0;
+ border: 0;
+ scrollbar-width: none;
+ }
+ </style>
+ <input id="disabled-input" disabled value="abcd"> efgh <br> <textarea rows=1 id="disabled-textarea" disabled>ijkl</textarea> mnop <br>
+</div>
+
+<script>
+function dragSelect(e, x1, x2, x3) {
+ dir = x2 > x1 ? 1 : -1;
+ synthesizeMouse(e, x1, 5, { type: "mousedown" });
+ synthesizeMouse(e, x1 + dir, 5, { type: "mousemove" });
+ if (x3)
+ synthesizeMouse(e, x3, 5, { type: "mousemove" });
+ synthesizeMouse(e, x2 - dir, 5, { type: "mousemove" });
+ synthesizeMouse(e, x2, 5, { type: "mouseup" });
+}
+
+SimpleTest.waitForExplicitFinish();
+waitUntilApzStable().then(async function() {
+ const docShell = SpecialPowers.wrap(window).docShell;
+
+ const clipboard = SpecialPowers.Services.clipboard;
+
+ function copySelectionToClipboard() {
+ return SimpleTest.promiseClipboardChange(
+ () => true,
+ () => {
+ SpecialPowers.doCommand(window, "cmd_copy");
+ }
+ );
+ }
+
+ function getLoadContext() {
+ return docShell.QueryInterface(SpecialPowers.Ci.nsILoadContext);
+ }
+
+ function getClipboardData(mime) {
+ var transferable = SpecialPowers.Cc[
+ "@mozilla.org/widget/transferable;1"
+ ].createInstance(SpecialPowers.Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = SpecialPowers.createBlankObject();
+ transferable.getTransferData(mime, data);
+ return data;
+ }
+
+ function testClipboardValue(mime, expected) {
+ var data = SpecialPowers.wrap(getClipboardData(mime));
+ is(
+ data.value == null
+ ? data.value
+ : data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
+ expected,
+ mime + " value in the clipboard"
+ );
+ return data.value;
+ }
+
+ async function runTestsOn(doc) {
+ for (let id of ["disabled-input", "disabled-textarea"]) {
+ let element = doc.getElementById(id);
+ dragSelect(element, 0, 60);
+ await copySelectionToClipboard();
+ testClipboardValue("text/plain", element.value.substr(0, 3));
+ }
+ }
+
+ await runTestsOn(document)
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("frameborder", "0");
+ iframe.srcdoc = `<!doctype html>${document.getElementById("content").outerHTML}`;
+ let iframeLoad = new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+ document.body.appendChild(iframe);
+
+ await iframeLoad;
+ iframe.width = window.innerWidth;
+ iframe.height = window.innerHeight;
+
+ await SimpleTest.promiseFocus(iframe.contentWindow);
+ await runTestsOn(iframe.contentDocument);
+
+ // Add a contenteditable element to test the case where there's an HTMLEditor
+ // around the page.
+ let div = document.createElement("div");
+ div.setAttribute("contenteditable", "true");
+ document.body.appendChild(div);
+
+ await runTestsOn(document);
+
+ SimpleTest.finish();
+});
+</script>
diff --git a/dom/base/test/test_createHTMLDocument.html b/dom/base/test/test_createHTMLDocument.html
new file mode 100644
index 0000000000..66b090d189
--- /dev/null
+++ b/dom/base/test/test_createHTMLDocument.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>createHTMLDocument</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="http://www.whatwg.org/html5/#creating-documents">
+<link rel="help" href="http://www.whatwg.org/html5/#document.title">
+<link rel="help" href="http://www.whatwg.org/html5/#dom-document-readystate">
+<body>
+<script>
+function isElement(element, localName) {
+ is(element.localName, localName);
+ is(element.namespaceURI, "http://www.w3.org/1999/xhtml");
+ is(element.tagName, localName.toUpperCase());
+ is(element.nodeName, localName.toUpperCase());
+ is(element.prefix, null);
+}
+function checkDoc(title, expectedtitle, normalizedtitle) {
+ var doc = document.implementation.createHTMLDocument(title);
+ is(doc.readyState, "complete");
+ is(doc.compatMode, "CSS1Compat");
+ // Opera doesn't have a doctype: DSK-311092
+ ok(doc.doctype, "Need a doctype");
+ is(doc.doctype.name, "html");
+ is(doc.doctype.publicId, "");
+ is(doc.doctype.systemId, "");
+ isElement(doc.documentElement, "html");
+ isElement(doc.documentElement.firstChild, "head");
+ if (title !== undefined) {
+ is(doc.documentElement.firstChild.childNodes.length, 1);
+ isElement(doc.documentElement.firstChild.firstChild, "title");
+ // Doesn't always work out in WebKit.
+ ok(doc.documentElement.firstChild.firstChild.firstChild, "Need a text node.");
+ is(doc.documentElement.firstChild.firstChild.firstChild.data, expectedtitle);
+ } else {
+ is(doc.documentElement.firstChild.childNodes.length, 0);
+ }
+ isElement(doc.documentElement.lastChild, "body");
+ is(doc.documentElement.lastChild.childNodes.length, 0);
+ is(doc.title, normalizedtitle);
+ doc.body.innerHTML = "foo";
+ is(doc.body.innerHTML, "foo", "innerHTML should work in HTML data documents!");
+}
+checkDoc("", "", "");
+checkDoc(null, "null", "null");
+checkDoc(undefined, "", "");
+checkDoc("foo bar baz", "foo bar baz", "foo bar baz");
+checkDoc("foo\t\tbar baz", "foo\t\tbar baz", "foo bar baz");
+checkDoc("foo\n\nbar baz", "foo\n\nbar baz", "foo bar baz");
+checkDoc("foo\f\fbar baz", "foo\f\fbar baz", "foo bar baz");
+checkDoc("foo\r\rbar baz", "foo\r\rbar baz", "foo bar baz");
+</script>
diff --git a/dom/base/test/test_current_inner_window.html b/dom/base/test/test_current_inner_window.html
new file mode 100644
index 0000000000..3e7a700e63
--- /dev/null
+++ b/dom/base/test/test_current_inner_window.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test that current inner window checks are correct after navigations/discards</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<iframe id="frame"></iframe>
+
+<script type="application/javascript">
+"use strict";
+
+const TEST_FILE = "file_current_inner_window.html";
+const BASE_PATH = location.pathname.replace(/[^\/]+$/, "");
+
+let frame = document.getElementById("frame");
+
+function loadInFrame(url) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", resolve, { once: true });
+ frame.contentWindow.location = url;
+ });
+}
+
+add_task(async function() {
+ await loadInFrame(TEST_FILE);
+
+ // Store the check function from the window before we navigate. After that,
+ // its bare word property accesses will continue referring to the same inner
+ // window no matter how many times the frame navigates.
+ let check1 = frame.contentWindow.isCurrentWinnerWindow;
+ ok(check1(),
+ "Initial inner window should be current before we navigate away");
+
+ await loadInFrame(`http://example.com/${BASE_PATH}/${TEST_FILE}`);
+ ok(!check1(),
+ "Initial inner window should no longer be current after we navigate away");
+ await SpecialPowers.spawn(frame, [], () => {
+ Assert.ok(this.content.wrappedJSObject.isCurrentWinnerWindow(),
+ "Remote inner window should be current after before we navigate away");
+ });
+
+ await loadInFrame(TEST_FILE);
+ ok(!check1(),
+ "Initial inner window should still not be current after we back to current process");
+ let check2 = frame.contentWindow.isCurrentWinnerWindow;
+ ok(check2(),
+ "Second in-process inner window should be current before we remove the frame");
+
+ frame.remove();
+ ok(!check1(),
+ "Initial inner window should still not be current after we remove the frame");
+ ok(check2(),
+ "Second in-process inner window should still be current after we remove the frame");
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_custom_element.html b/dom/base/test/test_custom_element.html
new file mode 100644
index 0000000000..7c87e0416f
--- /dev/null
+++ b/dom/base/test/test_custom_element.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body onload="startTests()">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129" target="_blank">Mozilla Bug 783129</a>
+ <iframe id="fooframe" src="/"></iframe>
+ <script type="application/javascript">
+
+ /** Test for Bug 783129 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function startTests() {
+ var frame = document.getElementById("fooframe");
+ class XFoo extends frame.contentWindow.HTMLElement {};
+ frame.contentWindow.customElements.define("x-foo", XFoo);
+ var elem = new XFoo();
+ is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
+
+ var anotherElem = $("fooframe").contentDocument.createElement("x-foo");
+ is(anotherElem.tagName, "X-FOO", "createElement should create an x-foo element.");
+ SimpleTest.finish();
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/test_custom_element_reflector.html b/dom/base/test/test_custom_element_reflector.html
new file mode 100644
index 0000000000..aa7bba3efe
--- /dev/null
+++ b/dom/base/test/test_custom_element_reflector.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>Custom Elements don't lose their reflectors</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<!-- First tests upgrading with an existing reflector, second without -->
+<custom-element></custom-element>
+<custom-element></custom-element>
+<script>
+ (function() {
+ // Ensure we create a reflector for the first element before-hand.
+ let firstElement = document.querySelector("custom-element");
+ }());
+
+ customElements.define("custom-element", class MyCustomElement extends HTMLElement {
+ myFunction() {
+ // Do nothing
+ }
+ });
+
+ ok(!!document.querySelector("custom-element").myFunction, "Has the right prototype before GC");;
+ ok(!!document.querySelectorAll("custom-element")[1].myFunction, "Has the right prototype before GC");;
+
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+
+ ok(!!document.querySelector("custom-element").myFunction, "Has the right prototype after GC");;
+ ok(!!document.querySelectorAll("custom-element")[1].myFunction, "Has the right prototype before GC");;
+</script>
diff --git a/dom/base/test/test_data_uri.html b/dom/base/test/test_data_uri.html
new file mode 100644
index 0000000000..aa1c0fdbda
--- /dev/null
+++ b/dom/base/test/test_data_uri.html
@@ -0,0 +1,189 @@
+<html>
+<head>
+ <title>Tests for Data URI</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ @font-face {
+ font-family: 'DataFont';
+ src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWYWQKAAABWAAAAFZjbWFwAA8D7wAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lehAVOgAAA6AAAAIHcG9zdP+uADUAAAWoAAAAKgABAAAAAQAAMhPyuV8PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEEAQQMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABB//8AAABB////wgABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEEAAE1hcmtBAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsAQQAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtBIDogNS0xMS0yMDA4AABNAGEAcgBrAEEAAE1hcmtBAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEEAAE1hcmtBAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACQAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+ }
+ </style>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", false);
+SimpleTest.registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
+});
+
+function imgListener(img) {
+ return new Promise((resolve, reject) => {
+ img.addEventListener("load", () => resolve());
+ img.addEventListener("error", () => reject());
+ });
+}
+
+function runTests()
+{
+ var iframe = document.getElementById("iframe");
+ iframe.src="data:text/html,hello";
+ let p1 = new Promise((resolve, reject) => {
+ iframe.onload = function() {
+ ok(SpecialPowers.wrap(iframe).contentDocument.nodePrincipal.isNullPrincipal,
+ "iframe should have NullPrincipal.");
+ resolve();
+ }
+ });
+
+ var iframe1 = document.getElementById("iframe1");
+ iframe1.src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82";
+ let p2 = new Promise((resolve, reject) => {
+ iframe1.onload = function() {
+ ok(SpecialPowers.wrap(iframe1).contentDocument.nodePrincipal.isNullPrincipal,
+ "iframe1 should have NullPrincipal.");
+ resolve();
+ }
+ });
+
+ var canvas = document.getElementById('canvas');
+ var ctx = canvas.getContext('2d');
+ ctx.fillRect(0, 0, canvas.height, canvas.width);
+ ctx.fillStyle = '#000';
+ var data = canvas.toDataURL('image/png');
+ var img = new Image();
+ img.src = data;
+ let p3 = imgListener(img).then(() => {
+ dump("img onload\n");
+ ctx.drawImage(img, 0, 0);
+ return new Promise((resolve, reject) => {
+ try {
+ ctx.getImageData(0, 0, 1, 1);
+ ok(true, "data:image should be same origin.");
+ resolve();
+ } catch (e) {
+ ok(false, "data:image is cross-origin.");
+ reject();
+ }});
+ }).then(() => {
+ ctx.clearRect(0, 0, canvas.height, canvas.width);
+ ctx.drawImage(document.getElementById('img'), 0, 0);
+ return new Promise((resolve, reject) => {
+ try {
+ canvas.toDataURL();
+ ok(true, "data:image should be same origin.");
+ resolve();
+ } catch (e) {
+ ok(false, "data:image is cross-origin.");
+ reject();
+ }});
+ }).then(() => {
+ var win = window.open("data:text/html,<script>parent.opener.postMessage('ok', '*');<\/script>");
+ return new Promise(resolve => {
+ window.onmessage = function (evt) {
+ is(evt.origin, "null", "The origin of data:text/html should be null.");
+ win.close();
+ resolve();
+ }});
+ });
+
+ var obj_doc = document.getElementById("obj_doc");
+ obj_doc.data="data:text/html,%3Cbody%3E%3Cbutton%3EChild%3C/button%3E%3C/body%3E"
+ let p4 = new Promise((resolve, reject) => {
+ obj_doc.onload = function() {
+ ok(SpecialPowers.wrap(obj_doc).contentDocument.nodePrincipal.isNullPrincipal,
+ "obj_doc.document should have NullPrincipal.");
+ resolve();
+ }
+ });
+
+ var obj_svg = document.getElementById("obj_svg");
+ obj_svg.data='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 200"><circle cx="100" cy="100" r="100" fill="green" fill-opacity="0.8"/></svg>'
+ let p5 = new Promise((resolve, reject) => {
+ obj_svg.onload = function() {
+ ok(SpecialPowers.wrap(obj_svg).contentDocument.nodePrincipal.isNullPrincipal,
+ "obj_svg.contentDocument should have NullPrincipal.");
+ ok(SpecialPowers.wrap(obj_svg).getSVGDocument().nodePrincipal.isNullPrincipal,
+ "obj_svg.getSVGDocument() should have NullPrincipal.");
+ resolve();
+ }
+ });
+
+ // Test if data:stylesheet is considered same origin.
+ let p6 = new Promise((resolve, reject) => {
+ // 1. Dynamically include a css by inserting a <link> tag.
+ let link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = "data:text/css,.green-text{color:rgb(0, 255, 0)}";
+ link.onload = function() {
+ let dataStyleSheet;
+ for (let i = 0; i < document.styleSheets.length; i++) {
+ let sheet = document.styleSheets[i];
+ if (sheet.href === link.href) {
+ dataStyleSheet = sheet;
+ break;
+ }
+ }
+ ok(dataStyleSheet, "Should have found data:stylesheet");
+
+ // 2. Try to access the rule. If data:stylesheet is not considered
+ // same origin, an exception will be thrown.
+ try {
+ let rule = dataStyleSheet.cssRules;
+ ok(true, "data:stylesheet is considered same origin.");
+ } catch (ex) {
+ ok(false, "data:stylesheet is NOT considered same origin: " + ex);
+ }
+
+ resolve();
+ };
+ document.head.appendChild(link);
+ });
+
+ // Test if data:font is same-origin.
+ let p7 = new Promise((resolve, reject) => {
+ let text = document.createElement('p');
+ // Cross-domain font will not load according to [1] so we try to apply
+ // data:font to this text and see if the font can be loaded.
+ // [1] https://www.w3.org/TR/css-fonts-3/#same-origin-restriction
+ text.style = 'font-family: DataFont';
+ text.innerHTML = "This text should trigger 'TestFont' to load.";
+ document.body.appendChild(text);
+
+ document.fonts.ready.then(fontFaces => {
+ is(fontFaces.size, 1, "should FontFace entry for data:font");
+ fontFaces.forEach(fontFace => {
+ is(fontFace.status, "loaded", "data:font should be same-origin");
+ });
+ resolve();
+ },
+ _ => {
+ ok(false, "data:font is not same-origin.");
+ reject();
+ });
+ });
+
+ Promise.all([p1, p2, p3, p4, p5, p6, p7]).then(() => {
+ SimpleTest.finish();
+ }).catch((e) => {
+ ok(false, "throwing " + e);
+ SimpleTest.finish();
+ });
+}
+</script>
+
+<body onload="runTests()">
+<img style="width: 100px; height: 100px;"
+ src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"
+ id="img">
+<iframe id="iframe"></iframe>
+<iframe id="iframe1" ></iframe>
+<canvas id="canvas" class="output" width="100" height="50"></canvas>
+
+<object id="obj_doc"></object>
+<object id="obj_svg"></object>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/test_delazification_strategy.html b/dom/base/test/test_delazification_strategy.html
new file mode 100644
index 0000000000..ed75182cf0
--- /dev/null
+++ b/dom/base/test/test_delazification_strategy.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1753709 -->
+<!-- Script delazification strategy is not supposed to have any observable
+ side-effect. To make it observable, the ScriptLoader is instrumented to
+ trigger events on the script tag. These events are used to validate that
+ the strategy is used as execpected. This does not garantee that all
+ functions are delazified properly, but this should be checked in the JS
+ engine test suite.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering eager delazification.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script type="application/javascript">
+ async function WaitForScriptTagEvent() {
+ var url = "file_delazification_strategy.html";
+ var iframe = document.createElement("iframe");
+
+ // Call the resolve function when the event is one of the expected events.
+ // This is made to be used by a promise and provided to event listeners.
+ function resolve_with_event(resolve, evt) {
+ // If we have multiple script tags in the loaded source, make sure
+ // we only watch a single one.
+ if (evt.target.id != "watchme")
+ return;
+
+ switch (evt.type) {
+ case "delazification_on_demand_only":
+ case "delazification_concurrent_depth_first":
+ case "delazification_parse_everything_eagerly":
+ resolve(evt.type.split('_').slice(1).join('_'));
+ break;
+ case "scriptloader_main_thread_compile":
+ resolve(evt.type);
+ break;
+ }
+ return;
+ }
+
+ // Create an event listener, which resolves a promise.
+ let log_event;
+ let scriptLoaderTrace = new Promise((resolve, reject) => {
+ log_event = resolve_with_event.bind(this, resolve);
+ });
+
+ // Wait until the iframe is fully loaded.
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ iframe.src = url;
+ document.body.appendChild(iframe);
+ });
+
+ // Register all events.
+ let events = [
+ "delazification_on_demand_only",
+ "delazification_concurrent_depth_first",
+ "delazification_parse_everything_eagerly",
+ "scriptloader_main_thread_compile"
+ ];
+ let iwin = iframe.contentWindow;
+ for (let evt of events) {
+ iwin.addEventListener(evt, log_event);
+ }
+
+ // Add a script tag, which will trigger one of the previous events.
+ let script = document.createElement("script");
+ script.setAttribute("id", "watchme");
+ script.setAttribute("src", "file_delazification_strategy.js");
+ iframe.contentDocument.body.appendChild(script);
+
+ // Wait for the event emitted by ScriptLoader, while processing the
+ // previous script.
+ let result = await scriptLoaderTrace;
+
+ // Remove the events and the iframe.
+ for (let evt of events) {
+ iwin.removeEventListener(evt, log_event);
+ }
+ document.body.removeChild(iframe);
+ return result;
+ }
+
+ // Setting dom.expose_test_interfaces pref causes the
+ // nsScriptLoadRequest to fire event on script tags, with information
+ // about its internal state. The ScriptLoader source send events to
+ // trace these and resolve a promise with the path taken by the
+ // script loader.
+ //
+ // Setting dom.script_loader.bytecode_cache.enabled to false in order
+ // to prevent the bytecode cache to perturb this test case.
+ //
+ // Setting dom.script_loader.external_scripts.speculate_* are used to
+ // force off-main-thread compilation, while hoping that we have enough
+ // processors to run the test case
+ //
+ // Setting dom.delazification.* are used to select the delazification
+ // strategy and to check that it is well selected.
+ promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.enabled', false],
+ ['dom.script_loader.external_scripts.speculate_non_parser_inserted.enable', true],
+ ['dom.script_loader.external_scripts.speculate_async.enabled', true],
+ ['dom.script_loader.external_scripts.speculate_link_preload.enabled', true],
+ // Parse everything eagerly
+ ['dom.script_loader.delazification.strategy', 255],
+ ['dom.script_loader.delazification.max_size', 0],
+ ['dom.script_loader.delazification.min_mem', 0],
+ ]});
+
+ assert_equals(await WaitForScriptTagEvent(), "on_demand_only",
+ "[1] AttemptAsyncScriptCompile: On demand only");
+ }, "Check that max_size can disable delazification strategy");
+
+ promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.enabled', false],
+ // Enable OffMainThread compilation for everything, and cross-fingers
+ // about the number of CPU.
+ ['dom.script_loader.external_scripts.speculate_non_parser_inserted.enable', true],
+ ['dom.script_loader.external_scripts.speculate_async.enabled', true],
+ ['dom.script_loader.external_scripts.speculate_link_preload.enabled', true],
+ // Parse everything eagerly
+ ['dom.script_loader.delazification.strategy', 255],
+ ['dom.script_loader.delazification.max_size', 10485760],
+ // 4 TB should of RAM be enough.
+ ['dom.script_loader.delazification.min_mem', 4096],
+ ]});
+
+ assert_equals(await WaitForScriptTagEvent(), "on_demand_only",
+ "[2] AttemptAsyncScriptCompile: On demand only");
+ }, "Check that min_mem can disable delazification strategy");
+
+ promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.enabled', false],
+ // Enable OffMainThread compilation for everything, and cross-fingers
+ // about the number of CPU.
+ ['dom.script_loader.external_scripts.speculate_non_parser_inserted.enable', true],
+ ['dom.script_loader.external_scripts.speculate_async.enabled', true],
+ ['dom.script_loader.external_scripts.speculate_link_preload.enabled', true],
+ ['dom.script_loader.delazification.max_size', 10485760],
+ ['dom.script_loader.delazification.min_mem', 0],
+ ]});
+
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.delazification.strategy', 0],
+ ]});
+ assert_equals(await WaitForScriptTagEvent(), "on_demand_only",
+ "[3] AttemptAsyncScriptCompile: On demand only");
+
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.delazification.strategy', 2],
+ ]});
+ assert_equals(await WaitForScriptTagEvent(), "concurrent_depth_first",
+ "[3] AttemptAsyncScriptCompile: Concurrent Depth First");
+
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.delazification.strategy', 255],
+ ]});
+ assert_equals(await WaitForScriptTagEvent(), "parse_everything_eagerly",
+ "[3] AttemptAsyncScriptCompile: Parse Everything Eagerly");
+ }, "Check enabling delazification strategy works");
+
+ done();
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1753709">Mozilla Bug 1753709</a>
+</body>
+</html>
diff --git a/dom/base/test/test_document.all_iteration.html b/dom/base/test/test_document.all_iteration.html
new file mode 100644
index 0000000000..a5140d9df1
--- /dev/null
+++ b/dom/base/test/test_document.all_iteration.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for document.all iteration behavior</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_array_equals([...document.all], document.getElementsByTagName("*"));
+}, "document.all should be iterable");
+</script>
diff --git a/dom/base/test/test_document.all_unqualified.html b/dom/base/test/test_document.all_unqualified.html
new file mode 100644
index 0000000000..648ff6bb48
--- /dev/null
+++ b/dom/base/test/test_document.all_unqualified.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 823283</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=823283">Mozilla Bug 823283</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form id="f" onreset="window.valueOfAll = all; SimpleTest.executeSoon(finishTest); return false;">
+</form>
+</div>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var all = 17;
+var valueOfAll = "initial value";
+
+function finishTest()
+{
+ is(valueOfAll, document.all,
+ "wrong value for |all| in event handler attribute; note that the wrong " +
+ "value may be |document.forms.f.all| in browsers with an 'all' property " +
+ "on elements");
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", function() { document.getElementById("f").reset(); });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_document_constructor.html b/dom/base/test/test_document_constructor.html
new file mode 100644
index 0000000000..f57a525eda
--- /dev/null
+++ b/dom/base/test/test_document_constructor.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1017932
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1017932</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1017932 **/
+ var doc = new Document;
+ ok(doc instanceof Document, "Should have a document");
+ ok(!(doc instanceof XMLDocument), "Should not be an XMLDocument");
+ ok(!("load" in doc), "Should not have a load() method");
+ is(Object.getPrototypeOf(doc), Document.prototype,
+ "Should have the right proto");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1017932">Mozilla Bug 1017932</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_document_importNode_document.html b/dom/base/test/test_document_importNode_document.html
new file mode 100644
index 0000000000..73afabe1bf
--- /dev/null
+++ b/dom/base/test/test_document_importNode_document.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1177914
+-->
+<head>
+ <title>Test for Bug 1177914</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1177914">Mozilla Bug 1177914</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var thrownException = false;
+
+try {
+ document.importNode(document);
+} catch(err) {
+ thrownException = err;
+}
+
+ok(thrownException !== false, "An exception should've been thrown");
+is(thrownException.name, "NotSupportedError", "A NotSupportedError exception should've been thrown");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_document_wireframe.html b/dom/base/test/test_document_wireframe.html
new file mode 100644
index 0000000000..ddb6ea888d
--- /dev/null
+++ b/dom/base/test/test_document_wireframe.html
@@ -0,0 +1,354 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for document.getWireframe()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ /**
+ * This test creates some simple webpages, captures wireframes for them, and
+ * then compares them against some expected wireframe structures.
+ */
+
+ /**
+ * Converts some RGB values into the same uint32_t nsColor representation
+ * that getWireframe uses.
+ *
+ * @param {Number} r
+ * Red color value.
+ * @param {Number} g
+ * Green color value.
+ * @param {Number} b
+ * Blue color value.
+ * @returns {Number}
+ * The red, green and blue values composed in a single uint32_t-compatible
+ * value.
+ */
+ function nscolor(r, g, b) {
+ return (255 << 24 | b << 16 | g << 8 | r) >>> 0;
+ }
+
+ const WHITE_NSCOLOR = nscolor(255, 255, 255);
+
+ const RED_RGB = "rgb(255, 0, 0)";
+ const RED_NSCOLOR = nscolor(255, 0, 0);
+ const GREEN_RGB = "rgb(0, 255, 0)";
+ const GREEN_NSCOLOR = nscolor(0, 255, 0);
+ const BLUE_RGB = "rgb(0, 0, 255)";
+ const BLUE_NSCOLOR = nscolor(0, 0, 255);
+ const BLACK_RGB = "rgb(0, 0, 0)";
+ const BLACK_NSCOLOR = nscolor(0, 0, 0);
+ const BUILDER = "http://mochi.test:8888/document-builder.sjs?html=";
+ const TEST_PATH = "http://mochi.test:8888/tests/dom/base/test/";
+
+ /**
+ * This array contains the definition of each test. Each test is an object
+ * that expects two properties:
+ *
+ * {String} html
+ * The markup to be loaded in the page iframe that a wireframe will be
+ * generated for.
+ * {Object} expectedWireframe
+ * An approximation of the wireframe that should be generated. The
+ * approximation is due to the fact that different platforms and
+ * execution environments might produce slightly different positioning
+ * of wireframe rects. We skip comparing the position of the rects, and
+ * only look at their dimensions (sometimes only one dimension if the
+ * other is potentially more variable - for example with text). Properties
+ * included in this object will do a strict comparison with the generated
+ * wireframe. Properties in the generated wireframe that are not in the
+ * expectedWireframe will be ignored.
+ */
+ const kTests = [{
+ // Base case: a simple solid background with a single character in the
+ // foreground.
+ html: `
+ <html>
+ <style>
+ body {
+ width: 500px;
+ height: 500px;
+ background-color: ${RED_RGB};
+ color: ${BLACK_RGB};
+ overflow: hidden;
+ font-size: 12px;
+ }
+ </style>
+ <body>x</body>
+ </html>
+ `,
+ expectedWireframe: {
+ canvasBackground: RED_NSCOLOR,
+ rects: [{
+ color: BLACK_NSCOLOR,
+ height: 12,
+ type: "text",
+ }]
+ },
+ }, {
+ // Additional background on top of the main background.
+ html: `
+ <html>
+ <style>
+ body {
+ background-color: ${RED_RGB};
+ color: ${BLACK_RGB};
+ overflow: hidden;
+ font-size: 12px;
+ }
+ div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ height: 20px;
+ background-color: ${GREEN_RGB};
+ }
+ </style>
+ <body>
+ <div>x</div>
+ </body>
+ </html>
+ `,
+ expectedWireframe: {
+ canvasBackground: RED_NSCOLOR,
+ rects: [{
+ color: GREEN_NSCOLOR,
+ height: 20,
+ width: 20,
+ type: "background",
+ }, {
+ color: BLACK_NSCOLOR,
+ height: 12,
+ type: "text",
+ }]
+ },
+ }, {
+ // Image on top of the main background with another background
+ // floating in the top right.
+ html: `
+ <html>
+ <style>
+ body {
+ background-color: ${RED_RGB};
+ color: ${BLACK_RGB};
+ overflow: hidden;
+ font-size: 12px;
+ }
+ div {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 20px;
+ height: 20px;
+ background-color: ${GREEN_RGB};
+ }
+ img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 50px;
+ width: 50px;
+ }
+ </style>
+ <body>
+ <img src="${TEST_PATH}/green.png"/>
+ <div>x</div>
+ </body>
+ </html>
+ `,
+ expectedWireframe: {
+ canvasBackground: RED_NSCOLOR,
+ rects: [{
+ color: 0,
+ height: 50,
+ width: 50,
+ type: "image",
+ }, {
+ color: GREEN_NSCOLOR,
+ height: 20,
+ width: 20,
+ type: "background",
+ }, {
+ color: BLACK_NSCOLOR,
+ height: 12,
+ type: "text",
+ }]
+ },
+ }, {
+ // Image on top of the main background with another background
+ // floating over the image
+ html: `
+ <html>
+ <style>
+ body {
+ width: 500px;
+ height: 500px;
+ background-color: ${RED_RGB};
+ color: ${BLACK_RGB};
+ overflow: hidden;
+ font-size: 12px;
+ }
+ div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ height: 20px;
+ background-color: ${BLUE_RGB};
+ }
+ img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 50px;
+ width: 50px;
+ }
+ </style>
+ <body>
+ <img src="${TEST_PATH}/green.png"/>
+ <div>x</div>
+ </body>
+ </html>
+ `,
+ expectedWireframe: {
+ canvasBackground: RED_NSCOLOR,
+ rects: [{
+ color: 0,
+ height: 50,
+ width: 50,
+ type: "image",
+ }, {
+ color: BLUE_NSCOLOR,
+ height: 20,
+ width: 20,
+ type: "background",
+ }, {
+ color: BLACK_NSCOLOR,
+ height: 12,
+ type: "text",
+ }]
+ },
+ }, {
+ // Bug 1759919 - Transformed items incorrectly causing us to not keep hit
+ // testing stuff.
+ html: `
+ <!doctype html>
+ <style>:root { background-color: white; } body { margin: 0 }</style>
+ <div style="transform: rotate(90deg); width: 20px; height: 20px; overflow: clip;">
+ <div style="width: 100%; height: 100%; background-color: blue;"></div>
+ </div>
+ <div style="transform: rotate(90deg); width: 20px; height: 20px; overflow: clip;">
+ <div style="width: 100%; height: 100%; background-color: red;"></div>
+ </div>
+ `,
+ expectedWireframe: {
+ canvasBackground: WHITE_NSCOLOR,
+ rects: [{
+ color: RED_NSCOLOR,
+ height: 20,
+ width: 20,
+ x: 0,
+ y: 0,
+ type: "background",
+ }, {
+ color: BLUE_NSCOLOR,
+ height: 20,
+ width: 20,
+ x: 0,
+ y: 20,
+ type: "background",
+ }],
+ }
+ }];
+
+ /**
+ * Returns a Promise once page has been loaded in frame.
+ *
+ * @param {Element} frame
+ * The iframe to load the page in.
+ * @param {String} page
+ * The URL of the page to load in the frame.
+ * @returns Promise
+ * @resolves undefined
+ * Once the load event has fired for the frame.
+ */
+ function loadInIframe(frame, page) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", resolve, { once: true });
+ frame.src = page;
+ });
+ }
+
+ /**
+ * Compares a generated wireframe to an Object that contains some or all of
+ * the expected structure of the generated wireframe.
+ *
+ * If the wireframe doesn't contain the expected number of rects, the
+ * serialized structure of both the wireframe and approximateWireframe will
+ * be dumped to stdout.
+ *
+ * @param {Wireframe} wireframe
+ * A wireframe generated via document.getWireframe()
+ * @param {Object} approximateWireframe
+ * An object that closely resembles a wireframe but the rects in the
+ * rects property do not need to contain all of the properties expected
+ * in a WireframeTaggedRect. Skipped properties won't be checked.
+ */
+ function assertApproximateWireframe(wireframe, approximateWireframe) {
+ is(
+ wireframe.canvasBackground,
+ approximateWireframe.canvasBackground,
+ "Canvas backgrounds match."
+ );
+ is(
+ wireframe.rects.length,
+ approximateWireframe.rects.length,
+ "Same rect count"
+ );
+ if (wireframe.rects.length != approximateWireframe.rects.length) {
+ dump(
+ "Generated wireframe: " + JSON.stringify(wireframe, null, "\t") + "\n"
+ );
+ dump(
+ "Expected approximate wireframe: " +
+ JSON.stringify(approximateWireframe, null, "\t") +
+ "\n"
+ );
+ }
+
+ for (let index = 0; index < approximateWireframe.length; ++index) {
+ let wireframeRect = wireframe.rects[index];
+ let approximationRect = approximateWireframe.rects[index];
+ for (let prop of approximationRect) {
+ is(
+ wireframeRect[prop],
+ approximationRect[prop],
+ `Property ${prop} should be equal.`
+ );
+ }
+ }
+ }
+
+ add_task(async () => {
+ const iframe = document.getElementById("iframe");
+
+ for (let testDefinition of kTests) {
+ let pageURL = BUILDER + encodeURIComponent(testDefinition.html);
+ await loadInIframe(iframe, pageURL);
+ let wireframe = SpecialPowers.wrap(
+ iframe.contentDocument
+ ).getWireframe();
+ assertApproximateWireframe(wireframe, testDefinition.expectedWireframe);
+ }
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<iframe id="iframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_domparser_null_char.html b/dom/base/test/test_domparser_null_char.html
new file mode 100644
index 0000000000..af35d89ec8
--- /dev/null
+++ b/dom/base/test/test_domparser_null_char.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=817469
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 817469</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=817469">Mozilla Bug 817469</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 817469 **/
+ var doc = new DOMParser().parseFromString("\x00<div id='myElement'>", "text/html");
+ isnot(doc.getElementById("myElement"), null, "Should not stop at null");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_domparsing.html b/dom/base/test/test_domparsing.html
new file mode 100644
index 0000000000..371fb1d178
--- /dev/null
+++ b/dom/base/test/test_domparsing.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test for the DOM Parsing and Serialization Standard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=816410">Mozilla Bug 816410</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+/** Test for Bug 816410 **/
+
+function throws(fn, type, message) {
+ try {
+ fn();
+ ok(false, message);
+ } catch (e) {
+ if (type) {
+ is(e.name, type, message);
+ } else {
+ ok(true, message);
+ }
+ }
+}
+
+let parser = new DOMParser();
+is(typeof parser.parseFromString, "function", "parseFromString should exist");
+is(typeof parser.parseFromBuffer, "undefined", "parseFromBuffer should NOT be visible from unprivileged callers");
+is(typeof parser.parseFromStream, "undefined", "parseFromStream should NOT be visible from unprivileged callers");
+is(typeof parser.init, "undefined", "init should NOT be visible from unprivileged callers");
+
+// The three-arguments constructor should not be visible from
+// unprivileged callers for interoperability with other browsers.
+// But we have no way to do that right now.
+try {
+ new DOMParser(undefined);
+ new DOMParser(null);
+ new DOMParser(false);
+ new DOMParser(0);
+ new DOMParser("");
+ new DOMParser({});
+} catch (e) {
+ todo(false, "DOMParser constructor should not throw for extra arguments");
+}
+
+let serializer = new XMLSerializer();
+is(typeof serializer.serializeToString, "function", "serializeToString should exist");
+is(typeof serializer.serializeToStream, "undefined", "serializeToStream should NOT be visible from unprivileged callers");
+
+// XMLSerializer constructor should not throw for extra arguments
+new XMLSerializer(undefined);
+new XMLSerializer(null);
+new XMLSerializer(false);
+new XMLSerializer(0);
+new XMLSerializer("");
+new XMLSerializer({});
+
+let tests = [
+ {input: "<html></html>", type: "text/html",
+ expected: '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>'},
+ {input: "<xml></xml>", type: "text/xml", expected: "<xml/>"},
+ {input: "<xml></xml>", type: "application/xml", expected: "<xml/>"},
+ {input: "<html></html>", type: "application/xhtml+xml", expected: "<html/>"},
+ {input: "<svg></svg>", type: "image/svg+xml", expected: "<svg/>"},
+];
+for (let t of tests) {
+ is(serializer.serializeToString(parser.parseFromString(t.input, t.type)), t.expected,
+ "parseFromString test for " + t.type);
+}
+
+throws(function() {
+ parser.parseFromString("<xml></xml>", "foo/bar");
+}, "TypeError", "parseFromString should throw for the unknown type");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_domrequest.html b/dom/base/test/test_domrequest.html
new file mode 100644
index 0000000000..1aea26f657
--- /dev/null
+++ b/dom/base/test/test_domrequest.html
@@ -0,0 +1,233 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOMRequest</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+"use strict";
+
+var reqserv = SpecialPowers.getDOMRequestService();
+ok("createRequest" in reqserv, "appears to be a service");
+
+function testBasics() {
+ // create a request
+ var req = reqserv.createRequest(window);
+ ok("result" in req, "request has result");
+ ok("error" in req, "request has error");
+ ok("onsuccess" in req, "request has onsuccess");
+ ok("onerror" in req, "request has onerror");
+ ok("readyState" in req, "request has readyState");
+ ok("then" in req, "request has then");
+
+ is(req.readyState, "pending", "readyState is pending");
+ is(req.result, undefined, "result is undefined");
+ is(req.onsuccess, null, "onsuccess is null");
+ is(req.onerror, null, "onerror is null");
+
+ runTest();
+}
+
+function testSuccess() {
+ // fire success
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onsuccess = function(e) {
+ ev = e;
+ }
+ var result = null;
+ var promise = req.then(function(r) {
+ is(r, "my result", "correct result when resolving the promise");
+ result = r;
+ runTest();
+ }, function(e) {
+ ok(false, "promise should not be rejected");
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireSuccess(req, "my result");
+ ok(ev, "got success event");
+ is(ev.type, "success", "correct type during success");
+ is(ev.target, req, "correct target during success");
+ is(req.readyState, "done", "correct readyState after success");
+ is(req.error, null, "correct error after success");
+ is(req.result, "my result", "correct result after success");
+ is(result, null, "Promise should not be resolved synchronously");
+}
+
+function testError() {
+ // fire error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ }
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMException, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireError(req, "OhMyError");
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "UnknownError", "correct error type after error");
+ is(req.error.message, "OhMyError", "correct error message after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+function testDetailedError() {
+ // fire detailed error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ };
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMException, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ SpecialPowers.wrwp(req).fireDetailedError(new DOMException("detailedError"));
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "UnknownError", "correct error type after error");
+ is(req.error.message, "detailedError", "correct error message after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+function testThenAfterSuccess() {
+ // fire success
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onsuccess = function(e) {
+ ev = e;
+ }
+ reqserv.fireSuccess(req, "my result");
+ ok(ev, "got success event");
+ is(ev.type, "success", "correct type during success");
+ is(ev.target, req, "correct target during success");
+ is(req.readyState, "done", "correct readyState after success");
+ is(req.error, null, "correct error after success");
+ is(req.result, "my result", "correct result after success");
+ var result = null;
+ var promise = req.then(function(r) {
+ is(r, "my result", "correct result when resolving the promise");
+ result = r;
+ runTest();
+ }, function(e) {
+ ok(false, "promise should not be rejected");
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ is(result, null, "Promise should not be resolved synchronously");
+}
+
+function testThenAfterError() {
+ // fire error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ }
+ reqserv.fireError(req, "OhMyError");
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "UnknownError", "correct error type after error");
+ is(req.error.message, "OhMyError", "correct error message after error");
+ is(req.result, undefined, "correct result after error");
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMException, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+function testDetailedError() {
+ // fire detailed error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ };
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMException, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ SpecialPowers.wrap(req).fireDetailedError(new DOMException("detailedError"));
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "Error", "correct error type after error");
+ is(req.error.message, "detailedError", "correct error message after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+var tests = [
+ testBasics,
+ testSuccess,
+ testError,
+ testDetailedError,
+ testThenAfterSuccess,
+ testThenAfterError,
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_domrequesthelper.xhtml b/dom/base/test/test_domrequesthelper.xhtml
new file mode 100644
index 0000000000..e5f3d8f0f1
--- /dev/null
+++ b/dom/base/test/test_domrequesthelper.xhtml
@@ -0,0 +1,547 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="DOMRequestHelper Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ const {DOMRequestIpcHelper} = ChromeUtils.import("resource://gre/modules/DOMRequestHelper.jsm");
+ let obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
+
+ function DummyHelperSubclass() {
+ this.onuninit = null;
+ }
+ DummyHelperSubclass.prototype = {
+ __proto__: DOMRequestIpcHelper.prototype,
+ uninit: function() {
+ if (typeof this.onuninit === "function") {
+ this.onuninit();
+ }
+ this.onuninit = null;
+ }
+ };
+
+ var dummy = new DummyHelperSubclass();
+ var isDOMRequestHelperDestroyed = false;
+
+ /**
+ * Init & destroy.
+ */
+ function initDOMRequestHelperTest(aMessages) {
+ // If we haven't initialized the DOMRequestHelper object, its private
+ // properties will be undefined, but once destroyDOMRequestHelper is
+ // called, they're set to null.
+ var expectedPrivatePropertyValues =
+ isDOMRequestHelperDestroyed ? null : undefined;
+
+ is(dummy._requests, expectedPrivatePropertyValues, "Request is expected");
+ is(dummy._messages, undefined, "Messages is undefined");
+ is(dummy._window, expectedPrivatePropertyValues, "Window is expected");
+
+ dummy.initDOMRequestHelper(window, aMessages);
+
+ ok(dummy._window, "Window exists");
+ is(dummy._window, window, "Correct window");
+ if (aMessages) {
+ is(typeof dummy._listeners, "object", "Listeners is an object");
+ }
+ }
+
+ function destroyDOMRequestHelperTest() {
+ dummy.destroyDOMRequestHelper();
+ isDOMRequestHelperDestroyed = true;
+
+ is(dummy._requests, null, "Request is null");
+ is(dummy._messages, undefined, "Messages is undefined");
+ is(dummy._window, null, "Window is null");
+ }
+
+ /**
+ * Message listeners.
+ */
+ function checkMessageListeners(aExpectedListeners, aCount) {
+ info("Checking message listeners\n" + "Expected listeners " +
+ JSON.stringify(aExpectedListeners) + " \nExpected count " + aCount);
+ let count = 0;
+ Object.keys(dummy._listeners).forEach(function(name) {
+ count++;
+ is(aExpectedListeners[name].weakRef, dummy._listeners[name].weakRef,
+ "Message found " + name + " - Same weakRef");
+ is(aExpectedListeners[name].count, dummy._listeners[name].count,
+ "Message found " + name + " - Same count");
+ });
+ is(aCount, count, "Correct number of listeners");
+ }
+
+ function addMessageListenersTest(aMessages, aExpectedListeners, aCount) {
+ dummy.addMessageListeners(aMessages);
+ info(JSON.stringify(dummy._listeners));
+ checkMessageListeners(aExpectedListeners, aCount);
+ }
+
+ function removeMessageListenersTest(aMessages, aExpectedListeners, aCount) {
+ dummy.removeMessageListeners(aMessages);
+ checkMessageListeners(aExpectedListeners, aCount);
+ }
+
+ /**
+ * Utility function to test window destruction behavior. In general this
+ * function does the following:
+ *
+ * 1) Create a new iframe
+ * 2) Create a new DOMRequestHelper
+ * 3) initDOMRequestHelper(), optionally with weak or strong listeners
+ * 4) Optionally force a garbage collection to reap weak references
+ * 5) Destroy the iframe triggering an inner-window-destroyed event
+ * 6) Callback with a boolean indicating if DOMRequestHelper.uninit() was
+ * called.
+ *
+ * Example usage:
+ *
+ * checkWindowDestruction({ messages: ["foo"], gc: true },
+ * function(uninitCalled) {
+ * // expect uninitCalled === false since GC with only weak refs
+ * });
+ */
+ const TOPIC = "inner-window-destroyed";
+ function checkWindowDestruction(aOptions, aCallback) {
+ aOptions = aOptions || {};
+ aOptions.messages = aOptions.messages || [];
+ aOptions.gc = !!aOptions.gc;
+
+ if (typeof aCallback !== "function") {
+ aCallback = function() { };
+ }
+
+ let uninitCalled = false;
+
+ // Use a secondary observer so we know when to expect the uninit(). We
+ // can then reasonably expect uninitCalled to be set properly on the
+ // next tick.
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic !== TOPIC) {
+ return;
+ }
+ obs.removeObserver(observer, TOPIC);
+ setTimeout(function() {
+ aCallback(uninitCalled);
+ });
+ }
+ };
+
+ let frame = document.createXULElement("iframe");
+ frame.onload = function() {
+ obs.addObserver(observer, TOPIC);
+ // Create dummy DOMRequestHelper specific to checkWindowDestruction()
+ let cwdDummy = new DummyHelperSubclass();
+ cwdDummy.onuninit = function() {
+ uninitCalled = true;
+
+ if (!aOptions.messages || !aOptions.messages.length) {
+ return;
+ }
+
+ // If all message listeners are removed, cwdDummy.receiveMessage
+ // should never be called.
+ ppmm.broadcastAsyncMessage(aOptions.messages[0].name);
+ };
+
+ // Test if we still receive messages from ppmm.
+ cwdDummy.receiveMessage = function(aMessage) {
+ ok(false, "cwdDummy.receiveMessage should NOT be called: " + aMessage.name);
+ };
+
+ cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages);
+ // Make sure to clear our strong ref here so that we can test our
+ // weak reference listeners and observer.
+ cwdDummy = null;
+ if (aOptions.gc) {
+ Cu.schedulePreciseGC(function() {
+ SpecialPowers.DOMWindowUtils.cycleCollect();
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ document.documentElement.removeChild(frame);
+ });
+ return;
+ }
+ document.documentElement.removeChild(frame);
+ };
+ document.documentElement.appendChild(frame);
+ }
+
+ /**
+ * Test steps.
+ */
+ var tests = [
+ function() {
+ info("== InitDOMRequestHelper no messages");
+ initDOMRequestHelperTest(null);
+ next();
+ },
+ function() {
+ info("== DestroyDOMRequestHelper");
+ destroyDOMRequestHelperTest();
+ next();
+ },
+ function() {
+ info("== InitDOMRequestHelper empty array");
+ initDOMRequestHelperTest([]);
+ checkMessageListeners({}, 0);
+ next();
+ },
+ function() {
+ info("== DestroyDOMRequestHelper");
+ destroyDOMRequestHelperTest();
+ next();
+ },
+ function() {
+ info("== InitDOMRequestHelper with strings array");
+ initDOMRequestHelperTest(["name1", "nameN"]);
+ checkMessageListeners({"name1": {weakRef: false, count: 1},
+ "nameN": {weakRef: false, count: 1}}, 2);
+ next();
+ },
+ function() {
+ info("== DestroyDOMRequestHelper");
+ destroyDOMRequestHelperTest();
+ next();
+ },
+ function() {
+ info("== InitDOMRequestHelper with objects array");
+ initDOMRequestHelperTest([{
+ name: "name1",
+ weakRef: false
+ }, {
+ name: "nameN",
+ weakRef: true
+ }]);
+ checkMessageListeners({
+ "name1": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 2);
+ next();
+ },
+ function() {
+ info("== AddMessageListeners empty array");
+ addMessageListenersTest([], {
+ "name1": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 2);
+ next();
+ },
+ function() {
+ info("== AddMessageListeners null");
+ addMessageListenersTest(null, {
+ "name1": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 2);
+ next();
+ },
+ function() {
+ info("== AddMessageListeners new listener, string only");
+ addMessageListenersTest("name2", {
+ "name1": {weakRef: false, count: 1},
+ "name2": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 3);
+ next();
+ },
+ function() {
+ info("== AddMessageListeners new listeners, strings array");
+ addMessageListenersTest(["name3", "name4"], {
+ "name1": {weakRef: false, count: 1},
+ "name2": {weakRef: false, count: 1},
+ "name3": {weakRef: false, count: 1},
+ "name4": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 5);
+ next();
+ },
+ function() {
+ info("== AddMessageListeners new listeners, objects array");
+ addMessageListenersTest([{
+ name: "name5",
+ weakRef: true
+ }, {
+ name: "name6",
+ weakRef: false
+ }], {
+ "name1": {weakRef: false, count: 1},
+ "name2": {weakRef: false, count: 1},
+ "name3": {weakRef: false, count: 1},
+ "name4": {weakRef: false, count: 1},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 7);
+ next();
+ },
+ function() {
+ info("== RemoveMessageListeners, null");
+ removeMessageListenersTest(null, {
+ "name1": {weakRef: false, count: 1},
+ "name2": {weakRef: false, count: 1},
+ "name3": {weakRef: false, count: 1},
+ "name4": {weakRef: false, count: 1},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 7);
+ next();
+ },
+ function() {
+ info("== RemoveMessageListeners, one message");
+ removeMessageListenersTest("name1", {
+ "name2": {weakRef: false, count: 1},
+ "name3": {weakRef: false, count: 1},
+ "name4": {weakRef: false, count: 1},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 6);
+ next();
+ },
+ function() {
+ info("== RemoveMessageListeners, array of messages");
+ removeMessageListenersTest(["name2", "name3"], {
+ "name4": {weakRef: false, count: 1},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 4);
+ next();
+ },
+ function() {
+ info("== RemoveMessageListeners, unknown message");
+ removeMessageListenersTest("unknown", {
+ "name4": {weakRef: false, count: 1},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 4);
+ next();
+ },
+ function() {
+ try {
+ info("== AddMessageListeners, same message, same kind");
+ addMessageListenersTest("name4", {
+ "name4": {weakRef: false, count: 2},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 4);
+ next();
+ } catch (ex) {
+ ok(false, "Unexpected exception " + ex);
+ }
+ },
+ function() {
+ info("== AddMessageListeners, same message, different kind");
+ try {
+ addMessageListenersTest({name: "name4", weakRef: true}, {
+ "name4": {weakRef: false, count: 2},
+ "name5": {weakRef: true, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 4);
+ ok(false, "Should have thrown an exception");
+ } catch (ex) {
+ ok(true, "Expected exception");
+ next();
+ }
+ },
+ function() {
+ info("== RemoveMessageListeners, message with two listeners");
+ try {
+ removeMessageListenersTest(["name4", "name5"], {
+ "name4": {weakRef: false, count: 1},
+ "name6": {weakRef: false, count: 1},
+ "nameN": {weakRef: true, count: 1}
+ }, 3);
+ next();
+ } catch (ex) {
+ ok(false, "Unexpected exception " + ex);
+ }
+ },
+ function() {
+ info("== Test createRequest()");
+ ok(DOMRequest, "DOMRequest object exists");
+ var req = dummy.createRequest();
+ ok(DOMRequest.isInstance(req), "Returned a DOMRequest");
+ next();
+ },
+ function() {
+ info("== Test getRequestId(), removeRequest() and getRequest()");
+ var req = dummy.createRequest();
+ var id = dummy.getRequestId(req);
+ is(typeof id, "string", "id is a string");
+ var req_ = dummy.getRequest(id);
+ is(req, req_, "Got correct request");
+ dummy.removeRequest(id);
+ req = dummy.getRequest(id);
+ is(req, undefined, "No request");
+ next();
+ },
+ function() {
+ info("== Test createPromise()");
+ ok(Promise, "Promise object exists");
+ var promise = dummy.createPromise(function(resolve, reject) {
+ resolve(true);
+ });
+ ok(promise instanceof Promise, "Returned a Promise");
+ promise.then(next);
+ },
+ function() {
+ info("== Test createPromiseWithId()");
+ var _resolverId;
+ var promise = dummy.createPromiseWithId(function(resolverId) {
+ _resolverId = resolverId;
+ });
+ var resolver = dummy.getPromiseResolver(_resolverId);
+ ok(promise instanceof Promise, "Returned a Promise");
+ ok(typeof _resolverId === "string", "resolverId is a string");
+ ok(resolver != null, "resolverId is a valid id");
+ next();
+ },
+ function() {
+ info("== Test getResolver()");
+ var id;
+ var resolver;
+ var promise = dummy.createPromise(function(resolve, reject) {
+ var r = { resolve: resolve, reject: reject };
+ id = dummy.getPromiseResolverId(r);
+ resolver = r;
+ ok(typeof id === "string", "id is a string");
+ r.resolve(true);
+ }).then(function(unused) {
+ var r = dummy.getPromiseResolver(id);
+ ok(resolver === r, "Get succeeded");
+ next();
+ });
+ },
+ function() {
+ info("== Test removeResolver");
+ var id;
+ var promise = dummy.createPromise(function(resolve, reject) {
+ var r = { resolve: resolve, reject: reject };
+ id = dummy.getPromiseResolverId(r);
+ ok(typeof id === "string", "id is a string");
+
+ var resolver = dummy.getPromiseResolver(id);
+ info("Got resolver " + JSON.stringify(resolver));
+ ok(resolver === r, "Resolver get succeeded");
+
+ r.resolve(true);
+ }).then(function(unused) {
+ dummy.removePromiseResolver(id);
+ var resolver = dummy.getPromiseResolver(id);
+ ok(resolver === undefined, "removeResolver: get failed");
+ next();
+ });
+ },
+ function() {
+ info("== Test takeResolver");
+ var id;
+ var resolver;
+ var promise = dummy.createPromise(function(resolve, reject) {
+ var r = { resolve: resolve, reject: reject };
+ id = dummy.getPromiseResolverId(r);
+ resolver = r;
+ ok(typeof id === "string", "id is a string");
+
+ var gotR = dummy.getPromiseResolver(id);
+ ok(gotR === r, "resolver get succeeded");
+
+ r.resolve(true);
+ }).then(function(unused) {
+ var r = dummy.takePromiseResolver(id);
+ ok(resolver === r, "take should succeed");
+
+ r = dummy.getPromiseResolver(id);
+ ok(r === undefined, "takeResolver: get failed");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed without messages and without GC");
+ checkWindowDestruction({ gc: false }, function(uninitCalled) {
+ ok(uninitCalled, "uninit() should have been called");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed without messages and with GC");
+ checkWindowDestruction({ gc: true }, function(uninitCalled) {
+ ok(!uninitCalled, "uninit() should NOT have been called");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed with weak messages and without GC");
+ checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }],
+ gc: false }, function(uninitCalled) {
+ ok(uninitCalled, "uninit() should have been called");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed with weak messages and with GC");
+ checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }],
+ gc: true }, function(uninitCalled) {
+ ok(!uninitCalled, "uninit() should NOT have been called");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed with strong messages and without GC");
+ checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }],
+ gc: false }, function(uninitCalled) {
+ ok(uninitCalled, "uninit() should have been called");
+ next();
+ });
+ },
+ function() {
+ info("== Test window destroyed with strong messages and with GC");
+ checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }],
+ gc: true }, function(uninitCalled) {
+ ok(uninitCalled, "uninit() should have been called");
+ next();
+ });
+ }
+ ];
+
+ function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ function start() {
+ SimpleTest.waitForExplicitFinish();
+ next();
+ }
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+</window>
diff --git a/dom/base/test/test_domwindowutils.html b/dom/base/test/test_domwindowutils.html
new file mode 100644
index 0000000000..7485669d90
--- /dev/null
+++ b/dom/base/test/test_domwindowutils.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for DOMWindowUtils</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var utils = SpecialPowers.getDOMWindowUtils(window);
+function test_sendMouseEventDefaults() {
+ var x = 1, y = 2, button = 1, clickCount = 2,
+ modifiers = SpecialPowers.Ci.nsIDOMWindowUtils.MODIFIER_SHIFT;
+
+ window.addEventListener("mousedown", function(evt) {
+ // Mandatory args
+ // coordinates may change slightly due to rounding
+ ok((evt.clientX <= x+2) && (evt.clientX >= x-2), "check x");
+ ok((evt.clientY <= y+2) && (evt.clientY >= y-2), "check y");
+ is(evt.button, button, "check button");
+ is(evt.detail, clickCount, "check click count");
+ is(evt.getModifierState("Shift"), true, "check modifiers");
+
+ // Default value for optionals
+ is(evt.mozPressure, 0, "check pressure");
+ is(evt.mozInputSource, MouseEvent.MOZ_SOURCE_MOUSE, "check input source");
+ is(evt.isSynthesized, undefined, "check isSynthesized is undefined in content");
+ is(SpecialPowers.wrap(evt).isSynthesized, true, "check isSynthesized is true from chrome");
+ SimpleTest.executeSoon(next);
+ }, {once: true});
+
+ // Only pass mandatory arguments and check default values
+ utils.sendMouseEvent("mousedown", x, y, button, clickCount, modifiers);
+}
+
+function test_sendMouseEventOptionals() {
+ var x = 1, y = 2, button = 1, clickCount = 3,
+ modifiers = SpecialPowers.Ci.nsIDOMWindowUtils.MODIFIER_SHIFT,
+ pressure = 0.5,
+ source = MouseEvent.MOZ_SOURCE_KEYBOARD;
+
+ window.addEventListener("mouseup", function(evt) {
+ is(evt.mozInputSource, source, "explicit input source is valid");
+ is(SpecialPowers.wrap(evt).isSynthesized, false, "we can dispatch event that don't look synthesized");
+ SimpleTest.executeSoon(next);
+ }, {once: true});
+
+ // Check explicit value for optional args
+ utils.sendMouseEvent("mouseup", x, y, button, clickCount, modifiers,
+ false, pressure, source, false);
+}
+
+function test_sendMouseEvent4thButton() {
+ const x = 1, y = 2, button = 3, clickCount = 1, modifiers = 0;
+
+ window.addEventListener("mousedown", evt => {
+ is(evt.buttons, 2 ** button, "check button");
+ SimpleTest.executeSoon(next);
+ }, { once: true });
+
+ utils.sendMouseEvent("mousedown", x, y, button, clickCount, modifiers);
+}
+
+function test_sendMouseEvent5thButton() {
+ const x = 1, y = 2, button = 4, clickCount = 1, modifiers = 0;
+
+ window.addEventListener("mousedown", evt => {
+ is(evt.buttons, 2 ** button, "check button");
+ SimpleTest.executeSoon(next);
+ }, { once: true });
+
+ utils.sendMouseEvent("mousedown", x, y, button, clickCount, modifiers);
+}
+
+function test_getUnanimatedComputedStyle() {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.animations-api.core.enabled", true],
+ ["dom.animations-api.getAnimations.enabled", true],
+ ["dom.animations-api.timelines.enabled", true],
+ ],
+ },
+ () => {
+ window.open("file_domwindowutils_animation.html");
+ }
+ );
+}
+
+function test_setDynamicToolbarMaxHeight() {
+ window.open("file_domwindowutils_dynamic_toolbar.html");
+}
+
+var tests = [
+ test_sendMouseEventDefaults,
+ test_sendMouseEventOptionals,
+ test_sendMouseEvent4thButton,
+ test_sendMouseEvent5thButton,
+ test_getUnanimatedComputedStyle,
+ test_setDynamicToolbarMaxHeight
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+function start() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.executeSoon(next);
+}
+
+window.addEventListener("load", start);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_element.matches.html b/dom/base/test/test_element.matches.html
new file mode 100644
index 0000000000..c47ee6024a
--- /dev/null
+++ b/dom/base/test/test_element.matches.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=886308
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886308</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 886308 **/
+ ok(document.head.matches("head"), "head should match 'head'");
+ ok(document.querySelector("link").matches("html *"), "link is a descendant of 'html'");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=886308">Mozilla Bug 886308</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_elementTraversal.html b/dom/base/test/test_elementTraversal.html
new file mode 100644
index 0000000000..ae9667db62
--- /dev/null
+++ b/dom/base/test/test_elementTraversal.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=444722
+-->
+<head>
+ <title>Test for the ElementTraversal spec</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="http://dev.w3.org/2006/webapi/ElementTraversal/publish/ElementTraversal.html">ElementTraversal</a>
+<div id="content" style="display: none">
+<span>span</span><div>div</div>
+<!--comment goes here-->
+<p id="p1">p1</p>
+text here
+<p id="p2">p2</p>
+<span>a<span>b</span>c<span>d</span>e</span>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var c = document.getElementById('content');
+var cc = c.children;
+
+var contents = ["span", "div", "p1", "p2", "abcde"];
+function testContent() {
+ for(i = 0, e = c.firstElementChild; e; e = e.nextElementSibling, i++) {
+ is(e.textContent, contents[i], "wrong element contents");
+ is(e, c.children[i], "wrong element");
+ is(e, c.children.item(i), "wrong element");
+ }
+ is(i, contents.length, "wrong number of element siblings");
+ is(i, c.childElementCount, "wrong number of child elements");
+ is(i, c.children.length, "wrong number of child elements");
+
+ // Nuke all elements to retest the child list.
+ c.innerHTML = c.innerHTML;
+
+ for(i--, e = c.lastElementChild; e; e = e.previousElementSibling, i--) {
+ is(e.textContent, contents[i], "g element contents");
+ is(e, c.children[i], "wrong element");
+ is(e, c.children.item(i), "wrong element");
+ }
+ is(i, -1, "wrong number of element siblings");
+}
+
+testContent();
+
+is(cc.length, 5, "wrong number of child elements");
+is(c.childElementCount, 5, "wrong number of child elements");
+
+var p1 = document.getElementById('p1');
+var p2 = document.getElementById('p2');
+is(p1.nextElementSibling, p2, "wrong sibling");
+is(p2.previousElementSibling, p1, "wrong sibling");
+
+u = document.createElement('u');
+u.textContent = 'u';
+c.insertBefore(u, p2);
+is(cc.length, 6, "wrong number of child elements");
+is(c.childElementCount, 6, "wrong number of child elements");
+is(p1.nextElementSibling, u, "wrong sibling");
+is(p2.previousElementSibling, u, "wrong sibling");
+
+contents.splice(3, 0, "u");
+testContent();
+
+var p1 = document.getElementById('p1');
+var p2 = document.getElementById('p2');
+c.removeChild(p1);
+c.removeChild(p2);
+is(cc.length, 4, "wrong number of child elements");
+is(c.childElementCount, 4, "wrong number of child elements");
+
+contents.splice(2, 1);
+contents.splice(3, 1);
+testContent();
+
+tw = document.createTreeWalker(document.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ null);
+e = document.documentElement;
+
+elemsTested = 0;
+done = false;
+while(!done) {
+ is(tw.currentNode, e, "wrong element:" + tw.currentNode + " != " + e);
+ elemsTested++;
+
+ if(tw.firstChild()) {
+ e = e.firstElementChild;
+ }
+ else {
+ while (!tw.nextSibling()) {
+ if (!tw.parentNode()) {
+ done = true;
+ break;
+ }
+ e = e.parentNode;
+ }
+ e = e.nextElementSibling;
+ }
+}
+is(elemsTested, document.getElementsByTagName("*").length,
+ "wrong number of elements");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_element_closest.html b/dom/base/test/test_element_closest.html
new file mode 100644
index 0000000000..2d06f2fbdd
--- /dev/null
+++ b/dom/base/test/test_element_closest.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055533
+-->
+<head>
+ <title>Test for Bug 1055533</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body id="body">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1055533">Mozilla Bug 1055533</a>
+ <div id="test8" class="div3">
+ <div id="test7" class="div2">
+ <div id="test6" class="div1">
+ <form id="test10" class="form2"></form>
+ <form id="test5" class="form1" name="form-a">
+ <input id="test1" class="input1" required>
+ <fieldset class="fieldset2" id="test2">
+ <select id="test3" class="select1" required>
+ <option default id="test4" value="">Test4</option>
+ <option selected id="test11">Test11</option>
+ <option id="test12">Test12</option>
+ <option id="test13">Test13</option>
+ </select>
+ <input id="test9" type="text" required>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </div>
+<script class="testbody" type="text/javascript">
+ test("select" , "test12", "test3");
+ test("fieldset" , "test13", "test2");
+ test("div" , "test13", "test6");
+ test("body" , "test3" , "body");
+
+ test("[default]" , "test4" , "test4");
+ test("[selected]" , "test4" , "");
+ test("[selected]" , "test11", "test11");
+ test('[name="form-a"]' , "test12", "test5");
+ test('form[name="form-a"]' , "test13", "test5");
+ test("input[required]" , "test9" , "test9");
+ test("select[required]" , "test9" , "");
+
+ test("div:not(.div1)" , "test13", "test7");
+ test("div.div3" , "test6" , "test8");
+ test("div#test7" , "test1" , "test7");
+
+ test(".div3 > .div2" , "test12", "test7");
+ test(".div3 > .div1" , "test12", "");
+ test("form > input[required]" , "test9" , "");
+ test("fieldset > select[required]", "test12", "test3");
+
+ test("input + fieldset" , "test6" , "");
+ test("form + form" , "test3" , "test5");
+ test("form + form" , "test5" , "test5");
+
+ test(":empty" , "test10", "test10");
+ test(":last-child" , "test11", "test2");
+ test(":first-child" , "test12", "test3");
+ test(":invalid" , "test11", "test2");
+
+ test(":scope" , "test4", "test4");
+ test("select > :scope" , "test4", "test4");
+ test("div > :scope" , "test4", "");
+ try {
+ test(":has(> :scope)" , "test4", "test3");
+ } catch(e) {
+ todo(false, ":has(> :scope) [:has is not implemented yet]");
+ }
+function test(aSelector, aElementId, aTargetId) {
+ var el = document.getElementById(aElementId).closest(aSelector);
+ if (el === null) {
+ is("", aTargetId, aSelector);
+ } else {
+ is(el.id, aTargetId, aSelector);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_embed_xorigin_document.html b/dom/base/test/test_embed_xorigin_document.html
new file mode 100644
index 0000000000..3075f1897b
--- /dev/null
+++ b/dom/base/test/test_embed_xorigin_document.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>test for embedding a cross-origin document</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+
+
+<script>
+// Get the path of the current test, without hostname or filename.
+const testPath = window.location.href.replace("http://mochi.test:8888", "");
+const testDir = testPath.substring(0, testPath.lastIndexOf('/') + 1);
+const embedHasBrowsingContext = SpecialPowers.getBoolPref("browser.opaqueResponseBlocking.syntheticBrowsingContext", false);
+const imageTypeORB = embedHasBrowsingContext
+ ? SpecialPowers.Ci.nsIObjectLoadingContent.TYPE_DOCUMENT
+ : SpecialPowers.Ci.nsIObjectLoadingContent.TYPE_IMAGE;
+
+const imageTypeStringORB = embedHasBrowsingContext ? "document" : "image";
+
+add_task(async function() {
+ // FIXME: Remove when bug 1658342 is fixed
+ await SpecialPowers.pushPrefEnv({
+ set: [["fission.remoteObjectEmbed", true]],
+ });
+
+ info("Loading image in embed");
+ let embed = document.createElement("embed");
+ document.body.appendChild(embed);
+
+ info("x-site image load from element");
+ embed.setAttribute("src", "http://example.com" + testDir + "green.png");
+ await new Promise(resolve => embed.addEventListener("load", resolve, { once: true }));
+ is(
+ SpecialPowers.wrap(embed).displayedType,
+ imageTypeORB,
+ `image load should have ${imageTypeStringORB} type`
+ );
+
+ if (!embedHasBrowsingContext) {
+ ok(!SpecialPowers.wrap(embed).frameLoader, "should not have frameloader");
+ ok(!SpecialPowers.wrap(embed).browsingContext, "should not have bc");
+ } else {
+ ok(SpecialPowers.wrap(embed).frameLoader, "should have frameloader");
+ ok(SpecialPowers.wrap(embed).browsingContext, "should have bc");
+ }
+
+ info("x-site document load from element");
+ embed.setAttribute("src", "http://example.com" + testDir + "file_empty.html");
+ await new Promise(resolve => embed.addEventListener("load", resolve, { once: true }));
+ is(
+ SpecialPowers.wrap(embed).displayedType,
+ SpecialPowers.Ci.nsIObjectLoadingContent.TYPE_DOCUMENT,
+ "document load should have document type"
+ );
+ ok(SpecialPowers.wrap(embed).frameLoader, "should have frameloader");
+ ok(SpecialPowers.wrap(embed).browsingContext, "should have bc");
+
+ info("load x-site document in iframe & compare processes");
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "http://example.com" + testDir + "file_empty.html");
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, { once: true }));
+
+ let embedRemoteType = await SpecialPowers.spawn(embed, [], () => Services.appinfo.remoteType);
+ let iframeRemoteType = await SpecialPowers.spawn(iframe, [], () => Services.appinfo.remoteType);
+ is(iframeRemoteType, embedRemoteType, "remote types should match");
+
+ info("x-site image load from docshell");
+ SpecialPowers.spawn(embed, ["http://example.com" + testDir + "green.png"], uri => {
+ content.location.href = uri;
+ })
+ await new Promise(resolve => embed.addEventListener("load", resolve, { once: true }));
+ is(
+ SpecialPowers.wrap(embed).displayedType,
+ SpecialPowers.Ci.nsIObjectLoadingContent.TYPE_DOCUMENT,
+ "image load via location should have document type"
+ );
+ ok(SpecialPowers.wrap(embed).frameLoader, "should have frameloader");
+ ok(SpecialPowers.wrap(embed).browsingContext, "should have bc");
+
+ info("x-site image load from element");
+ embed.setAttribute("src", "http://example.com" + testDir + "green.png");
+ await new Promise(resolve => embed.addEventListener("load", resolve, { once: true }));
+ is(
+ SpecialPowers.wrap(embed).displayedType,
+ imageTypeORB,
+ `image load should have ${imageTypeStringORB} type`
+ );
+
+ if (!embedHasBrowsingContext) {
+ ok(!SpecialPowers.wrap(embed).frameLoader, "should not have frameloader");
+ ok(!SpecialPowers.wrap(embed).browsingContext, "should not have bc");
+ } else {
+ ok(SpecialPowers.wrap(embed).frameLoader, "should have frameloader");
+ ok(SpecialPowers.wrap(embed).browsingContext, "should have bc");
+ }
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_encodeToStringWithMaxLength.html b/dom/base/test/test_encodeToStringWithMaxLength.html
new file mode 100644
index 0000000000..c3d6f509e3
--- /dev/null
+++ b/dom/base/test/test_encodeToStringWithMaxLength.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=995321
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 995321 - encodeToStringWithMaxLength</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function getEncoder() {
+ // Create a plaintext encoder without flags.
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/plain");
+ encoder.init(document, "text/plain", 0);
+ return encoder;
+ }
+
+ function testPlaintextSerializerWithMaxLength() {
+ var string = getEncoder().encodeToString();
+
+ var shorterString = getEncoder().encodeToStringWithMaxLength(1);
+ ok(shorterString.length < 1 + 72,
+ "test length is in the expected range after limiting the length to 1");
+ ok(string.startsWith(shorterString.trimRight()),
+ "the shorter string has the expected content");
+
+ shorterString = getEncoder().encodeToStringWithMaxLength(300);
+ ok(shorterString.length < 300 + 72,
+ "test length is in the expected range after limiting the length to 300");
+ ok(string.startsWith(shorterString.trimRight()),
+ "the shorter string has the expected content");
+
+ is(getEncoder().encodeToStringWithMaxLength(0), string,
+ "limiting the length to 0 should be ignored");
+
+ is(getEncoder().encodeToStringWithMaxLength(10000), string,
+ "limiting the length to a huge value should return the whole page");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testPlaintextSerializerWithMaxLength);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=995321">Mozilla Bug 995321</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+The <em>Mozilla</em> project is a global community of <strong>people</strong> who believe that openness, innovation, and opportunity are key to the continued health of the Internet. We have worked together since 1998 to ensure that the Internet is developed in a way that benefits everyone. We are best known for creating the Mozilla Firefox web browser.
+
+The Mozilla project uses a community-based approach to create world-class open source software and to develop new types of collaborative activities. We create communities of people involved in making the Internet experience better for all of us.
+
+As a result of these efforts, we have distilled a set of principles that we believe are critical for the Internet to continue to benefit the public good as well as commercial aspects of life. We set out these principles below.
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_encodeToStringWithRequiresReinitAfterOutput.html b/dom/base/test/test_encodeToStringWithRequiresReinitAfterOutput.html
new file mode 100644
index 0000000000..a1d2749ef8
--- /dev/null
+++ b/dom/base/test/test_encodeToStringWithRequiresReinitAfterOutput.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1352882
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1352882 - RequiresReinitAfterOutput</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function getEncoder() {
+ // Create a plaintext encoder without flags.
+ var encoder = SpecialPowers.Cu.createDocumentEncoder("text/plain");
+ encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+ return encoder;
+ }
+
+ function testPlaintextSerializerWithRequiresReinitAfterOutput() {
+ var encoder = getEncoder();
+
+ var str = encoder.encodeToString();
+ ok(str, "encodingToString should be successful");
+
+ SimpleTest.doesThrow(() => {
+ encoder.encodeToString();
+ }, 'encodeToString should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+ encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+ str = encoder.encodeToString();
+ ok(str, "encodingToString should be successful after calling init again");
+
+ encoder = getEncoder();
+
+ str = encoder.encodeToStringWithMaxLength(1000);
+ ok(str, "encodingToString should be successful");
+
+ SimpleTest.doesThrow(() => {
+ encoder.encodeToStringWithMaxLength(1000);
+ }, 'encodeToStringWithMaxLength should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+ encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+ str = encoder.encodeToStringWithMaxLength(1000);
+ ok(str, "encodingToStringWithMaxLength should be successful after calling init again");
+
+ encoder = getEncoder();
+
+ const UINT32_MAX = 4294967295;
+ let pipe = SpecialPowers.Cc["@mozilla.org/pipe;1"].createInstance(SpecialPowers.Ci.nsIPipe);
+ pipe.init(true, true, 0, UINT32_MAX, null);
+ let stream = pipe.outputStream;
+
+ encoder.setCharset("utf-8");
+ encoder.encodeToStream(stream);
+ ok(str, "encodingToStream should be successful");
+
+ SimpleTest.doesThrow(() => {
+ encoder.encodeToStream(stream);
+ }, 'encodeToStream should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+ encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+ encoder.encodeToStream(stream);
+ ok(true, "encodingToStream should be successful after calling init again");
+
+ stream.close();
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testPlaintextSerializerWithRequiresReinitAfterOutput);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1352882">Mozilla Bug 1352882</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+The <em>Mozilla</em> project is a global community of <strong>people</strong> who believe that openness, innovation, and opportunity are key to the continued health of the Internet. We have worked together since 1998 to ensure that the Internet is developed in a way that benefits everyone. We are best known for creating the Mozilla Firefox web browser.
+
+The Mozilla project uses a community-based approach to create world-class open source software and to develop new types of collaborative activities. We create communities of people involved in making the Internet experience better for all of us.
+
+As a result of these efforts, we have distilled a set of principles that we believe are critical for the Internet to continue to benefit the public good as well as commercial aspects of life. We set out these principles below.
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_eventsource_event_listener_leaks.html b/dom/base/test/test_eventsource_event_listener_leaks.html
new file mode 100644
index 0000000000..d775b2f193
--- /dev/null
+++ b/dom/base/test/test_eventsource_event_listener_leaks.html
@@ -0,0 +1,40 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450358 - Test EventSource event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate EventSource. Its important here that we create a
+// listener callback from the DOM objects back to the frame's global
+// in order to exercise the leak condition.
+async function useEventSource(contentWindow) {
+ let es = new contentWindow.EventSource("delayedServerEvents.sjs");
+ es.onmessage = _ => {
+ contentWindow.messageCount += 1;
+ };
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("EventSource", useEventSource);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_eventsourceservice_basic.html b/dom/base/test/test_eventsourceservice_basic.html
new file mode 100644
index 0000000000..da08fb0cd9
--- /dev/null
+++ b/dom/base/test/test_eventsourceservice_basic.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>EventSource event service basic test</title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var service = SpecialPowers.Cc["@mozilla.org/eventsourceevent/service;1"]
+ .getService(SpecialPowers.Ci.nsIEventSourceEventService);
+ok(!!service, "We have the nsIEventSourceEventService");
+
+var innerId = SpecialPowers.wrap(window).windowGlobalChild.innerWindowId;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var channelId = null;
+var listener = {
+ QueryInterface(aIID) {
+ if (
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIEventSourceEventListener)
+ ) {
+ return this;
+ }
+ throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ eventSourceConnectionOpened(httpChannelId) {
+ info("eventSourceConnectionOpened");
+ ok(httpChannelId > 0, "Channel ID received");
+ channelId = httpChannelId;
+ },
+ eventSourceConnectionClosed(httpChannelId) {
+ info("eventSourceConnectionClosed");
+ ok(httpChannelId > 0, "Channel ID received");
+ ok(httpChannelId === channelId, "Channel ID matched");
+ service.removeListener(innerId, listener);
+ ok(true, "Listener removed");
+ ok(!service.hasListenerFor(innerId), "hasListenerFor(innerId) should be false");
+ SimpleTest.finish();
+ },
+ eventReceived(httpChannelId, eventName, lastEventId, data, retry, timeStamp) {
+ info("eventReceived");
+ is(eventName, "message", "Event name is 'message'");
+ is(lastEventId, "1", "Last event id is '1'");
+ is(data, "msg 1", "Data is 'msg 1'");
+ ok(retry > 0, "Reconnection time received");
+ ok(httpChannelId === channelId, "Channel ID matched");
+ ok(timeStamp > 0, "TimeStamp received");
+ }
+}
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+ok(service.hasListenerFor(innerId), "hasListenerFor(innerId) should be true");
+addLoadEvent(function () {
+ const es = new EventSource("http://mochi.test:8888/tests/dom/base/test/eventsource_message.sjs");
+ es.onmessage = function (e) {
+ es.close();
+ };
+});
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_eventsourceservice_reconnect_error.html b/dom/base/test/test_eventsourceservice_reconnect_error.html
new file mode 100644
index 0000000000..cb124693c9
--- /dev/null
+++ b/dom/base/test/test_eventsourceservice_reconnect_error.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>EventSource event service reconnect error test</title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var service = SpecialPowers.Cc["@mozilla.org/eventsourceevent/service;1"]
+ .getService(SpecialPowers.Ci.nsIEventSourceEventService);
+ok(!!service, "We have the nsIEventSourceEventService");
+
+var innerId = SpecialPowers.wrap(window).windowGlobalChild.innerWindowId;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var channelId;
+var count = {
+ open: 0,
+ msg: 0,
+ close: 0
+};
+var listener = {
+ QueryInterface(aIID) {
+ if (
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIEventSourceEventListener)
+ ) {
+ return this;
+ }
+ throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ eventSourceConnectionOpened(httpChannelId) {
+ info("eventSourceConnectionOpened");
+ ok(httpChannelId > 0, "Channel ID received");
+ channelId = httpChannelId;
+ count.open++;
+ },
+ eventSourceConnectionClosed(httpChannelId) {
+ info("eventSourceConnectionClosed");
+ ok (httpChannelId === channelId, "Channel was closed on failure");
+ count.close++;
+ SimpleTest.requestFlakyTimeout("Test for open/close");
+ setTimeout(checkCallsCount, 2000);
+ },
+ eventReceived(httpChannelId, eventName, lastEventId, data, retry, timeStamp) {
+ info("eventReceived=", retry);
+ is(eventName, "message", "Event name is 'message'");
+ count.msg++;
+ }
+}
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+addLoadEvent(function () {
+ const es = new EventSource("http://mochi.test:8888/tests/dom/base/test/eventsource_reconnect.sjs?id=" + Date.now());
+});
+SimpleTest.waitForExplicitFinish();
+
+function checkCallsCount() {
+ ok(count.open == 1, "No new open event");
+ ok(count.close == 1, "No new close event");
+ ok(count.msg == 1, "No new message event");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_eventsourceservice_status_error.html b/dom/base/test/test_eventsourceservice_status_error.html
new file mode 100644
index 0000000000..55be8e0dc7
--- /dev/null
+++ b/dom/base/test/test_eventsourceservice_status_error.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>EventSource event service status error test</title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var service = SpecialPowers.Cc["@mozilla.org/eventsourceevent/service;1"]
+ .getService(SpecialPowers.Ci.nsIEventSourceEventService);
+ok(!!service, "We have the nsIEventSourceEventService");
+
+var innerId = SpecialPowers.wrap(window).windowGlobalChild.innerWindowId;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var listener = {
+ QueryInterface(aIID) {
+ if (
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIEventSourceEventListener)
+ ) {
+ return this;
+ }
+ throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ eventSourceConnectionOpened(httpChannelId) {
+ ok(false, "This should not happen");
+ },
+ eventSourceConnectionClosed(httpChannelId) {
+ ok(false, "This should not happen");
+ },
+ eventReceived(httpChannelId, eventName, lastEventId, data, retry, timeStamp) {
+ ok(false, "This should not happen");
+ }
+}
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+
+var NUM_TESTS = 2;
+addLoadEvent(function () {
+ doTest(404);
+ doTest(502);
+});
+
+SimpleTest.waitForExplicitFinish();
+
+var count = 0;
+function doTest(status) {
+ const es = new EventSource(
+ "http://mochi.test:8888/tests/dom/base/test/eventsource_message.sjs?status=" + status
+ );
+ es.onerror = function (e) {
+ count++;
+ if (count >= NUM_TESTS) {
+ SimpleTest.finish();
+ }
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_eventsourceservice_worker.html b/dom/base/test/test_eventsourceservice_worker.html
new file mode 100644
index 0000000000..99c89f4e50
--- /dev/null
+++ b/dom/base/test/test_eventsourceservice_worker.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>EventSource event service worker test</title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var service = SpecialPowers.Cc["@mozilla.org/eventsourceevent/service;1"]
+ .getService(SpecialPowers.Ci.nsIEventSourceEventService);
+ok(!!service, "We have the nsIEventSourceEventService");
+
+var innerId = SpecialPowers.wrap(window).windowGlobalChild.innerWindowId;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var channelId = null;
+var listener = {
+ QueryInterface(aIID) {
+ if (
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
+ SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIEventSourceEventListener)
+ ) {
+ return this;
+ }
+ throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ eventSourceConnectionOpened(httpChannelId) {
+ info("eventSourceConnectionOpened");
+ ok(httpChannelId > 0, "Channel ID received");
+ channelId = httpChannelId;
+ },
+ eventSourceConnectionClosed(httpChannelId) {
+ info("eventSourceConnectionClosed");
+ ok(httpChannelId === channelId, "Channel ID matched");
+ SimpleTest.finish();
+ },
+ eventReceived(httpChannelId, eventName, lastEventId, data, retry, timeStamp) {
+ info("eventReceived");
+ is(eventName, "message", "Event name is 'message'");
+ is(lastEventId, "1", "Last event id is '1'");
+ is(data, "msg 1", "Data is 'msg 1'");
+ ok(retry > 0, "Reconnection time received");
+ ok(httpChannelId === channelId, "Channel ID matched");
+ ok(timeStamp > 0, "TimeStamp received");
+ }
+}
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+addLoadEvent(function () {
+ const worker = new Worker("eventsource_worker.js");
+});
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_explicit_user_agent.html b/dom/base/test/test_explicit_user_agent.html
new file mode 100644
index 0000000000..58dffac67e
--- /dev/null
+++ b/dom/base/test/test_explicit_user_agent.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XMLHttpRequest.GetResponseHeader(foo) byte-inflates the output</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <meta charset="utf-8">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+ "use strict";
+
+ add_task(async function() {
+ await new Promise((r) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open('GET', 'file_explicit_user_agent.sjs', true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ is(xhr.getResponseHeader("Result-User-Agent"), navigator.userAgent,
+ "The resulting user-agent is the navigator's UA");
+ r();
+ }
+ }
+ xhr.send(null);
+ });
+
+ await new Promise((r) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open('GET', 'file_explicit_user_agent.sjs', true);
+ xhr.setRequestHeader('User-Agent', 'custom-ua/10.0');
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ is(xhr.getResponseHeader("Result-User-Agent"), 'custom-ua/10.0',
+ "The resulting user-agent is the custom UA");
+ r();
+ }
+ }
+ xhr.send(null);
+ });
+
+ var response = await fetch('file_explicit_user_agent.sjs', {
+ method: 'GET'
+ });
+ is(response.headers.get("Result-User-Agent"), navigator.userAgent,
+ "The user-agent is the navigator's UA");
+
+ var headers = new Headers();
+ headers.set('User-Agent', 'custom-ua/20.0');
+ var response2 = await fetch('file_explicit_user_agent.sjs', {
+ method: 'GET',
+ headers,
+ });
+ is(response2.headers.get("Result-User-Agent"), 'custom-ua/20.0',
+ "The user-agent is the custom UA");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_find.html b/dom/base/test/test_find.html
new file mode 100644
index 0000000000..4b71bba4ff
--- /dev/null
+++ b/dom/base/test/test_find.html
@@ -0,0 +1,204 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+const t = async_test("Test window.find / nsFind");
+
+function testFindable(findCount, textToFind, buildDoc, description) {
+ if (typeof findCount == "boolean")
+ findCount = findCount ? 1 : 0;
+ try {
+ const iframe = document.querySelector("iframe")
+ iframe.contentDocument.documentElement.innerHTML =
+ (typeof buildDoc == "string") ? buildDoc : "";
+
+ if (typeof buildDoc == "function")
+ buildDoc(iframe.contentDocument);
+
+ iframe.contentWindow.getSelection().removeAllRanges();
+ for (let i = findCount; i >= 0; --i) {
+ const expectFindable = i != 0;
+ assert_equals(
+ iframe.contentWindow.find(textToFind),
+ expectFindable,
+ "Should be " + (expectFindable ? "" : "not ") + "findable: " + description + ", text: " + textToFind + ", iter: " + (findCount - i + 1)
+ );
+ }
+
+ } catch (ex) {
+ assert_unreached(ex);
+ }
+}
+
+const INLINE_LIKE_DISPLAY_VALUES = [
+ "inline",
+ "inline-grid",
+ "inline-block",
+ "inline-flex",
+];
+
+const BLOCK_LIKE_DISPLAY_VALUES = [
+ "block",
+ "flex",
+ "grid",
+ "list-item",
+ "table-column-group",
+ "table-column",
+ "table-footer-group",
+ "table-header-group",
+ "table-row-group",
+ "table-row",
+ "table",
+];
+
+let runTests = t.step_func_done(function() {
+ testFindable(true, "me and me", `
+ me <div style="display: contents">and</div> me
+ `, "display: contents");
+
+ testFindable(true, "me me", `
+ me <div style="display: none">and</div> me
+ `, "display: none");
+
+ testFindable(false, "me and me", `
+ me <div style="display: none">and</div> me
+ `, "display: none");
+
+ for (const display of INLINE_LIKE_DISPLAY_VALUES) {
+ testFindable(true, "me and me", `
+ me <div style="display: ${display}">and</div> me
+ `, "div display: " + display);
+ testFindable(true, "me and me", `
+ me <span style="display: ${display}">and</span> me
+ `, "span display: " + display);
+ }
+
+ for (const display of BLOCK_LIKE_DISPLAY_VALUES) {
+ testFindable(false, "me and me", `
+ me <div style="display: ${display}">and</div> me
+ `, "div display: " + display);
+ testFindable(false, "me and me", `
+ me <span style="display: ${display}">and</span> me
+ `, "span display: " + display);
+ }
+
+ testFindable(false, "me and me", `
+ me <fieldset>and</fieldset> me
+ `);
+
+ testFindable(true, "This text should be visible", `
+ <div style="visibility: hidden">
+ <div style="visibility: visible">
+ This text should be visible
+ </div>
+ </div>
+ `);
+
+ testFindable(true, "This text should be visible", `
+ <style>:root { overflow: hidden }</style>
+ <div style="overflow: auto;">
+ <div style="height: 300vh"></div>
+ This text should be visible
+ </div>
+ `);
+
+ testFindable(true, "foobar", `
+ <body><script style="display: block;">foobar</` + `script></body>
+ `);
+
+
+ testFindable(true, "Shadow text", function(document) {
+ let div = document.createElement("div");
+ div.attachShadow({ mode: "open" }).innerHTML = `
+ Wohoo, this is Shadow text, yay!
+ `;
+ document.documentElement.appendChild(div);
+ }, "In Shadow DOM");
+
+ testFindable(true, "Shadow text", function(document) {
+ let div = document.createElement("div");
+ div.appendChild(document.createTextNode(
+ "Wohoo, this is Shadow text, yay!"
+ ));
+ div.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`;
+ document.documentElement.appendChild(div);
+ }, "Slotted content in Shadow DOM");
+
+ // TODO(emilio): This should work in an ideal world.
+ testFindable(false, "Shadow text", function(document) {
+ let div = document.createElement("div");
+ div.appendChild(document.createTextNode("text, yay!"));
+ div.attachShadow({ mode: "open" }).innerHTML = `This is Shadow <slot></slot>`;
+ document.documentElement.appendChild(div);
+ }, "Mixed shadow and non-shadow text");
+
+ testFindable(true, "Shadow", function(document) {
+ document.documentElement.innerHTML = `
+ Sources<span id="host"></span>
+ <div>whatever</div>
+ `;
+ document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "Shadow text";
+ }, "Test inside a shadow-root mid-match");
+
+ testFindable(false, "Outside shadow", function(document) {
+ document.documentElement.innerHTML = `
+ Outside <div id="host"></div> shadow
+ `;
+ document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "inside shadow";
+ }, "Block in different subtree");
+
+ // NOTE(emilio): It is probably doable / worth changing this to return true,
+ // maybe, by relaxing the security checks in the ranges nsFind returns or
+ // such.
+ //
+ // See bug 1442466 / bug 1510485 / bug 1505887.
+ testFindable(false, "foo", function(document) {
+ let input = document.createElement("input");
+ input.value = "foo";
+ document.documentElement.appendChild(input);
+ }, "Native anonymous content isn't exposed in window.find");
+
+ // Same as above, but in this case the check is warranted, we shouldn't
+ // expose this range.
+ testFindable(false, "find me", `
+ <style>div::before { content: "Do find me" }</style>
+ <div></div>
+ `, "Pseudo-element");
+
+ // Same as above.
+ testFindable(false, "find me", `
+ <img alt="Do find me">
+ `, "Image alt content");
+
+ // Same as above.
+ testFindable(false, "find me", `
+ <input type="submit" value="Do find me">
+ `, "Submit input value");
+
+ testFindable(false, "\0", `
+ &#0;
+ `);
+
+ testFindable(true, "\0", function(document) {
+ document.documentElement.appendChild(document.createTextNode("\0"));
+ }, "Inserted null characters are findable");
+
+ testFindable(false, "ab", `a<br>b`, "<br> forces a break even if there's no whitespace in between");
+
+ testFindable(true, "history.kafka", `
+ <code>database.history&#8203;.kafka.bootstrap.servers</code>
+ `, "ZWSP should be ignored");
+
+ testFindable(2, " ", "a b c", "Collapsed whitespace");
+});
+
+window.onload = function() {
+ let iframe = document.createElement("iframe");
+ iframe.onload = runTests;
+ iframe.srcdoc = "<!doctype html><html></html>";
+ document.body.appendChild(iframe);
+};
+</script>
+</body>
diff --git a/dom/base/test/test_find_bug1601118.html b/dom/base/test/test_find_bug1601118.html
new file mode 100644
index 0000000000..de4604bc45
--- /dev/null
+++ b/dom/base/test/test_find_bug1601118.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="container">
+ Fission <br/>
+ Some text <br/>
+ Some more text Fission or Fission or even <span>Fi</span>ssion<br/>
+ Fission <br/>
+ more text<br/>
+ <div>
+ in a nested block Fission stuff
+ </div>
+</div>
+<script>
+const kContainer = document.getElementById("container");
+const kExpectedCount = 6;
+
+// We expect surroundContents() to throw in the <span>Fi</span>ssion case.
+const kExpectedThrewCount = 1;
+
+// Keep a hang of the original DOM so as to test forwards and backwards navigation.
+const kContainerClone = kContainer.cloneNode(true);
+
+function advance(backwards) {
+ if (!window.find("Fiss", /* caseSensitive = */ true, backwards))
+ return { success: false };
+
+ let threw = false;
+ try {
+ window.getSelection().getRangeAt(0).surroundContents(document.createElement("mark"));
+ } catch (ex) {
+ threw = true;
+ }
+
+ // Sanity-check
+ assert_equals(window.getSelection().toString(), "Fiss");
+ return { success: true, threw };
+}
+
+function runTestForDirection(backwards) {
+ let threwCount = 0;
+ for (let i = 0; i < kExpectedCount; ++i) {
+ let result = advance(backwards);
+ assert_true(result.success, `Should've successfully advanced (${i} / ${kExpectedCount}, backwards: ${backwards})`);
+ if (result.threw)
+ threwCount++;
+ }
+ assert_false(advance(backwards).success, `Should've had exactly ${kExpectedCount} matches`);
+ assert_equals(threwCount, kExpectedThrewCount, "Should've thrown the expected number of times");
+ assert_equals(kContainer.querySelectorAll("mark").length, kExpectedCount - threwCount, "Should've created the expected number of marks");
+ assert_false(!!kContainer.querySelector("mark mark"), "Shouldn't have created nested marks");
+}
+
+test(function() {
+ runTestForDirection(false);
+ window.getSelection().removeAllRanges();
+ kContainer.innerHTML = kContainerClone.innerHTML;
+ runTestForDirection(true);
+}, "window.find() while marking with surroundContents");
+</script>
diff --git a/dom/base/test/test_find_bug1654683.html b/dom/base/test/test_find_bug1654683.html
new file mode 100644
index 0000000000..c4a8aea8af
--- /dev/null
+++ b/dom/base/test/test_find_bug1654683.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div class="before">
+ mozilla before
+</div>
+<input value="mozilla">
+<div class="after">
+ mozilla after
+</div>
+<script>
+function selectionContainedWithin(selector) {
+ for (let node = getSelection().anchorNode; node; node = node.parentNode) {
+ if (node.matches && node.matches(selector))
+ return true;
+ }
+ return false;
+}
+
+test(function () {
+ let input = document.querySelector("input");
+ input.focus();
+ assert_true(window.find("mozilla"), "Found the string, didn't throw");
+ assert_true(selectionContainedWithin(".after"), "Should've found the following node");
+ assert_true(!window.find("mozilla"), "No more matches forward");
+ assert_true(window.find("mozilla", /* caseSensitive = */ false, /* backwards = */ true), "Should find stuff backwards");
+ assert_true(selectionContainedWithin(".before"), "Should've found the node before the input (should not return the NAC range)");
+}, "window.find() doesn't throw if focused on an <input>");
+</script>
diff --git a/dom/base/test/test_find_nac.html b/dom/base/test/test_find_nac.html
new file mode 100644
index 0000000000..66bde6bbeb
--- /dev/null
+++ b/dom/base/test/test_find_nac.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input value="bar">
+<script>
+test(function() {
+ // The exact return value of this first call is tested in
+ // test_find.html.
+ window.find("bar");
+ assert_false(window.find("bar"));
+}, "window.find doesn't get stuck on NAC");
+</script>
diff --git a/dom/base/test/test_focus_design_mode.html b/dom/base/test/test_focus_design_mode.html
new file mode 100644
index 0000000000..331dff2094
--- /dev/null
+++ b/dom/base/test/test_focus_design_mode.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1690747
+-->
+<head>
+ <title>Test for Bug 1690747</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1690747">Mozilla Bug 1690747</a>
+<p id="display"></p>
+<div id='content'>
+</div>
+<pre id="test">
+<iframe src="https://example.org/tests/dom/base/test/file_focus_design_mode_inner.html"></iframe>
+<script type="application/javascript">
+
+/** Test for Bug 1690747 **/
+
+let iframe = document.querySelector("iframe");
+
+function waitForEvent(target, event, checkFn) {
+ return new Promise(resolve => {
+ target.addEventListener(event, e => {
+ if (checkFn && !checkFn(e)) {
+ return;
+ }
+ resolve();
+ }, { once: true });
+ });
+}
+
+async function getLog() {
+ let log = "";
+ SimpleTest.executeSoon(function() {
+ iframe.contentWindow.postMessage("getlog", "*");
+ });
+ await waitForEvent(window, "message", (e) => {
+ log = e.data;
+ return true;
+ });
+ return log;
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({ set: [["dom.disable_window_flip", true]] });
+});
+
+add_task(async function activeElementAfterBluring() {
+ iframe.contentWindow.postMessage("focus", "*");
+ is(await getLog(), "innerlog:windowfocus,activeElement:HTML,", "check activeElement");
+ iframe.contentWindow.blur();
+ is(await getLog(), "innerlog:windowfocus,activeElement:HTML,activeElement:HTML,", "check activeElement after bluring");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_focus_display_none_xorigin_iframe.html b/dom/base/test/test_focus_display_none_xorigin_iframe.html
new file mode 100644
index 0000000000..b04141b0fe
--- /dev/null
+++ b/dom/base/test/test_focus_display_none_xorigin_iframe.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1716762
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1716762</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1716762">Mozilla Bug 1716762</a><br>
+<input></input><br>
+<div id="target" style="display: none;">
+<iframe src="http://example.org/tests/dom/base/test/file_focus_display_none_xorigin_iframe_inner.html"></iframe>
+</div>
+<script type="text/javascript">
+
+let waitForMessage = function(aMsg) {
+ return new Promise(reslove => {
+ window.addEventListener("message", function handler(e) {
+ info(`main received message: ${e.data}`);
+ if (e.data === aMsg) {
+ window.removeEventListener("message", handler);
+ reslove();
+ }
+ });
+ });
+};
+
+let sendMessage = async function(aWindow, aMsg) {
+ aWindow.postMessage(aMsg, "*");
+ await waitForMessage("done");
+}
+
+let getFocus = function(aWindow) {
+ return new Promise(reslove => {
+ window.addEventListener("message", function handler(e) {
+ info(e.data);
+ reslove(e.data);
+ }, { once: true });
+ aWindow.postMessage("getfocus", "*");
+ });
+}
+
+/** Test for Bug 1716762 **/
+
+let input = document.querySelector("input");
+let iframe = document.querySelector("iframe");
+
+add_task(async function test_ancestor_display_none_init() {
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "INPUT", "focus should stay on input element");
+});
+
+add_task(async function test_remove_ancestor_display_none() {
+ // remove `display: none` from the ancestor of iframe
+ document.getElementById("target").style = "";
+ document.body.offsetWidth;
+
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "IFRAME", "focus should move to iframe element");
+});
+
+add_task(async function test_ancestor_display_none() {
+ // add `display: none` to the ancestor of iframe back
+ document.getElementById("target").style = "display: none;";
+ document.body.offsetWidth;
+
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "INPUT", "focus should stay on input element");
+});
+
+add_task(async function test_remove_ancestor_display_none_again() {
+ // remove `display: none` from the ancestor of iframe
+ document.getElementById("target").style = "";
+ document.body.offsetWidth;
+
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "IFRAME", "focus should move to iframe element");
+});
+
+add_task(async function test_iframe_display_none() {
+ // add `display: none` to iframe
+ iframe.style = "display: none;";
+ document.body.offsetWidth;
+
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "INPUT", "focus should stay on input element");
+});
+
+add_task(async function test_remove_iframe_display_none() {
+ // remove `display: none` from iframe
+ iframe.style = "";
+ document.body.offsetWidth;
+
+ // focus input element
+ input.focus();
+ is(document.activeElement.tagName, "INPUT", "focus should move to input element");
+
+ // focus input element in hidden iframe
+ await sendMessage(iframe.contentWindow, "focus");
+ is(document.activeElement.tagName, "IFRAME", "focus should move to iframe element");
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_focus_keyboard_event.html b/dom/base/test/test_focus_keyboard_event.html
new file mode 100644
index 0000000000..3f68516de3
--- /dev/null
+++ b/dom/base/test/test_focus_keyboard_event.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=552255
+-->
+<head>
+<title>Test for bug 552255</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=552255">Mozilla Bug 552255</a>
+<p id="display"></p>
+<input type="text"><br>
+<iframe src="http://example.org/tests/dom/base/test/file_empty.html"></iframe>
+<script>
+add_task(async function cross_origin_iframe() {
+ await SimpleTest.promiseFocus();
+
+ // Setup test in iframe
+ let iframe = document.querySelector("iframe");
+ await SpecialPowers.spawn(iframe, [], () => {
+ content.document.body.addEventListener("keydown", (e) => {
+ ok(false, `should not receive any keydown event, ${e.key}`);
+ });
+ content.addEventListener("focus", (e) => {
+ ok(false, `should not receive any focus event`);
+ });
+ });
+
+ let input = document.querySelector("input");
+ input.focus();
+
+ input.addEventListener("keydown", (e) => {
+ iframe.focus();
+ });
+
+ // Start test.
+ synthesizeKey("a");
+
+ await new Promise(SimpleTest.executeSoon);
+ ok(document.hasFocus(), "document should still have focus");
+ is(document.activeElement, input, "input should still have focus");
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_focus_scroll_padding_tab.html b/dom/base/test/test_focus_scroll_padding_tab.html
new file mode 100644
index 0000000000..1d3b0d3e92
--- /dev/null
+++ b/dom/base/test/test_focus_scroll_padding_tab.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<title>Test for bug 1617342</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ :root {
+ scroll-padding: 100px 0;
+ }
+ body {
+ margin: 0;
+ }
+ #fixed-footer {
+ height: 100px;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background: green;
+ }
+ #fixed-header {
+ height: 100px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ background: green;
+ }
+ .almost-full-padding {
+ height: calc(100vh - 200px);
+ }
+ .padding {
+ height: 100vh;
+ }
+</style>
+<div id="fixed-header"></div>
+<div class="padding"></div>
+<input>
+<div class="almost-full-padding"></div>
+<input>
+<div class="padding"></div>
+<input id="end">
+<div class="padding"></div>
+<div id="fixed-footer"></div>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(async function() {
+ let stack = [];
+ let end = document.getElementById("end");
+
+ let lastActiveElement = document.activeElement;
+
+ do {
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement.tagName, "INPUT", "Should focus inputs only, there's nothing else");
+ isnot(document.activeElement, lastActiveElement, "Focus should've moved once per tab keypress");
+ let rect = document.activeElement.getBoundingClientRect();
+ ok(rect.top >= 100, "Should not be covered by top bar");
+ ok(rect.bottom >= 100, "Should not be covered by bottom bar");
+ lastActiveElement = document.activeElement;
+ stack.push(lastActiveElement);
+ } while (document.activeElement != end)
+
+ do {
+ let previous = stack.pop();
+ let rect = document.activeElement.getBoundingClientRect();
+ ok(rect.top >= 100, "Should not be covered by top bar");
+ ok(rect.bottom >= 100, "Should not be covered by bottom bar");
+ is(document.activeElement, previous, "Focus should've moved backwards as expected");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ } while (stack.length);
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/dom/base/test/test_focus_scrollable_fieldset.html b/dom/base/test/test_focus_scrollable_fieldset.html
new file mode 100644
index 0000000000..444d078cb1
--- /dev/null
+++ b/dom/base/test/test_focus_scrollable_fieldset.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>Test for bug 1351248</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ fieldset {
+ overflow: auto;
+ height: 1em;
+ width: 1em;
+ }
+</style>
+<input type="text" id="start">
+
+<fieldset>
+ <input type="text">
+ <input type="text">
+ <input type="text">
+ <input type="text">
+ <input type="text">
+ <input type="text">
+</fieldset>
+
+<input type="text" id="end">
+<script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(async function() {
+ // Enable Full Keyboard Access emulation on Mac.
+ if (navigator.platform.indexOf("Mac") === 0) {
+ await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ }
+
+ const start = document.getElementById("start");
+
+ start.focus();
+
+ const end = document.getElementById("end");
+
+ is(document.activeElement, start, "Focus moved sanely");
+
+ let lastActiveElement = start;
+ let stack = [start];
+
+ do {
+ synthesizeKey("KEY_Tab");
+ isnot(document.activeElement, lastActiveElement, "Focus should've moved once per tab keypress");
+ lastActiveElement = document.activeElement;
+ stack.push(lastActiveElement);
+ } while (document.activeElement != end)
+
+ is(stack.length, document.querySelectorAll("input").length + 1, "Fieldset should be focusable");
+
+ do {
+ let previous = stack.pop();
+ is(document.activeElement, previous, "Focus should've moved backwards as expected");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ } while (stack.length);
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/dom/base/test/test_focus_scrollable_input.html b/dom/base/test/test_focus_scrollable_input.html
new file mode 100644
index 0000000000..ca48f3a263
--- /dev/null
+++ b/dom/base/test/test_focus_scrollable_input.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>Test for bug 1617342</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ input {
+ /* Make them small enough that any value overflows */
+ height: 10px;
+ width: 10px;
+ box-sizing: border-box;
+ font: 16px/1 monospace;
+ }
+</style>
+<input type="text" id="start" value="aaaaaaaaaa">
+
+<input type="text" value="aaaaaaaaaa">
+<input type="number" value="1111">
+<input type="search" value="aaaaaaa">
+<input type="url" value="https://fooooooooooo">
+
+<input type="text" id="end" value="aaaaaaaaaa">
+<script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(async function() {
+ // Enable Full Keyboard Access emulation on Mac.
+ if (navigator.platform.indexOf("Mac") === 0) {
+ await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ }
+
+ const start = document.getElementById("start");
+
+ start.focus();
+
+ const end = document.getElementById("end");
+
+ is(document.activeElement, start, "Focus moved sanely");
+
+ let lastActiveElement = start;
+ let stack = [start];
+
+ do {
+ synthesizeKey("KEY_Tab");
+ isnot(document.activeElement, lastActiveElement, "Focus should've moved once per tab keypress");
+ lastActiveElement = document.activeElement;
+ stack.push(lastActiveElement);
+ } while (document.activeElement != end)
+
+ do {
+ let previous = stack.pop();
+ is(document.activeElement, previous, "Focus should've moved backwards as expected");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ } while (stack.length);
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/dom/base/test/test_focus_shadow_dom.html b/dom/base/test/test_focus_shadow_dom.html
new file mode 100644
index 0000000000..3d8899eada
--- /dev/null
+++ b/dom/base/test/test_focus_shadow_dom.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1453693
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1453693</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1453693 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ async function runTests() {
+ // Enable Full Keyboard Access emulation on Mac.
+ if (navigator.platform.indexOf("Mac") === 0) {
+ await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ }
+ win = window.open("file_focus_shadow_dom.html", "", "width=300, height=300");
+ }
+
+ function didRunTests() {
+ setTimeout("SimpleTest.finish()");
+ }
+
+ ;
+
+ </script>
+</head>
+<body onload="SimpleTest.waitForFocus(runTests);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1453693">Mozilla Bug 1453693</a>
+</body>
+</html>
diff --git a/dom/base/test/test_focus_shadow_dom_root.html b/dom/base/test/test_focus_shadow_dom_root.html
new file mode 100644
index 0000000000..bf51dac3d1
--- /dev/null
+++ b/dom/base/test/test_focus_shadow_dom_root.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<title>Test for bug 1544826</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<div id="host"><a href="#" id="slotted">This is focusable too</a></div>
+<script>
+ const host = document.getElementById("host");
+ const shadow = host.attachShadow({ mode: "open" });
+ shadow.innerHTML = `
+ <a id="shadow-1" href="#">This is focusable</a>
+ <slot></slot>
+ <a id="shadow-2" href="#">So is this</a>
+ `;
+ document.documentElement.remove();
+ document.appendChild(host);
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(async function() {
+ // Enable Full Keyboard Access emulation on Mac.
+ if (navigator.platform.indexOf("Mac") === 0) {
+ await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ }
+
+ is(document.documentElement, host, "Host is the document element");
+ host.offsetTop;
+ synthesizeKey("KEY_Tab");
+ is(shadow.activeElement.id, "shadow-1", "First link in Shadow DOM is focused");
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement.id, "slotted", "Slotted link is focused");
+ synthesizeKey("KEY_Tab");
+ is(shadow.activeElement.id, "shadow-2", "Second link in Shadow DOM is focused");
+
+ // Now backwards.
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ is(document.activeElement.id, "slotted", "Backwards: Slotted link is focused");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ is(shadow.activeElement.id, "shadow-1", "Backwards: First slotted link is focused");
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/dom/base/test/test_fragment_sanitization.xhtml b/dom/base/test/test_fragment_sanitization.xhtml
new file mode 100644
index 0000000000..203aebc86f
--- /dev/null
+++ b/dom/base/test/test_fragment_sanitization.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1432966
+-->
+<window title="Mozilla Bug 1432966"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript"><![CDATA[
+
+const NS_HTML = "http://www.w3.org/1999/xhtml";
+
+function awaitLoad(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", resolve, {once: true});
+ });
+}
+
+async function testFrame(frame, html, expected = html) {
+ document.querySelector("body").appendChild(frame);
+ await awaitLoad(frame);
+
+ // Remove the xmlns attributes that will be automatically added when we're
+ // in an XML document, and break the comparison.
+ function unNS(text) {
+ return text.replace(RegExp(` xmlns="${NS_HTML}"`, "g"), "");
+ }
+
+ let doc = frame.contentDocument;
+ let body = doc.body || doc.documentElement;
+
+ let div = doc.createElementNS(NS_HTML, "div");
+ body.appendChild(div);
+
+ div.innerHTML = html;
+ is(unNS(div.innerHTML), expected, "innerHTML value");
+
+ div.innerHTML = "<div></div>";
+ div.firstChild.outerHTML = html;
+ is(unNS(div.innerHTML), expected, "outerHTML value");
+
+ div.textContent = "";
+ div.insertAdjacentHTML("beforeend", html);
+ is(unNS(div.innerHTML), expected, "insertAdjacentHTML('beforeend') value");
+
+ div.innerHTML = "<a>foo</a>";
+ div.firstChild.insertAdjacentHTML("afterend", html);
+ is(unNS(div.innerHTML), "<a>foo</a>" + expected, "insertAdjacentHTML('afterend') value");
+
+ frame.remove();
+}
+
+add_task(async function test_fragment_sanitization() {
+ const XUL_URL = "chrome://global/content/win.xhtml";
+ const HTML_URL = "chrome://mochitests/content/chrome/dom/base/test/file_empty.html";
+
+ const HTML = '<a onclick="foo()" href="javascript:foo"><script>bar()<\/script>Meh.</a><a href="http://foo/"></a>';
+ const SANITIZED = '<a>Meh.</a><a href="http://foo/"></a>';
+
+ info("Test content HTML document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = "http://example.com/";
+
+ await testFrame(frame, HTML);
+ }
+
+ info("Test chrome HTML document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = HTML_URL;
+
+ await testFrame(frame, HTML, SANITIZED);
+ }
+
+ info("Test chrome XUL document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = XUL_URL;
+
+ await testFrame(frame, HTML, SANITIZED);
+ }
+});
+
+ ]]></script>
+
+ <description style="-moz-user-focus: normal; user-select: text;"><![CDATA[
+ hello
+ world
+ ]]></description>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432966"
+ target="_blank">Mozilla Bug 1432966</a>
+ </body>
+</window>
diff --git a/dom/base/test/test_getAttribute_after_createAttribute.html b/dom/base/test/test_getAttribute_after_createAttribute.html
new file mode 100644
index 0000000000..5e2f4ec306
--- /dev/null
+++ b/dom/base/test/test_getAttribute_after_createAttribute.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var div = document.createElement("div");
+ var attr = document.createAttribute("FOO");
+ attr.value = "bar";
+ div.setAttributeNode(attr);
+ assert_equals(div.getAttribute("FOO"), "bar");
+}, "getAttribute should be able to get an attribute created via createAttribute");
+</script>
diff --git a/dom/base/test/test_getElementById.html b/dom/base/test/test_getElementById.html
new file mode 100644
index 0000000000..8489917c14
--- /dev/null
+++ b/dom/base/test/test_getElementById.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=933193
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 933193</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=933193">Mozilla Bug 933193</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 933193 **/
+ var kid = document.createElement("span");
+ kid.id = "test";
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.appendChild(kid);
+ is(svg.getElementById("test"), kid,
+ "Should find the right node when not in the DOM");
+
+ var newKid = document.createElement("span");
+ newKid.id = "test";
+ var newKidParent = document.createElement("span");
+ newKidParent.appendChild(newKid);
+ svg.insertBefore(newKidParent, kid);
+ is(svg.getElementById("test"), newKid,
+ "Should find the first right node when not in the DOM");
+ newKid.remove();
+ is(svg.getElementById("test"), kid,
+ "Should find the right node again when not in the DOM");
+
+ document.body.appendChild(svg);
+ is(svg.getElementById("test"), kid,
+ "Should find the right node when in the DOM");
+
+ is(document.getElementById("test").localName, "pre",
+ "document.getElementById should find the first element in the " +
+ "document with that id");
+
+ var frag = document.createDocumentFragment();
+ is(frag.getElementById("test"), null, "Shouldn't find what does not exist");
+ frag.appendChild(kid);
+ is(frag.getElementById("test"), kid,
+ "Should find the right node in the document fragment");
+ is(svg.getElementById("test"), null,
+ "Shouldn't find the kid since it's gone now");
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_getLastOverWindowPointerLocationInCSSPixels.html b/dom/base/test/test_getLastOverWindowPointerLocationInCSSPixels.html
new file mode 100644
index 0000000000..c1bbf723f6
--- /dev/null
+++ b/dom/base/test/test_getLastOverWindowPointerLocationInCSSPixels.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for Bug 1778486</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+<script src="chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+<style>
+div {
+ width: 50px;
+ height: 50px;
+};
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1778486">Mozilla Bug 1778486</a>
+<p id="display"></p>
+<div id="target_mouse" style="background: red;"></div>
+<div id="target_touch" style="background: green;"></div>
+<script>
+
+function waitForEvent(aTarget, aEvent) {
+ return new Promise(resolve => {
+ aTarget.addEventListener(aEvent, resolve, { once: true });
+ });
+}
+
+function getLastOverWindowPointerLocationScreenOffset() {
+ let x = {};
+ let y = {};
+ let topWindow = window.browsingContext.topChromeWindow;
+ topWindow.windowUtils.getLastOverWindowPointerLocationInCSSPixels(x, y);
+ return {
+ x: Math.trunc(x.value + topWindow.mozInnerScreenX),
+ y: Math.trunc(y.value + topWindow.mozInnerScreenY),
+ };
+}
+
+function getElementScreenOffsetAtCentral(aElement) {
+ let rect = aElement.getBoundingClientRect();
+ return {
+ x: Math.trunc(rect.width / 2 + rect.left + mozInnerScreenX),
+ y: Math.trunc(rect.height / 2 + rect.top + mozInnerScreenY),
+ };
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({ set: [["test.events.async.enabled", true]] });
+ await waitUntilApzStable();
+});
+
+/** Test for Bug 1778486 **/
+add_task(async function test_mouse() {
+ let target = document.getElementById("target_mouse");
+ let promise = waitForEvent(target, "click");
+ synthesizeMouseAtCenter(target, {});
+ await promise;
+
+ isDeeply(
+ getLastOverWindowPointerLocationScreenOffset(),
+ getElementScreenOffsetAtCentral(target),
+ "check last pointer location");
+});
+
+add_task(async function test_touch() {
+ let target = document.getElementById("target_touch");
+ let promise = waitForEvent(target, "pointerup");
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ await promise;
+
+ isDeeply(
+ getLastOverWindowPointerLocationScreenOffset(),
+ getElementScreenOffsetAtCentral(target),
+ "check last pointer location");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_getTranslationNodes.html b/dom/base/test/test_getTranslationNodes.html
new file mode 100644
index 0000000000..aa8b1229da
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes.html
@@ -0,0 +1,225 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<script type="application/javascript">
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+
+
+ function testTranslationRoot(rootNode) {
+ var translationNodes = utils.getTranslationNodes(rootNode);
+
+ var expectedResult = rootNode.getAttribute("expected");
+ var expectedLength = expectedResult.split(" ").length;
+
+ is(translationNodes.length, expectedLength,
+ "Correct number of translation nodes for testcase " + rootNode.id);
+
+ var resultList = [];
+ for (var i = 0; i < translationNodes.length; i++) {
+ var node = translationNodes.item(i).localName;
+ if (translationNodes.isTranslationRootAtIndex(i)) {
+ node += "[root]"
+ }
+ resultList.push(node);
+ }
+
+ is(resultList.length, translationNodes.length,
+ "Correct number of translation nodes for testcase " + rootNode.id);
+
+ is(resultList.join(" "), expectedResult,
+ "Correct list of translation nodes for testcase " + rootNode.id);
+ }
+
+ function runTest() {
+ isnot(utils, null, "nsIDOMWindowUtils");
+
+ var testcases = document.querySelectorAll("div[expected]");
+ for (var testcase of testcases) {
+ testTranslationRoot(testcase);
+ }
+
+ var testiframe = document.getElementById("testiframe");
+ var iframediv = testiframe.contentDocument.querySelector("div");
+ try {
+ var foo = utils.getTranslationNodes(iframediv);
+ ok(false, "Cannot use a node from a different document");
+ } catch (e) {
+ is(e.name, "WrongDocumentError", "Cannot use a node from a different document");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+
+<!-- Test that an inline element inside a root is not a root -->
+<div id="testcase1"
+ expected="div[root] span">
+ <div>
+ lorem ipsum <span>dolor</span> sit amet
+ </div>
+</div>
+
+<!-- Test that a usually inline element becomes a root if it is
+ displayed as a block -->
+<div id="testcase2"
+ expected="div[root] span[root]">
+ <div>
+ lorem ipsum <span style="display: block;">dolor</span> sit amet
+ </div>
+</div>
+
+<!-- Test that the content-less <div> is ignored and only the
+ <p> with content is returned -->
+<div id="testcase3"
+ expected="p[root]">
+ <div>
+ <p>lorem ipsum</p>
+ </div>
+</div>
+
+<!-- Test that an inline element which the parent is not a root
+ becomes a root -->
+<div id="testcase4"
+ expected="span[root]">
+ <div>
+ <span>lorem ipsum</span>
+ </div>
+</div>
+
+<!-- Test siblings -->
+<div id="testcase5"
+ expected="li[root] li[root]">
+ <ul>
+ <li>lorem</li>
+ <li>ipsum</li>
+ </ul>
+</div>
+
+<!-- Test <ul> with content outside li -->
+<div id="testcase6"
+ expected="ul[root] li[root] li[root]">
+ <ul>Lorem
+ <li>lorem</li>
+ <li>ipsum</li>
+ </ul>
+</div>
+
+<!-- Test inline siblings -->
+<div id="testcase7"
+ expected="ul[root] li li">
+ <ul>Lorem
+ <li style="display: inline">lorem</li>
+ <li style="display: inline">ipsum</li>
+ </ul>
+</div>
+
+<!-- Test inline siblings becoming roots -->
+<div id="testcase8"
+ expected="li[root] li[root]">
+ <ul>
+ <li style="display: inline">lorem</li>
+ <li style="display: inline">ipsum</li>
+ </ul>
+</div>
+
+<!-- Test that nodes with only punctuation, whitespace
+ or numbers are ignored -->
+<div id="testcase9"
+ expected="li[root] li[root]">
+ <ul>
+ <li>lorem</li>
+ <li>ipsum</li>
+ <li>-.,;'/!@#$%^*()</li>
+ <li>0123456789</li>
+ <li>
+ </li>
+ </ul>
+</div>
+
+<!-- Test paragraphs -->
+<div id="testcase10"
+ expected="p[root] a b p[root] a b">
+ <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b>amet</b>, consetetur</p>
+ <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b>amet</b>, consetetur</p>
+</div>
+
+<!-- Test that a display:none element is not ignored -->
+<div id="testcase11"
+ expected="p[root] a b">
+ <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b style="display:none">amet</b>, consetetur</p>
+</div>
+
+<!-- Test that deep nesting does not cause useless content to be returned -->
+<div id="testcase12"
+ expected="p[root]">
+ <div>
+ <div>
+ <div>
+ <p>Lorem ipsum</p>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Test that deep nesting does not cause useless content to be returned -->
+<div id="testcase13"
+ expected="div[root] p[root]">
+ <div>Lorem ipsum
+ <div>
+ <div>
+ <p>Lorem ipsum</p>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Test that non-html elements and elements that usually have non-translatable
+ content are ignored -->
+<div id="testcase14"
+ expected="div[root]">
+ <div>
+ Lorem Ipsum
+ <noscript>Lorem Ipsum</noscript>
+ <style>.dummyClass { color: blue; }</style>
+ <script> /* script tag */ </script>
+ <code> code </code>
+ <iframe id="testiframe"
+ srcdoc="<div>Lorem ipsum</div>">
+ </iframe>
+ <svg>lorem</svg>
+ <math>ipsum</math>
+ </div>
+</div>
+
+<!-- Test that nesting of inline elements won't produce roots as long as
+ the parents are in the list of translation nodes -->
+<div id="testcase15"
+ expected="p[root] a b span em">
+ <p>Lorem <a>ipsum <b>dolor <span>sit</span> amet</b></a>, <em>consetetur</em></p>
+</div>
+
+<!-- Test that comment nodes are not considered for translation -->
+<div id="testcase16"
+ expected="p[root] p[root]">
+ <p>Lorem ipsum</p>
+ <div> <!-- Comment --> </div>
+ <p>Lorem ipsum</p>
+</div>
+
+<!-- Test that comment nodes are not considered for translation -->
+<div id="testcase17"
+ expected="p[root]">
+ <div>
+ <!-- Comment -->
+ <p>Lorem Ipsum</p>
+ </div>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_getTranslationNodes_limit.html b/dom/base/test/test_getTranslationNodes_limit.html
new file mode 100644
index 0000000000..b6a89d03d7
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes_limit.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<script type="application/javascript">
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+
+ function runTest() {
+ isnot(utils, null, "nsIDOMWindowUtils");
+
+ for (var i = 0; i < 16000; i++) {
+ var text = document.createTextNode("a");
+ var node = document.createElement("b");
+ node.appendChild(text);
+ document.body.appendChild(node);
+ }
+
+ var translationRoots = utils.getTranslationNodes(document.body);
+ is (translationRoots.length, 15000, "Translation nodes were limited to 15000 nodes.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_gsp-qualified.html b/dom/base/test/test_gsp-qualified.html
new file mode 100644
index 0000000000..48504399e1
--- /dev/null
+++ b/dom/base/test/test_gsp-qualified.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=799875
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 799875</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=799875">Mozilla Bug 799875</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe srcdoc="<div id='test2'>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 799875 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(window.test, document.getElementById("test"),
+ "Global scope polluter should map ids even when qualified")
+ isnot(document.getElementById("test"), null,
+ "Should have element");
+ is(window[0].test2, window[0].document.getElementById("test2"),
+ "Global scope polluter should map ids across globals");
+ isnot(window[0].document.getElementById("test2"), null,
+ "Should have element in subframe");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_gsp-quirks.html b/dom/base/test/test_gsp-quirks.html
new file mode 100644
index 0000000000..68c0ab88d1
--- /dev/null
+++ b/dom/base/test/test_gsp-quirks.html
@@ -0,0 +1,27 @@
+<!-- Purposefully in quirks mode -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622491
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 622491</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622491">Mozilla Bug 622491</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622491 **/
+is(test, document.getElementById("test"), "Global scope polluter should map ids")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_gsp-standards.html b/dom/base/test/test_gsp-standards.html
new file mode 100644
index 0000000000..e7d4d88e21
--- /dev/null
+++ b/dom/base/test/test_gsp-standards.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622491
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 622491</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622491">Mozilla Bug 622491</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622491 **/
+is(test, document.getElementById("test"), "Global scope polluter should map ids")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_history_document_open.html b/dom/base/test/test_history_document_open.html
new file mode 100644
index 0000000000..be782d98e4
--- /dev/null
+++ b/dom/base/test/test_history_document_open.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=943418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 943418</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 943418 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function continueTest(f) {
+ // Make sure we're the entry script so errors get reported here
+ setTimeout(finishTest, 0, f);
+ }
+
+ function finishTest(f) {
+ f();
+ ok(true, "Got here");
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=943418">Mozilla Bug 943418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="file_history_document_open.html"></script>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_history_state_null.html b/dom/base/test/test_history_state_null.html
new file mode 100644
index 0000000000..d730c5586f
--- /dev/null
+++ b/dom/base/test/test_history_state_null.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949471
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 949471</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /** Test for Bug 949471 **/
+ is(history.state, null, "history.state should be null by default");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949471">Mozilla Bug 949471</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_html_colors_quirks.html b/dom/base/test/test_html_colors_quirks.html
new file mode 100644
index 0000000000..128c2f8b99
--- /dev/null
+++ b/dom/base/test/test_html_colors_quirks.html
@@ -0,0 +1,711 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=121738
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 121738</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=121738">Mozilla Bug 121738</a>
+<table id="table0"></table>
+<table id="table1"></table>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 121738 **/
+
+String.prototype.toAsciiLowerCase = function () {
+ var output = "";
+ for (var i = 0, len = this.length; i < len; ++i) {
+ if (this.charCodeAt(i) >= 0x41 && this.charCodeAt(i) <= 0x5A) {
+ output += String.fromCharCode(this.charCodeAt(i) + 0x20)
+ } else {
+ output += this.charAt(i);
+ }
+ }
+ return output;
+}
+
+var tests = [
+"#135",
+" #135",
+"#135 ",
+" #135 ",
+"# 135",
+"# 135",
+"#123456",
+" #123456",
+"#123456 ",
+" #123456 ",
+"# 123456",
+"# 123456",
+"aliceblue",
+"ALICEBLUE",
+"alıceblue",
+"alÄ°ceblue",
+" aliceblue",
+"aliceblue ",
+" aliceblue ",
+"antiquewhite",
+"aqua",
+"aquamarine",
+"azure",
+"beige",
+"bisque",
+"black",
+"blanchedalmond",
+"blue",
+"blueviolet",
+"brown",
+"burlywood",
+"cadetblue",
+"chartreuse",
+"chocolate",
+"coral",
+"cornflowerblue",
+"cornsilk",
+"crimson",
+"cyan",
+"darkblue",
+"darkcyan",
+"darkgoldenrod",
+"darkgray",
+"darkgreen",
+"darkgrey",
+"darkkhaki",
+"darkmagenta",
+"darkolivegreen",
+"darkorange",
+"darkorchid",
+"darkred",
+"darksalmon",
+"darkseagreen",
+"darkslateblue",
+"darkslategray",
+"darkslategrey",
+"darkturquoise",
+"darkviolet",
+"deeppink",
+"deepskyblue",
+"dimgray",
+"dimgrey",
+"dodgerblue",
+"firebrick",
+"floralwhite",
+"forestgreen",
+"fuchsia",
+"gainsboro",
+"ghostwhite",
+"gold",
+"goldenrod",
+"gray",
+"green",
+"greenyellow",
+"grey",
+"honeydew",
+"hotpink",
+"indianred",
+"indigo",
+"ivory",
+"khaki",
+"lavender",
+"lavenderblush",
+"lawngreen",
+"lemonchiffon",
+"lightblue",
+"lightcoral",
+"lightcyan",
+"lightgoldenrodyellow",
+"lightgray",
+"lightgreen",
+"lightgrey",
+"lightpink",
+"lightsalmon",
+"lightseagreen",
+"lightskyblue",
+"lightslategray",
+"lightslategrey",
+"lightsteelblue",
+"lightyellow",
+"lime",
+"limegreen",
+"linen",
+"magenta",
+"maroon",
+"mediumaquamarine",
+"mediumblue",
+"mediumorchid",
+"mediumpurple",
+"mediumseagreen",
+"mediumslateblue",
+"mediumspringgreen",
+"mediumturquoise",
+"mediumvioletred",
+"midnightblue",
+"mintcream",
+"mistyrose",
+"moccasin",
+"navajowhite",
+"navy",
+"oldlace",
+"olive",
+"olivedrab",
+"orange",
+"orangered",
+"orchid",
+"palegoldenrod",
+"palegreen",
+"paleturquoise",
+"palevioletred",
+"papayawhip",
+"peachpuff",
+"peru",
+"pink",
+"plum",
+"powderblue",
+"purple",
+"red",
+"rosybrown",
+"royalblue",
+"saddlebrown",
+"salmon",
+"sandybrown",
+"seagreen",
+"seashell",
+"sienna",
+"silver",
+"skyblue",
+"slateblue",
+"slategray",
+"slategrey",
+"snow",
+"springgreen",
+"steelblue",
+"tan",
+"teal",
+"thistle",
+"tomato",
+"turquoise",
+"violet",
+"wheat",
+"white",
+"whitesmoke",
+"yellow",
+"yellowgreen",
+"transparent",
+"TRANSPARENT",
+"",
+"inherit",
+"INHERIT",
+"KQ@m?-ldxNK{zH+(FL_owz>YNH^]",
+"aj9c)r+J&3)0E,- Lzv6K6nT@6?I}BY^\\g",
+"Cf}qJN|3D>m:^[7B*fhu>lC[B3\"T_-F",
+"h<s1pMd-8H[,\\caWH1oW3%M",
+"z{y]VZj)%)ZV<OTbO9\\Nbc|YL}4BI<DlUBk$EV`EbN}x/@SDN0(",
+"PSX2Ol8@",
+"d+gYXKUM'&D'S]o<9T\\:hj_i!|l!)e6R4Bo)-(965rlK\"K01C68pgkJ] fx?kjT.O&sY4",
+"[4\"tk)a/v17?0W!F`AyI[='2~;:DF6I>_<O$ he213u",
+"F|r9T&N69MWq3Jrj6",
+"dYR7}n&:Rq[J",
+"M;Z]r@(R([6aT`<sN?uO'2Kb~3U\\\\tQUDxLN1f/D(,Q0w|K;,t`,tQ~[W/c!uQg)d|:D\\U33!DK&d*C`Zc'U#",
+"kV)DKUb~h{SQCM;T*g2!Rj?>Sl=jY;3W9M{Fliu!=>tDY]5",
+"y>X\\kKN|~=J+7Pqp|%9R!nZ,@>mUW9<o;|02LV<fxihsBSKVaTdcae",
+"Q>jc|/:#qwzHL`lL%e~DbhQ+d^tpf9sx%o)jC1Nm}`G;rT2jo+M$=$?BC'|O^]hW^BBo_J->bWG1",
+"OIxA\\5HB7g3Rv;PD)z?jGe?<x`4~9&D9dSDP=ilUauI'qb",
+"aND[Al/^#;n'|V\"Vl$bh5\\G#{%y4#\\W0:ZgXe73ZuXrWcL4gr|B7,ijZZi{p)M+R9{C/&249G",
+"7xK-d6Tx]BU|T,DY.qCwusmV%Ksset",
+"I=UwM''S",
+"w|_;Qw(R:>Clf[#3JFr_+?'1D&}WaY_xaRyTpwio>C;Pjf/kIW{]KK:R&ARiP=_g_UqRVvFKG(OQo6y'wF]Fc",
+"G:",
+"+XZ%s7L3FmGFn]Y!J;.vpAUoGU,&WY8eQeGWW?Jq7ANeM}[?gsV) H\\@{8z_t$oS(_jSq]|9?W*sG%' (d%",
+"*P\"?'?NHA \\!{.S=+LD8Ltr^'=,$4uQ=tVL/T_b6m!PJ8*s*v`;6kp(+8i.}T!9p6{",
+"_@(w<\\DjMk c8/\"/ifJNT_2R>V'}{&72C2+7otvd,$M@Yqc)L=O.muEp28m&AY",
+"J!M#$z|n:+:6@7n*v)UCbkVp0;\"1#}o:i4B9oh=%'I",
+"0",
+"Krq?xAul2cRe&`*Fg2)bV/r>oJ`Z(ae,z%+`E@VkWH&`-jMZ<UW~jxDek;^j2\\Uq;C,Ss",
+"#b\\l~=y5H=#Jy(6FwH5]jU;6D",
+"YO|tw;`E_'G<d~juVPCla%K]q x\"oA-aW|Y@P$_$",
+"}rI\\5x724b29MEauSSX&!AT[J1ce?,rtLAA8X",
+"hlo8jd$D-dI=I#Be:BATkZPR~%Vfe0g_Xw^+wwhHQhC1;sn+P<b&J:~HfxVBX}9b/#HHPS",
+"+#[?UFGUVFn0Zn7yE#TEo{FV\\{6*+s+a=fR",
+"lhv.f!ENs~)?5)z:1^i@BQ|ol}9Cnkw&yV.PPx |y]@,?IL]0L_# b1'wl-]",
+"&DhZ!g%v.sF}4NoP~4<vKpaM0[12!2K!ziYC3`505I*D*J6k\\skbXJ}44J#4y2",
+"oK][N&iIV\"AEs3rIT-::L3&J^Sn42_J2yL= 2xI4o!b\"#2JiAt=",
+"^c;C^{0wD%|y~Z1X'z\\o^gI8L=@2^p3g/L6G?]Nuif;Zf15dF`IPt8",
+"62t-!*`U\\l%BFxi5B~[^~G!}h]DtXrd}y}af3",
+"?N5d9ydHPi?IhwU=41'",
+"GSZeLtA3YahI@oLy/6vT_[B`[PRZ1^.(n8`,8TyqVoCzMd!=9 e",
+"Yck5`_#NgS",
+"K9?z&o",
+"Isl2>%RB8T+,9?B{~A2{fEb[%",
+"&fV(`<ha/(T7J&X\\{YHt+5 =>%SaJ&W0_j]]\"",
+">!sQ/IYU\"Ikc\\ei;HlCcVJ\":G2/m]h1,GvOmxFOOvTUHjHu:LWE\"QU=) ",
+"7Fyx#>\"(\"N",
+"MO6\"Hd2H]r8BJ}z)%J18b<VZ5lrhI",
+"BGQ|tqdwj4};#x@?%ka[`DwgFWg*J+q/}]-\\\\-y#T",
+"zZ=JrTPxh}.(%frt58Cy=C4(*,4]:Gnz5(~iv4@u4re~6yp:zbU0(o.S+qd9eB]A5",
+"n]V3}^{9O<0cO\"rtglDO4Wc)_7Nu_JnK2EBbzRMV3b.Mj\"$9#,+-T\"N=7iPdD F<9_YWw3ZN*V;??*8VN8z?^MXi",
+"fGRSl*i>^*uy|c;5B}tKXu>5hZX:>CB{oWIrxE8@B/f{:u9]:bLO0/ZWeHoNfCc|kSh{/fXs9Y:UKaJ95vFFtB2Y",
+".&-4UOcxR\"Tbgc--@& hoUavCcQW^^fT}:I(d%o}J2t*BRA1{YGXB9>AYu^Bv#rEu`pN65_-r.IQD.?Cc/B({YtK[2KMmVOC3*2J",
+"H3<MOq'81C#\\nUjQc xlsF@c2R<e);T~G]^N0_*M<j!jub~k,mgZ(.>GERhwS;kmmKC?1l} qQ&zcXK?g)S OmF^=E^TlTC)/",
+"8\\5tFz:sod",
+"ILUc5c!'K.7.P&=S,pSYB{",
+"%\\(6.jC\"C4\\2{TYdx,Ln^",
+"tL3HnWq q!'k!#R@2bkvIAI]hk)64-i?uk]oK(nQiKg$`m6tx|#l>m1bEK^&.X[o'b\"\\',A16n>9ZAPI9>",
+"{#Mn0,S~a\"!gw",
+"dv+6'7Dd)fz",
+"9o1*Q:{6#3f uHx\"_",
+".43_zr}X+BtruMV!H!xw 8!9I_}zlBT3W52)rh,9ngeu0o[V_s*z'8x9*yjF!",
+"y3Nm`>P:seF'V'?+<={oU5QQ",
+".#Os_jv,\"@-zsY8j'POOYnY?0ONn?i#d4tqp?~.OF#VC.=<t<+feuf{#@O7lXC@+#t_uKGue%Dk9z",
+"0Ep=zwydU-V<)9<9\\`[4,d^B&Gbo#'HTSEC;qU&1| ocNd69#{nbmYJMlj6Mfs3`w&pc(poie *ZOJIp7%}cVnfml",
+"746}e(rye4lT7#B.Or8j1->Xs@o8f0}/e>uvvkNS[3UC2F]#[>^f74jxoo&9{^ED#,CV\"k'0|xI",
+".D1{.:9gHW}]36RlUQ?!-\"0dn:+(/e@b)|'B",
+"!Nh9xY2y?5ku5f}%]JWw~OfeYdcgI#It` T)3VzYl5gChve[I'rEqyJ \"@(z.Y%fEJ:9k(",
+"J^$^L8:qI=yrerd0kxJBEwby6[>9[) NpqN%)h",
+"l&\\K{s9]aL(,dX?B\"1g1*@9BY~=UO+AWvVRI;ar4p8Tsy~Qk-=x(yp38:j|g'5H~e4",
+"(]Pe,1fTS_P%[xY96#,rwQz=P?z",
+")PC#?b'&|maJBt.6Izz%vV5e!9Sy|G!*Q",
+"pzAG^@/J&)Dc}GP*xO]ezW:*PV|Dt^fF<8GgF_1i=A\"@>nt?yOa|zS<8`/;uY~^ozjvX!K#%us!>2IYITh]Zy<^dq?&\"nqV]ZdZ",
+"T<xz*/H&}36<(4E^/Z1m<#_G0R:=qX?1`*6Y&r'SIO]9OR;m5-Zq?PU^jvLKPLW2wPf",
+")CLhmCI%TwB+t:h.@Vp-#({d0W/R_(^f34LC=V)A",
+"yCT_hl%fUL<T&e;ePsT.pJG@|jO,[pN]]Q<Yu=Lp6X6&$Ka#",
+" $4X",
+"rp",
+"t^(*U<Qdi$!vyg5D\"yQZd<K6<Qptsvzi~D}.Uf?P$E>}t8VP^;3Tamv2Z$1<",
+",",
+"EGCXZ_{eqIwfG-o7o\\\"!ZWTPLd,U-k$Cz]%:vNWdo}vDh!ONtM>mMP{/Cg+2<.J\"a*n#Rtnha",
+"LQ3sIr7Q_ wSD7Zzv$-vxr|3P`pas0#Ze/---{PSwJ3{!a*[k'nFgC\"W+@4URi?qJk&Tt@`abNms40#A^XcAt}",
+"^i356'hX\"hKaZGfTZ|C@#}b3LGz1\">qcH{L8{Fs?O5%:EqQWuro_lc=]gWLVR\\~!J\"[>,H",
+"i{*q<O{Dt^n|FY3,FG>WIRqPH.os$9^P=|yA9?P;MOw;VBwZ^>K=\"%J9SBlv%0+o5k73rW!`l_",
+"-PCM&!G~o#Za^s&)qJELr\\P^\\={_xTFp:%@JF.PeRX\\7b8K",
+"V_sFicB+wx5",
+"\"X^\\d}b9.W.2\"O!yAL21\"Ny5:)=Q3*D|TAzzr^0Yflzjdc!p*.yW,B",
+"kZ`wCP>9Bq5S!r!Vi|Uy/C&H[kz/f^{(Z[OGw'S0\"",
+"Jn%1^rUnNB|%=q%^v*bN|I40}#Htn{G!#~CNAN0KvZcB>Ita(,n",
+"-PlhE[^J55Ui",
+"z`h`uQJ{J",
+"eV\\q5Q4o@Y*,IRMcnpqj5>Id\\yBe?pKH3uF&c<c}:E9[uaH$ 8dXCmI+!C'q@PkE<NVRq~GRW<tfyt/i@%dwI&rL",
+"UGzLF+o3)Ezs=nMxqd^\"=q.Ik}Tk2I`X)R8]Zmy/WQp,|]TdbP)5 J+#Hm6SmWtQ+h?.MQ1W#oyp\\F,'JL>rLtjiHOA",
+"&joOSw7XZVvSt4ZCT*:aq:3ns!v|r);~7gN8'_D",
+"Y<q|Mhn5Nrcb+dR=10pQF5]r@/*7P`79w/htSm2,C~1&sUW{N@v:t9d;HPG&xrI\"YD;V9Y$'g,W'J=GV3,YK",
+"Gx&#{;]l/?[{SyX`kTeo",
+"30PU7@<'58.hRWsJTa9L.hVQ8}7=$}ih4|$Y*9z3[aooT!]}+>b{1JH^.jjEU{,dAXSCbtEh6",
+"%2~x8=A!RW@8N/`hQz`)gl}1DOU9{>Ie'L> 4e]m;kt =isEQ(\\TeI7hWgK-K! p^K'\":3;dxTLO",
+"\\ ):{Woay[4",
+"\\{Ih&}*8^x6@`V@DZB`rSuhYm4i@TW^t9Hx[^`IVumjXc1vA\"~wt8^Jf:US6Z\\xaS&",
+"lo $6<EP|=gAEpd\\M6YDg\"*0m",
+/*
+"ActiveBorder",
+"ActiveCaption",
+"AppWorkspace",
+"Background",
+"ButtonFace",
+"ButtonHighlight",
+"ButtonShadow",
+"ButtonText",
+"CaptionText",
+"GrayText",
+"Highlight",
+"HighlightText",
+"InactiveBorder",
+"InactiveCaption",
+"InactiveCaptionText",
+"InfoBackground",
+"InfoText",
+"Menu",
+"MenuText",
+"Scrollbar",
+"ThreeDDarkShadow",
+"ThreeDFace",
+"ThreeDHighlight",
+"ThreeDLightShadow",
+"ThreeDShadow",
+"Window",
+"WindowFrame",
+"WindowText",
+*/
+"currentColor",
+"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#",
+"#1",
+"#12",
+"#123",
+"#1234",
+"#12x",
+"abc",
+"123",
+"#0ab0cd0ef",
+"#0ab0cd0e",
+"#0ab0cd0",
+"#0ab0cdaef",
+"#0ab0cdae",
+"#0ab0cda"
+];
+
+var references = [
+"#113355",
+"#001350",
+"#135000",
+"#001350",
+"#013500",
+"#000135",
+"#123456",
+"#002356",
+"#124500",
+"#002356",
+"#013460",
+"#001245",
+"#f0f8ff",
+"#f0f8ff",
+"#a0ce00",
+"#a0ce00",
+"#0ace0e",
+"#a0ebe0",
+"#00cee0",
+"#faebd7",
+"#00ffff",
+"#7fffd4",
+"#f0ffff",
+"#f5f5dc",
+"#ffe4c4",
+"#000000",
+"#ffebcd",
+"#0000ff",
+"#8a2be2",
+"#a52a2a",
+"#deb887",
+"#5f9ea0",
+"#7fff00",
+"#d2691e",
+"#ff7f50",
+"#6495ed",
+"#fff8dc",
+"#dc143c",
+"#00ffff",
+"#00008b",
+"#008b8b",
+"#b8860b",
+"#a9a9a9",
+"#006400",
+"#a9a9a9",
+"#bdb76b",
+"#8b008b",
+"#556b2f",
+"#ff8c00",
+"#9932cc",
+"#8b0000",
+"#e9967a",
+"#8fbc8f",
+"#483d8b",
+"#2f4f4f",
+"#2f4f4f",
+"#00ced1",
+"#9400d3",
+"#ff1493",
+"#00bfff",
+"#696969",
+"#696969",
+"#1e90ff",
+"#b22222",
+"#fffaf0",
+"#228b22",
+"#ff00ff",
+"#dcdcdc",
+"#f8f8ff",
+"#ffd700",
+"#daa520",
+"#808080",
+"#008000",
+"#adff2f",
+"#808080",
+"#f0fff0",
+"#ff69b4",
+"#cd5c5c",
+"#4b0082",
+"#fffff0",
+"#f0e68c",
+"#e6e6fa",
+"#fff0f5",
+"#7cfc00",
+"#fffacd",
+"#add8e6",
+"#f08080",
+"#e0ffff",
+"#fafad2",
+"#d3d3d3",
+"#90ee90",
+"#d3d3d3",
+"#ffb6c1",
+"#ffa07a",
+"#20b2aa",
+"#87cefa",
+"#778899",
+"#778899",
+"#b0c4de",
+"#ffffe0",
+"#00ff00",
+"#32cd32",
+"#faf0e6",
+"#ff00ff",
+"#800000",
+"#66cdaa",
+"#0000cd",
+"#ba55d3",
+"#9370db",
+"#3cb371",
+"#7b68ee",
+"#00fa9a",
+"#48d1cc",
+"#c71585",
+"#191970",
+"#f5fffa",
+"#ffe4e1",
+"#ffe4b5",
+"#ffdead",
+"#000080",
+"#fdf5e6",
+"#808000",
+"#6b8e23",
+"#ffa500",
+"#ff4500",
+"#da70d6",
+"#eee8aa",
+"#98fb98",
+"#afeeee",
+"#db7093",
+"#ffefd5",
+"#ffdab9",
+"#cd853f",
+"#ffc0cb",
+"#dda0dd",
+"#b0e0e6",
+"#800080",
+"#ff0000",
+"#bc8f8f",
+"#4169e1",
+"#8b4513",
+"#fa8072",
+"#f4a460",
+"#2e8b57",
+"#fff5ee",
+"#a0522d",
+"#c0c0c0",
+"#87ceeb",
+"#6a5acd",
+"#708090",
+"#708090",
+"#fffafa",
+"#00ff7f",
+"#4682b4",
+"#d2b48c",
+"#008080",
+"#d8bfd8",
+"#ff6347",
+"#40e0d0",
+"#ee82ee",
+"#f5deb3",
+"#ffffff",
+"#f5f5f5",
+"#ffff00",
+"#9acd32",
+"transparent",
+"transparent",
+"transparent",
+"#00e000",
+"#00e000",
+"#0df000",
+"#0000b0",
+"#007b30",
+"#008001",
+"#004b00",
+"#002080",
+"#099600",
+"#0120e2",
+"#f00630",
+"#d00000",
+"#200000",
+"#0c00d0",
+"#07900a",
+"#db020b",
+"#7000a0",
+"#b00c02",
+"#d6d000",
+"#000000",
+"#1d00f0",
+"#000000",
+"#a00000",
+"#00b600",
+"#007028",
+"#00b0b9",
+"#000000",
+"#f00000",
+"#b00005",
+"#0ea000",
+"#72000a",
+"#ba0c00",
+"#0fe000",
+"#50000b",
+"#000c44",
+"#ae4202",
+"#00005d",
+"#0000af",
+"#5d0041",
+"#a00000",
+"#c00000",
+"#090000",
+"#0209fe",
+"#a00500",
+"#c0100e",
+"#7f0000",
+"#600000",
+"#d00d00",
+"#0004d9",
+"#c00000",
+"#0500ff",
+"#0000c3",
+"#200000",
+"#80f00d",
+"#0c7000",
+"#00c4d0",
+"#a000a0",
+"#00a000",
+"#d0070f",
+"#900600",
+"#002090",
+"#30ef00",
+"#0000e0",
+"#b000c0",
+"#80c200",
+"#0900e0",
+"#0005e0",
+"#800b00",
+"#b0a000",
+"#e00000",
+"#000690",
+"#d00000",
+"#010020",
+"#0000c0",
+"#000060",
+"#004000",
+"#000000",
+"#000da0",
+"#000000",
+"#00d000",
+"#000fa0",
+"#0c5000",
+"#000030",
+"#a0007b",
+"#00cb05",
+"#023000",
+"#9bc000",
+"#b000b0",
+"#00e055",
+"#000000",
+"#0dc0d0",
+"#20600a",
+"#70a070",
+"#f50000",
+"#0000e0",
+"#a900cb",
+"#0000d0",
+"#000a40",
+"#d00060",
+"#000ad0",
+/*
+"ActiveBorder",
+"ActiveCaption",
+"AppWorkspace",
+"Background",
+"ButtonFace",
+"ButtonHighlight",
+"ButtonShadow",
+"ButtonText",
+"CaptionText",
+"GrayText",
+"Highlight",
+"HighlightText",
+"InactiveBorder",
+"InactiveCaption",
+"InactiveCaptionText",
+"InfoBackground",
+"InfoText",
+"Menu",
+"MenuText",
+"Scrollbar",
+"ThreeDDarkShadow",
+"ThreeDFace",
+"ThreeDHighlight",
+"ThreeDLightShadow",
+"ThreeDShadow",
+"Window",
+"WindowFrame",
+"WindowText",
+*/
+"#c0e000",
+"#0000f0",
+"#000000",
+"#0000f0",
+"#000000",
+"#000000",
+"#010000",
+"#010200",
+"#112233",
+"#123400",
+"#010200",
+"#0a0b0c",
+"#010203",
+"#abcdef",
+"#abcde0",
+"#abcd00",
+"#0a0cae",
+"#0a0cae",
+"#0a0ca0"
+];
+
+var todos = {
+" #135": true,
+"#135 ": true,
+" #135 ": true,
+"# 135": true,
+" #123456": true,
+"#123456 ": true,
+" #123456 ": true,
+"# 123456": true,
+" aliceblue": true,
+"aliceblue ": true,
+" aliceblue ": true,
+"H3<MOq'81C#\\nUjQc xlsF@c2R<e);T~G]^N0_*M<j!jub~k,mgZ(.>GERhwS;kmmKC?1l} qQ&zcXK?g)S OmF^=E^TlTC)/": true,
+" $4X": true,
+"UGzLF+o3)Ezs=nMxqd^\"=q.Ik}Tk2I`X)R8]Zmy/WQp,|]TdbP)5 J+#Hm6SmWtQ+h?.MQ1W#oyp\\F,'JL>rLtjiHOA": true
+};
+
+var table0 = document.getElementById("table0");
+var table1 = document.getElementById("table1");
+var cs0 = document.defaultView.getComputedStyle(table0);
+var cs1 = document.defaultView.getComputedStyle(table1);
+var result;
+var reference;
+var log = "";
+var len = tests.length;
+is(tests.length, references.length, "array length mismatch");
+for (var i = 0; i < len; ++i) {
+ table0.setAttribute("bgColor", tests[i]);
+ table1.style.backgroundColor = references[i];
+ ((tests[i] in todos) ? todo_is : is)(
+ cs0.getPropertyValue("background-color"),
+ cs1.getPropertyValue("background-color"),
+ "html color '" + tests[i] + "' should match '" + references[i] + "'");
+}
+</script>
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_html_colors_standards.html b/dom/base/test/test_html_colors_standards.html
new file mode 100644
index 0000000000..0418f10e48
--- /dev/null
+++ b/dom/base/test/test_html_colors_standards.html
@@ -0,0 +1,712 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=121738
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 121738</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=121738">Mozilla Bug 121738</a>
+<table id="table0"></table>
+<table id="table1"></table>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 121738 **/
+
+String.prototype.toAsciiLowerCase = function () {
+ var output = "";
+ for (var i = 0, len = this.length; i < len; ++i) {
+ if (this.charCodeAt(i) >= 0x41 && this.charCodeAt(i) <= 0x5A) {
+ output += String.fromCharCode(this.charCodeAt(i) + 0x20)
+ } else {
+ output += this.charAt(i);
+ }
+ }
+ return output;
+}
+
+var tests = [
+"#135",
+" #135",
+"#135 ",
+" #135 ",
+"# 135",
+"# 135",
+"#123456",
+" #123456",
+"#123456 ",
+" #123456 ",
+"# 123456",
+"# 123456",
+"aliceblue",
+"ALICEBLUE",
+"alıceblue",
+"alÄ°ceblue",
+" aliceblue",
+"aliceblue ",
+" aliceblue ",
+"antiquewhite",
+"aqua",
+"aquamarine",
+"azure",
+"beige",
+"bisque",
+"black",
+"blanchedalmond",
+"blue",
+"blueviolet",
+"brown",
+"burlywood",
+"cadetblue",
+"chartreuse",
+"chocolate",
+"coral",
+"cornflowerblue",
+"cornsilk",
+"crimson",
+"cyan",
+"darkblue",
+"darkcyan",
+"darkgoldenrod",
+"darkgray",
+"darkgreen",
+"darkgrey",
+"darkkhaki",
+"darkmagenta",
+"darkolivegreen",
+"darkorange",
+"darkorchid",
+"darkred",
+"darksalmon",
+"darkseagreen",
+"darkslateblue",
+"darkslategray",
+"darkslategrey",
+"darkturquoise",
+"darkviolet",
+"deeppink",
+"deepskyblue",
+"dimgray",
+"dimgrey",
+"dodgerblue",
+"firebrick",
+"floralwhite",
+"forestgreen",
+"fuchsia",
+"gainsboro",
+"ghostwhite",
+"gold",
+"goldenrod",
+"gray",
+"green",
+"greenyellow",
+"grey",
+"honeydew",
+"hotpink",
+"indianred",
+"indigo",
+"ivory",
+"khaki",
+"lavender",
+"lavenderblush",
+"lawngreen",
+"lemonchiffon",
+"lightblue",
+"lightcoral",
+"lightcyan",
+"lightgoldenrodyellow",
+"lightgray",
+"lightgreen",
+"lightgrey",
+"lightpink",
+"lightsalmon",
+"lightseagreen",
+"lightskyblue",
+"lightslategray",
+"lightslategrey",
+"lightsteelblue",
+"lightyellow",
+"lime",
+"limegreen",
+"linen",
+"magenta",
+"maroon",
+"mediumaquamarine",
+"mediumblue",
+"mediumorchid",
+"mediumpurple",
+"mediumseagreen",
+"mediumslateblue",
+"mediumspringgreen",
+"mediumturquoise",
+"mediumvioletred",
+"midnightblue",
+"mintcream",
+"mistyrose",
+"moccasin",
+"navajowhite",
+"navy",
+"oldlace",
+"olive",
+"olivedrab",
+"orange",
+"orangered",
+"orchid",
+"palegoldenrod",
+"palegreen",
+"paleturquoise",
+"palevioletred",
+"papayawhip",
+"peachpuff",
+"peru",
+"pink",
+"plum",
+"powderblue",
+"purple",
+"red",
+"rosybrown",
+"royalblue",
+"saddlebrown",
+"salmon",
+"sandybrown",
+"seagreen",
+"seashell",
+"sienna",
+"silver",
+"skyblue",
+"slateblue",
+"slategray",
+"slategrey",
+"snow",
+"springgreen",
+"steelblue",
+"tan",
+"teal",
+"thistle",
+"tomato",
+"turquoise",
+"violet",
+"wheat",
+"white",
+"whitesmoke",
+"yellow",
+"yellowgreen",
+"transparent",
+"TRANSPARENT",
+"",
+"inherit",
+"INHERIT",
+"KQ@m?-ldxNK{zH+(FL_owz>YNH^]",
+"aj9c)r+J&3)0E,- Lzv6K6nT@6?I}BY^\\g",
+"Cf}qJN|3D>m:^[7B*fhu>lC[B3\"T_-F",
+"h<s1pMd-8H[,\\caWH1oW3%M",
+"z{y]VZj)%)ZV<OTbO9\\Nbc|YL}4BI<DlUBk$EV`EbN}x/@SDN0(",
+"PSX2Ol8@",
+"d+gYXKUM'&D'S]o<9T\\:hj_i!|l!)e6R4Bo)-(965rlK\"K01C68pgkJ] fx?kjT.O&sY4",
+"[4\"tk)a/v17?0W!F`AyI[='2~;:DF6I>_<O$ he213u",
+"F|r9T&N69MWq3Jrj6",
+"dYR7}n&:Rq[J",
+"M;Z]r@(R([6aT`<sN?uO'2Kb~3U\\\\tQUDxLN1f/D(,Q0w|K;,t`,tQ~[W/c!uQg)d|:D\\U33!DK&d*C`Zc'U#",
+"kV)DKUb~h{SQCM;T*g2!Rj?>Sl=jY;3W9M{Fliu!=>tDY]5",
+"y>X\\kKN|~=J+7Pqp|%9R!nZ,@>mUW9<o;|02LV<fxihsBSKVaTdcae",
+"Q>jc|/:#qwzHL`lL%e~DbhQ+d^tpf9sx%o)jC1Nm}`G;rT2jo+M$=$?BC'|O^]hW^BBo_J->bWG1",
+"OIxA\\5HB7g3Rv;PD)z?jGe?<x`4~9&D9dSDP=ilUauI'qb",
+"aND[Al/^#;n'|V\"Vl$bh5\\G#{%y4#\\W0:ZgXe73ZuXrWcL4gr|B7,ijZZi{p)M+R9{C/&249G",
+"7xK-d6Tx]BU|T,DY.qCwusmV%Ksset",
+"I=UwM''S",
+"w|_;Qw(R:>Clf[#3JFr_+?'1D&}WaY_xaRyTpwio>C;Pjf/kIW{]KK:R&ARiP=_g_UqRVvFKG(OQo6y'wF]Fc",
+"G:",
+"+XZ%s7L3FmGFn]Y!J;.vpAUoGU,&WY8eQeGWW?Jq7ANeM}[?gsV) H\\@{8z_t$oS(_jSq]|9?W*sG%' (d%",
+"*P\"?'?NHA \\!{.S=+LD8Ltr^'=,$4uQ=tVL/T_b6m!PJ8*s*v`;6kp(+8i.}T!9p6{",
+"_@(w<\\DjMk c8/\"/ifJNT_2R>V'}{&72C2+7otvd,$M@Yqc)L=O.muEp28m&AY",
+"J!M#$z|n:+:6@7n*v)UCbkVp0;\"1#}o:i4B9oh=%'I",
+"0",
+"Krq?xAul2cRe&`*Fg2)bV/r>oJ`Z(ae,z%+`E@VkWH&`-jMZ<UW~jxDek;^j2\\Uq;C,Ss",
+"#b\\l~=y5H=#Jy(6FwH5]jU;6D",
+"YO|tw;`E_'G<d~juVPCla%K]q x\"oA-aW|Y@P$_$",
+"}rI\\5x724b29MEauSSX&!AT[J1ce?,rtLAA8X",
+"hlo8jd$D-dI=I#Be:BATkZPR~%Vfe0g_Xw^+wwhHQhC1;sn+P<b&J:~HfxVBX}9b/#HHPS",
+"+#[?UFGUVFn0Zn7yE#TEo{FV\\{6*+s+a=fR",
+"lhv.f!ENs~)?5)z:1^i@BQ|ol}9Cnkw&yV.PPx |y]@,?IL]0L_# b1'wl-]",
+"&DhZ!g%v.sF}4NoP~4<vKpaM0[12!2K!ziYC3`505I*D*J6k\\skbXJ}44J#4y2",
+"oK][N&iIV\"AEs3rIT-::L3&J^Sn42_J2yL= 2xI4o!b\"#2JiAt=",
+"^c;C^{0wD%|y~Z1X'z\\o^gI8L=@2^p3g/L6G?]Nuif;Zf15dF`IPt8",
+"62t-!*`U\\l%BFxi5B~[^~G!}h]DtXrd}y}af3",
+"?N5d9ydHPi?IhwU=41'",
+"GSZeLtA3YahI@oLy/6vT_[B`[PRZ1^.(n8`,8TyqVoCzMd!=9 e",
+"Yck5`_#NgS",
+"K9?z&o",
+"Isl2>%RB8T+,9?B{~A2{fEb[%",
+"&fV(`<ha/(T7J&X\\{YHt+5 =>%SaJ&W0_j]]\"",
+">!sQ/IYU\"Ikc\\ei;HlCcVJ\":G2/m]h1,GvOmxFOOvTUHjHu:LWE\"QU=) ",
+"7Fyx#>\"(\"N",
+"MO6\"Hd2H]r8BJ}z)%J18b<VZ5lrhI",
+"BGQ|tqdwj4};#x@?%ka[`DwgFWg*J+q/}]-\\\\-y#T",
+"zZ=JrTPxh}.(%frt58Cy=C4(*,4]:Gnz5(~iv4@u4re~6yp:zbU0(o.S+qd9eB]A5",
+"n]V3}^{9O<0cO\"rtglDO4Wc)_7Nu_JnK2EBbzRMV3b.Mj\"$9#,+-T\"N=7iPdD F<9_YWw3ZN*V;??*8VN8z?^MXi",
+"fGRSl*i>^*uy|c;5B}tKXu>5hZX:>CB{oWIrxE8@B/f{:u9]:bLO0/ZWeHoNfCc|kSh{/fXs9Y:UKaJ95vFFtB2Y",
+".&-4UOcxR\"Tbgc--@& hoUavCcQW^^fT}:I(d%o}J2t*BRA1{YGXB9>AYu^Bv#rEu`pN65_-r.IQD.?Cc/B({YtK[2KMmVOC3*2J",
+"H3<MOq'81C#\\nUjQc xlsF@c2R<e);T~G]^N0_*M<j!jub~k,mgZ(.>GERhwS;kmmKC?1l} qQ&zcXK?g)S OmF^=E^TlTC)/",
+"8\\5tFz:sod",
+"ILUc5c!'K.7.P&=S,pSYB{",
+"%\\(6.jC\"C4\\2{TYdx,Ln^",
+"tL3HnWq q!'k!#R@2bkvIAI]hk)64-i?uk]oK(nQiKg$`m6tx|#l>m1bEK^&.X[o'b\"\\',A16n>9ZAPI9>",
+"{#Mn0,S~a\"!gw",
+"dv+6'7Dd)fz",
+"9o1*Q:{6#3f uHx\"_",
+".43_zr}X+BtruMV!H!xw 8!9I_}zlBT3W52)rh,9ngeu0o[V_s*z'8x9*yjF!",
+"y3Nm`>P:seF'V'?+<={oU5QQ",
+".#Os_jv,\"@-zsY8j'POOYnY?0ONn?i#d4tqp?~.OF#VC.=<t<+feuf{#@O7lXC@+#t_uKGue%Dk9z",
+"0Ep=zwydU-V<)9<9\\`[4,d^B&Gbo#'HTSEC;qU&1| ocNd69#{nbmYJMlj6Mfs3`w&pc(poie *ZOJIp7%}cVnfml",
+"746}e(rye4lT7#B.Or8j1->Xs@o8f0}/e>uvvkNS[3UC2F]#[>^f74jxoo&9{^ED#,CV\"k'0|xI",
+".D1{.:9gHW}]36RlUQ?!-\"0dn:+(/e@b)|'B",
+"!Nh9xY2y?5ku5f}%]JWw~OfeYdcgI#It` T)3VzYl5gChve[I'rEqyJ \"@(z.Y%fEJ:9k(",
+"J^$^L8:qI=yrerd0kxJBEwby6[>9[) NpqN%)h",
+"l&\\K{s9]aL(,dX?B\"1g1*@9BY~=UO+AWvVRI;ar4p8Tsy~Qk-=x(yp38:j|g'5H~e4",
+"(]Pe,1fTS_P%[xY96#,rwQz=P?z",
+")PC#?b'&|maJBt.6Izz%vV5e!9Sy|G!*Q",
+"pzAG^@/J&)Dc}GP*xO]ezW:*PV|Dt^fF<8GgF_1i=A\"@>nt?yOa|zS<8`/;uY~^ozjvX!K#%us!>2IYITh]Zy<^dq?&\"nqV]ZdZ",
+"T<xz*/H&}36<(4E^/Z1m<#_G0R:=qX?1`*6Y&r'SIO]9OR;m5-Zq?PU^jvLKPLW2wPf",
+")CLhmCI%TwB+t:h.@Vp-#({d0W/R_(^f34LC=V)A",
+"yCT_hl%fUL<T&e;ePsT.pJG@|jO,[pN]]Q<Yu=Lp6X6&$Ka#",
+" $4X",
+"rp",
+"t^(*U<Qdi$!vyg5D\"yQZd<K6<Qptsvzi~D}.Uf?P$E>}t8VP^;3Tamv2Z$1<",
+",",
+"EGCXZ_{eqIwfG-o7o\\\"!ZWTPLd,U-k$Cz]%:vNWdo}vDh!ONtM>mMP{/Cg+2<.J\"a*n#Rtnha",
+"LQ3sIr7Q_ wSD7Zzv$-vxr|3P`pas0#Ze/---{PSwJ3{!a*[k'nFgC\"W+@4URi?qJk&Tt@`abNms40#A^XcAt}",
+"^i356'hX\"hKaZGfTZ|C@#}b3LGz1\">qcH{L8{Fs?O5%:EqQWuro_lc=]gWLVR\\~!J\"[>,H",
+"i{*q<O{Dt^n|FY3,FG>WIRqPH.os$9^P=|yA9?P;MOw;VBwZ^>K=\"%J9SBlv%0+o5k73rW!`l_",
+"-PCM&!G~o#Za^s&)qJELr\\P^\\={_xTFp:%@JF.PeRX\\7b8K",
+"V_sFicB+wx5",
+"\"X^\\d}b9.W.2\"O!yAL21\"Ny5:)=Q3*D|TAzzr^0Yflzjdc!p*.yW,B",
+"kZ`wCP>9Bq5S!r!Vi|Uy/C&H[kz/f^{(Z[OGw'S0\"",
+"Jn%1^rUnNB|%=q%^v*bN|I40}#Htn{G!#~CNAN0KvZcB>Ita(,n",
+"-PlhE[^J55Ui",
+"z`h`uQJ{J",
+"eV\\q5Q4o@Y*,IRMcnpqj5>Id\\yBe?pKH3uF&c<c}:E9[uaH$ 8dXCmI+!C'q@PkE<NVRq~GRW<tfyt/i@%dwI&rL",
+"UGzLF+o3)Ezs=nMxqd^\"=q.Ik}Tk2I`X)R8]Zmy/WQp,|]TdbP)5 J+#Hm6SmWtQ+h?.MQ1W#oyp\\F,'JL>rLtjiHOA",
+"&joOSw7XZVvSt4ZCT*:aq:3ns!v|r);~7gN8'_D",
+"Y<q|Mhn5Nrcb+dR=10pQF5]r@/*7P`79w/htSm2,C~1&sUW{N@v:t9d;HPG&xrI\"YD;V9Y$'g,W'J=GV3,YK",
+"Gx&#{;]l/?[{SyX`kTeo",
+"30PU7@<'58.hRWsJTa9L.hVQ8}7=$}ih4|$Y*9z3[aooT!]}+>b{1JH^.jjEU{,dAXSCbtEh6",
+"%2~x8=A!RW@8N/`hQz`)gl}1DOU9{>Ie'L> 4e]m;kt =isEQ(\\TeI7hWgK-K! p^K'\":3;dxTLO",
+"\\ ):{Woay[4",
+"\\{Ih&}*8^x6@`V@DZB`rSuhYm4i@TW^t9Hx[^`IVumjXc1vA\"~wt8^Jf:US6Z\\xaS&",
+"lo $6<EP|=gAEpd\\M6YDg\"*0m",
+/*
+"ActiveBorder",
+"ActiveCaption",
+"AppWorkspace",
+"Background",
+"ButtonFace",
+"ButtonHighlight",
+"ButtonShadow",
+"ButtonText",
+"CaptionText",
+"GrayText",
+"Highlight",
+"HighlightText",
+"InactiveBorder",
+"InactiveCaption",
+"InactiveCaptionText",
+"InfoBackground",
+"InfoText",
+"Menu",
+"MenuText",
+"Scrollbar",
+"ThreeDDarkShadow",
+"ThreeDFace",
+"ThreeDHighlight",
+"ThreeDLightShadow",
+"ThreeDShadow",
+"Window",
+"WindowFrame",
+"WindowText",
+*/
+"currentColor",
+"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f",
+"#",
+"#1",
+"#12",
+"#123",
+"#1234",
+"#12x",
+"abc",
+"123",
+"#0ab0cd0ef",
+"#0ab0cd0e",
+"#0ab0cd0",
+"#0ab0cdaef",
+"#0ab0cdae",
+"#0ab0cda"
+];
+
+var references = [
+"#113355",
+"#001350",
+"#135000",
+"#001350",
+"#013500",
+"#000135",
+"#123456",
+"#002356",
+"#124500",
+"#002356",
+"#013460",
+"#001245",
+"#f0f8ff",
+"#f0f8ff",
+"#a0ce00",
+"#a0ce00",
+"#0ace0e",
+"#a0ebe0",
+"#00cee0",
+"#faebd7",
+"#00ffff",
+"#7fffd4",
+"#f0ffff",
+"#f5f5dc",
+"#ffe4c4",
+"#000000",
+"#ffebcd",
+"#0000ff",
+"#8a2be2",
+"#a52a2a",
+"#deb887",
+"#5f9ea0",
+"#7fff00",
+"#d2691e",
+"#ff7f50",
+"#6495ed",
+"#fff8dc",
+"#dc143c",
+"#00ffff",
+"#00008b",
+"#008b8b",
+"#b8860b",
+"#a9a9a9",
+"#006400",
+"#a9a9a9",
+"#bdb76b",
+"#8b008b",
+"#556b2f",
+"#ff8c00",
+"#9932cc",
+"#8b0000",
+"#e9967a",
+"#8fbc8f",
+"#483d8b",
+"#2f4f4f",
+"#2f4f4f",
+"#00ced1",
+"#9400d3",
+"#ff1493",
+"#00bfff",
+"#696969",
+"#696969",
+"#1e90ff",
+"#b22222",
+"#fffaf0",
+"#228b22",
+"#ff00ff",
+"#dcdcdc",
+"#f8f8ff",
+"#ffd700",
+"#daa520",
+"#808080",
+"#008000",
+"#adff2f",
+"#808080",
+"#f0fff0",
+"#ff69b4",
+"#cd5c5c",
+"#4b0082",
+"#fffff0",
+"#f0e68c",
+"#e6e6fa",
+"#fff0f5",
+"#7cfc00",
+"#fffacd",
+"#add8e6",
+"#f08080",
+"#e0ffff",
+"#fafad2",
+"#d3d3d3",
+"#90ee90",
+"#d3d3d3",
+"#ffb6c1",
+"#ffa07a",
+"#20b2aa",
+"#87cefa",
+"#778899",
+"#778899",
+"#b0c4de",
+"#ffffe0",
+"#00ff00",
+"#32cd32",
+"#faf0e6",
+"#ff00ff",
+"#800000",
+"#66cdaa",
+"#0000cd",
+"#ba55d3",
+"#9370db",
+"#3cb371",
+"#7b68ee",
+"#00fa9a",
+"#48d1cc",
+"#c71585",
+"#191970",
+"#f5fffa",
+"#ffe4e1",
+"#ffe4b5",
+"#ffdead",
+"#000080",
+"#fdf5e6",
+"#808000",
+"#6b8e23",
+"#ffa500",
+"#ff4500",
+"#da70d6",
+"#eee8aa",
+"#98fb98",
+"#afeeee",
+"#db7093",
+"#ffefd5",
+"#ffdab9",
+"#cd853f",
+"#ffc0cb",
+"#dda0dd",
+"#b0e0e6",
+"#800080",
+"#ff0000",
+"#bc8f8f",
+"#4169e1",
+"#8b4513",
+"#fa8072",
+"#f4a460",
+"#2e8b57",
+"#fff5ee",
+"#a0522d",
+"#c0c0c0",
+"#87ceeb",
+"#6a5acd",
+"#708090",
+"#708090",
+"#fffafa",
+"#00ff7f",
+"#4682b4",
+"#d2b48c",
+"#008080",
+"#d8bfd8",
+"#ff6347",
+"#40e0d0",
+"#ee82ee",
+"#f5deb3",
+"#ffffff",
+"#f5f5f5",
+"#ffff00",
+"#9acd32",
+"transparent",
+"transparent",
+"transparent",
+"#00e000",
+"#00e000",
+"#0df000",
+"#0000b0",
+"#007b30",
+"#008001",
+"#004b00",
+"#002080",
+"#099600",
+"#0120e2",
+"#f00630",
+"#d00000",
+"#200000",
+"#0c00d0",
+"#07900a",
+"#db020b",
+"#7000a0",
+"#b00c02",
+"#d6d000",
+"#000000",
+"#1d00f0",
+"#000000",
+"#a00000",
+"#00b600",
+"#007028",
+"#00b0b9",
+"#000000",
+"#f00000",
+"#b00005",
+"#0ea000",
+"#72000a",
+"#ba0c00",
+"#0fe000",
+"#50000b",
+"#000c44",
+"#ae4202",
+"#00005d",
+"#0000af",
+"#5d0041",
+"#a00000",
+"#c00000",
+"#090000",
+"#0209fe",
+"#a00500",
+"#c0100e",
+"#7f0000",
+"#600000",
+"#d00d00",
+"#0004d9",
+"#c00000",
+"#0500ff",
+"#0000c3",
+"#200000",
+"#80f00d",
+"#0c7000",
+"#00c4d0",
+"#a000a0",
+"#00a000",
+"#d0070f",
+"#900600",
+"#002090",
+"#30ef00",
+"#0000e0",
+"#b000c0",
+"#80c200",
+"#0900e0",
+"#0005e0",
+"#800b00",
+"#b0a000",
+"#e00000",
+"#000690",
+"#d00000",
+"#010020",
+"#0000c0",
+"#000060",
+"#004000",
+"#000000",
+"#000da0",
+"#000000",
+"#00d000",
+"#000fa0",
+"#0c5000",
+"#000030",
+"#a0007b",
+"#00cb05",
+"#023000",
+"#9bc000",
+"#b000b0",
+"#00e055",
+"#000000",
+"#0dc0d0",
+"#20600a",
+"#70a070",
+"#f50000",
+"#0000e0",
+"#a900cb",
+"#0000d0",
+"#000a40",
+"#d00060",
+"#000ad0",
+/*
+"ActiveBorder",
+"ActiveCaption",
+"AppWorkspace",
+"Background",
+"ButtonFace",
+"ButtonHighlight",
+"ButtonShadow",
+"ButtonText",
+"CaptionText",
+"GrayText",
+"Highlight",
+"HighlightText",
+"InactiveBorder",
+"InactiveCaption",
+"InactiveCaptionText",
+"InfoBackground",
+"InfoText",
+"Menu",
+"MenuText",
+"Scrollbar",
+"ThreeDDarkShadow",
+"ThreeDFace",
+"ThreeDHighlight",
+"ThreeDLightShadow",
+"ThreeDShadow",
+"Window",
+"WindowFrame",
+"WindowText",
+*/
+"#c0e000",
+"#0000f0",
+"#000000",
+"#0000f0",
+"#000000",
+"#000000",
+"#010000",
+"#010200",
+"#112233",
+"#123400",
+"#010200",
+"#0a0b0c",
+"#010203",
+"#abcdef",
+"#abcde0",
+"#abcd00",
+"#0a0cae",
+"#0a0cae",
+"#0a0ca0"
+];
+
+var todos = {
+" #135": true,
+"#135 ": true,
+" #135 ": true,
+"# 135": true,
+" #123456": true,
+"#123456 ": true,
+" #123456 ": true,
+"# 123456": true,
+" aliceblue": true,
+"aliceblue ": true,
+" aliceblue ": true,
+"H3<MOq'81C#\\nUjQc xlsF@c2R<e);T~G]^N0_*M<j!jub~k,mgZ(.>GERhwS;kmmKC?1l} qQ&zcXK?g)S OmF^=E^TlTC)/": true,
+" $4X": true,
+"UGzLF+o3)Ezs=nMxqd^\"=q.Ik}Tk2I`X)R8]Zmy/WQp,|]TdbP)5 J+#Hm6SmWtQ+h?.MQ1W#oyp\\F,'JL>rLtjiHOA": true
+};
+
+var table0 = document.getElementById("table0");
+var table1 = document.getElementById("table1");
+var cs0 = document.defaultView.getComputedStyle(table0);
+var cs1 = document.defaultView.getComputedStyle(table1);
+var result;
+var reference;
+var log = "";
+var len = tests.length;
+is(tests.length, references.length, "array length mismatch");
+for (var i = 0; i < len; ++i) {
+ table0.setAttribute("bgColor", tests[i]);
+ table1.style.backgroundColor = references[i];
+ ((tests[i] in todos) ? todo_is : is)(
+ cs0.getPropertyValue("background-color"),
+ cs1.getPropertyValue("background-color"),
+ "html color '" + tests[i] + "' should match '" + references[i] + "'");
+}
+</script>
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_htmlcopyencoder.html b/dom/base/test/test_htmlcopyencoder.html
new file mode 100644
index 0000000000..eecd925f89
--- /dev/null
+++ b/dom/base/test/test_htmlcopyencoder.html
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test on the html copy encoder</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422403">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testHtmlCopyEncoder () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createHTMLCopyEncoder();
+ var out, expected;
+
+ var node = document.getElementById('draggable');
+
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = 'This is a <em>draggable</em> bit of text.';
+ is(out, expected, "test container node ");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>";
+ is(out, expected, "test node");
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = "<div style=\"display: none\">\n\n<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>\n\n</div>";
+ is(out, expected, "test selection");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputAbsoluteLinks | de.OutputEncodeHTMLEntities | de.OutputSelectionOnly | de.OutputRaw);
+ encoder.setSelection(select);
+ var outContext = {value:''}, outInfo = {value:''};
+ out = encoder.encodeToStringWithContext(outContext, outInfo);
+ expected = "<div style=\"display: none\">\n\n<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>\n\n</div>";
+ is(out, expected, "test encodeToStringWithContext with selection ");
+
+ node.nextSibling.data="\nfoo bar\n";
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>";
+ is(out, expected, "test selection with additional data");
+
+ node = document.getElementById('aList');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = '\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n';
+ is(out, expected, "test list container node");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<ol id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>";
+ is(out, expected, "test list node");
+
+ var liList = node.getElementsByTagName("li");
+ var range = document.createRange();
+
+ // selection start at the first child of the ol, and end after the element ol
+ range.setStart(node, 1);
+ range.setEnd(node.parentNode, 2);
+ select.removeAllRanges();
+ select.addRange(range);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the first child of the ol, and end after the element ol");
+
+ // selection start at the third child of the ol, and end after the element ol
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList"><li value=\"2\">sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol");
+
+
+ // selection start at the third child of the ol, and end after the element ol + ol start at the value 5
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id=\"aList\" start=\"5\"><li value=\"6\">sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol + ol start at the value 5");
+
+ // selection contains only some child of the ol
+ node.removeAttribute("start");
+ range.setStart(node, 3);
+ range.setEnd(node, 5);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol");
+
+ // selection contains only some child of the ol + ol start at the value 5
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol + ol start at the value 5");
+
+ // selection contains only some child of the ol + a value is set on the first li
+ node.removeAttribute("start");
+ liList[0].setAttribute("value","8");
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ is(out, expected, "test list selection with range: selection contains only some child of the ol + ol start at the value 5");
+
+ select.selectAllChildren(node);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id=\"aList\">\n <li value=\"8\">Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ is(out, expected, "test list selection with a value on a LI");
+
+ //test Bug 436703
+ node = document.getElementById('aContentEditable');
+ select.selectAllChildren(node);
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<p>one</p><p>two</p>';
+ is(out, expected, "select all children in an contentEditable div should not select the div itself");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlCopyEncoder);
+
+</script>
+</pre>
+<div style="display: none">
+
+<div id="draggable" ondragstart="doDragStartSelection(event)">This is a <em>draggable</em> bit of text.</div>
+
+</div>
+<div style="display: none">
+
+<ol id="aList">
+ <li>Lorem ipsum dolor</li>
+ <li>sit amet, <strong>consectetuer</strong> </li>
+ <li>adipiscing elit</li>
+ <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>
+ <li>aptent taciti</li>
+</ol>
+foo bar
+</div>
+
+<div id="aContentEditable" contentEditable="true"><p>one</p><p>two</p></div>
+</body>
+</html>
diff --git a/dom/base/test/test_htmlcopyencoder.xhtml b/dom/base/test/test_htmlcopyencoder.xhtml
new file mode 100644
index 0000000000..8740a54f02
--- /dev/null
+++ b/dom/base/test/test_htmlcopyencoder.xhtml
@@ -0,0 +1,179 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+-->
+<head>
+ <title>Test the html copy encoder with XHTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422403">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+//<![CDATA[
+function testHtmlCopyEncoder () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cu.createHTMLCopyEncoder();
+ var out, expected;
+
+ var node = document.getElementById('draggable');
+
+
+ // in the following tests, we must use the OutputLFLineBreak flag, to avoid
+ // to have the default line break of the platform in the result, so the test
+ // can pass on all platform
+
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = 'This is a <em>draggable</em> <br>bit of text.';
+ is(out, expected, "test container node ");
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> <br>bit of text.</div>";
+ is(out, expected, "test node");
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = "<div style=\"display: none;\">\n\n<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> <br>bit of text.</div>\n\n</div>";
+ todo_is(out, expected, "test selection");
+
+ node.nextSibling.data="\nfoo bar\n";
+ encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em>\n <br>bit of text.</div>";
+ todo_is(out, expected, "test selection with additional data");
+
+ node = document.getElementById('aList');
+
+ var select = window.getSelection();
+ select.selectAllChildren(node);
+
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus \naliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ todo_is(out, expected, "test list selection");
+
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setContainerNode(node);
+ out = encoder.encodeToString();
+ expected = '\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n';
+ is(out, expected, "test list container node");
+
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setNode(node);
+ out = encoder.encodeToString();
+ expected = "<ol id=\"aList\">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>";
+ is(out, expected, "test list node");
+
+ var liList = node.getElementsByTagName("li");
+ var range = document.createRange();
+
+ // selection start at the first child of the ol, and end after the element ol
+ range.setStart(node, 1);
+ range.setEnd(node.parentNode, 2);
+ select.removeAllRanges();
+ select.addRange(range);
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList">\n <li>Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ todo_is(out, expected, "test list selection with range: selection start at the first child of the ol, and end after the element ol");
+
+ // selection start at the third child of the ol, and end after the element ol
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id="aList"><li value=\"2\">sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ todo_is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol");
+
+
+ // selection start at the third child of the ol, and end after the element ol + ol start at the value 5
+ range.setStart(node, 3);
+ range.setEnd(node.parentNode, 2);
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol start=\"5\" id=\"aList\"><li value=\"6\">sit amet, <strong>consectetuer</strong>\n </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ todo_is(out, expected, "test list selection with range: selection start at the third child of the ol, and end after the element ol + ol start at the value 5");
+
+
+ // selection contains only some child of the ol
+ node.removeAttribute("start");
+ range.setStart(node, 3);
+ range.setEnd(node, 5);
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ todo_is(out, expected, "test list selection with range: selection contains only some child of the ol");
+
+ // selection contains only some child of the ol + ol start at the value 5
+ node.setAttribute("start","5");
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ todo_is(out, expected, "test list selection with range: selection contains only some child of the ol + ol start at the value 5");
+
+
+ // selection contains only some child of the ol + a value is set on the first li
+ node.removeAttribute("start");
+ liList[0].setAttribute("value","8");
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<li>sit amet, <strong>consectetuer</strong> </li>\n ';
+ todo_is(out, expected, "test list selection: contains only some child of the ol + a value is set on the first li");
+
+ select.selectAllChildren(node);
+ encoder.init(document, "text/html",de.OutputLFLineBreak | de.OutputSelectionOnly);
+ encoder.setSelection(select);
+ out = encoder.encodeToString();
+ expected = '<ol id=\"aList\">\n <li value=\"8\">Lorem ipsum dolor</li>\n <li>sit amet, <strong>consectetuer</strong> </li>\n <li>adipiscing elit</li>\n <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus \naliquet lectus. Nunc vitae eros. Class</li>\n <li>aptent taciti</li>\n</ol>';
+ todo_is(out, expected, "test list selection with a value on a LI");
+
+ SimpleTest.finish();
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(testHtmlCopyEncoder);
+//]]>
+</script>
+</pre>
+<div style="display: none">
+
+<div id="draggable" ondragstart="doDragStartSelection(event)">This is a <em>draggable</em> <br/>bit of text.</div>
+
+</div>
+<div style="display: none">
+
+<ol id="aList">
+ <li>Lorem ipsum dolor</li>
+ <li>sit amet, <strong>consectetuer</strong> </li>
+ <li>adipiscing elit</li>
+ <li>Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class</li>
+ <li>aptent taciti</li>
+</ol>
+foo bar
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_iframe_event_listener_leaks.html b/dom/base/test/test_iframe_event_listener_leaks.html
new file mode 100644
index 0000000000..a497730ac6
--- /dev/null
+++ b/dom/base/test/test_iframe_event_listener_leaks.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1451426 - Test iframe event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate iframe. Its important here that we create a
+// listener callback from the DOM objects back to the frame's global
+// in order to exercise the leak condition.
+async function useIFrame(contentWindow) {
+ let f = contentWindow.document.createElement("iframe");
+ contentWindow.document.body.appendChild(f);
+ f.onload = _ => {
+ contentWindow.loadCount += 1;
+ };
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("IFrame", useIFrame);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_iframe_referrer.html b/dom/base/test/test_iframe_referrer.html
new file mode 100644
index 0000000000..17f0e915e7
--- /dev/null
+++ b/dom/base/test/test_iframe_referrer.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test iframe referrer policy attribute for Bug 1175736</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that iframe referrer attribute is honoured correctly
+ * regular loads
+ * regression tests that meta referrer is still working even if attribute referrers are enabled
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1175736
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-iframe-policy-test"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (iframe) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ DESC: "origin (iframe) with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "no-referrer (iframe) with origin in meta",
+ RESULT: 'none'},
+ {NAME: 'no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "no-referrer in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'origin-with-no-meta',
+ META_POLICY: '',
+ DESC: "origin (iframe) with no meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "same-origin with origin in meta",
+ RESULT: 'full'},
+
+ // 1. Downgrade.
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin-when-cross-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // 2. No downgrade.
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'origin in meta strict-origin-when-cross-origin in attr',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'strict-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ DESC: "strict-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ DESC: "same-origin with origin in meta",
+ RESULT: 'none'},
+ ]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_iframe_referrer_changing.html b/dom/base/test/test_iframe_referrer_changing.html
new file mode 100644
index 0000000000..d3a39480a3
--- /dev/null
+++ b/dom/base/test/test_iframe_referrer_changing.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test iframe referrer policy attribute for Bug 1175736</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that iframe referrer attribute is honoured correctly
+ * testing setAttribute and .referrer
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1175736
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY"];
+
+ const testCases = [
+ {ACTION: ["generate-iframe-changing-policy-test-set-attribute"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NEW_ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'no-referrer-unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "no-referrer (iframe, orginally unsafe-url) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "unsafe-url (iframe, orginally origin) with no-referrer in meta",
+ RESULT: 'full'}]},
+ {ACTION: ["generate-iframe-changing-policy-test-property"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'unsafe-url-no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "unsafe-url (iframe, orginally no-referrer) with origin in meta",
+ RESULT: 'full'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_iframe_referrer_invalid.html b/dom/base/test/test_iframe_referrer_invalid.html
new file mode 100644
index 0000000000..8f6ba2126c
--- /dev/null
+++ b/dom/base/test/test_iframe_referrer_invalid.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test iframe referrer policy attribute for Bug 1175736</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that iframe referrer attribute is honoured correctly
+ * invalid referrer policies
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1175736
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-iframe-policy-test"],
+ TESTS: [
+ // setting invalid refer values -> we expect either full referrer (default)
+ // or whatever is specified in the meta referrer policy
+
+ // Note that for those test cases which require cross-origin test, we use different
+ // scheme to result in cross-origin request.
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-no-meta',
+ META_POLICY: '',
+ DESC: "origin-when-cross-origin (iframe) with no meta",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'default',
+ NAME: 'default-with-no-meta',
+ META_POLICY: '',
+ DESC: "default (iframe) with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'something',
+ NAME: 'something-with-no-meta',
+ META_POLICY: '',
+ DESC: "something (iframe) with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "origin-when-cross-origin (iframe) with no-referrer in meta",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ DESC: "origin-when-cross-origin (iframe) with unsafe-url in meta",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "origin-when-cross-origin (iframe) with origin in meta",
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'origin'},
+ {NAME: 'origin-in-meta',
+ META_POLICY: 'origin',
+ DESC: "origin in meta",
+ RESULT: 'origin'},
+ {NAME: 'no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ DESC: "no-referrer in meta",
+ RESULT: 'none'}]}
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_innersize_scrollport.html b/dom/base/test/test_innersize_scrollport.html
new file mode 100644
index 0000000000..c820d3f5d1
--- /dev/null
+++ b/dom/base/test/test_innersize_scrollport.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 919437</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=919437">Mozilla Bug 919437</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 919437 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ var oldWidth = window.innerWidth;
+ var oldHeight = window.innerHeight;
+ var newWidth = oldWidth / 2;
+ var newHeight = oldHeight / 2;
+
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.setVisualViewportSize(newWidth, newHeight);
+ is(window.innerWidth, oldWidth, "innerWidth unaffected by changes to visual viewport size");
+ is(window.innerHeight, oldHeight, "innerHeight unaffected by changes to visual viewport size");
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_input_vsync_alignment_inner_event_loop.html b/dom/base/test/test_input_vsync_alignment_inner_event_loop.html
new file mode 100644
index 0000000000..022d06663f
--- /dev/null
+++ b/dom/base/test/test_input_vsync_alignment_inner_event_loop.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body >
+<input />
+<script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ function checkResult() {
+ ok(true, "didn't crash");
+ SimpleTest.finish();
+ }
+
+ // Dispatch an vsync event to the queue
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => {
+ });
+ });
+
+ // Dispatch the first input tasks. Since there's a vsync
+ // task in the queue, so this task takes the priority and runs
+ // first.
+ SpecialPowers.Services.tm.dispatchToMainThread(function() {
+ let secondInputRun = false;
+
+ // Dispatch the second input task to the queue.
+ SpecialPowers.Services.tm.dispatchToMainThread(function() {
+ secondInputRun = true;
+ }, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_INPUT_HIGH);
+
+ // Inner event loop runs and picks the second input task to run.
+ SpecialPowers.Services.tm.spinEventLoopUntil(
+ "Test(vsync_alignment_inner_event_loop.html)", () => secondInputRun);
+
+ setTimeout(checkResult, 0);
+ }, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_INPUT_HIGH);
+ }
+
+ SpecialPowers.pushPrefEnv({
+ set: [["dom.input_events.strict_input_vsync_alignment", true]]
+ }, runTest());
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_input_vsync_alignment_input_while_vsync.html b/dom/base/test/test_input_vsync_alignment_input_while_vsync.html
new file mode 100644
index 0000000000..6d1ac469fe
--- /dev/null
+++ b/dom/base/test/test_input_vsync_alignment_input_while_vsync.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body >
+<input />
+<script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function triggerKey() {
+ SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ var win = Services.wm.getMostRecentBrowserWindow();
+ for (let i = 0; i < 200; ++i) {
+ EventUtils.synthesizeKey("a", {}, win);
+ }
+ });
+ }
+
+ function runTest() {
+ const input = document.querySelector("input");
+ input.focus();
+
+ let didInputRun = false;
+
+ input.addEventListener("input", function() {
+ if (!didInputRun) {
+ didInputRun = true;
+ window.requestAnimationFrame(() => {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "slow.sjs", false);
+ xhr.send();
+ ok(true, "Didn't crash!");
+ SimpleTest.finish();
+ });
+ }
+ });
+
+ triggerKey();
+ }
+ runTest();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_input_vsync_alignment_lower_than_normal.html b/dom/base/test/test_input_vsync_alignment_lower_than_normal.html
new file mode 100644
index 0000000000..1d06e506b4
--- /dev/null
+++ b/dom/base/test/test_input_vsync_alignment_lower_than_normal.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body >
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ let runOrder = [];
+ function checkResult() {
+ if (runOrder.length == 3) {
+ if (runOrder[0] === "Normal") {
+ isDeeply(
+ runOrder,
+ ["Normal", "InputHigh", "Vsync"],
+ "Input priority tasks should let normal tasks to run first when there's no pending vsync"
+ );
+ SimpleTest.finish();
+ } else {
+ runOrder = [];
+ runTest();
+ }
+ }
+ }
+
+ function runTest() {
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => {
+ runOrder.push("Vsync");
+ checkResult();
+ });
+ SpecialPowers.Services.tm.dispatchToMainThread(function() {
+ runOrder.push("InputHigh");
+ checkResult();
+ }, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_INPUT_HIGH);
+ SpecialPowers.Services.tm.dispatchToMainThread(function() {
+ runOrder.push("Normal");
+ checkResult();
+ }, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_NORMAL);
+ });
+ }
+
+ SpecialPowers.pushPrefEnv({
+ set: [["dom.input_events.strict_input_vsync_alignment", true]]
+ }, runTest());
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_integer_attr_with_leading_zero.html b/dom/base/test/test_integer_attr_with_leading_zero.html
new file mode 100644
index 0000000000..816fb331c4
--- /dev/null
+++ b/dom/base/test/test_integer_attr_with_leading_zero.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for parsing of integer attributes with leading zero</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var td = document.createElement("td");
+var li = document.createElement("li");
+// Array of tests: "values" are the values to set, "tdreflection" is the
+// corresponding td.rowspan value, "lireflection" is the corresponding li.value
+// value.
+var testData = [
+ {
+ values: [
+ "2",
+ "02",
+ "002",
+ "00002",
+ ],
+ tdreflection: 2,
+ lireflection: 2,
+ },
+ {
+ values: [
+ "-2",
+ "-02",
+ "-002",
+ "-00002",
+ ],
+ tdreflection: 1,
+ lireflection: -2,
+ },
+ {
+ values: [
+ "-0",
+ "-00",
+ "0",
+ "00",
+ ],
+ tdreflection: 0,
+ lireflection: 0,
+ },
+];
+
+for (var data of testData) {
+ for (var value of data.values) {
+ td.setAttribute("rowspan", value);
+ li.setAttribute("value", value);
+ test(function() {
+ assert_equals(td.rowSpan, data.tdreflection);
+ }, `<td> reflection for ${value}`);
+ test(function() {
+ assert_equals(td.getAttribute("rowspan"), value);
+ }, `<td> setAttribute roundtripping for ${value}`);
+ test(function() {
+ assert_equals(li.value, data.lireflection);
+ }, `<li> reflection for ${value}`);
+ test(function() {
+ assert_equals(li.getAttribute("value"), value);
+ }, `<li> setAttribute roundtripping for ${value}`);
+ }
+}
+</script>
diff --git a/dom/base/test/test_intersectionobservers.html b/dom/base/test/test_intersectionobservers.html
new file mode 100644
index 0000000000..97fa173cef
--- /dev/null
+++ b/dom/base/test/test_intersectionobservers.html
@@ -0,0 +1,1221 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1243846
+
+Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
+
+Original license header:
+
+Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1243846</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1243846">Mozilla Bug 1243846</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+ /* eslint "no-shadow": ["error", {"allow": ["done", "next"]}] */
+ var tests = [];
+ var curDescribeMsg = '';
+ var curItMsg = '';
+
+ function beforeEach_fn() { };
+ function afterEach_fn() { };
+
+ function before(fn) {
+ fn();
+ }
+
+ function beforeEach(fn) {
+ beforeEach_fn = fn;
+ }
+
+ function afterEach(fn) {
+ afterEach_fn = fn;
+ }
+
+ function it(msg, fn) {
+ tests.push({
+ msg: `${msg} [${curDescribeMsg}]`,
+ fn
+ });
+ }
+
+ var callbacks = [];
+ function callDelayed(fn) {
+ callbacks.push(fn);
+ }
+
+ requestAnimationFrame(function tick() {
+ var i = callbacks.length;
+ while (i--) {
+ var cb = callbacks[i];
+ SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
+ callbacks.splice(i, 1);
+ }
+ requestAnimationFrame(tick);
+ });
+
+ function expect(val) {
+ return {
+ to: {
+ throwException (regexp) {
+ try {
+ val();
+ ok(false, `${curItMsg} - an exception should have beeen thrown`);
+ } catch (e) {
+ ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
+ }
+ },
+ get be() {
+ var fn = function (expected) {
+ is(val, expected, curItMsg);
+ };
+ fn.ok = function () {
+ ok(val, curItMsg);
+ };
+ fn.greaterThan = function (other) {
+ ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
+ };
+ fn.lessThan = function (other) {
+ ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
+ };
+ return fn;
+ },
+ eql (expected) {
+ if (Array.isArray(expected)) {
+ if (!Array.isArray(val)) {
+ ok(false, curItMsg, `${curItMsg} - should be an array,`);
+ return;
+ }
+ is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
+ if (expected.length != val.length) {
+ return;
+ }
+ for (var i = 0; i < expected.length; i++) {
+ is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
+ if (expected[i] != val[i]) {
+ return;
+ }
+ }
+ ok(true);
+ }
+ },
+ }
+ }
+ }
+
+ function describe(msg, fn) {
+ curDescribeMsg = msg;
+ fn();
+ curDescribeMsg = '';
+ }
+
+ function next() {
+ var test = tests.shift();
+ if (test) {
+ console.log(test.msg);
+ curItMsg = test.msg;
+ var fn = test.fn;
+ beforeEach_fn();
+ if (fn.length) {
+ fn(function () {
+ afterEach_fn();
+ next();
+ });
+ } else {
+ fn();
+ afterEach_fn();
+ next();
+ }
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ var sinon = {
+ spy () {
+ var cbs = [];
+ var fn = function () {
+ fn.callCount++;
+ fn.lastCall = { args: arguments };
+ if (cbs.length) {
+ cbs.shift()();
+ }
+ };
+ fn.callCount = 0;
+ fn.lastCall = { args: [] };
+ fn.waitForNotification = (fn1) => {
+ cbs.push(fn1);
+ };
+ return fn;
+ }
+ };
+
+ var ASYNC_TIMEOUT = 300;
+
+
+ var io;
+ var noop = function() {};
+
+
+ // References to DOM elements, which are accessible to any test
+ // and reset prior to each test so state isn't shared.
+ var rootEl;
+ var grandParentEl;
+ var parentEl;
+ var targetEl1;
+ var targetEl2;
+ var targetEl3;
+ var targetEl4;
+ var targetEl5;
+
+
+ describe('IntersectionObserver', function() {
+
+ before(function() {
+
+ });
+
+
+ beforeEach(function() {
+ addStyles();
+ addFixtures();
+ });
+
+
+ afterEach(function() {
+ if (io && 'disconnect' in io) io.disconnect();
+ io = null;
+
+ window.onmessage = null;
+
+ removeStyles();
+ removeFixtures();
+ });
+
+
+ describe('constructor', function() {
+
+ it('throws when callback is not a function', function() {
+ expect(function() {
+ io = new IntersectionObserver(null);
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('instantiates root correctly', function() {
+ io = new IntersectionObserver(noop);
+ expect(io.root).to.be(null);
+
+ io = new IntersectionObserver(noop, {root: rootEl});
+ expect(io.root).to.be(rootEl);
+ });
+
+
+ it('throws when root is not an Element', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {root: 'foo'});
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('instantiates rootMargin correctly', function() {
+ io = new IntersectionObserver(noop, {rootMargin: '10px'});
+ expect(io.rootMargin).to.be('10px 10px 10px 10px');
+
+ io = new IntersectionObserver(noop, {rootMargin: '10px -5%'});
+ expect(io.rootMargin).to.be('10px -5% 10px -5%');
+
+ io = new IntersectionObserver(noop, {rootMargin: '10px 20% 0px'});
+ expect(io.rootMargin).to.be('10px 20% 0px 20%');
+
+ io = new IntersectionObserver(noop, {rootMargin: '0px 0px -5% 5px'});
+ expect(io.rootMargin).to.be('0px 0px -5% 5px');
+ });
+
+
+ it('throws when rootMargin is not in pixels or percent', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {rootMargin: 'auto'});
+ }).to.throwException(/pixels.*percent/i);
+ });
+
+
+ it('instantiates thresholds correctly', function() {
+ io = new IntersectionObserver(noop);
+ expect(io.thresholds).to.eql([0]);
+
+ io = new IntersectionObserver(noop, {threshold: 0.5});
+ expect(io.thresholds).to.eql([0.5]);
+
+ io = new IntersectionObserver(noop, {threshold: [0.25, 0.5, 0.75]});
+ expect(io.thresholds).to.eql([0.25, 0.5, 0.75]);
+
+ io = new IntersectionObserver(noop, {threshold: [1, .5, 0]});
+ expect(io.thresholds).to.eql([0, .5, 1]);
+ });
+
+ it('throws when a threshold value is not between 0 and 1', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {threshold: [0, -1]});
+ }).to.throwException(/threshold/i);
+ });
+
+ it('throws when a threshold value is not a number', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {threshold: "foo"});
+ }).to.throwException(/.*/i);
+ });
+
+ });
+
+
+ describe('observe', function() {
+
+ it('throws when target is not an Element', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop);
+ io.observe(null);
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('triggers if target intersects when observing begins', function(done) {
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl});
+ io.observe(targetEl1);
+ });
+
+
+ it('triggers with the correct arguments', function(done) {
+ io = new IntersectionObserver(function(records, observer) {
+ expect(records.length).to.be(1);
+ expect(records[0] instanceof IntersectionObserverEntry).to.be.ok();
+ expect(observer).to.be(io);
+ expect(this).to.be(io);
+ done();
+ }, {root: rootEl});
+ io.observe(targetEl1);
+ });
+
+
+ it('does trigger if target does not intersect when observing begins',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ targetEl2.style.top = '-40px';
+ io.observe(targetEl2);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ });
+
+
+ it('triggers if target or root becomes invisible',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.display = 'none';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.display = 'block';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.style.display = 'none';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.style.display = 'block';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(5);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ ], done);
+ });
+
+
+ it('handles container elements with non-visible overflow',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-40px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ parentEl.style.overflow = 'visible';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('observes one target at a single threshold correctly', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl, threshold: 0.5});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.left = '-5px';
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be.greaterThan(0.5);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-15px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be.lessThan(0.5);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-25px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-10px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+
+ it('observes multiple targets at multiple thresholds correctly',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {
+ root: rootEl,
+ threshold: [1, 0.5, 0]
+ });
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-15px';
+ targetEl2.style.top = '-5px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '205px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.25);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.75);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-5px';
+ targetEl2.style.top = '-15px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '195px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.75);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.25);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.25);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '5px';
+ targetEl2.style.top = '-25px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '185px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.75);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '15px';
+ targetEl2.style.top = '-35px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '175px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl3);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles rootMargin properly', function(done) {
+
+ parentEl.style.overflow = 'visible';
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-20px';
+ targetEl2.style.top = '-20px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '200px';
+ targetEl4.style.top = '180px';
+ targetEl4.style.left = '180px';
+
+ runSequence([
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(.5);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(1);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '10px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.5);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '-10px 10%'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.5);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '-5% -2.5% 0px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.25);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '5% -2.5% -10px -190px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ }
+ ], done);
+ });
+
+
+ it('handles targets on the boundary of root', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-21px';
+ targetEl2.style.top = '-20px';
+ targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-20px';
+ targetEl2.style.top = '-21px';
+ targetEl2.style.left = '0px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be.ok();
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '-20px';
+ targetEl1.style.left = '200px';
+ targetEl2.style.top = '200px';
+ targetEl2.style.left = '200px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl3.style.top = '20px';
+ targetEl3.style.left = '-20px';
+ targetEl4.style.top = '-20px';
+ targetEl4.style.left = '20px';
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be.ok();
+ expect(records[0].target).to.be(targetEl3);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl4);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+
+ it('handles zero-size targets within the root coordinate space',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '0px';
+ targetEl1.style.width = '0px';
+ targetEl1.style.height = '0px';
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].isIntersecting).to.be.ok();
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '-1px';
+ spy.waitForNotification(function() {
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be(false);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles root/target elements not yet in the DOM', function(done) {
+
+ rootEl.remove();
+ targetEl1.remove();
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ callDelayed(done);
+ },
+ function(done) {
+ document.getElementById('fixtures').appendChild(rootEl);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ parentEl.insertBefore(targetEl1, targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ grandParentEl.remove();
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.appendChild(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.remove();
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(5);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles sub-root element scrolling', function(done) {
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl});
+
+ io.observe(targetEl3);
+ callDelayed(function() {
+ parentEl.scrollLeft = 40;
+ });
+ });
+
+
+ it('supports CSS transitions and transforms', function(done) {
+
+ targetEl1.style.top = '220px';
+ targetEl1.style.left = '220px';
+
+ var callCount = 0;
+
+ io = new IntersectionObserver(function(records) {
+ callCount++;
+ if (callCount <= 1) {
+ return;
+ }
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl, threshold: [1]});
+
+ io.observe(targetEl1);
+ callDelayed(function() {
+ targetEl1.style.transform = 'translateX(-40px) translateY(-40px)';
+ });
+ });
+
+
+ it('uses the viewport when no root is specified', function(done) {
+ window.onmessage = function (e) {
+ expect(e.data).to.be.ok();
+ win.close();
+ done();
+ };
+
+ var win = window.open("intersectionobserver_window.html");
+ });
+
+ it('triggers only once if observed multiple times (and does not crash when collected)', function(done) {
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+ io.observe(targetEl1);
+ io.observe(targetEl1);
+ io.observe(targetEl1);
+
+ spy.waitForNotification(function() {
+ callDelayed(function () {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ });
+ });
+
+ });
+
+ describe('observe subframe', function () {
+
+ it('boundingClientRect matches target.getBoundingClientRect() for an element inside an iframe',
+ function(done) {
+
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].boundingClientRect.top, targetEl5.getBoundingClientRect().top);
+ expect(records[0].boundingClientRect.left, targetEl5.getBoundingClientRect().left);
+ expect(records[0].boundingClientRect.width, targetEl5.getBoundingClientRect().width);
+ expect(records[0].boundingClientRect.height, targetEl5.getBoundingClientRect().height);
+ done();
+ }, {threshold: [1]});
+
+ targetEl4.onload = function () {
+ targetEl5 = targetEl4.contentDocument.getElementById('target5');
+ io.observe(targetEl5);
+ }
+
+ targetEl4.src = "intersectionobserver_iframe.html";
+ });
+
+ it('rootBounds is set to null for cross-origin observations', function(done) {
+
+ window.onmessage = function (e) {
+ expect(e.data).to.be(true);
+ done();
+ };
+
+ targetEl4.src = "http://example.org/tests/dom/base/test/intersectionobserver_cross_domain_iframe.html";
+
+ });
+
+ });
+
+ describe('takeRecords', function() {
+
+ it('supports getting records before the callback is invoked', function(done) {
+
+ var lastestRecords = [];
+ io = new IntersectionObserver(function(records) {
+ lastestRecords = lastestRecords.concat(records);
+ }, {root: rootEl});
+ io.observe(targetEl1);
+
+ window.requestAnimationFrame && requestAnimationFrame(function wait() {
+ lastestRecords = lastestRecords.concat(io.takeRecords());
+ if (!lastestRecords.length) {
+ requestAnimationFrame(wait);
+ return;
+ }
+ callDelayed(function() {
+ expect(lastestRecords.length).to.be(1);
+ expect(lastestRecords[0].intersectionRatio).to.be(1);
+ done();
+ });
+ });
+
+ });
+
+ });
+
+ describe('unobserve', function() {
+
+ it('removes targets from the internal store', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ io.unobserve(targetEl1);
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '-40px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl2);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ io.unobserve(targetEl2);
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(2);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+ });
+
+ describe('disconnect', function() {
+
+ it('removes all targets and stops listening for changes', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ io.disconnect();
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '-40px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+ });
+
+ });
+
+
+ /**
+ * Runs a sequence of function and when finished invokes the done callback.
+ * Each function in the sequence is invoked with its own done function and
+ * it should call that function once it's complete.
+ * @param {Array<Function>} functions An array of async functions.
+ * @param {Function} done A final callback to be invoked once all function
+ * have run.
+ */
+ function runSequence(functions, done) {
+ var next = functions.shift();
+ if (next) {
+ next(function() {
+ runSequence(functions, done);
+ });
+ } else {
+ done && done();
+ }
+ }
+
+
+ /**
+ * Sorts an array of records alphebetically by ascending ID. Since the current
+ * native implementation doesn't sort change entries by `observe` order, we do
+ * that ourselves for the non-polyfill case. Since all tests call observe
+ * on targets in sequential order, this should always match.
+ * https://crbug.com/613679
+ * @param {Array<IntersectionObserverEntry>} entries The entries to sort.
+ * @return {Array<IntersectionObserverEntry>} The sorted array.
+ */
+ function sortRecords(entries) {
+ entries = entries.sort(function(a, b) {
+ return a.target.id < b.target.id ? -1 : 1;
+ });
+ return entries;
+ }
+
+
+ /**
+ * Adds the common styles used by all tests to the page.
+ */
+ function addStyles() {
+ var styles = document.createElement('style');
+ styles.id = 'styles';
+ document.documentElement.appendChild(styles);
+
+ var cssText =
+ '#root {' +
+ ' position: relative;' +
+ ' width: 400px;' +
+ ' height: 200px;' +
+ ' background: #eee' +
+ '}' +
+ '#grand-parent {' +
+ ' position: relative;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ '}' +
+ '#parent {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 200px;' +
+ ' overflow: hidden;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ ' background: #ddd;' +
+ '}' +
+ '#target1, #target2, #target3, #target4 {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 0px;' +
+ ' width: 20px;' +
+ ' height: 20px;' +
+ ' transform: translateX(0px) translateY(0px);' +
+ ' transition: transform .5s;' +
+ ' background: #f00;' +
+ ' border: none;' +
+ '}';
+
+ styles.innerHTML = cssText;
+ }
+
+
+ /**
+ * Adds the DOM fixtures used by all tests to the page and assigns them to
+ * global variables so they can be referenced within the tests.
+ */
+ function addFixtures() {
+ var fixtures = document.createElement('div');
+ fixtures.id = 'fixtures';
+
+ fixtures.innerHTML =
+ '<div id="root">' +
+ ' <div id="grand-parent">' +
+ ' <div id="parent">' +
+ ' <div id="target1"></div>' +
+ ' <div id="target2"></div>' +
+ ' <div id="target3"></div>' +
+ ' <iframe id="target4"></iframe>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>';
+
+ document.body.appendChild(fixtures);
+
+ rootEl = document.getElementById('root');
+ grandParentEl = document.getElementById('grand-parent');
+ parentEl = document.getElementById('parent');
+ targetEl1 = document.getElementById('target1');
+ targetEl2 = document.getElementById('target2');
+ targetEl3 = document.getElementById('target3');
+ targetEl4 = document.getElementById('target4');
+ }
+
+
+ /**
+ * Removes the common styles from the page.
+ */
+ function removeStyles() {
+ var styles = document.getElementById('styles');
+ styles.remove();
+ }
+
+
+ /**
+ * Removes the DOM fixtures from the page and resets the global references.
+ */
+ function removeFixtures() {
+ var fixtures = document.getElementById('fixtures');
+ fixtures.remove();
+
+ rootEl = null;
+ grandParentEl = null;
+ parentEl = null;
+ targetEl1 = null;
+ targetEl2 = null;
+ targetEl3 = null;
+ targetEl4 = null;
+ }
+
+ function onLoad() {
+ next();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_link_prefetch.html b/dom/base/test/test_link_prefetch.html
new file mode 100644
index 0000000000..5a5e7a0bbc
--- /dev/null
+++ b/dom/base/test/test_link_prefetch.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test link policy attribute for Bug 1264165</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that link referrer attributes are honoured correctly
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1264165
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-link-policy-test"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'prefetch-unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'prefetch',
+ DESC: "prefetch-unsafe-url with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'prefetch-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ REL: 'prefetch',
+ DESC: "prefetch-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'prefetch-no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'prefetch',
+ DESC: "prefetch-no-referrer with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'prefetch-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'prefetch',
+ DESC: "prefetch-same-origin with origin in meta",
+ RESULT: 'full'},
+ {NAME: 'prefetch-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'prefetch',
+ DESC: "prefetch-no-referrer in meta",
+ RESULT: 'none'},
+
+ // Downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'prefetch-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta downgrade in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'prefetch-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta strict-origin in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'prefetch-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // No downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'prefetch-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta downgrade in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'prefetch-origin-with-no-meta',
+ META_POLICY: '',
+ REL: 'prefetch',
+ DESC: "prefetch-origin with no meta",
+ RESULT: 'origin'},
+
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'prefetch-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta strict-origin in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'prefetch-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'prefetch-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'prefetch',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ // Cross origin
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'prefetch-origin-when-cross-origin-with-no-meta',
+ META_POLICY: '',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'prefetch',
+ DESC: "prefetch-origin-when-cross-origin with no meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'prefetch-origin-when-cross-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'prefetch',
+ DESC: "prefetch-origin-when-cross-origin with no-referrer in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'prefetch-origin-when-cross-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'prefetch',
+ DESC: "prefetch-origin-when-cross-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'prefetch-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'prefetch',
+ DESC: "prefetch-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'prefetch-strict-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'prefetch',
+ DESC: "prefetch-strict-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'prefetch-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'prefetch',
+ DESC: "prefetch-same-origin with origin in meta",
+ RESULT: 'none'},
+
+ // Invalid
+ {ATTRIBUTE_POLICY: 'default',
+ NAME: 'prefetch-default-with-no-meta',
+ META_POLICY: '',
+ REL: 'prefetch',
+ DESC: "prefetch-default with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'something',
+ NAME: 'prefetch-something-with-no-meta',
+ META_POLICY: '',
+ REL: 'prefetch',
+ DESC: "prefetch-something with no meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-set-attribute"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NEW_ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'prefetch-no-referrer-unsafe-url-set-attribute-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'prefetch',
+ DESC: "prefetch-no-referrer-set-attribute (orginally unsafe-url) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'prefetch-unsafe-url-origin-set-attribute-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'prefetch',
+ DESC: "prefetch-unsafe-url-set-attribute(orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-property"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'prefetch-unsafe-url-no-referrer-property-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'prefetch',
+ DESC: "prefetch-unsafe-url-property (orginally no-referrer) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'prefetch-unsafe-url-origin-property-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'prefetch',
+ DESC: "prefetch-unsafe-url-property (orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+ ];
+
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_link_preload.html b/dom/base/test/test_link_preload.html
new file mode 100644
index 0000000000..6e57f4a46d
--- /dev/null
+++ b/dom/base/test/test_link_preload.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test preload referrer policy for Bug 1399780</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that link referrer attributes are honoured correctly for rel=preload
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1399780
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-link-policy-test"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'preload-unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'preload',
+ DESC: "preload-unsafe-url with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'preload-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ REL: 'preload',
+ DESC: "preload-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'preload-no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'preload',
+ DESC: "preload-no-referrer with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'preload-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'preload',
+ DESC: "preload-same-origin with origin in meta",
+ RESULT: 'full'},
+ {NAME: 'preload-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'preload',
+ DESC: "preload-no-referrer in meta",
+ RESULT: 'none'},
+
+ // Downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'preload-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta downgrade in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'preload-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta strict-origin in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'preload-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // No downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'preload-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta downgrade in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'preload-origin-with-no-meta',
+ META_POLICY: '',
+ REL: 'preload',
+ DESC: "preload-origin with no meta",
+ RESULT: 'origin'},
+
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'preload-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta strict-origin in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'preload-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'preload-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'preload',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ // Cross origin
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'preload-origin-when-cross-origin-with-no-meta',
+ META_POLICY: '',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'preload',
+ DESC: "preload-origin-when-cross-origin with no meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'preload-origin-when-cross-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'preload',
+ DESC: "preload-origin-when-cross-origin with no-referrer in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'preload-origin-when-cross-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'preload',
+ DESC: "preload-origin-when-cross-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'preload-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'preload',
+ DESC: "preload-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'preload-strict-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'preload',
+ DESC: "preload-strict-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'preload-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'preload',
+ DESC: "preload-same-origin with origin in meta",
+ RESULT: 'none'},
+
+ // Invalid
+ {ATTRIBUTE_POLICY: 'default',
+ NAME: 'preload-default-with-no-meta',
+ META_POLICY: '',
+ REL: 'preload',
+ DESC: "preload-default with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'something',
+ NAME: 'preload-something-with-no-meta',
+ META_POLICY: '',
+ REL: 'preload',
+ DESC: "preload-something with no meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-set-attribute"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NEW_ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'preload-no-referrer-unsafe-url-set-attribute-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'preload',
+ DESC: "preload-no-referrer-set-attribute (orginally unsafe-url) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'preload-unsafe-url-origin-set-attribute-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'preload',
+ DESC: "preload-unsafe-url-set-attribute(orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-property"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'preload-unsafe-url-no-referrer-property-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'preload',
+ DESC: "preload-unsafe-url-property (orginally no-referrer) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'preload-unsafe-url-origin-property-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'preload',
+ DESC: "preload-unsafe-url-property (orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+ ];
+
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_link_stylesheet.html b/dom/base/test/test_link_stylesheet.html
new file mode 100644
index 0000000000..d699d22ee2
--- /dev/null
+++ b/dom/base/test/test_link_stylesheet.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test link policy attribute for Bug 1264165</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing that link referrer attributes are honoured correctly
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1264165
+ -->
+
+ <script type="application/javascript">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+ const testCases = [
+ {ACTION: ["generate-link-policy-test"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'stylesheet-unsafe-url-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'stylesheet',
+ DESC: "stylesheet-unsafe-url with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'stylesheet-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'stylesheet-no-referrer-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'stylesheet',
+ DESC: "stylesheet-no-referrer with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'stylesheet-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'stylesheet',
+ DESC: "stylesheet-same-origin with origin in meta",
+ RESULT: 'full'},
+ {NAME: 'stylesheet-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'stylesheet',
+ DESC: "stylesheet-no-referrer in meta",
+ RESULT: 'none'},
+
+ // Downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'stylesheet-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta downgrade in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'stylesheet-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta strict-origin in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'stylesheet-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ RESULT: 'none'},
+
+ // No downgrade.
+ {ATTRIBUTE_POLICY: 'no-referrer-when-downgrade',
+ NAME: 'stylesheet-origin-in-meta-downgrade-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta downgrade in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ {ATTRIBUTE_POLICY: 'origin',
+ NAME: 'stylesheet-origin-with-no-meta',
+ META_POLICY: '',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin with no meta",
+ RESULT: 'origin'},
+
+ {ATTRIBUTE_POLICY: 'strict-origin',
+ NAME: 'stylesheet-origin-in-meta-strict-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta strict-origin in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'stylesheet-origin-in-meta-strict-origin-when-cross-origin-in-attr',
+ META_POLICY: 'origin',
+ DESC: 'stylesheet-origin in meta strict-origin-when-cross-origin in attr',
+ REL: 'stylesheet',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'https',
+ RESULT: 'full'},
+
+ // Cross origin
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'stylesheet-origin-when-cross-origin-with-no-meta',
+ META_POLICY: '',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin-when-cross-origin with no meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'stylesheet-origin-when-cross-origin-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin-when-cross-origin with no-referrer in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'stylesheet-origin-when-cross-origin-with-unsafe-url-in-meta',
+ META_POLICY: 'unsafe-url',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin-when-cross-origin with unsafe-url in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'origin-when-cross-origin',
+ NAME: 'stylesheet-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'https',
+ SCHEME_TO: 'http',
+ REL: 'stylesheet',
+ DESC: "stylesheet-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'strict-origin-when-cross-origin',
+ NAME: 'stylesheet-strict-origin-when-cross-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'stylesheet',
+ DESC: "stylesheet-strict-origin-when-cross-origin with origin in meta",
+ RESULT: 'origin'},
+ {ATTRIBUTE_POLICY: 'same-origin',
+ NAME: 'stylesheet-same-origin-with-origin-in-meta',
+ META_POLICY: 'origin',
+ SCHEME_FROM: 'http',
+ SCHEME_TO: 'https',
+ REL: 'stylesheet',
+ DESC: "stylesheet-same-origin with origin in meta",
+ RESULT: 'none'},
+
+ // Invalid
+ {ATTRIBUTE_POLICY: 'default',
+ NAME: 'stylesheet-default-with-no-meta',
+ META_POLICY: '',
+ REL: 'stylesheet',
+ DESC: "stylesheet-default with no meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'something',
+ NAME: 'stylesheet-something-with-no-meta',
+ META_POLICY: '',
+ REL: 'stylesheet',
+ DESC: "stylesheet-something with no meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-set-attribute"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'unsafe-url',
+ NEW_ATTRIBUTE_POLICY: 'no-referrer',
+ NAME: 'stylesheet-no-referrer-unsafe-url-set-attribute-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'stylesheet',
+ DESC: "stylesheet-no-referrer-set-attribute (orginally unsafe-url) with origin in meta",
+ RESULT: 'none'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'stylesheet-unsafe-url-origin-set-attribute-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'stylesheet',
+ DESC: "stylesheet-unsafe-url-set-attribute (orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+
+ {ACTION: ["generate-link-policy-test-property"],
+ TESTS: [
+ {ATTRIBUTE_POLICY: 'no-referrer',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'stylesheet-unsafe-url-no-referrer-property-with-origin-in-meta',
+ META_POLICY: 'origin',
+ REL: 'stylesheet',
+ DESC: "stylesheet-unsafe-url-property (orginally no-referrer) with origin in meta",
+ RESULT: 'full'},
+ {ATTRIBUTE_POLICY: 'origin',
+ NEW_ATTRIBUTE_POLICY: 'unsafe-url',
+ NAME: 'stylesheet-unsafe-url-origin-property-with-no-referrer-in-meta',
+ META_POLICY: 'no-referrer',
+ REL: 'stylesheet',
+ DESC: "stylesheet-unsafe-url-property (orginally origin) with no-referrer in meta",
+ RESULT: 'full'},
+ ]},
+ ];
+ </script>
+ <script type="application/javascript" src="/tests/dom/base/test/referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_location_href_unknown_protocol.html b/dom/base/test/test_location_href_unknown_protocol.html
new file mode 100644
index 0000000000..15448ff82d
--- /dev/null
+++ b/dom/base/test/test_location_href_unknown_protocol.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for window.location setter to an unknown protocol (bug 1528305).</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+SimpleTest.waitForExplicitFinish();
+let beforeunload = false;
+let unload = false;
+
+window.onChildBeforeUnload = function() {
+ beforeunload = true;
+};
+
+window.onChildUnload = function() {
+ unload = true;
+};
+
+let win;
+window.onChildLoadTimedOut = function() {
+ ok(!unload, "shouldn't have unloaded child window");
+ ok(beforeunload, "should've fired a beforeunload event");
+ win.close();
+ SimpleTest.finish();
+};
+
+win = window.open("file_location_href_unknown_protocol.html");
+</script>
diff --git a/dom/base/test/test_lock_orientation_after_fullscreen.html b/dom/base/test/test_lock_orientation_after_fullscreen.html
new file mode 100644
index 0000000000..bb448f2b8d
--- /dev/null
+++ b/dom/base/test/test_lock_orientation_after_fullscreen.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1757431
+-->
+<head>
+<title>Test for Bug 1757431</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1757431">Mozilla Bug 1757431</a>
+<div id="fullscreen">fullscreen</div>
+<script>
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenorientation.allow-lock", true]]
+ });
+
+ const element = document.getElementById("fullscreen");
+ await SpecialPowers.wrap(element).requestFullscreen();
+ let newOrientationLock = SpecialPowers.getDOMWindowUtils(window).orientationLock;
+
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(newOrientationLock, 0, "Orientation lock in browsing context should be none by enterFullscreen");
+
+ const originalOrientationType = window.screen.orientation.type;
+ const newOrientationType = originalOrientationType.startsWith("landscape") ? "portrait-primary" : "landscape-primary";
+
+ await window.screen.orientation.lock(newOrientationType);
+ newOrientationLock = SpecialPowers.getDOMWindowUtils(window).orientationLock;
+ if (newOrientationType == "portrait-primary") {
+ is(newOrientationLock, 1, "Orientation lock in browsing context should be portrait-primary");
+ } else {
+ is(newOrientationLock, 4, "Orientation lock in browsing context should be landscape-primary");
+ }
+
+ await window.screen.orientation.lock(originalOrientationType);
+ newOrientationLock = SpecialPowers.getDOMWindowUtils(window).orientationLock;
+ if (originalOrientationType == "portrait-primary") {
+ is(newOrientationLock, 1, "Orientation lock in browsing context should be portrait-primary");
+ } else {
+ is(newOrientationLock, 4, "Orientation lock in browsing context should be landscape-primary");
+ }
+
+ await document.exitFullscreen();
+
+ // Wait fullscreen change in ScreenOrientation
+ await new Promise(r =>
+ window.requestAnimationFrame(() => window.requestAnimationFrame(r))
+ );
+
+ newOrientationLock = SpecialPowers.getDOMWindowUtils(window).orientationLock;
+ is(newOrientationLock, 0, "Orientation lock in browsing context should be none by exitFullscreen");
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_lock_orientation_with_pending_fullscreen.html b/dom/base/test/test_lock_orientation_with_pending_fullscreen.html
new file mode 100644
index 0000000000..7df60a7e72
--- /dev/null
+++ b/dom/base/test/test_lock_orientation_with_pending_fullscreen.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1744288
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1744288</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=174488">Mozilla Bug 1744288</a>
+<script>
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+
+add_task(async function test_pending_fullscreen_request() {
+ if (kIsWin) {
+ // CI won't run on Windows tablet mode.
+ ok(true, "Skip on Windows");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenorientation.allow-lock", true]]
+ });
+
+ SpecialPowers.wrap(document.documentElement).requestFullscreen();
+ let gotException = false;
+ try {
+ await window.screen.orientation.lock("any");
+ } catch (e) {
+ gotException = true;
+ }
+ ok(!gotException, "No exception even if fullscreen request is pending.");
+
+ window.screen.orientation.unlock();
+ try {
+ await document.exitFullscreen();
+ } catch (e) {
+ }
+});
+
+// Gecko doesn't allow orientation lock without fullscreen by default
+add_task(async function test_no_fullscreen_request() {
+ if (kIsWin) {
+ // CI won't run on Windows tablet mode.
+ ok(true, "Skip on Windows");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenorientation.allow-lock", true]]
+ });
+
+ let gotException = false;
+ try {
+ await window.screen.orientation.lock("any");
+ } catch (e) {
+ gotException = true;
+ }
+ ok(gotException, "Should throw an exception when fullscreen request is nothing.");
+});
+
+// Gecko doesn't allow orientation lock after fullscreen request is canceled
+add_task(async function test_cancel_pending_fullscreen_request() {
+ if (kIsWin) {
+ // CI won't run on Windows tablet mode.
+ ok(true, "Skip on Windows");
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenorientation.allow-lock", true]]
+ });
+
+ const element = document.createElement("div");
+ document.body.appendChild(element);
+ SpecialPowers.wrap(element).requestFullscreen().then(() => {
+ ok(false, "Fullscreen request should be canceled.");
+ }, () => {
+ ok(true, "Fullscreen request is canceled.");
+ });
+ let gotException = false;
+ try {
+ const promise = window.screen.orientation.lock("any");
+ // Removing element causes that fullscreen request is canceled.
+ document.body.removeChild(element);
+ await promise;
+ } catch (e) {
+ gotException = true;
+ }
+ ok(gotException, "Should throw an exception when pending fullscreen request is canceled.");
+
+ try {
+ window.screen.orientation.unlock();
+ await document.exitFullscreen();
+ } catch (e) {
+ }
+});
+
+add_task(async function test_dont_leak_memory_with_pending_fullscreen_request() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenorientation.allow-lock", true]]
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.setAttribute("allowFullScreen", "");
+ iframe.src = "file_lock_orientation_with_pending_fullscreen.html";
+
+ const promise = new Promise(resolve => {
+ window.addEventListener("message", function handler(e) {
+ if (e.data === "pending") {
+ document.body.removeChild(iframe);
+ window.removeEventListener("message", handler);
+ resolve();
+ }
+ });
+ });
+
+ document.body.appendChild(iframe);
+ await promise;
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_messagePort.html b/dom/base/test/test_messagePort.html
new file mode 100644
index 0000000000..c4fc9d11ce
--- /dev/null
+++ b/dom/base/test/test_messagePort.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=912456
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 912456 - port cloning</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912456">Mozilla Bug 912456</a>
+<script type="application/javascript">
+
+ function testTransfer() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ ok(evt.data.port, "Port has been received!");
+
+ var a1 = new MessageChannel();
+ ok(a1, "MessageChannel created");
+
+ try {
+ evt.data.port.postMessage({port: a1.port2});
+ ok(false, "PostMessage should throw! - no transfered port");
+ } catch(e) {
+ ok(true, "PostMessage should throw! - no transfered port");
+ }
+
+ try {
+ evt.data.port.postMessage({port: a1.port2}, [a1.port2, a1.port2]);
+ ok(false, "PostMessage should throw - no duplicate!");
+ } catch(e) {
+ ok(true, "PostMessage should throw - no duplicate!");
+ }
+
+ evt.data.port.postMessage({port: a1.port2}, [a1.port2]);
+ }
+
+ a.port1.onmessage = function(evt) {
+ ok(evt.data.port, "Port has been received!");
+ window.removeEventListener('message', receiveMessage);
+ runTest();
+ }
+
+ try {
+ postMessage({ port: a.port2}, 42, '*');
+ ok(false, "PostMessage should throw! - no transfered port");
+ } catch(e) {
+ ok(true, "PostMessage should throw! - no transfered port");
+ }
+
+ try {
+ postMessage({ port: a.port2}, 42, '*', [a.port2, a.port2]);
+ ok(false, "PostMessage should throw - no duplicate!");
+ } catch(e) {
+ ok(true, "PostMessage should throw - no duplicate!");
+ }
+
+ postMessage({port: a.port2}, '*', [a.port2]);
+ }
+
+ function testPorts() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ ok(evt.data, "Data is 42");
+ ok(evt.ports, "Port is received");
+ is(evt.ports.length, 1, "Ports.length is 1");
+
+ var a1 = new MessageChannel();
+ ok(a1, "MessageChannel created");
+
+ evt.ports[0].postMessage(42, [a1.port2]);
+ }
+
+ a.port1.onmessage = function(evt) {
+ ok(evt.data, "Data is 42");
+ ok(evt.ports, "Port is received");
+ is(evt.ports.length, 1, "Ports.length is 1");
+ window.removeEventListener('message', receiveMessage);
+ runTest();
+ }
+
+ postMessage(42, '*', [a.port2]);
+ }
+
+ var tests = [
+ testTransfer,
+ testPorts
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_messagemanager_send_principal.html b/dom/base/test/test_messagemanager_send_principal.html
new file mode 100644
index 0000000000..31cc700317
--- /dev/null
+++ b/dom/base/test/test_messagemanager_send_principal.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Principal in MessageManager</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+
+ <script type="application/javascript">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ const childFrameURL =
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
+
+ function childFrameScript() {
+ "use strict";
+
+
+ addMessageListener("test:content", function(message) {
+ sendAsyncMessage("test:result", "is nsIPrincipal: " +
+ (message.data instanceof Ci.nsIPrincipal ? "OK" : "KO"));
+
+ sendAsyncMessage("test:result", "principal.origin: " +
+ ("origin" in message.data ? "OK" : "KO"));
+ });
+
+ addMessageListener("test:system", function(message) {
+ sendAsyncMessage("test:result", "isSystemPrincipal: " +
+ (message.data.isSystemPrincipal ? "OK" : "KO"));
+ });
+
+ addMessageListener("test:ep", function(message) {
+ sendAsyncMessage("test:result", "expanded principal: " +
+ (message.data.isExpandedPrincipal ? "OK" : "KO"));
+ sendAsyncMessage("test:result", "correct origin: " +
+ (message.data.origin == "[Expanded Principal [http://bar.example.com, http://foo.example.com]]" ? "OK" : "KO"));
+ });
+
+ addMessageListener("test:null", function(message) {
+ sendAsyncMessage("test:result", "is nsIPrincipal: " +
+ (message.data instanceof Ci.nsIPrincipal ? "OK" : "KO"));
+
+ sendAsyncMessage("test:result", "isNullPrincipal: " +
+ (message.data.isNullPrincipal ? "OK" : "KO"));
+ sendAsyncMessage("test:result", "DONE");
+ });
+ }
+
+ function runTests() {
+ ok("Browser prefs set.");
+
+ let iframe = document.createXULElement("browser");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("forcemessagemanager", "true");
+ iframe.id = "iframe";
+ iframe.src = childFrameURL;
+
+ let sb = new Cu.Sandbox(['http://foo.example.com', 'http://bar.example.com']);
+ let ep = Cu.getObjectPrincipal(sb);
+
+ iframe.addEventListener("load", function() {
+ ok(true, "Got iframe load event.");
+
+ let mm = iframe.messageManager;
+ mm.addMessageListener("test:result", function(message) {
+ // We need to wrap to access message.json, and unwrap to do the
+ // identity check.
+ var msg = SpecialPowers.unwrap(SpecialPowers.wrap(message).data);
+ if (/OK$/.exec(msg)) {
+ ok(true, msg);
+ } else if(/KO$/.exec(msg)) {
+ ok(true, false);
+ } else if (/DONE/.exec(msg)) {
+ SimpleTest.finish();
+ }
+ });
+ mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
+ false);
+
+ mm.sendAsyncMessage("test:content", window.document.nodePrincipal);
+
+ let system = Services.scriptSecurityManager.getSystemPrincipal();
+ mm.sendAsyncMessage("test:system", system);
+
+ mm.sendAsyncMessage("test:ep", ep);
+
+ let nullP = Services.scriptSecurityManager.createNullPrincipal({});
+ mm.sendAsyncMessage("test:null", nullP);
+ });
+
+ document.body.appendChild(iframe);
+ }
+
+ addEventListener("load", function() {
+ info("Got load event.");
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["browser.pagethumbnails.capturing_disabled", true]
+ ]
+ }, runTests);
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_refresh_referrer.html b/dom/base/test/test_meta_refresh_referrer.html
new file mode 100644
index 0000000000..2af0a1f1f8
--- /dev/null
+++ b/dom/base/test/test_meta_refresh_referrer.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name='referrer' content='origin'>
+ <title>Test for referrer of meta refresh request</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * Listen for notifications from the child.
+ */
+window.addEventListener("message", function(event) {
+ if (event.data == "childLoadComplete") {
+ advance();
+ }
+});
+
+var tests = (function*() {
+ var iframe = document.getElementById("testframe");
+
+ // reset counter to make the test pass --repeat test
+ yield reset();
+
+ // load the test frame
+ yield iframe.src =
+ '/tests/dom/base/test/iframe_meta_refresh.sjs?action=test&load=refresh';
+
+ // check the test result
+ yield checkResults(referrerHeaderChecker);
+
+ // complete.
+ SimpleTest.finish();
+})();
+
+function referrerHeaderChecker(results) {
+ var expected = {'count': 2, 'referrers': ['origin', 'none']};
+ is(results.count, expected.count, "Correct number of referrer header");
+ is(results.referrers[0], expected.referrers[0], "Correct load referrer header");
+ is(results.referrers[1], expected.referrers[1], "Correct refresh referrer header");
+
+ advance();
+}
+
+/**
+ * helper to perform an XHR.
+ */
+function doXHR(url, onSuccess, onFail) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ if (xhr.status == 200) {
+ onSuccess(xhr);
+ } else {
+ onFail(xhr);
+ }
+ };
+ xhr.open('GET', url, true);
+ xhr.send(null);
+}
+
+/**
+ * Grabs the results via XHR and passes to checker.
+ */
+function checkResults(checker) {
+ doXHR('/tests/dom/base/test/iframe_meta_refresh.sjs?action=results',
+ function(xhr) {
+ checker(JSON.parse(xhr.responseText));
+ },
+ function(xhr) {
+ ok(false, "Can't get results from server.");
+ });
+}
+
+/**
+ * Reset the counter.
+ */
+function reset() {
+ doXHR('/tests/dom/base/test/iframe_meta_refresh.sjs?action=reset',
+ advance,
+ function(xhr) {
+ ok(false, "error in reset state");
+ SimpleTest.finish();
+ });
+}
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html></html>
diff --git a/dom/base/test/test_mozMatchesSelector.html b/dom/base/test/test_mozMatchesSelector.html
new file mode 100644
index 0000000000..3f163e07a9
--- /dev/null
+++ b/dom/base/test/test_mozMatchesSelector.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for legacy mozMatchesSelector</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=test></div>
+<script>
+test(function() {
+ var element = document.getElementById("test");
+ assert_true(element.matches("#test"), "matches");
+ assert_true(element.mozMatchesSelector("#test"), "mozMatchesSelector");
+});
+</script>
diff --git a/dom/base/test/test_mutationobservers.html b/dom/base/test/test_mutationobservers.html
new file mode 100644
index 0000000000..80e42713f7
--- /dev/null
+++ b/dom/base/test/test_mutationobservers.html
@@ -0,0 +1,862 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=641821
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 641821</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641821">Mozilla Bug 641821</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 641821 **/
+
+SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly. (But make sure marquee has time to initialize itself.)");
+
+var div = document.createElement("div");
+
+var M;
+if ("MozMutationObserver" in window) {
+ M = window.MozMutationObserver;
+} else if ("WebKitMutationObserver" in window) {
+ M = window.WebKitMutationObserver;
+} else {
+ M = window.MutationObserver;
+}
+
+function log(str) {
+ var d = document.createElement("div");
+ d.textContent = str;
+ if (str.includes("PASSED")) {
+ d.setAttribute("style", "color: green;");
+ } else {
+ d.setAttribute("style", "color: red;");
+ }
+ document.getElementById("log").appendChild(d);
+}
+
+// Some helper functions so that this test runs also outside mochitest.
+if (!("ok" in window)) {
+ window.ok = function(val, str) {
+ log(str + (val ? " PASSED\n" : " FAILED\n"));
+ }
+}
+
+if (!("is" in window)) {
+ window.is = function(val, refVal, str) {
+ log(str + (val == refVal? " PASSED " : " FAILED ") +
+ (val != refVal ? "expected " + refVal + " got " + val + "\n" : "\n"));
+ }
+}
+
+if (!("isnot" in window)) {
+ window.isnot = function(val, refVal, str) {
+ log(str + (val != refVal? " PASSED " : " FAILED ") +
+ (val == refVal ? "Didn't expect " + refVal + "\n" : "\n"));
+ }
+}
+
+if (!("SimpleTest" in window)) {
+ window.SimpleTest =
+ {
+ finish() {
+ document.getElementById("log").appendChild(document.createTextNode("DONE"));
+ },
+ waitForExplicitFinish() {}
+ }
+}
+
+function then(thenFn) {
+ setTimeout(function() {
+ if (thenFn) {
+ setTimeout(thenFn, 0);
+ } else {
+ SimpleTest.finish();
+ }
+ }, 0);
+}
+
+var m;
+var m2;
+var m3;
+var m4;
+
+// Checks normal 'this' handling.
+// Tests also basic attribute handling.
+function runTest() {
+ m = new M(function(records, observer) {
+ is(observer, m, "2nd parameter should be the mutation observer");
+ is(observer, this, "2nd parameter should be 'this'");
+ is(records.length, 1, "Should have one record.");
+ is(records[0].type, "attributes", "Should have got attributes record");
+ is(records[0].target, div, "Should have got div as target");
+ is(records[0].attributeName, "foo", "Should have got record about foo attribute");
+ observer.disconnect();
+ then(testThisBind);
+ m = null;
+ });
+ m.observe(div, { attributes: true, attributeFilter: ["foo"] });
+ div.setAttribute("foo", "bar");
+}
+
+// 'this' handling when fn.bind() is used.
+function testThisBind() {
+ var child = div.appendChild(document.createElement("div"));
+ var gchild = child.appendChild(document.createElement("div"));
+ m = new M((function(records, observer) {
+ is(observer, m, "2nd parameter should be the mutation observer");
+ isnot(observer, this, "2nd parameter should be 'this'");
+ is(records.length, 3, "Should have one record.");
+ is(records[0].type, "attributes", "Should have got attributes record");
+ is(records[0].target, div, "Should have got div as target");
+ is(records[0].attributeName, "foo", "Should have got record about foo attribute");
+ is(records[0].oldValue, "bar", "oldValue should be bar");
+ is(records[1].type, "attributes", "Should have got attributes record");
+ is(records[1].target, div, "Should have got div as target");
+ is(records[1].attributeName, "foo", "Should have got record about foo attribute");
+ is(records[1].oldValue, "bar2", "oldValue should be bar2");
+ is(records[2].type, "attributes", "Should have got attributes record");
+ is(records[2].target, gchild, "Should have got div as target");
+ is(records[2].attributeName, "foo", "Should have got record about foo attribute");
+ is(records[2].oldValue, null, "oldValue should be bar2");
+ observer.disconnect();
+ then(testCharacterData);
+ m = null;
+ }).bind(window));
+ m.observe(div, { attributes: true, attributeOldValue: true, subtree: true });
+ div.setAttribute("foo", "bar2");
+ div.removeAttribute("foo");
+ div.removeChild(child);
+ child.removeChild(gchild);
+ div.appendChild(gchild);
+ div.removeChild(gchild);
+ gchild.setAttribute("foo", "bar");
+}
+
+function testCharacterData() {
+ m = new M(function(records, observer) {
+ is(records[0].type, "characterData", "Should have got characterData");
+ is(records[0].oldValue, null, "Shouldn't have got oldData");
+ observer.disconnect();
+ m = null;
+ });
+ m2 = new M(function(records, observer) {
+ is(records[0].type, "characterData", "Should have got characterData");
+ is(records[0].oldValue, "foo", "Should have got oldData");
+ observer.disconnect();
+ m2 = null;
+ });
+ m3 = new M(function(records, observer) {
+ ok(false, "This should not be called!");
+ observer.disconnect();
+ m3 = null;
+ });
+ m4 = new M(function(records, observer) {
+ is(records[0].oldValue, null, "Shouldn't have got oldData");
+ observer.disconnect();
+ m3.disconnect();
+ m3 = null;
+ then(testChildList);
+ m4 = null;
+ });
+
+ div.appendChild(document.createTextNode("foo"));
+ m.observe(div, { characterData: true, subtree: true });
+ m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true});
+ // If observing the same node twice, only the latter option should apply.
+ m3.observe(div, { characterData: true, subtree: true });
+ m3.observe(div, { characterData: true, subtree: false });
+ m4.observe(div.firstChild, { characterData: true, subtree: false });
+
+ div.firstChild.data = "bar";
+}
+
+function testChildList() {
+ var fc = div.firstChild;
+ m = new M(function(records, observer) {
+ is(records[0].type, "childList", "Should have got childList");
+ is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ is(records[0].removedNodes[0], fc, "Should have removed a text node");
+ observer.disconnect();
+ then(testChildList2);
+ m = null;
+ });
+ m.observe(div, { childList: true});
+ div.firstChild.remove();
+}
+
+function testChildList2() {
+ div.innerHTML = "<span>1</span><span>2</span>";
+ m = new M(function(records, observer) {
+ is(records[0].type, "childList", "Should have got childList");
+ is(records[0].removedNodes.length, 2, "Should have got removedNodes");
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ observer.disconnect();
+ then(testChildList3);
+ m = null;
+ });
+ m.observe(div, { childList: true });
+ div.innerHTML = "<span><span>foo</span></span>";
+}
+
+function testChildList3() {
+ m = new M(function(records, observer) {
+ is(records[0].type, "childList", "Should have got childList");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ observer.disconnect();
+ then(testChildList4);
+ m = null;
+ });
+ m.observe(div, { childList: true });
+ div.textContent = "hello";
+}
+
+function testChildList4() {
+ div.textContent = null;
+ var df = document.createDocumentFragment();
+ var t1 = df.appendChild(document.createTextNode("Hello "));
+ var t2 = df.appendChild(document.createTextNode("world!"));
+ var s1 = div.appendChild(document.createElement("span"));
+ s1.textContent = "foo";
+ var s2 = div.appendChild(document.createElement("span"));
+ function callback(records, observer) {
+ is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div");
+ is(records[0].removedNodes.length, 2, "Should have got removedNodes");
+ is(records[0].removedNodes[0], t1, "Should be the 1st textnode");
+ is(records[0].removedNodes[1], t2, "Should be the 2nd textnode");
+ is(records[1].addedNodes.length, 2, "Should have got addedNodes");
+ is(records[1].addedNodes[0], t1, "Should be the 1st textnode");
+ is(records[1].addedNodes[1], t2, "Should be the 2nd textnode");
+ is(records[1].previousSibling, s1, "Should have previousSibling");
+ is(records[1].nextSibling, s2, "Should have nextSibling");
+ is(records[2].type, "characterData", "3rd record should be characterData");
+ is(records[2].target, t1, "target should be the textnode");
+ is(records[2].oldValue, "Hello ", "oldValue was 'Hello '");
+ observer.disconnect();
+ then(testChildList5);
+ m = null;
+ };
+ m = new M(callback);
+ m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true });
+ m.observe(div, { childList: true });
+
+ // Make sure transient observers aren't leaked.
+ var leakTest = new M(function(){});
+ leakTest.observe(div, { characterData: true, subtree: true });
+
+ div.insertBefore(df, s2);
+ s1.firstChild.data = "bar"; // This should *not* create a record.
+ t1.data = "Hello the whole "; // This should create a record.
+}
+
+function testChildList5() {
+ div.textContent = null;
+ var c1 = div.appendChild(document.createElement("div"));
+ var c2 = document.createElement("div");
+ var div2 = document.createElement("div");
+ var c3 = div2.appendChild(document.createElement("div"));
+ var c4 = document.createElement("div");
+ var c5 = document.createElement("div");
+ var df = document.createDocumentFragment();
+ var emptyDF = document.createDocumentFragment();
+ var dfc1 = df.appendChild(document.createElement("div"));
+ var dfc2 = df.appendChild(document.createElement("div"));
+ var dfc3 = df.appendChild(document.createElement("div"));
+ m = new M(function(records, observer) {
+ is(records.length, 6 , "");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ is(records[0].removedNodes[0], c1, "");
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ is(records[0].addedNodes[0], c2, "");
+ is(records[0].previousSibling, null, "");
+ is(records[0].nextSibling, null, "");
+ is(records[1].removedNodes.length, 1, "Should have got removedNodes");
+ is(records[1].removedNodes[0], c3, "");
+ is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[1].previousSibling, null, "");
+ is(records[1].nextSibling, null, "");
+ is(records[2].removedNodes.length, 1, "Should have got removedNodes");
+ is(records[2].removedNodes[0], c2, "");
+ is(records[2].addedNodes.length, 1, "Should have got addedNodes");
+ is(records[2].addedNodes[0], c3, "");
+ is(records[2].previousSibling, null, "");
+ is(records[2].nextSibling, null, "");
+ // Check document fragment handling
+ is(records[5].removedNodes.length, 1, "");
+ is(records[5].removedNodes[0], c4, "");
+ is(records[5].addedNodes.length, 3, "");
+ is(records[5].addedNodes[0], dfc1, "");
+ is(records[5].addedNodes[1], dfc2, "");
+ is(records[5].addedNodes[2], dfc3, "");
+ is(records[5].previousSibling, c3, "");
+ is(records[5].nextSibling, c5, "");
+ observer.disconnect();
+ then(testNestedMutations);
+ m = null;
+ });
+ m.observe(div, { childList: true, subtree: true });
+ m.observe(div2, { childList: true, subtree: true });
+ div.replaceChild(c2, c1);
+ div.replaceChild(c3, c2);
+ div.appendChild(c4);
+ div.appendChild(c5);
+ div.replaceChild(df, c4);
+ div.appendChild(emptyDF); // empty document shouldn't cause mutation records
+}
+
+function testNestedMutations() {
+ div.textContent = null;
+ div.appendChild(document.createTextNode("foo"));
+ var m2WasCalled = false;
+ m = new M(function(records, observer) {
+ is(records[0].type, "characterData", "Should have got characterData");
+ observer.disconnect();
+ m = null;
+ m3 = new M(function(recordsInner, observerInnder) {
+ ok(m2WasCalled, "m2 should have been called before m3!");
+ is(recordsInner[0].type, "characterData", "Should have got characterData");
+ observerInnder.disconnect();
+ then(testAdoptNode);
+ m3 = null;
+ });
+ m3.observe(div, { characterData: true, subtree: true});
+ div.firstChild.data = "foo";
+ });
+ m2 = new M(function(records, observer) {
+ m2WasCalled = true;
+ is(records[0].type, "characterData", "Should have got characterData");
+ observer.disconnect();
+ m2 = null;
+ });
+ m2.observe(div, { characterData: true, subtree: true});
+ div.appendChild(document.createTextNode("foo"));
+ m.observe(div, { characterData: true, subtree: true });
+
+ div.firstChild.data = "bar";
+}
+
+function testAdoptNode() {
+ var d1 = document.implementation.createHTMLDocument(null);
+ var d2 = document.implementation.createHTMLDocument(null);
+ var addedNode;
+ m = new M(function(records, observer) {
+ is(records.length, 3, "Should have 2 records");
+ is(records[0].target.ownerDocument, d1, "ownerDocument should be the initial document")
+ is(records[1].target.ownerDocument, d2, "ownerDocument should be the new document");
+ is(records[2].type, "attributes", "Should have got attribute mutation")
+ is(records[2].attributeName, "foo", "Should have got foo attribute mutation")
+ observer.disconnect();
+ then(testOuterHTML);
+ m = null;
+ });
+ m.observe(d1, { childList: true, subtree: true, attributes: true });
+ d2.body.appendChild(d1.body);
+ addedNode = d2.body.lastChild.appendChild(d2.createElement("div"));
+ addedNode.setAttribute("foo", "bar");
+}
+
+function testOuterHTML() {
+ var doc = document.implementation.createHTMLDocument(null);
+ var d1 = doc.body.appendChild(document.createElement("div"));
+ var d2 = doc.body.appendChild(document.createElement("div"));
+ var d3 = doc.body.appendChild(document.createElement("div"));
+ var d4 = doc.body.appendChild(document.createElement("div"));
+ m = new M(function(records, observer) {
+ is(records.length, 4, "Should have 1 record");
+ is(records[0].removedNodes.length, 1, "Should have 1 removed nodes");
+ is(records[0].addedNodes.length, 2, "Should have 2 added nodes");
+ is(records[0].previousSibling, null, "");
+ is(records[0].nextSibling, d2, "");
+ is(records[1].removedNodes.length, 1, "Should have 1 removed nodes");
+ is(records[1].addedNodes.length, 2, "Should have 2 added nodes");
+ is(records[1].previousSibling, records[0].addedNodes[1], "");
+ is(records[1].nextSibling, d3, "");
+ is(records[2].removedNodes.length, 1, "Should have 1 removed nodes");
+ is(records[2].addedNodes.length, 2, "Should have 2 added nodes");
+ is(records[2].previousSibling, records[1].addedNodes[1], "");
+ is(records[2].nextSibling, d4, "");
+ is(records[3].removedNodes.length, 1, "Should have 1 removed nodes");
+ is(records[3].addedNodes.length, 0);
+ is(records[3].previousSibling, records[2].addedNodes[1], "");
+ is(records[3].nextSibling, null, "");
+ observer.disconnect();
+ then(testInsertAdjacentHTML);
+ m = null;
+ });
+ m.observe(doc, { childList: true, subtree: true });
+ d1.outerHTML = "<div>1</div><div>1</div>";
+ d2.outerHTML = "<div>2</div><div>2</div>";
+ d3.outerHTML = "<div>3</div><div>3</div>";
+ d4.outerHTML = "";
+}
+
+function testInsertAdjacentHTML() {
+ var doc = document.implementation.createHTMLDocument(null);
+ var d1 = doc.body.appendChild(document.createElement("div"));
+ var d2 = doc.body.appendChild(document.createElement("div"));
+ var d3 = doc.body.appendChild(document.createElement("div"));
+ var d4 = doc.body.appendChild(document.createElement("div"));
+ m = new M(function(records, observer) {
+ is(records.length, 4, "");
+ is(records[0].target, doc.body, "");
+ is(records[0].previousSibling, null, "");
+ is(records[0].nextSibling, d1, "");
+ is(records[1].target, d2, "");
+ is(records[1].previousSibling, null, "");
+ is(records[1].nextSibling, null, "");
+ is(records[2].target, d3, "");
+ is(records[2].previousSibling, null, "");
+ is(records[2].nextSibling, null, "");
+ is(records[3].target, doc.body, "");
+ is(records[3].previousSibling, d4, "");
+ is(records[3].nextSibling, null, "");
+ observer.disconnect();
+ then(testSyncXHR);
+ m = null;
+ });
+ m.observe(doc, { childList: true, subtree: true });
+ d1.insertAdjacentHTML("beforebegin", "<div></div><div></div>");
+ d2.insertAdjacentHTML("afterbegin", "<div></div><div></div>");
+ d3.insertAdjacentHTML("beforeend", "<div></div><div></div>");
+ d4.insertAdjacentHTML("afterend", "<div></div><div></div>");
+}
+
+
+var callbackHandled = false;
+
+function testSyncXHR() {
+ div.textContent = null;
+ m = new M(function(records, observer) {
+ is(records.length, 1, "");
+ is(records[0].addedNodes.length, 1, "");
+ callbackHandled = true;
+ observer.disconnect();
+ m = null;
+ });
+ m.observe(div, { childList: true, subtree: true });
+ div.innerHTML = "<div>hello</div>";
+ var x = new XMLHttpRequest();
+ x.open("GET", window.location, false);
+ x.send();
+ ok(!callbackHandled, "Shouldn't have called the mutation callback!");
+ setTimeout(testSyncXHR2, 0);
+}
+
+function testSyncXHR2() {
+ ok(callbackHandled, "Should have called the mutation callback!");
+ then(testTakeRecords);
+}
+
+function testTakeRecords() {
+ var s = "<span>1</span><span>2</span>";
+ div.innerHTML = s;
+ var takenRecords;
+ m = new M(function(records, observer) {
+ is(records.length, 3, "Should have got 3 records");
+
+ is(records[0].type, "attributes", "Should have got attributes");
+ is(records[0].attributeName, "foo", "");
+ is(records[0].attributeNamespace, null, "");
+ is(records[0].prevValue, null, "");
+ is(records[1].type, "childList", "Should have got childList");
+ is(records[1].removedNodes.length, 2, "Should have got removedNodes");
+ is(records[1].addedNodes.length, 2, "Should have got addedNodes");
+ is(records[2].type, "attributes", "Should have got attributes");
+ is(records[2].attributeName, "foo", "");
+
+ is(records.length, takenRecords.length, "Should have had similar mutations");
+ is(records[0].type, takenRecords[0].type, "Should have had similar mutations");
+ is(records[1].type, takenRecords[1].type, "Should have had similar mutations");
+ is(records[2].type, takenRecords[2].type, "Should have had similar mutations");
+
+ is(records[1].removedNodes.length, takenRecords[1].removedNodes.length, "Should have had similar mutations");
+ is(records[1].addedNodes.length, takenRecords[1].addedNodes.length, "Should have had similar mutations");
+
+ is(m.takeRecords().length, 0, "Shouldn't have any records");
+ observer.disconnect();
+ then(testMutationObserverAndEvents);
+ m = null;
+ });
+ m.observe(div, { childList: true, attributes: true });
+ div.setAttribute("foo", "bar");
+ div.innerHTML = s;
+ div.removeAttribute("foo");
+ takenRecords = m.takeRecords();
+ div.setAttribute("foo", "bar");
+ div.innerHTML = s;
+ div.removeAttribute("foo");
+}
+
+function testTakeRecords() {
+ function mutationListener(e) {
+ ++mutationEventCount;
+ is(e.attrChange, MutationEvent.ADDITION, "unexpected change");
+ }
+
+ m = new M(function(records, observer) {
+ is(records.length, 2, "Should have got 2 records");
+ is(records[0].type, "attributes", "Should have got attributes");
+ is(records[0].attributeName, "foo", "");
+ is(records[0].oldValue, null, "");
+ is(records[1].type, "attributes", "Should have got attributes");
+ is(records[1].attributeName, "foo", "");
+ is(records[1].oldValue, "bar", "");
+ observer.disconnect();
+ div.removeEventListener("DOMAttrModified", mutationListener);
+ then(testExpandos);
+ m = null;
+ });
+ m.observe(div, { attributes: true, attributeOldValue: true });
+ var mutationEventCount = 0;
+ div.addEventListener("DOMAttrModified", mutationListener);
+ div.setAttribute("foo", "bar");
+ div.setAttribute("foo", "bar");
+ is(mutationEventCount, 1, "Should have got only one mutation event!");
+}
+
+function testExpandos() {
+ m2 = new M(function(records, observer) {
+ is(observer.expandoProperty, true);
+ observer.disconnect();
+ then(testOutsideShadowDOM);
+ });
+ m2.expandoProperty = true;
+ m2.observe(div, { attributes: true });
+ m2 = null;
+ if (SpecialPowers) {
+ // Run GC several times to see if the expando property disappears.
+
+ SpecialPowers.gc();
+ SpecialPowers.gc();
+ SpecialPowers.gc();
+ SpecialPowers.gc();
+ }
+ div.setAttribute("foo", "bar2");
+}
+
+function testOutsideShadowDOM() {
+ if (!div.attachShadow) {
+ todo(false, "Skipping testOutsideShadowDOM and testInsideShadowDOM " +
+ "because attachShadow is not supported");
+ then(testMarquee);
+ return;
+ }
+ m = new M(function(records, observer) {
+ is(records.length, 1);
+ is(records[0].type, "attributes", "Should have got attributes");
+ observer.disconnect();
+ then(testMarquee);
+ });
+ m.observe(div, {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ })
+ var sr = div.attachShadow({ mode: "open" });
+ sr.innerHTML = "<div" + ">text</" + "div>";
+ sr.firstChild.setAttribute("foo", "bar");
+ sr.firstChild.firstChild.data = "text2";
+ sr.firstChild.appendChild(document.createElement("div"));
+ div.setAttribute("foo", "bar");
+}
+
+function testMarquee() {
+ m = new M(function(records, observer) {
+ is(records.length, 1);
+ is(records[0].type, "attributes");
+ is(records[0].attributeName, "ok");
+ is(records[0].oldValue, null);
+ observer.disconnect();
+ then(testStyleCreate);
+ });
+ var marquee = document.createElement("marquee");
+ m.observe(marquee, {
+ attributes: true,
+ attributeOldValue: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ });
+ document.body.appendChild(marquee);
+ setTimeout(function() {marquee.setAttribute("ok", "ok")}, 500);
+}
+
+function testStyleCreate() {
+ m = new M(function(records, observer) {
+ is(records.length, 1, "number of records");
+ is(records[0].type, "attributes", "record.type");
+ is(records[0].attributeName, "style", "record.attributeName");
+ is(records[0].oldValue, null, "record.oldValue");
+ isnot(div.getAttribute("style"), null, "style attribute after creation");
+ observer.disconnect();
+ m = null;
+ div.removeAttribute("style");
+ then(testStyleModify);
+ });
+ m.observe(div, { attributes: true, attributeOldValue: true });
+ is(div.getAttribute("style"), null, "style attribute before creation");
+ div.style.color = "blue";
+}
+
+function testStyleModify() {
+ div.style.color = "yellow";
+ m = new M(function(records, observer) {
+ is(records.length, 1, "number of records");
+ is(records[0].type, "attributes", "record.type");
+ is(records[0].attributeName, "style", "record.attributeName");
+ isnot(div.getAttribute("style"), null, "style attribute after modification");
+ observer.disconnect();
+ m = null;
+ div.removeAttribute("style");
+ then(testStyleRead);
+ });
+ m.observe(div, { attributes: true });
+ isnot(div.getAttribute("style"), null, "style attribute before modification");
+ div.style.color = "blue";
+}
+
+function testStyleRead() {
+ m = new M(function(records, observer) {
+ is(records.length, 1, "number of records");
+ is(records[0].type, "attributes", "record.type");
+ is(records[0].attributeName, "data-test", "record.attributeName");
+ is(div.getAttribute("style"), null, "style attribute after read");
+ observer.disconnect();
+ div.removeAttribute("data-test");
+ m = null;
+ then(testStyleRemoveProperty);
+ });
+ m.observe(div, { attributes: true });
+ is(div.getAttribute("style"), null, "style attribute before read");
+ var value = div.style.color; // shouldn't generate any mutation records
+ div.setAttribute("data-test", "a");
+}
+
+function testStyleRemoveProperty() {
+ div.style.color = "blue";
+ m = new M(function(records, observer) {
+ is(records.length, 1, "number of records");
+ is(records[0].type, "attributes", "record.type");
+ is(records[0].attributeName, "style", "record.attributeName");
+ isnot(div.getAttribute("style"), null, "style attribute after successful removeProperty");
+ observer.disconnect();
+ m = null;
+ div.removeAttribute("style");
+ then(testStyleRemoveProperty2);
+ });
+ m.observe(div, { attributes: true });
+ isnot(div.getAttribute("style"), null, "style attribute before successful removeProperty");
+ div.style.removeProperty("color");
+}
+
+function testStyleRemoveProperty2() {
+ m = new M(function(records, observer) {
+ is(records.length, 1, "number of records");
+ is(records[0].type, "attributes", "record.type");
+ is(records[0].attributeName, "data-test", "record.attributeName");
+ is(div.getAttribute("style"), null, "style attribute after unsuccessful removeProperty");
+ observer.disconnect();
+ m = null;
+ div.removeAttribute("data-test");
+ then(testAttributeRecordMerging1);
+ });
+ m.observe(div, { attributes: true });
+ is(div.getAttribute("style"), null, "style attribute before unsuccessful removeProperty");
+ div.style.removeProperty("color"); // shouldn't generate any mutation records
+ div.setAttribute("data-test", "a");
+}
+
+function testAttributeRecordMerging1() {
+ ok(true, "testAttributeRecordMerging1");
+ m = new M(function(records, observer) {
+ is(records.length, 2);
+ is(records[0].type, "attributes");
+ is(records[0].target, div);
+ is(records[0].attributeName, "foo");
+ is(records[0].attributeNamespace, null);
+ is(records[0].oldValue, null);
+
+ is(records[1].type, "attributes");
+ is(records[1].target, div.firstChild);
+ is(records[1].attributeName, "foo");
+ is(records[1].attributeNamespace, null);
+ is(records[1].oldValue, null);
+ observer.disconnect();
+ div.innerHTML = "";
+ div.removeAttribute("foo");
+ then(testAttributeRecordMerging2);
+ });
+ m.observe(div, {
+ attributes: true,
+ subtree: true
+ });
+ SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+ div.setAttribute("foo", "bar_1");
+ div.setAttribute("foo", "bar_2");
+ div.innerHTML = "<div></div>";
+ div.firstChild.setAttribute("foo", "bar_1");
+ div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging2() {
+ ok(true, "testAttributeRecordMerging2");
+ m = new M(function(records, observer) {
+ is(records.length, 2);
+ is(records[0].type, "attributes");
+ is(records[0].target, div);
+ is(records[0].attributeName, "foo");
+ is(records[0].attributeNamespace, null);
+ is(records[0].oldValue, "initial");
+
+ is(records[1].type, "attributes");
+ is(records[1].target, div.firstChild);
+ is(records[1].attributeName, "foo");
+ is(records[1].attributeNamespace, null);
+ is(records[1].oldValue, "initial");
+ observer.disconnect();
+ div.innerHTML = "";
+ div.removeAttribute("foo");
+ then(testAttributeRecordMerging3);
+ });
+
+ div.setAttribute("foo", "initial");
+ div.innerHTML = "<div></div>";
+ div.firstChild.setAttribute("foo", "initial");
+ m.observe(div, {
+ attributes: true,
+ subtree: true,
+ attributeOldValue: true
+ });
+ SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+ div.setAttribute("foo", "bar_1");
+ div.setAttribute("foo", "bar_2");
+ div.firstChild.setAttribute("foo", "bar_1");
+ div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging3() {
+ ok(true, "testAttributeRecordMerging3");
+ m = new M(function(records, observer) {
+ is(records.length, 4);
+ is(records[0].type, "attributes");
+ is(records[0].target, div);
+ is(records[0].attributeName, "foo");
+ is(records[0].attributeNamespace, null);
+ is(records[0].oldValue, "initial");
+
+ is(records[1].type, "attributes");
+ is(records[1].target, div.firstChild);
+ is(records[1].attributeName, "foo");
+ is(records[1].attributeNamespace, null);
+ is(records[1].oldValue, "initial");
+
+ is(records[2].type, "attributes");
+ is(records[2].target, div);
+ is(records[2].attributeName, "foo");
+ is(records[2].attributeNamespace, null);
+ is(records[2].oldValue, "bar_1");
+
+ is(records[3].type, "attributes");
+ is(records[3].target, div.firstChild);
+ is(records[3].attributeName, "foo");
+ is(records[3].attributeNamespace, null);
+ is(records[3].oldValue, "bar_1");
+
+ observer.disconnect();
+ div.innerHTML = "";
+ div.removeAttribute("foo");
+ then(testAttributeRecordMerging4);
+ });
+
+ div.setAttribute("foo", "initial");
+ div.innerHTML = "<div></div>";
+ div.firstChild.setAttribute("foo", "initial");
+ m.observe(div, {
+ attributes: true,
+ subtree: true,
+ attributeOldValue: true
+ });
+ SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+ // No merging should happen.
+ div.setAttribute("foo", "bar_1");
+ div.firstChild.setAttribute("foo", "bar_1");
+ div.setAttribute("foo", "bar_2");
+ div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging4() {
+ ok(true, "testAttributeRecordMerging4");
+ m = new M(function(records, observer) {
+ });
+
+ div.setAttribute("foo", "initial");
+ div.innerHTML = "<div></div>";
+ div.firstChild.setAttribute("foo", "initial");
+ m.observe(div, {
+ attributes: true,
+ subtree: true,
+ attributeOldValue: true
+ });
+ SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+ div.setAttribute("foo", "bar_1");
+ div.setAttribute("foo", "bar_2");
+ div.firstChild.setAttribute("foo", "bar_1");
+ div.firstChild.setAttribute("foo", "bar_2");
+
+ var records = m.takeRecords();
+
+ is(records.length, 2);
+ is(records[0].type, "attributes");
+ is(records[0].target, div);
+ is(records[0].attributeName, "foo");
+ is(records[0].attributeNamespace, null);
+ is(records[0].oldValue, "initial");
+
+ is(records[1].type, "attributes");
+ is(records[1].target, div.firstChild);
+ is(records[1].attributeName, "foo");
+ is(records[1].attributeNamespace, null);
+ is(records[1].oldValue, "initial");
+ m.disconnect();
+ div.innerHTML = "";
+ div.removeAttribute("foo");
+ then(testChromeOnly);
+}
+
+function testChromeOnly() {
+ // Content can't access chromeOnlyNodes
+ try {
+ var mo = new M(function(records, observer) { });
+ mo.observe(div, { chromeOnlyNodes: true });
+ ok(false, "Should have thrown when trying to observe with chrome-only init");
+ } catch (e) {
+ ok(true, "Throws when trying to observe with chrome-only init");
+ }
+
+ then();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_named_frames.html b/dom/base/test/test_named_frames.html
new file mode 100644
index 0000000000..675a36e0cf
--- /dev/null
+++ b/dom/base/test/test_named_frames.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=823227
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 823227</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 823227 **/
+
+ SimpleTest.waitForExplicitFinish();
+ function doTest() {
+ is(frames[0].foo, frames[0][0],
+ "Should be able to look up frames by name in SVG documents");
+ is(frames[1].foo, frames[1][0],
+ "Should be able to look up frames by name in XML documents");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(doTest);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=823227">Mozilla Bug 823227</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject><iframe name='foo' id='foo' xmlns='http://www.w3.org/1999/xhtml'/></foreignObject></svg>"></iframe>
+<iframe src="data:text/xml,<root><iframe name='foo' id='foo' xmlns='http://www.w3.org/1999/xhtml'/></root>"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_navigatorPrefOverride.html b/dom/base/test/test_navigatorPrefOverride.html
new file mode 100644
index 0000000000..478b14e791
--- /dev/null
+++ b/dom/base/test/test_navigatorPrefOverride.html
@@ -0,0 +1,54 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for navigator property override</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ ok(navigator.appName, "This is used just to populate the cache");
+ ok(navigator.appVersion, "This is used just to populate the cache");
+
+ // B2G could have an empty platform.
+ info(navigator.platform);
+
+ ok(navigator.userAgent, "This is used just to populate the cache");
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"],
+ ]},
+ function() {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ var nav = ifr.contentWindow.navigator;
+ isnot(navigator.appName, nav.appName, "appName should match");
+ isnot(navigator.appVersion, nav.appVersion, "appVersion should match");
+ isnot(navigator.platform, nav.platform, "platform should match");
+ isnot(navigator.userAgent, nav.userAgent, "userAgent should match");
+ SimpleTest.finish();
+ });
+
+ document.getElementById('content').appendChild(ifr);
+ }
+ );
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_navigator_cookieEnabled.html b/dom/base/test/test_navigator_cookieEnabled.html
new file mode 100644
index 0000000000..1935e9024c
--- /dev/null
+++ b/dom/base/test/test_navigator_cookieEnabled.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1753586
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Navigator.cookieEnabled</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ "use strict";
+
+ /** Test for Bug 1753586 **/
+
+ const BEHAVIOR_REJECT = 2;
+ const BEHAVIOR_REJECT_TRACKER = 4;
+ const BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN = 5;
+
+ const TESTS = [
+ { cookieBehavior: BEHAVIOR_REJECT, expect: false },
+ { cookieBehavior: BEHAVIOR_REJECT_TRACKER, expect: true },
+ {
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ expect: true,
+ },
+ ];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function createAndWaitIframeLoading(win, src, sandbox) {
+ return new Promise(resolve => {
+ let iframe = win.document.createElement("iframe");
+
+ iframe.addEventListener(
+ "load",
+ () => resolve(iframe),
+ { once: true }
+ );
+
+ win.document.body.appendChild(iframe);
+ if (sandbox) {
+ iframe.sandbox = sandbox;
+ }
+ iframe.src = src;
+ });
+ }
+
+ async function runTestInAnotherWindow(expect) {
+ let win = window.open();
+
+ is(
+ win.navigator.cookieEnabled,
+ expect,
+ `Navigator.cookieEnabled returns expected value in first-party context.`
+ );
+
+ // Create a first-party iframe and check if navigator.cookieEnabled is
+ // expected.
+ let iframe = await createAndWaitIframeLoading(
+ win,
+ "http://mochi.test:8888/"
+ );
+
+ await SpecialPowers.spawn(iframe, [expect], value => {
+ is(
+ content.navigator.cookieEnabled,
+ value,
+ "Navigator.cookieEnabled returns expected value in first-party iframe."
+ );
+ });
+
+ // Create a third-party iframe and check if navigator.cookieEnabled is
+ // expected.
+ iframe = await createAndWaitIframeLoading(
+ win,
+ "http://example.com/"
+ );
+
+ await SpecialPowers.spawn(iframe, [expect], value => {
+ is(
+ content.navigator.cookieEnabled,
+ value,
+ "Navigator.cookieEnabled returns expected value in third-party iframe."
+ );
+ });
+
+ // Create a sandboxed iframe and check if navigator.cookieEnabled is
+ // expected. It should still return true even if setting cookies in
+ // sandboxed iframe will throw a security error.
+ iframe = await createAndWaitIframeLoading(
+ win,
+ "http://mochi.test:8888/",
+ "allow-scripts"
+ );
+
+ await SpecialPowers.spawn(iframe, [expect], value => {
+ is(
+ content.navigator.cookieEnabled,
+ value,
+ "Navigator.cookieEnabled returns expected value in sandboxed iframe."
+ );
+ });
+
+ win.close();
+ }
+
+ add_task(async function doTests() {
+ for (let test of TESTS) {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", test.cookieBehavior],
+ ]});
+
+ // We need to run the test in another window so that the cookieBehavior
+ // setting would take effect.
+ await runTestInAnotherWindow(test.expect);
+ }
+ });
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/base/test/test_navigator_hardwareConcurrency.html b/dom/base/test/test_navigator_hardwareConcurrency.html
new file mode 100644
index 0000000000..9b201d4f31
--- /dev/null
+++ b/dom/base/test/test_navigator_hardwareConcurrency.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Navigator.hardwareConcurrency</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ "use strict";
+
+ function resistFingerprinting(value) {
+ return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+ }
+
+ var x = navigator.hardwareConcurrency;
+ is(typeof x, "number", "hardwareConcurrency should be a number.");
+ ok(x > 0, "hardwareConcurrency should be greater than 0.");
+
+ SimpleTest.waitForExplicitFinish();
+
+ resistFingerprinting(true).then(() => {
+ const y = navigator.hardwareConcurrency;
+ ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+ resistFingerprinting(false).then(() => {
+ const z = navigator.hardwareConcurrency;
+ ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+ SimpleTest.finish();
+ });
+ });
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_navigator_language.html b/dom/base/test/test_navigator_language.html
new file mode 100644
index 0000000000..4fc7e47865
--- /dev/null
+++ b/dom/base/test/test_navigator_language.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=889335
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for NavigatorLanguage</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=889335">Mozilla Bug 889335</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ /** Test for NavigatorLanguage **/
+ var actualLanguageChangesFromHandler = 0;
+ var actualLanguageChangesFromAVL = 0;
+ var expectedLanguageChanges = 0;
+ var emptyLocale = SpecialPowers.Services.locale.webExposedLocales[0];
+
+ var testValues = [
+ { accept_languages: 'foo', language: 'foo', languages: ['foo'] },
+ { accept_languages: '', language: emptyLocale, languages: [emptyLocale] },
+ { accept_languages: 'foo,bar', language: 'foo', languages: [ 'foo', 'bar' ] },
+ { accept_languages: ' foo , bar ', language: 'foo', languages: [ 'foo', 'bar' ] },
+ { accept_languages: ' foo ; bar ', language: 'foo ; bar', languages: [ 'foo ; bar' ] },
+ { accept_languages: '_foo_', language: '_foo_', languages: ['_foo_'] },
+ { accept_languages: 'en_', language: 'en-', languages: ['en-'] },
+ { accept_languages: 'en__', language: 'en-_', languages: ['en-_'] },
+ { accept_languages: 'en_US, fr_FR', language: 'en-US', languages: ['en-US', 'fr-FR'] },
+ { accept_languages: 'en_US_CA', language: 'en-US_CA', languages: ['en-US_CA'] },
+ { accept_languages: 'en_us-ca', language: 'en-US-CA', languages: ['en-US-CA'] },
+ { accept_languages: 'en_us-cal, en_us-c', language: 'en-US-cal', languages: ['en-US-cal', 'en-US-c'] },
+ ];
+
+ var currentTestIdx = 0;
+ var tests = [];
+ function nextTest() {
+ currentTestIdx++;
+ if (currentTestIdx >= tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ tests[currentTestIdx]();
+ }
+
+ // Check that the API is there.
+ tests.push(function testAPIPresence() {
+ ok('language' in window.navigator);
+ ok('languages' in window.navigator);
+ ok('onlanguagechange' in window);
+
+ nextTest();
+ });
+
+ // Check that calling navigator.languages return the same array, unless there
+ // was a change.
+ tests.push(function testArrayCached() {
+ var previous = navigator.languages;
+ is(navigator.languages, navigator.languages, "navigator.languages is cached");
+ is(navigator.languages, previous, "navigator.languages is cached");
+
+ window.onlanguagechange = function() {
+ isnot(navigator.languages, previous, "navigator.languages cached value was updated");
+ window.onlanguagechange = null;
+
+ nextTest();
+ }
+
+ setTimeout(function() {
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'testArrayCached']]});
+ }, 0);
+ });
+
+ // Test that event handler inside the <body> works as expected and that the
+ // event has the expected properties.
+ tests.push(function testEventProperties() {
+ document.body.setAttribute('onlanguagechange',
+ "document.body.removeAttribute('onlanguagechange');" +
+ "is(event.cancelable, false); is(event.bubbles, false);" +
+ "nextTest();");
+
+ setTimeout(function() {
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'testEventProperties']]}, function() {});
+ }, 0);
+ });
+
+ // Check that the returned values such as the behavior when the underlying
+ // languages change.
+ tests.push(function testBasicBehaviour() {
+ function checkIfDoneAndProceed() {
+ if (actualLanguageChangesFromHandler == actualLanguageChangesFromAVL) {
+ if (genEvents.next().done) {
+ window.onlanguagechange = null;
+ window.removeEventListener('languagechange', languageChangeAVL);
+ nextTest();
+ }
+ }
+ }
+ window.onlanguagechange = function() {
+ actualLanguageChangesFromHandler++;
+ checkIfDoneAndProceed();
+ }
+ function languageChangeAVL() {
+ actualLanguageChangesFromAVL++;
+ checkIfDoneAndProceed();
+ }
+ window.addEventListener('languagechange', languageChangeAVL);
+
+ function* testEvents() {
+ for (var i = 0; i < testValues.length; ++i) {
+ var data = testValues[i];
+ setTimeout(function(d) {
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', d.accept_languages]]});
+ }, 0, data);
+ expectedLanguageChanges++;
+ yield undefined;
+
+ is(actualLanguageChangesFromAVL, expectedLanguageChanges);
+ is(actualLanguageChangesFromHandler, expectedLanguageChanges);
+
+ is(navigator.language, data.language);
+ is(navigator.languages.length, data.languages.length);
+ if (navigator.languages.length) {
+ is(navigator.languages[0], navigator.language)
+ }
+ for (var j = 0; j < navigator.languages.length; ++j) {
+ is(navigator.languages[j], data.languages[j]);
+ }
+ }
+ }
+
+ var genEvents = testEvents();
+ genEvents.next();
+ });
+
+ // Check that the languagechange event isn't sent twice if the preference
+ // is set to the same value.
+ tests.push(function testOnlyFireIfRealChange() {
+ function* changeLanguage() {
+ setTimeout(function() {
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'fr-CA']]});
+ });
+ yield undefined;
+
+ setTimeout(function() {
+ // Twice the same change, should fire only one event.
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'fr-CA']]});
+ setTimeout(function() {
+ // A real change to tell the test it should now count how many changes were
+ // received until now.
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'fr-FR']]});
+ });
+ });
+ yield undefined;
+ }
+
+ var genChanges = changeLanguage();
+
+ var doubleEventCount = 0;
+ window.onlanguagechange = function() {
+ if (navigator.language == 'fr-FR') {
+ is(1, doubleEventCount);
+ window.onlanguagechange = null;
+ nextTest();
+ return;
+ }
+
+ if (navigator.language == 'fr-CA') {
+ doubleEventCount++;
+ }
+ genChanges.next();
+ }
+
+ genChanges.next();
+ });
+
+ // Check that there is no crash when a change happen after a window listening
+ // to them is killed.
+ tests.push(function testThatAddingAnEventDoesNotHaveSideEffects() {
+ var frame = document.createElement('iframe');
+ frame.srcdoc = '<script>window.onlanguagechange=function(){}<\/script>';
+ document.body.appendChild(frame);
+
+ frame.contentWindow.onload = function() {
+ document.body.removeChild(frame);
+ frame = null;
+
+ SpecialPowers.exactGC(function() {
+ // This should not crash.
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'en-GB']]}, nextTest);
+ });
+ }
+ });
+
+ // There is one test using document.body.
+ addLoadEvent(function() {
+ tests[0]();
+ });
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_navigator_resolve_identity_xrays.xhtml b/dom/base/test/test_navigator_resolve_identity_xrays.xhtml
new file mode 100644
index 0000000000..b4e0632bac
--- /dev/null
+++ b/dom/base/test/test_navigator_resolve_identity_xrays.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=985827
+-->
+<window title="Mozilla Bug 985827"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=985827"
+ target="_blank">Mozilla Bug 985827</a>
+ <iframe id="t"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 985827 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var iframe = document.getElementById("t");
+
+ var dir = "chrome://mochitests/content/chrome/dom/base/test/";
+ iframe.src = dir + "file_navigator_resolve_identity_xrays.xhtml";
+ iframe.onload = function() { finish(); };
+
+ function finish() {
+ SimpleTest.finish();
+ }
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/test_nested_event_loop_spin_and_idle_tasks.html b/dom/base/test/test_nested_event_loop_spin_and_idle_tasks.html
new file mode 100644
index 0000000000..940e22527c
--- /dev/null
+++ b/dom/base/test/test_nested_event_loop_spin_and_idle_tasks.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Nested event loop spinning and idle task handling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ if (!location.search.includes("newpage")) {
+ SimpleTest.waitForExplicitFinish();
+ var win = window.open(this.location + "?newpage");
+ win.onload = function() {
+ var xhr = new XMLHttpRequest();
+ // Spin the event loop using a synchronous XMLHttpRequest.
+ xhr.open("GET", "slow.sjs", false);
+ xhr.send();
+ ok(win.didRunIdleCallback, "Should have run an idle callback.");
+ win.close();
+ SimpleTest.finish();
+ }
+ } else {
+ var didRunIdleCallback = false;
+ requestIdleCallback(function() {
+ didRunIdleCallback = true;
+ });
+ }
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_nodelist_holes.html b/dom/base/test/test_nodelist_holes.html
new file mode 100644
index 0000000000..0f69776d0b
--- /dev/null
+++ b/dom/base/test/test_nodelist_holes.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=699826
+-->
+<head>
+ <title>Test for Bug 699826</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=699826">Mozilla Bug 699826</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 699826 **/
+HTMLCollection.prototype[0] = "PASSProto0";
+HTMLCollection.prototype[1] = "PASSProto1";
+var list = document.getElementsByTagName("testtag");
+is(list[0], "PASSProto0", "Should expose proto properties on the list");
+is(list[1], "PASSProto1", "Should expose more proto properties on the list");
+
+var testtag = document.createElement("testtag");
+
+document.body.appendChild(testtag);
+
+is(list[0], testtag, "Should expose elements in the list");
+is(list[1], "PASSProto1", "Should expose proto properties out of range on the list");
+
+testtag.remove();
+
+is(list[0], "PASSProto0", "Should expose proto properties on the list after removal");
+is(list[1], "PASSProto1", "Should expose more proto properties on the list after removal");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_openDialogChromeOnly.html b/dom/base/test/test_openDialogChromeOnly.html
new file mode 100644
index 0000000000..7902554e2b
--- /dev/null
+++ b/dom/base/test/test_openDialogChromeOnly.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=931768
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 931768</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 931768 **/
+
+ try {
+ openDialog(AppConstants.BROWSER_CHROME_URL);
+ ok(false, "Calling openDialog from unprivileged script should throw.");
+ } catch (e) {
+ ok(e instanceof ReferenceError,
+ "openDialog shouldn't be available to unprivileged script.");
+ }
+</script>
+</body>
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=931768">Mozilla Bug 931768</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_open_null_features.html b/dom/base/test/test_open_null_features.html
new file mode 100644
index 0000000000..b7f3fb5008
--- /dev/null
+++ b/dom/base/test/test_open_null_features.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1009529
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1009529</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1009529 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var win1 = open("about:blank", "_blank", null);
+ var win2 = open("about:blank", "_blank", "");
+ for (var k in win1) {
+ var v;
+ try {
+ v = win1[k];
+ } catch (ex) {}
+ if (v instanceof win1.BarProp) {
+ is(v.visible, win2[k] && win2[k].visible, "Both windows should have the same value for " + k);
+ }
+ }
+
+ var closeCount = 0;
+ var closeInc = function(e) {
+ this.removeEventListener("unload", closeInc, true);
+ closeCount++;
+ if (closeCount == 2) {
+ SimpleTest.finish();
+ }
+ };
+ win1.addEventListener("unload", closeInc, true);
+ win2.addEventListener("unload", closeInc, true);
+ win1.close();
+ win2.close();
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1009529">Mozilla Bug 1009529</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_pasting_svg_image.html b/dom/base/test/test_pasting_svg_image.html
new file mode 100644
index 0000000000..be22f92f3a
--- /dev/null
+++ b/dom/base/test/test_pasting_svg_image.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test pasting &lt;svg&gt;&lt;image href&gt;</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ const kPasteTargetId = "pasteTarget";
+ const kTestContentId = "testContent";
+
+ function selectSVG() {
+ const testContent = document.getElementById(kTestContentId);
+ document.getSelection().selectAllChildren(testContent);
+ }
+
+ async function copyToClipboard() {
+ function validatorFn(aData) {
+ const testContent = document.getElementById(kTestContentId);
+
+ let expectedData = testContent.outerHTML;
+ if (navigator.platform.includes(kPlatformWindows)) {
+ expectedData = kTextHtmlPrefixClipboardDataWindows +
+ expectedData + kTextHtmlSuffixClipboardDataWindows;
+ }
+
+ return SimpleTest.stripLinebreaksAndWhitespaceAfterTags(aData) ==
+ SimpleTest.stripLinebreaksAndWhitespaceAfterTags(expectedData);
+ }
+
+ function setupFn() {
+ synthesizeKey("c", { accelKey: true } /* aEvent */);
+ }
+
+ const flavor = "text/html";
+
+ await SimpleTest.promiseClipboardChange(validatorFn, setupFn, flavor);
+ }
+
+ async function pasteTo(aTargetElement) {
+ document.getSelection().selectAllChildren(aTargetElement);
+
+ const promiseInputEvent = new Promise(resolve => {
+ document.addEventListener("input", resolve,
+ { once: true } /* options */);
+ synthesizeKey("v", { accelKey: true } /* aEvent */);
+ });
+
+ await promiseInputEvent;
+ }
+
+ async function runTest() {
+ selectSVG();
+ await copyToClipboard();
+
+ // Get the test-content before pasting, because pasting will duplicate
+ // ids.
+ const expectedPastedInnerHTML =
+ SimpleTest.stripLinebreaksAndWhitespaceAfterTags(
+ document.getElementById(kTestContentId).outerHTML);
+
+ const pasteTargetElement = document.getElementById(kPasteTargetId);
+ await pasteTo(pasteTargetElement);
+
+ const pastedInnerHTMLWithoutLinebreaksAndWhitespaceAfterTags =
+ SimpleTest.stripLinebreaksAndWhitespaceAfterTags(
+ pasteTargetElement.innerHTML);
+
+ is(pastedInnerHTMLWithoutLinebreaksAndWhitespaceAfterTags,
+ expectedPastedInnerHTML,
+ "Pasted HTML is as expected.");
+
+ SimpleTest.finish()
+ }
+
+ function onLoad() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(runTest);
+ };
+ </script>
+</head>
+<body onload="onLoad()">
+ <h6>
+ Test for <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1669050">bug 1669050</a>
+ </h6>
+<div id="testContent">
+ foo
+ <svg>
+ <image
+ href="http://mochi.test:8888/tests/dom/base/test/name_of_some_image_file.png"
+ height="200" width="200">
+ </image>
+ </svg>
+ bar
+</div>
+<div contenteditable id="pasteTarget"/>
+</body>
+</html>
diff --git a/dom/base/test/test_pdf_print.html b/dom/base/test/test_pdf_print.html
new file mode 100644
index 0000000000..6e73ae2a09
--- /dev/null
+++ b/dom/base/test/test_pdf_print.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title><!-- TODO: insert title here --></title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ const blob = new Blob(["x"], { type: "application/pdf" });
+ const blobURL = URL.createObjectURL(blob);
+ const blobFrame = document.createElement("iframe");
+ blobFrame.src = blobURL;
+ document.getElementById("content").appendChild(blobFrame);
+
+ const dataURL = "data:application/pdf,";
+ const dataFrame = document.createElement("iframe");
+ dataFrame.src = dataURL;
+ document.getElementById("content").appendChild(dataFrame);
+
+ addLoadEvent(function() {
+ // blob:// URLs inherit their origin, so the window inside blobFrame
+ // should be same-orgin with us except for the PDF viewer messing with
+ // origins.
+ const printFunc = blobFrame.contentWindow.print;
+ is(typeof printFunc, "function", "Should have a 'print' function");
+ ok(Object.getOwnPropertyNames(blobFrame.contentWindow).includes("print"),
+ "Should see 'print' property in property names");
+
+ try {
+ // data: URLs get nonce origins, so the window inside dataFrame is not
+ // same-origin with us in any way.
+ dataFrame.contentWindow.print;
+ ok(false, "Should throw on cross-origin .print access");
+ } catch (e) {
+ ok(/Permission denied/.test(e.message), "Should have a security error");
+ }
+ ok(!Object.getOwnPropertyNames(dataFrame.contentWindow).includes("print"),
+ "Should not see 'print' property in property names");
+
+ try {
+ printFunc.call(dataFrame.contentWindow);
+ ok(false, "Should throw on cross-origin call");
+ } catch (e) {
+ ok(/Permission to call/.test(e.message),
+ "Should have a security error for call");
+ }
+
+ // It'd be nice to test that calling the function works right, but if it
+ // does it'll put up the print dialog, which is not helpful in an
+ // automated test.
+ SimpleTest.finish();
+ });
+ </script>
+</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_plugin_freezing.html b/dom/base/test/test_plugin_freezing.html
new file mode 100644
index 0000000000..99174aa9bb
--- /dev/null
+++ b/dom/base/test/test_plugin_freezing.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for plugin freezing and thawing</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content" style="display: none">
+</div>
+<embed id='e1' type='application/x-test'></embed>
+<script>
+var e1 = document.getElementById('e1');
+var w;
+
+var testIndex = 0;
+var tests;
+
+window.addEventListener("unload", function() {
+ e1.stopWatchingInstanceCount();
+});
+
+function nextTest() {
+ if (testIndex == tests.length) {
+ if (w) {
+ w.close();
+ }
+ SimpleTest.waitForFocus(function() {
+ SimpleTest.finish();
+ });
+ return;
+ }
+
+ var test = tests[testIndex];
+ ++testIndex;
+ test();
+}
+
+function waitForInstanceCount(n) {
+ if (e1.getInstanceCount() == n) {
+ ok(true, "reached instance count " + n);
+ nextTest();
+ return;
+ }
+ setTimeout(function() { waitForInstanceCount(n); }, 0);
+}
+
+tests = [
+ function() { waitForInstanceCount(1); },
+ function() { w.location.href = "about:blank";
+ waitForInstanceCount(0); },
+];
+
+try {
+ e1.startWatchingInstanceCount();
+ var w = window.open("data:text/html,<embed id='e2' type='application/x-test'></embed>");
+ SimpleTest.waitForFocus(nextTest, w);
+ SimpleTest.waitForExplicitFinish();
+} catch (err) {
+ todo(false, "Instances already being watched?");
+}
+
+</script>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_postMessage_originAttributes.html b/dom/base/test/test_postMessage_originAttributes.html
new file mode 100644
index 0000000000..7dab671ec1
--- /dev/null
+++ b/dom/base/test/test_postMessage_originAttributes.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test window.postMessages from system principal to window with non-default originAttributes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<iframe id="target-iframe"></iframe>
+<script type="application/javascript">
+
+add_task(async function() {
+ let iframe = document.querySelector("#target-iframe");
+
+ let win = SpecialPowers.wrap(iframe).contentWindow;
+ let docShell = win.docShell;
+
+ // Add private browsing ID to docShell origin and load document.
+ docShell.setOriginAttributes({privateBrowsingId: 1});
+
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, true);
+
+ iframe.src = SimpleTest.getTestFileURL("file_receiveMessage.html");
+ });
+
+ // Set up console monitor to wait for warning.
+ const expectedMessage = (
+ 'Attempting to post a message to window with url ' +
+ '"http://mochi.test:8888/tests/dom/base/test/file_receiveMessage.html" ' +
+ 'and origin "http://mochi.test:8888^privateBrowsingId=1" from a system ' +
+ 'principal scope with mismatched origin "[System Principal]".');
+
+ let consolePromise = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [e => e.message == expectedMessage]);
+ });
+
+ // Post to the content window via SpecialPowers' system principal scope.
+ win.postMessage("Hello. o/", "http://mochi.test:8888");
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ SimpleTest.endMonitorConsole();
+ await consolePromise;
+
+ // Check that the window received and handled the message.
+ is(win.document.body.textContent, "|Hello. o/",
+ "Content window received the expected message");
+});
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_postMessage_solidus.html b/dom/base/test/test_postMessage_solidus.html
new file mode 100644
index 0000000000..42124e35ef
--- /dev/null
+++ b/dom/base/test/test_postMessage_solidus.html
@@ -0,0 +1,93 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949488
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 949488 - basic support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949488">Mozilla Bug 949488</a>
+ <div id="content"></div>
+ <script type="application/javascript">
+
+ function selfMessage() {
+ addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ is(evt.data, 1, "Message received");
+ removeEventListener('message', receiveMessage);
+ runTest();
+ }
+
+ postMessage(1, '/');
+ }
+
+ function frameOk() {
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_postMessage_solidus.html");
+
+ var div = document.getElementById("content");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ is(evt.data, 2, "Message received");
+ removeEventListener('message', receiveMessage);
+ runTest();
+ }
+
+ ifr.contentWindow.postMessage(2, '/');
+ }
+ }
+
+ function frameWrong() {
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "http://www.example.com/tests/dom/base/test/iframe_postMessage_solidus.html");
+
+ var div = document.getElementById("content");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ is(evt.data, 3, "Message received");
+ removeEventListener('message', receiveMessage);
+ runTest();
+ }
+
+ ifr.contentWindow.postMessage(4, '/');
+ SimpleTest.executeSoon(function() {
+ ifr.contentWindow.postMessage(3, '*');
+ });
+ }
+ }
+
+ var tests = [
+ selfMessage,
+ frameOk,
+ frameWrong
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_postMessages_broadcastChannel.html b/dom/base/test/test_postMessages_broadcastChannel.html
new file mode 100644
index 0000000000..a3864d0710
--- /dev/null
+++ b/dom/base/test/test_postMessages_broadcastChannel.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for postMessages cloning/transferring objects</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="common_postMessages.js"></script>
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript">
+
+// PostMessage for BroadcastChannel
+tests.push(function test_broadcastChannel() {
+ info("Testing broadcastChannel");
+
+ var bc1 = new BroadcastChannel('postMessagesTest');
+ var bc2 = new BroadcastChannel('postMessagesTest');
+
+ var resolve;
+
+ function resolvePromise(data) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp(data);
+ }
+
+ bc2.onmessage = function(e) {
+ resolvePromise({ data: e.data, ports: [], error: false });
+ }
+
+ bc2.onmessageerror = function(e) {
+ resolvePromise({ error: true });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: false,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ if (ports.length) {
+ rr();
+ return;
+ }
+
+ resolve = r;
+ bc1.postMessage(what);
+ });
+ },
+
+ finished() {
+ bc1.close();
+ bc2.close();
+ next();
+ }
+ });
+});
+
+// PostMessage for BroadcastChannel in workers
+tests.push(function test_broadcastChannel_inWorkers() {
+ info("Testing broadcastChannel in Workers");
+
+ var bc = new BroadcastChannel('postMessagesTest_inWorkers');
+ var resolve;
+
+ var w = new Worker('worker_postMessages.js');
+ w.postMessage('broadcastChannel');
+ w.onmessage = function(e) {
+ is(e.data, 'ok', "Worker ready!");
+
+ w.onmessage = function(e1) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e1.data, ports: e1.ports, error: e1.data === "error" });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: false,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ if (ports.length) {
+ rr();
+ return;
+ }
+
+ resolve = r;
+ bc.postMessage(what);
+ });
+ },
+
+ finished() {
+ bc.close();
+ w.terminate();
+ next();
+ }
+ });
+ }
+});
+
+// PostMessage for BroadcastChannel in SharedWorkers
+tests.push(function test_broadcastChannel_inSharedWorkers() {
+ info("Testing broadcastChannel in SharedWorkers");
+
+ var bc = new BroadcastChannel('postMessagesTest_inWorkers');
+ var resolve;
+
+ var w = new SharedWorker('worker_postMessages.js');
+ w.port.postMessage('broadcastChannel');
+ w.port.onmessage = function(e) {
+ is(e.data, 'ok', "Worker ready!");
+
+ w.port.onmessage = function(e1) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e1.data, ports: e1.ports, error: e1.data === "error" });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: false,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ if (ports.length) {
+ rr();
+ return;
+ }
+
+ resolve = r;
+ bc.postMessage(what);
+ });
+ },
+
+ finished() {
+ bc.close();
+ w.port.postMessage("terminate");
+ next();
+ }
+ });
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_postMessages_messagePort.html b/dom/base/test/test_postMessages_messagePort.html
new file mode 100644
index 0000000000..5797c23c5c
--- /dev/null
+++ b/dom/base/test/test_postMessages_messagePort.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for postMessages cloning/transferring objects</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="common_postMessages.js"></script>
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript">
+
+// PostMessage for MessagePort
+tests.push(function test_messagePort() {
+ info("Testing messagePort");
+
+ var mc = new MessageChannel();
+ var resolve;
+
+ function resolvePromise(data) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp(data);
+ }
+
+ mc.port2.onmessage = function(e) {
+ resolvePromise({ data: e.data, ports: e.ports, error: false });
+ }
+
+ mc.port2.onmessageerror = function(e) {
+ resolvePromise({ error: true });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ mc.port1.postMessage(what, ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ next();
+ }
+ });
+});
+
+// PostMessage for MessagePort in Workers
+tests.push(function test_messagePort_inWorkers() {
+ info("Testing messagePort in workers");
+
+ var mc = new MessageChannel();
+ var resolve;
+
+ var w = new Worker('worker_postMessages.js');
+ w.postMessage('messagePort', [ mc.port2 ]);
+ w.onmessage = function(e) {
+ is(e.data, 'ok', "Worker ready!");
+
+ w.onmessage = function(e1) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e1.data, ports: e1.ports, error: e1.data === "error" });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ mc.port1.postMessage(what, ports);
+ } catch(ex) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ w.terminate();
+ next();
+ }
+ });
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_postMessages_window.html b/dom/base/test/test_postMessages_window.html
new file mode 100644
index 0000000000..12b757867b
--- /dev/null
+++ b/dom/base/test/test_postMessages_window.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for postMessages cloning/transferring objects</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="common_postMessages.js"></script>
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript">
+
+// PostMessage to the same window.
+tests.push(function test_windowToWindow() {
+ info("Testing window to window");
+ var resolve;
+
+ function resolvePromise(data) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp(data);
+ }
+
+ onmessage = function(e) {
+ resolvePromise({ data: e.data, ports: e.ports, error: false });
+ }
+
+ onmessageerror = function(e) {
+ resolvePromise({ error: true });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+
+ try {
+ postMessage(what, '*', ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ onmessage = null;
+ onmessageerror = null;
+ next();
+ }
+ });
+});
+
+// iframe helper class
+function test_windowToIframeURL(url, clonableObjectsSameProcess) {
+ var resolve;
+
+ onmessage = function(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports, error: e.data === "error" });
+ }
+
+ var ifr = document.createElement('iframe');
+ ifr.src = url;
+ ifr.onload = function() {
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ ifr.contentWindow.postMessage(what, '*', ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ document.body.removeChild(ifr);
+ onmessage = null;
+ next();
+ }
+ });
+ }
+ document.body.appendChild(ifr);
+}
+
+// PostMessage to iframe
+tests.push(function test_windowToIframe() {
+ info("Testing window to iframe");
+ test_windowToIframeURL('iframe_postMessages.html', true);
+});
+
+// PostMessage to cross-origin iframe
+tests.push(function test_windowToCrossOriginIframe() {
+ info("Testing window to cross-origin iframe");
+ test_windowToIframeURL('http://example.com/tests/dom/base/test/iframe_postMessages.html', false);
+});
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_postMessages_workers.html b/dom/base/test/test_postMessages_workers.html
new file mode 100644
index 0000000000..994c0201e6
--- /dev/null
+++ b/dom/base/test/test_postMessages_workers.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for postMessages cloning/transferring objects</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="common_postMessages.js"></script>
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript">
+
+// PostMessage for Workers
+tests.push(function test_workers() {
+ info("Testing Workers");
+
+ var resolve;
+
+ var w = new Worker('worker_postMessages.js');
+ w.postMessage('workers');
+ w.onmessage = function(e) {
+ is(e.data, 'ok', "Worker ready!");
+
+ w.onmessage = function(e1) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e1.data, ports: e1.ports, error: e1.data === "error" });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ w.postMessage(what, ports);
+ } catch(ex) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ w.terminate();
+ next();
+ }
+ });
+ }
+});
+
+// PostMessage for SharedWorkers
+tests.push(function test_sharedWorkers() {
+ info("Testing SharedWorkers");
+
+ var resolve;
+
+ var w = new SharedWorker('worker_postMessages.js');
+ w.port.postMessage('sharedworkers');
+ w.port.onmessage = function(e) {
+ is(e.data, 'ok', "Worker ready!");
+
+ w.port.onmessage = function(e1) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e1.data, ports: e1.ports, error: e1.data === "error" });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: true,
+ send(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ w.port.postMessage(what, ports);
+ } catch(ex) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished() {
+ w.port.postMessage("terminate");
+ next();
+ }
+ });
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_processing_instruction_update_stylesheet.xhtml b/dom/base/test/test_processing_instruction_update_stylesheet.xhtml
new file mode 100644
index 0000000000..85aeea25a2
--- /dev/null
+++ b/dom/base/test/test_processing_instruction_update_stylesheet.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="data:text/css;charset=UTF-8,p{color:red}" type="text/css"?>
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=888864
+-->
+<head>
+ <title>Test for Bug 888864</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 888864 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function changeColorAndRun(callback) {
+ var piNode = document.firstChild;
+ piNode.data = 'href="data:text/css;charset=UTF-8,p{color:green}" type="text/css"';
+ piNode.addEventListener("load", callback);
+ }
+
+ function runTest() {
+ var previousColor = window.getComputedStyle(document.getElementById("display")).
+ getPropertyValue("color");
+ changeColorAndRun(function() {
+ var afterChange = window.getComputedStyle(document.getElementById("display")).
+ getPropertyValue("color");
+ isnot(previousColor, afterChange,
+ "Color of the p element should change.");
+ SimpleTest.finish();
+ });
+ }
+ ]]>
+</script>
+</head>
+<body onload="runTest();">
+<p id="display">This changes color</p>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=888864">Mozilla Bug 888864</a>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_progress_events_for_gzip_data.html b/dom/base/test/test_progress_events_for_gzip_data.html
new file mode 100644
index 0000000000..2d73511457
--- /dev/null
+++ b/dom/base/test/test_progress_events_for_gzip_data.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test progess events in case of gzipped data.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoadData()">
+<script class="testbody" type="text/javascript">"use strict";
+SimpleTest.waitForExplicitFinish();
+
+var url = "send_gzip_content.sjs";
+var loaded = 0;
+var total = 0;
+
+function onProgress(e) {
+ if(e.lengthComputable) {
+ loaded = e.loaded;
+ total = e.total;
+ if (loaded > total) {
+ ok(false, "We have loaded more bytes (" + loaded +
+ ") than the total amount of bytes (" + total +
+ ") available!!!");
+ }
+ }
+}
+
+function onLoadData() {
+ var xhr = new XMLHttpRequest();
+ xhr.addEventListener('progress', onProgress);
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ is(loaded, total, "loaded should be equal to total");
+ isnot(loaded, 0, "loaded should be bigger than 0");
+ SimpleTest.finish();
+ }
+ }
+ xhr.send(null);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_pushState_structuredclone.html b/dom/base/test/test_pushState_structuredclone.html
new file mode 100644
index 0000000000..7051b15a2a
--- /dev/null
+++ b/dom/base/test/test_pushState_structuredclone.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>history.pushState with serializable objects in state</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<script>
+ add_task(async function test() {
+ let state = {
+ b: new Blob(['1234567890']),
+ // CryptoKey
+ k: await crypto.subtle.generateKey({ name: "HMAC", length: 256, hash: {name: "SHA-256"} }, true, ["sign", "verify"]),
+ mr: new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6]),
+ m: new DOMMatrix([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
+ pr: new DOMPointReadOnly(1, 2, 3, 4),
+ p: new DOMPoint(4, 3, 2, 1),
+ q: new DOMQuad(new DOMRectReadOnly(1, 2, 3, 4)),
+ rr: new DOMRectReadOnly(1, 2, 3, 4),
+ r: new DOMRect(1, 2, 3, 4),
+ f: new File(['9876543210'], ''),
+ i: new ImageData(4, 4),
+ // RTCCertificate
+ c: await RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256' }),
+ };
+
+ history.pushState(state, "", "?withState");
+ ok(history.state instanceof Object,
+ "pushState with an object should set history.state.");
+ SimpleTest.isDeeply(history.state, state,
+ "Check that history.state retains all serializable fields.");
+
+ history.pushState(undefined, "", "?withoutState");
+ let promisePoppedState = new Promise(resolve => {
+ window.addEventListener('popstate', event => {
+ resolve(event.state);
+ }, { once: true });
+ });
+ history.back();
+ let poppedState = await promisePoppedState;
+ ok(poppedState instanceof Object,
+ "Going back from pushState with an object should return an object in the popstate event.");
+ SimpleTest.isDeeply(poppedState, state,
+ "Check that going back from pushState retains all serializable fields.");
+ });
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_range_bounds.html b/dom/base/test/test_range_bounds.html
new file mode 100644
index 0000000000..657d315198
--- /dev/null
+++ b/dom/base/test/test_range_bounds.html
@@ -0,0 +1,305 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421640
+-->
+<head>
+ <title>Test for Bug 396392</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396392">Mozilla Bug Range getClientRects and getBoundingClientRect</a>
+<div id="content" style="font-family:monospace;font-size:12px;width:100px">
+<p>000000<span>0</span></p><div>00000<span>0</span></div><p>0000<span>0000</span>0000</p><div><span>000000000000 00000000000000 000000</span></div><div>000000000000 00000000000003 100305</div>
+</div>
+<div id="mixeddir" style="font-family:monospace;font-size:12px;width:100px"><span>english <bdo id="bdo" dir="rtl">rtl-overide english</bdo> word</span></div>
+<div id="mixeddir2" style="font-family:monospace;font-size:12px"><span>english <bdo id="bdo2" dir="rtl">rtl-override english</bdo> word</span></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var isLTR = true;
+var isTransformed = false;
+
+function annotateName(name) {
+ return (isLTR ? 'isLTR ' : 'isRTL ') +
+ (isTransformed ? 'transformed ' : '') + name;
+}
+
+function isEmptyRect(rect, name) {
+ name = annotateName(name);
+ is(rect.left, 0, name+'empty rect should have left = 0');
+ is(rect.right, 0, name+'empty rect should have right = 0');
+ is(rect.top, 0, name+'empty rect should have top = 0');
+ is(rect.bottom, 0, name+'empty rect should have bottom = 0');
+ is(rect.width, 0, name+'empty rect should have width = 0');
+ is(rect.height, 0, name+'empty rect should have height = 0');
+}
+
+function isEmptyRectList(rectlist, name) {
+ name = annotateName(name);
+ is(rectlist.length, 0, name + 'empty rectlist should have zero rects');
+}
+
+// round coordinates to the nearest 1/256 of a pixel
+function roundCoord(x) {
+ return Math.round(x * 256) / 256;
+}
+
+function _getRect(r) {
+ if (r.length) //array
+ return "{left:"+roundCoord(r[0])+",right:"+roundCoord(r[1])+
+ ",top:" +roundCoord(r[2])+",bottom:"+roundCoord(r[3])+
+ ",width:"+roundCoord(r[4])+",height:"+roundCoord(r[5])+"}";
+ else
+ return "{left:"+roundCoord(r.left)+",right:"+roundCoord(r.right)+
+ ",top:"+roundCoord(r.top)+",bottom:"+roundCoord(r.bottom)+
+ ",width:"+roundCoord(r.width)+",height:"+roundCoord(r.height)+"}";
+}
+
+function runATest(obj) {
+ var range = document.createRange();
+ try {
+ range.setStart(obj.range[0],obj.range[1]);
+ if (obj.range.length>2) {
+ range.setEnd(obj.range[2]||obj.range[0], obj.range[3]);
+ }
+ //test getBoundingClientRect()
+ var rect = range.getBoundingClientRect();
+ var testname = 'range.getBoundingClientRect for ' + obj.name;
+ if (obj.rect) {
+ is(_getRect(rect),_getRect(obj.rect), annotateName(testname));
+ } else {
+ isEmptyRect(rect,testname+": ");
+ }
+ //test getClientRects()
+ var rectlist = range.getClientRects();
+ testname = 'range.getClientRects for ' + obj.name;
+ if (!obj.rectList) {
+ //rectList is not specified, use obj.rect to figure out rectList
+ obj.rectList = obj.rect?[obj.rect]:[];
+ }
+ if (!obj.rectList.length) {
+ isEmptyRectList(rectlist, testname+": ");
+ } else {
+ is(rectlist.length, obj.rectList.length,
+ annotateName(testname+' should return '+obj.rectList.length+' rects.'));
+ if(!obj.rectList.forEach){
+ //convert RectList to a real array
+ obj.rectList=Array.prototype.slice.call(obj.rectList, 0);
+ }
+ obj.rectList.forEach(function(r,i) {
+ is(_getRect(rectlist[i]),_getRect(r),
+ annotateName(testname+": item at "+i));
+ });
+ }
+ } finally {
+ range.detach();
+ }
+}
+/** Test for Bug 396392 **/
+function doTest(){
+ var root = document.getElementById('content');
+ var firstP = root.firstElementChild, spanInFirstP = firstP.childNodes[1],
+ firstDiv = root.childNodes[2], spanInFirstDiv = firstDiv.childNodes[1],
+ secondP = root.childNodes[3], spanInSecondP = secondP.childNodes[1],
+ secondDiv = root.childNodes[4], spanInSecondDiv = secondDiv.firstChild,
+ thirdDiv = root.childNodes[5];
+ var firstPRect = firstP.getBoundingClientRect(),
+ spanInFirstPRect = spanInFirstP.getBoundingClientRect(),
+ firstDivRect = firstDiv.getBoundingClientRect(),
+ spanInFirstDivRect = spanInFirstDiv.getBoundingClientRect(),
+ secondPRect = secondP.getBoundingClientRect(),
+ secondDivRect = secondDiv.getBoundingClientRect(),
+ spanInSecondPRect = spanInSecondP.getBoundingClientRect(),
+ spanInSecondDivRect = spanInSecondDiv.getBoundingClientRect(),
+ spanInSecondDivRectList = spanInSecondDiv.getClientRects();
+ var widthPerchar = spanInSecondPRect.width / spanInSecondP.firstChild.length;
+ var testcases = [
+ {name:'nodesNotInDocument', range:[document.createTextNode('abc'), 1],
+ rect:null},
+ {name:'collapsedInBlockNode', range:[firstP, 2], rect:null},
+ {name:'collapsedAtBeginningOfTextNode', range:[firstP.firstChild, 0],
+ rect:[spanInFirstPRect.left - 6 * widthPerchar,
+ spanInFirstPRect.left - 6 * widthPerchar, spanInFirstPRect.top,
+ spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
+ {name:'collapsedWithinTextNode', range:[firstP.firstChild, 1],
+ rect:[spanInFirstPRect.left - 5 * widthPerchar,
+ spanInFirstPRect.left - 5 * widthPerchar,
+ spanInFirstPRect.top, spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
+ {name:'collapsedAtEndOfTextNode', range:[firstP.firstChild, 6],
+ rect:[spanInFirstPRect.left, spanInFirstPRect.left,
+ spanInFirstPRect.top, spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
+ {name:'singleBlockNode', range:[root, 1, root, 2], rect:firstPRect},
+ {name:'twoBlockNodes', range:[root, 1, root, 3],
+ rect:[firstPRect.left, firstPRect.right, firstPRect.top,
+ firstDivRect.bottom, firstPRect.width,
+ firstDivRect.bottom - firstPRect.top],
+ rectList:[firstPRect, firstDivRect]},
+ {name:'endOfTextNodeToEndOfAnotherTextNodeInAnotherBlock',
+ range:[spanInFirstP.firstChild, 1, firstDiv.firstChild, 5],
+ rect:[spanInFirstDivRect.left - 5*widthPerchar, spanInFirstDivRect.left,
+ spanInFirstDivRect.top, spanInFirstDivRect.bottom, 5 * widthPerchar,
+ spanInFirstDivRect.height]},
+ {name:'startOfTextNodeToStartOfAnotherTextNodeInAnotherBlock',
+ range:[spanInFirstP.firstChild, 0, firstDiv.firstChild, 0],
+ rect:[spanInFirstPRect.left, spanInFirstPRect.left + widthPerchar, spanInFirstPRect.top,
+ spanInFirstPRect.bottom, widthPerchar, spanInFirstPRect.height]},
+ {name:'endPortionOfATextNode', range:[firstP.firstChild, 3,
+ firstP.firstChild, 6],
+ rect:[spanInFirstPRect.left - 3*widthPerchar, spanInFirstPRect.left,
+ spanInFirstPRect.top, spanInFirstPRect.bottom, 3*widthPerchar, spanInFirstPRect.height]},
+ {name:'startPortionOfATextNode', range:[firstP.firstChild, 0,
+ firstP.firstChild, 3],
+ rect:[spanInFirstPRect.left - 6*widthPerchar,
+ spanInFirstPRect.left - 3*widthPerchar, spanInFirstPRect.top,
+ spanInFirstPRect.bottom, 3 * widthPerchar, spanInFirstPRect.height]},
+ {name:'spanTextNodes', range:[secondP.firstChild, 1, secondP.lastChild, 1],
+ rect:[spanInSecondPRect.left - 3*widthPerchar, spanInSecondPRect.right +
+ widthPerchar, spanInSecondPRect.top, spanInSecondPRect.bottom,
+ spanInSecondPRect.width + 4*widthPerchar, spanInSecondPRect.height],
+ rectList:[[spanInSecondPRect.left - 3*widthPerchar, spanInSecondPRect.left,
+ spanInSecondPRect.top, spanInSecondPRect.bottom, 3 * widthPerchar,
+ spanInSecondPRect.height],
+ spanInSecondPRect,
+ [spanInSecondPRect.right, spanInSecondPRect.right + widthPerchar,
+ spanInSecondPRect.top, spanInSecondPRect.bottom, widthPerchar,
+ spanInSecondPRect.height]]}
+ ];
+ testcases.forEach(runATest);
+
+ // testcases that have different ranges in LTR and RTL
+ var directionDependentTestcases;
+ if (isLTR) {
+ directionDependentTestcases = [
+ {name:'spanAcrossLines',range:[spanInSecondDiv.firstChild, 1, spanInSecondDiv.firstChild, 30],
+ rect: spanInSecondDivRect,
+ rectList:[[spanInSecondDivRectList[0].left+widthPerchar,
+ spanInSecondDivRectList[0].right, spanInSecondDivRectList[0].top,
+ spanInSecondDivRectList[0].bottom, spanInSecondDivRectList[0].width - widthPerchar,
+ spanInSecondDivRectList[0].height],
+ spanInSecondDivRectList[1],
+ [spanInSecondDivRectList[2].left,
+ spanInSecondDivRectList[2].right - 4 * widthPerchar, spanInSecondDivRectList[2].top,
+ spanInSecondDivRectList[2].bottom,
+ spanInSecondDivRectList[2].width - 4 * widthPerchar,
+ spanInSecondDivRectList[2].height]]},
+ {name:'textAcrossLines',range:[thirdDiv.firstChild, 13, thirdDiv.firstChild, 28],
+ rect: [spanInSecondDivRectList[1].left, spanInSecondDivRectList[1].right,
+ spanInSecondDivRectList[1].top + secondDivRect.height,
+ spanInSecondDivRectList[1].bottom + secondDivRect.height,
+ spanInSecondDivRectList[1].width, spanInSecondDivRectList[1].height]}
+ ];
+ } else {
+ directionDependentTestcases = [
+ {name:'spanAcrossLines',range:[spanInSecondDiv.firstChild, 1, spanInSecondDiv.firstChild, 30],
+ rect: spanInSecondDivRect,
+ rectList:[[spanInSecondDivRectList[0].left+widthPerchar,
+ spanInSecondDivRectList[0].right, spanInSecondDivRectList[0].top,
+ spanInSecondDivRectList[0].bottom, spanInSecondDivRectList[0].width - widthPerchar,
+ spanInSecondDivRectList[0].height],
+ spanInSecondDivRectList[1],
+ spanInSecondDivRectList[2],
+ spanInSecondDivRectList[3],
+ [spanInSecondDivRectList[4].left,
+ spanInSecondDivRectList[4].right - 4 * widthPerchar,
+ spanInSecondDivRectList[4].top,
+ spanInSecondDivRectList[4].bottom,
+ spanInSecondDivRectList[4].width - 4 * widthPerchar,
+ spanInSecondDivRectList[4].height]]},
+ {name:'textAcrossLines',range:[thirdDiv.firstChild, 13, thirdDiv.firstChild, 28],
+ rect: [spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].right,
+ spanInSecondDivRectList[2].top + secondDivRect.height,
+ spanInSecondDivRectList[2].bottom + secondDivRect.height,
+ spanInSecondDivRectList[2].width, spanInSecondDivRectList[2].height],
+ rectList:[[spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].right,
+ spanInSecondDivRectList[2].top + secondDivRect.height,
+ spanInSecondDivRectList[2].bottom + secondDivRect.height,
+ spanInSecondDivRectList[2].width, spanInSecondDivRectList[2].height],
+ [spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].left,
+ spanInSecondDivRectList[2].top + secondDivRect.height,
+ spanInSecondDivRectList[2].bottom + secondDivRect.height,
+ 0, spanInSecondDivRectList[2].height]]}
+ ];
+ }
+ directionDependentTestcases.forEach(runATest);
+}
+function testMixedDir(){
+ var root = document.getElementById('mixeddir');
+ var firstSpan = root.firstElementChild, firstSpanRect=firstSpan.getBoundingClientRect(),
+ firstSpanRectList = firstSpan.getClientRects();
+ runATest({name:'mixeddir',range:[firstSpan.firstChild,0,firstSpan.lastChild,firstSpan.lastChild.length],
+ rect: firstSpanRect, rectList:firstSpanRectList});
+
+ root = document.getElementById('mixeddir2');
+ firstSpan = root.firstElementChild;
+ firstSpanRect = firstSpan.getBoundingClientRect();
+ bdo = document.getElementById('bdo2');
+ bdoRect=bdo.getBoundingClientRect();
+ var widthPerChar = bdoRect.width / bdo.firstChild.length;
+ runATest({name:'mixeddirPartial', range:[firstSpan.firstChild, 3,
+ bdo.firstChild, 7],
+ rect: [firstSpanRect.left + 3*widthPerChar, bdoRect.right,
+ bdoRect.top, bdoRect.bottom,
+ (firstSpan.firstChild.length + bdo.firstChild.length - 3) *
+ widthPerChar,
+ bdoRect.height],
+ rectList:[[firstSpanRect.left + 3*widthPerChar,
+ bdoRect.left,
+ firstSpanRect.top, firstSpanRect.bottom,
+ (firstSpan.firstChild.length - 3) * widthPerChar,
+ firstSpanRect.height],
+ [bdoRect.right - 7 * widthPerChar, bdoRect.right,
+ bdoRect.top, bdoRect.bottom,
+ 7*widthPerChar, bdoRect.height]]});
+}
+
+function testShadowDOM() {
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+ var doc = ifr.contentDocument;
+ var d = doc.createElement("div");
+ var sr = d.attachShadow({mode: "open"});
+ sr.innerHTML = "<div>inside shadow DOM</div>";
+ doc.body.appendChild(d);
+ var r = new ifr.contentWindow.Range();
+ r.selectNode(sr.firstChild);
+ var rect = r.getBoundingClientRect();
+ isnot(rect.width, 0, "Div element inside shadow shouldn't have zero size.");
+ isnot(rect.height, 0, "Div element inside shadow shouldn't have zero size.");
+}
+
+function test(){
+ //test ltr
+ doTest();
+
+ //test rtl
+ isLTR = false;
+ var root = document.getElementById('content');
+ root.dir = 'rtl';
+ doTest();
+ isLTR = true;
+ root.dir = 'ltr';
+
+ testMixedDir();
+
+ //test transforms
+ isTransformed = true;
+ root.style.transform = "translate(30px,50px)";
+ doTest();
+
+ testShadowDOM();
+ SimpleTest.finish();
+}
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_reentrant_flush.html b/dom/base/test/test_reentrant_flush.html
new file mode 100644
index 0000000000..9b606aebc9
--- /dev/null
+++ b/dom/base/test/test_reentrant_flush.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=709256
+-->
+<head>
+ <title>Test for Bug 709256</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=709256">Mozilla Bug 709256</a>
+<p id="display">
+<iframe id="test"
+ style="width: 100px" srcdoc="<body style='width: 100%'>">
+</iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 709256 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ifr = $("test");
+ var bod = ifr.contentDocument.body;
+
+ is(bod.getBoundingClientRect().width, 100,
+ "Width of body should be 100px to start with");
+
+ var resizeHandlerRan = false;
+
+ function handleResize() {
+ resizeHandlerRan = true;
+ is(bod.getBoundingClientRect().width, 50,
+ "Width of body should now be 50px");
+ }
+
+ var win = ifr.contentWindow;
+
+ win.addEventListener("resize", handleResize);
+ SpecialPowers.setFullZoom(win, 2);
+
+ is(resizeHandlerRan, false,
+ "Resize handler should not have run yet for this test to be valid");
+
+ // Now flush out layout on the subdocument, to trigger the resize handler
+ is(bod.getBoundingClientRect().width, 50, "Width of body should still be 50px");
+ window.requestAnimationFrame(function() {
+ is(resizeHandlerRan, true, "Resize handler should have run");
+ win.removeEventListener("resize", handleResize);
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_root_iframe.html b/dom/base/test/test_root_iframe.html
new file mode 100644
index 0000000000..2e19f19efc
--- /dev/null
+++ b/dom/base/test/test_root_iframe.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=511084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 511084</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511084">Mozilla Bug 511084</a>
+<p id="display"><iframe></iframe></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ /** Test for Bug 511084 **/
+ var doc = frames[0].document;
+ doc.replaceChild(doc.createElement("iframe"), doc.documentElement);
+ ok(frames[0][0] instanceof frames[0][0].Window,
+ "Should have a subframe window for a root iframe");
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_sandbox_and_document_uri.html b/dom/base/test/test_sandbox_and_document_uri.html
new file mode 100644
index 0000000000..27b9c87f52
--- /dev/null
+++ b/dom/base/test/test_sandbox_and_document_uri.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test sandbox inheritance and document uri handling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ add_task(async function() {
+ await new Promise((resolve) => {
+ window.onmessage = (event) => {
+ is(event.data, "done");
+ resolve();
+ }
+ });
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<iframe sandbox="allow-popups allow-scripts" srcdoc=
+ "<script>
+ window.onmessage = function(event) { parent.postMessage(event.data, '*'); }
+ /* Open a cross-origin page */
+ window.onload = function() { window.open('http://example.org/tests/dom/base/test/file_sandbox_and_document_uri.html'); }
+ </script>"
+ ></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_sandboxed_blob_uri.html b/dom/base/test/test_sandboxed_blob_uri.html
new file mode 100644
index 0000000000..09568d7641
--- /dev/null
+++ b/dom/base/test/test_sandboxed_blob_uri.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Principal in MessageManager</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+
+ <script type="application/javascript">
+ "use strict";
+
+ var sb = new Cu.Sandbox('https://example.com', { wantGlobalProperties: [ 'Blob', 'URL' ] });
+ Cu.evalInSandbox('var u = URL.createObjectURL(new Blob(["text"], { type: "text/plain" }));', sb);
+ Cu.nukeSandbox(sb);
+ Cu.forceCC();
+
+ ok(true, "are we leaking blobs?");
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_sanitize_xhr.html b/dom/base/test/test_sanitize_xhr.html
new file mode 100644
index 0000000000..3cc0845916
--- /dev/null
+++ b/dom/base/test/test_sanitize_xhr.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1673164
+-->
+<html>
+<head>
+ <title>Test for sanitizing with XHR-loaded owner doc</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet"
+ type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var url = "http://mochi.test:8888/chrome/dom/base/test/file_empty.html"
+var req = new XMLHttpRequest();
+req.open("GET", url, false);
+req.overrideMimeType("text/xml");
+req.send(null);
+var doc = req.responseXML;
+var pu = Cc["@mozilla.org/parserutils;1"].createInstance(Ci.nsIParserUtils);
+var flags = pu.SanitizerDropForms | pu.SanitizerDropMedia;
+var uri = SpecialPowers.Services.io.newURI(url);
+var context = doc.createElement("div");
+var fragment = pu.parseFragment("<form><img onerror=alert(1)><p></p></form>", flags, false, uri, context);
+
+is(fragment.firstChild.localName, "p", "Should have only p.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_screen_orientation.html b/dom/base/test/test_screen_orientation.html
new file mode 100644
index 0000000000..de0dca8b8b
--- /dev/null
+++ b/dom/base/test/test_screen_orientation.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=760735
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Screen Orientation API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=760735">Screen Orientation API</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Tests for Screen Orientation API **/
+
+// Screen Orientation API testing is problematic in that not every platform
+// supports orientation-locking, and locking requires running in full-screen.
+// So for now, only negative/sanity testing is done here, as that works globally.
+
+function unexpectedEvent(event) {
+ ok(false, "No unexpected orientation change events received");
+}
+
+window.screen.addEventListener("mozorientationchange", unexpectedEvent);
+try {
+ const allowedOrientations = ["portrait-primary", "portrait-secondary",
+ "landscape-primary", "landscape-secondary"];
+
+ var initialOrientation = window.screen.mozOrientation;
+
+ // Sanity tests
+ isnot(allowedOrientations.indexOf(initialOrientation), -1,
+ "mozOrientation is valid (value=" + initialOrientation + ")");
+
+ // Negative tests
+ ok(!window.screen.mozLockOrientation("Foobar-Bazbam"), "Cannot lock to an invalid string");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(""), "Cannot lock to an empty string");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(["Foobar", "Bazbam"]), "Cannot lock to an invalid string");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(["foo"]), "Cannot lock to an invalid string");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation([""]), "Cannot lock to an empty string");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(42), "Cannot lock to a number");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(undefined), "Cannot lock to undefined");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(null), "Cannot lock to null");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation({}), "Cannot lock to a non-Array object");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(["Foobar", 42]), "Cannot lock to a number");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(["Foobar", null]), "Cannot lock to null");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+
+ ok(!window.screen.mozLockOrientation(["Foobar", undefined]), "Cannot lock to undefined");
+ is(window.screen.mozOrientation, initialOrientation, "Orientation is unchanged");
+} catch (e) {
+ ok(false, "Got unexpected exception: " + e);
+} finally {
+ window.screen.removeEventListener("mozorientationchange", unexpectedEvent);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_script_loader_crossorigin_data_url.html b/dom/base/test/test_script_loader_crossorigin_data_url.html
new file mode 100644
index 0000000000..cfbb90c8df
--- /dev/null
+++ b/dom/base/test/test_script_loader_crossorigin_data_url.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for handling of 'crossorigin' attribute on script with data: URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ // We're going to mess with window.onerror.
+ setup({ allow_uncaught_exception: true });
+</script>
+<!-- First check that data: scripts with @crossorigin run at all -->
+<script>
+ var ran = false;
+</script>
+<script crossorigin src="data:application/javascript,ran = true"></script>
+<script>
+test(function() {
+ assert_true(ran);
+}, "script@crossorigin with data: src should have run");
+</script>
+<!-- Then check that their syntax errors are not sanitized -->
+<script>
+var errorFired = false;
+ran = false;
+window.onerror = function(message, uri, line) {
+ errorFired = true;
+ test(function() {
+ assert_equals(line, 3);
+ }, "Should have a useful line number for exception in script@crossorigin with data: src");
+}
+</script>
+<script crossorigin src="data:application/javascript,var%20a;%0aran=true%0anoSuchFunctionHere()"></script>
+<script>
+ test(function() {
+ assert_true(ran, "Script with error should have run");
+ assert_true(errorFired, "Script with error should have fired onerror");
+ }, "Should run and correctly fire onerror");
+</script>
diff --git a/dom/base/test/test_script_loader_js_cache.html b/dom/base/test/test_script_loader_js_cache.html
new file mode 100644
index 0000000000..de168ad109
--- /dev/null
+++ b/dom/base/test/test_script_loader_js_cache.html
@@ -0,0 +1,264 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=900784 -->
+<!-- The JS bytecode cache is not supposed to be observable. To make it
+ observable, the ScriptLoader is instrumented to trigger events on the
+ script tag. These events are followed to reconstruct the code path taken by
+ the script loader and associate a simple name which is checked in these
+ test cases.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for saving and loading bytecode in/from the necko cache</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script type="application/javascript">
+ // This is the state machine of the trace events produced by the
+ // ScriptLoader. This state machine is used to give a name to each
+ // code path, such that we can assert each code path with a single word.
+ var scriptLoaderStateMachine = {
+ "scriptloader_load_source": {
+ "scriptloader_execute": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "bytecode_saved",
+ "scriptloader_bytecode_failed": "bytecode_failed"
+ },
+ "scriptloader_no_encode": "source_exec"
+ },
+ "scriptloader_evaluate_module": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "module_bytecode_saved",
+ "scriptloader_bytecode_failed": "module_bytecode_failed"
+ },
+ "scriptloader_no_encode": "module_source_exec"
+ },
+ },
+ "scriptloader_load_bytecode": {
+ "scriptloader_fallback": {
+ // Replicate the top-level state machine without
+ // "scriptloader_load_bytecode" transition.
+ "scriptloader_load_source": {
+ "scriptloader_execute": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "fallback_bytecode_saved",
+ "scriptloader_bytecode_failed": "fallback_bytecode_failed"
+ },
+ "scriptloader_no_encode": "fallback_source_exec"
+ },
+ "scriptloader_evaluate_module": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "module_fallback_bytecode_saved",
+ "scriptloader_bytecode_failed": "module_fallback_bytecode_failed",
+ },
+ "scriptloader_no_encode": "module_fallback_source_exec",
+ },
+ }
+ },
+ "scriptloader_execute": "bytecode_exec",
+ "scriptloader_evaluate_module": "module_bytecode_exec",
+ }
+ };
+
+ function WaitForScriptTagEvent(url) {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ var stateMachine = scriptLoaderStateMachine;
+ var stateHistory = [];
+ var stateMachineResolve, stateMachineReject;
+ var statePromise = new Promise((resolve, reject) => {
+ stateMachineResolve = resolve;
+ stateMachineReject = reject;
+ });
+ var ping = 0;
+
+ // Walk the script loader state machine with the emitted events.
+ function log_event(evt) {
+ // If we have multiple script tags in the loaded source, make sure
+ // we only watch a single one.
+ if (evt.target.id != "watchme")
+ return;
+
+ dump("## ScriptLoader event: " + evt.type + "\n");
+ stateHistory.push(evt.type)
+ if (typeof stateMachine == "object")
+ stateMachine = stateMachine[evt.type];
+ if (typeof stateMachine == "string") {
+ // We arrived to a final state, report the name of it.
+ var result = stateMachine;
+ if (ping) {
+ result = `${result} & ping(=${ping})`;
+ }
+ stateMachineResolve(result);
+ } else if (stateMachine === undefined) {
+ // We followed an unknown transition, report the known history.
+ stateMachineReject(stateHistory);
+ }
+ }
+
+ var iwin = iframe.contentWindow;
+ iwin.addEventListener("scriptloader_load_source", log_event);
+ iwin.addEventListener("scriptloader_load_bytecode", log_event);
+ iwin.addEventListener("scriptloader_generate_bytecode", log_event);
+ iwin.addEventListener("scriptloader_execute", log_event);
+ iwin.addEventListener("scriptloader_evaluate_module", log_event);
+ iwin.addEventListener("scriptloader_encode", log_event);
+ iwin.addEventListener("scriptloader_no_encode", log_event);
+ iwin.addEventListener("scriptloader_bytecode_saved", log_event);
+ iwin.addEventListener("scriptloader_bytecode_failed", log_event);
+ iwin.addEventListener("scriptloader_fallback", log_event);
+ iwin.addEventListener("ping", (evt) => {
+ ping += 1;
+ dump(`## Content event: ${evt.type} (=${ping})\n`);
+ });
+ iframe.src = url;
+
+ statePromise.then(() => {
+ document.body.removeChild(iframe);
+ });
+ return statePromise;
+ }
+
+ async function basicTest(isModule) {
+ const prefix = isModule ? "module_" : "";
+ const name = isModule ? "module" : "script";
+
+ // Setting dom.expose_test_interfaces pref causes the
+ // nsScriptLoadRequest to fire event on script tags, with information
+ // about its internal state. The ScriptLoader source send events to
+ // trace these and resolve a promise with the path taken by the
+ // script loader.
+ //
+ // Setting dom.script_loader.bytecode_cache.strategy to -1 causes the
+ // nsScriptLoadRequest to force all the conditions necessary to make a
+ // script be saved as bytecode in the alternate data storage provided
+ // by the channel (necko cache).
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ // Load the test page, and verify that the code path taken by the
+ // nsScriptLoadRequest corresponds to the code path which is loading a
+ // source and saving it as bytecode.
+ var stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache.html`);
+ assert_equals(await stateMachineResult, `${prefix}bytecode_saved`,
+ `[1-${name}] ScriptLoadRequest status after the first visit`);
+
+ // Reload the same test page, and verify that the code path taken by
+ // the nsScriptLoadRequest corresponds to the code path which is
+ // loading bytecode and executing it.
+ stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache.html`);
+ assert_equals(await stateMachineResult, `${prefix}bytecode_exec`,
+ `[2-${name}] ScriptLoadRequest status after the second visit`);
+
+ // Load another page which loads the same script with an SRI, while
+ // the cached bytecode does not have any. This should fallback to
+ // loading the source before saving the bytecode once more.
+ stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache_with_sri.html`);
+ assert_equals(await stateMachineResult, `${prefix}fallback_bytecode_saved`,
+ `[3-${name}] ScriptLoadRequest status after the SRI hash`);
+
+ // Loading a page, which has the same SRI should verify the SRI and
+ // continue by executing the bytecode.
+ var stateMachineResult1 = WaitForScriptTagEvent(`file_${prefix}js_cache_with_sri.html`);
+
+ // Loading a page which does not have a SRI while we have one in the
+ // cache should not change anything. We should also be able to load
+ // the cache simultanesouly.
+ var stateMachineResult2 = WaitForScriptTagEvent(`file_${prefix}js_cache.html`);
+
+ assert_equals(await stateMachineResult1, `${prefix}bytecode_exec`,
+ `[4-${name}] ScriptLoadRequest status after same SRI hash`);
+ assert_equals(await stateMachineResult2, `${prefix}bytecode_exec`,
+ `[5-${name}] ScriptLoadRequest status after visit with no SRI`);
+
+ if (!isModule) {
+ // Load a page that uses the same script as a module and verify that we
+ // re-parse it from source.
+ stateMachineResult = WaitForScriptTagEvent("file_js_cache_module.html");
+ assert_equals(await stateMachineResult, "module_bytecode_saved",
+ `[6-${name}] ScriptLoadRequest status for a module`);
+ } else {
+ // Load a page that uses the same module script as a regular script and
+ // verify that we re-parse it from source.
+ stateMachineResult = WaitForScriptTagEvent("file_module_js_cache_no_module.html");
+ assert_equals(await stateMachineResult, "bytecode_saved",
+ `[6-${name}] ScriptLoadRequest status for a script`);
+ }
+ }
+
+ promise_test(async function() {
+ await basicTest(false);
+ }, "Check the JS bytecode cache for script");
+
+ promise_test(async function() {
+ await basicTest(true);
+ }, "Check the JS bytecode cache for module");
+
+ promise_test(async function() {
+ // (see above)
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ // The test page add a new script which generate a "ping" event, which
+ // should be recorded before the bytecode is stored in the cache.
+ var stateMachineResult =
+ WaitForScriptTagEvent("file_js_cache_save_after_load.html");
+ assert_equals(await stateMachineResult, "bytecode_saved & ping(=3)",
+ "Wait on all scripts to be executed");
+
+ }, "Save bytecode after the initialization of the page");
+
+ promise_test(async function() {
+ // (see above)
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ // The test page loads a script which contains a syntax error, we should
+ // not attempt to encode any bytecode for it.
+ var stateMachineResult =
+ WaitForScriptTagEvent("file_js_cache_syntax_error.html");
+ assert_equals(await stateMachineResult, "source_exec",
+ "Check the lack of bytecode encoding");
+
+ }, "Do not save bytecode on compilation errors");
+
+ promise_test(async function() {
+ // (see above)
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1],
+ ['browser.cache.jsbc_compression_level', 2]
+ ]});
+
+ // Load the test page, and verify that the code path taken by the
+ // nsScriptLoadRequest corresponds to the code path which is loading a
+ // source and saving it as compressed bytecode.
+ var stateMachineResult = WaitForScriptTagEvent(`file_js_cache.html`);
+ assert_equals(await stateMachineResult, `bytecode_saved`,
+ `[1-script] ScriptLoadRequest status after the first visit`);
+
+ // Reload the same test page, and verify that the code path taken by
+ // the nsScriptLoadRequest corresponds to the code path which is
+ // loading compressed bytecode, decompressing it, and executing it.
+ stateMachineResult = WaitForScriptTagEvent(`file_js_cache.html`);
+ assert_equals(await stateMachineResult, `bytecode_exec`,
+ `[2-script] ScriptLoadRequest status after the second visit`);
+ }, "Check the JS bytecode cache can save and load compressed bytecode");
+
+ done();
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=900784">Mozilla Bug 900784</a>
+</body>
+</html>
diff --git a/dom/base/test/test_script_loader_js_cache_frames.html b/dom/base/test/test_script_loader_js_cache_frames.html
new file mode 100644
index 0000000000..722038ca0f
--- /dev/null
+++ b/dom/base/test/test_script_loader_js_cache_frames.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1436400 -->
+<!-- The JS bytecode cache is not supposed to be observable. To make it
+ observable, the ScriptLoader is instrumented to trigger events on the
+ script tag.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for saving and loading module bytecode in/from the necko cache</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+
+// List of testcases
+//
+// src
+// HTML file loaded in iframe
+// saveEvents
+// Non-ordered multi-set of expected events dispatched during loading the
+// "save" iframe in the HTML file
+// loadEvents
+// Non-ordered multi-set of expected events dispatched during loading the
+// "load" iframe in the HTML file
+const tests = [
+ // Same module is loaded by script element in 2 frames.
+ {
+ src: "file_script_module_frames_element.html",
+ saveEvents: [
+ // file_script_module_frames_element_shared.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ loadEvents: [
+ // file_script_module_frames_element_shared.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ "test_evaluated",
+ ],
+ },
+
+ // Same module is imported in 2 frames.
+ {
+ src: "file_script_module_frames_import.html",
+ saveEvents: [
+ // file_script_module_frames_import_save.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_frames_import_shared.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ loadEvents: [
+ // file_script_module_frames_import_load.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_frames_import_shared.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Same module is dynamically imported in 2 frames.
+ {
+ src: "file_script_module_frames_dynamic.html",
+ saveEvents: [
+ // file_script_module_frames_dynamic_save.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_frames_dynamic_shared.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ loadEvents: [
+ // file_script_module_frames_dynamic_load.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_frames_dynamic_shared.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ ],
+ },
+];
+
+promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ for (const { src, saveEvents, loadEvents } of tests) {
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ const iwin = iframe.contentWindow;
+
+ dump("## Start: " + src + "\n");
+ let observedSaveEvents = [];
+ await new Promise(resolve => {
+ function logEvent(evt) {
+ const type = evt.type.replace(/^save_/, "");
+
+ dump("## ScriptLoader event: " + type + "\n");
+ observedSaveEvents.push(type);
+ if (observedSaveEvents.length == saveEvents.length) {
+ resolve();
+ }
+ }
+ iwin.addEventListener("save_scriptloader_load_source", logEvent);
+ iwin.addEventListener("save_scriptloader_load_bytecode", logEvent);
+ iwin.addEventListener("save_scriptloader_execute", logEvent);
+ iwin.addEventListener("save_scriptloader_evaluate_module", logEvent);
+ iwin.addEventListener("save_scriptloader_encode", logEvent);
+ iwin.addEventListener("save_scriptloader_no_encode", logEvent);
+ iwin.addEventListener("save_scriptloader_bytecode_saved", logEvent);
+ iwin.addEventListener("save_scriptloader_bytecode_failed", logEvent);
+ iwin.addEventListener("save_scriptloader_fallback", logEvent);
+ iwin.addEventListener("save_test_evaluated", logEvent);
+ iframe.src = src;
+ });
+
+ // The event order is non-deterministic.
+ // Just compare them as multi-set.
+ saveEvents.sort();
+ observedSaveEvents.sort();
+ assert_equals(
+ JSON.stringify(observedSaveEvents),
+ JSON.stringify(saveEvents),
+ `Expected events should be observed for ${src} while saving`);
+
+ let observedLoadEvents = [];
+ const loadFrameEventPromise = new Promise(resolve => {
+ function logEvent(evt) {
+ const type = evt.type.replace(/^load_/, "");
+
+ dump("## ScriptLoader event: " + type + "\n");
+ observedLoadEvents.push(type);
+ if (observedLoadEvents.length == loadEvents.length) {
+ resolve();
+ }
+ }
+ iwin.addEventListener("load_scriptloader_load_source", logEvent);
+ iwin.addEventListener("load_scriptloader_load_bytecode", logEvent);
+ iwin.addEventListener("load_scriptloader_execute", logEvent);
+ iwin.addEventListener("load_scriptloader_evaluate_module", logEvent);
+ iwin.addEventListener("load_scriptloader_encode", logEvent);
+ iwin.addEventListener("load_scriptloader_no_encode", logEvent);
+ iwin.addEventListener("load_scriptloader_bytecode_saved", logEvent);
+ iwin.addEventListener("load_scriptloader_bytecode_failed", logEvent);
+ iwin.addEventListener("load_scriptloader_fallback", logEvent);
+ iwin.addEventListener("load_test_evaluated", logEvent);
+
+ });
+
+ // Make sure the "load" iframe is fully loaded.
+ await iwin.loadFramePromise;
+ iwin.document.getElementById("load").contentWindow.doLoad();
+
+ await loadFrameEventPromise;
+
+ // The event order is non-deterministic.
+ // Just compare them as multi-set.
+ loadEvents.sort();
+ observedLoadEvents.sort();
+ assert_equals(
+ JSON.stringify(observedLoadEvents),
+ JSON.stringify(loadEvents),
+ `Expected events should be observed for ${src} while loading`);
+
+ document.body.removeChild(iframe);
+ }
+}, "Test module bytecode save and load");
+
+done();
+ </script>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436400">Mozilla Bug 1436400</a>
+</body>
+</html>
diff --git a/dom/base/test/test_script_loader_js_cache_module.html b/dom/base/test/test_script_loader_js_cache_module.html
new file mode 100644
index 0000000000..8b15d91b6f
--- /dev/null
+++ b/dom/base/test/test_script_loader_js_cache_module.html
@@ -0,0 +1,537 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1436400 -->
+<!-- The JS bytecode cache is not supposed to be observable. To make it
+ observable, the ScriptLoader is instrumented to trigger events on the
+ script tag.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for saving and loading module bytecode in/from the necko cache</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+
+// List of testcases
+//
+// src
+// HTML file loaded twice in an iframe
+// encodeEvents
+// Non-ordered multi-set of expected events dispatched during loading the
+// HTML file at the first time
+// decodeEvents
+// Non-ordered multi-set of expected events dispatched during loading the
+// HTML file at the second time
+const tests = [
+ // Module without import.
+ {
+ src: "file_script_module_single.html",
+ encodeEvents: [
+ // file_script_module_single.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ decodeEvents: [
+ // file_script_module_single.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ "test_evaluated",
+ ],
+ },
+
+ // Module with import.
+ {
+ src: "file_script_module_import.html",
+ encodeEvents: [
+ // file_script_module_import.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_import_imported.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ ],
+ decodeEvents: [
+ // file_script_module_import.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_import_imported.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is imported twice.
+ {
+ src: "file_script_module_import_multi.html",
+ encodeEvents: [
+ // file_script_module_import_multi.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_import_multi_imported_once.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_multi_imported_twice.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_import_multi.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_import_multi_imported_once.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_import_multi_imported_twice.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is imported twice from different elements.
+ {
+ src: "file_script_module_import_multi_elems.html",
+ encodeEvents: [
+ // file_script_module_import_multi_elems_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_import_multi_elems_2.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_import_multi_elems_imported_once_1.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_multi_elems_imported_once_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_multi_elems_imported_once_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_multi_elems_imported_twice.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_import_multi_elems_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_import_multi_elems_2.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_import_multi_elems_imported_once_1.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_import_multi_elems_imported_once_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_import_multi_elems_imported_once_3.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_import_multi_elems_imported_twice.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is imported, and then loaded by element.
+ {
+ src: "file_script_module_import_and_element.html",
+ encodeEvents: [
+ // file_script_module_import_and_element.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_import_and_element_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_and_element_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_import_and_element_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_import_and_element.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_import_and_element_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+
+ // file_script_module_import_and_element_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_import_and_element_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is loaded by element, and then imported.
+ {
+ src: "file_script_module_element_and_import.html",
+ encodeEvents: [
+ // file_script_module_element_and_import.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_element_and_import_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_element_and_import_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_element_and_import_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_element_and_import.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+ "test_evaluated",
+
+ // file_script_module_element_and_import_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level
+
+ // file_script_module_element_and_import_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_element_and_import_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Module with dynamic import.
+ {
+ src: "file_script_module_dynamic_import.html",
+ encodeEvents: [
+ // file_script_module_dynamic_import.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_dynamic_import_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_dynamic_import.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "test_evaluated",
+
+ // file_script_module_dynamic_import_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ ],
+ },
+
+ // Single module is imported dynamically, and then loaded by element.
+ {
+ src: "file_script_module_dynamic_and_element.html",
+ encodeEvents: [
+ // file_script_module_dynamic_and_element.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_dynamic_and_element_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_dynamic_and_element_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_dynamic_and_element_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_dynamic_and_element.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "test_evaluated",
+
+ // file_script_module_dynamic_and_element_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+
+ // file_script_module_dynamic_and_element_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_dynamic_and_element_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is loaded by element, and then imported dynamically.
+ {
+ src: "file_script_module_element_and_dynamic.html",
+ encodeEvents: [
+ // file_script_module_element_and_dynamic.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_element_and_dynamic_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_element_and_dynamic_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_element_and_dynamic_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_element_and_dynamic.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "test_evaluated",
+
+ // file_script_module_element_and_dynamic_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+
+ // file_script_module_element_and_dynamic_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_element_and_dynamic_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is imported dynamically, and then statically.
+ {
+ src: "file_script_module_dynamic_and_static.html",
+ encodeEvents: [
+ // file_script_module_dynamic_and_static.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_dynamic_and_static_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_dynamic_and_static_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_dynamic_and_static_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_dynamic_and_static.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "test_evaluated",
+
+ // file_script_module_dynamic_and_static_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+
+ // file_script_module_dynamic_and_static_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_dynamic_and_static_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+
+ // Single module is imported statically and then dynamically.
+ {
+ src: "file_script_module_static_and_dynamic.html",
+ encodeEvents: [
+ // file_script_module_static_and_dynamic.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_static_and_dynamic_imported_1.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_static_and_dynamic_imported_2.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+
+ // file_script_module_static_and_dynamic_imported_3.js
+ "scriptloader_load_source",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ ],
+ decodeEvents: [
+ // file_script_module_static_and_dynamic.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (element)
+ "test_evaluated",
+
+ // file_script_module_static_and_dynamic_imported_1.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // dispatched only for top-level (dynamic)
+
+ // file_script_module_static_and_dynamic_imported_2.js
+ "scriptloader_load_bytecode",
+
+ // file_script_module_static_and_dynamic_imported_3.js
+ "scriptloader_load_bytecode",
+ ],
+ },
+];
+
+promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ for (const { src, encodeEvents, decodeEvents } of tests) {
+ for (let i = 0; i < 2; i++) {
+ const expectedEvents = i == 0 ? encodeEvents : decodeEvents;
+
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ const iwin = iframe.contentWindow;
+
+ dump("## Start: " + src + "\n");
+ let observedEvents = [];
+ await new Promise(resolve => {
+ function logEvent(evt) {
+ if (evt.type != "test_evaluated") {
+ if (!/^watchme/.test(evt.target.id)) {
+ return;
+ }
+ }
+ dump("## ScriptLoader event: " + evt.type + "\n");
+ observedEvents.push(evt.type);
+ if (observedEvents.length == expectedEvents.length) {
+ resolve();
+ }
+ }
+ iwin.addEventListener("scriptloader_load_source", logEvent);
+ iwin.addEventListener("scriptloader_load_bytecode", logEvent);
+ iwin.addEventListener("scriptloader_execute", logEvent);
+ iwin.addEventListener("scriptloader_evaluate_module", logEvent);
+ iwin.addEventListener("scriptloader_encode", logEvent);
+ iwin.addEventListener("scriptloader_no_encode", logEvent);
+ iwin.addEventListener("scriptloader_bytecode_saved", logEvent);
+ iwin.addEventListener("scriptloader_bytecode_failed", logEvent);
+ iwin.addEventListener("scriptloader_fallback", logEvent);
+ iwin.addEventListener("test_evaluated", logEvent);
+ iframe.src = src;
+ });
+
+ // The event order is non-deterministic.
+ // Just compare them as multi-set.
+ expectedEvents.sort();
+ observedEvents.sort();
+ assert_equals(
+ JSON.stringify(observedEvents),
+ JSON.stringify(expectedEvents),
+ `Expected events should be observed for ${src} during ${i == 0 ? "encoding" : "decoding"}`);
+
+ document.body.removeChild(iframe);
+ }
+ }
+}, "Test module bytecode save and load");
+
+done();
+ </script>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436400">Mozilla Bug 1436400</a>
+</body>
+</html>
diff --git a/dom/base/test/test_script_loader_js_cache_module_sri.html b/dom/base/test/test_script_loader_js_cache_module_sri.html
new file mode 100644
index 0000000000..9a7c7a57e8
--- /dev/null
+++ b/dom/base/test/test_script_loader_js_cache_module_sri.html
@@ -0,0 +1,425 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1436400 -->
+<!-- The JS bytecode cache is not supposed to be observable. To make it
+ observable, the ScriptLoader is instrumented to trigger events on the
+ script tag.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for request fallback for SRI mismatch on module bytecode</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="application/javascript">
+
+// List of testcases
+//
+// prep
+// HTML file loaded in iframe that prepares the bytecode cache
+// prepEvents
+// Non-ordered multi-set of expected events dispatched during loading the
+// "prep" HTML file
+// src
+// HTML file loaded in iframe after preparation finishes
+// events
+// Non-ordered multi-set of expected events dispatched during loading the
+// "src" HTML file
+const tests = [
+ // 1. Module bytecode is saved with integrity
+ // 2. Module bytecode is loaded by script element with integrity
+ {
+ prep: "file_script_module_sri_basic_prep.html",
+ prepEvents: [
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_basic.html",
+ events: [
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element with integrity
+ {
+ prep: "file_script_module_sri_fallback_prep.html",
+ prepEvents: [
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_fallback.html",
+ events: [
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element with wrong integrity
+ //
+ // The module script is not evaluated
+ {
+ prep: "file_script_module_sri_fallback_failure_prep.html",
+ prepEvents: [
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_fallback_failure.html",
+ events: [
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+
+ // This event is dispatched by another script, after the first module
+ // script load is terminated.
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element without integrity, and then
+ // loaded by script element with integrity
+ //
+ // The integrity attribute is ignored because the first request without
+ // integrity is shared between them.
+ {
+ prep: "file_script_module_sri_elem_elem_1_prep.html",
+ prepEvents: [
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_elem_elem_1.html",
+ events: [
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ "test_evaluated",
+
+ // NOTE: scriptloader_evaluate_module is dispatched even if
+ // the module is already evaluated before and it's evaluation is
+ // skipped this time.
+ "scriptloader_evaluate_module",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element with integrity, and then
+ // loaded by script element without integrity
+ //
+ // The request with integrity is shared between them.
+ {
+ prep: "file_script_module_sri_elem_elem_2_prep.html",
+ prepEvents: [
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_elem_elem_2.html",
+ events: [
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ "scriptloader_evaluate_module",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element with integrity, and then
+ // imported statically.
+ //
+ // The request with integrity is shared between them.
+ {
+ prep: "file_script_module_sri_elem_import_prep.html",
+ prepEvents: [
+ // file_script_module_sri_elem_import_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_elem_import.html",
+ events: [
+ // file_script_module_sri_elem_import_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_sri_elem_import.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is imported statically, and
+ // loaded by script element with integrity
+ //
+ // Even if the import is performed first, the script element's request
+ // with integrity is used because of preload.
+ {
+ prep: "file_script_module_sri_import_elem_prep.html",
+ prepEvents: [
+ // file_script_module_sri_import_elem_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_import_elem.html",
+ events: [
+ // file_script_module_sri_import_elem.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_sri_import_elem_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is imported statically, and
+ // loaded by script element with integrity, without preload
+ //
+ // The request without integrity triggered by import is shared between
+ // them.
+ {
+ prep: "file_script_module_sri_import_elem_nopreload_prep.html",
+ prepEvents: [
+ // file_script_module_sri_import_elem_nopreload_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_import_elem_nopreload.html",
+ events: [
+ // file_script_module_sri_import_elem_nopreload.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_sri_import_elem_nopreload_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is loaded by script element with integrity, and then
+ // imported dynamically.
+ //
+ // The request with integrity is shared between them.
+ {
+ prep: "file_script_module_sri_elem_dynamic_prep.html",
+ prepEvents: [
+ // file_script_module_sri_elem_dynamic_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_elem_dynamic.html",
+ events: [
+ // file_script_module_sri_elem_dynamic_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // (element)
+ "scriptloader_evaluate_module", // (dynamic)
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+
+ // file_script_module_sri_elem_dynamic.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is imported dynamically, and then
+ // loaded by script element with integrity
+ //
+ // Even if the dynamic import is performed first, the script element's
+ // request with integrity is used because of preload.
+ {
+ prep: "file_script_module_sri_dynamic_elem_prep.html",
+ prepEvents: [
+ // file_script_module_sri_dynamic_elem_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_dynamic_elem.html",
+ events: [
+ // file_script_module_sri_dynamic_elem_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_fallback",
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module", // (element)
+ "scriptloader_evaluate_module", // (dynamic)
+ "scriptloader_encode",
+ "test_evaluated",
+
+ // file_script_module_sri_dynamic_elem.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+
+ // 1. Module bytecode is saved without integrity
+ // 2. Module bytecode is imported dynamically, and then
+ // loaded by script element with integrity, without preload
+ //
+ // The request without integrity triggered by dynamic import is shared
+ // between them.
+ {
+ prep: "file_script_module_sri_dynamic_elem_nopreload_prep.html",
+ prepEvents: [
+ // file_script_module_sri_dynamic_elem_nopreload_imported.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ src: "file_script_module_sri_dynamic_elem_nopreload.html",
+ events: [
+ // file_script_module_sri_dynamic_elem_nopreload_imported.js
+ "scriptloader_load_bytecode",
+ "scriptloader_evaluate_module", // (element)
+ "scriptloader_evaluate_module", // (dynamic)
+ "test_evaluated",
+
+ // file_script_module_sri_dynamic_elem_nopreload.js
+ "scriptloader_load_source",
+ "scriptloader_evaluate_module",
+ "scriptloader_encode",
+ "scriptloader_bytecode_saved",
+ "test_evaluated",
+ ],
+ },
+];
+
+promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ for (const { prep, prepEvents, src, events } of tests) {
+ for (let i = 0; i < 2; i++) {
+ const expectedEvents = i == 0 ? prepEvents : events;
+ const currentSrc = i == 0 ? prep : src;
+
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ const iwin = iframe.contentWindow;
+
+ dump("## Start: " + currentSrc + "\n");
+ let observedEvents = [];
+ await new Promise(resolve => {
+ function logEvent(evt) {
+ if (evt.type != "test_evaluated") {
+ if (!/^watchme/.test(evt.target.id)) {
+ return;
+ }
+ }
+ dump("## ScriptLoader event: " + evt.type + "\n");
+ observedEvents.push(evt.type);
+ if (observedEvents.length == expectedEvents.length) {
+ resolve();
+ }
+ }
+ iwin.addEventListener("scriptloader_load_source", logEvent);
+ iwin.addEventListener("scriptloader_load_bytecode", logEvent);
+ iwin.addEventListener("scriptloader_execute", logEvent);
+ iwin.addEventListener("scriptloader_evaluate_module", logEvent);
+ iwin.addEventListener("scriptloader_encode", logEvent);
+ iwin.addEventListener("scriptloader_no_encode", logEvent);
+ iwin.addEventListener("scriptloader_bytecode_saved", logEvent);
+ iwin.addEventListener("scriptloader_bytecode_failed", logEvent);
+ iwin.addEventListener("scriptloader_fallback", logEvent);
+ iwin.addEventListener("test_evaluated", logEvent);
+ iframe.src = currentSrc;
+ });
+
+ // The event order is non-deterministic.
+ // Just compare them as multi-set.
+ expectedEvents.sort();
+ observedEvents.sort();
+ assert_equals(
+ JSON.stringify(observedEvents),
+ JSON.stringify(expectedEvents),
+ `Expected events should be observed for ${currentSrc}`);
+
+ document.body.removeChild(iframe);
+ }
+ }
+}, "Test module bytecode save and load");
+
+done();
+ </script>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436400">Mozilla Bug 1436400</a>
+</body>
+</html>
diff --git a/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html b/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
new file mode 100644
index 0000000000..76e8168706
--- /dev/null
+++ b/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
@@ -0,0 +1,253 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.sendQueryContentEvent() and .sendSelectionSetEvent()</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div contenteditable>abc<br>def</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const kLineBreak = navigator.platform.indexOf("Win") == 0 ? "\r\n" : "\n";
+
+SimpleTest.waitForFocus(async () => {
+ const gUtils = window.windowUtils;
+
+ function escape(aStr)
+ {
+ var result = aStr.replace("\n", "\\n");
+ return result.replace("\r", "\\r");
+ }
+
+ function waitForFlushingIMEContentObserver() {
+ return new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ ));
+ }
+
+ const editor = document.querySelector("div[contenteditable]");
+ editor.focus();
+
+ // NOTE: For compatibility, calling without flags should work as with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK.
+
+ // QueryTextContent
+ var expectedStr = escape(("abc" + kLineBreak + "def").substr(2, 4));
+ var result = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_CONTENT, 2, 4, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ is(escape(result.text), expectedStr,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) got unexpected string");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_CONTENT, 2, 4, 0, 0);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT) should succeed");
+ is(escape(result.text), expectedStr,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT) should return same string as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+
+ expectedStr = escape(("abc\ndef").substr(2, 4));
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_CONTENT, 2, 4, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+ is(escape(result.text), expectedStr,
+ "sendQueryContentEvent(QUERY_TEXT_CONTENT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) got unexpected string");
+
+ // QueryCaretRect
+ getSelection().collapse(editor.firstChild, 0);
+
+ var caretNative = gUtils.sendQueryContentEvent(gUtils.QUERY_CARET_RECT, 6, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(caretNative.succeeded,
+ "sendQueryContentEvent(QUERY_CARET_RECT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ var caretXP = gUtils.sendQueryContentEvent(gUtils.QUERY_CARET_RECT, 6 - kLineBreak.length + 1, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(caretXP.succeeded,
+ "sendQueryContentEvent(QUERY_CARET_RECT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+
+ is(caretXP.top, caretNative.top,
+ "The caret top should be same");
+ is(caretXP.left, caretNative.left,
+ "The caret left should be same");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_CARET_RECT, 6, 0, 0, 0);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_CARET_RECT) should succeed");
+ is(result.top, caretNative.top,
+ "sendQueryContentEvent(QUERY_CARET_RECT) should return same top as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.left, caretNative.left,
+ "sendQueryContentEvent(QUERY_CARET_RECT) should return same left as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+
+ // QueryTextRect
+ var textRectNative = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT, 6, 1, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(textRectNative.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_RECT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ var textRectXP = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT, 6 - kLineBreak.length + 1, 1, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(textRectXP.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_RECT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+
+ is(textRectXP.top, textRectNative.top,
+ "The text top should be same");
+ is(textRectXP.left, textRectNative.left,
+ "The text left should be same");
+ is(textRectXP.height, textRectNative.height,
+ "The text height should be same");
+ is(textRectXP.width, textRectNative.width,
+ "The text width should be same");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT, 6, 1, 0, 0);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should succeed");
+ is(result.top, textRectNative.top,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should return same top as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.left, textRectNative.left,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should return same left as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.height, textRectNative.height,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should return same height as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.width, textRectNative.width,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should return same width as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+
+ // QueryTextRectArray
+ var textRectArray = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT_ARRAY, 1, 2, 0, 0);
+ ok(textRectArray.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should succeed");
+ var textRect = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT, 1, 2, 0, 0);
+ ok(textRect.succeeded,
+ "sendQueryContentEvent(QUERY_TEXT_RECT) should succeed");
+ var left = {};
+ var top = {};
+ var width = {};
+ var height = {};
+ textRectArray.getCharacterRect(0, left, top, width, height);
+ is(top.value, textRect.top,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same top that returns QUERY_TEXT_RECT");
+ is(left.value, textRect.left,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same left that returns QUERY_TEXT_RECT");
+
+ var left2 = {};
+ var top2 = {};
+ var width2 = {};
+ var height2 = {};
+ textRectArray.getCharacterRect(1, left2, top2, width2, height2);
+ isfuzzy(width.value + width2.value, textRect.width, 2,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same width that QUERY_TEXT_RECT is returned for offset 1 and 2");
+
+ is(height.value, textRect.height,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same height that returns QUERY_TEXT_RECT");
+
+ // QueryCharacterAtOffset
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_CHARACTER_AT_POINT, 0, 0, textRectNative.left + 1, textRectNative.top + 1,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_CHARACTER_AT_POINT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ is(result.top, textRectNative.top,
+ "The character top is wrong");
+ is(result.left, textRectNative.left,
+ "The character left is wrong");
+ is(result.height, textRectNative.height,
+ "The character height is wrong");
+ is(result.width, textRectNative.width,
+ "The character width is wrong");
+ is(result.offset, 6,
+ "The character offset is wrong");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_CHARACTER_AT_POINT, 0, 0, textRectNative.left + 1, textRectNative.top + 1);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_CHARACTER_AT_POINT) should succeed");
+ is(result.top, textRectNative.top,
+ "The character top should be same as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.left, textRectNative.left,
+ "The character left should be same as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.height, textRectNative.height,
+ "The character height should be same as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.width, textRectNative.width,
+ "The character width should be same as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+ is(result.offset, 6,
+ "The character offset should be same as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_CHARACTER_AT_POINT, 0, 0, textRectXP.left + 1, textRectXP.top + 1,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_CHARACTER_AT_POINT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+ is(result.top, textRectXP.top,
+ "The character top is wrong");
+ is(result.left, textRectXP.left,
+ "The character left is wrong");
+ is(result.height, textRectXP.height,
+ "The character height is wrong");
+ is(result.width, textRectXP.width,
+ "The character width is wrong");
+ is(result.offset, 6 - kLineBreak.length + 1,
+ "The character offset is wrong");
+
+ // SelectionSet and QuerySelectedText
+ await waitForFlushingIMEContentObserver();
+ var selectionSet = gUtils.sendSelectionSetEvent(0, 6, gUtils.SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(selectionSet,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ expectedStr = escape(("abc" + kLineBreak + "def").substr(0, 6));
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_SELECTED_TEXT, 0, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ ok(!result.reversed,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK) should set non-reversed selection");
+ is(escape(result.text), expectedStr,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) got unexpected string");
+
+ await waitForFlushingIMEContentObserver();
+ selectionSet = gUtils.sendSelectionSetEvent(0, 6, gUtils.SELECTION_SET_FLAG_USE_XP_LINE_BREAK);
+ ok(selectionSet,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_XP_LINE_BREAK) should succeed");
+ expectedStr = escape(("abc\ndef").substr(0, 6));
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_SELECTED_TEXT, 0, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+ ok(!result.reversed,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_XP_LINE_BREAK) should set non-reversed selection");
+ is(escape(result.text), expectedStr,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) got unexpected string");
+
+ getSelection().collapse(editor, 0);
+ await waitForFlushingIMEContentObserver();
+ var selectionSet = gUtils.sendSelectionSetEvent(0, 6, gUtils.SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK | gUtils.SELECTION_SET_FLAG_REVERSE);
+ ok(selectionSet,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_SELECTED_TEXT, 0, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
+ ok(result.reversed,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK | SELECTION_SET_FLAG_REVERSE) should set reversed selection");
+
+ selectionSet = gUtils.sendSelectionSetEvent(0, 6, gUtils.SELECTION_SET_FLAG_USE_XP_LINE_BREAK | gUtils.SELECTION_SET_FLAG_REVERSE);
+ ok(selectionSet,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_XP_LINE_BREAK | SELECTION_SET_FLAG_REVERSE) should succeed");
+
+ result = gUtils.sendQueryContentEvent(gUtils.QUERY_SELECTED_TEXT, 0, 0, 0, 0,
+ gUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ ok(result.succeeded,
+ "sendQueryContentEvent(QUERY_SELECTED_TEXT, QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK) should succeed");
+ ok(result.reversed,
+ "sendSelectionSetEvent(0, 6, SELECTION_SET_FLAG_USE_XP_LINE_BREAK | SELECTION_SET_FLAG_REVERSE) should set reversed selection");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_sendSelectionSetEvent_with_same_range.html b/dom/base/test/test_sendSelectionSetEvent_with_same_range.html
new file mode 100644
index 0000000000..fadcbed7ad
--- /dev/null
+++ b/dom/base/test/test_sendSelectionSetEvent_with_same_range.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing nsIDOMWindowUtils.sendSelectionSetEvent not update selection if result range is same in serialized text within the ContentEventHandler rules</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+const kLineBreak = navigator.platform.indexOf("Win") == 0 ? "\r\n" : "\n";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const editingHost = document.querySelector("div[contenteditable]");
+
+ function waitForFlushingIMEContentObserver() {
+ return new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ ));
+ }
+
+ await (async function test_setSelection_when_selection_collapsed_at_end_of_inline_element() {
+ editingHost.innerHTML = "<b>abc</b>def";
+ getSelection().collapse(editingHost.querySelector("b").firstChild, "abc".length);
+ await waitForFlushingIMEContentObserver();
+ const ret = windowUtils.sendSelectionSetEvent(
+ 3,
+ 0,
+ SpecialPowers.Ci.nsIDOMWindowUtils.SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK
+ );
+ ok(
+ ret,
+ "test_setSelection_when_selection_collapsed_at_end_of_inline_element: eSetSelection event should be succeeded"
+ );
+ is(
+ getSelection().focusNode,
+ editingHost.querySelector("b").firstChild,
+ "test_setSelection_when_selection_collapsed_at_end_of_inline_element: focus node should not be changed from the text node in the <b>"
+ );
+ is(
+ getSelection().focusOffset,
+ "abc".length,
+ "test_setSelection_when_selection_collapsed_at_end_of_inline_element: focus offset should not be changed from end of the text node"
+ );
+ })();
+
+ await (async function test_setSelection_when_selection_collapsed_after_inline_element() {
+ editingHost.innerHTML = "<b>abc</b>def";
+ getSelection().collapse(editingHost.querySelector("b").nextSibling, 0);
+ await waitForFlushingIMEContentObserver();
+ const ret = windowUtils.sendSelectionSetEvent(
+ 3,
+ 0,
+ SpecialPowers.Ci.nsIDOMWindowUtils.SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK
+ );
+ ok(
+ ret,
+ "test_setSelection_when_selection_collapsed_after_inline_element: eSetSelection event should be succeeded"
+ );
+ is(
+ getSelection().focusNode,
+ editingHost.querySelector("b").nextSibling,
+ "test_setSelection_when_selection_collapsed_after_inline_element: focus node should not be changed from the text node after the <b>"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_setSelection_when_selection_collapsed_after_inline_element: focus offset should not be changed from start of the text node"
+ );
+ })();
+
+ await (async function test_setSelection_when_selection_collapsed_in_empty_block_element() {
+ editingHost.innerHTML = "<p style=\"width:1em;height:1em;\"></p>\n";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ await waitForFlushingIMEContentObserver();
+ const ret = windowUtils.sendSelectionSetEvent(
+ kLineBreak.length,
+ 0,
+ SpecialPowers.Ci.nsIDOMWindowUtils.SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK
+ );
+ ok(
+ ret,
+ "test_setSelection_when_selection_collapsed_in_empty_block_element: eSetSelection event should be succeeded"
+ );
+ is(
+ getSelection().focusNode,
+ editingHost.querySelector("p"),
+ "test_setSelection_when_selection_collapsed_in_empty_block_element: focus node should not be changed from the empty <div>"
+ );
+ })();
+
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body>
+<div contenteditable></div>
+</body>
+</html>
diff --git a/dom/base/test/test_serializer_noscript.html b/dom/base/test/test_serializer_noscript.html
new file mode 100644
index 0000000000..156440e03a
--- /dev/null
+++ b/dom/base/test/test_serializer_noscript.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for document.blockParsing</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body onload=runTest();>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function checkDoc(aDoc, aScriptState) {
+ const root = aDoc.documentElement;
+ let enc = Cu.createDocumentEncoder("text/html");
+ enc.init(
+ aDoc,
+ "text/html",
+ Ci.nsIDocumentEncoder.OutputEncodeBasicEntities |
+ Ci.nsIDocumentEncoder.OutputLFLineBreak |
+ Ci.nsIDocumentEncoder.OutputBodyOnly |
+ Ci.nsIDocumentEncoder.OutputRaw
+ );
+
+ let str = enc.encodeToString();
+
+ is(str, "<body><noscript>&lt;/noscript&gt;<img></noscript>\n</body>", "Serialization matches expectation with scripting " + aScriptState);
+}
+
+function runTest() {
+ const doc = new DOMParser().parseFromString("<body><noscript>&lt;/noscript&gt;<img></noscript>\n", "text/html");
+ checkDoc(doc, "disabled");
+ checkDoc(document.getElementsByTagName("iframe")[0].contentDocument, "enabled");
+ SimpleTest.finish();
+}
+</script>
+<iframe src="file_serializer_noscript.html"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/test_setInterval_from_start.html b/dom/base/test/test_setInterval_from_start.html
new file mode 100644
index 0000000000..129705c65c
--- /dev/null
+++ b/dom/base/test/test_setInterval_from_start.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1252268
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1378394</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378394">Mozilla Bug 1378394</a>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Some of our platforms are quite slow. Use very large time values to
+// try to avoid raciness.
+const burnTimeMS = 5000;
+const intervalTimeMS = 10000;
+
+// The overall interval should include our callback "burn time". So we
+// expect the delay to be the remaining time.
+const expectedDelayMS = intervalTimeMS - burnTimeMS;
+
+// Allow some margin for error because of slow test platforms.
+const allowedMarginMS = burnTimeMS / 2;
+
+let id;
+let lastEndTime;
+
+function interval()
+{
+ let start = performance.now();
+
+ if (lastEndTime !== undefined) {
+ let delta = start - lastEndTime;
+ ok(delta <= expectedDelayMS + allowedMarginMS,
+ 'interval should not fire too late');
+ ok(delta >= expectedDelayMS - allowedMarginMS,
+ 'interval should not fire too early');
+ clearInterval(id);
+ SimpleTest.finish();
+ return;
+ }
+
+ while((performance.now() - start) < burnTimeMS);
+
+ lastEndTime = performance.now();
+}
+
+id = setInterval(interval, intervalTimeMS);
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_setInterval_uncatchable_exception.html b/dom/base/test/test_setInterval_uncatchable_exception.html
new file mode 100644
index 0000000000..3a23be8d76
--- /dev/null
+++ b/dom/base/test/test_setInterval_uncatchable_exception.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1252268
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1252268</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1252268">Mozilla Bug 1252268</a>
+
+<script type="text/javascript">
+ function go() {
+ SimpleTest.requestFlakyTimeout("I'm smarter than the test harness");
+
+ var ranOnce = false;
+ var finished = false;
+
+ var interval = setInterval(function () {
+ if (ranOnce) {
+ ok(false, "Don't execute me again!");
+ clearInterval(interval);
+ if (!finished) {
+ finished = true;
+ SimpleTest.finish();
+ }
+ } else {
+ ranOnce = true;
+ ok(true, "Ran the first time");
+ try {
+ TestFunctions.throwUncatchableException();
+ ok(false, "How did we get here!");
+ } catch (e) {
+ ok(false, "How did we get here!?");
+ }
+ }
+ }, 100);
+
+ setTimeout(function() {
+ if (!finished) {
+ finished = true;
+ SimpleTest.finish();
+ }
+ }, 1000);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go);
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_setTimeoutWith0.html b/dom/base/test/test_setTimeoutWith0.html
new file mode 100644
index 0000000000..ff098ee33a
--- /dev/null
+++ b/dom/base/test/test_setTimeoutWith0.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+ <title>Test for setTimeout and strings containing 0</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+var x = 0;
+setTimeout("x++; '\x00'; x++;");
+setTimeout(function() {
+ is(x, 2, "We want to see 2 here");
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
+
+
diff --git a/dom/base/test/test_settimeout_extra_arguments.html b/dom/base/test/test_settimeout_extra_arguments.html
new file mode 100644
index 0000000000..b790eff069
--- /dev/null
+++ b/dom/base/test/test_settimeout_extra_arguments.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for setTimeout with a string argument and more than 2 arguments</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ t1 = async_test("setTimeout with more than 2 arguments, first argument a string, should work");
+ t2 = async_test("setInterval with more than 2 arguments, first argument a string, should work");
+ setTimeout("t1.done()", 0, {});
+ var interval = setInterval("clearInterval(interval); t2.done()", 0, {});
+</script>
diff --git a/dom/base/test/test_settimeout_inner.html b/dom/base/test/test_settimeout_inner.html
new file mode 100644
index 0000000000..aeda2cdc88
--- /dev/null
+++ b/dom/base/test/test_settimeout_inner.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=936129
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 936129</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 936129 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function test1Done()
+ {
+ ok(true, "Bareword setTimeout should work after calling document.open().");
+
+ var iframe = document.getElementById("testFrame");
+ iframe.onload = function () {
+ window.runTest2 = iframe.contentWindow.runTest2;
+ iframe.onload = function () {
+ window.runTest2();
+ setTimeout(allDone);
+ }
+
+ // Per whatwg spec, "If the src attribute and the srcdoc attribute are
+ // both specified together, the srcdoc attribute takes priority."
+ //
+ // So if we were to use src attribute here, it will be considered as a
+ // no-op, so simply use a simple srcdoc here.
+ iframe.srcdoc = "<html></html>";
+ }
+ iframe.srcdoc = "<script>function runTest2() { setTimeout('parent.test2Done()'); };<" + "/script>";
+ }
+ window.test2DoneCalled = false;
+ function test2Done()
+ {
+ window.test2DoneCalled = true;
+ }
+ function allDone()
+ {
+ ok(!window.test2DoneCalled, "Bareword setTimeout should be a noop after the document for the window context that it's called on isn't active anymore.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=936129">Mozilla Bug 936129</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="testFrame" src="file_settimeout_inner.html"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_setting_opener.html b/dom/base/test/test_setting_opener.html
new file mode 100644
index 0000000000..3021c2d0de
--- /dev/null
+++ b/dom/base/test/test_setting_opener.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=868996
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 868996</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 868996 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var sb1, sb2;
+ var Cu = SpecialPowers.Cu;
+
+ function testOpenerSet() {
+ // Use setTimeout to make the relevant onerror run in this window
+ var win = window.open("file1_setting_opener.html");
+ // A sandbox for the window
+ sb1 = new Cu.Sandbox(win, {wantXrays: true })
+ sb1.win = win
+ // And a sandbox using the expanded principal.
+ sb2 = new Cu.Sandbox([win], {wantXrays: true })
+ sb2.win = win
+ }
+
+ function evalsb(str, sb) {
+ // Have to unwrap() to get objects we care about
+ return SpecialPowers.unwrap(Cu.evalInSandbox(str, sb));
+ }
+
+ function basicOpenerTest(win) {
+ is(win.opener, window, "Opening a window should give it the right opener");
+ is(evalsb("win.opener", sb1), window,
+ "Reading opener in sandbox 1 should work");
+ is(evalsb("win.opener", sb2), window,
+ "Reading opener in sandbox 2 should work");
+
+ win.opener = $("x").contentWindow;
+ evalsb("win.opener = win.opener.document.getElementById('y').contentWindow", sb1);
+ evalsb("win.opener = win.opener.document.getElementById('z').contentWindow", sb2);
+
+ is(win.opener, $("x").contentWindow, "Should be able to set an opener to a different window");
+ is(evalsb("win.opener", sb1), $("y").contentWindow,
+ "Should be able to set the opener to a different window in a sandbox one");
+ is(evalsb("win.opener", sb2), $("z").contentWindow,
+ "Should be able to set the opener to a different window in a sandbox two");
+
+ win.location = "file2_setting_opener.html";
+ }
+
+ function continueOpenerTest(win) {
+ is(win.opener, window, "Navigating a window should have reset the opener we stashed on it temporarily");
+ is(evalsb("win.opener", sb1), window,
+ "Navigating a window should have reset the opener in sb1");
+ is(evalsb("win.opener", sb2), window,
+ "Navigating a window should have reset the opener in sb2");
+
+ win.opener = 5;
+ evalsb("win.opener = 5", sb1);
+ evalsb("win.opener = 5", sb2);
+ is(win.opener, 5, "Should be able to set an opener to a primitive");
+ is(evalsb("win.opener", sb1), 5,
+ "Should be able to set the opener to a primitive in a sandbox one");
+ is(evalsb("win.opener", sb2), 5,
+ "Should be able to set the opener to a primitive in a sandbox two");
+ win.location = "file3_setting_opener.html";
+ }
+
+ function continueOpenerTest2(win) {
+ is(win.opener, window,
+ "Navigating a window again should have reset the opener we stashed on it temporarily");
+ is(evalsb("win.opener", sb1), window,
+ "Navigating a window again should have reset the opener in sb1");
+ is(evalsb("win.opener", sb2), window,
+ "Navigating a window again should have reset the opener in sb2");
+
+ win.opener = null;
+ is(win.opener, null, "Should be able to set the opener to null");
+ is(evalsb("win.opener", sb1), null,
+ "Setting the opener to null should be visible in sb1");
+ is(evalsb("win.opener", sb2), null,
+ "Setting the opener to null should be visible in sb2");
+
+ win.location = "file4_setting_opener.html";
+ // Now poll for that load, since we have no way for the window to
+ // communicate with us now
+ setTimeout(checkForLoad, 0, win);
+ }
+
+ function checkForLoad(win) {
+ if (!win.document.documentElement ||
+ win.document.documentElement.innerText != "Loaded") {
+ setTimeout(checkForLoad, 0, win);
+ return;
+ }
+
+ is(win.opener, null, "Null opener should persist across navigations");
+ is(evalsb("win.opener", sb1), null,
+ "Null opener should persist across navigations in sb1");
+ is(evalsb("win.opener", sb2), null,
+ "Null opener should persist across navigations in sb2");
+
+ win.close();
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testOpenerSet);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=868996">Mozilla Bug 868996</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="x"></iframe>
+<iframe id="y"></iframe>
+<iframe id="z"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_shared_compartment1.html b/dom/base/test/test_shared_compartment1.html
new file mode 100644
index 0000000000..c86eacb0a8
--- /dev/null
+++ b/dom/base/test/test_shared_compartment1.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1530608
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1530608</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1530608 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var Cu = SpecialPowers.Cu;
+ var isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment;
+
+ var testsDone = 0;
+ function finishIfDone() {
+ testsDone++;
+ if (testsDone === 4) {
+ SimpleTest.finish();
+ }
+ }
+
+ // Test 1: same-origin iframe.
+ function testFrame1() {
+ var frameWin = document.getElementById("frame1").contentWindow;
+ ok(isSameCompartment(window, frameWin),
+ "Same-origin iframe must be same-compartment");
+ finishIfDone();
+ }
+
+ // Test 2: cross-origin iframe.
+ function testFrame2() {
+ var frameWin = document.getElementById("frame2").contentWindow;
+ ok(!isSameCompartment(window, frameWin),
+ "Cross-origin iframe must be cross-compartment");
+ finishIfDone();
+ }
+
+ // Test 3: same-site, cross-origin iframe.
+ function testFrame3() {
+ var frame = document.getElementById("frame3");
+ ok(!isSameCompartment(window, frame.contentWindow),
+ "Same-site cross-origin iframe must be cross-compartment");
+
+ // Now load a same-origin page in this iframe.
+ frame.onload = function() {
+ ok(isSameCompartment(window, frame.contentWindow),
+ "Frame must be same-compartment now");
+ finishIfDone();
+ };
+ frame.src = "file_empty.html";
+ }
+
+ // Test 4: dynamically created iframe.
+ addLoadEvent(function() {
+ var frame = document.createElement("iframe");
+ document.body.appendChild(frame);
+ ok(isSameCompartment(window, frame.contentWindow),
+ "Newly created iframe must be same-compartment");
+ finishIfDone();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1530608">Mozilla Bug 1530608</a>
+
+<iframe id="frame1" onload="testFrame1()" src="file_empty.html"></iframe>
+<iframe id="frame2" onload="testFrame2()" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe>
+<iframe id="frame3" onload="testFrame3()" src="http://test1.mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe>
+
+</body>
+</html>
diff --git a/dom/base/test/test_shared_compartment2.html b/dom/base/test/test_shared_compartment2.html
new file mode 100644
index 0000000000..44f605d09a
--- /dev/null
+++ b/dom/base/test/test_shared_compartment2.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1530608
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1530608</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1530608 **/
+ SimpleTest.waitForExplicitFinish();
+
+ // We have the following origins:
+ //
+ // 1: this page: mochi.test:8888
+ // 2: iframe: test1.mochi.test:8888
+ // 3: inner iframe: mochi.test:8888
+ //
+ // Test that 1 and 2 are cross-compartment (because cross-origin), but 1 and 3
+ // are same-compartment.
+
+ function go(innerWin) {
+ var Cu = SpecialPowers.Cu;
+ var isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment;
+
+ var frame = document.getElementById("frame");
+ ok(!isSameCompartment(window, frame.contentWindow),
+ "Cross-origin iframe must be cross-compartment");
+
+ ok(isSameCompartment(window, innerWin),
+ "Same-origin inner iframe must be same-compartment");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1530608">Mozilla Bug 1530608</a>
+
+<iframe id="frame" src="http://test1.mochi.test:8888/tests/dom/base/test/iframe_shared_compartment2a.html"></iframe>
+
+</body>
+</html>
diff --git a/dom/base/test/test_structuredclone_backref.html b/dom/base/test/test_structuredclone_backref.html
new file mode 100644
index 0000000000..6b31582787
--- /dev/null
+++ b/dom/base/test/test_structuredclone_backref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1538622">Mozilla Bug 1538622</a>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ let o1 = new ImageData(25, 1),
+ o2 = new ImageData(50, 1),
+ o3 = new ImageData(75, 1),
+ o4 = new ImageData(100, 1);
+
+ let data = {
+ img1: o1,
+ img2: o2,
+ img3: o3,
+ img4: o4,
+ img5: o4,
+ };
+
+ window.addEventListener("message", windowMessage);
+
+ window.postMessage(data);
+
+ function windowMessage(e) {
+ let dataCopied = e.data;
+ ok(dataCopied.img5 instanceof ImageData, "backref ImageData should still be an ImageData");
+ is(dataCopied.img5, dataCopied.img4, "backref ImageData should be the referenced one");
+ SimpleTest.finish();
+ }
+</script>
diff --git a/dom/base/test/test_structuredclone_error.html b/dom/base/test/test_structuredclone_error.html
new file mode 100644
index 0000000000..c682cff01d
--- /dev/null
+++ b/dom/base/test/test_structuredclone_error.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=XXX">Mozilla Bug XXX</a>
+
+<script>
+ const tests = [
+ {obj: (() => {}), msg: "Function object could not be cloned."},
+ {obj: document.body, msg: "HTMLBodyElement object could not be cloned."},
+ {obj: {foo: new Audio()}, msg: "HTMLAudioElement object could not be cloned."},
+ ]
+
+ for (const test of tests) {
+ let message = undefined;
+ try {
+ structuredClone(test.obj);
+ } catch (e) {
+ message = e.message;
+ }
+
+ is(message, test.msg, 'Threw correct DataCloneError');
+ }
+</script>
diff --git a/dom/base/test/test_style_cssText.html b/dom/base/test/test_style_cssText.html
new file mode 100644
index 0000000000..869063020a
--- /dev/null
+++ b/dom/base/test/test_style_cssText.html
@@ -0,0 +1,85 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Test for Bug 1391169</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style id="style"></style>
+</head>
+<body>
+<pre id="log">
+Log is:
+</pre>
+<script>
+let styleElement = document.getElementById("style");
+let logElement = document.getElementById("log");
+console.log("logElement is " + logElement);
+
+function log(text)
+{
+ logElement.innerHTML += text + "\n";
+}
+
+function textContentToCssText(text)
+{
+ // Pass input in via textContent.
+ styleElement.textContent = text;
+
+ // Read output from concatenated cssText of all rules.
+ let s = "";
+ let rules = document.styleSheets[1].cssRules;
+ for (let i = 0; i < rules.length; ++i) {
+ s += rules.item(i).cssText;
+ }
+ return s;
+}
+
+function noWhitespace(text)
+{
+ return text.replace(/\s/g, "");
+}
+
+function testData(input)
+{
+ let text;
+ let pass1Goal;
+ if (typeof(input) == "string") {
+ // Only text data, assume characters should be the same.
+ text = input;
+ pass1Goal = input;
+ } else {
+ [text, pass1Goal] = input;
+ }
+
+ let pass1Text = textContentToCssText(text);
+ is(noWhitespace(pass1Text), noWhitespace(pass1Goal), "textContent --> cssText correct characters emitted with input \"" + text + "\"");
+
+ let pass2Text = textContentToCssText(pass1Text);
+ is(pass2Text, pass1Text, "textContent --> cssText roundtrip with input \"" + text + "\"");
+
+ log(text + " --> " + pass1Text + " --> " + pass2Text);
+}
+
+let data = [
+ "*{}",
+ "* *{}",
+ "* > *{}",
+ "*>*{}",
+ "* * *{}",
+ "* > * > *{}",
+ "* + *{}",
+ "* ~ *{}",
+ ["*|*{}", "*{}"],
+ ["*|* > *{}", "* > *{}"],
+ "#tag{}",
+ "tag{}",
+ "@namespace tag url(\"fakeURL\"); tag|*{}",
+ "@namespace tag url(\"fakeURL\"); tag|* + *{}",
+];
+
+for (let i = 0; i < data.length; i++) {
+ testData(data[i]);
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_suppressed_events_and_scrolling.html b/dom/base/test/test_suppressed_events_and_scrolling.html
new file mode 100644
index 0000000000..2a1d5ea493
--- /dev/null
+++ b/dom/base/test/test_suppressed_events_and_scrolling.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test event suppression and scrolling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ let testWin = window.open("file_suppressed_events_and_scrolling.html");
+ // The order of xhrDone and didScroll is random.
+ let xhrDone = false;
+ let didScroll = false;
+ window.onmessage = function(e) {
+ let iframeWindow = testWin.document.body.firstChild.contentWindow;
+ info(e.data);
+ if (e.data == "doscroll") {
+ iframeWindow.scrollTo(0, 1500);
+ } else if (e.data == "xhr_done") {
+ xhrDone = true;
+ if (didScroll) {
+ iframeWindow.scrollTo(0, 3000);
+ }
+ } else if (e.data == "didscroll") {
+ if (didScroll && xhrDone) {
+ // We got the second scroll event.
+ ok(true, "Should have got two scroll events");
+ testWin.close();
+ SimpleTest.finish();
+ }
+ didScroll = true;
+ if (xhrDone) {
+ iframeWindow.scrollTo(0, 3000);
+ }
+ }
+ }
+ }
+ </script>
+</head>
+<body onload="run()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_suppressed_events_nested_iframe.html b/dom/base/test/test_suppressed_events_nested_iframe.html
new file mode 100644
index 0000000000..1d31c7c5d6
--- /dev/null
+++ b/dom/base/test/test_suppressed_events_nested_iframe.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1730117
+-->
+<head>
+ <title>Test event suppression on nested iframe</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1730117">Mozilla Bug 1730117</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function waitForMessage(aMsg) {
+ return new Promise((aResolve) => {
+ window.addEventListener("message", function handler(e) {
+ info(`receive: ${e.data}`);
+ if (e.data != aMsg) {
+ return;
+ }
+
+ window.removeEventListener("message", handler);
+ aResolve();
+ });
+ });
+}
+
+/** Test for Bug 1730117 **/
+
+add_task(async function test_sync_xhr() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["test.events.async.enabled", true],
+ ["dom.events.coalesce.mousemove", false],
+ ]});
+
+ let w = window.open("file_suppressed_events_top_xhr.html");
+ await waitForMessage("done");
+ w.close();
+});
+
+add_task(async function test_modalstate() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["test.events.async.enabled", false],
+ ["dom.events.coalesce.mousemove", false],
+ ]});
+
+ let w = window.open("file_suppressed_events_top_modalstate.html");
+ await waitForMessage("done");
+ w.close();
+});
+
+add_task(async function test_suppress_event_handling() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["test.events.async.enabled", false],
+ ["dom.events.coalesce.mousemove", false],
+ ]});
+
+ let w = window.open("file_suppressed_events_top.html");
+ await waitForMessage("done");
+ w.close();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_suppressed_microtasks.html b/dom/base/test/test_suppressed_microtasks.html
new file mode 100644
index 0000000000..f5d3336386
--- /dev/null
+++ b/dom/base/test/test_suppressed_microtasks.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test microtask suppression</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ var previousTask = -1;
+ function test() {
+ let win = window.open("about:blank");
+ win.onload = function() {
+ win.onmessage = function() {
+ win.start = win.performance.now();
+ win.didRunMicrotask = false;
+ win.onmessage = function() {
+ ok(win.didRunMicrotask, "Should have run a microtask.");
+ let period = win.performance.now() - win.start;
+ win.opener.ok(
+ period < 200,
+ "Running a task should be fast. Took " + period + "ms.");
+ win.onmessage = null;
+ }
+ win.queueMicrotask(function() { win.didRunMicrotask = true; });
+ win.postMessage("measurementMessage", "*");
+ }
+ win.postMessage("initialMessage", "*");
+
+ const last = 500000;
+ for (let i = 0; i < last + 1; ++i) {
+ window.queueMicrotask(function() {
+ // Check that once microtasks are unsuppressed, they are handled in
+ // the correct order.
+ if (previousTask != i - 1) {
+ // Explicitly optimize out cases which pass.
+ ok(false, "Microtasks should be handled in order.");
+ }
+ previousTask = i;
+ if (i == last) {
+ win.close();
+ SimpleTest.finish();
+ }
+ });
+ }
+
+ // Synchronous XMLHttpRequest suppresses microtasks.
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "slow.sjs", false);
+ xhr.send();
+ is(previousTask, -1, "Shouldn't have run microtasks during a sync XHR.");
+ }
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_text_wholeText.html b/dom/base/test/test_text_wholeText.html
new file mode 100644
index 0000000000..48ba08de58
--- /dev/null
+++ b/dom/base/test/test_text_wholeText.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421765
+-->
+<head>
+ <title>Text.wholeText tests</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=421765">Mozilla Bug 421765</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<iframe id="xmlDocument" src="wholeTexty-helper.xml"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 421765 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var xmlDoc;
+
+function text(t) { return document.createTextNode(t); }
+function element() { return document.createElement("div"); }
+function cdata(t)
+{
+ xmlDoc = $("xmlDocument").contentDocument;
+ // document.createCDATASection isn't implemented; clone for the win
+ var node = xmlDoc.documentElement.firstChild.cloneNode(false);
+ is(node.nodeType, Node.CDATA_SECTION_NODE,
+ "er, why isn't this a CDATA section node?");
+ node.data = t;
+ return node;
+}
+
+
+function firstTests()
+{
+ var outer = element();
+ var first = text("first");
+ var second = element();
+ second.appendChild(text("element contents"));
+ outer.appendChild(first);
+ outer.appendChild(second);
+
+ is(first.wholeText, "first", "wrong wholeText for first");
+
+ var insertedText = text("-continued");
+ outer.insertBefore(insertedText, second);
+
+ is(first.wholeText, "first-continued",
+ "wrong wholeText for first after insertedText insertion");
+ is(insertedText.wholeText, "first-continued",
+ "wrong wholeText for insertedText after insertedText insertion");
+
+ var cdataNode = cdata("zero-")
+ outer.insertBefore(cdataNode, first);
+
+ is(first.wholeText, "zero-first-continued",
+ "wrong wholeText for first after cdataNode insertion");
+ is(cdataNode.wholeText, "zero-first-continued",
+ "wrong wholeText for cdataNode after cdataNode insertion");
+ is(insertedText.wholeText, "zero-first-continued",
+ "wrong wholeText for insertedText after cdataNode insertion");
+
+ outer.insertBefore(element(), first);
+
+ is(first.wholeText, "first-continued",
+ "wrong wholeText for first after element insertion");
+ is(cdataNode.wholeText, "zero-",
+ "wrong wholeText for cdataNode after element insertion");
+ is(insertedText.wholeText, "first-continued",
+ "wrong wholeText for insertedText after element insertion");
+
+ var cdataNode2 = cdata("-interrupted");
+ outer.insertBefore(cdataNode2, insertedText);
+
+ is(first.wholeText, "first-interrupted-continued",
+ "wrong wholeText for first after cdataNode2 insertion");
+ is(cdataNode2.wholeText, "first-interrupted-continued",
+ "wrong wholeText for cdataNode2 after cdataNode2 insertion");
+ is(insertedText.wholeText, "first-interrupted-continued",
+ "wrong wholeText for insertedText after cdataNode2 insertion");
+}
+
+function middleTests()
+{
+ var outer = element();
+ var first = element();
+ var last = element();
+ var middle = text("middle");
+ first.appendChild(text("first element contents"));
+ last.appendChild(text("last element contents"));
+ outer.appendChild(first);
+ outer.appendChild(middle);
+ outer.appendChild(last);
+
+ is(middle.wholeText, "middle", "wrong wholeText for middle");
+
+ var beforeMiddle = text("before-");
+ outer.insertBefore(beforeMiddle, middle);
+
+ is(middle.wholeText, "before-middle",
+ "wrong wholeText for middle after beforeMiddle insertion");
+ is(beforeMiddle.wholeText, "before-middle",
+ "wrong wholeText for beforeMiddle after beforeMiddle insertion");
+
+ var midElement = element();
+ midElement.appendChild(text("middle element"));
+ outer.insertBefore(midElement, middle);
+
+ is(middle.wholeText, "middle",
+ "wrong wholeText for middle after midElement insertion");
+ is(beforeMiddle.wholeText, "before-",
+ "wrong wholeText for beforeMiddle after midElement insertion");
+
+ var cdataNode = cdata("after");
+ outer.insertBefore(cdataNode, midElement);
+
+ is(cdataNode.wholeText, "before-after",
+ "wrong wholeText for cdataNode after cdataNode insertion");
+ is(beforeMiddle.wholeText, "before-after",
+ "wrong wholeText for beforeMiddle after cdataNode insertion");
+ is(middle.wholeText, "middle",
+ "wrong wholeText for middle after cdataNode insertion");
+
+ var cdataNode2 = cdata("before-");
+ outer.insertBefore(cdataNode2, middle);
+
+ is(cdataNode.wholeText, "before-after",
+ "wrong wholeText for cdataNode after cdataNode2 insertion");
+ is(beforeMiddle.wholeText, "before-after",
+ "wrong wholeText for beforeMiddle after cdataNode2 insertion");
+ is(cdataNode2.wholeText, "before-middle",
+ "wrong wholeText for middle after cdataNode2 insertion");
+ is(middle.wholeText, "before-middle",
+ "wrong wholeText for middle after cdataNode2 insertion");
+}
+
+function lastTests()
+{
+ var outer = element();
+ var first = element();
+ var second = text("second");
+ first.appendChild(text("element contents"));
+ outer.appendChild(first);
+ outer.appendChild(second);
+
+ is(second.wholeText, "second", "wrong wholeText for second");
+
+ var insertedText = text("before-");
+ outer.insertBefore(insertedText, second);
+
+ is(second.wholeText, "before-second",
+ "wrong wholeText for second after insertedText insertion");
+ is(insertedText.wholeText, "before-second",
+ "wrong wholeText for insertedText after insertedText insertion");
+
+ var cdataNode = cdata("zero-")
+ outer.insertBefore(cdataNode, insertedText);
+
+ is(cdataNode.wholeText, "zero-before-second",
+ "wrong wholeText for cdataNode after cdataNode insertion");
+ is(second.wholeText, "zero-before-second",
+ "wrong wholeText for second after cdataNode insertion");
+ is(insertedText.wholeText, "zero-before-second",
+ "wrong wholeText for insertedText after cdataNode insertion");
+
+ outer.insertBefore(element(), second);
+
+ is(second.wholeText, "second",
+ "wrong wholeText for second after element insertion");
+ is(cdataNode.wholeText, "zero-before-",
+ "wrong wholeText for cdataNode after element insertion");
+ is(insertedText.wholeText, "zero-before-",
+ "wrong wholeText for insertedText after element insertion");
+
+ var cdataNode2 = cdata("interrupted-");
+ outer.insertBefore(cdataNode2, insertedText);
+
+ is(second.wholeText, "second",
+ "wrong wholeText for second after cdataNode2 insertion");
+ is(cdataNode2.wholeText, "zero-interrupted-before-",
+ "wrong wholeText for cdataNode2 after cdataNode2 insertion");
+ is(insertedText.wholeText, "zero-interrupted-before-",
+ "wrong wholeText for insertedText after cdataNode2 insertion");
+}
+
+function noParentTests()
+{
+ var textNode = text("foobar");
+ is(textNode.wholeText, textNode.data,
+ "orphaned textNode should have wholeText == data");
+ is(textNode.wholeText, "foobar",
+ "orphaned textNode should have wholeText == 'foobar'");
+
+ var cdataSection = cdata("baz");
+ is(cdataSection.wholeText, cdataSection.data,
+ "orphaned cdatasection should have wholeText == data");
+ is(cdataSection.wholeText, "baz",
+ "orphaned cdatasection should have wholeText == data");
+}
+
+function tests()
+{
+ try
+ {
+ firstTests();
+ middleTests();
+ lastTests();
+ noParentTests();
+ }
+ catch (e)
+ {
+ ok(false, "error thrown: " + e);
+ }
+ finally
+ {
+ SimpleTest.finish();
+ }
+}
+
+window.addEventListener("load", tests);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_textnode_normalize_in_selection.html b/dom/base/test/test_textnode_normalize_in_selection.html
new file mode 100644
index 0000000000..75d5ed0032
--- /dev/null
+++ b/dom/base/test/test_textnode_normalize_in_selection.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=804784
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 804784</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=804784">Mozilla Bug 804784</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 804784 **/
+
+var sel = document.getSelection();
+var flush = true;
+var dry = true;
+var run = "";
+var empty_range;
+var empty_first_text_range;
+var empty_last_text_range;
+var full_range;
+
+function check(range, expected, test)
+{
+ is(""+range, expected, test);
+ is(""+empty_range, "", "empty range test after: "+test);
+ is(""+empty_first_text_range, "", "empty first text range test after: "+test);
+ if (empty_last_text_range) is(""+empty_last_text_range, "", "empty last text range test after: "+test);
+ is(""+full_range, full_range.startContainer.textContent, "full range test after: "+test);
+}
+
+function newDiv()
+{
+ var div = document.createElement('div');
+ for (var i = 0; i < arguments.length; ++i) {
+ div.appendChild(document.createTextNode(arguments[i]));
+ }
+ document.body.appendChild(div)
+ empty_range = document.createRange();
+ empty_range.setStart(div,0);
+ empty_range.setEnd(div,0);
+ var firstTextNode = div.childNodes[0];
+ var lastTextNode = div.childNodes[div.childNodes.length - 1];
+ empty_first_text_range = document.createRange();
+ empty_first_text_range.setStart(firstTextNode,0);
+ empty_first_text_range.setEnd(firstTextNode,0);
+ empty_last_text_range = null;
+ if (firstTextNode != lastTextNode) {
+ empty_last_text_range = document.createRange();
+ empty_last_text_range.setStart(lastTextNode,0);
+ empty_last_text_range.setEnd(lastTextNode,0);
+ }
+ full_range = document.createRange();
+ full_range.setStart(div,0);
+ full_range.setEnd(div,div.childNodes.length);
+ return div;
+}
+
+function selEnd(div,child,index,s)
+{
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(start, index);
+ r.setEnd(div, div.childNodes.length);
+ if (!dry) div.normalize();
+ check(r,s,run+" selEnd "+child+","+index);
+}
+
+function selStart(div,child,index,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 0);
+ r.setEnd(start, index);
+ if (!dry) div.normalize();
+ check(r,s,run+" selStart "+child+","+index);
+}
+
+function selMiddleStart(div,child,index,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 1);
+ r.setEnd(start, index);
+ div.normalize();
+ check(r,s,run+" selMiddleStart "+child+","+index);
+}
+
+function selMiddleEnd(div,child,index,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(start, index);
+ r.setEnd(div, 2);
+ if (!dry) div.normalize();
+ check(r,s,run+" selMiddleEnd "+child+","+index);
+}
+
+function mergeBefore(div,child,index,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 1);
+ r.setEnd(start, index);
+ if (!dry) div.normalize();
+ check(r,s,run+" mergeBefore "+child+","+index);
+}
+
+function runTests(s)
+{
+ run = s+":";
+ selEnd(newDiv('111'), 0,0,'111');
+ selEnd(newDiv('111'), 0,1,'11');
+ selEnd(newDiv('111'), 0,2,'1');
+ selEnd(newDiv(''), 0,0,'');
+ selEnd(newDiv('',''), 1,0,'');
+ selEnd(newDiv('','',''), 1,0,'');
+ selEnd(newDiv('111','222'), 0,1,'11222');
+ selEnd(newDiv('111','222'), 0,2,'1222');
+ selEnd(newDiv('111','222'), 1,1,'22');
+ selEnd(newDiv('','222'), 1,2,'2');
+ selEnd(newDiv('111',''), 0,1,'11');
+ selEnd(newDiv('111','222'), 1,2,'2');
+ selEnd(newDiv('111','222','333'), 1,1,'22333');
+ selEnd(newDiv('111','222','333'), 1,2,'2333');
+ selEnd(newDiv('111','','333'), 0,2,'1333');
+ selEnd(newDiv('111','','333'), 1,0,'333');
+ selEnd(newDiv('111','','333'), 2,0,'333');
+
+ selStart(newDiv('111'), 0,0,'');
+ selStart(newDiv('111'), 0,1,'1');
+ selStart(newDiv('111'), 0,2,'11');
+ selStart(newDiv(''), 0,0,'');
+ selStart(newDiv('111','222'), 0,1,'1');
+ selStart(newDiv('111','222'), 0,2,'11');
+ selStart(newDiv('111','222'), 1,1,'1112');
+ selStart(newDiv('111','222'), 1,2,'11122');
+ selStart(newDiv('111',''), 1,0,'111');
+ selStart(newDiv('111',''), 0,2,'11');
+ selStart(newDiv('111','222','333'), 1,1,'1112');
+ selStart(newDiv('111','222','333'), 1,2,'11122');
+ selStart(newDiv('111','222','333'), 1,2,'11122');
+ selStart(newDiv('111','','333'), 1,0,'111');
+
+ selMiddleStart(newDiv('111','222','333'), 1,1,'2');
+ selMiddleStart(newDiv('111','222','333'), 1,2,'22');
+ selMiddleStart(newDiv('111','222','333'), 2,1,'2223');
+ selMiddleStart(newDiv('111','222','333'), 2,2,'22233');
+ selMiddleStart(newDiv('111','','333'), 2,2,'33');
+ selMiddleStart(newDiv('111','222',''), 2,0,'222');
+
+ selMiddleEnd(newDiv('111','222','333'), 0,1,'11222');
+ selMiddleEnd(newDiv('111','222','333'), 0,2,'1222');
+ selMiddleEnd(newDiv('111','222','333'), 1,1,'22');
+ selMiddleEnd(newDiv('111','222','333'), 1,2,'2');
+ selMiddleEnd(newDiv('111','','333'), 1,0,'');
+ selMiddleEnd(newDiv('','222','333'), 0,0,'222');
+
+ mergeBefore(newDiv('111','222'), 1,1,'2');
+ mergeBefore(newDiv('111','222','333'), 1,2,'22');
+ mergeBefore(newDiv('111','222','333'), 2,1,'2223');
+ mergeBefore(newDiv('111','222','333'), 2,2,'22233');
+ mergeBefore(newDiv('111','','333'), 2,0,'');
+ mergeBefore(newDiv('111','','333'), 2,2,'33');
+}
+
+function boom()
+{
+ runTests("dry run"); // this is to verify the result strings without normalize()
+ dry = false;
+ flush = false;
+ runTests("no flush");
+ flush = true;
+ runTests("flush");
+}
+
+boom();
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_textnode_split_in_selection.html b/dom/base/test/test_textnode_split_in_selection.html
new file mode 100644
index 0000000000..2bd5c49201
--- /dev/null
+++ b/dom/base/test/test_textnode_split_in_selection.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=803924
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 803924</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=803924">Mozilla Bug 803924</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 803924 **/
+
+var sel = document.getSelection();
+var flush = true;
+var dry = true;
+var run = "";
+var empty_range;
+var empty_first_text_range;
+var empty_last_text_range;
+var full_range;
+
+function check(range, expected, test)
+{
+ is(""+range, expected, test);
+ is(""+empty_range, "", "empty range test after: "+test);
+ is(""+empty_first_text_range, "", "empty first text range test after: "+test);
+ if (empty_last_text_range) is(""+empty_last_text_range, "", "empty last text range test after: "+test);
+ is(""+full_range, full_range.startContainer.textContent, "full range test after: "+test);
+}
+
+function newDiv()
+{
+ var div = document.createElement('div');
+ for (var i = 0; i < arguments.length; ++i) {
+ div.appendChild(document.createTextNode(arguments[i]));
+ }
+ document.body.appendChild(div)
+ empty_range = document.createRange();
+ empty_range.setStart(div,0);
+ empty_range.setEnd(div,0);
+ var firstTextNode = div.childNodes[0];
+ var lastTextNode = div.childNodes[div.childNodes.length - 1];
+ empty_first_text_range = document.createRange();
+ empty_first_text_range.setStart(firstTextNode,0);
+ empty_first_text_range.setEnd(firstTextNode,0);
+ empty_last_text_range = null;
+ if (firstTextNode != lastTextNode) {
+ empty_last_text_range = document.createRange();
+ empty_last_text_range.setStart(lastTextNode,0);
+ empty_last_text_range.setEnd(lastTextNode,0);
+ }
+ full_range = document.createRange();
+ full_range.setStart(div,0);
+ full_range.setEnd(div,div.childNodes.length);
+ return div;
+}
+
+function selEnd(div,child,index,split,s)
+{
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(start, index);
+ r.setEnd(div, div.childNodes.length);
+ if (!dry) start.splitText(split);
+ check(r,s,run+" selEnd "+child+","+index+","+split);
+}
+
+function selStart(div,child,index,split,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 0);
+ r.setEnd(start, index);
+ if (!dry) start.splitText(split);
+ check(r,s,run+" selStart "+child+","+index+","+split);
+}
+
+function selMiddleStart(div,child,index,split,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 1);
+ r.setEnd(start, index);
+ if (!dry) start.splitText(split);
+ check(r,s,run+" selMiddleStart "+child+","+index+","+split);
+}
+
+function selMiddleEnd(div,child,index,split,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(start, index);
+ r.setEnd(div, 2);
+ if (!dry) start.splitText(split);
+ check(r,s,run+" selMiddleEnd "+child+","+index+","+split);
+}
+
+function splitBefore(div,child,index,split,s)
+{
+ if (flush) document.body.getClientRects();
+ var start = div.childNodes[child];
+ var r = document.createRange();
+ sel.addRange(r);
+ r.setStart(div, 1);
+ r.setEnd(start, index);
+ if (!dry) div.childNodes[0].splitText(split);
+ check(r,s,run+" splitBefore "+child+","+index+","+split);
+}
+
+function runTests(s)
+{
+ run = s+":";
+ selEnd(newDiv('111'), 0,0,0,'111');
+ selEnd(newDiv('111'), 0,0,1,'111');
+ selEnd(newDiv('111'), 0,0,3,'111');
+ selEnd(newDiv(''), 0,0,0,'');
+ selEnd(newDiv('111'), 0,1,0,'11');
+ selEnd(newDiv('111'), 0,2,1,'1');
+ selEnd(newDiv('111'), 0,1,3,'11');
+ selEnd(newDiv('111','222'), 0,1,0,'11222');
+ selEnd(newDiv('111','222'), 0,2,1,'1222');
+ selEnd(newDiv('111','222'), 0,1,3,'11222');
+ selEnd(newDiv('111','222'), 1,1,0,'22');
+ selEnd(newDiv('111','222'), 1,2,1,'2');
+ selEnd(newDiv('','222'), 1,1,1,'22');
+ selEnd(newDiv('','222'), 0,0,0,'222');
+ selEnd(newDiv('111',''), 0,1,0,'11');
+ selEnd(newDiv('111','222'), 1,1,3,'22');
+ selEnd(newDiv('111','222','333'), 1,1,0,'22333');
+ selEnd(newDiv('111','222','333'), 1,2,1,'2333');
+ selEnd(newDiv('111','222','333'), 1,1,3,'22333');
+ selEnd(newDiv('111','222',''), 1,1,3,'22');
+ selEnd(newDiv('111','','333'), 0,1,3,'11333');
+
+ selStart(newDiv('111'), 0,0,0,'');
+ selStart(newDiv('111'), 0,0,1,'');
+ selStart(newDiv('111'), 0,0,3,'');
+ selStart(newDiv('111'), 0,1,0,'1');
+ selStart(newDiv('111'), 0,2,1,'11');
+ selStart(newDiv('111'), 0,1,3,'1');
+ selStart(newDiv(''), 0,0,0,'');
+ selStart(newDiv('111','222'), 0,1,0,'1');
+ selStart(newDiv('111','222'), 0,2,1,'11');
+ selStart(newDiv('111','222'), 0,1,3,'1');
+ selStart(newDiv('111','222'), 1,1,0,'1112');
+ selStart(newDiv('111','222'), 1,2,1,'11122');
+ selStart(newDiv('111','222'), 1,1,3,'1112');
+ selStart(newDiv('','222'), 1,1,2,'2');
+ selStart(newDiv('','222'), 0,0,0,'');
+ selStart(newDiv('111',''), 1,0,0,'111');
+ selStart(newDiv('111','222','333'), 1,1,0,'1112');
+ selStart(newDiv('111','222','333'), 1,2,1,'11122');
+ selStart(newDiv('111','222','333'), 1,1,3,'1112');
+ selStart(newDiv('111','','333'), 1,0,0,'111');
+ selStart(newDiv('111','222',''), 1,1,3,'1112');
+
+ selMiddleStart(newDiv('111','222','333'), 1,1,0,'2');
+ selMiddleStart(newDiv('111','222','333'), 1,2,1,'22');
+ selMiddleStart(newDiv('111','222','333'), 1,1,3,'2');
+ selMiddleStart(newDiv('111','222','333'), 2,1,0,'2223');
+ selMiddleStart(newDiv('111','222','333'), 2,2,1,'22233');
+ selMiddleStart(newDiv('111','222','333'), 2,1,3,'2223');
+ selMiddleStart(newDiv('111','','333'), 2,1,2,'3');
+ selMiddleStart(newDiv('111','','333'), 1,0,0,'');
+
+ selMiddleEnd(newDiv('111','222','333'), 0,1,0,'11222');
+ selMiddleEnd(newDiv('111','222','333'), 0,2,1,'1222');
+ selMiddleEnd(newDiv('111','222','333'), 0,1,3,'11222');
+ selMiddleEnd(newDiv('111','222','333'), 1,1,0,'22');
+ selMiddleEnd(newDiv('111','222','333'), 1,2,1,'2');
+ selMiddleEnd(newDiv('111','222','333'), 1,1,3,'22');
+ selMiddleEnd(newDiv('111','','333'), 0,1,2,'11');
+ selMiddleEnd(newDiv('111','','333'), 0,1,3,'11');
+ selMiddleEnd(newDiv('111','','333'), 1,0,0,'');
+
+ splitBefore(newDiv('111','222','333'), 1,1,0,'2');
+ splitBefore(newDiv('111','222','333'), 1,2,1,'22');
+ splitBefore(newDiv('111','222','333'), 1,1,3,'2');
+ splitBefore(newDiv('111','222','333'), 2,1,0,'2223');
+ splitBefore(newDiv('111','222','333'), 2,2,1,'22233');
+ splitBefore(newDiv('111','222','333'), 2,1,3,'2223');
+ splitBefore(newDiv('','222','333'), 1,1,0,'2');
+ splitBefore(newDiv('','','333'), 1,0,0,'');
+ splitBefore(newDiv('','222',''), 2,0,0,'222');
+ splitBefore(newDiv('111','','333'), 2,1,2,'3');
+}
+
+function boom()
+{
+ runTests("dry run"); // this is to verify the result strings without splitText()
+ dry = false;
+ flush = false;
+ runTests("no flush");
+ flush = true;
+ runTests("flush");
+}
+
+boom();
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_timeout_clamp.html b/dom/base/test/test_timeout_clamp.html
new file mode 100644
index 0000000000..1f0496235e
--- /dev/null
+++ b/dom/base/test/test_timeout_clamp.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1378586
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1378586</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378586">Mozilla Bug 1378586</a>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// We need to clear our nesting level periodically. We do this by firing
+// a postMessage() to get a runnable on the event loop without any setTimeout()
+// nesting.
+function clearNestingLevel() {
+ return new Promise(resolve => {
+ window.addEventListener('message', () => {
+ resolve();
+ }, {once: true});
+ postMessage('done', '*');
+ });
+}
+
+function delayByTimeoutChain(iterations) {
+ return new Promise(resolve => {
+ let count = 0;
+ function tick() {
+ count += 1;
+ if (count >= iterations) {
+ resolve();
+ return;
+ }
+ setTimeout(tick, 0);
+ }
+ setTimeout(tick, 0);
+ });
+}
+
+function delayByInterval(iterations) {
+ return new Promise(resolve => {
+ let count = 0;
+ function tick() {
+ count += 1;
+ if (count >= iterations) {
+ resolve();
+ return;
+ }
+ }
+ setInterval(tick, 0);
+ });
+}
+
+function testNestedIntervals() {
+ return new Promise(resolve => {
+ const runCount = 100;
+ let counter = 0;
+ let intervalId = 0;
+ let prevInitTime = performance.now();
+ let totalTime = 0;
+ function intervalCallback() {
+ let now = performance.now();
+ let delay = now - prevInitTime;
+ totalTime += delay;
+ prevInitTime = now;
+ clearInterval(intervalId);
+ if (++counter < runCount) {
+ intervalId = setInterval(intervalCallback, 0);
+ } else {
+ // Delays should be clamped to 4ms after the initial calls, but allow
+ // some fuzziness, so divide by 2.
+ let expectedTime = runCount * 4 / 2;
+ ok(totalTime > expectedTime, "Should not run callbacks too fast.");
+ resolve();
+ }
+ }
+
+ // Use the timeout value defined in the spec, 4ms.
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.min_timeout_value", 4]]},
+ intervalCallback);
+ });
+}
+
+// Use a very long clamp delay to make it easier to measure the change
+// in automation. Some of our test servers are very slow and noisy.
+const clampDelayMS = 10000;
+
+// We expect that we will clamp on the 5th callback. This should
+// be the same for both setTimeout() chains and setInterval().
+const expectedClampIteration = 5;
+
+async function runTests() {
+ // Things like pushPrefEnv() can use setTimeout() internally which may give
+ // us a nesting level. Clear the nesting level to start so this doesn't
+ // confuse the test.
+ await clearNestingLevel();
+
+ // Verify a setTimeout() chain clamps correctly
+ let start = performance.now();
+ await delayByTimeoutChain(expectedClampIteration);
+ let stop = performance.now();
+ let delta = stop - start;
+
+ ok(delta >= clampDelayMS, "setTimeout() chain clamped: " + stop + " - " + start + " = " + delta);
+ ok(delta < (2*clampDelayMS), "setTimeout() chain did not clamp twice");
+
+ await clearNestingLevel();
+
+ // Verify setInterval() clamps correctly
+ start = performance.now();
+ await delayByInterval(expectedClampIteration);
+ stop = performance.now();
+ delta = stop - start;
+
+ ok(delta >= clampDelayMS, "setInterval() clamped: " + stop + " - " + start + " = " + delta);
+ ok(delta < (2*clampDelayMS), "setInterval() did not clamp twice");
+
+ await clearNestingLevel();
+
+ // Verify a setTimeout() chain will continue to clamp past the first
+ // expected iteration.
+ const expectedDelay = (1 + expectedClampIteration) * clampDelayMS;
+
+ start = performance.now();
+ await delayByTimeoutChain(2 * expectedClampIteration);
+ stop = performance.now();
+ delta = stop - start;
+
+ ok(delta >= expectedDelay, "setTimeout() chain continued to clamp: " + stop + " - " + start + " = " + delta);
+
+ await clearNestingLevel();
+
+ // Verify setInterval() will continue to clamp past the first expected
+ // iteration.
+ start = performance.now();
+ await delayByTimeoutChain(2 * expectedClampIteration);
+ stop = performance.now();
+ delta = stop - start;
+
+ ok(delta >= expectedDelay, "setInterval() continued to clamp: " + stop + " - " + start + " = " + delta);
+
+ await testNestedIntervals();
+
+ SimpleTest.finish();
+}
+
+// It appears that it's possible to get unlucky with time jittering and fail this test.
+// If start is jittered upwards, everything executes very quickly, and delta has
+// a very high midpoint, we may have taken between 10 and 10.002 seconds to execute; but
+// it will appear to be 9.998. Turn off jitter (and add logging) to test this.
+SpecialPowers.pushPrefEnv({ 'set': [
+ ["dom.min_timeout_value", clampDelayMS],
+ ["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false],
+ ]}, runTests);
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_timer_flood.html b/dom/base/test/test_timer_flood.html
new file mode 100644
index 0000000000..afbc841b34
--- /dev/null
+++ b/dom/base/test/test_timer_flood.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test Behavior During Timer Flood</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+// This test takes a long time to run and it times out on Android debug as a result.
+SimpleTest.requestLongerTimeout(5);
+
+function onLoad() {
+ return new Promise(resolve => {
+ addEventListener('load', resolve, { once: true });
+ });
+}
+
+// Create a frame that executes a timer flood. The frame signals
+// that is ready once the flood has had a chance to warm up.
+function withFloodFrame() {
+ return new Promise(resolve => {
+ let frame = document.createElement('iframe');
+ addEventListener('message', function onMsg(evt) {
+ if (evt.data === 'STARTED') {
+ removeEventListener('message', onMsg);
+ resolve(frame);
+ }
+ });
+ frame.src = 'file_timer_flood.html';
+ document.body.appendChild(frame);
+ });
+}
+
+// Test that we can load documents during a timer flood.
+function testFrameLoad() {
+ return new Promise(resolve => {
+ let frame = document.createElement('iframe');
+ frame.addEventListener('load', _ => {
+ frame.remove();
+ resolve();
+ }, { once: true });
+ document.body.appendChild(frame);
+ });
+}
+
+// Test that we can perform network requests while a timer flood
+// is occuring.
+function testFetch(url) {
+ return fetch(url).then(response => {
+ return response.text();
+ });
+}
+
+// Test that we can run animations for 5 seconds while a timer
+// flood is occuring.
+function testRequestAnimationFrame() {
+ return new Promise(resolve => {
+ let remainingFrames = 5 * 60;
+ function nextFrame() {
+ remainingFrames -= 1;
+ if (remainingFrames > 0) {
+ requestAnimationFrame(nextFrame);
+ } else {
+ resolve();
+ }
+ };
+ requestAnimationFrame(nextFrame);
+ });
+}
+
+let floodFrame;
+
+onLoad().then(_ => {
+ // Start a timer flood in a frame.
+ return withFloodFrame();
+}).then(frame => {
+ floodFrame = frame;
+
+ // Next we are going to start a bunch of asynchronous work that we
+ // expect to complete in spite of the timer flood. The type of work
+ // is a bit arbitrary, but is chosen to reflect the kinds of things
+ // we would like the browser to be able to do even when pages are
+ // abusing timers. Feel free to add more types of work here, but
+ // think carefully before removing anything.
+ let tests = [];
+
+ // Verify we can perform a variety of work while the timer flood
+ // is running.
+ for (let i = 0; i < 20; ++i) {
+ tests.push(testFrameLoad());
+ tests.push(testFetch('file_timer_flood.html'));
+ }
+ // Verify that animations still work while the timer flood is running.
+ // Note that we do one long run of animations instead of parallel runs
+ // like the other activities because of the way requestAnimationFrame()
+ // is scheduled. Parallel animations would not result in any additional
+ // runnables be placed on the event queue.
+ tests.push(testRequestAnimationFrame());
+
+ // Wait for all tests to finish. If we do not handle the timer flood
+ // well then this will likely time out.
+ return Promise.all(tests);
+}).then(_ => {
+ ok(true, 'completed tests without timing out');
+ floodFrame.remove();
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_title.html b/dom/base/test/test_title.html
new file mode 100644
index 0000000000..7b7a3f9f09
--- /dev/null
+++ b/dom/base/test/test_title.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test for titles</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ </style>
+</head>
+
+<body onload="runTests()">
+
+<p id="display"></p>
+<div style="display:none;">
+ <iframe id="html1" src="data:text/html,<html><head><title id='t'>Test</title></head></html>"></iframe>
+ <iframe id="html2" src="data:text/html,<html><head><title id='t'>Test</title><title>Foo</title></head></html>"></iframe>
+ <iframe id="html3" src="data:text/html,<html></html>"></iframe>
+ <iframe id="xhtml1" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><body><title id='t'>Test</title></body></html>"></iframe>
+ <iframe id="xhtml2" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Test</title>"></iframe>
+ <iframe id="xhtml3" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Te<div>bogus</div>st</title>"></iframe>
+ <iframe id="xhtml4" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'/>"></iframe>
+ <iframe id="xhtml5" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head/></html>"></iframe>
+ <iframe id="xhtml6" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head><style/></head></html>"></iframe>
+ <iframe id="svg1" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"></iframe>
+ <iframe id="svg2" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"></iframe>
+</div>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ function testStatic(id, expect, description) {
+ var myFrame = document.getElementById(id);
+ var wrappedDoc = SpecialPowers.wrap(myFrame).contentDocument;
+ is(wrappedDoc.title, expect, description);
+ }
+
+ testStatic("html1", "Test", "HTML <title>");
+ testStatic("html2", "Test", "choose the first HTML <title>");
+ testStatic("html3", "", "No title");
+ testStatic("xhtml1", "Test", "XHTML <title> in body");
+ testStatic("xhtml2", "Test", "XHTML <title> as root element");
+ testStatic("xhtml3", "Test", "XHTML <title> containing an element");
+ testStatic("svg1", "Test", "SVG <title>");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_toScreenRect.html b/dom/base/test/test_toScreenRect.html
new file mode 100644
index 0000000000..7a3656ac36
--- /dev/null
+++ b/dom/base/test/test_toScreenRect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<script>
+SimpleTest.waitForExplicitFinish();
+window.open('file_toScreenRect.html');
+</script>
+</html>
diff --git a/dom/base/test/test_treewalker_nextsibling.xml b/dom/base/test/test_treewalker_nextsibling.xml
new file mode 100644
index 0000000000..e38852dba2
--- /dev/null
+++ b/dom/base/test/test_treewalker_nextsibling.xml
@@ -0,0 +1,96 @@
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css" ?>
+<root>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js" xmlns="http://www.w3.org/1999/xhtml"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none;"></div>
+ <textarea id="test" style="height: 300px; max-width: 800px; overflow: scroll;"
+ rows="10" cols="160" readonly="readonly"/>
+ </body>
+
+<pre id="test" xmlns="http://www.w3.org/1999/xhtml">
+<script class="testbody" type="text/javascript" xmlns="http://www.w3.org/1999/xhtml">
+<![CDATA[
+var passedNodes = new WeakMap();
+function setPass(aNode) {
+ passedNodes.set(aNode, true);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", function() {
+ var walker = document.createTreeWalker(
+ document,
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_DOCUMENT,
+ null
+ );
+ setPass(walker.firstChild());
+ while (walker.nextSibling()) {
+ setPass(walker.currentNode);
+ }
+
+/*
+From http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/traversal.html#Traversal-TreeWalker
+Omitting nodes from the logical view of a subtree can result in a structure that
+is substantially different from the same subtree in the complete, unfiltered
+document. Nodes that are siblings in the TreeWalker view may be children of
+different, widely separated nodes in the original view. For instance, consider a
+NodeFilter that skips all nodes except for Text nodes and the root node of a
+document. In the logical view that results, all text nodes will be siblings and
+appear as direct children of the root node, no matter how deeply nested the
+structure of the original document.
+*/
+
+ walker2 = document.createTreeWalker(document, NodeFilter.SHOW_TEXT, null);
+ while (walker2.nextNode()) {
+ var cNode = walker2.currentNode;
+ ok(passedNodes.get(cNode), "Every text node should appear: " + walker2.currentNode.nodeValue);
+ walker.currentNode = cNode;
+ var parent = walker.parentNode();
+ is(parent, document, "parent of text node should be document");
+
+ // Check nextSibling's previousSibling.
+ walker.currentNode = cNode;
+ if (walker.nextSibling()) {
+ is(cNode, walker.previousSibling(), "nextSibling.previousSibling should be consistent");
+ }
+
+ // Check previousSibling's nextSibling.
+ walker.currentNode = cNode;
+ if (walker.previousSibling()) {
+ is(cNode, walker.nextSibling(), "previousSibling.nextSibling should be consistent");
+ }
+ }
+ SimpleTest.finish();
+}, true);
+]]>
+</script>
+</pre>
+
+ <test>
+ zero
+ <one>
+ one-A
+ <two>
+ two-A
+ </two>
+ <two>
+ two-B
+ </two>
+ one-B
+ </one>
+ <one>
+ one-C
+ <two>
+ two-D
+ </two>
+ <two>
+ two-E
+ </two>
+ one-F
+ </one>
+ zero
+ </test>
+</root>
+
diff --git a/dom/base/test/test_urgent_start.html b/dom/base/test/test_urgent_start.html
new file mode 100644
index 0000000000..1dd13c20c2
--- /dev/null
+++ b/dom/base/test/test_urgent_start.html
@@ -0,0 +1,269 @@
+<!DOCTYPE HTML>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1348050
+ Test for fetch and xhr to guarantee we only mark channel as urgent-start when
+ it is triggered by user input events.
+
+ For { Fetch, SRC-*, XHR }, do the test as following:
+ Step 1: Verify them not mark the channel when there is no any input event.
+ Step 2: Verify them mark the channel there is a user input event.
+ Step 3: Verify them not mark the channel when there is a non input event.
+
+ In each steps, it shows that we only mark channel on direct triggering task.
+ We won't mark the channel for additional task(setTimeout) or
+ micro-task(promise).
+-->
+<html>
+<head>
+ <title>Test for urgent-start on Fetch and XHR</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet"
+ type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<img id="image"></body>
+<audio autoplay id="audio"></audio>
+<iframe id="iframe"></iframe>
+<input type="image" id="input"></input>
+<embed id="embed"></embed>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+const topic_request = "http-on-opening-request";
+const topic_response = "http-on-examine-response";
+const topic_cachedResponse = "http-on-examine-cached-response";
+const scope = "http://mochi.test:8888/chrome/dom/base/test/"
+const url = scope + "file_empty.html";
+
+let expectedResults = [];
+let testcases = [
+ "fetch",
+ "src-embed",
+ "src-img",
+ "src-input",
+ "src-media",
+ "xhr",
+];
+let testcase;
+
+function isUrgentStart(aClassFlags) {
+ if (!aClassFlags) {
+ return false;
+ }
+
+ const urgentStartFlag = 1 << 6;
+ return !!(urgentStartFlag & aClassFlags);
+}
+
+// Test for setTimeout (task)
+function testSetTimeout() {
+ return new Promise(aResolve =>
+ setTimeout(function() {
+ testSimple().then(aResolve);
+ }, 0));
+}
+
+// Test for promise chain (micro-task)
+function testPromise() {
+ return Promise.resolve().then(testSimple);
+}
+
+function testSimple() {
+ let testUrl = url + "?" + expectedResults.length;
+
+ if (testcase == "fetch") {
+ return fetch(testUrl);
+ } else if (testcase == "src-embed") {
+ document.getElementById('embed').src = testUrl;
+ return Promise.resolve();
+ } else if (testcase == "src-img") {
+ document.getElementById('image').src = testUrl;
+ return Promise.resolve();
+ } else if (testcase == "src-input") {
+ document.getElementById('input').src = testUrl;
+ return Promise.resolve();
+ } else if (testcase == "src-media") {
+ document.getElementById('audio').src = testUrl;
+ return Promise.resolve();
+ } else if (testcase == "xhr") {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", testUrl, true);
+ xhr.send(null);
+ return Promise.resolve();
+ }
+
+ ok(false, "Shouldn't go here.");
+}
+
+function sendRequsetAndCheckUrgentStart(aEventToTest) {
+ info("SendRequsetAndCheckUrgentStart");
+
+ let promise1, promise2;
+ let promise1_resolve, promise2_resolve;
+
+ function checkUrgentStart(aSubject) {
+ var channel = aSubject.QueryInterface(Ci.nsIChannel);
+ if (!channel.URI.spec.includes(scope) ) {
+ return;
+ }
+
+ info("CheckUrgentStart");
+
+ let cos = channel.QueryInterface(Ci.nsIClassOfService);
+
+ let expectedResult = expectedResults.shift();
+ is(isUrgentStart(cos.classFlags), expectedResult,
+ "Expect get: " + expectedResult + ", get: " +
+ isUrgentStart(cos.classFlags) + " in the " +
+ (9 - expectedResults.length) + " test of " + testcase);
+
+ // Make sure we've run the check.
+ promise1_resolve();
+ }
+
+ // Resolve this after we've gotten response to prevent from sending too many
+ // requests to Necko in a short time.
+ function getResponse(aSubject) {
+ var channel = aSubject.QueryInterface(Ci.nsIChannel);
+ if (!channel.URI.spec.includes(scope) ) {
+ return;
+ }
+ info("GetResponse");
+
+ promise2_resolve();
+ }
+
+ SpecialPowers.addObserver(checkUrgentStart, topic_request);
+ SpecialPowers.addObserver(getResponse, topic_response);
+ SpecialPowers.addObserver(getResponse, topic_cachedResponse);
+
+ return Promise.resolve()
+ .then(() => {
+ promise1 = new Promise(aResolve => { promise1_resolve = aResolve; });
+ promise2 = new Promise(aResolve => { promise2_resolve = aResolve; });
+ return Promise.all([addListenerAndSendEvent(testSimple, aEventToTest),
+ promise1,
+ promise2]);
+ })
+ .then(() => {
+ promise1 = new Promise(aResolve => { promise1_resolve = aResolve; });
+ promise2 = new Promise(aResolve => { promise2_resolve = aResolve; });
+ return Promise.all([addListenerAndSendEvent(testSetTimeout, aEventToTest),
+ promise1,
+ promise2]);
+ })
+ .then(() => {
+ promise1 = new Promise(aResolve => { promise1_resolve = aResolve; });
+ promise2 = new Promise(aResolve => { promise2_resolve = aResolve; });
+ return Promise.all([addListenerAndSendEvent(testPromise, aEventToTest),
+ promise1,
+ promise2]);
+ })
+ .then(() => {
+ // remove obs if we've tested each three conditions
+ // (simple, promise, setTimeout).
+ SpecialPowers.removeObserver(checkUrgentStart, topic_request);
+ SpecialPowers.removeObserver(getResponse,topic_response);
+ SpecialPowers.removeObserver(getResponse, topic_cachedResponse);
+ return Promise.resolve();
+ });
+}
+
+function addListenerAndSendEvent(aFunction, aEventToTest) {
+ info("AddListenerAndSendEvent:" + aEventToTest);
+
+ let eventHandle = function () {
+ return aFunction();
+ };
+
+ if (aEventToTest === TestEvent.USER_INPUT_EVENT) {
+ // User Input Event
+ window.addEventListener("mousedown", eventHandle, {once: true});
+ } else if (aEventToTest === TestEvent.NONUSER_INPUT_EVENT) {
+ window.addEventListener("message", eventHandle, {once: true});
+ }
+
+ if (aEventToTest === TestEvent.USER_INPUT_EVENT) {
+ // User Input Event
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
+ } else if (aEventToTest === TestEvent.NONUSER_INPUT_EVENT) {
+ window.postMessage("hello", "*");
+ } else if (aEventToTest === TestEvent.NOEVENT) {
+ eventHandle();
+ }
+}
+
+const TestEvent = {
+ NOEVENT: 0,
+ USER_INPUT_EVENT: 1,
+ NONUSER_INPUT_EVENT: 2,
+};
+
+function executeTest() {
+ is(expectedResults.length, 0, "expectedResults should be 0 be executeTest.");
+
+ // We will test fetch first and then xhr.
+ testcase = testcases.shift();
+ info("Verify " + testcase);
+
+ expectedResults = [
+ /* SimpleTest without any events */ false,
+ /* PromiseTest without any events */ false,
+ /* SetTimeoutTest without any events */ false,
+ /* SimpleTest with a user input event */ true,
+ /* PromiseTest with a user input event */ false,
+ /* SetTimeoutTest with user input event */ false,
+ /* SimpleTest with a non user input event */ false,
+ /* PromiseTest with a non user input event */ false,
+ /* SetTimeoutTest with a non user input event */ false,
+ ];
+
+ return Promise.resolve()
+ // Verify urgent-start is not set when the request is not triggered by any
+ // events.
+ .then(() => sendRequsetAndCheckUrgentStart(TestEvent.NOEVENT))
+
+ // Verify urgent-start is set only when the request is triggered by a user
+ // input event. (not for another microtask (e.g. promise-chain) and
+ // task (e.g. setTimeout)).
+ .then(() => sendRequsetAndCheckUrgentStart(TestEvent.USER_INPUT_EVENT))
+
+ // Verify urgent-start is not set when the request is triggered by a non user
+ // input event.
+ .then(() => sendRequsetAndCheckUrgentStart(TestEvent.NONUSER_INPUT_EVENT))
+ .then(_ => {
+ if (testcases.length !== 0) {
+ // Run the other test if we still have tests needed to be run.
+ return executeTest();
+ }
+
+ return Promise.resolve();
+ });
+}
+
+function endCheck() {
+ info("End Check: make sure that we've done all the tests.");
+
+ is(testcases.length, 0, "All the tests should be executed.");
+ is(expectedResults.length, 0, "All the tests should be executed.");
+
+ return Promise.resolve();
+}
+
+function runTest() {
+ return SpecialPowers.pushPrefEnv({"set": [["network.http.rcwn.enabled", false]]})
+ .then(executeTest)
+ .then(endCheck)
+ .catch(aError => ok(false, "Some test failed with error " + aError))
+ .then(SimpleTest.finish);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_user_select.html b/dom/base/test/test_user_select.html
new file mode 100644
index 0000000000..343772fbf7
--- /dev/null
+++ b/dom/base/test/test_user_select.html
@@ -0,0 +1,357 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>user-select selection tests</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+body { font-family: Ahem; font-size: 20px; }
+s, .non-selectable { user-select: none; }
+n { display: none; }
+a { position:absolute; bottom: 0; right:0; }
+.text { user-select: text; }
+</style>
+
+</head>
+<body>
+
+<div id="test1">aaaaaaa<s>bbbbbbbb</s>ccccccc</div>
+<div id="test2"><s>aaaaaaa</s>bbbbbbbbccccccc</div>
+<div id="test3">aaaaaaabbbbbbbb<s>ccccccc</s></div>
+<div id="test4">aaaaaaa<x><s>bbbbbbbb</s></x>ccccccc</div>
+<div id="test5"><x><s>aaaaaaa</s></x>bbbbbbbbccccccc</div>
+<div id="test6">aaaaaaabbbbbbbb<x><s>ccccccc</s></x></div>
+<div id="test7">aaaaaaa<x><s><n>bbbb</n>bbbb</s></x>ccccccc</div>
+<div id="test8"><x><s>aa<n>aaa</n>aa</s></x>bbbbbbbbccccccc</div>
+<div id="test9">aaaaaaabbbbbbbb<x><s>cc<n>ccccc</n></s></x></div>
+<div id="testA">aaaaaaa<n>bbb<s>bbbbb</s></n>ccccccc</div>
+<div id="testB"><n><s>aaaa</s>aaa</n>bbbbbbbbccccccc</div>
+<div id="testC">aaaaaaabbbbbbbb<n>cc<s>c</s>cccc</n></div>
+<div id="testE">aaa<s id="testEc1">aaaa<a class="text">bbbb</a>dd<a>cccc</a>ddddddd</s>eeee</div>
+<div id="testI">aaa<span contenteditable="true" spellcheck="false">bbb</span><s>ccc</s>ddd</div>
+<div id="testF">aaaa
+<div class="non-selectable">x</div>
+<div class="non-selectable">x</div>
+<div class="non-selectable">x</div>
+bbbb</div>
+<div id="testG" style="white-space:pre">aaaa
+<div class="non-selectable">x</div>
+<div class="non-selectable">x</div>
+<div class="non-selectable">x</div>
+bbbb</div>
+<div id="testH" style="white-space:pre">aaaa
+<div class="non-selectable">x</div><input>
+bbbbbbb</div>
+
+<iframe id="testD" srcdoc="<body>aaaa<span style='user-select:none'>bbbb</span>cccc"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function test()
+{
+ function clear(w)
+ {
+ var sel = (w ? w : window).getSelection();
+ sel.removeAllRanges();
+ }
+ function doneTest(e)
+ {
+ // We hide the elements we're done with so that later tests
+ // are inside the rather narrow iframe mochitest gives us.
+ // It matters for synthesizeMouse event tests.
+ e.style.display = 'none';
+ e.offsetHeight;
+ }
+
+ function dragSelect(e, x1, x2, x3)
+ {
+ dir = x2 > x1 ? 1 : -1;
+ synthesizeMouse(e, x1, 5, { type: "mousedown" });
+ synthesizeMouse(e, x1 + dir, 5, { type: "mousemove" });
+ if (x3)
+ synthesizeMouse(e, x3, 5, { type: "mousemove" });
+ synthesizeMouse(e, x2 - dir, 5, { type: "mousemove" });
+ synthesizeMouse(e, x2, 5, { type: "mouseup" });
+ }
+
+ function shiftClick(e, x)
+ {
+ synthesizeMouse(e, x, 5, { type: "mousedown", shiftKey: true });
+ synthesizeMouse(e, x, 5, { type: "mouseup", shiftKey: true });
+ }
+
+ function init(arr, e)
+ {
+ clear();
+ var sel = window.getSelection();
+ for (i = 0; i < arr.length; ++i) {
+ var data = arr[i];
+ var r = new Range()
+ r.setStart(node(e, data[0]), data[1]);
+ r.setEnd(node(e, data[2]), data[3]);
+ sel.addRange(r);
+ }
+ }
+
+ function NL(s) { return s.replace(/(\r\n|\n\r|\r)/g, '\n'); }
+
+ function checkText(text, e)
+ {
+ var sel = window.getSelection();
+ is(NL(sel.toString()), text, e.id + ": selected text")
+ }
+
+ function checkRangeText(text, index)
+ {
+ var r = window.getSelection().getRangeAt(index);
+ is(NL(r.toString()), text, e.id + ": range["+index+"].toString()")
+ }
+
+ function node(e, arg)
+ {
+ if (typeof arg == "number")
+ return arg == -1 ? e : e.childNodes[arg];
+ return arg;
+ }
+
+ function checkRangeCount(n, e)
+ {
+ var sel = window.getSelection();
+ is(sel.rangeCount, n, e.id + ": Selection range count");
+ }
+
+ function checkRange(i, expected, e) {
+ var sel = window.getSelection();
+ var r = sel.getRangeAt(i);
+ is(r.startContainer, node(e, expected[0]), e.id + ": range["+i+"].startContainer");
+ is(r.startOffset, expected[1], e.id + ": range["+i+"].startOffset");
+ is(r.endContainer, node(e, expected[2]), e.id + ": range["+i+"].endContainer");
+ is(r.endOffset, expected[3], e.id + ": range["+i+"].endOffset");
+ }
+
+ function checkRanges(arr, e)
+ {
+ checkRangeCount(arr.length, e);
+ for (i = 0; i < arr.length; ++i) {
+ var expected = arr[i];
+ checkRange(i, expected, e);
+ }
+ }
+
+ // ======================================================
+ // ================== dragSelect tests ==================
+ // ======================================================
+
+ var e = document.getElementById('test1');
+ dragSelect(e, 20, 340);
+ checkText('aaaaaacc', e);
+ checkRanges([[0,1,-1,1], [2,0,2,2]], e);
+
+ clear();
+ dragSelect(e, 20, 260, 120);
+ checkText('aaaaa', e);
+ checkRanges([[0,1,0,6]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test2');
+ dragSelect(e, 20, 340);
+ checkText('', e);
+ checkRanges([], e);
+
+ clear();
+ dragSelect(e, 340, 20, 141);
+ checkText('bbbbbbbbcc', e);
+ checkRanges([[1,0,1,10]], e);
+ // #test2 is used again below
+
+ clear();
+ e = document.getElementById('test3');
+ dragSelect(e, 20, 340, 295);
+ checkText('aaaaaabbbbbbbb', e);
+ checkRanges([[0,1,0,15]], e);
+ // #test3 is used again below
+
+ clear();
+ e = document.getElementById('test4');
+ dragSelect(e, 20, 340);
+ checkText('aaaaaacc', e);
+ checkRanges([[0,1,1,0], [2,0,2,2]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test5');
+ dragSelect(e, 340, 20, 141);
+ checkText('bbbbbbbbcc', e);
+ checkRanges([[1,0,1,10]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test6');
+ dragSelect(e, 20, 340, 295);
+ checkText('aaaaaabbbbbbbb', e);
+ checkRanges([[0,1,0,15]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test7');
+ dragSelect(e, 20, 340);
+ checkText('aaaaaacccccc', e);
+ checkRanges([[0,1,1,0], [2,0,2,6]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test8');
+ dragSelect(e, 340, 20, 140);
+ checkText('bbbbbccccc', e);
+ checkRanges([[1,3,1,13]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('test9');
+ dragSelect(e, 20, 340, 295);
+ checkText('aaaaaabbbbbbbb', e);
+ checkRanges([[0,1,0,15]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testA');
+ dragSelect(e, 20, 340);
+ checkText('aaaaaaccccccc', e);
+ checkRanges([[0,1,2,7]], e);
+ checkRangeText('aaaaaabbbbbbbbccccccc', 0);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testB');
+ dragSelect(e, 340, 20, 140);
+ checkText('bbbbbbbccccccc', e);
+ checkRanges([[1,1,1,15]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testE');
+ dragSelect(e, 20, 360, 295);
+ checkText('aa\nbbbb\nee', e);
+ checkRangeCount(3, e);
+ checkRange(0, [0,1,-1,1], e);
+ checkRange(1, [1,0,-1,2], e.children[0]);
+ checkRange(2, [2,0,2,2], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testI');
+ dragSelect(e, 200, 80);
+ checkText('bbd', e);
+ checkRangeCount(2, e);
+ doneTest(e);
+
+ // ======================================================
+ // ================== shift+click tests =================
+ // ======================================================
+
+ // test extending a selection that starts in a -moz-user-select:none node
+ clear();
+ e = document.getElementById('test2');
+ init([[0,0,0,1]], e);
+ checkRangeText('aaaaaaa', 0);
+ checkText('', e);
+ shiftClick(e, 340);
+ checkRangeText('bbbbbbbbcc', 0);
+ checkText('bbbbbbbbcc', e);
+ checkRanges([[-1,1,1,10]], e);
+ doneTest(e);
+
+ // test extending a selection that end in a -moz-user-select:none node
+ clear();
+ e = document.getElementById('test3');
+ init([[1,0,1,1]], e);
+ checkRangeText('ccccccc', 0);
+ checkText('', e);
+ shiftClick(e, 20);
+ checkRangeText('aaaaaabbbbbbbb', 0);
+ checkText('aaaaaabbbbbbbb', e);
+ checkRanges([[0,1,-1,1]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testF');
+ synthesizeMouse(e, 1, 1, {});
+ synthesizeMouse(e, 400, 100, { shiftKey: true });
+ checkText("aaaa bbbb", e);
+ checkRanges([[0,0,-1,1],[6,0,6,5]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testG');
+ synthesizeMouse(e, 1, 1, {});
+ synthesizeMouse(e, 400, 180, { shiftKey: true });
+ checkText("aaaa\n\n\n\nbbbb", e);
+ checkRanges([[0,0,-1,1],[2,0,-1,3],[4,0,-1,5],[6,0,6,5]], e);
+ doneTest(e);
+
+ clear();
+ e = document.getElementById('testH');
+ synthesizeMouse(e, 1, 1, {});
+ synthesizeMouse(e, 30, 90, { shiftKey: true });
+ synthesizeMouse(e, 50, 90, { shiftKey: true });
+ synthesizeMouse(e, 70, 90, { shiftKey: true });
+ checkText("aaaa\n\nbbb", e);
+ checkRanges([[0,0,-1,1],[-1,2,3,4]], e);
+
+ doneTest(e);
+ // ======================================================
+ // ==================== Script tests ====================
+ // ======================================================
+
+ clear();
+ e = document.getElementById('testD');
+ clear(e.contentWindow);
+ sel = e.contentWindow.getSelection();
+ sel.selectAllChildren(e.contentDocument.body);
+ is(window.getSelection().rangeCount, 0, "testD: no selection in outer window");
+ is(sel.toString(), 'aaaacccc', "testD: scripted selection");
+ is(sel.rangeCount, 1, "testD: scripted selection isn't filtered");
+ is(sel.getRangeAt(0).toString(), 'aaaabbbbcccc', "testD: scripted selection isn't filtered");
+
+ // ======================================================
+ // ================== Kbd command tests =================
+ // ======================================================
+
+ clear();
+ e = document.getElementById('testD');
+ clear(e.contentWindow);
+ e.contentWindow.focus();
+ synthesizeKey("a", { accelKey:true }, e.contentWindow);
+ sel = e.contentWindow.getSelection();
+ is(window.getSelection().rangeCount, 0, "testD: no selection in outer window");
+ is(sel.toString(), 'aaaacccc', "testD: kbd selection");
+ is(sel.rangeCount, 2, "testD: kbd selection is filtered");
+ is(sel.getRangeAt(0).toString(), 'aaaa', "testD: kbd selection is filtered");
+ is(sel.getRangeAt(1).toString(), 'cccc', "testD: kbd selection is filtered");
+ doneTest(e);
+
+ clear();
+ SimpleTest.finish();
+}
+
+// These tests depends on the Ahem font being loaded and rendered so wait for
+// font to load, then wait a frame for them to be rendered too.
+window.onload = function() {
+ document.fonts.ready.then(function() {
+ requestAnimationFrame(test);
+ });
+};
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_viewport_metrics_on_landscape_content.html b/dom/base/test/test_viewport_metrics_on_landscape_content.html
new file mode 100644
index 0000000000..ec3cfec473
--- /dev/null
+++ b/dom/base/test/test_viewport_metrics_on_landscape_content.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script>
+'use strict';
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.meta-viewport.enabled", true],
+ ]
+}, () => {
+ // We need to open a new window to avoid running tests in an iframe since
+ // we want to test metrics on the root document.
+ window.open("file_viewport_metrics_on_landscape_content.html");
+});
+</script>
diff --git a/dom/base/test/test_viewport_scroll.html b/dom/base/test/test_viewport_scroll.html
new file mode 100644
index 0000000000..90c2e85355
--- /dev/null
+++ b/dom/base/test/test_viewport_scroll.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for mapping of scrollTop/scrollLeft to viewport</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="doTest()">
+<p id="display"></p>
+
+<iframe id="quirks"></iframe>
+<iframe id="standards"></iframe>
+<iframe id="xml"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var quirks = document.getElementById("quirks");
+var standards = document.getElementById("standards");
+var xml = document.getElementById("xml");
+
+quirks.src = "file_viewport_scroll_quirks.html";
+standards.srcdoc = "<!DOCTYPE HTML><html><body style='height:2000px; width:2000px;'>";
+xml.src = "file_viewport_scroll_xml.xml";
+
+function subtest(winProp, elemProp, win, correctElement, elemToSet, otherElem1, otherElem2) {
+ win.scrollTo(50, 50);
+ elemToSet[elemProp] = 100;
+ if (elemToSet == correctElement) {
+ is(Math.round(win[winProp]), 100, "Setting " + elemToSet.name + "." + elemProp + " should scroll");
+ is(elemToSet[elemProp], 100, "Reading back " + elemToSet.name + "." + elemProp + " after scrolling");
+ } else {
+ is(Math.round(win[winProp]), 50, "Setting " + elemToSet.name + "." + elemProp + " should not scroll");
+ is(elemToSet[elemProp], 0, "Reading back " + elemToSet.name + "." + elemProp + " after not scrolling");
+ }
+ if (otherElem1 == correctElement) {
+ is(otherElem1[elemProp], 50, "Reading back " + otherElem1.name + "." + elemProp + " (correct element) after not scrolling");
+ } else {
+ is(otherElem1[elemProp], 0, "Reading back " + otherElem1.name + "." + elemProp + " (irrelevant element)");
+ }
+ if (otherElem2 == correctElement) {
+ is(otherElem2[elemProp], 50, "Reading back " + otherElem2.name + "." + elemProp + " (correct element) after not scrolling");
+ } else {
+ is(otherElem2[elemProp], 0, "Reading back " + otherElem2.name + "." + elemProp + " (irrelevant element)");
+ }
+}
+
+function testScroll(winProp, elemProp, win, elemToSet, otherElem1, otherElem2) {
+ subtest(winProp, elemProp, win, elemToSet, elemToSet, otherElem1, otherElem2);
+ subtest(winProp, elemProp, win, elemToSet, otherElem1, elemToSet, otherElem2);
+ subtest(winProp, elemProp, win, elemToSet, otherElem2, elemToSet, otherElem1);
+}
+
+function doTest() {
+ var quirksRoot = quirks.contentDocument.documentElement;
+ quirksRoot.name = "quirks HTML";
+ var quirksBody = quirks.contentDocument.body;
+ quirksBody.name = "quirks BODY";
+ var quirksBody2 = quirks.contentDocument.createElement("body");
+ quirksBody2.name = "quirks other BODY";
+ quirksRoot.appendChild(quirksBody2);
+ testScroll("scrollX", "scrollLeft", quirks.contentWindow, quirksBody, quirksRoot, quirksBody2);
+ testScroll("scrollY", "scrollTop", quirks.contentWindow, quirksBody, quirksRoot, quirksBody2);
+
+ var standardsRoot = standards.contentDocument.documentElement;
+ standardsRoot.name = "standards HTML";
+ var standardsBody = standards.contentDocument.body;
+ standardsBody.name = "standards BODY";
+ var standardsBody2 = standards.contentDocument.createElement("body");
+ standardsBody2.name = "standards other BODY";
+ standardsRoot.appendChild(standardsBody2);
+ testScroll("scrollX", "scrollLeft", standards.contentWindow, standardsRoot, standardsBody, standardsBody2);
+ testScroll("scrollY", "scrollTop", standards.contentWindow, standardsRoot, standardsBody, standardsBody2);
+
+ var xmlRoot = xml.contentDocument.documentElement;
+ xmlRoot.name = "XML root";
+ var xmlOther = xmlRoot.firstChild;
+ xmlOther.name = "XML other";
+ testScroll("scrollX", "scrollLeft", xml.contentWindow, xmlRoot, xmlOther, xmlOther);
+ testScroll("scrollY", "scrollTop", xml.contentWindow, xmlRoot, xmlOther, xmlOther);
+
+ SimpleTest.finish();
+}
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_viewsource_forbidden_in_object.html b/dom/base/test/test_viewsource_forbidden_in_object.html
new file mode 100644
index 0000000000..ee48b2f051
--- /dev/null
+++ b/dom/base/test/test_viewsource_forbidden_in_object.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=973837
+-->
+<head>
+<meta charset="utf-8">
+<title>Tests for Bug 973837</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ const OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+
+ function runObjectURITest(testCase) {
+ var testObject = document.getElementById("testObject");
+ testObject.data = testCase.URI;
+
+ testObject instanceof OBJLC;
+ testObject = SpecialPowers.wrap(testObject);
+
+ is(testObject.displayedType, OBJLC.TYPE_NULL, testCase.desc +
+ " testObject.displayedType should be TYPE_NULL (4)");
+ runNextTest();
+ }
+
+ var testCaseIndex = -1;
+ testCases = [
+ {
+ desc: "Test 1: view-source should not be allowed in an object.",
+ URI: "view-source:file_general_document.html"
+ },
+ {
+ desc: "Test 2: jar:view-source should not be allowed in an object",
+ URI: "jar:view-source:file_general_document.html/!/"
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runObjectURITest(testCases[testCaseIndex]);
+ }
+
+ addLoadEvent(runNextTest);
+</script>
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=973837">Mozilla Bug 973837</a>
+<p id="display"></p>
+
+<object id="testObject"></object>
+
+</body>
+</html>
diff --git a/dom/base/test/test_w3element_traversal.html b/dom/base/test/test_w3element_traversal.html
new file mode 100644
index 0000000000..efbcc41a89
--- /dev/null
+++ b/dom/base/test/test_w3element_traversal.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>W3 Tests for Element Traversal - HTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <p id="parentEl_count">
+ <span id="first_element_child_count">
+ <span></span>
+ <span></span>
+ </span>
+ <span id="middle_element_child_count"></span>
+ <span id="last_element_child_count"></span>
+ </p>
+
+
+ <p id="parentEl_nochild">
+ </p>
+
+ <p id="parentEl_null">
+ </p>
+
+ <p id="parentEl_dynamicadd">
+ <span id="first_emement_child_add"></span>
+ </p>
+
+ <p id="parentEl_dynamicremove">
+ <span id="first_emement_child_remove"></span>
+ <span id="last_emement_child_remove"></span>
+ </p>
+
+
+ <p id="parentEl_fec">
+ <span id="first_element_child_fec"></span>
+ </p>
+
+ <p id="parentEl_lec">
+ <span id="first_element_child_lec"></span>
+ <span id="last_element_child_lec"></span>
+ </p>
+
+ <p id="parentEl_namespace">
+ <pickle:span id="first_element_child_namespace"></pickle:span>
+ </p>
+
+ <p id="parentEl_nes">
+ <span id="first_element_child_nes"></span>
+ <span id="last_element_child_nes"></span>
+ </p>
+
+ <p id="parentEl_pes">
+ <span id="first_element_child_pes"></span>
+ <span id="middle_element_child_pes"></span>
+ <span id="last_element_child_pes"></span>
+ </p>
+
+ <p id="parentEl_sibnull">
+ <span id="first_element_child_sibnull"></span>
+ </p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+
+function runTest() {
+
+ //from et-childElementCount.html
+ var parentEl = document.getElementById("parentEl_count");
+ is(parentEl.childElementCount && 3, parentEl.childElementCount, "Child Element Count is mismatched");
+
+ //from et-childElementCount-nochild.html
+ var parentEl_nochild = document.getElementById("parentEl_nochild");
+ is(parentEl_nochild.childElementCount, 0, "Child Element count is not 0");
+
+ //from et-childElementCount-null.html
+ parentEl = document.getElementById("parentEl_null");
+ is(null == parentEl.firstElementChild, null == parentEl.lastElementChild, "firstElementChild or lastElementChild is not null");
+
+ //from et-dynamic-add.html
+ parentEl = document.getElementById("parentEl_dynamicadd");
+ var newChild = document.createElement("span")
+ parentEl.appendChild( newChild );
+ is(parentEl.childElementCount && 2, parentEl.childElementCount, "failed to add span element");
+
+ //from et-dynamic-remove.html
+ parentEl = document.getElementById("parentEl_dynamicremove");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild( lec );
+ is(parentEl.childElementCount && 1, parentEl.childElementCount, "failed to remove span element");
+
+ //from et-firstElementChild.html
+ parentEl = document.getElementById("parentEl_fec");
+ var fec = parentEl.firstElementChild;
+ is(fec.nodeType, 1, "failed to get firstElementChild");
+ is(fec.getAttribute("id"), "first_element_child_fec", "failed to get firstElementChild");
+ isnot(fec, null, "failed to get firstElementChild");
+
+ //from et-lastElementChild.html
+ parentEl = document.getElementById("parentEl_lec");
+ var lec = parentEl.lastElementChild;
+ is(lec.nodeType, 1, "failed to get lastElementChild");
+ is(lec.getAttribute("id"), "last_element_child_lec", "failed to get lastElementChild");
+ isnot(lec, null, "failed to get lastElementChild");
+
+ //from et-namespace.html
+ parentEl = document.getElementById("parentEl_namespace");
+ var fec = parentEl.firstElementChild;
+ isnot(fec, null, "failed to get firstElementChild in namespace");
+ is(fec.getAttribute("id"), "first_element_child_namespace", "failed to get firstElementChild in namespace");
+
+ //from et-nextElementSibling.html
+ parentEl = document.getElementById("parentEl_nes");
+ var fec = parentEl.firstElementChild;
+ var nes = fec.nextElementSibling;
+ is(nes.nodeType, 1, "failed to get nextElementSibling");
+ is(nes.getAttribute("id"), "last_element_child_nes", "failed to get nextElementSibling");
+ isnot(nes, null, "failed to get nextElementSibling");
+
+ //from et-previousElementSibling.html
+ var lec = document.getElementById("last_element_child_pes");
+ var pes = lec.previousElementSibling;
+ is(pes.nodeType, 1, "failed to get previousElementSibling");
+ is(pes.getAttribute("id"), "middle_element_child_pes", "failed to get previousElementSibling");
+ isnot(pes, null, "failed to get previousElementSibling");
+
+ //from et-siblingElement-null.html
+ var fec = document.getElementById("first_element_child_sibnull");
+ var pes = fec.previousElementSibling;
+ var nes = fec.nextElementSibling;
+ is(pes, null, "got unexpected previousElementSibling");
+ is(nes, null, "got unexpected nextElementSibling");
+
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+addLoadEvent(SimpleTest.finish)
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_w3element_traversal.xhtml b/dom/base/test/test_w3element_traversal.xhtml
new file mode 100644
index 0000000000..334d184304
--- /dev/null
+++ b/dom/base/test/test_w3element_traversal.xhtml
@@ -0,0 +1,149 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:pickle="http://ns.example.org/pickle" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>W3 Tests for Element Traversal - XHTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <p id="parentEl_count">
+ <span id="first_element_child_count">
+ <span></span>
+ <span></span>
+ </span>
+ <span id="middle_element_child_count"></span>
+ <span id="last_element_child_count"></span>
+ </p>
+
+
+ <p id="parentEl_nochild">
+ </p>
+
+ <p id="parentEl_null">
+ </p>
+
+ <p id="parentEl_dynamicadd">
+ <span id="first_emement_child_add"></span>
+ </p>
+
+ <p id="parentEl_dynamicremove">
+ <span id="first_emement_child_remove"></span>
+ <span id="last_emement_child_remove"></span>
+ </p>
+
+
+ <p id="parentEl_fec">
+ <span id="first_element_child_fec"></span>
+ </p>
+
+ <p id="parentEl_lec">
+ <span id="first_element_child_lec"></span>
+ <span id="last_element_child_lec"></span>
+ </p>
+
+ <div id="parentEl_namespace">
+ <pickle:dill />
+ </div>
+
+ <p id="parentEl_nes">
+ <span id="first_element_child_nes"></span>
+ <span id="last_element_child_nes"></span>
+ </p>
+
+ <p id="parentEl_pes">
+ <span id="first_element_child_pes"></span>
+ <span id="middle_element_child_pes"></span>
+ <span id="last_element_child_pes"></span>
+ </p>
+
+ <p id="parentEl_sibnull">
+ <span id="first_element_child_sibnull"></span>
+ </p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+
+
+function runTest() {
+
+ //from et-childElementCount.html
+ var parentEl = document.getElementById("parentEl_count");
+ is(parentEl.childElementCount && 3, parentEl.childElementCount, "Child Element Count is mismatched");
+
+ //from et-childElementCount-nochild.html
+ var parentEl_nochild = document.getElementById("parentEl_nochild");
+ is(parentEl_nochild.childElementCount, 0, "Child Element count is not 0");
+
+ //from et-childElementCount-null.html
+ parentEl = document.getElementById("parentEl_null");
+ is(null == parentEl.firstElementChild, null == parentEl.lastElementChild, "firstElementChild or lastElementChild is not null");
+
+ //from et-dynamic-add.html
+ parentEl = document.getElementById("parentEl_dynamicadd");
+ var newChild = document.createElement("span")
+ parentEl.appendChild( newChild );
+ is(parentEl.childElementCount && 2, parentEl.childElementCount, "failed to add span element");
+
+ //from et-dynamic-remove.html
+ parentEl = document.getElementById("parentEl_dynamicremove");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild( lec );
+ is(parentEl.childElementCount && 1, parentEl.childElementCount, "failed to remove span element");
+
+ //from et-firstElementChild.html
+ parentEl = document.getElementById("parentEl_fec");
+ var fec = parentEl.firstElementChild;
+ is(fec.nodeType, 1, "failed to get firstElementChild");
+ is(fec.getAttribute("id"), "first_element_child_fec", "failed to get firstElementChild");
+ isnot(fec, null, "failed to get firstElementChild");
+
+ //from et-lastElementChild.html
+ parentEl = document.getElementById("parentEl_lec");
+ var lec = parentEl.lastElementChild;
+ is(lec.nodeType, 1, "failed to get lastElementChild");
+ is(lec.getAttribute("id"), "last_element_child_lec", "failed to get lastElementChild");
+ isnot(lec, null, "failed to get lastElementChild");
+
+ //from et-namespace.html
+ parentEl = document.getElementById("parentEl_namespace");
+ var nChild = parentEl.firstElementChild;
+ is(nChild && "dill", nChild.localName, "failed to get a namespace element");
+
+
+ //from et-nextElementSibling.html
+ parentEl = document.getElementById("parentEl_nes");
+ var fec = parentEl.firstElementChild;
+ var nes = fec.nextElementSibling;
+ is(nes.nodeType, 1, "failed to get nextElementSibling");
+ is(nes.getAttribute("id"), "last_element_child_nes", "failed to get nextElementSibling");
+ isnot(nes, null, "failed to get nextElementSibling");
+
+ //from et-previousElementSibling.html
+ var lec = document.getElementById("last_element_child_pes");
+ var pes = lec.previousElementSibling;
+ is(pes.nodeType, 1, "failed to get previousElementSibling");
+ is(pes.getAttribute("id"), "middle_element_child_pes", "failed to get previousElementSibling");
+ isnot(pes, null, "failed to get previousElementSibling");
+
+ //from et-siblingElement-null.html
+ var fec = document.getElementById("first_element_child_sibnull");
+ var pes = fec.previousElementSibling;
+ var nes = fec.nextElementSibling;
+ is(pes, null, "got unexpected previousElementSibling");
+ is(nes, null, "got unexpected nextElementSibling");
+
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+addLoadEvent(SimpleTest.finish)
+]]></script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_w3element_traversal_svg.html b/dom/base/test/test_w3element_traversal_svg.html
new file mode 100644
index 0000000000..596200b424
--- /dev/null
+++ b/dom/base/test/test_w3element_traversal_svg.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for ElementTraversal via SVG</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<iframe id="svg" src="w3element_traversal.svg"></iframe>
+
+<pre id="test">
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ var doc = $("svg").contentDocument;
+
+ //et-namespace.svg
+ var parentEl = doc.getElementById("parentEl_namespace");
+ var nChild = parentEl.firstElementChild;
+ is(nChild && "dill", nChild.localName, "failed to get child with namespace")
+
+ //et-previousElementSibling.svg
+ var lec = doc.getElementById("last_element_child_pes");
+ var pes = lec.previousElementSibling;
+ isnot(pes, null, "previousElementSibling is null");
+ is(pes.nodeType, 1, "previousElementSibling returned the wrong node type");
+ is(pes.getAttribute("id"), "middle_element_child_pes", "previousElementSibling returned the wrong child");
+
+ //et-sibling_null.svg
+ var fec = doc.getElementById("first_element_child_sibnull");
+ var pes = fec.previousElementSibling;
+ var nes = fec.nextElementSibling;
+ is(pes, null, "previousElementSibling is not null");
+ is(nes, null, "nextElementSibling is not null");
+
+ //et-nextElementSibling.svg
+ fec = doc.getElementById("first_element_child_nes");
+ var nes = fec.nextElementSibling;
+ isnot(nes, null, "nextElementSibling returned NULL");
+ is(nes.nodeType, 1, "nextElementSibling returned wrong node type");
+ is(nes.getAttribute("id"), "last_element_child_nes", "nextElementSibling returned wrong node id");
+
+ //et-lastElementChild.svg
+ var parentEl = doc.getElementById("parentEl_lec");
+ var lec = parentEl.lastElementChild;
+ isnot(lec, null, "lastElementChild returned null");
+ is(lec.nodeType, 1, "lastElementChild returned wrong nodeType");
+ is(lec.getAttribute("id"), "last_element_child_lec", "lastElementChild returned wrong id");
+
+ //et-firstElementChild.svg
+ var parentEl = doc.getElementById("parentEl_fec");
+ var fec = parentEl.firstElementChild;
+ isnot(fec, null, "firstElementChild returned null");
+ is(fec.nodeType, 1, "firstElementChild returned wrong nodeType");
+ is(fec.getAttribute("id"), "first_element_child_fec", "firstElementChild returned wrong id");
+
+ //et-entity.svg
+ var parentEl = doc.getElementById("parentEl_entity");
+ var fec = parentEl.firstElementChild;
+ isnot(fec, null, "firstElementChild returned null");
+ is(fec.nodeType, 1, "firstElementChild returned wrong nodeType");
+ is(fec.getAttribute("id"), "first_element_child_entity", "firstElementChild returned wrong id");
+
+ //et-dynamic-remove.svg
+ var parentEl = doc.getElementById("parentEl_dynremove");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild( lec );
+ is(parentEl.childElementCount && 1, parentEl.childElementCount, "failed to removeChild");
+
+ //et-dynamic-add.svg
+ var parentEl = doc.getElementById("parentEl_dynadd");
+ var newChild = doc.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ parentEl.appendChild( newChild );
+ is(parentEl.childElementCount && 2, parentEl.childElementCount, "failed to appendChild");
+
+ //et-childElement-null.svg
+ var parentEl = doc.getElementById("parentEl_null");
+ var fec = parentEl.firstElementChild;
+ var lec = parentEl.lastElementChild;
+ is(fec, null, "expected null from firstElementChild");
+ is(lec, null, "expected null from lastElementChild");
+
+
+ //et-childElementCount.svg
+ var parentEl = doc.getElementById("parentEl_count");
+ is(parentEl.childElementCount && 3, parentEl.childElementCount, "got wrong childElementCount");
+
+ //et-childElementCount-nochild.svg
+ var parentEl = doc.getElementById("parentEl_nochild");
+ is(0, parentEl.childElementCount, "got wrong childElementCount");
+
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_warning_for_blocked_cross_site_request.html b/dom/base/test/test_warning_for_blocked_cross_site_request.html
new file mode 100644
index 0000000000..c1e597b6ad
--- /dev/null
+++ b/dom/base/test/test_warning_for_blocked_cross_site_request.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=713980
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 713980</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <!-- Load a cross-origin webfont without CORS (common pain point) and some
+ other styles that require anonymous CORS -->
+ <style>
+ @font-face {
+ font-family: "bad_cross_origin_webfont";
+ src: url('http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
+ }
+ div#bad_webfont { font-family: "bad_cross_origin_webfont"; }
+
+ div#bad_shape_outside { shape-outside: url('http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=bad_shape_outside&type=image/png'); }
+
+ div#bad_mask_image { mask-image: url('http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=bad_mask_image&type=image/svg+xml'); }
+ </style>
+</head>
+<body>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var tests = {
+ xhr : {
+ uri_test : "http://invalid",
+ result : null,
+ category: "CORSAllowOriginNotMatchingOrigin"
+ },
+ font : {
+ uri_test : "font_bad",
+ result : null,
+ category: "CORSMissingAllowOrigin2",
+ },
+ shape_outside : {
+ uri_test : "bad_shape_outside",
+ result : null,
+ category: "CORSMissingAllowOrigin2",
+ ignore_windowID: true,
+ },
+ mask_image : {
+ uri_test : "bad_mask_image",
+ result : null,
+ category: "CORSMissingAllowOrigin2",
+ ignore_windowID: true,
+ },
+}
+
+function testsComplete() {
+ for (var testName in tests) {
+ var test = tests[testName];
+ if (test.result == null) {
+ info("Still waiting on (at least) " + testName + ".");
+ return false;
+ }
+ }
+ return true;
+}
+
+SpecialPowers.registerConsoleListener(function CORSMsgListener(aMsg) {
+ if (!/Cross-Origin Request Blocked/.test(aMsg.message))
+ return;
+
+ for (var testName in tests) {
+ var test = tests[testName];
+ var category = test.category;
+ if (test.result != null)
+ continue;
+
+ var testRegexp = new RegExp(test.uri_test);
+ if (testRegexp.test(aMsg.message)) {
+ test.result = true;
+ ok(true, "Got \"Cross-site request blocked\" warning message for " + testName);
+ ok(aMsg.category == category,
+ "Got warning message with category \"" + aMsg.category + "\", expected \"" + category + "\"");
+ // Got the message we wanted - make sure it is destined for a valid inner window
+ if(!test.ignore_windowID) {
+ ok(aMsg.windowID != 0, "Valid (non-zero) windowID for the cross-site request blocked message.");
+ }
+ break;
+ }
+ }
+
+ if (testsComplete()) {
+ SimpleTest.executeSoon(cleanup);
+ }
+});
+
+function cleanup() {
+ SpecialPowers.postConsoleSentinel();
+ SimpleTest.finish();
+}
+
+// Send a cross-origin XHR request without CORS
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?allowOrigin=http://invalid", true);
+xhr.send(null);
+
+let badDiv;
+
+// Create a div that triggers a cross-origin webfont request
+// We do this in Javascript in order to guarantee the console listener has
+// already been registered; otherwise, there could be a race.
+badDiv = document.createElement('div');
+badDiv.setAttribute('id', 'bad_webfont');
+document.body.appendChild(badDiv);
+
+// Create a div that triggers a cross-origin request for a shape-outside image
+badDiv = document.createElement('div');
+badDiv.setAttribute('id', 'bad_shape_outside');
+document.body.appendChild(badDiv);
+
+// Create a div that triggers a cross-origin request for a mask-image
+badDiv = document.createElement('div');
+badDiv.setAttribute('id', 'bad_mask_image');
+document.body.appendChild(badDiv);
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_close.html b/dom/base/test/test_window_close.html
new file mode 100644
index 0000000000..5b0d7e6fa6
--- /dev/null
+++ b/dom/base/test/test_window_close.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>test window.close()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var b = new BroadcastChannel("windowclose");
+
+ const link = "link";
+ const windowopen = "window.open()";
+ var tests = [
+ {
+ type: windowopen,
+ noopener: true,
+ shouldCloseWithoutHistory: true,
+ shouldCloseWithHistory: false
+ },
+ {
+ type: windowopen,
+ noopener: false,
+ shouldCloseWithoutHistory: true,
+ shouldCloseWithHistory: true
+ },
+ {
+ type: link,
+ noopener: true,
+ shouldCloseWithoutHistory: true,
+ shouldCloseWithHistory: false
+ },
+ {
+ type: link,
+ noopener: false,
+ shouldCloseWithoutHistory: true,
+ shouldCloseWithHistory: true
+ }
+ ];
+
+ var loadTypes = ["withouthistory", "withhistory"];
+
+ async function start() {
+ // If Fission is disabled, the pref is no-op.
+ await SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]});
+
+ for (let test of tests) {
+ await SpecialPowers.pushPrefEnv({ set: [["dom.allow_scripts_to_close_windows", false]]});
+ if (test.type == windowopen) {
+ for (let loadType of loadTypes) {
+ var features = test.noopener ? "noopener" : "";
+ window.open("file_window_close.html?" + loadType, "", features);
+ await new Promise(function(r) {
+ b.onmessage = function(e) {
+ var expectedClose = loadType == "withouthistory" ?
+ test.shouldCloseWithoutHistory : test.shouldCloseWithHistory;
+ is(e.data, expectedClose ? "closed" : "blocked",
+ "Expected close on " + loadType + ": " + expectedClose);
+ r();
+ }
+ });
+ }
+ } else if (test.type == link) {
+ var rel = test.noopener ? "rel='noopener'" : "";
+ for (let loadType of loadTypes) {
+ document.getElementById("content").innerHTML =
+ "<a href='file_window_close.html?" + loadType + "'" +
+ " target='foo' " + rel + "'>link</a>";
+ var p = new Promise(function(r) {
+ b.onmessage = function(e) {
+ var expectedClose = loadType == "withouthistory" ?
+ test.shouldCloseWithoutHistory : test.shouldCloseWithHistory;
+ is(e.data, expectedClose ? "closed" : "blocked",
+ "Expected close on " + loadType + ": " + expectedClose);
+ r();
+ }
+ });
+ document.getElementById("content").firstChild.click();
+ await p;
+ }
+ }
+ }
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="setTimeout(start)">
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_constructor.html b/dom/base/test/test_window_constructor.html
new file mode 100644
index 0000000000..6e7058356d
--- /dev/null
+++ b/dom/base/test/test_window_constructor.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=824217
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 824217</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622491">Mozilla Bug 824217</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe name="constructor" src="about:blank"></iframe>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ // Ideally we'd test that this property lives on the right place in the
+ // [[Prototype]] chain, with the right attributes there, but we don't
+ // implement this right yet, so just test the value's what's expected.
+ is(window.constructor, Window,
+ "should have gotten Window, not the constructor frame");
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_content.html b/dom/base/test/test_window_content.html
new file mode 100644
index 0000000000..8ffc6ed03f
--- /dev/null
+++ b/dom/base/test/test_window_content.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1400139
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1400139</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1400139 **/
+ var props = [];
+ for (var prop in window) props.push(prop);
+ is(props.indexOf("content"), -1, "Should not have a property named 'content'");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1400139">Mozilla Bug 1400139</a>
+<p id="display"></p>
+<div id="notcontent" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_cross_origin_props.html b/dom/base/test/test_window_cross_origin_props.html
new file mode 100644
index 0000000000..1f7600c6e0
--- /dev/null
+++ b/dom/base/test/test_window_cross_origin_props.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=946067
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 946067</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 946067 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function doGet(prop, thisObj) {
+ return Object.getOwnPropertyDescriptor(window, prop).get.call(thisObj);
+ }
+
+ function doSet(prop, thisObj, value) {
+ return Object.getOwnPropertyDescriptor(window, prop).set.call(thisObj, value);
+ }
+
+ window.onload = function() {
+ frames[0].focus();
+ is(document.activeElement.id, "x", "Should have focused first subframe");
+ frames[0].blur();
+ window.focus.call(frames[1]);
+ is(document.activeElement.id, "y", "Should have focused second subframe");
+ window.blur.call(frames[1]);
+
+ frames[0].close();
+ is(frames[0].closed, false, "Subframe is not closed");
+ window.close.call(frames[0]);
+ is(doGet("closed", frames[0]), false, "Subframe is still not closed");
+
+ is(frames[0].frames, frames[0], "window.frames === window");
+ is(doGet("frames", frames[0]), frames[0],
+ "Really, window.frames === window");
+
+ try {
+ frames[0].frames = 1;
+ ok(false, "Should throw when setting .frames");
+ } catch (e) {
+ ok(true, "Should throw when setting .frames");
+ }
+ try {
+ doSet("frames", frames[0], 1);
+ ok(false, "Should still throw when setting .frames");
+ } catch (e) {
+ ok(true, "Should still throw when setting .frames");
+ }
+
+ // Just check whether we can get the location without throwing:
+ is(frames[0].location, doGet("location", frames[0]),
+ "Should be same Location object");
+
+ is(frames[0].length, 0, "404 page has no subframes");
+ is(doGet("length", frames[0]), 0, "404 page has no subframes");
+
+ is(frames[0].opener, null, "subframe has no opener");
+ is(doGet("opener", frames[0]), null, "subframe still has no opener");
+
+ is(frames[0].parent, window, "subframe has us as parent");
+ is(doGet("parent", frames[0]), window, "subframe still has us as parent");
+
+ // Check that postMessage doesn't throw
+ frames[0].postMessage(null, "*");
+
+ is(frames[0].self, frames[0], "self should work");
+ is(doGet("self", frames[0]), frames[0], "self should still work");
+
+ is(frames[0].top, window.top, "Our subframe's top should be our top");
+ is(doGet("top", frames[0]), window.top,
+ "Our subframe's top should still be our top");
+
+ is(frames[0].window, frames[0], "window getter should work");
+ is(doGet("window", frames[0]), frames[0], "window getter should still work");
+ isnot(Object.getOwnPropertyDescriptor(window, "window").get, undefined,
+ "Should have a getter here");
+
+ // Finally, check that we can set the location
+ frames[0].location = "about:blank";
+ doSet("location", frames[1], "about:blank");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946067">Mozilla Bug 946067</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <iframe id="x" src="http://www.example.com/nosuchpageIhope"></iframe>
+ <iframe id="y" src="http://www.example.com/nosuchpageIhope"></iframe>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_define_nonconfigurable.html b/dom/base/test/test_window_define_nonconfigurable.html
new file mode 100644
index 0000000000..9d35c687f0
--- /dev/null
+++ b/dom/base/test/test_window_define_nonconfigurable.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1107443
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1107443</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ var { AppConstants } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+ );
+
+ /**
+ * Test for Bug 1107443, modified when it was backed out in bug 1329323.
+ * This is now testing the _current_ behavior, not the desired one; expect
+ * failures in this test and needing to update it when bug 1329324 is
+ * fixed.
+ */
+ var retval = Object.defineProperty(window, "nosuchprop",
+ { value: 5, configurable: false });
+ todo_is(retval, null,
+ "Should return null when 'failing' to define non-configurable property via Object.defineProperty.")
+
+ var desc = Object.getOwnPropertyDescriptor(window, "nosuchprop");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop' now");
+ todo_is(desc.configurable, true,
+ "Property 'nosuchprop' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop' should be readonly");
+ is(desc.value, 5, "Property 'nosuchprop' should have the right value");
+
+ retval = Object.defineProperties(window, {
+ "firstProp": { value: 1 },
+ "secondProp": { value: 2, configurable: false },
+ "thirdProp": { value: 3 },
+ });
+ todo_is(retval, null,
+ "Should return null when 'failing' to define non-configurable property via Object.defineProperties.")
+ // The properties should all be defined.
+ for (var [prop, val] of [["firstProp", 1], ["secondProp", 2], ["thirdProp", 3]]) {
+ desc = Object.getOwnPropertyDescriptor(window, prop);
+ is(typeof(desc), "object", `Should have a property '${prop}' now`);
+ todo_is(desc.configurable, true,
+ `Property '${prop}' should be configurable`);
+ is(desc.writable, false, `Property '${prop}' should be readonly`);
+ is(desc.value, val, `Property '${prop}' should have the right value`);
+ }
+
+ retval = Object.defineProperty(window, "nosuchprop2", { value: 6 });
+ is(retval, window,
+ "Should return object when succesfully defining 'nosuchprop2'");
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop2");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop2' now");
+ todo_is(desc.configurable, true,
+ "Property 'nosuchprop2' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop2' should be readonly");
+ is(desc.value, 6, "Property 'nosuchprop2' should have the right value");
+
+ retval = Object.defineProperty(window, "nosuchprop3",
+ { value: 7, configurable: true });
+ is(retval, window,
+ "Should return object when succesfully defining 'nosuchprop3'");
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop3");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop3' now");
+ is(desc.configurable, true,
+ "Property 'nosuchprop3' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop3' should be readonly");
+ is(desc.value, 7, "Property 'nosuchprop3' should have the right value");
+
+ retval = Reflect.defineProperty(window, "nosuchprop4",
+ { value: 8, configurable: false });
+ todo_is(retval, false,
+ "Should not be able to Reflect.defineProperty if non-configurable");
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop4");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop4' now");
+ todo_is(desc.configurable, true,
+ "Property 'nosuchprop4' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop4' should be readonly");
+ is(desc.value, 8, "Property 'nosuchprop4' should have the right value");
+
+ retval = Reflect.defineProperty(window, "nosuchprop5",
+ { value: 9 });
+ is(retval, true,
+ "Should be able to Reflect.defineProperty with default configurability");
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop5");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop5' now");
+ todo_is(desc.configurable, true,
+ "Property 'nosuchprop5' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop5' should be readonly");
+ is(desc.value, 9, "Property 'nosuchprop5' should have the right value");
+
+ retval = Reflect.defineProperty(window, "nosuchprop6",
+ { value: 10, configurable: true });
+ is(retval, true,
+ "Should be able to Reflect.defineProperty if configurable");
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop6");
+ is(typeof(desc), "object", "Should have a property 'nosuchprop6' now");
+ is(desc.configurable, true, "Property 'nosuchprop6' should be configurable");
+ is(desc.writable, false, "Property 'nosuchprop6' should be readonly");
+ is(desc.value, 10, "Property 'nosuchprop6' should have the right value");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107443">Mozilla Bug 1107443</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_define_symbol.html b/dom/base/test/test_window_define_symbol.html
new file mode 100644
index 0000000000..352e8e7736
--- /dev/null
+++ b/dom/base/test/test_window_define_symbol.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1082672
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1082672 part 2</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1082672">Mozilla Bug 1082672</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+if (typeof Symbol === "function") {
+ var sym = Symbol("ponies");
+ Object.defineProperty(window, sym, {configurable: true, value: 3});
+ is(window[sym], 3);
+} else {
+ ok(true, "no Symbols in this build");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_element_enumeration.html b/dom/base/test/test_window_element_enumeration.html
new file mode 100644
index 0000000000..138fa89d6a
--- /dev/null
+++ b/dom/base/test/test_window_element_enumeration.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=959992
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 959992</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=959992">Mozilla Bug 959992</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <img id="one" name="two">
+ <div id="three" name="four"></div>
+ <div id=""></div>
+ <img name="">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 959992 **/
+ var names1 = Object.getOwnPropertyNames(window);
+ var names2 = [];
+ var gsp = Object.getPrototypeOf(Window.prototype);
+ var names3 = Object.getOwnPropertyNames(gsp);
+ for (var i in window) {
+ names2.push(i);
+ }
+
+ is(names1.indexOf(""), -1,
+ "Element with empty id/name should not be in our own prop list");
+ is(names2.indexOf(""), -1,
+ "Element with empty id/name name should not be in our enumeration list");
+ is(names3.indexOf(""), -1,
+ "Element with empty id/name should not be in GSP own prop list");
+
+ is(names1.indexOf("one"), -1,
+ "<img> with id should not be in our own prop list");
+ is(names2.indexOf("one"), -1,
+ "<img> with id should not be in our enumeration list");
+ isnot(names3.indexOf("one"), -1,
+ "<img> with id should be in GSP own prop list");
+
+ is(names1.indexOf("two"), -1,
+ "<img> with name should not be in our own prop list");
+ is(names2.indexOf("two"), -1,
+ "<img> with name should not be in our enumeration list");
+ isnot(names3.indexOf("two"), -1,
+ "<img> with name should be in GSP own prop list");
+
+ is(names1.indexOf("three"), -1,
+ "<div> with id should not be in our own prop list");
+ is(names2.indexOf("three"), -1,
+ "<div> with id should not be in our enumeration list");
+ todo_isnot(names3.indexOf("three"), -1,
+ "<div> with id should be in GSP own prop list");
+
+ is(names1.indexOf("four"), -1,
+ "<div> with name should not be in our own prop list");
+ is(names2.indexOf("four"), -1,
+ "<div> with name should not be in our enumeration list");
+ is(names3.indexOf("four"), -1,
+ "<div> with name should not be in GSP own prop list");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_enumeration.html b/dom/base/test/test_window_enumeration.html
new file mode 100644
index 0000000000..9d62741029
--- /dev/null
+++ b/dom/base/test/test_window_enumeration.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=807222
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 807222</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=807222">Mozilla Bug 807222</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 807222 **/
+var expectedProps = [ "Image", "Audio", "Option",
+ "PerformanceTiming", "CSS2Properties", "SVGElement" ];
+var actualProps = Object.getOwnPropertyNames(window);
+
+for (var i = 0; i < expectedProps.length; ++i) {
+ isnot(actualProps.indexOf(expectedProps[i]), -1,
+ "getOwnPropertyNames should include " + expectedProps[i]);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_extensible.html b/dom/base/test/test_window_extensible.html
new file mode 100644
index 0000000000..ddc6e0d3d0
--- /dev/null
+++ b/dom/base/test/test_window_extensible.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=856384
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 856384</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856384">Mozilla Bug 856384</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe name="constructor" src="about:blank"></iframe>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ is(Object.isExtensible(window), true, "Windows are extensible by default");
+
+ try
+ {
+ Object.preventExtensions(window);
+ throw "didn't throw";
+ }
+ catch (e)
+ {
+ is(e instanceof TypeError, true,
+ "preventExtensions(window) should throw a TypeError, instead got: " + e);
+ is(Object.isExtensible(window), true,
+ 'Windows are extensible after an extensibility "change" attempt');
+ }
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_focus_by_close_and_open.html b/dom/base/test/test_window_focus_by_close_and_open.html
new file mode 100644
index 0000000000..d9b833c26f
--- /dev/null
+++ b/dom/base/test/test_window_focus_by_close_and_open.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>test for window focus by window.close() and window.open()</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function start() {
+ var w = window.open("", "_blank");
+ w.finished = function() {
+ ok(true, "1st new window had focus");
+ w.close();
+ w = window.open("", "_blank");
+ w.finished = function() {
+ ok(true, "2nd new window had focus");
+ w.close();
+ SimpleTest.finish();
+ };
+ w.location = "file_window_focus_by_close_and_open.html";
+ };
+ w.location = "file_window_focus_by_close_and_open.html";
+}
+</script>
+</head>
+<body onload="setTimeout(start)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1695135">Mozilla Bug 1695135</a>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_indexing.html b/dom/base/test/test_window_indexing.html
new file mode 100644
index 0000000000..32713039b0
--- /dev/null
+++ b/dom/base/test/test_window_indexing.html
@@ -0,0 +1,139 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=823228
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 823228</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=823228">Mozilla Bug 823228</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 823228 **/
+ is(window, window.frames, "These better be equal");
+ ok("0" in window, "We have a subframe");
+ ok("1" in window, "We have two subframes");
+ ok(!("2" in window), "But we don't have three subframes");
+ window[2] = "myString";
+ ok(!("2" in window), "Should not be able to set indexed expando");
+ Object.getPrototypeOf(window)[3] = "Hey there";
+ ok("3" in window, "Should be walking up the proto chain");
+
+ is(window[0].name, "x", "First frame is x");
+ is(window[1].name, "y", "Second frame is y");
+ is(window[2], undefined, "We should still not have our expando");
+ is(window[3], "Hey there", "We should still have our prop on the proto chain");
+
+ var x = $("x");
+ var y = $("y");
+
+ is(x.contentWindow, window[0], "First frame should have correct window");
+ is(y.contentWindow, window[1], "Second frame should have correct window");
+
+ // set() hook test
+ throws(TypeError, function () {
+ "use strict";
+ window[1] = "FAIL strict";
+ });
+
+ // set() hook test
+ window[1] = "FAIL";
+ is(window[1].name, "y", "Second frame is still y");
+ y.remove();
+ ok(!("1" in window), "We no longer have two subframes");
+ is(window[1], undefined, "We should not have a value here");
+
+ // defineProperty() hook test
+ function throws(errorCtor, f) {
+ try {
+ f();
+ } catch (exc) {
+ if (!(exc instanceof errorCtor))
+ throw exc;
+ return;
+ }
+ throw new Error("expected " + errCtor.name + ", no exception thrown: " + f);
+ }
+
+ x.parentNode.appendChild(y);
+ throws(TypeError, function () {
+ Object.defineProperty(window, "1", { value: "FAIL2", configurable: true,
+ writable: true });
+ });
+ y.remove();
+ ok(!("1" in window), "We no longer have two subframes, again");
+ is(window[1], undefined, "We should not have a value here either");
+
+ // More set() hook test
+ throws(TypeError, function () {
+ "use strict";
+ window[1] = "FAIL3 strict";
+ });
+ window[1] = "FAIL3";
+ ok(!("1" in window), "We shouldn't allow indexed expandos");
+ is(window[1], undefined, "We should not have a value for an indexed expando");
+ var desc = Object.getOwnPropertyDescriptor(window, "1");
+ is(desc, undefined, "We really really shouldn't have indexed expandos");
+
+ x.parentNode.appendChild(y);
+ is(window[1], y.contentWindow, "Second frame should now be visible");
+ desc = Object.getOwnPropertyDescriptor(window, "1");
+ ok(desc.configurable, "Subframe should be configurable");
+ ok(desc.enumerable, "Subframe should be configurable");
+ ok(!desc.writable, "Subframe should not be writable");
+ is(desc.value, y.contentWindow, "Subframe should have correct value");
+
+ y.remove();
+ is(window[1], undefined, "And now we should be back to no [1] property");
+
+ // And more defineProperty()
+ throws(TypeError, function () {
+ Object.defineProperty(window, "1", { value: "FAIL2", configurable: true,
+ writable: true });
+ });
+ ok(!("1" in window), "Defining indexed properties really just shouldn't work");
+ is(window[1], undefined, "Defining past end of list should not work");
+
+ // Enumeration tests
+ x.parentNode.appendChild(y);
+
+ var names = Object.getOwnPropertyNames(window);
+ is(names[0], "0", "Must start with 0");
+ is(names[1], "1", "Must continue with 1");
+ is(names.indexOf("2"), -1, "And 2, an attempted expando, should not be in there");
+ is(names.indexOf("3"), -1, "But no 3; that's on the proto");
+
+ names = [];
+ for (var name in window) {
+ names.push(name);
+ }
+ is(names[0], "0", "Enumeration must start with 0");
+ is(names[1], "1", "Enumeration must continue with 1");
+ is(names.indexOf("2"), -1, "Enumeration: but no expando 2");
+ isnot(names.indexOf("3"), -1, "Enumeration: and then 3, defined on the proto");
+ is(names.indexOf("4"), -1, "But no 4 around");
+
+ // Delete tests
+ is(delete window[1], false, "Deleting supported index should return false");
+ is(window[1], y.contentWindow, "Shouldn't be able to delete a supported index");
+ y.remove();
+ is(window[1], undefined,
+ "And now we should have no property here");
+ is(delete window[1], true, "Deleting unsupported index should return true");
+ is(window[1], undefined,
+ "And we shouldn't have things magically appear due to delete");
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_window_keys.html b/dom/base/test/test_window_keys.html
new file mode 100644
index 0000000000..8bbe6e2179
--- /dev/null
+++ b/dom/base/test/test_window_keys.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1372371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1372371</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1372371">Mozilla Bug 1372371</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 1372371 **/
+ var list = Object.keys(window);
+ is(list.indexOf("WebSocket"), -1, "WebSocket should not be enumerable");
+ is(list.indexOf("0"), 0, "Indexed props should be enumerable");
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_window_named_frame_enumeration.html b/dom/base/test/test_window_named_frame_enumeration.html
new file mode 100644
index 0000000000..d5436b1fee
--- /dev/null
+++ b/dom/base/test/test_window_named_frame_enumeration.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1019417
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1019417</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1019417 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ var names1 = Object.getOwnPropertyNames(window);
+ var names2 = [];
+ var gsp = Object.getPrototypeOf(Window.prototype);
+ var names3 = Object.getOwnPropertyNames(gsp);
+ for (var i in window) {
+ names2.push(i);
+ }
+
+ is(names1.indexOf(""), -1,
+ "Frame with no name or empty name should not be in our own prop list");
+ is(names2.indexOf(""), -1,
+ "Frame with no name or empty name should not be in our enumeration list");
+ is(names3.indexOf(""), -1,
+ "Frame with no name or empty name should not be in GSP own prop list");
+ is(names1.indexOf("x"), -1,
+ "Frame with about:blank loaded should not be in our own prop list");
+ is(names2.indexOf("x"), -1,
+ "Frame with about:blank loaded should not be in our enumeration list");
+ isnot(names3.indexOf("x"), -1,
+ "Frame with about:blank loaded should be in GSP own prop list");
+ is(names1.indexOf("y"), -1,
+ "Frame with same-origin loaded should not be in our own prop list");
+ is(names2.indexOf("y"), -1,
+ "Frame with same-origin loaded should not be in our enumeration list");
+ isnot(names3.indexOf("y"), -1,
+ "Frame with same-origin loaded should be in GSP own prop list");
+ is(names1.indexOf("z"), -1,
+ "Frame with cross-origin loaded should not be in our own prop list");
+ is(names2.indexOf("z"), -1,
+ "Frame with cross-origin loaded should not be in our enumeration list");
+ isnot(names3.indexOf("z"), -1,
+ "Frame with cross-origin loaded should be in GSP own prop list");
+ is(names1.indexOf("sameorigin"), -1,
+ "Frame with same-origin changed name should not be in our own prop list");
+ is(names2.indexOf("sameorigin"), -1,
+ "Frame with same-origin changed name should not be in our enumeration list");
+ isnot(names3.indexOf("sameorigin"), -1,
+ "Frame with same-origin changed name should be in GSP own prop list");
+ is(names1.indexOf("crossorigin"), -1,
+ "Frame with cross-origin changed name should not be in our own prop list");
+ is(names2.indexOf("crossorigin"), -1,
+ "Frame with cross-origin changed name should not be in our enumeration list");
+ is(names3.indexOf("crossorigin"), -1,
+ "Frame with cross-origin changed name should not be in GSP own prop list");
+
+ is(Object.getOwnPropertyDescriptor(gsp, ""), undefined,
+ "Should not have empty string as a named frame");
+ isnot(Object.getOwnPropertyDescriptor(gsp, "x"), undefined,
+ "Should have about:blank subframe as a named frame");
+ isnot(Object.getOwnPropertyDescriptor(gsp, "y"), undefined,
+ "Should have same-origin subframe as a named frame");
+ isnot(Object.getOwnPropertyDescriptor(gsp, "z"), undefined,
+ "Should have cross-origin subframe as a named frame");
+ isnot(Object.getOwnPropertyDescriptor(gsp, "sameorigin"), undefined,
+ "Should have same-origin changed name as a named frame");
+ is(Object.getOwnPropertyDescriptor(gsp, "crossorigin"), undefined,
+ "Should not have cross-origin-origin changed name as a named frame");
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1019417">Mozilla Bug 1019417</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe></iframe>
+<iframe name=""></iframe>
+<iframe name="x"></iframe>
+<iframe name="y"
+ src="http://mochi.test:8888/tests/dom/base/test/file_empty.html"></iframe>
+<iframe name="z"
+ src="http://example.com/tests/dom/base/test/file_empty.html"></iframe>
+<iframe name="v"
+ src="http://mochi.test:8888/tests/dom/base/test/file_setname.html?sameorigin"></iframe>
+<iframe name="w"
+ src="http://example.com/tests/dom/base/test/file_setname.html?crossorigin"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_window_own_props.html b/dom/base/test/test_window_own_props.html
new file mode 100644
index 0000000000..55322e3b1e
--- /dev/null
+++ b/dom/base/test/test_window_own_props.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1372371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1372371</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1372371">Mozilla Bug 1372371</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 1372371 **/
+ var list = Object.getOwnPropertyNames(window);
+ // Pick an interface name we would not have resolved here yet.
+ isnot(list.indexOf("WebSocket"), -1, "WebSocket should be a property");
+ is(list.indexOf("0"), 0, "Indexed props should be properties");
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_window_proto.html b/dom/base/test/test_window_proto.html
new file mode 100644
index 0000000000..944f953bc4
--- /dev/null
+++ b/dom/base/test/test_window_proto.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws(new TypeError, function() {
+ Object.setPrototypeOf(window, Object.create(window));
+ }, "Setting prototype via setPrototypeOf");
+
+ assert_throws(new TypeError, function() {
+ window.__proto__ = Object.create(window);
+ }, "Setting prototype via __proto__");
+}, "Setting the prototype of a window to something that has the window on its proto chain should throw");
+</script>
diff --git a/dom/base/test/test_writable-replaceable.html b/dom/base/test/test_writable-replaceable.html
new file mode 100644
index 0000000000..422e0d67a6
--- /dev/null
+++ b/dom/base/test/test_writable-replaceable.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 823283</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=823283">Mozilla Bug 823283</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 823283 **/
+
+function createTest(prop, typeStr, valCode, replaceable)
+{
+ var newType = replaceable ? typeof(valCode) : typeStr;
+ var code =
+ 'is(typeof ' + prop + ', "' + typeStr + '", "' + prop + ': bad unqualified before-state");\n' +
+ 'is(typeof window.' + prop + ', "' + typeStr + '", "' + prop + ': bad qualified before-state");\n' +
+ '\n' +
+ prop + ' = ' + valCode + ';\n' +
+ '\n' +
+ 'is(typeof ' + prop + ', "' + newType + '", "' + prop + ': bad unqualified after-state");\n' +
+ 'is(typeof window.' + prop + ', "' + newType + '", "' + prop + ': bad qualified after-state");';
+
+ return Function(code);
+}
+
+[
+ ["innerHeight", "number", '"123"', true],
+ ["innerWidth", "number", '"456"', true],
+ ["outerHeight", "number", '"654"', true],
+ ["outerWidth", "number", '"321"', true],
+ ["screenX", "number", '"17"', true],
+ ["screenY", "number", '"42"', true],
+ ["status", "string", '{}', false],
+ ["name", "string", '{}', false],
+].forEach(function(args)
+{
+ createTest.apply(null, args)();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_x-frame-options.html b/dom/base/test/test_x-frame-options.html
new file mode 100644
index 0000000000..d8586e7974
--- /dev/null
+++ b/dom/base/test/test_x-frame-options.html
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for X-Frame-Options response header</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<iframe style="width:100%;height:300px;" id="harness"></iframe>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/dom/base/test/";
+
+var testFramesLoaded = async function() {
+ var harness = document.getElementById("harness").contentDocument;
+
+ // iframe from same origin, no X-F-O header - should load
+ var frame = harness.getElementById("control1");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test1 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test1, "control1", "test control1");
+ });
+
+ // iframe from different origin, no X-F-O header - should load
+ frame = harness.getElementById("control2");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test2 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test2, "control2", "test control2");
+ });
+
+ // iframe from same origin, X-F-O: DENY - should not load
+ frame = harness.getElementById("deny");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test3 = this.content.document.getElementById("test");
+ Assert.equal(test3, null, "test deny");
+ });
+
+ // iframe from same origin, X-F-O: SAMEORIGIN - should load
+ frame = harness.getElementById("sameorigin1");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test4 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test4, "sameorigin1", "test sameorigin1");
+ });
+
+ // iframe from different origin, X-F-O: SAMEORIGIN - should not load
+ frame = harness.getElementById("sameorigin2");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test5 = this.content.document.getElementById("test");
+ Assert.equal(test5, null, "test sameorigin2");
+ });
+
+ // iframe from different origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should not load
+ frame = harness.getElementById("sameorigin5");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test6 = this.content.document.getElementById("test");
+ Assert.equal(test6, null, "test sameorigin5");
+ });
+
+ // iframe from same origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should load
+ frame = harness.getElementById("sameorigin6");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test7 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test7, "sameorigin6", "test sameorigin6");
+ });
+
+ // iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should load
+ frame = harness.getElementById("sameorigin7");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test8 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test8, "sameorigin7", "test sameorigin7");
+ });
+
+ // iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should not load
+ frame = harness.getElementById("sameorigin8");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test9 = this.content.document.getElementById("test");
+ Assert.equal(test9, null, "test sameorigin8");
+ });
+
+ // iframe from same origin, X-F-O: DENY,SAMEORIGIN - should not load
+ frame = harness.getElementById("mixedpolicy");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test10 = this.content.document.getElementById("test");
+ Assert.equal(test10, null, "test mixedpolicy");
+ });
+
+ // iframe from different origin, allow-from: this origin - should load
+ frame = harness.getElementById("allow-from-allow");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test11 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test11, "allow-from-allow", "test allow-from-allow");
+ });
+
+ // iframe from different origin, with allow-from: other - should load as we no longer support allow-from (Bug 1301529)
+ frame = harness.getElementById("allow-from-deny");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test12 = this.content.document.getElementById("test");
+ Assert.notEqual(test12, null, "test allow-from-deny");
+ });
+
+ // iframe from different origin, X-F-O: SAMEORIGIN, multipart - should not load
+ frame = harness.getElementById("sameorigin-multipart");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test13 = this.content.document.getElementById("test");
+ Assert.equal(test13, null, "test sameorigin-multipart");
+ });
+
+ // iframe from same origin, X-F-O: SAMEORIGIN, multipart - should load
+ frame = harness.getElementById("sameorigin-multipart2");
+ await SpecialPowers.spawn(frame, [], () => {
+ var test14 = this.content.document.getElementById("test").textContent;
+ Assert.equal(test14, "sameorigin-multipart2", "test sameorigin-multipart2");
+ });
+
+
+ // frames from bug 836132 tests, no longer supported allow-from
+ {
+ frame = harness.getElementById("allow-from-allow-1");
+ var theTestResult = frame.contentDocument.getElementById("test");
+ isnot(theTestResult, null, "test afa1 should have been allowed");
+ if(theTestResult) {
+ is(theTestResult.textContent, "allow-from-allow-1", "test allow-from-allow-1");
+ }
+ }
+ // Verify allow-from no longer works
+ for (var i = 1; i<=14; i++) {
+ frame = harness.getElementById("allow-from-deny-" + i);
+ var theTestResult = frame.contentDocument.getElementById("test");
+ isnot(theTestResult, null, "test allow-from-deny-" + i);
+ }
+
+ // call tests to check principal comparison, e.g. a document can open a window
+ // to a data: or javascript: document which frames an
+ // X-Frame-Options: SAMEORIGIN document and the frame should load
+ testFrameInJSURI();
+};
+
+// test that a document can be framed under a javascript: URL opened by the
+// same site as the frame
+// We can't set a load event listener before calling document.open/document.write, because those will remove such listeners. So we need to define a function that the new window will be able to call.
+function frameInJSURILoaded(win) {
+ var test = win.document.getElementById("sameorigin3")
+ .contentDocument.getElementById("test");
+ ok(test != null, "frame under javascript: URL should have loaded.");
+ win.close();
+
+ testFrameNotLoadedInDataURI();
+}
+
+var testFrameInJSURI = function() {
+ var html = '<iframe id="sameorigin3" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin3&xfo=sameorigin"></iframe>';
+ var win = window.open();
+ win.location.href = "javascript:document.open(); onload = opener.frameInJSURILoaded.bind(null, window); document.write('"+html+"');document.close();";
+};
+
+// test an iframe with X-FRAME-OPTIONS shouldn't be loaded in a cross-origin window,
+var testFrameNotLoadedInDataURI = function() {
+ // In this case we load two iframes, one is sameorigin4, which will have X-FRAME-OPTIONS,
+ // the other is postmessage, which won't get the XFO header.
+ // And because now window is navigated to a data: URI, which is considered as cross origin,
+ // So win.onload won't be fired, so we use the iframe 'postmessage' to know the iframes
+ // have been loaded.
+ var html = `<iframe id="sameorigin4" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin4&xfo=sameorigin"></iframe>
+ <iframe id="postmessage" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=postmessage"></iframe>`;
+ var win = window.open();
+ window.onmessage = function(evt) {
+ var iframe = SpecialPowers.wrap(win).document.getElementById("sameorigin4");
+ var test = iframe.contentDocument.getElementById("test");
+ ok(test == null, "frame under data: URL should have blocked.");
+ win.close();
+
+ SimpleTest.finish();
+ };
+ win.location.href = "data:text/html,"+html;
+};
+
+SimpleTest.waitForExplicitFinish();
+
+// load the test harness
+SpecialPowers.pushPrefEnv({
+ "set": [["security.data_uri.block_toplevel_data_uri_navigations", false],]
+}, function() {
+ document.getElementById("harness").src = "file_x-frame-options_main.html";
+});
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/base/test/test_youtube_flash_embed.html b/dom/base/test/test_youtube_flash_embed.html
new file mode 100644
index 0000000000..2c7cddaefd
--- /dev/null
+++ b/dom/base/test/test_youtube_flash_embed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1240471
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1240471</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ let path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/file_youtube_flash_embed.html';
+ onmessage = function(e) {
+ let msg = JSON.parse(e.data);
+ if (msg.fn == "finish") {
+ SimpleTest.finish();
+ return;
+ }
+ self[msg.fn].apply(null, msg.args);
+ }
+ function onLoad() {
+ // The test file must be loaded into youtube.com domain
+ // because it needs unprivileged access to fullscreenEnabled.
+ ifr.src = "https://mochitest.youtube.com" + path;
+ }
+ </script>
+ </head>
+ <body onload="onLoad()">
+ <iframe id="ifr" allowfullscreen></iframe>
+ </body>
+</html>
diff --git a/dom/base/test/unit/1_original.xml b/dom/base/test/unit/1_original.xml
new file mode 100644
index 0000000000..4b7915159d
--- /dev/null
+++ b/dom/base/test/unit/1_original.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+
+<foo /> <!-- é --> \ No newline at end of file
diff --git a/dom/base/test/unit/1_result.xml b/dom/base/test/unit/1_result.xml
new file mode 100644
index 0000000000..61d4458be9
--- /dev/null
+++ b/dom/base/test/unit/1_result.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo/>
+<!-- é --> \ No newline at end of file
diff --git a/dom/base/test/unit/2_original.xml b/dom/base/test/unit/2_original.xml
new file mode 100644
index 0000000000..16eeb817ff
--- /dev/null
+++ b/dom/base/test/unit/2_original.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="htp://mozilla.org/ns">
+ <baz/><!-- a comment --> <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+<mozilla> a a a a a éèàùûî</mozilla>
+ <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox>
+ <?xml-foo "hey" ?>
+</bar>
+ <!-- a comment
+ on several lines-->
+ <?xml-foo "another pi on two lines"
+ example="hello"?>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/2_result_1.xml b/dom/base/test/unit/2_result_1.xml
new file mode 100644
index 0000000000..16eeb817ff
--- /dev/null
+++ b/dom/base/test/unit/2_result_1.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="htp://mozilla.org/ns">
+ <baz/><!-- a comment --> <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+<mozilla> a a a a a éèàùûî</mozilla>
+ <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox>
+ <?xml-foo "hey" ?>
+</bar>
+ <!-- a comment
+ on several lines-->
+ <?xml-foo "another pi on two lines"
+ example="hello"?>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/2_result_2.xml b/dom/base/test/unit/2_result_2.xml
new file mode 100644
index 0000000000..c3eeadb58f
--- /dev/null
+++ b/dom/base/test/unit/2_result_2.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="htp://mozilla.org/ns">
+ <baz/><!-- a comment -->
+ <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+ <mozilla> a a a a a éèàùûî</mozilla>
+ <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox>
+ <?xml-foo "hey" ?>
+ </bar>
+ <!-- a comment
+ on several lines-->
+ <?xml-foo "another pi on two lines"
+ example="hello"?>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/2_result_3.xml b/dom/base/test/unit/2_result_3.xml
new file mode 100644
index 0000000000..906ac89ee5
--- /dev/null
+++ b/dom/base/test/unit/2_result_3.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="htp://mozilla.org/ns">
+ <baz/><!-- a comment -->
+ <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+ <mozilla> a a a a a éèàùûî</mozilla>
+ <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer
+ adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis
+ ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent
+ taciti sociosqu ad litora torquent per conubia nostra, per
+ inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum
+ sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris
+ quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum
+ posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia
+ libero ullamcorper laoreet. Cras quis nisi at odio consectetuer
+ molestie.</firefox>
+ <?xml-foo "hey" ?>
+ </bar>
+ <!-- a comment
+ on several lines-->
+ <?xml-foo "another pi on two lines"
+ example="hello"?>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/2_result_4.xml b/dom/base/test/unit/2_result_4.xml
new file mode 100644
index 0000000000..27ed219211
--- /dev/null
+++ b/dom/base/test/unit/2_result_4.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="htp://mozilla.org/ns">
+ <baz/><!-- a comment --> <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+<mozilla> a a a a a éèàùûî</mozilla>
+ <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer
+adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum.
+Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti
+sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos.
+Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac,
+est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non
+ lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec
+ sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi
+at odio consectetuer molestie.</firefox>
+ <?xml-foo "hey" ?>
+</bar>
+ <!-- a comment
+ on several lines-->
+ <?xml-foo "another pi on two lines"
+ example="hello"?>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/3_original.xml b/dom/base/test/unit/3_original.xml
new file mode 100644
index 0000000000..eb9c1bd656
--- /dev/null
+++ b/dom/base/test/unit/3_original.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo>
+Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit. Nameusapien. Sed viverralacus. this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros.
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/3_result.xml b/dom/base/test/unit/3_result.xml
new file mode 100644
index 0000000000..e556c61e58
--- /dev/null
+++ b/dom/base/test/unit/3_result.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo>
+ Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit.
+ Nameusapien. Sed viverralacus.
+ this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column
+ Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros.
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/3_result_2.xml b/dom/base/test/unit/3_result_2.xml
new file mode 100644
index 0000000000..2df257ca75
--- /dev/null
+++ b/dom/base/test/unit/3_result_2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo>
+Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit.
+Nameusapien. Sed viverralacus.
+this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column
+ Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros.
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/4_original.xml b/dom/base/test/unit/4_original.xml
new file mode 100644
index 0000000000..b985da960e
--- /dev/null
+++ b/dom/base/test/unit/4_original.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="http://mozilla.org/ns" xmlns:falsexul="http://mozilla.org/ns3">
+ <!-- document to test namespaces-->
+ <baz/>
+ <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+ <mozilla xmlns="http://mozilla.org/ns2"> a a a <moz>a a</moz> éèàùûî</mozilla>
+ <firefox>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</firefox>
+ </bar>
+
+ <xul xmlns="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description other:yes="no">xul fake</description>
+ </box>
+ </xul>
+
+ <falsexul:xul xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description>xul fake</description>
+ <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what>
+ </box>
+ </falsexul:xul>
+
+ <ho:xul xmlns="http://mozilla.org/ns4" xmlns:ho="http://mozilla.org/ns4" xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description ho:foo="bar" bla="hello" other:yes="no" ho:foo2="bar2">xul fake</description>
+ </box>
+ </ho:xul>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_1.xml b/dom/base/test/unit/4_result_1.xml
new file mode 100644
index 0000000000..b985da960e
--- /dev/null
+++ b/dom/base/test/unit/4_result_1.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="http://mozilla.org/ns" xmlns:falsexul="http://mozilla.org/ns3">
+ <!-- document to test namespaces-->
+ <baz/>
+ <bar> &lt;robots&gt; &amp; &lt;aliens&gt;
+ <mozilla xmlns="http://mozilla.org/ns2"> a a a <moz>a a</moz> éèàùûî</mozilla>
+ <firefox>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</firefox>
+ </bar>
+
+ <xul xmlns="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description other:yes="no">xul fake</description>
+ </box>
+ </xul>
+
+ <falsexul:xul xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description>xul fake</description>
+ <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what>
+ </box>
+ </falsexul:xul>
+
+ <ho:xul xmlns="http://mozilla.org/ns4" xmlns:ho="http://mozilla.org/ns4" xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description ho:foo="bar" bla="hello" other:yes="no" ho:foo2="bar2">xul fake</description>
+ </box>
+ </ho:xul>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_2.xml b/dom/base/test/unit/4_result_2.xml
new file mode 100644
index 0000000000..bc408b431c
--- /dev/null
+++ b/dom/base/test/unit/4_result_2.xml
@@ -0,0 +1,7 @@
+<falsexul:xul xmlns:falsexul="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other">
+ <box xmlns="http://mozilla.org/ns">
+ <other:what>lorem ipsum</other:what>
+ <description>xul fake</description>
+ <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what>
+ </box>
+ </falsexul:xul> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_3.xml b/dom/base/test/unit/4_result_3.xml
new file mode 100644
index 0000000000..30c8b47de7
--- /dev/null
+++ b/dom/base/test/unit/4_result_3.xml
@@ -0,0 +1,4 @@
+<box xmlns="http://mozilla.org/ns3">
+ <other:what xmlns:other="http://mozilla.org/ns/other">lorem ipsum</other:what>
+ <description other:yes="no" xmlns:other="http://mozilla.org/ns/other">xul fake</description>
+ </box> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_4.xml b/dom/base/test/unit/4_result_4.xml
new file mode 100644
index 0000000000..9346d5d170
--- /dev/null
+++ b/dom/base/test/unit/4_result_4.xml
@@ -0,0 +1,4 @@
+<box xmlns="http://mozilla.org/ns4">
+ <other:what xmlns:other="http://mozilla.org/ns/other">lorem ipsum</other:what>
+ <description ho:foo="bar" xmlns:ho="http://mozilla.org/ns4" bla="hello" other:yes="no" xmlns:other="http://mozilla.org/ns/other" ho:foo2="bar2">xul fake</description>
+ </box> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_5.xml b/dom/base/test/unit/4_result_5.xml
new file mode 100644
index 0000000000..4bb152576a
--- /dev/null
+++ b/dom/base/test/unit/4_result_5.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="http://mozilla.org/ns"
+xmlns:falsexul="http://mozilla.org/ns3">
+ <!-- document to test namespaces-->
+ <baz/>
+ <bar> &lt;robots&gt; &amp;
+ &lt;aliens&gt;
+ <mozilla
+ xmlns="http://mozilla.org/ns2"> a
+ a a
+ <moz>a a</moz> éèàùûî</mozilla>
+ <firefox>Lorem ipsum dolor sit amet,
+ consectetuer adipiscing elit.</firefox>
+ </bar>
+ <xul xmlns="http://mozilla.org/ns3"
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description other:yes="no">xul
+ fake</description>
+ </box>
+ </xul>
+ <falsexul:xul
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description>xul fake</description>
+ <what
+xmlns="http://mozilla.org/ns/other">lorem
+ ipsum
+ <falsexul:label value="hello"/>
+ the return</what>
+ </box>
+ </falsexul:xul>
+ <ho:xul xmlns="http://mozilla.org/ns4"
+ xmlns:ho="http://mozilla.org/ns4"
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description ho:foo="bar"
+ bla="hello" other:yes="no"
+ ho:foo2="bar2">xul fake</description>
+ </box>
+ </ho:xul>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/4_result_6.xml b/dom/base/test/unit/4_result_6.xml
new file mode 100644
index 0000000000..ce18f72a81
--- /dev/null
+++ b/dom/base/test/unit/4_result_6.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo">
+<foo xmlns="http://mozilla.org/ns"
+xmlns:falsexul="http://mozilla.org/ns3">
+ <!-- document to test namespaces-->
+ <baz/>
+ <bar> &lt;robots&gt; &amp;
+&lt;aliens&gt;
+ <mozilla
+xmlns="http://mozilla.org/ns2"> a a a <moz>a
+ a</moz> éèàùûî</mozilla>
+ <firefox>Lorem ipsum dolor sit
+amet, consectetuer adipiscing elit.</firefox>
+ </bar>
+
+ <xul xmlns="http://mozilla.org/ns3"
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description other:yes="no">xul
+fake</description>
+ </box>
+ </xul>
+
+ <falsexul:xul
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description>xul fake</description>
+ <what
+xmlns="http://mozilla.org/ns/other">lorem
+ ipsum <falsexul:label value="hello"/>
+the return</what>
+ </box>
+ </falsexul:xul>
+
+ <ho:xul
+xmlns="http://mozilla.org/ns4"
+xmlns:ho="http://mozilla.org/ns4"
+xmlns:other="http://mozilla.org/ns/other">
+ <box>
+ <other:what>lorem ipsum</other:what>
+ <description ho:foo="bar"
+bla="hello" other:yes="no"
+ho:foo2="bar2">xul fake</description>
+ </box>
+ </ho:xul>
+</foo> \ No newline at end of file
diff --git a/dom/base/test/unit/empty_document.xml b/dom/base/test/unit/empty_document.xml
new file mode 100644
index 0000000000..ebd60b08c7
--- /dev/null
+++ b/dom/base/test/unit/empty_document.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" ?>
+<!-- empty document for use in tests that don't need any particular DOM -->
+<root/>
diff --git a/dom/base/test/unit/head_utilities.js b/dom/base/test/unit/head_utilities.js
new file mode 100644
index 0000000000..4b6d41ff1b
--- /dev/null
+++ b/dom/base/test/unit/head_utilities.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {
+ HTTP_400,
+ HTTP_401,
+ HTTP_402,
+ HTTP_403,
+ HTTP_404,
+ HTTP_405,
+ HTTP_406,
+ HTTP_407,
+ HTTP_408,
+ HTTP_409,
+ HTTP_410,
+ HTTP_411,
+ HTTP_412,
+ HTTP_413,
+ HTTP_414,
+ HTTP_415,
+ HTTP_417,
+ HTTP_500,
+ HTTP_501,
+ HTTP_502,
+ HTTP_503,
+ HTTP_504,
+ HTTP_505,
+ HttpError,
+ HttpServer,
+} = ChromeUtils.import("resource://testing-common/httpd.js");
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+const nsIDocumentEncoder = Ci.nsIDocumentEncoder;
+const replacementChar =
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
+
+function loadContentFile(aFile, aCharset) {
+ // if(aAsIso == undefined) aAsIso = false;
+ if (aCharset == undefined) {
+ aCharset = "UTF-8";
+ }
+
+ var file = do_get_file(aFile);
+
+ var chann = NetUtil.newChannel({
+ uri: Services.io.newFileURI(file),
+ loadUsingSystemPrincipal: true,
+ });
+ chann.contentCharset = aCharset;
+
+ /* var inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ inputStream.init(chann.open());
+ return inputStream.read(file.fileSize);
+ */
+
+ var inputStream = Cc[
+ "@mozilla.org/intl/converter-input-stream;1"
+ ].createInstance(Ci.nsIConverterInputStream);
+ inputStream.init(chann.open(), aCharset, 1024, replacementChar);
+ var str = {},
+ content = "";
+ while (inputStream.readString(4096, str) != 0) {
+ content += str.value;
+ }
+ return content;
+}
diff --git a/dom/base/test/unit/head_xml.js b/dom/base/test/unit/head_xml.js
new file mode 100644
index 0000000000..ed96926c80
--- /dev/null
+++ b/dom/base/test/unit/head_xml.js
@@ -0,0 +1,152 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const I = Ci;
+const C = Cc;
+
+const nsIFile = I.nsIFile;
+const nsIProperties = I.nsIProperties;
+const nsIFileInputStream = I.nsIFileInputStream;
+const nsIInputStream = I.nsIInputStream;
+
+function getParser() {
+ var parser = new DOMParser();
+ parser.forceEnableXULXBL();
+ return parser;
+}
+
+var __testsDirectory = null;
+
+function ParseFile(file) {
+ if (typeof file == "string") {
+ if (!__testsDirectory) {
+ __testsDirectory = do_get_cwd();
+ }
+ var fileObj = __testsDirectory.clone();
+ fileObj.append(file);
+ file = fileObj;
+ }
+
+ Assert.equal(file instanceof nsIFile, true);
+
+ var fileStr =
+ C["@mozilla.org/network/file-input-stream;1"].createInstance(
+ nsIFileInputStream
+ );
+ // Init for readonly reading
+ fileStr.init(file, 0x01, 0o400, nsIFileInputStream.CLOSE_ON_EOF);
+ return ParseXML(fileStr);
+}
+
+function ParseXML(data) {
+ if (typeof data == "string") {
+ return getParser().parseFromString(data, "application/xml");
+ }
+
+ Assert.equal(data instanceof nsIInputStream, true);
+
+ return getParser().parseFromStream(
+ data,
+ "UTF-8",
+ data.available(),
+ "application/xml"
+ );
+}
+
+function DOMSerializer() {
+ return new XMLSerializer();
+}
+
+function SerializeXML(node) {
+ return DOMSerializer().serializeToString(node);
+}
+
+function roundtrip(obj) {
+ if (typeof obj == "string") {
+ return SerializeXML(ParseXML(obj));
+ }
+
+ Assert.equal(Node.isInstance(obj), true);
+ return ParseXML(SerializeXML(obj));
+}
+
+function do_compare_attrs(e1, e2) {
+ const xmlns = "http://www.w3.org/2000/xmlns/";
+
+ var a1 = e1.attributes;
+ var a2 = e2.attributes;
+ for (var i = 0; i < a1.length; ++i) {
+ var att = a1.item(i);
+ // Don't test for namespace decls, since those can just sorta be
+ // scattered about
+ if (att.namespaceURI != xmlns) {
+ var att2 = a2.getNamedItemNS(att.namespaceURI, att.localName);
+ if (!att2) {
+ do_throw(
+ "Missing attribute with namespaceURI '" +
+ att.namespaceURI +
+ "' and localName '" +
+ att.localName +
+ "'"
+ );
+ }
+ Assert.equal(att.value, att2.value);
+ }
+ }
+}
+
+function do_check_equiv(dom1, dom2) {
+ Assert.equal(dom1.nodeType, dom2.nodeType);
+ switch (dom1.nodeType) {
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ Assert.equal(dom1.target, dom2.target);
+ Assert.equal(dom1.data, dom2.data);
+ // fall through
+ case Node.TEXT_NODE:
+ case Node.CDATA_SECTION_NODE:
+ case Node.COMMENT_NODE:
+ Assert.equal(dom1.data, dom2.data);
+ break;
+ case Node.ELEMENT_NODE:
+ Assert.equal(dom1.namespaceURI, dom2.namespaceURI);
+ Assert.equal(dom1.localName, dom2.localName);
+ // Compare attrs in both directions -- do_compare_attrs does a
+ // subset check.
+ do_compare_attrs(dom1, dom2);
+ do_compare_attrs(dom2, dom1);
+ // Fall through
+ case Node.DOCUMENT_NODE:
+ Assert.equal(dom1.childNodes.length, dom2.childNodes.length);
+ for (var i = 0; i < dom1.childNodes.length; ++i) {
+ do_check_equiv(dom1.childNodes.item(i), dom2.childNodes.item(i));
+ }
+ break;
+ }
+}
+
+function do_check_serialize(dom) {
+ do_check_equiv(dom, roundtrip(dom));
+}
+
+function Pipe() {
+ var p = C["@mozilla.org/pipe;1"].createInstance(I.nsIPipe);
+ p.init(false, false, 0, 0xffffffff, null);
+ return p;
+}
+
+function ScriptableInput(arg) {
+ if (arg instanceof I.nsIPipe) {
+ arg = arg.inputStream;
+ }
+
+ var str = C["@mozilla.org/scriptableinputstream;1"].createInstance(
+ I.nsIScriptableInputStream
+ );
+
+ str.init(arg);
+
+ return str;
+}
diff --git a/dom/base/test/unit/isequalnode_data.xml b/dom/base/test/unit/isequalnode_data.xml
new file mode 100644
index 0000000000..4b72f5d502
--- /dev/null
+++ b/dom/base/test/unit/isequalnode_data.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" ?>
+<!DOCTYPE Test [
+ <!ATTLIST test id ID #REQUIRED>
+]>
+
+<root>
+
+ <test id="test_setAttribute">
+ <foo/>
+ <foo/>
+ </test>
+
+ <test id="test_normalization">
+ <bar/>
+ <bar/>
+ </test>
+
+ <test id="test_whitespace">
+
+ <!--
+ Tests here consist of isEqualNode comparisons of the first and third
+ (zero-indexed) child nodes of each test.
+
+ In the typical case this means that the zeroth, second, and fourth
+ children are whitespace and the first and third are the nodes being
+ compared for equality or inequality.
+
+ In some cases, however, where either node is a Text node, this pattern
+ must of necessity be violated. Examples of such tests include the
+ test_text# tests.
+
+ As a result of this, BE CAREFUL NOT TO INTRODUCE STRAY WHITESPACE WHEN
+ EDITING THIS FILE. You have been warned.
+ -->
+
+ <test id="test_pi1">
+ <?pi data?>
+ <?pi data?>
+ </test>
+ <test id="test_pi2">
+ <?pi data?>
+ <?pi data?>
+ </test>
+ <test id="test_pi3">
+ <?pi data?>
+ <?pi data ?>
+ </test>
+ <test id="test_pi4">
+ <?pi ?>
+ <?pi ?>
+ </test>
+ <test id="test_pi5">
+ <?pi?>
+ <?pi ?>
+ </test>
+
+ <test id="test_elt1">
+ <foo></foo>
+ <foo> </foo>
+ </test>
+ <test id="test_elt2">
+ <foo></foo>
+ <foo>
+</foo>
+ </test>
+ <test id="test_elt3">
+ <foo ></foo>
+ <foo></foo>
+ </test>
+ <test id="test_elt4">
+ <bar xmlns="http://example.com/"/>
+ <bar/>
+ </test>
+ <test id="test_elt5">
+ <bar xmlns="http://example.com/"/>
+ <bar xmlns="http://example.com"/>
+ </test>
+
+ <test id="test_comment1">
+ <!--foo-->
+ <!--foo-->
+ </test>
+ <test id="test_comment2">
+ <!--foo-->
+ <!--foo -->
+ </test>
+ <test id="test_comment3">
+ <!--foo-->
+ <!--foo
+-->
+ </test>
+ <test id="test_comment4">
+ <!--
+foo-->
+ <!--
+foo-->
+ </test>
+
+ <test id="test_text1"><placeholder-dont-move/>
+<placeholder-dont-move/>
+<placeholder-dont-move/>
+ </test>
+ <test id="test_text2"><placeholder-dont-move/>
+<placeholder-dont-move/> <placeholder-dont-move/>
+ </test>
+ <test id="test_text3"><placeholder-dont-move/>
+<placeholder-dont-move/><![CDATA[
+]]>
+ </test>
+
+ <test id="test_cdata1">
+ <![CDATA[ ]]><placeholder-dont-move/> <placeholder-dont-move/>
+ </test>
+ <test id="test_cdata2">
+ <![CDATA[ ]]>
+ <![CDATA[ ]]>
+ </test>
+ <test id="test_cdata3">
+ <![CDATA[ ]]>
+ <![CDATA[ ]]>
+ </test>
+ <test id="test_cdata4">
+ <![CDATA[]]>
+ <![CDATA[
+]]>
+ </test>
+ <test id="test_cdata5">
+ <![CDATA[ ]]>
+ <![CDATA[
+]]>
+ </test>
+
+ </test>
+
+ <test id="test_namespaces">
+ <test id="test_ns1">
+ <foo xmlns:quiz="http://example.com/"
+ quiz:q="fun"/>
+ <foo xmlns:f="http://example.com/"
+ f:q="fun"/>
+ </test>
+ <test id="test_ns2">
+ <quiz:foo xmlns:quiz="http://example.com/"
+ q="fun"/>
+ <f:foo xmlns:f="http://example.com/"
+ q="fun"/>
+ </test>
+ </test>
+
+</root>
diff --git a/dom/base/test/unit/nodelist_data_1.xml b/dom/base/test/unit/nodelist_data_1.xml
new file mode 100644
index 0000000000..ddde596a27
--- /dev/null
+++ b/dom/base/test/unit/nodelist_data_1.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" ?>
+<!DOCTYPE Test [
+ <!ATTLIST test id ID #REQUIRED>
+ <!ATTLIST foo:test id ID #REQUIRED>
+ <!ATTLIST foo2:test id ID #REQUIRED>
+ <!ATTLIST bar:test id ID #REQUIRED>
+]>
+
+<!-- Comment -->
+
+<?This-is-a-PI ?>
+
+<root xmlns:foo="foo"
+ xmlns:bar="bar"
+ xmlns:foo2="foo">
+
+ <test id="test1">
+ </test>
+
+ <test id="test2">
+ <!-- Another comment -->
+ <test id="test3">
+ </test>
+
+ <test id="test4" xmlns="foo">
+ <test id="test5">
+ </test>
+
+ <bar:test id="test6" />
+ </test>
+
+ <foo:test id="test7">
+ </foo:test>
+
+ <foo2:test id="test8">
+ <?Another-PI ?>
+ <baz />
+ </foo2:test>
+
+ <bar:test id="test9">
+ </bar:test>
+ </test>
+
+ <foo:test id="test10">
+ <foo2:test id="test11">
+ <bar:test id="test12">
+ </bar:test>
+ </foo2:test>
+ </foo:test>
+
+ <foo2:test id="test13">
+ </foo2:test>
+
+ <bar:test id="test14">
+ </bar:test>
+
+</root>
+
diff --git a/dom/base/test/unit/nodelist_data_2.xhtml b/dom/base/test/unit/nodelist_data_2.xhtml
new file mode 100644
index 0000000000..247ef0353c
--- /dev/null
+++ b/dom/base/test/unit/nodelist_data_2.xhtml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!DOCTYPE window [
+ <!ENTITY fooSet '
+ <addOrRemove foo:foo="foo"/>
+ <addOrRemove foo:foo="bar"/>
+ <addOrRemove foo:bar="foo"/>
+ <addOrRemove foo:bar="bar"/>
+ <addOrRemove foo:foo="foo" foo:bar="bar"/>
+ <addOrRemove foo2:foo="foo"/>
+ <addOrRemove foo="foo"/>
+ <addOrRemove foo="bar"/>
+ <addOrRemove bar="bar" foo="foo"/>
+'>
+]>
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:foo="foo"
+ xmlns:foo2="foo"
+ xmlns:bar="bar"
+ >
+ <vbox id="boxes">
+ <groupbox id="master1" foo:foo="bar">
+ &fooSet;
+ <groupbox foo:foo="foo">
+ &fooSet;
+ </groupbox>
+ </groupbox>
+ <groupbox id="master2" foo:foo="foo">
+ &fooSet;
+ <groupbox foo:foo="bar">
+ &fooSet;
+ </groupbox>
+ </groupbox>
+ <groupbox id="master3">
+ &fooSet;
+ <groupbox foo2:foo="foo">
+ &fooSet;
+ </groupbox>
+ </groupbox>
+ <groupbox id="external">
+ &fooSet;
+ </groupbox>
+ </vbox>
+</window>
diff --git a/dom/base/test/unit/test_blockParsing.js b/dom/base/test/unit/test_blockParsing.js
new file mode 100644
index 0000000000..cdc4f852e1
--- /dev/null
+++ b/dom/base/test/unit/test_blockParsing.js
@@ -0,0 +1,113 @@
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+function delay() {
+ return new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+}
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com"],
+});
+
+// XML document with only a <script> tag as the document element.
+const PAGE_URL = "http://example.com/";
+server.registerPathHandler("/", (request, response) => {
+ response.setHeader("Content-Type", "application/xhtml+xml");
+ response.write(String.raw`<!DOCTYPE html>
+ <script xmlns="http://www.w3.org/1999/xhtml" src="slow.js"/>
+ `);
+});
+
+let resolveResumeScriptPromise;
+let resumeScriptPromise = new Promise(resolve => {
+ resolveResumeScriptPromise = resolve;
+});
+
+let resolveScriptRequestPromise;
+let scriptRequestPromise = new Promise(resolve => {
+ resolveScriptRequestPromise = resolve;
+});
+
+// An empty script which waits to complete until `resumeScriptPromise`
+// resolves.
+server.registerPathHandler("/slow.js", async (request, response) => {
+ response.processAsync();
+ resolveScriptRequestPromise();
+
+ await resumeScriptPromise;
+
+ response.setHeader("Content-type", "text/javascript");
+ response.write("");
+ response.finish();
+});
+
+// Tests that attempting to block parsing for a <script> tag while the
+// parser is already blocked is handled correctly, and does not cause
+// crashes or hangs.
+add_task(async function test_nested_blockParser() {
+ // Wait for the document element of the page to be inserted, and block
+ // the parser when it is.
+ let resolveBlockerPromise;
+ let blockerPromise;
+ let docElementPromise = TestUtils.topicObserved(
+ "document-element-inserted",
+ doc => {
+ if (doc.location.href === PAGE_URL) {
+ blockerPromise = new Promise(resolve => {
+ resolveBlockerPromise = resolve;
+ });
+
+ doc.blockParsing(blockerPromise);
+ return true;
+ }
+ return false;
+ }
+ );
+
+ // Begin loading the page.
+ let pagePromise = XPCShellContentUtils.loadContentPage(PAGE_URL, {
+ remote: false,
+ });
+
+ // Wait for the document element to be inserted.
+ await docElementPromise;
+ // Wait for the /slow.js script request to initiate.
+ await scriptRequestPromise;
+
+ // Make some trips through the event loop to be safe.
+ await delay();
+ await delay();
+
+ // Allow the /slow.js script request to complete.
+ resolveResumeScriptPromise();
+
+ // Make some trips through the event loop so that the <script> request
+ // unblocks the parser.
+ await delay();
+ await delay();
+
+ // Release the parser blocker added in the observer above.
+ resolveBlockerPromise();
+
+ // Make some trips through the event loop to allow the parser to
+ // unblock.
+ await delay();
+ await delay();
+
+ // Wait for the document to finish loading, and then close it.
+ let page = await pagePromise;
+ await page.close();
+});
diff --git a/dom/base/test/unit/test_bug553888.js b/dom/base/test/unit/test_bug553888.js
new file mode 100644
index 0000000000..b136660ab5
--- /dev/null
+++ b/dom/base/test/unit/test_bug553888.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var server = new HttpServer();
+server.start(-1);
+
+const SERVER_PORT = server.identity.primaryPort;
+const HTTP_BASE = "http://localhost:" + SERVER_PORT;
+const redirectPath = "/redirect";
+const headerCheckPath = "/headerCheck";
+const redirectURL = HTTP_BASE + redirectPath;
+const headerCheckURL = HTTP_BASE + headerCheckPath;
+
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", headerCheckURL, false);
+}
+
+function headerCheckHandler(metadata, response) {
+ try {
+ let headerValue = metadata.getHeader("X-Custom-Header");
+ Assert.equal(headerValue, "present");
+ } catch (e) {
+ do_throw("No header present after redirect");
+ }
+ try {
+ metadata.getHeader("X-Unwanted-Header");
+ do_throw("Unwanted header present after redirect");
+ } catch (x) {}
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain");
+ response.write("");
+}
+
+function run_test() {
+ server.registerPathHandler(redirectPath, redirectHandler);
+ server.registerPathHandler(headerCheckPath, headerCheckHandler);
+
+ do_test_pending();
+ var request = new XMLHttpRequest();
+ request.open("GET", redirectURL, true);
+ request.setRequestHeader("X-Custom-Header", "present");
+ request.addEventListener("readystatechange", function () {
+ if (request.readyState == 4) {
+ Assert.equal(request.status, 200);
+ server.stop(do_test_finished);
+ }
+ });
+ request.send();
+ try {
+ request.setRequestHeader("X-Unwanted-Header", "present");
+ do_throw("Shouldn't be able to set a header after send");
+ } catch (x) {}
+}
diff --git a/dom/base/test/unit/test_bug737966.js b/dom/base/test/unit/test_bug737966.js
new file mode 100644
index 0000000000..a5b8f48425
--- /dev/null
+++ b/dom/base/test/unit/test_bug737966.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* If charset parameter is invalid, the encoding should be detected as UTF-8 */
+
+function run_test() {
+ let body = '<?xml version="1.0"><html>%c3%80</html>';
+ let result = '<?xml version="1.0"><html>\u00c0</html>';
+
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "data:text/xml;charset=abc," + body, false);
+ xhr.send(null);
+
+ Assert.equal(xhr.responseText, result);
+}
diff --git a/dom/base/test/unit/test_cancelPrefetch.js b/dom/base/test/unit/test_cancelPrefetch.js
new file mode 100644
index 0000000000..6663d60fbb
--- /dev/null
+++ b/dom/base/test/unit/test_cancelPrefetch.js
@@ -0,0 +1,149 @@
+var prefetch = Cc["@mozilla.org/prefetch-service;1"].getService(
+ Ci.nsIPrefetchService
+);
+
+var ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+var ios = Services.io;
+var prefs = Services.prefs;
+
+var parser = new DOMParser();
+
+var doc;
+
+var docbody =
+ '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' +
+ '<link id="node1"/><link id="node2"/>' +
+ "</body></html>";
+
+var node1;
+var node2;
+
+function run_test() {
+ prefs.setBoolPref("network.prefetch-next", true);
+
+ doc = parser.parseFromString(docbody, "text/html");
+
+ node1 = doc.getElementById("node1");
+ node2 = doc.getElementById("node2");
+
+ run_next_test();
+}
+
+add_test(function test_cancel1() {
+ var uri = ios.newURI("http://localhost/1");
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+
+ prefetch.prefetchURI(uri, referrerInfo, node1, true);
+
+ Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue");
+
+ // Trying to prefetch again the same uri with the same node will fail.
+ var didFail = 0;
+
+ try {
+ prefetch.prefetchURI(uri, referrerInfo, node1, true);
+ } catch (e) {
+ didFail = 1;
+ }
+
+ Assert.ok(
+ didFail == 1,
+ "Prefetching the same request with the same node fails."
+ );
+
+ Assert.ok(prefetch.hasMoreElements(), "There is still request in the queue");
+
+ prefetch.cancelPrefetchPreloadURI(uri, node1);
+
+ Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue");
+ run_next_test();
+});
+
+add_test(function test_cancel2() {
+ // Prefetch a uri with 2 different nodes. There should be 2 request
+ // in the queue and canceling one will not cancel the other.
+
+ var uri = ios.newURI("http://localhost/1");
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+
+ prefetch.prefetchURI(uri, referrerInfo, node1, true);
+ prefetch.prefetchURI(uri, referrerInfo, node2, true);
+
+ Assert.ok(prefetch.hasMoreElements(), "There are requests in the queue");
+
+ prefetch.cancelPrefetchPreloadURI(uri, node1);
+
+ Assert.ok(
+ prefetch.hasMoreElements(),
+ "There is still one more request in the queue"
+ );
+
+ prefetch.cancelPrefetchPreloadURI(uri, node2);
+
+ Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue");
+ run_next_test();
+});
+
+add_test(function test_cancel3() {
+ // Request a prefetch of a uri. Trying to cancel a prefetch for the same uri
+ // with a different node will fail.
+ var uri = ios.newURI("http://localhost/1");
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+
+ prefetch.prefetchURI(uri, referrerInfo, node1, true);
+
+ Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue");
+
+ var didFail = 0;
+
+ try {
+ prefetch.cancelPrefetchPreloadURI(uri, node2, true);
+ } catch (e) {
+ didFail = 1;
+ }
+ Assert.ok(didFail == 1, "Canceling the request failed");
+
+ Assert.ok(
+ prefetch.hasMoreElements(),
+ "There is still a request in the queue"
+ );
+
+ prefetch.cancelPrefetchPreloadURI(uri, node1);
+ Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue");
+ run_next_test();
+});
+
+add_test(function test_cancel4() {
+ // Request a prefetch of a uri. Trying to cancel a prefetch for a different uri
+ // with the same node will fail.
+ var uri1 = ios.newURI("http://localhost/1");
+ var uri2 = ios.newURI("http://localhost/2");
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri1);
+
+ prefetch.prefetchURI(uri1, referrerInfo, node1, true);
+
+ Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue");
+
+ var didFail = 0;
+
+ try {
+ prefetch.cancelPrefetchPreloadURI(uri2, node1);
+ } catch (e) {
+ didFail = 1;
+ }
+ Assert.ok(didFail == 1, "Canceling the request failed");
+
+ Assert.ok(
+ prefetch.hasMoreElements(),
+ "There is still a request in the queue"
+ );
+
+ prefetch.cancelPrefetchPreloadURI(uri1, node1);
+ Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue");
+ run_next_test();
+});
diff --git a/dom/base/test/unit/test_chromeutils_base64.js b/dom/base/test/unit/test_chromeutils_base64.js
new file mode 100644
index 0000000000..ca2722016b
--- /dev/null
+++ b/dom/base/test/unit/test_chromeutils_base64.js
@@ -0,0 +1,140 @@
+"use strict";
+
+function run_test() {
+ test_base64URLEncode();
+ test_base64URLDecode();
+}
+
+// Test vectors from RFC 4648, section 10.
+let textTests = {
+ "": "",
+ f: "Zg",
+ fo: "Zm8",
+ foo: "Zm9v",
+ foob: "Zm9vYg",
+ fooba: "Zm9vYmE",
+ foobar: "Zm9vYmFy",
+};
+
+// Examples from RFC 4648, section 9.
+let binaryTests = [
+ {
+ decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]),
+ encoded: "FPucA9l-",
+ },
+ {
+ decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]),
+ encoded: "FPucA9k",
+ },
+ {
+ decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]),
+ encoded: "FPucAw",
+ },
+];
+
+function padEncodedValue(value) {
+ switch (value.length % 4) {
+ case 0:
+ return value;
+ case 2:
+ return value + "==";
+ case 3:
+ return value + "=";
+ default:
+ throw new TypeError("Invalid encoded value");
+ }
+}
+
+function testEncode(input, encoded) {
+ equal(
+ ChromeUtils.base64URLEncode(input, { pad: false }),
+ encoded,
+ encoded + " without padding"
+ );
+ equal(
+ ChromeUtils.base64URLEncode(input, { pad: true }),
+ padEncodedValue(encoded),
+ encoded + " with padding"
+ );
+}
+
+function test_base64URLEncode() {
+ throws(
+ _ => ChromeUtils.base64URLEncode(new Uint8Array(0)),
+ /TypeError/,
+ "Should require encoding options"
+ );
+ throws(
+ _ => ChromeUtils.base64URLEncode(new Uint8Array(0), {}),
+ /TypeError/,
+ "Encoding should require the padding option"
+ );
+
+ for (let { decoded, encoded } of binaryTests) {
+ testEncode(decoded, encoded);
+ }
+
+ let textEncoder = new TextEncoder();
+ for (let decoded of Object.keys(textTests)) {
+ let input = textEncoder.encode(decoded);
+ testEncode(input, textTests[decoded]);
+ }
+}
+
+function testDecode(input, decoded) {
+ let buffer = ChromeUtils.base64URLDecode(input, { padding: "reject" });
+ deepEqual(new Uint8Array(buffer), decoded, input + " with padding rejected");
+
+ let paddedValue = padEncodedValue(input);
+ buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "ignore" });
+ deepEqual(new Uint8Array(buffer), decoded, input + " with padding ignored");
+
+ if (paddedValue.length > input.length) {
+ throws(
+ _ => ChromeUtils.base64URLDecode(paddedValue, { padding: "reject" }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ paddedValue + " with padding rejected should throw"
+ );
+
+ throws(
+ _ => ChromeUtils.base64URLDecode(input, { padding: "require" }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ input + " with padding required should throw"
+ );
+
+ buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "require" });
+ deepEqual(
+ new Uint8Array(buffer),
+ decoded,
+ paddedValue + " with padding required"
+ );
+ }
+}
+
+function test_base64URLDecode() {
+ throws(
+ _ => ChromeUtils.base64URLDecode(""),
+ /TypeError/,
+ "Should require decoding options"
+ );
+ throws(
+ _ => ChromeUtils.base64URLDecode("", {}),
+ /TypeError/,
+ "Decoding should require the padding option"
+ );
+ throws(
+ _ => ChromeUtils.base64URLDecode("", { padding: "chocolate" }),
+ /TypeError/,
+ "Decoding should throw for invalid padding policy"
+ );
+
+ for (let { decoded, encoded } of binaryTests) {
+ testDecode(encoded, decoded);
+ }
+
+ let textEncoder = new TextEncoder();
+ for (let decoded of Object.keys(textTests)) {
+ let expectedBuffer = textEncoder.encode(decoded);
+ testDecode(textTests[decoded], expectedBuffer);
+ }
+}
diff --git a/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js b/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js
new file mode 100644
index 0000000000..f7d6a89e71
--- /dev/null
+++ b/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js
@@ -0,0 +1,40 @@
+"use strict";
+
+// Test ChromeUtils.getXPCOMErrorName
+
+add_task(function test_getXPCOMErrorName() {
+ info("Force the initialization of NSS to get the error names right");
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+ Assert.equal(
+ ChromeUtils.getXPCOMErrorName(Cr.NS_OK),
+ "NS_OK",
+ "getXPCOMErrorName works for NS_OK"
+ );
+
+ Assert.equal(
+ ChromeUtils.getXPCOMErrorName(Cr.NS_ERROR_FAILURE),
+ "NS_ERROR_FAILURE",
+ "getXPCOMErrorName works for NS_ERROR_FAILURE"
+ );
+
+ const nssErrors = Cc["@mozilla.org/nss_errors_service;1"].getService(
+ Ci.nsINSSErrorsService
+ );
+ Assert.equal(
+ ChromeUtils.getXPCOMErrorName(
+ nssErrors.getXPCOMFromNSSError(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)
+ ),
+ "NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, SEC_ERROR_IO)",
+ "getXPCOMErrorName works for NSS_SEC_ERROR_BASE"
+ );
+ // See https://searchfox.org/mozilla-central/rev/a48e21143960b383004afa9ff9411c5cf6d5a958/security/nss/lib/util/secerr.h#20
+ const SEC_ERROR_BAD_DATA = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE + 2;
+ Assert.equal(
+ ChromeUtils.getXPCOMErrorName(
+ nssErrors.getXPCOMFromNSSError(SEC_ERROR_BAD_DATA)
+ ),
+ "NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, SEC_ERROR_BAD_DATA)",
+ "getXPCOMErrorName works for NSS's SEC_ERROR_BAD_DATA"
+ );
+});
diff --git a/dom/base/test/unit/test_chromeutils_shallowclone.js b/dom/base/test/unit/test_chromeutils_shallowclone.js
new file mode 100644
index 0000000000..03e0d443c9
--- /dev/null
+++ b/dom/base/test/unit/test_chromeutils_shallowclone.js
@@ -0,0 +1,60 @@
+"use strict";
+
+add_task(function test_shallowclone() {
+ // Check that shallow cloning an object with regular properties,
+ // results into a new object with all properties from the source object.
+ const fullyCloneableObject = {
+ numProp: 123,
+ strProp: "str",
+ boolProp: true,
+ arrayProp: [{ item1: "1", item2: "2" }],
+ fnProp() {
+ return "fn result";
+ },
+ promise: Promise.resolve("promised-value"),
+ weakmap: new WeakMap(),
+ proxy: new Proxy({}, {}),
+ };
+
+ let clonedObject = ChromeUtils.shallowClone(fullyCloneableObject);
+
+ Assert.deepEqual(
+ clonedObject,
+ fullyCloneableObject,
+ "Got the expected cloned object for an object with regular properties"
+ );
+
+ // Check that shallow cloning an object with getters and setters properties,
+ // results into a new object without all the properties from the source object excluded
+ // its getters and setters.
+ const objectWithGetterAndSetter = {
+ get myGetter() {
+ return "getter result";
+ },
+ set mySetter(v) {},
+ myFunction() {
+ return "myFunction result";
+ },
+ };
+
+ clonedObject = ChromeUtils.shallowClone(objectWithGetterAndSetter);
+
+ Assert.deepEqual(
+ clonedObject,
+ {
+ myFunction: objectWithGetterAndSetter.myFunction,
+ },
+ "Got the expected cloned object for an object with getters and setters"
+ );
+
+ // Check that shallow cloning a proxy object raises the expected exception..
+ const proxyObject = new Proxy(fullyCloneableObject, {});
+
+ Assert.throws(
+ () => {
+ ChromeUtils.shallowClone(proxyObject);
+ },
+ /Shallow cloning a proxy object is not allowed/,
+ "Got the expected error on ChromeUtils.shallowClone called on a proxy object"
+ );
+});
diff --git a/dom/base/test/unit/test_delete_range.xml b/dom/base/test/unit/test_delete_range.xml
new file mode 100644
index 0000000000..c8d50bd32a
--- /dev/null
+++ b/dom/base/test/unit/test_delete_range.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+This file holds serialized tests for DOM Range tests on extractContents.
+The <test/> elements designate individual tests. Each one has the following:
+
+* A <source/> element, designating the start conditions of the test,
+* A <result/> element, designating what the source element should look like
+ after the extraction,
+* An <extract/> element, designating what the extracted content should look like.
+
+The <split/> element designates a split between two DOM nodes. This element will
+be removed before the actual test, and the two nodes on either side will not be
+merged.
+
+The <empty-cdata/> element designates an empty character data section. Before
+the test executes, this element is replaced with an actual CDATASection node.
+
+For the <source/> element, there are four attributes:
+
+* startContainer: A XPath to the startContainer of the range.
+* endContainer: A XPath to the endContainer of the range.
+* startOffset: The startOffset of the range.
+* endOffset: The endOffset of the range.
+
+Note this test may need updating with a fix for bug 401276. The spec states
+adjacent nodes after an extraction should be merged if possible, but using the
+normalize() method, which could have unintended side effects... For now, we're
+not permitting that, pending clarification.
+
+Please make sure the first test in this document always tests a range where the
+start container and end container are the same text node, and where the start
+offset and end offsets are valid and inequal. Some of the additional range
+tests (after the bulk of the delete/extract tests) depend on it.
+ -->
+<root>
+ <!-- Extracting from a text node. -->
+ <test>
+ <source startContainer="text()[1]"
+ endContainer="text()[1]"
+ startOffset="4"
+ endOffset="10">The quick fox</source>
+ <result>The fox</result>
+ <extract>quick </extract>
+ </test>
+
+ <!-- Extracting from a CDATA section. -->
+ <test>
+ <source startContainer="text()[1]"
+ endContainer="text()[1]"
+ startOffset="4"
+ endOffset="10"><![CDATA[The quick fox]]></source>
+ <result><![CDATA[The fox]]></result>
+ <extract><![CDATA[quick ]]></extract>
+ </test>
+
+ <!-- Snipping the start of a text node. -->
+ <test>
+ <source startContainer="text()[1]"
+ endContainer="text()[1]"
+ startOffset="0"
+ endOffset="4"><![CDATA[The quick fox]]></source>
+ <result><![CDATA[quick fox]]></result>
+ <extract><![CDATA[The ]]></extract>
+ </test>
+
+ <!-- Extracting from a comment. -->
+ <test>
+ <source startContainer="comment()[1]"
+ endContainer="comment()[1]"
+ startOffset="4"
+ endOffset="10"><!--The quick fox--></source>
+ <result><!--The fox--></result>
+ <extract><!--quick --></extract>
+ </test>
+
+ <!-- Snipping whole nodes -->
+ <test>
+ <source startContainer="."
+ endContainer="."
+ startOffset="0"
+ endOffset="2">Fox<fox/>Fox<bear/><!--comment--></source>
+ <result>Fox<bear/><!--comment--></result>
+ <extract>Fox<fox/></extract>
+ </test>
+
+ <!-- Snipping whole nodes -->
+ <test>
+ <source startContainer="."
+ endContainer="."
+ startOffset="1"
+ endOffset="3">Fox<fox/>Fox<bear/><!--comment--></source>
+ <result>Fox<bear/><!--comment--></result>
+ <extract><fox/>Fox</extract>
+ </test>
+
+ <!-- Snipping a mixture of nodes and portions of text -->
+ <test>
+ <source startContainer="text()[2]"
+ startOffset="1"
+ endContainer="comment()[1]"
+ endOffset="3">Fox<fox/>Fox<bear><?cow ?></bear><!--comment--></source>
+ <result>Fox<fox/>F<!--ment--></result>
+ <extract>ox<bear><?cow ?></bear><!--com--></extract>
+ </test>
+
+ <!-- Extracting with a collapsed range from a text node. -->
+ <test>
+ <source startContainer="text()[1]"
+ endContainer="text()[1]"
+ startOffset="4"
+ endOffset="4">The quick fox</source>
+ <result>The quick fox</result>
+ <extract/>
+ </test>
+
+ <!-- Extracting with a collapsed range from a non-text node. -->
+ <test>
+ <source startContainer="."
+ endContainer="."
+ startOffset="0"
+ endOffset="0">Fox<fox/>Fox<bear/><!--comment--></source>
+ <result>Fox<fox/>Fox<bear/><!--comment--></result>
+ <extract/>
+ </test>
+</root>
diff --git a/dom/base/test/unit/test_error_codes.js b/dom/base/test/unit/test_error_codes.js
new file mode 100644
index 0000000000..72b9e371d1
--- /dev/null
+++ b/dom/base/test/unit/test_error_codes.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var gExpectedStatus = null;
+var gNextTestFunc = null;
+
+var prefs = Services.prefs;
+
+var asyncXHR = {
+ load() {
+ var request = new XMLHttpRequest();
+ request.open("GET", "http://localhost:4444/test_error_code.xml", true);
+
+ var self = this;
+ request.addEventListener("error", function (event) {
+ self.onError(event);
+ });
+ request.send(null);
+ },
+ onError: function doAsyncRequest_onError(event) {
+ var request = event.target.channel.QueryInterface(Ci.nsIRequest);
+ Assert.equal(request.status, gExpectedStatus);
+ gNextTestFunc();
+ },
+};
+
+function run_test() {
+ do_test_pending();
+ do_timeout(0, run_test_pt1);
+}
+
+// network offline
+function run_test_pt1() {
+ try {
+ Services.io.manageOfflineStatus = false;
+ } catch (e) {}
+ Services.io.offline = true;
+ prefs.setBoolPref("network.dns.offline-localhost", false);
+ // We always resolve localhost as it's hardcoded without the following pref:
+ prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ gExpectedStatus = Cr.NS_ERROR_OFFLINE;
+ gNextTestFunc = run_test_pt2;
+ dump("Testing error returned by async XHR when the network is offline\n");
+ asyncXHR.load();
+}
+
+// connection refused
+function run_test_pt2() {
+ Services.io.offline = false;
+ prefs.clearUserPref("network.dns.offline-localhost");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+
+ gExpectedStatus = Cr.NS_ERROR_CONNECTION_REFUSED;
+ gNextTestFunc = end_test;
+ dump("Testing error returned by aync XHR when the connection is refused\n");
+ asyncXHR.load();
+}
+
+function end_test() {
+ do_test_finished();
+}
diff --git a/dom/base/test/unit/test_generate_xpath.js b/dom/base/test/unit/test_generate_xpath.js
new file mode 100644
index 0000000000..e8c7043690
--- /dev/null
+++ b/dom/base/test/unit/test_generate_xpath.js
@@ -0,0 +1,85 @@
+function run_test() {
+ test_generate_xpath();
+}
+
+// TEST CODE
+
+function test_generate_xpath() {
+ let docString = `
+ <html>
+ <body>
+ <label><input type="checkbox" id="input1" />Input 1</label>
+ <label><input type="checkbox" id="input2'" />Input 2</label>
+ <label><input type="checkbox" id='"input3"' />Input 3</label>
+ <label><input type="checkbox"/>Input 4</label>
+ <label><input type="checkbox" />Input 5</label>
+ </body>
+ </html>
+ `;
+ let doc = getParser().parseFromString(docString, "text/html");
+
+ // Test generate xpath for body.
+ info("Test generate xpath for body node");
+ let body = doc.getElementsByTagName("body")[0];
+ let bodyXPath = body.generateXPath();
+ let bodyExpXPath = "/xhtml:html/xhtml:body";
+ equal(bodyExpXPath, bodyXPath, " xpath generated for body");
+
+ // Test generate xpath for input with id.
+ info("Test generate xpath for input with id");
+ let inputWithId = doc.getElementById("input1");
+ let inputWithIdXPath = inputWithId.generateXPath();
+ let inputWithIdExpXPath = "//xhtml:input[@id='input1']";
+ equal(
+ inputWithIdExpXPath,
+ inputWithIdXPath,
+ " xpath generated for input with id"
+ );
+
+ // Test generate xpath for input with id has single quote.
+ info("Test generate xpath for input with id has single quote");
+ let inputWithIdSingleQuote = doc.getElementsByTagName("input")[1];
+ let inputWithIdXPathSingleQuote = inputWithIdSingleQuote.generateXPath();
+ let inputWithIdExpXPathSingleQuote = '//xhtml:input[@id="input2\'"]';
+ equal(
+ inputWithIdExpXPathSingleQuote,
+ inputWithIdXPathSingleQuote,
+ " xpath generated for input with id"
+ );
+
+ // Test generate xpath for input with id has double quote.
+ info("Test generate xpath for input with id has double quote");
+ let inputWithIdDoubleQuote = doc.getElementsByTagName("input")[2];
+ let inputWithIdXPathDoubleQuote = inputWithIdDoubleQuote.generateXPath();
+ let inputWithIdExpXPathDoubleQuote = "//xhtml:input[@id='\"input3\"']";
+ equal(
+ inputWithIdExpXPathDoubleQuote,
+ inputWithIdXPathDoubleQuote,
+ " xpath generated for input with id"
+ );
+
+ // Test generate xpath for input with id has both single and double quote.
+ info("Test generate xpath for input with id has single and double quote");
+ let inputWithIdSingleDoubleQuote = doc.getElementsByTagName("input")[3];
+ inputWithIdSingleDoubleQuote.setAttribute("id", "\"input'4");
+ let inputWithIdXPathSingleDoubleQuote =
+ inputWithIdSingleDoubleQuote.generateXPath();
+ let inputWithIdExpXPathSingleDoubleQuote =
+ "//xhtml:input[@id=concat('\"input',\"'\",'4')]";
+ equal(
+ inputWithIdExpXPathSingleDoubleQuote,
+ inputWithIdXPathSingleDoubleQuote,
+ " xpath generated for input with id"
+ );
+
+ // Test generate xpath for input without id.
+ info("Test generate xpath for input without id");
+ let inputNoId = doc.getElementsByTagName("input")[4];
+ let inputNoIdXPath = inputNoId.generateXPath();
+ let inputNoIdExpXPath = "/xhtml:html/xhtml:body/xhtml:label[5]/xhtml:input";
+ equal(
+ inputNoIdExpXPath,
+ inputNoIdXPath,
+ " xpath generated for input without id"
+ );
+}
diff --git a/dom/base/test/unit/test_htmlserializer.js b/dom/base/test/unit/test_htmlserializer.js
new file mode 100644
index 0000000000..17995dddde
--- /dev/null
+++ b/dom/base/test/unit/test_htmlserializer.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testAttrsOutsideBodyWithRawBodyOnly() {
+ // Create a simple HTML document in which the header contains a tag with set
+ // attributes
+ const htmlString = `<html><head><link rel="stylesheet" href="foo"/></head><body>some content</body></html>`;
+
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlString, "text/html");
+
+ // Sanity check
+ const linkElems = doc.head.getElementsByTagName("link");
+ Assert.equal(
+ linkElems.length,
+ 1,
+ "document header should contain one link element"
+ );
+ Assert.equal(
+ linkElems[0].rel,
+ "stylesheet",
+ "link element should have rel attribute set"
+ );
+
+ // Verify that the combination of raw output and body-only does not allow
+ // attributes from header elements to creep into the output string
+ const encoder = Cu.createDocumentEncoder("text/html");
+ encoder.init(
+ doc,
+ "text/html",
+ Ci.nsIDocumentEncoder.OutputRaw | Ci.nsIDocumentEncoder.OutputBodyOnly
+ );
+
+ const result = encoder.encodeToString();
+ Assert.equal(
+ result,
+ "<body>some content</body>",
+ "output should not contain attributes from head elements"
+ );
+});
+
+add_task(async function testAttrsInsideBodyWithRawBodyOnly() {
+ // Create a simple HTML document in which the body contains a tag with set
+ // attributes
+ const htmlString = `<html><head><link rel="stylesheet" href="foo"/></head><body><span id="foo">some content</span></body></html>`;
+
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlString, "text/html");
+
+ // Sanity check
+ const spanElem = doc.getElementById("foo");
+ Assert.ok(spanElem, "should be able to get span element by ID");
+
+ // Verify that the combination of raw output and body-only does not strip
+ // tag attributes inside the body
+ const encoder = Cu.createDocumentEncoder("text/html");
+ encoder.init(
+ doc,
+ "text/html",
+ Ci.nsIDocumentEncoder.OutputRaw | Ci.nsIDocumentEncoder.OutputBodyOnly
+ );
+
+ const result = encoder.encodeToString();
+ Assert.equal(
+ result,
+ `<body><span id="foo">some content</span></body>`,
+ "output should not contain attributes from head elements"
+ );
+});
diff --git a/dom/base/test/unit/test_isequalnode.js b/dom/base/test/unit/test_isequalnode.js
new file mode 100644
index 0000000000..1b9491fcdc
--- /dev/null
+++ b/dom/base/test/unit/test_isequalnode.js
@@ -0,0 +1,390 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// TEST CODE
+
+var doc; // cache for use in all tests
+
+add_setup(function init() {
+ doc = ParseFile("isequalnode_data.xml");
+});
+
+add_task(function test_isEqualNode_setAttribute() {
+ // NOTE: 0, 2 are whitespace
+ var test1 = doc.getElementById("test_setAttribute");
+ var node1 = test1.childNodes.item(1);
+ var node2 = test1.childNodes.item(3);
+
+ check_eq_nodes(node1, node2);
+
+ node1.setAttribute("bar", "baz");
+ check_neq_nodes(node1, node2);
+
+ node2.setAttribute("bar", "baz");
+ check_eq_nodes(node1, node2);
+
+ // the null namespace is equivalent to no namespace -- section 1.3.3
+ // (XML Namespaces) of DOM 3 Core
+ node1.setAttributeNS(null, "quux", "17");
+ check_neq_nodes(node1, node2);
+
+ node2.setAttribute("quux", "17");
+ check_eq_nodes(node1, node2);
+
+ node2.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet");
+ check_neq_nodes(node1, node2);
+
+ node1.setAttribute("seamonkey", "rheet");
+ check_neq_nodes(node1, node2);
+
+ node1.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet");
+ check_neq_nodes(node1, node2);
+
+ // this overwrites the namespaced "seamonkey" attribute added to node2
+ // earlier, because this simply sets whatever attribute has the fully
+ // qualified name "seamonkey" (the setAttributeNS attribute string wasn't
+ // prefixed) -- consequently, node1 and node2 are still unequal
+ node2.setAttribute("seamonkey", "rheet");
+ check_neq_nodes(node1, node2);
+});
+
+add_task(function test_isEqualNode_clones() {
+ // tests all elements and attributes in the document
+ var all_elts = doc.getElementsByTagName("*");
+ for (var i = 0; i < all_elts.length; i++) {
+ var elt = all_elts.item(i);
+ check_eq_nodes(elt, elt.cloneNode(true));
+
+ var attrs = elt.attributes;
+ for (var j = 0; j < attrs.length; j++) {
+ var attr = attrs.item(j);
+ check_eq_nodes(attr, attr.cloneNode(true));
+ }
+ }
+
+ var elm = doc.createElement("foo");
+ check_eq_nodes(elm, elm.cloneNode(true));
+ check_eq_nodes(elm, elm.cloneNode(false));
+
+ elm.setAttribute("fiz", "eit");
+ check_eq_nodes(elm, elm.cloneNode(true));
+ check_eq_nodes(elm, elm.cloneNode(false));
+
+ elm.setAttributeNS("http://example.com/", "trendoid", "arthroscope");
+ check_eq_nodes(elm, elm.cloneNode(true));
+ check_eq_nodes(elm, elm.cloneNode(false));
+
+ var elm2 = elm.cloneNode(true);
+ check_eq_nodes(elm, elm2);
+
+ const TEXT = "fetishist";
+
+ elm.textContent = TEXT;
+ check_neq_nodes(elm, elm2);
+
+ check_neq_nodes(elm, elm.cloneNode(false));
+ check_eq_nodes(elm, elm.cloneNode(true));
+
+ elm2.appendChild(doc.createTextNode(TEXT));
+ check_eq_nodes(elm, elm2);
+
+ var att = doc.createAttribute("bar");
+ check_eq_nodes(att, att.cloneNode(true));
+ check_eq_nodes(att, att.cloneNode(false));
+});
+
+add_task(function test_isEqualNode_variety() {
+ const nodes = [
+ doc.createElement("foo"),
+ doc.createElementNS("http://example.com/", "foo"),
+ doc.createElementNS("http://example.org/", "foo"),
+ doc.createElementNS("http://example.com/", "FOO"),
+ doc.createAttribute("foo", "href='biz'"),
+ doc.createAttributeNS("http://example.com/", "foo", "href='biz'"),
+ doc.createTextNode("foo"),
+ doc.createTextNode(" "),
+ doc.createTextNode(" "),
+ doc.createComment("foo"),
+ doc.createProcessingInstruction("foo", "href='biz'"),
+ doc.implementation.createDocumentType("foo", "href='biz'", ""),
+ doc.implementation.createDocument("http://example.com/", "foo", null),
+ doc.createDocumentFragment(),
+ ];
+
+ for (var i = 0; i < nodes.length; i++) {
+ for (var j = i; j < nodes.length; j++) {
+ if (i == j) {
+ check_eq_nodes(nodes[i], nodes[j]);
+ } else {
+ check_neq_nodes(nodes[i], nodes[j]);
+ }
+ }
+ }
+});
+
+add_task(function test_isEqualNode_normalization() {
+ var norm = doc.getElementById("test_normalization");
+ var node1 = norm.childNodes.item(1);
+ var node2 = norm.childNodes.item(3);
+
+ check_eq_nodes(node1, node2);
+
+ node1.appendChild(doc.createTextNode(""));
+ check_neq_nodes(node1, node2);
+
+ node1.normalize();
+ check_eq_nodes(node1, node2);
+
+ node2.appendChild(doc.createTextNode("fun"));
+ node2.appendChild(doc.createTextNode("ctor"));
+ node1.appendChild(doc.createTextNode("functor"));
+ check_neq_nodes(node1, node2);
+
+ node1.normalize();
+ check_neq_nodes(node1, node2);
+
+ node2.normalize();
+ check_eq_nodes(node1, node2);
+
+ // reset
+ while (node1.hasChildNodes()) {
+ node1.removeChild(node1.childNodes.item(0));
+ }
+ while (node2.hasChildNodes()) {
+ node2.removeChild(node2.childNodes.item(0));
+ }
+
+ // attribute normalization testing
+
+ var at1 = doc.createAttribute("foo");
+ var at2 = doc.createAttribute("foo");
+ check_eq_nodes(at1, at2);
+
+ // Attr.appendChild isn't implemented yet (bug 56758), so don't run this yet
+ if (false) {
+ at1.appendChild(doc.createTextNode("rasp"));
+ at2.appendChild(doc.createTextNode("rasp"));
+ check_eq_nodes(at1, at2);
+
+ at1.appendChild(doc.createTextNode(""));
+ check_neq_nodes(at1, at2);
+
+ at1.normalize();
+ check_eq_nodes(at1, at2);
+
+ at1.appendChild(doc.createTextNode("berry"));
+ check_neq_nodes(at1, at2);
+
+ at2.appendChild(doc.createTextNode("ber"));
+ check_neq_nodes(at1, at2);
+
+ at2.appendChild(doc.createTextNode("ry"));
+ check_neq_nodes(at1, at2);
+
+ at1.normalize();
+ check_neq_nodes(at1, at2);
+
+ at2.normalize();
+ check_eq_nodes(at1, at2);
+ }
+
+ node1.setAttributeNode(at1);
+ check_neq_nodes(node1, node2);
+
+ node2.setAttributeNode(at2);
+ check_eq_nodes(node1, node2);
+
+ var n1text1 = doc.createTextNode("ratfink");
+ var n1elt = doc.createElement("fruitcake");
+ var n1text2 = doc.createTextNode("hydrospanner");
+
+ node1.appendChild(n1text1);
+ node1.appendChild(n1elt);
+ node1.appendChild(n1text2);
+
+ check_neq_nodes(node1, node2);
+
+ var n2text1a = doc.createTextNode("rat");
+ var n2text1b = doc.createTextNode("fink");
+ var n2elt = doc.createElement("fruitcake");
+ var n2text2 = doc.createTextNode("hydrospanner");
+
+ node2.appendChild(n2text1b);
+ node2.appendChild(n2elt);
+ node2.appendChild(n2text2);
+ check_neq_nodes(node1, node2);
+
+ node2.insertBefore(n2text1a, n2text1b);
+ check_neq_nodes(node1, node2);
+
+ var tmp_node1 = node1.cloneNode(true);
+ tmp_node1.normalize();
+ var tmp_node2 = node2.cloneNode(true);
+ tmp_node2.normalize();
+ check_eq_nodes(tmp_node1, tmp_node2);
+
+ n2elt.appendChild(doc.createTextNode(""));
+ check_neq_nodes(node1, node2);
+
+ tmp_node1 = node1.cloneNode(true);
+ tmp_node1.normalize();
+ tmp_node2 = node2.cloneNode(true);
+ tmp_node2.normalize();
+ check_eq_nodes(tmp_node1, tmp_node2);
+
+ var typeText1 = doc.createTextNode("type");
+ n2elt.appendChild(typeText1);
+ tmp_node1 = node1.cloneNode(true);
+ tmp_node1.normalize();
+ tmp_node2 = node2.cloneNode(true);
+ tmp_node2.normalize();
+ check_neq_nodes(tmp_node1, tmp_node2);
+
+ n1elt.appendChild(doc.createTextNode("typedef"));
+ tmp_node1 = node1.cloneNode(true);
+ tmp_node1.normalize();
+ tmp_node2 = node2.cloneNode(true);
+ tmp_node2.normalize();
+ check_neq_nodes(tmp_node1, tmp_node2);
+ check_neq_nodes(n1elt, n2elt);
+
+ var typeText2 = doc.createTextNode("def");
+ n2elt.appendChild(typeText2);
+ tmp_node1 = node1.cloneNode(true);
+ tmp_node1.normalize();
+ tmp_node2 = node2.cloneNode(true);
+ tmp_node2.normalize();
+ check_eq_nodes(tmp_node1, tmp_node2);
+ check_neq_nodes(node1, node2);
+
+ n2elt.insertBefore(doc.createTextNode(""), typeText2);
+ check_neq_nodes(node1, node2);
+
+ n2elt.insertBefore(doc.createTextNode(""), typeText2);
+ check_neq_nodes(node1, node2);
+
+ n2elt.insertBefore(doc.createTextNode(""), typeText1);
+ check_neq_nodes(node1, node2);
+
+ node1.normalize();
+ node2.normalize();
+ check_eq_nodes(node1, node2);
+});
+
+add_task(function test_isEqualNode_whitespace() {
+ equality_check_kids("test_pi1", true);
+ equality_check_kids("test_pi2", true);
+ equality_check_kids("test_pi3", false);
+ equality_check_kids("test_pi4", true);
+ equality_check_kids("test_pi5", true);
+
+ equality_check_kids("test_elt1", false);
+ equality_check_kids("test_elt2", false);
+ equality_check_kids("test_elt3", true);
+ equality_check_kids("test_elt4", false);
+ equality_check_kids("test_elt5", false);
+
+ equality_check_kids("test_comment1", true);
+ equality_check_kids("test_comment2", false);
+ equality_check_kids("test_comment3", false);
+ equality_check_kids("test_comment4", true);
+
+ equality_check_kids("test_text1", true);
+ equality_check_kids("test_text2", false);
+ equality_check_kids("test_text3", false);
+
+ equality_check_kids("test_cdata1", false);
+ equality_check_kids("test_cdata2", true);
+ equality_check_kids("test_cdata3", false);
+ equality_check_kids("test_cdata4", false);
+ equality_check_kids("test_cdata5", false);
+});
+
+add_task(function test_isEqualNode_namespaces() {
+ equality_check_kids("test_ns1", false);
+ equality_check_kids("test_ns2", false);
+
+ // XXX want more tests here!
+});
+
+// XXX This test is skipped:
+// should Node.isEqualNode(null) throw or return false?
+add_task(function test_isEqualNode_null() {
+ check_neq_nodes(doc, null);
+
+ var elts = doc.getElementsByTagName("*");
+ for (var i = 0; i < elts.length; i++) {
+ var elt = elts.item(i);
+ check_neq_nodes(elt, null);
+
+ var attrs = elt.attributes;
+ for (var j = 0; j < attrs.length; j++) {
+ var att = attrs.item(j);
+ check_neq_nodes(att, null);
+
+ for (var k = 0; k < att.childNodes.length; k++) {
+ check_neq_nodes(att.childNodes.item(k), null);
+ }
+ }
+ }
+}).skip();
+
+add_task(function test_isEqualNode_wholeDoc() {
+ doc = ParseFile("isequalnode_data.xml");
+ var doc2 = ParseFile("isequalnode_data.xml");
+ var tw1 = doc.createTreeWalker(doc, NodeFilter.SHOW_ALL, null);
+ var tw2 = doc2.createTreeWalker(doc2, NodeFilter.SHOW_ALL, null);
+ do {
+ check_eq_nodes(tw1.currentNode, tw2.currentNode);
+ tw1.nextNode();
+ } while (tw2.nextNode());
+});
+
+// TESTING FUNCTIONS
+
+/**
+ * Compares the first and third (zero-indexed) child nodes of the element
+ * (typically to allow whitespace) referenced by parentId for isEqualNode
+ * equality or inequality based on the value of areEqual.
+ *
+ * Note that this means that the contents of the element referenced by parentId
+ * are whitespace-sensitive, and a stray space introduced during an edit to the
+ * file could result in a correct but unexpected (in)equality failure.
+ */
+function equality_check_kids(parentId, areEqual) {
+ var parent = doc.getElementById(parentId);
+ var kid1 = parent.childNodes.item(1);
+ var kid2 = parent.childNodes.item(3);
+
+ if (areEqual) {
+ check_eq_nodes(kid1, kid2);
+ } else {
+ check_neq_nodes(kid1, kid2);
+ }
+}
+
+function check_eq_nodes(n1, n2) {
+ if (n1 && !n1.isEqualNode(n2)) {
+ do_throw(n1 + " should be equal to " + n2);
+ }
+ if (n2 && !n2.isEqualNode(n1)) {
+ do_throw(n2 + " should be equal to " + n1);
+ }
+ if (!n1 && !n2) {
+ do_throw("nodes both null!");
+ }
+}
+
+function check_neq_nodes(n1, n2) {
+ if (n1 && n1.isEqualNode(n2)) {
+ do_throw(n1 + " should not be equal to " + n2);
+ }
+ if (n2 && n2.isEqualNode(n1)) {
+ do_throw(n2 + " should not be equal to " + n1);
+ }
+ if (!n1 && !n2) {
+ do_throw("n1 and n2 both null!");
+ }
+}
diff --git a/dom/base/test/unit/test_js_dev_error_interceptor.js b/dom/base/test/unit/test_js_dev_error_interceptor.js
new file mode 100644
index 0000000000..8ccc0dcaf8
--- /dev/null
+++ b/dom/base/test/unit/test_js_dev_error_interceptor.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function errors() {
+ return [
+ // The following two errors MUST NOT be captured.
+ new Error("This is an error: " + Math.random()),
+ new RangeError("This is a RangeError: " + Math.random()),
+ new TypeError("This is a TypeError: " + Math.random()),
+ "This is a string: " + Math.random(),
+ null,
+ undefined,
+ Math.random(),
+ {},
+
+ // The following errors MUST be captured.
+ new SyntaxError("This is a SyntaxError: " + Math.random()),
+ new ReferenceError("This is a ReferenceError: " + Math.random()),
+ ];
+}
+
+function isDeveloperError(e) {
+ if (e == null || typeof e != "object") {
+ return false;
+ }
+
+ return e.constructor == SyntaxError || e.constructor == ReferenceError;
+}
+
+function run_test() {
+ ChromeUtils.clearRecentJSDevError();
+ Assert.equal(ChromeUtils.recentJSDevError, undefined);
+
+ for (let exn of errors()) {
+ ChromeUtils.clearRecentJSDevError();
+ try {
+ throw exn;
+ } catch (e) {
+ // Discard error.
+ }
+ if (isDeveloperError(exn)) {
+ Assert.equal(ChromeUtils.recentJSDevError.message, "" + exn);
+ } else {
+ Assert.equal(ChromeUtils.recentJSDevError, undefined);
+ }
+ ChromeUtils.clearRecentJSDevError();
+ Assert.equal(ChromeUtils.recentJSDevError, undefined);
+ }
+}
diff --git a/dom/base/test/unit/test_nodelist.js b/dom/base/test/unit/test_nodelist.js
new file mode 100644
index 0000000000..0ad24b9bd5
--- /dev/null
+++ b/dom/base/test/unit/test_nodelist.js
@@ -0,0 +1,345 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+function run_test() {
+ test_getElementsByTagName();
+ test_getElementsByTagNameNS();
+ test_getElementsByAttribute();
+ test_getElementsByAttributeNS();
+
+ // What else should we test?
+ // XXXbz we need more tests here to test liveness!
+}
+
+function test_getElementsByTagName() {
+ var doc = ParseFile("nodelist_data_1.xml");
+ var root = doc.documentElement;
+
+ // Check that getElementsByTagName returns an HTMLCollection.
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagName("*")),
+ "HTMLCollection"
+ );
+ Assert.ok(
+ ChromeUtils.getClassName(root.getElementsByTagName("*")),
+ "HTMLCollection"
+ );
+
+ // Check that getElementsByTagName excludes the element it's called on.
+ Assert.equal(
+ doc.getElementsByTagName("*").length,
+ root.getElementsByTagName("*").length + 1
+ );
+ Assert.equal(doc.getElementById("test2").getElementsByTagName("*").length, 8);
+ Assert.equal(
+ doc.getElementById("test2").getElementsByTagName("test").length,
+ 3
+ );
+
+ // Check that the first element of getElementsByTagName on the document is
+ // the right thing.
+ Assert.equal(doc.getElementsByTagName("*").item(0), root);
+
+ // Check that we get the right things in the right order
+ var numTests = doc.getElementsByTagName("test").length;
+ Assert.equal(numTests, 5);
+
+ for (var i = 1; i <= numTests; ++i) {
+ Assert.ok(Element.isInstance(doc.getElementById("test" + i)));
+ Assert.equal(
+ doc.getElementById("test" + i),
+ doc.getElementsByTagName("test").item(i - 1)
+ );
+ }
+
+ // Check that we handle tagnames containing ':' correctly
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagName("foo:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagName("foo:test").length, 2);
+
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagName("foo2:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagName("foo2:test").length, 3);
+
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagName("bar:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagName("bar:test").length, 4);
+}
+
+function test_getElementsByTagNameNS() {
+ var doc = ParseFile("nodelist_data_1.xml");
+ var root = doc.documentElement;
+
+ // Check that getElementsByTagNameNS returns an HTMLCollection.
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagNameNS("*", "*")),
+ "HTMLCollection"
+ );
+ Assert.equal(
+ ChromeUtils.getClassName(root.getElementsByTagNameNS("*", "*")),
+ "HTMLCollection"
+ );
+
+ // Check that passing "" and null for the namespace URI gives the same result
+ var list1 = doc.getElementsByTagNameNS("", "test");
+ var list2 = doc.getElementsByTagNameNS(null, "test");
+ Assert.equal(list1.length, list2.length);
+ for (var i = 0; i < list1.length; ++i) {
+ Assert.equal(list1.item(i), list2.item(i));
+ }
+
+ // Check that getElementsByTagNameNS excludes the element it's called on.
+ Assert.equal(
+ doc.getElementsByTagNameNS("*", "*").length,
+ root.getElementsByTagNameNS("*", "*").length + 1
+ );
+ Assert.equal(
+ doc.getElementById("test2").getElementsByTagNameNS("*", "*").length,
+ 8
+ );
+ Assert.equal(
+ doc.getElementById("test2").getElementsByTagNameNS("", "test").length,
+ 1
+ );
+ Assert.equal(
+ doc.getElementById("test2").getElementsByTagNameNS("*", "test").length,
+ 7
+ );
+
+ // Check that the first element of getElementsByTagNameNS on the document is
+ // the right thing.
+ Assert.equal(doc.getElementsByTagNameNS("*", "*").item(0), root);
+ Assert.equal(doc.getElementsByTagNameNS(null, "*").item(0), root);
+
+ // Check that we get the right things in the right order
+
+ var numTests = doc.getElementsByTagNameNS("*", "test").length;
+ Assert.equal(numTests, 14);
+
+ for (i = 1; i <= numTests; ++i) {
+ Assert.ok(Element.isInstance(doc.getElementById("test" + i)));
+ Assert.equal(
+ doc.getElementById("test" + i),
+ doc.getElementsByTagNameNS("*", "test").item(i - 1)
+ );
+ }
+
+ // Check general proper functioning of having a non-wildcard namespace.
+ var test2 = doc.getElementById("test2");
+ Assert.equal(doc.getElementsByTagNameNS("", "test").length, 3);
+ Assert.equal(test2.getElementsByTagNameNS("", "test").length, 1);
+ Assert.equal(doc.getElementsByTagNameNS("foo", "test").length, 7);
+ Assert.equal(test2.getElementsByTagNameNS("foo", "test").length, 4);
+ Assert.equal(doc.getElementsByTagNameNS("foo2", "test").length, 0);
+ Assert.equal(test2.getElementsByTagNameNS("foo2", "test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("bar", "test").length, 4);
+ Assert.equal(test2.getElementsByTagNameNS("bar", "test").length, 2);
+
+ // Check that we handle tagnames containing ':' correctly
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "foo:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagNameNS(null, "foo:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("foo", "foo:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("bar", "foo:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("*", "foo:test").length, 0);
+
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "foo2:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagNameNS(null, "foo2:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("foo2", "foo2:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("bar", "foo2:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("*", "foo2:test").length, 0);
+
+ Assert.equal(
+ ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "bar:test")),
+ "HTMLCollection"
+ );
+ Assert.equal(doc.getElementsByTagNameNS(null, "bar:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("bar", "bar:test").length, 0);
+ Assert.equal(doc.getElementsByTagNameNS("*", "bar:test").length, 0);
+
+ // Check that previously-unknown namespaces are handled right. Note that we
+ // can just hardcode the strings, since we're running only once in XPCshell.
+ // If someone wants to run these in a browser, some use of Math.random() may
+ // be in order.
+ list1 = doc.getElementsByTagNameNS("random-bogus-namespace", "foo");
+ list2 = doc.documentElement.getElementsByTagNameNS(
+ "random-bogus-namespace2",
+ "foo"
+ );
+ Assert.notEqual(list1, list2);
+ Assert.equal(list1.length, 0);
+ Assert.equal(list2.length, 0);
+ var newNode = doc.createElementNS("random-bogus-namespace", "foo");
+ doc.documentElement.appendChild(newNode);
+ newNode = doc.createElementNS("random-bogus-namespace2", "foo");
+ doc.documentElement.appendChild(newNode);
+ Assert.equal(list1.length, 1);
+ Assert.equal(list2.length, 1);
+}
+
+function test_getElementsByAttribute() {
+ var doc = ParseFile("nodelist_data_2.xhtml");
+ var root = doc.documentElement;
+
+ Assert.equal(ChromeUtils.getClassName(root), "XULElement");
+
+ Assert.equal(
+ ChromeUtils.getClassName(root.getElementsByAttribute("foo", "foo")),
+ "HTMLCollection"
+ );
+
+ var master1 = doc.getElementById("master1");
+ var master2 = doc.getElementById("master2");
+ var master3 = doc.getElementById("master3");
+ var external = doc.getElementById("external");
+
+ Assert.equal(ChromeUtils.getClassName(master1), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(master2), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(master3), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(external), "XULElement");
+
+ // Basic tests
+ Assert.equal(root.getElementsByAttribute("foo", "foo").length, 14);
+ Assert.equal(master1.getElementsByAttribute("foo", "foo").length, 4);
+
+ Assert.equal(root.getElementsByAttribute("foo", "bar").length, 7);
+ Assert.equal(master1.getElementsByAttribute("foo", "bar").length, 2);
+
+ Assert.equal(root.getElementsByAttribute("bar", "bar").length, 7);
+ Assert.equal(master1.getElementsByAttribute("bar", "bar").length, 2);
+
+ Assert.equal(root.getElementsByAttribute("foo", "*").length, 21);
+ Assert.equal(master1.getElementsByAttribute("foo", "*").length, 6);
+
+ // Test the various combinations of attributes with colons in the name
+ Assert.equal(root.getElementsByAttribute("foo:foo", "foo").length, 16);
+ Assert.equal(master1.getElementsByAttribute("foo:foo", "foo").length, 5);
+ Assert.equal(master2.getElementsByAttribute("foo:foo", "foo").length, 4);
+ Assert.equal(master3.getElementsByAttribute("foo:foo", "foo").length, 4);
+ Assert.equal(external.getElementsByAttribute("foo:foo", "foo").length, 2);
+
+ Assert.equal(root.getElementsByAttribute("foo:foo", "bar").length, 9);
+ Assert.equal(master1.getElementsByAttribute("foo:foo", "bar").length, 2);
+ Assert.equal(master2.getElementsByAttribute("foo:foo", "bar").length, 3);
+ Assert.equal(master3.getElementsByAttribute("foo:foo", "bar").length, 2);
+ Assert.equal(external.getElementsByAttribute("foo:foo", "bar").length, 1);
+
+ Assert.equal(root.getElementsByAttribute("foo:bar", "foo").length, 7);
+ Assert.equal(master1.getElementsByAttribute("foo:bar", "foo").length, 2);
+ Assert.equal(master2.getElementsByAttribute("foo:bar", "foo").length, 2);
+ Assert.equal(master3.getElementsByAttribute("foo:bar", "foo").length, 2);
+ Assert.equal(external.getElementsByAttribute("foo:bar", "foo").length, 1);
+
+ Assert.equal(root.getElementsByAttribute("foo:bar", "bar").length, 14);
+ Assert.equal(master1.getElementsByAttribute("foo:bar", "bar").length, 4);
+ Assert.equal(master2.getElementsByAttribute("foo:bar", "bar").length, 4);
+ Assert.equal(master3.getElementsByAttribute("foo:bar", "bar").length, 4);
+ Assert.equal(external.getElementsByAttribute("foo:bar", "bar").length, 2);
+
+ Assert.equal(root.getElementsByAttribute("foo2:foo", "foo").length, 8);
+ Assert.equal(master1.getElementsByAttribute("foo2:foo", "foo").length, 2);
+ Assert.equal(master2.getElementsByAttribute("foo2:foo", "foo").length, 2);
+ Assert.equal(master3.getElementsByAttribute("foo2:foo", "foo").length, 3);
+ Assert.equal(external.getElementsByAttribute("foo2:foo", "foo").length, 1);
+
+ Assert.equal(root.getElementsByAttribute("foo:foo", "*").length, 25);
+ Assert.equal(master1.getElementsByAttribute("foo:foo", "*").length, 7);
+ Assert.equal(master2.getElementsByAttribute("foo:foo", "*").length, 7);
+ Assert.equal(master3.getElementsByAttribute("foo:foo", "*").length, 6);
+ Assert.equal(external.getElementsByAttribute("foo:foo", "*").length, 3);
+
+ Assert.equal(root.getElementsByAttribute("foo2:foo", "bar").length, 0);
+ Assert.equal(root.getElementsByAttribute("foo:foo", "baz").length, 0);
+}
+
+function test_getElementsByAttributeNS() {
+ var doc = ParseFile("nodelist_data_2.xhtml");
+ var root = doc.documentElement;
+
+ Assert.equal(ChromeUtils.getClassName(root), "XULElement");
+
+ // Check that getElementsByAttributeNS returns an HTMLCollection.
+ Assert.equal(
+ ChromeUtils.getClassName(root.getElementsByAttributeNS("*", "*", "*")),
+ "HTMLCollection"
+ );
+
+ var master1 = doc.getElementById("master1");
+ var master2 = doc.getElementById("master2");
+ var master3 = doc.getElementById("master3");
+ var external = doc.getElementById("external");
+
+ Assert.equal(ChromeUtils.getClassName(master1), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(master2), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(master3), "XULElement");
+ Assert.equal(ChromeUtils.getClassName(external), "XULElement");
+
+ // Test wildcard namespace
+ Assert.equal(root.getElementsByAttributeNS("*", "foo", "foo").length, 38);
+ Assert.equal(master1.getElementsByAttributeNS("*", "foo", "foo").length, 11);
+ Assert.equal(master2.getElementsByAttributeNS("*", "foo", "foo").length, 10);
+ Assert.equal(master3.getElementsByAttributeNS("*", "foo", "foo").length, 11);
+
+ Assert.equal(root.getElementsByAttributeNS("*", "foo", "bar").length, 16);
+ Assert.equal(master1.getElementsByAttributeNS("*", "foo", "bar").length, 4);
+ Assert.equal(master2.getElementsByAttributeNS("*", "foo", "bar").length, 5);
+ Assert.equal(master3.getElementsByAttributeNS("*", "foo", "bar").length, 4);
+
+ Assert.equal(root.getElementsByAttributeNS("*", "bar", "bar").length, 21);
+ Assert.equal(master1.getElementsByAttributeNS("*", "bar", "bar").length, 6);
+ Assert.equal(master2.getElementsByAttributeNS("*", "bar", "bar").length, 6);
+ Assert.equal(master3.getElementsByAttributeNS("*", "bar", "bar").length, 6);
+
+ Assert.equal(root.getElementsByAttributeNS("*", "foo", "*").length, 54);
+ Assert.equal(master1.getElementsByAttributeNS("*", "foo", "*").length, 15);
+ Assert.equal(master2.getElementsByAttributeNS("*", "foo", "*").length, 15);
+ Assert.equal(master3.getElementsByAttributeNS("*", "foo", "*").length, 15);
+
+ // Test null namespace. This should be the same as getElementsByAttribute.
+ Assert.equal(
+ root.getElementsByAttributeNS("", "foo", "foo").length,
+ root.getElementsByAttribute("foo", "foo").length
+ );
+ Assert.equal(
+ master1.getElementsByAttributeNS("", "foo", "foo").length,
+ master1.getElementsByAttribute("foo", "foo").length
+ );
+ Assert.equal(
+ master2.getElementsByAttributeNS("", "foo", "foo").length,
+ master2.getElementsByAttribute("foo", "foo").length
+ );
+ Assert.equal(
+ master3.getElementsByAttributeNS("", "foo", "foo").length,
+ master3.getElementsByAttribute("foo", "foo").length
+ );
+
+ // Test namespace "foo"
+ Assert.equal(root.getElementsByAttributeNS("foo", "foo", "foo").length, 24);
+ Assert.equal(master1.getElementsByAttributeNS("foo", "foo", "foo").length, 7);
+ Assert.equal(master2.getElementsByAttributeNS("foo", "foo", "foo").length, 6);
+ Assert.equal(master3.getElementsByAttributeNS("foo", "foo", "foo").length, 7);
+
+ Assert.equal(root.getElementsByAttributeNS("foo", "foo", "bar").length, 9);
+ Assert.equal(master1.getElementsByAttributeNS("foo", "foo", "bar").length, 2);
+ Assert.equal(master2.getElementsByAttributeNS("foo", "foo", "bar").length, 3);
+ Assert.equal(master3.getElementsByAttributeNS("foo", "foo", "bar").length, 2);
+
+ Assert.equal(root.getElementsByAttributeNS("foo", "bar", "foo").length, 7);
+ Assert.equal(master1.getElementsByAttributeNS("foo", "bar", "foo").length, 2);
+ Assert.equal(master2.getElementsByAttributeNS("foo", "bar", "foo").length, 2);
+ Assert.equal(master3.getElementsByAttributeNS("foo", "bar", "foo").length, 2);
+
+ Assert.equal(root.getElementsByAttributeNS("foo", "bar", "bar").length, 14);
+ Assert.equal(master1.getElementsByAttributeNS("foo", "bar", "bar").length, 4);
+ Assert.equal(master2.getElementsByAttributeNS("foo", "bar", "bar").length, 4);
+ Assert.equal(master3.getElementsByAttributeNS("foo", "bar", "bar").length, 4);
+}
diff --git a/dom/base/test/unit/test_normalize.js b/dom/base/test/unit/test_normalize.js
new file mode 100644
index 0000000000..b7d89f14f8
--- /dev/null
+++ b/dom/base/test/unit/test_normalize.js
@@ -0,0 +1,100 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ /*
+ * NOTE: [i] is not allowed in this test, since it's done via classinfo and
+ * we don't have that in xpcshell; the workaround is item(i). Suck.
+ */
+ init();
+
+ test_element();
+
+ // more tests would be nice here (such as for documents), but the primary
+ // uses of Node.normalize() are in test_element; stuff beyond this is either
+ // unimplemented or is unlikely to be used all that much within a browser
+ // DOM implementation
+}
+
+// TEST CODE
+
+var doc; // cache for use in all tests
+
+function init() {
+ doc = ParseFile("empty_document.xml");
+}
+
+function test_element() {
+ var x = doc.createElement("funk");
+
+ // one empty Text node
+ x.appendChild(doc.createTextNode(""));
+ Assert.equal(x.childNodes.length, 1);
+
+ x.normalize();
+ Assert.equal(x.childNodes.length, 0);
+
+ // multiple empty Text nodes
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(doc.createTextNode(""));
+ Assert.equal(x.childNodes.length, 2);
+
+ x.normalize();
+ Assert.equal(x.childNodes.length, 0);
+
+ // empty Text node followed by other Text node
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ Assert.equal(x.childNodes.length, 2);
+
+ x.normalize();
+ Assert.equal(x.childNodes.length, 1);
+ Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi");
+
+ // Text node followed by empty Text node
+ clearKids(x);
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ x.appendChild(doc.createTextNode(""));
+ Assert.equal(x.childNodes.length, 2);
+
+ x.normalize();
+ Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi");
+
+ // Text node followed by empty Text node followed by other Node
+ clearKids(x);
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(doc.createElement("jazzy"));
+ Assert.equal(x.childNodes.length, 3);
+
+ x.normalize();
+ Assert.equal(x.childNodes.length, 2);
+ Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi");
+ Assert.equal(x.childNodes.item(1).nodeName, "jazzy");
+
+ // Nodes are recursively normalized
+ clearKids(x);
+ var kid = doc.createElement("eit");
+ kid.appendChild(doc.createTextNode(""));
+
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(kid);
+ Assert.equal(x.childNodes.length, 3);
+ Assert.equal(x.childNodes.item(2).childNodes.length, 1);
+
+ x.normalize();
+ Assert.equal(x.childNodes.length, 2);
+ Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi");
+ Assert.equal(x.childNodes.item(1).childNodes.length, 0);
+}
+
+// UTILITY FUNCTIONS
+
+function clearKids(node) {
+ while (node.hasChildNodes()) {
+ node.removeChild(node.childNodes.item(0));
+ }
+}
diff --git a/dom/base/test/unit/test_range.js b/dom/base/test/unit/test_range.js
new file mode 100644
index 0000000000..8b9f5c0b8b
--- /dev/null
+++ b/dom/base/test/unit/test_range.js
@@ -0,0 +1,465 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const UNORDERED_TYPE = 8; // XPathResult.ANY_UNORDERED_NODE_TYPE
+
+/**
+ * Determine if the data node has only ignorable white-space.
+ *
+ * @return NodeFilter.FILTER_SKIP if it does.
+ * @return NodeFilter.FILTER_ACCEPT otherwise.
+ */
+function isWhitespace(aNode) {
+ return /\S/.test(aNode.nodeValue)
+ ? NodeFilter.FILTER_SKIP
+ : NodeFilter.FILTER_ACCEPT;
+}
+
+/**
+ * Create a DocumentFragment with cloned children equaling a node's children.
+ *
+ * @param aNode The node to copy from.
+ *
+ * @return DocumentFragment node.
+ */
+function getFragment(aNode) {
+ var frag = aNode.ownerDocument.createDocumentFragment();
+ for (var i = 0; i < aNode.childNodes.length; i++) {
+ frag.appendChild(aNode.childNodes.item(i).cloneNode(true));
+ }
+ return frag;
+}
+
+// Goodies from head_content.js
+const parser = getParser();
+
+/**
+ * Translate an XPath to a DOM node. This method uses a document
+ * fragment as context node.
+ *
+ * @param aContextNode The context node to apply the XPath to.
+ * @param aPath The XPath to use.
+ *
+ * @return Node The target node retrieved from the XPath.
+ */
+function evalXPathInDocumentFragment(aContextNode, aPath) {
+ Assert.equal(ChromeUtils.getClassName(aContextNode), "DocumentFragment");
+ Assert.ok(aContextNode.childNodes.length);
+ if (aPath == ".") {
+ return aContextNode;
+ }
+
+ // Separate the fragment's xpath lookup from the rest.
+ var firstSlash = aPath.indexOf("/");
+ if (firstSlash == -1) {
+ firstSlash = aPath.length;
+ }
+ var prefix = aPath.substr(0, firstSlash);
+ var realPath = aPath.substr(firstSlash + 1);
+ if (!realPath) {
+ realPath = ".";
+ }
+
+ // Set up a special node filter to look among the fragment's child nodes.
+ var childIndex = 1;
+ var bracketIndex = prefix.indexOf("[");
+ if (bracketIndex != -1) {
+ childIndex = Number(
+ prefix.substring(bracketIndex + 1, prefix.indexOf("]"))
+ );
+ Assert.ok(childIndex > 0);
+ prefix = prefix.substr(0, bracketIndex);
+ }
+
+ var targetType = NodeFilter.SHOW_ELEMENT;
+ var targetNodeName = prefix;
+ if (prefix.indexOf("processing-instruction(") == 0) {
+ targetType = NodeFilter.SHOW_PROCESSING_INSTRUCTION;
+ targetNodeName = prefix.substring(
+ prefix.indexOf("(") + 2,
+ prefix.indexOf(")") - 1
+ );
+ }
+ switch (prefix) {
+ case "text()":
+ targetType = NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION;
+ targetNodeName = null;
+ break;
+ case "comment()":
+ targetType = NodeFilter.SHOW_COMMENT;
+ targetNodeName = null;
+ break;
+ case "node()":
+ targetType = NodeFilter.SHOW_ALL;
+ targetNodeName = null;
+ }
+
+ var filter = {
+ count: 0,
+
+ // NodeFilter
+ acceptNode: function acceptNode(aNode) {
+ if (aNode.parentNode != aContextNode) {
+ // Don't bother looking at kids either.
+ return NodeFilter.FILTER_REJECT;
+ }
+
+ if (targetNodeName && targetNodeName != aNode.nodeName) {
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ this.count++;
+ if (this.count != childIndex) {
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ return NodeFilter.FILTER_ACCEPT;
+ },
+ };
+
+ // Look for the node matching the step from the document fragment.
+ var walker = aContextNode.ownerDocument.createTreeWalker(
+ aContextNode,
+ targetType,
+ filter
+ );
+ var targetNode = walker.nextNode();
+ Assert.notEqual(targetNode, null);
+
+ // Apply our remaining xpath to the found node.
+ var expr = aContextNode.ownerDocument.createExpression(realPath, null);
+ var result = expr.evaluate(targetNode, UNORDERED_TYPE, null);
+ return result.singleNodeValue;
+}
+
+/**
+ * Get a DOM range corresponding to the test's source node.
+ *
+ * @param aSourceNode <source/> element with range information.
+ * @param aFragment DocumentFragment generated with getFragment().
+ *
+ * @return Range object.
+ */
+function getRange(aSourceNode, aFragment) {
+ Assert.ok(Element.isInstance(aSourceNode));
+ Assert.equal(ChromeUtils.getClassName(aFragment), "DocumentFragment");
+ var doc = aSourceNode.ownerDocument;
+
+ var containerPath = aSourceNode.getAttribute("startContainer");
+ var startContainer = evalXPathInDocumentFragment(aFragment, containerPath);
+ var startOffset = Number(aSourceNode.getAttribute("startOffset"));
+
+ containerPath = aSourceNode.getAttribute("endContainer");
+ var endContainer = evalXPathInDocumentFragment(aFragment, containerPath);
+ var endOffset = Number(aSourceNode.getAttribute("endOffset"));
+
+ var range = doc.createRange();
+ range.setStart(startContainer, startOffset);
+ range.setEnd(endContainer, endOffset);
+ return range;
+}
+
+/**
+ * Get the document for a given path, and clean it up for our tests.
+ *
+ * @param aPath The path to the local document.
+ */
+function getParsedDocument(aPath) {
+ return do_parse_document(aPath, "application/xml").then(
+ processParsedDocument
+ );
+}
+
+function processParsedDocument(doc) {
+ Assert.ok(doc.documentElement.localName != "parsererror");
+ Assert.equal(ChromeUtils.getClassName(doc), "XMLDocument");
+
+ // Clean out whitespace.
+ var walker = doc.createTreeWalker(
+ doc,
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION,
+ isWhitespace
+ );
+ while (walker.nextNode()) {
+ var parent = walker.currentNode.parentNode;
+ parent.removeChild(walker.currentNode);
+ walker.currentNode = parent;
+ }
+
+ // Clean out mandatory splits between nodes.
+ var splits = doc.getElementsByTagName("split");
+ var i;
+ for (i = splits.length - 1; i >= 0; i--) {
+ let node = splits.item(i);
+ node.remove();
+ }
+ splits = null;
+
+ // Replace empty CDATA sections.
+ var emptyData = doc.getElementsByTagName("empty-cdata");
+ for (i = emptyData.length - 1; i >= 0; i--) {
+ let node = emptyData.item(i);
+ var cdata = doc.createCDATASection("");
+ node.parentNode.replaceChild(cdata, node);
+ }
+
+ return doc;
+}
+
+/**
+ * Run the extraction tests.
+ */
+function run_extract_test() {
+ var filePath = "test_delete_range.xml";
+ getParsedDocument(filePath).then(do_extract_test);
+}
+
+function do_extract_test(doc) {
+ var tests = doc.getElementsByTagName("test");
+
+ // Run our deletion, extraction tests.
+ for (var i = 0; i < tests.length; i++) {
+ dump("Configuring for test " + i + "\n");
+ var currentTest = tests.item(i);
+
+ // Validate the test is properly formatted for what this harness expects.
+ var baseSource = currentTest.firstChild;
+ Assert.equal(baseSource.nodeName, "source");
+ var baseResult = baseSource.nextSibling;
+ Assert.equal(baseResult.nodeName, "result");
+ var baseExtract = baseResult.nextSibling;
+ Assert.equal(baseExtract.nodeName, "extract");
+ Assert.equal(baseExtract.nextSibling, null);
+
+ /* We do all our tests on DOM document fragments, derived from the test
+ element's children. This lets us rip the various fragments to shreds,
+ while preserving the original elements so we can make more copies of
+ them.
+
+ After the range's extraction or deletion is done, we use
+ Node.isEqualNode() between the altered source fragment and the
+ result fragment. We also run isEqualNode() between the extracted
+ fragment and the fragment from the baseExtract node. If they are not
+ equal, we have failed a test.
+
+ We also have to ensure the original nodes on the end points of the
+ range are still in the source fragment. This is bug 332148. The nodes
+ may not be replaced with equal but separate nodes. The range extraction
+ may alter these nodes - in the case of text containers, they will - but
+ the nodes must stay there, to preserve references such as user data,
+ event listeners, etc.
+
+ First, an extraction test.
+ */
+
+ var resultFrag = getFragment(baseResult);
+ var extractFrag = getFragment(baseExtract);
+
+ dump("Extract contents test " + i + "\n\n");
+ var baseFrag = getFragment(baseSource);
+ var baseRange = getRange(baseSource, baseFrag);
+ var startContainer = baseRange.startContainer;
+ var endContainer = baseRange.endContainer;
+
+ var cutFragment = baseRange.extractContents();
+ dump("cutFragment: " + cutFragment + "\n");
+ if (cutFragment) {
+ Assert.ok(extractFrag.isEqualNode(cutFragment));
+ } else {
+ Assert.equal(extractFrag.firstChild, null);
+ }
+ Assert.ok(baseFrag.isEqualNode(resultFrag));
+
+ dump("Ensure the original nodes weren't extracted - test " + i + "\n\n");
+ var walker = doc.createTreeWalker(baseFrag, NodeFilter.SHOW_ALL, null);
+ var foundStart = false;
+ var foundEnd = false;
+ do {
+ if (walker.currentNode == startContainer) {
+ foundStart = true;
+ }
+
+ if (walker.currentNode == endContainer) {
+ // An end container node should not come before the start container node.
+ Assert.ok(foundStart);
+ foundEnd = true;
+ break;
+ }
+ } while (walker.nextNode());
+ Assert.ok(foundEnd);
+
+ /* Now, we reset our test for the deleteContents case. This one differs
+ from the extractContents case only in that there is no extracted document
+ fragment to compare against. So we merely compare the starting fragment,
+ minus the extracted content, against the result fragment.
+ */
+ dump("Delete contents test " + i + "\n\n");
+ baseFrag = getFragment(baseSource);
+ baseRange = getRange(baseSource, baseFrag);
+ startContainer = baseRange.startContainer;
+ endContainer = baseRange.endContainer;
+ baseRange.deleteContents();
+ Assert.ok(baseFrag.isEqualNode(resultFrag));
+
+ dump("Ensure the original nodes weren't deleted - test " + i + "\n\n");
+ walker = doc.createTreeWalker(baseFrag, NodeFilter.SHOW_ALL, null);
+ foundStart = false;
+ foundEnd = false;
+ do {
+ if (walker.currentNode == startContainer) {
+ foundStart = true;
+ }
+
+ if (walker.currentNode == endContainer) {
+ // An end container node should not come before the start container node.
+ Assert.ok(foundStart);
+ foundEnd = true;
+ break;
+ }
+ } while (walker.nextNode());
+ Assert.ok(foundEnd);
+
+ // Clean up after ourselves.
+ walker = null;
+ }
+}
+
+/**
+ * Miscellaneous tests not covered above.
+ */
+function run_miscellaneous_tests() {
+ var filePath = "test_delete_range.xml";
+ getParsedDocument(filePath).then(do_miscellaneous_tests);
+}
+
+function isText(node) {
+ return (
+ node.nodeType == node.TEXT_NODE || node.nodeType == node.CDATA_SECTION_NODE
+ );
+}
+
+function do_miscellaneous_tests(doc) {
+ var tests = doc.getElementsByTagName("test");
+
+ // Let's try some invalid inputs to our DOM range and see what happens.
+ var currentTest = tests.item(0);
+ var baseSource = currentTest.firstChild;
+
+ var baseFrag = getFragment(baseSource);
+
+ var baseRange = getRange(baseSource, baseFrag);
+ var startContainer = baseRange.startContainer;
+ var endContainer = baseRange.endContainer;
+ var startOffset = baseRange.startOffset;
+ var endOffset = baseRange.endOffset;
+
+ // Text range manipulation.
+ if (
+ endOffset > startOffset &&
+ startContainer == endContainer &&
+ isText(startContainer)
+ ) {
+ // Invalid start node
+ try {
+ baseRange.setStart(null, 0);
+ do_throw("Should have thrown NOT_OBJECT_ERR!");
+ } catch (e) {
+ Assert.equal(e.constructor.name, "TypeError");
+ }
+
+ // Invalid start node
+ try {
+ baseRange.setStart({}, 0);
+ do_throw("Should have thrown SecurityError!");
+ } catch (e) {
+ Assert.equal(e.constructor.name, "TypeError");
+ }
+
+ // Invalid index
+ try {
+ baseRange.setStart(startContainer, -1);
+ do_throw("Should have thrown IndexSizeError!");
+ } catch (e) {
+ Assert.equal(e.name, "IndexSizeError");
+ }
+
+ // Invalid index
+ var newOffset = isText(startContainer)
+ ? startContainer.nodeValue.length + 1
+ : startContainer.childNodes.length + 1;
+ try {
+ baseRange.setStart(startContainer, newOffset);
+ do_throw("Should have thrown IndexSizeError!");
+ } catch (e) {
+ Assert.equal(e.name, "IndexSizeError");
+ }
+
+ newOffset--;
+ // Valid index
+ baseRange.setStart(startContainer, newOffset);
+ Assert.equal(baseRange.startContainer, baseRange.endContainer);
+ Assert.equal(baseRange.startOffset, newOffset);
+ Assert.ok(baseRange.collapsed);
+
+ // Valid index
+ baseRange.setEnd(startContainer, 0);
+ Assert.equal(baseRange.startContainer, baseRange.endContainer);
+ Assert.equal(baseRange.startOffset, 0);
+ Assert.ok(baseRange.collapsed);
+ } else {
+ do_throw(
+ "The first test should be a text-only range test. Test is invalid."
+ );
+ }
+
+ /* See what happens when a range has a startContainer in one fragment, and an
+ endContainer in another. According to the DOM spec, section 2.4, the range
+ should collapse to the new container and offset. */
+ baseRange = getRange(baseSource, baseFrag);
+ startContainer = baseRange.startContainer;
+ startOffset = baseRange.startOffset;
+ endContainer = baseRange.endContainer;
+ endOffset = baseRange.endOffset;
+
+ dump("External fragment test\n\n");
+
+ var externalTest = tests.item(1);
+ var externalSource = externalTest.firstChild;
+ var externalFrag = getFragment(externalSource);
+ var externalRange = getRange(externalSource, externalFrag);
+
+ baseRange.setEnd(externalRange.endContainer, 0);
+ Assert.equal(baseRange.startContainer, externalRange.endContainer);
+ Assert.equal(baseRange.startOffset, 0);
+ Assert.ok(baseRange.collapsed);
+
+ /*
+ // XXX ajvincent if rv == WRONG_DOCUMENT_ERR, return false?
+ do_check_false(baseRange.isPointInRange(startContainer, startOffset));
+ do_check_false(baseRange.isPointInRange(startContainer, startOffset + 1));
+ do_check_false(baseRange.isPointInRange(endContainer, endOffset));
+ */
+
+ // Requested by smaug: A range involving a comment as a document child.
+ doc = parser.parseFromString("<!-- foo --><foo/>", "application/xml");
+ Assert.equal(ChromeUtils.getClassName(doc), "XMLDocument");
+ Assert.equal(doc.childNodes.length, 2);
+ baseRange = doc.createRange();
+ baseRange.setStart(doc.firstChild, 1);
+ baseRange.setEnd(doc.firstChild, 2);
+ var frag = baseRange.extractContents();
+ Assert.equal(frag.childNodes.length, 1);
+ Assert.ok(ChromeUtils.getClassName(frag.firstChild) == "Comment");
+ Assert.equal(frag.firstChild.nodeType, frag.COMMENT_NODE);
+ Assert.equal(frag.firstChild.nodeValue, "f");
+
+ /* smaug also requested attribute tests. Sadly, those are not yet supported
+ in ranges - see https://bugzilla.mozilla.org/show_bug.cgi?id=302775.
+ */
+}
+
+function run_test() {
+ run_extract_test();
+ run_miscellaneous_tests();
+}
diff --git a/dom/base/test/unit/test_serializers_entities.js b/dom/base/test/unit/test_serializers_entities.js
new file mode 100644
index 0000000000..55778ce3d6
--- /dev/null
+++ b/dom/base/test/unit/test_serializers_entities.js
@@ -0,0 +1,99 @@
+const encoders = {
+ xml: doc => {
+ let enc = Cu.createDocumentEncoder("text/xml");
+ enc.init(doc, "text/xml", Ci.nsIDocumentEncoder.OutputLFLineBreak);
+ return enc;
+ },
+ html: doc => {
+ let enc = Cu.createDocumentEncoder("text/html");
+ enc.init(doc, "text/html", Ci.nsIDocumentEncoder.OutputLFLineBreak);
+ return enc;
+ },
+ htmlBasic: doc => {
+ let enc = Cu.createDocumentEncoder("text/html");
+ enc.init(
+ doc,
+ "text/html",
+ Ci.nsIDocumentEncoder.OutputEncodeBasicEntities |
+ Ci.nsIDocumentEncoder.OutputLFLineBreak
+ );
+ return enc;
+ },
+ xhtml: doc => {
+ let enc = Cu.createDocumentEncoder("application/xhtml+xml");
+ enc.init(
+ doc,
+ "application/xhtml+xml",
+ Ci.nsIDocumentEncoder.OutputLFLineBreak
+ );
+ return enc;
+ },
+};
+
+// Which characters should we encode as entities? It depends on the serializer.
+const encodeAll = { html: true, htmlBasic: true, xhtml: true, xml: true };
+const encodeHTMLBasic = {
+ html: false,
+ htmlBasic: true,
+ xhtml: false,
+ xml: false,
+};
+const encodeXML = { html: false, htmlBasic: false, xhtml: true, xml: true };
+const encodeNone = { html: false, htmlBasic: false, xhtml: false, xml: false };
+const encodingInfoMap = new Map([
+ // Basic sanity chars '<', '>', '"', '&' get encoded in all cases.
+ ["<", encodeAll],
+ [">", encodeAll],
+ ["&", encodeAll],
+ // nbsp is only encoded with the HTML encoder when encoding basic entities.
+ ["\xA0", encodeHTMLBasic],
+]);
+
+const encodingMap = new Map([
+ ["<", "&lt;"],
+ [">", "&gt;"],
+ ["&", "&amp;"],
+ // nbsp is only encoded with the HTML encoder when encoding basic entities.
+ ["\xA0", "&nbsp;"],
+]);
+
+function encodingInfoForChar(c) {
+ var info = encodingInfoMap.get(c);
+ if (info) {
+ return info;
+ }
+ return encodeNone;
+}
+
+function encodingForChar(c, type) {
+ var info = encodingInfoForChar(c);
+ if (!info[type]) {
+ return c;
+ }
+ return encodingMap.get(c);
+}
+
+const doc = new DOMParser().parseFromString("<root></root>", "text/xml");
+const root = doc.documentElement;
+for (let i = 0; i < 255; ++i) {
+ let el = doc.createElement("span");
+ el.textContent = String.fromCharCode(i);
+ root.appendChild(el);
+}
+for (let type of ["xml", "xhtml", "htmlBasic", "html"]) {
+ let str = encoders[type](doc).encodeToString();
+ const prefix = "<root><span>";
+ const suffix = "</span></root>";
+ Assert.ok(str.startsWith(prefix), `${type} serialization starts correctly`);
+ Assert.ok(str.endsWith(suffix), `${type} serialization ends correctly`);
+ str = str.substring(prefix.length, str.length - suffix.length);
+ let encodings = str.split("</span><span>");
+ for (let i = 0; i < 255; ++i) {
+ let c = String.fromCharCode(i);
+ Assert.equal(
+ encodingForChar(c, type),
+ encodings[i],
+ `${type} encoding of char ${i} is correct`
+ );
+ }
+}
diff --git a/dom/base/test/unit/test_serializers_entities_in_attr.js b/dom/base/test/unit/test_serializers_entities_in_attr.js
new file mode 100644
index 0000000000..2497a480a7
--- /dev/null
+++ b/dom/base/test/unit/test_serializers_entities_in_attr.js
@@ -0,0 +1,108 @@
+const encoders = {
+ xml: doc => {
+ let enc = Cu.createDocumentEncoder("text/xml");
+ enc.init(doc, "text/xml", Ci.nsIDocumentEncoder.OutputLFLineBreak);
+ return enc;
+ },
+ html: doc => {
+ let enc = Cu.createDocumentEncoder("text/html");
+ enc.init(doc, "text/html", Ci.nsIDocumentEncoder.OutputLFLineBreak);
+ return enc;
+ },
+ htmlBasic: doc => {
+ let enc = Cu.createDocumentEncoder("text/html");
+ enc.init(
+ doc,
+ "text/html",
+ Ci.nsIDocumentEncoder.OutputEncodeBasicEntities |
+ Ci.nsIDocumentEncoder.OutputLFLineBreak
+ );
+ return enc;
+ },
+ xhtml: doc => {
+ let enc = Cu.createDocumentEncoder("application/xhtml+xml");
+ enc.init(
+ doc,
+ "application/xhtml+xml",
+ Ci.nsIDocumentEncoder.OutputLFLineBreak
+ );
+ return enc;
+ },
+};
+
+// Which characters should we encode as entities? It depends on the serializer.
+const encodeAll = { html: true, htmlBasic: true, xhtml: true, xml: true };
+const encodeHTMLBasic = {
+ html: false,
+ htmlBasic: true,
+ xhtml: false,
+ xml: false,
+};
+const encodeXML = { html: false, htmlBasic: false, xhtml: true, xml: true };
+const encodeNone = { html: false, htmlBasic: false, xhtml: false, xml: false };
+const encodingInfoMap = new Map([
+ // Basic sanity chars '<', '>', '"', '&' get encoded in all cases.
+ ["<", encodeAll],
+ [">", encodeAll],
+ ['"', encodeAll],
+ ["&", encodeAll],
+ // nbsp is only encoded with the HTML encoder when encoding basic entities.
+ ["\xA0", encodeHTMLBasic],
+ // Whitespace bits are only encoded in XML.
+ ["\n", encodeXML],
+ ["\r", encodeXML],
+ ["\t", encodeXML],
+]);
+
+const encodingMap = new Map([
+ ["<", "&lt;"],
+ [">", "&gt;"],
+ ['"', "&quot;"],
+ ["&", "&amp;"],
+ ["\xA0", "&nbsp;"],
+ ["\n", "&#xA;"],
+ ["\r", "&#xD;"],
+ ["\t", "&#9;"],
+]);
+
+function encodingInfoForChar(c) {
+ var info = encodingInfoMap.get(c);
+ if (info) {
+ return info;
+ }
+ return encodeNone;
+}
+
+function encodingForChar(c, type) {
+ var info = encodingInfoForChar(c);
+ if (!info[type]) {
+ return c;
+ }
+ return encodingMap.get(c);
+}
+
+const doc = new DOMParser().parseFromString("<root></root>", "text/xml");
+const root = doc.documentElement;
+for (let i = 0; i < 255; ++i) {
+ let el = doc.createElement("span");
+ el.setAttribute("x", String.fromCharCode(i));
+ el.textContent = " ";
+ root.appendChild(el);
+}
+for (let type of ["xml", "xhtml", "htmlBasic", "html"]) {
+ let str = encoders[type](doc).encodeToString();
+ const prefix = '<root><span x="';
+ const suffix = '"> </span></root>';
+ Assert.ok(str.startsWith(prefix), `${type} serialization starts correctly`);
+ Assert.ok(str.endsWith(suffix), `${type} serialization ends correctly`);
+ str = str.substring(prefix.length, str.length - suffix.length);
+ let encodings = str.split('"> </span><span x="');
+ for (let i = 0; i < 255; ++i) {
+ let c = String.fromCharCode(i);
+ Assert.equal(
+ encodingForChar(c, type),
+ encodings[i],
+ `${type} encoding of char ${i} is correct`
+ );
+ }
+}
diff --git a/dom/base/test/unit/test_structuredcloneholder.js b/dom/base/test/unit/test_structuredcloneholder.js
new file mode 100644
index 0000000000..fd9afc7f03
--- /dev/null
+++ b/dom/base/test/unit/test_structuredcloneholder.js
@@ -0,0 +1,159 @@
+"use strict";
+
+const global = this;
+
+add_task(async function test_structuredCloneHolder() {
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI("http://example.com/"),
+ {}
+ );
+
+ let sandbox = Cu.Sandbox(principal);
+
+ const obj = { foo: [{ bar: "baz" }] };
+
+ let holder = new StructuredCloneHolder("", "", obj);
+
+ // Test same-compartment deserialization
+
+ let res = holder.deserialize(global, true);
+
+ notEqual(
+ res,
+ obj,
+ "Deserialized result is a different object from the original"
+ );
+
+ deepEqual(
+ res,
+ obj,
+ "Deserialized result is deeply equivalent to the original"
+ );
+
+ equal(
+ Cu.getObjectPrincipal(res),
+ Cu.getObjectPrincipal(global),
+ "Deserialized result has the correct principal"
+ );
+
+ // Test non-object-value round-trip.
+
+ equal(
+ new StructuredCloneHolder("", "", "foo").deserialize(global),
+ "foo",
+ "Round-tripping non-object values works as expected"
+ );
+
+ // Test cross-compartment deserialization
+
+ res = holder.deserialize(sandbox, true);
+
+ notEqual(
+ res,
+ obj,
+ "Cross-compartment-deserialized result is a different object from the original"
+ );
+
+ deepEqual(
+ res,
+ obj,
+ "Cross-compartment-deserialized result is deeply equivalent to the original"
+ );
+
+ equal(
+ Cu.getObjectPrincipal(res),
+ principal,
+ "Cross-compartment-deserialized result has the correct principal"
+ );
+
+ // Test message manager transportability
+
+ const MSG = "StructuredCloneHolder";
+
+ let resultPromise = new Promise(resolve => {
+ Services.ppmm.addMessageListener(MSG, resolve);
+ });
+
+ Services.cpmm.sendAsyncMessage(MSG, holder);
+
+ res = await resultPromise;
+
+ ok(
+ StructuredCloneHolder.isInstance(res.data),
+ "Sending structured clone holders through message managers works as expected"
+ );
+
+ deepEqual(
+ res.data.deserialize(global, true),
+ obj,
+ "Sending structured clone holders through message managers works as expected"
+ );
+
+ // Test that attempting to deserialize a neutered holder throws.
+
+ deepEqual(
+ holder.deserialize(global),
+ obj,
+ "Deserialized result is correct when discarding data"
+ );
+
+ Assert.throws(
+ () => holder.deserialize(global),
+ err => err.result == Cr.NS_ERROR_NOT_INITIALIZED,
+ "Attempting to deserialize neutered holder throws"
+ );
+
+ Assert.throws(
+ () => holder.deserialize(global, true),
+ err => err.result == Cr.NS_ERROR_NOT_INITIALIZED,
+ "Attempting to deserialize neutered holder throws"
+ );
+});
+
+// Test that X-rays passed to an exported function are serialized
+// through their exported wrappers.
+add_task(async function test_structuredCloneHolder_xray() {
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI("http://example.com/"),
+ {}
+ );
+
+ let sandbox1 = Cu.Sandbox(principal, { wantXrays: true });
+
+ let sandbox2 = Cu.Sandbox(principal, { wantXrays: true });
+ Cu.evalInSandbox(`this.x = {y: "z", get z() { return "q" }}`, sandbox2);
+
+ sandbox1.x = sandbox2.x;
+
+ let holder;
+ Cu.exportFunction(
+ function serialize(val) {
+ holder = new StructuredCloneHolder("", "", val, sandbox1);
+ },
+ sandbox1,
+ { defineAs: "serialize" }
+ );
+
+ Cu.evalInSandbox(`serialize(x)`, sandbox1);
+
+ const obj = { y: "z" };
+
+ let res = holder.deserialize(global);
+
+ deepEqual(
+ res,
+ obj,
+ "Deserialized result is deeply equivalent to the expected object"
+ );
+ deepEqual(
+ res,
+ sandbox2.x,
+ "Deserialized result is deeply equivalent to the X-ray-wrapped object"
+ );
+
+ equal(
+ Cu.getObjectPrincipal(res),
+ Cu.getObjectPrincipal(global),
+ "Deserialized result has the correct principal"
+ );
+});
diff --git a/dom/base/test/unit/test_thirdpartyutil.js b/dom/base/test/unit/test_thirdpartyutil.js
new file mode 100644
index 0000000000..777dc8dc62
--- /dev/null
+++ b/dom/base/test/unit/test_thirdpartyutil.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test ThirdPartyUtil methods. See mozIThirdPartyUtil.
+
+var prefs = Services.prefs;
+
+// Since this test creates a TYPE_DOCUMENT channel via javascript, it will
+// end up using the wrong LoadInfo constructor. Setting this pref will disable
+// the ContentPolicyType assertion in the constructor.
+prefs.setBoolPref("network.loadinfo.skip_type_assertion", true);
+
+var NS_ERROR_INVALID_ARG = Cr.NS_ERROR_INVALID_ARG;
+
+function do_check_throws(f, result, stack) {
+ if (!stack) {
+ try {
+ // We might not have a 'Components' object.
+ stack = Components.stack.caller;
+ } catch (e) {}
+ }
+
+ try {
+ f();
+ } catch (exc) {
+ Assert.equal(exc.result, result);
+ return;
+ }
+ do_throw("expected " + result + " exception, none thrown", stack);
+}
+
+function run_test() {
+ let util = Cc["@mozilla.org/thirdpartyutil;1"].getService(
+ Ci.mozIThirdPartyUtil
+ );
+
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ let spec1 = "http://foo.com/foo.html";
+ let spec2 = "http://bar.com/bar.html";
+ let uri1 = NetUtil.newURI(spec1);
+ let uri2 = NetUtil.newURI(spec2);
+ const contentPolicyType = Ci.nsIContentPolicy.TYPE_DOCUMENT;
+ let channel1 = NetUtil.newChannel({
+ uri: uri1,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType,
+ });
+ NetUtil.newChannel({
+ uri: uri2,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType,
+ });
+
+ // Create some file:// URIs.
+ let filespec1 = "file://foo.txt";
+ let filespec2 = "file://bar.txt";
+ let fileuri1 = NetUtil.newURI(filespec1);
+ let fileuri2 = NetUtil.newURI(filespec2);
+ NetUtil.newChannel({ uri: fileuri1, loadUsingSystemPrincipal: true });
+ NetUtil.newChannel({ uri: fileuri2, loadUsingSystemPrincipal: true });
+
+ // Test isThirdPartyURI.
+ Assert.ok(!util.isThirdPartyURI(uri1, uri1));
+ Assert.ok(util.isThirdPartyURI(uri1, uri2));
+ Assert.ok(util.isThirdPartyURI(uri2, uri1));
+ Assert.ok(!util.isThirdPartyURI(fileuri1, fileuri1));
+ Assert.ok(!util.isThirdPartyURI(fileuri1, fileuri2));
+ Assert.ok(util.isThirdPartyURI(uri1, fileuri1));
+ do_check_throws(function () {
+ util.isThirdPartyURI(uri1, null);
+ }, NS_ERROR_INVALID_ARG);
+ do_check_throws(function () {
+ util.isThirdPartyURI(null, uri1);
+ }, NS_ERROR_INVALID_ARG);
+ do_check_throws(function () {
+ util.isThirdPartyURI(null, null);
+ }, NS_ERROR_INVALID_ARG);
+
+ // We can't test isThirdPartyWindow since we can't really set up a window
+ // hierarchy. We leave that to mochitests.
+
+ // Test isThirdPartyChannel. As above, we can't test the bits that require
+ // a load context or window heirarchy. Because of bug 1259873, we assume
+ // that these are not third-party.
+ do_check_throws(function () {
+ util.isThirdPartyChannel(null);
+ }, NS_ERROR_INVALID_ARG);
+ Assert.ok(!util.isThirdPartyChannel(channel1));
+ Assert.ok(!util.isThirdPartyChannel(channel1, uri1));
+ Assert.ok(util.isThirdPartyChannel(channel1, uri2));
+
+ let httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ Assert.ok(!util.isThirdPartyChannel(channel1));
+ Assert.ok(!util.isThirdPartyChannel(channel1, uri1));
+ Assert.ok(util.isThirdPartyChannel(channel1, uri2));
+}
diff --git a/dom/base/test/unit/test_treewalker.js b/dom/base/test/unit/test_treewalker.js
new file mode 100644
index 0000000000..a932965370
--- /dev/null
+++ b/dom/base/test/unit/test_treewalker.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ test_treeWalker_currentNode();
+}
+
+// TEST CODE
+
+function test_treeWalker_currentNode() {
+ var XHTMLDocString = '<html xmlns="http://www.w3.org/1999/xhtml">';
+ XHTMLDocString += "<body><input/>input</body></html>";
+
+ var doc = ParseXML(XHTMLDocString);
+
+ var body = doc.getElementsByTagName("body")[0];
+ var filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
+ var walker = doc.createTreeWalker(body, filter, null);
+ walker.currentNode = body.firstChild;
+ walker.nextNode();
+}
diff --git a/dom/base/test/unit/test_xhr_document.js b/dom/base/test/unit/test_xhr_document.js
new file mode 100644
index 0000000000..aa96c283c0
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_document.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var server = new HttpServer();
+server.start(-1);
+
+var docbody =
+ '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>';
+
+function handler(metadata, response) {
+ var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+ let body = NetUtil.readInputStreamToString(
+ metadata.bodyInputStream,
+ metadata.bodyInputStream.available()
+ );
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.write(body, body.length);
+}
+
+function run_test() {
+ do_test_pending();
+ server.registerPathHandler("/foo", handler);
+
+ var parser = new DOMParser();
+ let doc = parser.parseFromString(docbody, "text/html");
+ let xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ Assert.equal(xhr.responseText, docbody);
+ server.stop(do_test_finished);
+ };
+ xhr.onerror = function () {
+ Assert.equal(false, false);
+ server.stop(do_test_finished);
+ };
+ xhr.open(
+ "POST",
+ "http://localhost:" + server.identity.primaryPort + "/foo",
+ true
+ );
+ xhr.send(doc);
+}
diff --git a/dom/base/test/unit/test_xhr_origin_attributes.js b/dom/base/test/unit/test_xhr_origin_attributes.js
new file mode 100644
index 0000000000..26848af479
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_origin_attributes.js
@@ -0,0 +1,53 @@
+let server = new HttpServer();
+server.start(-1);
+
+let body =
+ "<!DOCTYPE HTML><html><head><meta charset='utf-8'></head><body></body></html>";
+
+function handler(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html", false);
+
+ if (!request.hasHeader("Cookie")) {
+ response.setHeader("Set-Cookie", "test", false);
+ ok(true);
+ } else {
+ ok(false);
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function run_test() {
+ do_test_pending();
+ server.registerPathHandler("/foo", handler);
+
+ let xhr = new XMLHttpRequest();
+ xhr.open(
+ "GET",
+ "http://localhost:" + server.identity.primaryPort + "/foo",
+ true
+ );
+ xhr.send(null);
+
+ xhr.onload = function () {
+ // We create another XHR to connect to the same site, but this time we
+ // specify with different origin attributes, which will make the XHR use a
+ // different cookie-jar than the previous one.
+ let xhr2 = new XMLHttpRequest();
+ xhr2.open(
+ "GET",
+ "http://localhost:" + server.identity.primaryPort + "/foo",
+ true
+ );
+ xhr2.setOriginAttributes({ userContextId: 1 });
+ xhr2.send(null);
+
+ let loadInfo = xhr2.channel.loadInfo;
+ Assert.equal(loadInfo.originAttributes.userContextId, 1);
+
+ xhr2.onload = function () {
+ server.stop(do_test_finished);
+ };
+ };
+}
diff --git a/dom/base/test/unit/test_xhr_standalone.js b/dom/base/test/unit/test_xhr_standalone.js
new file mode 100644
index 0000000000..94f2d7d642
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_standalone.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test setting .responseType and .withCredentials is allowed
+// in non-window non-Worker context
+
+function run_test() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "data:,", false);
+ var exceptionThrown = false;
+ try {
+ xhr.responseType = "";
+ xhr.withCredentials = false;
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ Assert.equal(false, exceptionThrown);
+}
diff --git a/dom/base/test/unit/test_xml_parser.js b/dom/base/test/unit/test_xml_parser.js
new file mode 100644
index 0000000000..0d6675fdab
--- /dev/null
+++ b/dom/base/test/unit/test_xml_parser.js
@@ -0,0 +1,48 @@
+function run_test() {
+ for (var i = 0; i < tests.length && tests[i][0]; ++i) {
+ if (!tests[i][0].call()) {
+ do_throw(tests[i][1]);
+ }
+ }
+}
+
+var tests = [
+ [test1, "Unable to parse basic XML document"],
+ [test2, "ParseXML doesn't return Document"],
+ [test3, "ParseXML return value's documentElement is not Element"],
+ [test4, ""],
+ [test5, ""],
+ [test6, ""],
+ [null],
+];
+
+function test1() {
+ return ParseXML("<root/>");
+}
+
+function test2() {
+ return ChromeUtils.getClassName(ParseXML("<root/>")) === "XMLDocument";
+}
+
+function test3() {
+ return Element.isInstance(ParseXML("<root/>").documentElement);
+}
+
+function test4() {
+ var doc = ParseXML("<root/>");
+ Assert.equal(doc.documentElement.namespaceURI, null);
+ return true;
+}
+
+function test5() {
+ var doc = ParseXML("<root xmlns=''/>");
+ Assert.equal(doc.documentElement.namespaceURI, null);
+ return true;
+}
+
+function test6() {
+ var doc = ParseXML("<root xmlns='ns1'/>");
+ Assert.notEqual(doc.documentElement.namespaceURI, null);
+ Assert.equal(doc.documentElement.namespaceURI, "ns1");
+ return true;
+}
diff --git a/dom/base/test/unit/test_xml_serializer.js b/dom/base/test/unit/test_xml_serializer.js
new file mode 100644
index 0000000000..11fbb02f6f
--- /dev/null
+++ b/dom/base/test/unit/test_xml_serializer.js
@@ -0,0 +1,421 @@
+// The xml serializer uses the default line break of the plateform.
+// So we need to know the value of this default line break, in order
+// to build correctly the reference strings for tests.
+// This variable will contain this value.
+var LB;
+
+function run_test() {
+ if (mozinfo.os == "win") {
+ LB = "\r\n";
+ } else {
+ LB = "\n";
+ }
+
+ for (var i = 0; i < tests.length && tests[i]; ++i) {
+ tests[i].call();
+ }
+}
+
+var tests = [
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ test6,
+ test7,
+ test8,
+ test9,
+ test10,
+ null,
+];
+
+function testString(str) {
+ Assert.equal(roundtrip(str), str);
+}
+
+function test1() {
+ // Basic round-tripping which we expect to hand back the same text
+ // as we passed in (not strictly required for correctness in some of
+ // those cases, but best for readability of serializer output)
+ testString("<root/>");
+ testString("<root><child/></root>");
+ testString('<root xmlns=""/>');
+ testString('<root xml:lang="en"/>');
+ testString('<root xmlns="ns1"><child xmlns="ns2"/></root>');
+ testString('<root xmlns="ns1"><child xmlns=""/></root>');
+ testString('<a:root xmlns:a="ns1"><child/></a:root>');
+ testString('<a:root xmlns:a="ns1"><a:child/></a:root>');
+ testString('<a:root xmlns:a="ns1"><b:child xmlns:b="ns1"/></a:root>');
+ testString('<a:root xmlns:a="ns1"><a:child xmlns:a="ns2"/></a:root>');
+ testString(
+ '<a:root xmlns:a="ns1"><b:child xmlns:b="ns1" b:attr=""/></a:root>'
+ );
+}
+
+function test2() {
+ // Test setting of "xmlns" attribute in the null namespace
+
+ // XXXbz are these tests needed? What should happen here? These
+ // may be bogus.
+
+ // Setting random "xmlns" attribute
+ var doc = ParseXML('<root xmlns="ns1"/>');
+ doc.documentElement.setAttribute("xmlns", "ns2");
+ do_check_serialize(doc);
+}
+
+function test3() {
+ // Test basic appending of kids. Again, we're making assumptions
+ // about how our serializer will serialize simple DOMs.
+ var doc = ParseXML('<root xmlns="ns1"/>');
+ var root = doc.documentElement;
+ var child = doc.createElementNS("ns2", "child");
+ root.appendChild(child);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1"><child xmlns="ns2"/></root>'
+ );
+
+ doc = ParseXML('<root xmlns="ns1"/>');
+ root = doc.documentElement;
+ child = doc.createElementNS("ns2", "prefix:child");
+ root.appendChild(child);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1"><prefix:child xmlns:prefix="ns2"/></root>'
+ );
+
+ doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>');
+ root = doc.documentElement;
+ child = doc.createElementNS("ns2", "prefix:child");
+ root.appendChild(child);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<prefix:root xmlns:prefix="ns1"><a0:child xmlns:a0="ns2"/>' +
+ "</prefix:root>"
+ );
+}
+
+function test4() {
+ // setAttributeNS tests
+
+ var doc = ParseXML('<root xmlns="ns1"/>');
+ var root = doc.documentElement;
+ root.setAttributeNS("ns1", "prefix:local", "val");
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1" prefix:local="val" xmlns:prefix="ns1"/>'
+ );
+
+ doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>');
+ root = doc.documentElement;
+ root.setAttributeNS("ns1", "local", "val");
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<prefix:root xmlns:prefix="ns1" prefix:local="val"/>'
+ );
+
+ doc = ParseXML('<root xmlns="ns1"/>');
+ root = doc.documentElement;
+ root.setAttributeNS("ns2", "local", "val");
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1" a0:local="val" xmlns:a0="ns2"/>'
+ );
+
+ // Handling of prefix-generation for non-null-namespace attributes
+ // which have the same namespace as the current default namespace
+ // (bug 301260).
+ doc = ParseXML('<root xmlns="ns1"/>');
+ root = doc.documentElement;
+ root.setAttributeNS("ns1", "local", "val");
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1" a0:local="val" xmlns:a0="ns1"/>'
+ );
+
+ // Tree-walking test
+ doc = ParseXML(
+ '<root xmlns="ns1" xmlns:a="ns2">' +
+ '<child xmlns:b="ns2" xmlns:a="ns3">' +
+ "<child2/></child></root>"
+ );
+ root = doc.documentElement;
+ var node = root.firstChild.firstChild;
+ node.setAttributeNS("ns4", "l1", "v1");
+ node.setAttributeNS("ns4", "p2:l2", "v2");
+ node.setAttributeNS("", "l3", "v3");
+ node.setAttributeNS("ns3", "l4", "v4");
+ node.setAttributeNS("ns3", "p5:l5", "v5");
+ node.setAttributeNS("ns3", "a:l6", "v6");
+ node.setAttributeNS("ns2", "l7", "v7");
+ node.setAttributeNS("ns2", "p8:l8", "v8");
+ node.setAttributeNS("ns2", "b:l9", "v9");
+ node.setAttributeNS("ns2", "a:l10", "v10");
+ node.setAttributeNS("ns1", "a:l11", "v11");
+ node.setAttributeNS("ns1", "b:l12", "v12");
+ node.setAttributeNS("ns1", "l13", "v13");
+ do_check_serialize(doc);
+ // Note: we end up with "a2" as the prefix on "l11" and "l12" because we use
+ // "a1" earlier, and discard it in favor of something we get off the
+ // namespace stack, apparently
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1" xmlns:a="ns2">' +
+ '<child xmlns:b="ns2" xmlns:a="ns3">' +
+ '<child2 a0:l1="v1" xmlns:a0="ns4"' +
+ ' a0:l2="v2"' +
+ ' l3="v3"' +
+ ' a:l4="v4"' +
+ ' a:l5="v5"' +
+ ' a:l6="v6"' +
+ ' b:l7="v7"' +
+ ' b:l8="v8"' +
+ ' b:l9="v9"' +
+ ' b:l10="v10"' +
+ ' a2:l11="v11" xmlns:a2="ns1"' +
+ ' a2:l12="v12"' +
+ ' a2:l13="v13"/></child></root>'
+ );
+}
+
+function test5() {
+ // Handling of kids in the null namespace when the default is a
+ // different namespace (bug 301260).
+ var doc = ParseXML('<root xmlns="ns1"/>');
+ var child = doc.createElement("child");
+ doc.documentElement.appendChild(child);
+ do_check_serialize(doc);
+ Assert.equal(SerializeXML(doc), '<root xmlns="ns1"><child xmlns=""/></root>');
+}
+
+function test6() {
+ // Handling of not using a namespace prefix (or default namespace!)
+ // that's not bound to our namespace in our scope (bug 301260).
+ var doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>');
+ var root = doc.documentElement;
+ var child1 = doc.createElementNS("ns2", "prefix:child1");
+ var child2 = doc.createElementNS("ns1", "prefix:child2");
+ child1.appendChild(child2);
+ root.appendChild(child1);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<prefix:root xmlns:prefix="ns1"><a0:child1 xmlns:a0="ns2">' +
+ "<prefix:child2/></a0:child1></prefix:root>"
+ );
+
+ doc = ParseXML(
+ '<root xmlns="ns1"><prefix:child1 xmlns:prefix="ns2"/></root>'
+ );
+ root = doc.documentElement;
+ child1 = root.firstChild;
+ child2 = doc.createElementNS("ns1", "prefix:child2");
+ child1.appendChild(child2);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1"><prefix:child1 xmlns:prefix="ns2">' +
+ "<child2/></prefix:child1></root>"
+ );
+
+ doc = ParseXML(
+ '<prefix:root xmlns:prefix="ns1">' +
+ '<prefix:child1 xmlns:prefix="ns2"/></prefix:root>'
+ );
+ root = doc.documentElement;
+ child1 = root.firstChild;
+ child2 = doc.createElementNS("ns1", "prefix:child2");
+ child1.appendChild(child2);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<prefix:root xmlns:prefix="ns1"><prefix:child1 xmlns:prefix="ns2">' +
+ '<a0:child2 xmlns:a0="ns1"/></prefix:child1></prefix:root>'
+ );
+
+ doc = ParseXML('<root xmlns="ns1"/>');
+ root = doc.documentElement;
+ child1 = doc.createElementNS("ns2", "child1");
+ child2 = doc.createElementNS("ns1", "child2");
+ child1.appendChild(child2);
+ root.appendChild(child1);
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="ns1"><child1 xmlns="ns2"><child2 xmlns="ns1"/>' +
+ "</child1></root>"
+ );
+}
+
+function test7() {
+ // Handle xmlns attribute declaring a default namespace on a non-namespaced
+ // element (bug 326994).
+ var doc = ParseXML('<root xmlns=""/>');
+ var root = doc.documentElement;
+ root.setAttributeNS(
+ "http://www.w3.org/2000/xmlns/",
+ "xmlns",
+ "http://www.w3.org/1999/xhtml"
+ );
+ do_check_serialize(doc);
+ Assert.equal(SerializeXML(doc), "<root/>");
+
+ doc = ParseXML('<root xmlns=""><child1/></root>');
+ root = doc.documentElement;
+ root.setAttributeNS(
+ "http://www.w3.org/2000/xmlns/",
+ "xmlns",
+ "http://www.w3.org/1999/xhtml"
+ );
+ do_check_serialize(doc);
+ Assert.equal(SerializeXML(doc), "<root><child1/></root>");
+
+ doc = ParseXML(
+ '<root xmlns="http://www.w3.org/1999/xhtml">' +
+ '<child1 xmlns=""><child2/></child1></root>'
+ );
+ root = doc.documentElement;
+
+ var child1 = root.firstChild;
+ child1.setAttributeNS(
+ "http://www.w3.org/2000/xmlns/",
+ "xmlns",
+ "http://www.w3.org/1999/xhtml"
+ );
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="http://www.w3.org/1999/xhtml"><child1 xmlns="">' +
+ "<child2/></child1></root>"
+ );
+
+ doc = ParseXML(
+ '<root xmlns="http://www.w3.org/1999/xhtml">' +
+ '<child1 xmlns="">' +
+ '<child2 xmlns="http://www.w3.org/1999/xhtml"></child2>' +
+ "</child1></root>"
+ );
+ root = doc.documentElement;
+ child1 = root.firstChild;
+ var child2 = child1.firstChild;
+ child1.setAttributeNS(
+ "http://www.w3.org/2000/xmlns/",
+ "xmlns",
+ "http://www.w3.org/1999/xhtml"
+ );
+ child2.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");
+ do_check_serialize(doc);
+ Assert.equal(
+ SerializeXML(doc),
+ '<root xmlns="http://www.w3.org/1999/xhtml"><child1 xmlns="">' +
+ '<a0:child2 xmlns:a0="http://www.w3.org/1999/xhtml" xmlns=""></a0:child2></child1></root>'
+ );
+}
+
+function test8() {
+ // Test behavior of serializing with a given charset.
+ var str1 = '<?xml version="1.0" encoding="windows-1252"?>' + LB + "<root/>";
+ var str2 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + "<root/>";
+ var doc1 = ParseXML(str1);
+ var doc2 = ParseXML(str2);
+
+ var p = Pipe();
+ DOMSerializer().serializeToStream(doc1, p.outputStream, "windows-1252");
+ p.outputStream.close();
+ Assert.equal(ScriptableInput(p).read(-1), str1);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc2, p.outputStream, "windows-1252");
+ p.outputStream.close();
+ Assert.equal(ScriptableInput(p).read(-1), str1);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc1, p.outputStream, "UTF-8");
+ p.outputStream.close();
+ Assert.equal(ScriptableInput(p).read(-1), str2);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc2, p.outputStream, "UTF-8");
+ p.outputStream.close();
+ Assert.equal(ScriptableInput(p).read(-1), str2);
+}
+
+function test9() {
+ // Test behavior of serializing between given charsets, using
+ // windows-1252-representable text.
+ var contents =
+ // eslint-disable-next-line no-useless-concat
+ "<root>" + "\u00BD + \u00BE == \u00BD\u00B2 + \u00BC + \u00BE" + "</root>";
+ var str1 = '<?xml version="1.0" encoding="windows-1252"?>' + LB + contents;
+ var str2 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + contents;
+ var str3 = '<?xml version="1.0" encoding="UTF-16"?>' + LB + contents;
+ var doc1 = ParseXML(str1);
+ var doc2 = ParseXML(str2);
+ var doc3 = ParseXML(str3);
+
+ checkSerialization(doc1, "windows-1252", str1);
+ checkSerialization(doc2, "windows-1252", str1);
+ checkSerialization(doc3, "windows-1252", str1);
+
+ checkSerialization(doc1, "UTF-8", str2);
+ checkSerialization(doc2, "UTF-8", str2);
+ checkSerialization(doc3, "UTF-8", str2);
+
+ checkSerialization(doc1, "UTF-16", str2);
+ checkSerialization(doc2, "UTF-16", str2);
+ checkSerialization(doc3, "UTF-16", str2);
+}
+
+function test10() {
+ // Test behavior of serializing between given charsets, using
+ // Unicode characters (XXX but only BMP ones because I don't know
+ // how to create one with non-BMP characters, either with JS strings
+ // or using DOM APIs).
+ var contents =
+ "<root>" +
+ "AZaz09 \u007F " + // U+000000 to U+00007F
+ "\u0080 \u0398 \u03BB \u0725 " + // U+000080 to U+0007FF
+ "\u0964 \u0F5F \u20AC \uFFFB" + // U+000800 to U+00FFFF
+ "</root>";
+ var str1 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + contents;
+ var str2 = '<?xml version="1.0" encoding="UTF-16"?>' + LB + contents;
+ var doc1 = ParseXML(str1);
+ var doc2 = ParseXML(str2);
+
+ checkSerialization(doc1, "UTF8", str1);
+ checkSerialization(doc2, "UTF8", str1);
+
+ checkSerialization(doc1, "UTF-16", str1);
+ checkSerialization(doc2, "UTF-16", str1);
+}
+
+function checkSerialization(doc, toCharset, expectedString) {
+ var p = Pipe();
+ DOMSerializer().serializeToStream(doc, p.outputStream, toCharset);
+ p.outputStream.close();
+
+ var inCharset = toCharset == "UTF-16" ? "UTF-8" : toCharset;
+ var cin = C["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ I.nsIConverterInputStream
+ );
+ cin.init(p.inputStream, inCharset, 1024, 0x0);
+
+ // compare the first expectedString.length characters for equality
+ var outString = {};
+ var count = cin.readString(expectedString.length, outString);
+ Assert.equal(count, expectedString.length);
+ Assert.equal(outString.value, expectedString);
+
+ // if there's anything more in the stream, it's a bug
+ Assert.equal(0, cin.readString(1, outString));
+ Assert.equal(outString.value, "");
+}
diff --git a/dom/base/test/unit/test_xmlserializer.js b/dom/base/test/unit/test_xmlserializer.js
new file mode 100644
index 0000000000..86f926def5
--- /dev/null
+++ b/dom/base/test/unit/test_xmlserializer.js
@@ -0,0 +1,179 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+async function xmlEncode(aFile, aFlags, aCharset) {
+ if (aFlags == undefined) {
+ aFlags = 0;
+ }
+ if (aCharset == undefined) {
+ aCharset = "UTF-8";
+ }
+
+ var doc = await do_parse_document(aFile, "text/xml");
+
+ var encoder = Cu.createDocumentEncoder("text/xml");
+ encoder.setCharset(aCharset);
+ encoder.init(doc, "text/xml", aFlags);
+ return encoder.encodeToString();
+}
+
+add_task(async function test_encoding() {
+ var result, expected;
+ const de = Ci.nsIDocumentEncoder;
+
+ result = await xmlEncode("1_original.xml", de.OutputLFLineBreak);
+ expected = loadContentFile("1_result.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode("2_original.xml", de.OutputLFLineBreak);
+ expected = loadContentFile("2_result_1.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode("2_original.xml", de.OutputCRLineBreak);
+ expected = expected.replace(/\n/g, "\r");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputLFLineBreak | de.OutputCRLineBreak
+ );
+ expected = expected.replace(/\r/gm, "\r\n");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputLFLineBreak | de.OutputFormatted
+ );
+ expected = loadContentFile("2_result_2.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ expected = loadContentFile("2_result_3.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputLFLineBreak | de.OutputWrap
+ );
+ expected = loadContentFile("2_result_4.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "3_original.xml",
+ de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ expected = loadContentFile("3_result.xml");
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "3_original.xml",
+ de.OutputLFLineBreak | de.OutputWrap
+ );
+ expected = loadContentFile("3_result_2.xml");
+ Assert.equal(expected, result);
+
+ // tests on namespaces
+ var doc = await do_parse_document("4_original.xml", "text/xml");
+
+ var encoder = Cu.createDocumentEncoder("text/xml");
+ encoder.setCharset("UTF-8");
+ encoder.init(doc, "text/xml", de.OutputLFLineBreak);
+
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_1.xml");
+ Assert.equal(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[9]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_2.xml");
+ Assert.equal(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[7].childNodes[1]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_3.xml");
+ Assert.equal(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[11].childNodes[1]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_4.xml");
+ Assert.equal(expected, result);
+
+ encoder.setCharset("UTF-8");
+ // it doesn't support this flags
+ encoder.init(
+ doc,
+ "text/xml",
+ de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ encoder.setWrapColumn(40);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_5.xml");
+ Assert.equal(expected, result);
+
+ encoder.init(doc, "text/xml", de.OutputLFLineBreak | de.OutputWrap);
+ encoder.setWrapColumn(40);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_6.xml");
+ Assert.equal(expected, result);
+});
+
+// OutputRaw should cause OutputWrap and OutputFormatted to be ignored.
+// Check by encoding each test file and making sure the result matches what
+// was fed in.
+add_task(async function test_outputRaw() {
+ let result, expected;
+ const de = Ci.nsIDocumentEncoder;
+
+ expected = loadContentFile("2_original.xml");
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap
+ );
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "2_original.xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ Assert.equal(expected, result);
+
+ expected = loadContentFile("3_original.xml");
+ result = await xmlEncode(
+ "3_original.xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap
+ );
+ Assert.equal(expected, result);
+
+ result = await xmlEncode(
+ "3_original.xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ Assert.equal(expected, result);
+
+ expected = loadContentFile("4_original.xml");
+ let doc = await do_parse_document("4_original.xml", "text/xml");
+ let encoder = Cu.createDocumentEncoder("text/xml");
+ encoder.setCharset("UTF-8");
+ encoder.init(
+ doc,
+ "text/xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap
+ );
+ encoder.setWrapColumn(40);
+ result = encoder.encodeToString();
+ Assert.equal(expected, result);
+
+ encoder.init(
+ doc,
+ "text/xml",
+ de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap
+ );
+ encoder.setWrapColumn(40);
+ result = encoder.encodeToString();
+ Assert.equal(expected, result);
+});
diff --git a/dom/base/test/unit/xpcshell.ini b/dom/base/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..ef9b3b818f
--- /dev/null
+++ b/dom/base/test/unit/xpcshell.ini
@@ -0,0 +1,70 @@
+[DEFAULT]
+head = head_utilities.js
+support-files =
+ 1_original.xml
+ 1_result.xml
+ 2_original.xml
+ 2_result_1.xml
+ 2_result_2.xml
+ 2_result_3.xml
+ 2_result_4.xml
+ 3_original.xml
+ 3_result.xml
+ 3_result_2.xml
+ 4_original.xml
+ 4_result_1.xml
+ 4_result_2.xml
+ 4_result_3.xml
+ 4_result_4.xml
+ 4_result_5.xml
+ 4_result_6.xml
+ empty_document.xml
+ isequalnode_data.xml
+ nodelist_data_1.xml
+ nodelist_data_2.xhtml
+ test_delete_range.xml
+
+[test_blockParsing.js]
+skip-if =
+ debug # We fail an assertion if we block parsing on a self-closing element
+ toolkit == 'android'
+ appname == 'thunderbird' # The test needs to run without e10s, can't do that.
+[test_bug553888.js]
+[test_bug737966.js]
+[test_error_codes.js]
+run-sequentially = Hardcoded 4444 port.
+# Bug 1018414: hardcoded localhost doesn't work properly on some OS X installs
+skip-if = os == 'mac'
+[test_htmlserializer.js]
+[test_isequalnode.js]
+head = head_xml.js
+[test_nodelist.js]
+head = head_xml.js
+[test_normalize.js]
+head = head_xml.js
+[test_range.js]
+head = head_xml.js
+[test_serializers_entities.js]
+[test_serializers_entities_in_attr.js]
+[test_structuredcloneholder.js]
+[test_thirdpartyutil.js]
+[test_treewalker.js]
+head = head_xml.js
+[test_xhr_document.js]
+[test_xhr_standalone.js]
+[test_xhr_origin_attributes.js]
+[test_xml_parser.js]
+head = head_xml.js
+[test_xml_serializer.js]
+head = head_xml.js
+[test_xmlserializer.js]
+[test_cancelPrefetch.js]
+[test_chromeutils_base64.js]
+[test_chromeutils_getXPCOMErrorName.js]
+[test_chromeutils_shallowclone.js]
+[test_generate_xpath.js]
+head = head_xml.js
+[test_js_dev_error_interceptor.js]
+# This feature is implemented only in NIGHTLY.
+run-if = nightly_build
+
diff --git a/dom/base/test/unit_ipc/test_bug553888_wrap.js b/dom/base/test/unit_ipc/test_bug553888_wrap.js
new file mode 100644
index 0000000000..0dede092e2
--- /dev/null
+++ b/dom/base/test/unit_ipc/test_bug553888_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_bug553888.js");
+}
diff --git a/dom/base/test/unit_ipc/test_xhr_document_ipc.js b/dom/base/test/unit_ipc/test_xhr_document_ipc.js
new file mode 100644
index 0000000000..1f65969b27
--- /dev/null
+++ b/dom/base/test/unit_ipc/test_xhr_document_ipc.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_xhr_document.js");
+}
diff --git a/dom/base/test/unit_ipc/xpcshell.ini b/dom/base/test/unit_ipc/xpcshell.ini
new file mode 100644
index 0000000000..e02cfb4e45
--- /dev/null
+++ b/dom/base/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+skip-if = toolkit == 'android' || socketprocess_networking
+support-files =
+ !/dom/base/test/unit/test_bug553888.js
+ !/dom/base/test/unit/test_xhr_document.js
+
+[test_bug553888_wrap.js]
+prefs = network.allow_raw_sockets_in_content_processes=true
+[test_xhr_document_ipc.js]
+prefs = network.allow_raw_sockets_in_content_processes=true
diff --git a/dom/base/test/useractivation/file_clipboard_common.js b/dom/base/test/useractivation/file_clipboard_common.js
new file mode 100644
index 0000000000..fe172e52c8
--- /dev/null
+++ b/dom/base/test/useractivation/file_clipboard_common.js
@@ -0,0 +1,505 @@
+// This test is called from both test_clipboard_editor.html and test_clipboard_noeditor.html
+// This is to test that the code works both in the presence of a contentEditable node, and in the absense of one
+var WATCH_TIMEOUT = 300;
+
+// Some global variables to make the debug messages easier to track down
+var gTestN0 = 0,
+ gTestN1 = 0,
+ gTestN2 = 0;
+function testLoc() {
+ return " " + gTestN0 + " - " + gTestN1 + " - " + gTestN2;
+}
+
+// Listen for cut & copy events
+var gCopyCount = 0,
+ gCutCount = 0;
+document.addEventListener("copy", function () {
+ gCopyCount++;
+});
+document.addEventListener("cut", function () {
+ gCutCount++;
+});
+
+// Helper methods
+function selectNode(aSelector, aCb) {
+ var dn = document.querySelector(aSelector);
+ var range = document.createRange();
+ range.selectNodeContents(dn);
+ window.getSelection().removeAllRanges();
+ window.getSelection().addRange(range);
+ if (aCb) {
+ aCb();
+ }
+}
+
+function selectInputNode(aSelector, aCb) {
+ var dn = document.querySelector(aSelector);
+ synthesizeMouse(dn, 10, 10, {});
+ SimpleTest.executeSoon(function () {
+ synthesizeKey("A", { accelKey: true });
+ // Clear the user activation state which is set from synthesized mouse and
+ // key event.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ SimpleTest.executeSoon(aCb);
+ });
+}
+
+// Callback functions for attaching to the button
+function execCommand(aCommand, aShouldSucceed, aAsync = false) {
+ var cb = function (e) {
+ e.preventDefault();
+ document.removeEventListener("keydown", cb);
+
+ if (aAsync) {
+ setTimeout(() => {
+ is(
+ aShouldSucceed,
+ document.execCommand(aCommand),
+ "Keydown caused " + aCommand + " invocation" + testLoc()
+ );
+ }, 0);
+ } else {
+ is(
+ aShouldSucceed,
+ document.execCommand(aCommand),
+ "Keydown caused " + aCommand + " invocation" + testLoc()
+ );
+ }
+ };
+ return cb;
+}
+
+// The basic test set. Tries to cut/copy everything
+function cutCopyAll(
+ aDoCut,
+ aDoCopy,
+ aDone,
+ aNegate,
+ aClipOverride,
+ aJustClipboardNegate
+) {
+ var execCommandAlwaysSucceed = !!(aClipOverride || aJustClipboardNegate);
+
+ function waitForClipboard(aCond, aSetup, aNext, aNegateOne) {
+ if (aClipOverride) {
+ aCond = aClipOverride;
+ aNegateOne = false;
+ }
+ if (aNegate || aNegateOne || aJustClipboardNegate) {
+ SimpleTest.waitForClipboard(
+ null,
+ aSetup,
+ aNext,
+ aNext,
+ "text/plain",
+ WATCH_TIMEOUT,
+ true
+ );
+ } else {
+ SimpleTest.waitForClipboard(aCond, aSetup, aNext, aNext);
+ }
+ }
+
+ function validateCutCopy(aExpectedCut, aExpectedCopy) {
+ if (aNegate) {
+ aExpectedCut = aExpectedCopy = 0;
+ } // When we are negating - we always expect callbacks not to be run
+
+ is(
+ aExpectedCut,
+ gCutCount,
+ (aExpectedCut > 0
+ ? "Expect cut callback to run"
+ : "Expect cut callback not to run") + testLoc()
+ );
+ is(
+ aExpectedCopy,
+ gCopyCount,
+ (aExpectedCopy > 0
+ ? "Expect copy callback to run"
+ : "Expect copy callback not to run") + testLoc()
+ );
+ gCutCount = gCopyCount = 0;
+ }
+
+ function step(n) {
+ function nextStep() {
+ step(n + 1);
+ }
+
+ // Reset the user activation state before running next test.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ document.querySelector("span").textContent = "span text";
+ document.querySelector("input[type=text]").value = "text text";
+ document.querySelector("input[type=password]").value = "password text";
+ document.querySelector("textarea").value = "textarea text";
+
+ var contentEditableNode = document.querySelector(
+ "div[contentEditable=true]"
+ );
+ if (contentEditableNode) {
+ contentEditableNode.textContent = "contenteditable text";
+ }
+
+ gTestN2 = n;
+ switch (n) {
+ case 0:
+ // copy on readonly selection
+ selectNode("span");
+ waitForClipboard(
+ "span text",
+ function () {
+ aDoCopy(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 1:
+ validateCutCopy(0, 1);
+
+ // cut on readonly selection
+ selectNode("span");
+
+ waitForClipboard(
+ "span text",
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 2:
+ validateCutCopy(1, 0);
+
+ // copy on textbox selection
+ selectInputNode("input[type=text]", nextStep);
+ return;
+
+ case 3:
+ waitForClipboard(
+ "text text",
+ function () {
+ selectInputNode("input[type=text]", function () {
+ aDoCopy(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 4:
+ validateCutCopy(0, 1);
+
+ // cut on textbox selection
+ selectInputNode("input[type=text]", nextStep);
+ return;
+
+ case 5:
+ waitForClipboard(
+ "text text",
+ function () {
+ aDoCut(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 6:
+ validateCutCopy(1, 0);
+
+ // copy on password selection
+ selectInputNode("input[type=password]", nextStep);
+ return;
+
+ case 7:
+ waitForClipboard(
+ null,
+ function () {
+ aDoCopy(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 8:
+ validateCutCopy(0, 1);
+
+ // cut on password selection
+ selectInputNode("input[type=password]", nextStep);
+ return;
+
+ case 9:
+ waitForClipboard(
+ null,
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 10:
+ validateCutCopy(1, 0);
+
+ // copy on textarea selection
+ selectInputNode("textarea", nextStep);
+ return;
+
+ case 11:
+ waitForClipboard(
+ "textarea text",
+ function () {
+ aDoCopy(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 12:
+ validateCutCopy(0, 1);
+
+ // cut on password selection
+ selectInputNode("textarea", nextStep);
+ return;
+
+ case 13:
+ waitForClipboard(
+ "textarea text",
+ function () {
+ aDoCut(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 14:
+ validateCutCopy(1, 0);
+
+ // copy on no selection
+ document.querySelector("textarea").blur();
+
+ waitForClipboard(
+ null,
+ function () {
+ aDoCopy(true);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 15:
+ validateCutCopy(0, 1);
+
+ // cut on no selection
+ waitForClipboard(
+ null,
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 16:
+ validateCutCopy(1, 0);
+
+ if (!document.querySelector("div[contentEditable=true]")) {
+ // We're done! (no contentEditable node!)
+ step(-1);
+ return;
+ }
+ break;
+
+ case 17:
+ // copy on contenteditable selection
+ waitForClipboard(
+ "contenteditable text",
+ function () {
+ selectNode("div[contentEditable=true]", function () {
+ aDoCopy(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 18:
+ validateCutCopy(0, 1);
+ break;
+
+ case 19:
+ // cut on contenteditable selection
+ waitForClipboard(
+ "contenteditable text",
+ function () {
+ selectNode("div[contentEditable=true]", function () {
+ aDoCut(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 20:
+ validateCutCopy(1, 0);
+ break;
+
+ default:
+ aDone();
+ return;
+ }
+
+ SimpleTest.executeSoon(function () {
+ step(n + 1);
+ });
+ }
+
+ step(0);
+}
+
+function allMechanisms(aCb, aClipOverride, aNegateAll) {
+ function testStep(n) {
+ gTestN1 = n;
+ switch (n) {
+ /** Test for Bug 1012662 **/
+ case 0:
+ // Keyboard issued
+ cutCopyAll(
+ function docut(aSucc) {
+ synthesizeKey("x", { accelKey: true });
+ },
+ function docopy(aSucc) {
+ synthesizeKey("c", { accelKey: true });
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ case 1:
+ // Button issued
+ cutCopyAll(
+ function docut(aSucc) {
+ document.addEventListener("keydown", execCommand("cut", aSucc));
+ sendString("Q");
+ },
+ function docopy(aSucc) {
+ document.addEventListener("keydown", execCommand("copy", aSucc));
+ sendString("Q");
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ case 2:
+ // Not triggered by user gesture
+ cutCopyAll(
+ function doCut(aSucc) {
+ is(
+ false,
+ document.execCommand("cut"),
+ "Can't directly execCommand not in a user callback"
+ );
+ },
+ function doCopy(aSucc) {
+ is(
+ false,
+ document.execCommand("copy"),
+ "Can't directly execCommand not in a user callback"
+ );
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ true,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ /** Test for Bug 1597857 **/
+ case 3:
+ // Button issued async
+ cutCopyAll(
+ function docut(aSucc) {
+ document.addEventListener(
+ "keydown",
+ execCommand("cut", aSucc, true)
+ );
+ sendString("Q");
+ },
+ function docopy(aSucc) {
+ document.addEventListener(
+ "keydown",
+ execCommand("copy", aSucc, true)
+ );
+ sendString("Q");
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ default:
+ aCb();
+ }
+ }
+ testStep(0);
+}
+
+// Run the tests
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5); // On the emulator - this times out occasionally
+SimpleTest.waitForFocus(function () {
+ function justCancel(aEvent) {
+ aEvent.preventDefault();
+ }
+
+ function override(aEvent) {
+ aEvent.clipboardData.setData("text/plain", "overridden");
+ aEvent.preventDefault();
+ }
+
+ allMechanisms(function () {
+ gTestN0 = 1;
+ document.addEventListener("cut", override);
+ document.addEventListener("copy", override);
+
+ allMechanisms(function () {
+ gTestN0 = 2;
+ document.removeEventListener("cut", override);
+ document.removeEventListener("copy", override);
+ document.addEventListener("cut", justCancel);
+ document.addEventListener("copy", justCancel);
+
+ allMechanisms(
+ function () {
+ SimpleTest.finish();
+ },
+ null,
+ true
+ );
+ }, "overridden");
+ });
+});
diff --git a/dom/base/test/useractivation/file_empty.html b/dom/base/test/useractivation/file_empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/useractivation/file_empty.html
diff --git a/dom/base/test/useractivation/file_iframe_check_user_activation.html b/dom/base/test/useractivation/file_iframe_check_user_activation.html
new file mode 100644
index 0000000000..0a3ec734a6
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_check_user_activation.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ parent.postMessage("loaded", "*");
+};
+onmessage = function(e) {
+ if (e.data === "get") {
+ parent.postMessage({
+ isActivated: SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ hasBeenActivated: SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ lastActivationTimestamp: SpecialPowers.wrap(document).lastUserGestureTimeStamp,
+ }, "*");
+ }
+};
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/file_iframe_consume_user_activation.html b/dom/base/test/useractivation/file_iframe_consume_user_activation.html
new file mode 100644
index 0000000000..ad27453f1a
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_consume_user_activation.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ parent.postMessage("done", "*");
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/useractivation/file_iframe_user_activated.html b/dom/base/test/useractivation/file_iframe_user_activated.html
new file mode 100644
index 0000000000..8d188001e8
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_user_activated.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ parent.postMessage("done", "*");
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html b/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html
new file mode 100644
index 0000000000..25bc2037c3
--- /dev/null
+++ b/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation popup</title>
+</head>
+<body>
+<iframe sandbox="allow-top-navigation-by-user-activation allow-scripts allow-same-origin"></iframe>
+<script>
+ window.addEventListener("message", (e) => {
+ if (e.data === "triggerIframeLoad") {
+ // Load into the the iframe a script which notifies the opener of top level navigation or if it was prevented.
+ let script = `window.opener.postMessage("topNavigation");`;
+ frames[0].frameElement.src = `javascript:try{window.top.location='javascript:${encodeURIComponent(script)}';} catch (e) {window.top.opener.postMessage("topNavigationBlocked");}`;
+ } else {
+ window.opener.postMessage("unexpected");
+ }
+ });
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/mochitest.ini b/dom/base/test/useractivation/mochitest.ini
new file mode 100644
index 0000000000..26c8594be8
--- /dev/null
+++ b/dom/base/test/useractivation/mochitest.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+ file_iframe_user_activated.html
+ file_iframe_check_user_activation.html
+ file_iframe_consume_user_activation.html
+ file_clipboard_common.js
+prefs =
+ formhelper.autozoom.force-disable.test-only=true
+
+[test_useractivation_has_been_activated.html]
+[test_useractivation_key_events.html]
+[test_useractivation_transient.html]
+[test_useractivation_sandbox_transient.html]
+support-files = file_useractivation_sandbox_transient_popup.html
+[test_useractivation_scrollbar.html]
+skip-if = os == 'android' # scrollbar not showed on mobile
+[test_useractivation_transient_consuming.html]
+[test_clipboard_editor.html]
+[test_clipboard_noeditor.html]
+[test_popup_blocker_mouse_event.html]
+[test_popup_blocker_pointer_event.html]
+[test_popup_blocker_async_callback.html]
diff --git a/dom/base/test/useractivation/moz.build b/dom/base/test/useractivation/moz.build
new file mode 100644
index 0000000000..60b508c774
--- /dev/null
+++ b/dom/base/test/useractivation/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
diff --git a/dom/base/test/useractivation/test_clipboard_editor.html b/dom/base/test/useractivation/test_clipboard_editor.html
new file mode 100644
index 0000000000..691e8e4a20
--- /dev/null
+++ b/dom/base/test/useractivation/test_clipboard_editor.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1012662
+https://bugzilla.mozilla.org/show_bug.cgi?id=1597857
+-->
+<head>
+ <title>Test for Clipboard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1012662">Mozilla Bug 1012662</a><br>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597857">Mozilla Bug 1597857</a>
+<p id="display"></p>
+
+<div id="content">
+ <span>span text</span>
+ <input type="text" value="text text">
+ <input type="password" value="password text">
+ <textarea>textarea text</textarea>
+ <div contentEditable="true">contenteditable text</div>
+</div>
+
+<pre id="test">
+<script type="application/javascript" src="file_clipboard_common.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_clipboard_noeditor.html b/dom/base/test/useractivation/test_clipboard_noeditor.html
new file mode 100644
index 0000000000..f7f3d6cb99
--- /dev/null
+++ b/dom/base/test/useractivation/test_clipboard_noeditor.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1012662
+-->
+<head>
+ <title>Test for Clipboard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1012662">Mozilla Bug 1012662</a><br>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597857">Mozilla Bug 1597857</a>
+<p id="display"></p>
+
+<div id="content">
+ <span>span text</span>
+ <input type="text" value="text text">
+ <input type="password" value="password text">
+ <textarea>textarea text</textarea>
+</div>
+
+<pre id="test">
+<script type="application/javascript" src="file_clipboard_common.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_async_callback.html b/dom/base/test/useractivation/test_popup_blocker_async_callback.html
new file mode 100644
index 0000000000..dc53596531
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_async_callback.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by mouse events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+SimpleTest.requestFlakyTimeout("Need to test setTimeout");
+
+function startTest(aTestAsyncFun, aAllowPopup = true) {
+ return new Promise(aResolve => {
+ let target = document.getElementById("target");
+ target.addEventListener("click", (e) => {
+ aTestAsyncFun(() => {
+ let w = window.open("about:blank");
+ is(!!w, aAllowPopup, `Should ${aAllowPopup ? "allow" : "block"} popup`);
+ if (w) {
+ w.close();
+ }
+ aResolve();
+ });
+ }, {once: true});
+ synthesizeMouseAtCenter(target, {type: "mousedown"});
+ synthesizeMouseAtCenter(target, {type: "mouseup"});
+ });
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.disable_open_during_load", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+[
+ // setTimeout
+ function testSetTimout(aCallback) {
+ setTimeout(aCallback, 500);
+ },
+ // fetch
+ function testFetch(aCallback) {
+ fetch("../dummy.html").then(aCallback);
+ },
+ // requestStorageAccess
+ function testRequestStorageAccess(aCallback) {
+ document.requestStorageAccess().then(aCallback);
+ },
+ // serviceWorker.getRegistration
+ function testGetServiceWorkerRegistration(aCallback) {
+ navigator.serviceWorker.getRegistration("/app").then(aCallback);
+ },
+].forEach(testAsyncFun => {
+ add_task(async function() {
+ info(`start ${testAsyncFun.name}`);
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await startTest(testAsyncFun);
+ await new Promise(aResolve => SimpleTest.executeSoon(aResolve));
+ });
+});
+
+// Test popup should be blocked if user transient is timeout
+add_task(async function timeout() {
+ info(`start user transient timeout`);
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.user_activation.transient.timeout", 1000],
+ ]});
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await startTest((aCallback) => {
+ setTimeout(aCallback, 2000);
+ }, false);
+ await new Promise(aResolve => SimpleTest.executeSoon(aResolve));
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_mouse_event.html b/dom/base/test/useractivation/test_popup_blocker_mouse_event.html
new file mode 100644
index 0000000000..fd94150f1e
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_mouse_event.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by mouse events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+function sendMouseEvent(element, eventName, button, listenEventName, handler) {
+ let needToCheckHandler = false;
+ let handlerIsCalled = false;
+ if (listenEventName && handler) {
+ needToCheckHandler = true;
+ element.addEventListener(listenEventName, (e) => {
+ handler(e);
+ handlerIsCalled = true;
+ }, {once: true});
+ }
+ synthesizeMouseAtCenter(element, {type: eventName, button});
+ if (needToCheckHandler) {
+ ok(handlerIsCalled, "Handler should be called");
+ }
+}
+
+function checkAllowOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(w, `Should allow popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+}
+
+function checkBlockOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(!w, `Should block popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+}
+
+add_setup(async function() {
+ const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION;
+ let xorigin = SimpleTest.getTestFileURL("").replace(location.hostname, 'mochi.xorigin-test');
+ await SpecialPowers.pushPermissions([
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': document},
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': xorigin}
+ ]);
+
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve));
+});
+
+const LEFT_BUTTON = 0;
+const MIDDLE_BUTTON = 1;
+const RIGHT_BUTTON = 2;
+let target = document.getElementById("target");
+
+add_task(function testMouseDownUpMove() {
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "mousedown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "mouseup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "mousedown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "mouseup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "mousedown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "mouseup", checkBlockOpenPopup);
+});
+
+add_task(function testMouseClick() {
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "click", checkAllowOpenPopup);
+});
+
+add_task(function testMouseAuxclick() {
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "auxclick", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "auxclick", checkAllowOpenPopup);
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_pointer_event.html b/dom/base/test/useractivation/test_popup_blocker_pointer_event.html
new file mode 100644
index 0000000000..8d0b2c8cd1
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_pointer_event.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by pointer events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div id="target" style="width: 50px; height: 50px; background: green"></div>
+ <script>
+
+ function sendMouseEvent(element, eventName, button, listenEventName, handler) {
+ let needToCheckHandler = false;
+ let handlerIsCalled = false;
+ if (listenEventName && handler) {
+ needToCheckHandler = true;
+ element.addEventListener(listenEventName, (e) => {
+ handler(e);
+ handlerIsCalled = true;
+ }, {once: true});
+ }
+ synthesizeMouseAtCenter(element, {type: eventName, button});
+ if (needToCheckHandler) {
+ ok(handlerIsCalled, "Handler should be called");
+ }
+ }
+
+ function checkAllowOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(w, `Should allow popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+ }
+
+ function checkBlockOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(!w, `Should block popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+ }
+
+ add_setup(async function() {
+ const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION;
+ let xorigin = SimpleTest.getTestFileURL("").replace(location.hostname, 'mochi.xorigin-test');
+ await SpecialPowers.pushPermissions([
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': document},
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': xorigin}
+ ]);
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve));
+ });
+
+ const LEFT_BUTTON = 0;
+ const MIDDLE_BUTTON = 1;
+ const RIGHT_BUTTON = 2;
+ let target = document.getElementById("target");
+
+ add_task(function testPointerEventDefault() {
+ // By default, only allow opening popup in the pointerup listener.
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+
+ add_task(async function testPointerEventAddPointerDownToPref() {
+ // Adding pointerdown to preference
+ await SpecialPowers.pushPrefEnv({"set": [["dom.popup_allowed_events",
+ "pointerdown pointerup"]]});
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+
+ add_task(async function testPointerEventAddPointerMoveToPref() {
+ // Adding pointermove to preference should have no effect.
+ await SpecialPowers.pushPrefEnv({"set": [["dom.popup_allowed_events",
+ "pointerdown pointerup pointermove"]]});
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_useractivation_has_been_activated.html b/dom/base/test/useractivation/test_useractivation_has_been_activated.html
new file mode 100644
index 0000000000..f46618915b
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_has_been_activated.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: has been user gesture activated</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+let [iframe0, iframe1] = document.querySelectorAll("iframe");
+
+function doCheck(aDocument, aName, aHasBeenUserGestureActivated) {
+ is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
+ aHasBeenUserGestureActivated,
+ `check has-been-user-activated on the ${aName}`);
+ if (aHasBeenUserGestureActivated) {
+ ok(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp > 0,
+ `check last-user-gesture-timestamp on the ${aName}`);
+ } else {
+ is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp, 0,
+ `check last-user-gesture-timestamp on the ${aName}`);
+ }
+}
+
+add_task(async function checkInitialStatus() {
+ doCheck(document, "top-level document", false);
+ doCheck(frames[0].document, "first iframe", false);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function triggerUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ // We should also propagate to all the ancestors.
+ doCheck(document, "top-level document", true);
+ doCheck(frames[0].document, "first iframe", true);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function iframeNavigation() {
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+ // We should reset the flag on iframe that navigates away from current page,
+ // but the flag on its ancestor isn't changed.
+ doCheck(document, "top-level document", true);
+ doCheck(frames[0].document, "first iframe", false);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ doCheck(document, "top-level document", false);
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_user_activated.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ doCheck(document, "top-level document", true);
+ doCheck(frames[1].document, "second iframe", false);
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function propagateToSameOriginConnectedSubframe() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ iframe0.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_check_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data !== "loaded") {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // Trigger user activation on top-level document.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ doCheck(document, "top-level document", true);
+ doCheck(iframe1.contentDocument, "second iframe", true);
+
+ iframe0.contentWindow.postMessage("get", "*");
+ await waitForEvent(window, "message", (event) => {
+ if (typeof event.data === "object") {
+ ok(!event.data.hasBeenActivated, "check has-been-user-activated on the first iframe");
+ is(event.data.lastActivationTimestamp, 0, "check last-user-gesture-timestamp on the first iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_key_events.html b/dom/base/test/useractivation/test_useractivation_key_events.html
new file mode 100644
index 0000000000..d97906c22b
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_key_events.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: key events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+function synthesizeKeyAndWait(aKey, aEvent) {
+ let promise = new Promise(aResolve => {
+ document.addEventListener("keydown", function(e) {
+ e.preventDefault();
+ aResolve();
+ }, { once: true });
+ });
+ synthesizeKey(aKey, aEvent, window);
+ return promise;
+}
+
+add_task(async function TestPrintableKey() {
+ let tests = [ 'a', 'b', 'c', 'A', 'B', '1', '2', '3' ];
+
+ for (let key of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, {});
+ ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ `check has-been-user-activated for ${key}`);
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ `check has-valid-transient-user-activation for ${key}`);
+ }
+});
+
+add_task(async function TestNonPrintableKey() {
+ let tests = [ [ "KEY_Alt", false],
+ [ "KEY_Backspace", false],
+ [ "KEY_Escape" , false ],
+ [ "KEY_Tab" , false ],
+ // Treat as user input
+ [ "KEY_Enter", true],
+ [ " ", true] ];
+
+ for (let [key, expectedResult] of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, {});
+ is(SpecialPowers.wrap(document).hasBeenUserGestureActivated, expectedResult,
+ `check has-been-user-activated for "${key}"`);
+ is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation, expectedResult,
+ `check has-valid-transient-user-activation for "${key}"`);
+ }
+});
+
+add_task(async function TestModifier() {
+ let tests = [ [ 'a', { altKey: true }, false],
+ [ 'a', { ctrlKey: true }, false],
+ [ 'a', { metaKey: true }, false],
+ [ 'a', { osKey: true }, false],
+ [ 'c', { altKey: true }, false ],
+ [ 'c', { osKey: true }, false ],
+ [ 'v', { altKey: true }, false ],
+ [ 'v', { osKey: true }, false ],
+ [ 'x', { altKey: true }, false ],
+ [ 'x', { osKey: true }, false ],
+ // Treat as user input
+ [ 'a', { altGraphKey: true }, true ],
+ [ 'a', { fnKey: true }, true ],
+ [ 'a', { shiftKey: true }, true ],
+ [ 'c', { accelKey: true }, true ],
+ [ 'v', { accelKey: true }, true ],
+ [ 'x', { accelKey: true }, true ] ];
+
+ for (let [key, event, expectedResult] of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, event);
+ is(SpecialPowers.wrap(document).hasBeenUserGestureActivated, expectedResult,
+ `check has-been-user-activated for ${key} with ${JSON.stringify(event)}`);
+ is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation, expectedResult,
+ `check has-valid-transient-user-activation for ${key} with ${JSON.stringify(event)}`);
+ }
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_sandbox_transient.html b/dom/base/test/useractivation/test_useractivation_sandbox_transient.html
new file mode 100644
index 0000000000..6b0fcb50f0
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_sandbox_transient.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+function checkPopupActivated(popup, activated) {
+ let state = activated ? "valid" : "invalid";
+ ok(activated === SpecialPowers.wrap(popup.document).hasValidTransientUserGestureActivation,
+ `check has-${state}-transient-user-activation on top-level document`);
+ ok(activated === SpecialPowers.wrap(popup.frames[0].document).hasValidTransientUserGestureActivation,
+ `check has-${state}-transient-user-activation on iframe`);
+}
+
+add_task(async function triggerUserActivation() {
+ let message = waitForEvent(window, "message", (e) => {
+ if (e.data === "topNavigation") {
+ ok(true, "Top navigation changed");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ });
+ let popup = window.open("./file_useractivation_sandbox_transient_popup.html", "_blank");
+ await new Promise((r) => {
+ popup.addEventListener("load", r);
+ });
+ checkPopupActivated(popup, false);
+ // Create user gesture into iframe within popup just opened
+ SpecialPowers.wrap(popup.frames[0].frameElement.contentDocument).notifyUserGestureActivation();
+ checkPopupActivated(popup, true);
+ // Notify popup to load into the frame
+ popup.postMessage("triggerIframeLoad");
+ await message;
+ popup.close();
+});
+
+add_task(async function triggerUserActivationTimeout() {
+ let message = waitForEvent(window, "message", (e) => {
+ // Top navigation MUST be blocked
+ if (e.data === "topNavigationBlocked") {
+ ok(true, "Top navigation blocked");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ });
+ let popup = window.open("./file_useractivation_sandbox_transient_popup.html", "_blank");
+ await new Promise((r) => {
+ popup.addEventListener("load", r);
+ });
+ checkPopupActivated(popup, false);
+ // Create user gesture into iframe within popup just opened
+ SpecialPowers.wrap(popup.frames[0].frameElement.contentDocument).notifyUserGestureActivation();
+ checkPopupActivated(popup, true);
+ // Ensure we timeout user gesture
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ checkPopupActivated(popup, false);
+ aResolve();
+ }, timeout);
+ });
+ // Notify popup to load into the frame
+ popup.postMessage("triggerIframeLoad");
+ await message;
+ popup.close();
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_scrollbar.html b/dom/base/test/useractivation/test_useractivation_scrollbar.html
new file mode 100644
index 0000000000..778d6d0898
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_scrollbar.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>User activation test: consume transient flag</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<textarea style="height: 100px; resize: none">
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+</textarea>
+<div id="target" style="height: 100px; width: 200px; overflow: scroll">
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+</div>
+<div style="height: 2000px; width: 500px; background-color: green;"></div>
+<script>
+
+// Open a new window to ensure scrollbar is always visible.
+if (!opener) {
+ add_task(async function init() {
+ // Turn on the prefs that force overlay scrollbars to always be visible.
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.testing.overlay-scrollbars.always-visible", true]],
+ });
+ });
+
+ async function testOnNewWindow(aPrefValue) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.user_activation.ignore_scrollbars", aPrefValue]],
+ });
+
+ let win = window.open(location);
+ // wait for message
+ await new Promise((aResolve) => {
+ window.addEventListener("message", function listener(event) {
+ if ("done" == event.data) {
+ window.removeEventListener("message", listener);
+ aResolve();
+ }
+ });
+ });
+ win.close();
+ }
+
+ add_task(async function test_pref_on() {
+ await testOnNewWindow(true);
+ });
+
+ add_task(async function test_pref_off() {
+ await testOnNewWindow(false);
+ });
+} else {
+ SimpleTest.waitForFocus(async function() {
+ function waitForEvent(aTarget, aEvent) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aResolve();
+ }, { once: true });
+ });
+ }
+
+ let ignoreScrollbars = SpecialPowers.getBoolPref("dom.user_activation.ignore_scrollbars");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let textarea = document.querySelector("textarea");
+ var rect = textarea.getBoundingClientRect();
+
+ // click scrollbar of textarea
+ let promise = waitForEvent(textarea, "scroll");
+ synthesizeMouse(textarea, rect.width - 5, rect.height / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let div = document.querySelector("div[id=target]");
+ rect = div.getBoundingClientRect();
+
+ // click scrollbar of div
+ promise = waitForEvent(div, "scroll");
+ synthesizeMouse(div, rect.width - 5, rect.height / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let body = document.querySelector("body");
+
+ // click scrollbar of page
+ promise = waitForEvent(document, "scroll");
+ synthesizeMouse(body, innerWidth - 10, innerHeight / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ opener.postMessage("done", "*");
+ }, window);
+}
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_transient.html b/dom/base/test/useractivation/test_useractivation_transient.html
new file mode 100644
index 0000000000..d3148e5d1d
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_transient.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+let [iframe0, iframe1] = document.querySelectorAll("iframe");
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+add_task(async function checkInitialStatus() {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on second iframe");
+});
+
+add_task(async function triggerUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // We should also propagate to all the ancestors.
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function iframeNavigation() {
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+ // We should reset the flag on iframe that navigates away from current page,
+ // but the flag on its ancestor isn't changed.
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function triggerUserActivationTimeout() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // hasValidTransientUserGestureActivation should return false after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ aResolve();
+ }, timeout);
+ });
+
+ // Trigger user activation again.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_user_activated.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // hasValidTransientUserGestureActivation should return false after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ aResolve();
+ }, timeout);
+ });
+});
+
+add_task(async function propagateToSameOriginConnectedSubframe() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ iframe0.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_check_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data !== "loaded") {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // Trigger user activation on top-level document.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(iframe1.contentDocument).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+
+ iframe0.contentWindow.postMessage("get", "*");
+ await waitForEvent(window, "message", (event) => {
+ if (typeof event.data === "object") {
+ ok(!event.data.isActivated, "check has-valid-transient-user-activation on the first iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_transient_consuming.html b/dom/base/test/useractivation/test_useractivation_transient_consuming.html
new file mode 100644
index 0000000000..4178d24477
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_transient_consuming.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: consume transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+function doCheck(aDocument, aName, aHasBeenUserGestureActivated,
+ aHasValidTransientUserGestureActivation,
+ aLastUserGestureTimeStamp) {
+ is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
+ aHasBeenUserGestureActivated,
+ `check has-been-user-activated on the ${aName}`);
+ is(SpecialPowers.wrap(aDocument).hasValidTransientUserGestureActivation,
+ aHasValidTransientUserGestureActivation,
+ `check has-valid-transient-user-activation on the ${aName}`);
+ is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp,
+ aLastUserGestureTimeStamp,
+ `check last-user-gesture-timestamp on the ${aName}`);
+}
+
+add_task(async function checkInitialStatus() {
+ doCheck(document, "top-level document", false, false, 0);
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+
+ doCheck(frames[0].document, "first iframe", false, false, 0);
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+
+ doCheck(frames[1].document, "second iframe", false, false, 0);
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+});
+
+add_task(async function consumeTransientUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ let lastTimeStampTop = SpecialPowers.wrap(document).lastUserGestureTimeStamp;
+ let lastTimeStampFirst = SpecialPowers.wrap(frames[0].document).lastUserGestureTimeStamp;
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ // Consuming a transient-user-activation should affect all tree.
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+
+ // Check has-valid-transient-user-activation and should not affect
+ // has-been-user-activated
+ doCheck(document, "top-level document", true, false, lastTimeStampTop);
+ doCheck(frames[0].document, "first iframe", true, false, lastTimeStampFirst);
+ doCheck(frames[1].document, "second iframe", false, false, 0);
+});
+
+add_task(async function consumeTransientUserActivationTimeout() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Should not able to consume successfully after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ aResolve();
+ }, timeout);
+ });
+
+ // Trigger user activation again.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ // Consuming a transient-user-activation should affect all tree.
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+});
+
+add_task(async function iframeNavigation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Navigate away from current page.
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ ok(SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_consume_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/variable_style_sheet.sjs b/dom/base/test/variable_style_sheet.sjs
new file mode 100644
index 0000000000..8e4848a7f4
--- /dev/null
+++ b/dom/base/test/variable_style_sheet.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/css", false);
+
+ var accessCount;
+ var state = getState("varSSAccessCount");
+ if (!state) {
+ setState("varSSAccessCount", "0");
+ accessCount = 0;
+ } else {
+ setState("varSSAccessCount", (parseInt(state) + 1).toString());
+ accessCount = parseInt(state) + 1;
+ }
+
+ response.write(
+ "#content { background-color: rgb(" + (accessCount % 256) + ", 0, 0); }"
+ );
+}
diff --git a/dom/base/test/w3element_traversal.svg b/dom/base/test/w3element_traversal.svg
new file mode 100644
index 0000000000..86e4292d6d
--- /dev/null
+++ b/dom/base/test/w3element_traversal.svg
@@ -0,0 +1,70 @@
+<!DOCTYPE svg
+[
+<!ENTITY tree "<tspan id='first_element_child_entity'></tspan>">
+]>
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:pickle="http://ns.example.org/pickle"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+
+ <text >
+ <tspan id="first_element_child_namespace"></tspan>
+ </text>
+ <g id="parentEl_namespace">
+ <pickle:dill />
+ </g>
+
+ <text id="parentEl_pes" >
+ <tspan id="first_element_child_pes"></tspan>
+ <tspan id="middle_element_child_pes"></tspan>
+ <tspan id="last_element_child_pes"></tspan>
+ </text>
+
+
+ <text id="parentEl" >
+ <tspan id="first_element_child_sibnull"></tspan>
+ </text>
+
+ <text id="parentEl_nes" >
+ <tspan id="first_element_child_nes"></tspan>
+ <tspan id="last_element_child_nes"></tspan>
+ </text>
+
+ <text id="parentEl_lec" >
+ <tspan id="first_element_child_lec"></tspan>
+ <tspan id="last_element_child_lec"></tspan>
+ </text>
+
+
+ <text id="parentEl_fec" >
+ <tspan id="first_element_child_fec"></tspan>
+ </text>
+
+ <text id="parentEl_entity" >&tree;</text>
+
+ <text id="parentEl_dynremove" >
+ <tspan id="first_element_child_dynremove"></tspan>
+ <tspan id="last_element_child_dynremove"></tspan>
+ </text>
+
+ <text id="parentEl_dynadd" >
+ <tspan id="first_element_child_dynadd"></tspan>
+ </text>
+
+ <text id="parentEl_null" ></text>
+
+ <text id="parentEl_count" >
+ <tspan id="first_element_child_count">
+ <tspan></tspan>
+ <tspan></tspan>
+ </tspan>
+ <tspan id="middle_element_child"></tspan>
+ <tspan id="last_element_child_count"></tspan>
+ </text>
+
+ <text id="parentEl_nochild"></text>
+
+</svg>
+
diff --git a/dom/base/test/wholeTexty-helper.xml b/dom/base/test/wholeTexty-helper.xml
new file mode 100644
index 0000000000..779a54c8b4
--- /dev/null
+++ b/dom/base/test/wholeTexty-helper.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<!DOCTYPE document
+ [
+ <!ENTITY foobar "baz<quux/>">
+ ]>
+<document><![CDATA[]]><entity>&foobar;</entity></document>
diff --git a/dom/base/test/worker_postMessages.js b/dom/base/test/worker_postMessages.js
new file mode 100644
index 0000000000..77bfa80748
--- /dev/null
+++ b/dom/base/test/worker_postMessages.js
@@ -0,0 +1,73 @@
+function test_workers() {
+ onmessage = function (e) {
+ postMessage(e.data, e.ports);
+ };
+
+ onmessageerror = function (e) {
+ postMessage("error");
+ };
+}
+
+function test_sharedWorkers(port) {
+ port.onmessage = function (e) {
+ if (e.data == "terminate") {
+ close();
+ } else {
+ port.postMessage(e.data, e.ports);
+ }
+ };
+
+ port.onmessageerror = function (e) {
+ port.postMessage("error");
+ };
+}
+
+function test_broadcastChannel(obj) {
+ var bc = new BroadcastChannel("postMessagesTest_inWorkers");
+ bc.onmessage = function (e) {
+ obj.postMessage(e.data);
+ };
+
+ bc.onmessageerror = function () {
+ obj.postMessage("error");
+ };
+}
+
+function test_messagePort(port) {
+ port.onmessage = function (e) {
+ postMessage(e.data, e.ports);
+ };
+
+ port.onmessageerror = function (e) {
+ postMessage("error");
+ };
+}
+
+onconnect = function (e) {
+ e.ports[0].onmessage = ee => {
+ if (ee.data == "sharedworkers") {
+ test_sharedWorkers(e.ports[0]);
+ e.ports[0].postMessage("ok");
+ } else if (ee.data == "broadcastChannel") {
+ test_broadcastChannel(e.ports[0]);
+ e.ports[0].postMessage("ok");
+ } else if (ee.data == "terminate") {
+ close();
+ }
+ };
+};
+
+onmessage = function (e) {
+ if (e.data == "workers") {
+ test_workers();
+ postMessage("ok");
+ } else if (e.data == "broadcastChannel") {
+ test_broadcastChannel(self);
+ postMessage("ok");
+ } else if (e.data == "messagePort") {
+ test_messagePort(e.ports[0]);
+ postMessage("ok");
+ } else {
+ postMessage("ko");
+ }
+};
diff --git a/dom/base/usecounters.py b/dom/base/usecounters.py
new file mode 100644
index 0000000000..7e2c7148ec
--- /dev/null
+++ b/dom/base/usecounters.py
@@ -0,0 +1,89 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import collections
+import re
+
+
+def read_conf(conf_filename):
+ # Can't read/write from a single StringIO, so make a new one for reading.
+ stream = open(conf_filename, "r")
+
+ def parse_counters(stream):
+ for line_num, line in enumerate(stream):
+ line = line.rstrip("\n")
+ if not line or line.startswith("//"):
+ # empty line or comment
+ continue
+ m = re.match(r"method ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line)
+ if m:
+ interface_name, method_name = m.groups()
+ yield {
+ "type": "method",
+ "interface_name": interface_name,
+ "method_name": method_name,
+ }
+ continue
+ m = re.match(r"attribute ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line)
+ if m:
+ interface_name, attribute_name = m.groups()
+ yield {
+ "type": "attribute",
+ "interface_name": interface_name,
+ "attribute_name": attribute_name,
+ }
+ continue
+ m = re.match(r"custom ([A-Za-z0-9_]+) (.*)$", line)
+ if m:
+ name, desc = m.groups()
+ yield {"type": "custom", "name": name, "desc": desc}
+ continue
+ raise ValueError(
+ "error parsing %s at line %d" % (conf_filename, line_num + 1)
+ )
+
+ return parse_counters(stream)
+
+
+def generate_histograms(filename, is_for_worker=False):
+ # The mapping for use counters to telemetry histograms depends on the
+ # ordering of items in the dictionary.
+
+ # The ordering of the ending for workers depends on the WorkerType defined
+ # in WorkerPrivate.h.
+ endings = (
+ ["DEDICATED_WORKER", "SHARED_WORKER", "SERVICE_WORKER"]
+ if is_for_worker
+ else ["DOCUMENT", "PAGE"]
+ )
+
+ items = collections.OrderedDict()
+ for counter in read_conf(filename):
+
+ def append_counter(name, desc):
+ items[name] = {
+ "expires_in_version": "never",
+ "kind": "boolean",
+ "description": desc,
+ }
+
+ def append_counters(name, desc):
+ for ending in endings:
+ append_counter(
+ "USE_COUNTER2_%s_%s" % (name, ending),
+ "Whether a %s %s" % (ending.replace("_", " ").lower(), desc),
+ )
+
+ if counter["type"] == "method":
+ method = "%s.%s" % (counter["interface_name"], counter["method_name"])
+ append_counters(method.replace(".", "_").upper(), "called %s" % method)
+ elif counter["type"] == "attribute":
+ attr = "%s.%s" % (counter["interface_name"], counter["attribute_name"])
+ counter_name = attr.replace(".", "_").upper()
+ append_counters("%s_getter" % counter_name, "got %s" % attr)
+ append_counters("%s_setter" % counter_name, "set %s" % attr)
+ elif counter["type"] == "custom":
+ append_counters(counter["name"].upper(), counter["desc"])
+
+ return items